Skip to content

Commit

Permalink
issue122 context propagation tests
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Musgrove <[email protected]>
  • Loading branch information
mmusgrov committed Apr 7, 2019
1 parent 2644ebd commit 395cf21
Show file tree
Hide file tree
Showing 15 changed files with 806 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
* <p>
* Newly created LRAs are uniquely identified and the id is referred to as the
* LRA context. The context is passed around using a JAX-RS request/response header
* called {@value #LRA_HTTP_HEADER}.
* called {@value #LRA_HTTP_CONTEXT_HEADER}.
* The implementation (of the LRA specification) is expected to manage this context
* and the application developer is expected to declaratively control the creation,
* propagation and destruction of LRAs using the {@link LRA} annotation. When a JAX-RS bean
Expand All @@ -78,7 +78,7 @@
* <p>
* Resource methods can access the LRA context id, if required, by inspecting the request headers
* using standard JAX-RS mechanisms, ie `@Context` or by injecting it via the JAX-RS {@link HeaderParam}
* annotation with value {@value #LRA_HTTP_HEADER}. This may be useful, for example, for
* annotation with value {@value #LRA_HTTP_CONTEXT_HEADER}. This may be useful, for example, for
* associating business work with an LRA.
* </p>
*/
Expand All @@ -91,7 +91,7 @@
* via an HTTP header field with the following name. The value contains
* the LRA id associated with the HTTP request/response
*/
String LRA_HTTP_HEADER = "Long-Running-Action";
String LRA_HTTP_CONTEXT_HEADER = "Long-Running-Action";

/**
* the name of the HTTP header field that contains a recovery URI corresponding
Expand Down Expand Up @@ -129,7 +129,7 @@
* <p>
* When an LRA is present its identifer <b>MUST</b> be made available to
* the business logic in the JAX-RS request and response header
* {@value #LRA_HTTP_HEADER}.
* {@value #LRA_HTTP_CONTEXT_HEADER}.
*
* @return whether a bean method is to be executed within a transaction context.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
* results in the creation of a new LRA then the participant will still be removed
* from the incoming context and will be enlisted with the new context (and the method
* will execute with this new context). Note that in this case the context exposed in
* the `LRA_HTTP_HEADER` JAX-RS header will be set to the new LRA (and not the original
* the `LRA_HTTP_CONTEXT_HEADER` JAX-RS header will be set to the new LRA (and not the original
* one), ie the orignal context will not be available to the business logic.
* </p>
*
Expand Down
22 changes: 11 additions & 11 deletions spec/src/main/asciidoc/microprofile-lra-spec.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ available to the business logic. This LRA is referred to as the `active context`
For JAX-RS resource methods the identifier is made available via request and
response headers which the business method can obtain using standard JAX-RS mechanisms,
ie `@Context` or by injecting a JAX-RS header param with the name specified by
the Java constant `LRA_HTTP_HEADER` as defined <<source-LRA, in the LRA annotation class>>.
the Java constant `LRA_HTTP_CONTEXT_HEADER` as defined <<source-LRA, in the LRA annotation class>>.
This header is referred to as the `context header`.

The implementation MUST propagate the `active context` on outgoing JAX-RS
Expand Down Expand Up @@ -465,7 +465,7 @@ the LRA to be finished. If it is closed or cancelled depends on the Response sta

When an LRA is present its identifier MUST be made available to
the business logic via request and response headers as described earlier
(using a header name specified in the Java constant `LRA_HTTP_HEADER`).
(using a header name specified in the Java constant `LRA_HTTP_CONTEXT_HEADER`).

The following is a simple example of how to start an LRA and how to receive
a notification when the LRA is later cancelled (`@Compensate` is invoked)
Expand All @@ -479,7 +479,7 @@ public class SimpleLRAParticipant
{
@Path("/cdi")
@LRA(LRA.Type.REQUIRES_NEW)
public void doInTransaction(@HeaderParam(LRA_HTTP_HEADER) String lraId)
public void doInTransaction(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId)
{
/*
* Perform business actions in the context of the LRA identified by the
Expand All @@ -493,7 +493,7 @@ public class SimpleLRAParticipant
@PUT
@Path("/complete")
@Complete
public Response completeWork(@HeaderParam(LRA_HTTP_HEADER) String lraId,
public Response completeWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId,
String userData)
{
/*
Expand All @@ -509,7 +509,7 @@ public class SimpleLRAParticipant
@PUT
@Path("/compensate")
@Compensate
public Response compensateWork(@HeaderParam(LRA_HTTP_HEADER) String lraId,
public Response compensateWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId,
String userData)
{
/*
Expand All @@ -526,7 +526,7 @@ public class SimpleLRAParticipant
@DELETE
@Path("/forget")
@Forget
public Response forgetWork(@HeaderParam(LRA_HTTP_HEADER) String lraId)
public Response forgetWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId)
{
// clean up any resources that were used in the context of lraId
}
Expand Down Expand Up @@ -582,7 +582,7 @@ For example:
@PUT
@Path("/compensate")
@Compensate
public Response compensateWork(@HeaderParam(LRA.LRA_HTTP_HEADER) String lraId)
public Response compensateWork(@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) String lraId)
{
// compensate for whatever activity the business logic has associated with lraId
return Response.ok(ParticipantStatus.Compensated.name()).build();
Expand Down Expand Up @@ -671,7 +671,7 @@ section for when this feature could be useful.
@Produces(MediaType.APPLICATION_JSON)
@LRA(LRA.Type.SUPPORTS)
@NestedLRA
public Booking bookFlight(@HeaderParam(LRA_HTTP_HEADER) String lraId,
public Booking bookFlight(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId,
@QueryParam("flightNumber") String flightNumber) {
return service.book(lraId, flightNumber);
}
Expand All @@ -694,7 +694,7 @@ example, to indicate that an LRA should automatically cancel after 100 milliseco
@Produces(MediaType.APPLICATION_JSON)
@LRA(value = LRA.Type.REQUIRED, timeLimit = 100, timeUnit = ChronoUnit.MILLIS)
public Response theClockIsTicking(
@HeaderParam(LRA.LRA_HTTP_HEADER) String lraId) {...}
@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) String lraId) {...}
----

Furthermore, the ability to compensate or complete may be transient capabilities of a
Expand All @@ -711,7 +711,7 @@ indicate that its' ability to compensate is limited to 100 milliseconds could be
@PUT
@Path("/compensate")
@Compensate(timeLimit = 100, timeUnit = ChronoUnit.MILLIS)
public Response completeWork(@HeaderParam(LRA_HTTP_HEADER) String lraId,
public Response completeWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId,
String userData) { ... }
----

Expand All @@ -738,7 +738,7 @@ An example of this annotation is shown next:
@PUT
@Path("/leave")
@Leave
public Response leaveWork(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
public Response leaveWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
// clean up since this participant is no longer associated with the LRA
}
----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ private synchronized Response invokeRestEndpoint(String lra, String basePath, St
Invocation.Builder builder = resourcePath.request();

if (lra != null) {
builder.header(LRA.LRA_HTTP_HEADER, lra);
builder.header(LRA.LRA_HTTP_CONTEXT_HEADER, lra);
}

return builder.put(Entity.text(""));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/*
*******************************************************************************
* Copyright (c) 2019 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package org.eclipse.microprofile.lra.tck;

import org.eclipse.microprofile.lra.annotation.Complete;
import org.eclipse.microprofile.lra.annotation.Forget;
import org.eclipse.microprofile.lra.annotation.ParticipantStatus;
import org.eclipse.microprofile.lra.annotation.Status;
import org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;

import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER;
import static org.eclipse.microprofile.lra.tck.TckContextTests.HttpMethod.GET;
import static org.eclipse.microprofile.lra.tck.TckContextTests.HttpMethod.PUT;
import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.CONTEXT_CHECK_LRA_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.LEAVE_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.LRA_TCK_FAULT_CODE_HEADER;
import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.LRA_TCK_FAULT_TYPE_HEADER;
import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.LRA_TCK_HTTP_CONTEXT_HEADER;
import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.METRIC_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.NEW_LRA_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.REQUIRED_LRA_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.RESET_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.STATUS_PATH;
import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.TCK_CONTEXT_RESOURCE_PATH;
import static org.junit.Assert.assertEquals;

/**
* test that Compensate, Complete, Status, Forget and Leave annotations work without an LRA annotation
* test that resource methods that make outgoing requests do not modify the current context when they return
*/
@RunWith(Arquillian.class)
public class TckContextTests extends TckTestBase {

enum HttpMethod {GET, PUT, POST}

@Deployment(name = "TckContextTests")
public static WebArchive deploy() {
return TckTestBase.deploy(TckContextTests.class.getSimpleName().toLowerCase());
}

@Before
public void before() {
super.before();
invoke(RESET_PATH, HttpMethod.PUT, null, 200, null, 200);
}

@Test
public void testBasicContextPropagation() {
String lra = invoke(NEW_LRA_PATH, PUT, null, 200, ContextTckResource.EndPhase.SUCCESS, 200);
invoke(REQUIRED_LRA_PATH, PUT, lra, 200, ContextTckResource.EndPhase.SUCCESS, 200);

// verify that the resource was not asked to complete
String completions = invoke(METRIC_PATH + "/" + Complete.class.getName(), GET, lra);

assertEquals(testName.getMethodName() + ": Resource was not asked to complete",
Integer.toString(1), completions);
}

@Test
public void testStatus() {
// call a resource that begins and ends an LRA and coerces the resource to return ACCEPTED when asked to complete
String lra = invoke(REQUIRED_LRA_PATH, PUT, null, 200, ContextTckResource.EndPhase.ACCEPTED, 202);

// verfiy that the resource was asked to complete and is in the state Conpleting
String status = invoke(STATUS_PATH, HttpMethod.GET, lra, 202, ContextTckResource.EndPhase.SUCCESS, 200);
assertEquals(testName.getMethodName() + ": participant is not completing", ParticipantStatus.Completing.name(), status);

// clear the EndPhase override data so that the next status request returns completed or compensated
invoke(STATUS_PATH, PUT, lra, 200, ContextTckResource.EndPhase.SUCCESS, 200);

// trigger a replay of the end phase
replayEndPhase(TCK_CONTEXT_RESOURCE_PATH);

// and verify that the resoure was asked to complete
status = invoke(STATUS_PATH, HttpMethod.GET, lra, 200, ContextTckResource.EndPhase.SUCCESS, 200);
assertEquals(testName.getMethodName() + ": participant is not completed", ParticipantStatus.Completed.name(), status);
}

@Test
public void testLeave() {
// call a resource that begins but does not ends an LRA
String lra = invoke(NEW_LRA_PATH, PUT, null, 200, ContextTckResource.EndPhase.SUCCESS, 200);
// verfiy that the resource is active
String status = invoke(STATUS_PATH, HttpMethod.GET, lra, 200, ContextTckResource.EndPhase.SUCCESS, 200);

assertEquals(testName.getMethodName() + ": participant is not active", ParticipantStatus.Active.name(), status);

// ask the resource to leave the active LRA
invoke(LEAVE_PATH, PUT, lra);

// end the LRA via a different resource since using the same one will re-enlist it
lraClient.closeLRA(lra);

// verify that the resource was not asked to complete
String completions = invoke(METRIC_PATH + "/" + Complete.class.getName(), GET, lra);


assertEquals(testName.getMethodName() + ": Resource left but was still asked to complete",
Integer.toString(0), completions);
}

@Test
public void testForget() {
// call a resource that begins and ends an LRA and coerces the resource to return FAILED when asked to complete
String lra = invoke(REQUIRED_LRA_PATH, PUT, null, 200, ContextTckResource.EndPhase.FAILED, 500);

// trigger a replay attempt
replayEndPhase(TCK_CONTEXT_RESOURCE_PATH);

// the implementation should have called status which will have returned 500
String count1 = invoke(METRIC_PATH + "/" + Status.class.getName(), GET, lra);
assertEquals(testName.getMethodName() + " resource status should have been called", "1", count1);

// the implementation should not call forget until it knows the particpant status
String count2 = invoke(METRIC_PATH + "/" + Forget.class.getName(), GET, lra);
assertEquals(testName.getMethodName() + " resource forget should not have been called", "0", count2);

// clear the fault
invoke(STATUS_PATH, PUT, lra, 200, ContextTckResource.EndPhase.FAILED, 200);

// trigger a replay of the end phase
replayEndPhase(TCK_CONTEXT_RESOURCE_PATH);

// the implementation should have called status again which will have returned 200
count1 = invoke(METRIC_PATH + "/" + Status.class.getName(), GET, lra);
assertEquals(testName.getMethodName() + " resource status should have been called again", "2", count1);
// the implementation should call forget since it knows the particpant status
count2 = invoke(METRIC_PATH + "/" + Forget.class.getName(), GET, lra);
assertEquals(testName.getMethodName() + " resource forget should have been called", "1", count2);
}

// invoke a method in an LRA context which performs various outgoing calls checking that the notion of active context
// conforms with what is written in the specification
@Test
public void testContextAfterRemoteCalls() {
invoke(CONTEXT_CHECK_LRA_PATH, PUT, null);
}

private String invoke(String where, HttpMethod method, String lraContext) {
return invoke(where, method, lraContext, 200, ContextTckResource.EndPhase.SUCCESS, 200);
}

private String invoke(String where, HttpMethod method, String lraContext, int expectStatus,
ContextTckResource.EndPhase finishWith, int finishStatus) {
WebTarget resourcePath = tckSuiteTarget.path(TCK_CONTEXT_RESOURCE_PATH).path(where);
Invocation.Builder builder = resourcePath.request()
.header(LRA_TCK_FAULT_TYPE_HEADER, finishWith)
.header(LRA_TCK_FAULT_CODE_HEADER, finishStatus);
Response response;

if (where.startsWith(METRIC_PATH) || (where.startsWith(STATUS_PATH) && method == PUT)) {
builder.header(LRA_TCK_HTTP_CONTEXT_HEADER, lraContext);
} else {
builder.header(LRA_HTTP_CONTEXT_HEADER, lraContext);
}

switch (method) {
case GET:
response = builder.get();
break;
case PUT:
response = builder.put(Entity.text(""));
break;
case POST:
response = builder.post(Entity.text(""));
break;
default:
throw new IllegalArgumentException("HTTP method not supported: " + method);
}

return checkStatusReadAndCloseResponse(Response.Status.fromStatusCode(expectStatus), response, resourcePath);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ private void resourceRequest(String path, boolean startLRA, int expectedStatus,
URI lra = startLRA ? lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS) : null;

if (lra != null) {
target = target.header(LRA.LRA_HTTP_HEADER, lra);
target = target.header(LRA.LRA_HTTP_CONTEXT_HEADER, lra);
}

Response response = target.get();
Expand Down
Loading

0 comments on commit 395cf21

Please sign in to comment.