Skip to content

Device Advisor CI automation #291

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 11 commits into from
Apr 14, 2022
Merged
48 changes: 48 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: CI

on:
push:
branches:
- '*'
- '!main'

env:
BUILDER_VERSION: v0.8.28
BUILDER_SOURCE: releases
BUILDER_HOST: https://d19elf31gohf1l.cloudfront.net
PACKAGE_NAME: aws-iot-device-sdk-python-v2
LINUX_BASE_IMAGE: ubuntu-16-x64
RUN: ${{ github.run_id }}-${{ github.run_number }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_DATEST_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_DATEST_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1

jobs:

al2:
runs-on: ubuntu-latest
steps:
# We can't use the `uses: docker://image` version yet, GitHub lacks authentication for actions -> packages
- name: Build ${{ env.PACKAGE_NAME }}
run: |
aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh
./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-al2-x64 build -p ${{ env.PACKAGE_NAME }}

windows:
runs-on: windows-latest
steps:
- name: Build ${{ env.PACKAGE_NAME }}
run: |
python -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder.pyz')"
python builder.pyz build -p ${{ env.PACKAGE_NAME }}

osx:
runs-on: macos-latest
steps:
- name: Build ${{ env.PACKAGE_NAME }}
run: |
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 }}


15 changes: 14 additions & 1 deletion builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,18 @@
["{python}", "-m", "pip", "install", ".", "--verbose"],
["{python}", "-m", "pip", "install", "boto3", "autopep8"],
["{python}", "-m", "unittest", "discover", "--verbose"]
]
],
"build_steps": [
"python3 -m pip install ."
],
"test_steps": [
"python3 -m pip install boto3",
"python3 deviceadvisor/script/DATestRun.py"
],
"env": {
"DA_TOPIC": "test/da",
"DA_SHADOW_PROPERTY": "datest",
"DA_SHADOW_VALUE_SET": "ON",
"DA_SHADOW_VALUE_DEFAULT": "OFF"
}
}
19 changes: 19 additions & 0 deletions deviceadvisor/script/DATestConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"tests" :["MQTT Connect", "MQTT Publish", "MQTT Subscribe", "Shadow Publish", "Shadow Update"],
"test_suite_ids" :
{
"MQTT Connect" : "ejbdzmo3hf3v",
"MQTT Publish" : "euw7favf6an4",
"MQTT Subscribe" : "01o8vo6no7sd",
"Shadow Publish" : "elztm2jebc1q",
"Shadow Update" : "vuydgrbbbfce"
},
"test_exe_path" :
{
"MQTT Connect" : "mqtt_connect.py",
"MQTT Publish" : "mqtt_publish.py",
"MQTT Subscribe" : "mqtt_subscribe.py",
"Shadow Publish" : "shadow_update.py",
"Shadow Update" : "shadow_update.py"
}
}
215 changes: 215 additions & 0 deletions deviceadvisor/script/DATestRun.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import boto3
import uuid
import json
import os
import subprocess
from time import sleep

##############################################
# Cleanup Certificates and Things and created certificate and private key file
def delete_thing_with_certi(thingName, certiId, certiArn):
client.detach_thing_principal(
thingName = thingName,
principal = certiArn)
client.update_certificate(
certificateId =certiId,
newStatus ='INACTIVE')
client.delete_certificate(certificateId = certiId, forceDelete = True)
client.delete_thing(thingName = thingName)
os.remove(os.environ["DA_CERTI"])
os.remove(os.environ["DA_KEY"])


##############################################
# Initialize variables
# create aws clients
client = boto3.client('iot')
dataClient = boto3.client('iot-data')
deviceAdvisor = boto3.client('iotdeviceadvisor')

# load test config
f = open('deviceadvisor/script/DATestConfig.json')
DATestConfig = json.load(f)
f.close()

# create an temporary certificate/key file path
certificate_path = os.path.join(os.getcwd(), 'certificate.pem.crt')
key_path = os.path.join(os.getcwd(), 'private.pem.key')

