Skip to content

Commit 9319e13

Browse files
jogoldElad Ben-Israel
authored andcommitted
feat(ec2): vpn metrics (#1979)
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).
1 parent afbd591 commit 9319e13

File tree

8 files changed

+165
-13
lines changed

8 files changed

+165
-13
lines changed

packages/@aws-cdk/aws-ec2/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,3 +341,15 @@ vpc.addVpnConnection('Dynamic', {
341341
```
342342

343343
Routes will be propagated on the route tables associated with the private subnets.
344+
345+
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:
346+
```ts
347+
// Across all tunnels in the account/region
348+
const allDataOut = VpnConnection.metricAllTunnelDataOut();
349+
350+
// For a specific vpn connection
351+
const vpnConnection = vpc.addVpnConnection('Dynamic', {
352+
ip: '1.2.3.4'
353+
});
354+
const state = vpnConnection.metricTunnelState();
355+
```

packages/@aws-cdk/aws-ec2/lib/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ export * from './vpn';
1010

1111
// AWS::EC2 CloudFormation Resources:
1212
export * from './ec2.generated';
13+
14+
import './ec2-augmentations.generated';

packages/@aws-cdk/aws-ec2/lib/vpn.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import cloudwatch = require('@aws-cdk/aws-cloudwatch');
12
import cdk = require('@aws-cdk/cdk');
23
import net = require('net');
34
import { CfnCustomerGateway, CfnVPNConnection, CfnVPNConnectionRoute } from './ec2.generated';
@@ -98,6 +99,44 @@ export enum VpnConnectionType {
9899
}
99100

100101
export class VpnConnection extends cdk.Construct implements IVpnConnection {
102+
/**
103+
* Return the given named metric for all VPN connections in the account/region.
104+
*/
105+
public static metricAll(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric {
106+
return new cloudwatch.Metric({
107+
namespace: 'AWS/VPN',
108+
metricName,
109+
...props
110+
});
111+
}
112+
113+
/**
114+
* Metric for the tunnel state of all VPN connections in the account/region.
115+
*
116+
* @default average over 5 minutes
117+
*/
118+
public static metricAllTunnelState(props?: cloudwatch.MetricCustomization): cloudwatch.Metric {
119+
return this.metricAll('TunnelSate', { statistic: 'avg', ...props });
120+
}
121+
122+
/**
123+
* Metric for the tunnel data in of all VPN connections in the account/region.
124+
*
125+
* @default sum over 5 minutes
126+
*/
127+
public static metricAllTunnelDataIn(props?: cloudwatch.MetricCustomization): cloudwatch.Metric {
128+
return this.metricAll('TunnelDataIn', { statistic: 'sum', ...props });
129+
}
130+
131+
/**
132+
* Metric for the tunnel data out of all VPN connections.
133+
*
134+
* @default sum over 5 minutes
135+
*/
136+
public static metricAllTunnelDataOut(props?: cloudwatch.MetricCustomization): cloudwatch.Metric {
137+
return this.metricAll('TunnelDataOut', { statistic: 'sum', ...props });
138+
}
139+
101140
public readonly vpnId: string;
102141
public readonly customerGatewayId: string;
103142
public readonly customerGatewayIp: string;

packages/@aws-cdk/aws-ec2/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,13 @@
6262
},
6363
"dependencies": {
6464
"@aws-cdk/aws-iam": "^0.25.2",
65+
"@aws-cdk/aws-cloudwatch": "^0.25.2",
6566
"@aws-cdk/cdk": "^0.25.2",
6667
"@aws-cdk/cx-api": "^0.25.2"
6768
},
6869
"homepage": "https://github.com/awslabs/aws-cdk",
6970
"peerDependencies": {
71+
"@aws-cdk/aws-cloudwatch": "^0.25.2",
7072
"@aws-cdk/cdk": "^0.25.2",
7173
"@aws-cdk/cx-api": "^0.25.2"
7274
},
@@ -78,4 +80,4 @@
7880
"resource-attribute:@aws-cdk/aws-ec2.ISecurityGroup.securityGroupVpcId"
7981
]
8082
}
81-
}
83+
}

packages/@aws-cdk/aws-ec2/test/test.vpn.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { expect, haveResource, } from '@aws-cdk/assert';
22
import { Stack } from '@aws-cdk/cdk';
33
import { Test } from 'nodeunit';
4-
import { VpcNetwork } from '../lib';
4+
import { VpcNetwork, VpnConnection } from '../lib';
55

66
export = {
77
'can add a vpn connection to a vpc with a vpn gateway'(test: Test) {
@@ -258,6 +258,45 @@ export = {
258258
}
259259
}), /`tunnelInsideCidr`.+size/);
260260

261+
test.done();
262+
},
263+
264+
'can use metricTunnelState on a vpn connection'(test: Test) {
265+
// GIVEN
266+
const stack = new Stack();
267+
268+
const vpc = new VpcNetwork(stack, 'VpcNetwork', {
269+
vpnGateway: true
270+
});
271+
272+
const vpn = vpc.addVpnConnection('Vpn', {
273+
ip: '192.0.2.1'
274+
});
275+
276+
// THEN
277+
test.deepEqual(stack.node.resolve(vpn.metricTunnelState()), {
278+
dimensions: { VpnId: { Ref: 'VpcNetworkVpnA476C58D' } },
279+
namespace: 'AWS/VPN',
280+
metricName: 'TunnelState',
281+
periodSec: 300,
282+
statistic: 'Average'
283+
});
284+
285+
test.done();
286+
},
287+
288+
'can use metricAllTunnelDataOut'(test: Test) {
289+
// GIVEN
290+
const stack = new Stack();
291+
292+
// THEN
293+
test.deepEqual(stack.node.resolve(VpnConnection.metricAllTunnelDataOut()), {
294+
namespace: 'AWS/VPN',
295+
metricName: 'TunnelDataOut',
296+
periodSec: 300,
297+
statistic: 'Sum'
298+
});
299+
261300
test.done();
262301
}
263302
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"options": {
3+
"classFile": "vpn",
4+
"class": "VpnConnection",
5+
"interface": "IVpnConnection"
6+
},
7+
"metrics": {
8+
"namespace": "AWS/VPN",
9+
"dimensions": { "VpnId": "this.vpnId" },
10+
"metrics": [
11+
{
12+
"name": "TunnelState",
13+
"documentation": "The state of the tunnel. 0 indicates DOWN and 1 indicates UP."
14+
},
15+
{
16+
"name": "TunnelDataIn",
17+
"documentation": "The bytes received through the VPN tunnel.",
18+
"type": "count"
19+
},
20+
{
21+
"name": "TunnelDataOut",
22+
"documentation": "The bytes sent through the VPN tunnel.",
23+
"type": "count"
24+
}
25+
]
26+
}
27+
}

packages/@aws-cdk/cfnspec/lib/schema/augmentation.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,36 @@ export interface ResourceAugmentation {
66
* Metric augmentations for this resource type
77
*/
88
metrics?: ResourceMetricAugmentations;
9+
10+
/**
11+
* Options for this resource augmentation
12+
*
13+
* @default no options
14+
*/
15+
options?: AugmentationOptions;
16+
}
17+
18+
export interface AugmentationOptions {
19+
/**
20+
* The name of the file containing the class to be "augmented".
21+
*
22+
* @default kebab cased CloudFormation resource name + '-base'
23+
*/
24+
classFile?: string;
25+
26+
/**
27+
* The name of the class to be "augmented".
28+
*
29+
* @default CloudFormation resource name + 'Base'
30+
*/
31+
class?: string;
32+
33+
/**
34+
* The name of the interface to be "augmented".
35+
*
36+
* @default 'I' + CloudFormation resource name
37+
*/
38+
interface?: string;
939
}
1040

1141
export interface ResourceMetricAugmentations {
@@ -57,4 +87,4 @@ export enum MetricType {
5787
* property. The most useful aggregate of this type of metric is "Max".
5888
*/
5989
Gauge = 'gauge'
60-
}
90+
}

tools/cfn2ts/lib/augmentation-generator.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class AugmentationGenerator {
2424

2525
if (aug.metrics) {
2626
this.code.line('import cloudwatch = require("@aws-cdk/aws-cloudwatch");');
27-
this.emitMetricAugmentations(resourceTypeName, aug.metrics);
27+
this.emitMetricAugmentations(resourceTypeName, aug.metrics, aug.options);
2828
hadAugmentations = true;
2929
}
3030
}
@@ -39,18 +39,19 @@ export class AugmentationGenerator {
3939
return await this.code.save(dir);
4040
}
4141

42-
private emitMetricAugmentations(resourceTypeName: string, metrics: schema.ResourceMetricAugmentations) {
42+
private emitMetricAugmentations(resourceTypeName: string, metrics: schema.ResourceMetricAugmentations, options?: schema.AugmentationOptions) {
4343
const cfnName = SpecName.parse(resourceTypeName);
4444
const resourceName = genspec.CodeName.forCfnResource(cfnName, this.affix);
4545
const l2ClassName = resourceName.className.replace(/^Cfn/, '');
46+
const kebabL2ClassName = l2ClassName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
4647

47-
const baseClassName = l2ClassName + 'Base';
48-
const interfaceName = 'I' + l2ClassName;
49-
const baseClassModule = `./${l2ClassName.toLowerCase()}-base`;
48+
const classFile = `./${(options && options.classFile) || `${kebabL2ClassName}-base`}`;
49+
const className = (options && options.class) || l2ClassName + 'Base';
50+
const interfaceName = (options && options.interface) || 'I' + l2ClassName;
5051

51-
this.code.line(`import { ${baseClassName} } from "${baseClassModule}";`);
52+
this.code.line(`import { ${className} } from "${classFile}";`);
5253

53-
this.code.openBlock(`declare module "${baseClassModule}"`);
54+
this.code.openBlock(`declare module "${classFile}"`);
5455

5556
// Add to the interface
5657
this.code.openBlock(`interface ${interfaceName}`);
@@ -61,7 +62,7 @@ export class AugmentationGenerator {
6162
this.code.closeBlock();
6263

6364
// Add declaration to the base class (implementation added below)
64-
this.code.openBlock(`interface ${baseClassName}`);
65+
this.code.openBlock(`interface ${className}`);
6566
this.emitMetricFunctionDeclaration(cfnName);
6667
for (const m of metrics.metrics) {
6768
this.emitSpecificMetricFunctionDeclaration(m);
@@ -71,9 +72,9 @@ export class AugmentationGenerator {
7172
this.code.closeBlock();
7273

7374
// Emit the monkey patches for all methods
74-
this.emitMetricFunction(baseClassName, metrics);
75+
this.emitMetricFunction(className, metrics);
7576
for (const m of metrics.metrics) {
76-
this.emitSpecificMetricFunction(baseClassName, m);
77+
this.emitSpecificMetricFunction(className, m);
7778
}
7879
}
7980

0 commit comments

Comments
 (0)