From 3d2f290a7f85fe6d28318a225baf017ef6a68030 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Sat, 28 Oct 2023 21:43:46 -0700 Subject: [PATCH 01/34] Implemented fleet provisioning test --- .github/workflows/ci.yml | 8 + pom.xml | 1 + .../script/fleet_provisioning_cfg.json | 30 ++ servicetests/script/run_service_test.py | 193 ++++++++++ .../script/test_fleet_provisioning.py | 76 ++++ servicetests/tests/FleetProvisioning/pom.xml | 46 +++ .../fleetProvisioning/FleetProvisioning.java | 339 ++++++++++++++++++ .../commandlineutils/CommandLineUtils.java | 299 +++++++++++++++ 8 files changed, 992 insertions(+) create mode 100644 servicetests/script/fleet_provisioning_cfg.json create mode 100644 servicetests/script/run_service_test.py create mode 100644 servicetests/script/test_fleet_provisioning.py create mode 100644 servicetests/tests/FleetProvisioning/pom.xml create mode 100644 servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java create mode 100644 servicetests/tests/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8693820..d5f0c696 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -471,6 +471,14 @@ jobs: sudo apt-get update -y sudo apt-get install softhsm -y softhsm2-util --version + - name: configure AWS credentials (Fleet provisioning) + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: ${{ env.CI_FLEET_PROVISIONING_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - name: run Fleet Provisioning test + run: | + python3 ./servicetests/script/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ - name: configure AWS credentials (Connect and PubSub) uses: aws-actions/configure-aws-credentials@v2 with: diff --git a/pom.xml b/pom.xml index d1657a43..8263bcff 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,7 @@ samples/FleetProvisioning samples/Mqtt5/PubSub samples/Mqtt5/SharedSubscription + servicetests/tests/FleetProvisioning diff --git a/servicetests/script/fleet_provisioning_cfg.json b/servicetests/script/fleet_provisioning_cfg.json new file mode 100644 index 00000000..d7aba003 --- /dev/null +++ b/servicetests/script/fleet_provisioning_cfg.json @@ -0,0 +1,30 @@ +{ + "language": "Java", + "service_test_file": "servicetests/tests/FleetProvisioning", + "service_test_region": "us-east-1", + "service_test_main_class": "fleetProvisioning.FleetProvisioning", + "arguments": [ + { + "name": "--endpoint", + "secret": "ci/endpoint" + }, + { + "name": "--cert", + "secret": "ci/FleetProvisioning/cert", + "filename": "tmp_certificate.pem" + }, + { + "name": "--key", + "secret": "ci/FleetProvisioning/key", + "filename": "tmp_key.pem" + }, + { + "name": "--template_name", + "data": "CI_FleetProvisioning_Template" + }, + { + "name": "--template_parameters", + "data": "{SerialNumber:$INPUT_UUID}" + } + ] +} diff --git a/servicetests/script/run_service_test.py b/servicetests/script/run_service_test.py new file mode 100644 index 00000000..6b6fa7e0 --- /dev/null +++ b/servicetests/script/run_service_test.py @@ -0,0 +1,193 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0. + +import argparse +import os +import subprocess +import pathlib +import sys +import json +import boto3 + +current_folder = os.path.dirname(pathlib.Path(__file__).resolve()) +if sys.platform == "win32" or sys.platform == "cygwin": + current_folder += "\\" +else: + current_folder += "/" + +config_json = None +config_json_arguments_list = [] + +def setup_json_arguments_list(file, input_uuid=None): + global config_json + global config_json_arguments_list + + print("Attempting to get credentials from secrets using Boto3...") + secrets_client = boto3.client( + "secretsmanager", region_name=config_json['service_test_region']) + print("Processing arguments...") + + for argument in config_json['arguments']: + # Add the name of the argument + config_json_arguments_list.append(argument['name']) + + # Based on the data present, we need to process and add the data differently + try: + + # Is there a secret? If so, decode it! + if 'secret' in argument: + secret_data = secrets_client.get_secret_value( + SecretId=argument['secret'])["SecretString"] + + # Is this supposed to be stored in a file? + if 'filename' in argument: + with open(str(current_folder) + argument['filename'], "w") as file: + file.write(secret_data) + config_json_arguments_list.append( + str(current_folder) + argument['filename']) + else: + config_json_arguments_list.append(secret_data) + + # Raw data? just add it directly! + elif 'data' in argument: + tmp_value = argument['data'] + if isinstance(tmp_value, str) and input_uuid is not None: + if ("$INPUT_UUID" in tmp_value): + tmp_value = tmp_value.replace("$INPUT_UUID", input_uuid) + if (tmp_value != None and tmp_value != ""): + config_json_arguments_list.append(tmp_value) + + # None of the above? Just print an error + else: + print("ERROR - unknown or missing argument value!") + + except Exception as e: + print(f"Something went wrong processing {argument['name']}: {e}!") + return -1 + return 0 + + +def setup_service_test(file, input_uuid=None): + global config_json + + file_absolute = pathlib.Path(file).resolve() + json_file_data = "" + with open(file_absolute, "r") as json_file: + json_file_data = json_file.read() + + # Load the JSON data + config_json = json.loads(json_file_data) + + # Make sure required parameters are all there + if not 'language' in config_json or not 'service_test_file' in config_json \ + or not 'service_test_region' in config_json or not 'service_test_main_class' in config_json: + return -1 + + # Preprocess service test arguments (get secret data, etc) + setup_result = setup_json_arguments_list(file, input_uuid) + if setup_result != 0: + return setup_result + + print("JSON config file loaded!") + return 0 + + +def cleanup_service_test(): + global config_json + global config_json_arguments_list + + for argument in config_json['arguments']: + config_json_arguments_list.append(argument['name']) + + # Based on the data present, we need to process and add the data differently + try: + # Is there a file? If so, clean it! + if 'filename' in argument: + if (os.path.isfile(str(current_folder) + argument['filename'])): + os.remove(str(current_folder) + argument['filename']) + + # Windows 10 certificate store data? + if 'windows_cert_certificate' in argument and 'windows_cert_certificate_path' in argument \ + and 'windows_cert_key' in argument and 'windows_cert_key_path' in argument \ + and 'windows_cert_pfx_key_path' in argument: + + if (os.path.isfile(str(current_folder) + argument['windows_cert_certificate_path'])): + os.remove(str(current_folder) + + argument['windows_cert_certificate_path']) + if (os.path.isfile(str(current_folder) + argument['windows_cert_key_path'])): + os.remove(str(current_folder) + + argument['windows_cert_key_path']) + if (os.path.isfile(str(current_folder) + argument['windows_cert_pfx_key_path'])): + os.remove(str(current_folder) + + argument['windows_cert_pfx_key_path']) + + except Exception as e: + print(f"Something went wrong cleaning {argument['name']}!") + return -1 + + +def launch_service_test(): + global config_json + global config_json_arguments_list + + if (config_json == None): + print("No configuration JSON file data found!") + return -1 + + exit_code = 0 + + print("Launching service test...") + + # Flatten arguments down into a single string + arguments_as_string = "" + for i in range(0, len(config_json_arguments_list)): + arguments_as_string += str(config_json_arguments_list[i]) + if (i+1 < len(config_json_arguments_list)): + arguments_as_string += " " + + arguments = ["mvn", "compile", "exec:java"] + arguments.append("-pl") + arguments.append(config_json['service_test_file']) + arguments.append("-Dexec.mainClass=" + + config_json['service_test_main_class']) + arguments.append("-Daws.crt.ci=True") + + # We have to do this as a string, unfortunately, due to how -Dexec.args= works... + argument_string = subprocess.list2cmdline( + arguments) + " -Dexec.args=\"" + arguments_as_string + "\"" + print(f"Running cmd: {argument_string}") + service_test_return = subprocess.run(argument_string, shell=True) + exit_code = service_test_return.returncode + + cleanup_service_test() + return exit_code + + +def setup_service_test_and_launch(file, input_uuid=None): + setup_result = setup_service_test(file, input_uuid) + if setup_result != 0: + return setup_result + + print("About to launch service test...") + return launch_service_test() + + +def main(): + argument_parser = argparse.ArgumentParser( + description="Run service test in CI") + argument_parser.add_argument( + "--file", required=True, help="Configuration file to pull CI data from") + argument_parser.add_argument("--input_uuid", required=False, + help="UUID data to replace '$INPUT_UUID' with. Only works in Data field") + parsed_commands = argument_parser.parse_args() + + file = parsed_commands.file + input_uuid = parsed_commands.input_uuid + + print(f"Starting to launch service test: config {file}; input UUID: {input_uuid}") + test_result = setup_service_test_and_launch(file, input_uuid) + sys.exit(test_result) + + +if __name__ == "__main__": + main() diff --git a/servicetests/script/test_fleet_provisioning.py b/servicetests/script/test_fleet_provisioning.py new file mode 100644 index 00000000..a96effac --- /dev/null +++ b/servicetests/script/test_fleet_provisioning.py @@ -0,0 +1,76 @@ +import argparse +import boto3 +import uuid +import os +import sys +import run_service_test + + +def delete_thing(thing_name, region): + try: + iot_client = boto3.client('iot', region_name=region) + except Exception: + print("Error - could not make Boto3 client. Credentials likely could not be sourced") + return -1 + + thing_principals = None + try: + thing_principals = iot_client.list_thing_principals(thingName=thing_name) + except Exception: + print ("Could not get thing principals!") + return -1 + + try: + if thing_principals != None: + if thing_principals["principals"] != None: + if len(thing_principals["principals"]) > 0: + for principal in thing_principals["principals"]: + certificate_id = principal.split("/")[1] + iot_client.detach_thing_principal(thingName=thing_name, principal=principal) + iot_client.update_certificate(certificateId=certificate_id, newStatus ='INACTIVE') + iot_client.delete_certificate(certificateId=certificate_id, forceDelete=True) + except Exception as exception: + print (exception) + print ("Could not delete certificate!") + return -1 + + try: + iot_client.delete_thing(thingName=thing_name) + except Exception as exception: + print (exception) + print ("Could not delete IoT thing!") + return -1 + + print ("IoT thing deleted successfully") + return 0 + + +def main(): + argument_parser = argparse.ArgumentParser( + description="Run service test in CI") + argument_parser.add_argument( + "--input-uuid", required=False, help="UUID for thing name") + argument_parser.add_argument( + "--thing-name-prefix", required=False, default="", help="Prefix for a thing name") + argument_parser.add_argument( + "--region", required=False, default="us-east-1", help="The name of the region to use") + argument_parser.add_argument("--input_uuid", required=False, + help="UUID data to replace '$INPUT_UUID' with. Only works in Data field") + parsed_commands = argument_parser.parse_args() + + current_path = os.path.dirname(os.path.realpath(__file__)) + cfg_file = os.path.join(current_path, "fleet_provisioning_cfg.json") + input_uuid = parsed_commands.input_uuid if parsed_commands.input_uuid else str(uuid.uuid4()) + # Perform fleet provisioning. If it's successful, a newly created thing should appear. + result = run_service_test.setup_service_test_and_launch(cfg_file, input_uuid) + if result != 0: + sys.exit(result) + + thing_name = parsed_commands.thing_name_prefix + input_uuid + # Delete a thing created by fleet provisioning. + result = delete_thing(thing_name, parsed_commands.region) + sys.exit(result) + + +if __name__ == "__main__": + main() diff --git a/servicetests/tests/FleetProvisioning/pom.xml b/servicetests/tests/FleetProvisioning/pom.xml new file mode 100644 index 00000000..3ba03725 --- /dev/null +++ b/servicetests/tests/FleetProvisioning/pom.xml @@ -0,0 +1,46 @@ + + 4.0.0 + software.amazon.awssdk.iotdevicesdk + FleetProvisioningServiceTest + jar + 1.0-SNAPSHOT + ${project.groupId}:${project.artifactId} + Java bindings for the AWS IoT Core Service + https://github.com/awslabs/aws-iot-device-sdk-java-v2 + + 1.8 + 1.8 + UTF-8 + + + + software.amazon.awssdk.iotdevicesdk + aws-iot-device-sdk + 1.0.0-SNAPSHOT + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.2.0 + + + add-source + generate-sources + + add-source + + + + ../Utils/CommandLineUtils + + + + + + + + diff --git a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java new file mode 100644 index 00000000..4f30d91d --- /dev/null +++ b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java @@ -0,0 +1,339 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +package fleetProvisioning; + +import software.amazon.awssdk.crt.CRT; +import software.amazon.awssdk.crt.CrtResource; +import software.amazon.awssdk.crt.CrtRuntimeException; +import software.amazon.awssdk.crt.mqtt.MqttClientConnection; +import software.amazon.awssdk.crt.mqtt.MqttClientConnectionEvents; +import software.amazon.awssdk.crt.mqtt.QualityOfService; +import software.amazon.awssdk.iot.AwsIotMqttConnectionBuilder; +import software.amazon.awssdk.iot.iotidentity.IotIdentityClient; +import software.amazon.awssdk.iot.iotidentity.model.CreateCertificateFromCsrRequest; +import software.amazon.awssdk.iot.iotidentity.model.CreateCertificateFromCsrResponse; +import software.amazon.awssdk.iot.iotidentity.model.CreateCertificateFromCsrSubscriptionRequest; +import software.amazon.awssdk.iot.iotidentity.model.CreateKeysAndCertificateRequest; +import software.amazon.awssdk.iot.iotidentity.model.CreateKeysAndCertificateResponse; +import software.amazon.awssdk.iot.iotidentity.model.CreateKeysAndCertificateSubscriptionRequest; +import software.amazon.awssdk.iot.iotidentity.model.ErrorResponse; +import software.amazon.awssdk.iot.iotidentity.model.RegisterThingRequest; +import software.amazon.awssdk.iot.iotidentity.model.RegisterThingResponse; +import software.amazon.awssdk.iot.iotidentity.model.RegisterThingSubscriptionRequest; + +import java.nio.file.Files; +import java.nio.file.Paths; + +import java.util.HashMap; +import com.google.gson.Gson; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import utils.commandlineutils.CommandLineUtils; + +public class FleetProvisioning { + + static CompletableFuture gotResponse; + static IotIdentityClient iotIdentityClient; + + static CreateKeysAndCertificateResponse createKeysAndCertificateResponse = null; + static CreateCertificateFromCsrResponse createCertificateFromCsrResponse = null; + static RegisterThingResponse registerThingResponse = null; + + static long responseWaitTimeMs = 5000L; // 5 seconds + + static void onRejectedKeys(ErrorResponse response) { + System.out.println("CreateKeysAndCertificate Request rejected, errorCode: " + response.errorCode + + ", errorMessage: " + response.errorMessage + + ", statusCode: " + response.statusCode); + + gotResponse.complete(null); + } + + static void onRejectedCsr(ErrorResponse response) { + System.out.println("CreateCertificateFromCsr Request rejected, errorCode: " + response.errorCode + + ", errorMessage: " + response.errorMessage + + ", statusCode: " + response.statusCode); + + gotResponse.complete(null); + } + + static void onRejectedRegister(ErrorResponse response) { + + System.out.println("RegisterThing Request rejected, errorCode: " + response.errorCode + + ", errorMessage: " + response.errorMessage + + ", statusCode: " + response.statusCode); + + gotResponse.complete(null); + } + + static void onCreateKeysAndCertificateAccepted(CreateKeysAndCertificateResponse response) { + if (response != null) { + System.out.println("CreateKeysAndCertificate response certificateId: " + response.certificateId); + if (createKeysAndCertificateResponse == null) { + createKeysAndCertificateResponse = response; + } else { + System.out.println("CreateKeysAndCertificate response received after having already gotten a response!"); + } + } else { + System.out.println("CreateKeysAndCertificate response is null"); + } + gotResponse.complete(null); + } + + static void onCreateCertificateFromCsrResponseAccepted(CreateCertificateFromCsrResponse response) { + if (response != null) { + System.out.println("CreateCertificateFromCsr response certificateId: " + response.certificateId); + if (createCertificateFromCsrResponse == null) { + createCertificateFromCsrResponse = response; + } else { + System.out.println("CreateCertificateFromCsr response received after having already gotten a response!"); + } + } else { + System.out.println("CreateCertificateFromCsr response is null"); + } + gotResponse.complete(null); + } + + static void onRegisterThingAccepted(RegisterThingResponse response) { + if (response != null) { + System.out.println("RegisterThing response thingName: " + response.thingName); + if (registerThingResponse == null) { + registerThingResponse = response; + } else { + System.out.println("RegisterThing response received after having already gotten a response!"); + } + } else { + System.out.println("RegisterThing response is null"); + } + gotResponse.complete(null); + } + + static void onException(Exception e) { + e.printStackTrace(); + System.out.println("Exception occurred " + e); + } + + public static void main(String[] args) { + + CommandLineUtils.ServiceTestCommandLineData cmdData = CommandLineUtils.getInputForServiceTest("FleetProvisioning", args); + + MqttClientConnectionEvents callbacks = new MqttClientConnectionEvents() { + @Override + public void onConnectionInterrupted(int errorCode) { + if (errorCode != 0) { + System.out.println("Connection interrupted: " + errorCode + ": " + CRT.awsErrorString(errorCode)); + } + } + + @Override + public void onConnectionResumed(boolean sessionPresent) { + System.out.println("Connection resumed: " + (sessionPresent ? "existing session" : "clean session")); + } + }; + + MqttClientConnection connection = null; + boolean exitWithError = false; + + try { + /** + * Create the MQTT connection from the builder + */ + AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder.newMtlsBuilderFromPath(cmdData.input_cert, cmdData.input_key); + if (cmdData.input_ca != "") { + builder.withCertificateAuthorityFromPath(null, cmdData.input_ca); + } + builder.withConnectionEventCallbacks(callbacks) + .withClientId(cmdData.input_clientId) + .withEndpoint(cmdData.input_endpoint) + .withPort((short)cmdData.input_port) + .withCleanSession(true) + .withProtocolOperationTimeoutMs(60000); + connection = builder.build(); + builder.close(); + + /** + * Verify the connection was created + */ + if (connection == null) + { + throw new RuntimeException("MQTT connection creation failed!"); + } + + // Create the identity client (Identity = Fleet Provisioning) + iotIdentityClient = new IotIdentityClient(connection); + + // Connect + CompletableFuture connected = connection.connect(); + boolean sessionPresent = connected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Connected to " + (!sessionPresent ? "new" : "existing") + " session!"); + + // Fleet Provision based on whether there is a CSR file path or not + if (cmdData.input_csrPath == null) { + createKeysAndCertificateWorkflow(cmdData.input_templateName, cmdData.input_templateParameters); + } else { + createCertificateFromCsrWorkflow(cmdData.input_templateName, cmdData.input_templateParameters, cmdData.input_csrPath); + } + + // Disconnect + CompletableFuture disconnected = connection.disconnect(); + disconnected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + + } catch (Exception ex) { + System.out.println("Exception encountered! " + "\n"); + ex.printStackTrace(); + exitWithError = true; + } finally { + if (connection != null) { + // Close the connection now that we are completely done with it. + connection.close(); + } + } + + CrtResource.waitForNoResources(); + System.out.println("Service test complete!"); + + if (exitWithError) { + System.exit(1); + } else { + System.exit(0); + } + } + + private static void SubscribeToRegisterThing(String input_templateName) throws Exception { + RegisterThingSubscriptionRequest registerThingSubscriptionRequest = new RegisterThingSubscriptionRequest(); + registerThingSubscriptionRequest.templateName = input_templateName; + + CompletableFuture subscribedRegisterAccepted = iotIdentityClient.SubscribeToRegisterThingAccepted( + registerThingSubscriptionRequest, + QualityOfService.AT_LEAST_ONCE, + FleetProvisioning::onRegisterThingAccepted, + FleetProvisioning::onException); + + subscribedRegisterAccepted.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Subscribed to SubscribeToRegisterThingAccepted"); + + CompletableFuture subscribedRegisterRejected = iotIdentityClient.SubscribeToRegisterThingRejected( + registerThingSubscriptionRequest, + QualityOfService.AT_LEAST_ONCE, + FleetProvisioning::onRejectedRegister, + FleetProvisioning::onException); + + subscribedRegisterRejected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Subscribed to SubscribeToRegisterThingRejected"); + } + + private static void createKeysAndCertificateWorkflow(String input_templateName, String input_templateParameters) throws Exception { + CreateKeysAndCertificateSubscriptionRequest createKeysAndCertificateSubscriptionRequest = new CreateKeysAndCertificateSubscriptionRequest(); + CompletableFuture keysSubscribedAccepted = iotIdentityClient.SubscribeToCreateKeysAndCertificateAccepted( + createKeysAndCertificateSubscriptionRequest, + QualityOfService.AT_LEAST_ONCE, + FleetProvisioning::onCreateKeysAndCertificateAccepted); + + keysSubscribedAccepted.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Subscribed to CreateKeysAndCertificateAccepted"); + + CompletableFuture keysSubscribedRejected = iotIdentityClient.SubscribeToCreateKeysAndCertificateRejected( + createKeysAndCertificateSubscriptionRequest, + QualityOfService.AT_LEAST_ONCE, + FleetProvisioning::onRejectedKeys); + + keysSubscribedRejected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Subscribed to CreateKeysAndCertificateRejected"); + + // Subscribes to the register thing accepted and rejected topics + SubscribeToRegisterThing(input_templateName); + + CompletableFuture publishKeys = iotIdentityClient.PublishCreateKeysAndCertificate( + new CreateKeysAndCertificateRequest(), + QualityOfService.AT_LEAST_ONCE); + + gotResponse = new CompletableFuture<>(); + publishKeys.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Published to CreateKeysAndCertificate"); + gotResponse.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Got response at CreateKeysAndCertificate"); + + // Verify the response is good + if (createKeysAndCertificateResponse == null) { + throw new Exception("Got invalid/error createKeysAndCertificateResponse"); + } + + gotResponse = new CompletableFuture<>(); + System.out.println("RegisterThing now...."); + RegisterThingRequest registerThingRequest = new RegisterThingRequest(); + registerThingRequest.certificateOwnershipToken = createKeysAndCertificateResponse.certificateOwnershipToken; + registerThingRequest.templateName = input_templateName; + + if (input_templateParameters != null && input_templateParameters != "") { + registerThingRequest.parameters = new Gson().fromJson(input_templateParameters, HashMap.class); + } + + CompletableFuture publishRegister = iotIdentityClient.PublishRegisterThing( + registerThingRequest, + QualityOfService.AT_LEAST_ONCE); + + publishRegister.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Published to RegisterThing"); + gotResponse.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Got response at RegisterThing"); + } + + private static void createCertificateFromCsrWorkflow(String input_templateName, String input_templateParameters, String input_csrPath) throws Exception { + CreateCertificateFromCsrSubscriptionRequest createCertificateFromCsrSubscriptionRequest = new CreateCertificateFromCsrSubscriptionRequest(); + CompletableFuture csrSubscribedAccepted = iotIdentityClient.SubscribeToCreateCertificateFromCsrAccepted( + createCertificateFromCsrSubscriptionRequest, + QualityOfService.AT_LEAST_ONCE, + FleetProvisioning::onCreateCertificateFromCsrResponseAccepted); + + csrSubscribedAccepted.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Subscribed to CreateCertificateFromCsrAccepted"); + + CompletableFuture csrSubscribedRejected = iotIdentityClient.SubscribeToCreateCertificateFromCsrRejected( + createCertificateFromCsrSubscriptionRequest, + QualityOfService.AT_LEAST_ONCE, + FleetProvisioning::onRejectedCsr); + + csrSubscribedRejected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Subscribed to CreateCertificateFromCsrRejected"); + + // Subscribes to the register thing accepted and rejected topics + SubscribeToRegisterThing(input_templateName); + + String csrContents = new String(Files.readAllBytes(Paths.get(input_csrPath))); + CreateCertificateFromCsrRequest createCertificateFromCsrRequest = new CreateCertificateFromCsrRequest(); + createCertificateFromCsrRequest.certificateSigningRequest = csrContents; + CompletableFuture publishCsr = iotIdentityClient.PublishCreateCertificateFromCsr( + createCertificateFromCsrRequest, + QualityOfService.AT_LEAST_ONCE); + + gotResponse = new CompletableFuture<>(); + publishCsr.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Published to CreateCertificateFromCsr"); + gotResponse.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Got response at CreateCertificateFromCsr"); + + // Verify the response is good + if (createCertificateFromCsrResponse == null) { + throw new Exception("Got invalid/error createCertificateFromCsrResponse"); + } + + gotResponse = new CompletableFuture<>(); + System.out.println("RegisterThing now...."); + RegisterThingRequest registerThingRequest = new RegisterThingRequest(); + registerThingRequest.certificateOwnershipToken = createCertificateFromCsrResponse.certificateOwnershipToken; + registerThingRequest.templateName = input_templateName; + registerThingRequest.parameters = new Gson().fromJson(input_templateParameters, HashMap.class); + CompletableFuture publishRegister = iotIdentityClient.PublishRegisterThing( + registerThingRequest, + QualityOfService.AT_LEAST_ONCE); + + publishRegister.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Published to RegisterThing"); + gotResponse.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Got response at RegisterThing"); + } +} diff --git a/servicetests/tests/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java b/servicetests/tests/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java new file mode 100644 index 00000000..dd1b39a3 --- /dev/null +++ b/servicetests/tests/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java @@ -0,0 +1,299 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +package utils.commandlineutils; + +import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.CompletableFuture; +import java.io.UnsupportedEncodingException; + +import software.amazon.awssdk.crt.*; +import software.amazon.awssdk.crt.io.*; +import software.amazon.awssdk.crt.mqtt.*; +import software.amazon.awssdk.crt.mqtt5.*; +import software.amazon.awssdk.crt.mqtt5.packets.*; +import software.amazon.awssdk.iot.AwsIotMqttConnectionBuilder; +import software.amazon.awssdk.iot.AwsIotMqtt5ClientBuilder; +import software.amazon.awssdk.crt.http.HttpProxyOptions; +import software.amazon.awssdk.crt.auth.credentials.X509CredentialsProvider; +import software.amazon.awssdk.crt.auth.credentials.CognitoCredentialsProvider; +import software.amazon.awssdk.crt.Log; +import software.amazon.awssdk.crt.Log.LogLevel; + +public class CommandLineUtils { + private String programName; + private final HashMap registeredCommands = new HashMap<>(); + private List commandArguments; + private boolean isCI; + + /** + * Functions for registering and command line arguments + */ + + public void registerProgramName(String newProgramName) { + programName = newProgramName; + } + + public void registerCommand(CommandLineOption option) { + if (registeredCommands.containsKey(option.commandName)) { + System.out.println("Cannot register command: " + option.commandName + ". Command already registered"); + return; + } + registeredCommands.put(option.commandName, option); + } + + public void registerCommand(String commandName, String exampleInput, String helpOutput) { + registerCommand(new CommandLineOption(commandName, exampleInput, helpOutput)); + } + + public void removeCommand(String commandName) { + registeredCommands.remove(commandName); + } + + public void updateCommandHelp(String commandName, String newCommandHelp) { + if (registeredCommands.containsKey(commandName)) { + registeredCommands.get(commandName).helpOutput = newCommandHelp; + } + } + + public void sendArguments(String[] arguments) { + // Automatically register the help command + registerCommand(m_cmd_help, "", "Prints this message"); + + commandArguments = Arrays.asList(arguments); + + // Automatically check for help and print if present + if (hasCommand(m_cmd_help)) + { + printHelp(); + if (isCI == true) { + throw new RuntimeException("Help argument called"); + } else { + System.exit(-1); + } + } + } + + public boolean hasCommand(String command) { + return commandArguments.contains("--" + command); + } + + public String getCommand(String command) { + for (Iterator iter = commandArguments.iterator(); iter.hasNext();) { + String value = iter.next(); + if (Objects.equals(value,"--" + command)) { + if (iter.hasNext()) { + return iter.next(); + } + else { + System.out.println("Error - found command but at end of arguments!\n"); + return ""; + } + } + } + return ""; + } + + public String getCommandOrDefault(String command, String commandDefault) { + if (commandArguments.contains("--" + command)) { + return getCommand(command); + } + return commandDefault; + } + + public String getCommandRequired(String command) { + if (commandArguments.contains("--" + command)) { + return getCommand(command); + } + printHelp(); + System.out.println("Missing required argument: --" + command + "\n"); + + if (isCI == true) { + throw new RuntimeException("Missing required argument"); + } else { + System.exit(-1); + } + return ""; + } + + public String getCommandRequired(String command, String commandAlt){ + if(commandArguments.contains("--" + commandAlt)){ + return getCommand(commandAlt); + } + return getCommandRequired(command); + } + + public void printHelp() { + System.out.println("Usage:"); + + String messageOne = programName; + for (String commandName : registeredCommands.keySet()) { + messageOne += " --" + commandName + " " + registeredCommands.get(commandName).exampleInput; + } + System.out.println(messageOne + "\n"); + + for (String commandName : registeredCommands.keySet()) { + messageOne += " --" + commandName + " " + registeredCommands.get(commandName).exampleInput; + System.out.println("* " + commandName + "\t\t" + registeredCommands.get(commandName).helpOutput); + } + } + + public void determineIfCI() { + String ciPropValue = System.getProperty("aws.crt.ci"); + isCI = ciPropValue != null && Boolean.valueOf(ciPropValue); + } + + /** + * Helper functions for registering commands + */ + + public void addCommonLoggingCommands() { + registerCommand(m_cmd_verbosity, "", "The amount of detail in the logging output of the service test." + + " Options: 'Fatal', 'Error', 'Warn', 'Info', 'Debug', 'Trace' or 'None' (optional, default='None')."); + registerCommand(m_cmd_log_destination, "", "Where logging should be routed to." + + " Options: 'Stdout', 'Stderr', 'File' (optional, default='Stderr')."); + registerCommand(m_cmd_log_file_name, "", "File name to save logging to." + + " (optional, default='log.txt')."); + } + + public void addClientIdAndPort() { + registerCommand(m_cmd_client_id, "", "Client id to use (optional, default='test-*')."); + registerCommand(m_cmd_port, "", "Port to connect to on the endpoint (optional, default='8883')."); + } + + public void addCommonMQTTCommands() { + registerCommand(m_cmd_endpoint, "", "The endpoint of the mqtt server, not including a port."); + registerCommand(m_cmd_ca_file, "", "Path to AmazonRootCA1.pem (optional, system trust store used by default)."); + } + + public void addKeyAndCertCommands() { + registerCommand(m_cmd_key_file, "", "Path to your key in PEM format."); + registerCommand(m_cmd_cert_file, "", "Path to your client certificate in PEM format."); + } + + /** + * Helper functions for parsing commands + */ + + private void parseCommonLoggingCommands(ServiceTestCommandLineData returnData){ + String verbosity = getCommandOrDefault(m_cmd_verbosity, "None"); + String log_destination = getCommandOrDefault(m_cmd_log_destination, "Stderr"); + String log_file_name = getCommandOrDefault(m_cmd_log_file_name, "log.txt"); + + if(verbosity != "None"){ + switch (log_destination) { + case "Stderr": + Log.initLoggingToStderr(LogLevel.valueOf(verbosity)); + break; + case "Stdout": + Log.initLoggingToStdout(LogLevel.valueOf(verbosity)); + break; + case "File": + Log.initLoggingToFile(LogLevel.valueOf(verbosity), log_file_name); + break; + default: + break; + } + } + } + + private void parseCommonMQTTCommands(ServiceTestCommandLineData returnData) { + returnData.input_endpoint = getCommandRequired(m_cmd_endpoint); + returnData.input_ca = getCommandOrDefault(m_cmd_ca_file, ""); + } + + private void parseKeyAndCertCommands(ServiceTestCommandLineData returnData) + { + returnData.input_cert = getCommandRequired(m_cmd_cert_file); + returnData.input_key = getCommandRequired(m_cmd_key_file); + } + + private void parseClientIdAndPort(ServiceTestCommandLineData returnData) { + returnData.input_clientId = getCommandOrDefault(m_cmd_client_id, "test-" + UUID.randomUUID().toString()); + returnData.input_port = Integer.parseInt(getCommandOrDefault(m_cmd_port, "8883")); + } + + public class ServiceTestCommandLineData + { + // General use + public String input_endpoint; + public String input_cert; + public String input_key; + public String input_ca; + public String input_clientId; + public int input_port; + // Fleet provisioning + public String input_templateName; + public String input_templateParameters; + public String input_csrPath; + } + + public ServiceTestCommandLineData parseServiceTestInputFleetProvisioning(String [] args) + { + addCommonLoggingCommands(); + addCommonMQTTCommands(); + addKeyAndCertCommands(); + addClientIdAndPort(); + registerCommand(m_cmd_fleet_template_name, "", "Provisioning template name."); + registerCommand(m_cmd_fleet_template_parameters, "", "Provisioning template parameters."); + registerCommand(m_cmd_fleet_template_csr, "", "Path to the CSR file (optional)."); + sendArguments(args); + + ServiceTestCommandLineData returnData = new ServiceTestCommandLineData(); + parseCommonLoggingCommands(returnData); + parseCommonMQTTCommands(returnData); + parseKeyAndCertCommands(returnData); + parseClientIdAndPort(returnData); + returnData.input_templateName = getCommandRequired(m_cmd_fleet_template_name); + returnData.input_templateParameters = getCommandRequired(m_cmd_fleet_template_parameters); + returnData.input_csrPath = getCommandOrDefault(m_cmd_fleet_template_csr, null); + return returnData; + } + + public static ServiceTestCommandLineData getInputForServiceTest(String serviceTestName, String[] args) + { + CommandLineUtils cmdUtils = new CommandLineUtils(); + cmdUtils.registerProgramName(serviceTestName); + cmdUtils.determineIfCI(); + + if (serviceTestName.equals("FleetProvisioning")) { + return cmdUtils.parseServiceTestInputFleetProvisioning(args); + } else { + throw new RuntimeException("Unknown service test name!"); + } + } + + /** + * Constants for commonly used/needed commands + */ + private static final String m_cmd_log_destination = "log_destination"; + private static final String m_cmd_log_file_name = "log_file_name"; + private static final String m_cmd_verbosity = "verbosity"; + private static final String m_cmd_endpoint = "endpoint"; + private static final String m_cmd_ca_file = "ca_file"; + private static final String m_cmd_cert_file = "cert"; + private static final String m_cmd_key_file = "key"; + private static final String m_cmd_client_id = "client_id"; + private static final String m_cmd_port = "port"; + private static final String m_cmd_help = "help"; + private static final String m_cmd_fleet_template_name = "template_name"; + private static final String m_cmd_fleet_template_parameters = "template_parameters"; + private static final String m_cmd_fleet_template_csr = "csr"; + private static final String m_cmd_region = "region"; + private static final String m_cmd_print_discover_resp_only = "print_discover_resp_only"; +} + +class CommandLineOption { + public String commandName; + public String exampleInput; + public String helpOutput; + + CommandLineOption(String name, String example, String help) { + commandName = name; + exampleInput = example; + helpOutput = help; + } +} From 378a351b775f4428b285fb301febdf777c73d486 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Mon, 6 Nov 2023 13:30:23 -0800 Subject: [PATCH 02/34] Move deleting thing to separate module --- .../script/test_fleet_provisioning.py | 43 +------------------ servicetests/tools/delete_iot_thing.py | 32 ++++++++++++++ 2 files changed, 34 insertions(+), 41 deletions(-) create mode 100644 servicetests/tools/delete_iot_thing.py diff --git a/servicetests/script/test_fleet_provisioning.py b/servicetests/script/test_fleet_provisioning.py index a96effac..17f6a5de 100644 --- a/servicetests/script/test_fleet_provisioning.py +++ b/servicetests/script/test_fleet_provisioning.py @@ -1,48 +1,9 @@ import argparse -import boto3 import uuid import os import sys import run_service_test - - -def delete_thing(thing_name, region): - try: - iot_client = boto3.client('iot', region_name=region) - except Exception: - print("Error - could not make Boto3 client. Credentials likely could not be sourced") - return -1 - - thing_principals = None - try: - thing_principals = iot_client.list_thing_principals(thingName=thing_name) - except Exception: - print ("Could not get thing principals!") - return -1 - - try: - if thing_principals != None: - if thing_principals["principals"] != None: - if len(thing_principals["principals"]) > 0: - for principal in thing_principals["principals"]: - certificate_id = principal.split("/")[1] - iot_client.detach_thing_principal(thingName=thing_name, principal=principal) - iot_client.update_certificate(certificateId=certificate_id, newStatus ='INACTIVE') - iot_client.delete_certificate(certificateId=certificate_id, forceDelete=True) - except Exception as exception: - print (exception) - print ("Could not delete certificate!") - return -1 - - try: - iot_client.delete_thing(thingName=thing_name) - except Exception as exception: - print (exception) - print ("Could not delete IoT thing!") - return -1 - - print ("IoT thing deleted successfully") - return 0 +import delete_iot_thing def main(): @@ -68,7 +29,7 @@ def main(): thing_name = parsed_commands.thing_name_prefix + input_uuid # Delete a thing created by fleet provisioning. - result = delete_thing(thing_name, parsed_commands.region) + result = delete_iot_thing.delete_iot_thing(thing_name, parsed_commands.region) sys.exit(result) diff --git a/servicetests/tools/delete_iot_thing.py b/servicetests/tools/delete_iot_thing.py new file mode 100644 index 00000000..32cb79b3 --- /dev/null +++ b/servicetests/tools/delete_iot_thing.py @@ -0,0 +1,32 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0. + +import boto3 + +def delete_iot_thing(thing_name, region): + try: + iot_client = boto3.client('iot', region_name=region) + except Exception as e: + print(f"ERROR: Could not make Boto3 client. Credentials likely could not be sourced. Exception: {e}") + return -1 + + try: + thing_principals = iot_client.list_thing_principals(thingName=thing_name) + print(f"principals: {thing_principals}") + for principal in thing_principals["principals"]: + certificate_id = principal.split("/")[1] + iot_client.detach_thing_principal(thingName=thing_name, principal=principal) + iot_client.update_certificate(certificateId=certificate_id, newStatus='INACTIVE') + iot_client.delete_certificate(certificateId=certificate_id, forceDelete=True) + except Exception as e: + print(f"ERROR: Could not delete certificate for IoT thing {thing_name}. Exception: {e}") + return -1 + + try: + iot_client.delete_thing(thingName=thing_name) + except Exception as e: + print(f"ERROR: Could not delete IoT thing {thing_name}. Exception: {e}") + return -1 + + print("IoT thing deleted successfully") + return 0 From 0c6fc93aceab3e4d2243818b79a32a3cdfbc392f Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Mon, 6 Nov 2023 13:34:50 -0800 Subject: [PATCH 03/34] Fix error message --- servicetests/tools/delete_iot_thing.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/servicetests/tools/delete_iot_thing.py b/servicetests/tools/delete_iot_thing.py index 32cb79b3..62f7810c 100644 --- a/servicetests/tools/delete_iot_thing.py +++ b/servicetests/tools/delete_iot_thing.py @@ -3,6 +3,7 @@ import boto3 + def delete_iot_thing(thing_name, region): try: iot_client = boto3.client('iot', region_name=region) @@ -19,7 +20,8 @@ def delete_iot_thing(thing_name, region): iot_client.update_certificate(certificateId=certificate_id, newStatus='INACTIVE') iot_client.delete_certificate(certificateId=certificate_id, forceDelete=True) except Exception as e: - print(f"ERROR: Could not delete certificate for IoT thing {thing_name}. Exception: {e}") + print("ERROR: Could not delete certificate for IoT thing " + f"{thing_name}, probably thing does not exist. Exception: {e}") return -1 try: From 6ca22560778082d6453606341df249eae42e3661 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Mon, 6 Nov 2023 13:43:06 -0800 Subject: [PATCH 04/34] Cleanup code --- servicetests/script/test_fleet_provisioning.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/servicetests/script/test_fleet_provisioning.py b/servicetests/script/test_fleet_provisioning.py index 17f6a5de..f49f1aee 100644 --- a/servicetests/script/test_fleet_provisioning.py +++ b/servicetests/script/test_fleet_provisioning.py @@ -1,3 +1,6 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0. + import argparse import uuid import os @@ -23,15 +26,15 @@ def main(): cfg_file = os.path.join(current_path, "fleet_provisioning_cfg.json") input_uuid = parsed_commands.input_uuid if parsed_commands.input_uuid else str(uuid.uuid4()) # Perform fleet provisioning. If it's successful, a newly created thing should appear. - result = run_service_test.setup_service_test_and_launch(cfg_file, input_uuid) - if result != 0: - sys.exit(result) + test_result = run_service_test.setup_service_test_and_launch(cfg_file, input_uuid) - thing_name = parsed_commands.thing_name_prefix + input_uuid # Delete a thing created by fleet provisioning. - result = delete_iot_thing.delete_iot_thing(thing_name, parsed_commands.region) - sys.exit(result) + # We want to try to delete thing even if test was unsuccessful. + thing_name = parsed_commands.thing_name_prefix + input_uuid + delete_result = delete_iot_thing.delete_iot_thing(thing_name, parsed_commands.region) + if test_result != 0 or delete_result != 0: + sys.exit(-1) if __name__ == "__main__": main() From f10641b00fb892daf9b452454cd15b56e9fa8835 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Mon, 6 Nov 2023 14:50:27 -0800 Subject: [PATCH 05/34] Fix log message --- servicetests/script/test_fleet_provisioning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/servicetests/script/test_fleet_provisioning.py b/servicetests/script/test_fleet_provisioning.py index f49f1aee..ee9f3ebf 100644 --- a/servicetests/script/test_fleet_provisioning.py +++ b/servicetests/script/test_fleet_provisioning.py @@ -11,7 +11,7 @@ def main(): argument_parser = argparse.ArgumentParser( - description="Run service test in CI") + description="Run Fleet Provisioning test in CI") argument_parser.add_argument( "--input-uuid", required=False, help="UUID for thing name") argument_parser.add_argument( From 7a74ae80453e243d5f347f81990c440a1ba5e86a Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Mon, 6 Nov 2023 14:55:46 -0800 Subject: [PATCH 06/34] fixup --- .../fleetProvisioning/FleetProvisioning.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java index 4f30d91d..5aa52ab7 100644 --- a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java +++ b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java @@ -117,8 +117,23 @@ static void onException(Exception e) { System.out.println("Exception occurred " + e); } - public static void main(String[] args) { + static MqttClientConnection createConnection() { + try (AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder + .newMtlsBuilderFromPath(DATestUtils.certificatePath, DATestUtils.keyPath)) { + builder.withClientId(clientId) + .withEndpoint(DATestUtils.endpoint) + .withPort(port) + .withCleanSession(true) + .withProtocolOperationTimeoutMs(60000); + + MqttClientConnection connection = builder.build(); + return connection; + } catch (Exception ex) { + throw new RuntimeException("Failed to create connection", ex); + } + } + public static void main(String[] args) { CommandLineUtils.ServiceTestCommandLineData cmdData = CommandLineUtils.getInputForServiceTest("FleetProvisioning", args); MqttClientConnectionEvents callbacks = new MqttClientConnectionEvents() { @@ -158,8 +173,7 @@ public void onConnectionResumed(boolean sessionPresent) { /** * Verify the connection was created */ - if (connection == null) - { + if (connection == null) { throw new RuntimeException("MQTT connection creation failed!"); } From 0e1a11f8171355e21ccbded98aafc5e20ad951ce Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Mon, 6 Nov 2023 15:07:58 -0800 Subject: [PATCH 07/34] fixup --- .../fleetProvisioning/FleetProvisioning.java | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java index 5aa52ab7..0149cf93 100644 --- a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java +++ b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java @@ -117,19 +117,35 @@ static void onException(Exception e) { System.out.println("Exception occurred " + e); } - static MqttClientConnection createConnection() { - try (AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder - .newMtlsBuilderFromPath(DATestUtils.certificatePath, DATestUtils.keyPath)) { - builder.withClientId(clientId) + static MqttClientConnection createConnection(Boolean useMqtt5) { + if (useMqtt5) { + DALifecycleEvents lifecycleEvents = new DALifecycleEvents(); + try (AwsIotMqtt5ClientBuilder builder = AwsIotMqtt5ClientBuilder.newDirectMqttBuilderWithMtlsFromPath( + DATestUtils.endpoint, DATestUtils.certificatePath, DATestUtils.keyPath)) { + ConnectPacket.ConnectPacketBuilder connectProperties = new ConnectPacket.ConnectPacketBuilder(); + connectProperties.withClientId(clientId); + builder.withConnectProperties(connectProperties); + builder.withLifeCycleEvents(lifecycleEvents); + builder.withPort((long)port); + Mqtt5Client client = builder.build(); + builder.close(); + return new MqttClientConnection(client, null); + } catch (Exception ex) { + throw new RuntimeException("Failed to create MQTT311 connection from MQTT5 client", ex); + } + } else { + try (AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder + .newMtlsBuilderFromPath(DATestUtils.certificatePath, DATestUtils.keyPath)) { + builder.withClientId(clientId) .withEndpoint(DATestUtils.endpoint) .withPort(port) .withCleanSession(true) .withProtocolOperationTimeoutMs(60000); - MqttClientConnection connection = builder.build(); - return connection; - } catch (Exception ex) { - throw new RuntimeException("Failed to create connection", ex); + return builder.build(); + } catch (Exception ex) { + throw new RuntimeException("Failed to create MQTT311 connection", ex); + } } } @@ -150,10 +166,9 @@ public void onConnectionResumed(boolean sessionPresent) { } }; - MqttClientConnection connection = null; boolean exitWithError = false; - try { + try (MqttClientConnection connection = createConnection(true)) { /** * Create the MQTT connection from the builder */ @@ -200,11 +215,6 @@ public void onConnectionResumed(boolean sessionPresent) { System.out.println("Exception encountered! " + "\n"); ex.printStackTrace(); exitWithError = true; - } finally { - if (connection != null) { - // Close the connection now that we are completely done with it. - connection.close(); - } } CrtResource.waitForNoResources(); From 5e1306e5b53a517a865f26374f041e6cb4fe8c41 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Mon, 6 Nov 2023 16:38:23 -0800 Subject: [PATCH 08/34] Mqtt3 and mqtt5 fleet provisioning --- ...json => mqtt3_fleet_provisioning_cfg.json} | 0 .../script/mqtt5_fleet_provisioning_cfg.json | 34 +++++ servicetests/tests/FleetProvisioning/pom.xml | 1 + .../fleetProvisioning/FleetProvisioning.java | 143 +++++++++++------- .../ServiceTestLifecycleEvents.java | 55 +++++++ .../commandlineutils/CommandLineUtils.java | 7 + 6 files changed, 186 insertions(+), 54 deletions(-) rename servicetests/script/{fleet_provisioning_cfg.json => mqtt3_fleet_provisioning_cfg.json} (100%) create mode 100644 servicetests/script/mqtt5_fleet_provisioning_cfg.json create mode 100644 servicetests/tests/ServiceTestLifecycleEvents/ServiceTestLifecycleEvents.java diff --git a/servicetests/script/fleet_provisioning_cfg.json b/servicetests/script/mqtt3_fleet_provisioning_cfg.json similarity index 100% rename from servicetests/script/fleet_provisioning_cfg.json rename to servicetests/script/mqtt3_fleet_provisioning_cfg.json diff --git a/servicetests/script/mqtt5_fleet_provisioning_cfg.json b/servicetests/script/mqtt5_fleet_provisioning_cfg.json new file mode 100644 index 00000000..0f484f02 --- /dev/null +++ b/servicetests/script/mqtt5_fleet_provisioning_cfg.json @@ -0,0 +1,34 @@ +{ + "language": "Java", + "service_test_file": "servicetests/tests/FleetProvisioning", + "service_test_region": "us-east-1", + "service_test_main_class": "fleetProvisioning.FleetProvisioning", + "arguments": [ + { + "name": "--use_mqtt5", + "data": "true" + }, + { + "name": "--endpoint", + "secret": "ci/endpoint" + }, + { + "name": "--cert", + "secret": "ci/FleetProvisioning/cert", + "filename": "tmp_certificate.pem" + }, + { + "name": "--key", + "secret": "ci/FleetProvisioning/key", + "filename": "tmp_key.pem" + }, + { + "name": "--template_name", + "data": "CI_FleetProvisioning_Template" + }, + { + "name": "--template_parameters", + "data": "{SerialNumber:$INPUT_UUID}" + } + ] +} diff --git a/servicetests/tests/FleetProvisioning/pom.xml b/servicetests/tests/FleetProvisioning/pom.xml index 3ba03725..47aedc40 100644 --- a/servicetests/tests/FleetProvisioning/pom.xml +++ b/servicetests/tests/FleetProvisioning/pom.xml @@ -36,6 +36,7 @@ ../Utils/CommandLineUtils + ../ServiceTestLifecycleEvents diff --git a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java index 0149cf93..d89ef637 100644 --- a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java +++ b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java @@ -11,7 +11,11 @@ import software.amazon.awssdk.crt.mqtt.MqttClientConnection; import software.amazon.awssdk.crt.mqtt.MqttClientConnectionEvents; import software.amazon.awssdk.crt.mqtt.QualityOfService; +import software.amazon.awssdk.crt.mqtt5.Mqtt5ClientOptions; +import software.amazon.awssdk.crt.mqtt5.Mqtt5Client; +import software.amazon.awssdk.crt.mqtt5.packets.ConnectPacket; import software.amazon.awssdk.iot.AwsIotMqttConnectionBuilder; +import software.amazon.awssdk.iot.AwsIotMqtt5ClientBuilder; import software.amazon.awssdk.iot.iotidentity.IotIdentityClient; import software.amazon.awssdk.iot.iotidentity.model.CreateCertificateFromCsrRequest; import software.amazon.awssdk.iot.iotidentity.model.CreateCertificateFromCsrResponse; @@ -33,6 +37,7 @@ import java.util.concurrent.TimeUnit; import utils.commandlineutils.CommandLineUtils; +import ServiceTestLifecycleEvents.ServiceTestLifecycleEvents; public class FleetProvisioning { @@ -45,6 +50,63 @@ public class FleetProvisioning { static long responseWaitTimeMs = 5000L; // 5 seconds + abstract static class ConnectionWrapper implements AutoCloseable { + public abstract CompletableFuture start(); + public abstract CompletableFuture stop(); + + public abstract MqttClientConnection getConnection(); + } + + static final class Mqtt3ConnectionWrapper extends ConnectionWrapper { + static MqttClientConnection connection; + + @Override + public CompletableFuture start() { + return connection.connect(); + } + + @Override + public CompletableFuture stop() { + return connection.disconnect(); + } + + @Override + public void close() { + connection.close(); + } + + @Override + public MqttClientConnection getConnection() { + return connection; + } + }; + + static final class Mqtt5ConnectionWrapper extends ConnectionWrapper { + static Mqtt5Client client; + static MqttClientConnection connection; + + @Override + public CompletableFuture start() { + return connection.connect(); + } + + @Override + public CompletableFuture stop() { + return connection.disconnect(); + } + + @Override + public void close() { + client.close(); + connection.close(); + } + + @Override + public MqttClientConnection getConnection() { + return connection; + } + }; + static void onRejectedKeys(ErrorResponse response) { System.out.println("CreateKeysAndCertificate Request rejected, errorCode: " + response.errorCode + ", errorMessage: " + response.errorMessage + @@ -117,32 +179,40 @@ static void onException(Exception e) { System.out.println("Exception occurred " + e); } - static MqttClientConnection createConnection(Boolean useMqtt5) { + static ConnectionWrapper createConnection(CommandLineUtils.ServiceTestCommandLineData cmdData, Boolean useMqtt5) { if (useMqtt5) { - DALifecycleEvents lifecycleEvents = new DALifecycleEvents(); + ServiceTestLifecycleEvents lifecycleEvents = new ServiceTestLifecycleEvents(); try (AwsIotMqtt5ClientBuilder builder = AwsIotMqtt5ClientBuilder.newDirectMqttBuilderWithMtlsFromPath( - DATestUtils.endpoint, DATestUtils.certificatePath, DATestUtils.keyPath)) { + cmdData.input_endpoint, cmdData.input_cert, cmdData.input_key)) { ConnectPacket.ConnectPacketBuilder connectProperties = new ConnectPacket.ConnectPacketBuilder(); - connectProperties.withClientId(clientId); + connectProperties.withClientId(cmdData.input_clientId); builder.withConnectProperties(connectProperties); builder.withLifeCycleEvents(lifecycleEvents); - builder.withPort((long)port); - Mqtt5Client client = builder.build(); - builder.close(); - return new MqttClientConnection(client, null); + builder.withPort((long)cmdData.input_port); + Mqtt5ConnectionWrapper connWrapper = new Mqtt5ConnectionWrapper(); + connWrapper.client = builder.build(); + connWrapper.connection = new MqttClientConnection(connWrapper.client, null); + if (connWrapper.connection == null) { + throw new RuntimeException("MQTT5 connection creation failed!"); + } + return connWrapper; } catch (Exception ex) { throw new RuntimeException("Failed to create MQTT311 connection from MQTT5 client", ex); } } else { try (AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder - .newMtlsBuilderFromPath(DATestUtils.certificatePath, DATestUtils.keyPath)) { - builder.withClientId(clientId) - .withEndpoint(DATestUtils.endpoint) - .withPort(port) + .newMtlsBuilderFromPath(cmdData.input_cert, cmdData.input_key)) { + builder.withClientId(cmdData.input_clientId) + .withEndpoint(cmdData.input_endpoint) + .withPort((short)cmdData.input_port) .withCleanSession(true) .withProtocolOperationTimeoutMs(60000); - - return builder.build(); + Mqtt3ConnectionWrapper connWrapper = new Mqtt3ConnectionWrapper(); + connWrapper.connection = builder.build(); + if (connWrapper.connection == null) { + throw new RuntimeException("MQTT311 connection creation failed!"); + } + return connWrapper; } catch (Exception ex) { throw new RuntimeException("Failed to create MQTT311 connection", ex); } @@ -152,51 +222,16 @@ static MqttClientConnection createConnection(Boolean useMqtt5) { public static void main(String[] args) { CommandLineUtils.ServiceTestCommandLineData cmdData = CommandLineUtils.getInputForServiceTest("FleetProvisioning", args); - MqttClientConnectionEvents callbacks = new MqttClientConnectionEvents() { - @Override - public void onConnectionInterrupted(int errorCode) { - if (errorCode != 0) { - System.out.println("Connection interrupted: " + errorCode + ": " + CRT.awsErrorString(errorCode)); - } - } - - @Override - public void onConnectionResumed(boolean sessionPresent) { - System.out.println("Connection resumed: " + (sessionPresent ? "existing session" : "clean session")); - } - }; - boolean exitWithError = false; - try (MqttClientConnection connection = createConnection(true)) { - /** - * Create the MQTT connection from the builder - */ - AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder.newMtlsBuilderFromPath(cmdData.input_cert, cmdData.input_key); - if (cmdData.input_ca != "") { - builder.withCertificateAuthorityFromPath(null, cmdData.input_ca); - } - builder.withConnectionEventCallbacks(callbacks) - .withClientId(cmdData.input_clientId) - .withEndpoint(cmdData.input_endpoint) - .withPort((short)cmdData.input_port) - .withCleanSession(true) - .withProtocolOperationTimeoutMs(60000); - connection = builder.build(); - builder.close(); - - /** - * Verify the connection was created - */ - if (connection == null) { - throw new RuntimeException("MQTT connection creation failed!"); - } + System.out.println("==================== " + cmdData.input_use_mqtt5); + try (ConnectionWrapper connection = createConnection(cmdData, cmdData.input_use_mqtt5)) { // Create the identity client (Identity = Fleet Provisioning) - iotIdentityClient = new IotIdentityClient(connection); + iotIdentityClient = new IotIdentityClient(connection.getConnection()); // Connect - CompletableFuture connected = connection.connect(); + CompletableFuture connected = connection.start(); boolean sessionPresent = connected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); System.out.println("Connected to " + (!sessionPresent ? "new" : "existing") + " session!"); @@ -208,7 +243,7 @@ public void onConnectionResumed(boolean sessionPresent) { } // Disconnect - CompletableFuture disconnected = connection.disconnect(); + CompletableFuture disconnected = connection.stop(); disconnected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); } catch (Exception ex) { diff --git a/servicetests/tests/ServiceTestLifecycleEvents/ServiceTestLifecycleEvents.java b/servicetests/tests/ServiceTestLifecycleEvents/ServiceTestLifecycleEvents.java new file mode 100644 index 00000000..ef391ca1 --- /dev/null +++ b/servicetests/tests/ServiceTestLifecycleEvents/ServiceTestLifecycleEvents.java @@ -0,0 +1,55 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +package ServiceTestLifecycleEvents; + +import software.amazon.awssdk.crt.CRT; +import software.amazon.awssdk.crt.mqtt5.packets.DisconnectPacket; +import software.amazon.awssdk.crt.mqtt5.*; +import software.amazon.awssdk.crt.mqtt5.Mqtt5Client; +import software.amazon.awssdk.crt.mqtt5.Mqtt5ClientOptions; + +import java.util.concurrent.CompletableFuture; + +final public class ServiceTestLifecycleEvents implements Mqtt5ClientOptions.LifecycleEvents { + CompletableFuture connectedFuture = new CompletableFuture<>(); + CompletableFuture stoppedFuture = new CompletableFuture<>(); + + @Override + public void onAttemptingConnect(Mqtt5Client client, OnAttemptingConnectReturn onAttemptingConnectReturn) { + System.out.println("Mqtt5 Client: Attempting connection..."); + } + + @Override + public void onConnectionSuccess(Mqtt5Client client, OnConnectionSuccessReturn onConnectionSuccessReturn) { + System.out.println("Mqtt5 Client: Connection success, client ID: " + + onConnectionSuccessReturn.getNegotiatedSettings().getAssignedClientID()); + connectedFuture.complete(null); + } + + @Override + public void onConnectionFailure(Mqtt5Client client, OnConnectionFailureReturn onConnectionFailureReturn) { + String errorString = CRT.awsErrorString(onConnectionFailureReturn.getErrorCode()); + System.out.println("Mqtt5 Client: Connection failed with error: " + errorString); + connectedFuture.completeExceptionally(new Exception("Could not connect: " + errorString)); + } + + @Override + public void onDisconnection(Mqtt5Client client, OnDisconnectionReturn onDisconnectionReturn) { + System.out.println("Mqtt5 Client: Disconnected"); + DisconnectPacket disconnectPacket = onDisconnectionReturn.getDisconnectPacket(); + if (disconnectPacket != null) { + System.out.println("\tDisconnection packet code: " + disconnectPacket.getReasonCode()); + System.out.println("\tDisconnection packet reason: " + disconnectPacket.getReasonString()); + } + } + + @Override + public void onStopped(Mqtt5Client client, OnStoppedReturn onStoppedReturn) { + System.out.println("Mqtt5 Client: Stopped"); + stoppedFuture.complete(null); + } +} + diff --git a/servicetests/tests/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java b/servicetests/tests/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java index dd1b39a3..14152f90 100644 --- a/servicetests/tests/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java +++ b/servicetests/tests/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java @@ -200,6 +200,10 @@ private void parseCommonLoggingCommands(ServiceTestCommandLineData returnData){ } } + private void parseUseMqtt5(ServiceTestCommandLineData returnData) { + returnData.input_use_mqtt5 = Boolean.parseBoolean(getCommandOrDefault(m_cmd_use_mqtt5, "false")); + } + private void parseCommonMQTTCommands(ServiceTestCommandLineData returnData) { returnData.input_endpoint = getCommandRequired(m_cmd_endpoint); returnData.input_ca = getCommandOrDefault(m_cmd_ca_file, ""); @@ -219,6 +223,7 @@ private void parseClientIdAndPort(ServiceTestCommandLineData returnData) { public class ServiceTestCommandLineData { // General use + public Boolean input_use_mqtt5; public String input_endpoint; public String input_cert; public String input_key; @@ -243,6 +248,7 @@ public ServiceTestCommandLineData parseServiceTestInputFleetProvisioning(String sendArguments(args); ServiceTestCommandLineData returnData = new ServiceTestCommandLineData(); + parseUseMqtt5(returnData); parseCommonLoggingCommands(returnData); parseCommonMQTTCommands(returnData); parseKeyAndCertCommands(returnData); @@ -269,6 +275,7 @@ public static ServiceTestCommandLineData getInputForServiceTest(String serviceTe /** * Constants for commonly used/needed commands */ + private static final String m_cmd_use_mqtt5 = "use_mqtt5"; private static final String m_cmd_log_destination = "log_destination"; private static final String m_cmd_log_file_name = "log_file_name"; private static final String m_cmd_verbosity = "verbosity"; From 912e098a0d9cc0d5c444dfd93035bdc2ca1b97a6 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Mon, 6 Nov 2023 17:25:35 -0800 Subject: [PATCH 09/34] Working version --- .../script/mqtt3_fleet_provisioning_cfg.json | 4 + .../script/mqtt5_fleet_provisioning_cfg.json | 4 +- .../script/test_fleet_provisioning.py | 8 +- servicetests/tests/FleetProvisioning/pom.xml | 1 + .../fleetProvisioning/FleetProvisioning.java | 145 +++++------------- .../commandlineutils/CommandLineUtils.java | 28 ++-- .../Mqtt3ClientConnectionWrapper.java | 34 ++++ .../Mqtt5ClientConnectionWrapper.java | 37 +++++ .../MqttClientConnectionWrapper.java | 17 ++ 9 files changed, 152 insertions(+), 126 deletions(-) create mode 100644 servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/Mqtt3ClientConnectionWrapper.java create mode 100644 servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/Mqtt5ClientConnectionWrapper.java create mode 100644 servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapper.java diff --git a/servicetests/script/mqtt3_fleet_provisioning_cfg.json b/servicetests/script/mqtt3_fleet_provisioning_cfg.json index d7aba003..431ca3e3 100644 --- a/servicetests/script/mqtt3_fleet_provisioning_cfg.json +++ b/servicetests/script/mqtt3_fleet_provisioning_cfg.json @@ -4,6 +4,10 @@ "service_test_region": "us-east-1", "service_test_main_class": "fleetProvisioning.FleetProvisioning", "arguments": [ + { + "name": "--mqtt_version", + "data": 3 + }, { "name": "--endpoint", "secret": "ci/endpoint" diff --git a/servicetests/script/mqtt5_fleet_provisioning_cfg.json b/servicetests/script/mqtt5_fleet_provisioning_cfg.json index 0f484f02..9ff6886f 100644 --- a/servicetests/script/mqtt5_fleet_provisioning_cfg.json +++ b/servicetests/script/mqtt5_fleet_provisioning_cfg.json @@ -5,8 +5,8 @@ "service_test_main_class": "fleetProvisioning.FleetProvisioning", "arguments": [ { - "name": "--use_mqtt5", - "data": "true" + "name": "--mqtt_version", + "data": 5 }, { "name": "--endpoint", diff --git a/servicetests/script/test_fleet_provisioning.py b/servicetests/script/test_fleet_provisioning.py index ee9f3ebf..91183696 100644 --- a/servicetests/script/test_fleet_provisioning.py +++ b/servicetests/script/test_fleet_provisioning.py @@ -18,13 +18,15 @@ def main(): "--thing-name-prefix", required=False, default="", help="Prefix for a thing name") argument_parser.add_argument( "--region", required=False, default="us-east-1", help="The name of the region to use") - argument_parser.add_argument("--input_uuid", required=False, - help="UUID data to replace '$INPUT_UUID' with. Only works in Data field") + argument_parser.add_argument( + "--mqtt-version", required=True, choices=[3, 5], type=int, help="MQTT protocol version to use") parsed_commands = argument_parser.parse_args() current_path = os.path.dirname(os.path.realpath(__file__)) - cfg_file = os.path.join(current_path, "fleet_provisioning_cfg.json") + cfg_file_pfx = "mqtt3_" if parsed_commands.mqtt_version == 3 else "mqtt5_" + cfg_file = os.path.join(current_path, cfg_file_pfx + "fleet_provisioning_cfg.json") input_uuid = parsed_commands.input_uuid if parsed_commands.input_uuid else str(uuid.uuid4()) + # Perform fleet provisioning. If it's successful, a newly created thing should appear. test_result = run_service_test.setup_service_test_and_launch(cfg_file, input_uuid) diff --git a/servicetests/tests/FleetProvisioning/pom.xml b/servicetests/tests/FleetProvisioning/pom.xml index 47aedc40..3424b6f2 100644 --- a/servicetests/tests/FleetProvisioning/pom.xml +++ b/servicetests/tests/FleetProvisioning/pom.xml @@ -36,6 +36,7 @@ ../Utils/CommandLineUtils + ../Utils/MqttClientConnectionWrapper ../ServiceTestLifecycleEvents diff --git a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java index d89ef637..2204fb18 100644 --- a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java +++ b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java @@ -37,6 +37,7 @@ import java.util.concurrent.TimeUnit; import utils.commandlineutils.CommandLineUtils; +import utils.mqttclientconnectionwrapper.*; import ServiceTestLifecycleEvents.ServiceTestLifecycleEvents; public class FleetProvisioning { @@ -50,68 +51,52 @@ public class FleetProvisioning { static long responseWaitTimeMs = 5000L; // 5 seconds - abstract static class ConnectionWrapper implements AutoCloseable { - public abstract CompletableFuture start(); - public abstract CompletableFuture stop(); - - public abstract MqttClientConnection getConnection(); - } - - static final class Mqtt3ConnectionWrapper extends ConnectionWrapper { - static MqttClientConnection connection; - - @Override - public CompletableFuture start() { - return connection.connect(); - } - - @Override - public CompletableFuture stop() { - return connection.disconnect(); - } - - @Override - public void close() { - connection.close(); - } - - @Override - public MqttClientConnection getConnection() { - return connection; - } - }; - - static final class Mqtt5ConnectionWrapper extends ConnectionWrapper { - static Mqtt5Client client; - static MqttClientConnection connection; - - @Override - public CompletableFuture start() { - return connection.connect(); - } - - @Override - public CompletableFuture stop() { - return connection.disconnect(); - } - - @Override - public void close() { - client.close(); - connection.close(); - } - - @Override - public MqttClientConnection getConnection() { - return connection; + static MqttClientConnectionWrapper createConnection(CommandLineUtils.ServiceTestCommandLineData cmdData, Integer mqttVersion) { + if (mqttVersion == 3) { + try (AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder + .newMtlsBuilderFromPath(cmdData.input_cert, cmdData.input_key)) { + builder.withClientId(cmdData.input_clientId) + .withEndpoint(cmdData.input_endpoint) + .withPort((short)cmdData.input_port) + .withCleanSession(true) + .withProtocolOperationTimeoutMs(60000); + Mqtt3ClientConnectionWrapper connWrapper = new Mqtt3ClientConnectionWrapper(); + connWrapper.connection = builder.build(); + if (connWrapper.connection == null) { + throw new RuntimeException("MQTT311 connection creation failed!"); + } + return connWrapper; + } catch (Exception ex) { + throw new RuntimeException("Failed to create MQTT311 connection", ex); + } + } else if (mqttVersion == 5) { + ServiceTestLifecycleEvents lifecycleEvents = new ServiceTestLifecycleEvents(); + try (AwsIotMqtt5ClientBuilder builder = AwsIotMqtt5ClientBuilder.newDirectMqttBuilderWithMtlsFromPath( + cmdData.input_endpoint, cmdData.input_cert, cmdData.input_key)) { + ConnectPacket.ConnectPacketBuilder connectProperties = new ConnectPacket.ConnectPacketBuilder(); + connectProperties.withClientId(cmdData.input_clientId); + builder.withConnectProperties(connectProperties); + builder.withLifeCycleEvents(lifecycleEvents); + builder.withPort((long)cmdData.input_port); + Mqtt5ClientConnectionWrapper connWrapper = new Mqtt5ClientConnectionWrapper(); + connWrapper.client = builder.build(); + connWrapper.connection = new MqttClientConnection(connWrapper.client, null); + if (connWrapper.connection == null) { + throw new RuntimeException("MQTT5 connection creation failed!"); + } + return connWrapper; + } catch (Exception ex) { + throw new RuntimeException("Failed to create MQTT311 connection from MQTT5 client", ex); + } + } else { + throw new RuntimeException("Invalid MQTT version specified: " + mqttVersion); } - }; + } static void onRejectedKeys(ErrorResponse response) { System.out.println("CreateKeysAndCertificate Request rejected, errorCode: " + response.errorCode + ", errorMessage: " + response.errorMessage + ", statusCode: " + response.statusCode); - gotResponse.complete(null); } @@ -119,16 +104,13 @@ static void onRejectedCsr(ErrorResponse response) { System.out.println("CreateCertificateFromCsr Request rejected, errorCode: " + response.errorCode + ", errorMessage: " + response.errorMessage + ", statusCode: " + response.statusCode); - gotResponse.complete(null); } static void onRejectedRegister(ErrorResponse response) { - System.out.println("RegisterThing Request rejected, errorCode: " + response.errorCode + ", errorMessage: " + response.errorMessage + ", statusCode: " + response.statusCode); - gotResponse.complete(null); } @@ -179,54 +161,12 @@ static void onException(Exception e) { System.out.println("Exception occurred " + e); } - static ConnectionWrapper createConnection(CommandLineUtils.ServiceTestCommandLineData cmdData, Boolean useMqtt5) { - if (useMqtt5) { - ServiceTestLifecycleEvents lifecycleEvents = new ServiceTestLifecycleEvents(); - try (AwsIotMqtt5ClientBuilder builder = AwsIotMqtt5ClientBuilder.newDirectMqttBuilderWithMtlsFromPath( - cmdData.input_endpoint, cmdData.input_cert, cmdData.input_key)) { - ConnectPacket.ConnectPacketBuilder connectProperties = new ConnectPacket.ConnectPacketBuilder(); - connectProperties.withClientId(cmdData.input_clientId); - builder.withConnectProperties(connectProperties); - builder.withLifeCycleEvents(lifecycleEvents); - builder.withPort((long)cmdData.input_port); - Mqtt5ConnectionWrapper connWrapper = new Mqtt5ConnectionWrapper(); - connWrapper.client = builder.build(); - connWrapper.connection = new MqttClientConnection(connWrapper.client, null); - if (connWrapper.connection == null) { - throw new RuntimeException("MQTT5 connection creation failed!"); - } - return connWrapper; - } catch (Exception ex) { - throw new RuntimeException("Failed to create MQTT311 connection from MQTT5 client", ex); - } - } else { - try (AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder - .newMtlsBuilderFromPath(cmdData.input_cert, cmdData.input_key)) { - builder.withClientId(cmdData.input_clientId) - .withEndpoint(cmdData.input_endpoint) - .withPort((short)cmdData.input_port) - .withCleanSession(true) - .withProtocolOperationTimeoutMs(60000); - Mqtt3ConnectionWrapper connWrapper = new Mqtt3ConnectionWrapper(); - connWrapper.connection = builder.build(); - if (connWrapper.connection == null) { - throw new RuntimeException("MQTT311 connection creation failed!"); - } - return connWrapper; - } catch (Exception ex) { - throw new RuntimeException("Failed to create MQTT311 connection", ex); - } - } - } - public static void main(String[] args) { CommandLineUtils.ServiceTestCommandLineData cmdData = CommandLineUtils.getInputForServiceTest("FleetProvisioning", args); boolean exitWithError = false; - System.out.println("==================== " + cmdData.input_use_mqtt5); - - try (ConnectionWrapper connection = createConnection(cmdData, cmdData.input_use_mqtt5)) { + try (MqttClientConnectionWrapper connection = createConnection(cmdData, cmdData.input_mqtt_version)) { // Create the identity client (Identity = Fleet Provisioning) iotIdentityClient = new IotIdentityClient(connection.getConnection()); @@ -245,7 +185,6 @@ public static void main(String[] args) { // Disconnect CompletableFuture disconnected = connection.stop(); disconnected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); - } catch (Exception ex) { System.out.println("Exception encountered! " + "\n"); ex.printStackTrace(); diff --git a/servicetests/tests/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java b/servicetests/tests/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java index 14152f90..9f919b1d 100644 --- a/servicetests/tests/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java +++ b/servicetests/tests/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java @@ -12,14 +12,6 @@ import software.amazon.awssdk.crt.*; import software.amazon.awssdk.crt.io.*; -import software.amazon.awssdk.crt.mqtt.*; -import software.amazon.awssdk.crt.mqtt5.*; -import software.amazon.awssdk.crt.mqtt5.packets.*; -import software.amazon.awssdk.iot.AwsIotMqttConnectionBuilder; -import software.amazon.awssdk.iot.AwsIotMqtt5ClientBuilder; -import software.amazon.awssdk.crt.http.HttpProxyOptions; -import software.amazon.awssdk.crt.auth.credentials.X509CredentialsProvider; -import software.amazon.awssdk.crt.auth.credentials.CognitoCredentialsProvider; import software.amazon.awssdk.crt.Log; import software.amazon.awssdk.crt.Log.LogLevel; @@ -164,8 +156,8 @@ public void addClientIdAndPort() { registerCommand(m_cmd_port, "", "Port to connect to on the endpoint (optional, default='8883')."); } - public void addCommonMQTTCommands() { - registerCommand(m_cmd_endpoint, "", "The endpoint of the mqtt server, not including a port."); + public void addCommonMqttCommands() { + registerCommand(m_cmd_endpoint, "", "The endpoint of the MQTT server, not including a port."); registerCommand(m_cmd_ca_file, "", "Path to AmazonRootCA1.pem (optional, system trust store used by default)."); } @@ -200,11 +192,11 @@ private void parseCommonLoggingCommands(ServiceTestCommandLineData returnData){ } } - private void parseUseMqtt5(ServiceTestCommandLineData returnData) { - returnData.input_use_mqtt5 = Boolean.parseBoolean(getCommandOrDefault(m_cmd_use_mqtt5, "false")); + private void parseMqttVersion(ServiceTestCommandLineData returnData) { + returnData.input_mqtt_version = Integer.parseInt(getCommandOrDefault(m_cmd_mqtt_version, "3")); } - private void parseCommonMQTTCommands(ServiceTestCommandLineData returnData) { + private void parseCommonMqttCommands(ServiceTestCommandLineData returnData) { returnData.input_endpoint = getCommandRequired(m_cmd_endpoint); returnData.input_ca = getCommandOrDefault(m_cmd_ca_file, ""); } @@ -223,7 +215,7 @@ private void parseClientIdAndPort(ServiceTestCommandLineData returnData) { public class ServiceTestCommandLineData { // General use - public Boolean input_use_mqtt5; + public int input_mqtt_version; public String input_endpoint; public String input_cert; public String input_key; @@ -239,7 +231,7 @@ public class ServiceTestCommandLineData public ServiceTestCommandLineData parseServiceTestInputFleetProvisioning(String [] args) { addCommonLoggingCommands(); - addCommonMQTTCommands(); + addCommonMqttCommands(); addKeyAndCertCommands(); addClientIdAndPort(); registerCommand(m_cmd_fleet_template_name, "", "Provisioning template name."); @@ -248,9 +240,9 @@ public ServiceTestCommandLineData parseServiceTestInputFleetProvisioning(String sendArguments(args); ServiceTestCommandLineData returnData = new ServiceTestCommandLineData(); - parseUseMqtt5(returnData); + parseMqttVersion(returnData); parseCommonLoggingCommands(returnData); - parseCommonMQTTCommands(returnData); + parseCommonMqttCommands(returnData); parseKeyAndCertCommands(returnData); parseClientIdAndPort(returnData); returnData.input_templateName = getCommandRequired(m_cmd_fleet_template_name); @@ -275,7 +267,7 @@ public static ServiceTestCommandLineData getInputForServiceTest(String serviceTe /** * Constants for commonly used/needed commands */ - private static final String m_cmd_use_mqtt5 = "use_mqtt5"; + private static final String m_cmd_mqtt_version = "mqtt_version"; private static final String m_cmd_log_destination = "log_destination"; private static final String m_cmd_log_file_name = "log_file_name"; private static final String m_cmd_verbosity = "verbosity"; diff --git a/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/Mqtt3ClientConnectionWrapper.java b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/Mqtt3ClientConnectionWrapper.java new file mode 100644 index 00000000..eea43031 --- /dev/null +++ b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/Mqtt3ClientConnectionWrapper.java @@ -0,0 +1,34 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +package utils.mqttclientconnectionwrapper; + +import software.amazon.awssdk.crt.mqtt.MqttClientConnection; + +import java.util.concurrent.CompletableFuture; + +final public class Mqtt3ClientConnectionWrapper extends MqttClientConnectionWrapper { + public static MqttClientConnection connection; + + @Override + public CompletableFuture start() { + return connection.connect(); + } + + @Override + public CompletableFuture stop() { + return connection.disconnect(); + } + + @Override + public void close() { + connection.close(); + } + + @Override + public MqttClientConnection getConnection() { + return connection; + } +}; diff --git a/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/Mqtt5ClientConnectionWrapper.java b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/Mqtt5ClientConnectionWrapper.java new file mode 100644 index 00000000..54dc2644 --- /dev/null +++ b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/Mqtt5ClientConnectionWrapper.java @@ -0,0 +1,37 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +package utils.mqttclientconnectionwrapper; + +import software.amazon.awssdk.crt.mqtt.MqttClientConnection; +import software.amazon.awssdk.crt.mqtt5.Mqtt5Client; + +import java.util.concurrent.CompletableFuture; + +final public class Mqtt5ClientConnectionWrapper extends MqttClientConnectionWrapper { + public static Mqtt5Client client; + public static MqttClientConnection connection; + + @Override + public CompletableFuture start() { + return connection.connect(); + } + + @Override + public CompletableFuture stop() { + return connection.disconnect(); + } + + @Override + public void close() { + client.close(); + connection.close(); + } + + @Override + public MqttClientConnection getConnection() { + return connection; + } +}; diff --git a/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapper.java b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapper.java new file mode 100644 index 00000000..00367ed6 --- /dev/null +++ b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapper.java @@ -0,0 +1,17 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +package utils.mqttclientconnectionwrapper; + +import software.amazon.awssdk.crt.mqtt.MqttClientConnection; + +import java.util.concurrent.CompletableFuture; + +abstract public class MqttClientConnectionWrapper implements AutoCloseable { + public abstract CompletableFuture start(); + public abstract CompletableFuture stop(); + + public abstract MqttClientConnection getConnection(); +} From 8a5c6ea2ae8899ad688a7c9a47bea3f16b112fd2 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Mon, 6 Nov 2023 17:36:20 -0800 Subject: [PATCH 10/34] Fix CI --- .github/workflows/ci.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5f0c696..63c5e88e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -476,9 +476,14 @@ jobs: with: role-to-assume: ${{ env.CI_FLEET_PROVISIONING_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} - - name: run Fleet Provisioning test + - name: run Fleet Provisioning test for MQTT311 run: | - python3 ./servicetests/script/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ + export PYTHONPATH=${PWD}/servicetests/tools:$PYTHONPATH + python3 ./servicetests/script/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 3 + - name: run Fleet Provisioning test for MQTT5 + run: | + export PYTHONPATH=${PWD}/servicetests/tools:$PYTHONPATH + python3 ./servicetests/script/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 5 - name: configure AWS credentials (Connect and PubSub) uses: aws-actions/configure-aws-credentials@v2 with: From f2fec2a3380d4e64ac569c88088bdd1981e951c7 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Tue, 7 Nov 2023 09:42:56 -0800 Subject: [PATCH 11/34] wip --- .gitignore | 3 + .../script/mqtt3_fleet_provisioning_cfg.json | 6 +- .../script/mqtt5_fleet_provisioning_cfg.json | 6 +- servicetests/script/run_service_test.py | 193 ------------------ .../tools => utils}/delete_iot_thing.py | 0 utils/{run_sample_ci.py => run_in_ci.py} | 81 ++++---- 6 files changed, 53 insertions(+), 236 deletions(-) delete mode 100644 servicetests/script/run_service_test.py rename {servicetests/tools => utils}/delete_iot_thing.py (100%) rename utils/{run_sample_ci.py => run_in_ci.py} (85%) diff --git a/.gitignore b/.gitignore index 2e5ab984..94c8b3f7 100644 --- a/.gitignore +++ b/.gitignore @@ -185,3 +185,6 @@ bin/ # docs are updated automatically by .github/workflows/docs.yml docs/ + +# Python cache +__pycache__ diff --git a/servicetests/script/mqtt3_fleet_provisioning_cfg.json b/servicetests/script/mqtt3_fleet_provisioning_cfg.json index 431ca3e3..22135d6f 100644 --- a/servicetests/script/mqtt3_fleet_provisioning_cfg.json +++ b/servicetests/script/mqtt3_fleet_provisioning_cfg.json @@ -1,8 +1,8 @@ { "language": "Java", - "service_test_file": "servicetests/tests/FleetProvisioning", - "service_test_region": "us-east-1", - "service_test_main_class": "fleetProvisioning.FleetProvisioning", + "runnable_file": "servicetests/tests/FleetProvisioning", + "runnable_region": "us-east-1", + "runnable_main_class": "fleetProvisioning.FleetProvisioning", "arguments": [ { "name": "--mqtt_version", diff --git a/servicetests/script/mqtt5_fleet_provisioning_cfg.json b/servicetests/script/mqtt5_fleet_provisioning_cfg.json index 9ff6886f..17d11aa9 100644 --- a/servicetests/script/mqtt5_fleet_provisioning_cfg.json +++ b/servicetests/script/mqtt5_fleet_provisioning_cfg.json @@ -1,8 +1,8 @@ { "language": "Java", - "service_test_file": "servicetests/tests/FleetProvisioning", - "service_test_region": "us-east-1", - "service_test_main_class": "fleetProvisioning.FleetProvisioning", + "runnable_file": "servicetests/tests/FleetProvisioning", + "runnable_region": "us-east-1", + "runnable_main_class": "fleetProvisioning.FleetProvisioning", "arguments": [ { "name": "--mqtt_version", diff --git a/servicetests/script/run_service_test.py b/servicetests/script/run_service_test.py deleted file mode 100644 index 6b6fa7e0..00000000 --- a/servicetests/script/run_service_test.py +++ /dev/null @@ -1,193 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0. - -import argparse -import os -import subprocess -import pathlib -import sys -import json -import boto3 - -current_folder = os.path.dirname(pathlib.Path(__file__).resolve()) -if sys.platform == "win32" or sys.platform == "cygwin": - current_folder += "\\" -else: - current_folder += "/" - -config_json = None -config_json_arguments_list = [] - -def setup_json_arguments_list(file, input_uuid=None): - global config_json - global config_json_arguments_list - - print("Attempting to get credentials from secrets using Boto3...") - secrets_client = boto3.client( - "secretsmanager", region_name=config_json['service_test_region']) - print("Processing arguments...") - - for argument in config_json['arguments']: - # Add the name of the argument - config_json_arguments_list.append(argument['name']) - - # Based on the data present, we need to process and add the data differently - try: - - # Is there a secret? If so, decode it! - if 'secret' in argument: - secret_data = secrets_client.get_secret_value( - SecretId=argument['secret'])["SecretString"] - - # Is this supposed to be stored in a file? - if 'filename' in argument: - with open(str(current_folder) + argument['filename'], "w") as file: - file.write(secret_data) - config_json_arguments_list.append( - str(current_folder) + argument['filename']) - else: - config_json_arguments_list.append(secret_data) - - # Raw data? just add it directly! - elif 'data' in argument: - tmp_value = argument['data'] - if isinstance(tmp_value, str) and input_uuid is not None: - if ("$INPUT_UUID" in tmp_value): - tmp_value = tmp_value.replace("$INPUT_UUID", input_uuid) - if (tmp_value != None and tmp_value != ""): - config_json_arguments_list.append(tmp_value) - - # None of the above? Just print an error - else: - print("ERROR - unknown or missing argument value!") - - except Exception as e: - print(f"Something went wrong processing {argument['name']}: {e}!") - return -1 - return 0 - - -def setup_service_test(file, input_uuid=None): - global config_json - - file_absolute = pathlib.Path(file).resolve() - json_file_data = "" - with open(file_absolute, "r") as json_file: - json_file_data = json_file.read() - - # Load the JSON data - config_json = json.loads(json_file_data) - - # Make sure required parameters are all there - if not 'language' in config_json or not 'service_test_file' in config_json \ - or not 'service_test_region' in config_json or not 'service_test_main_class' in config_json: - return -1 - - # Preprocess service test arguments (get secret data, etc) - setup_result = setup_json_arguments_list(file, input_uuid) - if setup_result != 0: - return setup_result - - print("JSON config file loaded!") - return 0 - - -def cleanup_service_test(): - global config_json - global config_json_arguments_list - - for argument in config_json['arguments']: - config_json_arguments_list.append(argument['name']) - - # Based on the data present, we need to process and add the data differently - try: - # Is there a file? If so, clean it! - if 'filename' in argument: - if (os.path.isfile(str(current_folder) + argument['filename'])): - os.remove(str(current_folder) + argument['filename']) - - # Windows 10 certificate store data? - if 'windows_cert_certificate' in argument and 'windows_cert_certificate_path' in argument \ - and 'windows_cert_key' in argument and 'windows_cert_key_path' in argument \ - and 'windows_cert_pfx_key_path' in argument: - - if (os.path.isfile(str(current_folder) + argument['windows_cert_certificate_path'])): - os.remove(str(current_folder) + - argument['windows_cert_certificate_path']) - if (os.path.isfile(str(current_folder) + argument['windows_cert_key_path'])): - os.remove(str(current_folder) + - argument['windows_cert_key_path']) - if (os.path.isfile(str(current_folder) + argument['windows_cert_pfx_key_path'])): - os.remove(str(current_folder) + - argument['windows_cert_pfx_key_path']) - - except Exception as e: - print(f"Something went wrong cleaning {argument['name']}!") - return -1 - - -def launch_service_test(): - global config_json - global config_json_arguments_list - - if (config_json == None): - print("No configuration JSON file data found!") - return -1 - - exit_code = 0 - - print("Launching service test...") - - # Flatten arguments down into a single string - arguments_as_string = "" - for i in range(0, len(config_json_arguments_list)): - arguments_as_string += str(config_json_arguments_list[i]) - if (i+1 < len(config_json_arguments_list)): - arguments_as_string += " " - - arguments = ["mvn", "compile", "exec:java"] - arguments.append("-pl") - arguments.append(config_json['service_test_file']) - arguments.append("-Dexec.mainClass=" + - config_json['service_test_main_class']) - arguments.append("-Daws.crt.ci=True") - - # We have to do this as a string, unfortunately, due to how -Dexec.args= works... - argument_string = subprocess.list2cmdline( - arguments) + " -Dexec.args=\"" + arguments_as_string + "\"" - print(f"Running cmd: {argument_string}") - service_test_return = subprocess.run(argument_string, shell=True) - exit_code = service_test_return.returncode - - cleanup_service_test() - return exit_code - - -def setup_service_test_and_launch(file, input_uuid=None): - setup_result = setup_service_test(file, input_uuid) - if setup_result != 0: - return setup_result - - print("About to launch service test...") - return launch_service_test() - - -def main(): - argument_parser = argparse.ArgumentParser( - description="Run service test in CI") - argument_parser.add_argument( - "--file", required=True, help="Configuration file to pull CI data from") - argument_parser.add_argument("--input_uuid", required=False, - help="UUID data to replace '$INPUT_UUID' with. Only works in Data field") - parsed_commands = argument_parser.parse_args() - - file = parsed_commands.file - input_uuid = parsed_commands.input_uuid - - print(f"Starting to launch service test: config {file}; input UUID: {input_uuid}") - test_result = setup_service_test_and_launch(file, input_uuid) - sys.exit(test_result) - - -if __name__ == "__main__": - main() diff --git a/servicetests/tools/delete_iot_thing.py b/utils/delete_iot_thing.py similarity index 100% rename from servicetests/tools/delete_iot_thing.py rename to utils/delete_iot_thing.py diff --git a/utils/run_sample_ci.py b/utils/run_in_ci.py similarity index 85% rename from utils/run_sample_ci.py rename to utils/run_in_ci.py index 512d6fe2..a145f176 100644 --- a/utils/run_sample_ci.py +++ b/utils/run_in_ci.py @@ -9,7 +9,7 @@ import sys import json # Needs to be installed via pip -import boto3 # - for launching sample +import boto3 current_folder = os.path.dirname(pathlib.Path(__file__).resolve()) if sys.platform == "win32" or sys.platform == "cygwin": @@ -23,13 +23,13 @@ pfx_certificate_store_location = "CurrentUser\\My" pfx_password = "" # Setting a password causes issues, but an empty string is valid so we use that -def setup_json_arguments_list(parsed_commands): +def setup_json_arguments_list(file, input_uuid=None): global config_json global config_json_arguments_list print("Attempting to get credentials from secrets using Boto3...") - secrets_client = boto3.client("secretsmanager", region_name=config_json['sample_region']) - print ("Processing arguments...") + secrets_client = boto3.client("secretsmanager", region_name=config_json['runnable_region']) + print("Processing arguments...") for argument in config_json['arguments']: # Add the name of the argument @@ -80,18 +80,18 @@ def setup_json_arguments_list(parsed_commands): # Raw data? just add it directly! elif 'data' in argument: tmp_value = argument['data'] - if isinstance(tmp_value, str) and 'input_uuid' in parsed_commands: + if isinstance(tmp_value, str) and input_uuid is not None: if ("$INPUT_UUID" in tmp_value): - tmp_value = tmp_value.replace("$INPUT_UUID", parsed_commands.input_uuid) + tmp_value = tmp_value.replace("$INPUT_UUID", input_uuid) if (tmp_value != None and tmp_value != ""): config_json_arguments_list.append(tmp_value) # None of the above? Just print an error else: - print ("ERROR - unknown or missing argument value!") + print("ERROR - unknown or missing argument value!") except Exception as e: - print (f"Something went wrong processing {argument['name']}!") + print(f"Something went wrong processing {argument['name']}: {e}!") return -1 return 0 @@ -186,10 +186,10 @@ def make_windows_pfx_file(certificate_file_path, private_key_path, pfx_file_path print("ERROR - Windows PFX file can only be created on a Windows platform!") return 1 -def setup_sample(parsed_commands): +def setup_runnable(file, input_uuid=None): global config_json - file_absolute = pathlib.Path(parsed_commands.file).resolve() + file_absolute = pathlib.Path(file).resolve() json_file_data = "" with open(file_absolute, "r") as json_file: json_file_data = json_file.read() @@ -198,20 +198,20 @@ def setup_sample(parsed_commands): config_json = json.loads(json_file_data) # Make sure required parameters are all there - if not 'language' in config_json or not 'sample_file' in config_json \ - or not 'sample_region' in config_json or not 'sample_main_class' in config_json: + if not 'language' in config_json or not 'runnable_file' in config_json \ + or not 'runnable_region' in config_json or not 'runnable_main_class' in config_json: return -1 - # Preprocess sample arguments (get secret data, etc) - setup_result = setup_json_arguments_list(parsed_commands) + # Preprocess runnable arguments (get secret data, etc) + setup_result = setup_json_arguments_list(file, input_uuid) if setup_result != 0: return setup_result - print ("JSON config file loaded!") + print("JSON config file loaded!") return 0 -def cleanup_sample(): +def cleanup_runnable): global config_json global config_json_arguments_list @@ -227,8 +227,8 @@ def cleanup_sample(): # Windows 10 certificate store data? if 'windows_cert_certificate' in argument and 'windows_cert_certificate_path' in argument \ - and 'windows_cert_key' in argument and 'windows_cert_key_path' in argument \ - and 'windows_cert_pfx_key_path' in argument: + and 'windows_cert_key' in argument and 'windows_cert_key_path' in argument \ + and 'windows_cert_pfx_key_path' in argument: if (os.path.isfile(str(current_folder) + argument['windows_cert_certificate_path'])): os.remove(str(current_folder) + argument['windows_cert_certificate_path']) @@ -238,26 +238,26 @@ def cleanup_sample(): os.remove(str(current_folder) + argument['windows_cert_pfx_key_path']) except Exception as e: - print (f"Something went wrong cleaning {argument['name']}!") + print(f"Something went wrong cleaning {argument['name']}!") return -1 -def launch_sample(): +def launch_runnable(): global config_json global config_json_arguments_list if (config_json == None): - print ("No configuration JSON file data found!") + print("No configuration JSON file data found!") return -1 exit_code = 0 - print("Launching sample...") + print("Launching runnable...") # Java if (config_json['language'] == "Java"): - # Flatten arguments down into a asingle string + # Flatten arguments down into a single string arguments_as_string = "" for i in range(0, len(config_json_arguments_list)): arguments_as_string += str(config_json_arguments_list[i]) @@ -266,14 +266,15 @@ def launch_sample(): arguments = ["mvn", "compile", "exec:java"] arguments.append("-pl") - arguments.append(config_json['sample_file']) - arguments.append("-Dexec.mainClass=" + config_json['sample_main_class']) + arguments.append(config_json['runnable_file']) + arguments.append("-Dexec.mainClass=" + config_json['runnable_main_class']) arguments.append("-Daws.crt.ci=True") # We have to do this as a string, unfortunately, due to how -Dexec.args= works... argument_string = subprocess.list2cmdline(arguments) + " -Dexec.args=\"" + arguments_as_string + "\"" - sample_return = subprocess.run(argument_string, shell=True) - exit_code = sample_return.returncode + print(f"Running cmd: {argument_string}") + runnable_return = subprocess.run(argument_string, shell=True) + exit_code = runnable_return.returncode # C++ elif (config_json['language'] == "CPP"): @@ -323,27 +324,33 @@ def launch_sample(): else: exit_code = 1 - cleanup_sample() + cleanup_runnable() return exit_code -def setup_sample_and_launch(parsed_commands): - setup_result = setup_sample(parsed_commands) + +def setup_and_launch(file, input_uuid=None): + setup_result = setup_runnable(file, input_uuid) if setup_result != 0: return setup_result - print ("About to launch sample...") - return launch_sample() + print("About to launch runnable...") + return launch_runnable() + def main(): argument_parser = argparse.ArgumentParser( - description="Run Sample in CI") + description="Run runnable in CI") argument_parser.add_argument("--file", required=True, help="Configuration file to pull CI data from") - argument_parser.add_argument("--input_uuid", required=False, help="UUID data to replace '$INPUT_UUID' with. Only works in Data field") + argument_parser.add_argument("--input_uuid", required=False, + help="UUID data to replace '$INPUT_UUID' with. Only works in Data field") parsed_commands = argument_parser.parse_args() - print("Starting to launch sample...") - sample_result = setup_sample_and_launch(parsed_commands) - sys.exit(sample_result) + file = parsed_commands.file + input_uuid = parsed_commands.input_uuid + + print(f"Starting to launch runnable: config {file}; input UUID: {input_uuid}") + test_result = setup_and_launch(file, input_uuid) + sys.exit(test_result) if __name__ == "__main__": From 8ee50d2917487e699aae2bd2d15e3ca8deb2a81c Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Tue, 7 Nov 2023 10:05:42 -0800 Subject: [PATCH 12/34] wip --- .../mqtt3_fleet_provisioning_cfg.json | 0 .../mqtt5_fleet_provisioning_cfg.json | 0 .../test_fleet_provisioning.py | 15 ++++++++------- utils/{delete_iot_thing.py => ci_iot_thing.py} | 0 utils/run_in_ci.py | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) rename servicetests/{script => test_cases}/mqtt3_fleet_provisioning_cfg.json (100%) rename servicetests/{script => test_cases}/mqtt5_fleet_provisioning_cfg.json (100%) rename servicetests/{script => test_cases}/test_fleet_provisioning.py (74%) rename utils/{delete_iot_thing.py => ci_iot_thing.py} (100%) diff --git a/servicetests/script/mqtt3_fleet_provisioning_cfg.json b/servicetests/test_cases/mqtt3_fleet_provisioning_cfg.json similarity index 100% rename from servicetests/script/mqtt3_fleet_provisioning_cfg.json rename to servicetests/test_cases/mqtt3_fleet_provisioning_cfg.json diff --git a/servicetests/script/mqtt5_fleet_provisioning_cfg.json b/servicetests/test_cases/mqtt5_fleet_provisioning_cfg.json similarity index 100% rename from servicetests/script/mqtt5_fleet_provisioning_cfg.json rename to servicetests/test_cases/mqtt5_fleet_provisioning_cfg.json diff --git a/servicetests/script/test_fleet_provisioning.py b/servicetests/test_cases/test_fleet_provisioning.py similarity index 74% rename from servicetests/script/test_fleet_provisioning.py rename to servicetests/test_cases/test_fleet_provisioning.py index 91183696..5e1da52b 100644 --- a/servicetests/script/test_fleet_provisioning.py +++ b/servicetests/test_cases/test_fleet_provisioning.py @@ -5,17 +5,18 @@ import uuid import os import sys -import run_service_test -import delete_iot_thing +import run_in_ci +import ci_iot_thing def main(): argument_parser = argparse.ArgumentParser( description="Run Fleet Provisioning test in CI") argument_parser.add_argument( - "--input-uuid", required=False, help="UUID for thing name") + "--input-uuid", required=False, help="UUID for thing name. UUID will be generated if this option is omit") argument_parser.add_argument( - "--thing-name-prefix", required=False, default="", help="Prefix for a thing name") + "--thing-name-prefix", required=False, default="", + help="Prefix for a thing name, should be the same that Fleet Provisioning template uses") argument_parser.add_argument( "--region", required=False, default="us-east-1", help="The name of the region to use") argument_parser.add_argument( @@ -28,12 +29,12 @@ def main(): input_uuid = parsed_commands.input_uuid if parsed_commands.input_uuid else str(uuid.uuid4()) # Perform fleet provisioning. If it's successful, a newly created thing should appear. - test_result = run_service_test.setup_service_test_and_launch(cfg_file, input_uuid) + test_result = run_in_ci.setup_and_launch(cfg_file, input_uuid) # Delete a thing created by fleet provisioning. - # We want to try to delete thing even if test was unsuccessful. + # NOTE We want to try to delete thing even if test was unsuccessful. thing_name = parsed_commands.thing_name_prefix + input_uuid - delete_result = delete_iot_thing.delete_iot_thing(thing_name, parsed_commands.region) + delete_result = ci_iot_thing.delete_iot_thing(thing_name, parsed_commands.region) if test_result != 0 or delete_result != 0: sys.exit(-1) diff --git a/utils/delete_iot_thing.py b/utils/ci_iot_thing.py similarity index 100% rename from utils/delete_iot_thing.py rename to utils/ci_iot_thing.py diff --git a/utils/run_in_ci.py b/utils/run_in_ci.py index a145f176..ecd86b5a 100644 --- a/utils/run_in_ci.py +++ b/utils/run_in_ci.py @@ -211,7 +211,7 @@ def setup_runnable(file, input_uuid=None): return 0 -def cleanup_runnable): +def cleanup_runnable(): global config_json global config_json_arguments_list From c5f377d77d177f35398dd9e5d4d983d865c6d453 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Tue, 14 Nov 2023 10:22:11 -0800 Subject: [PATCH 13/34] Simplify test --- .../fleetProvisioning/FleetProvisioning.java | 87 +------------------ 1 file changed, 1 insertion(+), 86 deletions(-) diff --git a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java index 2204fb18..3493d5b5 100644 --- a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java +++ b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java @@ -17,9 +17,6 @@ import software.amazon.awssdk.iot.AwsIotMqttConnectionBuilder; import software.amazon.awssdk.iot.AwsIotMqtt5ClientBuilder; import software.amazon.awssdk.iot.iotidentity.IotIdentityClient; -import software.amazon.awssdk.iot.iotidentity.model.CreateCertificateFromCsrRequest; -import software.amazon.awssdk.iot.iotidentity.model.CreateCertificateFromCsrResponse; -import software.amazon.awssdk.iot.iotidentity.model.CreateCertificateFromCsrSubscriptionRequest; import software.amazon.awssdk.iot.iotidentity.model.CreateKeysAndCertificateRequest; import software.amazon.awssdk.iot.iotidentity.model.CreateKeysAndCertificateResponse; import software.amazon.awssdk.iot.iotidentity.model.CreateKeysAndCertificateSubscriptionRequest; @@ -46,7 +43,6 @@ public class FleetProvisioning { static IotIdentityClient iotIdentityClient; static CreateKeysAndCertificateResponse createKeysAndCertificateResponse = null; - static CreateCertificateFromCsrResponse createCertificateFromCsrResponse = null; static RegisterThingResponse registerThingResponse = null; static long responseWaitTimeMs = 5000L; // 5 seconds @@ -100,13 +96,6 @@ static void onRejectedKeys(ErrorResponse response) { gotResponse.complete(null); } - static void onRejectedCsr(ErrorResponse response) { - System.out.println("CreateCertificateFromCsr Request rejected, errorCode: " + response.errorCode + - ", errorMessage: " + response.errorMessage + - ", statusCode: " + response.statusCode); - gotResponse.complete(null); - } - static void onRejectedRegister(ErrorResponse response) { System.out.println("RegisterThing Request rejected, errorCode: " + response.errorCode + ", errorMessage: " + response.errorMessage + @@ -128,20 +117,6 @@ static void onCreateKeysAndCertificateAccepted(CreateKeysAndCertificateResponse gotResponse.complete(null); } - static void onCreateCertificateFromCsrResponseAccepted(CreateCertificateFromCsrResponse response) { - if (response != null) { - System.out.println("CreateCertificateFromCsr response certificateId: " + response.certificateId); - if (createCertificateFromCsrResponse == null) { - createCertificateFromCsrResponse = response; - } else { - System.out.println("CreateCertificateFromCsr response received after having already gotten a response!"); - } - } else { - System.out.println("CreateCertificateFromCsr response is null"); - } - gotResponse.complete(null); - } - static void onRegisterThingAccepted(RegisterThingResponse response) { if (response != null) { System.out.println("RegisterThing response thingName: " + response.thingName); @@ -175,12 +150,7 @@ public static void main(String[] args) { boolean sessionPresent = connected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); System.out.println("Connected to " + (!sessionPresent ? "new" : "existing") + " session!"); - // Fleet Provision based on whether there is a CSR file path or not - if (cmdData.input_csrPath == null) { - createKeysAndCertificateWorkflow(cmdData.input_templateName, cmdData.input_templateParameters); - } else { - createCertificateFromCsrWorkflow(cmdData.input_templateName, cmdData.input_templateParameters, cmdData.input_csrPath); - } + createKeysAndCertificateWorkflow(cmdData.input_templateName, cmdData.input_templateParameters); // Disconnect CompletableFuture disconnected = connection.stop(); @@ -279,59 +249,4 @@ private static void createKeysAndCertificateWorkflow(String input_templateName, gotResponse.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); System.out.println("Got response at RegisterThing"); } - - private static void createCertificateFromCsrWorkflow(String input_templateName, String input_templateParameters, String input_csrPath) throws Exception { - CreateCertificateFromCsrSubscriptionRequest createCertificateFromCsrSubscriptionRequest = new CreateCertificateFromCsrSubscriptionRequest(); - CompletableFuture csrSubscribedAccepted = iotIdentityClient.SubscribeToCreateCertificateFromCsrAccepted( - createCertificateFromCsrSubscriptionRequest, - QualityOfService.AT_LEAST_ONCE, - FleetProvisioning::onCreateCertificateFromCsrResponseAccepted); - - csrSubscribedAccepted.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); - System.out.println("Subscribed to CreateCertificateFromCsrAccepted"); - - CompletableFuture csrSubscribedRejected = iotIdentityClient.SubscribeToCreateCertificateFromCsrRejected( - createCertificateFromCsrSubscriptionRequest, - QualityOfService.AT_LEAST_ONCE, - FleetProvisioning::onRejectedCsr); - - csrSubscribedRejected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); - System.out.println("Subscribed to CreateCertificateFromCsrRejected"); - - // Subscribes to the register thing accepted and rejected topics - SubscribeToRegisterThing(input_templateName); - - String csrContents = new String(Files.readAllBytes(Paths.get(input_csrPath))); - CreateCertificateFromCsrRequest createCertificateFromCsrRequest = new CreateCertificateFromCsrRequest(); - createCertificateFromCsrRequest.certificateSigningRequest = csrContents; - CompletableFuture publishCsr = iotIdentityClient.PublishCreateCertificateFromCsr( - createCertificateFromCsrRequest, - QualityOfService.AT_LEAST_ONCE); - - gotResponse = new CompletableFuture<>(); - publishCsr.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); - System.out.println("Published to CreateCertificateFromCsr"); - gotResponse.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); - System.out.println("Got response at CreateCertificateFromCsr"); - - // Verify the response is good - if (createCertificateFromCsrResponse == null) { - throw new Exception("Got invalid/error createCertificateFromCsrResponse"); - } - - gotResponse = new CompletableFuture<>(); - System.out.println("RegisterThing now...."); - RegisterThingRequest registerThingRequest = new RegisterThingRequest(); - registerThingRequest.certificateOwnershipToken = createCertificateFromCsrResponse.certificateOwnershipToken; - registerThingRequest.templateName = input_templateName; - registerThingRequest.parameters = new Gson().fromJson(input_templateParameters, HashMap.class); - CompletableFuture publishRegister = iotIdentityClient.PublishRegisterThing( - registerThingRequest, - QualityOfService.AT_LEAST_ONCE); - - publishRegister.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); - System.out.println("Published to RegisterThing"); - gotResponse.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); - System.out.println("Got response at RegisterThing"); - } } From e1bd1d87b40cbb806d9d8080e4c4d27fbdc65001 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Tue, 14 Nov 2023 10:27:48 -0800 Subject: [PATCH 14/34] FIx CI --- .github/workflows/ci.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d40b9c2..ad766899 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -478,12 +478,11 @@ jobs: aws-region: ${{ env.AWS_DEFAULT_REGION }} - name: run Fleet Provisioning test for MQTT311 run: | - export PYTHONPATH=${PWD}/servicetests/tools:$PYTHONPATH - python3 ./servicetests/script/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 3 + export PYTHONPATH=${PWD}/utils:$PYTHONPATH + python3 ./servicetests/test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 3 - name: run Fleet Provisioning test for MQTT5 run: | - export PYTHONPATH=${PWD}/servicetests/tools:$PYTHONPATH - python3 ./servicetests/script/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 5 + python3 ./servicetests/test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 5 - name: configure AWS credentials (Connect and PubSub) uses: aws-actions/configure-aws-credentials@v2 with: From b1098dd56e9b18edee0a735eee0faa797a206516 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Tue, 14 Nov 2023 10:30:18 -0800 Subject: [PATCH 15/34] FIx CI --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad766899..aff998fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -482,6 +482,7 @@ jobs: python3 ./servicetests/test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 3 - name: run Fleet Provisioning test for MQTT5 run: | + export PYTHONPATH=${PWD}/utils:$PYTHONPATH python3 ./servicetests/test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 5 - name: configure AWS credentials (Connect and PubSub) uses: aws-actions/configure-aws-credentials@v2 From da04c2b5cf2f4e8e78546d83780793c16e36e775 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Tue, 14 Nov 2023 10:53:15 -0800 Subject: [PATCH 16/34] Reorganize service tests --- .github/workflows/ci.yml | 2 ++ pom.xml | 1 - servicetests/pom.xml | 29 +++++++++++++++++++ .../mqtt3_fleet_provisioning_cfg.json | 2 +- .../mqtt5_fleet_provisioning_cfg.json | 2 +- 5 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 servicetests/pom.xml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aff998fb..6d23482a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -477,11 +477,13 @@ jobs: role-to-assume: ${{ env.CI_FLEET_PROVISIONING_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} - name: run Fleet Provisioning test for MQTT311 + working-directory: ./servicetests run: | export PYTHONPATH=${PWD}/utils:$PYTHONPATH python3 ./servicetests/test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 3 - name: run Fleet Provisioning test for MQTT5 run: | + working-directory: ./servicetests export PYTHONPATH=${PWD}/utils:$PYTHONPATH python3 ./servicetests/test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 5 - name: configure AWS credentials (Connect and PubSub) diff --git a/pom.xml b/pom.xml index 8263bcff..d1657a43 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,6 @@ samples/FleetProvisioning samples/Mqtt5/PubSub samples/Mqtt5/SharedSubscription - servicetests/tests/FleetProvisioning diff --git a/servicetests/pom.xml b/servicetests/pom.xml new file mode 100644 index 00000000..1b75356a --- /dev/null +++ b/servicetests/pom.xml @@ -0,0 +1,29 @@ + + 4.0.0 + software.amazon.awssdk.iotdevicesdk + ServiceClientTests + pom + 1.0-SNAPSHOT + + + + software.amazon.awssdk.iotdevicesdk + aws-iot-device-sdk + 1.0.0-SNAPSHOT + + + + tests/FleetProvisioning + + + + + + org.codehaus.mojo + versions-maven-plugin + 2.7 + + + + diff --git a/servicetests/test_cases/mqtt3_fleet_provisioning_cfg.json b/servicetests/test_cases/mqtt3_fleet_provisioning_cfg.json index 22135d6f..4eaba492 100644 --- a/servicetests/test_cases/mqtt3_fleet_provisioning_cfg.json +++ b/servicetests/test_cases/mqtt3_fleet_provisioning_cfg.json @@ -1,6 +1,6 @@ { "language": "Java", - "runnable_file": "servicetests/tests/FleetProvisioning", + "runnable_file": "tests/FleetProvisioning", "runnable_region": "us-east-1", "runnable_main_class": "fleetProvisioning.FleetProvisioning", "arguments": [ diff --git a/servicetests/test_cases/mqtt5_fleet_provisioning_cfg.json b/servicetests/test_cases/mqtt5_fleet_provisioning_cfg.json index 17d11aa9..ccf387e9 100644 --- a/servicetests/test_cases/mqtt5_fleet_provisioning_cfg.json +++ b/servicetests/test_cases/mqtt5_fleet_provisioning_cfg.json @@ -1,6 +1,6 @@ { "language": "Java", - "runnable_file": "servicetests/tests/FleetProvisioning", + "runnable_file": "tests/FleetProvisioning", "runnable_region": "us-east-1", "runnable_main_class": "fleetProvisioning.FleetProvisioning", "arguments": [ From 111b98e6ed6b10bdfaa19a8e46ad2eb9cf00378c Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Tue, 14 Nov 2023 10:54:35 -0800 Subject: [PATCH 17/34] Fix CI --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d23482a..5d7afe6f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -480,12 +480,12 @@ jobs: working-directory: ./servicetests run: | export PYTHONPATH=${PWD}/utils:$PYTHONPATH - python3 ./servicetests/test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 3 + python3 ./test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 3 - name: run Fleet Provisioning test for MQTT5 run: | working-directory: ./servicetests export PYTHONPATH=${PWD}/utils:$PYTHONPATH - python3 ./servicetests/test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 5 + python3 ./test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 5 - name: configure AWS credentials (Connect and PubSub) uses: aws-actions/configure-aws-credentials@v2 with: From c10b3737183e45022157c28d1a2bb86a005e5174 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Tue, 14 Nov 2023 10:58:32 -0800 Subject: [PATCH 18/34] Fix CI --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d7afe6f..62d27c25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -479,12 +479,12 @@ jobs: - name: run Fleet Provisioning test for MQTT311 working-directory: ./servicetests run: | - export PYTHONPATH=${PWD}/utils:$PYTHONPATH + export PYTHONPATH=${{ github.workspace }}/utils:$PYTHONPATH python3 ./test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 3 - name: run Fleet Provisioning test for MQTT5 run: | working-directory: ./servicetests - export PYTHONPATH=${PWD}/utils:$PYTHONPATH + export PYTHONPATH=${{ github.workspace }}/utils:$PYTHONPATH python3 ./test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 5 - name: configure AWS credentials (Connect and PubSub) uses: aws-actions/configure-aws-credentials@v2 From c8ab0aa962c65c7f86decd80b0272b4ed4aea1b4 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Tue, 14 Nov 2023 11:00:29 -0800 Subject: [PATCH 19/34] Fix CI --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62d27c25..e8670cd2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -479,12 +479,12 @@ jobs: - name: run Fleet Provisioning test for MQTT311 working-directory: ./servicetests run: | - export PYTHONPATH=${{ github.workspace }}/utils:$PYTHONPATH + export PYTHONPATH=${{ github.workspace }}/utils python3 ./test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 3 - name: run Fleet Provisioning test for MQTT5 + working-directory: ./servicetests run: | - working-directory: ./servicetests - export PYTHONPATH=${{ github.workspace }}/utils:$PYTHONPATH + export PYTHONPATH=${{ github.workspace }}/utils python3 ./test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 5 - name: configure AWS credentials (Connect and PubSub) uses: aws-actions/configure-aws-credentials@v2 From 953941e470a832af544d2a339230a0e41af8e0f8 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Tue, 14 Nov 2023 11:15:21 -0800 Subject: [PATCH 20/34] Use cmdutils from samples --- .../commandlineutils/CommandLineUtils.java | 7 + servicetests/tests/FleetProvisioning/pom.xml | 2 +- .../fleetProvisioning/FleetProvisioning.java | 4 +- .../commandlineutils/CommandLineUtils.java | 298 ------------------ 4 files changed, 10 insertions(+), 301 deletions(-) delete mode 100644 servicetests/tests/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java diff --git a/samples/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java b/samples/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java index 51f4f4d3..ad2e3a10 100644 --- a/samples/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java +++ b/samples/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java @@ -234,6 +234,10 @@ private void parseCommonLoggingCommands(SampleCommandLineData returnData){ } } + private void parseMqttVersion(SampleCommandLineData returnData) { + returnData.input_mqtt_version = Integer.parseInt(getCommandOrDefault(m_cmd_mqtt_version, "3")); + } + private void parseCommonMQTTCommands(SampleCommandLineData returnData) { returnData.input_endpoint = getCommandRequired(m_cmd_endpoint); returnData.input_ca = getCommandOrDefault(m_cmd_ca_file, ""); @@ -290,6 +294,7 @@ public class SampleCommandLineData public String input_ca; public String input_clientId; public int input_port; + public int input_mqtt_version; // Proxy public String input_proxyHost; public int input_proxyPort; @@ -458,6 +463,7 @@ public SampleCommandLineData parseSampleInputFleetProvisioning(String [] args) sendArguments(args); SampleCommandLineData returnData = new SampleCommandLineData(); + parseMqttVersion(returnData); parseCommonLoggingCommands(returnData); parseCommonMQTTCommands(returnData); parseKeyAndCertCommands(returnData); @@ -749,6 +755,7 @@ public static SampleCommandLineData getInputForIoTSample(String sampleName, Stri /** * Constants for commonly used/needed commands */ + private static final String m_cmd_mqtt_version = "mqtt_version"; private static final String m_cmd_log_destination = "log_destination"; private static final String m_cmd_log_file_name = "log_file_name"; private static final String m_cmd_verbosity = "verbosity"; diff --git a/servicetests/tests/FleetProvisioning/pom.xml b/servicetests/tests/FleetProvisioning/pom.xml index 3424b6f2..470b7790 100644 --- a/servicetests/tests/FleetProvisioning/pom.xml +++ b/servicetests/tests/FleetProvisioning/pom.xml @@ -35,7 +35,7 @@ - ../Utils/CommandLineUtils + ../../../samples/Utils/CommandLineUtils ../Utils/MqttClientConnectionWrapper ../ServiceTestLifecycleEvents diff --git a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java index 3493d5b5..e68b215c 100644 --- a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java +++ b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java @@ -47,7 +47,7 @@ public class FleetProvisioning { static long responseWaitTimeMs = 5000L; // 5 seconds - static MqttClientConnectionWrapper createConnection(CommandLineUtils.ServiceTestCommandLineData cmdData, Integer mqttVersion) { + static MqttClientConnectionWrapper createConnection(CommandLineUtils.SampleCommandLineData cmdData, Integer mqttVersion) { if (mqttVersion == 3) { try (AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder .newMtlsBuilderFromPath(cmdData.input_cert, cmdData.input_key)) { @@ -137,7 +137,7 @@ static void onException(Exception e) { } public static void main(String[] args) { - CommandLineUtils.ServiceTestCommandLineData cmdData = CommandLineUtils.getInputForServiceTest("FleetProvisioning", args); + CommandLineUtils.SampleCommandLineData cmdData = CommandLineUtils.getInputForIoTSample("FleetProvisioningSample", args); boolean exitWithError = false; diff --git a/servicetests/tests/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java b/servicetests/tests/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java deleted file mode 100644 index 9f919b1d..00000000 --- a/servicetests/tests/Utils/CommandLineUtils/utils/commandlineutils/CommandLineUtils.java +++ /dev/null @@ -1,298 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0. - */ - -package utils.commandlineutils; - -import java.util.*; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.CompletableFuture; -import java.io.UnsupportedEncodingException; - -import software.amazon.awssdk.crt.*; -import software.amazon.awssdk.crt.io.*; -import software.amazon.awssdk.crt.Log; -import software.amazon.awssdk.crt.Log.LogLevel; - -public class CommandLineUtils { - private String programName; - private final HashMap registeredCommands = new HashMap<>(); - private List commandArguments; - private boolean isCI; - - /** - * Functions for registering and command line arguments - */ - - public void registerProgramName(String newProgramName) { - programName = newProgramName; - } - - public void registerCommand(CommandLineOption option) { - if (registeredCommands.containsKey(option.commandName)) { - System.out.println("Cannot register command: " + option.commandName + ". Command already registered"); - return; - } - registeredCommands.put(option.commandName, option); - } - - public void registerCommand(String commandName, String exampleInput, String helpOutput) { - registerCommand(new CommandLineOption(commandName, exampleInput, helpOutput)); - } - - public void removeCommand(String commandName) { - registeredCommands.remove(commandName); - } - - public void updateCommandHelp(String commandName, String newCommandHelp) { - if (registeredCommands.containsKey(commandName)) { - registeredCommands.get(commandName).helpOutput = newCommandHelp; - } - } - - public void sendArguments(String[] arguments) { - // Automatically register the help command - registerCommand(m_cmd_help, "", "Prints this message"); - - commandArguments = Arrays.asList(arguments); - - // Automatically check for help and print if present - if (hasCommand(m_cmd_help)) - { - printHelp(); - if (isCI == true) { - throw new RuntimeException("Help argument called"); - } else { - System.exit(-1); - } - } - } - - public boolean hasCommand(String command) { - return commandArguments.contains("--" + command); - } - - public String getCommand(String command) { - for (Iterator iter = commandArguments.iterator(); iter.hasNext();) { - String value = iter.next(); - if (Objects.equals(value,"--" + command)) { - if (iter.hasNext()) { - return iter.next(); - } - else { - System.out.println("Error - found command but at end of arguments!\n"); - return ""; - } - } - } - return ""; - } - - public String getCommandOrDefault(String command, String commandDefault) { - if (commandArguments.contains("--" + command)) { - return getCommand(command); - } - return commandDefault; - } - - public String getCommandRequired(String command) { - if (commandArguments.contains("--" + command)) { - return getCommand(command); - } - printHelp(); - System.out.println("Missing required argument: --" + command + "\n"); - - if (isCI == true) { - throw new RuntimeException("Missing required argument"); - } else { - System.exit(-1); - } - return ""; - } - - public String getCommandRequired(String command, String commandAlt){ - if(commandArguments.contains("--" + commandAlt)){ - return getCommand(commandAlt); - } - return getCommandRequired(command); - } - - public void printHelp() { - System.out.println("Usage:"); - - String messageOne = programName; - for (String commandName : registeredCommands.keySet()) { - messageOne += " --" + commandName + " " + registeredCommands.get(commandName).exampleInput; - } - System.out.println(messageOne + "\n"); - - for (String commandName : registeredCommands.keySet()) { - messageOne += " --" + commandName + " " + registeredCommands.get(commandName).exampleInput; - System.out.println("* " + commandName + "\t\t" + registeredCommands.get(commandName).helpOutput); - } - } - - public void determineIfCI() { - String ciPropValue = System.getProperty("aws.crt.ci"); - isCI = ciPropValue != null && Boolean.valueOf(ciPropValue); - } - - /** - * Helper functions for registering commands - */ - - public void addCommonLoggingCommands() { - registerCommand(m_cmd_verbosity, "", "The amount of detail in the logging output of the service test." + - " Options: 'Fatal', 'Error', 'Warn', 'Info', 'Debug', 'Trace' or 'None' (optional, default='None')."); - registerCommand(m_cmd_log_destination, "", "Where logging should be routed to." + - " Options: 'Stdout', 'Stderr', 'File' (optional, default='Stderr')."); - registerCommand(m_cmd_log_file_name, "", "File name to save logging to." + - " (optional, default='log.txt')."); - } - - public void addClientIdAndPort() { - registerCommand(m_cmd_client_id, "", "Client id to use (optional, default='test-*')."); - registerCommand(m_cmd_port, "", "Port to connect to on the endpoint (optional, default='8883')."); - } - - public void addCommonMqttCommands() { - registerCommand(m_cmd_endpoint, "", "The endpoint of the MQTT server, not including a port."); - registerCommand(m_cmd_ca_file, "", "Path to AmazonRootCA1.pem (optional, system trust store used by default)."); - } - - public void addKeyAndCertCommands() { - registerCommand(m_cmd_key_file, "", "Path to your key in PEM format."); - registerCommand(m_cmd_cert_file, "", "Path to your client certificate in PEM format."); - } - - /** - * Helper functions for parsing commands - */ - - private void parseCommonLoggingCommands(ServiceTestCommandLineData returnData){ - String verbosity = getCommandOrDefault(m_cmd_verbosity, "None"); - String log_destination = getCommandOrDefault(m_cmd_log_destination, "Stderr"); - String log_file_name = getCommandOrDefault(m_cmd_log_file_name, "log.txt"); - - if(verbosity != "None"){ - switch (log_destination) { - case "Stderr": - Log.initLoggingToStderr(LogLevel.valueOf(verbosity)); - break; - case "Stdout": - Log.initLoggingToStdout(LogLevel.valueOf(verbosity)); - break; - case "File": - Log.initLoggingToFile(LogLevel.valueOf(verbosity), log_file_name); - break; - default: - break; - } - } - } - - private void parseMqttVersion(ServiceTestCommandLineData returnData) { - returnData.input_mqtt_version = Integer.parseInt(getCommandOrDefault(m_cmd_mqtt_version, "3")); - } - - private void parseCommonMqttCommands(ServiceTestCommandLineData returnData) { - returnData.input_endpoint = getCommandRequired(m_cmd_endpoint); - returnData.input_ca = getCommandOrDefault(m_cmd_ca_file, ""); - } - - private void parseKeyAndCertCommands(ServiceTestCommandLineData returnData) - { - returnData.input_cert = getCommandRequired(m_cmd_cert_file); - returnData.input_key = getCommandRequired(m_cmd_key_file); - } - - private void parseClientIdAndPort(ServiceTestCommandLineData returnData) { - returnData.input_clientId = getCommandOrDefault(m_cmd_client_id, "test-" + UUID.randomUUID().toString()); - returnData.input_port = Integer.parseInt(getCommandOrDefault(m_cmd_port, "8883")); - } - - public class ServiceTestCommandLineData - { - // General use - public int input_mqtt_version; - public String input_endpoint; - public String input_cert; - public String input_key; - public String input_ca; - public String input_clientId; - public int input_port; - // Fleet provisioning - public String input_templateName; - public String input_templateParameters; - public String input_csrPath; - } - - public ServiceTestCommandLineData parseServiceTestInputFleetProvisioning(String [] args) - { - addCommonLoggingCommands(); - addCommonMqttCommands(); - addKeyAndCertCommands(); - addClientIdAndPort(); - registerCommand(m_cmd_fleet_template_name, "", "Provisioning template name."); - registerCommand(m_cmd_fleet_template_parameters, "", "Provisioning template parameters."); - registerCommand(m_cmd_fleet_template_csr, "", "Path to the CSR file (optional)."); - sendArguments(args); - - ServiceTestCommandLineData returnData = new ServiceTestCommandLineData(); - parseMqttVersion(returnData); - parseCommonLoggingCommands(returnData); - parseCommonMqttCommands(returnData); - parseKeyAndCertCommands(returnData); - parseClientIdAndPort(returnData); - returnData.input_templateName = getCommandRequired(m_cmd_fleet_template_name); - returnData.input_templateParameters = getCommandRequired(m_cmd_fleet_template_parameters); - returnData.input_csrPath = getCommandOrDefault(m_cmd_fleet_template_csr, null); - return returnData; - } - - public static ServiceTestCommandLineData getInputForServiceTest(String serviceTestName, String[] args) - { - CommandLineUtils cmdUtils = new CommandLineUtils(); - cmdUtils.registerProgramName(serviceTestName); - cmdUtils.determineIfCI(); - - if (serviceTestName.equals("FleetProvisioning")) { - return cmdUtils.parseServiceTestInputFleetProvisioning(args); - } else { - throw new RuntimeException("Unknown service test name!"); - } - } - - /** - * Constants for commonly used/needed commands - */ - private static final String m_cmd_mqtt_version = "mqtt_version"; - private static final String m_cmd_log_destination = "log_destination"; - private static final String m_cmd_log_file_name = "log_file_name"; - private static final String m_cmd_verbosity = "verbosity"; - private static final String m_cmd_endpoint = "endpoint"; - private static final String m_cmd_ca_file = "ca_file"; - private static final String m_cmd_cert_file = "cert"; - private static final String m_cmd_key_file = "key"; - private static final String m_cmd_client_id = "client_id"; - private static final String m_cmd_port = "port"; - private static final String m_cmd_help = "help"; - private static final String m_cmd_fleet_template_name = "template_name"; - private static final String m_cmd_fleet_template_parameters = "template_parameters"; - private static final String m_cmd_fleet_template_csr = "csr"; - private static final String m_cmd_region = "region"; - private static final String m_cmd_print_discover_resp_only = "print_discover_resp_only"; -} - -class CommandLineOption { - public String commandName; - public String exampleInput; - public String helpOutput; - - CommandLineOption(String name, String example, String help) { - commandName = name; - exampleInput = example; - helpOutput = help; - } -} From 711f7867310b2434ba6dc5af576bf794cf74b388 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Tue, 14 Nov 2023 11:20:15 -0800 Subject: [PATCH 21/34] Fix CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8670cd2..bf4e35fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -465,7 +465,7 @@ jobs: run: | java -version mvn install -Dmaven.test.skip - - name: Running samples in CI setup + - name: Running samples and service client tests in CI setup run: | python3 -m pip install boto3 sudo apt-get update -y From 911760339e1fdd2d301068c870ad8d0a11717b0c Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Wed, 15 Nov 2023 14:29:18 -0800 Subject: [PATCH 22/34] Experiment --- .../src/main/java/fleetProvisioning/FleetProvisioning.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java index e68b215c..58ca7f4a 100644 --- a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java +++ b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java @@ -141,6 +141,10 @@ public static void main(String[] args) { boolean exitWithError = false; + if (cmdData.input_mqtt_version < 10) { + throw new RuntimeException("Invalid MQTT version specified: " + mqttVersion); + } + try (MqttClientConnectionWrapper connection = createConnection(cmdData, cmdData.input_mqtt_version)) { // Create the identity client (Identity = Fleet Provisioning) iotIdentityClient = new IotIdentityClient(connection.getConnection()); From 77fb38bf577af4824e1865a50883c93bcdbf6005 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Wed, 15 Nov 2023 14:31:07 -0800 Subject: [PATCH 23/34] Experiment --- .../src/main/java/fleetProvisioning/FleetProvisioning.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java index 58ca7f4a..d991ba27 100644 --- a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java +++ b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java @@ -142,7 +142,7 @@ public static void main(String[] args) { boolean exitWithError = false; if (cmdData.input_mqtt_version < 10) { - throw new RuntimeException("Invalid MQTT version specified: " + mqttVersion); + throw new RuntimeException("Invalid MQTT version specified: " + cmdData.input_mqtt_version); } try (MqttClientConnectionWrapper connection = createConnection(cmdData, cmdData.input_mqtt_version)) { From fda955b6e041e0b9d5ac2363ebc3f94b2d31b2b3 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Wed, 15 Nov 2023 15:06:06 -0800 Subject: [PATCH 24/34] Revert experiment --- .../src/main/java/fleetProvisioning/FleetProvisioning.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java index d991ba27..e68b215c 100644 --- a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java +++ b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java @@ -141,10 +141,6 @@ public static void main(String[] args) { boolean exitWithError = false; - if (cmdData.input_mqtt_version < 10) { - throw new RuntimeException("Invalid MQTT version specified: " + cmdData.input_mqtt_version); - } - try (MqttClientConnectionWrapper connection = createConnection(cmdData, cmdData.input_mqtt_version)) { // Create the identity client (Identity = Fleet Provisioning) iotIdentityClient = new IotIdentityClient(connection.getConnection()); From 5138b1cb5aef6dc4268ef96e8c4d75096763333a Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Wed, 15 Nov 2023 15:24:23 -0800 Subject: [PATCH 25/34] Extract connection creation --- .../fleetProvisioning/FleetProvisioning.java | 57 +++--------------- .../MqttClientConnectionWrapperCreator.java | 58 +++++++++++++++++++ 2 files changed, 65 insertions(+), 50 deletions(-) create mode 100644 servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapperCreator.java diff --git a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java index e68b215c..612d431c 100644 --- a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java +++ b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java @@ -8,14 +8,7 @@ import software.amazon.awssdk.crt.CRT; import software.amazon.awssdk.crt.CrtResource; import software.amazon.awssdk.crt.CrtRuntimeException; -import software.amazon.awssdk.crt.mqtt.MqttClientConnection; -import software.amazon.awssdk.crt.mqtt.MqttClientConnectionEvents; import software.amazon.awssdk.crt.mqtt.QualityOfService; -import software.amazon.awssdk.crt.mqtt5.Mqtt5ClientOptions; -import software.amazon.awssdk.crt.mqtt5.Mqtt5Client; -import software.amazon.awssdk.crt.mqtt5.packets.ConnectPacket; -import software.amazon.awssdk.iot.AwsIotMqttConnectionBuilder; -import software.amazon.awssdk.iot.AwsIotMqtt5ClientBuilder; import software.amazon.awssdk.iot.iotidentity.IotIdentityClient; import software.amazon.awssdk.iot.iotidentity.model.CreateKeysAndCertificateRequest; import software.amazon.awssdk.iot.iotidentity.model.CreateKeysAndCertificateResponse; @@ -47,48 +40,6 @@ public class FleetProvisioning { static long responseWaitTimeMs = 5000L; // 5 seconds - static MqttClientConnectionWrapper createConnection(CommandLineUtils.SampleCommandLineData cmdData, Integer mqttVersion) { - if (mqttVersion == 3) { - try (AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder - .newMtlsBuilderFromPath(cmdData.input_cert, cmdData.input_key)) { - builder.withClientId(cmdData.input_clientId) - .withEndpoint(cmdData.input_endpoint) - .withPort((short)cmdData.input_port) - .withCleanSession(true) - .withProtocolOperationTimeoutMs(60000); - Mqtt3ClientConnectionWrapper connWrapper = new Mqtt3ClientConnectionWrapper(); - connWrapper.connection = builder.build(); - if (connWrapper.connection == null) { - throw new RuntimeException("MQTT311 connection creation failed!"); - } - return connWrapper; - } catch (Exception ex) { - throw new RuntimeException("Failed to create MQTT311 connection", ex); - } - } else if (mqttVersion == 5) { - ServiceTestLifecycleEvents lifecycleEvents = new ServiceTestLifecycleEvents(); - try (AwsIotMqtt5ClientBuilder builder = AwsIotMqtt5ClientBuilder.newDirectMqttBuilderWithMtlsFromPath( - cmdData.input_endpoint, cmdData.input_cert, cmdData.input_key)) { - ConnectPacket.ConnectPacketBuilder connectProperties = new ConnectPacket.ConnectPacketBuilder(); - connectProperties.withClientId(cmdData.input_clientId); - builder.withConnectProperties(connectProperties); - builder.withLifeCycleEvents(lifecycleEvents); - builder.withPort((long)cmdData.input_port); - Mqtt5ClientConnectionWrapper connWrapper = new Mqtt5ClientConnectionWrapper(); - connWrapper.client = builder.build(); - connWrapper.connection = new MqttClientConnection(connWrapper.client, null); - if (connWrapper.connection == null) { - throw new RuntimeException("MQTT5 connection creation failed!"); - } - return connWrapper; - } catch (Exception ex) { - throw new RuntimeException("Failed to create MQTT311 connection from MQTT5 client", ex); - } - } else { - throw new RuntimeException("Invalid MQTT version specified: " + mqttVersion); - } - } - static void onRejectedKeys(ErrorResponse response) { System.out.println("CreateKeysAndCertificate Request rejected, errorCode: " + response.errorCode + ", errorMessage: " + response.errorMessage + @@ -141,7 +92,13 @@ public static void main(String[] args) { boolean exitWithError = false; - try (MqttClientConnectionWrapper connection = createConnection(cmdData, cmdData.input_mqtt_version)) { + try (MqttClientConnectionWrapper connection = MqttClientConnectionWrapperCreator.createConnection( + cmdData.input_cert, + cmdData.input_key, + cmdData.input_clientId, + cmdData.input_endpoint, + cmdData.input_port, + cmdData.input_mqtt_version)) { // Create the identity client (Identity = Fleet Provisioning) iotIdentityClient = new IotIdentityClient(connection.getConnection()); diff --git a/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapperCreator.java b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapperCreator.java new file mode 100644 index 00000000..63e87e8a --- /dev/null +++ b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapperCreator.java @@ -0,0 +1,58 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +package utils.mqttclientconnectionwrapper; + +import software.amazon.awssdk.crt.mqtt.MqttClientConnection; +import software.amazon.awssdk.crt.mqtt5.packets.ConnectPacket; +import software.amazon.awssdk.iot.AwsIotMqttConnectionBuilder; +import software.amazon.awssdk.iot.AwsIotMqtt5ClientBuilder; + +import ServiceTestLifecycleEvents.ServiceTestLifecycleEvents; + +final public class MqttClientConnectionWrapperCreator { + public static MqttClientConnectionWrapper createConnection(String cert, String key, String clientId, String endpoint, + int port, Integer mqttVersion) { + if (mqttVersion == 3) { + try (AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder + .newMtlsBuilderFromPath(cert, key)) { + builder.withClientId(clientId) + .withEndpoint(endpoint) + .withPort((short)port) + .withCleanSession(true); + Mqtt3ClientConnectionWrapper connWrapper = new Mqtt3ClientConnectionWrapper(); + connWrapper.connection = builder.build(); + if (connWrapper.connection == null) { + throw new RuntimeException("MQTT311 connection creation failed!"); + } + return connWrapper; + } catch (Exception ex) { + throw new RuntimeException("Failed to create MQTT311 connection", ex); + } + } else if (mqttVersion == 5) { + ServiceTestLifecycleEvents lifecycleEvents = new ServiceTestLifecycleEvents(); + try (AwsIotMqtt5ClientBuilder builder = AwsIotMqtt5ClientBuilder.newDirectMqttBuilderWithMtlsFromPath( + endpoint, cert, key)) { + ConnectPacket.ConnectPacketBuilder connectProperties = new ConnectPacket.ConnectPacketBuilder(); + connectProperties.withClientId(clientId); + builder.withConnectProperties(connectProperties); + builder.withLifeCycleEvents(lifecycleEvents); + builder.withPort((long)port); + Mqtt5ClientConnectionWrapper connWrapper = new Mqtt5ClientConnectionWrapper(); + connWrapper.client = builder.build(); + connWrapper.connection = new MqttClientConnection(connWrapper.client, null); + if (connWrapper.connection == null) { + throw new RuntimeException("MQTT5 connection creation failed!"); + } + return connWrapper; + } catch (Exception ex) { + throw new RuntimeException("Failed to create MQTT311 connection from MQTT5 client", ex); + } + } else { + throw new RuntimeException("Invalid MQTT version specified: " + mqttVersion); + } + } + +} From b222b3f0482e5177bb59b81c63aeb5ccb30aec3a Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Wed, 15 Nov 2023 15:36:56 -0800 Subject: [PATCH 26/34] CLen code --- .../MqttClientConnectionWrapperCreator.java | 79 +++++++++++-------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapperCreator.java b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapperCreator.java index 63e87e8a..529504d0 100644 --- a/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapperCreator.java +++ b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapperCreator.java @@ -13,46 +13,55 @@ import ServiceTestLifecycleEvents.ServiceTestLifecycleEvents; final public class MqttClientConnectionWrapperCreator { - public static MqttClientConnectionWrapper createConnection(String cert, String key, String clientId, String endpoint, - int port, Integer mqttVersion) { + public static MqttClientConnectionWrapper createConnection( + String cert, String key, String clientId, String endpoint, int port, Integer mqttVersion) { if (mqttVersion == 3) { - try (AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder - .newMtlsBuilderFromPath(cert, key)) { - builder.withClientId(clientId) - .withEndpoint(endpoint) - .withPort((short)port) - .withCleanSession(true); - Mqtt3ClientConnectionWrapper connWrapper = new Mqtt3ClientConnectionWrapper(); - connWrapper.connection = builder.build(); - if (connWrapper.connection == null) { - throw new RuntimeException("MQTT311 connection creation failed!"); - } - return connWrapper; - } catch (Exception ex) { - throw new RuntimeException("Failed to create MQTT311 connection", ex); - } + return createMqtt3Connection(cert, key, clientId, endpoint, port); } else if (mqttVersion == 5) { - ServiceTestLifecycleEvents lifecycleEvents = new ServiceTestLifecycleEvents(); - try (AwsIotMqtt5ClientBuilder builder = AwsIotMqtt5ClientBuilder.newDirectMqttBuilderWithMtlsFromPath( - endpoint, cert, key)) { - ConnectPacket.ConnectPacketBuilder connectProperties = new ConnectPacket.ConnectPacketBuilder(); - connectProperties.withClientId(clientId); - builder.withConnectProperties(connectProperties); - builder.withLifeCycleEvents(lifecycleEvents); - builder.withPort((long)port); - Mqtt5ClientConnectionWrapper connWrapper = new Mqtt5ClientConnectionWrapper(); - connWrapper.client = builder.build(); - connWrapper.connection = new MqttClientConnection(connWrapper.client, null); - if (connWrapper.connection == null) { - throw new RuntimeException("MQTT5 connection creation failed!"); - } - return connWrapper; - } catch (Exception ex) { - throw new RuntimeException("Failed to create MQTT311 connection from MQTT5 client", ex); - } + return createMqtt5Connection(cert, key, clientId, endpoint, port); } else { throw new RuntimeException("Invalid MQTT version specified: " + mqttVersion); } } + static MqttClientConnectionWrapper createMqtt3Connection( + String cert, String key, String clientId, String endpoint, int port) { + try (AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder + .newMtlsBuilderFromPath(cert, key)) { + builder.withClientId(clientId) + .withEndpoint(endpoint) + .withPort((short)port) + .withCleanSession(true); + Mqtt3ClientConnectionWrapper connWrapper = new Mqtt3ClientConnectionWrapper(); + connWrapper.connection = builder.build(); + if (connWrapper.connection == null) { + throw new RuntimeException("MQTT311 connection creation failed!"); + } + return connWrapper; + } catch (Exception ex) { + throw new RuntimeException("Failed to create MQTT311 connection", ex); + } + } + + static MqttClientConnectionWrapper createMqtt5Connection( + String cert, String key, String clientId, String endpoint, int port) { + ServiceTestLifecycleEvents lifecycleEvents = new ServiceTestLifecycleEvents(); + try (AwsIotMqtt5ClientBuilder builder = AwsIotMqtt5ClientBuilder.newDirectMqttBuilderWithMtlsFromPath( + endpoint, cert, key)) { + ConnectPacket.ConnectPacketBuilder connectProperties = new ConnectPacket.ConnectPacketBuilder(); + connectProperties.withClientId(clientId); + builder.withConnectProperties(connectProperties); + builder.withLifeCycleEvents(lifecycleEvents); + builder.withPort((long)port); + Mqtt5ClientConnectionWrapper connWrapper = new Mqtt5ClientConnectionWrapper(); + connWrapper.client = builder.build(); + connWrapper.connection = new MqttClientConnection(connWrapper.client, null); + if (connWrapper.connection == null) { + throw new RuntimeException("MQTT5 connection creation failed!"); + } + return connWrapper; + } catch (Exception ex) { + throw new RuntimeException("Failed to create MQTT311 connection from MQTT5 client", ex); + } + } } From a1369b5d351f8b3b563647add6b57182a62c5b73 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Wed, 15 Nov 2023 15:47:35 -0800 Subject: [PATCH 27/34] Clean code --- .../Mqtt3ClientConnectionWrapper.java | 8 ++++++++ .../Mqtt5ClientConnectionWrapper.java | 11 ++++++++++- .../MqttClientConnectionWrapper.java | 3 +++ .../MqttClientConnectionWrapperCreator.java | 17 +++-------------- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/Mqtt3ClientConnectionWrapper.java b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/Mqtt3ClientConnectionWrapper.java index eea43031..ac7cc08c 100644 --- a/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/Mqtt3ClientConnectionWrapper.java +++ b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/Mqtt3ClientConnectionWrapper.java @@ -6,12 +6,20 @@ package utils.mqttclientconnectionwrapper; import software.amazon.awssdk.crt.mqtt.MqttClientConnection; +import software.amazon.awssdk.iot.AwsIotMqttConnectionBuilder; import java.util.concurrent.CompletableFuture; final public class Mqtt3ClientConnectionWrapper extends MqttClientConnectionWrapper { public static MqttClientConnection connection; + public Mqtt3ClientConnectionWrapper(AwsIotMqttConnectionBuilder builder) { + connection = builder.build(); + if (connection == null) { + throw new RuntimeException("MQTT311 connection creation failed!"); + } + } + @Override public CompletableFuture start() { return connection.connect(); diff --git a/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/Mqtt5ClientConnectionWrapper.java b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/Mqtt5ClientConnectionWrapper.java index 54dc2644..b0908c6d 100644 --- a/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/Mqtt5ClientConnectionWrapper.java +++ b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/Mqtt5ClientConnectionWrapper.java @@ -7,6 +7,7 @@ import software.amazon.awssdk.crt.mqtt.MqttClientConnection; import software.amazon.awssdk.crt.mqtt5.Mqtt5Client; +import software.amazon.awssdk.iot.AwsIotMqtt5ClientBuilder; import java.util.concurrent.CompletableFuture; @@ -14,6 +15,14 @@ final public class Mqtt5ClientConnectionWrapper extends MqttClientConnectionWrap public static Mqtt5Client client; public static MqttClientConnection connection; + public Mqtt5ClientConnectionWrapper(AwsIotMqtt5ClientBuilder builder) { + client = builder.build(); + connection = new MqttClientConnection(client, null); + if (connection == null) { + throw new RuntimeException("MQTT5 connection creation failed!"); + } + } + @Override public CompletableFuture start() { return connection.connect(); @@ -26,8 +35,8 @@ public CompletableFuture stop() { @Override public void close() { - client.close(); connection.close(); + client.close(); } @Override diff --git a/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapper.java b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapper.java index 00367ed6..a8537733 100644 --- a/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapper.java +++ b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapper.java @@ -9,6 +9,9 @@ import java.util.concurrent.CompletableFuture; +/** + * Auxiliary class hiding differences between MQTT311 and MQTT5 connections. + */ abstract public class MqttClientConnectionWrapper implements AutoCloseable { public abstract CompletableFuture start(); public abstract CompletableFuture stop(); diff --git a/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapperCreator.java b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapperCreator.java index 529504d0..80868d47 100644 --- a/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapperCreator.java +++ b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapperCreator.java @@ -14,7 +14,7 @@ final public class MqttClientConnectionWrapperCreator { public static MqttClientConnectionWrapper createConnection( - String cert, String key, String clientId, String endpoint, int port, Integer mqttVersion) { + String cert, String key, String clientId, String endpoint, int port, int mqttVersion) { if (mqttVersion == 3) { return createMqtt3Connection(cert, key, clientId, endpoint, port); } else if (mqttVersion == 5) { @@ -32,12 +32,7 @@ static MqttClientConnectionWrapper createMqtt3Connection( .withEndpoint(endpoint) .withPort((short)port) .withCleanSession(true); - Mqtt3ClientConnectionWrapper connWrapper = new Mqtt3ClientConnectionWrapper(); - connWrapper.connection = builder.build(); - if (connWrapper.connection == null) { - throw new RuntimeException("MQTT311 connection creation failed!"); - } - return connWrapper; + return new Mqtt3ClientConnectionWrapper(builder); } catch (Exception ex) { throw new RuntimeException("Failed to create MQTT311 connection", ex); } @@ -53,13 +48,7 @@ static MqttClientConnectionWrapper createMqtt5Connection( builder.withConnectProperties(connectProperties); builder.withLifeCycleEvents(lifecycleEvents); builder.withPort((long)port); - Mqtt5ClientConnectionWrapper connWrapper = new Mqtt5ClientConnectionWrapper(); - connWrapper.client = builder.build(); - connWrapper.connection = new MqttClientConnection(connWrapper.client, null); - if (connWrapper.connection == null) { - throw new RuntimeException("MQTT5 connection creation failed!"); - } - return connWrapper; + return new Mqtt5ClientConnectionWrapper(builder); } catch (Exception ex) { throw new RuntimeException("Failed to create MQTT311 connection from MQTT5 client", ex); } From d2615ba5a7eb2b30e45ccc7481a24804136e1c0e Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Wed, 15 Nov 2023 15:49:33 -0800 Subject: [PATCH 28/34] Move main function --- .../fleetProvisioning/FleetProvisioning.java | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java index 612d431c..d01d3bb8 100644 --- a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java +++ b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java @@ -87,47 +87,6 @@ static void onException(Exception e) { System.out.println("Exception occurred " + e); } - public static void main(String[] args) { - CommandLineUtils.SampleCommandLineData cmdData = CommandLineUtils.getInputForIoTSample("FleetProvisioningSample", args); - - boolean exitWithError = false; - - try (MqttClientConnectionWrapper connection = MqttClientConnectionWrapperCreator.createConnection( - cmdData.input_cert, - cmdData.input_key, - cmdData.input_clientId, - cmdData.input_endpoint, - cmdData.input_port, - cmdData.input_mqtt_version)) { - // Create the identity client (Identity = Fleet Provisioning) - iotIdentityClient = new IotIdentityClient(connection.getConnection()); - - // Connect - CompletableFuture connected = connection.start(); - boolean sessionPresent = connected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); - System.out.println("Connected to " + (!sessionPresent ? "new" : "existing") + " session!"); - - createKeysAndCertificateWorkflow(cmdData.input_templateName, cmdData.input_templateParameters); - - // Disconnect - CompletableFuture disconnected = connection.stop(); - disconnected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); - } catch (Exception ex) { - System.out.println("Exception encountered! " + "\n"); - ex.printStackTrace(); - exitWithError = true; - } - - CrtResource.waitForNoResources(); - System.out.println("Service test complete!"); - - if (exitWithError) { - System.exit(1); - } else { - System.exit(0); - } - } - private static void SubscribeToRegisterThing(String input_templateName) throws Exception { RegisterThingSubscriptionRequest registerThingSubscriptionRequest = new RegisterThingSubscriptionRequest(); registerThingSubscriptionRequest.templateName = input_templateName; @@ -206,4 +165,45 @@ private static void createKeysAndCertificateWorkflow(String input_templateName, gotResponse.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); System.out.println("Got response at RegisterThing"); } + + public static void main(String[] args) { + CommandLineUtils.SampleCommandLineData cmdData = CommandLineUtils.getInputForIoTSample("FleetProvisioningSample", args); + + boolean exitWithError = false; + + try (MqttClientConnectionWrapper connection = MqttClientConnectionWrapperCreator.createConnection( + cmdData.input_cert, + cmdData.input_key, + cmdData.input_clientId, + cmdData.input_endpoint, + cmdData.input_port, + cmdData.input_mqtt_version)) { + // Create the identity client (Identity = Fleet Provisioning) + iotIdentityClient = new IotIdentityClient(connection.getConnection()); + + // Connect + CompletableFuture connected = connection.start(); + boolean sessionPresent = connected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Connected to " + (!sessionPresent ? "new" : "existing") + " session!"); + + createKeysAndCertificateWorkflow(cmdData.input_templateName, cmdData.input_templateParameters); + + // Disconnect + CompletableFuture disconnected = connection.stop(); + disconnected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + } catch (Exception ex) { + System.out.println("Exception encountered! " + "\n"); + ex.printStackTrace(); + exitWithError = true; + } + + CrtResource.waitForNoResources(); + System.out.println("Service test complete!"); + + if (exitWithError) { + System.exit(1); + } else { + System.exit(0); + } + } } From bf6f3ad6d5871cb65302c27d22b365c4cd189c3f Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Wed, 15 Nov 2023 15:58:18 -0800 Subject: [PATCH 29/34] cleanup --- servicetests/test_cases/test_fleet_provisioning.py | 3 ++- .../src/main/java/fleetProvisioning/FleetProvisioning.java | 4 +--- .../MqttClientConnectionWrapperCreator.java | 3 +-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/servicetests/test_cases/test_fleet_provisioning.py b/servicetests/test_cases/test_fleet_provisioning.py index 5e1da52b..7732678f 100644 --- a/servicetests/test_cases/test_fleet_provisioning.py +++ b/servicetests/test_cases/test_fleet_provisioning.py @@ -31,7 +31,8 @@ def main(): # Perform fleet provisioning. If it's successful, a newly created thing should appear. test_result = run_in_ci.setup_and_launch(cfg_file, input_uuid) - # Delete a thing created by fleet provisioning. + # Delete a thing created by fleet provisioning. If this fails, we assume that's because fleet provisioning failed to + # create a thing. # NOTE We want to try to delete thing even if test was unsuccessful. thing_name = parsed_commands.thing_name_prefix + input_uuid delete_result = ci_iot_thing.delete_iot_thing(thing_name, parsed_commands.region) diff --git a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java index d01d3bb8..4a524e41 100644 --- a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java +++ b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java @@ -31,7 +31,6 @@ import ServiceTestLifecycleEvents.ServiceTestLifecycleEvents; public class FleetProvisioning { - static CompletableFuture gotResponse; static IotIdentityClient iotIdentityClient; @@ -183,8 +182,7 @@ public static void main(String[] args) { // Connect CompletableFuture connected = connection.start(); - boolean sessionPresent = connected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); - System.out.println("Connected to " + (!sessionPresent ? "new" : "existing") + " session!"); + connected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); createKeysAndCertificateWorkflow(cmdData.input_templateName, cmdData.input_templateParameters); diff --git a/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapperCreator.java b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapperCreator.java index 80868d47..cd2c796f 100644 --- a/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapperCreator.java +++ b/servicetests/tests/Utils/MqttClientConnectionWrapper/utils/mqttclientconnectionwrapper/MqttClientConnectionWrapperCreator.java @@ -26,8 +26,7 @@ public static MqttClientConnectionWrapper createConnection( static MqttClientConnectionWrapper createMqtt3Connection( String cert, String key, String clientId, String endpoint, int port) { - try (AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder - .newMtlsBuilderFromPath(cert, key)) { + try (AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder.newMtlsBuilderFromPath(cert, key)) { builder.withClientId(clientId) .withEndpoint(endpoint) .withPort((short)port) From c23d7bbd0009febd21e78edadb473784e2568519 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Wed, 15 Nov 2023 16:07:29 -0800 Subject: [PATCH 30/34] cleanup --- .../src/main/java/fleetProvisioning/FleetProvisioning.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java index 4a524e41..58b5b266 100644 --- a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java +++ b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java @@ -190,13 +190,13 @@ public static void main(String[] args) { CompletableFuture disconnected = connection.stop(); disconnected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); } catch (Exception ex) { - System.out.println("Exception encountered! " + "\n"); + System.out.println("Exception encountered\n"); ex.printStackTrace(); exitWithError = true; } CrtResource.waitForNoResources(); - System.out.println("Service test complete!"); + System.out.println("Service test complete"); if (exitWithError) { System.exit(1); From 4444d01e171be2b85bb8f1253e08af2a1d8621ca Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Wed, 15 Nov 2023 16:36:44 -0800 Subject: [PATCH 31/34] Add CSR --- ...mqtt3_fleet_provisioning_with_csr_cfg.json | 39 ++++++++ ...mqtt5_fleet_provisioning_with_csr_cfg.json | 39 ++++++++ .../test_cases/test_fleet_provisioning.py | 8 +- .../fleetProvisioning/FleetProvisioning.java | 88 ++++++++++++++++++- 4 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 servicetests/test_cases/mqtt3_fleet_provisioning_with_csr_cfg.json create mode 100644 servicetests/test_cases/mqtt5_fleet_provisioning_with_csr_cfg.json diff --git a/servicetests/test_cases/mqtt3_fleet_provisioning_with_csr_cfg.json b/servicetests/test_cases/mqtt3_fleet_provisioning_with_csr_cfg.json new file mode 100644 index 00000000..74306a9c --- /dev/null +++ b/servicetests/test_cases/mqtt3_fleet_provisioning_with_csr_cfg.json @@ -0,0 +1,39 @@ +{ + "language": "Java", + "runnable_file": "tests/FleetProvisioning", + "runnable_region": "us-east-1", + "runnable_main_class": "fleetProvisioning.FleetProvisioning", + "arguments": [ + { + "name": "--mqtt_version", + "data": 3 + }, + { + "name": "--endpoint", + "secret": "ci/endpoint" + }, + { + "name": "--cert", + "secret": "ci/FleetProvisioning/cert", + "filename": "tmp_certificate.pem" + }, + { + "name": "--key", + "secret": "ci/FleetProvisioning/key", + "filename": "tmp_key.pem" + }, + { + "name": "--csr", + "secret": "ci/FleetProvisioning/csr", + "filename": "tmp_csr.pem" + }, + { + "name": "--template_name", + "data": "CI_FleetProvisioning_Template" + }, + { + "name": "--template_parameters", + "data": "{SerialNumber:$INPUT_UUID}" + } + ] +} diff --git a/servicetests/test_cases/mqtt5_fleet_provisioning_with_csr_cfg.json b/servicetests/test_cases/mqtt5_fleet_provisioning_with_csr_cfg.json new file mode 100644 index 00000000..4ac69b40 --- /dev/null +++ b/servicetests/test_cases/mqtt5_fleet_provisioning_with_csr_cfg.json @@ -0,0 +1,39 @@ +{ + "language": "Java", + "runnable_file": "tests/FleetProvisioning", + "runnable_region": "us-east-1", + "runnable_main_class": "fleetProvisioning.FleetProvisioning", + "arguments": [ + { + "name": "--mqtt_version", + "data": 5 + }, + { + "name": "--endpoint", + "secret": "ci/endpoint" + }, + { + "name": "--cert", + "secret": "ci/FleetProvisioning/cert", + "filename": "tmp_certificate.pem" + }, + { + "name": "--key", + "secret": "ci/FleetProvisioning/key", + "filename": "tmp_key.pem" + }, + { + "name": "--csr", + "secret": "ci/FleetProvisioning/csr", + "filename": "tmp_csr.pem" + }, + { + "name": "--template_name", + "data": "CI_FleetProvisioning_Template" + }, + { + "name": "--template_parameters", + "data": "{SerialNumber:$INPUT_UUID}" + } + ] +} diff --git a/servicetests/test_cases/test_fleet_provisioning.py b/servicetests/test_cases/test_fleet_provisioning.py index 7732678f..e82ac980 100644 --- a/servicetests/test_cases/test_fleet_provisioning.py +++ b/servicetests/test_cases/test_fleet_provisioning.py @@ -21,11 +21,14 @@ def main(): "--region", required=False, default="us-east-1", help="The name of the region to use") argument_parser.add_argument( "--mqtt-version", required=True, choices=[3, 5], type=int, help="MQTT protocol version to use") + argument_parser.add_argument( + "--use-csr", required=False, default=False, action='store_true', help="Create certificate from CSR") parsed_commands = argument_parser.parse_args() current_path = os.path.dirname(os.path.realpath(__file__)) - cfg_file_pfx = "mqtt3_" if parsed_commands.mqtt_version == 3 else "mqtt5_" - cfg_file = os.path.join(current_path, cfg_file_pfx + "fleet_provisioning_cfg.json") + cfg_file_mqtt_version = "mqtt3_" if parsed_commands.mqtt_version == 3 else "mqtt5_" + cfg_file_csr = "with_csr_" if parsed_commands.use_csr else "" + cfg_file = os.path.join(current_path, cfg_file_mqtt_version + "fleet_provisioning_" + cfg_file_csr + "cfg.json") input_uuid = parsed_commands.input_uuid if parsed_commands.input_uuid else str(uuid.uuid4()) # Perform fleet provisioning. If it's successful, a newly created thing should appear. @@ -40,5 +43,6 @@ def main(): if test_result != 0 or delete_result != 0: sys.exit(-1) + if __name__ == "__main__": main() diff --git a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java index 58b5b266..895708ba 100644 --- a/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java +++ b/servicetests/tests/FleetProvisioning/src/main/java/fleetProvisioning/FleetProvisioning.java @@ -10,6 +10,9 @@ import software.amazon.awssdk.crt.CrtRuntimeException; import software.amazon.awssdk.crt.mqtt.QualityOfService; import software.amazon.awssdk.iot.iotidentity.IotIdentityClient; +import software.amazon.awssdk.iot.iotidentity.model.CreateCertificateFromCsrRequest; +import software.amazon.awssdk.iot.iotidentity.model.CreateCertificateFromCsrResponse; +import software.amazon.awssdk.iot.iotidentity.model.CreateCertificateFromCsrSubscriptionRequest; import software.amazon.awssdk.iot.iotidentity.model.CreateKeysAndCertificateRequest; import software.amazon.awssdk.iot.iotidentity.model.CreateKeysAndCertificateResponse; import software.amazon.awssdk.iot.iotidentity.model.CreateKeysAndCertificateSubscriptionRequest; @@ -35,6 +38,7 @@ public class FleetProvisioning { static IotIdentityClient iotIdentityClient; static CreateKeysAndCertificateResponse createKeysAndCertificateResponse = null; + static CreateCertificateFromCsrResponse createCertificateFromCsrResponse = null; static RegisterThingResponse registerThingResponse = null; static long responseWaitTimeMs = 5000L; // 5 seconds @@ -46,6 +50,13 @@ static void onRejectedKeys(ErrorResponse response) { gotResponse.complete(null); } + static void onRejectedCsr(ErrorResponse response) { + System.out.println("CreateCertificateFromCsr Request rejected, errorCode: " + response.errorCode + + ", errorMessage: " + response.errorMessage + + ", statusCode: " + response.statusCode); + gotResponse.complete(null); + } + static void onRejectedRegister(ErrorResponse response) { System.out.println("RegisterThing Request rejected, errorCode: " + response.errorCode + ", errorMessage: " + response.errorMessage + @@ -67,6 +78,20 @@ static void onCreateKeysAndCertificateAccepted(CreateKeysAndCertificateResponse gotResponse.complete(null); } + static void onCreateCertificateFromCsrResponseAccepted(CreateCertificateFromCsrResponse response) { + if (response != null) { + System.out.println("CreateCertificateFromCsr response certificateId: " + response.certificateId); + if (createCertificateFromCsrResponse == null) { + createCertificateFromCsrResponse = response; + } else { + System.out.println("CreateCertificateFromCsr response received after having already gotten a response!"); + } + } else { + System.out.println("CreateCertificateFromCsr response is null"); + } + gotResponse.complete(null); + } + static void onRegisterThingAccepted(RegisterThingResponse response) { if (response != null) { System.out.println("RegisterThing response thingName: " + response.thingName); @@ -165,6 +190,61 @@ private static void createKeysAndCertificateWorkflow(String input_templateName, System.out.println("Got response at RegisterThing"); } + private static void createCertificateFromCsrWorkflow(String input_templateName, String input_templateParameters, String input_csrPath) throws Exception { + CreateCertificateFromCsrSubscriptionRequest createCertificateFromCsrSubscriptionRequest = new CreateCertificateFromCsrSubscriptionRequest(); + CompletableFuture csrSubscribedAccepted = iotIdentityClient.SubscribeToCreateCertificateFromCsrAccepted( + createCertificateFromCsrSubscriptionRequest, + QualityOfService.AT_LEAST_ONCE, + FleetProvisioning::onCreateCertificateFromCsrResponseAccepted); + + csrSubscribedAccepted.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Subscribed to CreateCertificateFromCsrAccepted"); + + CompletableFuture csrSubscribedRejected = iotIdentityClient.SubscribeToCreateCertificateFromCsrRejected( + createCertificateFromCsrSubscriptionRequest, + QualityOfService.AT_LEAST_ONCE, + FleetProvisioning::onRejectedCsr); + + csrSubscribedRejected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Subscribed to CreateCertificateFromCsrRejected"); + + // Subscribes to the register thing accepted and rejected topics + SubscribeToRegisterThing(input_templateName); + + String csrContents = new String(Files.readAllBytes(Paths.get(input_csrPath))); + CreateCertificateFromCsrRequest createCertificateFromCsrRequest = new CreateCertificateFromCsrRequest(); + createCertificateFromCsrRequest.certificateSigningRequest = csrContents; + CompletableFuture publishCsr = iotIdentityClient.PublishCreateCertificateFromCsr( + createCertificateFromCsrRequest, + QualityOfService.AT_LEAST_ONCE); + + gotResponse = new CompletableFuture<>(); + publishCsr.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Published to CreateCertificateFromCsr"); + gotResponse.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Got response at CreateCertificateFromCsr"); + + // Verify the response is good + if (createCertificateFromCsrResponse == null) { + throw new Exception("Got invalid/error createCertificateFromCsrResponse"); + } + + gotResponse = new CompletableFuture<>(); + System.out.println("RegisterThing now...."); + RegisterThingRequest registerThingRequest = new RegisterThingRequest(); + registerThingRequest.certificateOwnershipToken = createCertificateFromCsrResponse.certificateOwnershipToken; + registerThingRequest.templateName = input_templateName; + registerThingRequest.parameters = new Gson().fromJson(input_templateParameters, HashMap.class); + CompletableFuture publishRegister = iotIdentityClient.PublishRegisterThing( + registerThingRequest, + QualityOfService.AT_LEAST_ONCE); + + publishRegister.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Published to RegisterThing"); + gotResponse.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); + System.out.println("Got response at RegisterThing"); + } + public static void main(String[] args) { CommandLineUtils.SampleCommandLineData cmdData = CommandLineUtils.getInputForIoTSample("FleetProvisioningSample", args); @@ -177,6 +257,7 @@ public static void main(String[] args) { cmdData.input_endpoint, cmdData.input_port, cmdData.input_mqtt_version)) { + // Create the identity client (Identity = Fleet Provisioning) iotIdentityClient = new IotIdentityClient(connection.getConnection()); @@ -184,7 +265,12 @@ public static void main(String[] args) { CompletableFuture connected = connection.start(); connected.get(responseWaitTimeMs, TimeUnit.MILLISECONDS); - createKeysAndCertificateWorkflow(cmdData.input_templateName, cmdData.input_templateParameters); + // Fleet Provision based on whether there is a CSR file path or not + if (cmdData.input_csrPath == null) { + createKeysAndCertificateWorkflow(cmdData.input_templateName, cmdData.input_templateParameters); + } else { + createCertificateFromCsrWorkflow(cmdData.input_templateName, cmdData.input_templateParameters, cmdData.input_csrPath); + } // Disconnect CompletableFuture disconnected = connection.stop(); From ba2d4946647d1d1d4e43a181e8033486b70bb826 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Wed, 15 Nov 2023 16:39:26 -0800 Subject: [PATCH 32/34] Fix CI --- .github/workflows/ci.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf4e35fe..4ea0b6fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -476,16 +476,26 @@ jobs: with: role-to-assume: ${{ env.CI_FLEET_PROVISIONING_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} - - name: run Fleet Provisioning test for MQTT311 + - name: run Fleet Provisioning service client test for MQTT311 working-directory: ./servicetests run: | export PYTHONPATH=${{ github.workspace }}/utils python3 ./test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 3 - - name: run Fleet Provisioning test for MQTT5 + - name: run Fleet Provisioning service client test for MQTT5 working-directory: ./servicetests run: | export PYTHONPATH=${{ github.workspace }}/utils python3 ./test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 5 + - name: run Fleet Provisioning with CSR service client test for MQTT311 + working-directory: ./servicetests + run: | + export PYTHONPATH=${{ github.workspace }}/utils + python3 ./test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 3 --use-csr + - name: run Fleet Provisioning with CSR service client test for MQTT5 + working-directory: ./servicetests + run: | + export PYTHONPATH=${{ github.workspace }}/utils + python3 ./test_cases/test_fleet_provisioning.py --thing-name-prefix Fleet_Thing_ --mqtt-version 5 --use-csr - name: configure AWS credentials (Connect and PubSub) uses: aws-actions/configure-aws-credentials@v2 with: From c76ec17e8ae7777ef539be0e2b1727064d717ad1 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Wed, 15 Nov 2023 17:12:54 -0800 Subject: [PATCH 33/34] Use exceptions --- servicetests/test_cases/test_fleet_provisioning.py | 8 ++++++-- utils/ci_iot_thing.py | 13 +++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/servicetests/test_cases/test_fleet_provisioning.py b/servicetests/test_cases/test_fleet_provisioning.py index e82ac980..cc9c981a 100644 --- a/servicetests/test_cases/test_fleet_provisioning.py +++ b/servicetests/test_cases/test_fleet_provisioning.py @@ -38,9 +38,13 @@ def main(): # create a thing. # NOTE We want to try to delete thing even if test was unsuccessful. thing_name = parsed_commands.thing_name_prefix + input_uuid - delete_result = ci_iot_thing.delete_iot_thing(thing_name, parsed_commands.region) + try: + delete_result = ci_iot_thing.delete_iot_thing(thing_name, parsed_commands.region) + except Exception as e: + print(f"ERROR: Failed to delete thing: {e}") + test_result = -1 - if test_result != 0 or delete_result != 0: + if test_result != 0: sys.exit(-1) diff --git a/utils/ci_iot_thing.py b/utils/ci_iot_thing.py index 62f7810c..d1d89b5b 100644 --- a/utils/ci_iot_thing.py +++ b/utils/ci_iot_thing.py @@ -8,8 +8,8 @@ def delete_iot_thing(thing_name, region): try: iot_client = boto3.client('iot', region_name=region) except Exception as e: - print(f"ERROR: Could not make Boto3 client. Credentials likely could not be sourced. Exception: {e}") - return -1 + print(f"ERROR: Could not make Boto3 client. Credentials likely could not be sourced") + raise try: thing_principals = iot_client.list_thing_principals(thingName=thing_name) @@ -20,15 +20,12 @@ def delete_iot_thing(thing_name, region): iot_client.update_certificate(certificateId=certificate_id, newStatus='INACTIVE') iot_client.delete_certificate(certificateId=certificate_id, forceDelete=True) except Exception as e: - print("ERROR: Could not delete certificate for IoT thing " - f"{thing_name}, probably thing does not exist. Exception: {e}") - return -1 + print("ERROR: Could not delete certificate for IoT thing {thing_name}, probably thing does not exist") + raise try: iot_client.delete_thing(thingName=thing_name) except Exception as e: - print(f"ERROR: Could not delete IoT thing {thing_name}. Exception: {e}") - return -1 + raise print("IoT thing deleted successfully") - return 0 From 18f70b7e4c15c65c20095dca240ca0d0add61898 Mon Sep 17 00:00:00 2001 From: Igor Abdrakhimov Date: Wed, 15 Nov 2023 17:18:50 -0800 Subject: [PATCH 34/34] Fix deleting thing --- utils/ci_iot_thing.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/utils/ci_iot_thing.py b/utils/ci_iot_thing.py index d1d89b5b..db6c3661 100644 --- a/utils/ci_iot_thing.py +++ b/utils/ci_iot_thing.py @@ -1,6 +1,8 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0. +import sys + import boto3 @@ -8,24 +10,27 @@ def delete_iot_thing(thing_name, region): try: iot_client = boto3.client('iot', region_name=region) except Exception as e: - print(f"ERROR: Could not make Boto3 client. Credentials likely could not be sourced") + print(f"ERROR: Could not make Boto3 client. Credentials likely could not be sourced", file=sys.stderr) raise try: thing_principals = iot_client.list_thing_principals(thingName=thing_name) - print(f"principals: {thing_principals}") + print(f"principals: {thing_principals}", file=sys.stderr) for principal in thing_principals["principals"]: certificate_id = principal.split("/")[1] iot_client.detach_thing_principal(thingName=thing_name, principal=principal) iot_client.update_certificate(certificateId=certificate_id, newStatus='INACTIVE') iot_client.delete_certificate(certificateId=certificate_id, forceDelete=True) - except Exception as e: - print("ERROR: Could not delete certificate for IoT thing {thing_name}, probably thing does not exist") + except Exception: + print("ERROR: Could not delete certificate for IoT thing {thing_name}, probably thing does not exist", + file=sys.stderr) raise try: iot_client.delete_thing(thingName=thing_name) - except Exception as e: + except Exception: raise - print("IoT thing deleted successfully") + print("IoT thing deleted successfully", file=sys.stderr) + + return 0