diff --git a/uat/README.md b/uat/README.md index ec5f06eda..1f897cbfc 100644 --- a/uat/README.md +++ b/uat/README.md @@ -157,7 +157,7 @@ Example: java -Dggc.archive=greengrass-nucleus-latest.zip -Dtest.log.path=logs -Dtags="@GGMQ and not @SkipOnWindows" -jar testing-features/target/client-devices-auth-testing-features.jar ``` -@OffTheNetwork - scenarios with port 8883 blocked for some time +@OffTheNetwork - scenarios with port 8883 blocked for some time. These tests require OS level firewall should be activated on a machine there Nucleus is running: iptables on Linux and advfirewall on Windows. To run tests matching ALL following criteria and all tags should be listed using "and" between tags ```bash diff --git a/uat/mqtt-client-control/src/main/java/com/aws/greengrass/testing/mqtt/client/control/api/addon/Event.java b/uat/mqtt-client-control/src/main/java/com/aws/greengrass/testing/mqtt/client/control/api/addon/Event.java index fd1f5f311..e8ac93b1c 100644 --- a/uat/mqtt-client-control/src/main/java/com/aws/greengrass/testing/mqtt/client/control/api/addon/Event.java +++ b/uat/mqtt-client-control/src/main/java/com/aws/greengrass/testing/mqtt/client/control/api/addon/Event.java @@ -16,13 +16,13 @@ enum Type { /** MQTT message is received. */ EVENT_TYPE_MQTT_MESSAGE, + /** MQTT connection is disconnected. */ + EVENT_TYPE_MQTT_DISCONNECTED, + // TODO: implement other events // /** MQTT Connecttion established. */ // EVENT_TYPE_MQTT_CONNECTED, - // /** MQTT connection is disconnected. */ - // EVENT_TYPE_MQTT_DISCONNECTED, - // /** Agent is registered and discovered. */ // EVENT_TYPE_AGENT_DISCOVERED, diff --git a/uat/mqtt-client-control/src/main/java/com/aws/greengrass/testing/mqtt/client/control/implementation/addon/MqttDisconnectEvent.java b/uat/mqtt-client-control/src/main/java/com/aws/greengrass/testing/mqtt/client/control/implementation/addon/MqttDisconnectEvent.java new file mode 100644 index 000000000..9b1e11059 --- /dev/null +++ b/uat/mqtt-client-control/src/main/java/com/aws/greengrass/testing/mqtt/client/control/implementation/addon/MqttDisconnectEvent.java @@ -0,0 +1,80 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.aws.greengrass.testing.mqtt.client.control.implementation.addon; + +import com.aws.greengrass.testing.mqtt.client.Mqtt5Disconnect; +import com.aws.greengrass.testing.mqtt.client.control.api.ConnectionControl; +import com.aws.greengrass.testing.mqtt.client.control.api.addon.EventFilter; +import lombok.Getter; +import lombok.NonNull; + +/** + * Implements event about MQTT connection has been disconnected. + */ +public class MqttDisconnectEvent extends EventImpl { + + private final ConnectionControl connectionControl; + + @Getter + private final Mqtt5Disconnect disconnect; + + @Getter + private final String osError; + + /** + * Creates instance of MqttDisconnectEvent. + * + * @param connectionControl the connection control which receives that message + * @param disconnect the disconnect gRPC event + * @param osError the OS error as received from the client + */ + public MqttDisconnectEvent(@NonNull ConnectionControl connectionControl, @NonNull Mqtt5Disconnect disconnect, + String osError) { + super(Type.EVENT_TYPE_MQTT_DISCONNECTED); + this.connectionControl = connectionControl; + this.disconnect = disconnect; + this.osError = osError; + } + + @Override + public String getConnectionName() { + return connectionControl.getConnectionName(); + } + + @Override + public boolean isMatched(@NonNull EventFilter filter) { + // check type and timestamp + boolean matched = super.isMatched(filter); + if (!matched) { + return false; + } + + // check connection + matched = compareConnection(filter.getConnectionControl(), filter.getAgentId(), filter.getConnectionId(), + filter.getConnectionName()); + return matched; + } + + @SuppressWarnings("PMD.CompareObjectsWithEquals") + private boolean compareConnection(ConnectionControl expectedConnectionControl, String agentId, Integer connectionId, + String connectionName) { + // 1'st priority + if (expectedConnectionControl != null) { + // compare references ! + return expectedConnectionControl == connectionControl; + } + + // 2'nd priority + if (agentId != null && connectionId != null) { + return agentId.equals(connectionControl.getAgentControl().getAgentId()) + && connectionId == connectionControl.getConnectionId(); + } + + // 3'th priority + // check connection name + return connectionName == null || connectionName.equals(getConnectionName()); + } +} diff --git a/uat/testing-features/src/main/java/com/aws/greengrass/steps/BrokerCertificateSteps.java b/uat/testing-features/src/main/java/com/aws/greengrass/steps/BrokerCertificateSteps.java index 5e4d29b7a..99e028d4a 100644 --- a/uat/testing-features/src/main/java/com/aws/greengrass/steps/BrokerCertificateSteps.java +++ b/uat/testing-features/src/main/java/com/aws/greengrass/steps/BrokerCertificateSteps.java @@ -24,8 +24,10 @@ import java.net.URI; import java.security.Principal; import java.security.PrivateKey; +import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -43,10 +45,18 @@ @ScenarioScoped public class BrokerCertificateSteps { private static final int GET_CERTIFICATE_TIMEOUT_SEC = 5; + private static final int GET_PRINCIPALS_TIMEOUT_SEC = 5; private final MqttBrokers mqttBrokers; - private final Map certificates = new HashMap<>(); + private final Map certificateInfos = new HashMap<>(); + + @AllArgsConstructor + @Getter + private static class CertificateInfo { + X509Certificate certificate; // TODO: can be list/array + Principal[] princinals; + } @AllArgsConstructor @Getter @@ -75,18 +85,53 @@ public BrokerCertificateSteps(MqttBrokers mqttBrokers) { */ @Then("I verify the certificate {string} equals the certificate {string}") public void verifyCertsAreEqual(String certNameA, String certNameB) { - X509Certificate certA = certificates.get(certNameA); - if (certA == null) { - throw new IllegalStateException(String.format("Certificate %s not found.", certNameA)); + CertificateInfo certInfoA = getCertificateInfo(certNameA); + CertificateInfo certInfoB = getCertificateInfo(certNameB); + if (!certInfoA.getCertificate().equals(certInfoB.getCertificate())) { + throw new IllegalStateException("Certificates are differ"); } + } - X509Certificate certB = certificates.get(certNameB); - if (certB == null) { - throw new IllegalStateException(String.format("Certificate %s not found.", certNameB)); + /** + * Checks is certificate contains endpoint in ubject alternative names extension. + * + * @param certName the name of certificate + * @param endpoint the name of endpoint or IP address + * @throws CertificateParsingException when could not extract subject alternative names from certificate + * @throws IllegalStateException on errors + */ + @Then("I verify that the subject alternative names of certificate {string} contains endpoint {string}") + public void verifyBrokerCertificateContainsEndpoint(String certName, String endpoint) + throws CertificateParsingException { + CertificateInfo certInfo = getCertificateInfo(certName); + Collection> altNames = certInfo.getCertificate().getSubjectAlternativeNames(); + if (altNames == null) { + throw new IllegalStateException("Missing Subject alternatiuve names of certificate"); } - if (!certA.equals(certB)) { - throw new IllegalStateException("Certificates are differ"); + for (List alt : altNames) { + Object altName = alt.get(1); + log.info("Cert alt name {}", altName); + if (endpoint.equals(altName)) { + return; + } + } + + throw new IllegalStateException("Endpoint not found in the subject alternative names of certificate"); + } + + /** + * Checks is certificate's accepted issuer list is missing or empty. + * + * @param certName the name of certificate + * @throws IllegalStateException on errors + */ + @Then("I verify the TLS accepted issuer list of certificate {string} is empty") + public void verifyAcceptedIssuerListEmpty(String certName) { + CertificateInfo certInfo = getCertificateInfo(certName); + Principal[] clientCertIssuers = certInfo.getPrincinals(); + if (clientCertIssuers != null && clientCertIssuers.length > 0) { + throw new IllegalStateException("Accepted issuer list is not empty"); } } @@ -129,9 +174,13 @@ public void retrieveServerCertificate(String brokerId, String certName) throw new IllegalStateException("Certificate array is empty"); } + Principal[] principal = futures.getPrincinal().get(GET_PRINCIPALS_TIMEOUT_SEC, + TimeUnit.SECONDS); + + // FIXME: strictly speaking we can have a array of certificates here X509Certificate cert = serverCerts[0]; - certificates.put(certName, cert); - log.info("Saved broker's '{}' certificate '{}'", brokerId, cert); + certificateInfos.put(certName, new CertificateInfo(cert, principal)); + log.info("Saved broker's '{}' certificate info '{}'", brokerId, cert); return; } catch (IllegalStateException | ExecutionException | TimeoutException ex) { lastException = ex; @@ -232,4 +281,20 @@ public X509Certificate[] getAcceptedIssuers() { return new CertificateFutures(serverCertsFut, clientCertIssuersFut); } } + + /** + * Get information of certificate by name. + * + * @param certName the name of certificate + * @return certificate information + * @throws IllegalStateException on errors + */ + private CertificateInfo getCertificateInfo(String certName) { + CertificateInfo certInfo = certificateInfos.get(certName); + if (certInfo == null) { + throw new IllegalStateException(String.format("Certificate %s not found.", certName)); + } + + return certInfo; + } } diff --git a/uat/testing-features/src/main/java/com/aws/greengrass/steps/MqttControlSteps.java b/uat/testing-features/src/main/java/com/aws/greengrass/steps/MqttControlSteps.java index ea2da18fc..6c6331cab 100644 --- a/uat/testing-features/src/main/java/com/aws/greengrass/steps/MqttControlSteps.java +++ b/uat/testing-features/src/main/java/com/aws/greengrass/steps/MqttControlSteps.java @@ -29,6 +29,7 @@ import com.aws.greengrass.testing.mqtt.client.control.implementation.PublishReasonCode; import com.aws.greengrass.testing.mqtt.client.control.implementation.SubscribeReasonCode; import com.aws.greengrass.testing.mqtt.client.control.implementation.addon.EventStorageImpl; +import com.aws.greengrass.testing.mqtt.client.control.implementation.addon.MqttDisconnectEvent; import com.aws.greengrass.testing.mqtt.client.control.implementation.addon.MqttMessageEvent; import com.aws.greengrass.testing.resources.AWSResources; import com.aws.greengrass.testing.resources.iot.IotCertificateSpec; @@ -245,6 +246,7 @@ public void onMessageReceived(ConnectionControl connectionControl, Mqtt5Message @Override public void onMqttDisconnect(ConnectionControl connectionControl, Mqtt5Disconnect disconnect, String error) { // TODO: also add to eventStore + eventStorage.addEvent(new MqttDisconnectEvent(connectionControl, disconnect, error)); log.info("MQTT client disconnected. Error: {}", error); } }; @@ -744,7 +746,7 @@ public void connect(String clientDeviceId, String componentId, String brokerId, // get address information about broker final List bc = mqttBrokers.getConnectivityInfo(brokerId); if (CollectionUtils.isNullOrEmpty(bc)) { - throw new RuntimeException("There is no address information about broker, " + throw new IllegalStateException("There is no address information about broker, " + "probably discovery step missing in scenario"); } @@ -780,7 +782,7 @@ public void connect(String clientDeviceId, String componentId, String brokerId, } if (lastException == null) { - throw new RuntimeException("No addresses to connect"); + throw new IllegalStateException("No addresses to connect"); } throw lastException; } @@ -826,7 +828,7 @@ public void canNotConnect(String clientDeviceId, String componentId, String brok return; } log.error("It was expected that there would be no connection, but connected"); - throw new RuntimeException("It was expected that there would be no connection, but connected"); + throw new IllegalStateException("It was expected that there would be no connection, but connected"); } /** @@ -913,16 +915,16 @@ private void subscribe(@NonNull String clientDeviceId, @NonNull String topicFilt MqttSubscribeReply mqttSubscribeReply = connectionControl.subscribeMqtt(DEFAULT_SUBSCRIPTION_ID, txUserProperties, mqtt5Subscription); if (mqttSubscribeReply == null) { - throw new RuntimeException("Do not receive reply to MQTT subscribe request"); + throw new IllegalStateException("Do not receive reply to MQTT subscribe request"); } List reasons = mqttSubscribeReply.getReasonCodesList(); if (reasons == null) { - throw new RuntimeException("Receive reply to MQTT subscribe request with missing reason codes"); + throw new IllegalStateException("Receive reply to MQTT subscribe request with missing reason codes"); } if (reasons.size() != 1 || reasons.get(0) == null) { - throw new RuntimeException("Receive reply to MQTT subscribe request with unexpected number " + throw new IllegalStateException("Receive reply to MQTT subscribe request with unexpected number " + "of reason codes should be 1 but has " + reasons.size()); } @@ -937,7 +939,7 @@ private void subscribe(@NonNull String clientDeviceId, @NonNull String topicFilt log.error("MQTT subscription has on topics filter {} been failed. Unexpected reason code {}", filter, reason); } - throw new RuntimeException("Receive reply to MQTT subscribe request with missing reason codes"); + throw new IllegalStateException("Receive reply to MQTT subscribe request with missing reason codes"); } } @@ -1004,19 +1006,19 @@ public void publish(String clientDeviceId, String topicString, int qos, String m Mqtt5Message mqtt5Message = buildMqtt5Message(qos, topic, message); MqttPublishReply mqttPublishReply = connectionControl.publishMqtt(mqtt5Message); if (mqttPublishReply == null) { - throw new RuntimeException("Do not receive reply to MQTT publish request"); + throw new IllegalStateException("Do not receive reply to MQTT publish request"); } final int reasonCode = mqttPublishReply.getReasonCode(); if (reasonCode != expectedStatus) { - throw new RuntimeException("MQTT publish completed with negative reason code " + reasonCode); + throw new IllegalStateException("MQTT publish completed with negative reason code " + reasonCode); } log.info("MQTT message '{}' has been succesfully published", message); } /** - * Verify is MQTT message is received in limited duration of time. + * Verify is MQTT message is received before and in limited duration of time. * * @param message content of message to receive * @param clientDeviceId the user defined client device id @@ -1035,7 +1037,7 @@ public void notReceivedMessage(String message, String clientDeviceId, String top } /** - * Verify is MQTT message is received in limited duration of time. + * Verify is MQTT message is received before and in limited duration of time. * * @param message content of message to receive * @param clientDeviceId the user defined client device id @@ -1055,7 +1057,7 @@ public void receivedMessage(String message, String clientDeviceId, String topicS /** - * Verify is MQTT message is received in limited duration of time. + * Verify is MQTT message is received before and in limited duration of time. * * @param message beginning of long message to receive * @param messageLength the length of long message @@ -1078,21 +1080,21 @@ public void receivedMessageBeginning(String message, int messageLength, String c } /** - * Verify is MQTT message is received in limited duration of time. + * Verify is MQTT message is received before in limited duration of time. * * @param message content of message to receive * @param clientDeviceId the user defined client device id * @param topicString the topic (not a filter) which message has been sent * @param value the duration of time to wait for message * @param unit the time unit to wait - * @param isExpectedMessage used for setting message expectation - * @throws TimeoutException when matched message was not received in specified duration of time - * @throws RuntimeException on internal errors + * @param isExpected used for setting message expectation + * @throws IllegalStateException when matched message was not received in specified duration of time * @throws InterruptedException then thread has been interrupted + * @throws RuntimeException on internal errors */ @SuppressWarnings("PMD.UseObjectForClearerAPI") - public void receive(String message, String clientDeviceId, String topicString, int value, - String unit, boolean isExpectedMessage) + private void receive(String message, String clientDeviceId, String topicString, int value, + String unit, boolean isExpected) throws TimeoutException, InterruptedException { // getting connectionControl by clientDeviceId final String clientDeviceThingName = getClientDeviceThingName(clientDeviceId); @@ -1127,31 +1129,104 @@ public void receive(String message, String clientDeviceId, String topicString, i try { events = eventStorage.awaitEvents(eventFilter, value, timeUnit); } catch (TimeoutException e) { - if (isExpectedMessage) { + if (isExpected) { log.error("No matched MQTT messages have been received, ex: {}", e.getMessage()); - throw new RuntimeException(e); + throw new IllegalStateException("No matched MQTT messages have been received", e); } } - if (!isExpectedMessage && !events.isEmpty()) { - throw new RuntimeException("MQTT unexpected messages have been received"); + + if (!isExpected && !events.isEmpty()) { + throw new IllegalStateException("MQTT unexpected messages have been received"); } } /** - * Clear message storage. + * Verify is connection is not disconnected before and in limited duration of time. + * + * @param clientDeviceId the user defined client device id + * @param value the duration of time to wait for message + * @param unit the time unit to wait + * @throws TimeoutException when matched message was not received in specified duration of time + * @throws RuntimeException on internal errors + * @throws InterruptedException then thread has been interrupted + */ + @And("device {string} is not disconnected within {int} {word}") + public void checkIsNotDisconnect(String clientDeviceId, int value, String unit) throws InterruptedException { + checkDisconnect(clientDeviceId, value, unit, false); + } + + /** + * Verify is connection disconnected before and in limited duration of time. + * + * @param clientDeviceId the user defined client device id + * @param value the duration of time to wait for message + * @param unit the time unit to wait + * @throws TimeoutException when matched message was not received in specified duration of time + * @throws RuntimeException on internal errors + * @throws InterruptedException then thread has been interrupted + */ + @And("device {string} disconnected within {int} {word}") + public void checkDisconnect(String clientDeviceId, int value, String unit) throws InterruptedException { + checkDisconnect(clientDeviceId, value, unit, true); + } + + /** + * Verify is connection is [not] disconnected before and in limited duration of time. + * + * @param clientDeviceId the user defined client device id + * @param value the duration of time to wait for message + * @param unit the time unit to wait + * @param isExpected used for setting disconnect expectation + * @throws IllegalStateException when matched message was not received in specified duration of time + * @throws InterruptedException then thread has been interrupted + * @throws RuntimeException on internal errors + */ + private void checkDisconnect(String clientDeviceId, int value, String unit, boolean isExpected) + throws InterruptedException { + final String clientDeviceThingName = getClientDeviceThingName(clientDeviceId); + ConnectionControl connectionControl = getConnectionControl(clientDeviceThingName); + + // build filter + EventFilter eventFilter = new EventFilter.Builder() + .withType(Event.Type.EVENT_TYPE_MQTT_DISCONNECTED) + .withConnectionControl(connectionControl) + .build(); + // convert time units + TimeUnit timeUnit = TimeUnit.valueOf(unit.toUpperCase()); + + // awaiting for message + log.info("Awaiting for disconnect on Thing '{}' for {} {}", clientDeviceThingName, value, unit); + + List events = new ArrayList<>(); + try { + events = eventStorage.awaitEvents(eventFilter, value, timeUnit); + } catch (TimeoutException e) { + if (isExpected) { + log.error("No matched MQTT messages have been received, ex: {}", e.getMessage()); + throw new IllegalStateException("No matched disconnect events have been received", e); + } + } + + if (!isExpected && !events.isEmpty()) { + throw new IllegalStateException("Unexpected disconnect have been received"); + } + } + + /** + * Clear event storage. * */ - @And("I clear message storage") + @And("I clear the event storage") public void clearStorage() { eventStorage.clear(); - log.info("Storage was cleared"); + log.info("Event storage was cleared"); } /** * Clear message storage. * */ - @And("I clear message storage and reset all MQTT settings to default") + @And("I clear the event storage and reset all MQTT settings to defaults") public void clearAnything() { clearStorage(); setMqttTimeoutSec(DEFAULT_MQTT_TIMEOUT_SEC); @@ -1254,17 +1329,17 @@ public void unsubscribe(String clientDeviceId, String filter) { List reasons = mqttUnsubscribeReply.getReasonCodesList(); if (reasons == null) { - throw new RuntimeException("Receive reply to MQTT unsubscribe request with missing reason codes"); + throw new IllegalStateException("Receive reply to MQTT unsubscribe request with missing reason codes"); } if (reasons.size() != 1 || reasons.get(0) == null) { - throw new RuntimeException("Receive reply to MQTT unsubscribe request with unexpected number " + throw new IllegalStateException("Receive reply to MQTT unsubscribe request with unexpected number " + "of reason codes should be 1 but has " + reasons.size()); } int reason = reasons.get(0); if (reason != PublishReasonCode.SUCCESS.getValue()) { - throw new RuntimeException("Receive reply to MQTT unsubscribe request with unsuccessful reason code " + throw new IllegalStateException("Receive reply to MQTT unsubscribe request with unsuccessful reason code " + reason); } log.info("MQTT topics filter {} has been unsubscribed", filter); @@ -1329,7 +1404,7 @@ private IotThingSpec getClientDeviceThingSpec(String clientDeviceThingName) { return resources.trackingSpecs(IotThingSpec.class) .filter(t -> clientDeviceThingName.equals(t.resource().thingName())) .findFirst() - .orElseThrow(() -> new RuntimeException("Thing spec is not found")); + .orElseThrow(() -> new IllegalStateException("Thing spec is not found")); } private TLSSettings buildTlsSettings(IotThingSpec thingSpec, List caList) { diff --git a/uat/testing-features/src/main/resources/greengrass/features/ggmq-1.feature b/uat/testing-features/src/main/resources/greengrass/features/ggmq-1.feature index e44af7f4f..e6b0b2e8f 100644 --- a/uat/testing-features/src/main/resources/greengrass/features/ggmq-1.feature +++ b/uat/testing-features/src/main/resources/greengrass/features/ggmq-1.feature @@ -1881,14 +1881,130 @@ Feature: GGMQ-1 And I connect device "subscriber" on to "localMqttBroker2" using mqtt "" When I subscribe "subscriber" to "iot_data_0" with qos 1 - When I publish from "publisher" to "iot_data_0" with qos 1 and message "Test message" - And message "Test message" received on "subscriber" from "iot_data_0" topic within 5 seconds + When I publish from "publisher" to "iot_data_0" with qos 1 and message "Test message t25" + And message "Test message t25" received on "subscriber" from "iot_data_0" topic within 5 seconds And I rename connection "publisher" to "publisher_old" # Reconnect publisher with the same device id And I connect device "publisher" on to "localMqttBroker1" using mqtt "" When I publish from "publisher" to "iot_data_0" with qos 1 and message "Connect again" And message "Connect again" received on "subscriber" from "iot_data_0" topic within 5 seconds + And device "publisher_old" disconnected within 10 seconds + + @mqtt3 @sdk-java + Examples: + | mqtt-v | name | agent | recipe | + | v3 | sdk-java | aws.greengrass.client.Mqtt5JavaSdkClient | client_java_sdk.yaml | + + @mqtt3 @mosquitto-c @SkipOnWindows + Examples: + | mqtt-v | name | agent | recipe | + | v3 | mosquitto-c | aws.greengrass.client.MqttMosquittoClient | client_mosquitto_c.yaml | + + @mqtt3 @paho-java + Examples: + | mqtt-v | name | agent | recipe | + | v3 | paho-java | aws.greengrass.client.Mqtt5JavaPahoClient | client_java_paho.yaml | + + @mqtt3 @paho-python + Examples: + | mqtt-v | name | agent | recipe | + | v3 | paho-python | aws.greengrass.client.Mqtt5PythonPahoClient | client_python_paho.yaml | + + @mqtt5 @sdk-java + Examples: + | mqtt-v | name | agent | recipe | + | v5 | sdk-java | aws.greengrass.client.Mqtt5JavaSdkClient | client_java_sdk.yaml | + + @mqtt5 @mosquitto-c @SkipOnWindows + Examples: + | mqtt-v | name | agent | recipe | + | v5 | mosquitto-c | aws.greengrass.client.MqttMosquittoClient | client_mosquitto_c.yaml | + + @mqtt5 @paho-java + Examples: + | mqtt-v | name | agent | recipe | + | v5 | paho-java | aws.greengrass.client.Mqtt5JavaPahoClient | client_java_paho.yaml | + + @mqtt5 @paho-python + Examples: + | mqtt-v | name | agent | recipe | + | v5 | paho-python | aws.greengrass.client.Mqtt5PythonPahoClient | client_python_paho.yaml | + + + @GGMQ-1-T26 + Scenario Outline: GGMQ-1-T26--: As a customer, my GGAD stays connected when CDA rotates cert for EMQX broker + When I create a Greengrass deployment with components + | aws.greengrass.clientdevices.Auth | LATEST | + | aws.greengrass.clientdevices.mqtt.EMQX | LATEST | + | aws.greengrass.clientdevices.IPDetector | LATEST | + | | classpath:/local-store/recipes/ | + And I create client device "clientDeviceTest" + When I associate "clientDeviceTest" with ggc + And I update my Greengrass deployment configuration, setting the component aws.greengrass.clientdevices.Auth configuration to: + """ +{ + "MERGE":{ + "deviceGroups":{ + "formatVersion":"2021-03-05", + "definitions":{ + "MyPermissiveDeviceGroup":{ + "selectionRule":"thingName: ${clientDeviceTest}", + "policyName":"MyPermissivePolicy" + } + }, + "policies":{ + "MyPermissivePolicy":{ + "AllowAll":{ + "statementDescription":"Allow client devices to perform all actions.", + "operations":[ + "*" + ], + "resources":[ + "*" + ] + } + } + } + } + } +} + """ + And I update my Greengrass deployment configuration, setting the component aws.greengrass.clientdevices.IPDetector configuration to: + """ +{ + "MERGE":{ + "includeIPv4LoopbackAddrs": "true" + } +} + """ + And I update my Greengrass deployment configuration, setting the component configuration to: + """ +{ + "MERGE":{ + "controlAddresses":"${mqttControlAddresses}", + "controlPort":"${mqttControlPort}" + } +} + """ + + And I deploy the Greengrass deployment configuration + Then the Greengrass deployment is COMPLETED on the device after 5 minutes + And the aws.greengrass.clientdevices.mqtt.EMQX log on the device contains the line "is running now!." within 1 minutes + + And I discover core device broker as "default_broker" from "clientDeviceTest" in OTF + And I connect device "clientDeviceTest" on to "default_broker" using mqtt "" + + Then I add IP address "127.0.0.2" to loopback interface + And I wait 60 seconds + + When I retrieve the certificate of broker "default_broker" and store as "BROKER_CERTIFICATE" + Then I verify that the subject alternative names of certificate "BROKER_CERTIFICATE" contains endpoint "127.0.0.2" + And I verify the TLS accepted issuer list of certificate "BROKER_CERTIFICATE" is empty + + Then device "clientDeviceTest" is not disconnected within 10 seconds + + And I remove IP address "127.0.0.2" from loopback interface @mqtt3 @sdk-java Examples: @@ -2189,7 +2305,7 @@ Feature: GGMQ-1 And message "First retained message" is not received on "subscriber" from "receive_last_retain_message" topic within 5 seconds And message "Second retained message" received on "subscriber" from "receive_last_retain_message" topic within 5 seconds - And I clear message storage + And I clear the event storage # 2. test case when first published message has retain and second not And I set MQTT publish 'retain' flag to true @@ -2203,7 +2319,7 @@ Feature: GGMQ-1 And message "Second message without retain" is not received on "subscriber" from "receive_only_retain_message_on_subscribe" topic within 5 seconds And message "First message with retain" received on "subscriber" from "receive_only_retain_message_on_subscribe" topic within 5 seconds - And I clear message storage + And I clear the event storage # 3. test case when subscribe twice with 'retain send at subscription' And I set MQTT publish 'retain' flag to true @@ -2214,11 +2330,11 @@ Feature: GGMQ-1 When I subscribe "subscriber" to "subscribe_twice_with_send_at_subscription" with qos 0 And message "Single message in case3" received on "subscriber" from "subscribe_twice_with_send_at_subscription" topic within 5 seconds - And I clear message storage + And I clear the event storage When I subscribe "subscriber" to "subscribe_twice_with_send_at_subscription" with qos 0 And message "Single message in case3" received on "subscriber" from "subscribe_twice_with_send_at_subscription" topic within 5 seconds - And I clear message storage + And I clear the event storage # 4. test case when subscribe twice with 'retain send at new subscription' when has retained message And I set MQTT publish 'retain' flag to true @@ -2228,14 +2344,14 @@ Feature: GGMQ-1 When I subscribe "subscriber" to "send_at_new_subscription" with qos 0 And message "Single retained message in case4" received on "subscriber" from "send_at_new_subscription" topic within 5 seconds - And I clear message storage + And I clear the event storage When I subscribe "subscriber" to "send_at_new_subscription" with qos 0 And message "Single retained message in case4" is not received on "subscriber" from "send_at_new_subscription" topic within 5 seconds - And I clear message storage + And I clear the event storage # 5. test case when has retained message and subscribe with 'do not send and subscription' - And I clear message storage + And I clear the event storage And I set MQTT publish 'retain' flag to true And I set MQTT subscribe 'retain handling' property to "MQTT5_RETAIN_DO_NOT_SEND_AT_SUBSCRIPTION" @@ -2243,7 +2359,7 @@ Feature: GGMQ-1 When I subscribe "subscriber" to "do_not_send_at_subscription" with qos 0 And message "Single retained message in case5" is not received on "subscriber" from "do_not_send_at_subscription" topic within 5 seconds - And I clear message storage + And I clear the event storage # 6. test case when has no retained messages and subscribed with 'send at subscription' And I set MQTT publish 'retain' flag to false @@ -2253,7 +2369,7 @@ Feature: GGMQ-1 When I subscribe "subscriber" to "t102_case6" with qos 0 And message "Single not retained message in case6" is not received on "subscriber" from "t102_case6" topic within 5 seconds - And I clear message storage + And I clear the event storage # 7. test case when no retained messages and subscribed with 'send at new subscription' And I set MQTT publish 'retain' flag to false @@ -2263,7 +2379,7 @@ Feature: GGMQ-1 When I subscribe "subscriber" to "t102_case7" with qos 0 And message "Single not retained message in case7" is not received on "subscriber" from "t102_case7" topic within 5 seconds - And I clear message storage + And I clear the event storage # 8. test case when no retaine messages and subscribed with 'do not send at subscription' And I set MQTT publish 'retain' flag to false @@ -2273,7 +2389,7 @@ Feature: GGMQ-1 When I subscribe "subscriber" to "t102_case8" with qos 0 And message "Single not retained message in case8" is not received on "subscriber" from "t102_case8" topic within 5 seconds - And I clear message storage and reset all MQTT settings to default + And I clear the event storage and reset all MQTT settings to default # C. SUBSCRIBE 'retain as published' tests @@ -2311,7 +2427,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "iot_data_1" with qos 0 and message "Hello world4" And message "Hello world4" received on "subscriber" from "iot_data_1" topic within 5 seconds - And I clear message storage and reset all MQTT settings to default + And I clear the event storage and reset all MQTT settings to defaults # D. PUBLISH 'user properties' tests @@ -2326,7 +2442,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "iot_data_2" with qos 0 and message "Expected userProperties are received" And message "Expected userProperties are received" received on "subscriber" from "iot_data_2" topic within 5 seconds - And I clear message storage + And I clear the event storage # 12. test case when publish 'user properties' are empty. # In that case 'user properties' on receive should not be equal to 'user properties' value on publish. @@ -2339,7 +2455,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "iot_data_2" with qos 0 and message "Expected userProperties are not received" And message "Expected userProperties are not received" is not received on "subscriber" from "iot_data_2" topic within 5 seconds - And I clear message storage + And I clear the event storage # 13. test case when publish 'user properties' are empty. # In that case 'user properties' on receive should ignore 'user properties' value on publish. @@ -2351,7 +2467,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "iot_data_3" with qos 0 and message "Ignore userProperties" And message "Ignore userProperties" received on "subscriber" from "iot_data_3" topic within 5 seconds - And I clear message storage + And I clear the event storage # 14. test case when publish 'user properties' are empty. # In that case 'user properties' on receive should be equal to 'user properties' value on publish. @@ -2362,7 +2478,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "iot_data_4" with qos 0 and message "Without userProperties" And message "Without userProperties" received on "subscriber" from "iot_data_4" topic within 5 seconds - And I clear message storage and reset all MQTT settings to default + And I clear the event storage and reset all MQTT settings to defaults # E. 'payload format indicator' tests @@ -2373,7 +2489,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "payload_format_indicator_false_false" with qos 0 and message "Payload format indicators false/false" And message "Payload format indicators false/false" received on "subscriber" from "payload_format_indicator_false_false" topic within 5 seconds - And I clear message storage + And I clear the event storage # 16. test case when both tx/rx payload format indicators set to 1 And I set MQTT publish 'payload format indicator' flag to true @@ -2382,7 +2498,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "payload_format_indicator_true_true" with qos 0 and message "Payload format indicators true/true" And message "Payload format indicators true/true" received on "subscriber" from "payload_format_indicator_true_true" topic within 5 seconds - And I clear message storage + And I clear the event storage # 17. test case when tx payload format indicator set to 1 and rx to 0 And I set MQTT publish 'payload format indicator' flag to true @@ -2391,7 +2507,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "payload_format_indicator_true_false" with qos 0 and message "Payload format indicators true/false" And message "Payload format indicators true/false" is not received on "subscriber" from "payload_format_indicator_true_false" topic within 5 seconds - And I clear message storage + And I clear the event storage # 18. test case when tx payload format indicator set to 0 and rx to 1 And I set MQTT publish 'payload format indicator' flag to false @@ -2400,7 +2516,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "payload_format_indicator_false_true" with qos 0 and message "Payload format indicators false/true" And message "Payload format indicators false/true" is not received on "subscriber" from "payload_format_indicator_false_true" topic within 5 seconds - And I clear message storage + And I clear the event storage # 19. test case when tx payload format indicator set to 1 and rx is unset And I set MQTT publish 'payload format indicator' flag to true @@ -2409,7 +2525,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "payload_format_indicator_true_null" with qos 0 and message "Payload format indicators true/null" And message "Payload format indicators true/null" received on "subscriber" from "payload_format_indicator_true_null" topic within 5 seconds - And I clear message storage + And I clear the event storage # 20. test case when tx payload format indicator set to 0 and rx is unset And I set MQTT publish 'payload format indicator' flag to false @@ -2418,7 +2534,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "payload_format_indicator_false_null" with qos 0 and message "Payload format indicators false/null" And message "Payload format indicators false/null" received on "subscriber" from "payload_format_indicator_false_null" topic within 5 seconds - And I clear message storage and reset all MQTT settings to default + And I clear the event storage and reset all MQTT settings to defaults # F. subscribe 'no local' tests @@ -2429,7 +2545,7 @@ Feature: GGMQ-1 When I publish from "subscriber" to "no_local_true" with qos 0 and message "First message no local true test" Then message "First message no local true test" is not received on "subscriber" from "no_local_true" topic within 5 seconds - And I clear message storage + And I clear the event storage # 22. test case when subscribe 'no local' set to false And I set MQTT subscribe 'no local' flag to false @@ -2438,7 +2554,7 @@ Feature: GGMQ-1 When I publish from "subscriber" to "no_local_false" with qos 0 and message "First message no local false test" Then message "First message no local false test" received on "subscriber" from "no_local_false" topic within 5 seconds - And I clear message storage and reset all MQTT settings to default + And I clear the event storage and reset all MQTT settings to defaults # G. publish/subscribe 'content type' tests @@ -2449,7 +2565,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "content_types_the_same" with qos 0 and message "Content types not null/not null" And message "Content types not null/not null" received on "subscriber" from "content_types_the_same" topic within 5 seconds - And I clear message storage + And I clear the event storage # 24. test case when tx content type set another value then rx And I set MQTT publish 'content type' to "another content type value" @@ -2458,7 +2574,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "content_type_not_the_same" with qos 0 and message "Different values of content types" And message "Different values of content type" is not received on "subscriber" from "content_type_not_the_same" topic within 5 seconds - And I clear message storage + And I clear the event storage # 25. test case when tx content type is null And I reset MQTT publish 'content type' @@ -2467,7 +2583,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "content_type_null_not_null" with qos 0 and message "Content types null/not null" And message "Content types null/not null" is not received on "subscriber" from "content_type_null_not_null" topic within 5 seconds - And I clear message storage + And I clear the event storage # 26. test case when rx content type is null And I set MQTT publish 'content type' to "text/plain; charset=utf-8" @@ -2476,7 +2592,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "content_type_not_null_null" with qos 0 and message "Content types not null/null" And message "Content types not null/null" received on "subscriber" from "content_type_not_null_null" topic within 5 seconds - And I clear message storage and reset all MQTT settings to default + And I clear the event storage and reset all MQTT settings to defaults # H. test 'message expiry interval' feature @@ -2488,7 +2604,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "message_expire_interval_50" with qos 0 and message "Message expiry interval was 50" And message "Message expiry interval was 50" received on "subscriber" from "message_expire_interval_50" topic within 5 seconds - And I clear message storage + And I clear the event storage # 28. test case when send message expiry interval 100 make 5 seconds pause and receive 95 And I set MQTT publish 'retain' flag to true @@ -2499,7 +2615,7 @@ Feature: GGMQ-1 When I subscribe "subscriber" to "message_expire_interval_100" with qos 0 And message "Message expiry interval was 100" received on "subscriber" from "message_expire_interval_100" topic within 5 seconds - And I clear message storage + And I clear the event storage # 29. test case when send message expiry interval 1 make 5 seconds pause and message do not forward by broker And I set MQTT publish 'retain' flag to true @@ -2510,7 +2626,7 @@ Feature: GGMQ-1 When I subscribe "subscriber" to "message_expire_interval_1" with qos 0 And message "Message expiry interval was 1" is not received on "subscriber" from "message_expire_interval_1" topic within 10 seconds - And I clear message storage and reset all MQTT settings to default + And I clear the event storage and reset all MQTT settings to defaults # I. test 'response topic' feature @@ -2522,7 +2638,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "response_topic_test_case_1" with qos 0 and message "Message with response topic 1" And message "Message with response topic 1" received on "subscriber" from "response_topic_test_case_1" topic within 5 seconds - And I clear message storage + And I clear the event storage # 31. test case when publish message with response topic set but expected response topic is not set And I set MQTT publish 'response topic' to "response_topic_2" @@ -2532,7 +2648,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "response_topic_test_case_2" with qos 0 and message "Message with response topic 2" And message "Message with response topic 2" received on "subscriber" from "response_topic_test_case_2" topic within 5 seconds - And I clear message storage + And I clear the event storage # 32. test case when response topic in publish is not set but expected in received message And I reset MQTT publish 'response topic' @@ -2542,7 +2658,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "response_topic_test_case_3" with qos 0 and message "Message without response topic 3" And message "Message without response topic 3" is not received on "subscriber" from "response_topic_test_case_3" topic within 10 seconds - And I clear message storage + And I clear the event storage # 33. test case when response topic in pulish and receive are not the same And I set MQTT publish 'response topic' to "response_topic_4" @@ -2552,7 +2668,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "response_topic_test_case_4" with qos 0 and message "Message with response topic 4" And message "Message with response topic 4" is not received on "subscriber" from "response_topic_test_case_4" topic within 10 seconds - And I clear message storage and reset all MQTT settings to default + And I clear the event storage and reset all MQTT settings to defaults # J. test 'correlation data' feature @@ -2572,7 +2688,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "correlation_data_test_case_2" with qos 0 and message "Message with correlation data 2" And message "Message with correlation data 2" received on "subscriber" from "correlation_data_test_case_2" topic within 5 seconds - And I clear message storage + And I clear the event storage # 36. test case when correlation data in publish is not set but expected in received message And I reset MQTT publish 'correlation data' @@ -2582,7 +2698,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "correlation_data_test_case_3" with qos 0 and message "Message without correlation data 3" And message "Message without correlation data 3" is not received on "subscriber" from "correlation_data_test_case_3" topic within 10 seconds - And I clear message storage + And I clear the event storage # 37. test case when correlation data in pulish and receive are not the same And I set MQTT publish 'correlation data' to "correlation_data_4" @@ -2592,7 +2708,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "correlation_data_test_case_4" with qos 0 and message "Message with correlation data 4" And message "Message with correlation data 4" is not received on "subscriber" from "correlation_data_test_case_4" topic within 10 seconds - And I clear message storage and reset all MQTT settings to default + And I clear the event storage and reset all MQTT settings to defaults # request response information is a CONNECT packet property And I disconnect device "publisher" with reason code 0 @@ -2608,7 +2724,7 @@ Feature: GGMQ-1 When I publish from "publisher" to "topic_request_response_information_is_set_true" with qos 0 and message "Message when request response information is true" And message "Message when request response information is true" received on "subscriber" from "topic_request_response_information_is_set_true" topic within 5 seconds - And I clear message storage + And I clear the event storage And I disconnect device "publisher" with reason code 0 # 39. test case when connect with 'request response information' flag set to false