constructor()

in source/infrastructure/lib/simulator.ts [75:274]


    constructor(scope: Construct, id: string, props: SimulatorConstructProps) {
        super(scope, id);

        const sourceCodeBucket = props.solutionConfig.sourceCodeBucket;
        const sourceCodePrefix = props.solutionConfig.sourceCodePrefix;


        const simulatorLambdaRole = new Role(this, 'EngineLambdaRole', {
            assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
            path: '/',
            inlinePolicies: {
                'S3Policy': new PolicyDocument({
                    statements: [
                        new PolicyStatement({
                            effect: Effect.ALLOW,
                            actions: [
                                's3:GetObject'
                            ],
                            resources: [props.routesBucket.bucketArn]
                        }),
                    ]
                }),
                'DynamoDBPolicy': new PolicyDocument({
                    statements: [
                        new PolicyStatement({
                            effect: Effect.ALLOW,
                            actions: ['dynamodb:GetItem'],
                            resources: [props.simulationTable.tableArn]
                        }),
                    ]
                }),
                'IoTPolicy': new PolicyDocument({
                    statements: [
                        new PolicyStatement({
                            effect: Effect.ALLOW,
                            actions: [
                                'iot:Publish'
                            ],
                            resources: [Stack.of(this).formatArn({ service: 'iot', resource: 'topic', resourceName: '*', arnFormat: ArnFormat.SLASH_RESOURCE_NAME })]
                        })
                    ]
                })
            }
        });
        simulatorLambdaRole.attachInlinePolicy(props.cloudWatchLogsPolicy);
        this.simulatorLambdaFunction = new LambdaFunction(this, 'EngineLambda', {
            code: Code.fromBucket(sourceCodeBucket, `${sourceCodePrefix}/simulator.zip`),
            description: 'IoT Device Simulator function',
            environment: {
                IOT_ENDPOINT: props.iotEndpointAddress,
                SEND_ANONYMOUS_METRIC: props.solutionConfig.sendAnonymousUsage,
                SOLUTION_ID: props.solutionConfig.solutionId,
                VERSION: props.solutionConfig.solutionVersion,
                UUID: props.uuid,
                ROUTE_BUCKET: props.routesBucket.bucketName
            },
            handler: 'index.handler',
            runtime: Runtime.NODEJS_14_X,
            timeout: Duration.minutes(15),
            role: simulatorLambdaRole
        });
        this.simulatorLambdaFunction.addEnvironment('SIM_TABLE', props.simulationTable.tableName)
        props.routesBucket.grantRead(this.simulatorLambdaFunction.grantPrincipal);

        const getDeviceTypeMap = new SFMap(this, 'getDeviceTypeMap', {
            "itemsPath": "$.simulation.devices",
            "resultPath": "$.simulation.devices",
            "parameters": {
                "typeId.$": "$$.Map.Item.Value.typeId",
                "amount.$": "$$.Map.Item.Value.amount"
            },
            "maxConcurrency": 0,
        });

        const getDeviceTypeInfo = new DynamoGetItem(this, 'getDeviceTypeInfo', {
            "table": props.deviceTypeTable,
            "key": {
                "typeId": DynamoAttributeValue.fromString(JsonPath.stringAt('$.typeId'))
            },
            "resultSelector": {
                "name.$": "$.Item.name",
                "topic.$": "$.Item.topic",
                "payload.$": "$.Item.payload"
            },
            "resultPath": "$.info",
        })

        const simulatorInvoke = new LambdaInvoke(this, 'simulatorInvoke', {
            lambdaFunction: this.simulatorLambdaFunction,
            outputPath: "$.Payload",
            payload: TaskInput.fromJsonPathAt("$"),
            retryOnServiceExceptions: true
        })
        const devicesRunning = new Choice(this, 'devicesRunning?');

        const updateSimTable = new DynamoUpdateItem(this, "UpdateSimTable", {
            "table": props.simulationTable,
            "key": {
                "simId": DynamoAttributeValue.fromString(JsonPath.stringAt('$.simulation.simId'))
            },
            "updateExpression": "SET stage = :stage, updatedAt = :time",
            "expressionAttributeValues": {
                ":stage": DynamoAttributeValue.fromString("sleeping"),
                ":time": DynamoAttributeValue.fromString(JsonPath.stringAt("$$.State.EnteredTime"))
            },
            "conditionExpression": "attribute_exists(simId)"
        });
        const done = new Succeed(this, 'Done');
        updateSimTable.addCatch(done, {errors: ["DynamoDB.ConditionalCheckFailedException"]});
        
        const definition = Chain
            .start(getDeviceTypeMap.iterator(getDeviceTypeInfo))
            .next(simulatorInvoke.addCatch(updateSimTable, { resultPath: '$.error' }))
            .next(devicesRunning
                .when(
                    Condition.booleanEquals('$.options.restart', true),
                    simulatorInvoke
                )
                .otherwise(updateSimTable.next(done))
            );

        const simulatorLogGroup = new LogGroup(this, 'StepFunctionsLogGroup', {
            retention: RetentionDays.ONE_YEAR,
            logGroupName: `/aws/vendedlogs/states/${Aws.STACK_NAME}-simulatorStepFunctionsLogGroup-${props.uniqueSuffix}`
        });

        addCfnSuppressRules(simulatorLogGroup, [{
            id: 'W84',
            reason: 'KMS encryption unnecessary for log group'
        }]);

        //Microservices Lambda Role
        const microservicesRole = new Role(this, 'MicroservicesRole', {
            assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
            path: '/',
            inlinePolicies: {
                'DynamoDBPolicy': new PolicyDocument({
                    statements: [
                        new PolicyStatement({
                            effect: Effect.ALLOW,
                            actions: [
                                'dynamodb:PutItem',
                                'dynamodb:DeleteItem',
                                'dynamodb:GetItem',
                                'dynamodb:Scan',
                                'dynamodb:Query',
                                'dynamodb:BatchGetItem'
                            ],
                            resources: [
                                props.simulationTable.tableArn,
                                props.deviceTypeTable.tableArn
                            ]
                        }),
                    ]
                })
            }
        });
        microservicesRole.attachInlinePolicy(props.cloudWatchLogsPolicy);

        this.microservicesLambdaFunction = new LambdaFunction(this, 'microservices', {
            code: Code.fromBucket(sourceCodeBucket, `${sourceCodePrefix}/microservices.zip`),
            description: 'IoT Device Simulator microservices function',
            environment: {
                SIMULATIONS_TBL: props.simulationTable.tableName,
                DEVICE_TYPES_TBL: props.deviceTypeTable.tableName,
                SEND_ANONYMOUS_METRIC: props.solutionConfig.sendAnonymousUsage,
                SOLUTION_ID: props.solutionConfig.solutionId,
                VERSION: props.solutionConfig.solutionVersion,
                UUID: props.uuid
            },
            handler: 'index.handler',
            runtime: Runtime.NODEJS_14_X,
            timeout: Duration.minutes(1),
            role: microservicesRole
        });

        const microservicesToStepfunctions = new LambdaToStepfunctions(this, "StepFunctions", {
            existingLambdaObj: this.microservicesLambdaFunction,
            stateMachineProps: {
                definition: definition,
                logs: {
                    destination: simulatorLogGroup,
                    level: LogLevel.ALL,
                    includeExecutionData: false
                }
            },
            stateMachineEnvironmentVariableName: "SIM_STEP_FUNCTION"
        });
        this.simulatorStepFunctions = microservicesToStepfunctions.stateMachine;
        addCfnSuppressRules(this.simulatorStepFunctions, [
            {
                id: 'W11',
                reason: 'CloudWatch logs actions do not support resource level permissions'
            },
            {
                id: 'W12',
                reason: 'CloudWatch logs actions do not support resource level permissions'
            }
        ]);
    }