diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.serverless-cluster.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.serverless-cluster.ts index e93cc8bbf5f95..21c1f4163a9ea 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.serverless-cluster.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/integ.serverless-cluster.ts @@ -14,6 +14,7 @@ const subnetGroup = new rds.SubnetGroup(stack, 'SubnetGroup', { vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, description: 'My Subnet Group', subnetGroupName: 'MyNotLowerCaseSubnetGroupName', + }); const cluster = new rds.ServerlessCluster(stack, 'Serverless Database', { @@ -26,6 +27,10 @@ const cluster = new rds.ServerlessCluster(stack, 'Serverless Database', { vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, subnetGroup, removalPolicy: cdk.RemovalPolicy.DESTROY, + scaling: { + secondsBeforeTimeout: cdk.Duration.seconds(300), + timeoutAction: String('RollbackCapacityChange'), + }, }); cluster.connections.allowDefaultPortFromAnyIpv4('Open to the world'); @@ -40,6 +45,10 @@ const noCopyTagsCluster = new rds.ServerlessCluster(stack, 'Serverless Database subnetGroup, removalPolicy: cdk.RemovalPolicy.DESTROY, copyTagsToSnapshot: false, + scaling: { + secondsBeforeTimeout: cdk.Duration.seconds(300), + timeoutAction: String('RollbackCapacityChange'), + }, }); noCopyTagsCluster.connections.allowDefaultPortFromAnyIpv4('Open to the world'); diff --git a/packages/aws-cdk-lib/aws-rds/README.md b/packages/aws-cdk-lib/aws-rds/README.md index 3833ff16eaa3e..177abb6ca6bd7 100644 --- a/packages/aws-cdk-lib/aws-rds/README.md +++ b/packages/aws-cdk-lib/aws-rds/README.md @@ -1013,6 +1013,8 @@ const cluster = new rds.ServerlessCluster(this, 'AnotherCluster', { autoPause: Duration.minutes(10), // default is to pause after 5 minutes of idle time minCapacity: rds.AuroraCapacityUnit.ACU_8, // default is 2 Aurora capacity units (ACUs) maxCapacity: rds.AuroraCapacityUnit.ACU_32, // default is 16 Aurora capacity units (ACUs) + secondsBeforeTimeout: Duration.seconds(300), // default is 300 seconds + timeoutAction: string("RollbackCapacityChange") // default is RollbackCapacityChange } }); ``` diff --git a/packages/aws-cdk-lib/aws-rds/lib/serverless-cluster.ts b/packages/aws-cdk-lib/aws-rds/lib/serverless-cluster.ts index ca2662b84e3ed..c5b2f057e3e9c 100644 --- a/packages/aws-cdk-lib/aws-rds/lib/serverless-cluster.ts +++ b/packages/aws-cdk-lib/aws-rds/lib/serverless-cluster.ts @@ -272,6 +272,20 @@ export interface ServerlessScalingOptions { * @default - automatic pause enabled after 5 minutes */ readonly autoPause?: Duration; + + /** + * The amount of time, in seconds, that Aurora Serverless v1 tries to find a scaling point to perform seamless scaling before enforcing the timeout action. + * @default - automatic timeout after + */ + readonly secondsBeforeTimeout? : Duration; + + /** + * The action to take when the timeout is reached, either ForceApplyCapacityChange or RollbackCapacityChange. + * ForceApplyCapacityChange sets the capacity to the specified value as soon as possible. + * RollbackCapacityChange, the default, ignores the capacity change if a scaling point isn't found in the timeout period. + * @default - RollbackCapacityChange + */ + readonly timeoutAction? : string; } /** @@ -441,9 +455,24 @@ abstract class ServerlessClusterNew extends ServerlessClusterBase { }); } + // valid timeout + private timeoutValidation(timeout: number | undefined, timeoutAction?: string | undefined): void { + if (timeout && (timeout < 60 || timeout > 600)) { + throw Error('seconds before timeout must be between 60 and 600 seconds.'); + } + + if (timeoutAction && (timeoutAction !== 'ForceApplyCapacityChange' && timeoutAction !== 'RollbackCapacityChange')) { + throw Error('timeout action must be ForceApplyCapacityChange or RollbackCapacityChange.'); + } + } + private renderScalingConfiguration(options: ServerlessScalingOptions): CfnDBCluster.ScalingConfigurationProperty { const minCapacity = options.minCapacity; const maxCapacity = options.maxCapacity; + const timeout = options.secondsBeforeTimeout?.toSeconds(); + const timeoutAction = options.timeoutAction; + + this.timeoutValidation(timeout, timeoutAction); if (minCapacity && maxCapacity && minCapacity > maxCapacity) { throw new Error('maximum capacity must be greater than or equal to minimum capacity.'); @@ -458,6 +487,8 @@ abstract class ServerlessClusterNew extends ServerlessClusterBase { autoPause: (secondsToAutoPause === 0) ? false : true, minCapacity: options.minCapacity, maxCapacity: options.maxCapacity, + secondsBeforeTimeout: (timeout === 300) ? undefined : timeout, + timeoutAction: options.timeoutAction, secondsUntilAutoPause: (secondsToAutoPause === 0) ? undefined : secondsToAutoPause, }; } diff --git a/packages/aws-cdk-lib/aws-rds/test/serverless-cluster.test.ts b/packages/aws-cdk-lib/aws-rds/test/serverless-cluster.test.ts index cae4e4a84b886..be0e2bbff17ab 100644 --- a/packages/aws-cdk-lib/aws-rds/test/serverless-cluster.test.ts +++ b/packages/aws-cdk-lib/aws-rds/test/serverless-cluster.test.ts @@ -462,6 +462,8 @@ describe('serverless cluster', () => { minCapacity: AuroraCapacityUnit.ACU_1, maxCapacity: AuroraCapacityUnit.ACU_128, autoPause: cdk.Duration.minutes(10), + secondsBeforeTimeout: cdk.Duration.seconds(0), + timeoutAction: String('RollbackCapacityChange'), }, }); @@ -472,6 +474,8 @@ describe('serverless cluster', () => { MaxCapacity: 128, MinCapacity: 1, SecondsUntilAutoPause: 600, + SecondsBeforeTimeout: 0, + TimeoutAction: 'RollbackCapacityChange', }, }); }); @@ -536,6 +540,37 @@ describe('serverless cluster', () => { }); }); + test('throws when invalid seconds before time out is specified', () => { + // GIVEN + const stack = testStack(); + const vpc = ec2.Vpc.fromLookup(stack, 'VPC', { isDefault: true }); + + expect(() => + new ServerlessCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA_MYSQL, + vpc, + scaling: { + secondsBeforeTimeout: cdk.Duration.seconds(650), + }, + })).toThrow(/seconds before timeout must be between 60 and 600 seconds./); + }); + + test('throws when invalid time out action set', () => { + // GIVEN + const stack = testStack(); + const vpc = ec2.Vpc.fromLookup(stack, 'VPC', { isDefault: true }); + + // WHEN + expect(() => + new ServerlessCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA_MYSQL, + vpc, + scaling: { + timeoutAction: String('helloworld'), + }, + })).toThrow(/timeout action must be ForceApplyCapacityChange or RollbackCapacityChange./); + }); + test('throws when invalid auto pause time is specified', () => { // GIVEN const stack = testStack(); @@ -942,3 +977,4 @@ function testStack(app?: cdk.App, id?: string): cdk.Stack { stack.node.setContext('availability-zones:12345:us-test-1', ['us-test-1a', 'us-test-1b']); return stack; } +