diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 921484350..9d818eec1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,7 @@ jobs: linux-compat: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: image: - manylinux2014-x64 diff --git a/.gitignore b/.gitignore index 3bd89fbe6..53fbfe9c2 100644 --- a/.gitignore +++ b/.gitignore @@ -175,4 +175,6 @@ $RECYCLE.BIN/ .project bin/ .classpath -.settings \ No newline at end of file +.settings + +*.log diff --git a/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/CauseServiceErrorResponseHandler.java b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/CauseServiceErrorResponseHandler.java new file mode 100644 index 000000000..fb418b9a4 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/CauseServiceErrorResponseHandler.java @@ -0,0 +1,42 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.model.CauseServiceErrorResponse; +import software.amazon.awssdk.eventstreamrpc.OperationResponse; +import software.amazon.awssdk.eventstreamrpc.StreamResponse; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.concurrent.CompletableFuture; + +public final class CauseServiceErrorResponseHandler implements StreamResponse { + private final OperationResponse operationResponse; + + public CauseServiceErrorResponseHandler( + final OperationResponse operationResponse) { + this.operationResponse = operationResponse; + } + + @Override + public CompletableFuture getRequestFlushFuture() { + return operationResponse.getRequestFlushFuture(); + } + + @Override + public CompletableFuture getResponse() { + return operationResponse.getResponse(); + } + + @Override + public CompletableFuture sendStreamEvent(final EventStreamJsonMessage event) { + return operationResponse.sendStreamEvent(event); + } + + @Override + public CompletableFuture closeStream() { + return operationResponse.closeStream(); + } + + @Override + public boolean isClosed() { + return operationResponse.isClosed(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/CauseStreamServiceToErrorResponseHandler.java b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/CauseStreamServiceToErrorResponseHandler.java new file mode 100644 index 000000000..c0e22a647 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/CauseStreamServiceToErrorResponseHandler.java @@ -0,0 +1,42 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.model.EchoStreamingMessage; +import software.amazon.awssdk.awstest.model.EchoStreamingResponse; +import software.amazon.awssdk.eventstreamrpc.OperationResponse; +import software.amazon.awssdk.eventstreamrpc.StreamResponse; + +import java.util.concurrent.CompletableFuture; + +public final class CauseStreamServiceToErrorResponseHandler implements StreamResponse { + private final OperationResponse operationResponse; + + public CauseStreamServiceToErrorResponseHandler( + final OperationResponse operationResponse) { + this.operationResponse = operationResponse; + } + + @Override + public CompletableFuture getRequestFlushFuture() { + return operationResponse.getRequestFlushFuture(); + } + + @Override + public CompletableFuture getResponse() { + return operationResponse.getResponse(); + } + + @Override + public CompletableFuture sendStreamEvent(final EchoStreamingMessage event) { + return operationResponse.sendStreamEvent(event); + } + + @Override + public CompletableFuture closeStream() { + return operationResponse.closeStream(); + } + + @Override + public boolean isClosed() { + return operationResponse.isClosed(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/EchoMessageResponseHandler.java b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/EchoMessageResponseHandler.java new file mode 100644 index 000000000..383db287f --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/EchoMessageResponseHandler.java @@ -0,0 +1,42 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.model.EchoMessageResponse; +import software.amazon.awssdk.eventstreamrpc.OperationResponse; +import software.amazon.awssdk.eventstreamrpc.StreamResponse; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.concurrent.CompletableFuture; + +public final class EchoMessageResponseHandler implements StreamResponse { + private final OperationResponse operationResponse; + + public EchoMessageResponseHandler( + final OperationResponse operationResponse) { + this.operationResponse = operationResponse; + } + + @Override + public CompletableFuture getRequestFlushFuture() { + return operationResponse.getRequestFlushFuture(); + } + + @Override + public CompletableFuture getResponse() { + return operationResponse.getResponse(); + } + + @Override + public CompletableFuture sendStreamEvent(final EventStreamJsonMessage event) { + return operationResponse.sendStreamEvent(event); + } + + @Override + public CompletableFuture closeStream() { + return operationResponse.closeStream(); + } + + @Override + public boolean isClosed() { + return operationResponse.isClosed(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/EchoStreamMessagesResponseHandler.java b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/EchoStreamMessagesResponseHandler.java new file mode 100644 index 000000000..7cef77131 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/EchoStreamMessagesResponseHandler.java @@ -0,0 +1,42 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.model.EchoStreamingMessage; +import software.amazon.awssdk.awstest.model.EchoStreamingResponse; +import software.amazon.awssdk.eventstreamrpc.OperationResponse; +import software.amazon.awssdk.eventstreamrpc.StreamResponse; + +import java.util.concurrent.CompletableFuture; + +public final class EchoStreamMessagesResponseHandler implements StreamResponse { + private final OperationResponse operationResponse; + + public EchoStreamMessagesResponseHandler( + final OperationResponse operationResponse) { + this.operationResponse = operationResponse; + } + + @Override + public CompletableFuture getRequestFlushFuture() { + return operationResponse.getRequestFlushFuture(); + } + + @Override + public CompletableFuture getResponse() { + return operationResponse.getResponse(); + } + + @Override + public CompletableFuture sendStreamEvent(final EchoStreamingMessage event) { + return operationResponse.sendStreamEvent(event); + } + + @Override + public CompletableFuture closeStream() { + return operationResponse.closeStream(); + } + + @Override + public boolean isClosed() { + return operationResponse.isClosed(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/EchoTestRPC.java b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/EchoTestRPC.java new file mode 100644 index 000000000..58c27739d --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/EchoTestRPC.java @@ -0,0 +1,33 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.model.CauseServiceErrorRequest; +import software.amazon.awssdk.awstest.model.EchoMessageRequest; +import software.amazon.awssdk.awstest.model.EchoStreamingMessage; +import software.amazon.awssdk.awstest.model.EchoStreamingRequest; +import software.amazon.awssdk.awstest.model.GetAllCustomersRequest; +import software.amazon.awssdk.awstest.model.GetAllProductsRequest; +import software.amazon.awssdk.eventstreamrpc.StreamResponseHandler; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Optional; + +public interface EchoTestRPC { + CauseServiceErrorResponseHandler causeServiceError(final CauseServiceErrorRequest request, + final Optional> streamResponseHandler); + + CauseStreamServiceToErrorResponseHandler causeStreamServiceToError( + final EchoStreamingRequest request, + final Optional> streamResponseHandler); + + EchoMessageResponseHandler echoMessage(final EchoMessageRequest request, + final Optional> streamResponseHandler); + + EchoStreamMessagesResponseHandler echoStreamMessages(final EchoStreamingRequest request, + final Optional> streamResponseHandler); + + GetAllCustomersResponseHandler getAllCustomers(final GetAllCustomersRequest request, + final Optional> streamResponseHandler); + + GetAllProductsResponseHandler getAllProducts(final GetAllProductsRequest request, + final Optional> streamResponseHandler); +} diff --git a/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/EchoTestRPCClient.java b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/EchoTestRPCClient.java new file mode 100644 index 000000000..829cc7a45 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/EchoTestRPCClient.java @@ -0,0 +1,63 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.model.CauseServiceErrorRequest; +import software.amazon.awssdk.awstest.model.EchoMessageRequest; +import software.amazon.awssdk.awstest.model.EchoStreamingMessage; +import software.amazon.awssdk.awstest.model.EchoStreamingRequest; +import software.amazon.awssdk.awstest.model.GetAllCustomersRequest; +import software.amazon.awssdk.awstest.model.GetAllProductsRequest; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCClient; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCConnection; +import software.amazon.awssdk.eventstreamrpc.StreamResponseHandler; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Optional; + +public class EchoTestRPCClient extends EventStreamRPCClient implements EchoTestRPC { + public EchoTestRPCClient(final EventStreamRPCConnection connection) { + super(connection); + } + + @Override + public CauseServiceErrorResponseHandler causeServiceError(final CauseServiceErrorRequest request, + final Optional> streamResponseHandler) { + final CauseServiceErrorOperationContext operationContext = EchoTestRPCServiceModel.getCauseServiceErrorModelContext(); + return new CauseServiceErrorResponseHandler(doOperationInvoke(operationContext, request, streamResponseHandler)); + } + + @Override + public CauseStreamServiceToErrorResponseHandler causeStreamServiceToError( + final EchoStreamingRequest request, + final Optional> streamResponseHandler) { + final CauseStreamServiceToErrorOperationContext operationContext = EchoTestRPCServiceModel.getCauseStreamServiceToErrorModelContext(); + return new CauseStreamServiceToErrorResponseHandler(doOperationInvoke(operationContext, request, streamResponseHandler)); + } + + @Override + public EchoMessageResponseHandler echoMessage(final EchoMessageRequest request, + final Optional> streamResponseHandler) { + final EchoMessageOperationContext operationContext = EchoTestRPCServiceModel.getEchoMessageModelContext(); + return new EchoMessageResponseHandler(doOperationInvoke(operationContext, request, streamResponseHandler)); + } + + @Override + public EchoStreamMessagesResponseHandler echoStreamMessages(final EchoStreamingRequest request, + final Optional> streamResponseHandler) { + final EchoStreamMessagesOperationContext operationContext = EchoTestRPCServiceModel.getEchoStreamMessagesModelContext(); + return new EchoStreamMessagesResponseHandler(doOperationInvoke(operationContext, request, streamResponseHandler)); + } + + @Override + public GetAllCustomersResponseHandler getAllCustomers(final GetAllCustomersRequest request, + final Optional> streamResponseHandler) { + final GetAllCustomersOperationContext operationContext = EchoTestRPCServiceModel.getGetAllCustomersModelContext(); + return new GetAllCustomersResponseHandler(doOperationInvoke(operationContext, request, streamResponseHandler)); + } + + @Override + public GetAllProductsResponseHandler getAllProducts(final GetAllProductsRequest request, + final Optional> streamResponseHandler) { + final GetAllProductsOperationContext operationContext = EchoTestRPCServiceModel.getGetAllProductsModelContext(); + return new GetAllProductsResponseHandler(doOperationInvoke(operationContext, request, streamResponseHandler)); + } +} diff --git a/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/GetAllCustomersResponseHandler.java b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/GetAllCustomersResponseHandler.java new file mode 100644 index 000000000..b9c623d6c --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/GetAllCustomersResponseHandler.java @@ -0,0 +1,42 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.model.GetAllCustomersResponse; +import software.amazon.awssdk.eventstreamrpc.OperationResponse; +import software.amazon.awssdk.eventstreamrpc.StreamResponse; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.concurrent.CompletableFuture; + +public final class GetAllCustomersResponseHandler implements StreamResponse { + private final OperationResponse operationResponse; + + public GetAllCustomersResponseHandler( + final OperationResponse operationResponse) { + this.operationResponse = operationResponse; + } + + @Override + public CompletableFuture getRequestFlushFuture() { + return operationResponse.getRequestFlushFuture(); + } + + @Override + public CompletableFuture getResponse() { + return operationResponse.getResponse(); + } + + @Override + public CompletableFuture sendStreamEvent(final EventStreamJsonMessage event) { + return operationResponse.sendStreamEvent(event); + } + + @Override + public CompletableFuture closeStream() { + return operationResponse.closeStream(); + } + + @Override + public boolean isClosed() { + return operationResponse.isClosed(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/GetAllProductsResponseHandler.java b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/GetAllProductsResponseHandler.java new file mode 100644 index 000000000..d9550dd53 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/awstest/GetAllProductsResponseHandler.java @@ -0,0 +1,42 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.model.GetAllProductsResponse; +import software.amazon.awssdk.eventstreamrpc.OperationResponse; +import software.amazon.awssdk.eventstreamrpc.StreamResponse; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.concurrent.CompletableFuture; + +public final class GetAllProductsResponseHandler implements StreamResponse { + private final OperationResponse operationResponse; + + public GetAllProductsResponseHandler( + final OperationResponse operationResponse) { + this.operationResponse = operationResponse; + } + + @Override + public CompletableFuture getRequestFlushFuture() { + return operationResponse.getRequestFlushFuture(); + } + + @Override + public CompletableFuture getResponse() { + return operationResponse.getResponse(); + } + + @Override + public CompletableFuture sendStreamEvent(final EventStreamJsonMessage event) { + return operationResponse.sendStreamEvent(event); + } + + @Override + public CompletableFuture closeStream() { + return operationResponse.closeStream(); + } + + @Override + public boolean isClosed() { + return operationResponse.isClosed(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/eventstreamrpc/EventStreamRPCClientTests.java b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/eventstreamrpc/EventStreamRPCClientTests.java index e62d27682..5183bec87 100644 --- a/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/eventstreamrpc/EventStreamRPCClientTests.java +++ b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/eventstreamrpc/EventStreamRPCClientTests.java @@ -1,14 +1,14 @@ package software.amazon.awssdk.eventstreamrpc; -import junit.framework.AssertionFailedError; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import software.amazon.awssdk.crt.CRT; +import org.opentest4j.AssertionFailedError; +import software.amazon.awssdk.crt.CrtResource; import software.amazon.awssdk.crt.Log; import software.amazon.awssdk.crt.eventstream.Header; -import software.amazon.awssdk.crt.eventstream.MessageType; import software.amazon.awssdk.crt.io.ClientBootstrap; import software.amazon.awssdk.crt.io.EventLoopGroup; +import software.amazon.awssdk.crt.io.HostResolver; import software.amazon.awssdk.crt.io.SocketOptions; import software.amazon.awssdk.eventstreamrpc.model.AccessDeniedException; import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; @@ -17,9 +17,12 @@ import java.util.LinkedList; import java.util.List; -import java.util.Optional; import java.util.Random; -import java.util.concurrent.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Consumer; public class EventStreamRPCClientTests { @@ -35,10 +38,6 @@ public static int randomPort() { @Test public void testConnectionEstablished() { final int port = randomPort(); - SocketOptions socketOptions = new SocketOptions(); - socketOptions.connectTimeoutMs = 3000; - socketOptions.domain = SocketOptions.SocketDomain.IPv4; - socketOptions.type = SocketOptions.SocketType.STREAM; //below class is generated and just gets instantiated for what it is final TestIpcServiceHandler service = new TestIpcServiceHandler(false, @@ -54,67 +53,70 @@ public void testConnectionEstablished() { final CompletableFuture connectedFuture = new CompletableFuture<>(); try(final EventLoopGroup elGroup = new EventLoopGroup(1); - final ClientBootstrap clientBootstrap = new ClientBootstrap(elGroup, null)) { - final RpcServer ipcServer = new RpcServer(elGroup, socketOptions, null, "127.0.0.1", port, service); - ipcServer.runServer(); - - final EventStreamRPCConnectionConfig config = new EventStreamRPCConnectionConfig( - clientBootstrap, elGroup, socketOptions, null, "127.0.0.1", port, () -> - { - final List
headers = new LinkedList<>(); - headers.add(Header.createHeader("client-name", "accepted.foo")); - return new MessageAmendInfo(headers, null); - } - ); - final EventStreamRPCConnection connection = new EventStreamRPCConnection(config); - final EventStreamRPCConnection.LifecycleHandler lifecycleHandler = new EventStreamRPCConnection.LifecycleHandler() { - @Override - public void onConnect() { - connectedFuture.complete(null); - } + final HostResolver hostResolver = new HostResolver(elGroup, 64); + final ClientBootstrap clientBootstrap = new ClientBootstrap(elGroup, hostResolver); + SocketOptions socketOptions = new SocketOptions()) { + socketOptions.connectTimeoutMs = 3000; + socketOptions.domain = SocketOptions.SocketDomain.IPv4; + socketOptions.type = SocketOptions.SocketType.STREAM; + try (final RpcServer ipcServer = new RpcServer(elGroup, socketOptions, null, "127.0.0.1", port, service)) { + ipcServer.runServer(); - @Override - public void onDisconnect(int errorCode) { - disconnectFuture.complete(null); + final EventStreamRPCConnectionConfig config = new EventStreamRPCConnectionConfig( + clientBootstrap, elGroup, socketOptions, null, "127.0.0.1", port, () -> + { + final List
headers = new LinkedList<>(); + headers.add(Header.createHeader("client-name", "accepted.foo")); + return CompletableFuture.completedFuture(new MessageAmendInfo(headers, null)); } + ); + final EventStreamRPCConnection connection = new EventStreamRPCConnection(config); + final EventStreamRPCConnection.LifecycleHandler lifecycleHandler = new EventStreamRPCConnection.LifecycleHandler() { + @Override + public void onConnect() { + connectedFuture.complete(null); + } - @Override - public boolean onError(Throwable t) { - if (!connectedFuture.isDone()) { - connectedFuture.completeExceptionally(t); + @Override + public void onDisconnect(int errorCode) { + disconnectFuture.complete(null); } - if (!disconnectFuture.isDone()) { - disconnectFuture.completeExceptionally(t); + + @Override + public boolean onError(Throwable t) { + if (!connectedFuture.isDone()) { + connectedFuture.completeExceptionally(t); + } + if (!disconnectFuture.isDone()) { + disconnectFuture.completeExceptionally(t); + } + return true; } - return true; - } - }; - final CompletableFuture initialConnect = connection.connect(lifecycleHandler); - //highly likely above line is establishing connection - Assertions.assertThrows(IllegalStateException.class, () -> connection.connect(lifecycleHandler)); - connectedFuture.get(2, TimeUnit.SECONDS); - Assertions.assertTrue(initialConnect.isDone() && !initialConnect.isCompletedExceptionally()); - //connection is fully established - Assertions.assertThrows(IllegalStateException.class, () -> connection.connect(lifecycleHandler)); - connection.disconnect(); - } catch (ExecutionException | TimeoutException | InterruptedException e) { - throw new RuntimeException(e); - } finally { - try { - disconnectFuture.get(5, TimeUnit.SECONDS); + }; + final CompletableFuture initialConnect = connection.connect(lifecycleHandler); + //highly likely above line is establishing connection + Assertions.assertThrows(IllegalStateException.class, () -> connection.connect(lifecycleHandler)); + connectedFuture.get(2, TimeUnit.SECONDS); + Assertions.assertTrue(initialConnect.isDone() && !initialConnect.isCompletedExceptionally()); + //connection is fully established + Assertions.assertThrows(IllegalStateException.class, () -> connection.connect(lifecycleHandler)); + connection.disconnect(); } catch (ExecutionException | TimeoutException | InterruptedException e) { throw new RuntimeException(e); + } finally { + try { + disconnectFuture.get(5, TimeUnit.SECONDS); + } catch (ExecutionException | TimeoutException | InterruptedException e) { + throw new RuntimeException(e); + } } } + CrtResource.waitForNoResources(); } @Test - public void testConnectionVersionMismatch() { + public void testClientClosedThrowsInitialFutureException() { final int port = randomPort(); - SocketOptions socketOptions = new SocketOptions(); - socketOptions.connectTimeoutMs = 3000; - socketOptions.domain = SocketOptions.SocketDomain.IPv4; - socketOptions.type = SocketOptions.SocketType.STREAM; //below class is generated and just gets instantiated for what it is final TestIpcServiceHandler service = new TestIpcServiceHandler(false, @@ -127,54 +129,121 @@ public void testConnectionVersionMismatch() { service.setAuthorizationHandler(TestAuthNZHandlers.getAuthZHandler()); try(final EventLoopGroup elGroup = new EventLoopGroup(1); - final ClientBootstrap clientBootstrap = new ClientBootstrap(elGroup, null)) { - final RpcServer ipcServer = new RpcServer(elGroup, socketOptions, null, "127.0.0.1", port, service); - ipcServer.runServer(); - - final EventStreamRPCConnectionConfig config = new EventStreamRPCConnectionConfig( - clientBootstrap, elGroup, socketOptions, null, "127.0.0.1", port, () -> - { - final List
headers = new LinkedList<>(); - headers.add(Header.createHeader("client-name", "accepted.foo")); - return new MessageAmendInfo(headers, null); - } - ); - final EventStreamRPCConnection connection = new EventStreamRPCConnection(config) { - @Override - protected String getVersionString() { - return "19.19.19"; + final HostResolver hostResolver = new HostResolver(elGroup, 64); + final ClientBootstrap clientBootstrap = new ClientBootstrap(elGroup, hostResolver); + SocketOptions socketOptions = new SocketOptions()) { + socketOptions.connectTimeoutMs = 3000; + socketOptions.domain = SocketOptions.SocketDomain.IPv4; + socketOptions.type = SocketOptions.SocketType.STREAM; + try (final RpcServer ipcServer = new RpcServer(elGroup, socketOptions, null, "127.0.0.1", port, service)) { + ipcServer.runServer(); + + final EventStreamRPCConnectionConfig config = new EventStreamRPCConnectionConfig( + clientBootstrap, elGroup, socketOptions, null, "127.0.0.1", port, () -> + { + final List
headers = new LinkedList<>(); + headers.add(Header.createHeader("client-name", "accepted.foo")); + return CompletableFuture.completedFuture(new MessageAmendInfo(headers, null)); } - }; - final CompletableFuture futureAccessDenied = new CompletableFuture<>(); - final CompletableFuture shutdownFuture = new CompletableFuture<>(); - final CompletableFuture initialConnect = connection.connect(new EventStreamRPCConnection.LifecycleHandler() { - @Override - public void onConnect() { - futureAccessDenied.completeExceptionally(new AssertionFailedError("onConnect lifecycle handler method should not be called!")); + ); + final EventStreamRPCConnection connection = new EventStreamRPCConnection(config); + final CompletableFuture initialConnect = connection.connect(new EventStreamRPCConnection.LifecycleHandler() { + @Override + public void onConnect() { } + + @Override + public void onDisconnect(int errorCode) { } + + @Override + public boolean onError(Throwable t) { + return true; + } + }); + connection.disconnect(); + try { + initialConnect.get(15, TimeUnit.SECONDS); + } catch (ExecutionException e) { + Assertions.assertEquals(EventStreamClosedException.class, e.getCause().getClass()); } - @Override - public void onDisconnect(int errorCode) { - shutdownFuture.complete(errorCode); + } + } catch (InterruptedException | TimeoutException e) { + Assertions.fail(e); + } + CrtResource.waitForNoResources(); + } + + @Test + public void testConnectionVersionMismatch() { + final int port = randomPort(); + + //below class is generated and just gets instantiated for what it is + final TestIpcServiceHandler service = new TestIpcServiceHandler(false, + request -> request, + EventStreamJsonMessage.class, EventStreamJsonMessage.class, + EventStreamJsonMessage.class, EventStreamJsonMessage.class); + + //handlers aren't relevant since no request will be made + service.setAuthenticationHandler(TestAuthNZHandlers.getAuthNHandler()); + service.setAuthorizationHandler(TestAuthNZHandlers.getAuthZHandler()); + + try (final EventLoopGroup elGroup = new EventLoopGroup(1); + final HostResolver hostResolver = new HostResolver(elGroup, 64); + final ClientBootstrap clientBootstrap = new ClientBootstrap(elGroup, hostResolver); + SocketOptions socketOptions = new SocketOptions()) { + socketOptions.connectTimeoutMs = 3000; + socketOptions.domain = SocketOptions.SocketDomain.IPv4; + socketOptions.type = SocketOptions.SocketType.STREAM; + try (final RpcServer ipcServer = new RpcServer(elGroup, socketOptions, null, "127.0.0.1", port, service)) { + ipcServer.runServer(); + + final EventStreamRPCConnectionConfig config = new EventStreamRPCConnectionConfig( + clientBootstrap, elGroup, socketOptions, null, "127.0.0.1", port, () -> + { + final List
headers = new LinkedList<>(); + headers.add(Header.createHeader("client-name", "accepted.foo")); + return CompletableFuture.completedFuture(new MessageAmendInfo(headers, null)); } - @Override - public boolean onError(Throwable t) { - futureAccessDenied.complete(t); - return true; + ); + try (final EventStreamRPCConnection connection = new EventStreamRPCConnection(config) { + @Override + protected String getVersionString() { + return "19.19.19"; + } + }) { + final CompletableFuture futureAccessDenied = new CompletableFuture<>(); + final CompletableFuture initialConnect = connection.connect(new EventStreamRPCConnection.LifecycleHandler() { + @Override + public void onConnect() { + futureAccessDenied.completeExceptionally(new AssertionFailedError("onConnect lifecycle handler method should not be called!")); + } + + @Override + public void onDisconnect(int errorCode) { + futureAccessDenied.completeExceptionally(new AssertionFailedError("onDisconnect lifecycle handler method should not be called!")); + } + + @Override + public boolean onError(Throwable t) { + futureAccessDenied.complete(t); + return true; + } + }); + try { + initialConnect.get(5, TimeUnit.SECONDS); + } catch (ExecutionException e) { + Assertions.assertEquals(AccessDeniedException.class, e.getCause().getClass()); + } } - }); - futureAccessDenied.get(5, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { + } + } catch (InterruptedException | TimeoutException e) { Assertions.fail(e); } + CrtResource.waitForNoResources(); } @Test public void testConnectionAccessDenied() { final int port = randomPort(); - SocketOptions socketOptions = new SocketOptions(); - socketOptions.connectTimeoutMs = 3000; - socketOptions.domain = SocketOptions.SocketDomain.IPv4; - socketOptions.type = SocketOptions.SocketType.STREAM; //below class is generated and just gets instantiated for what it is final TestIpcServiceHandler service = new TestIpcServiceHandler(false, @@ -188,50 +257,57 @@ public void testConnectionAccessDenied() { final Semaphore semaphore = new Semaphore(1); try(final EventLoopGroup elGroup = new EventLoopGroup(1); - final ClientBootstrap clientBootstrap = new ClientBootstrap(elGroup, null)) { - final RpcServer ipcServer = new RpcServer(elGroup, socketOptions, null, "127.0.0.1", port, service); - ipcServer.runServer(); + final HostResolver hostResolver = new HostResolver(elGroup, 64); + final ClientBootstrap clientBootstrap = new ClientBootstrap(elGroup, hostResolver); + SocketOptions socketOptions = new SocketOptions()) { + socketOptions.connectTimeoutMs = 3000; + socketOptions.domain = SocketOptions.SocketDomain.IPv4; + socketOptions.type = SocketOptions.SocketType.STREAM; + try (final RpcServer ipcServer = new RpcServer(elGroup, socketOptions, null, "127.0.0.1", port, service)) { + ipcServer.runServer(); - semaphore.acquire(); - final EventStreamRPCConnectionConfig config = new EventStreamRPCConnectionConfig( - clientBootstrap, elGroup, socketOptions, null, "127.0.0.1", port, () -> + semaphore.acquire(); + final EventStreamRPCConnectionConfig config = new EventStreamRPCConnectionConfig( + clientBootstrap, elGroup, socketOptions, null, "127.0.0.1", port, () -> { final List
headers = new LinkedList<>(); headers.add(Header.createHeader("client-name", "rejected.foo")); - return new MessageAmendInfo(headers, null); + return CompletableFuture.completedFuture(new MessageAmendInfo(headers, null)); } - ); - final EventStreamRPCConnection connection = new EventStreamRPCConnection(config); - final CompletableFuture initialConnect = connection.connect(new EventStreamRPCConnection.LifecycleHandler() { - @Override - public void onConnect() { - Assertions.fail("Full connection expected to be rejected"); - semaphore.release(); - } - @Override - public void onDisconnect(int errorCode) { - System.out.println("Client disconnected..."); - semaphore.release(); - } - @Override - public boolean onError(Throwable t) { - Assertions.assertEquals(t.getClass(), AccessDeniedException.class, "Expected access denied exception type!"); - semaphore.release(); - return false; + ); + try (final EventStreamRPCConnection connection = new EventStreamRPCConnection(config)) { + final CompletableFuture initialConnect = connection.connect(new EventStreamRPCConnection.LifecycleHandler() { + @Override + public void onConnect() { + Assertions.fail("Full connection expected to be rejected"); + semaphore.release(); + } + + @Override + public void onDisconnect(int errorCode) { + Assertions.fail("Disconnect callback should not be invoked on access denied"); + } + + @Override + public boolean onError(Throwable t) { + Assertions.assertEquals(t.getClass(), AccessDeniedException.class, "Expected access denied exception type!"); + semaphore.release(); + return false; + } + }); + semaphore.acquire(); + Assertions.assertTrue(initialConnect.isDone()); + try { + initialConnect.get(); + } catch (ExecutionException e) { + Assertions.assertTrue(e.getCause() instanceof AccessDeniedException); + } } - }); - semaphore.acquire(); - Assertions.assertTrue(initialConnect.isDone()); - try { - initialConnect.get(); - } catch (ExecutionException e) { - Assertions.assertTrue(e.getCause() instanceof AccessDeniedException); } - connection.disconnect(); - semaphore.acquire(); } catch (InterruptedException e) { throw new RuntimeException(e); } + CrtResource.waitForNoResources(); } /** @@ -242,10 +318,6 @@ public boolean onError(Throwable t) { */ public static void runDummyService(EventStreamRPCConnection.LifecycleHandler lifecycleHandler, Consumer testCode) { final int port = randomPort(); - SocketOptions socketOptions = new SocketOptions(); - socketOptions.connectTimeoutMs = 3000; - socketOptions.domain = SocketOptions.SocketDomain.IPv4; - socketOptions.type = SocketOptions.SocketType.STREAM; final TestIpcServiceHandler service = new TestIpcServiceHandler(false, request -> request, EventStreamJsonMessage.class, EventStreamJsonMessage.class, @@ -255,22 +327,28 @@ public static void runDummyService(EventStreamRPCConnection.LifecycleHandler lif service.setAuthorizationHandler(TestAuthNZHandlers.getAuthZHandler()); try(final EventLoopGroup elGroup = new EventLoopGroup(1); - final ClientBootstrap clientBootstrap = new ClientBootstrap(elGroup, null)) { - final RpcServer ipcServer = new RpcServer(elGroup, socketOptions, null, "127.0.0.1", port, service); - ipcServer.runServer(); + final HostResolver hostResolver = new HostResolver(elGroup, 64); + final ClientBootstrap clientBootstrap = new ClientBootstrap(elGroup, hostResolver); + SocketOptions socketOptions = new SocketOptions()) { + socketOptions.connectTimeoutMs = 3000; + socketOptions.domain = SocketOptions.SocketDomain.IPv4; + socketOptions.type = SocketOptions.SocketType.STREAM; + try (final RpcServer ipcServer = new RpcServer(elGroup, socketOptions, null, "127.0.0.1", port, service)) { + ipcServer.runServer(); - final EventStreamRPCConnectionConfig config = new EventStreamRPCConnectionConfig( - clientBootstrap, elGroup, socketOptions, null, "127.0.0.1", port, () -> + final EventStreamRPCConnectionConfig config = new EventStreamRPCConnectionConfig( + clientBootstrap, elGroup, socketOptions, null, "127.0.0.1", port, () -> { final List
headers = new LinkedList<>(); headers.add(Header.createHeader("client-name", "accepted.foo")); - return new MessageAmendInfo(headers, null); + return CompletableFuture.completedFuture(new MessageAmendInfo(headers, null)); } - ); - final EventStreamRPCConnection connection = new EventStreamRPCConnection(config); - final CompletableFuture initialConnect = connection.connect(lifecycleHandler); - initialConnect.get(2, TimeUnit.SECONDS); - testCode.accept(connection); + ); + final EventStreamRPCConnection connection = new EventStreamRPCConnection(config); + final CompletableFuture initialConnect = connection.connect(lifecycleHandler); + initialConnect.get(2, TimeUnit.SECONDS); + testCode.accept(connection); + } } catch (ExecutionException | TimeoutException | InterruptedException e) { Assertions.fail(e); } diff --git a/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/eventstreamrpc/test/TestAuthNZHandlers.java b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/eventstreamrpc/test/TestAuthNZHandlers.java new file mode 100644 index 000000000..2294ea09e --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/eventstreamrpc/test/TestAuthNZHandlers.java @@ -0,0 +1,88 @@ +package software.amazon.awssdk.eventstreamrpc.test; + +import software.amazon.awssdk.crt.eventstream.Header; +import software.amazon.awssdk.crt.eventstream.HeaderType; +import software.amazon.awssdk.eventstreamrpc.AuthenticationData; +import software.amazon.awssdk.eventstreamrpc.AuthenticationHandler; +import software.amazon.awssdk.eventstreamrpc.Authorization; +import software.amazon.awssdk.eventstreamrpc.AuthorizationHandler; +import software.amazon.awssdk.eventstreamrpc.MessageAmendInfo; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +/** + * Example + */ +public class TestAuthNZHandlers { + + public static CompletableFuture getClientAuth(String clientName) { + final List
headers = new ArrayList<>(1); + headers.add(Header.createHeader("client-name", clientName)); + return CompletableFuture.completedFuture(new MessageAmendInfo(headers, null)); + } + + public static class ClientNameAuthenicationData implements AuthenticationData { + private static String clientName; + + public ClientNameAuthenicationData(String clientName) { + this.clientName = clientName; + } + + /** + * Return a human readable string for who the identity of the client/caller is. This + * string must be appropriate for audit logs and enable tracing specific callers/clients + * to relevant decision and operations executed + * + * @return + */ + @Override + public String getIdentityLabel() { + return clientName; + } + } + + public static AuthenticationHandler getAuthNHandler() { + /** + * AuthN handler is privy to looking into the connect message and extracting whatever it needs/wants + * from the headers and/or payload and populating whatever/however it wants into the AuthenticationData + * type it decides is appropriate. The only restriction is that the associated Authorization handler + * should understand the authentication produced. + */ + return (List
headers, byte[] bytes) -> { + final Optional
nameHeader = headers.stream() + .filter(header -> header.getName().equals("client-name")).findFirst(); + if (nameHeader.isPresent() && nameHeader.get().getHeaderType().equals(HeaderType.String)) { + return new ClientNameAuthenicationData(nameHeader.get().getValueAsString()); + } + throw new RuntimeException("Authentication failed to find client-name string header!"); + }; + } + + public static AuthorizationHandler getAuthZHandler() { + return (AuthenticationData authN) -> { + try { + //framework checks for null authN so this shouldn't happen + if (authN == null) { + throw new RuntimeException("Null AuthN data passed in!"); + } + //check if the authN data type matches is a good idea, and necessary if there are other fields + //to open/inspect + if (authN instanceof ClientNameAuthenicationData) { + final ClientNameAuthenicationData data = (ClientNameAuthenicationData) authN; + //WARNING!!! this logic is for demonstration purposes + final String identityLabel = data.getIdentityLabel(); + return identityLabel.startsWith("accepted.") ? + Authorization.ACCEPT : Authorization.REJECT; + } + //good idea to log reasons for rejection with the authentication label so testers/users can understand + System.err.println("AuthN data type is not expected object type!"); + return Authorization.REJECT; + } catch (Exception e) { + return Authorization.REJECT; + } + }; + } +} diff --git a/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/eventstreamrpc/test/TestIpcServiceHandler.java b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/eventstreamrpc/test/TestIpcServiceHandler.java new file mode 100644 index 000000000..535ca2c4d --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-client/src/test/java/software/amazon/awssdk/eventstreamrpc/test/TestIpcServiceHandler.java @@ -0,0 +1,202 @@ +package software.amazon.awssdk.eventstreamrpc.test; + +import software.amazon.awssdk.crt.eventstream.ServerConnectionContinuationHandler; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCServiceHandler; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCServiceModel; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandler; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandlerContext; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.AbstractMap; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; +import java.util.function.Function; + +/** + * Test server handler operates on all operations and simply stores what it recieves in a way that's publicly + * retrievable for verifiable IO behavior + */ +public class TestIpcServiceHandler extends EventStreamRPCServiceHandler { + private final Queue> continuationHandlerQueue; + private boolean streamClosed; + + private final boolean isStreaming; + private Function requestHandler; + + private Class requestClass; + private Class responseClass; + private Class streamingRequestClass; + private Class streamingResponseClass; + + private EventStreamRPCServiceModel serviceModel; + + public TestIpcServiceHandler(boolean isStreaming, Function requestHandler, + Class requestClass, Class responseClass, Class streamingRequestClass, Class streamingResponseClass) { + this.isStreaming = isStreaming; + this.requestHandler = requestHandler; + this.requestClass = requestClass; + this.responseClass = responseClass; + this.streamingRequestClass = streamingRequestClass; + this.streamingResponseClass = streamingResponseClass; + this.continuationHandlerQueue = new LinkedList<>(); + + this.serviceModel = new EventStreamRPCServiceModel() { + @Override + public String getServiceName() { + return "TestIpcService"; + } + + @Override + public Collection getAllOperations() { + return new HashSet<>(); + } + + @Override + protected Optional> getServiceClassType(String applicationModelType) { + return Optional.empty(); + } + + @Override + public OperationModelContext getOperationModelContext(String operationName) { + return new OperationModelContext() { + @Override + public EventStreamRPCServiceModel getServiceModel() { + return serviceModel; + } + + @Override + public String getOperationName() { + return operationName; + } + + @Override + public Class getRequestTypeClass() { + return requestClass; + } + + @Override + public String getRequestApplicationModelType() { + return "aws.greengrass#" + requestClass.getSimpleName(); + } + + @Override + public Class getResponseTypeClass() { + return responseClass; + } + + @Override + public String getResponseApplicationModelType() { + return "aws.greengrass#" + responseClass.getSimpleName(); + } + + @Override + public Optional getStreamingRequestTypeClass() { + return Optional.of(streamingRequestClass); + } + + @Override + public Optional getStreamingRequestApplicationModelType() { + return Optional.of("aws.greengrass#" + streamingRequestClass.getSimpleName()); + } + + @Override + public Optional getStreamingResponseTypeClass() { + return Optional.of(streamingResponseClass); + } + + @Override + public Optional getStreamingResponseApplicationModelType() { + return Optional.of("aws.greengrass#" + streamingResponseClass.getSimpleName()); + } + + @Override + public boolean isStreamingOperation() { + return isStreaming; + } + }; + } + }; + } + + @Override + protected EventStreamRPCServiceModel getServiceModel() { + return serviceModel; + } + + /** + * Probably only useful for logging + * + * @return Returns the service name for the set of RPC operations + */ + @Override + public String getServiceName() { + return "TestIpcServiceHandler"; + } + + /** + * Exposes some things so tests can verify + */ + public static abstract class TestOperationContinuationHandler extends OperationContinuationHandler { + public TestOperationContinuationHandler(OperationContinuationHandlerContext context) { + super(context); + } + + public OperationContinuationHandlerContext getContextForTest() { + return getContext(); + } + } + + @Override + public Function getOperationHandler(final String operationName) { + final EventStreamRPCServiceModel svcModel = this.serviceModel; + System.out.println("Operation handler retrieved for operation name: " + operationName); + return (context) -> { + final TestOperationContinuationHandler handler = new TestOperationContinuationHandler(context) { + @Override + public OperationModelContext getOperationModelContext() { + return svcModel.getOperationModelContext(operationName); + } + + @Override + protected void onStreamClosed() { + streamClosed = true; + } + + @Override + public EventStreamJsonMessage handleRequest(EventStreamJsonMessage request) { + System.out.println("Handling request..."); + return requestHandler.apply(request); + } + + @Override + public void handleStreamEvent(EventStreamJsonMessage streamRequestEvent) { + System.out.println("Handling request stream event..."); + } + }; + continuationHandlerQueue.offer(new AbstractMap.SimpleImmutableEntry<>(operationName, handler)); + return handler; + }; + } + + @Override + public Set getAllOperations() { + //return no operations, IpcServer doesn't actually care about that + //only that validating all operations are set before start up + return new HashSet<>(); + } + + @Override + public boolean hasHandlerForOperation(String operation) { + return true; + } + + public Map.Entry popHandler() { + return continuationHandlerQueue.poll(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/main/java/software/amazon/awssdk/eventstreamrpc/EventStreamRPCServiceModel.java b/sdk/greengrass/event-stream-rpc-model/src/main/java/software/amazon/awssdk/eventstreamrpc/EventStreamRPCServiceModel.java index 923c25f4c..7fdeea707 100644 --- a/sdk/greengrass/event-stream-rpc-model/src/main/java/software/amazon/awssdk/eventstreamrpc/EventStreamRPCServiceModel.java +++ b/sdk/greengrass/event-stream-rpc-model/src/main/java/software/amazon/awssdk/eventstreamrpc/EventStreamRPCServiceModel.java @@ -1,19 +1,35 @@ package software.amazon.awssdk.eventstreamrpc; -import com.google.gson.*; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; -import software.amazon.awssdk.eventstreamrpc.model.*; +import software.amazon.awssdk.eventstreamrpc.model.AccessDeniedException; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; import software.amazon.awssdk.eventstreamrpc.model.UnsupportedOperationException; +import software.amazon.awssdk.eventstreamrpc.model.ValidationException; import java.io.IOException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.time.Instant; -import java.util.*; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; /** * Implementers of this service model are expected to likely be singletons. There @@ -36,6 +52,7 @@ public abstract class EventStreamRPCServiceModel { builder.registerTypeAdapterFactory(OptionalTypeAdapter.FACTORY); builder.registerTypeAdapter(byte[].class, new Base64BlobSerializerDeserializer()); builder.registerTypeAdapter(Instant.class, new InstantSerializerDeserializer()); + builder.excludeFieldsWithoutExposeAnnotation(); GSON = builder.create(); } diff --git a/sdk/greengrass/event-stream-rpc-model/src/main/java/software/amazon/awssdk/eventstreamrpc/SerializationException.java b/sdk/greengrass/event-stream-rpc-model/src/main/java/software/amazon/awssdk/eventstreamrpc/SerializationException.java index 69b0cf73b..843dd77b6 100644 --- a/sdk/greengrass/event-stream-rpc-model/src/main/java/software/amazon/awssdk/eventstreamrpc/SerializationException.java +++ b/sdk/greengrass/event-stream-rpc-model/src/main/java/software/amazon/awssdk/eventstreamrpc/SerializationException.java @@ -6,6 +6,6 @@ public SerializationException(Object object) { } public SerializationException(Object object, Throwable cause) { - super("Could not serialize object: " + object.toString()); + super("Could not serialize object: " + object.toString(), cause); } } diff --git a/sdk/greengrass/event-stream-rpc-model/src/main/java/software/amazon/awssdk/eventstreamrpc/model/EventStreamJsonMessage.java b/sdk/greengrass/event-stream-rpc-model/src/main/java/software/amazon/awssdk/eventstreamrpc/model/EventStreamJsonMessage.java index 34fea6a99..a3ad939db 100644 --- a/sdk/greengrass/event-stream-rpc-model/src/main/java/software/amazon/awssdk/eventstreamrpc/model/EventStreamJsonMessage.java +++ b/sdk/greengrass/event-stream-rpc-model/src/main/java/software/amazon/awssdk/eventstreamrpc/model/EventStreamJsonMessage.java @@ -23,7 +23,7 @@ default byte[] toPayload(final Gson gson) { if (payloadString == null || payloadString.isEmpty() || payloadString.equals("null")) { return "{}".getBytes(StandardCharsets.UTF_8); } - return gson.toJson(this).getBytes(StandardCharsets.UTF_8); + return payloadString.getBytes(StandardCharsets.UTF_8); } default EventStreamJsonMessage fromJson(final Gson gson, byte[] payload) { diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/CauseServiceErrorOperationContext.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/CauseServiceErrorOperationContext.java new file mode 100644 index 000000000..89abade06 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/CauseServiceErrorOperationContext.java @@ -0,0 +1,60 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.model.CauseServiceErrorRequest; +import software.amazon.awssdk.awstest.model.CauseServiceErrorResponse; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCServiceModel; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Optional; + +public class CauseServiceErrorOperationContext implements OperationModelContext { + @Override + public EventStreamRPCServiceModel getServiceModel() { + return EchoTestRPCServiceModel.getInstance(); + } + + @Override + public String getOperationName() { + return EchoTestRPCServiceModel.CAUSE_SERVICE_ERROR; + } + + @Override + public Class getRequestTypeClass() { + return CauseServiceErrorRequest.class; + } + + @Override + public Class getResponseTypeClass() { + return CauseServiceErrorResponse.class; + } + + @Override + public String getRequestApplicationModelType() { + return CauseServiceErrorRequest.APPLICATION_MODEL_TYPE; + } + + @Override + public String getResponseApplicationModelType() { + return CauseServiceErrorResponse.APPLICATION_MODEL_TYPE; + } + + @Override + public Optional> getStreamingRequestTypeClass() { + return Optional.empty(); + } + + @Override + public Optional> getStreamingResponseTypeClass() { + return Optional.empty(); + } + + public Optional getStreamingRequestApplicationModelType() { + return Optional.empty(); + } + + @Override + public Optional getStreamingResponseApplicationModelType() { + return Optional.empty(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/CauseStreamServiceToErrorOperationContext.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/CauseStreamServiceToErrorOperationContext.java new file mode 100644 index 000000000..c45130cd2 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/CauseStreamServiceToErrorOperationContext.java @@ -0,0 +1,60 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.model.EchoStreamingMessage; +import software.amazon.awssdk.awstest.model.EchoStreamingRequest; +import software.amazon.awssdk.awstest.model.EchoStreamingResponse; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCServiceModel; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; + +import java.util.Optional; + +public class CauseStreamServiceToErrorOperationContext implements OperationModelContext { + @Override + public EventStreamRPCServiceModel getServiceModel() { + return EchoTestRPCServiceModel.getInstance(); + } + + @Override + public String getOperationName() { + return EchoTestRPCServiceModel.CAUSE_STREAM_SERVICE_TO_ERROR; + } + + @Override + public Class getRequestTypeClass() { + return EchoStreamingRequest.class; + } + + @Override + public Class getResponseTypeClass() { + return EchoStreamingResponse.class; + } + + @Override + public String getRequestApplicationModelType() { + return EchoStreamingRequest.APPLICATION_MODEL_TYPE; + } + + @Override + public String getResponseApplicationModelType() { + return EchoStreamingResponse.APPLICATION_MODEL_TYPE; + } + + @Override + public Optional> getStreamingRequestTypeClass() { + return Optional.of(EchoStreamingMessage.class); + } + + @Override + public Optional> getStreamingResponseTypeClass() { + return Optional.of(EchoStreamingMessage.class); + } + + public Optional getStreamingRequestApplicationModelType() { + return Optional.of(EchoStreamingMessage.APPLICATION_MODEL_TYPE); + } + + @Override + public Optional getStreamingResponseApplicationModelType() { + return Optional.of(EchoStreamingMessage.APPLICATION_MODEL_TYPE); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/EchoMessageOperationContext.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/EchoMessageOperationContext.java new file mode 100644 index 000000000..63c842a32 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/EchoMessageOperationContext.java @@ -0,0 +1,60 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.model.EchoMessageRequest; +import software.amazon.awssdk.awstest.model.EchoMessageResponse; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCServiceModel; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Optional; + +public class EchoMessageOperationContext implements OperationModelContext { + @Override + public EventStreamRPCServiceModel getServiceModel() { + return EchoTestRPCServiceModel.getInstance(); + } + + @Override + public String getOperationName() { + return EchoTestRPCServiceModel.ECHO_MESSAGE; + } + + @Override + public Class getRequestTypeClass() { + return EchoMessageRequest.class; + } + + @Override + public Class getResponseTypeClass() { + return EchoMessageResponse.class; + } + + @Override + public String getRequestApplicationModelType() { + return EchoMessageRequest.APPLICATION_MODEL_TYPE; + } + + @Override + public String getResponseApplicationModelType() { + return EchoMessageResponse.APPLICATION_MODEL_TYPE; + } + + @Override + public Optional> getStreamingRequestTypeClass() { + return Optional.empty(); + } + + @Override + public Optional> getStreamingResponseTypeClass() { + return Optional.empty(); + } + + public Optional getStreamingRequestApplicationModelType() { + return Optional.empty(); + } + + @Override + public Optional getStreamingResponseApplicationModelType() { + return Optional.empty(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/EchoStreamMessagesOperationContext.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/EchoStreamMessagesOperationContext.java new file mode 100644 index 000000000..71e07dcec --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/EchoStreamMessagesOperationContext.java @@ -0,0 +1,60 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.model.EchoStreamingMessage; +import software.amazon.awssdk.awstest.model.EchoStreamingRequest; +import software.amazon.awssdk.awstest.model.EchoStreamingResponse; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCServiceModel; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; + +import java.util.Optional; + +public class EchoStreamMessagesOperationContext implements OperationModelContext { + @Override + public EventStreamRPCServiceModel getServiceModel() { + return EchoTestRPCServiceModel.getInstance(); + } + + @Override + public String getOperationName() { + return EchoTestRPCServiceModel.ECHO_STREAM_MESSAGES; + } + + @Override + public Class getRequestTypeClass() { + return EchoStreamingRequest.class; + } + + @Override + public Class getResponseTypeClass() { + return EchoStreamingResponse.class; + } + + @Override + public String getRequestApplicationModelType() { + return EchoStreamingRequest.APPLICATION_MODEL_TYPE; + } + + @Override + public String getResponseApplicationModelType() { + return EchoStreamingResponse.APPLICATION_MODEL_TYPE; + } + + @Override + public Optional> getStreamingRequestTypeClass() { + return Optional.of(EchoStreamingMessage.class); + } + + @Override + public Optional> getStreamingResponseTypeClass() { + return Optional.of(EchoStreamingMessage.class); + } + + public Optional getStreamingRequestApplicationModelType() { + return Optional.of(EchoStreamingMessage.APPLICATION_MODEL_TYPE); + } + + @Override + public Optional getStreamingResponseApplicationModelType() { + return Optional.of(EchoStreamingMessage.APPLICATION_MODEL_TYPE); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/EchoTestRPCServiceModel.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/EchoTestRPCServiceModel.java new file mode 100644 index 000000000..ea43936e0 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/EchoTestRPCServiceModel.java @@ -0,0 +1,140 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCServiceModel; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; +import software.amazon.awssdk.awstest.model.*; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class EchoTestRPCServiceModel extends EventStreamRPCServiceModel { + private static final EchoTestRPCServiceModel INSTANCE = new EchoTestRPCServiceModel(); + + public static final String SERVICE_NAMESPACE = "awstest"; + + public static final String SERVICE_NAME = SERVICE_NAMESPACE + "#" + "EchoTestRPC"; + + private static final Set SERVICE_OPERATION_SET = new HashSet(); + + private static final Map SERVICE_OPERATION_MODEL_MAP = new HashMap(); + + private static final Map> SERVICE_OBJECT_MODEL_MAP = new HashMap>(); + + public static final String CAUSE_SERVICE_ERROR = SERVICE_NAMESPACE + "#" + "CauseServiceError"; + + private static final CauseServiceErrorOperationContext _CAUSE_SERVICE_ERROR_OPERATION_CONTEXT = new CauseServiceErrorOperationContext(); + + public static final String CAUSE_STREAM_SERVICE_TO_ERROR = SERVICE_NAMESPACE + "#" + "CauseStreamServiceToError"; + + private static final CauseStreamServiceToErrorOperationContext _CAUSE_STREAM_SERVICE_TO_ERROR_OPERATION_CONTEXT = new CauseStreamServiceToErrorOperationContext(); + + public static final String ECHO_MESSAGE = SERVICE_NAMESPACE + "#" + "EchoMessage"; + + private static final EchoMessageOperationContext _ECHO_MESSAGE_OPERATION_CONTEXT = new EchoMessageOperationContext(); + + public static final String ECHO_STREAM_MESSAGES = SERVICE_NAMESPACE + "#" + "EchoStreamMessages"; + + private static final EchoStreamMessagesOperationContext _ECHO_STREAM_MESSAGES_OPERATION_CONTEXT = new EchoStreamMessagesOperationContext(); + + public static final String GET_ALL_CUSTOMERS = SERVICE_NAMESPACE + "#" + "GetAllCustomers"; + + private static final GetAllCustomersOperationContext _GET_ALL_CUSTOMERS_OPERATION_CONTEXT = new GetAllCustomersOperationContext(); + + public static final String GET_ALL_PRODUCTS = SERVICE_NAMESPACE + "#" + "GetAllProducts"; + + private static final GetAllProductsOperationContext _GET_ALL_PRODUCTS_OPERATION_CONTEXT = new GetAllProductsOperationContext(); + + static { + SERVICE_OPERATION_MODEL_MAP.put(CAUSE_SERVICE_ERROR, _CAUSE_SERVICE_ERROR_OPERATION_CONTEXT); + SERVICE_OPERATION_SET.add(CAUSE_SERVICE_ERROR); + SERVICE_OPERATION_MODEL_MAP.put(CAUSE_STREAM_SERVICE_TO_ERROR, _CAUSE_STREAM_SERVICE_TO_ERROR_OPERATION_CONTEXT); + SERVICE_OPERATION_SET.add(CAUSE_STREAM_SERVICE_TO_ERROR); + SERVICE_OPERATION_MODEL_MAP.put(ECHO_MESSAGE, _ECHO_MESSAGE_OPERATION_CONTEXT); + SERVICE_OPERATION_SET.add(ECHO_MESSAGE); + SERVICE_OPERATION_MODEL_MAP.put(ECHO_STREAM_MESSAGES, _ECHO_STREAM_MESSAGES_OPERATION_CONTEXT); + SERVICE_OPERATION_SET.add(ECHO_STREAM_MESSAGES); + SERVICE_OPERATION_MODEL_MAP.put(GET_ALL_CUSTOMERS, _GET_ALL_CUSTOMERS_OPERATION_CONTEXT); + SERVICE_OPERATION_SET.add(GET_ALL_CUSTOMERS); + SERVICE_OPERATION_MODEL_MAP.put(GET_ALL_PRODUCTS, _GET_ALL_PRODUCTS_OPERATION_CONTEXT); + SERVICE_OPERATION_SET.add(GET_ALL_PRODUCTS); + SERVICE_OBJECT_MODEL_MAP.put(CauseServiceErrorRequest.APPLICATION_MODEL_TYPE, CauseServiceErrorRequest.class); + SERVICE_OBJECT_MODEL_MAP.put(CauseServiceErrorResponse.APPLICATION_MODEL_TYPE, CauseServiceErrorResponse.class); + SERVICE_OBJECT_MODEL_MAP.put(Customer.APPLICATION_MODEL_TYPE, Customer.class); + SERVICE_OBJECT_MODEL_MAP.put(EchoMessageRequest.APPLICATION_MODEL_TYPE, EchoMessageRequest.class); + SERVICE_OBJECT_MODEL_MAP.put(EchoMessageResponse.APPLICATION_MODEL_TYPE, EchoMessageResponse.class); + SERVICE_OBJECT_MODEL_MAP.put(EchoStreamingMessage.APPLICATION_MODEL_TYPE, EchoStreamingMessage.class); + SERVICE_OBJECT_MODEL_MAP.put(EchoStreamingRequest.APPLICATION_MODEL_TYPE, EchoStreamingRequest.class); + SERVICE_OBJECT_MODEL_MAP.put(EchoStreamingResponse.APPLICATION_MODEL_TYPE, EchoStreamingResponse.class); + SERVICE_OBJECT_MODEL_MAP.put(FruitEnum.APPLICATION_MODEL_TYPE, FruitEnum.class); + SERVICE_OBJECT_MODEL_MAP.put(GetAllCustomersRequest.APPLICATION_MODEL_TYPE, GetAllCustomersRequest.class); + SERVICE_OBJECT_MODEL_MAP.put(GetAllCustomersResponse.APPLICATION_MODEL_TYPE, GetAllCustomersResponse.class); + SERVICE_OBJECT_MODEL_MAP.put(GetAllProductsRequest.APPLICATION_MODEL_TYPE, GetAllProductsRequest.class); + SERVICE_OBJECT_MODEL_MAP.put(GetAllProductsResponse.APPLICATION_MODEL_TYPE, GetAllProductsResponse.class); + SERVICE_OBJECT_MODEL_MAP.put(MessageData.APPLICATION_MODEL_TYPE, MessageData.class); + SERVICE_OBJECT_MODEL_MAP.put(Pair.APPLICATION_MODEL_TYPE, Pair.class); + SERVICE_OBJECT_MODEL_MAP.put(Product.APPLICATION_MODEL_TYPE, Product.class); + SERVICE_OBJECT_MODEL_MAP.put(ServiceError.APPLICATION_MODEL_TYPE, ServiceError.class); + } + + private EchoTestRPCServiceModel() { + } + + public static EchoTestRPCServiceModel getInstance() { + return INSTANCE; + } + + @Override + public String getServiceName() { + return "awstest#EchoTestRPC"; + } + + public static CauseServiceErrorOperationContext getCauseServiceErrorModelContext() { + return _CAUSE_SERVICE_ERROR_OPERATION_CONTEXT; + } + + public static CauseStreamServiceToErrorOperationContext getCauseStreamServiceToErrorModelContext( + ) { + return _CAUSE_STREAM_SERVICE_TO_ERROR_OPERATION_CONTEXT; + } + + public static EchoMessageOperationContext getEchoMessageModelContext() { + return _ECHO_MESSAGE_OPERATION_CONTEXT; + } + + public static EchoStreamMessagesOperationContext getEchoStreamMessagesModelContext() { + return _ECHO_STREAM_MESSAGES_OPERATION_CONTEXT; + } + + public static GetAllCustomersOperationContext getGetAllCustomersModelContext() { + return _GET_ALL_CUSTOMERS_OPERATION_CONTEXT; + } + + public static GetAllProductsOperationContext getGetAllProductsModelContext() { + return _GET_ALL_PRODUCTS_OPERATION_CONTEXT; + } + + @Override + public final Collection getAllOperations() { + // Return a defensive copy so caller cannot change internal structure of service model + return new HashSet(SERVICE_OPERATION_SET); + } + + @Override + protected Optional> getServiceClassType( + String applicationModelType) { + if (SERVICE_OBJECT_MODEL_MAP.containsKey(applicationModelType)) { + return Optional.of(SERVICE_OBJECT_MODEL_MAP.get(applicationModelType)); + } + return Optional.empty(); + } + + @Override + public OperationModelContext getOperationModelContext(String operationName) { + return SERVICE_OPERATION_MODEL_MAP.get(operationName); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/GetAllCustomersOperationContext.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/GetAllCustomersOperationContext.java new file mode 100644 index 000000000..06676a3eb --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/GetAllCustomersOperationContext.java @@ -0,0 +1,60 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.model.GetAllCustomersRequest; +import software.amazon.awssdk.awstest.model.GetAllCustomersResponse; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCServiceModel; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Optional; + +public class GetAllCustomersOperationContext implements OperationModelContext { + @Override + public EventStreamRPCServiceModel getServiceModel() { + return EchoTestRPCServiceModel.getInstance(); + } + + @Override + public String getOperationName() { + return EchoTestRPCServiceModel.GET_ALL_CUSTOMERS; + } + + @Override + public Class getRequestTypeClass() { + return GetAllCustomersRequest.class; + } + + @Override + public Class getResponseTypeClass() { + return GetAllCustomersResponse.class; + } + + @Override + public String getRequestApplicationModelType() { + return GetAllCustomersRequest.APPLICATION_MODEL_TYPE; + } + + @Override + public String getResponseApplicationModelType() { + return GetAllCustomersResponse.APPLICATION_MODEL_TYPE; + } + + @Override + public Optional> getStreamingRequestTypeClass() { + return Optional.empty(); + } + + @Override + public Optional> getStreamingResponseTypeClass() { + return Optional.empty(); + } + + public Optional getStreamingRequestApplicationModelType() { + return Optional.empty(); + } + + @Override + public Optional getStreamingResponseApplicationModelType() { + return Optional.empty(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/GetAllProductsOperationContext.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/GetAllProductsOperationContext.java new file mode 100644 index 000000000..b34307f2e --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/GetAllProductsOperationContext.java @@ -0,0 +1,60 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.model.GetAllProductsRequest; +import software.amazon.awssdk.awstest.model.GetAllProductsResponse; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCServiceModel; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Optional; + +public class GetAllProductsOperationContext implements OperationModelContext { + @Override + public EventStreamRPCServiceModel getServiceModel() { + return EchoTestRPCServiceModel.getInstance(); + } + + @Override + public String getOperationName() { + return EchoTestRPCServiceModel.GET_ALL_PRODUCTS; + } + + @Override + public Class getRequestTypeClass() { + return GetAllProductsRequest.class; + } + + @Override + public Class getResponseTypeClass() { + return GetAllProductsResponse.class; + } + + @Override + public String getRequestApplicationModelType() { + return GetAllProductsRequest.APPLICATION_MODEL_TYPE; + } + + @Override + public String getResponseApplicationModelType() { + return GetAllProductsResponse.APPLICATION_MODEL_TYPE; + } + + @Override + public Optional> getStreamingRequestTypeClass() { + return Optional.empty(); + } + + @Override + public Optional> getStreamingResponseTypeClass() { + return Optional.empty(); + } + + public Optional getStreamingRequestApplicationModelType() { + return Optional.empty(); + } + + @Override + public Optional getStreamingResponseApplicationModelType() { + return Optional.empty(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/CauseServiceErrorRequest.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/CauseServiceErrorRequest.java new file mode 100644 index 000000000..dae3d87b1 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/CauseServiceErrorRequest.java @@ -0,0 +1,43 @@ +package software.amazon.awssdk.awstest.model; + +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Objects; + +public class CauseServiceErrorRequest implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "awstest#CauseServiceErrorRequest"; + + public static final CauseServiceErrorRequest VOID; + + static { + VOID = new CauseServiceErrorRequest() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + public CauseServiceErrorRequest() { + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof CauseServiceErrorRequest)) return false; + if (this == rhs) return true; + final CauseServiceErrorRequest other = (CauseServiceErrorRequest)rhs; + boolean isEquals = true; + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/CauseServiceErrorResponse.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/CauseServiceErrorResponse.java new file mode 100644 index 000000000..20c71e19a --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/CauseServiceErrorResponse.java @@ -0,0 +1,43 @@ +package software.amazon.awssdk.awstest.model; + +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Objects; + +public class CauseServiceErrorResponse implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "awstest#CauseServiceErrorResponse"; + + public static final CauseServiceErrorResponse VOID; + + static { + VOID = new CauseServiceErrorResponse() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + public CauseServiceErrorResponse() { + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof CauseServiceErrorResponse)) return false; + if (this == rhs) return true; + final CauseServiceErrorResponse other = (CauseServiceErrorResponse)rhs; + boolean isEquals = true; + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/Customer.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/Customer.java new file mode 100644 index 000000000..50fac2e2e --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/Customer.java @@ -0,0 +1,105 @@ +package software.amazon.awssdk.awstest.model; + +import com.google.gson.annotations.Expose; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Objects; +import java.util.Optional; + +public class Customer implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "awstest#Customer"; + + public static final Customer VOID; + + static { + VOID = new Customer() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + @Expose( + serialize = true, + deserialize = true + ) + private Optional id; + + @Expose( + serialize = true, + deserialize = true + ) + private Optional firstName; + + @Expose( + serialize = true, + deserialize = true + ) + private Optional lastName; + + public Customer() { + this.id = Optional.empty(); + this.firstName = Optional.empty(); + this.lastName = Optional.empty(); + } + + public Long getId() { + if (id.isPresent()) { + return id.get(); + } + return null; + } + + public Customer setId(final Long id) { + this.id = Optional.ofNullable(id); + return this; + } + + public String getFirstName() { + if (firstName.isPresent()) { + return firstName.get(); + } + return null; + } + + public Customer setFirstName(final String firstName) { + this.firstName = Optional.ofNullable(firstName); + return this; + } + + public String getLastName() { + if (lastName.isPresent()) { + return lastName.get(); + } + return null; + } + + public Customer setLastName(final String lastName) { + this.lastName = Optional.ofNullable(lastName); + return this; + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof Customer)) return false; + if (this == rhs) return true; + final Customer other = (Customer)rhs; + boolean isEquals = true; + isEquals = isEquals && this.id.equals(other.id); + isEquals = isEquals && this.firstName.equals(other.firstName); + isEquals = isEquals && this.lastName.equals(other.lastName); + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(id, firstName, lastName); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoMessageRequest.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoMessageRequest.java new file mode 100644 index 000000000..c4ebc65a3 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoMessageRequest.java @@ -0,0 +1,65 @@ +package software.amazon.awssdk.awstest.model; + +import com.google.gson.annotations.Expose; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Objects; +import java.util.Optional; + +public class EchoMessageRequest implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "awstest#EchoMessageRequest"; + + public static final EchoMessageRequest VOID; + + static { + VOID = new EchoMessageRequest() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + @Expose( + serialize = true, + deserialize = true + ) + private Optional message; + + public EchoMessageRequest() { + this.message = Optional.empty(); + } + + public MessageData getMessage() { + if (message.isPresent()) { + return message.get(); + } + return null; + } + + public EchoMessageRequest setMessage(final MessageData message) { + this.message = Optional.ofNullable(message); + return this; + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof EchoMessageRequest)) return false; + if (this == rhs) return true; + final EchoMessageRequest other = (EchoMessageRequest)rhs; + boolean isEquals = true; + isEquals = isEquals && this.message.equals(other.message); + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(message); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoMessageResponse.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoMessageResponse.java new file mode 100644 index 000000000..bfe22e341 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoMessageResponse.java @@ -0,0 +1,65 @@ +package software.amazon.awssdk.awstest.model; + +import com.google.gson.annotations.Expose; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Objects; +import java.util.Optional; + +public class EchoMessageResponse implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "awstest#EchoMessageResponse"; + + public static final EchoMessageResponse VOID; + + static { + VOID = new EchoMessageResponse() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + @Expose( + serialize = true, + deserialize = true + ) + private Optional message; + + public EchoMessageResponse() { + this.message = Optional.empty(); + } + + public MessageData getMessage() { + if (message.isPresent()) { + return message.get(); + } + return null; + } + + public EchoMessageResponse setMessage(final MessageData message) { + this.message = Optional.ofNullable(message); + return this; + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof EchoMessageResponse)) return false; + if (this == rhs) return true; + final EchoMessageResponse other = (EchoMessageResponse)rhs; + boolean isEquals = true; + isEquals = isEquals && this.message.equals(other.message); + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(message); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoStreamingMessage.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoStreamingMessage.java new file mode 100644 index 000000000..8cce81f55 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoStreamingMessage.java @@ -0,0 +1,141 @@ +package software.amazon.awssdk.awstest.model; + +import com.google.gson.annotations.Expose; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Predicate; + +public class EchoStreamingMessage implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "awstest#EchoStreamingMessage"; + + private transient UnionMember setUnionMember; + + @Expose( + serialize = true, + deserialize = true + ) + private Optional streamMessage; + + @Expose( + serialize = true, + deserialize = true + ) + private Optional keyValuePair; + + public EchoStreamingMessage() { + this.streamMessage = Optional.empty(); + this.keyValuePair = Optional.empty(); + } + + public MessageData getStreamMessage() { + if (streamMessage.isPresent() && (setUnionMember == UnionMember.STREAM_MESSAGE)) { + return streamMessage.get(); + } + return null; + } + + public EchoStreamingMessage setStreamMessage(final MessageData streamMessage) { + if (setUnionMember != null) { + setUnionMember.nullify(this); + } + this.streamMessage = Optional.of(streamMessage); + this.setUnionMember = UnionMember.STREAM_MESSAGE; + return this; + } + + public Pair getKeyValuePair() { + if (keyValuePair.isPresent() && (setUnionMember == UnionMember.KEY_VALUE_PAIR)) { + return keyValuePair.get(); + } + return null; + } + + public EchoStreamingMessage setKeyValuePair(final Pair keyValuePair) { + if (setUnionMember != null) { + setUnionMember.nullify(this); + } + this.keyValuePair = Optional.of(keyValuePair); + this.setUnionMember = UnionMember.KEY_VALUE_PAIR; + return this; + } + + /** + * Returns an indicator for which enum member is set. Can be used to convert to proper type. + */ + public UnionMember getSetUnionMember() { + return setUnionMember; + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + public void selfDesignateSetUnionMember() { + int setCount = 0; + UnionMember[] members = UnionMember.values(); + for (int memberIdx = 0; memberIdx < UnionMember.values().length; ++memberIdx) { + if (members[memberIdx].isPresent(this)) { + ++setCount; + this.setUnionMember = members[memberIdx]; + } + } + // only bad outcome here is if there's more than one member set. It's possible for none to be set + if (setCount > 1) { + throw new IllegalArgumentException("More than one union member set for type: " + getApplicationModelType()); + } + } + + @Override + public void postFromJson() { + selfDesignateSetUnionMember(); + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof EchoStreamingMessage)) return false; + if (this == rhs) return true; + final EchoStreamingMessage other = (EchoStreamingMessage)rhs; + boolean isEquals = true; + isEquals = isEquals && this.streamMessage.equals(other.streamMessage); + isEquals = isEquals && this.keyValuePair.equals(other.keyValuePair); + isEquals = isEquals && this.setUnionMember.equals(other.setUnionMember); + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(streamMessage, keyValuePair, setUnionMember); + } + + public enum UnionMember { + STREAM_MESSAGE("STREAM_MESSAGE", (software.amazon.awssdk.awstest.model.EchoStreamingMessage obj) -> obj.streamMessage = Optional.empty(), (software.amazon.awssdk.awstest.model.EchoStreamingMessage obj) -> obj.streamMessage != null && obj.streamMessage.isPresent()), + + KEY_VALUE_PAIR("KEY_VALUE_PAIR", (software.amazon.awssdk.awstest.model.EchoStreamingMessage obj) -> obj.keyValuePair = Optional.empty(), (software.amazon.awssdk.awstest.model.EchoStreamingMessage obj) -> obj.keyValuePair != null && obj.keyValuePair.isPresent()); + + private String fieldName; + + private Consumer nullifier; + + private Predicate isPresent; + + UnionMember(String fieldName, Consumer nullifier, + Predicate isPresent) { + this.fieldName = fieldName; + this.nullifier = nullifier; + this.isPresent = isPresent; + } + + void nullify(EchoStreamingMessage obj) { + nullifier.accept(obj); + } + + boolean isPresent(EchoStreamingMessage obj) { + return isPresent.test(obj); + } + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoStreamingRequest.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoStreamingRequest.java new file mode 100644 index 000000000..9b55c8398 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoStreamingRequest.java @@ -0,0 +1,43 @@ +package software.amazon.awssdk.awstest.model; + +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Objects; + +public class EchoStreamingRequest implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "awstest#EchoStreamingRequest"; + + public static final EchoStreamingRequest VOID; + + static { + VOID = new EchoStreamingRequest() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + public EchoStreamingRequest() { + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof EchoStreamingRequest)) return false; + if (this == rhs) return true; + final EchoStreamingRequest other = (EchoStreamingRequest)rhs; + boolean isEquals = true; + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoStreamingResponse.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoStreamingResponse.java new file mode 100644 index 000000000..992dade08 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoStreamingResponse.java @@ -0,0 +1,43 @@ +package software.amazon.awssdk.awstest.model; + +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Objects; + +public class EchoStreamingResponse implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "awstest#EchoStreamingResponse"; + + public static final EchoStreamingResponse VOID; + + static { + VOID = new EchoStreamingResponse() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + public EchoStreamingResponse() { + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof EchoStreamingResponse)) return false; + if (this == rhs) return true; + final EchoStreamingResponse other = (EchoStreamingResponse)rhs; + boolean isEquals = true; + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoTestRPCError.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoTestRPCError.java new file mode 100644 index 000000000..920f4415f --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/EchoTestRPCError.java @@ -0,0 +1,24 @@ +package software.amazon.awssdk.awstest.model; + +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamOperationError; + +public abstract class EchoTestRPCError extends EventStreamOperationError implements EventStreamJsonMessage { + EchoTestRPCError(String errorCode, String errorMessage) { + super("awstest#EchoTestRPC", errorCode, errorMessage); + } + + public abstract String getErrorTypeString(); + + public boolean isRetryable() { + return getErrorTypeString().equals("server"); + } + + public boolean isServerError() { + return getErrorTypeString().equals("server"); + } + + public boolean isClientError() { + return getErrorTypeString().equals("client"); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/FruitEnum.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/FruitEnum.java new file mode 100644 index 000000000..980de096e --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/FruitEnum.java @@ -0,0 +1,50 @@ +package software.amazon.awssdk.awstest.model; + +import com.google.gson.annotations.SerializedName; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.HashMap; +import java.util.Map; + +public enum FruitEnum implements EventStreamJsonMessage { + @SerializedName("apl") + APPLE("apl"), + + @SerializedName("org") + ORANGE("org"), + + @SerializedName("ban") + BANANA("ban"), + + @SerializedName("pin") + PINEAPPLE("pin"); + + public static final String APPLICATION_MODEL_TYPE = "awstest#FruitEnum"; + + private static final Map lookup = new HashMap(); + + static { + for (FruitEnum value:FruitEnum.values()) { + lookup.put(value.getValue(), value); + } + } + + String value; + + FruitEnum(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + public static FruitEnum get(String value) { + return lookup.get(value); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/GetAllCustomersRequest.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/GetAllCustomersRequest.java new file mode 100644 index 000000000..bbb2c3bfa --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/GetAllCustomersRequest.java @@ -0,0 +1,43 @@ +package software.amazon.awssdk.awstest.model; + +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Objects; + +public class GetAllCustomersRequest implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "awstest#GetAllCustomersRequest"; + + public static final GetAllCustomersRequest VOID; + + static { + VOID = new GetAllCustomersRequest() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + public GetAllCustomersRequest() { + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof GetAllCustomersRequest)) return false; + if (this == rhs) return true; + final GetAllCustomersRequest other = (GetAllCustomersRequest)rhs; + boolean isEquals = true; + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/GetAllCustomersResponse.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/GetAllCustomersResponse.java new file mode 100644 index 000000000..97288be5d --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/GetAllCustomersResponse.java @@ -0,0 +1,66 @@ +package software.amazon.awssdk.awstest.model; + +import com.google.gson.annotations.Expose; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public class GetAllCustomersResponse implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "awstest#GetAllCustomersResponse"; + + public static final GetAllCustomersResponse VOID; + + static { + VOID = new GetAllCustomersResponse() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + @Expose( + serialize = true, + deserialize = true + ) + private Optional> customers; + + public GetAllCustomersResponse() { + this.customers = Optional.empty(); + } + + public List getCustomers() { + if (customers.isPresent()) { + return customers.get(); + } + return null; + } + + public GetAllCustomersResponse setCustomers(final List customers) { + this.customers = Optional.ofNullable(customers); + return this; + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof GetAllCustomersResponse)) return false; + if (this == rhs) return true; + final GetAllCustomersResponse other = (GetAllCustomersResponse)rhs; + boolean isEquals = true; + isEquals = isEquals && this.customers.equals(other.customers); + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(customers); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/GetAllProductsRequest.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/GetAllProductsRequest.java new file mode 100644 index 000000000..516f9fc11 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/GetAllProductsRequest.java @@ -0,0 +1,43 @@ +package software.amazon.awssdk.awstest.model; + +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Objects; + +public class GetAllProductsRequest implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "awstest#GetAllProductsRequest"; + + public static final GetAllProductsRequest VOID; + + static { + VOID = new GetAllProductsRequest() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + public GetAllProductsRequest() { + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof GetAllProductsRequest)) return false; + if (this == rhs) return true; + final GetAllProductsRequest other = (GetAllProductsRequest)rhs; + boolean isEquals = true; + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/GetAllProductsResponse.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/GetAllProductsResponse.java new file mode 100644 index 000000000..bd0cb17a8 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/GetAllProductsResponse.java @@ -0,0 +1,66 @@ +package software.amazon.awssdk.awstest.model; + +import com.google.gson.annotations.Expose; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +public class GetAllProductsResponse implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "awstest#GetAllProductsResponse"; + + public static final GetAllProductsResponse VOID; + + static { + VOID = new GetAllProductsResponse() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + @Expose( + serialize = true, + deserialize = true + ) + private Optional> products; + + public GetAllProductsResponse() { + this.products = Optional.empty(); + } + + public Map getProducts() { + if (products.isPresent()) { + return products.get(); + } + return null; + } + + public GetAllProductsResponse setProducts(final Map products) { + this.products = Optional.ofNullable(products); + return this; + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof GetAllProductsResponse)) return false; + if (this == rhs) return true; + final GetAllProductsResponse other = (GetAllProductsResponse)rhs; + boolean isEquals = true; + isEquals = isEquals && this.products.equals(other.products); + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(products); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/MessageData.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/MessageData.java new file mode 100644 index 000000000..bb4a3a656 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/MessageData.java @@ -0,0 +1,240 @@ +package software.amazon.awssdk.awstest.model; + +import com.google.gson.annotations.Expose; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCServiceModel; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +public class MessageData implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "awstest#MessageData"; + + public static final MessageData VOID; + + static { + VOID = new MessageData() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + @Expose( + serialize = true, + deserialize = true + ) + private Optional stringMessage; + + @Expose( + serialize = true, + deserialize = true + ) + private Optional booleanMessage; + + @Expose( + serialize = true, + deserialize = true + ) + private Optional timeMessage; + + @Expose( + serialize = true, + deserialize = true + ) + private Optional> documentMessage; + + @Expose( + serialize = true, + deserialize = true + ) + private Optional enumMessage; + + @Expose( + serialize = true, + deserialize = true + ) + private Optional blobMessage; + + @Expose( + serialize = true, + deserialize = true + ) + private Optional> stringListMessage; + + @Expose( + serialize = true, + deserialize = true + ) + private Optional> keyValuePairList; + + @Expose( + serialize = true, + deserialize = true + ) + private Optional> stringToValue; + + public MessageData() { + this.stringMessage = Optional.empty(); + this.booleanMessage = Optional.empty(); + this.timeMessage = Optional.empty(); + this.documentMessage = Optional.empty(); + this.enumMessage = Optional.empty(); + this.blobMessage = Optional.empty(); + this.stringListMessage = Optional.empty(); + this.keyValuePairList = Optional.empty(); + this.stringToValue = Optional.empty(); + } + + public String getStringMessage() { + if (stringMessage.isPresent()) { + return stringMessage.get(); + } + return null; + } + + public MessageData setStringMessage(final String stringMessage) { + this.stringMessage = Optional.ofNullable(stringMessage); + return this; + } + + public Boolean isBooleanMessage() { + if (booleanMessage.isPresent()) { + return booleanMessage.get(); + } + return null; + } + + public MessageData setBooleanMessage(final Boolean booleanMessage) { + this.booleanMessage = Optional.ofNullable(booleanMessage); + return this; + } + + public Instant getTimeMessage() { + if (timeMessage.isPresent()) { + return timeMessage.get(); + } + return null; + } + + public MessageData setTimeMessage(final Instant timeMessage) { + this.timeMessage = Optional.ofNullable(timeMessage); + return this; + } + + public Map getDocumentMessage() { + if (documentMessage.isPresent()) { + return documentMessage.get(); + } + return null; + } + + public MessageData setDocumentMessage(final Map documentMessage) { + this.documentMessage = Optional.ofNullable(documentMessage); + return this; + } + + public FruitEnum getEnumMessage() { + if (enumMessage.isPresent()) { + return FruitEnum.get(enumMessage.get()); + } + return null; + } + + public String getEnumMessageAsString() { + if (enumMessage.isPresent()) { + return enumMessage.get(); + } + return null; + } + + public void setEnumMessage(final FruitEnum enumMessage) { + this.enumMessage = Optional.ofNullable(enumMessage.getValue()); + } + + public MessageData setEnumMessage(final String enumMessage) { + this.enumMessage = Optional.ofNullable(enumMessage); + return this; + } + + public byte[] getBlobMessage() { + if (blobMessage.isPresent()) { + return blobMessage.get(); + } + return null; + } + + public MessageData setBlobMessage(final byte[] blobMessage) { + this.blobMessage = Optional.ofNullable(blobMessage); + return this; + } + + public List getStringListMessage() { + if (stringListMessage.isPresent()) { + return stringListMessage.get(); + } + return null; + } + + public MessageData setStringListMessage(final List stringListMessage) { + this.stringListMessage = Optional.ofNullable(stringListMessage); + return this; + } + + public List getKeyValuePairList() { + if (keyValuePairList.isPresent()) { + return keyValuePairList.get(); + } + return null; + } + + public MessageData setKeyValuePairList(final List keyValuePairList) { + this.keyValuePairList = Optional.ofNullable(keyValuePairList); + return this; + } + + public Map getStringToValue() { + if (stringToValue.isPresent()) { + return stringToValue.get(); + } + return null; + } + + public MessageData setStringToValue(final Map stringToValue) { + this.stringToValue = Optional.ofNullable(stringToValue); + return this; + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof MessageData)) return false; + if (this == rhs) return true; + final MessageData other = (MessageData)rhs; + boolean isEquals = true; + isEquals = isEquals && this.stringMessage.equals(other.stringMessage); + isEquals = isEquals && this.booleanMessage.equals(other.booleanMessage); + isEquals = isEquals && this.timeMessage.equals(other.timeMessage); + isEquals = isEquals && this.documentMessage.equals(other.documentMessage); + isEquals = isEquals && this.enumMessage.equals(other.enumMessage); + isEquals = isEquals && EventStreamRPCServiceModel.blobTypeEquals(this.blobMessage, other.blobMessage); + isEquals = isEquals && this.stringListMessage.equals(other.stringListMessage); + isEquals = isEquals && this.keyValuePairList.equals(other.keyValuePairList); + isEquals = isEquals && this.stringToValue.equals(other.stringToValue); + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(stringMessage, booleanMessage, timeMessage, documentMessage, enumMessage, blobMessage, stringListMessage, keyValuePairList, stringToValue); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/Pair.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/Pair.java new file mode 100644 index 000000000..807588eeb --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/Pair.java @@ -0,0 +1,85 @@ +package software.amazon.awssdk.awstest.model; + +import com.google.gson.annotations.Expose; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Objects; +import java.util.Optional; + +public class Pair implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "awstest#Pair"; + + public static final Pair VOID; + + static { + VOID = new Pair() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + @Expose( + serialize = true, + deserialize = true + ) + private Optional key; + + @Expose( + serialize = true, + deserialize = true + ) + private Optional value; + + public Pair() { + this.key = Optional.empty(); + this.value = Optional.empty(); + } + + public String getKey() { + if (key.isPresent()) { + return key.get(); + } + return null; + } + + public Pair setKey(final String key) { + this.key = Optional.ofNullable(key); + return this; + } + + public String getValue() { + if (value.isPresent()) { + return value.get(); + } + return null; + } + + public Pair setValue(final String value) { + this.value = Optional.ofNullable(value); + return this; + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof Pair)) return false; + if (this == rhs) return true; + final Pair other = (Pair)rhs; + boolean isEquals = true; + isEquals = isEquals && this.key.equals(other.key); + isEquals = isEquals && this.value.equals(other.value); + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(key, value); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/Product.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/Product.java new file mode 100644 index 000000000..27ff51efd --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/Product.java @@ -0,0 +1,85 @@ +package software.amazon.awssdk.awstest.model; + +import com.google.gson.annotations.Expose; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Objects; +import java.util.Optional; + +public class Product implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "awstest#Product"; + + public static final Product VOID; + + static { + VOID = new Product() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + @Expose( + serialize = true, + deserialize = true + ) + private Optional name; + + @Expose( + serialize = true, + deserialize = true + ) + private Optional price; + + public Product() { + this.name = Optional.empty(); + this.price = Optional.empty(); + } + + public String getName() { + if (name.isPresent()) { + return name.get(); + } + return null; + } + + public Product setName(final String name) { + this.name = Optional.ofNullable(name); + return this; + } + + public Integer getPrice() { + if (price.isPresent()) { + return price.get(); + } + return null; + } + + public Product setPrice(final Integer price) { + this.price = Optional.ofNullable(price); + return this; + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof Product)) return false; + if (this == rhs) return true; + final Product other = (Product)rhs; + boolean isEquals = true; + isEquals = isEquals && this.name.equals(other.name); + isEquals = isEquals && this.price.equals(other.price); + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(name, price); + } +} diff --git a/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/ServiceError.java b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/ServiceError.java new file mode 100644 index 000000000..9090ffc48 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-model/src/test/java/software/amazon/awssdk/awstest/model/ServiceError.java @@ -0,0 +1,97 @@ +package software.amazon.awssdk.awstest.model; + +import com.google.gson.annotations.Expose; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.util.Objects; +import java.util.Optional; + +public class ServiceError extends EchoTestRPCError implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "awstest#ServiceError"; + + public static final ServiceError VOID; + + static { + VOID = new ServiceError() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + @Expose( + serialize = true, + deserialize = true + ) + private Optional message; + + @Expose( + serialize = true, + deserialize = true + ) + private Optional value; + + public ServiceError(String errorMessage) { + super("ServiceError", errorMessage); + this.message = Optional.ofNullable(errorMessage); + this.value = Optional.empty(); + } + + public ServiceError() { + super("ServiceError", ""); + this.message = Optional.empty(); + this.value = Optional.empty(); + } + + @Override + public String getErrorTypeString() { + return "server"; + } + + public String getMessage() { + if (message.isPresent()) { + return message.get(); + } + return null; + } + + public ServiceError setMessage(final String message) { + this.message = Optional.ofNullable(message); + return this; + } + + public String getValue() { + if (value.isPresent()) { + return value.get(); + } + return null; + } + + public ServiceError setValue(final String value) { + this.value = Optional.ofNullable(value); + return this; + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof ServiceError)) return false; + if (this == rhs) return true; + final ServiceError other = (ServiceError)rhs; + boolean isEquals = true; + isEquals = isEquals && this.message.equals(other.message); + isEquals = isEquals && this.value.equals(other.value); + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(message, value); + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/DebugLoggingOperationHandler.java b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/DebugLoggingOperationHandler.java index 8f9410e91..b9ae991c0 100644 --- a/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/DebugLoggingOperationHandler.java +++ b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/DebugLoggingOperationHandler.java @@ -1,17 +1,18 @@ package software.amazon.awssdk.eventstreamrpc; import com.google.gson.Gson; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; import java.nio.charset.StandardCharsets; -import java.util.logging.Logger; /** * Useful to set as a handler for an operation with no implementation yet. */ public class DebugLoggingOperationHandler extends OperationContinuationHandler { - private static final Logger LOGGER = Logger.getLogger(DebugLoggingOperationHandler.class.getName()); + private static Logger LOGGER = LoggerFactory.getLogger(DebugLoggingOperationHandler.class); private final OperationModelContext operationModelContext; public DebugLoggingOperationHandler(final OperationModelContext modelContext, final OperationContinuationHandlerContext context) { @@ -31,14 +32,13 @@ public OperationModelContext stopServer() { - if (serverRunning.compareAndSet(true, false)) { - try { - if (listener != null) { - listener.close(); - return listener.getShutdownCompleteFuture(); - } - return CompletableFuture.completedFuture(null); - } finally { - listener = null; - try { - if (tlsContext != null) { - tlsContext.close(); - } - } finally { - if(serverBootstrap != null) { - serverBootstrap.close(); - } - } - tlsContext = null; - serverBootstrap = null; - } - } - return CompletableFuture.completedFuture(null); - } - - /** - * Ensures a call to stop server is called when it is closed - */ - @Override - public void close() { - stopServer(); - } - - /** - * Constructor supplied EventStreamRPCServiceHandler self validates that all expected operations - * have been wired (hand written -> dependency injected perhaps) before launching the service. - * - * Also verifies that auth handlers have been set - */ - private void validateServiceHandler() { - if (eventStreamRPCServiceHandler.getAuthenticationHandler() == null) { - throw new InvalidServiceConfigurationException(String.format("%s authentication handler is not set!", - eventStreamRPCServiceHandler.getServiceName())); - } - if (eventStreamRPCServiceHandler.getAuthorizationHandler() == null) { - throw new InvalidServiceConfigurationException(String.format("%s authorization handler is not set!", - eventStreamRPCServiceHandler.getServiceName())); - } - - final EventStreamRPCServiceModel serviceModel = eventStreamRPCServiceHandler.getServiceModel(); - - if (serviceModel == null) { - throw new InvalidServiceConfigurationException("Handler must not have a null service model"); - } - - if (serviceModel.getServiceName() == null || serviceModel.getServiceName().isEmpty()) { - throw new InvalidServiceConfigurationException("Service model's name is null!"); - } - - final Set unsetOperations = serviceModel.getAllOperations().stream().filter(operationName -> { - return serviceModel.getOperationModelContext(operationName) == null; - }).collect(Collectors.toSet()); - if (!unsetOperations.isEmpty()) { - throw new InvalidServiceConfigurationException(String.format("Service has the following unset operations {%s}", - unsetOperations.stream().collect(Collectors.joining(", ")))); - } - - //validates all handlers are set - eventStreamRPCServiceHandler.validateAllOperationsSet(); + super(eventLoopGroup, socketOptions, tlsContextOptions, hostname, port, serviceHandler); + LOGGER.warn("IpcServer class is DEPRECATED. Use RpcServer"); } } diff --git a/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandler.java b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandler.java index 91f4e40ad..357483251 100644 --- a/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandler.java +++ b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandler.java @@ -1,12 +1,7 @@ package software.amazon.awssdk.eventstreamrpc; -import java.nio.charset.StandardCharsets; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.logging.Logger; - +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.awssdk.crt.eventstream.Header; import software.amazon.awssdk.crt.eventstream.MessageFlags; import software.amazon.awssdk.crt.eventstream.MessageType; @@ -14,12 +9,17 @@ import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; import software.amazon.awssdk.eventstreamrpc.model.EventStreamOperationError; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + public abstract class OperationContinuationHandler extends ServerConnectionContinuationHandler implements StreamEventPublisher { - private static final Logger LOGGER = Logger.getLogger(OperationContinuationHandler.class.getName()); + private static Logger LOGGER = LoggerFactory.getLogger(OperationContinuationHandler.class); private OperationContinuationHandlerContext context; private List
initialRequestHeaders; @@ -35,16 +35,16 @@ public OperationContinuationHandler(final OperationContinuationHandlerContext co @Override final protected void onContinuationClosed() { - LOGGER.finer(String.format("%s stream continuation closed.", getOperationName())); + LOGGER.debug("{} stream continuation closed.", getOperationName()); + continuation.close(); try { onStreamClosed(); } catch(Exception e) { - LOGGER.severe(String.format("%s threw %s: %s", getOperationName(), e.getClass().getCanonicalName(), e.getMessage())); + LOGGER.error("{} threw {}: {}", getOperationName(), e.getClass().getCanonicalName(), e.getMessage()); } } - final protected Class getRequestClass() { return getOperationModelContext().getRequestTypeClass(); } @@ -157,17 +157,17 @@ final protected OperationContinuationHandlerContext getContext () { */ @Override final public CompletableFuture closeStream() { - LOGGER.fine(String.format("[%s] closing stream", getOperationName())); + LOGGER.debug("[{}] closing stream", getOperationName()); return continuation.sendMessage(null, null, MessageType.ApplicationMessage, MessageFlags.TerminateStream.getByteValue()) .whenComplete((res, ex) -> { - if (ex != null) { - LOGGER.fine(String.format("[%s] closed stream", getOperationName())); + continuation.close(); + if (ex == null) { + LOGGER.debug("[{}] closed stream", getOperationName()); } else { - LOGGER.fine(String.format("[%s] %s closing stream: ", getOperationName(), - ex.getClass().getName(), ex.getMessage())); + LOGGER.error("[{}] {} error closing stream: {}", getOperationName(), + ex.getClass().getName(), ex.getMessage()); } - continuation.close(); }); } @@ -229,29 +229,39 @@ private void invokeAfterHandleRequest() { try { afterHandleRequest(); } catch (Exception e) { - LOGGER.warning(String.format("%s.%s afterHandleRequest() threw %s: %s", + LOGGER.warn("{}.{} afterHandleRequest() threw {}: {}", getOperationModelContext().getServiceModel().getServiceName(), getOperationName(), e.getClass().getCanonicalName(), - e.getMessage())); + e.getMessage()); } } @Override - final protected void onContinuationMessage(List
list, byte[] bytes, MessageType messageType, int i) { - LOGGER.fine("Continuation native id: " + continuation.getNativeHandle()); + final protected void onContinuationMessage(List
list, byte[] bytes, MessageType messageType, int messageFlags) { + LOGGER.debug("Continuation native id: " + continuation.getNativeHandle()); + + //We can prevent a client from sending a request, and hanging up before receiving a response + //but doing so will prevent any work from being done + if (initialRequest == null && (messageFlags & MessageFlags.TerminateStream.getByteValue()) != 0) { + LOGGER.debug("Not invoking " + getOperationName() + " operation for client request received with a " + + "terminate flag set to 1"); + return; + } final EventStreamRPCServiceModel serviceModel = getOperationModelContext().getServiceModel(); - try { if (initialRequest != null) { - //TODO: FIX empty close messages arrive here and throw exception - final StreamingRequestType streamEvent = serviceModel.fromJson(getStreamingRequestClass(), bytes); - //exceptions occurring during this processing will result in closure of stream - handleStreamEvent(streamEvent); + // Empty close stream messages from the client are valid. Do not need any processing here. + if ((messageFlags & MessageFlags.TerminateStream.getByteValue()) != 0 && (bytes == null || bytes.length == 0)) { + return; + } else { + final StreamingRequestType streamEvent = serviceModel.fromJson(getStreamingRequestClass(), bytes); + //exceptions occurring during this processing will result in closure of stream + handleStreamEvent(streamEvent); + } } else { //this is the initial request initialRequestHeaders = new ArrayList<>(list); initialRequest = serviceModel.fromJson(getRequestClass(), bytes); //call into business logic - final ResponseType result = handleRequest(initialRequest); if (result != null) { if (!getResponseClass().isInstance(result)) { @@ -260,9 +270,9 @@ final protected void onContinuationMessage(List
list, byte[] bytes, Mess } sendMessage(result, !isStreamingOperation()).whenComplete((res, ex) -> { if (ex != null) { - LOGGER.severe(ex.getClass().getName() + " sending response message: " + ex.getMessage()); + LOGGER.error(ex.getClass().getName() + " sending response message: " + ex.getMessage()); } else { - LOGGER.finer("Response successfully sent"); + LOGGER.trace("Response successfully sent"); } }); invokeAfterHandleRequest(); @@ -280,18 +290,18 @@ final protected void onContinuationMessage(List
list, byte[] bytes, Mess byte[] outputPayload = "InternalServerError".getBytes(StandardCharsets.UTF_8); responseHeaders.add(Header.createHeader(EventStreamRPCServiceModel.CONTENT_TYPE_HEADER, EventStreamRPCServiceModel.CONTENT_TYPE_APPLICATION_TEXT)); - // TODO: are there any exceptions we wouldn't want to return a generic server fault? - // TODO: this is the kind of exception that should be logged with a request ID especially in a server-client context - LOGGER.severe(String.format("[%s] operation threw unexpected %s: %s", getOperationName(), - e.getClass().getCanonicalName(), e.getMessage())); + //are there any exceptions we wouldn't want to return a generic server fault? + //this is the kind of exception that should be logged with a request ID especially in a server-client context + LOGGER.error("[{}] operation threw unexpected {}: {}", getOperationName(), + e.getClass().getCanonicalName(), e.getMessage()); continuation.sendMessage(responseHeaders, outputPayload, MessageType.ApplicationError, MessageFlags.TerminateStream.getByteValue()) .whenComplete((res, ex) -> { if (ex != null) { - LOGGER.severe(ex.getClass().getName() + " sending error response message: " + ex.getMessage()); + LOGGER.error(ex.getClass().getName() + " sending error response message: " + ex.getMessage()); } else { - LOGGER.finer("Error response successfully sent"); + LOGGER.trace("Error response successfully sent"); } continuation.close(); }); diff --git a/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandlerFactory.java b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandlerFactory.java index 4607ae35b..331414ce5 100644 --- a/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandlerFactory.java +++ b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandlerFactory.java @@ -1,11 +1,11 @@ package software.amazon.awssdk.eventstreamrpc; +import software.amazon.awssdk.crt.eventstream.ServerConnectionContinuationHandler; + import java.util.Collection; import java.util.function.Function; import java.util.stream.Collectors; -import software.amazon.awssdk.crt.eventstream.ServerConnectionContinuationHandler; - /** * This is really the entire service interface base class */ diff --git a/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/RpcServer.java b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/RpcServer.java new file mode 100644 index 000000000..b5a2f047a --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/RpcServer.java @@ -0,0 +1,152 @@ +package software.amazon.awssdk.eventstreamrpc; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.crt.CRT; +import software.amazon.awssdk.crt.eventstream.ServerConnection; +import software.amazon.awssdk.crt.eventstream.ServerConnectionHandler; +import software.amazon.awssdk.crt.eventstream.ServerListener; +import software.amazon.awssdk.crt.eventstream.ServerListenerHandler; +import software.amazon.awssdk.crt.io.EventLoopGroup; +import software.amazon.awssdk.crt.io.ServerBootstrap; +import software.amazon.awssdk.crt.io.ServerTlsContext; +import software.amazon.awssdk.crt.io.SocketOptions; +import software.amazon.awssdk.crt.io.TlsContextOptions; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +public class RpcServer implements AutoCloseable { + private static final Logger LOGGER = LoggerFactory.getLogger(RpcServer.class); + + private final EventLoopGroup eventLoopGroup; + private final SocketOptions socketOptions; + private final TlsContextOptions tlsContextOptions; + private final String hostname; + private final int port; + private final EventStreamRPCServiceHandler eventStreamRPCServiceHandler; + + private ServerBootstrap serverBootstrap; + private ServerTlsContext tlsContext; + private ServerListener listener; + private AtomicBoolean serverRunning; + + public RpcServer(EventLoopGroup eventLoopGroup, SocketOptions socketOptions, TlsContextOptions tlsContextOptions, String hostname, int port, EventStreamRPCServiceHandler serviceHandler) { + this.eventLoopGroup = eventLoopGroup; + this.socketOptions = socketOptions; + this.tlsContextOptions = tlsContextOptions; + this.hostname = hostname; + this.port = port; + this.eventStreamRPCServiceHandler = serviceHandler; + this.serverRunning = new AtomicBoolean(false); + } + + /** + * Runs the server in the constructor supplied event loop group + */ + public void runServer() { + validateServiceHandler(); + if (!serverRunning.compareAndSet(false, true)) { + throw new IllegalStateException("Failed to start IpcServer. It's already started or has not completed a prior shutdown!"); + } + serverBootstrap = new ServerBootstrap(eventLoopGroup); + tlsContext = tlsContextOptions != null ? new ServerTlsContext(tlsContextOptions) : null; + listener = new ServerListener(hostname, (short) port, socketOptions, tlsContext, serverBootstrap, new ServerListenerHandler() { + @Override + public ServerConnectionHandler onNewConnection(ServerConnection serverConnection, int errorCode) { + try { + LOGGER.info("New connection code [" + CRT.awsErrorName(errorCode) + "] for " + serverConnection.getResourceLogDescription()); + final ServiceOperationMappingContinuationHandler operationHandler = + new ServiceOperationMappingContinuationHandler(serverConnection, eventStreamRPCServiceHandler); + return operationHandler; + } catch (Throwable e) { + LOGGER.error("Throwable caught in new connection: " + e.getMessage(), e); + return null; + } + } + + @Override + public void onConnectionShutdown(ServerConnection serverConnection, int errorCode) { + LOGGER.info("Server connection closed code [" + CRT.awsErrorString(errorCode) + "]: " + serverConnection.getResourceLogDescription()); + } + }); + LOGGER.info("IpcServer started..."); + } + + /** + * Stops running server and allows the caller to wait on a CompletableFuture + */ + public CompletableFuture stopServer() { + if (serverRunning.compareAndSet(true, false)) { + try { + if (listener != null) { + listener.close(); + return listener.getShutdownCompleteFuture(); + } + return CompletableFuture.completedFuture(null); + } finally { + listener = null; + try { + if (tlsContext != null) { + tlsContext.close(); + } + } finally { + if(serverBootstrap != null) { + serverBootstrap.close(); + } + } + tlsContext = null; + serverBootstrap = null; + } + } + return CompletableFuture.completedFuture(null); + } + + /** + * Ensures a call to stop server is called when it is closed + */ + @Override + public void close() { + stopServer(); + } + + /** + * Constructor supplied EventStreamRPCServiceHandler self validates that all expected operations + * have been wired (hand written -> dependency injected perhaps) before launching the service. + * + * Also verifies that auth handlers have been set + */ + private void validateServiceHandler() { + if (eventStreamRPCServiceHandler.getAuthenticationHandler() == null) { + throw new InvalidServiceConfigurationException(String.format("%s authentication handler is not set!", + eventStreamRPCServiceHandler.getServiceName())); + } + if (eventStreamRPCServiceHandler.getAuthorizationHandler() == null) { + throw new InvalidServiceConfigurationException(String.format("%s authorization handler is not set!", + eventStreamRPCServiceHandler.getServiceName())); + } + + final EventStreamRPCServiceModel serviceModel = eventStreamRPCServiceHandler.getServiceModel(); + + if (serviceModel == null) { + throw new InvalidServiceConfigurationException("Handler must not have a null service model"); + } + + if (serviceModel.getServiceName() == null || serviceModel.getServiceName().isEmpty()) { + throw new InvalidServiceConfigurationException("Service model's name is null!"); + } + + final Set unsetOperations = serviceModel.getAllOperations().stream().filter(operationName -> { + return serviceModel.getOperationModelContext(operationName) == null; + }).collect(Collectors.toSet()); + if (!unsetOperations.isEmpty()) { + throw new InvalidServiceConfigurationException(String.format("Service has the following unset operations {%s}", + unsetOperations.stream().collect(Collectors.joining(", ")))); + } + + //validates all handlers are set + eventStreamRPCServiceHandler.validateAllOperationsSet(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/ServiceOperationMappingContinuationHandler.java b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/ServiceOperationMappingContinuationHandler.java index 7cf602ef6..3bdb245b6 100644 --- a/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/ServiceOperationMappingContinuationHandler.java +++ b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/ServiceOperationMappingContinuationHandler.java @@ -1,18 +1,26 @@ package software.amazon.awssdk.eventstreamrpc; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.crt.eventstream.Header; +import software.amazon.awssdk.crt.eventstream.HeaderType; +import software.amazon.awssdk.crt.eventstream.MessageFlags; +import software.amazon.awssdk.crt.eventstream.MessageType; +import software.amazon.awssdk.crt.eventstream.ServerConnection; +import software.amazon.awssdk.crt.eventstream.ServerConnectionContinuation; +import software.amazon.awssdk.crt.eventstream.ServerConnectionContinuationHandler; +import software.amazon.awssdk.crt.eventstream.ServerConnectionHandler; + import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Function; -import java.util.logging.Logger; import java.util.stream.Collectors; -import software.amazon.awssdk.crt.eventstream.*; - public class ServiceOperationMappingContinuationHandler extends ServerConnectionHandler { - private static final Logger LOGGER = Logger.getLogger(ServiceOperationMappingContinuationHandler.class.getName()); + private static final Logger LOGGER = LoggerFactory.getLogger(ServiceOperationMappingContinuationHandler.class); private final EventStreamRPCServiceHandler serviceHandler; private AuthenticationData authenticationData; //should only be set once after AuthN @@ -69,13 +77,15 @@ protected void onConnectRequest(List
headers, byte[] payload) { Version.fromString(versionHeader.get()).equals(Version.getInstance())) { //version matches if (authentication == null) { - throw new IllegalStateException(String.format("%s has null authentication handler!")); + throw new IllegalStateException( + String.format("%s has null authentication handler!", serviceHandler.getServiceName())); } if (authorization == null) { - throw new IllegalStateException(String.format("%s has null authorization handler!")); + throw new IllegalStateException( + String.format("%s has null authorization handler!", serviceHandler.getServiceName())); } - LOGGER.finer(String.format("%s running authentication handler", serviceHandler.getServiceName())); + LOGGER.trace(String.format("%s running authentication handler", serviceHandler.getServiceName())); authenticationData = authentication.apply(headers, payload); if (authenticationData == null) { throw new IllegalStateException(String.format("%s authentication handler returned null", serviceHandler.getServiceName())); @@ -96,12 +106,12 @@ protected void onConnectRequest(List
headers, byte[] payload) { throw new RuntimeException("Unknown authorization decision for " + authenticationData.getIdentityLabel()); } } else { //version mismatch - LOGGER.warning(String.format("Client version {%s} mismatches server version {%s}", + LOGGER.warn(String.format("Client version {%s} mismatches server version {%s}", versionHeader.isPresent() ? versionHeader.get() : "null", Version.getInstance().getVersionString())); } } catch (Exception e) { - LOGGER.severe(String.format("%s occurred while attempting to authN/authZ connect: %s", e.getClass(), e.getMessage())); + LOGGER.error(String.format("%s occurred while attempting to authN/authZ connect: %s", e.getClass(), e.getMessage()), e); } finally { final String authLabel = authenticationData != null ? authenticationData.getIdentityLabel() : "null"; LOGGER.info("Sending connect response for " + authLabel); diff --git a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/EchoTestRPCService.java b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/EchoTestRPCService.java new file mode 100644 index 000000000..f89e981ce --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/EchoTestRPCService.java @@ -0,0 +1,102 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.crt.eventstream.ServerConnectionContinuationHandler; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCServiceHandler; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCServiceModel; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandlerContext; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +public final class EchoTestRPCService extends EventStreamRPCServiceHandler { + public static final String SERVICE_NAMESPACE = "awstest"; + + protected static final Set SERVICE_OPERATION_SET; + + public static final String GET_ALL_PRODUCTS = SERVICE_NAMESPACE + "#GetAllProducts"; + + public static final String CAUSE_SERVICE_ERROR = SERVICE_NAMESPACE + "#CauseServiceError"; + + public static final String CAUSE_STREAM_SERVICE_TO_ERROR = SERVICE_NAMESPACE + "#CauseStreamServiceToError"; + + public static final String ECHO_STREAM_MESSAGES = SERVICE_NAMESPACE + "#EchoStreamMessages"; + + public static final String ECHO_MESSAGE = SERVICE_NAMESPACE + "#EchoMessage"; + + public static final String GET_ALL_CUSTOMERS = SERVICE_NAMESPACE + "#GetAllCustomers"; + + static { + SERVICE_OPERATION_SET = new HashSet<>(); + SERVICE_OPERATION_SET.add(GET_ALL_PRODUCTS); + SERVICE_OPERATION_SET.add(CAUSE_SERVICE_ERROR); + SERVICE_OPERATION_SET.add(CAUSE_STREAM_SERVICE_TO_ERROR); + SERVICE_OPERATION_SET.add(ECHO_STREAM_MESSAGES); + SERVICE_OPERATION_SET.add(ECHO_MESSAGE); + SERVICE_OPERATION_SET.add(GET_ALL_CUSTOMERS); + } + + private final Map> operationSupplierMap; + + public EchoTestRPCService() { + this.operationSupplierMap = new HashMap<>(); + } + + @Override + public EventStreamRPCServiceModel getServiceModel() { + return EchoTestRPCServiceModel.getInstance(); + } + + public void setGetAllProductsHandler( + Function handler) { + operationSupplierMap.put(GET_ALL_PRODUCTS, handler); + } + + public void setCauseServiceErrorHandler( + Function handler) { + operationSupplierMap.put(CAUSE_SERVICE_ERROR, handler); + } + + public void setCauseStreamServiceToErrorHandler( + Function handler) { + operationSupplierMap.put(CAUSE_STREAM_SERVICE_TO_ERROR, handler); + } + + public void setEchoStreamMessagesHandler( + Function handler) { + operationSupplierMap.put(ECHO_STREAM_MESSAGES, handler); + } + + public void setEchoMessageHandler( + Function handler) { + operationSupplierMap.put(ECHO_MESSAGE, handler); + } + + public void setGetAllCustomersHandler( + Function handler) { + operationSupplierMap.put(GET_ALL_CUSTOMERS, handler); + } + + @Override + public Set getAllOperations() { + return SERVICE_OPERATION_SET; + } + + @Override + public boolean hasHandlerForOperation(String operation) { + return operationSupplierMap.containsKey(operation); + } + + @Override + public Function getOperationHandler( + String operation) { + return operationSupplierMap.get(operation); + } + + public void setOperationHandler(String operation, + Function handler) { + operationSupplierMap.put(operation, handler); + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractCauseServiceErrorOperationHandler.java b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractCauseServiceErrorOperationHandler.java new file mode 100644 index 000000000..bc4c322d9 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractCauseServiceErrorOperationHandler.java @@ -0,0 +1,23 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.EchoTestRPCServiceModel; +import software.amazon.awssdk.awstest.model.CauseServiceErrorRequest; +import software.amazon.awssdk.awstest.model.CauseServiceErrorResponse; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandler; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandlerContext; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public abstract class GeneratedAbstractCauseServiceErrorOperationHandler extends + OperationContinuationHandler { + protected GeneratedAbstractCauseServiceErrorOperationHandler( + OperationContinuationHandlerContext context) { + super(context); + } + + @Override + public OperationModelContext getOperationModelContext( + ) { + return EchoTestRPCServiceModel.getCauseServiceErrorModelContext(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractCauseStreamServiceToErrorOperationHandler.java b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractCauseStreamServiceToErrorOperationHandler.java new file mode 100644 index 000000000..3b725b5de --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractCauseStreamServiceToErrorOperationHandler.java @@ -0,0 +1,23 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.EchoTestRPCServiceModel; +import software.amazon.awssdk.awstest.model.EchoStreamingMessage; +import software.amazon.awssdk.awstest.model.EchoStreamingRequest; +import software.amazon.awssdk.awstest.model.EchoStreamingResponse; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandler; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandlerContext; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; + +public abstract class GeneratedAbstractCauseStreamServiceToErrorOperationHandler extends + OperationContinuationHandler { + protected GeneratedAbstractCauseStreamServiceToErrorOperationHandler( + OperationContinuationHandlerContext context) { + super(context); + } + + @Override + public OperationModelContext getOperationModelContext( + ) { + return EchoTestRPCServiceModel.getCauseStreamServiceToErrorModelContext(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractEchoMessageOperationHandler.java b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractEchoMessageOperationHandler.java new file mode 100644 index 000000000..93a1f217d --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractEchoMessageOperationHandler.java @@ -0,0 +1,23 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.EchoTestRPCServiceModel; +import software.amazon.awssdk.awstest.model.EchoMessageRequest; +import software.amazon.awssdk.awstest.model.EchoMessageResponse; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandler; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandlerContext; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public abstract class GeneratedAbstractEchoMessageOperationHandler extends + OperationContinuationHandler { + protected GeneratedAbstractEchoMessageOperationHandler( + OperationContinuationHandlerContext context) { + super(context); + } + + @Override + public OperationModelContext getOperationModelContext( + ) { + return EchoTestRPCServiceModel.getEchoMessageModelContext(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractEchoStreamMessagesOperationHandler.java b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractEchoStreamMessagesOperationHandler.java new file mode 100644 index 000000000..c1331becf --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractEchoStreamMessagesOperationHandler.java @@ -0,0 +1,23 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.EchoTestRPCServiceModel; +import software.amazon.awssdk.awstest.model.EchoStreamingMessage; +import software.amazon.awssdk.awstest.model.EchoStreamingRequest; +import software.amazon.awssdk.awstest.model.EchoStreamingResponse; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandler; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandlerContext; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; + +public abstract class GeneratedAbstractEchoStreamMessagesOperationHandler extends + OperationContinuationHandler { + protected GeneratedAbstractEchoStreamMessagesOperationHandler( + OperationContinuationHandlerContext context) { + super(context); + } + + @Override + public OperationModelContext getOperationModelContext( + ) { + return EchoTestRPCServiceModel.getEchoStreamMessagesModelContext(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractGetAllCustomersOperationHandler.java b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractGetAllCustomersOperationHandler.java new file mode 100644 index 000000000..ec72635d2 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractGetAllCustomersOperationHandler.java @@ -0,0 +1,23 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.EchoTestRPCServiceModel; +import software.amazon.awssdk.awstest.model.GetAllCustomersRequest; +import software.amazon.awssdk.awstest.model.GetAllCustomersResponse; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandler; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandlerContext; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public abstract class GeneratedAbstractGetAllCustomersOperationHandler extends + OperationContinuationHandler { + protected GeneratedAbstractGetAllCustomersOperationHandler( + OperationContinuationHandlerContext context) { + super(context); + } + + @Override + public OperationModelContext getOperationModelContext( + ) { + return EchoTestRPCServiceModel.getGetAllCustomersModelContext(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractGetAllProductsOperationHandler.java b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractGetAllProductsOperationHandler.java new file mode 100644 index 000000000..0750b6798 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/awstest/GeneratedAbstractGetAllProductsOperationHandler.java @@ -0,0 +1,23 @@ +package software.amazon.awssdk.awstest; + +import software.amazon.awssdk.awstest.EchoTestRPCServiceModel; +import software.amazon.awssdk.awstest.model.GetAllProductsRequest; +import software.amazon.awssdk.awstest.model.GetAllProductsResponse; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandler; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandlerContext; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public abstract class GeneratedAbstractGetAllProductsOperationHandler extends + OperationContinuationHandler { + protected GeneratedAbstractGetAllProductsOperationHandler( + OperationContinuationHandlerContext context) { + super(context); + } + + @Override + public OperationModelContext getOperationModelContext( + ) { + return EchoTestRPCServiceModel.getGetAllProductsModelContext(); + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/EchoTestServiceTests.java b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/EchoTestServiceTests.java index 9dae94d6d..9b970bb6d 100644 --- a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/EchoTestServiceTests.java +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/EchoTestServiceTests.java @@ -2,22 +2,44 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import software.amazon.awssdk.awstest.*; -import software.amazon.awssdk.awstest.model.*; +import software.amazon.awssdk.awstest.CauseServiceErrorResponseHandler; +import software.amazon.awssdk.awstest.CauseStreamServiceToErrorResponseHandler; +import software.amazon.awssdk.awstest.EchoMessageResponseHandler; +import software.amazon.awssdk.awstest.EchoStreamMessagesResponseHandler; +import software.amazon.awssdk.awstest.EchoTestRPC; +import software.amazon.awssdk.awstest.EchoTestRPCServiceModel; +import software.amazon.awssdk.awstest.model.CauseServiceErrorRequest; +import software.amazon.awssdk.awstest.model.EchoMessageRequest; +import software.amazon.awssdk.awstest.model.EchoMessageResponse; +import software.amazon.awssdk.awstest.model.EchoStreamingMessage; +import software.amazon.awssdk.awstest.model.EchoStreamingRequest; +import software.amazon.awssdk.awstest.model.FruitEnum; +import software.amazon.awssdk.awstest.model.MessageData; +import software.amazon.awssdk.awstest.model.Pair; +import software.amazon.awssdk.awstest.model.ServiceError; +import software.amazon.awssdk.crt.CRT; +import software.amazon.awssdk.crt.CrtResource; import software.amazon.awssdk.crt.Log; import software.amazon.awssdk.eventstreamrpc.echotest.EchoTestServiceRunner; import software.amazon.awssdk.eventstreamrpc.model.EventStreamOperationError; import java.time.Instant; import java.util.ArrayList; +import java.util.Collection; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.BiConsumer; +import java.util.stream.Collectors; public class EchoTestServiceTests { static { @@ -30,8 +52,8 @@ public class EchoTestServiceTests { final EchoMessageResponseHandler responseHandler = client.echoMessage(request, Optional.empty()); EchoMessageResponse response = null; try { - response = responseHandler.getResponse().get(); - } catch (InterruptedException | ExecutionException e) { + response = responseHandler.getResponse().get(10, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { Assertions.fail(e); } Assertions.assertEquals(request.getMessage(), response.getMessage(), "Data echoed back not equivalent!"); @@ -56,7 +78,7 @@ public void testInvokeEchoMessage() throws Exception { data.setBlobMessage(new byte[] {23, 42, -120, -3, 53}); DO_ECHO_FN.accept(client, data); - data.setTimeMessage(Instant.now()); + data.setTimeMessage(Instant.ofEpochSecond(1606173648)); DO_ECHO_FN.accept(client, data); }); @@ -68,29 +90,117 @@ public void testInvokeEchoMessage() throws Exception { //throw this because it means the client did have a problem Assertions.fail(e.getCause()); } + + CrtResource.waitForNoResources(); } - //@Test //this test takes too long to complete so turn it off by default + @Test //this test takes too long to complete so turn it off by default public void testLongRunningServerOperations() throws Exception { - EchoTestServiceRunner.runLocalEchoTestServerClientLoop((connection, client) -> { - //note the successive calls are actually growing the same original message - //rather than replacing any single field set. Instead of using lambdas, we could - //use a parameterized test, but this has the benefit of proving successive calls work cleanly + final int numIterations = Integer.parseInt(System.getProperty("numIterations", "10")); + final int threadPoolSize = Integer.parseInt(System.getProperty("threadPoolSize", "16")); //max threads, since tasks are IO intense, doesn't need to be large + final int parallelTaskMultiplyFactor = Integer.parseInt(System.getProperty("parallelTaskFactor", "10")); //however many tasks to run in parallel + final int taskLengthMultiplyFactor = Integer.parseInt(System.getProperty("taskLengthFactor", "10")); //whatever work each task does (very small), do it this many times within a single run with a short sleep in between + final long taskRepeatSleepDelayMs = 10; //time to sleep before repeating a tasks' impl + + final ArrayList> tasks = new ArrayList<>(); + final ExecutorService service = Executors.newFixedThreadPool(threadPoolSize); + + tasks.add((connection, client) -> { final MessageData data = new MessageData(); data.setEnumMessage(FruitEnum.PINEAPPLE); DO_ECHO_FN.accept(client, data); + data.setStringMessage("Hello EventStream RPC world"); + DO_ECHO_FN.accept(client, data); + + data.setBooleanMessage(true); + DO_ECHO_FN.accept(client, data); + + data.setBlobMessage(new byte[] {23, 42, -120, -3, 53}); + DO_ECHO_FN.accept(client, data); + + data.setTimeMessage(Instant.ofEpochSecond(1606173648)); + DO_ECHO_FN.accept(client, data); + }); + + tasks.add((connection, client) -> { + final CauseServiceErrorResponseHandler responseHandler = client.causeServiceError(new CauseServiceErrorRequest(), Optional.empty()); + futureCausesOperationError(responseHandler.getResponse(), ServiceError.class, "ServiceError"); + }); + tasks.add((connection, client) -> { + final CompletableFuture exceptionReceivedFuture = new CompletableFuture<>(); + final CauseStreamServiceToErrorResponseHandler streamErrorResponseHandler = client.causeStreamServiceToError(EchoStreamingRequest.VOID, Optional.of(new StreamResponseHandler() { + @Override + public void onStreamEvent(EchoStreamingMessage streamEvent) { + exceptionReceivedFuture.completeExceptionally(new RuntimeException("Stream event received when expecting error!")); + } + + @Override + public boolean onStreamError(Throwable error) { + //this is normal, but we are looking for a specific one + exceptionReceivedFuture.complete(error); + return true; + } + + @Override + public void onStreamClosed() { + if (!exceptionReceivedFuture.isDone()) { + exceptionReceivedFuture.completeExceptionally(new RuntimeException("Stream closed before exception thrown!")); + } + } + })); + try { - Thread.sleep(50); //sleep just so we're not completely pummeling the server? - } catch (InterruptedException e) { - e.printStackTrace(); + final EchoStreamingMessage msg = new EchoStreamingMessage(); + final MessageData data = new MessageData(); + data.setStringMessage("basicStringMessage"); + msg.setStreamMessage(data); + streamErrorResponseHandler.sendStreamEvent(msg); //sends message, exception should be is the response + final Throwable t = exceptionReceivedFuture.get(10, TimeUnit.SECONDS); + Assertions.assertTrue(t instanceof ServiceError); + final ServiceError error = (ServiceError)t; + Assertions.assertEquals("ServiceError", error.getErrorCode()); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Assertions.fail(e); + } + }); + int count[] = { 0 }; + EchoTestServiceRunner.runLocalEchoTestServerClientLoopUnixDomain( + CRT.getOSIdentifier().equals("windows") ? "\\\\.\\pipe\\TestP-" + UUID.randomUUID() : "/tmp/ipc.sock", + (connection, client) -> { + final Collection> taskFutures = new LinkedList<>(); + + for (int i = 0; i < parallelTaskMultiplyFactor; ++i) { //multiply the tasks evenly + taskFutures.addAll(tasks.stream() + .map(task -> service.submit(()-> { + for (int taskExecIndx = 0; taskExecIndx < taskLengthMultiplyFactor; ++taskExecIndx) { + task.accept(connection, client); + try { + Thread.sleep(taskRepeatSleepDelayMs); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + System.out.println("Task repeat..."); + } + })) + .collect(Collectors.toList())); } - }, 1 << 17); + + taskFutures.forEach(task -> { + try { + task.get(10, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Assertions.fail(e); + } + }); + System.out.println("ALL TASKS finished an ITERATION: " + ++count[0]); + }, numIterations); + CrtResource.waitForNoResources(); } public void futureCausesOperationError(final CompletableFuture future, Class clazz, String code) { try { - future.get(); + future.get(60, TimeUnit.SECONDS); } catch (ExecutionException e) { final Throwable t = e.getCause(); if (t == null) { @@ -101,8 +211,8 @@ public void futureCausesOperationError(final CompletableFuture future, Class< final EventStreamOperationError error = (EventStreamOperationError)t; Assertions.assertEquals(code, error.getErrorCode(), "Non-matching error code returned"); } - } catch (InterruptedException e) { - Assertions.fail(e.getCause()); + } catch (InterruptedException | TimeoutException e) { + throw new RuntimeException(e); } } @@ -116,6 +226,14 @@ public void testInvokeErrorOperation() throws Exception { final MessageData data = new MessageData(); data.setStringMessage("Post error string message"); DO_ECHO_FN.accept(client, data); + + final CauseServiceErrorResponseHandler responseHandler2 = client.causeServiceError(new CauseServiceErrorRequest(), Optional.empty()); + futureCausesOperationError(responseHandler2.getResponse(), ServiceError.class, "ServiceError"); + final CauseServiceErrorResponseHandler responseHandler3 = client.causeServiceError(new CauseServiceErrorRequest(), Optional.empty()); + futureCausesOperationError(responseHandler3.getResponse(), ServiceError.class, "ServiceError"); + + //call non error again + DO_ECHO_FN.accept(client, data); }); try { clientErrorAfter.get(1, TimeUnit.SECONDS); @@ -125,6 +243,117 @@ public void testInvokeErrorOperation() throws Exception { //throw this because it means the client did have a problem Assertions.fail(e.getCause()); } + CrtResource.waitForNoResources(); + } + + @Test + public void testInvokeEchoStreamMessagesClientClose() throws Exception { + final CompletableFuture clientErrorAfter = EchoTestServiceRunner.runLocalEchoTestServer((connection, client) -> { + final EchoStreamingRequest req = EchoStreamingRequest.VOID; + + final List messagesToSend = new ArrayList<>(); + + final EchoStreamingMessage msg1 = new EchoStreamingMessage(); + final MessageData data1 = new MessageData(); + data1.setStringMessage("fooStreamingMessage"); + msg1.setStreamMessage(data1); + messagesToSend.add(msg1); + + final EchoStreamingMessage msg2 = new EchoStreamingMessage(); + final MessageData data2 = new MessageData(); + data2.setEnumMessage(FruitEnum.ORANGE); + msg2.setStreamMessage(data2); + messagesToSend.add(msg2); + + final EchoStreamingMessage msg3 = new EchoStreamingMessage(); + final MessageData data3 = new MessageData(); + data3.setTimeMessage(Instant.ofEpochSecond(1606173648)); + msg3.setStreamMessage(data3); + messagesToSend.add(msg3); + + final EchoStreamingMessage msg4 = new EchoStreamingMessage(); + final MessageData data4 = new MessageData(); + final List listOfStrings = new ArrayList<>(3); + listOfStrings.add("item1"); + listOfStrings.add("item2"); + listOfStrings.add("item3"); + data4.setStringListMessage(listOfStrings); + msg4.setStreamMessage(data4); + messagesToSend.add(msg4); + + final EchoStreamingMessage msg5 = new EchoStreamingMessage(); + final Pair kvPair = new Pair(); + kvPair.setKey("keyTest"); + kvPair.setValue("testValue"); + msg5.setKeyValuePair(kvPair); + + final CompletableFuture finishedStreamingEvents = new CompletableFuture<>(); + final CompletableFuture streamClosedFuture = new CompletableFuture<>(); + final Iterator sentIterator = messagesToSend.iterator(); + final int numEventsVerified[] = new int[] { 0 }; + final EchoStreamMessagesResponseHandler responseHandler = client.echoStreamMessages(req, Optional.of(new StreamResponseHandler() { + @Override + public void onStreamEvent(EchoStreamingMessage streamEvent) { + if (sentIterator.hasNext()) { + final EchoStreamingMessage expectedMsg = sentIterator.next(); + if (!expectedMsg.equals(streamEvent)) { + finishedStreamingEvents.completeExceptionally(new RuntimeException("Steam message echo'd is not the same as sent!")); + } else { + numEventsVerified[0]++; + if (numEventsVerified[0] == messagesToSend.size()) { + finishedStreamingEvents.complete(null); + } + } + } + else { + finishedStreamingEvents.completeExceptionally(new RuntimeException("Service returned an extra unexpected message back over stream: " + + EchoTestRPCServiceModel.getInstance().toJsonString(streamEvent))); + } + } + + @Override + public boolean onStreamError(Throwable error) { + finishedStreamingEvents.completeExceptionally( + new RuntimeException("Service threw an error while waiting for stream events!", error)); + streamClosedFuture.completeExceptionally(new RuntimeException("Service threw an error while waiting for stream events!", error)); + return true; + } + + @Override + public void onStreamClosed() { + streamClosedFuture.complete(null); + } + })); + messagesToSend.stream().forEachOrdered(event -> { + responseHandler.sendStreamEvent(event); //no need to slow down? + }); + + try { + finishedStreamingEvents.get(5, TimeUnit.SECONDS); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + Assertions.fail(e); + } + + try { + responseHandler.closeStream().get(5, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); + } + + //after a streaming operation client closes, perform another operation on the same connection + final MessageData data = new MessageData(); + data.setEnumMessage(FruitEnum.PINEAPPLE); + DO_ECHO_FN.accept(client, data); + }); + try { + clientErrorAfter.get(1, TimeUnit.SECONDS); + } catch (TimeoutException e) { + //eat this because it means there was no exception which is good + } catch (ExecutionException e) { + //throw this because it means the client did have a problem + Assertions.fail(e.getCause()); + } + CrtResource.waitForNoResources(); } @Test @@ -148,7 +377,7 @@ public void testInvokeEchoStreamMessages() throws Exception { final EchoStreamingMessage msg3 = new EchoStreamingMessage(); final MessageData data3 = new MessageData(); - data3.setTimeMessage(Instant.now()); + data3.setTimeMessage(Instant.ofEpochSecond(1606173648)); msg3.setStreamMessage(data3); messagesToSend.add(msg3); @@ -240,12 +469,12 @@ public void onStreamClosed() { //throw this because it means the client did have a problem Assertions.fail(e.getCause()); } + CrtResource.waitForNoResources(); } @Test public void testInvokeEchoStreamError() throws Exception { final CompletableFuture clientErrorAfter = EchoTestServiceRunner.runLocalEchoTestServer((connection, client) -> { - final CompletableFuture exceptionReceivedFuture = new CompletableFuture<>(); final CauseStreamServiceToErrorResponseHandler streamErrorResponseHandler = client.causeStreamServiceToError(EchoStreamingRequest.VOID, Optional.of(new StreamResponseHandler() { @Override @@ -274,13 +503,11 @@ public void onStreamClosed() { data.setStringMessage("basicStringMessage"); msg.setStreamMessage(data); streamErrorResponseHandler.sendStreamEvent(msg); //sends message, exception should be is the response - final Throwable t = exceptionReceivedFuture.get(); + final Throwable t = exceptionReceivedFuture.get(60, TimeUnit.SECONDS); Assertions.assertTrue(t instanceof ServiceError); final ServiceError error = (ServiceError)t; Assertions.assertEquals("ServiceError", error.getErrorCode()); - } catch (InterruptedException e) { - Assertions.fail(e); - } catch (ExecutionException e) { + } catch (InterruptedException | ExecutionException | TimeoutException e) { Assertions.fail(e); } @@ -298,5 +525,6 @@ public void onStreamClosed() { //throw this because it means the client did have a problem Assertions.fail(e.getCause()); } + CrtResource.waitForNoResources(); } } diff --git a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/IpcServerTests.java b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/IpcServerTests.java index 5b7932962..ae340397d 100644 --- a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/IpcServerTests.java +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/IpcServerTests.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import software.amazon.awssdk.crt.CrtResource; import software.amazon.awssdk.crt.Log; import software.amazon.awssdk.crt.eventstream.ServerConnectionContinuationHandler; import software.amazon.awssdk.crt.io.EventLoopGroup; @@ -13,7 +14,11 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; -import java.util.*; +import java.util.Collection; +import java.util.HashSet; +import java.util.Optional; +import java.util.Random; +import java.util.Set; import java.util.function.Function; /** @@ -29,12 +34,8 @@ public static int randomPort() { } @Test - public void testStartStopIpcServer() { + public void testStartStopIpcServer() throws InterruptedException { final int port = randomPort(); - SocketOptions socketOptions = new SocketOptions(); - socketOptions.connectTimeoutMs = 3000; - socketOptions.domain = SocketOptions.SocketDomain.IPv4; - socketOptions.type = SocketOptions.SocketType.STREAM; final EventStreamRPCServiceHandler handler = new EventStreamRPCServiceHandler() { @Override @@ -74,27 +75,32 @@ public OperationModelContext getOperationModelContext(String operationName) { handler.setAuthenticationHandler(TestAuthNZHandlers.getAuthNHandler()); handler.setAuthorizationHandler(TestAuthNZHandlers.getAuthZHandler()); - try(final EventLoopGroup elGroup = new EventLoopGroup(1)) { - final IpcServer ipcServer = new IpcServer(elGroup, socketOptions, null, "127.0.0.1", port, handler); - ipcServer.runServer(); + try(final EventLoopGroup elGroup = new EventLoopGroup(1); + SocketOptions socketOptions = new SocketOptions()) { + socketOptions.connectTimeoutMs = 3000; + socketOptions.domain = SocketOptions.SocketDomain.IPv4; + socketOptions.type = SocketOptions.SocketType.STREAM; + + try (final IpcServer ipcServer = new IpcServer(elGroup, socketOptions, null, "127.0.0.1", port, handler)) { + ipcServer.runServer(); - Socket clientSocket = new Socket(); - SocketAddress address = new InetSocketAddress("127.0.0.1", port); - clientSocket.connect(address, 3000); - //no real assertion to be made here as long as the above connection works... - clientSocket.close(); + Socket clientSocket = new Socket(); + SocketAddress address = new InetSocketAddress("127.0.0.1", port); + clientSocket.connect(address, 3000); + //no real assertion to be made here as long as the above connection works... + clientSocket.close(); + Thread.sleep(1_000); + } } catch (IOException e) { throw new RuntimeException(e); } + + CrtResource.waitForNoResources(); } @Test public void testIpcServerDoubleStartFailure() { final int port = randomPort(); - SocketOptions socketOptions = new SocketOptions(); - socketOptions.connectTimeoutMs = 3000; - socketOptions.domain = SocketOptions.SocketDomain.IPv4; - socketOptions.type = SocketOptions.SocketType.STREAM; final EventStreamRPCServiceHandler handler = new EventStreamRPCServiceHandler() { @Override @@ -125,22 +131,24 @@ protected EventStreamRPCServiceModel getServiceModel() { handler.setAuthenticationHandler(TestAuthNZHandlers.getAuthNHandler()); handler.setAuthorizationHandler(TestAuthNZHandlers.getAuthZHandler()); - try (final EventLoopGroup elGroup = new EventLoopGroup(1)) { - final IpcServer ipcServer = new IpcServer(elGroup, socketOptions, null, "127.0.0.1", port, handler); - ipcServer.runServer(); - Assertions.assertThrows(IllegalStateException.class, () -> { + try (final EventLoopGroup elGroup = new EventLoopGroup(1); + SocketOptions socketOptions = new SocketOptions()) { + socketOptions.connectTimeoutMs = 3000; + socketOptions.domain = SocketOptions.SocketDomain.IPv4; + socketOptions.type = SocketOptions.SocketType.STREAM; + try(final IpcServer ipcServer = new IpcServer(elGroup, socketOptions, null, "127.0.0.1", port, handler)) { ipcServer.runServer(); - }); + Assertions.assertThrows(IllegalStateException.class, () -> { + ipcServer.runServer(); + }); + } } + CrtResource.waitForNoResources(); } @Test public void testIpcServerModelNotSet() { final int port = randomPort(); - SocketOptions socketOptions = new SocketOptions(); - socketOptions.connectTimeoutMs = 3000; - socketOptions.domain = SocketOptions.SocketDomain.IPv4; - socketOptions.type = SocketOptions.SocketType.STREAM; final EventStreamRPCServiceHandler handler = new EventStreamRPCServiceHandler() { @Override @@ -159,21 +167,23 @@ protected EventStreamRPCServiceModel getServiceModel() { handler.setAuthenticationHandler(TestAuthNZHandlers.getAuthNHandler()); handler.setAuthorizationHandler(TestAuthNZHandlers.getAuthZHandler()); - try (final EventLoopGroup elGroup = new EventLoopGroup(1)) { - final IpcServer ipcServer = new IpcServer(elGroup, socketOptions, null, "127.0.0.1", port, handler); - Assertions.assertThrows(InvalidServiceConfigurationException.class, () -> { - ipcServer.runServer(); - }); + try (final EventLoopGroup elGroup = new EventLoopGroup(1); + SocketOptions socketOptions = new SocketOptions()) { + socketOptions.connectTimeoutMs = 3000; + socketOptions.domain = SocketOptions.SocketDomain.IPv4; + socketOptions.type = SocketOptions.SocketType.STREAM; + try (final IpcServer ipcServer = new IpcServer(elGroup, socketOptions, null, "127.0.0.1", port, handler)) { + Assertions.assertThrows(InvalidServiceConfigurationException.class, () -> { + ipcServer.runServer(); + }); + } } + CrtResource.waitForNoResources(); } @Test public void testIpcServerOperationNotSet() { final int port = randomPort(); - SocketOptions socketOptions = new SocketOptions(); - socketOptions.connectTimeoutMs = 3000; - socketOptions.domain = SocketOptions.SocketDomain.IPv4; - socketOptions.type = SocketOptions.SocketType.STREAM; final Set OPERATION_SET = new HashSet<>(); OPERATION_SET.add("dummyOperationName"); @@ -206,21 +216,23 @@ protected EventStreamRPCServiceModel getServiceModel() { handler.setAuthenticationHandler(TestAuthNZHandlers.getAuthNHandler()); handler.setAuthorizationHandler(TestAuthNZHandlers.getAuthZHandler()); - try (final EventLoopGroup elGroup = new EventLoopGroup(1)) { - final IpcServer ipcServer = new IpcServer(elGroup, socketOptions, null, "127.0.0.1", port, handler); - Assertions.assertThrows(InvalidServiceConfigurationException.class, () -> { - ipcServer.runServer(); - }); + try (final EventLoopGroup elGroup = new EventLoopGroup(1); + SocketOptions socketOptions = new SocketOptions()) { + socketOptions.connectTimeoutMs = 3000; + socketOptions.domain = SocketOptions.SocketDomain.IPv4; + socketOptions.type = SocketOptions.SocketType.STREAM; + try (final IpcServer ipcServer = new IpcServer(elGroup, socketOptions, null, "127.0.0.1", port, handler)) { + Assertions.assertThrows(InvalidServiceConfigurationException.class, () -> { + ipcServer.runServer(); + }); + } } + CrtResource.waitForNoResources(); } @Test public void testIpcServerAuthNUnset() { final int port = randomPort(); - SocketOptions socketOptions = new SocketOptions(); - socketOptions.connectTimeoutMs = 3000; - socketOptions.domain = SocketOptions.SocketDomain.IPv4; - socketOptions.type = SocketOptions.SocketType.STREAM; final Set OPERATION_SET = new HashSet<>(); OPERATION_SET.add("dummyOperationName"); @@ -253,21 +265,23 @@ protected EventStreamRPCServiceModel getServiceModel() { //missing handler.setAuthenticationHandler(TestAuthNZHandlers.getAuthNHandler()); handler.setAuthorizationHandler(TestAuthNZHandlers.getAuthZHandler()); - try (final EventLoopGroup elGroup = new EventLoopGroup(1)) { - final IpcServer ipcServer = new IpcServer(elGroup, socketOptions, null, "127.0.0.1", port, handler); - Assertions.assertThrows(InvalidServiceConfigurationException.class, () -> { - ipcServer.runServer(); - }); + try (final EventLoopGroup elGroup = new EventLoopGroup(1); + SocketOptions socketOptions = new SocketOptions()) { + socketOptions.connectTimeoutMs = 3000; + socketOptions.domain = SocketOptions.SocketDomain.IPv4; + socketOptions.type = SocketOptions.SocketType.STREAM; + try (final IpcServer ipcServer = new IpcServer(elGroup, socketOptions, null, "127.0.0.1", port, handler)) { + Assertions.assertThrows(InvalidServiceConfigurationException.class, () -> { + ipcServer.runServer(); + }); + } } + CrtResource.waitForNoResources(); } @Test public void testIpcServerAuthZUnset() { final int port = randomPort(); - SocketOptions socketOptions = new SocketOptions(); - socketOptions.connectTimeoutMs = 3000; - socketOptions.domain = SocketOptions.SocketDomain.IPv4; - socketOptions.type = SocketOptions.SocketType.STREAM; final Set OPERATION_SET = new HashSet<>(); OPERATION_SET.add("dummyOperationName"); @@ -300,11 +314,17 @@ protected EventStreamRPCServiceModel getServiceModel() { handler.setAuthenticationHandler(TestAuthNZHandlers.getAuthNHandler()); //missing: handler.setAuthorizationHandler(TestAuthNZHandlers.getAuthZHandler()); - try (final EventLoopGroup elGroup = new EventLoopGroup(1)) { - final IpcServer ipcServer = new IpcServer(elGroup, socketOptions, null, "127.0.0.1", port, handler); - Assertions.assertThrows(InvalidServiceConfigurationException.class, () -> { - ipcServer.runServer(); - }); + try (final EventLoopGroup elGroup = new EventLoopGroup(1); + SocketOptions socketOptions = new SocketOptions()) { + socketOptions.connectTimeoutMs = 3000; + socketOptions.domain = SocketOptions.SocketDomain.IPv4; + socketOptions.type = SocketOptions.SocketType.STREAM; + try (final IpcServer ipcServer = new IpcServer(elGroup, socketOptions, null, "127.0.0.1", port, handler)) { + Assertions.assertThrows(InvalidServiceConfigurationException.class, () -> { + ipcServer.runServer(); + }); + } } + CrtResource.waitForNoResources(); } } diff --git a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandlerTests.java b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandlerTests.java deleted file mode 100644 index dc4fab20b..000000000 --- a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandlerTests.java +++ /dev/null @@ -1,10 +0,0 @@ -package software.amazon.awssdk.eventstreamrpc; - -import org.junit.jupiter.api.Test; - -public class OperationContinuationHandlerTests { - - @Test - void testSyncOperationInvoke() { - } -} diff --git a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/CauseServiceErrorHandler.java b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/CauseServiceErrorHandler.java new file mode 100644 index 000000000..2c32c9c4c --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/CauseServiceErrorHandler.java @@ -0,0 +1,30 @@ +package software.amazon.awssdk.eventstreamrpc.echotest; + +import software.amazon.awssdk.awstest.GeneratedAbstractCauseServiceErrorOperationHandler; +import software.amazon.awssdk.awstest.model.CauseServiceErrorRequest; +import software.amazon.awssdk.awstest.model.CauseServiceErrorResponse; +import software.amazon.awssdk.awstest.model.ServiceError; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandlerContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public class CauseServiceErrorHandler extends GeneratedAbstractCauseServiceErrorOperationHandler { + protected CauseServiceErrorHandler(OperationContinuationHandlerContext context) { + super(context); + } + + @Override + protected void onStreamClosed() { + //do nothing + } + + @Override + public CauseServiceErrorResponse handleRequest(CauseServiceErrorRequest request) { + throw new ServiceError("Intentionally thrown ServiceError"); + } + + + @Override + public void handleStreamEvent(EventStreamJsonMessage streamRequestEvent) { + throw new RuntimeException("Should not be handling a stream event when handling stream event."); + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/CauseStreamServiceToError.java b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/CauseStreamServiceToError.java new file mode 100644 index 000000000..052d37bf2 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/CauseStreamServiceToError.java @@ -0,0 +1,39 @@ +package software.amazon.awssdk.eventstreamrpc.echotest; + +import software.amazon.awssdk.awstest.GeneratedAbstractCauseStreamServiceToErrorOperationHandler; +import software.amazon.awssdk.awstest.model.EchoStreamingMessage; +import software.amazon.awssdk.awstest.model.EchoStreamingRequest; +import software.amazon.awssdk.awstest.model.EchoStreamingResponse; +import software.amazon.awssdk.awstest.model.ServiceError; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandlerContext; + +public class CauseStreamServiceToError extends GeneratedAbstractCauseStreamServiceToErrorOperationHandler { + protected CauseStreamServiceToError(OperationContinuationHandlerContext context) { + super(context); + } + + @Override + protected void onStreamClosed() { + //do nothing + } + + @Override + public EchoStreamingResponse handleRequest(EchoStreamingRequest request) { + return EchoStreamingResponse.VOID; + } + + /** + * Handle an incoming stream event from the connected client on the operation. + *

+ * If the implementation throws an exception, the framework will respond with the modeled + * exception to the client, if it is modeled. If it is not modeled, it will respond with + * an internal error and log appropriately. Either case, throwing an exception will result + * in closing the stream. To keep the stream open, do not throw + * + * @param streamRequestEvent + */ + @Override + public void handleStreamEvent(EchoStreamingMessage streamRequestEvent) { + throw new ServiceError("Intentionally caused ServiceError on stream"); + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/EchoMessageHandler.java b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/EchoMessageHandler.java new file mode 100644 index 000000000..4a3f27139 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/EchoMessageHandler.java @@ -0,0 +1,31 @@ +package software.amazon.awssdk.eventstreamrpc.echotest; + +import software.amazon.awssdk.awstest.GeneratedAbstractEchoMessageOperationHandler; +import software.amazon.awssdk.awstest.model.EchoMessageRequest; +import software.amazon.awssdk.awstest.model.EchoMessageResponse; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandlerContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public class EchoMessageHandler extends GeneratedAbstractEchoMessageOperationHandler { + protected EchoMessageHandler(OperationContinuationHandlerContext context) { + super(context); + } + + @Override + protected void onStreamClosed() { + //do nothing + } + + @Override + public EchoMessageResponse handleRequest(EchoMessageRequest request) { + final EchoMessageResponse response = new EchoMessageResponse(); + response.setMessage(request.getMessage()); + return response; + } + + @Override + public void handleStreamEvent(EventStreamJsonMessage streamRequestEvent) { + //maybe unsupported operation? + throw new RuntimeException("No stream event should be occurring on this operation"); + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/EchoStreamMessagesHandler.java b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/EchoStreamMessagesHandler.java new file mode 100644 index 000000000..35e212a4f --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/EchoStreamMessagesHandler.java @@ -0,0 +1,39 @@ +package software.amazon.awssdk.eventstreamrpc.echotest; + +import software.amazon.awssdk.awstest.GeneratedAbstractEchoStreamMessagesOperationHandler; +import software.amazon.awssdk.awstest.model.EchoStreamingMessage; +import software.amazon.awssdk.awstest.model.EchoStreamingRequest; +import software.amazon.awssdk.awstest.model.EchoStreamingResponse; +import software.amazon.awssdk.awstest.model.MessageData; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandlerContext; + +/** + * Handler responds to any stream message by sending the data right back. Specialhandling + */ +public class EchoStreamMessagesHandler extends GeneratedAbstractEchoStreamMessagesOperationHandler { + protected EchoStreamMessagesHandler(OperationContinuationHandlerContext context) { + super(context); + } + + @Override + protected void onStreamClosed() { + //do nothing + } + + @Override + public EchoStreamingResponse handleRequest(EchoStreamingRequest request) { + return EchoStreamingResponse.VOID; + } + + @Override + public void handleStreamEvent(EchoStreamingMessage streamRequestEvent) { + sendStreamEvent(streamRequestEvent); + if (streamRequestEvent.getSetUnionMember().equals(EchoStreamingMessage.UnionMember.STREAM_MESSAGE)) { + final MessageData data = streamRequestEvent.getStreamMessage(); + if ("close".equalsIgnoreCase(data.getStringMessage())) { + //follow with a stream close + closeStream(); + } + } + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/EchoTestServiceRunner.java b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/EchoTestServiceRunner.java new file mode 100644 index 000000000..c24d5297f --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/EchoTestServiceRunner.java @@ -0,0 +1,232 @@ +package software.amazon.awssdk.eventstreamrpc.echotest; + +import software.amazon.awssdk.awstest.EchoTestRPC; +import software.amazon.awssdk.awstest.EchoTestRPCClient; +import software.amazon.awssdk.awstest.EchoTestRPCService; +import software.amazon.awssdk.crt.CRT; +import software.amazon.awssdk.crt.CrtResource; +import software.amazon.awssdk.crt.io.ClientBootstrap; +import software.amazon.awssdk.crt.io.EventLoopGroup; +import software.amazon.awssdk.crt.io.HostResolver; +import software.amazon.awssdk.crt.io.SocketOptions; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCConnection; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCConnectionConfig; +import software.amazon.awssdk.eventstreamrpc.RpcServer; +import software.amazon.awssdk.eventstreamrpc.test.TestAuthNZHandlers; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; + +/** + * Helper to runs the echo server for unit tests, or any other sandbox testing + */ +public class EchoTestServiceRunner implements AutoCloseable { + private static final Random RANDOM = new Random(); //default instantiation uses time + public static int randomPort() { + return RANDOM.nextInt(65535-1024) + 1024; + } + + private RpcServer rpcServer; + private final EventLoopGroup elGroup; + private final SocketOptions.SocketDomain domain; + private final String hostname; + private final int port; + + public EchoTestServiceRunner(EventLoopGroup elGroup, String hostname, int port) { + this(elGroup, SocketOptions.SocketDomain.IPv4, hostname, port); + } + + + public EchoTestServiceRunner(EventLoopGroup elGroup, SocketOptions.SocketDomain domain, String hostname, int port) { + this.elGroup = elGroup; + this.hostname = hostname; + this.port = port; + this.domain = domain; + } + + + public void runService() { + try (SocketOptions socketOptions = new SocketOptions()) { + socketOptions.connectTimeoutMs = 3000; + socketOptions.domain = domain; + socketOptions.type = SocketOptions.SocketType.STREAM; + + final EchoTestRPCService service = new EchoTestRPCService(); + //wiring of operation handlers + service.setEchoMessageHandler(EchoMessageHandler::new); + service.setEchoStreamMessagesHandler(EchoStreamMessagesHandler::new); + service.setCauseServiceErrorHandler(CauseServiceErrorHandler::new); + service.setCauseStreamServiceToErrorHandler(CauseStreamServiceToError::new); + service.setGetAllCustomersHandler(GetAllCustomersHandler::new); + service.setGetAllProductsHandler(GetAllProductsHandler::new); + + service.setAuthenticationHandler(TestAuthNZHandlers.getAuthNHandler()); + service.setAuthorizationHandler(TestAuthNZHandlers.getAuthZHandler()); + rpcServer = new RpcServer(elGroup, socketOptions, null, hostname, port, service); + rpcServer.runServer(); + } + } + + @Override + public void close() throws Exception { + if (rpcServer != null) { + rpcServer.close(); + } + } + + /** + * Executes testClientLogic in a runnable. + * * If the connection encountered an error before the clientTestLogic completes this method will throw that error. + * * If the client completes normally first, this will complete normally. + * * If the client throws an exception first, this method will throw that exception + * + * Note: Use the returned CompletableFuture to check if any errors are occurring AFTER the testClientLogic runs. This + * may be needed/worth checking + * + * @param testClientLogic return + * @return A CompletableFuture of any connection level error that may have occurred after the testClientLogic completes + * @throws Exception throws an exception either from the test client logic having thrown, or the connection itself + * encountering an error before test client logic completes + */ + public static CompletableFuture runLocalEchoTestServer(final BiConsumer testClientLogic) throws Exception { + final int port = randomPort(); + final String hostname = "127.0.0.1"; + try (final EventLoopGroup elGroup = new EventLoopGroup(1); + final EchoTestServiceRunner runner = new EchoTestServiceRunner(elGroup, hostname, port); + final HostResolver resolver = new HostResolver(elGroup, 64); + final ClientBootstrap clientBootstrap = new ClientBootstrap(elGroup, resolver); + final SocketOptions socketOptions = new SocketOptions()) { + socketOptions.connectTimeoutMs = 3000; + socketOptions.domain = SocketOptions.SocketDomain.IPv4; + socketOptions.type = SocketOptions.SocketType.STREAM; + + runner.runService(); + final EventStreamRPCConnectionConfig config = new EventStreamRPCConnectionConfig(clientBootstrap, elGroup, + socketOptions, null, hostname, port, () -> TestAuthNZHandlers.getClientAuth("accepted.foo")); + try (EventStreamRPCConnection connection = new EventStreamRPCConnection(config)) { + final CompletableFuture connectFuture = new CompletableFuture<>(); + final CompletableFuture clientErrorFuture = new CompletableFuture<>(); //only completes exceptionally if there's an error + connection.connect(new EventStreamRPCConnection.LifecycleHandler() { + @Override + public void onConnect() { + connectFuture.complete(null); + } + + @Override + public void onDisconnect(int errorCode) { + if (!connectFuture.isDone()) { + connectFuture.completeExceptionally(new RuntimeException("Client initial connection failed due to: " + CRT.awsErrorName(errorCode))); + } else if (errorCode != CRT.AWS_CRT_SUCCESS) { + clientErrorFuture.completeExceptionally(new RuntimeException("Client disconnected due to: " + CRT.awsErrorName(errorCode))); + } else { } //don't care if it normal closure/disconnect + } + + @Override + public boolean onError(Throwable t) { + if (!connectFuture.isDone()) { + connectFuture.completeExceptionally(t); + } else { + clientErrorFuture.completeExceptionally(t); + } + return false; + } + }); + connectFuture.get(480, TimeUnit.SECONDS); //wait for connection to move forward + final EchoTestRPC client = new EchoTestRPCClient(connection); + final CompletableFuture runClientOrError = + CompletableFuture.anyOf(clientErrorFuture, + CompletableFuture.runAsync(() -> testClientLogic.accept(connection, client), + Executors.newSingleThreadExecutor())); + runClientOrError.get(240, TimeUnit.SECONDS); + return clientErrorFuture; + } + } + } + + /** + * Enables a bit of a performance/load test by reconnecting the client to the same running server multiple times + * and each time the client connects, it runs the test logic for the number of times. + * + * !!! WARNING SocketOptions.SocketDomain is LOCAL for this server. Test client must match + */ + public static void runLocalEchoTestServerClientLoopUnixDomain(final String domainSocket, + final BiConsumer testClientLogic, int nCount) throws Exception { + final int port = randomPort(); + Files.deleteIfExists(Paths.get(domainSocket)); + try (final EventLoopGroup elGroup = new EventLoopGroup(1); + final EchoTestServiceRunner runner = new EchoTestServiceRunner(elGroup, SocketOptions.SocketDomain.LOCAL, domainSocket, port); + final SocketOptions socketOptions = new SocketOptions(); + final HostResolver hostResolver = new HostResolver(elGroup, 64); + final ClientBootstrap clientBootstrap = new ClientBootstrap(elGroup, hostResolver)) { + socketOptions.connectTimeoutMs = 3000; + socketOptions.domain = SocketOptions.SocketDomain.LOCAL; + socketOptions.type = SocketOptions.SocketType.STREAM; + + runner.runService(); + for (int i = 0; i < nCount; ++i) { + final EventStreamRPCConnectionConfig config = new EventStreamRPCConnectionConfig(clientBootstrap, elGroup, + socketOptions, null, domainSocket, port, () -> TestAuthNZHandlers.getClientAuth("accepted.foo")); + try (EventStreamRPCConnection connection = new EventStreamRPCConnection(config)) { + final CompletableFuture connectFuture = new CompletableFuture<>(); + final CompletableFuture clientErrorFuture = new CompletableFuture<>(); //only completes exceptionally if there's an error + connection.connect(new EventStreamRPCConnection.LifecycleHandler() { + @Override + public void onConnect() { + connectFuture.complete(null); + } + + @Override + public void onDisconnect(int errorCode) { + if (!connectFuture.isDone()) { + connectFuture.completeExceptionally(new RuntimeException("Client initial connection failed due to: " + CRT.awsErrorName(errorCode))); + } else if (errorCode != CRT.AWS_CRT_SUCCESS) { + clientErrorFuture.completeExceptionally(new RuntimeException("Client disconnected due to: " + CRT.awsErrorName(errorCode))); + } else { + } //don't care if it normal closure/disconnect + } + + @Override + public boolean onError(Throwable t) { + if (!connectFuture.isDone()) { + connectFuture.completeExceptionally(t); + } else { + clientErrorFuture.completeExceptionally(t); + } + return false; + } + }); + connectFuture.get(30, TimeUnit.SECONDS); //wait for connection to move forward + final EchoTestRPC client = new EchoTestRPCClient(connection); + final CompletableFuture runClientOrError = + CompletableFuture.anyOf(clientErrorFuture, CompletableFuture.runAsync( + () -> testClientLogic.accept(connection, client), Executors.newSingleThreadExecutor())); + runClientOrError.get(240, TimeUnit.SECONDS); + } + } + } + //given this method is rather explicitly meant to be used in JUnit tests and intends to check per iteration + //calling waitForNoResources() is necessary to call here, and appropriate + CrtResource.waitForNoResources(); + } + + /** + * Runs this dumb service via CLI + * @param args + * @throws Exception + */ + public static void main(String []args) throws Exception { + try(final EventLoopGroup elGroup = new EventLoopGroup(1); + EchoTestServiceRunner runner = new EchoTestServiceRunner(elGroup, args[0], Integer.parseInt(args[1]))) { + runner.runService(); + final Semaphore semaphore = new Semaphore(1); + semaphore.acquire(); + semaphore.acquire(); //wait until control+C + } + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/GetAllCustomersHandler.java b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/GetAllCustomersHandler.java new file mode 100644 index 000000000..69b569d19 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/GetAllCustomersHandler.java @@ -0,0 +1,31 @@ +package software.amazon.awssdk.eventstreamrpc.echotest; + +import software.amazon.awssdk.awstest.GeneratedAbstractGetAllCustomersOperationHandler; +import software.amazon.awssdk.awstest.model.GetAllCustomersRequest; +import software.amazon.awssdk.awstest.model.GetAllCustomersResponse; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandlerContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public class GetAllCustomersHandler extends GeneratedAbstractGetAllCustomersOperationHandler { + + protected GetAllCustomersHandler(OperationContinuationHandlerContext context) { + super(context); + } + + @Override + protected void onStreamClosed() { + // do nothing + } + + @Override + public GetAllCustomersResponse handleRequest(GetAllCustomersRequest request) { + final GetAllCustomersResponse response = new GetAllCustomersResponse(); + return response; + } + + @Override + public void handleStreamEvent(EventStreamJsonMessage streamRequestEvent) { + // maybe unsupported operation? + throw new RuntimeException("No stream event should be occurring on this operation"); + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/GetAllProductsHandler.java b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/GetAllProductsHandler.java new file mode 100644 index 000000000..565bd140e --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/echotest/GetAllProductsHandler.java @@ -0,0 +1,31 @@ +package software.amazon.awssdk.eventstreamrpc.echotest; + +import software.amazon.awssdk.awstest.GeneratedAbstractGetAllProductsOperationHandler; +import software.amazon.awssdk.awstest.model.GetAllProductsRequest; +import software.amazon.awssdk.awstest.model.GetAllProductsResponse; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandlerContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public class GetAllProductsHandler extends GeneratedAbstractGetAllProductsOperationHandler { + + protected GetAllProductsHandler(OperationContinuationHandlerContext context) { + super(context); + } + + @Override + protected void onStreamClosed() { + // do nothing + } + + @Override + public GetAllProductsResponse handleRequest(GetAllProductsRequest request) { + final GetAllProductsResponse response = new GetAllProductsResponse(); + return response; + } + + @Override + public void handleStreamEvent(EventStreamJsonMessage streamRequestEvent) { + // maybe unsupported operation? + throw new RuntimeException("No stream event should be occurring on this operation"); + } +} diff --git a/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/GreengrassCoreIPCClientV2.java b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/GreengrassCoreIPCClientV2.java index 091c2c6de..1f2111d6f 100644 --- a/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/GreengrassCoreIPCClientV2.java +++ b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/GreengrassCoreIPCClientV2.java @@ -1,24 +1,5 @@ package software.amazon.awssdk.aws.greengrass; -import java.io.IOException; -import java.lang.AutoCloseable; -import java.lang.Boolean; -import java.lang.Exception; -import java.lang.InterruptedException; -import java.lang.Override; -import java.lang.Runnable; -import java.lang.RuntimeException; -import java.lang.String; -import java.lang.Throwable; -import java.lang.Void; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.function.Consumer; -import java.util.function.Function; import software.amazon.awssdk.aws.greengrass.model.ComponentUpdatePolicyEvents; import software.amazon.awssdk.aws.greengrass.model.ConfigurationUpdateEvents; import software.amazon.awssdk.aws.greengrass.model.CreateDebugPasswordRequest; @@ -83,11 +64,22 @@ import software.amazon.awssdk.crt.io.ClientBootstrap; import software.amazon.awssdk.crt.io.EventLoopGroup; import software.amazon.awssdk.crt.io.SocketOptions; +import software.amazon.awssdk.crt.io.SocketOptions.SocketDomain; import software.amazon.awssdk.eventstreamrpc.EventStreamRPCConnection; import software.amazon.awssdk.eventstreamrpc.EventStreamRPCConnectionConfig; import software.amazon.awssdk.eventstreamrpc.GreengrassConnectMessageSupplier; import software.amazon.awssdk.eventstreamrpc.StreamResponseHandler; +import java.io.IOException; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import java.util.function.Function; + /** * V2 Client for Greengrass. * !! Developer Preview !! - This class is currently in developer preview. @@ -102,7 +94,7 @@ public class GreengrassCoreIPCClientV2 implements AutoCloseable { protected EventStreamRPCConnection connection; GreengrassCoreIPCClientV2(GreengrassCoreIPC client, EventStreamRPCConnection connection, - Executor executor) { + Executor executor) { this.client = client; this.connection = connection; this.executor = executor; @@ -619,8 +611,7 @@ public StreamingResponse, final SubscribeToComponentUpdatesRequest request, Consumer onStreamEvent, Optional> onStreamError, Optional onStreamClosed) { - SubscribeToComponentUpdatesResponseHandler r = client.subscribeToComponentUpdates(request, Optional.of(getStreamingResponseHandler(onStreamEvent, onStreamError, onStreamClosed))); - return new StreamingResponse<>(r.getResponse(), r); + return this.subscribeToComponentUpdatesAsync(request, getStreamingResponseHandler(onStreamEvent, onStreamError, onStreamClosed)); } /** @@ -703,8 +694,7 @@ public StreamingResponse onStreamEvent, Optional> onStreamError, Optional onStreamClosed) { - SubscribeToConfigurationUpdateResponseHandler r = client.subscribeToConfigurationUpdate(request, Optional.of(getStreamingResponseHandler(onStreamEvent, onStreamError, onStreamClosed))); - return new StreamingResponse<>(r.getResponse(), r); + return this.subscribeToConfigurationUpdateAsync(request, getStreamingResponseHandler(onStreamEvent, onStreamError, onStreamClosed)); } /** @@ -786,8 +776,7 @@ public StreamingResponse, SubscribeToIoTCoreResponseHandler> subscribeToIoTCoreAsync( final SubscribeToIoTCoreRequest request, Consumer onStreamEvent, Optional> onStreamError, Optional onStreamClosed) { - SubscribeToIoTCoreResponseHandler r = client.subscribeToIoTCore(request, Optional.of(getStreamingResponseHandler(onStreamEvent, onStreamError, onStreamClosed))); - return new StreamingResponse<>(r.getResponse(), r); + return this.subscribeToIoTCoreAsync(request, getStreamingResponseHandler(onStreamEvent, onStreamError, onStreamClosed)); } /** @@ -868,8 +857,7 @@ public StreamingResponse, Subscrib public StreamingResponse, SubscribeToTopicResponseHandler> subscribeToTopicAsync( final SubscribeToTopicRequest request, Consumer onStreamEvent, Optional> onStreamError, Optional onStreamClosed) { - SubscribeToTopicResponseHandler r = client.subscribeToTopic(request, Optional.of(getStreamingResponseHandler(onStreamEvent, onStreamError, onStreamClosed))); - return new StreamingResponse<>(r.getResponse(), r); + return this.subscribeToTopicAsync(request, getStreamingResponseHandler(onStreamEvent, onStreamError, onStreamClosed)); } /** @@ -951,8 +939,7 @@ public StreamingResponse onStreamEvent, Optional> onStreamError, Optional onStreamClosed) { - SubscribeToValidateConfigurationUpdatesResponseHandler r = client.subscribeToValidateConfigurationUpdates(request, Optional.of(getStreamingResponseHandler(onStreamEvent, onStreamError, onStreamClosed))); - return new StreamingResponse<>(r.getResponse(), r); + return this.subscribeToValidateConfigurationUpdatesAsync(request, getStreamingResponseHandler(onStreamEvent, onStreamError, onStreamClosed)); } /** @@ -1202,11 +1189,13 @@ public static class Builder { protected EventStreamRPCConnection connection = null; + protected SocketDomain socketDomain = SocketDomain.LOCAL; + public GreengrassCoreIPCClientV2 build() throws IOException { if (client == null) { SocketOptions socketOptions = new SocketOptions(); socketOptions.connectTimeoutMs = 3000; - socketOptions.domain = SocketOptions.SocketDomain.LOCAL; + socketOptions.domain = this.socketDomain; socketOptions.type = SocketOptions.SocketType.STREAM; String ipcServerSocketPath = this.socketPath; String authToken = this.authToken; @@ -1257,6 +1246,11 @@ public Builder withSocketPath(String socketPath) { return this; } + public Builder withSocketDomain(SocketDomain domain) { + this.socketDomain = domain; + return this; + } + public Builder withPort(int port) { this.port = port; return this; diff --git a/sdk/greengrass/greengrass-client/src/test/java/GreengrassV2ClientTest.java b/sdk/greengrass/greengrass-client/src/test/java/GreengrassV2ClientTest.java new file mode 100644 index 000000000..755644d2f --- /dev/null +++ b/sdk/greengrass/greengrass-client/src/test/java/GreengrassV2ClientTest.java @@ -0,0 +1,213 @@ +import com.google.gson.Gson; +import greengrass.GeneratedAbstractCreateLocalDeploymentOperationHandler; +import greengrass.GeneratedAbstractSubscribeToTopicOperationHandler; +import greengrass.GreengrassCoreIPCService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.aws.greengrass.GreengrassCoreIPCClientV2; +import software.amazon.awssdk.aws.greengrass.SubscribeToTopicResponseHandler; +import software.amazon.awssdk.aws.greengrass.model.BinaryMessage; +import software.amazon.awssdk.aws.greengrass.model.CreateLocalDeploymentRequest; +import software.amazon.awssdk.aws.greengrass.model.CreateLocalDeploymentResponse; +import software.amazon.awssdk.aws.greengrass.model.SubscribeToTopicRequest; +import software.amazon.awssdk.aws.greengrass.model.SubscribeToTopicResponse; +import software.amazon.awssdk.aws.greengrass.model.SubscriptionResponseMessage; +import software.amazon.awssdk.crt.CrtResource; +import software.amazon.awssdk.crt.io.EventLoopGroup; +import software.amazon.awssdk.crt.io.SocketOptions; +import software.amazon.awssdk.eventstreamrpc.Authorization; +import software.amazon.awssdk.eventstreamrpc.GreengrassEventStreamConnectMessage; +import software.amazon.awssdk.eventstreamrpc.RpcServer; +import software.amazon.awssdk.eventstreamrpc.StreamResponseHandler; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class GreengrassV2ClientTest { + private static final Random RANDOM = new Random(); //default instantiation uses time + private int port; + private RpcServer ipcServer; + private GreengrassEventStreamConnectMessage authenticationRequest; + private GreengrassCoreIPCClientV2 client; + private CompletableFuture subscriptionClosed = new CompletableFuture<>(); + + public static int randomPort() { + return RANDOM.nextInt(65535 - 1024) + 1024; + } + + @BeforeEach + public void before() throws IOException { + port = randomPort(); + + try (final EventLoopGroup elGroup = new EventLoopGroup(1); + SocketOptions socketOptions = new SocketOptions()) { + socketOptions.connectTimeoutMs = 3000; + socketOptions.domain = SocketOptions.SocketDomain.IPv4; + socketOptions.type = SocketOptions.SocketType.STREAM; + + GreengrassCoreIPCService service = new GreengrassCoreIPCService(); + service.setCreateLocalDeploymentHandler((c) -> new GeneratedAbstractCreateLocalDeploymentOperationHandler(c) { + @Override + protected void onStreamClosed() { + } + + @Override + public CreateLocalDeploymentResponse handleRequest(CreateLocalDeploymentRequest request) { + return new CreateLocalDeploymentResponse().setDeploymentId("deployment"); + } + + @Override + public void handleStreamEvent(EventStreamJsonMessage streamRequestEvent) { + } + }); + service.setSubscribeToTopicHandler((c) -> new GeneratedAbstractSubscribeToTopicOperationHandler(c) { + @Override + protected void onStreamClosed() { + subscriptionClosed.complete(null); + } + + @Override + public SubscribeToTopicResponse handleRequest(SubscribeToTopicRequest request) { + new Thread(() -> { + sendStreamEvent(new SubscriptionResponseMessage().setBinaryMessage( + new BinaryMessage().setMessage("message".getBytes(StandardCharsets.UTF_8)))); + }).start(); + return new SubscribeToTopicResponse().setTopicName(request.getTopic()); + } + + @Override + public void handleStreamEvent(EventStreamJsonMessage streamRequestEvent) { + } + }); + service.setAuthenticationHandler((headers, bytes) -> { + authenticationRequest = new Gson().fromJson(new String(bytes), GreengrassEventStreamConnectMessage.class); + return () -> "connected"; + }); + service.setAuthorizationHandler(authenticationData -> Authorization.ACCEPT); + + ipcServer = new RpcServer(elGroup, socketOptions, null, "127.0.0.1", port, service); + ipcServer.runServer(); + + client = GreengrassCoreIPCClientV2.builder().withPort(port).withSocketPath("127.0.0.1") + .withSocketDomain(SocketOptions.SocketDomain.IPv4).withAuthToken("myAuthToken").build(); + } + } + + @AfterEach + public void after() throws Exception { + ipcServer.close(); + if (client != null) { + client.close(); + } + + CrtResource.waitForNoResources(); + } + + @Test + public void testV2Client() throws InterruptedException, ExecutionException, TimeoutException { + assertEquals(authenticationRequest.getAuthToken(), "myAuthToken"); + CreateLocalDeploymentResponse depResp = client.createLocalDeployment(new CreateLocalDeploymentRequest()); + assertEquals("deployment", depResp.getDeploymentId()); + + CompletableFuture asyncDepResp = + client.createLocalDeploymentAsync(new CreateLocalDeploymentRequest()); + assertEquals("deployment", asyncDepResp.get().getDeploymentId()); + + CompletableFuture receivedMessage = new CompletableFuture<>(); + CompletableFuture finalReceivedMessage = receivedMessage; + GreengrassCoreIPCClientV2.StreamingResponse subResp = + client.subscribeToTopic(new SubscribeToTopicRequest().setTopic("abc"), (x) -> { + if (!Thread.currentThread().getName().contains("pool")) { + System.out.println(Thread.currentThread().getName()); + finalReceivedMessage.completeExceptionally( + new RuntimeException("Ran on event loop instead of executor")); + } + finalReceivedMessage.complete(new String(x.getBinaryMessage().getMessage())); + }, Optional.empty(), Optional.empty()); + + assertEquals("message", receivedMessage.get()); + subResp.getHandler().closeStream().get(); + subscriptionClosed.get(1, TimeUnit.SECONDS); + + subscriptionClosed = new CompletableFuture<>(); + receivedMessage = new CompletableFuture<>(); + CompletableFuture finalReceivedMessage1 = receivedMessage; + subResp = client.subscribeToTopic(new SubscribeToTopicRequest().setTopic("abc"), new StreamResponseHandler() { + @Override + public void onStreamEvent(SubscriptionResponseMessage streamEvent) { + if (!Thread.currentThread().getName().contains("pool")) { + finalReceivedMessage1.completeExceptionally( + new RuntimeException("Ran on event loop instead of executor")); + } + finalReceivedMessage1.complete(new String(streamEvent.getBinaryMessage().getMessage())); + } + + @Override + public boolean onStreamError(Throwable error) { + return false; + } + + @Override + public void onStreamClosed() { + } + }); + + assertEquals("message", receivedMessage.get()); + subResp.getHandler().closeStream().get(); + subscriptionClosed.get(1, TimeUnit.SECONDS); + + subscriptionClosed = new CompletableFuture<>(); + receivedMessage = new CompletableFuture<>(); + CompletableFuture finalReceivedMessage2 = receivedMessage; + GreengrassCoreIPCClientV2.StreamingResponse, SubscribeToTopicResponseHandler> + subRespAsync = client.subscribeToTopicAsync(new SubscribeToTopicRequest().setTopic("abc"), + new StreamResponseHandler() { + @Override + public void onStreamEvent(SubscriptionResponseMessage streamEvent) { + if (!Thread.currentThread().getName().contains("pool")) { + finalReceivedMessage2.completeExceptionally( + new RuntimeException("Ran on event loop instead of executor")); + } + finalReceivedMessage2.complete(new String(streamEvent.getBinaryMessage().getMessage())); + } + + @Override + public boolean onStreamError(Throwable error) { + return false; + } + + @Override + public void onStreamClosed() { + } + }); + + assertEquals("message", receivedMessage.get()); + subRespAsync.getHandler().closeStream().get(); + subscriptionClosed.get(1, TimeUnit.SECONDS); + + subscriptionClosed = new CompletableFuture<>(); + receivedMessage = new CompletableFuture<>(); + CompletableFuture finalReceivedMessage3 = receivedMessage; + subRespAsync = client.subscribeToTopicAsync(new SubscribeToTopicRequest().setTopic("abc"), (x) -> { + if (!Thread.currentThread().getName().contains("pool")) { + finalReceivedMessage3.completeExceptionally( + new RuntimeException("Ran on event loop instead of executor")); + } + finalReceivedMessage3.complete(new String(x.getBinaryMessage().getMessage())); + }, Optional.empty(), Optional.empty()); + + assertEquals("message", receivedMessage.get()); + subRespAsync.getHandler().closeStream().get(); + subscriptionClosed.get(1, TimeUnit.SECONDS); + } +} diff --git a/sdk/greengrass/greengrass-client/src/test/java/greengrass/GeneratedAbstractCreateLocalDeploymentOperationHandler.java b/sdk/greengrass/greengrass-client/src/test/java/greengrass/GeneratedAbstractCreateLocalDeploymentOperationHandler.java new file mode 100644 index 000000000..8345b5778 --- /dev/null +++ b/sdk/greengrass/greengrass-client/src/test/java/greengrass/GeneratedAbstractCreateLocalDeploymentOperationHandler.java @@ -0,0 +1,22 @@ +package greengrass; + +import software.amazon.awssdk.aws.greengrass.GreengrassCoreIPCServiceModel; +import software.amazon.awssdk.aws.greengrass.model.CreateLocalDeploymentRequest; +import software.amazon.awssdk.aws.greengrass.model.CreateLocalDeploymentResponse; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandler; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandlerContext; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public abstract class GeneratedAbstractCreateLocalDeploymentOperationHandler extends OperationContinuationHandler { + protected GeneratedAbstractCreateLocalDeploymentOperationHandler( + OperationContinuationHandlerContext context) { + super(context); + } + + @Override + public OperationModelContext getOperationModelContext( + ) { + return GreengrassCoreIPCServiceModel.getCreateLocalDeploymentModelContext(); + } +} diff --git a/sdk/greengrass/greengrass-client/src/test/java/greengrass/GeneratedAbstractSubscribeToTopicOperationHandler.java b/sdk/greengrass/greengrass-client/src/test/java/greengrass/GeneratedAbstractSubscribeToTopicOperationHandler.java new file mode 100644 index 000000000..2e9def9f1 --- /dev/null +++ b/sdk/greengrass/greengrass-client/src/test/java/greengrass/GeneratedAbstractSubscribeToTopicOperationHandler.java @@ -0,0 +1,23 @@ +package greengrass; + +import software.amazon.awssdk.aws.greengrass.GreengrassCoreIPCServiceModel; +import software.amazon.awssdk.aws.greengrass.model.SubscribeToTopicRequest; +import software.amazon.awssdk.aws.greengrass.model.SubscribeToTopicResponse; +import software.amazon.awssdk.aws.greengrass.model.SubscriptionResponseMessage; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandler; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandlerContext; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public abstract class GeneratedAbstractSubscribeToTopicOperationHandler extends OperationContinuationHandler { + protected GeneratedAbstractSubscribeToTopicOperationHandler( + OperationContinuationHandlerContext context) { + super(context); + } + + @Override + public OperationModelContext getOperationModelContext( + ) { + return GreengrassCoreIPCServiceModel.getSubscribeToTopicModelContext(); + } +} diff --git a/sdk/greengrass/greengrass-client/src/test/java/greengrass/GreengrassCoreIPCService.java b/sdk/greengrass/greengrass-client/src/test/java/greengrass/GreengrassCoreIPCService.java new file mode 100644 index 000000000..08a01eb57 --- /dev/null +++ b/sdk/greengrass/greengrass-client/src/test/java/greengrass/GreengrassCoreIPCService.java @@ -0,0 +1,71 @@ +package greengrass; + +import software.amazon.awssdk.aws.greengrass.GreengrassCoreIPCServiceModel; +import software.amazon.awssdk.crt.eventstream.ServerConnectionContinuationHandler; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCServiceHandler; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCServiceModel; +import software.amazon.awssdk.eventstreamrpc.OperationContinuationHandlerContext; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +public final class GreengrassCoreIPCService extends EventStreamRPCServiceHandler { + public static final String SERVICE_NAMESPACE = "aws.greengrass"; + + protected static final Set SERVICE_OPERATION_SET; + + public static final String SUBSCRIBE_TO_TOPIC = SERVICE_NAMESPACE + "#SubscribeToTopic"; + + public static final String CREATE_LOCAL_DEPLOYMENT = SERVICE_NAMESPACE + "#CreateLocalDeployment"; + + static { + SERVICE_OPERATION_SET = new HashSet<>(); + SERVICE_OPERATION_SET.add(SUBSCRIBE_TO_TOPIC); + SERVICE_OPERATION_SET.add(CREATE_LOCAL_DEPLOYMENT); + } + + private final Map> operationSupplierMap; + + public GreengrassCoreIPCService() { + this.operationSupplierMap = new HashMap<>(); + } + + @Override + public EventStreamRPCServiceModel getServiceModel() { + return GreengrassCoreIPCServiceModel.getInstance(); + } + + public void setSubscribeToTopicHandler( + Function handler) { + operationSupplierMap.put(SUBSCRIBE_TO_TOPIC, handler); + } + + public void setCreateLocalDeploymentHandler( + Function handler) { + operationSupplierMap.put(CREATE_LOCAL_DEPLOYMENT, handler); + } + + @Override + public Set getAllOperations() { + return SERVICE_OPERATION_SET; + } + + @Override + public boolean hasHandlerForOperation(String operation) { + return operationSupplierMap.containsKey(operation); + } + + @Override + public Function getOperationHandler( + String operation) { + return operationSupplierMap.get(operation); + } + + public void setOperationHandler(String operation, + Function handler) { + operationSupplierMap.put(operation, handler); + } +} diff --git a/sdk/pom.xml b/sdk/pom.xml index be3b0d510..4620bf80f 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -45,9 +45,27 @@ 0.15.9 - junit - junit - 4.13.1 + org.slf4j + slf4j-api + 1.7.30 + test + + + org.junit.jupiter + junit-jupiter-api + 5.8.1 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.8.1 + test + + + org.json + json + 20200518 test @@ -85,7 +103,6 @@ - + greengrass/event-stream-rpc-server/src/main/java + greengrass/event-stream-rpc-server/src/test/java + greengrass/greengrass-client/src/test/java - --> + + maven-surefire-plugin + 2.22.2 + + false + + org.apache.maven.plugins