Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(uat): add core device discover to SDK-based client #395

Merged
merged 8 commits into from
Aug 22, 2023
6 changes: 5 additions & 1 deletion uat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,11 @@ mvn javadoc:javadoc
```
The main html-file will be located in each module by path **target/site/apidocs/index.html**

See individual README.md files for Python and C clients.

## Limitations
MQTT clients based on IoT Device SDK for Java v2, mosquitto C, Paho Java, Paho Python do no provide API to get information from PUBREC/PUBREL/PUBCOMP packages used when messages published with QoS 2.

Not all features of MQTT v5.0 have been implemented in clients and are supported by gRPC proto and the control as was requested, these are not bugs but designed by requirement.
Not all features of MQTT v5.0 have been implemented in clients and are supported by gRPC proto and the control as was requested, these are not bugs but designed by requirement.

Discovery of Core device broker feature is implemented only in the client based on AWS IoT device SDK library.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package com.aws.greengrass.testing.mqtt5.client;

import com.aws.greengrass.testing.mqtt.client.CoreDeviceDiscoveryReply;
import com.aws.greengrass.testing.mqtt.client.CoreDeviceDiscoveryRequest;
import com.aws.greengrass.testing.mqtt5.client.exceptions.DiscoveryException;

/**
* Interface of discovery client.
*/
public interface DiscoveryClient {

/**
* Does discovery of Core device broker.
*
* @param request the request
* @return formatted gRPC response
* @throws DiscoveryException on errors
*/
CoreDeviceDiscoveryReply discoveryCoreDevice(CoreDeviceDiscoveryRequest request) throws DiscoveryException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ public interface GRPCLink {
* Handle all gRPC requests received from control.
*
* @param mqttLib MQTT library
* @param discoveryClient the discovery client
* @return shutdown reason as received from control or null
* @throws GRPCException on errors
* @throws InterruptedException when thread has been interrupted
*/
String handleRequests(@NonNull MqttLib mqttLib) throws GRPCException, InterruptedException;
String handleRequests(@NonNull MqttLib mqttLib, @NonNull DiscoveryClient discoveryClient)
throws GRPCException, InterruptedException;

/**
* Unregister agent from control.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package com.aws.greengrass.testing.mqtt5.client;

import com.aws.greengrass.testing.mqtt5.client.discover.DiscoveryClientImpl;
import com.aws.greengrass.testing.mqtt5.client.exceptions.ClientException;
import com.aws.greengrass.testing.mqtt5.client.grpc.GRPCLibImpl;
import com.aws.greengrass.testing.mqtt5.client.sdkmqtt.MqttLibImpl;
Expand Down Expand Up @@ -107,7 +108,7 @@ private static void doAll(String... args) throws Exception {
GRPCLink link = gprcLib.makeLink(arguments.getAgentId(), arguments.getHosts(), arguments.getPort());

try (MqttLib mqttLib = new MqttLibImpl()) {
String reason = link.handleRequests(mqttLib);
String reason = link.handleRequests(mqttLib, new DiscoveryClientImpl());
link.shutdown(reason);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package com.aws.greengrass.testing.mqtt5.client.discover;

import com.aws.greengrass.testing.mqtt.client.CoreDeviceConnectivityInfo;
import com.aws.greengrass.testing.mqtt.client.CoreDeviceDiscoveryReply;
import com.aws.greengrass.testing.mqtt.client.CoreDeviceDiscoveryRequest;
import com.aws.greengrass.testing.mqtt.client.CoreDeviceGroup;
import com.aws.greengrass.testing.mqtt5.client.exceptions.DiscoveryException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import software.amazon.awssdk.crt.io.SocketOptions;
import software.amazon.awssdk.crt.io.TlsContextOptions;
import software.amazon.awssdk.iot.discovery.DiscoveryClient;
import software.amazon.awssdk.iot.discovery.DiscoveryClientConfig;
import software.amazon.awssdk.iot.discovery.model.ConnectivityInfo;
import software.amazon.awssdk.iot.discovery.model.DiscoverResponse;
import software.amazon.awssdk.iot.discovery.model.GGCore;
import software.amazon.awssdk.iot.discovery.model.GGGroup;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
* Implementation of discovery client.
*/
public class DiscoveryClientImpl implements com.aws.greengrass.testing.mqtt5.client.DiscoveryClient {
private static final Logger logger = LogManager.getLogger(DiscoveryClientImpl.class);

@Override
public CoreDeviceDiscoveryReply discoveryCoreDevice(CoreDeviceDiscoveryRequest request)
throws DiscoveryException {
try (SocketOptions socketOptions = new SocketOptions();
TlsContextOptions tlsOptions = TlsContextOptions.createWithMtls(request.getCert(), request.getKey())
.withCertificateAuthority(request.getCa())
.withAlpnList(DiscoveryClient.TLS_EXT_ALPN);
DiscoveryClientConfig config = new DiscoveryClientConfig(tlsOptions, socketOptions, request.getRegion(),
1, null);
DiscoveryClient client = new DiscoveryClient(config)) {
CompletableFuture<DiscoverResponse> discoverFuture = client.discover(request.getThingName());
try {
DiscoverResponse response = discoverFuture.get(request.getTimeout(), TimeUnit.SECONDS);
return convertResponseToReply(response);
} catch (InterruptedException | ExecutionException | TimeoutException ex) {
logger.atError().withThrowable(ex).log("Failed during discover");
throw new DiscoveryException("Could not do discovery", ex);
}
}
}

private CoreDeviceDiscoveryReply convertResponseToReply(final DiscoverResponse response)
throws DiscoveryException {
if (response == null) {
throw new DiscoveryException("Discovery response is missing");
}

final List<GGGroup> groups = response.getGGGroups();
if (groups == null || groups.isEmpty() || groups.get(0) == null) {
throw new DiscoveryException("Groups are missing in discovery response");
}

CoreDeviceDiscoveryReply.Builder builder = CoreDeviceDiscoveryReply.newBuilder();
for (final GGGroup group : groups) {
List<String> ca = group.getCAs();
logger.atInfo().log("Discovered groupId {} with {} CA", group.getGGGroupId(), ca.size());
CoreDeviceGroup.Builder groupBuiler = CoreDeviceGroup.newBuilder();
groupBuiler.addAllCaList(ca);

for (final GGCore core : group.getCores()) {
logger.atInfo().log("Discovered Core with thing Arn {}", core.getThingArn());
for (final ConnectivityInfo ci : core.getConnectivity()) {
logger.atInfo().log("Discovered connectivity info: id {} host {} port {}", ci.getId(),
ci.getHostAddress(), ci.getPortNumber());

CoreDeviceConnectivityInfo cdc = CoreDeviceConnectivityInfo.newBuilder()
.setHost(ci.getHostAddress())
.setPort(ci.getPortNumber())
.build();

groupBuiler.addConnectivityInfoList(cdc);
}
}
builder.addGroupList(groupBuiler.build());
}

return builder.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package com.aws.greengrass.testing.mqtt5.client.exceptions;

/**
* Client's exception related to discovery parts.
*/
public class DiscoveryException extends ClientException {
private static final long serialVersionUID = -2081564070408021325L;

public DiscoveryException() {
super();
}

public DiscoveryException(String message) {
super(message);
}

public DiscoveryException(String message, Throwable cause) {
super(message, cause);
}

public DiscoveryException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}

public DiscoveryException(Throwable cause) {
super(cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

package com.aws.greengrass.testing.mqtt5.client.grpc;

import com.aws.greengrass.testing.mqtt.client.CoreDeviceDiscoveryReply;
import com.aws.greengrass.testing.mqtt.client.CoreDeviceDiscoveryRequest;
import com.aws.greengrass.testing.mqtt.client.Empty;
import com.aws.greengrass.testing.mqtt.client.Mqtt5ConnAck;
import com.aws.greengrass.testing.mqtt.client.Mqtt5Message;
Expand All @@ -23,9 +25,11 @@
import com.aws.greengrass.testing.mqtt.client.MqttUnsubscribeRequest;
import com.aws.greengrass.testing.mqtt.client.ShutdownRequest;
import com.aws.greengrass.testing.mqtt.client.TLSSettings;
import com.aws.greengrass.testing.mqtt5.client.DiscoveryClient;
import com.aws.greengrass.testing.mqtt5.client.GRPCClient;
import com.aws.greengrass.testing.mqtt5.client.MqttConnection;
import com.aws.greengrass.testing.mqtt5.client.MqttLib;
import com.aws.greengrass.testing.mqtt5.client.exceptions.DiscoveryException;
import com.aws.greengrass.testing.mqtt5.client.exceptions.MqttException;
import io.grpc.Grpc;
import io.grpc.InsecureServerCredentials;
Expand All @@ -49,6 +53,8 @@ class GRPCControlServer {

private static final String CONNECTION_WITH_DOES_NOT_FOUND = "connection with id {} doesn't found";
private static final String CONNECTION_DOES_NOT_FOUND = "connection doesn't found";
private static final String EMPTY_CERTIFICATE = "empty certificate";
private static final String EMPTY_PRIVATE_KEY = "empty private key";

private static final int TIMEOUT_MIN = 1;

Expand Down Expand Up @@ -77,6 +83,7 @@ class GRPCControlServer {
private final int boundPort;

private MqttLib mqttLib;
private DiscoveryClient discoveryClient;
private String shutdownReason;


Expand Down Expand Up @@ -201,18 +208,18 @@ public void createMqttConnection(MqttConnectRequest request,

String cert = tls.getCert();
if (cert == null || cert.isEmpty()) {
logger.atWarn().log("empty certificate");
logger.atWarn().log(EMPTY_CERTIFICATE);
responseObserver.onError(Status.INVALID_ARGUMENT
.withDescription("empty certificate")
.withDescription(EMPTY_CERTIFICATE)
.asRuntimeException());
return;
}

String key = tls.getKey();
if (key == null || key.isEmpty()) {
logger.atWarn().log("empty private key");
logger.atWarn().log(EMPTY_PRIVATE_KEY);
responseObserver.onError(Status.INVALID_ARGUMENT
.withDescription("empty private key")
.withDescription(EMPTY_PRIVATE_KEY)
.asRuntimeException());
return;
}
Expand Down Expand Up @@ -589,6 +596,82 @@ public void unsubscribeMqtt(MqttUnsubscribeRequest request,
responseObserver.onNext(builder.build());
responseObserver.onCompleted();
}

/**
* Handler of DiscoveryCoreDevice gRPC call.
*
* @param request incoming request
* @param responseObserver response control
*/
@Override
public void discoveryCoreDevice(CoreDeviceDiscoveryRequest request,
StreamObserver<CoreDeviceDiscoveryReply> responseObserver) {
int timeout = request.getTimeout();
if (timeout < TIMEOUT_MIN) {
logger.atWarn().log("invalid unsubscribe timeout {}, must be >= {}", timeout, TIMEOUT_MIN);
responseObserver.onError(Status.INVALID_ARGUMENT
.withDescription("invalid unsubscribe timeout, must be >= 1")
.asRuntimeException());
return;
}

final String ca = request.getCa();
if (ca == null || ca.isEmpty()) {
logger.atWarn().log("empty CA");
responseObserver.onError(Status.INVALID_ARGUMENT
.withDescription("empty CA")
.asRuntimeException());
return;
}

final String cert = request.getCert();
if (cert == null || cert.isEmpty()) {
logger.atWarn().log(EMPTY_CERTIFICATE);
responseObserver.onError(Status.INVALID_ARGUMENT
.withDescription(EMPTY_CERTIFICATE)
.asRuntimeException());
return;
}

final String key = request.getKey();
if (key == null || key.isEmpty()) {
logger.atWarn().log(EMPTY_PRIVATE_KEY);
responseObserver.onError(Status.INVALID_ARGUMENT
.withDescription(EMPTY_PRIVATE_KEY)
.asRuntimeException());
return;
}

final String thingName = request.getThingName();
if (thingName == null || thingName.isEmpty()) {
logger.atWarn().log("empty thing name");
responseObserver.onError(Status.INVALID_ARGUMENT
.withDescription("empty thing name")
.asRuntimeException());
return;
}

final String region = request.getRegion();
if (region == null || region.isEmpty()) {
logger.atWarn().log("empty region");
responseObserver.onError(Status.INVALID_ARGUMENT
.withDescription("empty region")
.asRuntimeException());
return;
}

CoreDeviceDiscoveryReply reply;
try {
reply = discoveryClient.discoveryCoreDevice(request);
} catch (DiscoveryException ex) {
logger.atError().withThrowable(ex).log("exception during discovery");
responseObserver.onError(ex);
return;
}

responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}

/**
Expand Down Expand Up @@ -636,8 +719,9 @@ public String getShutdownReason() {
*
* @param mqttLib reference to MQTT side of the client to handler incoming requests
*/
public void waiting(MqttLib mqttLib) throws InterruptedException {
public void waiting(MqttLib mqttLib, DiscoveryClient discoveryClient) throws InterruptedException {
this.mqttLib = mqttLib;
this.discoveryClient = discoveryClient;
logger.atInfo().log("Server awaitTermination");
server.awaitTermination();
logger.atInfo().log("Server awaitTermination done");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package com.aws.greengrass.testing.mqtt5.client.grpc;

import com.aws.greengrass.testing.mqtt5.client.DiscoveryClient;
import com.aws.greengrass.testing.mqtt5.client.GRPCLink;
import com.aws.greengrass.testing.mqtt5.client.MqttLib;
import com.aws.greengrass.testing.mqtt5.client.exceptions.GRPCException;
Expand Down Expand Up @@ -92,9 +93,10 @@ public GRPCControlServer newServer(@NonNull GRPCDiscoveryClient client, @NonNull
}

@Override
public String handleRequests(@NonNull MqttLib mqttLib) throws GRPCException, InterruptedException {
public String handleRequests(@NonNull MqttLib mqttLib, @NonNull DiscoveryClient discoveryClient)
throws GRPCException, InterruptedException {
logger.atInfo().log("Handle gRPC requests");
server.waiting(mqttLib);
server.waiting(mqttLib, discoveryClient);
return "Agent shutdown by OTF request '" + server.getShutdownReason() + "'";
}

Expand Down
Loading
Loading