# load environment variables requried for testing
shadowProperty = os.environ['DA_SHADOW_PROPERTY']
shadowDefault = os.environ['DA_SHADOW_VALUE_DEFAULT']

# test result
test_result = {}


##############################################
# Run device advisor
for test_name in DATestConfig['tests']:
##############################################
# create a test thing
thing_name = "DATest_" + str(uuid.uuid4())
try:
# create_thing_response:
# {
# 'thingName': 'string',
# 'thingArn': 'string',
# 'thingId': 'string'
# }
print("[Device Advisor]Info: Started to create thing...")
create_thing_response = client.create_thing(
thingName=thing_name
)
os.environ["DA_THING_NAME"] = thing_name

except Exception as e:
print("[Device Advisor]Error: Failed to create thing: " + thing_name)
exit(-1)


##############################################
# create certificate and keys used for testing
try:
print("[Device Advisor]Info: Started to create certificate...")
# create_cert_response:
# {
# 'certificateArn': 'string',
# 'certificateId': 'string',
# 'certificatePem': 'string',
# 'keyPair':
# {
# 'PublicKey': 'string',
# 'PrivateKey': 'string'
# }
# }
create_cert_response = client.create_keys_and_certificate(
setAsActive=True
)
# write certificate to file
f = open(certificate_path, "w")
f.write(create_cert_response['certificatePem'])
f.close()

# write private key to file
f = open(key_path, "w")
f.write(create_cert_response['keyPair']['PrivateKey'])
f.close()

# setup environment variable
os.environ["DA_CERTI"] = certificate_path
os.environ["DA_KEY"] = key_path

except:
client.delete_thing(thingName = thing_name)
print("[Device Advisor]Error: Failed to create certificate.")
exit(-1)

##############################################
# attach certification to thing
try:
print("[Device Advisor]Info: Attach certificate to test thing...")
# attache the certificate to thing
client.attach_thing_principal(
thingName = thing_name,
principal = create_cert_response['certificateArn']
)

certificate_arn = create_cert_response['certificateArn']
certificate_id = create_cert_response['certificateId']

except:
delete_thing_with_certi(thing_name, certificate_id ,certificate_arn )
print("[Device Advisor]Error: Failed to attach certificate.")
exit(-1)


try:
######################################
# set default shadow, for shadow update, if the
# shadow does not exists, update will fail
payload_shadow = json.dumps(
{
"state": {
"desired": {
shadowProperty: shadowDefault
},
"reported": {
shadowProperty: shadowDefault
}
}
})
shadow_response = dataClient.update_thing_shadow(
thingName = thing_name,
payload = payload_shadow)
get_shadow_response = dataClient.get_thing_shadow(thingName = thing_name)
# make sure shadow is created before we go to next step
while(get_shadow_response is None):
get_shadow_response = dataClient.get_thing_shadow(thingName = thing_name)

# start device advisor test
# test_start_response
# {
# 'suiteRunId': 'string',
# 'suiteRunArn': 'string',
# 'createdAt': datetime(2015, 1, 1)
# }
print("[Device Advisor]Info: Start device advisor test: " + test_name)
test_start_response = deviceAdvisor.start_suite_run(
suiteDefinitionId=DATestConfig['test_suite_ids'][test_name],
suiteRunConfiguration={
'primaryDevice': {
'thingArn': create_thing_response['thingArn'],
},
'parallelRun': True
})

# get DA endpoint
endpoint_response = deviceAdvisor.get_endpoint(
thingArn = create_thing_response['thingArn']
)
os.environ['DA_ENDPOINT'] = endpoint_response['endpoint']

