Skip to content

Commit

Permalink
Shadow update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
alfred2g committed Dec 1, 2023
1 parent bea2e40 commit b7f01b4
Show file tree
Hide file tree
Showing 9 changed files with 313 additions and 0 deletions.
1 change: 1 addition & 0 deletions .builder/actions/build_samples.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def run(self, env):

servicetests = [
'servicetests/tests/JobsExecution/',
'servicetests/tests/ShadowUpdate/',
]

for sample_path in samples:
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ env:
CI_MQTT5_ROLE: arn:aws:iam::180635532705:role/CI_MQTT5_Role
CI_GREENGRASS_ROLE: arn:aws:iam::180635532705:role/CI_Greengrass_Role
CI_JOBS_SERVICE_CLIENT_ROLE: arn:aws:iam::180635532705:role/CI_JobsServiceClient_Role
CI_SHADOW_SERVICE_CLIENT_ROLE: arn:aws:iam::180635532705:role/CI_ShadowServiceClient_Role

jobs:
linux-compat:
Expand Down Expand Up @@ -476,6 +477,31 @@ jobs:
run: |
export PYTHONPATH=${{ github.workspace }}/aws-iot-device-sdk-cpp-v2/utils
python3 ./test_cases/test_jobs_execution.py --config-file ${{ env.CI_SERVICE_TESTS_CFG_FOLDER }}/mqtt5_jobs_cfg.json
- name: configure AWS credentials (Shadow)
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: ${{ env.CI_SHADOW_SERVICE_CLIENT_ROLE }}
aws-region: ${{ env.AWS_DEFAULT_REGION }}
- name: run Shadow service client test for MQTT311
working-directory: ./aws-iot-device-sdk-cpp-v2/servicetests
run: |
export PYTHONPATH=${{ github.workspace }}/aws-iot-device-sdk-js-v2/utils
python3 ./test_cases/test_shadow_update.py --config-file test_cases/mqtt3_shadow_cfg.json
- name: run Shadow service client test for MQTT5
working-directory: ./aws-iot-device-sdk-cpp-v2/servicetests
run: |
export PYTHONPATH=${{ github.workspace }}/aws-iot-device-sdk-js-v2/utils
python3 ./test_cases/test_shadow_update.py --config-file test_cases/mqtt5_shadow_cfg.json
- name: run Named Shadow service client test for MQTT311
working-directory: ./aws-iot-device-sdk-cpp-v2/servicetests
run: |
export PYTHONPATH=${{ github.workspace }}/aws-iot-device-sdk-js-v2/utils
python3 ./test_cases/test_shadow_update.py --config-file test_cases/mqtt3_named_shadow_cfg.json
- name: run Named Shadow service client test for MQTT5
working-directory: ./aws-iot-device-sdk-cpp-v2/servicetests
run: |
export PYTHONPATH=${{ github.workspace }}/aws-iot-device-sdk-cpp-v2/utils
python3 ./test_cases/test_shadow_update.py --config-file test_cases/mqtt5_named_shadow_cfg.json
- name: configure AWS credentials (Connect and PubSub)
uses: aws-actions/configure-aws-credentials@v1
Expand Down
5 changes: 5 additions & 0 deletions samples/utils/CommandLineUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ namespace Utils
static const char *m_cmd_proxy_user_name = "proxy_user_name";
static const char *m_cmd_proxy_password = "proxy_password";
static const char *m_cmd_shadow_property = "shadow_property";
static const char *m_cmd_shadow_name = "shadow_name";
static const char *m_cmd_shadow_value = "shadow_value";
static const char *m_cmd_region = "region";
static const char *m_cmd_pkcs12_file = "pkcs12_file";
static const char *m_cmd_pkcs12_password = "pkcs12_password";
Expand Down Expand Up @@ -961,6 +963,9 @@ namespace Utils
returnData.input_shadowProperty = cmdUtils.GetCommandOrDefault(m_cmd_shadow_property, "color");
returnData.input_clientId =
cmdUtils.GetCommandOrDefault(m_cmd_client_id, Aws::Crt::String("test-") + Aws::Crt::UUID().ToString());

returnData.input_shadowName = cmdUtils.GetCommandOrDefault(m_cmd_shadow_name , "");
returnData.input_shadowValue = cmdUtils.GetCommandOrDefault(m_cmd_shadow_value , "");
return returnData;
}

