Skip to content

Device Defender custom metrics support #401

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 68 commits into from
May 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
7a05f97
Initial work on custom metrics support in Device Defender API
TwistedTwigleg Mar 10, 2022
a4e5888
Added function to use std function instead of directly passing c styl…
TwistedTwigleg Mar 10, 2022
1f19409
Adjusted RegisterCustomMetricNumber function so it compiles successfu…
TwistedTwigleg Mar 10, 2022
034ac9f
Added beginning of device defender sample
TwistedTwigleg Mar 11, 2022
cea883e
Cleaned up DeviceDefender sample
TwistedTwigleg Mar 14, 2022
7feb723
Adjusted device defender sample and added it to README
TwistedTwigleg Mar 14, 2022
71b932c
Added remaining data types to DeviceDefender sample, adjusted README
TwistedTwigleg Mar 14, 2022
efacc65
Fixed clang-format issues
TwistedTwigleg Mar 15, 2022
7e6716c
Added predefined custom metrics for CPU usage, memory usage, and proc…
TwistedTwigleg Mar 15, 2022
6b56070
Clang-format fixes
TwistedTwigleg Mar 15, 2022
e5b58a1
Added condition to check if cpu usage retrieval was succcessful
TwistedTwigleg Mar 15, 2022
f6d21bc
Removed warnings over unused variables in Device Defender
TwistedTwigleg Mar 15, 2022
b5d6168
minor clang format fix
TwistedTwigleg Mar 15, 2022
864a948
Merge branch 'main' of https://github.com/aws/aws-iot-device-sdk-cpp-…
TwistedTwigleg Mar 15, 2022
12953dc
Modified Device Defender sample to use command line module
TwistedTwigleg Mar 15, 2022
e4e9638
Minor clang-format fix
TwistedTwigleg Mar 15, 2022
e7514df
Added double number support, fixed improper use of aws_array_list all…
TwistedTwigleg Mar 15, 2022
8bf8f9d
Merge branch 'DeviceDefenderCustomMetricsSupport' of https://github.c…
TwistedTwigleg Mar 15, 2022
b084681
Merge branch 'main' of https://github.com/aws/aws-iot-device-sdk-cpp-…
TwistedTwigleg Mar 15, 2022
2dc1bd8
Clang-format fixes
TwistedTwigleg Mar 15, 2022
914d92d
Adjusted Device Defender code after feedback changes in aws-c-iot to …
TwistedTwigleg Mar 17, 2022
08137ea
Part one of adding support for std vector and making c++ to c layer f…
TwistedTwigleg Mar 17, 2022
708f5e1
Merged with latest version of master (forgot to pull prior to startin…
TwistedTwigleg Mar 17, 2022
031f1c7
Merge branch 'DeviceDefenderCustomMetricsSupport' of https://github.c…
TwistedTwigleg Mar 17, 2022
078cc2a
Fixed segmentation fault issue. Was passing pointer to aws_string rat…
TwistedTwigleg Mar 18, 2022
0fcfaa5
Fixed segmentation fault with tests. Now just need to fix segmentatio…
TwistedTwigleg Mar 18, 2022
d29a4d8
Fixed issue causing crashes with Device Defender sample
TwistedTwigleg Mar 18, 2022
1326352
Clang-format fixes
TwistedTwigleg Mar 18, 2022
59609c8
Removed accidental vscode folder
TwistedTwigleg Mar 18, 2022
0517b7d
Fixed submodules being incorrect
TwistedTwigleg Mar 18, 2022
ca38d20
Adjusted code based on review: changed types, simplified code, and mo…
TwistedTwigleg Mar 24, 2022
5be978a
Formatted code with clang-format
TwistedTwigleg Mar 24, 2022
cd43529
Initial working version of a more C++ like way to handle custom metri…
TwistedTwigleg Mar 24, 2022
fba5bd0
Cleaned up code and removed commented out old code
TwistedTwigleg Mar 24, 2022
9f2330a
Applied Clang-Format
TwistedTwigleg Mar 24, 2022
8028b7a
Added initial CI. Needs to be tested to make sure Python file works f…
TwistedTwigleg Mar 25, 2022
84d4bab
Added CI tests and adjusted README to show working policy for DeviceD…
TwistedTwigleg Mar 25, 2022
3a14705
Adjusted code based on code review
TwistedTwigleg Mar 28, 2022
4ab1d9b
Fixed memory leak with shared pointers
TwistedTwigleg Mar 29, 2022
dfb3661
Merge branch 'main' of https://github.com/aws/aws-iot-device-sdk-cpp-…
TwistedTwigleg Mar 29, 2022
559267c
Clang-format fixes
TwistedTwigleg Mar 29, 2022
993a0b2
Merge branch 'main' into DeviceDefenderCustomMetricsSupport
TwistedTwigleg Apr 4, 2022
f25bfe3
Adjusted how shared pointers are made so they use Crt::MakeShared
TwistedTwigleg Apr 11, 2022
0bfb4f2
Merge branch 'main' of https://github.com/aws/aws-iot-device-sdk-cpp-…
TwistedTwigleg Apr 11, 2022
9885018
Merge branch 'DeviceDefenderCustomMetricsSupport' of https://github.c…
TwistedTwigleg Apr 11, 2022
1e202c1
Modified custom metric reporting to use functions in aws-c-common to …
TwistedTwigleg Apr 11, 2022
1f3d277
Modified function call to get system metrics
TwistedTwigleg Apr 11, 2022
5a415c3
Adjusted to fit latest changes to aws-c-common
TwistedTwigleg Apr 12, 2022
27a122d
Clang-format fixes
TwistedTwigleg Apr 12, 2022
2b1db37
Super minor clang format change I missed...
TwistedTwigleg Apr 12, 2022
c907d3e
Adjusted to use int64_t for process and memory count
TwistedTwigleg Apr 13, 2022
ad0671c
WIP change to using C sampler for CPU usage
TwistedTwigleg Apr 13, 2022
b56cd94
Adjusted to fit design in aws-c-common for CPU polling
TwistedTwigleg Apr 14, 2022
7bedccf
Fixed clang-format issues
TwistedTwigleg Apr 14, 2022
80f4399
Adjusted to use latest changes in aws-c-common PR
TwistedTwigleg Apr 14, 2022
ec8a734
Merge branch 'main' of https://github.com/aws/aws-iot-device-sdk-cpp-…
TwistedTwigleg Apr 20, 2022
8dcc8ea
Updated to work with latest changes in other DeviceDefender PRs
TwistedTwigleg Apr 21, 2022
90b1d3a
Removed CPU, process, and memory usage custom metric functions from c…
TwistedTwigleg Apr 28, 2022
03a09ad
Merge branch 'main' of https://github.com/aws/aws-iot-device-sdk-cpp-…
TwistedTwigleg May 3, 2022
661e962
Adjusted builder to make its own policy dynamically
TwistedTwigleg May 3, 2022
3f05ada
Generate policy dynamically, general fixes to DeviceDefender codebuil…
TwistedTwigleg May 3, 2022
456e3ab
Use latest main version of aws-c-iot - will need to make release for …
TwistedTwigleg May 3, 2022
8c95f09
Use main branch of crt
TwistedTwigleg May 4, 2022
d932dd4
Bump to rerun CI
TwistedTwigleg May 4, 2022
4ceeec7
Bump two
TwistedTwigleg May 4, 2022
4236581
Update to use latest CRT release
TwistedTwigleg May 5, 2022
8569979
Remove unused import in device defender header, use simplified MQTT c…
TwistedTwigleg May 5, 2022
22465c4
Code review changes. Adjusted definition insulation and adjusted cust…
TwistedTwigleg May 5, 2022
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
17 changes: 17 additions & 0 deletions .builder/actions/build_samples.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ def run(self, env):
'samples/secure_tunneling/secure_tunnel',
'samples/secure_tunneling/tunnel_notification',
]

