Skip to content

Commit

Permalink
feat(rds): add support for monitoring to database cluster
Browse files Browse the repository at this point in the history
Lets a user specify a monitoring interval period. This change will also
auto-generate a valid Role to manage DB instances monitoring.

This change adds an optional prop `monitoringIntervalSec`

closes #2826

BREAKING CHANGE:
* rds: `monitoringInterval` in `DatabaseInstanceNewProps` has been renamed to `monitoringIntervalSec`
  • Loading branch information
rpanfili committed Jun 20, 2019
1 parent 5baa31f commit 6e626c5
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 15 deletions.
40 changes: 31 additions & 9 deletions packages/@aws-cdk/aws-rds/lib/cluster.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import ec2 = require('@aws-cdk/aws-ec2');
import { ManagedPolicy, Role, ServicePrincipal } from '@aws-cdk/aws-iam';
import kms = require('@aws-cdk/aws-kms');
import secretsmanager = require('@aws-cdk/aws-secretsmanager');
import { Construct, RemovalPolicy, Resource, Token } from '@aws-cdk/cdk';
import { Construct, Duration, RemovalPolicy, Resource, Token } from '@aws-cdk/cdk';
import { DatabaseClusterAttributes, IDatabaseCluster } from './cluster-ref';
import { DatabaseSecret } from './database-secret';
import { Endpoint } from './endpoint';
Expand Down Expand Up @@ -128,6 +129,14 @@ export interface DatabaseClusterProps {
* @default - Retain cluster.
*/
readonly removalPolicy?: RemovalPolicy

/**
* The interval, in seconds, between points when Amazon RDS collects enhanced
* monitoring metrics for the DB instances.
*
* @default no enhanced monitoring
*/
readonly monitoringInterval?: Duration;
}

