Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dynamodb): custom timeout for replication operation #13354

Merged
merged 5 commits into from
Mar 8, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions packages/@aws-cdk/aws-dynamodb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,17 @@ globalTable.autoScaleWriteCapacity({
}).scaleOnUtilization({ targetUtilizationPercent: 75 });
```

When adding a replica region for a large table, you might want to increase the
timeout for the replication operation:

```ts
const globalTable = new dynamodb.Table(this, 'Table', {
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
replicationRegions: ['us-east-1', 'us-east-2', 'us-west-2'],
replicationTimeout: Duration.hours(2), // defaults to Duration.minutes(30)
});
```

## Encryption

All user data stored in Amazon DynamoDB is fully encrypted at rest. When creating a new table, you can choose to encrypt using the following customer master keys (CMK) to encrypt your table:
Expand Down
19 changes: 16 additions & 3 deletions packages/@aws-cdk/aws-dynamodb/lib/replica-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,26 @@ import { Construct } from 'constructs';
// eslint-disable-next-line no-duplicate-imports, import/order
import { Construct as CoreConstruct } from '@aws-cdk/core';

/**
* Properties for a ReplicaProvider
*/
export interface ReplicaProviderProps {
/**
* The timeout for the replication operation.
*
* @default Duration.minutes(30)
*/
readonly timeout?: Duration;
}

export class ReplicaProvider extends NestedStack {
/**
* Creates a stack-singleton resource provider nested stack.
*/
public static getOrCreate(scope: Construct) {
public static getOrCreate(scope: Construct, props: ReplicaProviderProps = {}) {
const stack = Stack.of(scope);
const uid = '@aws-cdk/aws-dynamodb.ReplicaProvider';
return stack.node.tryFindChild(uid) as ReplicaProvider || new ReplicaProvider(stack, uid);
return stack.node.tryFindChild(uid) as ReplicaProvider ?? new ReplicaProvider(stack, uid, props);
}

/**
Expand All @@ -34,7 +46,7 @@ export class ReplicaProvider extends NestedStack {
*/
public readonly isCompleteHandler: lambda.Function;

private constructor(scope: Construct, id: string) {
private constructor(scope: Construct, id: string, props: ReplicaProviderProps = {}) {
super(scope as CoreConstruct, id);

const code = lambda.Code.fromAsset(path.join(__dirname, 'replica-handler'));
Expand Down Expand Up @@ -80,6 +92,7 @@ export class ReplicaProvider extends NestedStack {
onEventHandler: this.onEventHandler,
isCompleteHandler: this.isCompleteHandler,
queryInterval: Duration.seconds(10),
totalTimeout: props.timeout,
});
}
}
17 changes: 12 additions & 5 deletions packages/@aws-cdk/aws-dynamodb/lib/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
import * as iam from '@aws-cdk/aws-iam';
import * as kms from '@aws-cdk/aws-kms';
import {
Aws, CfnCondition, CfnCustomResource, CustomResource, Fn,
IResource, Lazy, Names, RemovalPolicy, Resource, Stack, Token,
Aws, CfnCondition, CfnCustomResource, CustomResource, Duration,
Fn, IResource, Lazy, Names, RemovalPolicy, Resource, Stack, Token,
} from '@aws-cdk/core';
import { Construct } from 'constructs';
import { DynamoDBMetrics } from './dynamodb-canned-metrics.generated';
Expand Down Expand Up @@ -218,6 +218,13 @@ export interface TableOptions {
* @experimental
*/
readonly replicationRegions?: string[];

/**
* The timeout for a table replication operation in a single region.
*
* @default Duration.minutes(30)
*/
readonly replicationTimeout?: Duration;
}

/**
Expand Down Expand Up @@ -1135,7 +1142,7 @@ export class Table extends TableBase {
}

if (props.replicationRegions && props.replicationRegions.length > 0) {
this.createReplicaTables(props.replicationRegions);
this.createReplicaTables(props.replicationRegions, props.replicationTimeout);
}
}

Expand Down Expand Up @@ -1451,14 +1458,14 @@ export class Table extends TableBase {
*
* @param regions regions where to create tables
*/
private createReplicaTables(regions: string[]) {
private createReplicaTables(regions: string[], timeout?: Duration) {
const stack = Stack.of(this);

if (!Token.isUnresolved(stack.region) && regions.includes(stack.region)) {
throw new Error('`replicationRegions` cannot include the region where this stack is deployed.');
}

const provider = ReplicaProvider.getOrCreate(this);
const provider = ReplicaProvider.getOrCreate(this, { timeout });

// Documentation at https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/V2gt_IAM.html
// is currently incorrect. AWS Support recommends `dynamodb:*` in both source and destination regions
Expand Down
35 changes: 23 additions & 12 deletions packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as appscaling from '@aws-cdk/aws-applicationautoscaling';
import * as iam from '@aws-cdk/aws-iam';
import * as kms from '@aws-cdk/aws-kms';
import { App, Aws, CfnDeletionPolicy, ConstructNode, Duration, PhysicalName, RemovalPolicy, Resource, Stack, Tags } from '@aws-cdk/core';
import * as cr from '@aws-cdk/custom-resources';
import { testLegacyBehavior } from 'cdk-build-tools/lib/feature-flag';
import { Construct } from 'constructs';
import {
Expand All @@ -20,6 +21,8 @@ import {
CfnTable,
} from '../lib';

jest.mock('@aws-cdk/custom-resources');

/* eslint-disable quote-props */

// CDK parameters
Expand Down Expand Up @@ -2295,12 +2298,6 @@ describe('global', () => {
// THEN
expect(stack).toHaveResource('Custom::DynamoDBReplica', {
Properties: {
ServiceToken: {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed because @aws-cdk/custom-resources is now mocked.

'Fn::GetAtt': [
'awscdkawsdynamodbReplicaProviderNestedStackawscdkawsdynamodbReplicaProviderNestedStackResource18E3F12D',
'Outputs.awscdkawsdynamodbReplicaProviderframeworkonEventF9504691Arn',
],
},
TableName: {
Ref: 'TableCD117FA1',
},
Expand All @@ -2311,12 +2308,6 @@ describe('global', () => {

expect(stack).toHaveResource('Custom::DynamoDBReplica', {
Properties: {
ServiceToken: {
'Fn::GetAtt': [
'awscdkawsdynamodbReplicaProviderNestedStackawscdkawsdynamodbReplicaProviderNestedStackResource18E3F12D',
'Outputs.awscdkawsdynamodbReplicaProviderframeworkonEventF9504691Arn',
],
},
TableName: {
Ref: 'TableCD117FA1',
},
Expand Down Expand Up @@ -2814,6 +2805,26 @@ describe('global', () => {
// THEN
expect(SynthUtils.toCloudFormation(stack).Conditions).toBeUndefined();
});

test('can configure timeout', () => {
// GIVEN
const stack = new Stack();

// WHEN
new Table(stack, 'Table', {
partitionKey: {
name: 'id',
type: AttributeType.STRING,
},
replicationRegions: ['eu-central-1'],
replicationTimeout: Duration.hours(1),
});

// THEN
expect(cr.Provider).toHaveBeenCalledWith(expect.anything(), expect.any(String), expect.objectContaining({
totalTimeout: Duration.hours(1),
}));
});
});

test('L1 inside L2 expects removalpolicy to have been set', () => {
Expand Down