defender_samples = []
# Linux only builds
if sys.platform == "linux" or sys.platform == "linux2":
defender_samples.append('samples/device_defender/basic_report')

da_samples = [
'deviceadvisor/tests/mqtt_connect',
'deviceadvisor/tests/mqtt_publish',
Expand Down Expand Up @@ -60,4 +66,15 @@ def run(self, env):
'--build', build_path,
'--config', 'RelWithDebInfo'])

for sample_path in defender_samples:
build_path = os.path.join('build', sample_path)
steps.append(['cmake',
f'-B{build_path}',
f'-H{sample_path}',
f'-DCMAKE_PREFIX_PATH={env.install_dir}',
'-DCMAKE_BUILD_TYPE=RelWithDebInfo'])
steps.append(['cmake',
'--build', build_path,
'--config', 'RelWithDebInfo'])

return Builder.Script(steps)
3 changes: 2 additions & 1 deletion builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
],
"test_steps": [
"python3 -m pip install boto3",
"python3 deviceadvisor/script/DATestRun.py"],
"python3 deviceadvisor/script/DATestRun.py",
"python3 devicedefender/script/DDTestRun.py"],
"variants" : {
"skip_sample": {
"!test_steps": [],
Expand Down
62 changes: 62 additions & 0 deletions devicedefender/include/aws/iotdevicedefender/DeviceDefender.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ namespace Aws

using ReportFormat = aws_iotdevice_defender_report_format;

using CustomMetricNumberFunction = std::function<int(double *)>;
using CustomMetricNumberListFunction = std::function<int(Crt::Vector<double> *)>;
using CustomMetricStringListFunction = std::function<int(Crt::Vector<Crt::String> *)>;
using CustomMetricIpListFunction = std::function<int(Crt::Vector<Crt::String> *)>;

/**
* A base class used to store all custom metrics in the same container. Only used internally.
*/
class AWS_IOTDEVICEDEFENDER_API CustomMetricBase;

/**
* Enum used to expose the status of a DeviceDefenderV1 task.
*/
Expand Down Expand Up @@ -77,6 +87,54 @@ namespace Aws
*/
int LastError() const noexcept { return m_lastError; }

/**
* Registers a custom metric number function to the Device Defender result. Will call the "metricFunc"
* function that is passed in each time a report is generated so it's data can be passed along with the
* other device defender payload data with the metric name of "metricName".
* @param metricName The key name for the data.
* @param metricFunc The function that is called to get the number data.
*/
void RegisterCustomMetricNumber(
const Crt::String metricName,
CustomMetricNumberFunction metricFunc) noexcept;

/**
* Registers a custom metric number list function to the Device Defender result. Will call the "metricFunc"
* function that is passed in each time a report is generated so it's data can be passed along with the
* other device defender payload data with the metric name of "metricName".
*
* @param metricName The key name for the data.
* @param metricFunc The function that is called to get the number list data.
*/
void RegisterCustomMetricNumberList(
const Crt::String metricName,
CustomMetricNumberListFunction metricFunc) noexcept;

/**
* Registers a custom metric string list function to the Device Defender result. Will call the "metricFunc"
* function that is passed in each time a report is generated so it's data can be passed along with the
* other device defender payload data with the metric name of "metricName".
*
* Only valid IP addresses will show up in the Device Defender metrics even if it sends correctly.
* @param metricName The key name for the data.
* @param metricFunc The function that is called to get the string list data.
*/
void RegisterCustomMetricStringList(
const Crt::String metricName,
CustomMetricStringListFunction metricFunc) noexcept;

/**
* Registers a custom metric IP address list function to the Device Defender result. Will call the
* "metricFunc" function that is passed in each time a report is generated so it's data can be passed along
* with the other device defender payload data with the metric name of "metricName".
*
* @param metricName The key name for the data.
* @param metricFunc The function that is called to get the IP address list data.
*/
void RegisterCustomMetricIpAddressList(
const Crt::String metricName,
CustomMetricIpListFunction metricFunc) noexcept;

private:
Crt::Allocator *m_allocator;
ReportTaskStatus m_status;
Expand All @@ -98,6 +156,10 @@ namespace Aws
void *cancellationUserdata = nullptr) noexcept;

static void s_onDefenderV1TaskCancelled(void *userData);

// Holds all of the custom metrics created for this task. These are pointers that will be
// automatically created and cleaned by ReportTask when it is destroyed.
Crt::Vector<std::shared_ptr<CustomMetricBase>> storedCustomMetrics;
};

