From 429b1019b91524d096a52b4a67bee7409a9d481b Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Fri, 8 Mar 2019 18:56:24 +0100 Subject: [PATCH 1/3] feat(ec2): add vpn metrics Add VPN metrics (TunnelState, TunnelDataIn, TunnelDataOut) across all tunnels and per connection by adding a new augmentation. Adapt AugmentationGenerator to convert resource name to Kebab case module name and to support name overrides for class, interface and module when these cannot be directly derived from the CloudFormation resource name: no base class or resource name not really Pascal case (e.g. VPNConnection). --- packages/@aws-cdk/aws-ec2/README.md | 2 + packages/@aws-cdk/aws-ec2/lib/index.ts | 2 + packages/@aws-cdk/aws-ec2/lib/vpn.ts | 39 ++++++++++++++++++ packages/@aws-cdk/aws-ec2/package.json | 4 +- packages/@aws-cdk/aws-ec2/test/test.vpn.ts | 41 ++++++++++++++++++- .../augmentations/AWS_EC2_VPNConnection.json | 27 ++++++++++++ .../cfnspec/lib/schema/augmentation.ts | 24 ++++++++++- tools/cfn2ts/lib/augmentation-generator.ts | 11 ++--- 8 files changed, 142 insertions(+), 8 deletions(-) create mode 100644 packages/@aws-cdk/cfnspec/lib/augmentations/AWS_EC2_VPNConnection.json diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 4cda8dcbc4cfa..71e82e327ffb6 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -341,3 +341,5 @@ vpc.addVpnConnection('Dynamic', { ``` Routes will be propagated on the route tables associated with the private subnets. + +VPN connections expose metric across all tunnels and per connection. 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..d0860d78f9e03 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. + */ + 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. + * + * @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. + * + * @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..1f977c2fbfdda --- /dev/null +++ b/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_EC2_VPNConnection.json @@ -0,0 +1,27 @@ +{ + "overrides": { + "interface": "IVpnConnection", + "class": "VpnConnection", + "module": "vpn" + }, + "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..d7deed5eb68f9 100644 --- a/packages/@aws-cdk/cfnspec/lib/schema/augmentation.ts +++ b/packages/@aws-cdk/cfnspec/lib/schema/augmentation.ts @@ -6,6 +6,28 @@ export interface ResourceAugmentation { * Metric augmentations for this resource type */ metrics?: ResourceMetricAugmentations; + + /** + * Overrides for this resource augmentation + */ + overrides?: ResourceOverrides; +} + +export interface ResourceOverrides { + /** + * The name of the resource class + */ + class?: string; + + /** + * The name of the resource interface + */ + interface?: string; + + /** + * The name of the module + */ + module?: string; } export interface ResourceMetricAugmentations { @@ -57,4 +79,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..57b39bd5beb66 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.overrides); hadAugmentations = true; } } @@ -39,14 +39,15 @@ export class AugmentationGenerator { return await this.code.save(dir); } - private emitMetricAugmentations(resourceTypeName: string, metrics: schema.ResourceMetricAugmentations) { + private emitMetricAugmentations(resourceTypeName: string, metrics: schema.ResourceMetricAugmentations, overrides?: schema.ResourceOverrides) { 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 baseClassName = (overrides && overrides.class) || l2ClassName + 'Base'; + const interfaceName = (overrides && overrides.interface) || 'I' + l2ClassName; + const baseClassModule = `./${(overrides && overrides.module) || `${kebabL2ClassName}-base`}`; this.code.line(`import { ${baseClassName} } from "${baseClassModule}";`); From 91437709802e7f2df916bb0b352f712ea351a2db Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 11 Mar 2019 14:25:12 +0100 Subject: [PATCH 2/3] Address PR comments --- packages/@aws-cdk/aws-ec2/README.md | 12 +++++++++++- packages/@aws-cdk/aws-ec2/lib/vpn.ts | 6 +++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 71e82e327ffb6..26a04b5a6d5df 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -342,4 +342,14 @@ vpc.addVpnConnection('Dynamic', { Routes will be propagated on the route tables associated with the private subnets. -VPN connections expose metric across all tunnels and per connection. +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/vpn.ts b/packages/@aws-cdk/aws-ec2/lib/vpn.ts index d0860d78f9e03..bed2b9cdc5d98 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpn.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpn.ts @@ -100,7 +100,7 @@ export enum VpnConnectionType { export class VpnConnection extends cdk.Construct implements IVpnConnection { /** - * Return the given named metric for all VPN connections. + * 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({ @@ -111,7 +111,7 @@ export class VpnConnection extends cdk.Construct implements IVpnConnection { } /** - * Metric for the tunnel state of all VPN connections. + * Metric for the tunnel state of all VPN connections in the account/region. * * @default average over 5 minutes */ @@ -120,7 +120,7 @@ export class VpnConnection extends cdk.Construct implements IVpnConnection { } /** - * Metric for the tunnel data in of all VPN connections. + * Metric for the tunnel data in of all VPN connections in the account/region. * * @default sum over 5 minutes */ From 04f8e31dd302106e97fad6810a6978aee858da26 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 11 Mar 2019 15:35:07 +0100 Subject: [PATCH 3/3] Rename augmentation overrides to augmentation options --- .../augmentations/AWS_EC2_VPNConnection.json | 6 ++--- .../cfnspec/lib/schema/augmentation.ts | 26 ++++++++++++------- tools/cfn2ts/lib/augmentation-generator.ts | 20 +++++++------- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_EC2_VPNConnection.json b/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_EC2_VPNConnection.json index 1f977c2fbfdda..e424938599469 100644 --- a/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_EC2_VPNConnection.json +++ b/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_EC2_VPNConnection.json @@ -1,8 +1,8 @@ { - "overrides": { - "interface": "IVpnConnection", + "options": { + "classFile": "vpn", "class": "VpnConnection", - "module": "vpn" + "interface": "IVpnConnection" }, "metrics": { "namespace": "AWS/VPN", diff --git a/packages/@aws-cdk/cfnspec/lib/schema/augmentation.ts b/packages/@aws-cdk/cfnspec/lib/schema/augmentation.ts index d7deed5eb68f9..29de1490e0759 100644 --- a/packages/@aws-cdk/cfnspec/lib/schema/augmentation.ts +++ b/packages/@aws-cdk/cfnspec/lib/schema/augmentation.ts @@ -8,26 +8,34 @@ export interface ResourceAugmentation { metrics?: ResourceMetricAugmentations; /** - * Overrides for this resource augmentation + * Options for this resource augmentation + * + * @default no options */ - overrides?: ResourceOverrides; + options?: AugmentationOptions; } -export interface ResourceOverrides { +export interface AugmentationOptions { /** - * The name of the resource class + * The name of the file containing the class to be "augmented". + * + * @default kebab cased CloudFormation resource name + '-base' */ - class?: string; + classFile?: string; /** - * The name of the resource interface + * The name of the class to be "augmented". + * + * @default CloudFormation resource name + 'Base' */ - interface?: string; + class?: string; /** - * The name of the module + * The name of the interface to be "augmented". + * + * @default 'I' + CloudFormation resource name */ - module?: string; + interface?: string; } export interface ResourceMetricAugmentations { diff --git a/tools/cfn2ts/lib/augmentation-generator.ts b/tools/cfn2ts/lib/augmentation-generator.ts index 57b39bd5beb66..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, aug.overrides); + this.emitMetricAugmentations(resourceTypeName, aug.metrics, aug.options); hadAugmentations = true; } } @@ -39,19 +39,19 @@ export class AugmentationGenerator { return await this.code.save(dir); } - private emitMetricAugmentations(resourceTypeName: string, metrics: schema.ResourceMetricAugmentations, overrides?: schema.ResourceOverrides) { + 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 = (overrides && overrides.class) || l2ClassName + 'Base'; - const interfaceName = (overrides && overrides.interface) || 'I' + l2ClassName; - const baseClassModule = `./${(overrides && overrides.module) || `${kebabL2ClassName}-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}`); @@ -62,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); @@ -72,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); } }