Skip to content

Commit

Permalink
issue120 Add TCK support for timing out LRAs
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Musgrove <[email protected]>
  • Loading branch information
mmusgrov committed Mar 29, 2019
1 parent 477e5fe commit 7414a8c
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 45 deletions.
131 changes: 119 additions & 12 deletions tck/src/main/java/org/eclipse/microprofile/lra/tck/LRAClientOps.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import static junit.framework.TestCase.fail;
import static org.eclipse.microprofile.lra.tck.participant.api.NonParticipatingTckResource.END_PATH;
Expand All @@ -41,8 +49,11 @@
import static org.eclipse.microprofile.lra.tck.participant.api.ParticipatingTckResource.TCK_PARTICIPANT_RESOURCE_PATH;

public class LRAClientOps {
private static final Logger LOGGER = Logger.getLogger(TckLRATypeTests.class.getName());

/**
* TODO recovery needs to be properly speced
* TODO recovery needs to be properly speced: see the proposal
* on issue https://github.com/eclipse/microprofile-lra/issues/116
* It is used in the two tests for participants that return 202:
* {@link TckTests#acceptCancelTest()} and {@link TckTests#acceptCloseTest()}
* to connect to an endpoint that will trigger recovery (the alternative would
Expand All @@ -68,9 +79,13 @@ public class LRAClientOps {
static final String LRA_RECOVERY_PATH_KEY = "lra.http.recovery.path";

private final WebTarget target;
private final ScheduledExecutorService executor;
private final Map<LRATask, ScheduledFuture<?>> lraTasks;

public LRAClientOps(WebTarget target) {
this.target = target;
this.executor = Executors.newSingleThreadScheduledExecutor();
this.lraTasks = new HashMap<>();
}

// see if it is possible to join with an LRA - if it is possible to do that then the LRA is still active
Expand Down Expand Up @@ -105,7 +120,8 @@ private Response invokeRestEndpoint(URI lra, String basePath, String path, int c
return invokeRestEndpoint(lra == null ? null : lra.toASCIIString(), basePath, path, coerceResponse);
}

private Response invokeRestEndpoint(String lra, String basePath, String path, int coerceResponse) {
// synchronize access to the connection since it is shared with the LRA background cancellation code
private synchronized Response invokeRestEndpoint(String lra, String basePath, String path, int coerceResponse) {
WebTarget resourcePath = target.path(basePath).path(path).queryParam(STATUS_CODE_QUERY_NAME, coerceResponse);
Invocation.Builder builder = resourcePath.request();

Expand Down Expand Up @@ -144,31 +160,122 @@ private URI toURI(String lra) throws GenericLRAException {
}
}

public URI startLRA(URI parentLRA, String clientID, Long timeout, ChronoUnit unit)
public URI startLRA(URI parentLRA, String clientID, long timeout, ChronoUnit unit)
throws GenericLRAException {
return toURI(invokeRestEndpoint(parentLRA,
String lra = invokeRestEndpoint(parentLRA,
TCK_NON_PARTICIPANT_RESOURCE_PATH, START_BUT_DONT_END_PATH, 200)
.readEntity(String.class));
}
.readEntity(String.class);

void cancelLRA(String lra) {
invokeRestEndpointAndReturnLRA(lra, TCK_NON_PARTICIPANT_RESOURCE_PATH, END_PATH, 200);
if (timeout > 0L) {
scheduleCancelation(clientID, toURI(lra), timeout, unit);
}

return toURI(lra);
}

void cancelLRA(URI lraId) throws GenericLRAException {
cancelCancelation(lraId);

invokeRestEndpointAndReturnLRA(lraId.toASCIIString(), TCK_NON_PARTICIPANT_RESOURCE_PATH, END_PATH, 500);
}

private void cancelLRA(String clientId, URI lra) {
LOGGER.warning("cancelling LRA from the timer: clientId: " + clientId + " LRA id: " + lra.toASCIIString());
cancelLRA(lra);
throw new IllegalArgumentException("LRA timed out prematurely");
}

void closeLRA(URI lraId) throws GenericLRAException {
cancelCancelation(lraId);

invokeRestEndpointAndReturnLRA(lraId.toASCIIString(), TCK_NON_PARTICIPANT_RESOURCE_PATH, END_PATH, 200);
}


public void closeLRA(String lraId) {
invokeRestEndpointAndReturnLRA(lraId, TCK_NON_PARTICIPANT_RESOURCE_PATH, END_PATH, 200);
void closeLRA(String lraId) {
closeLRA(toURI(lraId));
}

void leaveLRA(URI lra, String basePath, String resourcePath) throws GenericLRAException {
private void leaveLRA(URI lra, String basePath, String resourcePath) throws GenericLRAException {
invokeRestEndpoint(lra, basePath, resourcePath, 200);
}

/*
* Include support for timing out LRAs. A timed out LRA will generally produce a test failure.
*/

private static TimeUnit timeUnit(ChronoUnit unit) {
switch (unit) {
case NANOS:
return TimeUnit.NANOSECONDS;
case MICROS:
return TimeUnit.MICROSECONDS;
case MILLIS:
return TimeUnit.MILLISECONDS;
case SECONDS:
return TimeUnit.SECONDS;
case MINUTES:
return TimeUnit.MINUTES;
case HOURS:
return TimeUnit.HOURS;
case DAYS:
return TimeUnit.DAYS;
default:
throw new IllegalArgumentException("ChronoUnit cannot be converted to TimeUnit: " + unit);
}
}

/**
* arrange for an LRA to be automatically cancelled after a specified timeout
*
* @param clientId client assigned arbitrary identifier for the LRA
* @param lra the LRA that should be cancelled when the time limit is reached
* @param timeout the time to wait before attempting cancellation of the LRA
* @param unit the time unit
*/
private void scheduleCancelation(String clientId, URI lra, long timeout, ChronoUnit unit) {
lraTasks.put(new LRATask(clientId, lra), executor.schedule(() -> cancelLRA(clientId, lra), timeout, timeUnit(unit)));
}

private void cancelCancelation(URI lraId) {
LRATask lra = new LRATask(null, lraId);

if (lraTasks.containsKey(lra)) {
lraTasks.remove(lra).cancel(false);
}
}

void cleanUp(Logger logger, String testName) {
lraTasks.forEach((lra, future) -> {
logger.warning("Test: " + testName + " didn't finish LRA " + lra.lra + " with clientId " + lra.clientId);
cancelLRA(lra.lra);
});
}

// class to store the clientId associated with an LRA
private class LRATask {
final String clientId; // client assigned arbitrary identifier for the LRA
final URI lra; // the LRA that clientId is associated with

LRATask(String clientId, URI lra) {
this.clientId = clientId;
this.lra = lra;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LRATask lraTask = (LRATask) o;
return lra.equals(lraTask.lra);
}

@Override
public int hashCode() {
return Objects.hash(lra);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
Expand All @@ -45,17 +46,17 @@
import java.time.temporal.ChronoUnit;
import java.util.logging.Logger;

import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.MANDATORY_WITH_END_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.MANDATORY_WITH_END_FALSE_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.MANDATORY_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.NEVER_WITH_END_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.NEVER_WITH_END_FALSE_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.NEVER_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.NOT_SUPPORTED_WITH_END_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.NOT_SUPPORTED_WITH_END_FALSE_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.NOT_SUPPORTED_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.REQUIRED_WITH_END_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.REQUIRED_WITH_END_FALSE_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.REQUIRED_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.REQUIRES_NEW_WITH_END_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.REQUIRES_NEW_WITH_END_FALSE_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.REQUIRES_NEW_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.SUPPORTS_WITH_END_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.SUPPORTS_WITH_END_FALSE_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.SUPPORTS_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.TCK_LRA_TYPE_RESOURCE_PATH;

Expand Down Expand Up @@ -102,7 +103,7 @@ public class TckLRATypeTests {

private WebTarget tckSuiteTarget;

@Deployment(name = "lra-type-tck-tests", managed = true, testable = true)
@Deployment(name = "lra-type-tck-tests")
public static WebArchive deploy() {
String archiveName = TckLRATypeTests.class.getSimpleName().toLowerCase();
return ShrinkWrap
Expand Down Expand Up @@ -131,6 +132,11 @@ public void before() {
}
}

@After
public void after() {
lraClient.cleanUp(LOGGER, testName.getMethodName());
}

private void setUpTestCase() {
tckSuiteClient = ClientBuilder.newClient();
}
Expand Down Expand Up @@ -200,66 +206,66 @@ public void neverWithoutLRA() {
resourceRequest(NEVER_PATH, false, 200, MethodLRACheck.NOT_PRESENT, false);
}

// same set of tests except that the invoked method end = true set on the LRA annotation
// same set of tests except that the invoked method has end = false set on the LRA annotation

@Test
public void requiredEndWithLRA() {
resourceRequest(REQUIRED_WITH_END_PATH, true, 200, MethodLRACheck.EQUALS, true);
resourceRequest(REQUIRED_WITH_END_FALSE_PATH, true, 200, MethodLRACheck.EQUALS, true);
}

@Test
public void requiredEndWithoutLRA() {
resourceRequest(REQUIRED_WITH_END_PATH, false, 200, MethodLRACheck.NOT_EQUALS, true);
resourceRequest(REQUIRED_WITH_END_FALSE_PATH, false, 200, MethodLRACheck.NOT_EQUALS, true);
}

@Test
public void requiresEndNewWithLRA() {
resourceRequest(REQUIRES_NEW_WITH_END_PATH, true, 200, MethodLRACheck.NOT_EQUALS, true);
resourceRequest(REQUIRES_NEW_WITH_END_FALSE_PATH, true, 200, MethodLRACheck.NOT_EQUALS, true);
}

@Test
public void requiresEndNewWithoutLRA() {
resourceRequest(REQUIRES_NEW_WITH_END_PATH, false, 200, MethodLRACheck.NOT_EQUALS, true);
resourceRequest(REQUIRES_NEW_WITH_END_FALSE_PATH, false, 200, MethodLRACheck.NOT_EQUALS, true);
}

@Test
public void mandatoryEndWithLRA() {
resourceRequest(MANDATORY_WITH_END_PATH, true, 200, MethodLRACheck.EQUALS, true);
resourceRequest(MANDATORY_WITH_END_FALSE_PATH, true, 200, MethodLRACheck.EQUALS, true);
}

@Test
public void mandatoryEndWithoutLRA() {
resourceRequest(MANDATORY_WITH_END_PATH, false, 412, MethodLRACheck.NOT_PRESENT, false);
resourceRequest(MANDATORY_WITH_END_FALSE_PATH, false, 412, MethodLRACheck.NOT_PRESENT, false);
}

@Test
public void supportsEndWithLRA() {
resourceRequest(SUPPORTS_WITH_END_PATH, true, 200, MethodLRACheck.EQUALS, true);
resourceRequest(SUPPORTS_WITH_END_FALSE_PATH, true, 200, MethodLRACheck.EQUALS, true);
}

@Test
public void supportsEndWithoutLRA() {
resourceRequest(SUPPORTS_WITH_END_PATH, false, 200, MethodLRACheck.NOT_PRESENT, false);
resourceRequest(SUPPORTS_WITH_END_FALSE_PATH, false, 200, MethodLRACheck.NOT_PRESENT, false);
}

@Test
public void notSupportedEndWithRA() {
resourceRequest(NOT_SUPPORTED_WITH_END_PATH, true, 200, MethodLRACheck.NOT_PRESENT, false);
resourceRequest(NOT_SUPPORTED_WITH_END_FALSE_PATH, true, 200, MethodLRACheck.NOT_PRESENT, false);
}

@Test
public void notSupportedEndWithoutLRA() {
resourceRequest(NOT_SUPPORTED_WITH_END_PATH, false, 200, MethodLRACheck.NOT_PRESENT, false);
resourceRequest(NOT_SUPPORTED_WITH_END_FALSE_PATH, false, 200, MethodLRACheck.NOT_PRESENT, false);
}

@Test
public void neverWithEndRA() {
resourceRequest(NEVER_WITH_END_PATH, true, 412, MethodLRACheck.NOT_PRESENT, false);
resourceRequest(NEVER_WITH_END_FALSE_PATH, true, 412, MethodLRACheck.NOT_PRESENT, false);
}

@Test
public void neverWithoutEndLRA() {
resourceRequest(NEVER_WITH_END_PATH, false, 200, MethodLRACheck.NOT_PRESENT, false);
resourceRequest(NEVER_WITH_END_FALSE_PATH, false, 200, MethodLRACheck.NOT_PRESENT, false);
}

/**
Expand All @@ -280,7 +286,11 @@ private void resourceRequest(String path, boolean startLRA, int expectedStatus,
.path(path).request();
URI lra = startLRA ? lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS) : null;

Response response = lra == null ? target.get() : target.header(LRA.LRA_HTTP_HEADER, lra).get();
if (lra != null) {
target = target.header(LRA.LRA_HTTP_HEADER, lra);
}

Response response = target.get();

try {
String methodLRA = response.readEntity(String.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ public class LRATypeTckResource {
public static final String MANDATORY_PATH = "/mandatory";
public static final String NEVER_PATH = "/never";

public static final String REQUIRED_WITH_END_PATH = "/end-required";
public static final String REQUIRES_NEW_WITH_END_PATH = "/end-requires-new";
public static final String SUPPORTS_WITH_END_PATH = "/end-supports";
public static final String NOT_SUPPORTED_WITH_END_PATH = "/end-not-supported";
public static final String MANDATORY_WITH_END_PATH = "/end-mandatory";
public static final String NEVER_WITH_END_PATH = "/end-never";
public static final String REQUIRED_WITH_END_FALSE_PATH = "/end-required";
public static final String REQUIRES_NEW_WITH_END_FALSE_PATH = "/end-requires-new";
public static final String SUPPORTS_WITH_END_FALSE_PATH = "/end-supports";
public static final String NOT_SUPPORTED_WITH_END_FALSE_PATH = "/end-not-supported";
public static final String MANDATORY_WITH_END_FALSE_PATH = "/end-mandatory";
public static final String NEVER_WITH_END_FALSE_PATH = "/end-never";

// resource methods for each LRA.Type attribute
@GET
Expand Down Expand Up @@ -88,37 +88,37 @@ public Response neverLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {

// similar set of resources methods but with the LRA.end attribute set to false
@GET
@Path(REQUIRED_WITH_END_PATH)
@Path(REQUIRED_WITH_END_FALSE_PATH)
@LRA(value = LRA.Type.REQUIRED, end = false)
public Response requiredEndLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
return Response.ok(lraId).build();
}
@GET
@Path(REQUIRES_NEW_WITH_END_PATH)
@Path(REQUIRES_NEW_WITH_END_FALSE_PATH)
@LRA(value = LRA.Type.REQUIRES_NEW, end = false)
public Response requiresNewEndLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
return Response.ok(lraId).build();
}
@GET
@Path(MANDATORY_WITH_END_PATH)
@Path(MANDATORY_WITH_END_FALSE_PATH)
@LRA(value = LRA.Type.MANDATORY, end = false)
public Response mandatoryEndLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
return Response.ok(lraId).build();
}
@GET
@Path(SUPPORTS_WITH_END_PATH)
@Path(SUPPORTS_WITH_END_FALSE_PATH)
@LRA(value = LRA.Type.SUPPORTS, end = false)
public Response supportsEndLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
return Response.ok(lraId).build();
}
@GET
@Path(NOT_SUPPORTED_WITH_END_PATH)
@Path(NOT_SUPPORTED_WITH_END_FALSE_PATH)
@LRA(value = LRA.Type.NOT_SUPPORTED, end = false)
public Response notSupportedEndLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
return Response.ok(lraId).build();
}
@GET
@Path(NEVER_WITH_END_PATH)
@Path(NEVER_WITH_END_FALSE_PATH)
@LRA(value = LRA.Type.NEVER, end = false)
public Response neverEndLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
return Response.ok(lraId).build();
Expand Down

0 comments on commit 7414a8c

Please sign in to comment.