Skip to content

Commit

Permalink
Implement retry for UpdateTimestampService (#524)
Browse files Browse the repository at this point in the history
  • Loading branch information
JorWo authored Jan 26, 2024
1 parent 5865e5c commit ec64dd4
Show file tree
Hide file tree
Showing 12 changed files with 216 additions and 46 deletions.
11 changes: 6 additions & 5 deletions src/main/java/edu/hawaii/its/api/service/ExecutorService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,24 @@
import org.springframework.stereotype.Service;

import edu.hawaii.its.api.wrapper.Command;
import edu.hawaii.its.api.wrapper.Results;

import edu.internet2.middleware.grouperClientExt.org.apache.commons.logging.Log;
import edu.internet2.middleware.grouperClientExt.org.apache.commons.logging.LogFactory;

@Service("executor")
@Service
public class ExecutorService {
protected final Log logger = LogFactory.getLog(getClass());

public <T> T execute(Command<T> command) {
T result = null;
public <T extends Results> T execute(Command<T> command) {
String text = "execute; " + command.getClass().getSimpleName() + ": ";
try {
result = command.execute();
T result = command.execute();
logger.debug(text + "execution success");
return result;
} catch (Exception e) {
logger.error(text + e);
}
return result;
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
@Service("grouperApiService")
public class GrouperApiService {

@Autowired ExecutorService exec;
@Autowired
private ExecutorService exec;

/**
* Check if a UH identifier is listed in a group.
Expand Down
43 changes: 43 additions & 0 deletions src/main/java/edu/hawaii/its/api/service/RetryExecutorService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package edu.hawaii.its.api.service;

import org.springframework.stereotype.Service;

import edu.hawaii.its.api.wrapper.Command;
import edu.hawaii.its.api.wrapper.Results;

import edu.internet2.middleware.grouperClientExt.org.apache.commons.logging.Log;
import edu.internet2.middleware.grouperClientExt.org.apache.commons.logging.LogFactory;

@Service
public class RetryExecutorService {
protected final Log logger = LogFactory.getLog(getClass());

private static final int MAX_RETRIES = 2;
private static final int DELAY = 1000;

public <T extends Results> T execute(Command<T> command) {
String text = "execute; " + command.getClass().getSimpleName() + ": ";
for (int i = 0; i <= MAX_RETRIES; i++) {
try {
T result = command.execute();
if (result.getResultCode().equals("SUCCESS")) {
logger.debug(text + "execution success");
return result;
}
} catch (Exception e) {
logger.error(text + e);
}
logger.debug(text + "Execution failed, retrying in " + DELAY * i + " ms");
delay(i);
}
return null;
}

protected void delay(int i) {
try {
Thread.sleep((long) DELAY * i);
} catch (InterruptedException ie) {
logger.error(ie);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public class UpdateTimestampService {
@Autowired
public GroupPathService groupPathService;

@Autowired
public RetryExecutorService retryExec;

public GroupingTimestampResults update(GroupingResult groupingResult) {
if (groupingResult.getResultCode().equals("SUCCESS")) {
List<String> groupPaths = getGroupingGroupPaths(groupingResult.getGroupPath());
Expand All @@ -36,7 +39,8 @@ public GroupingTimestampResults update(GroupingResult groupingResult) {
}

private GroupingTimestampResults updateLastModifiedTimestamp(List<String> groupPaths) {
UpdatedTimestampResults updatedTimestampResults = new UpdateTimestampCommand(groupPaths).execute();
UpdatedTimestampResults updatedTimestampResults = retryExec.execute(new UpdateTimestampCommand()
.addGroupPaths(groupPaths));
GroupingTimestampResults groupingsTimestampResults = new GroupingTimestampResults(updatedTimestampResults);
logger.debug("GroupingsTimestampResult; + " + JsonUtil.asJson(groupingsTimestampResults));
return groupingsTimestampResults;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,41 +19,28 @@ public class UpdateTimestampCommand extends GrouperCommand implements Command<Up
private static final String DATE_FORMAT = "yyyyMMdd'T'HHmm";
protected final GcAssignAttributes gcAssignAttributes;

public UpdateTimestampCommand(String groupPath) {
Objects.requireNonNull(groupPath, "groupPath cannot be null");
public UpdateTimestampCommand() {
this.gcAssignAttributes = new GcAssignAttributes()
.assignContentType("text/x-json") // Remove after upgrading to Grouper 4
.assignAttributeAssignType(ASSIGN_TYPE_GROUP)
.assignAttributeAssignOperation(OPERATION_ASSIGN_ATTRIBUTE)
.addOwnerGroupName(groupPath)
.addAttributeDefNameName(YYYYMMDDTHHMM)
.assignAttributeAssignValueOperation(OPERATION_REPLACE_VALUES)
.addValue(new DateTimeAttributeValue(Dates.formatDate(
Dates.truncateDatePlus60Seconds(LocalDateTime.now()),
DATE_FORMAT)).getWsAttributeAssignValue());
}

public UpdateTimestampCommand(List<String> groupPaths) {
if (groupPaths.isEmpty()) {
throw new IllegalStateException("groupPaths cannot be empty");
}
this.gcAssignAttributes = new GcAssignAttributes()
.assignContentType("text/x-json") // Remove after upgrading to Grouper 4
.assignAttributeAssignType(ASSIGN_TYPE_GROUP)
.assignAttributeAssignOperation(OPERATION_ASSIGN_ATTRIBUTE)
.addAttributeDefNameName(YYYYMMDDTHHMM)
.assignAttributeAssignValueOperation(OPERATION_REPLACE_VALUES)
.addValue(new DateTimeAttributeValue(Dates.formatDate(
Dates.truncateDatePlus60Seconds(LocalDateTime.now()),
DATE_FORMAT)).getWsAttributeAssignValue());
for (String path : groupPaths) {
Objects.requireNonNull(path, "Path cannot be null");
this.gcAssignAttributes.addOwnerGroupName(path);
}
public UpdateTimestampCommand addGroupPath(String groupPath) {
this.gcAssignAttributes.addOwnerGroupName(groupPath);
return this;
}

public UpdateTimestampCommand() {
this.gcAssignAttributes = new GcAssignAttributes();
public UpdateTimestampCommand addGroupPaths(List<String> groupPaths) {
for (String groupPath : groupPaths) {
this.gcAssignAttributes.addOwnerGroupName(groupPath);
}
return this;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public UpdatedTimestampResults() {
groups = Collections.emptyList();
previousTimestampResults = Collections.emptyList();
currentTimestampResults = Collections.emptyList();

}

@Override
Expand Down
10 changes: 10 additions & 0 deletions src/main/resources/application-integrationTest.properties
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,29 @@ groupings.api.test.test-name-2=tst02name
groupings.api.test.test-name-3=tst03name
groupings.api.test.test-name-4=tst04name
groupings.api.test.test-name-5=tst05name
groupings.api.test.test-dept-name-1=Testf-iwt-1 TestIAM-dept
groupings.api.test.test-dept-name-2=Testf-iwt-2 TestIAM-dept
groupings.api.test.test-username-1=testiwta
groupings.api.test.test-username-2=testiwtb
groupings.api.test.test-username-3=testiwtc
groupings.api.test.test-username-4=testiwtd
groupings.api.test.test-username-5=testiwte
groupings.api.test.test-dept-username-1=testiwt1
groupings.api.test.test-dept-username-2=testiwt2
groupings.api.test.test-uh-number-1=99997010
groupings.api.test.test-uh-number-2=99997027
groupings.api.test.test-uh-number-3=99997033
groupings.api.test.test-uh-number-4=99997043
groupings.api.test.test-uh-number-5=99997056
# test-dept-uh-number-1 left intentionally blank (See Project Security and Test Data in bwiki).
groupings.api.test.test-dept-uh-number-1=
groupings.api.test.test-dept-uh-number-2=testiwt2
groupings.api.test.uh-usernames=${groupings.api.test.test-username-1},${groupings.api.test.test-username-2},${groupings.api.test.test-username-3},${groupings.api.test.test-username-4},${groupings.api.test.test-username-5}
groupings.api.test.uh-numbers=${groupings.api.test.test-uh-number-1},${groupings.api.test.test-uh-number-2},${groupings.api.test.test-uh-number-3},${groupings.api.test.test-uh-number-4},${groupings.api.test.test-uh-number-5}
groupings.api.test.names=${groupings.api.test.test-name-1},${groupings.api.test.test-name-2},${groupings.api.test.test-name-3},${groupings.api.test.test-name-4},${groupings.api.test.test-name-5}
groupings.api.test.dept-uh-usernames=${groupings.api.test.test-dept-username-1},${groupings.api.test.test-dept-username-2}
groupings.api.test.dept-uh-numbers=${groupings.api.test.test-dept-uh-number-1},${groupings.api.test.test-dept-uh-number-2}
groupings.api.test.dept-names=${groupings.api.test.test-dept-name-1},${groupings.api.test.test-dept-name-2}
groupings.api.test.path.single.test-user-1=tmp:${groupings.api.test.test-username-1}:${groupings.api.test.test-username-1}-single
groupings.api.test.path.single.include.test-user-1=${groupings.api.test.path.single.test-user-1}:include
groupings.api.test.path.single.exclude.test-user-1=${groupings.api.test.path.single.test-user-1}:exclude
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,25 @@

import edu.hawaii.its.api.configuration.SpringBootWebApplication;
import edu.hawaii.its.api.wrapper.Command;
import edu.hawaii.its.api.wrapper.MockResults;

@ActiveProfiles("localTest")
@SpringBootTest(classes = { SpringBootWebApplication.class })
public class ExecutorServiceTest {

@Mock
private Command<String> mockCommand;
private Command<MockResults> mockCommand;

@Autowired
private ExecutorService executorService;

@Test
public void executeTest() {
doReturn("execution success").when(mockCommand).execute();
doReturn(new MockResults("SUCCESS")).when(mockCommand).execute();
assertNotNull(executorService.execute(mockCommand));
assertEquals("execution success", executorService.execute(mockCommand));
assertEquals("SUCCESS", executorService.execute(mockCommand).getResultCode());

doThrow(new RuntimeException("execution error")).when(mockCommand).execute();
doThrow(new RuntimeException()).when(mockCommand).execute();
assertNull(executorService.execute(mockCommand));
}

Expand Down
115 changes: 115 additions & 0 deletions src/test/java/edu/hawaii/its/api/service/RetryExecutorServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package edu.hawaii.its.api.service;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;

import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

import edu.hawaii.its.api.configuration.SpringBootWebApplication;
import edu.hawaii.its.api.wrapper.Command;
import edu.hawaii.its.api.wrapper.MockResults;

@ActiveProfiles("localTest")
@SpringBootTest(classes = { SpringBootWebApplication.class })
public class RetryExecutorServiceTest {

@Mock
private Command<MockResults> mockCommand;

@Autowired
private RetryExecutorService retryExec;

@Test
public void successfulRetry() {
doReturn(new MockResults("SUCCESS")).when(mockCommand).execute();
assertSuccessfulRetry(mockCommand);

doThrow(RuntimeException.class).when(mockCommand).execute();
doReturn(new MockResults("SUCCESS")).when(mockCommand).execute();
assertSuccessfulRetry(mockCommand);

doReturn(new MockResults("FAILURE")).when(mockCommand).execute();
doReturn(new MockResults("SUCCESS")).when(mockCommand).execute();
assertSuccessfulRetry(mockCommand);

doThrow(RuntimeException.class).when(mockCommand).execute();
doThrow(RuntimeException.class).when(mockCommand).execute();
doReturn(new MockResults("SUCCESS")).when(mockCommand).execute();
assertSuccessfulRetry(mockCommand);

doReturn(new MockResults("FAILURE")).when(mockCommand).execute();
doReturn(new MockResults("FAILURE")).when(mockCommand).execute();
doReturn(new MockResults("SUCCESS")).when(mockCommand).execute();
assertSuccessfulRetry(mockCommand);

doThrow(RuntimeException.class).when(mockCommand).execute();
doReturn(new MockResults("FAILURE")).when(mockCommand).execute();
doReturn(new MockResults("SUCCESS")).when(mockCommand).execute();
assertSuccessfulRetry(mockCommand);

doReturn(new MockResults("FAILURE")).when(mockCommand).execute();
doThrow(RuntimeException.class).when(mockCommand).execute();
doReturn(new MockResults("SUCCESS")).when(mockCommand).execute();
assertSuccessfulRetry(mockCommand);
}

@Test
public void unsuccessfulRetry() {
doThrow(RuntimeException.class).when(mockCommand).execute();
doThrow(RuntimeException.class).when(mockCommand).execute();
doThrow(RuntimeException.class).when(mockCommand).execute();
assertUnsuccessfulRetry(mockCommand);

doReturn(new MockResults("FAILURE")).when(mockCommand).execute();
doThrow(RuntimeException.class).when(mockCommand).execute();
doThrow(RuntimeException.class).when(mockCommand).execute();
assertUnsuccessfulRetry(mockCommand);

doThrow(RuntimeException.class).when(mockCommand).execute();
doReturn(new MockResults("FAILURE")).when(mockCommand).execute();
doThrow(RuntimeException.class).when(mockCommand).execute();
assertUnsuccessfulRetry(mockCommand);

doThrow(RuntimeException.class).when(mockCommand).execute();
doThrow(RuntimeException.class).when(mockCommand).execute();
doReturn(new MockResults("FAILURE")).when(mockCommand).execute();
assertUnsuccessfulRetry(mockCommand);

doReturn(new MockResults("FAILURE")).when(mockCommand).execute();
doReturn(new MockResults("FAILURE")).when(mockCommand).execute();
doThrow(RuntimeException.class).when(mockCommand).execute();
assertUnsuccessfulRetry(mockCommand);

doThrow(RuntimeException.class).when(mockCommand).execute();
doReturn(new MockResults("FAILURE")).when(mockCommand).execute();
doReturn(new MockResults("FAILURE")).when(mockCommand).execute();
assertUnsuccessfulRetry(mockCommand);
}

@Test
public void catchInterruptedException() {
Thread thread = new Thread(() -> retryExec.delay(0));
thread.start();
thread.interrupt();
assertNotNull(thread);
}

private void assertSuccessfulRetry(Command<MockResults> command) {
MockResults result = retryExec.execute(command);
assertNotNull(result);
assertEquals(result.getResultCode(), "SUCCESS");
}

private void assertUnsuccessfulRetry(Command<MockResults> command) {
MockResults result = retryExec.execute(command);
assertTrue(result == null || !result.getResultCode().equals("SUCCESS"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public class TestSubjectService {
@Value("${groupings.api.test.uh-numbers}")
private List<String> TEST_UH_NUMBERS;

@Value("${groupings.api.test.dept-uh-usernames}")
private List<String> TEST_DEPT_UH_USERNAMES;

@Test
public void constructor() {
assertNotNull(subjectService);
Expand All @@ -43,6 +46,8 @@ public void isValidIdentifier() {
for (String number : TEST_UH_NUMBERS) {
assertTrue(subjectService.isValidIdentifier(number));
}
assertFalse(subjectService.isValidIdentifier(TEST_DEPT_UH_USERNAMES.get(0))); // testiwt1 is invalid
assertTrue(subjectService.isValidIdentifier(TEST_DEPT_UH_USERNAMES.get(1))); // testiwt2 is valid
assertFalse(subjectService.isValidIdentifier("invalid-identifier"));
}

Expand Down
14 changes: 14 additions & 0 deletions src/test/java/edu/hawaii/its/api/wrapper/MockResults.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package edu.hawaii.its.api.wrapper;

public class MockResults extends Results {
private String resultCode;

public MockResults(String resultCode) {
this.resultCode = resultCode;
}

@Override
public String getResultCode() {
return resultCode;
}
}
Loading

0 comments on commit ec64dd4

Please sign in to comment.