Expand Down
2 changes: 2 additions & 0 deletions samples/utils/CommandLineUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ namespace Utils
Aws::Crt::String input_proxyPassword;
// Shadow
Aws::Crt::String input_shadowProperty;
Aws::Crt::String input_shadowName;
Aws::Crt::String input_shadowValue;
// PKCS12
Aws::Crt::String input_pkcs12File;
Aws::Crt::String input_pkcs12Password;
Expand Down
40 changes: 40 additions & 0 deletions servicetests/test_cases/mqtt3_named_shadow_cfg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"language": "Javascript",
"runnable_file": "../build/servicetests/tests/ShadowUpdate/shadow-update",
"runnable_region": "us-east-1",
"runnable_main_class": "",
"arguments": [
{
"name": "--mqtt_version",
"data": "3"
},
{
"name": "--endpoint",
"secret": "ci/endpoint"
},
{
"name": "--cert",
"data": "tests/ShadowUpdate/certificate.pem.crt"
},
{
"name": "--key",
"data": "tests/ShadowUpdate/private.pem.key"
},
{
"name": "--thing_name",
"data": "ServiceTest_Shadow_$INPUT_UUID"
},
{
"name": "--shadow_property",
"data": "color"
},
{
"name": "--shadow_value",
"data": "on"
},
{
"name": "--shadow_name",
"data": "testShadow"
}
]
}
36 changes: 36 additions & 0 deletions servicetests/test_cases/mqtt3_shadow_cfg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"language": "Javascript",
"runnable_file": "../build/servicetests/tests/ShadowUpdate/shadow-update",
"runnable_region": "us-east-1",
"runnable_main_class": "",
"arguments": [
{
"name": "--mqtt_version",
"data": "3"
},
{
"name": "--endpoint",
"secret": "ci/endpoint"
},
{
"name": "--cert",
"data": "tests/ShadowUpdate/certificate.pem.crt"
},
{
"name": "--key",
"data": "tests/ShadowUpdate/private.pem.key"
},
{
"name": "--thing_name",
"data": "ServiceTest_Shadow_$INPUT_UUID"
},
{
"name": "--shadow_property",
"data": "color"
},
{
"name": "--shadow_value",
"data": "on"
}
]
}
40 changes: 40 additions & 0 deletions servicetests/test_cases/mqtt5_named_shadow_cfg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"language": "Javascript",
"runnable_file": "../build/servicetests/tests/ShadowUpdate/shadow-update",
"runnable_region": "us-east-1",
"runnable_main_class": "",
"arguments": [
{
"name": "--mqtt_version",
"data": "5"
},
{
"name": "--endpoint",
"secret": "ci/endpoint"
},
{
"name": "--cert",
"data": "tests/ShadowUpdate/certificate.pem.crt"
},
{
"name": "--key",
"data": "tests/ShadowUpdate/private.pem.key"
},
{
"name": "--thing_name",
"data": "ServiceTest_Shadow_$INPUT_UUID"
},
{
"name": "--shadow_property",
"data": "color"
},
{
"name": "--shadow_value",
"data": "on"
},
{
"name": "--shadow_name",
"data": "testShadow"
}
]
}
36 changes: 36 additions & 0 deletions servicetests/test_cases/mqtt5_shadow_cfg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"language": "CPP",
"runnable_file": "../build/servicetests/tests/ShadowUpdate/shadow-update",
"runnable_region": "us-east-1",
"runnable_main_class": "",
"arguments": [
{
"name": "--mqtt_version",
"data": "5"
},
{
"name": "--endpoint",
"secret": "ci/endpoint"
},
{
"name": "--cert",
"data": "tests/ShadowUpdate/certificate.pem.crt"
},
{
"name": "--key",
"data": "tests/ShadowUpdate/private.pem.key"
},
{
"name": "--thing_name",
"data": "ServiceTest_Shadow_$INPUT_UUID"
},
{
"name": "--shadow_property",
"data": "color"
},
{
"name": "--shadow_value",
"data": "on"
}
]
}
127 changes: 127 additions & 0 deletions servicetests/test_cases/test_shadow_update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-3.0.

import argparse
import json
import os
import sys
import uuid

import boto3

import run_in_ci
import ci_iot_thing


