diff --git a/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/README.md b/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/README.md index e9b9f47ac..2beda98b4 100644 --- a/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/README.md @@ -59,6 +59,7 @@ _Parameters_ |existingStreamObj?|[`kinesis.Stream`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesis.Stream.html)|Existing instance of Kinesis Stream, providing both this and `kinesisStreamProps` will cause an error.| |kinesisStreamProps?|[`kinesis.StreamProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesis.StreamProps.html)|Optional user-provided props to override the default props for the Kinesis stream.| |logGroupProps?|[`logs.LogGroupProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-logs.LogGroupProps.html)|User provided props to override the default props for for the CloudWatchLogs LogGroup.| +|createCloudWatchAlarms|`boolean`|Whether to create recommended CloudWatch alarms for Kinesis Data Stream. Default value is set to `true`| ## Pattern Properties @@ -69,6 +70,7 @@ _Parameters_ |apiGatewayCloudWatchRole|[`iam.Role`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iam.Role.html)|Returns an instance of the iam.Role created by the construct for API Gateway for CloudWatch access.| |apiGatewayLogGroup|[`logs.LogGroup`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-logs.LogGroup.html)|Returns an instance of the LogGroup created by the construct for API Gateway access logging to CloudWatch.| |kinesisStream|[`kinesis.Stream`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesis.Stream.html)|Returns an instance of the Kinesis stream created or used by the pattern.| +|cloudwatchAlarms?|[`cloudwatch.Alarm[]`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudwatch.Alarm.html)|Returns an array of recommended CloudWatch Alarms created by the construct for Kinesis Data stream| ## Sample API Usage diff --git a/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/lib/index.ts index d17e69710..a67badf60 100644 --- a/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/lib/index.ts @@ -19,6 +19,7 @@ import * as defaults from '@aws-solutions-constructs/core'; // Note: To ensure CDKv2 compatibility, keep the import statement for Construct separate import { Construct } from '@aws-cdk/core'; import * as logs from '@aws-cdk/aws-logs'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; /** * @summary The properties for the ApiGatewayToKinesisStreamsProps class. @@ -81,6 +82,12 @@ export interface ApiGatewayToKinesisStreamsProps { * @default - Default props are used */ readonly logGroupProps?: logs.LogGroupProps + /** + * Whether to create recommended CloudWatch alarms + * + * @default - Alarms are created + */ + readonly createCloudWatchAlarms?: boolean; } /** @@ -92,6 +99,7 @@ export class ApiGatewayToKinesisStreams extends Construct { public readonly apiGatewayCloudWatchRole: iam.Role; public readonly apiGatewayLogGroup: logs.LogGroup; public readonly kinesisStream: kinesis.Stream; + public readonly cloudwatchAlarms?: cloudwatch.Alarm[]; /** * @summary Constructs a new instance of the ApiGatewayToKinesisStreams class. @@ -154,6 +162,11 @@ export class ApiGatewayToKinesisStreams extends Construct { requestValidator, requestModel: { 'application/json': this.getPutRecordsModel(props.putRecordsRequestModel) } }); + + if (props.createCloudWatchAlarms === undefined || props.createCloudWatchAlarms) { + // Deploy best practices CW Alarms for Kinesis Stream + this.cloudwatchAlarms = defaults.buildKinesisStreamCWAlarms(this); + } } private getPutRecordTemplate(putRecordTemplate?: string): string { diff --git a/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/package.json b/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/package.json index e8b857733..9b7c209ec 100644 --- a/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/package.json @@ -58,6 +58,7 @@ "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kinesis": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-solutions-constructs/core": "0.0.0", "constructs": "^3.2.0" }, @@ -87,7 +88,8 @@ "@aws-cdk/aws-kinesis": "0.0.0", "@aws-solutions-constructs/core": "0.0.0", "constructs": "^3.2.0", - "@aws-cdk/aws-logs": "0.0.0" + "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-cloudwatch": "0.0.0" }, "keywords": [ "aws", diff --git a/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/test/apigateway-kinesis.test.ts b/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/test/apigateway-kinesis.test.ts index 0c589fe73..421d1881a 100644 --- a/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/test/apigateway-kinesis.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/test/apigateway-kinesis.test.ts @@ -29,6 +29,7 @@ test('Test construct properties', () => { expect(pattern.apiGatewayCloudWatchRole !== null); expect(pattern.apiGatewayLogGroup !== null); expect(pattern.kinesisStream !== null); + expect(pattern.cloudwatchAlarms !== null); }); // -------------------------------------------------------------- @@ -80,24 +81,33 @@ test('Test deployment w/ overwritten properties', () => { ShardCount: 1, Name: 'my-stream' }); + + // Test for Cloudwatch Alarms + expect(stack).toCountResources('AWS::CloudWatch::Alarm', 2); }); // -------------------------------------------------------------- -// Test deployment w/ existing stream +// Test deployment w/ existing stream without default cloudwatch alarms // -------------------------------------------------------------- test('Test deployment w/ existing stream', () => { const stack = new Stack(); - new ApiGatewayToKinesisStreams(stack, 'api-gateway-kinesis', { + const construct = new ApiGatewayToKinesisStreams(stack, 'api-gateway-kinesis', { apiGatewayProps: {}, existingStreamObj: new kinesis.Stream(stack, 'ExistingStream', { shardCount: 5, retentionPeriod: Duration.days(4) - }) + }), + createCloudWatchAlarms: false }); expect(stack).toHaveResource('AWS::Kinesis::Stream', { ShardCount: 5, RetentionPeriodHours: 96 }); + + expect(construct.cloudwatchAlarms == null); + + // Since createCloudWatchAlars is set to false, no Alarm should exist + expect(stack).not.toHaveResource('AWS::CloudWatch::Alarm'); }); diff --git a/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/test/integ.apigateway-kinesis-overwrite.expected.json b/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/test/integ.apigateway-kinesis-overwrite.expected.json index 20c8d2923..95dca0772 100644 --- a/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/test/integ.apigateway-kinesis-overwrite.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/test/integ.apigateway-kinesis-overwrite.expected.json @@ -477,6 +477,32 @@ ] } }, + "testapigatewaykinesisoverwriteKinesisStreamGetRecordsIteratorAgeAlarmAF0BEF52": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Consumer Record Processing Falling Behind, there is risk for data loss due to record expiration.", + "MetricName": "GetRecords.IteratorAgeMilliseconds", + "Namespace": "AWS/Kinesis", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 2592000 + } + }, + "testapigatewaykinesisoverwriteKinesisStreamReadProvisionedThroughputExceededAlarm5C0040FB": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Consumer Application is Reading at a Slower Rate Than Expected.", + "MetricName": "ReadProvisionedThroughputExceeded", + "Namespace": "AWS/Kinesis", + "Period": 300, + "Statistic": "Average", + "Threshold": 0 + } + }, "KinesisStream46752A3E": { "Type": "AWS::Kinesis::Stream", "Properties": { diff --git a/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/test/integ.no-arguments.expected.json index 54687ca14..05b7b943e 100644 --- a/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/test/integ.no-arguments.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-apigateway-kinesisstreams/test/integ.no-arguments.expected.json @@ -514,6 +514,32 @@ ] } }, + "testapigatewaykinesisdefaultKinesisStreamGetRecordsIteratorAgeAlarm0638BB32": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Consumer Record Processing Falling Behind, there is risk for data loss due to record expiration.", + "MetricName": "GetRecords.IteratorAgeMilliseconds", + "Namespace": "AWS/Kinesis", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 2592000 + } + }, + "testapigatewaykinesisdefaultKinesisStreamReadProvisionedThroughputExceededAlarmE7251F6A": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Consumer Application is Reading at a Slower Rate Than Expected.", + "MetricName": "ReadProvisionedThroughputExceeded", + "Namespace": "AWS/Kinesis", + "Period": 300, + "Statistic": "Average", + "Threshold": 0 + } + }, "KinesisStream46752A3E": { "Type": "AWS::Kinesis::Stream", "Properties": { diff --git a/source/patterns/@aws-solutions-constructs/aws-wafwebacl-apigateway/test/integ.wafwebacl-apigateway-kinesisstreams.expected.json b/source/patterns/@aws-solutions-constructs/aws-wafwebacl-apigateway/test/integ.wafwebacl-apigateway-kinesisstreams.expected.json index 90873f99e..74fa00be6 100644 --- a/source/patterns/@aws-solutions-constructs/aws-wafwebacl-apigateway/test/integ.wafwebacl-apigateway-kinesisstreams.expected.json +++ b/source/patterns/@aws-solutions-constructs/aws-wafwebacl-apigateway/test/integ.wafwebacl-apigateway-kinesisstreams.expected.json @@ -513,6 +513,32 @@ ] } }, + "ApiGatwayToKinesisStreamsKinesisStreamGetRecordsIteratorAgeAlarm1705B52A": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Consumer Record Processing Falling Behind, there is risk for data loss due to record expiration.", + "MetricName": "GetRecords.IteratorAgeMilliseconds", + "Namespace": "AWS/Kinesis", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 2592000 + } + }, + "ApiGatwayToKinesisStreamsKinesisStreamReadProvisionedThroughputExceededAlarm69C57002": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Consumer Application is Reading at a Slower Rate Than Expected.", + "MetricName": "ReadProvisionedThroughputExceeded", + "Namespace": "AWS/Kinesis", + "Period": 300, + "Statistic": "Average", + "Threshold": 0 + } + }, "KinesisStream46752A3E": { "Type": "AWS::Kinesis::Stream", "Properties": {