while True:
# sleep for 1s every loop to avoid TooManyRequestsException
sleep(1)
test_result_responds = deviceAdvisor.get_suite_run(
suiteDefinitionId=DATestConfig['test_suite_ids'][test_name],
suiteRunId=test_start_response['suiteRunId']
)
# If the status is PENDING or the responds does not loaded, the test suite is still loading
if (test_result_responds['status'] == 'PENDING' or
len(test_result_responds['testResult']['groups']) == 0 or # test group has not been loaded
len(test_result_responds['testResult']['groups'][0]['tests']) == 0 or #test case has not been loaded
test_result_responds['testResult']['groups'][0]['tests'][0]['status'] == 'PENDING'):
continue

# Start to run the test sample after the status turns into RUNNING
elif (test_result_responds['status'] == 'RUNNING' and
test_result_responds['testResult']['groups'][0]['tests'][0]['status'] == 'RUNNING'):
exe_path = os.path.join("deviceadvisor/tests/",DATestConfig['test_exe_path'][test_name])
result = subprocess.run('python3 ' + exe_path, timeout = 60*2, shell = True)
# If the test finalizing then store the test result
elif (test_result_responds['status'] != 'RUNNING'):
test_result[test_name] = test_result_responds['status']
if(test_result[test_name] == "PASS"):
delete_thing_with_certi(thing_name, certificate_id ,certificate_arn )
break
except Exception as e:
print("[Device Advisor]Error: Failed to test: "+ test_name + e)
exit(-1)

##############################################
# print result and cleanup things
print(test_result)
failed = False
for test in test_result:
if(test_result[test] != "PASS" and
test_result[test] != "PASS_WITH_WARNINGS"):
print("[Device Advisor]Error: Test \"" + test + "\" Failed with status:" + test_result[test])
failed = True
if failed:
# if the test failed, we dont clean the Thing so that we can track the error
exit(-1)

exit(0)
4 changes: 3 additions & 1 deletion deviceadvisor/tests/mqtt_connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
endpoint=DATestUtils.endpoint,
cert_filepath=DATestUtils.certificatePath,
pri_key_filepath=DATestUtils.keyPath,
client_id = DATestUtils.client_id)
client_id = DATestUtils.client_id,
clean_session = True,
ping_timeout_ms = 6000)

connect_future = mqtt_connection.connect()

Expand Down
4 changes: 3 additions & 1 deletion deviceadvisor/tests/mqtt_publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
endpoint=DATestUtils.endpoint,
cert_filepath=DATestUtils.certificatePath,
pri_key_filepath=DATestUtils.keyPath,
client_id=DATestUtils.client_id)
client_id=DATestUtils.client_id,
clean_session = True,
ping_timeout_ms = 6000)
connect_future = mqtt_connection.connect()

# Future.result() waits until a result is available
Expand Down
9 changes: 5 additions & 4 deletions deviceadvisor/tests/mqtt_subscribe.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
endpoint=DATestUtils.endpoint,
cert_filepath=DATestUtils.certificatePath,
pri_key_filepath=DATestUtils.keyPath,
client_id = DATestUtils.client_id)
client_id = DATestUtils.client_id,
clean_session = True,
ping_timeout_ms = 6000)

connect_future = mqtt_connection.connect()

Expand All @@ -25,10 +27,9 @@
# Subscribe
subscribe_future, packet_id = mqtt_connection.subscribe(
topic=DATestUtils.topic,
qos=mqtt.QoS.AT_LEAST_ONCE)

qos=mqtt.QoS.AT_MOST_ONCE)
subscribe_future.result()

# Disconnect
disconnect_future = mqtt_connection.disconnect()
disconnect_future.result()
Expand Down
4 changes: 3 additions & 1 deletion deviceadvisor/tests/shadow_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
endpoint=DATestUtils.endpoint,
cert_filepath=DATestUtils.certificatePath,
pri_key_filepath=DATestUtils.keyPath,
client_id = DATestUtils.client_id)
client_id = DATestUtils.client_id,
clean_session = True,
ping_timeout_ms = 6000)

connect_future = mqtt_connection.connect()
connect_future.result()
Expand Down