def get_shadow_attrs(config_file):
with open(config_file) as f:
json_data = json.load(f)
shadow_name = next((json_arg["data"] for json_arg in json_data["arguments"] if json_arg.get("name", "") == "--shadow_name"), "")
shadow_property = next((json_arg["data"] for json_arg in json_data["arguments"] if json_arg.get("name", "") == "--shadow_property"), "")
shadow_desired_value = next((json_arg["data"] for json_arg in json_data["arguments"] if json_arg.get("name", "") == "--shadow_value"), "")
return [shadow_name, shadow_property, shadow_desired_value]


def main():
argument_parser = argparse.ArgumentParser(
description="Run Shadow test in CI")
argument_parser.add_argument(
"--config-file", required=True,
help="JSON file providing command-line arguments for a test")
argument_parser.add_argument(
"--input-uuid", required=False, help="UUID for thing name. UUID will be generated if this option is omit")
argument_parser.add_argument(
"--region", required=False, default="us-east-1", help="The name of the region to use")
parsed_commands = argument_parser.parse_args()

[shadow_name, shadow_property, shadow_desired_value] = get_shadow_attrs(parsed_commands.config_file)
print(f"Shadow name: '{shadow_name}'")
print(f"Shadow property: '{shadow_property}'")
print(f"Shadow desired value: '{shadow_desired_value}'")

try:
iot_data_client = boto3.client('iot-data', region_name=parsed_commands.region)
secrets_client = boto3.client("secretsmanager", region_name=parsed_commands.region)
except Exception as e:
print(f"ERROR: Could not make Boto3 iot-data client. Credentials likely could not be sourced. Exception: {e}",
file=sys.stderr)
return -1

input_uuid = parsed_commands.input_uuid if parsed_commands.input_uuid else str(uuid.uuid4())

thing_name = "ServiceTest_Shadow_" + input_uuid
policy_name = secrets_client.get_secret_value(
SecretId="ci/ShadowServiceClientTest/policy_name")["SecretString"]

# Temporary certificate/key file path.
certificate_path = os.path.join(os.getcwd(), "tests/ShadowUpdate/certificate.pem.crt")
key_path = os.path.join(os.getcwd(), "tests/ShadowUpdate/private.pem.key")

try:
ci_iot_thing.create_iot_thing(
thing_name=thing_name,
region=parsed_commands.region,
policy_name=policy_name,
certificate_path=certificate_path,
key_path=key_path)
except Exception as e:
print(f"ERROR: Failed to create IoT thing: {e}")
sys.exit(-1)

# Perform Shadow test. If it's successful, a shadow should appear for a specified thing.
try:
test_result = run_in_ci.setup_and_launch(parsed_commands.config_file, input_uuid)
except Exception as e:
print(f"ERROR: Failed to execute Jobs test: {e}")
test_result = -1

# Test reported success, verify that shadow was indeed updated.
if test_result == 0:
print("Verifying that shadow was updated")
shadow_value = None
try:
if shadow_name:
thing_shadow = iot_data_client.get_thing_shadow(thingName=thing_name, shadowName=shadow_name)
else:
thing_shadow = iot_data_client.get_thing_shadow(thingName=thing_name)

payload = thing_shadow['payload'].read()
data = json.loads(payload)
shadow_value = data.get('state', {}).get('reported', {}).get(shadow_property, None)
if shadow_value != shadow_desired_value:
print(f"ERROR: Could not verify thing shadow: {shadow_property} is not set to desired value "
f"'{shadow_desired_value}'; shadow actual state: {data}")
test_result = -1
except KeyError as e:
print(f"ERROR: Could not verify thing shadow: key {e} does not exist in shadow response: {thing_shadow}")
test_result = -1
except Exception as e:
print(f"ERROR: Could not verify thing shadow: {e}")
test_result = -1

if test_result == 0:
print("Test succeeded")

# Delete a thing created for this test run.
# NOTE We want to try to delete thing even if test was unsuccessful.
try:
ci_iot_thing.delete_iot_thing(thing_name, parsed_commands.region)
except Exception as e:
print(f"ERROR: Failed to delete thing: {e}")
# Fail the test if unable to delete thing, so this won't remain unnoticed.
test_result = -1

try:
if os.path.isfile(certificate_path):
os.remove(certificate_path)
if os.path.isfile(key_path):
os.remove(key_path)
except Exception as e:
print(f"WARNING: Failed to delete local files: {e}")

if test_result != 0:
sys.exit(-1)


if __name__ == "__main__":
main()

0 comments on commit b7f01b4

Please sign in to comment.