diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 4cda8dcbc4cfa..26a04b5a6d5df 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -341,3 +341,15 @@ vpc.addVpnConnection('Dynamic', { ``` Routes will be propagated on the route tables associated with the private subnets. + +VPN connections expose [metrics (cloudwatch.Metric)](https://github.com/awslabs/aws-cdk/blob/master/packages/%40aws-cdk/aws-cloudwatch/README.md) across all tunnels in the account/region and per connection: +```ts +// Across all tunnels in the account/region +const allDataOut = VpnConnection.metricAllTunnelDataOut(); + +// For a specific vpn connection +const vpnConnection = vpc.addVpnConnection('Dynamic', { + ip: '1.2.3.4' +}); +const state = vpnConnection.metricTunnelState(); +``` diff --git a/packages/@aws-cdk/aws-ec2/lib/index.ts b/packages/@aws-cdk/aws-ec2/lib/index.ts index 1188dff779727..fdfe81aa9b97a 100644 --- a/packages/@aws-cdk/aws-ec2/lib/index.ts +++ b/packages/@aws-cdk/aws-ec2/lib/index.ts @@ -10,3 +10,5 @@ export * from './vpn'; // AWS::EC2 CloudFormation Resources: export * from './ec2.generated'; + +import './ec2-augmentations.generated'; diff --git a/packages/@aws-cdk/aws-ec2/lib/vpn.ts b/packages/@aws-cdk/aws-ec2/lib/vpn.ts index bab383c4b8aee..bed2b9cdc5d98 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpn.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpn.ts @@ -1,3 +1,4 @@ +import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import cdk = require('@aws-cdk/cdk'); import net = require('net'); import { CfnCustomerGateway, CfnVPNConnection, CfnVPNConnectionRoute } from './ec2.generated'; @@ -98,6 +99,44 @@ export enum VpnConnectionType { } export class VpnConnection extends cdk.Construct implements IVpnConnection { + /** + * Return the given named metric for all VPN connections in the account/region. + */ + public static metricAll(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return new cloudwatch.Metric({ + namespace: 'AWS/VPN', + metricName, + ...props + }); + } + + /** + * Metric for the tunnel state of all VPN connections in the account/region. + * + * @default average over 5 minutes + */ + public static metricAllTunnelState(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metricAll('TunnelSate', { statistic: 'avg', ...props }); + } + + /** + * Metric for the tunnel data in of all VPN connections in the account/region. + * + * @default sum over 5 minutes + */ + public static metricAllTunnelDataIn(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metricAll('TunnelDataIn', { statistic: 'sum', ...props }); + } + + /** + * Metric for the tunnel data out of all VPN connections. + * + * @default sum over 5 minutes + */ + public static metricAllTunnelDataOut(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metricAll('TunnelDataOut', { statistic: 'sum', ...props }); + } + public readonly vpnId: string; public readonly customerGatewayId: string; public readonly customerGatewayIp: string; diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index bedd96bbedd73..e23ca395a28b2 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -62,11 +62,13 @@ }, "dependencies": { "@aws-cdk/aws-iam": "^0.25.2", + "@aws-cdk/aws-cloudwatch": "^0.25.2", "@aws-cdk/cdk": "^0.25.2", "@aws-cdk/cx-api": "^0.25.2" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { + "@aws-cdk/aws-cloudwatch": "^0.25.2", "@aws-cdk/cdk": "^0.25.2", "@aws-cdk/cx-api": "^0.25.2" }, @@ -78,4 +80,4 @@ "resource-attribute:@aws-cdk/aws-ec2.ISecurityGroup.securityGroupVpcId" ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpn.ts b/packages/@aws-cdk/aws-ec2/test/test.vpn.ts index 6dcfd3e92e29b..3d88da1409339 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpn.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpn.ts @@ -1,7 +1,7 @@ import { expect, haveResource, } from '@aws-cdk/assert'; import { Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; -import { VpcNetwork } from '../lib'; +import { VpcNetwork, VpnConnection } from '../lib'; export = { 'can add a vpn connection to a vpc with a vpn gateway'(test: Test) { @@ -258,6 +258,45 @@ export = { } }), /`tunnelInsideCidr`.+size/); + test.done(); + }, + + 'can use metricTunnelState on a vpn connection'(test: Test) { + // GIVEN + const stack = new Stack(); + + const vpc = new VpcNetwork(stack, 'VpcNetwork', { + vpnGateway: true + }); + + const vpn = vpc.addVpnConnection('Vpn', { + ip: '192.0.2.1' + }); + + // THEN + test.deepEqual(stack.node.resolve(vpn.metricTunnelState()), { + dimensions: { VpnId: { Ref: 'VpcNetworkVpnA476C58D' } }, + namespace: 'AWS/VPN', + metricName: 'TunnelState', + periodSec: 300, + statistic: 'Average' + }); + + test.done(); + }, + + 'can use metricAllTunnelDataOut'(test: Test) { + // GIVEN + const stack = new Stack(); + + // THEN + test.deepEqual(stack.node.resolve(VpnConnection.metricAllTunnelDataOut()), { + namespace: 'AWS/VPN', + metricName: 'TunnelDataOut', + periodSec: 300, + statistic: 'Sum' + }); + test.done(); } }; diff --git a/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_EC2_VPNConnection.json b/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_EC2_VPNConnection.json new file mode 100644 index 0000000000000..e424938599469 --- /dev/null +++ b/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_EC2_VPNConnection.json @@ -0,0 +1,27 @@ +{ + "options": { + "classFile": "vpn", + "class": "VpnConnection", + "interface": "IVpnConnection" + }, + "metrics": { + "namespace": "AWS/VPN", + "dimensions": { "VpnId": "this.vpnId" }, + "metrics": [ + { + "name": "TunnelState", + "documentation": "The state of the tunnel. 0 indicates DOWN and 1 indicates UP." + }, + { + "name": "TunnelDataIn", + "documentation": "The bytes received through the VPN tunnel.", + "type": "count" + }, + { + "name": "TunnelDataOut", + "documentation": "The bytes sent through the VPN tunnel.", + "type": "count" + } + ] + } +} diff --git a/packages/@aws-cdk/cfnspec/lib/schema/augmentation.ts b/packages/@aws-cdk/cfnspec/lib/schema/augmentation.ts index a47a16a96e0fd..29de1490e0759 100644 --- a/packages/@aws-cdk/cfnspec/lib/schema/augmentation.ts +++ b/packages/@aws-cdk/cfnspec/lib/schema/augmentation.ts @@ -6,6 +6,36 @@ export interface ResourceAugmentation { * Metric augmentations for this resource type */ metrics?: ResourceMetricAugmentations; + + /** + * Options for this resource augmentation + * + * @default no options + */ + options?: AugmentationOptions; +} + +export interface AugmentationOptions { + /** + * The name of the file containing the class to be "augmented". + * + * @default kebab cased CloudFormation resource name + '-base' + */ + classFile?: string; + + /** + * The name of the class to be "augmented". + * + * @default CloudFormation resource name + 'Base' + */ + class?: string; + + /** + * The name of the interface to be "augmented". + * + * @default 'I' + CloudFormation resource name + */ + interface?: string; } export interface ResourceMetricAugmentations { @@ -57,4 +87,4 @@ export enum MetricType { * property. The most useful aggregate of this type of metric is "Max". */ Gauge = 'gauge' -} \ No newline at end of file +} diff --git a/tools/cfn2ts/lib/augmentation-generator.ts b/tools/cfn2ts/lib/augmentation-generator.ts index 34cc57c82c825..94521c94c27de 100644 --- a/tools/cfn2ts/lib/augmentation-generator.ts +++ b/tools/cfn2ts/lib/augmentation-generator.ts @@ -24,7 +24,7 @@ export class AugmentationGenerator { if (aug.metrics) { this.code.line('import cloudwatch = require("@aws-cdk/aws-cloudwatch");'); - this.emitMetricAugmentations(resourceTypeName, aug.metrics); + this.emitMetricAugmentations(resourceTypeName, aug.metrics, aug.options); hadAugmentations = true; } } @@ -39,18 +39,19 @@ export class AugmentationGenerator { return await this.code.save(dir); } - private emitMetricAugmentations(resourceTypeName: string, metrics: schema.ResourceMetricAugmentations) { + private emitMetricAugmentations(resourceTypeName: string, metrics: schema.ResourceMetricAugmentations, options?: schema.AugmentationOptions) { const cfnName = SpecName.parse(resourceTypeName); const resourceName = genspec.CodeName.forCfnResource(cfnName, this.affix); const l2ClassName = resourceName.className.replace(/^Cfn/, ''); + const kebabL2ClassName = l2ClassName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); - const baseClassName = l2ClassName + 'Base'; - const interfaceName = 'I' + l2ClassName; - const baseClassModule = `./${l2ClassName.toLowerCase()}-base`; + const classFile = `./${(options && options.classFile) || `${kebabL2ClassName}-base`}`; + const className = (options && options.class) || l2ClassName + 'Base'; + const interfaceName = (options && options.interface) || 'I' + l2ClassName; - this.code.line(`import { ${baseClassName} } from "${baseClassModule}";`); + this.code.line(`import { ${className} } from "${classFile}";`); - this.code.openBlock(`declare module "${baseClassModule}"`); + this.code.openBlock(`declare module "${classFile}"`); // Add to the interface this.code.openBlock(`interface ${interfaceName}`); @@ -61,7 +62,7 @@ export class AugmentationGenerator { this.code.closeBlock(); // Add declaration to the base class (implementation added below) - this.code.openBlock(`interface ${baseClassName}`); + this.code.openBlock(`interface ${className}`); this.emitMetricFunctionDeclaration(cfnName); for (const m of metrics.metrics) { this.emitSpecificMetricFunctionDeclaration(m); @@ -71,9 +72,9 @@ export class AugmentationGenerator { this.code.closeBlock(); // Emit the monkey patches for all methods - this.emitMetricFunction(baseClassName, metrics); + this.emitMetricFunction(className, metrics); for (const m of metrics.metrics) { - this.emitSpecificMetricFunction(baseClassName, m); + this.emitSpecificMetricFunction(className, m); } }