/**
Expand Down
259 changes: 259 additions & 0 deletions devicedefender/script/DDTestRun.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@

import boto3
import uuid
import os
import subprocess
import platform
from time import sleep

# On something other than Linux? Pass the test instantly since Device Defender is only supported on Linux
if platform.system() != "Linux":
print("[Device Defender]Info: Skipped Test - " + platform.system() +
" not supported (Only Linux supported currently)")
exit(0)

##############################################
# 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)

print("[Device Defender]Info: Deleted thing with name: " + thingName)


##############################################
# Initialize variables
# create aws client
client = boto3.client('iot', region_name='us-east-1')
# 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')
# Other variables
metrics_added = []
thing_arn = None
client_made_thing = False
client_made_policy = False

##############################################
# create a test thing
thing_name = "DDTest_" + str(uuid.uuid4())
try:
# create_thing_response:
# {
# 'thingName': 'string',
# 'thingArn': 'string',
# 'thingId': 'string'
# }
print("[Device Defender]Info: Started to create thing...")
create_thing_response = client.create_thing(
thingName=thing_name
)
#print(create_thing_response)
thing_arn = create_thing_response["thingArn"]
client_made_thing = True

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

##############################################
# create certificate and keys used for testing
try:
print("[Device Defender]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()

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

##############################################
# Create policy
try:
print("[Device Defender]Info: Started to create policy...")
# {
# 'policyName': 'string',
# 'policyArn': 'string',
# 'policyDocument': 'string',
# 'policyVersionId': 'string'
# }

# We only need a short section of the thing arn
thing_arn_split = thing_arn.split(":")
thing_arn_short = thing_arn_split[0] + ':' + thing_arn_split[1] + ':' + thing_arn_split[2] + ':' + thing_arn_split[3] + ":" + thing_arn_split[4]
policy_document_json = (
'{'
'"Version": "2012-10-17",'
'"Statement": ['
'{'
'"Effect": "Allow",'
'"Action": ['
'"iot:Publish",'
'"iot:Subscribe",'
'"iot:RetainPublish"'
'],'
f'"Resource": "{thing_arn_short}:*/$aws/things/*/defender/metrics/*"'
'},'
'{'
'"Effect": "Allow",'
'"Action": "iot:Connect",'
f'"Resource": "{thing_arn_short}:client/*"'
'}'
']'
'}'
)
create_policy_response = client.create_policy(
policyName=thing_name + "_policy",
policyDocument=policy_document_json
)
client_made_policy = True
except Exception as e:
if client_made_thing:
client.delete_thing(thingName=thing_name)
if client_made_policy:
client.delete_policy(policyName=thing_name + "_policy")
print("[Device Defender]Error: Failed to create policy.")
exit(-1)

##############################################

##############################################
# attach certification to thing
try:
print("[Device Defender]Info: Attach policy to certificate...")
# attach policy to thing
client.attach_policy(
policyName=thing_name + "_policy",
target=create_cert_response["certificateArn"]
)

print("[Device Defender]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:
if certificate_id:
delete_thing_with_certi(thing_name, certificate_id, certificate_arn)
else:
client.delete_thing(thingName=thing_name)

if client_made_policy:
client.delete_policy(policyName=thing_name + "_policy")

print("[Device Defender]Error: Failed to attach certificate.")
exit(-1)

##############################################
# Run device defender
try:

# Get the Device Defender endpoint
endpoint_response = client.describe_endpoint(
endpointType='iot:Data-ATS')["endpointAddress"]

print("[Device Defender]Info: Adding custom metrics...")
metrics_to_add = [
{"name": "CustonNumber", "display_name": "DD Custom Number", "type": "number"},
{"name": "CustomNumberTwo",
"display_name": "DD Custom Number 2", "type": "number"},
{"name": "CustomNumberList",
"display_name": "DD Custom Number List", "type": "number-list"},
{"name": "CustomStringList",
"display_name": "DD Custom String List", "type": "string-list"},
{"name": "CustomIPList", "display_name": "DD Custom IP List",
"type": "ip-address-list"},
{"name": "cpu_usage", "display_name": "DD Cpu Usage", "type": "number"},
{"name": "memory_usage", "display_name": "DD Memory Usage", "type": "number"},
{"name": "process_count", "display_name": "DD Process count", "type": "number"}
]
for current_metric in metrics_to_add:
try:
client.create_custom_metric(
metricName=current_metric["name"],
displayName=current_metric["display_name"],
metricType=current_metric["type"],
tags=[]
)
metrics_added.append(current_metric["name"])
print("[Device Defender]Info: Metric with name: " +
current_metric["name"] + " added.")
except:
print("[Device Defender]Info: Metric with name: " + current_metric["name"] +
" already present. Skipping and will not delete...")
continue

print("[Device Defender]Info: Running sample (this should take ~60 seconds).")
# Run the sample:
exe_path = "build/samples/device_defender/basic_report/"
# If running locally, comment out the line above and uncomment the line below:
#exe_path = "samples/device_defender/basic_report/build/"

# Windows has a different build folder structure, but this ONLY runs on Linux currently so we do not need to worry about it
exe_path = os.path.join(exe_path, "basic-report")
print("[Device Defender]Info: Start to run: " + exe_path)
# The Device Defender sample will take ~1 minute to run even if successful
# (since samples are sent every minute)
arguments = [exe_path, "--endpoint", endpoint_response, "--cert",
certificate_path, "--key", key_path, "--thing_name", thing_name, "--count", "2"]
result = subprocess.run(arguments, timeout=60*2, check=True)
print("[Device Defender]Info: Sample finished running.")

# There does not appear to be any way to get the metrics from the device - so we'll assume that if it didn't return -1, then it worked

# delete custom metrics we added
for metric_name in metrics_added:
client.delete_custom_metric(metricName=metric_name)

# Delete
delete_thing_with_certi(thing_name, certificate_id, certificate_arn)
client.delete_policy(policyName=thing_name + "_policy")

except Exception as e:
# delete custom metrics we added
for metric_name in metrics_added:
client.delete_custom_metric(metricName=metric_name)

if client_made_thing:
delete_thing_with_certi(thing_name, certificate_id, certificate_arn)
if client_made_policy:
client.delete_policy(policyName=thing_name + "_policy")

print("[Device Defender]Error: Failed to test: Basic Report")
exit(-1)

print("[Device Defender]Info: Basic Report sample test passed")
exit(0)
Loading