diff --git a/samples/README.md b/samples/README.md index f9457ac56..c70c7236e 100644 --- a/samples/README.md +++ b/samples/README.md @@ -14,7 +14,7 @@ * [Secure Tunnel](./secure_tunneling/secure_tunnel/README.md) * [Secure Tunnel Notification](./secure_tunneling/tunnel_notification/README.md) * [Shadow](./shadow/shadow_sync/README.md) -* [Jobs](./jobs/describe_job_execution/README.md) +* [Jobs](./jobsjob_execution/README.md) * [Fleet provisioning](./fleet_provisioning/fleet_provisioning/README.md) * [Greengrass discovery](./greengrass/basic_discovery/README.md) * [Greengrass IPC](./greengrass/ipc/README.md) diff --git a/samples/jobs/job_execution/README.md b/samples/jobs/job_execution/README.md index 9b32156c8..94c093174 100644 --- a/samples/jobs/job_execution/README.md +++ b/samples/jobs/job_execution/README.md @@ -2,12 +2,12 @@ [**Return to main sample list**](../../README.md) -This sample uses the AWS IoT [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) Service to describe jobs to execute. [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) is a service that allows you to define and respond to remote operation requests defined through the AWS IoT Core website or via any other device (or CLI command) that can access the [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) service. +This sample uses the AWS IoT [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) Service to describe jobs, it also executes them. [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) is a service that allows you to define and respond to remote operation requests defined through the AWS IoT Core website or via any other device (or CLI command) that can access the [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) service. Note: This sample requires you to create jobs for your device to execute. See [instructions here](https://docs.aws.amazon.com/iot/latest/developerguide/create-manage-jobs.html) for how to make jobs. -On startup, the sample describes the jobs that are pending execution. +On startup, the sample describes the jobs that are pending execution, it then executes them for you. Your IoT Core Thing's [Policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) must provide privileges for this sample to connect, subscribe, publish, and receive. Below is a sample policy that can be used on your IoT Core Thing that will allow this sample to run as intended. @@ -72,11 +72,11 @@ Note that in a real application, you may want to avoid the use of wildcards in y Use the following command to run the Jobs sample: ``` sh -./describe-job-execution --endpoint --cert --key --thing_name --job_id +./job-execution --endpoint --cert --key --thing_name --job_id ``` You can also pass a Certificate Authority file (CA) if your certificate and key combination requires it: ``` sh -./describe-job-execution --endpoint --cert --key --thing_name --job_id --ca_file +./job-execution --endpoint --cert --key --thing_name --job_id --ca_file ``` diff --git a/samples/jobs/mqtt5_job_execution/README.md b/samples/jobs/mqtt5_job_execution/README.md index e97c806d6..6c312729a 100644 --- a/samples/jobs/mqtt5_job_execution/README.md +++ b/samples/jobs/mqtt5_job_execution/README.md @@ -2,12 +2,12 @@ [**Return to main sample list**](../../README.md) -This sample uses the AWS IoT [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) Service to describe jobs to execute. [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) is a service that allows you to define and respond to remote operation requests defined through the AWS IoT Core website or via any other device (or CLI command) that can access the [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) service. +This sample uses the AWS IoT [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) Service to describe jobs to execute, it them executes them. [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) is a service that allows you to define and respond to remote operation requests defined through the AWS IoT Core website or via any other device (or CLI command) that can access the [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) service. Note: This sample requires you to create jobs for your device to execute. See [instructions here](https://docs.aws.amazon.com/iot/latest/developerguide/create-manage-jobs.html) for how to make jobs. -On startup, the sample describes the jobs that are pending execution. +On startup, the sample describes the jobs that are pending execution, and then it executes them. Your IoT Core Thing's [Policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) must provide privileges for this sample to connect, subscribe, publish, and receive. Below is a sample policy that can be used on your IoT Core Thing that will allow this sample to run as intended. @@ -72,13 +72,13 @@ Note that in a real application, you may want to avoid the use of wildcards in y Use the following command to run the Jobs sample: ``` sh -./mqtt5-describe-job-execution --endpoint --cert --key --thing_name --job_id +./mqtt5-job-execution --endpoint --cert --key --thing_name --job_id ``` You can also pass a Certificate Authority file (CA) if your certificate and key combination requires it: ``` sh -./mqtt5-describe-job-execution --endpoint --cert --key --thing_name --job_id --ca_file +./mqtt5-job-execution --endpoint --cert --key --thing_name --job_id --ca_file ``` ## Service Client Notes diff --git a/samples/jobs/mqtt5_job_execution/main.cpp b/samples/jobs/mqtt5_job_execution/main.cpp index f4d2e0483..6870d54f7 100644 --- a/samples/jobs/mqtt5_job_execution/main.cpp +++ b/samples/jobs/mqtt5_job_execution/main.cpp @@ -28,6 +28,14 @@ using namespace Aws::Crt; using namespace Aws::Iotjobs; +void updateJobExecution( + JobStatus status, + String thingName, + String currentJobId, + IotJobsClient &jobsClient, + int32_t ¤tVersionNumber, + int64_t ¤tExecutionNumber); + int main(int argc, char *argv[]) { /************************ Setup ****************************/ @@ -167,6 +175,174 @@ int main(int argc, char *argv[]) jobsClient.PublishDescribeJobExecution( std::move(describeJobExecutionRequest), AWS_MQTT_QOS_AT_LEAST_ONCE, publishHandler); publishDescribeJobExeCompletedPromise.get_future().wait(); + + if (cmdData.input_isCI == false) + { + Aws::Crt::String currentJobId; + int64_t currentExecutionNumber; + int32_t currentVersionNumber; + + std::promise pendingExecutionPromise; + + { + auto OnSubscribeToStartNextPendingJobExecutionAcceptedResponse = + [&](StartNextJobExecutionResponse *response, int ioErr) { + if (ioErr) + { + fprintf(stderr, "Error %d occurred\n", ioErr); + exit(-1); + } + if (response) + { + fprintf(stdout, "Start Job %s\n", response->Execution.value().JobId.value().c_str()); + currentJobId = response->Execution->JobId.value(); + currentExecutionNumber = response->Execution->ExecutionNumber.value(); + currentVersionNumber = response->Execution->VersionNumber.value(); + } + else + { + fprintf(stdout, "Could not get Job Id exiting\n"); + exit(-1); + } + pendingExecutionPromise.set_value(); + }; + + StartNextPendingJobExecutionSubscriptionRequest subscriptionRequest; + subscriptionRequest.ThingName = cmdData.input_thingName; + subAckedPromise = std::promise(); + jobsClient.SubscribeToStartNextPendingJobExecutionAccepted( + subscriptionRequest, + AWS_MQTT_QOS_AT_LEAST_ONCE, + OnSubscribeToStartNextPendingJobExecutionAcceptedResponse, + subAckHandler); + + subAckedPromise.get_future().wait(); + + subAckedPromise = std::promise(); + jobsClient.SubscribeToStartNextPendingJobExecutionRejected( + subscriptionRequest, AWS_MQTT_QOS_AT_LEAST_ONCE, failureHandler, subAckHandler); + + subAckedPromise.get_future().wait(); + + StartNextPendingJobExecutionRequest publishRequest; + publishRequest.ThingName = cmdData.input_thingName; + publishRequest.StepTimeoutInMinutes = 15L; + + publishDescribeJobExeCompletedPromise = std::promise(); + jobsClient.PublishStartNextPendingJobExecution( + publishRequest, AWS_MQTT_QOS_AT_LEAST_ONCE, publishHandler); + + pendingExecutionPromise.get_future().wait(); + } + + updateJobExecution( + JobStatus::IN_PROGRESS, + cmdData.input_thingName, + currentJobId, + jobsClient, + currentVersionNumber, + currentExecutionNumber); + // Pretend doing some work + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + updateJobExecution( + JobStatus::SUCCEEDED, + cmdData.input_thingName, + currentJobId, + jobsClient, + currentVersionNumber, + currentExecutionNumber); + } + } + + // Wait just a little bit to let the console print + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + // Disconnect + if (connection->Disconnect()) + { + connectionClosedPromise.get_future().wait(); + } + + return 0; +} + +void updateJobExecution( + JobStatus status, + String thingName, + String currentJobId, + IotJobsClient &jobsClient, + int32_t ¤tVersionNumber, + int64_t ¤tExecutionNumber) +{ + std::promise publishDescribeJobExeCompletedPromise; + std::promise pendingExecutionPromise = std::promise(); + std::promise subAckedPromise; + + UpdateJobExecutionSubscriptionRequest subscriptionRequest; + subscriptionRequest.ThingName = thingName; + subscriptionRequest.JobId = currentJobId; + + auto subAckHandler = [&](int) { + // if error code returns it will be recorded by the other callback + subAckedPromise.set_value(); + }; + auto failureHandler = [&](RejectedError *rejectedError, int ioErr) { + if (ioErr) + { + fprintf(stderr, "Error %d occurred\n", ioErr); + exit(-1); + } + if (rejectedError) + { + fprintf( + stderr, + "Service Error %d occurred. Message %s\n", + (int)rejectedError->Code.value(), + rejectedError->Message->c_str()); + return; + } + }; + auto subscribeHandler = [&](UpdateJobExecutionResponse *response, int ioErr) { + (void)response; + if (ioErr) + { + fprintf(stderr, "Error %d occurred\n", ioErr); + exit(-1); + } + fprintf(stdout, "Marked job %s currentJobId SUCCEEDED", currentJobId.c_str()); + pendingExecutionPromise.set_value(); + }; + + auto publishHandler = [&](int ioErr) { + if (ioErr) + { + fprintf(stderr, "Error %d occurred\n", ioErr); + exit(-1); + } + publishDescribeJobExeCompletedPromise.set_value(); + }; + subAckedPromise = std::promise(); + jobsClient.SubscribeToUpdateJobExecutionAccepted( + subscriptionRequest, AWS_MQTT_QOS_AT_LEAST_ONCE, subscribeHandler, subAckHandler); + subAckedPromise.get_future().wait(); + + subAckedPromise = std::promise(); + jobsClient.SubscribeToUpdateJobExecutionRejected( + subscriptionRequest, AWS_MQTT_QOS_AT_LEAST_ONCE, failureHandler, subAckHandler); + subAckedPromise.get_future().wait(); + + UpdateJobExecutionRequest publishRequest; + publishRequest.ThingName = thingName; + publishRequest.JobId = currentJobId; + publishRequest.ExecutionNumber = currentExecutionNumber; + publishRequest.Status = status; + publishRequest.ExpectedVersion = currentVersionNumber++; + + jobsClient.PublishUpdateJobExecution(publishRequest, AWS_MQTT_QOS_AT_LEAST_ONCE, publishHandler); + + pendingExecutionPromise.get_future().wait(); +} } // Wait just a little bit to let the console print diff --git a/samples/utils/CommandLineUtils.cpp b/samples/utils/CommandLineUtils.cpp index 72a7c7387..b828bbeb3 100644 --- a/samples/utils/CommandLineUtils.cpp +++ b/samples/utils/CommandLineUtils.cpp @@ -536,7 +536,7 @@ namespace Utils returnData.input_clientId = cmdUtils.GetCommandOrDefault(m_cmd_client_id, Aws::Crt::String("test-") + Aws::Crt::UUID().ToString()); returnData.input_thingName = cmdUtils.GetCommandRequired(m_cmd_thing_name); - returnData.input_jobId = cmdUtils.GetCommandRequired(m_cmd_job_id); + returnData.input_jobId = atoi(cmdUtils.GetCommandOrDefault(m_cmd_job_id, "1").c_str()); return returnData; } diff --git a/servicetests/test_cases/mqtt3_jobs_cfg.json b/servicetests/test_cases/mqtt3_jobs_cfg.json index 32e7960ff..6fd18779f 100644 --- a/servicetests/test_cases/mqtt3_jobs_cfg.json +++ b/servicetests/test_cases/mqtt3_jobs_cfg.json @@ -23,10 +23,6 @@ { "name": "--thing_name", "data": "ServiceTest_Jobs_$INPUT_UUID" - }, - { - "name": "--job_id", - "data": "1" } ] } diff --git a/servicetests/test_cases/mqtt5_jobs_cfg.json b/servicetests/test_cases/mqtt5_jobs_cfg.json index a042cd750..6243b0d50 100644 --- a/servicetests/test_cases/mqtt5_jobs_cfg.json +++ b/servicetests/test_cases/mqtt5_jobs_cfg.json @@ -23,10 +23,6 @@ { "name": "--thing_name", "data": "ServiceTest_Jobs_$INPUT_UUID" - }, - { - "name": "--job_id", - "data": "1" } ] } diff --git a/servicetests/test_cases/test_jobs_execution.py b/servicetests/test_cases/test_jobs_execution.py index 211c8b1b0..b49c0cee0 100644 --- a/servicetests/test_cases/test_jobs_execution.py +++ b/servicetests/test_cases/test_jobs_execution.py @@ -6,6 +6,7 @@ import os import sys import uuid +import time import boto3 @@ -56,17 +57,19 @@ def main(): thing_job = 'ERROR' i = 0; - while 'ERROR' in thing_job and i <= 3: + 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('thing job is {thing_job}'); + print(f'thing job is {thing_job}'); if 'ERROR' in thing_job: i = i + 1; else: break; except Exception as e: - print(f"ERROR: Could not verify Job execution: {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: