Skip to content

Commit

Permalink
feat(neptune-alpha): support for Neptune serverless (aws#26445)
Browse files Browse the repository at this point in the history
Adds support for [Neptune serverless](https://docs.aws.amazon.com/neptune/latest/userguide/neptune-serverless-using.html).

Example of how to launch a Neptune serverless cluster:
```
new DatabaseCluster(stack, 'Database', {
  vpc,
  instanceType: InstanceType.SERVERLESS,
  clusterParameterGroup,
  removalPolicy: cdk.RemovalPolicy.DESTROY,
  serverlessScalingConfiguration: {
    minCapacity: 1,
    maxCapacity: 5,
  },
});
```

Closes aws#26428 

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
lpizzinidev authored and bmoffatt committed Jul 28, 2023
1 parent 45c1156 commit f1a54ec
Show file tree
Hide file tree
Showing 14 changed files with 1,976 additions and 0 deletions.
18 changes: 18 additions & 0 deletions packages/@aws-cdk/aws-neptune-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,21 @@ instance.metric('SparqlRequestsPerSec') // instance-level SparqlErrors metric
```

For more details on the available metrics, refer to https://docs.aws.amazon.com/neptune/latest/userguide/cw-metrics.html

## Neptune Serverless

You can configure a Neptune Serverless cluster using the dedicated instance type along with the
`serverlessScalingConfiguration` property.

> Visit [Using Amazon Neptune Serverless](https://docs.aws.amazon.com/neptune/latest/userguide/neptune-serverless-using.html) for more details.
```ts
const cluster = new neptune.DatabaseCluster(this, 'ServerlessDatabase', {
vpc,
instanceType: neptune.InstanceType.SERVERLESS,
serverlessScalingConfiguration: {
minCapacity: 1,
maxCapacity: 5,
},
});
```
41 changes: 41 additions & 0 deletions packages/@aws-cdk/aws-neptune-alpha/lib/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ export class LogType {
public constructor(public readonly value: string) {}
}

export interface ServerlessScalingConfiguration {
/**
* Minimum NCU capacity (min value 1)
*/
readonly minCapacity: number;

/**
* Maximum NCU capacity (min value 2.5 - max value 128)
*/
readonly maxCapacity: number;
}

/**
* Properties for a new database cluster
*/
Expand Down Expand Up @@ -297,6 +309,14 @@ export interface DatabaseClusterProps {
* @default - a new role is created.
*/
readonly cloudwatchLogsRetentionRole?: iam.IRole;

/**
* Specify minimum and maximum NCUs capacity for a serverless cluster.
* See https://docs.aws.amazon.com/neptune/latest/userguide/neptune-serverless-capacity-scaling.html
*
* @default - required if instanceType is db.serverless
*/
readonly serverlessScalingConfiguration?: ServerlessScalingConfiguration;
}

/**
Expand Down Expand Up @@ -565,6 +585,12 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu

this.enableIamAuthentication = props.iamAuthentication;

if (props.instanceType === InstanceType.SERVERLESS && !props.serverlessScalingConfiguration) {
throw new Error('You need to specify a serverless scaling configuration with a db.serverless instance type.');
}

this.validateServerlessScalingConfiguration(props.serverlessScalingConfiguration);

// Create the Neptune cluster
const cluster = new CfnDBCluster(this, 'Resource', {
// Basic
Expand All @@ -585,6 +611,7 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu
// CloudWatch Logs exports
enableCloudwatchLogsExports: props.cloudwatchLogsExports?.map(logType => logType.value),
storageEncrypted,
serverlessScalingConfiguration: props.serverlessScalingConfiguration,
});

cluster.applyRemovalPolicy(props.removalPolicy, {
Expand Down Expand Up @@ -649,4 +676,18 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu
securityGroups: securityGroups,
});
}

private validateServerlessScalingConfiguration(serverlessScalingConfiguration?: ServerlessScalingConfiguration) {
if (!serverlessScalingConfiguration) return;
if (serverlessScalingConfiguration.minCapacity < 1) {
throw new Error(`ServerlessScalingConfiguration minCapacity must be greater or equal than 1, received ${serverlessScalingConfiguration.minCapacity}`);
}
if (serverlessScalingConfiguration.maxCapacity < 2.5 || serverlessScalingConfiguration.maxCapacity > 128) {
throw new Error(`ServerlessScalingConfiguration maxCapacity must be between 2.5 and 128, reveived ${serverlessScalingConfiguration.maxCapacity}`);
}
if (serverlessScalingConfiguration.minCapacity >= serverlessScalingConfiguration.maxCapacity) {
throw new Error(`ServerlessScalingConfiguration minCapacity ${serverlessScalingConfiguration.minCapacity} ` +
`must be less than serverlessScalingConfiguration maxCapacity ${serverlessScalingConfiguration.maxCapacity}`);
}
}
}
5 changes: 5 additions & 0 deletions packages/@aws-cdk/aws-neptune-alpha/lib/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ export class InstanceType {
*/
public static readonly T3_MEDIUM = InstanceType.of('db.t3.medium');

/**
* db.serverless
*/
public static readonly SERVERLESS = InstanceType.of('db.serverless');

/**
* Build an InstanceType from given string or token, such as CfnParameter.
*/
Expand Down
76 changes: 76 additions & 0 deletions packages/@aws-cdk/aws-neptune-alpha/test/cluster.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,82 @@ describe('DatabaseCluster', () => {
Threshold: 1,
});
});

test('should instantiate a serverless cluster', () => {
// GIVEN
const stack = testStack();
const vpc = new ec2.Vpc(stack, 'VPC');

// WHEN
new DatabaseCluster(stack, 'Database', {
vpc,
instanceType: InstanceType.SERVERLESS,
serverlessScalingConfiguration: {
minCapacity: 1,
maxCapacity: 10,
},
});

// THEN
Template.fromStack(stack).hasResource('AWS::Neptune::DBCluster', {
Properties: {
ServerlessScalingConfiguration: {
MinCapacity: 1,
MaxCapacity: 10,
},
},
});

Template.fromStack(stack).hasResourceProperties('AWS::Neptune::DBInstance', {
DBInstanceClass: 'db.serverless',
});
});

test('should validate serverlessScalingConfiguration', () => {
// GIVEN
const stack = testStack();
const vpc = new ec2.Vpc(stack, 'VPC');

expect(() => {
new DatabaseCluster(stack, 'Database0', {
vpc,
instanceType: InstanceType.SERVERLESS,
});
}).toThrow(/You need to specify a serverless scaling configuration with a db.serverless instance type./);

expect(() => {
new DatabaseCluster(stack, 'Database1', {
vpc,
instanceType: InstanceType.SERVERLESS,
serverlessScalingConfiguration: {
minCapacity: 0,
maxCapacity: 10,
},
});
}).toThrow(/ServerlessScalingConfiguration minCapacity must be greater or equal than 1, received 0/);

expect(() => {
new DatabaseCluster(stack, 'Database2', {
vpc,
instanceType: InstanceType.SERVERLESS,
serverlessScalingConfiguration: {
minCapacity: 1,
maxCapacity: 200,
},
});
}).toThrow(/ServerlessScalingConfiguration maxCapacity must be between 2.5 and 128, reveived 200/);

expect(() => {
new DatabaseCluster(stack, 'Database3', {
vpc,
instanceType: InstanceType.SERVERLESS,
serverlessScalingConfiguration: {
minCapacity: 10,
maxCapacity: 5,
},
});
}).toThrow(/ServerlessScalingConfiguration minCapacity 10 must be less than serverlessScalingConfiguration maxCapacity 5/);
});
});

function testStack() {
Expand Down
18 changes: 18 additions & 0 deletions packages/@aws-cdk/aws-neptune-alpha/test/instance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,24 @@ describe('DatabaseInstance', () => {
Threshold: 1,
});
});

test('should instantiate a serverless instance', () => {
// GIVEN
const stack = testStack();

// WHEN
new DatabaseInstance(stack, 'Instance', {
cluster: stack.cluster,
instanceType: InstanceType.SERVERLESS,
});

// THEN
Template.fromStack(stack).hasResource('AWS::Neptune::DBInstance', {
Properties: {
DBInstanceClass: 'db.serverless',
},
});
});
});

class TestStack extends cdk.Stack {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "32.0.0",
"files": {
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
"source": {
"path": "ClusterServerlessTestDefaultTestDeployAssert8C9220E6.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"Parameters": {
"BootstrapVersion": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/cdk-bootstrap/hnb659fds/version",
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
}
},
"Rules": {
"CheckBootstrapVersion": {
"Assertions": [
{
"Assert": {
"Fn::Not": [
{
"Fn::Contains": [
[
"1",
"2",
"3",
"4",
"5"
],
{
"Ref": "BootstrapVersion"
}
]
}
]
},
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "32.0.0",
"files": {
"2d99c86b451354d15c576c59505ff89003cb23ed1c48a33b0b57bf3eee545c16": {
"source": {
"path": "aws-cdk-neptune-serverless-integ.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "2d99c86b451354d15c576c59505ff89003cb23ed1c48a33b0b57bf3eee545c16.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Loading

0 comments on commit f1a54ec

Please sign in to comment.