diff --git a/packages/@aws-cdk/aws-neptune/README.md b/packages/@aws-cdk/aws-neptune/README.md index bb1c45dbcfd55..a2c1d5ebcdd01 100644 --- a/packages/@aws-cdk/aws-neptune/README.md +++ b/packages/@aws-cdk/aws-neptune/README.md @@ -121,7 +121,7 @@ const cluster = new neptune.DatabaseCluster(this, 'Database', { }); ``` -Additionally it is also possible to add replicas using `DatabaseInstance` for an existing cluster. +Additionally, it is also possible to add replicas using `DatabaseInstance` for an existing cluster. ```ts fixture=with-cluster const replica1 = new neptune.DatabaseInstance(this, 'Instance', { @@ -143,3 +143,14 @@ new neptune.DatabaseCluster(this, 'Cluster', { autoMinorVersionUpgrade: true, }); ``` + +## Metrics + +Both `DatabaseCluster` and `DatabaseInstance` provide a `metric()` method to help with cluster-level and instance-level monitoring. + +```ts +cluster.metric('SparqlErrors'); // cluster-level SparqlErrors metric +instance.metric('SparqlErrors') // instance-level SparqlErrors metric +``` + +For more details on the available metrics, refer to https://docs.aws.amazon.com/neptune/latest/userguide/cw-metrics.html diff --git a/packages/@aws-cdk/aws-neptune/lib/cluster.ts b/packages/@aws-cdk/aws-neptune/lib/cluster.ts index f30bf46851f0f..f2e998332c304 100644 --- a/packages/@aws-cdk/aws-neptune/lib/cluster.ts +++ b/packages/@aws-cdk/aws-neptune/lib/cluster.ts @@ -1,3 +1,4 @@ +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; @@ -284,6 +285,14 @@ export interface IDatabaseCluster extends IResource, ec2.IConnectable { * Grant the given identity connection access to the database. */ grantConnect(grantee: iam.IGrantable): iam.Grant; + + /** + * Return the given named metric associated with this DatabaseCluster instance + * + * @see https://docs.aws.amazon.com/neptune/latest/userguide/cw-metrics.html + * @see https://docs.aws.amazon.com/neptune/latest/userguide/cw-dimensions.html + */ + metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric; } /** @@ -398,6 +407,17 @@ export abstract class DatabaseClusterBase extends Resource implements IDatabaseC public grantConnect(grantee: iam.IGrantable): iam.Grant { return this.grant(grantee, 'neptune-db:*'); } + + public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return new cloudwatch.Metric({ + namespace: 'AWS/Neptune', + dimensionsMap: { + DBClusterIdentifier: this.clusterIdentifier, + }, + metricName, + ...props, + }); + } } /** diff --git a/packages/@aws-cdk/aws-neptune/lib/instance.ts b/packages/@aws-cdk/aws-neptune/lib/instance.ts index b05003dffcb40..336606c6b9386 100644 --- a/packages/@aws-cdk/aws-neptune/lib/instance.ts +++ b/packages/@aws-cdk/aws-neptune/lib/instance.ts @@ -1,3 +1,4 @@ +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; @@ -165,6 +166,14 @@ export interface IDatabaseInstance extends cdk.IResource { * @attribute Port */ readonly dbInstanceEndpointPort: string; + + /** + * Return the given named metric associated with this database instance + * + * @see https://docs.aws.amazon.com/neptune/latest/userguide/cw-metrics.html + * @see https://docs.aws.amazon.com/neptune/latest/userguide/cw-dimensions.html + */ + metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric; } /** @@ -233,27 +242,64 @@ export interface DatabaseInstanceProps { } /** - * A database instance - * - * @resource AWS::Neptune::DBInstance + * A new or imported database instance. */ -export class DatabaseInstance extends cdk.Resource implements IDatabaseInstance { - +export abstract class DatabaseInstanceBase extends cdk.Resource implements IDatabaseInstance { /** * Import an existing database instance. */ public static fromDatabaseInstanceAttributes(scope: Construct, id: string, attrs: DatabaseInstanceAttributes): IDatabaseInstance { - class Import extends cdk.Resource implements IDatabaseInstance { + class Import extends DatabaseInstanceBase implements IDatabaseInstance { public readonly defaultPort = ec2.Port.tcp(attrs.port); public readonly instanceIdentifier = attrs.instanceIdentifier; public readonly dbInstanceEndpointAddress = attrs.instanceEndpointAddress; public readonly dbInstanceEndpointPort = attrs.port.toString(); public readonly instanceEndpoint = new Endpoint(attrs.instanceEndpointAddress, attrs.port); } - return new Import(scope, id); } + /** + * @inheritdoc + */ + public abstract readonly dbInstanceEndpointAddress: string; + + /** + * @inheritdoc + */ + public abstract readonly dbInstanceEndpointPort: string; + + /** + * @inheritdoc + */ + public abstract readonly instanceEndpoint: Endpoint; + + /** + * @inheritdoc + */ + public abstract readonly instanceIdentifier: string; + + /** + * @inheritdoc + */ + public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return new cloudwatch.Metric({ + namespace: 'AWS/Neptune', + dimensionsMap: { + DBInstanceIdentifier: this.instanceIdentifier, + }, + metricName, + ...props, + }); + } +} + +/** + * A database instance + * + * @resource AWS::Neptune::DBInstance + */ +export class DatabaseInstance extends DatabaseInstanceBase implements IDatabaseInstance { /** * The instance's database cluster diff --git a/packages/@aws-cdk/aws-neptune/package.json b/packages/@aws-cdk/aws-neptune/package.json index f1fc53edc893c..6b38c7760a3bf 100644 --- a/packages/@aws-cdk/aws-neptune/package.json +++ b/packages/@aws-cdk/aws-neptune/package.json @@ -90,6 +90,7 @@ "@types/jest": "^27.5.2" }, "dependencies": { + "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kms": "0.0.0", @@ -97,6 +98,7 @@ "constructs": "^10.0.0" }, "peerDependencies": { + "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kms": "0.0.0", diff --git a/packages/@aws-cdk/aws-neptune/test/cluster.test.ts b/packages/@aws-cdk/aws-neptune/test/cluster.test.ts index e92386cfefcb0..54b2ed415f131 100644 --- a/packages/@aws-cdk/aws-neptune/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-neptune/test/cluster.test.ts @@ -1,4 +1,5 @@ import { Match, Template } from '@aws-cdk/assertions'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; @@ -660,6 +661,46 @@ describe('DatabaseCluster', () => { }); + test('metric - constructs metric with correct namespace and dimension and inputs', () => { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new DatabaseCluster(stack, 'Cluster', { + vpc, + instanceType: InstanceType.R5_LARGE, + }); + + // WHEN + const metric = cluster.metric('SparqlErrors'); + new cloudwatch.Alarm(stack, 'Alarm', { + evaluationPeriods: 1, + threshold: 0, + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, + metric: metric, + }); + + // THEN + expect(metric).toEqual(new cloudwatch.Metric({ + namespace: 'AWS/Neptune', + dimensionsMap: { + DBClusterIdentifier: cluster.clusterIdentifier, + }, + metricName: 'SparqlErrors', + })); + Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { + Namespace: 'AWS/Neptune', + MetricName: 'SparqlErrors', + Dimensions: [ + { + Name: 'DBClusterIdentifier', + Value: stack.resolve(cluster.clusterIdentifier), + }, + ], + ComparisonOperator: 'GreaterThanThreshold', + EvaluationPeriods: 1, + Threshold: 0, + }); + }); }); function testStack() { diff --git a/packages/@aws-cdk/aws-neptune/test/instance.test.ts b/packages/@aws-cdk/aws-neptune/test/instance.test.ts index ed83e1506496a..49e9e01f593c3 100644 --- a/packages/@aws-cdk/aws-neptune/test/instance.test.ts +++ b/packages/@aws-cdk/aws-neptune/test/instance.test.ts @@ -1,4 +1,5 @@ import { Template } from '@aws-cdk/assertions'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; @@ -140,6 +141,46 @@ describe('DatabaseInstance', () => { test('instance type from string throws if missing db prefix', () => { expect(() => { InstanceType.of('r5.xlarge');}).toThrowError(/instance type must start with 'db.'/); }); + + test('metric - constructs metric with correct namespace and dimension and inputs', () => { + // GIVEN + const stack = testStack(); + const instance = new DatabaseInstance(stack, 'Instance', { + cluster: stack.cluster, + instanceType: InstanceType.R5_LARGE, + }); + + // WHEN + const metric = instance.metric('SparqlErrors'); + new cloudwatch.Alarm(stack, 'Alarm', { + evaluationPeriods: 1, + threshold: 0, + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, + metric: metric, + }); + + // THEN + expect(metric).toEqual(new cloudwatch.Metric({ + namespace: 'AWS/Neptune', + dimensionsMap: { + DBInstanceIdentifier: instance.instanceIdentifier, + }, + metricName: 'SparqlErrors', + })); + Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { + Namespace: 'AWS/Neptune', + MetricName: 'SparqlErrors', + Dimensions: [ + { + Name: 'DBInstanceIdentifier', + Value: stack.resolve(instance.instanceIdentifier), + }, + ], + ComparisonOperator: 'GreaterThanThreshold', + EvaluationPeriods: 1, + Threshold: 0, + }); + }); }); class TestStack extends cdk.Stack { @@ -160,6 +201,5 @@ class TestStack extends cdk.Stack { } function testStack() { - const stack = new TestStack(undefined, undefined, { env: { account: '12345', region: 'us-test-1' } }); - return stack; + return new TestStack(undefined, undefined, { env: { account: '12345', region: 'us-test-1' } }); }