Skip to content

Run jobs in CI service tests #535

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ env:
CI_DEVICE_ADVISOR: arn:aws:iam::180635532705:role/CI_DeviceAdvisor_Role
CI_MQTT5_ROLE: arn:aws:iam::180635532705:role/CI_MQTT5_Role
CI_BUILD_AND_TEST_ROLE: arn:aws:iam::180635532705:role/V2_SDK_Unit_Testing
CI_JOBS_SERVICE_CLIENT_ROLE: arn:aws:iam::180635532705:role/CI_JobsServiceClient_Role
CI_SERVICE_ROLE_CFG_FOLDER: "./aws-iot-device-sdk-python-v2/servicetests/test_cases"

jobs:

Expand Down Expand Up @@ -204,6 +206,23 @@ jobs:
python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')"
chmod a+x builder
./builder build -p ${{ env.PACKAGE_NAME }}

- name: configure AWS credentials (service tests Jobs)
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: ${{ env.CI_JOBS_SERVICE_CLIENT_ROLE}}
aws-region: ${{ env.AWS_DEFAULT_REGION }}
- name: run MQTT3 Jobs servicetests
working-directory: ./aws-iot-device-sdk-python-v2/servicetests
run: |
export PYTHONPATH=${{ github.workspace }}/aws-iot-device-sdk-python-v2/utils:${{ github.workspace }}/aws-iot-device-sdk-python-v2/samples
python3 ./test_cases/test_jobs_execution.py --config-file test_cases/mqtt3_jobs_cfg.json
- name: run MQTT5 Jobs servicetests
working-directory: ./aws-iot-device-sdk-python-v2/servicetests
run: |
export PYTHONPATH=${{ github.workspace }}/aws-iot-device-sdk-python-v2/utils:${{ github.workspace }}/aws-iot-device-sdk-python-v2/samples
python3 ./test_cases/test_jobs_execution.py --config-file test_cases/mqtt5_jobs_cfg.json

- name: configure AWS credentials (Connect and PubSub)
uses: aws-actions/configure-aws-credentials@v1
with:
Expand Down
3 changes: 3 additions & 0 deletions samples/utils/command_line_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ def parse_sample_input_jobs():
cmdUtils.register_command(CommandLineUtils.m_cmd_port, "<int>", "Connection port. AWS IoT supports 443 and 8883 (optional, default=8883).", type=int)
cmdUtils.register_command(CommandLineUtils.m_cmd_thing_name, "<str>", "The name assigned to your IoT Thing", required=True)
cmdUtils.register_command(CommandLineUtils.m_cmd_job_time, "<int>", "Emulate working on a job by sleeping this many seconds (optional, default='5')", default=5, type=int)
cmdUtils.register_command(CommandLineUtils.m_cmd_mqtt_version, "<int>", "mqtt version (optional, default='5')", default=5, type=int)
cmdUtils.get_args()

cmdData = CommandLineUtils.CmdData()
Expand All @@ -475,6 +476,7 @@ def parse_sample_input_jobs():
cmdData.input_thing_name = cmdUtils.get_command_required(CommandLineUtils.m_cmd_thing_name)
cmdData.input_job_time = int(cmdUtils.get_command(CommandLineUtils.m_cmd_job_time, 5))
cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None
cmdData.input_mqtt_version = int(cmdUtils.get_command(CommandLineUtils.m_cmd_mqtt_version, 5))
return cmdData

def parse_sample_input_mqtt5_custom_authorizer_connect():
Expand Down Expand Up @@ -877,3 +879,4 @@ def parse_sample_input_pkcs12_connect():
m_cmd_pkcs12_file = "pkcs12_file"
m_cmd_pkcs12_password = "pkcs12_password"
m_cmd_region = "region"
m_cmd_mqtt_version = "mqtt_version"
28 changes: 28 additions & 0 deletions servicetests/test_cases/mqtt3_jobs_cfg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"language": "Python",
"runnable_file": "./tests/JobsExecution/jobs.py",
"runnable_region": "us-east-1",
"runnable_main_class": "",
"arguments": [
{
"name": "--mqtt_version",
"data": "3"
},
{
"name": "--endpoint",
"secret": "ci/endpoint"
},
{
"name": "--cert",
"data": "tests/JobsExecution/certificate.pem.crt"
},
{
"name": "--key",
"data": "tests/JobsExecution/private.pem.key"
},
{
"name": "--thing_name",
"data": "ServiceTest_Jobs_$INPUT_UUID"
}
]
}
28 changes: 28 additions & 0 deletions servicetests/test_cases/mqtt5_jobs_cfg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"language": "Python",
"runnable_file": "./tests/JobsExecution/jobs.py",
"runnable_region": "us-east-1",
"runnable_main_class": "",
"arguments": [
{
"name": "--mqtt_version",
"data": "5"
},
{
"name": "--endpoint",
"secret": "ci/endpoint"
},
{
"name": "--cert",
"data": "tests/JobsExecution/certificate.pem.crt"
},
{
"name": "--key",
"data": "tests/JobsExecution/private.pem.key"
},
{
"name": "--thing_name",
"data": "ServiceTest_Jobs_$INPUT_UUID"
}
]
}
120 changes: 120 additions & 0 deletions servicetests/test_cases/test_jobs_execution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0.

import argparse
import json
import os
import sys
import uuid
import time

import boto3

import run_in_ci
import ci_iot_thing


def main():
argument_parser = argparse.ArgumentParser(
description="Run Jobs 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()

try:
iot_client = boto3.client('iot', 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_Jobs_" + input_uuid
policy_name = secrets_client.get_secret_value(
SecretId="ci/JobsServiceClientTest/policy_name")["SecretString"]

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

try:
ci_iot_thing.create_iot_thing(
thing_name=thing_name,
thing_group="CI_ServiceClient_Thing_Group",
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)

thing_job = 'ERROR'
i = 0;
while 'ERROR' in thing_job and i <= 4:
try:
job_id = secrets_client.get_secret_value(SecretId="ci/JobsServiceClientTest/job_id")["SecretString"]
thing_job = iot_client.describe_job_execution(jobId=job_id, thingName=thing_name)
print(f'thing job is {thing_job}');
if 'ERROR' in thing_job:
i = i + 1;
else:
break;
except Exception as e:
print(f"Waiting for a newly created thing to be ready for the Job {e}")
i = i + 1;
time.sleep(1);

# Perform Jobs test. If it's successful, the Job execution should be marked as SUCCEEDED for the 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 Job was indeed executed by the thing.
if test_result == 0:
print("Verifying that Job was executed")
try:
job_id = secrets_client.get_secret_value(SecretId="ci/JobsServiceClientTest/job_id")["SecretString"]
thing_job = iot_client.describe_job_execution(jobId=job_id, thingName=thing_name)
job_status = thing_job.get('execution', {}).get('status', {})
if job_status != 'SUCCEEDED':
print(f"ERROR: Could not verify Job execution; Job info: {thing_job}")
test_result = -1
except Exception as e:
print(f"ERROR: Could not verify Job execution: {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()
Loading