@@ -8,10 +8,14 @@ import { definitions, LwM2MObjectID } from '@hello.nrfcloud.com/proto-map/lwm2m'
8
8
import { FOTAJobStatus } from '@hello.nrfcloud.com/proto/hello'
9
9
import {
10
10
Duration ,
11
+ aws_dynamodb as DynamoDB ,
12
+ aws_events as Events ,
11
13
aws_lambda_event_sources as EventSources ,
14
+ aws_events_targets as EventsTargets ,
12
15
aws_iam as IAM ,
13
16
aws_iot as IoT ,
14
17
aws_lambda as Lambda ,
18
+ Stack ,
15
19
aws_stepfunctions_tasks as StepFunctionsTasks ,
16
20
type aws_logs as Logs ,
17
21
} from 'aws-cdk-lib'
@@ -27,7 +31,6 @@ import {
27
31
StateMachineType ,
28
32
Succeed ,
29
33
TaskInput ,
30
- type IStateMachine ,
31
34
} from 'aws-cdk-lib/aws-stepfunctions'
32
35
import {
33
36
DynamoAttributeValue ,
@@ -44,7 +47,7 @@ import type { DeviceStorage } from '../DeviceStorage.js'
44
47
* save the amount of data that needs to be transferred.
45
48
*/
46
49
export class MultiBundleFOTAFlow extends Construct {
47
- public readonly stateMachine : IStateMachine
50
+ public readonly stateMachine : StateMachine
48
51
public readonly GetDeviceFirmwareDetails : PackedLambdaFn
49
52
public readonly GetNextBundle : PackedLambdaFn
50
53
public readonly CreateFOTAJob : PackedLambdaFn
@@ -53,6 +56,7 @@ export class MultiBundleFOTAFlow extends Construct {
53
56
public readonly WaitForUpdateAppliedCallback : PackedLambdaFn
54
57
public readonly WaitForUpdateApplied : PackedLambdaFn
55
58
public readonly startMultiBundleFOTAFlow : PackedLambdaFn
59
+ public readonly abortMultiBundleFOTAFlow : PackedLambdaFn
56
60
57
61
public constructor (
58
62
parent : Construct ,
@@ -205,6 +209,9 @@ export class MultiBundleFOTAFlow extends Construct {
205
209
nextUpdateAt : DynamoAttributeValue . fromString (
206
210
JsonPath . stateEnteredTime ,
207
211
) ,
212
+ parentJobId : DynamoAttributeValue . fromString (
213
+ JsonPath . executionName ,
214
+ ) ,
208
215
} ,
209
216
resultPath : '$.DynamoDB' ,
210
217
} ) ,
@@ -437,7 +444,7 @@ export class MultiBundleFOTAFlow extends Construct {
437
444
} )
438
445
deviceFOTA . nrfCloudJobStatusTable . grantReadWriteData ( this . stateMachine )
439
446
440
- const startMultiBundleFOTAFlow = new PackedLambdaFn (
447
+ this . startMultiBundleFOTAFlow = new PackedLambdaFn (
441
448
this ,
442
449
'startMultiBundleFOTAFlow' ,
443
450
lambdas . start ,
@@ -459,10 +466,9 @@ export class MultiBundleFOTAFlow extends Construct {
459
466
logGroup : deviceFOTA . logGroup ,
460
467
} ,
461
468
)
462
- this . startMultiBundleFOTAFlow = startMultiBundleFOTAFlow
463
- this . stateMachine . grantStartExecution ( startMultiBundleFOTAFlow . fn )
464
- deviceStorage . devicesTable . grantReadData ( startMultiBundleFOTAFlow . fn )
465
- deviceFOTA . jobTable . grantWriteData ( startMultiBundleFOTAFlow . fn )
469
+ this . stateMachine . grantStartExecution ( this . startMultiBundleFOTAFlow . fn )
470
+ deviceStorage . devicesTable . grantReadData ( this . startMultiBundleFOTAFlow . fn )
471
+ deviceFOTA . jobTable . grantWriteData ( this . startMultiBundleFOTAFlow . fn )
466
472
467
473
this . WaitForFOTAJobCompletion = new PackedLambdaFn (
468
474
this ,
@@ -556,6 +562,109 @@ export class MultiBundleFOTAFlow extends Construct {
556
562
principal : new IAM . ServicePrincipal ( 'iot.amazonaws.com' ) ,
557
563
sourceArn : waitForFirmwareVersionReportRule . attrArn ,
558
564
} )
565
+
566
+ // Users can abort the FOTA flow
567
+ this . abortMultiBundleFOTAFlow = new PackedLambdaFn (
568
+ this ,
569
+ 'abortMultiBundleFOTAFlow' ,
570
+ lambdas . abort ,
571
+ {
572
+ description : 'REST entry point for aborting running FOTA flows' ,
573
+ environment : {
574
+ DEVICES_TABLE_NAME : deviceStorage . devicesTable . tableName ,
575
+ STATE_MACHINE_ARN : this . stateMachine . stateMachineArn ,
576
+ } ,
577
+ layers,
578
+ logGroup : deviceFOTA . logGroup ,
579
+ initialPolicy : [
580
+ new IAM . PolicyStatement ( {
581
+ actions : [ 'states:DescribeExecution' , 'states:StopExecution' ] ,
582
+ resources : [
583
+ `arn:aws:states:${ Stack . of ( this ) . region } :${ Stack . of ( this ) . account } :execution:${ this . stateMachine . stateMachineName } :*` ,
584
+ ] ,
585
+ } ) ,
586
+ ] ,
587
+ } ,
588
+ )
589
+ deviceStorage . devicesTable . grantReadData ( this . abortMultiBundleFOTAFlow . fn )
590
+
591
+ // Handles failed or cancelled step function executions
592
+ const onStepFunctionFail = new PackedLambdaFn (
593
+ this ,
594
+ 'onFail' ,
595
+ lambdas . onFail ,
596
+ {
597
+ description : 'Handles failed or cancelled step function executions' ,
598
+ environment : {
599
+ STATE_MACHINE_ARN : this . stateMachine . stateMachineArn ,
600
+ JOB_TABLE_NAME : deviceFOTA . jobTable . tableName ,
601
+ } ,
602
+ layers,
603
+ logGroup : deviceFOTA . logGroup ,
604
+ } ,
605
+ )
606
+ deviceFOTA . jobTable . grantReadWriteData ( onStepFunctionFail . fn )
607
+ new Events . Rule ( this , 'onStepFunctionFailRule' , {
608
+ eventPattern : {
609
+ source : [ 'aws.states' ] ,
610
+ detail : {
611
+ status : [ 'FAILED' , 'TIMED_OUT' , 'ABORTED' ] ,
612
+ stateMachineArn : [ this . stateMachine . stateMachineArn ] ,
613
+ } ,
614
+ } ,
615
+ targets : [ new EventsTargets . LambdaFunction ( onStepFunctionFail . fn ) ] ,
616
+ } )
617
+
618
+ // Cancel the FOTA job on nRF Cloud if the step function is cancelled
619
+ const parentJobIdIndexName = 'parentJobIdIndex'
620
+ deviceFOTA . nrfCloudJobStatusTable . addGlobalSecondaryIndex ( {
621
+ indexName : parentJobIdIndexName ,
622
+ partitionKey : {
623
+ name : 'parentJobId' ,
624
+ type : DynamoDB . AttributeType . STRING ,
625
+ } ,
626
+ sortKey : {
627
+ name : 'jobId' ,
628
+ type : DynamoDB . AttributeType . STRING ,
629
+ } ,
630
+ projectionType : DynamoDB . ProjectionType . INCLUDE ,
631
+ nonKeyAttributes : [ 'status' ] ,
632
+ } )
633
+ const cancelFOTAJob = new PackedLambdaFn (
634
+ this ,
635
+ 'cancelFOTAJob' ,
636
+ lambdas . cancelFOTAJob ,
637
+ {
638
+ description :
639
+ 'Cancel the FOTA job on nRF Cloud if the step function is cancelled' ,
640
+ layers,
641
+ timeout : Duration . minutes ( 1 ) ,
642
+ logGroup : deviceFOTA . logGroup ,
643
+ environment : {
644
+ NRF_CLOUD_JOB_STATUS_TABLE_NAME :
645
+ deviceFOTA . nrfCloudJobStatusTable . tableName ,
646
+ NRF_CLOUD_JOB_STATUS_TABLE_PARENT_JOB_ID_INDEX_NAME :
647
+ parentJobIdIndexName ,
648
+ } ,
649
+ } ,
650
+ )
651
+ deviceFOTA . nrfCloudJobStatusTable . grantReadData ( cancelFOTAJob . fn )
652
+ cancelFOTAJob . fn . addEventSource (
653
+ new EventSources . DynamoEventSource ( deviceFOTA . jobTable , {
654
+ startingPosition : Lambda . StartingPosition . LATEST ,
655
+ filters : [
656
+ Lambda . FilterCriteria . filter ( {
657
+ dynamodb : {
658
+ NewImage : {
659
+ status : {
660
+ S : [ FOTAJobStatus . FAILED ] ,
661
+ } ,
662
+ } ,
663
+ } ,
664
+ } ) ,
665
+ ] ,
666
+ } ) ,
667
+ )
559
668
}
560
669
}
561
670
0 commit comments