/**
Expand Down Expand Up @@ -280,10 +289,10 @@ export class DatabaseCluster extends DatabaseClusterBase {
});

const securityGroup = props.instanceProps.securityGroup !== undefined ?
props.instanceProps.securityGroup : new ec2.SecurityGroup(this, 'SecurityGroup', {
description: 'RDS security group',
vpc: props.instanceProps.vpc
});
props.instanceProps.securityGroup : new ec2.SecurityGroup(this, 'SecurityGroup', {
description: 'RDS security group',
vpc: props.instanceProps.vpc
});
this.securityGroupId = securityGroup.securityGroupId;

let secret;
Expand All @@ -310,8 +319,8 @@ export class DatabaseCluster extends DatabaseClusterBase {
masterUserPassword: secret
? secret.secretValueFromJson('password').toString()
: (props.masterUser.password
? props.masterUser.password.toString()
: undefined),
? props.masterUser.password.toString()
: undefined),
backupRetentionPeriod: props.backup && props.backup.retention && props.backup.retention.toDays(),
preferredBackupWindow: props.backup && props.backup.preferredWindow,
preferredMaintenanceWindow: props.preferredMaintenanceWindow,
Expand Down Expand Up @@ -345,12 +354,23 @@ export class DatabaseCluster extends DatabaseClusterBase {

// Get the actual subnet objects so we can depend on internet connectivity.
const internetConnected = props.instanceProps.vpc.selectSubnets(props.instanceProps.vpcSubnets).internetConnectedDependency;

let monitoringRole;
if (props.monitoringInterval && props.monitoringInterval.toSeconds()) {
monitoringRole = new Role(this, "MonitoringRole", {
assumedBy: new ServicePrincipal("monitoring.rds.amazonaws.com"),
managedPolicies: [
ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonRDSEnhancedMonitoringRole')
]
});
}

for (let i = 0; i < instanceCount; i++) {
const instanceIndex = i + 1;

const instanceIdentifier = props.instanceIdentifierBase != null ? `${props.instanceIdentifierBase}${instanceIndex}` :
props.clusterIdentifier != null ? `${props.clusterIdentifier}instance${instanceIndex}` :
undefined;
props.clusterIdentifier != null ? `${props.clusterIdentifier}instance${instanceIndex}` :
undefined;

const publiclyAccessible = props.instanceProps.vpcSubnets && props.instanceProps.vpcSubnets.subnetType === ec2.SubnetType.PUBLIC;

Expand All @@ -366,6 +386,8 @@ export class DatabaseCluster extends DatabaseClusterBase {
// This is already set on the Cluster. Unclear to me whether it should be repeated or not. Better yes.
dbSubnetGroupName: subnetGroup.refAsString,
dbParameterGroupName: props.instanceProps.parameterGroup && props.instanceProps.parameterGroup.parameterGroupName,
monitoringInterval: props.monitoringInterval && props.monitoringInterval.toSeconds(),
monitoringRoleArn: monitoringRole && monitoringRole.roleArn
});

instance.applyRemovalPolicy(props.removalPolicy, {
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-rds/lib/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import secretsmanager = require('@aws-cdk/aws-secretsmanager');
import { Construct, Duration, IResource, RemovalPolicy, Resource, SecretValue, Stack, Token } from '@aws-cdk/cdk';
import { DatabaseSecret } from './database-secret';
import { Endpoint } from './endpoint';
import { IOptionGroup} from './option-group';
import { IOptionGroup } from './option-group';
import { IParameterGroup } from './parameter-group';
import { DatabaseClusterEngine } from './props';
import { CfnDBInstance, CfnDBInstanceProps, CfnDBSubnetGroup } from './rds.generated';
Expand Down Expand Up @@ -484,7 +484,7 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData
this.securityGroupId = this.securityGroup.securityGroupId;

let monitoringRole;
if (props.monitoringInterval) {
if (props.monitoringInterval && props.monitoringInterval.toSeconds()) {
monitoringRole = new iam.Role(this, 'MonitoringRole', {
assumedBy: new iam.ServicePrincipal('monitoring.rds.amazonaws.com'),
managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonRDSEnhancedMonitoringRole')],
Expand Down
67 changes: 63 additions & 4 deletions packages/@aws-cdk/aws-rds/test/test.cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export = {
DBSubnetGroupName: { Ref: "DatabaseSubnets56F17B9A" },
MasterUsername: "admin",
MasterUserPassword: "tooshort",
VpcSecurityGroupIds: [ {"Fn::GetAtt": ["DatabaseSecurityGroup5C91FDCB", "GroupId"]}]
VpcSecurityGroupIds: [{ "Fn::GetAtt": ["DatabaseSecurityGroup5C91FDCB", "GroupId"] }]
},
DeletionPolicy: 'Retain',
UpdateReplacePolicy: 'Retain'
Expand Down Expand Up @@ -70,7 +70,7 @@ export = {
DBSubnetGroupName: { Ref: "DatabaseSubnets56F17B9A" },
MasterUsername: "admin",
MasterUserPassword: "tooshort",
VpcSecurityGroupIds: [ {"Fn::GetAtt": ["DatabaseSecurityGroup5C91FDCB", "GroupId"]}]
VpcSecurityGroupIds: [{ "Fn::GetAtt": ["DatabaseSecurityGroup5C91FDCB", "GroupId"] }]
}));

test.done();
Expand Down Expand Up @@ -105,7 +105,7 @@ export = {
DBSubnetGroupName: { Ref: "DatabaseSubnets56F17B9A" },
MasterUsername: "admin",
MasterUserPassword: "tooshort",
VpcSecurityGroupIds: [ "SecurityGroupId12345" ]
VpcSecurityGroupIds: ["SecurityGroupId12345"]
}));

test.done();
Expand Down Expand Up @@ -318,12 +318,71 @@ export = {
EngineVersion: "10.7",
}));

test.done();
},

"cluster with enabled monitoring"(test: Test) {
// GIVEN
const stack = testStack();
const vpc = new ec2.Vpc(stack, "VPC");

// WHEN
new DatabaseCluster(stack, "Database", {
engine: DatabaseClusterEngine.Aurora,
instances: 1,
masterUser: {
username: "admin"
},
instanceProps: {
instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL),
vpc
},
monitoringInterval: cdk.Duration.minutes(1),
});

// THEN
expect(stack).to(haveResource("AWS::RDS::DBInstance", {
MonitoringInterval: 60,
MonitoringRoleArn: {
"Fn::GetAtt": ["DatabaseMonitoringRole576991DA", "Arn"]
}
}, ResourcePart.Properties));

expect(stack).to(haveResource("AWS::IAM::Role", {
AssumeRolePolicyDocument: {
Statement: [
{
Action: "sts:AssumeRole",
Effect: "Allow",
Principal: {
Service: "monitoring.rds.amazonaws.com"
}
}
],
Version: "2012-10-17"
},
ManagedPolicyArns: [
{
"Fn::Join": [
"",
[
"arn:",
{
Ref: "AWS::Partition"
},
":iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole"
]
]
}
]
}));

test.done();
}
};

function testStack() {
const stack = new cdk.Stack(undefined, undefined, { env: { account: '12345', region: 'us-test-1' }});
const stack = new cdk.Stack(undefined, undefined, { env: { account: '12345', region: 'us-test-1' } });
stack.node.setContext('availability-zones:12345:us-test-1', ['us-test-1a', 'us-test-1b']);
return stack;
}

0 comments on commit 6e626c5

Please sign in to comment.