From 85c4e64ee7f4e7eb7b6c8cbda448bbaefacde122 Mon Sep 17 00:00:00 2001 From: Barun Ray Date: Mon, 3 Sep 2018 23:10:12 -0700 Subject: [PATCH] feat(aws-dynamodb): support table application autoscaling (#637) Allows adding an application auto-scaling for a DynamoDB table --- CHANGELOG.md | 1 + packages/@aws-cdk/aws-dynamodb/README.md | 59 ++ packages/@aws-cdk/aws-dynamodb/lib/table.ts | 169 +++- packages/@aws-cdk/aws-dynamodb/package.json | 1 + .../aws-dynamodb/test/test.dynamodb.ts | 939 ++++++++++++++++++ 5 files changed, 1163 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30421ebfac4f6..e43468e1f9126 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - AWS::AppSync::DataSource HttpConfig (__added__) - AWS::DAX::Cluster SSESpecification (__added__) - AWS::DynamoDB::Table Stream (__added__) + - AWS::DynamoDB::Table AutoScalingSupport (__added__) - AWS::EC2::VPCEndpoint IsPrivateDnsEnabled (__added__) - AWS::EC2::VPCEndpoint SecurityGroupIds (__added__) - AWS::EC2::VPCEndpoint SubnetIds (__added__) diff --git a/packages/@aws-cdk/aws-dynamodb/README.md b/packages/@aws-cdk/aws-dynamodb/README.md index 32d14725fb425..0e4bd0a382b92 100644 --- a/packages/@aws-cdk/aws-dynamodb/README.md +++ b/packages/@aws-cdk/aws-dynamodb/README.md @@ -11,3 +11,62 @@ const customTable = new dynamodb.Table(stack, 'CustomTable', { tableName: 'MyTableName' // Default is CloudFormation-generated, which is the preferred approach }) ``` + +### Setup Auto Scaling for DynamoDB Table +further reading: +https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/AutoScaling.html +https://aws.amazon.com/blogs/database/how-to-use-aws-cloudformation-to-configure-auto-scaling-for-amazon-dynamodb-tables-and-indexes/ + +#### Setup via Constructor +```ts +import dynamodb = require('@aws-cdk/aws-dynamodb'); + +const customTable = new dynamodb.Table(stack, 'CustomTable', { + readCapacity: readUnits, // Default is 5 + writeCapacity: writeUnits, // Default is 5 + tableName: 'MyTableName', // Default is CloudFormation-generated, which is the preferred approach + readAutoScaling: { + minCapacity: 500, + maxCapacity: 5000, + targetValue: 75.0, + scaleInCooldown: 30, + scaleOutCooldown: 30, + scalingPolicyName: 'MyAwesomeReadPolicyName' + }, + writeAutoScaling: { + minCapacity: 50, + maxCapacity: 500, + targetValue: 50.0, + scaleInCooldown: 10, + scaleOutCooldown: 10, + scalingPolicyName: 'MyAwesomeWritePolicyName' + }, +}); +``` + +#### Setup via addAutoScaling +```ts +import dynamodb = require('@aws-cdk/aws-dynamodb'); + +const customTable = new dynamodb.Table(stack, 'CustomTable', { + readCapacity: readUnits, // Default is 5 + writeCapacity: writeUnits, // Default is 5 + tableName: 'MyTableName' // Default is CloudFormation-generated, which is the preferred approach +}); +table.addReadAutoScaling({ + minCapacity: 500, + maxCapacity: 5000, + targetValue: 75.0, + scaleInCooldown: 30, + scaleOutCooldown: 30, + scalingPolicyName: 'MyAwesomeReadPolicyName' +}); +table.addWriteAutoScaling({ + minCapacity: 50, + maxCapacity: 500, + targetValue: 50.0, + scaleInCooldown: 10, + scaleOutCooldown: 10, + scalingPolicyName: 'MyAwesomeWritePolicyName' +}); +``` \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index d5fdf087c0074..89f4fbcf2e7e2 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -1,5 +1,7 @@ -import { Construct } from '@aws-cdk/cdk'; -import { cloudformation, TableArn, TableName, TableStreamArn } from './dynamodb.generated'; +import { cloudformation as applicationautoscaling } from '@aws-cdk/aws-applicationautoscaling'; +import { Role } from '@aws-cdk/aws-iam'; +import { Construct, PolicyStatement, PolicyStatementEffect, ServicePrincipal } from '@aws-cdk/cdk'; +import { cloudformation as dynamodb, TableArn, TableName, TableStreamArn } from './dynamodb.generated'; const HASH_KEY_TYPE = 'HASH'; const RANGE_KEY_TYPE = 'RANGE'; @@ -30,8 +32,58 @@ export interface TableProps { * @default undefined, streams are disbaled */ streamSpecification?: StreamViewType; + + /** + * AutoScalingProps configuration to configure Read AutoScaling for the DyanmoDB table. + * This field is optional and this can be achieved via addReadAutoScaling. + * @default undefined, read auto scaling is disabled + */ + readAutoScaling?: AutoScalingProps; + + /** + * AutoScalingProps configuration to configure Write AutoScaling for the DyanmoDB table. + * This field is optional and this can be achieved via addWriteAutoScaling. + * @default undefined, write auto scaling is disabled + */ + writeAutoScaling?: AutoScalingProps; } +/* tslint:disable:max-line-length */ +export interface AutoScalingProps { + /** + * The minimum value that Application Auto Scaling can use to scale a target during a scaling activity. + * @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-applicationautoscaling-scalabletarget.html#cfn-applicationautoscaling-scalabletarget-mincapacity + */ + minCapacity: number; + /** + * The maximum value that Application Auto Scaling can use to scale a target during a scaling activity. + * @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-applicationautoscaling-scalabletarget.html#cfn-applicationautoscaling-scalabletarget-maxcapacity + */ + maxCapacity: number; + /** + * Application Auto Scaling ensures that the ratio of consumed capacity to provisioned capacity stays at or near this value. You define TargetValue as a percentage. + * @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-applicationautoscaling-scalingpolicy-targettrackingscalingpolicyconfiguration.html#cfn-applicationautoscaling-scalingpolicy-targettrackingscalingpolicyconfiguration-targetvalue + */ + targetValue: number; + /** + * The amount of time, in seconds, after a scale in activity completes before another scale in activity can start. + * @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-applicationautoscaling-scalingpolicy-targettrackingscalingpolicyconfiguration.html#cfn-applicationautoscaling-scalingpolicy-targettrackingscalingpolicyconfiguration-scaleincooldown + */ + scaleInCooldown: number; + /** + * The amount of time, in seconds, after a scale out activity completes before another scale out activity can start. + * @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-applicationautoscaling-scalingpolicy-targettrackingscalingpolicyconfiguration.html#cfn-applicationautoscaling-scalingpolicy-targettrackingscalingpolicyconfiguration-scaleoutcooldown + */ + scaleOutCooldown: number; + /** + * A name for the scaling policy. + * @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-applicationautoscaling-scalingpolicy.html#cfn-applicationautoscaling-scalingpolicy-policyname + * @default {TableName}[ReadCapacity|WriteCapacity]ScalingPolicy + */ + scalingPolicyName?: string; +} +/* tslint:enable:max-line-length */ + /** * Provides a DynamoDB table. */ @@ -40,10 +92,13 @@ export class Table extends Construct { public readonly tableName: TableName; public readonly tableStreamArn: TableStreamArn; - private readonly table: cloudformation.TableResource; + private readonly table: dynamodb.TableResource; + + private readonly keySchema = new Array(); + private readonly attributeDefinitions = new Array(); - private readonly keySchema = new Array(); - private readonly attributeDefinitions = new Array(); + private readScalingPolicyResource?: applicationautoscaling.ScalingPolicyResource; + private writeScalingPolicyResource?: applicationautoscaling.ScalingPolicyResource; constructor(parent: Construct, name: string, props: TableProps = {}) { super(parent, name); @@ -51,7 +106,7 @@ export class Table extends Construct { const readCapacityUnits = props.readCapacity || 5; const writeCapacityUnits = props.writeCapacity || 5; - this.table = new cloudformation.TableResource(this, 'Resource', { + this.table = new dynamodb.TableResource(this, 'Resource', { tableName: props.tableName, keySchema: this.keySchema, attributeDefinitions: this.attributeDefinitions, @@ -64,6 +119,14 @@ export class Table extends Construct { this.tableArn = this.table.tableArn; this.tableName = this.table.ref; this.tableStreamArn = this.table.tableStreamArn; + + if (props.readAutoScaling) { + this.addReadAutoScaling(props.readAutoScaling); + } + + if (props.writeAutoScaling) { + this.addWriteAutoScaling(props.writeAutoScaling); + } } public addPartitionKey(name: string, type: KeyAttributeType): this { @@ -76,6 +139,14 @@ export class Table extends Construct { return this; } + public addReadAutoScaling(props: AutoScalingProps) { + this.readScalingPolicyResource = this.buildAutoScaling(this.readScalingPolicyResource, 'Read', props); + } + + public addWriteAutoScaling(props: AutoScalingProps) { + this.writeScalingPolicyResource = this.buildAutoScaling(this.writeScalingPolicyResource, 'Write', props); + } + public validate(): string[] { const errors = new Array(); if (!this.findKey(HASH_KEY_TYPE)) { @@ -84,6 +155,92 @@ export class Table extends Construct { return errors; } + private validateAutoScalingProps(props: AutoScalingProps) { + if (props.targetValue < 10 || props.targetValue > 90) { + throw new RangeError("scalingTargetValue for predefined metric type DynamoDBReadCapacityUtilization/" + + "DynamoDBWriteCapacityUtilization must be between 10 and 90; Provided value is: " + props.targetValue); + } + if (props.scaleInCooldown < 0) { + throw new RangeError("scaleInCooldown must be greater than or equal to 0; Provided value is: " + props.scaleInCooldown); + } + if (props.scaleOutCooldown < 0) { + throw new RangeError("scaleOutCooldown must be greater than or equal to 0; Provided value is: " + props.scaleOutCooldown); + } + if (props.maxCapacity < 0) { + throw new RangeError("maximumCapacity must be greater than or equal to 0; Provided value is: " + props.maxCapacity); + } + if (props.minCapacity < 0) { + throw new RangeError("minimumCapacity must be greater than or equal to 0; Provided value is: " + props.minCapacity); + } + } + + private buildAutoScaling(scalingPolicyResource: applicationautoscaling.ScalingPolicyResource | undefined, + scalingType: string, + props: AutoScalingProps) { + if (scalingPolicyResource) { + throw new Error(`${scalingType} Auto Scaling already defined for Table`); + } + + this.validateAutoScalingProps(props); + const autoScalingRole = this.buildAutoScalingRole(`${scalingType}AutoScalingRole`); + + const scalableTargetResource = new applicationautoscaling.ScalableTargetResource( + this, `${scalingType}CapacityScalableTarget`, this.buildScalableTargetResourceProps( + `dynamodb:table:${scalingType}CapacityUnits`, autoScalingRole, props)); + + return new applicationautoscaling.ScalingPolicyResource( + this, `${scalingType}CapacityScalingPolicy`, + this.buildScalingPolicyResourceProps(`DynamoDB${scalingType}CapacityUtilization`, `${scalingType}Capacity`, + scalableTargetResource, props)); + } + + private buildAutoScalingRole(roleResourceName: string) { + const autoScalingRole = new Role(this, roleResourceName, { + assumedBy: new ServicePrincipal('application-autoscaling.amazonaws.com') + }); + autoScalingRole.addToPolicy(new PolicyStatement(PolicyStatementEffect.Allow) + .addActions("dynamodb:DescribeTable", "dynamodb:UpdateTable") + .addResource(this.tableArn)); + autoScalingRole.addToPolicy(new PolicyStatement(PolicyStatementEffect.Allow) + .addActions("cloudwatch:PutMetricAlarm", "cloudwatch:DescribeAlarms", "cloudwatch:GetMetricStatistics", + "cloudwatch:SetAlarmState", "cloudwatch:DeleteAlarms") + .addAllResources()); + return autoScalingRole; + } + + private buildScalableTargetResourceProps(scalableDimension: string, + scalingRole: Role, + props: AutoScalingProps) { + return { + maxCapacity: props.maxCapacity, + minCapacity: props.minCapacity, + resourceId: `table/${this.tableName}`, + roleArn: scalingRole.roleArn, + scalableDimension, + serviceNamespace: 'dynamodb' + }; + } + + private buildScalingPolicyResourceProps(predefinedMetricType: string, + scalingParameter: string, + scalableTargetResource: applicationautoscaling.ScalableTargetResource, + props: AutoScalingProps) { + const scalingPolicyName = props.scalingPolicyName || `${this.tableName}${scalingParameter}ScalingPolicy`; + return { + policyName: scalingPolicyName, + policyType: 'TargetTrackingScaling', + scalingTargetId: scalableTargetResource.ref, + targetTrackingScalingPolicyConfiguration: { + predefinedMetricSpecification: { + predefinedMetricType + }, + scaleInCooldown: props.scaleInCooldown, + scaleOutCooldown: props.scaleOutCooldown, + targetValue: props.targetValue + } + }; + } + private findKey(keyType: string) { return this.keySchema.find(prop => prop.keyType === keyType); } diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index cfb4e958278a8..b52e07e95fd74 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -53,6 +53,7 @@ "pkglint": "^0.8.2" }, "dependencies": { + "@aws-cdk/aws-applicationautoscaling": "^0.8.2", "@aws-cdk/aws-iam": "^0.8.2", "@aws-cdk/cdk": "^0.8.2" }, diff --git a/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts b/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts index 3fda4c4e7bbcf..7a6eaa2dd8ac2 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts @@ -229,8 +229,947 @@ export = { } }); + test.done(); + }, + + 'when specifying Read Auto Scaling'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + table.addReadAutoScaling({ + minCapacity: 50, + maxCapacity: 500, + targetValue: 75.0, + scaleInCooldown: 80, + scaleOutCooldown: 60, + scalingPolicyName: 'MyAwesomePolicyName' + }); + const template = app.synthesizeTemplate(); + + test.deepEqual(template, { Resources: + { MyTable794EDED1: + { Type: 'AWS::DynamoDB::Table', + Properties: + { KeySchema: + [ { AttributeName: 'partitionKey', KeyType: 'HASH' }, + { AttributeName: 'sortKey', KeyType: 'RANGE' } ], + ProvisionedThroughput: { ReadCapacityUnits: 42, WriteCapacityUnits: 1337 }, + AttributeDefinitions: + [ { AttributeName: 'partitionKey', AttributeType: 'S' }, + { AttributeName: 'sortKey', AttributeType: 'B' } ], + TableName: 'MyTable' } }, + MyTableReadAutoScalingRoleFEE68E49: + { Type: 'AWS::IAM::Role', + Properties: + { AssumeRolePolicyDocument: + { Statement: + [ { Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'application-autoscaling.amazonaws.com' } } ], + Version: '2012-10-17' } } }, + MyTableReadAutoScalingRoleDefaultPolicyF6A1975F: + { Type: 'AWS::IAM::Policy', + Properties: + { PolicyDocument: + { Statement: + [ { Action: [ 'dynamodb:DescribeTable', 'dynamodb:UpdateTable' ], + Effect: 'Allow', + Resource: { 'Fn::GetAtt': [ 'MyTable794EDED1', 'Arn' ] } }, + { Action: [ 'cloudwatch:PutMetricAlarm', 'cloudwatch:DescribeAlarms', 'cloudwatch:GetMetricStatistics', + 'cloudwatch:SetAlarmState', 'cloudwatch:DeleteAlarms' ], + Effect: 'Allow', Resource: '*' } ], + Version: '2012-10-17' }, + PolicyName: 'MyTableReadAutoScalingRoleDefaultPolicyF6A1975F', + Roles: [ { Ref: 'MyTableReadAutoScalingRoleFEE68E49' } ] } }, + MyTableReadCapacityScalableTarget72B0B3BF: + { Type: 'AWS::ApplicationAutoScaling::ScalableTarget', + Properties: + { MaxCapacity: 500, + MinCapacity: 50, + ResourceId: + { 'Fn::Join': [ '', [ 'table/', { Ref: 'MyTable794EDED1' } ] ] }, + RoleARN: + { 'Fn::GetAtt': [ 'MyTableReadAutoScalingRoleFEE68E49', 'Arn' ] }, + ScalableDimension: 'dynamodb:table:ReadCapacityUnits', + ServiceNamespace: 'dynamodb' } }, + MyTableReadCapacityScalingPolicyCC18E396: + { Type: 'AWS::ApplicationAutoScaling::ScalingPolicy', + Properties: + { PolicyName: 'MyAwesomePolicyName', + PolicyType: 'TargetTrackingScaling', + ScalingTargetId: { Ref: 'MyTableReadCapacityScalableTarget72B0B3BF' }, + TargetTrackingScalingPolicyConfiguration: + { PredefinedMetricSpecification: { PredefinedMetricType: 'DynamoDBReadCapacityUtilization' }, + ScaleInCooldown: 80, + ScaleOutCooldown: 60, + TargetValue: 75 } } } } }); + + test.done(); + }, + + 'when specifying Read Auto Scaling via constructor'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337, + readAutoScaling: { + minCapacity: 50, + maxCapacity: 500, + targetValue: 75.0, + scaleInCooldown: 80, + scaleOutCooldown: 60, + scalingPolicyName: 'MyAwesomePolicyName' + } + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + const template = app.synthesizeTemplate(); + + test.deepEqual(template, { Resources: + { MyTable794EDED1: + { Type: 'AWS::DynamoDB::Table', + Properties: + { KeySchema: + [ { AttributeName: 'partitionKey', KeyType: 'HASH' }, + { AttributeName: 'sortKey', KeyType: 'RANGE' } ], + ProvisionedThroughput: { ReadCapacityUnits: 42, WriteCapacityUnits: 1337 }, + AttributeDefinitions: + [ { AttributeName: 'partitionKey', AttributeType: 'S' }, + { AttributeName: 'sortKey', AttributeType: 'B' } ], + TableName: 'MyTable' } }, + MyTableReadAutoScalingRoleFEE68E49: + { Type: 'AWS::IAM::Role', + Properties: + { AssumeRolePolicyDocument: + { Statement: + [ { Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'application-autoscaling.amazonaws.com' } } ], + Version: '2012-10-17' } } }, + MyTableReadAutoScalingRoleDefaultPolicyF6A1975F: + { Type: 'AWS::IAM::Policy', + Properties: + { PolicyDocument: + { Statement: + [ { Action: [ 'dynamodb:DescribeTable', 'dynamodb:UpdateTable' ], + Effect: 'Allow', + Resource: { 'Fn::GetAtt': [ 'MyTable794EDED1', 'Arn' ] } }, + { Action: [ 'cloudwatch:PutMetricAlarm', 'cloudwatch:DescribeAlarms', 'cloudwatch:GetMetricStatistics', + 'cloudwatch:SetAlarmState', 'cloudwatch:DeleteAlarms' ], + Effect: 'Allow', Resource: '*' } ], + Version: '2012-10-17' }, + PolicyName: 'MyTableReadAutoScalingRoleDefaultPolicyF6A1975F', + Roles: [ { Ref: 'MyTableReadAutoScalingRoleFEE68E49' } ] } }, + MyTableReadCapacityScalableTarget72B0B3BF: + { Type: 'AWS::ApplicationAutoScaling::ScalableTarget', + Properties: + { MaxCapacity: 500, + MinCapacity: 50, + ResourceId: + { 'Fn::Join': [ '', [ 'table/', { Ref: 'MyTable794EDED1' } ] ] }, + RoleARN: + { 'Fn::GetAtt': [ 'MyTableReadAutoScalingRoleFEE68E49', 'Arn' ] }, + ScalableDimension: 'dynamodb:table:ReadCapacityUnits', + ServiceNamespace: 'dynamodb' } }, + MyTableReadCapacityScalingPolicyCC18E396: + { Type: 'AWS::ApplicationAutoScaling::ScalingPolicy', + Properties: + { PolicyName: 'MyAwesomePolicyName', + PolicyType: 'TargetTrackingScaling', + ScalingTargetId: { Ref: 'MyTableReadCapacityScalableTarget72B0B3BF' }, + TargetTrackingScalingPolicyConfiguration: + { PredefinedMetricSpecification: { PredefinedMetricType: 'DynamoDBReadCapacityUtilization' }, + ScaleInCooldown: 80, + ScaleOutCooldown: 60, + TargetValue: 75 } } } } }); + + test.done(); + }, + + 'error when specifying Read Auto Scaling via constructor and attempting to addReadAutoScaling'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337, + readAutoScaling: { + minCapacity: 50, + maxCapacity: 500, + targetValue: 75.0, + scaleInCooldown: 80, + scaleOutCooldown: 60, + scalingPolicyName: 'MyAwesomePolicyName' + } + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + test.throws(() => table.addReadAutoScaling({ + minCapacity: 500, + maxCapacity: 5000, + targetValue: 25.0, + scaleInCooldown: 40, + scaleOutCooldown: 20, + scalingPolicyName: 'MySecondAwesomePolicyName' + }), /Read Auto Scaling already defined for Table/); + + test.done(); + }, + + 'when specifying Read Auto Scaling without scalingPolicyName'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + table.addReadAutoScaling({ + minCapacity: 50, + maxCapacity: 500, + targetValue: 75.0, + scaleInCooldown: 80, + scaleOutCooldown: 60 + }); + const template = app.synthesizeTemplate(); + + test.deepEqual(template, { Resources: + { MyTable794EDED1: + { Type: 'AWS::DynamoDB::Table', + Properties: + { KeySchema: + [ { AttributeName: 'partitionKey', KeyType: 'HASH' }, + { AttributeName: 'sortKey', KeyType: 'RANGE' } ], + ProvisionedThroughput: { ReadCapacityUnits: 42, WriteCapacityUnits: 1337 }, + AttributeDefinitions: + [ { AttributeName: 'partitionKey', AttributeType: 'S' }, + { AttributeName: 'sortKey', AttributeType: 'B' } ], + TableName: 'MyTable' } }, + MyTableReadAutoScalingRoleFEE68E49: + { Type: 'AWS::IAM::Role', + Properties: + { AssumeRolePolicyDocument: + { Statement: + [ { Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'application-autoscaling.amazonaws.com' } } ], + Version: '2012-10-17' } } }, + MyTableReadAutoScalingRoleDefaultPolicyF6A1975F: + { Type: 'AWS::IAM::Policy', + Properties: + { PolicyDocument: + { Statement: + [ { Action: [ 'dynamodb:DescribeTable', 'dynamodb:UpdateTable' ], + Effect: 'Allow', + Resource: { 'Fn::GetAtt': [ 'MyTable794EDED1', 'Arn' ] } }, + { Action: [ 'cloudwatch:PutMetricAlarm', 'cloudwatch:DescribeAlarms', 'cloudwatch:GetMetricStatistics', + 'cloudwatch:SetAlarmState', 'cloudwatch:DeleteAlarms' ], + Effect: 'Allow', Resource: '*' } ], + Version: '2012-10-17' }, + PolicyName: 'MyTableReadAutoScalingRoleDefaultPolicyF6A1975F', + Roles: [ { Ref: 'MyTableReadAutoScalingRoleFEE68E49' } ] } }, + MyTableReadCapacityScalableTarget72B0B3BF: + { Type: 'AWS::ApplicationAutoScaling::ScalableTarget', + Properties: + { MaxCapacity: 500, + MinCapacity: 50, + ResourceId: + { 'Fn::Join': [ '', [ 'table/', { Ref: 'MyTable794EDED1' } ] ] }, + RoleARN: + { 'Fn::GetAtt': [ 'MyTableReadAutoScalingRoleFEE68E49', 'Arn' ] }, + ScalableDimension: 'dynamodb:table:ReadCapacityUnits', + ServiceNamespace: 'dynamodb' } }, + MyTableReadCapacityScalingPolicyCC18E396: + { Type: 'AWS::ApplicationAutoScaling::ScalingPolicy', + Properties: + { PolicyName: + { 'Fn::Join': [ '', [ { Ref: 'MyTable794EDED1' }, 'ReadCapacityScalingPolicy' ] ] }, + PolicyType: 'TargetTrackingScaling', + ScalingTargetId: { Ref: 'MyTableReadCapacityScalableTarget72B0B3BF' }, + TargetTrackingScalingPolicyConfiguration: + { PredefinedMetricSpecification: { PredefinedMetricType: 'DynamoDBReadCapacityUtilization' }, + ScaleInCooldown: 80, + ScaleOutCooldown: 60, + TargetValue: 75 } } } } }); + + test.done(); + }, + + 'when specifying Read Auto Scaling without scalingPolicyName without Table Name'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + table.addReadAutoScaling({ + minCapacity: 50, + maxCapacity: 500, + targetValue: 75.0, + scaleInCooldown: 80, + scaleOutCooldown: 60 + }); + const template = app.synthesizeTemplate(); + + test.deepEqual(template, { Resources: + { MyTable794EDED1: + { Type: 'AWS::DynamoDB::Table', + Properties: + { KeySchema: + [ { AttributeName: 'partitionKey', KeyType: 'HASH' }, + { AttributeName: 'sortKey', KeyType: 'RANGE' } ], + ProvisionedThroughput: { ReadCapacityUnits: 42, WriteCapacityUnits: 1337 }, + AttributeDefinitions: + [ { AttributeName: 'partitionKey', AttributeType: 'S' }, + { AttributeName: 'sortKey', AttributeType: 'B' } ] } }, + MyTableReadAutoScalingRoleFEE68E49: + { Type: 'AWS::IAM::Role', + Properties: + { AssumeRolePolicyDocument: + { Statement: + [ { Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'application-autoscaling.amazonaws.com' } } ], + Version: '2012-10-17' } } }, + MyTableReadAutoScalingRoleDefaultPolicyF6A1975F: + { Type: 'AWS::IAM::Policy', + Properties: + { PolicyDocument: + { Statement: + [ { Action: [ 'dynamodb:DescribeTable', 'dynamodb:UpdateTable' ], + Effect: 'Allow', + Resource: { 'Fn::GetAtt': [ 'MyTable794EDED1', 'Arn' ] } }, + { Action: [ 'cloudwatch:PutMetricAlarm', 'cloudwatch:DescribeAlarms', 'cloudwatch:GetMetricStatistics', + 'cloudwatch:SetAlarmState', 'cloudwatch:DeleteAlarms' ], + Effect: 'Allow', Resource: '*' } ], + Version: '2012-10-17' }, + PolicyName: 'MyTableReadAutoScalingRoleDefaultPolicyF6A1975F', + Roles: [ { Ref: 'MyTableReadAutoScalingRoleFEE68E49' } ] } }, + MyTableReadCapacityScalableTarget72B0B3BF: + { Type: 'AWS::ApplicationAutoScaling::ScalableTarget', + Properties: + { MaxCapacity: 500, + MinCapacity: 50, + ResourceId: + { 'Fn::Join': [ '', [ 'table/', { Ref: 'MyTable794EDED1' } ] ] }, + RoleARN: + { 'Fn::GetAtt': [ 'MyTableReadAutoScalingRoleFEE68E49', 'Arn' ] }, + ScalableDimension: 'dynamodb:table:ReadCapacityUnits', + ServiceNamespace: 'dynamodb' } }, + MyTableReadCapacityScalingPolicyCC18E396: + { Type: 'AWS::ApplicationAutoScaling::ScalingPolicy', + Properties: + { PolicyName: + { 'Fn::Join': [ '', [ { Ref: 'MyTable794EDED1' }, 'ReadCapacityScalingPolicy' ] ] }, + PolicyType: 'TargetTrackingScaling', + ScalingTargetId: { Ref: 'MyTableReadCapacityScalableTarget72B0B3BF' }, + TargetTrackingScalingPolicyConfiguration: + { PredefinedMetricSpecification: { PredefinedMetricType: 'DynamoDBReadCapacityUtilization' }, + ScaleInCooldown: 80, + ScaleOutCooldown: 60, + TargetValue: 75 } } } } }); + + test.done(); + }, + + 'error when specifying Read Auto Scaling with invalid scalingTargetValue < 10'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + test.throws(() => table.addReadAutoScaling({ + minCapacity: 50, + maxCapacity: 500, + targetValue: 5.0, + scaleInCooldown: 80, + scaleOutCooldown: 60 + // tslint:disable-next-line:max-line-length + }), /scalingTargetValue for predefined metric type DynamoDBReadCapacityUtilization\/DynamoDBWriteCapacityUtilization must be between 10 and 90; Provided value is: 5/); + + test.done(); + }, + + 'error when specifying Read Auto Scaling with invalid scalingTargetValue > 90'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + test.throws(() => table.addReadAutoScaling({ + minCapacity: 50, + maxCapacity: 500, + targetValue: 95.0, + scaleInCooldown: 80, + scaleOutCooldown: 60 + // tslint:disable-next-line:max-line-length + }), /scalingTargetValue for predefined metric type DynamoDBReadCapacityUtilization\/DynamoDBWriteCapacityUtilization must be between 10 and 90; Provided value is: 95/); + + test.done(); + }, + + 'error when specifying Read Auto Scaling with invalid scaleInCooldown'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + test.throws(() => table.addReadAutoScaling({ + minCapacity: 50, + maxCapacity: 500, + targetValue: 50.0, + scaleInCooldown: -5, + scaleOutCooldown: 60 + }), /scaleInCooldown must be greater than or equal to 0; Provided value is: -5/); + + test.done(); + }, + + 'error when specifying Read Auto Scaling with invalid scaleOutCooldown'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + test.throws(() => table.addReadAutoScaling({ + minCapacity: 50, + maxCapacity: 500, + targetValue: 50.0, + scaleInCooldown: 80, + scaleOutCooldown: -5 + }), /scaleOutCooldown must be greater than or equal to 0; Provided value is: -5/); + + test.done(); + }, + + 'error when specifying Read Auto Scaling with invalid maximumCapacity'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + test.throws(() => table.addReadAutoScaling({ + minCapacity: 50, + maxCapacity: -5, + targetValue: 50.0, + scaleInCooldown: 80, + scaleOutCooldown: 60 + }), /maximumCapacity must be greater than or equal to 0; Provided value is: -5/); + + test.done(); + }, + + 'error when specifying Read Auto Scaling with invalid minimumCapacity'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + test.throws(() => table.addReadAutoScaling({ + minCapacity: -5, + maxCapacity: 500, + targetValue: 50.0, + scaleInCooldown: 80, + scaleOutCooldown: 60 + }), /minimumCapacity must be greater than or equal to 0; Provided value is: -5/); + + test.done(); + }, + + 'when specifying Write Auto Scaling'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + table.addWriteAutoScaling({ + minCapacity: 50, + maxCapacity: 500, + targetValue: 75.0, + scaleInCooldown: 80, + scaleOutCooldown: 60, + scalingPolicyName: 'MyAwesomePolicyName' + }); + const template = app.synthesizeTemplate(); + + test.deepEqual(template, { Resources: + { MyTable794EDED1: + { Type: 'AWS::DynamoDB::Table', + Properties: + { KeySchema: + [ { AttributeName: 'partitionKey', KeyType: 'HASH' }, + { AttributeName: 'sortKey', KeyType: 'RANGE' } ], + ProvisionedThroughput: { ReadCapacityUnits: 42, WriteCapacityUnits: 1337 }, + AttributeDefinitions: + [ { AttributeName: 'partitionKey', AttributeType: 'S' }, + { AttributeName: 'sortKey', AttributeType: 'B' } ], + TableName: 'MyTable' } }, + MyTableWriteAutoScalingRoleDF7775DE: + { Type: 'AWS::IAM::Role', + Properties: + { AssumeRolePolicyDocument: + { Statement: + [ { Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'application-autoscaling.amazonaws.com' } } ], + Version: '2012-10-17' } } }, + MyTableWriteAutoScalingRoleDefaultPolicyBF1A7EBB: + { Type: 'AWS::IAM::Policy', + Properties: + { PolicyDocument: + { Statement: + [ { Action: [ 'dynamodb:DescribeTable', 'dynamodb:UpdateTable' ], + Effect: 'Allow', + Resource: { 'Fn::GetAtt': [ 'MyTable794EDED1', 'Arn' ] } }, + { Action: [ 'cloudwatch:PutMetricAlarm', 'cloudwatch:DescribeAlarms', 'cloudwatch:GetMetricStatistics', + 'cloudwatch:SetAlarmState', 'cloudwatch:DeleteAlarms' ], + Effect: 'Allow', Resource: '*' } ], + Version: '2012-10-17' }, + PolicyName: 'MyTableWriteAutoScalingRoleDefaultPolicyBF1A7EBB', + Roles: [ { Ref: 'MyTableWriteAutoScalingRoleDF7775DE' } ] } }, + MyTableWriteCapacityScalableTarget56F9809A: + { Type: 'AWS::ApplicationAutoScaling::ScalableTarget', + Properties: + { MaxCapacity: 500, + MinCapacity: 50, + ResourceId: + { 'Fn::Join': [ '', [ 'table/', { Ref: 'MyTable794EDED1' } ] ] }, + RoleARN: + { 'Fn::GetAtt': [ 'MyTableWriteAutoScalingRoleDF7775DE', 'Arn' ] }, + ScalableDimension: 'dynamodb:table:WriteCapacityUnits', + ServiceNamespace: 'dynamodb' } }, + MyTableWriteCapacityScalingPolicy766EAD7A: + { Type: 'AWS::ApplicationAutoScaling::ScalingPolicy', + Properties: + { PolicyName: 'MyAwesomePolicyName', + PolicyType: 'TargetTrackingScaling', + ScalingTargetId: { Ref: 'MyTableWriteCapacityScalableTarget56F9809A' }, + TargetTrackingScalingPolicyConfiguration: + { PredefinedMetricSpecification: { PredefinedMetricType: 'DynamoDBWriteCapacityUtilization' }, + ScaleInCooldown: 80, + ScaleOutCooldown: 60, + TargetValue: 75 } } } } }); + + test.done(); + }, + + 'when specifying Write Auto Scaling via constructor'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337, + writeAutoScaling: { + minCapacity: 50, + maxCapacity: 500, + targetValue: 75.0, + scaleInCooldown: 80, + scaleOutCooldown: 60, + scalingPolicyName: 'MyAwesomePolicyName' + } + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + const template = app.synthesizeTemplate(); + + test.deepEqual(template, { Resources: + { MyTable794EDED1: + { Type: 'AWS::DynamoDB::Table', + Properties: + { KeySchema: + [ { AttributeName: 'partitionKey', KeyType: 'HASH' }, + { AttributeName: 'sortKey', KeyType: 'RANGE' } ], + ProvisionedThroughput: { ReadCapacityUnits: 42, WriteCapacityUnits: 1337 }, + AttributeDefinitions: + [ { AttributeName: 'partitionKey', AttributeType: 'S' }, + { AttributeName: 'sortKey', AttributeType: 'B' } ], + TableName: 'MyTable' } }, + MyTableWriteAutoScalingRoleDF7775DE: + { Type: 'AWS::IAM::Role', + Properties: + { AssumeRolePolicyDocument: + { Statement: + [ { Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'application-autoscaling.amazonaws.com' } } ], + Version: '2012-10-17' } } }, + MyTableWriteAutoScalingRoleDefaultPolicyBF1A7EBB: + { Type: 'AWS::IAM::Policy', + Properties: + { PolicyDocument: + { Statement: + [ { Action: [ 'dynamodb:DescribeTable', 'dynamodb:UpdateTable' ], + Effect: 'Allow', + Resource: { 'Fn::GetAtt': [ 'MyTable794EDED1', 'Arn' ] } }, + { Action: [ 'cloudwatch:PutMetricAlarm', 'cloudwatch:DescribeAlarms', 'cloudwatch:GetMetricStatistics', + 'cloudwatch:SetAlarmState', 'cloudwatch:DeleteAlarms' ], + Effect: 'Allow', Resource: '*' } ], + Version: '2012-10-17' }, + PolicyName: 'MyTableWriteAutoScalingRoleDefaultPolicyBF1A7EBB', + Roles: [ { Ref: 'MyTableWriteAutoScalingRoleDF7775DE' } ] } }, + MyTableWriteCapacityScalableTarget56F9809A: + { Type: 'AWS::ApplicationAutoScaling::ScalableTarget', + Properties: + { MaxCapacity: 500, + MinCapacity: 50, + ResourceId: + { 'Fn::Join': [ '', [ 'table/', { Ref: 'MyTable794EDED1' } ] ] }, + RoleARN: + { 'Fn::GetAtt': [ 'MyTableWriteAutoScalingRoleDF7775DE', 'Arn' ] }, + ScalableDimension: 'dynamodb:table:WriteCapacityUnits', + ServiceNamespace: 'dynamodb' } }, + MyTableWriteCapacityScalingPolicy766EAD7A: + { Type: 'AWS::ApplicationAutoScaling::ScalingPolicy', + Properties: + { PolicyName: 'MyAwesomePolicyName', + PolicyType: 'TargetTrackingScaling', + ScalingTargetId: { Ref: 'MyTableWriteCapacityScalableTarget56F9809A' }, + TargetTrackingScalingPolicyConfiguration: + { PredefinedMetricSpecification: { PredefinedMetricType: 'DynamoDBWriteCapacityUtilization' }, + ScaleInCooldown: 80, + ScaleOutCooldown: 60, + TargetValue: 75 } } } } }); + + test.done(); + }, + + 'error when specifying Write Auto Scaling via constructor and attempting to addWriteAutoScaling'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337, + writeAutoScaling: { + minCapacity: 50, + maxCapacity: 500, + targetValue: 75.0, + scaleInCooldown: 80, + scaleOutCooldown: 60, + scalingPolicyName: 'MyAwesomePolicyName' + } + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + test.throws(() => table.addWriteAutoScaling({ + minCapacity: 500, + maxCapacity: 5000, + targetValue: 25.0, + scaleInCooldown: 40, + scaleOutCooldown: 20, + scalingPolicyName: 'MySecondAwesomePolicyName' + }), /Write Auto Scaling already defined for Table/); + + test.done(); + }, + + 'when specifying Write Auto Scaling without scalingPolicyName'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + table.addWriteAutoScaling({ + minCapacity: 50, + maxCapacity: 500, + targetValue: 75.0, + scaleInCooldown: 80, + scaleOutCooldown: 60 + }); + const template = app.synthesizeTemplate(); + + test.deepEqual(template, { Resources: + { MyTable794EDED1: + { Type: 'AWS::DynamoDB::Table', + Properties: + { KeySchema: + [ { AttributeName: 'partitionKey', KeyType: 'HASH' }, + { AttributeName: 'sortKey', KeyType: 'RANGE' } ], + ProvisionedThroughput: { ReadCapacityUnits: 42, WriteCapacityUnits: 1337 }, + AttributeDefinitions: + [ { AttributeName: 'partitionKey', AttributeType: 'S' }, + { AttributeName: 'sortKey', AttributeType: 'B' } ], + TableName: 'MyTable' } }, + MyTableWriteAutoScalingRoleDF7775DE: + { Type: 'AWS::IAM::Role', + Properties: + { AssumeRolePolicyDocument: + { Statement: + [ { Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'application-autoscaling.amazonaws.com' } } ], + Version: '2012-10-17' } } }, + MyTableWriteAutoScalingRoleDefaultPolicyBF1A7EBB: + { Type: 'AWS::IAM::Policy', + Properties: + { PolicyDocument: + { Statement: + [ { Action: [ 'dynamodb:DescribeTable', 'dynamodb:UpdateTable' ], + Effect: 'Allow', + Resource: { 'Fn::GetAtt': [ 'MyTable794EDED1', 'Arn' ] } }, + { Action: [ 'cloudwatch:PutMetricAlarm', 'cloudwatch:DescribeAlarms', 'cloudwatch:GetMetricStatistics', + 'cloudwatch:SetAlarmState', 'cloudwatch:DeleteAlarms' ], + Effect: 'Allow', Resource: '*' } ], + Version: '2012-10-17' }, + PolicyName: 'MyTableWriteAutoScalingRoleDefaultPolicyBF1A7EBB', + Roles: [ { Ref: 'MyTableWriteAutoScalingRoleDF7775DE' } ] } }, + MyTableWriteCapacityScalableTarget56F9809A: + { Type: 'AWS::ApplicationAutoScaling::ScalableTarget', + Properties: + { MaxCapacity: 500, + MinCapacity: 50, + ResourceId: + { 'Fn::Join': [ '', [ 'table/', { Ref: 'MyTable794EDED1' } ] ] }, + RoleARN: + { 'Fn::GetAtt': [ 'MyTableWriteAutoScalingRoleDF7775DE', 'Arn' ] }, + ScalableDimension: 'dynamodb:table:WriteCapacityUnits', + ServiceNamespace: 'dynamodb' } }, + MyTableWriteCapacityScalingPolicy766EAD7A: + { Type: 'AWS::ApplicationAutoScaling::ScalingPolicy', + Properties: + { PolicyName: + { 'Fn::Join': [ '', [ { Ref: 'MyTable794EDED1' }, 'WriteCapacityScalingPolicy' ] ] }, + PolicyType: 'TargetTrackingScaling', + ScalingTargetId: { Ref: 'MyTableWriteCapacityScalableTarget56F9809A' }, + TargetTrackingScalingPolicyConfiguration: + { PredefinedMetricSpecification: { PredefinedMetricType: 'DynamoDBWriteCapacityUtilization' }, + ScaleInCooldown: 80, + ScaleOutCooldown: 60, + TargetValue: 75 } } } } }); + + test.done(); + }, + + 'when specifying Write Auto Scaling without scalingPolicyName without Table Name'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + table.addWriteAutoScaling({ + minCapacity: 50, + maxCapacity: 500, + targetValue: 75.0, + scaleInCooldown: 80, + scaleOutCooldown: 60 + }); + const template = app.synthesizeTemplate(); + + test.deepEqual(template, { Resources: + { MyTable794EDED1: + { Type: 'AWS::DynamoDB::Table', + Properties: + { KeySchema: + [ { AttributeName: 'partitionKey', KeyType: 'HASH' }, + { AttributeName: 'sortKey', KeyType: 'RANGE' } ], + ProvisionedThroughput: { ReadCapacityUnits: 42, WriteCapacityUnits: 1337 }, + AttributeDefinitions: + [ { AttributeName: 'partitionKey', AttributeType: 'S' }, + { AttributeName: 'sortKey', AttributeType: 'B' } ] } }, + MyTableWriteAutoScalingRoleDF7775DE: + { Type: 'AWS::IAM::Role', + Properties: + { AssumeRolePolicyDocument: + { Statement: + [ { Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { Service: 'application-autoscaling.amazonaws.com' } } ], + Version: '2012-10-17' } } }, + MyTableWriteAutoScalingRoleDefaultPolicyBF1A7EBB: + { Type: 'AWS::IAM::Policy', + Properties: + { PolicyDocument: + { Statement: + [ { Action: [ 'dynamodb:DescribeTable', 'dynamodb:UpdateTable' ], + Effect: 'Allow', + Resource: { 'Fn::GetAtt': [ 'MyTable794EDED1', 'Arn' ] } }, + { Action: [ 'cloudwatch:PutMetricAlarm', 'cloudwatch:DescribeAlarms', 'cloudwatch:GetMetricStatistics', + 'cloudwatch:SetAlarmState', 'cloudwatch:DeleteAlarms' ], + Effect: 'Allow', Resource: '*' } ], + Version: '2012-10-17' }, + PolicyName: 'MyTableWriteAutoScalingRoleDefaultPolicyBF1A7EBB', + Roles: [ { Ref: 'MyTableWriteAutoScalingRoleDF7775DE' } ] } }, + MyTableWriteCapacityScalableTarget56F9809A: + { Type: 'AWS::ApplicationAutoScaling::ScalableTarget', + Properties: + { MaxCapacity: 500, + MinCapacity: 50, + ResourceId: + { 'Fn::Join': [ '', [ 'table/', { Ref: 'MyTable794EDED1' } ] ] }, + RoleARN: + { 'Fn::GetAtt': [ 'MyTableWriteAutoScalingRoleDF7775DE', 'Arn' ] }, + ScalableDimension: 'dynamodb:table:WriteCapacityUnits', + ServiceNamespace: 'dynamodb' } }, + MyTableWriteCapacityScalingPolicy766EAD7A: + { Type: 'AWS::ApplicationAutoScaling::ScalingPolicy', + Properties: + { PolicyName: + { 'Fn::Join': [ '', [ { Ref: 'MyTable794EDED1' }, 'WriteCapacityScalingPolicy' ] ] }, + PolicyType: 'TargetTrackingScaling', + ScalingTargetId: { Ref: 'MyTableWriteCapacityScalableTarget56F9809A' }, + TargetTrackingScalingPolicyConfiguration: + { PredefinedMetricSpecification: { PredefinedMetricType: 'DynamoDBWriteCapacityUtilization' }, + ScaleInCooldown: 80, + ScaleOutCooldown: 60, + TargetValue: 75 } } } } }); + + test.done(); + }, + + 'error when specifying Write Auto Scaling with invalid scalingTargetValue < 10'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + test.throws(() => table.addWriteAutoScaling({ + minCapacity: 50, + maxCapacity: 500, + targetValue: 5.0, + scaleInCooldown: 80, + scaleOutCooldown: 60 + // tslint:disable-next-line:max-line-length + }), /scalingTargetValue for predefined metric type DynamoDBReadCapacityUtilization\/DynamoDBWriteCapacityUtilization must be between 10 and 90; Provided value is: 5/); + + test.done(); + }, + + 'error when specifying Write Auto Scaling with invalid scalingTargetValue > 90'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + test.throws(() => table.addWriteAutoScaling({ + minCapacity: 50, + maxCapacity: 500, + targetValue: 95.0, + scaleInCooldown: 80, + scaleOutCooldown: 60 + // tslint:disable-next-line:max-line-length + }), /scalingTargetValue for predefined metric type DynamoDBReadCapacityUtilization\/DynamoDBWriteCapacityUtilization must be between 10 and 90; Provided value is: 95/); + + test.done(); + }, + + 'error when specifying Write Auto Scaling with invalid scaleInCooldown'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + test.throws(() => table.addWriteAutoScaling({ + minCapacity: 50, + maxCapacity: 500, + targetValue: 50.0, + scaleInCooldown: -5, + scaleOutCooldown: 60 + }), /scaleInCooldown must be greater than or equal to 0; Provided value is: -5/); + + test.done(); + }, + + 'error when specifying Write Auto Scaling with invalid scaleOutCooldown'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + test.throws(() => table.addWriteAutoScaling({ + minCapacity: 50, + maxCapacity: 500, + targetValue: 50.0, + scaleInCooldown: 80, + scaleOutCooldown: -5 + }), /scaleOutCooldown must be greater than or equal to 0; Provided value is: -5/); + + test.done(); + }, + + 'error when specifying Write Auto Scaling with invalid maximumCapacity'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + test.throws(() => table.addWriteAutoScaling({ + minCapacity: 50, + maxCapacity: -5, + targetValue: 50.0, + scaleInCooldown: 80, + scaleOutCooldown: 60 + }), /maximumCapacity must be greater than or equal to 0; Provided value is: -5/); + + test.done(); + }, + + 'error when specifying Write Auto Scaling with invalid minimumCapacity'(test: Test) { + const app = new TestApp(); + const table = new Table(app.stack, 'MyTable', { + tableName: 'MyTable', + readCapacity: 42, + writeCapacity: 1337 + }); + table.addPartitionKey('partitionKey', KeyAttributeType.String); + table.addSortKey('sortKey', KeyAttributeType.Binary); + test.throws(() => table.addWriteAutoScaling({ + minCapacity: -5, + maxCapacity: 500, + targetValue: 50.0, + scaleInCooldown: 80, + scaleOutCooldown: 60 + }), /minimumCapacity must be greater than or equal to 0; Provided value is: -5/); + test.done(); } + }; class TestApp {