Skip to content

Commit

Permalink
feat(elbv2): add metrics to INetworkLoadBalancer
Browse files Browse the repository at this point in the history
fix: #10850

By adding the metrics methods to the `INetworkLoadBalancer` interface, it allows
to create these metrics also for NLBs that are imported via the `fromXXX`
methods.

Given that to create the metrics for NLBs, only the full name of the NLB is
required, and this attribute is available at the constructs returned by the
`fromXXX` methods.
  • Loading branch information
Gustavo Muenz committed Jan 27, 2023
1 parent 660198b commit 9f12266
Show file tree
Hide file tree
Showing 2 changed files with 212 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as cxschema from '@aws-cdk/cloud-assembly-schema';
import { Resource } from '@aws-cdk/core';
import { Arn, ArnFormat, Resource, ResourceProps } from '@aws-cdk/core';
import * as cxapi from '@aws-cdk/cx-api';
import { Construct } from 'constructs';
import { NetworkELBMetrics } from '../elasticloadbalancingv2-canned-metrics.generated';
Expand Down Expand Up @@ -58,6 +58,71 @@ export interface NetworkLoadBalancerAttributes {
export interface NetworkLoadBalancerLookupOptions extends BaseLoadBalancerLookupOptions {
}

/**
* The metrics for a network load balancer.
*/
class NetworkLoadBalancerMetrics implements INetworkLoadBalancerMetrics {
private readonly loadBalancerFullName: string;
private readonly scope: Construct;

constructor(scope: Construct, loadBalancerArn: string) {
this.scope = scope;
const arnComponents = Arn.split(loadBalancerArn, ArnFormat.SLASH_RESOURCE_NAME);
if (!arnComponents.resourceName) {
throw new Error(`Provided ARN does not belong to a load balancer: ${loadBalancerArn}`);
}
this.loadBalancerFullName = arnComponents.resourceName;
}

public custom(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric {
return new cloudwatch.Metric({
namespace: 'AWS/NetworkELB',
metricName,
dimensionsMap: { LoadBalancer: this.loadBalancerFullName },
...props,
}).attachTo(this.scope);
}

public activeFlowCount(props?: cloudwatch.MetricOptions) {
return this.cannedMetric(NetworkELBMetrics.activeFlowCountAverage, props);
}

public consumedLCUs(props?: cloudwatch.MetricOptions) {
return this.cannedMetric(NetworkELBMetrics.consumedLcUsAverage, {
statistic: 'Sum',
...props,
});
}

public newFlowCount(props?: cloudwatch.MetricOptions) {
return this.cannedMetric(NetworkELBMetrics.newFlowCountSum, props);
}

public processedBytes(props?: cloudwatch.MetricOptions) {
return this.cannedMetric(NetworkELBMetrics.processedBytesSum, props);
}

public tcpClientResetCount(props?: cloudwatch.MetricOptions) {
return this.cannedMetric(NetworkELBMetrics.tcpClientResetCountSum, props);
}
public tcpElbResetCount(props?: cloudwatch.MetricOptions) {
return this.cannedMetric(NetworkELBMetrics.tcpElbResetCountSum, props);
}
public tcpTargetResetCount(props?: cloudwatch.MetricOptions) {
return this.cannedMetric(NetworkELBMetrics.tcpTargetResetCountSum, props);
}

private cannedMetric(
fn: (dims: { LoadBalancer: string }) => cloudwatch.MetricProps,
props?: cloudwatch.MetricOptions,
): cloudwatch.Metric {
return new cloudwatch.Metric({
...fn({ LoadBalancer: this.loadBalancerFullName }),
...props,
}).attachTo(this.scope);
}
}

/**
* Define a new network load balancer
*
Expand All @@ -77,9 +142,17 @@ export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoa
}

public static fromNetworkLoadBalancerAttributes(scope: Construct, id: string, attrs: NetworkLoadBalancerAttributes): INetworkLoadBalancer {

class Import extends Resource implements INetworkLoadBalancer {
public readonly loadBalancerArn = attrs.loadBalancerArn;
public readonly vpc?: ec2.IVpc = attrs.vpc;
public readonly metrics: INetworkLoadBalancerMetrics;

constructor(props: ResourceProps) {
super(scope, id, props);
this.metrics = new NetworkLoadBalancerMetrics(this, this.loadBalancerArn);
}

public addListener(lid: string, props: BaseNetworkListenerProps): NetworkListener {
return new NetworkListener(this, lid, {
loadBalancer: this,
Expand All @@ -100,14 +173,17 @@ export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoa
}
}

return new Import(scope, id, { environmentFromArn: attrs.loadBalancerArn });
return new Import({ environmentFromArn: attrs.loadBalancerArn });
}

public readonly metrics: INetworkLoadBalancerMetrics;

constructor(scope: Construct, id: string, props: NetworkLoadBalancerProps) {
super(scope, id, props, {
type: 'network',
});

this.metrics = new NetworkLoadBalancerMetrics(this, this.loadBalancerArn);
if (props.crossZoneEnabled) { this.setAttribute('load_balancing.cross_zone.enabled', 'true'); }
}

Expand All @@ -127,6 +203,7 @@ export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoa
* Return the given named metric for this Network Load Balancer
*
* @default Average over 5 minutes
* @deprecated Use ``NetworkLoadBalancer.metrics.custom`` instead
*/
public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric {
return new cloudwatch.Metric({
Expand All @@ -145,21 +222,20 @@ export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoa
* opening a TCP connection to a target counts as a single flow.
*
* @default Average over 5 minutes
* @deprecated Use ``NetworkLoadBalancer.metrics.activeFlowCount`` instead
*/
public metricActiveFlowCount(props?: cloudwatch.MetricOptions) {
return this.cannedMetric(NetworkELBMetrics.activeFlowCountAverage, props);
return this.metrics.activeFlowCount(props);
}

/**
* The number of load balancer capacity units (LCU) used by your load balancer.
*
* @default Sum over 5 minutes
* @deprecated Use ``NetworkLoadBalancer.metrics.activeFlowCount`` instead
*/
public metricConsumedLCUs(props?: cloudwatch.MetricOptions) {
return this.cannedMetric(NetworkELBMetrics.consumedLcUsAverage, {
statistic: 'Sum',
...props,
});
return this.metrics.consumedLCUs(props);
}

/**
Expand Down Expand Up @@ -192,18 +268,20 @@ export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoa
* The total number of new TCP flows (or connections) established from clients to targets in the time period.
*
* @default Sum over 5 minutes
* @deprecated Use ``NetworkLoadBalancer.metrics.newFlowCount`` instead
*/
public metricNewFlowCount(props?: cloudwatch.MetricOptions) {
return this.cannedMetric(NetworkELBMetrics.newFlowCountSum, props);
return this.metrics.newFlowCount(props);
}

/**
* The total number of bytes processed by the load balancer, including TCP/IP headers.
*
* @default Sum over 5 minutes
* @deprecated Use ``NetworkLoadBalancer.metrics.processedBytes`` instead
*/
public metricProcessedBytes(props?: cloudwatch.MetricOptions) {
return this.cannedMetric(NetworkELBMetrics.processedBytesSum, props);
return this.metrics.processedBytes(props);
}

/**
Expand All @@ -212,18 +290,20 @@ export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoa
* These resets are generated by the client and forwarded by the load balancer.
*
* @default Sum over 5 minutes
* @deprecated Use ``NetworkLoadBalancer.metrics.tcpClientResetCount`` instead
*/
public metricTcpClientResetCount(props?: cloudwatch.MetricOptions) {
return this.cannedMetric(NetworkELBMetrics.tcpClientResetCountSum, props);
return this.metrics.tcpClientResetCount(props);
}

/**
* The total number of reset (RST) packets generated by the load balancer.
*
* @default Sum over 5 minutes
* @deprecated Use ``NetworkLoadBalancer.metrics.tcpElbResetCount`` instead
*/
public metricTcpElbResetCount(props?: cloudwatch.MetricOptions) {
return this.cannedMetric(NetworkELBMetrics.tcpElbResetCountSum, props);
return this.metrics.tcpElbResetCount(props);
}

/**
Expand All @@ -232,19 +312,81 @@ export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoa
* These resets are generated by the target and forwarded by the load balancer.
*
* @default Sum over 5 minutes
* @deprecated Use ``NetworkLoadBalancer.metrics.tcpTargetResetCount`` instead
*/
public metricTcpTargetResetCount(props?: cloudwatch.MetricOptions) {
return this.cannedMetric(NetworkELBMetrics.tcpTargetResetCountSum, props);
return this.metrics.tcpTargetResetCount(props);
}
}

private cannedMetric(
fn: (dims: { LoadBalancer: string }) => cloudwatch.MetricProps,
props?: cloudwatch.MetricOptions): cloudwatch.Metric {
return new cloudwatch.Metric({
...fn({ LoadBalancer: this.loadBalancerFullName }),
...props,
}).attachTo(this);
}
/**
* Contains all metrics for a Network Load Balancer.
*/
export interface INetworkLoadBalancerMetrics {

/**
* Return the given named metric for this Network Load Balancer
*
* @default Average over 5 minutes
*/
custom(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric;

/**
* The total number of concurrent TCP flows (or connections) from clients to targets.
*
* This metric includes connections in the SYN_SENT and ESTABLISHED states.
* TCP connections are not terminated at the load balancer, so a client
* opening a TCP connection to a target counts as a single flow.
*
* @default Average over 5 minutes
*/
activeFlowCount(props?: cloudwatch.MetricOptions): cloudwatch.Metric;

/**
* The number of load balancer capacity units (LCU) used by your load balancer.
*
* @default Sum over 5 minutes
*/
consumedLCUs(props?: cloudwatch.MetricOptions): cloudwatch.Metric;

/**
* The total number of new TCP flows (or connections) established from clients to targets in the time period.
*
* @default Sum over 5 minutes
*/
newFlowCount(props?: cloudwatch.MetricOptions): cloudwatch.Metric;

/**
* The total number of bytes processed by the load balancer, including TCP/IP headers.
*
* @default Sum over 5 minutes
*/
processedBytes(props?: cloudwatch.MetricOptions): cloudwatch.Metric;

/**
* The total number of reset (RST) packets sent from a client to a target.
*
* These resets are generated by the client and forwarded by the load balancer.
*
* @default Sum over 5 minutes
*/
tcpClientResetCount(props?: cloudwatch.MetricOptions): cloudwatch.Metric;

/**
* The total number of reset (RST) packets generated by the load balancer.
*
* @default Sum over 5 minutes
*/
tcpElbResetCount(props?: cloudwatch.MetricOptions): cloudwatch.Metric;

/**
* The total number of reset (RST) packets sent from a target to a client.
*
* These resets are generated by the target and forwarded by the load balancer.
*
* @default Sum over 5 minutes
*/
tcpTargetResetCount(props?: cloudwatch.MetricOptions): cloudwatch.Metric;
}

/**
Expand All @@ -257,6 +399,11 @@ export interface INetworkLoadBalancer extends ILoadBalancerV2, ec2.IVpcEndpointS
*/
readonly vpc?: ec2.IVpc;

/**
* All metrics available for this load balancer
*/
readonly metrics: INetworkLoadBalancerMetrics;

/**
* Add a listener to this load balancer
*
Expand All @@ -270,13 +417,15 @@ class LookedUpNetworkLoadBalancer extends Resource implements INetworkLoadBalanc
public readonly loadBalancerDnsName: string;
public readonly loadBalancerArn: string;
public readonly vpc?: ec2.IVpc;
public readonly metrics: INetworkLoadBalancerMetrics;

constructor(scope: Construct, id: string, props: cxapi.LoadBalancerContextResponse) {
super(scope, id, { environmentFromArn: props.loadBalancerArn });

this.loadBalancerArn = props.loadBalancerArn;
this.loadBalancerCanonicalHostedZoneId = props.loadBalancerCanonicalHostedZoneId;
this.loadBalancerDnsName = props.loadBalancerDnsName;
this.metrics = new NetworkLoadBalancerMetrics(this, props.loadBalancerArn);

this.vpc = ec2.Vpc.fromLookup(this, 'Vpc', {
vpcId: props.vpcId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,24 @@ describe('tests', () => {
expect(alb.env.region).toEqual('us-west-2');
});

test('imported load balancer can have metrics', () => {
const stack = new cdk.Stack();

// WHEN
const arn = 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/network/my-load-balancer/50dc6c495c0c9188';
const nlb = elbv2.NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, 'NLB', {
loadBalancerArn: arn,
});

const metric = nlb.metrics.custom('MetricName');

// THEN
expect(metric.namespace).toEqual('AWS/NetworkELB');
expect(stack.resolve(metric.dimensions)).toEqual({
LoadBalancer: 'network/my-load-balancer/50dc6c495c0c9188',
});
});

test('Trivial construction: internal with Isolated subnets only', () => {
// GIVEN
const stack = new cdk.Stack();
Expand Down Expand Up @@ -607,5 +625,30 @@ describe('tests', () => {
Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::NetworkLoadBalancer', 0);
Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::Listener', 1);
});
test('Can create metrics from a looked-up NetworkLoadBalancer', () => {
// GIVEN
const app = new cdk.App();
const stack = new cdk.Stack(app, 'stack', {
env: {
account: '123456789012',
region: 'us-west-2',
},
});

const loadBalancer = elbv2.NetworkLoadBalancer.fromLookup(stack, 'a', {
loadBalancerTags: {
some: 'tag',
},
});

// WHEN
const metric = loadBalancer.metrics.custom('MetricName');

// THEN
expect(metric.namespace).toEqual('AWS/NetworkELB');
expect(stack.resolve(metric.dimensions)).toEqual({
LoadBalancer: 'network/my-load-balancer/50dc6c495c0c9188',
});
});
});
});

0 comments on commit 9f12266

Please sign in to comment.