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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discovery

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 13f192d

Discover 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.CoreDeviceDiscoverReply;
import com.aws.greengrass.testing.mqtt.client.CoreDeviceDiscoverRequest;
import com.aws.greengrass.testing.mqtt5.client.exceptions.DiscoverException;

/**
* Interface of discovery client.
*/
public interface DiscoverClient {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discovery

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 13f192d


/**
* Does discover of Core device broker.
*
* @param request the request
* @return formatted gRPC response
* @throws DiscoverException on errors
*/
CoreDeviceDiscoverReply discoverCoreDevice(CoreDeviceDiscoverRequest request) throws DiscoverException;
}
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 discoverClient the discover 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 DiscoverClient discoverClient)
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.DiscoverClientImpl;
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 DiscoverClientImpl());
link.shutdown(reason);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* 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.CoreDeviceDiscoverReply;
import com.aws.greengrass.testing.mqtt.client.CoreDeviceDiscoverRequest;
import com.aws.greengrass.testing.mqtt.client.CoreDeviceGroup;
import com.aws.greengrass.testing.mqtt5.client.DiscoverClient;
import com.aws.greengrass.testing.mqtt5.client.exceptions.DiscoverException;
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 discover client.
*/
public class DiscoverClientImpl implements DiscoverClient {
private static final Logger logger = LogManager.getLogger(DiscoverClientImpl.class);

@Override
public CoreDeviceDiscoverReply discoverCoreDevice(CoreDeviceDiscoverRequest request)
throws DiscoverException {
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 DiscoverException("Could not do discover", ex);
}
}
}

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

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

CoreDeviceDiscoverReply.Builder builder = CoreDeviceDiscoverReply.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 discover parts.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discovery

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 13f192d

*/
public class DiscoverException extends ClientException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discovery

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 13f192d

private static final long serialVersionUID = -2081564070408021325L;

public DiscoverException() {
super();
}

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

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

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

public DiscoverException(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.CoreDeviceDiscoverReply;
import com.aws.greengrass.testing.mqtt.client.CoreDeviceDiscoverRequest;
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.DiscoverClient;
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.DiscoverException;
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 DiscoverClient discoverClient;
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 DiscoverCoreDevice gRPC call.
*
* @param request incoming request
* @param responseObserver response control
*/
@Override
public void discoverCoreDevice(CoreDeviceDiscoverRequest request,
StreamObserver<CoreDeviceDiscoverReply> 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;
}

CoreDeviceDiscoverReply reply;
try {
reply = discoverClient.discoverCoreDevice(request);
} catch (DiscoverException ex) {
logger.atError().withThrowable(ex).log("exception during discover");
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, DiscoverClient discoverClient) throws InterruptedException {
this.mqttLib = mqttLib;
this.discoverClient = discoverClient;
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.DiscoverClient;
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 DiscoverClient discoverClient)
throws GRPCException, InterruptedException {
logger.atInfo().log("Handle gRPC requests");
server.waiting(mqttLib);
server.waiting(mqttLib, discoverClient);
return "Agent shutdown by OTF request '" + server.getShutdownReason() + "'";
}

Expand Down
Loading
Loading