diff --git a/README.md b/README.md index eb2a138d1..796ed7176 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ mvn clean install ``` sh # NOTE: use the latest version of the CRT here + git clone --branch v0.11.5 https://github.com/awslabs/aws-crt-java.git git clone https://github.com/awslabs/aws-iot-device-sdk-java-v2.git diff --git a/samples/BasicPubSub/src/main/java/pubsub/PubSub.java b/samples/BasicPubSub/src/main/java/pubsub/PubSub.java index 95bfb8064..cf433e027 100644 --- a/samples/BasicPubSub/src/main/java/pubsub/PubSub.java +++ b/samples/BasicPubSub/src/main/java/pubsub/PubSub.java @@ -22,7 +22,6 @@ import software.amazon.awssdk.iot.AwsIotMqttConnectionBuilder; import software.amazon.awssdk.iot.iotjobs.model.RejectedError; -import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.util.UUID; import java.util.concurrent.CompletableFuture; diff --git a/samples/Greengrass/src/main/java/greengrass/BasicDiscovery.java b/samples/Greengrass/src/main/java/greengrass/BasicDiscovery.java index f4d54b9d9..b55544ec7 100644 --- a/samples/Greengrass/src/main/java/greengrass/BasicDiscovery.java +++ b/samples/Greengrass/src/main/java/greengrass/BasicDiscovery.java @@ -5,7 +5,6 @@ package greengrass; -import software.amazon.awssdk.crt.CRT; import software.amazon.awssdk.crt.CrtResource; import software.amazon.awssdk.crt.CrtRuntimeException; import software.amazon.awssdk.crt.Log; @@ -25,7 +24,6 @@ import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.regex.Pattern; import static software.amazon.awssdk.iot.discovery.DiscoveryClient.TLS_EXT_ALPN; @@ -165,8 +163,8 @@ public static void main(String[] args) { try(final DiscoveryClientConfig discoveryClientConfig = new DiscoveryClientConfig(clientBootstrap, tlsCtxOptions, new SocketOptions(), region, 1, proxyOptions); - final DiscoveryClient discoveryClient = new DiscoveryClient(discoveryClientConfig); - final MqttClientConnection connection = getClientFromDiscovery(discoveryClient, clientBootstrap)) { + final DiscoveryClient discoveryClient = new DiscoveryClient(discoveryClientConfig); + final MqttClientConnection connection = getClientFromDiscovery(discoveryClient, clientBootstrap)) { if ("subscribe".equals(mode) || "both".equals(mode)) { final CompletableFuture subFuture = connection.subscribe(topic, QualityOfService.AT_MOST_ONCE, message -> { diff --git a/samples/Identity/src/main/java/identity/FleetProvisioningSample.java b/samples/Identity/src/main/java/identity/FleetProvisioningSample.java index dc25a573e..cc21ca6da 100644 --- a/samples/Identity/src/main/java/identity/FleetProvisioningSample.java +++ b/samples/Identity/src/main/java/identity/FleetProvisioningSample.java @@ -28,14 +28,10 @@ import software.amazon.awssdk.iot.iotidentity.model.RegisterThingSubscriptionRequest; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; -import java.util.Map; import com.google.gson.Gson; -import java.util.LinkedList; -import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; diff --git a/samples/README.md b/samples/README.md index 45a3de723..4773abeeb 100644 --- a/samples/README.md +++ b/samples/README.md @@ -5,6 +5,7 @@ * [fleet provisioning](#fleet-provisioning) **Additional sample apps not described below:** + * [BasicPubSub](https://github.com/aws/aws-iot-device-sdk-java-v2/tree/main/samples/BasicPubSub) * [Greengrass](https://github.com/aws/aws-iot-device-sdk-java-v2/tree/main/samples/Greengrass) * [PubSubStress](https://github.com/aws/aws-iot-device-sdk-java-v2/tree/main/samples/PubSubStress) diff --git a/samples/RawPubSub/src/main/java/rawpubsub/RawPubSub.java b/samples/RawPubSub/src/main/java/rawpubsub/RawPubSub.java index 3c27eaa84..31b2076f8 100644 --- a/samples/RawPubSub/src/main/java/rawpubsub/RawPubSub.java +++ b/samples/RawPubSub/src/main/java/rawpubsub/RawPubSub.java @@ -16,7 +16,6 @@ import software.amazon.awssdk.crt.mqtt.*; import software.amazon.awssdk.iot.iotjobs.model.RejectedError; -import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; diff --git a/sdk/greengrass/.gitattributes b/sdk/greengrass/.gitattributes new file mode 100644 index 000000000..00a51aff5 --- /dev/null +++ b/sdk/greengrass/.gitattributes @@ -0,0 +1,6 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf + diff --git a/sdk/greengrass/.gitignore b/sdk/greengrass/.gitignore new file mode 100644 index 000000000..614ad2df3 --- /dev/null +++ b/sdk/greengrass/.gitignore @@ -0,0 +1,9 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build + +.idea +*.swp +*.swo diff --git a/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/AuthenticationData.java b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/AuthenticationData.java new file mode 100644 index 000000000..204309ed7 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/AuthenticationData.java @@ -0,0 +1,16 @@ +package software.amazon.awssdk.eventstreamrpc; + +/** + * Exact implementation of this is between the EventStreamRPCServiceHandler at the Authentication handler itself + */ +public interface AuthenticationData { + + /** + * 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 + */ + public String getIdentityLabel(); +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/AuthenticationHandler.java b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/AuthenticationHandler.java new file mode 100644 index 000000000..8fffa4660 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/AuthenticationHandler.java @@ -0,0 +1,14 @@ +package software.amazon.awssdk.eventstreamrpc; + +import software.amazon.awssdk.crt.eventstream.Header; + +import java.util.List; +import java.util.function.BiFunction; + +/** + * apply() accepts the connection message and produces authentication data from it to at least be + * used for authorization decisions + * + * Exact implementation is up to service implementations to decide what it is and how to handle it + */ +public interface AuthenticationHandler extends BiFunction, byte[], AuthenticationData> { } diff --git a/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/Authorization.java b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/Authorization.java new file mode 100644 index 000000000..0862a2f2e --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/Authorization.java @@ -0,0 +1,11 @@ +package software.amazon.awssdk.eventstreamrpc; + +/** + * Authorization decision object contains the decision in general + * and the authentication data along with it. + * + */ +public enum Authorization { + ACCEPT, + REJECT +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/AuthorizationHandler.java b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/AuthorizationHandler.java new file mode 100644 index 000000000..1958cf42f --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/AuthorizationHandler.java @@ -0,0 +1,13 @@ +package software.amazon.awssdk.eventstreamrpc; + +import java.util.function.Function; + +/** + * Handler receives the input data of the connection message and produces an authorization result + * which is a decision on accept or rejecting the connection + * + * -The apply function must return an Authorization object with a non-null AuthenticationData object + * returned. It's great idea for implementations to log appropriate input + * + */ +public interface AuthorizationHandler extends Function { } 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 new file mode 100644 index 000000000..8f9410e91 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/DebugLoggingOperationHandler.java @@ -0,0 +1,60 @@ +package software.amazon.awssdk.eventstreamrpc; + +import com.google.gson.Gson; +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 final OperationModelContext operationModelContext; + + public DebugLoggingOperationHandler(final OperationModelContext modelContext, final OperationContinuationHandlerContext context) { + super(context); + this.operationModelContext = modelContext; + } + + @Override + public OperationModelContext getOperationModelContext() { + return operationModelContext; + } + + /** + * Called when the underlying continuation is closed. Gives operations a chance to cleanup whatever + * resources may be on the other end of an open stream. Also invoked when an underlying ServerConnection + * is closed associated with the stream/continuation + */ + @Override + protected void onStreamClosed() { + LOGGER.info(String.format("%s operation onStreamClosed()", + operationModelContext.getOperationName())); + } + + @Override + public EventStreamJsonMessage handleRequest(EventStreamJsonMessage request) { + LOGGER.info(String.format("%s operation handleRequest() :: %s", operationModelContext.getOperationName(), + operationModelContext.getServiceModel().toJson(request))); + return new EventStreamJsonMessage() { + @Override + public byte[] toPayload(Gson gson) { + return "{}".getBytes(StandardCharsets.UTF_8); + } + + @Override + public String getApplicationModelType() { + return operationModelContext.getResponseApplicationModelType(); + } + }; + } + + @Override + public void handleStreamEvent(EventStreamJsonMessage streamRequestEvent) { + LOGGER.info(String.format("%s operation handleStreamEvent() :: %s", operationModelContext.getOperationName(), + operationModelContext.getServiceModel().toJson(streamRequestEvent))); + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/EventStreamRPCServiceHandler.java b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/EventStreamRPCServiceHandler.java new file mode 100644 index 000000000..0508a8b90 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/EventStreamRPCServiceHandler.java @@ -0,0 +1,60 @@ +package software.amazon.awssdk.eventstreamrpc; + +import java.util.Collection; + +public abstract class EventStreamRPCServiceHandler implements OperationContinuationHandlerFactory { + private AuthenticationHandler authenticationHandler; + private AuthorizationHandler authorizationHandler; + + public EventStreamRPCServiceHandler() { + authorizationHandler = null; + } + + protected abstract EventStreamRPCServiceModel getServiceModel(); + + /** + * Probably only useful for logging + * @return Returns the service name for the set of RPC operations + */ + public String getServiceName() { + return getServiceModel().getServiceName(); + } + + /** + * TODO: How may we want to protect this from being re-assigned after service creation? + * @param handler + */ + public void setAuthorizationHandler(final AuthorizationHandler handler) { + this.authorizationHandler = handler; + } + + /** + * Use this to determine if the connection should be accepted or rejected for this service + * + * @return + */ + public AuthorizationHandler getAuthorizationHandler() { + return authorizationHandler; + } + + @Override + public Collection getAllOperations() { + return getServiceModel().getAllOperations(); + } + + /** + * Pulls caller/client identity when server connection occurs + * @return + */ + public AuthenticationHandler getAuthenticationHandler() { + return authenticationHandler; + } + + /** + * TODO: How may we want to protect this from being re-assigned after service creation? + * @param authenticationHandler + */ + public void setAuthenticationHandler(AuthenticationHandler authenticationHandler) { + this.authenticationHandler = authenticationHandler; + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/InvalidServiceConfigurationException.java b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/InvalidServiceConfigurationException.java new file mode 100644 index 000000000..b83d123f0 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/InvalidServiceConfigurationException.java @@ -0,0 +1,15 @@ +package software.amazon.awssdk.eventstreamrpc; + +public class InvalidServiceConfigurationException extends RuntimeException { + public InvalidServiceConfigurationException(String msg) { + super(msg); + } + + public InvalidServiceConfigurationException(String msg, Throwable cause) { + super(msg, cause); + } + + public InvalidServiceConfigurationException(Throwable cause) { + super(cause); + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/IpcServer.java b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/IpcServer.java new file mode 100644 index 000000000..f596eb4e7 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/IpcServer.java @@ -0,0 +1,152 @@ +package software.amazon.awssdk.eventstreamrpc; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +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; + +public class IpcServer implements AutoCloseable { + private static final Logger LOGGER = Logger.getLogger(IpcServer.class.getName()); + + 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 IpcServer(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.log(Level.SEVERE, "Throwable caught in new connection: " + e.getMessage()); + 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/OperationContinuationHandler.java b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandler.java new file mode 100644 index 000000000..91f4e40ad --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandler.java @@ -0,0 +1,300 @@ +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 software.amazon.awssdk.crt.eventstream.Header; +import software.amazon.awssdk.crt.eventstream.MessageFlags; +import software.amazon.awssdk.crt.eventstream.MessageType; +import software.amazon.awssdk.crt.eventstream.ServerConnectionContinuationHandler; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamOperationError; + +public abstract class OperationContinuationHandler + + extends ServerConnectionContinuationHandler + implements StreamEventPublisher { + private static final Logger LOGGER = Logger.getLogger(OperationContinuationHandler.class.getName()); + + private OperationContinuationHandlerContext context; + private List
initialRequestHeaders; + private RequestType initialRequest; + + abstract public OperationModelContext + getOperationModelContext(); + + public OperationContinuationHandler(final OperationContinuationHandlerContext context) { + super(context.getContinuation()); + this.context = context; + } + + @Override + final protected void onContinuationClosed() { + LOGGER.finer(String.format("%s stream continuation closed.", getOperationName())); + try { + onStreamClosed(); + } + catch(Exception e) { + LOGGER.severe(String.format("%s threw %s: %s", getOperationName(), e.getClass().getCanonicalName(), e.getMessage())); + } + } + + + final protected Class getRequestClass() { + return getOperationModelContext().getRequestTypeClass(); + } + + final protected Class getResponseClass() { + return getOperationModelContext().getResponseTypeClass(); + } + + final protected Class getStreamingRequestClass() { + return getOperationModelContext().getStreamingRequestTypeClass().get(); + } + + final protected Class getStreamingResponseClass() { + return getOperationModelContext().getStreamingResponseTypeClass().get(); + } + + /** + * Returns the operation name implemented by the handler. Generated code should populate this + * @return + */ + private String getOperationName() { + return getOperationModelContext().getOperationName(); + } + + /** + * Called when the underlying continuation is closed. Gives operations a chance to cleanup whatever + * resources may be on the other end of an open stream. Also invoked when an underlying ServerConnection + * is closed associated with the stream/continuation + */ + protected abstract void onStreamClosed(); + + /** + * Should return true iff operation has either streaming input or output. If neither, return false and only allows + * an initial-request -> initial->response before closing the continuation. + * + * @return + */ + final protected boolean isStreamingOperation() { + return getOperationModelContext().isStreamingOperation(); + } + + /** + * Main request handler for any operation to do work on an initial request. Streaming response operations + * still must send an initial-response which is empty. + * + * Implementers should not call sendStreamEvent() during handleRequest() to send a streaming response after + * an initial-response. This would violate the sequence of messages expected to occur for the specific + * operation. Override "afterHandleRequest()" as a way of being informed of the quickest possible time + * to sent a stream response after handleRequest returns. + * + * @param request + * @return + */ + public abstract ResponseType handleRequest(final RequestType request); + + /** + * Override to appropriately enforce stream responses are sent after the initial response. + * This only gets called if handleRequest returns normally and starts to send a response. + */ + public void afterHandleRequest() { } + + /** + * 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 + */ + public abstract void handleStreamEvent(final StreamingRequestType streamRequestEvent); + + /** + * Retrieves the underlying EventStream request headers for inspection. Pulling these headers + * out shouldn't be necessary as it means operations are aware of the underlying protocol. Any + * headers needed to be pulled are candidates for what should be in the service model directly + * @return + */ + final protected List
getInitialRequestHeaders() { + return initialRequestHeaders; //not a defensive copy + } + + /** + * Retrieves the initial request object that initiated the stream + * + * For use in handler implementations if initial request is wanted to handle further in-out events + * May be unecessary memory, but also initial request may be used by framework to log errors with + * 'request-id' like semantics + * + * @return + */ + final protected RequestType getInitialRequest() { + return initialRequest; + } + + /** + * Retrieves the operation handler context. Use for inspecting state outside of the + * limited scope of this operation handler. + * + * @return + */ + final protected OperationContinuationHandlerContext getContext () { + return context; + } + + /** + * TODO: close stream should be sent with the final message, or separately? Either should be fine + * @return + */ + @Override + final public CompletableFuture closeStream() { + LOGGER.fine(String.format("[%s] 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())); + } else { + LOGGER.fine(String.format("[%s] %s closing stream: ", getOperationName(), + ex.getClass().getName(), ex.getMessage())); + } + continuation.close(); + }); + } + + /** + * Used so other processes/events going on in the server can push events back into this + * operation's opened continuation + * + * @param streamingResponse + */ + final public CompletableFuture sendStreamEvent(final StreamingResponseType streamingResponse) { + return sendMessage(streamingResponse, false); + } + + final protected CompletableFuture sendMessage(final EventStreamJsonMessage message, final boolean close) { + if (continuation.isClosed()) { //is this check necessary? + return CompletableFuture.supplyAsync(() -> { throw new EventStreamClosedException(continuation.getNativeHandle()); }); + } + final List
responseHeaders = new ArrayList<>(); + byte[] outputPayload = getOperationModelContext().getServiceModel().toJson(message); + responseHeaders.add(Header.createHeader(EventStreamRPCServiceModel.CONTENT_TYPE_HEADER, + EventStreamRPCServiceModel.CONTENT_TYPE_APPLICATION_JSON)); + responseHeaders.add(Header.createHeader(EventStreamRPCServiceModel.SERVICE_MODEL_TYPE_HEADER, message.getApplicationModelType())); + + return continuation.sendMessage(responseHeaders, outputPayload, MessageType.ApplicationMessage, + close ? MessageFlags.TerminateStream.getByteValue() : 0) + .whenComplete((res, ex) -> { + if (close) { + continuation.close(); + } + }); + } + + /** + * Sends an error over the stream. Same method is used for errors from the initial response or any errors + * that occur while the stream is open. It will always close the stream/continuation on the same message + * using the terminate flag on the same message + * @param message + * @return + */ + final protected CompletableFuture sendModeledError(final EventStreamJsonMessage message) { + if (continuation.isClosed()) { //is this check necessary? + return CompletableFuture.supplyAsync(() -> { throw new EventStreamClosedException(continuation.getNativeHandle()); }); + } + final List
responseHeaders = new ArrayList<>(); + byte[] outputPayload = getOperationModelContext().getServiceModel().toJson(message); + responseHeaders.add(Header.createHeader(EventStreamRPCServiceModel.CONTENT_TYPE_HEADER, + EventStreamRPCServiceModel.CONTENT_TYPE_APPLICATION_JSON)); + responseHeaders.add(Header.createHeader(EventStreamRPCServiceModel.SERVICE_MODEL_TYPE_HEADER, message.getApplicationModelType())); + + return continuation.sendMessage(responseHeaders, outputPayload, + MessageType.ApplicationError, MessageFlags.TerminateStream.getByteValue()) + .whenComplete((res, ex) -> { + //complete silence on any error closing here + continuation.close(); + }); + } + + private void invokeAfterHandleRequest() { + try { + afterHandleRequest(); + } catch (Exception e) { + LOGGER.warning(String.format("%s.%s afterHandleRequest() threw %s: %s", + getOperationModelContext().getServiceModel().getServiceName(), + getOperationName(), e.getClass().getCanonicalName(), + e.getMessage())); + } + } + + @Override + final protected void onContinuationMessage(List
list, byte[] bytes, MessageType messageType, int i) { + LOGGER.fine("Continuation native id: " + continuation.getNativeHandle()); + 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); + } 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)) { + throw new RuntimeException("Handler for operation [" + getOperationName() + + "] did not return expected type. Found: " + result.getClass().getName()); + } + sendMessage(result, !isStreamingOperation()).whenComplete((res, ex) -> { + if (ex != null) { + LOGGER.severe(ex.getClass().getName() + " sending response message: " + ex.getMessage()); + } else { + LOGGER.finer("Response successfully sent"); + } + }); + invokeAfterHandleRequest(); + } else { + //not streaming, but null response? we have a problem + throw new RuntimeException("Operation handler returned null response!"); + } + } + } catch (EventStreamOperationError e) { + //We do not check if the specific exception thrown is a part of the core service? + sendModeledError(e); + invokeAfterHandleRequest(); + } catch (Exception e) { + final List
responseHeaders = new ArrayList<>(1); + 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())); + + 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()); + } + else { + LOGGER.finer("Error response successfully sent"); + } + continuation.close(); + }); + } + } +} diff --git a/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandlerContext.java b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandlerContext.java new file mode 100644 index 000000000..d6e85ec7a --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandlerContext.java @@ -0,0 +1,38 @@ +package software.amazon.awssdk.eventstreamrpc; + +import software.amazon.awssdk.crt.eventstream.ServerConnection; +import software.amazon.awssdk.crt.eventstream.ServerConnectionContinuation; + +/** + * When the server picks up a new incoming stream for an operation, and it has context that must + * be exposed to an operation handler, that access should be granted here. + * + * Any intentional exposure to the server connection state or the client that connected, anything + * that is beyond the operation's knowledge and information from request or stream inputs should + * be populated here + */ +public class OperationContinuationHandlerContext { + private final ServerConnection serverConnection; + private final ServerConnectionContinuation continuation; + private final AuthenticationData authenticationData; + + public OperationContinuationHandlerContext(final ServerConnection connection, + final ServerConnectionContinuation continuation, + final AuthenticationData authenticationData) { + this.serverConnection = connection; + this.continuation = continuation; + this.authenticationData = authenticationData; + } + + public ServerConnection getServerConnection() { + return serverConnection; + } + + public ServerConnectionContinuation getContinuation() { + return continuation; + } + + public AuthenticationData getAuthenticationData() { + return authenticationData; + } +} 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 new file mode 100644 index 000000000..4607ae35b --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandlerFactory.java @@ -0,0 +1,27 @@ +package software.amazon.awssdk.eventstreamrpc; + +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 + */ +public interface OperationContinuationHandlerFactory { + Function getOperationHandler(final String operationName); + Collection getAllOperations(); + boolean hasHandlerForOperation(String operation); + + //this may not be a good use of a default method impl as implementers can override it + //also InvalidServiceConfigurationException is a needed exception to be thrown from IpcServer + default void validateAllOperationsSet() { + if (!getAllOperations().stream().allMatch(op -> hasHandlerForOperation(op))) { + String unmappedOperations = getAllOperations().stream() + .filter(op -> !hasHandlerForOperation(op)).collect(Collectors.joining(",")); + throw new InvalidServiceConfigurationException(this.getClass().getName() + + " does not have all operations mapped! Unmapped operations: {" + unmappedOperations + "}"); + } + } +} 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 new file mode 100644 index 000000000..7cf602ef6 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/main/java/software/amazon/awssdk/eventstreamrpc/ServiceOperationMappingContinuationHandler.java @@ -0,0 +1,159 @@ +package software.amazon.awssdk.eventstreamrpc; + +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 final EventStreamRPCServiceHandler serviceHandler; + private AuthenticationData authenticationData; //should only be set once after AuthN + + public ServiceOperationMappingContinuationHandler(final ServerConnection serverConnection, final EventStreamRPCServiceHandler handler) { + super(serverConnection); + this.serviceHandler = handler; + this.authenticationData = null; + } + + @Override + protected void onProtocolMessage(List
headers, byte[] payload, MessageType messageType, int messageFlags) { + if (messageType == MessageType.Ping) { + int responseMessageFlag = 0; + MessageType responseMessageType = MessageType.PingResponse; + connection.sendProtocolMessage(headers.stream().filter(header -> !header.getName().startsWith(":")) + .collect(Collectors.toList()), payload, responseMessageType, responseMessageFlag); + } else if (messageType == MessageType.Connect) { + onConnectRequest(headers, payload); + } else if (messageType != MessageType.PingResponse) { + int responseMessageFlag = 0; + MessageType responseMessageType = MessageType.ServerError; + + String responsePayload = + "{ \"error\": \"Unrecognized Message Type\" }" + + "\"message\": \" message type value: " + messageType.getEnumValue() + " is not recognized as a valid request path.\" }"; + + Header contentTypeHeader = Header.createHeader(":content-type", "application/json"); + List
responseHeaders = new ArrayList<>(); + responseHeaders.add(contentTypeHeader); + CompletableFuture voidCompletableFuture = connection.sendProtocolMessage(responseHeaders, responsePayload.getBytes(StandardCharsets.UTF_8), responseMessageType, responseMessageFlag); + voidCompletableFuture.thenAccept(result -> {connection.closeConnection(0); this.close();}); + } + } + + /** + * Post: authenticationData should not be null + * @param headers + * @param payload + */ + protected void onConnectRequest(List
headers, byte[] payload) { + final int[] responseMessageFlag = { 0 }; + final MessageType acceptResponseType = MessageType.ConnectAck; + + final AuthenticationHandler authentication = serviceHandler.getAuthenticationHandler(); + final AuthorizationHandler authorization = serviceHandler.getAuthorizationHandler(); + + try { + final Optional versionHeader = headers.stream() + .filter(header -> header.getHeaderType() == HeaderType.String + && header.getName().equals(EventStreamRPCServiceModel.VERSION_HEADER)) + .map(header -> header.getValueAsString()) + .findFirst(); + if (versionHeader.isPresent() && + Version.fromString(versionHeader.get()).equals(Version.getInstance())) { + //version matches + if (authentication == null) { + throw new IllegalStateException(String.format("%s has null authentication handler!")); + } + if (authorization == null) { + throw new IllegalStateException(String.format("%s has null authorization handler!")); + } + + LOGGER.finer(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())); + } + LOGGER.info(String.format("%s authenticated identity: %s", serviceHandler.getServiceName(), authenticationData.getIdentityLabel())); + + final Authorization authorizationDecision = authorization.apply(authenticationData); + switch (authorizationDecision) { + case ACCEPT: + LOGGER.info("Connection accepted for " + authenticationData.getIdentityLabel()); + responseMessageFlag[0] = MessageFlags.ConnectionAccepted.getByteValue(); + break; + case REJECT: + LOGGER.info("Connection rejected for: " + authenticationData.getIdentityLabel()); + break; + default: + //got a big problem if this is the outcome. Someone forgot to update this switch-case + throw new RuntimeException("Unknown authorization decision for " + authenticationData.getIdentityLabel()); + } + } else { //version mismatch + LOGGER.warning(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())); + } finally { + final String authLabel = authenticationData != null ? authenticationData.getIdentityLabel() : "null"; + LOGGER.info("Sending connect response for " + authLabel); + connection.sendProtocolMessage(null, null, acceptResponseType, responseMessageFlag[0]) + .whenComplete((res, ex) -> { + //TODO: removing log statements due to known issue of locking up + if (ex != null) { + //LOGGER.severe(String.format("Sending connection response for %s threw exception (%s): %s", + // authLabel, ex.getClass().getCanonicalName(), ex.getMessage())); + } + else { + //LOGGER.info("Successfully sent connection response for: " + authLabel); + } + if (responseMessageFlag[0] != MessageFlags.ConnectionAccepted.getByteValue()) { + //LOGGER.info("Closing connection due to connection not being accepted..."); + connection.closeConnection(0); + } + }); + } + } + + @Override + protected ServerConnectionContinuationHandler onIncomingStream(ServerConnectionContinuation continuation, String operationName) { + final OperationContinuationHandlerContext operationContext = new OperationContinuationHandlerContext( + connection, continuation, authenticationData); + final Function registeredOperationHandlerFn = + serviceHandler.getOperationHandler(operationName); + if (registeredOperationHandlerFn != null) { + return registeredOperationHandlerFn.apply(operationContext); + } else { + return new ServerConnectionContinuationHandler(continuation) { + @Override + protected void onContinuationClosed() { + close(); + } + + @Override + protected void onContinuationMessage(List
headers, byte[] payload, MessageType messageType, int messageFlags) { + int responseMessageFlag = MessageFlags.TerminateStream.getByteValue(); + MessageType responseMessageType = MessageType.ApplicationError; + + String responsePayload = + "{ \"error\": \"Unsupported Operation\", " + + "\"message\": \"" + operationName + " is an unsupported operation.\" }"; + + Header contentTypeHeader = Header.createHeader(":content-type", "application/json"); + List
responseHeaders = new ArrayList<>(); + responseHeaders.add(contentTypeHeader); + + continuation.sendMessage(responseHeaders, responsePayload.getBytes(StandardCharsets.UTF_8), responseMessageType, responseMessageFlag); + } + }; + } + } +} 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 new file mode 100644 index 000000000..9dae94d6d --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/EchoTestServiceTests.java @@ -0,0 +1,302 @@ +package software.amazon.awssdk.eventstreamrpc; + +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.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.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.BiConsumer; + +public class EchoTestServiceTests { + static { + Log.initLoggingToFile(Log.LogLevel.Trace, "crt-EchoTestService.log"); + } + + final BiConsumer DO_ECHO_FN = (client, data) -> { + final EchoMessageRequest request = new EchoMessageRequest(); + request.setMessage(data); + final EchoMessageResponseHandler responseHandler = client.echoMessage(request, Optional.empty()); + EchoMessageResponse response = null; + try { + response = responseHandler.getResponse().get(); + } catch (InterruptedException | ExecutionException e) { + Assertions.fail(e); + } + Assertions.assertEquals(request.getMessage(), response.getMessage(), "Data echoed back not equivalent!"); + }; + + @Test + public void testInvokeEchoMessage() throws Exception { + final CompletableFuture clientErrorAfter = EchoTestServiceRunner.runLocalEchoTestServer((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 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.now()); + 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()); + } + } + + //@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 MessageData data = new MessageData(); + data.setEnumMessage(FruitEnum.PINEAPPLE); + DO_ECHO_FN.accept(client, data); + + try { + Thread.sleep(50); //sleep just so we're not completely pummeling the server? + } catch (InterruptedException e) { + e.printStackTrace(); + } + }, 1 << 17); + } + + public void futureCausesOperationError(final CompletableFuture future, Class clazz, String code) { + try { + future.get(); + } catch (ExecutionException e) { + final Throwable t = e.getCause(); + if (t == null) { + Assertions.fail("ExecutionException thrown has no CausedBy exception. Something else went wrong with future completion"); + } else if(!clazz.isInstance(t)) { + Assertions.fail("ExecutionException thrown has unexpected caused type", t); + } else { + final EventStreamOperationError error = (EventStreamOperationError)t; + Assertions.assertEquals(code, error.getErrorCode(), "Non-matching error code returned"); + } + } catch (InterruptedException e) { + Assertions.fail(e.getCause()); + } + } + + @Test + public void testInvokeErrorOperation() throws Exception { + final CompletableFuture clientErrorAfter = EchoTestServiceRunner.runLocalEchoTestServer((connection, client) -> { + final CauseServiceErrorResponseHandler responseHandler = client.causeServiceError(new CauseServiceErrorRequest(), Optional.empty()); + futureCausesOperationError(responseHandler.getResponse(), ServiceError.class, "ServiceError"); + + //after an error, perform another operation on the same connection that should still be open for business + final MessageData data = new MessageData(); + data.setStringMessage("Post error string message"); + 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()); + } + } + + @Test + public void testInvokeEchoStreamMessages() 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.now()); + 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); + } + + //after a streaming operation, perform another operation on the same connection + final MessageData data = new MessageData(); + data.setEnumMessage(FruitEnum.PINEAPPLE); + DO_ECHO_FN.accept(client, data); + + //now command the stream to close + final EchoStreamingMessage closeMsg = new EchoStreamingMessage(); + final MessageData dataClose = new MessageData(); + dataClose.setStringMessage("close"); //implementation of the close operation in test-codegen-model + closeMsg.setStreamMessage(dataClose); + responseHandler.sendStreamEvent(closeMsg); + try { + streamClosedFuture.get(5, TimeUnit.SECONDS); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + Assertions.fail(e); + } + }); + 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()); + } + } + + @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 + 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 { + 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(); + Assertions.assertTrue(t instanceof ServiceError); + final ServiceError error = (ServiceError)t; + Assertions.assertEquals("ServiceError", error.getErrorCode()); + } catch (InterruptedException e) { + Assertions.fail(e); + } catch (ExecutionException e) { + Assertions.fail(e); + } + + //after a streaming response error, perform another operation on the same + //connection that should still be open for business + final MessageData data = new MessageData(); + data.setStringMessage("Post stream error string message"); + 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()); + } + } +} 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 new file mode 100644 index 000000000..5b7932962 --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/IpcServerTests.java @@ -0,0 +1,310 @@ +package software.amazon.awssdk.eventstreamrpc; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.crt.Log; +import software.amazon.awssdk.crt.eventstream.ServerConnectionContinuationHandler; +import software.amazon.awssdk.crt.io.EventLoopGroup; +import software.amazon.awssdk.crt.io.SocketOptions; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; +import software.amazon.awssdk.eventstreamrpc.test.TestAuthNZHandlers; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.util.*; +import java.util.function.Function; + +/** + * Note: use different ports for different tests + */ +public class IpcServerTests { + private static final Random RANDOM = new Random(); //default instantiation uses time + public static int randomPort() { + return RANDOM.nextInt(65535-1024) + 1024; + } + static { + Log.initLoggingToFile(Log.LogLevel.Trace, "crt-IpcServerTests.log"); + } + + @Test + public void testStartStopIpcServer() { + 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 + protected EventStreamRPCServiceModel getServiceModel() { + return new EventStreamRPCServiceModel() { + @Override + public String getServiceName() { return "TestService"; } + + /** + * Retreives all operations on the service + * + * @return + */ + @Override + public Collection getAllOperations() { return new HashSet<>(); } + + @Override + protected Optional> getServiceClassType(String applicationModelType) { + return Optional.empty(); + } + + @Override + public OperationModelContext getOperationModelContext(String operationName) { + return null; + } + }; + } + + @Override + public Function getOperationHandler(String operationName) { + return null; + } + + @Override + public boolean hasHandlerForOperation(String operation) { return true; } + }; + 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(); + + 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(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @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 + protected EventStreamRPCServiceModel getServiceModel() { + return new EventStreamRPCServiceModel() { + @Override + public String getServiceName() { return "TestService"; } + + @Override + public Collection getAllOperations() { return new HashSet<>(); } + + @Override + protected Optional> getServiceClassType(String applicationModelType) { return Optional.empty(); } + + @Override + public OperationModelContext getOperationModelContext(String operationName) { return null; } + }; + } + + @Override + public Function getOperationHandler(String operationName) { + return null; + } + + @Override + public boolean hasHandlerForOperation(String operation) { return true; } + }; + 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, () -> { + ipcServer.runServer(); + }); + } + } + + @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 + protected EventStreamRPCServiceModel getServiceModel() { + return null; + } + + @Override + public Function getOperationHandler(String operationName) { + return null; + } + + @Override + public boolean hasHandlerForOperation(String operation) { return true; } //what'll trigger the validation failure + }; + 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(); + }); + } + } + + @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"); + + final EventStreamRPCServiceHandler handler = new EventStreamRPCServiceHandler() { + @Override + protected EventStreamRPCServiceModel getServiceModel() { + return new EventStreamRPCServiceModel() { + @Override + public String getServiceName() { return "TestService"; } + + @Override + public Collection getAllOperations() { return OPERATION_SET; } + + @Override + protected Optional> getServiceClassType(String applicationModelType) { return Optional.empty(); } + + @Override + public OperationModelContext getOperationModelContext(String operationName) { return null; } + }; + } + + @Override + public Function getOperationHandler(String operationName) { + return null; + } + + @Override + public boolean hasHandlerForOperation(String operation) { return false; } //what'll trigger the validation failure + }; + 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(); + }); + } + } + + @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"); + + final EventStreamRPCServiceHandler handler = new EventStreamRPCServiceHandler() { + @Override + protected EventStreamRPCServiceModel getServiceModel() { + return new EventStreamRPCServiceModel() { + @Override + public String getServiceName() { return "TestService"; } + + @Override + public Collection getAllOperations() { return new HashSet<>(); } + + @Override + protected Optional> getServiceClassType(String applicationModelType) { return Optional.empty(); } + + @Override + public OperationModelContext getOperationModelContext(String operationName) { return null; } + }; + } + + @Override + public Function getOperationHandler(String operationName) { + return null; + } + + @Override + public boolean hasHandlerForOperation(String operation) { return true; } + }; + //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(); + }); + } + } + + @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"); + + final EventStreamRPCServiceHandler handler = new EventStreamRPCServiceHandler() { + @Override + protected EventStreamRPCServiceModel getServiceModel() { + return new EventStreamRPCServiceModel() { + @Override + public String getServiceName() { return "TestService"; } + + @Override + public Collection getAllOperations() { return new HashSet<>(); } + + @Override + protected Optional> getServiceClassType(String applicationModelType) { return Optional.empty(); } + + @Override + public OperationModelContext getOperationModelContext(String operationName) { return null; } + }; + } + + @Override + public Function getOperationHandler(String operationName) { + return null; + } + + @Override + public boolean hasHandlerForOperation(String operation) { return true; } + }; + 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(); + }); + } + } +} 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 new file mode 100644 index 000000000..dc4fab20b --- /dev/null +++ b/sdk/greengrass/event-stream-rpc-server/src/test/java/software/amazon/awssdk/eventstreamrpc/OperationContinuationHandlerTests.java @@ -0,0 +1,10 @@ +package software.amazon.awssdk.eventstreamrpc; + +import org.junit.jupiter.api.Test; + +public class OperationContinuationHandlerTests { + + @Test + void testSyncOperationInvoke() { + } +} diff --git a/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/GreengrassCoreIPC.java b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/GreengrassCoreIPC.java index 0aa19dd3a..8de1e4011 100644 --- a/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/GreengrassCoreIPC.java +++ b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/GreengrassCoreIPC.java @@ -16,9 +16,11 @@ import software.amazon.awssdk.aws.greengrass.model.ListComponentsRequest; import software.amazon.awssdk.aws.greengrass.model.ListLocalDeploymentsRequest; import software.amazon.awssdk.aws.greengrass.model.ListNamedShadowsForThingRequest; +import software.amazon.awssdk.aws.greengrass.model.PauseComponentRequest; import software.amazon.awssdk.aws.greengrass.model.PublishToIoTCoreRequest; import software.amazon.awssdk.aws.greengrass.model.PublishToTopicRequest; import software.amazon.awssdk.aws.greengrass.model.RestartComponentRequest; +import software.amazon.awssdk.aws.greengrass.model.ResumeComponentRequest; import software.amazon.awssdk.aws.greengrass.model.SendConfigurationValidityReportRequest; import software.amazon.awssdk.aws.greengrass.model.StopComponentRequest; import software.amazon.awssdk.aws.greengrass.model.SubscribeToComponentUpdatesRequest; @@ -39,6 +41,9 @@ public interface GreengrassCoreIPC { SubscribeToIoTCoreResponseHandler subscribeToIoTCore(final SubscribeToIoTCoreRequest request, final Optional> streamResponseHandler); + ResumeComponentResponseHandler resumeComponent(final ResumeComponentRequest request, + final Optional> streamResponseHandler); + PublishToIoTCoreResponseHandler publishToIoTCore(final PublishToIoTCoreRequest request, final Optional> streamResponseHandler); @@ -120,6 +125,9 @@ ListLocalDeploymentsResponseHandler listLocalDeployments( StopComponentResponseHandler stopComponent(final StopComponentRequest request, final Optional> streamResponseHandler); + PauseComponentResponseHandler pauseComponent(final PauseComponentRequest request, + final Optional> streamResponseHandler); + CreateLocalDeploymentResponseHandler createLocalDeployment( final CreateLocalDeploymentRequest request, final Optional> streamResponseHandler); diff --git a/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/GreengrassCoreIPCClient.java b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/GreengrassCoreIPCClient.java index b7e4d583b..479a2cd44 100644 --- a/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/GreengrassCoreIPCClient.java +++ b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/GreengrassCoreIPCClient.java @@ -17,9 +17,11 @@ import software.amazon.awssdk.aws.greengrass.model.ListComponentsRequest; import software.amazon.awssdk.aws.greengrass.model.ListLocalDeploymentsRequest; import software.amazon.awssdk.aws.greengrass.model.ListNamedShadowsForThingRequest; +import software.amazon.awssdk.aws.greengrass.model.PauseComponentRequest; import software.amazon.awssdk.aws.greengrass.model.PublishToIoTCoreRequest; import software.amazon.awssdk.aws.greengrass.model.PublishToTopicRequest; import software.amazon.awssdk.aws.greengrass.model.RestartComponentRequest; +import software.amazon.awssdk.aws.greengrass.model.ResumeComponentRequest; import software.amazon.awssdk.aws.greengrass.model.SendConfigurationValidityReportRequest; import software.amazon.awssdk.aws.greengrass.model.StopComponentRequest; import software.amazon.awssdk.aws.greengrass.model.SubscribeToComponentUpdatesRequest; @@ -51,6 +53,13 @@ public SubscribeToIoTCoreResponseHandler subscribeToIoTCore( return new SubscribeToIoTCoreResponseHandler(doOperationInvoke(operationContext, request, streamResponseHandler)); } + @Override + public ResumeComponentResponseHandler resumeComponent(final ResumeComponentRequest request, + final Optional> streamResponseHandler) { + final ResumeComponentOperationContext operationContext = GreengrassCoreIPCServiceModel.getResumeComponentModelContext(); + return new ResumeComponentResponseHandler(doOperationInvoke(operationContext, request, streamResponseHandler)); + } + @Override public PublishToIoTCoreResponseHandler publishToIoTCore(final PublishToIoTCoreRequest request, final Optional> streamResponseHandler) { @@ -231,6 +240,13 @@ public StopComponentResponseHandler stopComponent(final StopComponentRequest req return new StopComponentResponseHandler(doOperationInvoke(operationContext, request, streamResponseHandler)); } + @Override + public PauseComponentResponseHandler pauseComponent(final PauseComponentRequest request, + final Optional> streamResponseHandler) { + final PauseComponentOperationContext operationContext = GreengrassCoreIPCServiceModel.getPauseComponentModelContext(); + return new PauseComponentResponseHandler(doOperationInvoke(operationContext, request, streamResponseHandler)); + } + @Override public CreateLocalDeploymentResponseHandler createLocalDeployment( final CreateLocalDeploymentRequest request, diff --git a/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/PauseComponentResponseHandler.java b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/PauseComponentResponseHandler.java new file mode 100644 index 000000000..723153b41 --- /dev/null +++ b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/PauseComponentResponseHandler.java @@ -0,0 +1,43 @@ +package software.amazon.awssdk.aws.greengrass; + +import java.lang.Override; +import java.lang.Void; +import java.util.concurrent.CompletableFuture; +import software.amazon.awssdk.aws.greengrass.model.PauseComponentResponse; +import software.amazon.awssdk.eventstreamrpc.OperationResponse; +import software.amazon.awssdk.eventstreamrpc.StreamResponse; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public final class PauseComponentResponseHandler implements StreamResponse { + private final OperationResponse operationResponse; + + public PauseComponentResponseHandler( + 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/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/ResumeComponentResponseHandler.java b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/ResumeComponentResponseHandler.java new file mode 100644 index 000000000..55b9589f1 --- /dev/null +++ b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/client/software/amazon/awssdk/aws/greengrass/ResumeComponentResponseHandler.java @@ -0,0 +1,43 @@ +package software.amazon.awssdk.aws.greengrass; + +import java.lang.Override; +import java.lang.Void; +import java.util.concurrent.CompletableFuture; +import software.amazon.awssdk.aws.greengrass.model.ResumeComponentResponse; +import software.amazon.awssdk.eventstreamrpc.OperationResponse; +import software.amazon.awssdk.eventstreamrpc.StreamResponse; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public final class ResumeComponentResponseHandler implements StreamResponse { + private final OperationResponse operationResponse; + + public ResumeComponentResponseHandler( + 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/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/GreengrassCoreIPCServiceModel.java b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/GreengrassCoreIPCServiceModel.java index 56196aeef..0571daf55 100644 --- a/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/GreengrassCoreIPCServiceModel.java +++ b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/GreengrassCoreIPCServiceModel.java @@ -53,6 +53,8 @@ import software.amazon.awssdk.aws.greengrass.model.ListNamedShadowsForThingResponse; import software.amazon.awssdk.aws.greengrass.model.LocalDeployment; import software.amazon.awssdk.aws.greengrass.model.MQTTMessage; +import software.amazon.awssdk.aws.greengrass.model.PauseComponentRequest; +import software.amazon.awssdk.aws.greengrass.model.PauseComponentResponse; import software.amazon.awssdk.aws.greengrass.model.PostComponentUpdateEvent; import software.amazon.awssdk.aws.greengrass.model.PreComponentUpdateEvent; import software.amazon.awssdk.aws.greengrass.model.PublishMessage; @@ -66,6 +68,8 @@ import software.amazon.awssdk.aws.greengrass.model.ResourceNotFoundError; import software.amazon.awssdk.aws.greengrass.model.RestartComponentRequest; import software.amazon.awssdk.aws.greengrass.model.RestartComponentResponse; +import software.amazon.awssdk.aws.greengrass.model.ResumeComponentRequest; +import software.amazon.awssdk.aws.greengrass.model.ResumeComponentResponse; import software.amazon.awssdk.aws.greengrass.model.RunWithInfo; import software.amazon.awssdk.aws.greengrass.model.SecretValue; import software.amazon.awssdk.aws.greengrass.model.SendConfigurationValidityReportRequest; @@ -84,6 +88,7 @@ import software.amazon.awssdk.aws.greengrass.model.SubscribeToValidateConfigurationUpdatesRequest; import software.amazon.awssdk.aws.greengrass.model.SubscribeToValidateConfigurationUpdatesResponse; import software.amazon.awssdk.aws.greengrass.model.SubscriptionResponseMessage; +import software.amazon.awssdk.aws.greengrass.model.SystemResourceLimits; import software.amazon.awssdk.aws.greengrass.model.UnauthorizedError; import software.amazon.awssdk.aws.greengrass.model.UpdateConfigurationRequest; import software.amazon.awssdk.aws.greengrass.model.UpdateConfigurationResponse; @@ -116,6 +121,10 @@ public class GreengrassCoreIPCServiceModel extends EventStreamRPCServiceModel { private static final SubscribeToIoTCoreOperationContext _SUBSCRIBE_TO_IOT_CORE_OPERATION_CONTEXT = new SubscribeToIoTCoreOperationContext(); + public static final String RESUME_COMPONENT = SERVICE_NAMESPACE + "#" + "ResumeComponent"; + + private static final ResumeComponentOperationContext _RESUME_COMPONENT_OPERATION_CONTEXT = new ResumeComponentOperationContext(); + public static final String PUBLISH_TO_IOT_CORE = SERVICE_NAMESPACE + "#" + "PublishToIoTCore"; private static final PublishToIoTCoreOperationContext _PUBLISH_TO_IOT_CORE_OPERATION_CONTEXT = new PublishToIoTCoreOperationContext(); @@ -212,6 +221,10 @@ public class GreengrassCoreIPCServiceModel extends EventStreamRPCServiceModel { private static final StopComponentOperationContext _STOP_COMPONENT_OPERATION_CONTEXT = new StopComponentOperationContext(); + public static final String PAUSE_COMPONENT = SERVICE_NAMESPACE + "#" + "PauseComponent"; + + private static final PauseComponentOperationContext _PAUSE_COMPONENT_OPERATION_CONTEXT = new PauseComponentOperationContext(); + public static final String CREATE_LOCAL_DEPLOYMENT = SERVICE_NAMESPACE + "#" + "CreateLocalDeployment"; private static final CreateLocalDeploymentOperationContext _CREATE_LOCAL_DEPLOYMENT_OPERATION_CONTEXT = new CreateLocalDeploymentOperationContext(); @@ -219,6 +232,8 @@ public class GreengrassCoreIPCServiceModel extends EventStreamRPCServiceModel { static { SERVICE_OPERATION_MODEL_MAP.put(SUBSCRIBE_TO_IOT_CORE, _SUBSCRIBE_TO_IOT_CORE_OPERATION_CONTEXT); SERVICE_OPERATION_SET.add(SUBSCRIBE_TO_IOT_CORE); + SERVICE_OPERATION_MODEL_MAP.put(RESUME_COMPONENT, _RESUME_COMPONENT_OPERATION_CONTEXT); + SERVICE_OPERATION_SET.add(RESUME_COMPONENT); SERVICE_OPERATION_MODEL_MAP.put(PUBLISH_TO_IOT_CORE, _PUBLISH_TO_IOT_CORE_OPERATION_CONTEXT); SERVICE_OPERATION_SET.add(PUBLISH_TO_IOT_CORE); SERVICE_OPERATION_MODEL_MAP.put(SUBSCRIBE_TO_CONFIGURATION_UPDATE, _SUBSCRIBE_TO_CONFIGURATION_UPDATE_OPERATION_CONTEXT); @@ -267,17 +282,21 @@ public class GreengrassCoreIPCServiceModel extends EventStreamRPCServiceModel { SERVICE_OPERATION_SET.add(LIST_LOCAL_DEPLOYMENTS); SERVICE_OPERATION_MODEL_MAP.put(STOP_COMPONENT, _STOP_COMPONENT_OPERATION_CONTEXT); SERVICE_OPERATION_SET.add(STOP_COMPONENT); + SERVICE_OPERATION_MODEL_MAP.put(PAUSE_COMPONENT, _PAUSE_COMPONENT_OPERATION_CONTEXT); + SERVICE_OPERATION_SET.add(PAUSE_COMPONENT); SERVICE_OPERATION_MODEL_MAP.put(CREATE_LOCAL_DEPLOYMENT, _CREATE_LOCAL_DEPLOYMENT_OPERATION_CONTEXT); SERVICE_OPERATION_SET.add(CREATE_LOCAL_DEPLOYMENT); SERVICE_OBJECT_MODEL_MAP.put(SubscribeToIoTCoreRequest.APPLICATION_MODEL_TYPE, SubscribeToIoTCoreRequest.class); SERVICE_OBJECT_MODEL_MAP.put(SubscribeToIoTCoreResponse.APPLICATION_MODEL_TYPE, SubscribeToIoTCoreResponse.class); SERVICE_OBJECT_MODEL_MAP.put(ServiceError.APPLICATION_MODEL_TYPE, ServiceError.class); SERVICE_OBJECT_MODEL_MAP.put(UnauthorizedError.APPLICATION_MODEL_TYPE, UnauthorizedError.class); + SERVICE_OBJECT_MODEL_MAP.put(ResumeComponentRequest.APPLICATION_MODEL_TYPE, ResumeComponentRequest.class); + SERVICE_OBJECT_MODEL_MAP.put(ResumeComponentResponse.APPLICATION_MODEL_TYPE, ResumeComponentResponse.class); + SERVICE_OBJECT_MODEL_MAP.put(ResourceNotFoundError.APPLICATION_MODEL_TYPE, ResourceNotFoundError.class); SERVICE_OBJECT_MODEL_MAP.put(PublishToIoTCoreRequest.APPLICATION_MODEL_TYPE, PublishToIoTCoreRequest.class); SERVICE_OBJECT_MODEL_MAP.put(PublishToIoTCoreResponse.APPLICATION_MODEL_TYPE, PublishToIoTCoreResponse.class); SERVICE_OBJECT_MODEL_MAP.put(SubscribeToConfigurationUpdateRequest.APPLICATION_MODEL_TYPE, SubscribeToConfigurationUpdateRequest.class); SERVICE_OBJECT_MODEL_MAP.put(SubscribeToConfigurationUpdateResponse.APPLICATION_MODEL_TYPE, SubscribeToConfigurationUpdateResponse.class); - SERVICE_OBJECT_MODEL_MAP.put(ResourceNotFoundError.APPLICATION_MODEL_TYPE, ResourceNotFoundError.class); SERVICE_OBJECT_MODEL_MAP.put(DeleteThingShadowRequest.APPLICATION_MODEL_TYPE, DeleteThingShadowRequest.class); SERVICE_OBJECT_MODEL_MAP.put(DeleteThingShadowResponse.APPLICATION_MODEL_TYPE, DeleteThingShadowResponse.class); SERVICE_OBJECT_MODEL_MAP.put(InvalidArgumentsError.APPLICATION_MODEL_TYPE, InvalidArgumentsError.class); @@ -327,6 +346,8 @@ public class GreengrassCoreIPCServiceModel extends EventStreamRPCServiceModel { SERVICE_OBJECT_MODEL_MAP.put(ListLocalDeploymentsResponse.APPLICATION_MODEL_TYPE, ListLocalDeploymentsResponse.class); SERVICE_OBJECT_MODEL_MAP.put(StopComponentRequest.APPLICATION_MODEL_TYPE, StopComponentRequest.class); SERVICE_OBJECT_MODEL_MAP.put(StopComponentResponse.APPLICATION_MODEL_TYPE, StopComponentResponse.class); + SERVICE_OBJECT_MODEL_MAP.put(PauseComponentRequest.APPLICATION_MODEL_TYPE, PauseComponentRequest.class); + SERVICE_OBJECT_MODEL_MAP.put(PauseComponentResponse.APPLICATION_MODEL_TYPE, PauseComponentResponse.class); SERVICE_OBJECT_MODEL_MAP.put(CreateLocalDeploymentRequest.APPLICATION_MODEL_TYPE, CreateLocalDeploymentRequest.class); SERVICE_OBJECT_MODEL_MAP.put(CreateLocalDeploymentResponse.APPLICATION_MODEL_TYPE, CreateLocalDeploymentResponse.class); SERVICE_OBJECT_MODEL_MAP.put(InvalidRecipeDirectoryPathError.APPLICATION_MODEL_TYPE, InvalidRecipeDirectoryPathError.class); @@ -355,6 +376,7 @@ public class GreengrassCoreIPCServiceModel extends EventStreamRPCServiceModel { SERVICE_OBJECT_MODEL_MAP.put(PreComponentUpdateEvent.APPLICATION_MODEL_TYPE, PreComponentUpdateEvent.class); SERVICE_OBJECT_MODEL_MAP.put(PostComponentUpdateEvent.APPLICATION_MODEL_TYPE, PostComponentUpdateEvent.class); SERVICE_OBJECT_MODEL_MAP.put(RunWithInfo.APPLICATION_MODEL_TYPE, RunWithInfo.class); + SERVICE_OBJECT_MODEL_MAP.put(SystemResourceLimits.APPLICATION_MODEL_TYPE, SystemResourceLimits.class); } private GreengrassCoreIPCServiceModel() { @@ -373,6 +395,10 @@ public static SubscribeToIoTCoreOperationContext getSubscribeToIoTCoreModelConte return _SUBSCRIBE_TO_IOT_CORE_OPERATION_CONTEXT; } + public static ResumeComponentOperationContext getResumeComponentModelContext() { + return _RESUME_COMPONENT_OPERATION_CONTEXT; + } + public static PublishToIoTCoreOperationContext getPublishToIoTCoreModelContext() { return _PUBLISH_TO_IOT_CORE_OPERATION_CONTEXT; } @@ -474,6 +500,10 @@ public static StopComponentOperationContext getStopComponentModelContext() { return _STOP_COMPONENT_OPERATION_CONTEXT; } + public static PauseComponentOperationContext getPauseComponentModelContext() { + return _PAUSE_COMPONENT_OPERATION_CONTEXT; + } + public static CreateLocalDeploymentOperationContext getCreateLocalDeploymentModelContext() { return _CREATE_LOCAL_DEPLOYMENT_OPERATION_CONTEXT; } diff --git a/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/PauseComponentOperationContext.java b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/PauseComponentOperationContext.java new file mode 100644 index 000000000..3c6cc476b --- /dev/null +++ b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/PauseComponentOperationContext.java @@ -0,0 +1,62 @@ +package software.amazon.awssdk.aws.greengrass; + +import java.lang.Class; +import java.lang.Override; +import java.lang.String; +import java.util.Optional; +import software.amazon.awssdk.aws.greengrass.model.PauseComponentRequest; +import software.amazon.awssdk.aws.greengrass.model.PauseComponentResponse; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCServiceModel; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public class PauseComponentOperationContext implements OperationModelContext { + @Override + public EventStreamRPCServiceModel getServiceModel() { + return GreengrassCoreIPCServiceModel.getInstance(); + } + + @Override + public String getOperationName() { + return GreengrassCoreIPCServiceModel.PAUSE_COMPONENT; + } + + @Override + public Class getRequestTypeClass() { + return PauseComponentRequest.class; + } + + @Override + public Class getResponseTypeClass() { + return PauseComponentResponse.class; + } + + @Override + public String getRequestApplicationModelType() { + return PauseComponentRequest.APPLICATION_MODEL_TYPE; + } + + @Override + public String getResponseApplicationModelType() { + return PauseComponentResponse.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/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/ResumeComponentOperationContext.java b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/ResumeComponentOperationContext.java new file mode 100644 index 000000000..f22eeec0e --- /dev/null +++ b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/ResumeComponentOperationContext.java @@ -0,0 +1,62 @@ +package software.amazon.awssdk.aws.greengrass; + +import java.lang.Class; +import java.lang.Override; +import java.lang.String; +import java.util.Optional; +import software.amazon.awssdk.aws.greengrass.model.ResumeComponentRequest; +import software.amazon.awssdk.aws.greengrass.model.ResumeComponentResponse; +import software.amazon.awssdk.eventstreamrpc.EventStreamRPCServiceModel; +import software.amazon.awssdk.eventstreamrpc.OperationModelContext; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public class ResumeComponentOperationContext implements OperationModelContext { + @Override + public EventStreamRPCServiceModel getServiceModel() { + return GreengrassCoreIPCServiceModel.getInstance(); + } + + @Override + public String getOperationName() { + return GreengrassCoreIPCServiceModel.RESUME_COMPONENT; + } + + @Override + public Class getRequestTypeClass() { + return ResumeComponentRequest.class; + } + + @Override + public Class getResponseTypeClass() { + return ResumeComponentResponse.class; + } + + @Override + public String getRequestApplicationModelType() { + return ResumeComponentRequest.APPLICATION_MODEL_TYPE; + } + + @Override + public String getResponseApplicationModelType() { + return ResumeComponentResponse.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/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/PauseComponentRequest.java b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/PauseComponentRequest.java new file mode 100644 index 000000000..08bfa36c5 --- /dev/null +++ b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/PauseComponentRequest.java @@ -0,0 +1,66 @@ +package software.amazon.awssdk.aws.greengrass.model; + +import com.google.gson.annotations.Expose; +import java.lang.Object; +import java.lang.Override; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public class PauseComponentRequest implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "aws.greengrass#PauseComponentRequest"; + + public static final PauseComponentRequest VOID; + + static { + VOID = new PauseComponentRequest() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + @Expose( + serialize = true, + deserialize = true + ) + private Optional componentName; + + public PauseComponentRequest() { + this.componentName = Optional.empty(); + } + + public String getComponentName() { + if (componentName.isPresent()) { + return componentName.get(); + } + return null; + } + + public void setComponentName(final String componentName) { + this.componentName = Optional.ofNullable(componentName); + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof PauseComponentRequest)) return false; + if (this == rhs) return true; + final PauseComponentRequest other = (PauseComponentRequest)rhs; + boolean isEquals = true; + isEquals = isEquals && this.componentName.equals(other.componentName); + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(componentName); + } +} diff --git a/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/PauseComponentResponse.java b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/PauseComponentResponse.java new file mode 100644 index 000000000..3a43e5112 --- /dev/null +++ b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/PauseComponentResponse.java @@ -0,0 +1,45 @@ +package software.amazon.awssdk.aws.greengrass.model; + +import java.lang.Object; +import java.lang.Override; +import java.lang.String; +import java.util.Objects; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public class PauseComponentResponse implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "aws.greengrass#PauseComponentResponse"; + + public static final PauseComponentResponse VOID; + + static { + VOID = new PauseComponentResponse() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + public PauseComponentResponse() { + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof PauseComponentResponse)) return false; + if (this == rhs) return true; + final PauseComponentResponse other = (PauseComponentResponse)rhs; + boolean isEquals = true; + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(); + } +} diff --git a/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/ResumeComponentRequest.java b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/ResumeComponentRequest.java new file mode 100644 index 000000000..d963eb929 --- /dev/null +++ b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/ResumeComponentRequest.java @@ -0,0 +1,66 @@ +package software.amazon.awssdk.aws.greengrass.model; + +import com.google.gson.annotations.Expose; +import java.lang.Object; +import java.lang.Override; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public class ResumeComponentRequest implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "aws.greengrass#ResumeComponentRequest"; + + public static final ResumeComponentRequest VOID; + + static { + VOID = new ResumeComponentRequest() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + @Expose( + serialize = true, + deserialize = true + ) + private Optional componentName; + + public ResumeComponentRequest() { + this.componentName = Optional.empty(); + } + + public String getComponentName() { + if (componentName.isPresent()) { + return componentName.get(); + } + return null; + } + + public void setComponentName(final String componentName) { + this.componentName = Optional.ofNullable(componentName); + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof ResumeComponentRequest)) return false; + if (this == rhs) return true; + final ResumeComponentRequest other = (ResumeComponentRequest)rhs; + boolean isEquals = true; + isEquals = isEquals && this.componentName.equals(other.componentName); + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(componentName); + } +} diff --git a/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/ResumeComponentResponse.java b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/ResumeComponentResponse.java new file mode 100644 index 000000000..a0186f283 --- /dev/null +++ b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/ResumeComponentResponse.java @@ -0,0 +1,45 @@ +package software.amazon.awssdk.aws.greengrass.model; + +import java.lang.Object; +import java.lang.Override; +import java.lang.String; +import java.util.Objects; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public class ResumeComponentResponse implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "aws.greengrass#ResumeComponentResponse"; + + public static final ResumeComponentResponse VOID; + + static { + VOID = new ResumeComponentResponse() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + public ResumeComponentResponse() { + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof ResumeComponentResponse)) return false; + if (this == rhs) return true; + final ResumeComponentResponse other = (ResumeComponentResponse)rhs; + boolean isEquals = true; + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(); + } +} diff --git a/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/RunWithInfo.java b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/RunWithInfo.java index 4e537fdfb..b7f2a8f8a 100644 --- a/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/RunWithInfo.java +++ b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/RunWithInfo.java @@ -28,8 +28,15 @@ public boolean isVoid() { ) private Optional posixUser; + @Expose( + serialize = true, + deserialize = true + ) + private Optional systemResourceLimits; + public RunWithInfo() { this.posixUser = Optional.empty(); + this.systemResourceLimits = Optional.empty(); } public String getPosixUser() { @@ -43,6 +50,17 @@ public void setPosixUser(final String posixUser) { this.posixUser = Optional.ofNullable(posixUser); } + public SystemResourceLimits getSystemResourceLimits() { + if (systemResourceLimits.isPresent()) { + return systemResourceLimits.get(); + } + return null; + } + + public void setSystemResourceLimits(final SystemResourceLimits systemResourceLimits) { + this.systemResourceLimits = Optional.ofNullable(systemResourceLimits); + } + @Override public String getApplicationModelType() { return APPLICATION_MODEL_TYPE; @@ -56,11 +74,12 @@ public boolean equals(Object rhs) { final RunWithInfo other = (RunWithInfo)rhs; boolean isEquals = true; isEquals = isEquals && this.posixUser.equals(other.posixUser); + isEquals = isEquals && this.systemResourceLimits.equals(other.systemResourceLimits); return isEquals; } @Override public int hashCode() { - return Objects.hash(posixUser); + return Objects.hash(posixUser, systemResourceLimits); } } diff --git a/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/SystemResourceLimits.java b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/SystemResourceLimits.java new file mode 100644 index 000000000..95906219f --- /dev/null +++ b/sdk/greengrass/greengrass-client/src/event-stream-rpc-java/model/software/amazon/awssdk/aws/greengrass/model/SystemResourceLimits.java @@ -0,0 +1,87 @@ +package software.amazon.awssdk.aws.greengrass.model; + +import com.google.gson.annotations.Expose; +import java.lang.Double; +import java.lang.Long; +import java.lang.Object; +import java.lang.Override; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; +import software.amazon.awssdk.eventstreamrpc.model.EventStreamJsonMessage; + +public class SystemResourceLimits implements EventStreamJsonMessage { + public static final String APPLICATION_MODEL_TYPE = "aws.greengrass#SystemResourceLimits"; + + public static final SystemResourceLimits VOID; + + static { + VOID = new SystemResourceLimits() { + @Override + public boolean isVoid() { + return true; + } + }; + } + + @Expose( + serialize = true, + deserialize = true + ) + private Optional memory; + + @Expose( + serialize = true, + deserialize = true + ) + private Optional cpus; + + public SystemResourceLimits() { + this.memory = Optional.empty(); + this.cpus = Optional.empty(); + } + + public Long getMemory() { + if (memory.isPresent()) { + return memory.get(); + } + return null; + } + + public void setMemory(final Long memory) { + this.memory = Optional.ofNullable(memory); + } + + public Double getCpus() { + if (cpus.isPresent()) { + return cpus.get(); + } + return null; + } + + public void setCpus(final Double cpus) { + this.cpus = Optional.ofNullable(cpus); + } + + @Override + public String getApplicationModelType() { + return APPLICATION_MODEL_TYPE; + } + + @Override + public boolean equals(Object rhs) { + if (rhs == null) return false; + if (!(rhs instanceof SystemResourceLimits)) return false; + if (this == rhs) return true; + final SystemResourceLimits other = (SystemResourceLimits)rhs; + boolean isEquals = true; + isEquals = isEquals && this.memory.equals(other.memory); + isEquals = isEquals && this.cpus.equals(other.cpus); + return isEquals; + } + + @Override + public int hashCode() { + return Objects.hash(memory, cpus); + } +}