From b1251139290b9b0deeb4b833315e69fa630340d7 Mon Sep 17 00:00:00 2001 From: AWS CDK Automation <43080478+aws-cdk-automation@users.noreply.github.com> Date: Tue, 11 May 2021 10:34:27 +0100 Subject: [PATCH] chore: forward merge 'master' into 'v2-main' (#14601) * chore(cloudfront): remove the use of calculateFunctionHash (#14583) `calculateFunctionHash()` was used to compute the 'refresh token' of the custom resource for the EdgeFunction construct. This method is private to the lambda module and is deemed to be changed. Instead, use the lambda function version's logical id. The logical id of the version includes computing the function hash (among others) and is a more reliable determinant of whether the underlying function version changed. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* * feat(cloudwatch): validate parameters for a metric dimensions (closes #3116) (#14365) As per #3116, the changes in this PR validate metric dimension values (length, and checking if the value is null or undefined) and throw errors if the values are not valid. I've also corrected a comment in the metric-types.ts to use the correct method name * feat(appmesh): change HealthChecks to use protocol-specific union-like classes (#14432) BREAKING CHANGE: HealthChecks require use of static factory methods fixes https://github.com/aws/aws-cdk/issues/11640 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* Co-authored-by: Niranjan Jayakar Co-authored-by: OksanaH <34384274+OksanaH@users.noreply.github.com> Co-authored-by: Dominic Fezzie Co-authored-by: Elad Ben-Israel --- packages/@aws-cdk/aws-appmesh/README.md | 26 +-- .../@aws-cdk/aws-appmesh/lib/health-checks.ts | 189 ++++++++++++++++++ packages/@aws-cdk/aws-appmesh/lib/index.ts | 1 + .../@aws-cdk/aws-appmesh/lib/private/utils.ts | 42 ---- .../@aws-cdk/aws-appmesh/lib/route-spec.ts | 2 +- .../aws-appmesh/lib/shared-interfaces.ts | 59 +----- .../lib/virtual-gateway-listener.ts | 38 +--- .../aws-appmesh/lib/virtual-node-listener.ts | 38 +--- packages/@aws-cdk/aws-appmesh/package.json | 3 +- .../@aws-cdk/aws-appmesh/test/integ.mesh.ts | 20 +- .../aws-appmesh/test/test.health-check.ts | 80 ++------ .../@aws-cdk/aws-appmesh/test/test.mesh.ts | 4 +- .../aws-appmesh/test/test.virtual-gateway.ts | 11 +- .../aws-appmesh/test/test.virtual-node.ts | 6 +- .../lib/experimental/edge-function.ts | 23 ++- ...ribution-lambda-cross-region.expected.json | 4 +- .../aws-cloudwatch/lib/metric-types.ts | 6 +- .../@aws-cdk/aws-cloudwatch/lib/metric.ts | 24 ++- .../aws-cloudwatch/test/test.metrics.ts | 73 +++++++ 19 files changed, 363 insertions(+), 286 deletions(-) create mode 100644 packages/@aws-cdk/aws-appmesh/lib/health-checks.ts diff --git a/packages/@aws-cdk/aws-appmesh/README.md b/packages/@aws-cdk/aws-appmesh/README.md index 0adcc52901ba2..067575fb73ec0 100644 --- a/packages/@aws-cdk/aws-appmesh/README.md +++ b/packages/@aws-cdk/aws-appmesh/README.md @@ -149,15 +149,13 @@ const node = mesh.addVirtualNode('virtual-node', { }), listeners: [appmesh.VirtualNodeListener.httpNodeListener({ port: 8081, - healthCheck: { + healthCheck: appmesh.HealthCheck.http({ healthyThreshold: 3, interval: Duration.seconds(5), // minimum path: `/health-check-path`, - port: 8080, - protocol: Protocol.HTTP, timeout: Duration.seconds(2), // minimum unhealthyThreshold: 2, - }, + }), })], accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), }); @@ -173,15 +171,13 @@ const node = new VirtualNode(this, 'node', { }), listeners: [appmesh.VirtualNodeListener.httpNodeListener({ port: 8080, - healthCheck: { + healthCheck: appmesh.HealthCheck.http({ healthyThreshold: 3, interval: Duration.seconds(5), // min path: '/ping', - port: 8080, - protocol: Protocol.HTTP, timeout: Duration.seconds(2), // min unhealthyThreshold: 2, - }, + }), timeout: { idle: cdk.Duration.seconds(5), }, @@ -207,15 +203,13 @@ const node = new VirtualNode(this, 'node', { }), listeners: [appmesh.VirtualNodeListener.httpNodeListener({ port: 8080, - healthCheck: { + healthCheck: appmesh.HealthCheck.http({ healthyThreshold: 3, interval: Duration.seconds(5), // min path: '/ping', - port: 8080, - protocol: Protocol.HTTP, timeout: Duration.seconds(2), // min unhealthyThreshold: 2, - }, + }), timeout: { idle: cdk.Duration.seconds(5), }, @@ -494,9 +488,9 @@ const gateway = new appmesh.VirtualGateway(stack, 'gateway', { mesh: mesh, listeners: [appmesh.VirtualGatewayListener.http({ port: 443, - healthCheck: { + healthCheck: appmesh.HealthCheck.http({ interval: cdk.Duration.seconds(10), - }, + }), })], backendDefaults: { clientPolicy: appmesh.ClientPolicy.acmTrust({ @@ -517,9 +511,9 @@ const gateway = mesh.addVirtualGateway('gateway', { virtualGatewayName: 'virtualGateway', listeners: [appmesh.VirtualGatewayListener.http({ port: 443, - healthCheck: { + healthCheck: appmesh.HealthCheck.http({ interval: cdk.Duration.seconds(10), - }, + }), })], }); ``` diff --git a/packages/@aws-cdk/aws-appmesh/lib/health-checks.ts b/packages/@aws-cdk/aws-appmesh/lib/health-checks.ts new file mode 100644 index 0000000000000..5ce022251acc6 --- /dev/null +++ b/packages/@aws-cdk/aws-appmesh/lib/health-checks.ts @@ -0,0 +1,189 @@ +import * as cdk from '@aws-cdk/core'; +import { CfnVirtualGateway, CfnVirtualNode } from './appmesh.generated'; +import { Protocol } from './shared-interfaces'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from 'constructs'; + +/** + * Properties used to define healthchecks. + */ +interface HealthCheckCommonOptions { + /** + * The number of consecutive successful health checks that must occur before declaring listener healthy. + * + * @default 2 + */ + readonly healthyThreshold?: number; + + /** + * The time period between each health check execution. + * + * @default Duration.seconds(5) + */ + readonly interval?: cdk.Duration; + + /** + * The amount of time to wait when receiving a response from the health check. + * + * @default Duration.seconds(2) + */ + readonly timeout?: cdk.Duration; + + /** + * The number of consecutive failed health checks that must occur before declaring a listener unhealthy. + * + * @default - 2 + */ + readonly unhealthyThreshold?: number; +} + +/** + * Properties used to define HTTP Based healthchecks. + */ +export interface HttpHealthCheckOptions extends HealthCheckCommonOptions { + /** + * The destination path for the health check request. + * + * @default / + */ + readonly path?: string; +} + +/** + * Properties used to define GRPC Based healthchecks. + */ +export interface GrpcHealthCheckOptions extends HealthCheckCommonOptions { } + +/** + * Properties used to define TCP Based healthchecks. + */ +export interface TcpHealthCheckOptions extends HealthCheckCommonOptions { } + +/** + * All Properties for Health Checks for mesh endpoints + */ +export interface HealthCheckConfig { + /** + * VirtualNode CFN configuration for Health Checks + * + * @default - no health checks + */ + readonly virtualNodeHealthCheck?: CfnVirtualNode.HealthCheckProperty; + + /** + * VirtualGateway CFN configuration for Health Checks + * + * @default - no health checks + */ + readonly virtualGatewayHealthCheck?: CfnVirtualGateway.VirtualGatewayHealthCheckPolicyProperty; +} + +/** + * Options used for creating the Health Check object + */ +export interface HealthCheckBindOptions { + /** + * Port for Health Check interface + * + * @default - no default port is provided + */ + readonly defaultPort?: number; +} + + +/** + * Contains static factory methods for creating health checks for different protocols + */ +export abstract class HealthCheck { + /** + * Construct a HTTP health check + */ + public static http(options: HttpHealthCheckOptions = {}): HealthCheck { + return new HealthCheckImpl(Protocol.HTTP, options.healthyThreshold, options.unhealthyThreshold, options.interval, options.timeout, options.path); + } + + /** + * Construct a HTTP2 health check + */ + public static http2(options: HttpHealthCheckOptions = {}): HealthCheck { + return new HealthCheckImpl(Protocol.HTTP2, options.healthyThreshold, options.unhealthyThreshold, options.interval, options.timeout, options.path); + } + + /** + * Construct a GRPC health check + */ + public static grpc(options: GrpcHealthCheckOptions = {}): HealthCheck { + return new HealthCheckImpl(Protocol.GRPC, options.healthyThreshold, options.unhealthyThreshold, options.interval, options.timeout); + } + + /** + * Construct a TCP health check + */ + public static tcp(options: TcpHealthCheckOptions = {}): HealthCheck { + return new HealthCheckImpl(Protocol.TCP, options.healthyThreshold, options.unhealthyThreshold, options.interval, options.timeout); + } + + /** + * Called when the AccessLog type is initialized. Can be used to enforce + * mutual exclusivity with future properties + */ + public abstract bind(scope: Construct, options: HealthCheckBindOptions): HealthCheckConfig; +} + +class HealthCheckImpl extends HealthCheck { + constructor( + private readonly protocol: Protocol, + private readonly healthyThreshold: number = 2, + private readonly unhealthyThreshold: number = 2, + private readonly interval: cdk.Duration = cdk.Duration.seconds(5), + private readonly timeout: cdk.Duration = cdk.Duration.seconds(2), + private readonly path?: string) { + super(); + if (healthyThreshold < 2 || healthyThreshold > 10) { + throw new Error('healthyThreshold must be between 2 and 10'); + } + + if (unhealthyThreshold < 2 || unhealthyThreshold > 10) { + throw new Error('unhealthyThreshold must be between 2 and 10'); + } + + if (interval.toMilliseconds() < 5000 || interval.toMilliseconds() > 300_000) { + throw new Error('interval must be between 5 seconds and 300 seconds'); + } + + if (timeout.toMilliseconds() < 2000 || timeout.toMilliseconds() > 60_000) { + throw new Error('timeout must be between 2 seconds and 60 seconds'); + } + + // Default to / for HTTP Health Checks + if (path === undefined && (protocol === Protocol.HTTP || protocol === Protocol.HTTP2)) { + this.path = '/'; + } + } + + public bind(_scope: Construct, options: HealthCheckBindOptions): HealthCheckConfig { + return { + virtualNodeHealthCheck: { + protocol: this.protocol, + healthyThreshold: this.healthyThreshold, + unhealthyThreshold: this.unhealthyThreshold, + intervalMillis: this.interval.toMilliseconds(), + timeoutMillis: this.timeout.toMilliseconds(), + path: this.path, + port: options.defaultPort, + }, + virtualGatewayHealthCheck: { + protocol: this.protocol, + healthyThreshold: this.healthyThreshold, + unhealthyThreshold: this.unhealthyThreshold, + intervalMillis: this.interval.toMilliseconds(), + timeoutMillis: this.timeout.toMilliseconds(), + path: this.path, + port: options.defaultPort, + }, + }; + } + +} diff --git a/packages/@aws-cdk/aws-appmesh/lib/index.ts b/packages/@aws-cdk/aws-appmesh/lib/index.ts index 1f5ca87def34d..4365a00da1279 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/index.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/index.ts @@ -16,3 +16,4 @@ export * from './virtual-gateway-listener'; export * from './gateway-route'; export * from './gateway-route-spec'; export * from './client-policy'; +export * from './health-checks'; diff --git a/packages/@aws-cdk/aws-appmesh/lib/private/utils.ts b/packages/@aws-cdk/aws-appmesh/lib/private/utils.ts index 7b5cfe620de1c..8b6bd42f5b27e 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/private/utils.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/private/utils.ts @@ -1,45 +1,3 @@ -import * as cdk from '@aws-cdk/core'; -import { CfnVirtualGateway, CfnVirtualNode } from '../appmesh.generated'; - -type AppMeshHealthCheck = CfnVirtualNode.HealthCheckProperty | CfnVirtualGateway.VirtualGatewayHealthCheckPolicyProperty - -/** - * Validates health check properties, throws an error if they are misconfigured. - * - * @param healthCheck Healthcheck property from a Virtual Node or Virtual Gateway - */ -export function validateHealthChecks(healthCheck: AppMeshHealthCheck) { - (Object.keys(healthCheck) as Array) - .filter((key) => - HEALTH_CHECK_PROPERTY_THRESHOLDS[key] && - typeof healthCheck[key] === 'number' && - !cdk.Token.isUnresolved(healthCheck[key]), - ).map((key) => { - const [min, max] = HEALTH_CHECK_PROPERTY_THRESHOLDS[key]!; - const value = healthCheck[key]!; - - if (value < min) { - throw new Error(`The value of '${key}' is below the minimum threshold (expected >=${min}, got ${value})`); - } - if (value > max) { - throw new Error(`The value of '${key}' is above the maximum threshold (expected <=${max}, got ${value})`); - } - }); -} - -/** - * Minimum and maximum thresholds for HeathCheck numeric properties - * - * @see https://docs.aws.amazon.com/app-mesh/latest/APIReference/API_HealthCheckPolicy.html - */ -const HEALTH_CHECK_PROPERTY_THRESHOLDS: {[key in (keyof AppMeshHealthCheck)]?: [number, number]} = { - healthyThreshold: [2, 10], - intervalMillis: [5000, 300000], - port: [1, 65535], - timeoutMillis: [2000, 60000], - unhealthyThreshold: [2, 10], -}; - /** * Generated Connection pool config */ diff --git a/packages/@aws-cdk/aws-appmesh/lib/route-spec.ts b/packages/@aws-cdk/aws-appmesh/lib/route-spec.ts index f3fc872bb6e41..31e4055c5baf0 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/route-spec.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/route-spec.ts @@ -1,7 +1,7 @@ import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnRoute } from './appmesh.generated'; -import { Protocol, HttpTimeout, GrpcTimeout, TcpTimeout } from './shared-interfaces'; +import { HttpTimeout, GrpcTimeout, Protocol, TcpTimeout } from './shared-interfaces'; import { IVirtualNode } from './virtual-node'; /** diff --git a/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts b/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts index 61872d4b56bcd..513b7a8e553fa 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts @@ -56,6 +56,8 @@ export interface TcpTimeout { /** * Enum of supported AppMesh protocols + * + * @deprecated not for use outside package */ export enum Protocol { HTTP = 'http', @@ -64,63 +66,6 @@ export enum Protocol { GRPC = 'grpc', } -/** - * Properties used to define healthchecks when creating virtual nodes. - * All values have a default if only specified as {} when creating. - * If property not set, then no healthchecks will be defined. - */ -export interface HealthCheck { - /** - * Number of successful attempts before considering the node UP - * - * @default 2 - */ - readonly healthyThreshold?: number; - - /** - * Interval in milliseconds to re-check - * - * @default 5 seconds - */ - readonly interval?: cdk.Duration; - - /** - * The path where the application expects any health-checks, this can also be the application path. - * - * @default / - */ - readonly path?: string; - - /** - * The TCP port number for the healthcheck - * - * @default - same as corresponding port mapping - */ - readonly port?: number; - - /** - * The protocol to use for the healthcheck, for convinience a const enum has been defined. - * Protocol.HTTP or Protocol.TCP - * - * @default - same as corresponding port mapping - */ - readonly protocol?: Protocol; - - /** - * Timeout in milli-seconds for the healthcheck to be considered a fail. - * - * @default 2 seconds - */ - readonly timeout?: cdk.Duration; - - /** - * Number of failed attempts before considering the node DOWN. - * - * @default 2 - */ - readonly unhealthyThreshold?: number; -} - /** * Represents the outlier detection for a listener. */ diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway-listener.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway-listener.ts index cf429b388bb7a..6eb42c2c917e5 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway-listener.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway-listener.ts @@ -1,16 +1,16 @@ -import * as cdk from '@aws-cdk/core'; -import { Construct } from 'constructs'; import { CfnVirtualGateway } from './appmesh.generated'; -import { validateHealthChecks, ConnectionPoolConfig } from './private/utils'; +import { HealthCheck } from './health-checks'; +import { ConnectionPoolConfig } from './private/utils'; import { GrpcConnectionPool, - HealthCheck, Http2ConnectionPool, HttpConnectionPool, Protocol, } from './shared-interfaces'; import { TlsCertificate, TlsCertificateConfig } from './tls-certificate'; +import { Construct } from 'constructs'; + /** * Represents the properties needed to define a Listeners for a VirtualGateway */ @@ -140,7 +140,7 @@ class VirtualGatewayListenerImpl extends VirtualGatewayListener { port: this.port, protocol: this.protocol, }, - healthCheck: this.healthCheck ? renderHealthCheck(this.healthCheck, this.protocol, this.port): undefined, + healthCheck: this.healthCheck?.bind(scope, { defaultPort: this.port }).virtualGatewayHealthCheck, tls: tlsConfig ? renderTls(tlsConfig) : undefined, connectionPool: this.connectionPool ? renderConnectionPool(this.connectionPool, this.protocol) : undefined, }, @@ -159,34 +159,6 @@ function renderTls(tlsCertificateConfig: TlsCertificateConfig): CfnVirtualGatewa }; } -function renderHealthCheck(hc: HealthCheck, listenerProtocol: Protocol, - listenerPort: number): CfnVirtualGateway.VirtualGatewayHealthCheckPolicyProperty { - - if (hc.protocol === Protocol.TCP) { - throw new Error('TCP health checks are not permitted for gateway listeners'); - } - - if (hc.protocol === Protocol.GRPC && hc.path) { - throw new Error('The path property cannot be set with Protocol.GRPC'); - } - - const protocol = hc.protocol? hc.protocol : listenerProtocol; - - const healthCheck: CfnVirtualGateway.VirtualGatewayHealthCheckPolicyProperty = { - healthyThreshold: hc.healthyThreshold || 2, - intervalMillis: (hc.interval || cdk.Duration.seconds(5)).toMilliseconds(), // min - path: hc.path || ((protocol === Protocol.HTTP || protocol === Protocol.HTTP2) ? '/' : undefined), - port: hc.port || listenerPort, - protocol: hc.protocol || listenerProtocol, - timeoutMillis: (hc.timeout || cdk.Duration.seconds(2)).toMilliseconds(), - unhealthyThreshold: hc.unhealthyThreshold || 2, - }; - - validateHealthChecks(healthCheck); - - return healthCheck; -} - function renderConnectionPool(connectionPool: ConnectionPoolConfig, listenerProtocol: Protocol): CfnVirtualGateway.VirtualGatewayConnectionPoolProperty { return ({ diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-node-listener.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-node-listener.ts index f1606613950b6..67dd2bb8a3762 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-node-listener.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-node-listener.ts @@ -1,13 +1,14 @@ -import * as cdk from '@aws-cdk/core'; -import { Construct } from 'constructs'; import { CfnVirtualNode } from './appmesh.generated'; -import { validateHealthChecks, ConnectionPoolConfig } from './private/utils'; +import { HealthCheck } from './health-checks'; +import { ConnectionPoolConfig } from './private/utils'; import { - GrpcConnectionPool, GrpcTimeout, HealthCheck, Http2ConnectionPool, HttpConnectionPool, + GrpcConnectionPool, GrpcTimeout, Http2ConnectionPool, HttpConnectionPool, HttpTimeout, OutlierDetection, Protocol, TcpConnectionPool, TcpTimeout, } from './shared-interfaces'; import { TlsCertificate, TlsCertificateConfig } from './tls-certificate'; +import { Construct } from 'constructs'; + /** * Properties for a VirtualNode listener */ @@ -182,7 +183,7 @@ class VirtualNodeListenerImpl extends VirtualNodeListener { port: this.port, protocol: this.protocol, }, - healthCheck: this.healthCheck ? this.renderHealthCheck(this.healthCheck) : undefined, + healthCheck: this.healthCheck?.bind(scope, { defaultPort: this.port }).virtualNodeHealthCheck, timeout: this.timeout ? this.renderTimeout(this.timeout) : undefined, tls: tlsConfig ? this.renderTls(tlsConfig) : undefined, outlierDetection: this.outlierDetection ? this.renderOutlierDetection(this.outlierDetection) : undefined, @@ -201,32 +202,6 @@ class VirtualNodeListenerImpl extends VirtualNodeListener { }; } - private renderHealthCheck(hc: HealthCheck): CfnVirtualNode.HealthCheckProperty | undefined { - if (hc === undefined) { return undefined; } - - if (hc.protocol === Protocol.TCP && hc.path) { - throw new Error('The path property cannot be set with Protocol.TCP'); - } - - if (hc.protocol === Protocol.GRPC && hc.path) { - throw new Error('The path property cannot be set with Protocol.GRPC'); - } - - const healthCheck: CfnVirtualNode.HealthCheckProperty = { - healthyThreshold: hc.healthyThreshold || 2, - intervalMillis: (hc.interval || cdk.Duration.seconds(5)).toMilliseconds(), // min - path: hc.path || (hc.protocol === Protocol.HTTP ? '/' : undefined), - port: hc.port || this.port, - protocol: hc.protocol || this.protocol, - timeoutMillis: (hc.timeout || cdk.Duration.seconds(2)).toMilliseconds(), - unhealthyThreshold: hc.unhealthyThreshold || 2, - }; - - validateHealthChecks(healthCheck); - - return healthCheck; - } - private renderTimeout(timeout: HttpTimeout): CfnVirtualNode.ListenerTimeoutProperty { return ({ [this.protocol]: { @@ -267,4 +242,3 @@ class VirtualNodeListenerImpl extends VirtualNodeListener { }); } } - diff --git a/packages/@aws-cdk/aws-appmesh/package.json b/packages/@aws-cdk/aws-appmesh/package.json index 58873a1b8271f..56c6af1f11ad5 100644 --- a/packages/@aws-cdk/aws-appmesh/package.json +++ b/packages/@aws-cdk/aws-appmesh/package.json @@ -184,7 +184,8 @@ "duration-prop-type:@aws-cdk/aws-appmesh.TcpVirtualNodeListenerOptions.timeout", "duration-prop-type:@aws-cdk/aws-appmesh.GrpcRouteSpecOptions.timeout", "duration-prop-type:@aws-cdk/aws-appmesh.HttpRouteSpecOptions.timeout", - "duration-prop-type:@aws-cdk/aws-appmesh.TcpRouteSpecOptions.timeout" + "duration-prop-type:@aws-cdk/aws-appmesh.TcpRouteSpecOptions.timeout", + "no-unused-type:@aws-cdk/aws-appmesh.Protocol" ] }, "stability": "experimental", diff --git a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts index 68709def26f95..3e1b18a0073b1 100644 --- a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts @@ -31,10 +31,10 @@ const virtualService = new appmesh.VirtualService(stack, 'service', { const node = mesh.addVirtualNode('node', { serviceDiscovery: appmesh.ServiceDiscovery.dns(`node1.${namespace.namespaceName}`), listeners: [appmesh.VirtualNodeListener.http({ - healthCheck: { + healthCheck: appmesh.HealthCheck.http({ healthyThreshold: 3, path: '/check-path', - }, + }), })], backends: [appmesh.Backend.virtualService(virtualService)], }); @@ -67,15 +67,13 @@ router.addRoute('route-1', { const node2 = mesh.addVirtualNode('node2', { serviceDiscovery: appmesh.ServiceDiscovery.dns(`node2.${namespace.namespaceName}`), listeners: [appmesh.VirtualNodeListener.http({ - healthCheck: { + healthCheck: appmesh.HealthCheck.http({ healthyThreshold: 3, interval: cdk.Duration.seconds(5), path: '/check-path2', - port: 8080, - protocol: appmesh.Protocol.HTTP, timeout: cdk.Duration.seconds(2), unhealthyThreshold: 2, - }, + }), })], backendDefaults: { clientPolicy: appmesh.ClientPolicy.fileTrust({ @@ -93,15 +91,13 @@ const node2 = mesh.addVirtualNode('node2', { const node3 = mesh.addVirtualNode('node3', { serviceDiscovery: appmesh.ServiceDiscovery.dns(`node3.${namespace.namespaceName}`), listeners: [appmesh.VirtualNodeListener.http({ - healthCheck: { + healthCheck: appmesh.HealthCheck.http({ healthyThreshold: 3, interval: cdk.Duration.seconds(5), path: '/check-path3', - port: 8080, - protocol: appmesh.Protocol.HTTP, timeout: cdk.Duration.seconds(2), unhealthyThreshold: 2, - }, + }), })], backendDefaults: { clientPolicy: appmesh.ClientPolicy.fileTrust({ @@ -208,9 +204,9 @@ new appmesh.VirtualGateway(stack, 'gateway2', { mesh: mesh, listeners: [appmesh.VirtualGatewayListener.http({ port: 443, - healthCheck: { + healthCheck: appmesh.HealthCheck.http({ interval: cdk.Duration.seconds(10), - }, + }), tlsCertificate: appmesh.TlsCertificate.file({ certificateChainPath: 'path/to/certChain', privateKeyPath: 'path/to/privateKey', diff --git a/packages/@aws-cdk/aws-appmesh/test/test.health-check.ts b/packages/@aws-cdk/aws-appmesh/test/test.health-check.ts index 1ba7dc425da07..57c6aa9ee1d61 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.health-check.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.health-check.ts @@ -22,14 +22,14 @@ export = { // WHEN const toThrow = (millis: number) => getNode(stack).addListener(appmesh.VirtualNodeListener.http2({ - healthCheck: { interval: cdk.Duration.millis(millis) }, + healthCheck: appmesh.HealthCheck.http2({ interval: cdk.Duration.millis(millis) }), })); // THEN test.doesNotThrow(() => toThrow(min)); test.doesNotThrow(() => toThrow(max)); - test.throws(() => toThrow(min - 1), /below the minimum threshold/); - test.throws(() => toThrow(max + 1), /above the maximum threshold/); + test.throws(() => toThrow(min - 1), /interval must be between 5 seconds and 300 seconds/); + test.throws(() => toThrow(max + 1), /interval must be between 5 seconds and 300 seconds/); test.done(); }, @@ -41,32 +41,14 @@ export = { // WHEN const toThrow = (millis: number) => getNode(stack).addListener(appmesh.VirtualNodeListener.http2({ - healthCheck: { timeout: cdk.Duration.millis(millis) }, + healthCheck: appmesh.HealthCheck.http2({ timeout: cdk.Duration.millis(millis) }), })); // THEN test.doesNotThrow(() => toThrow(min)); test.doesNotThrow(() => toThrow(max)); - test.throws(() => toThrow(min - 1), /below the minimum threshold/); - test.throws(() => toThrow(max + 1), /above the maximum threshold/); - - test.done(); - }, - 'port'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - const [min, max] = [1, 65535]; - - // WHEN - const toThrow = (port: number) => getNode(stack).addListener(appmesh.VirtualNodeListener.http({ - healthCheck: { port }, - })); - - // THEN - test.doesNotThrow(() => toThrow(min)); - test.doesNotThrow(() => toThrow(max)); - test.throws(() => toThrow(max + 1), /above the maximum threshold/); + test.throws(() => toThrow(min - 1), /timeout must be between 2 seconds and 60 seconds/); + test.throws(() => toThrow(max + 1), /timeout must be between 2 seconds and 60 seconds/); test.done(); }, @@ -78,14 +60,14 @@ export = { // WHEN const toThrow = (healthyThreshold: number) => getNode(stack).addListener(appmesh.VirtualNodeListener.http({ - healthCheck: { healthyThreshold }, + healthCheck: appmesh.HealthCheck.http({ healthyThreshold }), })); // THEN test.doesNotThrow(() => toThrow(min)); test.doesNotThrow(() => toThrow(max)); - test.throws(() => toThrow(min - 1), /below the minimum threshold/); - test.throws(() => toThrow(max + 1), /above the maximum threshold/); + test.throws(() => toThrow(min - 1), /healthyThreshold must be between 2 and 10/); + test.throws(() => toThrow(max + 1), /healthyThreshold must be between 2 and 10/); test.done(); }, @@ -97,53 +79,15 @@ export = { // WHEN const toThrow = (unhealthyThreshold: number) => getNode(stack).addListener(appmesh.VirtualNodeListener.http({ - healthCheck: { unhealthyThreshold }, + healthCheck: appmesh.HealthCheck.http({ unhealthyThreshold }), })); // THEN test.doesNotThrow(() => toThrow(min)); test.doesNotThrow(() => toThrow(max)); - test.throws(() => toThrow(min - 1), /below the minimum threshold/); - test.throws(() => toThrow(max + 1), /above the maximum threshold/); + test.throws(() => toThrow(min - 1), /unhealthyThreshold must be between 2 and 10/); + test.throws(() => toThrow(max + 1), /unhealthyThreshold must be between 2 and 10/); test.done(); }, - 'throws if path and Protocol.TCP'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - const toThrow = (protocol: appmesh.Protocol) => getNode(stack).addListener(appmesh.VirtualNodeListener.http({ - healthCheck: { - protocol, - path: '/', - }, - })); - - // THEN - test.doesNotThrow(() => toThrow(appmesh.Protocol.HTTP)); - test.throws(() => toThrow(appmesh.Protocol.TCP), /The path property cannot be set with Protocol.TCP/); - - test.done(); - }, - - 'throws if path and Protocol.GRPC'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - const toThrow = (protocol: appmesh.Protocol) => getNode(stack).addListener(appmesh.VirtualNodeListener.http({ - healthCheck: { - protocol, - path: '/', - }, - })); - - // THEN - test.doesNotThrow(() => toThrow(appmesh.Protocol.HTTP)); - test.throws(() => toThrow(appmesh.Protocol.GRPC), /The path property cannot be set with Protocol.GRPC/); - - test.done(); - }, - }; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts b/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts index ac71a80017d0a..dfecbc4292c88 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts @@ -213,13 +213,13 @@ export = { serviceDiscovery: appmesh.ServiceDiscovery.dns('test.domain.local'), listeners: [appmesh.VirtualNodeListener.http({ port: 8080, - healthCheck: { + healthCheck: appmesh.HealthCheck.http({ healthyThreshold: 3, path: '/', interval: cdk.Duration.seconds(5), // min timeout: cdk.Duration.seconds(2), // min unhealthyThreshold: 2, - }, + }), })], }); diff --git a/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts b/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts index 96d3c7cba9210..bafc0eccd6822 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts @@ -23,9 +23,9 @@ export = { mesh: mesh, listeners: [appmesh.VirtualGatewayListener.http({ port: 443, - healthCheck: { + healthCheck: appmesh.HealthCheck.http({ interval: cdk.Duration.seconds(10), - }, + }), })], }); @@ -33,9 +33,7 @@ export = { mesh: mesh, listeners: [appmesh.VirtualGatewayListener.http2({ port: 443, - healthCheck: { - interval: cdk.Duration.seconds(10), - }, + healthCheck: appmesh.HealthCheck.http2({ interval: cdk.Duration.seconds(10) }), })], }); @@ -115,8 +113,7 @@ export = { virtualGatewayName: 'test-gateway', listeners: [appmesh.VirtualGatewayListener.grpc({ port: 80, - healthCheck: { - }, + healthCheck: appmesh.HealthCheck.grpc(), })], mesh: mesh, accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), diff --git a/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts b/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts index f143b0025c1db..b993309547ab8 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts @@ -163,7 +163,7 @@ export = { serviceDiscovery: appmesh.ServiceDiscovery.dns('test'), listeners: [appmesh.VirtualNodeListener.http2({ port: 80, - healthCheck: {}, + healthCheck: appmesh.HealthCheck.http2(), timeout: { idle: cdk.Duration.seconds(10) }, })], }); @@ -219,7 +219,9 @@ export = { node.addListener(appmesh.VirtualNodeListener.tcp({ port: 80, - healthCheck: { timeout: cdk.Duration.seconds(3) }, + healthCheck: appmesh.HealthCheck.tcp({ + timeout: cdk.Duration.seconds(3), + }), timeout: { idle: cdk.Duration.seconds(10) }, })); diff --git a/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts b/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts index 5f3f5e30e5fad..7527fbf7c21ec 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts @@ -3,12 +3,10 @@ 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 lambda from '@aws-cdk/aws-lambda'; -// hack, as this is not exported by the Lambda module -import { calculateFunctionHash } from '@aws-cdk/aws-lambda/lib/function-hash'; import * as ssm from '@aws-cdk/aws-ssm'; import { - CustomResource, CustomResourceProvider, CustomResourceProviderRuntime, - Resource, Stack, Stage, Token, + CfnResource, CustomResource, CustomResourceProvider, CustomResourceProviderRuntime, + Lazy, Resource, Stack, Stage, Token, } from '@aws-cdk/core'; import { Construct, Node } from 'constructs'; @@ -156,17 +154,18 @@ export class EdgeFunction extends Resource implements lambda.IVersion { addEdgeLambdaToRoleTrustStatement(edgeFunction.role!); // Store the current version's ARN to be retrieved by the cross region reader below. + const version = edgeFunction.currentVersion; new ssm.StringParameter(edgeFunction, 'Parameter', { parameterName, - stringValue: edgeFunction.currentVersion.edgeArn, + stringValue: version.edgeArn, }); - const edgeArn = this.createCrossRegionArnReader(parameterNamePrefix, parameterName, edgeFunction); + const edgeArn = this.createCrossRegionArnReader(parameterNamePrefix, parameterName, version); return { edgeFunction, edgeArn }; } - private createCrossRegionArnReader(parameterNamePrefix: string, parameterName: string, edgeFunction: lambda.Function): string { + private createCrossRegionArnReader(parameterNamePrefix: string, parameterName: string, version: lambda.Version): string { // Prefix of the parameter ARN that applies to all EdgeFunctions. // This is necessary because the `CustomResourceProvider` is a singleton, and the `policyStatement` // must work for multiple EdgeFunctions. @@ -195,7 +194,15 @@ export class EdgeFunction extends Resource implements lambda.IVersion { Region: EdgeFunction.EDGE_REGION, ParameterName: parameterName, // This is used to determine when the function has changed, to refresh the ARN from the custom resource. - RefreshToken: calculateFunctionHash(edgeFunction), + // + // Use the logical id of the function version. Whenever a function version changes, the logical id must be + // changed for it to take effect - a good candidate for RefreshToken. + RefreshToken: Lazy.uncachedString({ + produce: () => { + const cfn = version.node.defaultChild as CfnResource; + return this.stack.resolve(cfn.logicalId); + }, + }), }, }); diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.expected.json index 6da7e8717d61f..d95e038c2c890 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.expected.json +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda-cross-region.expected.json @@ -12,7 +12,7 @@ }, "Region": "us-east-1", "ParameterName": "/cdk/EdgeFunctionArn/eu-west-1/integ-distribution-lambda-cross-region/Lambda", - "RefreshToken": "4412ddb0ae449da20173ca211c51fddc" + "RefreshToken": "LambdaCurrentVersionDF706F6A97fb843e9bd06fcd2bb15eeace80e13e" }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" @@ -138,7 +138,7 @@ }, "Region": "us-east-1", "ParameterName": "/cdk/EdgeFunctionArn/eu-west-1/integ-distribution-lambda-cross-region/Lambda2", - "RefreshToken": "8f81ceb404ac454f09648e62822d9ca9" + "RefreshToken": "Lambda2CurrentVersion72012B74b9eef8becb98501bc795baca3c6169c4" }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/metric-types.ts b/packages/@aws-cdk/aws-cloudwatch/lib/metric-types.ts index eaa48446672f9..296400ee7f910 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/metric-types.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/metric-types.ts @@ -12,14 +12,14 @@ export interface IMetric { /** * Turn this metric object into an alarm configuration * - * @deprecated Use `toMetricsConfig()` instead. + * @deprecated Use `toMetricConfig()` instead. */ toAlarmConfig(): MetricAlarmConfig; /** * Turn this metric object into a graph configuration * - * @deprecated Use `toMetricsConfig()` instead. + * @deprecated Use `toMetricConfig()` instead. */ toGraphConfig(): MetricGraphConfig; } @@ -27,6 +27,8 @@ export interface IMetric { /** * Metric dimension * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cw-dimension.html + * */ export interface Dimension { /** diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts b/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts index bfb1e94dd19aa..f5a7340b90368 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts @@ -212,7 +212,9 @@ export class Metric implements IMetric { if (periodSec !== 1 && periodSec !== 5 && periodSec !== 10 && periodSec !== 30 && periodSec % 60 !== 0) { throw new Error(`'period' must be 1, 5, 10, 30, or a multiple of 60 seconds, received ${periodSec}`); } - + if (props.dimensions) { + this.validateDimensions(props.dimensions); + } this.dimensions = props.dimensions; this.namespace = props.namespace; this.metricName = props.metricName; @@ -391,6 +393,26 @@ export class Metric implements IMetric { return list; } + + private validateDimensions(dims: DimensionHash): void { + var dimsArray = Object.keys(dims); + if (dimsArray?.length > 10) { + throw new Error(`The maximum number of dimensions is 10, received ${dimsArray.length}`); + } + + dimsArray.map(key => { + if (dims[key] === undefined || dims[key] === null) { + throw new Error(`Dimension value of '${dims[key]}' is invalid`); + }; + if (key.length < 1 || key.length > 255) { + throw new Error(`Dimension name must be at least 1 and no more than 255 characters; received ${key}`); + }; + + if (dims[key].length < 1 || dims[key].length > 255) { + throw new Error(`Dimension value must be at least 1 and no more than 255 characters; received ${dims[key]}`); + }; + }); + } } function asString(x?: unknown): string | undefined { diff --git a/packages/@aws-cdk/aws-cloudwatch/test/test.metrics.ts b/packages/@aws-cdk/aws-cloudwatch/test/test.metrics.ts index 18e1840e35cb6..e1dc9230e1404 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/test.metrics.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/test.metrics.ts @@ -51,4 +51,77 @@ export = { test.done(); }, + + 'cannot use null dimension value'(test: Test) { + test.throws(() => { + new Metric({ + namespace: 'Test', + metricName: 'ACount', + period: cdk.Duration.minutes(10), + dimensions: { + DimensionWithNull: null, + }, + }); + }, /Dimension value of 'null' is invalid/); + + test.done(); + }, + + 'cannot use undefined dimension value'(test: Test) { + test.throws(() => { + new Metric({ + namespace: 'Test', + metricName: 'ACount', + period: cdk.Duration.minutes(10), + dimensions: { + DimensionWithUndefined: undefined, + }, + }); + }, /Dimension value of 'undefined' is invalid/); + + test.done(); + }, + + 'cannot use long dimension values'(test: Test) { + const arr = new Array(256); + const invalidDimensionValue = arr.fill('A', 0).join(''); + + test.throws(() => { + new Metric({ + namespace: 'Test', + metricName: 'ACount', + period: cdk.Duration.minutes(10), + dimensions: { + DimensionWithLongValue: invalidDimensionValue, + }, + }); + }, `Dimension value must be at least 1 and no more than 255 characters; received ${invalidDimensionValue}`); + + test.done(); + }, + + 'throws error when there are more than 10 dimensions'(test: Test) { + test.throws(() => { + new Metric({ + namespace: 'Test', + metricName: 'ACount', + period: cdk.Duration.minutes(10), + dimensions: { + dimensionA: 'value1', + dimensionB: 'value2', + dimensionC: 'value3', + dimensionD: 'value4', + dimensionE: 'value5', + dimensionF: 'value6', + dimensionG: 'value7', + dimensionH: 'value8', + dimensionI: 'value9', + dimensionJ: 'value10', + dimensionK: 'value11', + }, + } ); + }, /The maximum number of dimensions is 10, received 11/); + + test.done(); + }, };