diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts index 583ca06435c09..614a1eeea2312 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts @@ -259,17 +259,28 @@ export class AppMeshExtension extends ServiceExtension { throw new Error('You must add a CloudMap namespace to the ECS cluster in order to use the AppMesh extension'); } + function addListener(protocol: appmesh.Protocol, port: number): appmesh.VirtualNodeListener { + switch (protocol) { + case appmesh.Protocol.HTTP : + return appmesh.VirtualNodeListener.http({ port }); + + case appmesh.Protocol.HTTP2 : + return appmesh.VirtualNodeListener.http2({ port }); + + case appmesh.Protocol.GRPC : + return appmesh.VirtualNodeListener.grpc({ port }); + + case appmesh.Protocol.TCP : + return appmesh.VirtualNodeListener.tcp({ port }); + } + } + // Create a virtual node for the name service this.virtualNode = new appmesh.VirtualNode(this.scope, `${this.parentService.id}-virtual-node`, { mesh: this.mesh, virtualNodeName: this.parentService.id, cloudMapService: service.cloudMapService, - listener: { - portMapping: { - port: containerextension.trafficPort, - protocol: this.protocol, - }, - }, + listeners: [addListener(this.protocol, containerextension.trafficPort)], }); // Create a virtual router for this service. This allows for retries @@ -326,7 +337,7 @@ export class AppMeshExtension extends ServiceExtension { // Next update the app mesh config so that the local Envoy // proxy on this service knows how to route traffic to // nodes from the other service. - this.virtualNode.addBackends(otherAppMesh.virtualService); + this.virtualNode.addBackend(otherAppMesh.virtualService); } private virtualRouterListener(port: number): appmesh.VirtualRouterListener { diff --git a/packages/@aws-cdk/aws-appmesh/README.md b/packages/@aws-cdk/aws-appmesh/README.md index 9c0a4b7f1da2e..dff8a6808c701 100644 --- a/packages/@aws-cdk/aws-appmesh/README.md +++ b/packages/@aws-cdk/aws-appmesh/README.md @@ -139,11 +139,8 @@ const service = namespace.createService('Svc'); const node = mesh.addVirtualNode('virtual-node', { cloudMapService: service, - listener: { - portMapping: { - port: 8081, - protocol: Protocol.HTTP, - }, + listeners: [appmesh.VirtualNodeListener.httpNodeListener({ + port: 8081, healthCheck: { healthyThreshold: 3, interval: Duration.seconds(5), // minimum @@ -153,9 +150,9 @@ const node = mesh.addVirtualNode('virtual-node', { timeout: Duration.seconds(2), // minimum unhealthyThreshold: 2, }, - }, + })], accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), -}) +}); ``` Create a `VirtualNode` with the the constructor and add tags. @@ -164,11 +161,8 @@ Create a `VirtualNode` with the the constructor and add tags. const node = new VirtualNode(this, 'node', { mesh, cloudMapService: service, - listener: { - portMapping: { - port: 8080, - protocol: Protocol.HTTP, - }, + listeners: [appmesh.VirtualNodeListener.httpNodeListener({ + port: 8080, healthCheck: { healthyThreshold: 3, interval: Duration.seconds(5), // min @@ -177,15 +171,18 @@ const node = new VirtualNode(this, 'node', { protocol: Protocol.HTTP, timeout: Duration.seconds(2), // min unhealthyThreshold: 2, + }, + timeout: { + idle: cdk.Duration.seconds(5), }, - }, + })], accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), }); cdk.Tag.add(node, 'Environment', 'Dev'); ``` -The listeners property can be left blank and added later with the `node.addListeners()` method. The `healthcheck` property is optional but if specifying a listener, the `portMappings` must contain at least one property. +The `listeners` property can be left blank and added later with the `node.addListener()` method. The `healthcheck` and `timeout` properties are optional but if specifying a listener, the `port` must be added. ## Adding a Route diff --git a/packages/@aws-cdk/aws-appmesh/lib/index.ts b/packages/@aws-cdk/aws-appmesh/lib/index.ts index d95c017c2071e..4c09b13ba730c 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/index.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/index.ts @@ -7,6 +7,7 @@ export * from './virtual-node'; export * from './virtual-router'; export * from './virtual-router-listener'; export * from './virtual-service'; +export * from './virtual-node-listener'; export * from './virtual-gateway'; export * from './virtual-gateway-listener'; export * from './gateway-route'; diff --git a/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts b/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts index 39111389b0a03..21ab96b6ce56a 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts @@ -68,44 +68,6 @@ export interface HealthCheck { readonly unhealthyThreshold?: number; } -/** - * Port mappings for resources that require these attributes, such as VirtualNodes and Routes - */ -export interface PortMapping { - /** - * Port mapped to the VirtualNode / Route - * - * @default 8080 - */ - readonly port: number; - - /** - * Protocol for the VirtualNode / Route, only GRPC, HTTP, HTTP2, or TCP is supported - * - * @default HTTP - */ - readonly protocol: Protocol; -} - -/** - * Represents the properties needed to define healthy and active listeners for nodes - */ -export interface VirtualNodeListener { - /** - * Array of PortMappingProps for the listener - * - * @default - HTTP port 8080 - */ - readonly portMapping?: PortMapping; - - /** - * Health checking strategy upstream nodes should use when communicating with the listener - * - * @default - no healthcheck - */ - readonly healthCheck?: HealthCheck; -} - /** * All Properties for Envoy Access logs for mesh endpoints */ diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-node-listener.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-node-listener.ts new file mode 100644 index 0000000000000..e3e433d8e25d8 --- /dev/null +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-node-listener.ts @@ -0,0 +1,219 @@ +import * as cdk from '@aws-cdk/core'; +import { CfnVirtualNode } from './appmesh.generated'; +import { validateHealthChecks } from './private/utils'; +import { HealthCheck, Protocol } from './shared-interfaces'; + +/** + * Properties for a VirtualNode listener + */ +export interface VirtualNodeListenerConfig { + /** + * Single listener config for a VirtualNode + */ + readonly listener: CfnVirtualNode.ListenerProperty, +} + +/** + * Represents the properties needed to define a Listeners for a VirtualNode + */ +interface VirtualNodeListenerCommonOptions { + /** + * Port to listen for connections on + * + * @default - 8080 + */ + readonly port?: number + + /** + * The health check information for the listener + * + * @default - no healthcheck + */ + readonly healthCheck?: HealthCheck; +} + +/** + * Represent the HTTP Node Listener prorperty + */ +export interface HttpVirtualNodeListenerOptions extends VirtualNodeListenerCommonOptions { + /** + * Timeout for HTTP protocol + * + * @default - None + */ + readonly timeout?: HttpTimeout; +} + +/** + * Represent the GRPC Node Listener prorperty + */ +export interface GrpcVirtualNodeListenerOptions extends VirtualNodeListenerCommonOptions { + /** + * Timeout for GRPC protocol + * + * @default - None + */ + readonly timeout?: GrpcTimeout; +} + +/** + * Represent the TCP Node Listener prorperty + */ +export interface TcpVirtualNodeListenerOptions extends VirtualNodeListenerCommonOptions { + /** + * Timeout for TCP protocol + * + * @default - None + */ + readonly timeout?: TcpTimeout; +} + +/** + * Represents timeouts for HTTP protocols. + */ +export interface HttpTimeout { + /** + * Represents an idle timeout. The amount of time that a connection may be idle. + * + * @default - none + */ + readonly idle?: cdk.Duration; + + /** + * Represents per request timeout. + * + * @default - 15 s + */ + readonly perRequest?: cdk.Duration; +} + +/** + * Represents timeouts for GRPC protocols. + */ +export interface GrpcTimeout { + /** + * Represents an idle timeout. The amount of time that a connection may be idle. + * + * @default - none + */ + readonly idle?: cdk.Duration; + + /** + * Represents per request timeout. + * + * @default - 15 s + */ + readonly perRequest?: cdk.Duration; +} + +/** + * Represents timeouts for TCP protocols. + */ +export interface TcpTimeout { + /** + * Represents an idle timeout. The amount of time that a connection may be idle. + * + * @default - none + */ + readonly idle?: cdk.Duration; +} + +/** + * Defines listener for a VirtualNode + */ +export abstract class VirtualNodeListener { + /** + * Returns an HTTP Listener for a VirtualNode + */ + public static http(props: HttpVirtualNodeListenerOptions = {}): VirtualNodeListener { + return new VirtualNodeListenerImpl(Protocol.HTTP, props.healthCheck, props.timeout, props.port); + } + + /** + * Returns an HTTP2 Listener for a VirtualNode + */ + public static http2(props: HttpVirtualNodeListenerOptions = {}): VirtualNodeListener { + return new VirtualNodeListenerImpl(Protocol.HTTP2, props.healthCheck, props.timeout, props.port); + } + + /** + * Returns an GRPC Listener for a VirtualNode + */ + public static grpc(props: GrpcVirtualNodeListenerOptions = {}): VirtualNodeListener { + return new VirtualNodeListenerImpl(Protocol.GRPC, props.healthCheck, props.timeout, props.port); + } + + /** + * Returns an TCP Listener for a VirtualNode + */ + public static tcp(props: TcpVirtualNodeListenerOptions = {}): VirtualNodeListener { + return new VirtualNodeListenerImpl(Protocol.TCP, props.healthCheck, props.timeout, props.port); + } + + /** + * Binds the current object when adding Listener to a VirtualNode + */ + public abstract bind(scope: cdk.Construct): VirtualNodeListenerConfig; + +} + +class VirtualNodeListenerImpl extends VirtualNodeListener { + constructor(private readonly protocol: Protocol, + private readonly healthCheck: HealthCheck | undefined, + private readonly timeout: HttpTimeout | undefined, + private readonly port: number = 8080) { super(); } + + public bind(_scope: cdk.Construct): VirtualNodeListenerConfig { + return { + listener: { + portMapping: { + port: this.port, + protocol: this.protocol, + }, + healthCheck: this.healthCheck ? this.renderHealthCheck(this.healthCheck) : undefined, + timeout: this.timeout ? this.renderTimeout(this.timeout) : undefined, + }, + }; + } + + 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]: { + idle: timeout?.idle !== undefined ? { + unit: 'ms', + value: timeout?.idle.toMilliseconds(), + } : undefined, + perRequest: timeout?.perRequest !== undefined ? { + unit: 'ms', + value: timeout?.perRequest.toMilliseconds(), + } : undefined, + }, + }); + } +} diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts index b8b9da2026715..fd8b2cadc87db 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts @@ -3,8 +3,8 @@ import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnVirtualNode } from './appmesh.generated'; import { IMesh, Mesh } from './mesh'; -import { validateHealthChecks } from './private/utils'; -import { AccessLog, HealthCheck, PortMapping, Protocol, VirtualNodeListener } from './shared-interfaces'; +import { AccessLog } from './shared-interfaces'; +import { VirtualNodeListener, VirtualNodeListenerConfig } from './virtual-node-listener'; import { IVirtualService } from './virtual-service'; /** @@ -34,15 +34,6 @@ export interface IVirtualNode extends cdk.IResource { */ readonly mesh: IMesh; - /** - * Utility method to add backends for existing or new VirtualNodes - */ - addBackends(...props: IVirtualService[]): void; - - /** - * Utility method to add Node Listeners for new or existing VirtualNodes - */ - addListeners(...listeners: VirtualNodeListener[]): void; } /** @@ -95,7 +86,7 @@ export interface VirtualNodeBaseProps { * * @default - No listeners */ - readonly listener?: VirtualNodeListener; + readonly listeners?: VirtualNodeListener[]; /** * Access Logging Configuration for the virtual node @@ -130,71 +121,10 @@ abstract class VirtualNodeBase extends cdk.Resource implements IVirtualNode { * The Mesh which the VirtualNode belongs to */ public abstract readonly mesh: IMesh; - - protected readonly backends = new Array(); - protected readonly listeners = new Array(); - - /** - * Add a VirtualServices that this node is expected to send outbound traffic to - */ - public addBackends(...props: IVirtualService[]) { - for (const s of props) { - this.backends.push({ - virtualService: { - virtualServiceName: s.virtualServiceName, - }, - }); - } - } - - /** - * Utility method to add an inbound listener for this virtual node - */ - public addListeners(...listeners: VirtualNodeListener[]) { - if (this.listeners.length + listeners.length > 1) { - throw new Error('VirtualNode may have at most one listener'); - } - - for (const listener of listeners) { - const portMapping = listener.portMapping || { port: 8080, protocol: Protocol.HTTP }; - this.listeners.push({ - portMapping, - healthCheck: renderHealthCheck(listener.healthCheck, portMapping), - }); - } - } -} - -function renderHealthCheck(hc: HealthCheck | undefined, pm: PortMapping): 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 protocol = hc.protocol ?? pm.protocol; - - const healthCheck: CfnVirtualNode.HealthCheckProperty = { - healthyThreshold: hc.healthyThreshold || 2, - intervalMillis: (hc.interval || cdk.Duration.seconds(5)).toMilliseconds(), // min - path: hc.path || (protocol === Protocol.HTTP ? '/' : undefined), - port: hc.port || pm.port, - protocol: hc.protocol || pm.protocol, - timeoutMillis: (hc.timeout || cdk.Duration.seconds(2)).toMilliseconds(), - unhealthyThreshold: hc.unhealthyThreshold || 2, - }; - - validateHealthChecks(healthCheck); - - return healthCheck; } /** - * VirtualNode represents a newly defined App Mesh VirtualNode + * VirtualNode represents a newly defined AppMesh VirtualNode * * Any inbound traffic that your virtual node expects should be specified as a * listener. Any outbound traffic that your virtual node expects to reach @@ -245,6 +175,9 @@ export class VirtualNode extends VirtualNodeBase { */ public readonly mesh: IMesh; + private readonly backends = new Array(); + private readonly listeners = new Array(); + constructor(scope: Construct, id: string, props: VirtualNodeProps) { super(scope, id, { physicalName: props.virtualNodeName || cdk.Lazy.stringValue({ produce: () => cdk.Names.uniqueId(this) }), @@ -252,8 +185,8 @@ export class VirtualNode extends VirtualNodeBase { this.mesh = props.mesh; - this.addBackends(...props.backends || []); - this.addListeners(...props.listener ? [props.listener] : []); + props.backends?.forEach(backend => this.addBackend(backend)); + props.listeners?.forEach(listener => this.addListener(listener)); const accessLogging = props.accessLog?.bind(this); const node = new CfnVirtualNode(this, 'Resource', { @@ -261,7 +194,7 @@ export class VirtualNode extends VirtualNodeBase { meshName: this.mesh.meshName, spec: { backends: cdk.Lazy.anyValue({ produce: () => this.backends }, { omitEmptyArray: true }), - listeners: cdk.Lazy.anyValue({ produce: () => this.listeners }, { omitEmptyArray: true }), + listeners: cdk.Lazy.anyValue({ produce: () => this.listeners.map(listener => listener.listener) }, { omitEmptyArray: true }), serviceDiscovery: { dns: props.dnsHostName !== undefined ? { hostname: props.dnsHostName } : undefined, awsCloudMap: props.cloudMapService !== undefined ? { @@ -283,6 +216,24 @@ export class VirtualNode extends VirtualNodeBase { resourceName: this.physicalName, }); } + + /** + * Utility method to add an inbound listener for this VirtualNode + */ + public addListener(listener: VirtualNodeListener) { + this.listeners.push(listener.bind(this)); + } + + /** + * Add a Virtual Services that this node is expected to send outbound traffic to + */ + public addBackend(virtualService: IVirtualService) { + this.backends.push({ + virtualService: { + virtualServiceName: virtualService.virtualServiceName, + }, + }); + } } function renderAttributes(attrs?: {[key: string]: string}) { diff --git a/packages/@aws-cdk/aws-appmesh/package.json b/packages/@aws-cdk/aws-appmesh/package.json index 2789d9085284c..6b58c9981965a 100644 --- a/packages/@aws-cdk/aws-appmesh/package.json +++ b/packages/@aws-cdk/aws-appmesh/package.json @@ -153,6 +153,7 @@ "resource-attribute:@aws-cdk/aws-appmesh.VirtualGateway.virtualGatewayResourceOwner", "resource-attribute:@aws-cdk/aws-appmesh.VirtualGateway.virtualGatewayUid", "resource-attribute:@aws-cdk/aws-appmesh.VirtualNode.virtualNodeMeshName", + "duration-prop-type:@aws-cdk/aws-appmesh.VirtualNodeListener.timeout", "resource-attribute:@aws-cdk/aws-appmesh.VirtualNode.virtualNodeMeshOwner", "resource-attribute:@aws-cdk/aws-appmesh.VirtualNode.virtualNodeResourceOwner", "resource-attribute:@aws-cdk/aws-appmesh.VirtualNode.virtualNodeUid", @@ -172,7 +173,11 @@ "props-default-doc:@aws-cdk/aws-appmesh.VirtualRouterAttributes.virtualRouterName", "docs-public-apis:@aws-cdk/aws-appmesh.Protocol.HTTP", "docs-public-apis:@aws-cdk/aws-appmesh.Protocol.HTTP2", - "docs-public-apis:@aws-cdk/aws-appmesh.Protocol.GRPC" + "docs-public-apis:@aws-cdk/aws-appmesh.Protocol.GRPC", + "duration-prop-type:@aws-cdk/aws-appmesh.GrpcVirtualNodeListenerOptions.timeout", + "duration-prop-type:@aws-cdk/aws-appmesh.HttpVirtualNodeListenerOptions.timeout", + "duration-prop-type:@aws-cdk/aws-appmesh.Http2VirtualNodeListenerOptions.timeout", + "duration-prop-type:@aws-cdk/aws-appmesh.TcpVirtualNodeListenerOptions.timeout" ] }, "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 3de6eb20c77d2..8bad51d706a32 100644 --- a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts @@ -31,18 +31,18 @@ const virtualService = mesh.addVirtualService('service', { const node = mesh.addVirtualNode('node', { dnsHostName: `node1.${namespace.namespaceName}`, - listener: { + listeners: [appmesh.VirtualNodeListener.http({ healthCheck: { healthyThreshold: 3, path: '/check-path', }, - }, + })], backends: [ virtualService, ], }); -node.addBackends(new appmesh.VirtualService(stack, 'service-2', { +node.addBackend(new appmesh.VirtualService(stack, 'service-2', { virtualServiceName: 'service2.domain.local', mesh, }), @@ -60,7 +60,7 @@ router.addRoute('route-1', { const node2 = mesh.addVirtualNode('node2', { dnsHostName: `node2.${namespace.namespaceName}`, - listener: { + listeners: [appmesh.VirtualNodeListener.http({ healthCheck: { healthyThreshold: 3, interval: cdk.Duration.seconds(5), @@ -70,7 +70,7 @@ const node2 = mesh.addVirtualNode('node2', { timeout: cdk.Duration.seconds(2), unhealthyThreshold: 2, }, - }, + })], backends: [ new appmesh.VirtualService(stack, 'service-3', { virtualServiceName: 'service3.domain.local', @@ -81,7 +81,7 @@ const node2 = mesh.addVirtualNode('node2', { const node3 = mesh.addVirtualNode('node3', { dnsHostName: `node3.${namespace.namespaceName}`, - listener: { + listeners: [appmesh.VirtualNodeListener.http({ healthCheck: { healthyThreshold: 3, interval: cdk.Duration.seconds(5), @@ -91,7 +91,7 @@ const node3 = mesh.addVirtualNode('node3', { timeout: cdk.Duration.seconds(2), unhealthyThreshold: 2, }, - }, + })], accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), }); 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 6f33658598efa..d65177a124b70 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.health-check.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.health-check.ts @@ -21,9 +21,9 @@ export = { const [min, max] = [5000, 300000]; // WHEN - const toThrow = (millis: number) => getNode(stack).addListeners({ + const toThrow = (millis: number) => getNode(stack).addListener(appmesh.VirtualNodeListener.http2({ healthCheck: { interval: cdk.Duration.millis(millis) }, - }); + })); // THEN test.doesNotThrow(() => toThrow(min)); @@ -40,9 +40,9 @@ export = { const [min, max] = [2000, 60000]; // WHEN - const toThrow = (millis: number) => getNode(stack).addListeners({ + const toThrow = (millis: number) => getNode(stack).addListener(appmesh.VirtualNodeListener.http2({ healthCheck: { timeout: cdk.Duration.millis(millis) }, - }); + })); // THEN test.doesNotThrow(() => toThrow(min)); @@ -59,9 +59,9 @@ export = { const [min, max] = [1, 65535]; // WHEN - const toThrow = (port: number) => getNode(stack).addListeners({ + const toThrow = (port: number) => getNode(stack).addListener(appmesh.VirtualNodeListener.http({ healthCheck: { port }, - }); + })); // THEN test.doesNotThrow(() => toThrow(min)); @@ -79,9 +79,9 @@ export = { const [min, max] = [2, 10]; // WHEN - const toThrow = (healthyThreshold: number) => getNode(stack).addListeners({ + const toThrow = (healthyThreshold: number) => getNode(stack).addListener(appmesh.VirtualNodeListener.http({ healthCheck: { healthyThreshold }, - }); + })); // THEN test.doesNotThrow(() => toThrow(min)); @@ -98,9 +98,9 @@ export = { const [min, max] = [2, 10]; // WHEN - const toThrow = (unhealthyThreshold: number) => getNode(stack).addListeners({ + const toThrow = (unhealthyThreshold: number) => getNode(stack).addListener(appmesh.VirtualNodeListener.http({ healthCheck: { unhealthyThreshold }, - }); + })); // THEN test.doesNotThrow(() => toThrow(min)); @@ -115,12 +115,12 @@ export = { const stack = new cdk.Stack(); // WHEN - const toThrow = (protocol: appmesh.Protocol) => getNode(stack).addListeners({ + const toThrow = (protocol: appmesh.Protocol) => getNode(stack).addListener(appmesh.VirtualNodeListener.http({ healthCheck: { protocol, path: '/', }, - }); + })); // THEN test.doesNotThrow(() => toThrow(appmesh.Protocol.HTTP)); @@ -134,12 +134,12 @@ export = { const stack = new cdk.Stack(); // WHEN - const toThrow = (protocol: appmesh.Protocol) => getNode(stack).addListeners({ + const toThrow = (protocol: appmesh.Protocol) => getNode(stack).addListener(appmesh.VirtualNodeListener.http({ healthCheck: { protocol, path: '/', }, - }); + })); // THEN test.doesNotThrow(() => toThrow(appmesh.Protocol.HTTP)); @@ -148,4 +148,4 @@ export = { 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 690dce4a08ddc..1f67c708e561a 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts @@ -57,7 +57,7 @@ export = { 'When adding a Virtual Router to existing mesh': { 'with at least one complete port mappings': { - 'shoulld create proper router'(test: Test) { + 'should create proper router'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -208,12 +208,9 @@ export = { const node = mesh.addVirtualNode('test-node', { dnsHostName: 'test.domain.local', - listener: { - portMapping: { - port: 8080, - protocol: appmesh.Protocol.HTTP, - }, - }, + listeners: [appmesh.VirtualNodeListener.http({ + port: 8080, + })], }); mesh.addVirtualService('service2', { @@ -287,12 +284,9 @@ export = { mesh.addVirtualNode('test-node', { dnsHostName: 'test.domain.local', - listener: { - portMapping: { - port: 8080, - protocol: appmesh.Protocol.HTTP, - }, - }, + listeners: [appmesh.VirtualNodeListener.http({ + port: 8080, + })], }); // THEN @@ -329,11 +323,8 @@ export = { mesh.addVirtualNode('test-node', { dnsHostName: 'test.domain.local', - listener: { - portMapping: { - port: 8080, - protocol: appmesh.Protocol.HTTP, - }, + listeners: [appmesh.VirtualNodeListener.http({ + port: 8080, healthCheck: { healthyThreshold: 3, path: '/', @@ -341,7 +332,7 @@ export = { timeout: cdk.Duration.seconds(2), // min unhealthyThreshold: 2, }, - }, + })], }); // THEN @@ -388,12 +379,9 @@ export = { mesh.addVirtualNode('test-node', { dnsHostName: 'test.domain.local', - listener: { - portMapping: { - port: 8080, - protocol: appmesh.Protocol.HTTP, - }, - }, + listeners: [appmesh.VirtualNodeListener.http({ + port: 8080, + })], backends: [ service1, ], 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 06928a4a25351..75c7d0579e71b 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts @@ -28,41 +28,39 @@ export = { const node = new appmesh.VirtualNode(stack, 'test-node', { mesh, dnsHostName: 'test', - listener: {}, backends: [service1], }); - node.addBackends(service2); + node.addBackend(service2); // THEN - expect(stack).to( - haveResourceLike('AWS::AppMesh::VirtualNode', { - Spec: { - Backends: [ - { - VirtualService: { - VirtualServiceName: { - 'Fn::GetAtt': ['service1A48078CF', 'VirtualServiceName'], - }, + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualNode', { + Spec: { + Backends: [ + { + VirtualService: { + VirtualServiceName: { + 'Fn::GetAtt': ['service1A48078CF', 'VirtualServiceName'], }, }, - { - VirtualService: { - VirtualServiceName: { - 'Fn::GetAtt': ['service27C65CF7D', 'VirtualServiceName'], - }, + }, + { + VirtualService: { + VirtualServiceName: { + 'Fn::GetAtt': ['service27C65CF7D', 'VirtualServiceName'], }, }, - ], - }, - }), - ); + }, + ], + }, + })); test.done(); }, }, + 'when a single portmapping is added': { - 'should add the portmapping to the resoource'(test: Test) { + 'should add the portmapping to the resource'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -75,28 +73,184 @@ export = { dnsHostName: 'test', }); - node.addListeners({ - portMapping: { - port: 8081, - protocol: appmesh.Protocol.TCP, + node.addListener(appmesh.VirtualNodeListener.tcp({ + port: 8081, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualNode', { + Spec: { + Listeners: [ + { + PortMapping: { + Port: 8081, + Protocol: 'tcp', + }, + }, + ], }, + })); + + test.done(); + }, + }, + + 'when a listener is added with timeout': { + 'should add the listener timeout to the resource'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + new appmesh.VirtualNode(stack, 'test-node', { + mesh, + dnsHostName: 'test', + listeners: [appmesh.VirtualNodeListener.grpc({ + port: 80, + timeout: { + idle: cdk.Duration.seconds(10), + perRequest: cdk.Duration.seconds(10), + }, + })], }); // THEN - expect(stack).to( - haveResourceLike('AWS::AppMesh::VirtualNode', { - Spec: { - Listeners: [ - { - PortMapping: { - Port: 8081, - Protocol: 'tcp', + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualNode', { + Spec: { + Listeners: [ + { + PortMapping: { + Port: 80, + Protocol: 'grpc', + }, + Timeout: { + GRPC: { + Idle: { + Unit: 'ms', + Value: 10000, + }, + PerRequest: { + Unit: 'ms', + Value: 10000, + }, }, }, - ], - }, - }), - ); + }, + ], + }, + })); + + test.done(); + }, + }, + + 'when a listener is added with healthcheck ': { + 'should add a default listener healthcheck to the resource'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + new appmesh.VirtualNode(stack, 'test-node', { + mesh, + dnsHostName: 'test', + listeners: [appmesh.VirtualNodeListener.http2({ + port: 80, + healthCheck: {}, + timeout: { idle: cdk.Duration.seconds(10) }, + })], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualNode', { + Spec: { + Listeners: [ + { + HealthCheck: { + HealthyThreshold: 2, + IntervalMillis: 5000, + Port: 80, + Protocol: 'http2', + TimeoutMillis: 2000, + UnhealthyThreshold: 2, + }, + PortMapping: { + Port: 80, + Protocol: 'http2', + }, + Timeout: { + HTTP2: { + Idle: { + Unit: 'ms', + Value: 10000, + }, + }, + }, + }, + ], + }, + })); + + test.done(); + }, + }, + + 'when a listener is added with healthcheck with user defined props': { + 'should add a listener healthcheck to the resource'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + const node = new appmesh.VirtualNode(stack, 'test-node', { + mesh, + dnsHostName: 'test', + }); + + node.addListener(appmesh.VirtualNodeListener.tcp({ + port: 80, + healthCheck: { timeout: cdk.Duration.seconds(3) }, + timeout: { idle: cdk.Duration.seconds(10) }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualNode', { + Spec: { + Listeners: [ + { + HealthCheck: { + HealthyThreshold: 2, + IntervalMillis: 5000, + Port: 80, + Protocol: 'tcp', + TimeoutMillis: 3000, + UnhealthyThreshold: 2, + }, + PortMapping: { + Port: 80, + Protocol: 'tcp', + }, + Timeout: { + TCP: { + Idle: { + Unit: 'ms', + Value: 10000, + }, + }, + }, + }, + ], + }, + })); test.done(); }, diff --git a/packages/@aws-cdk/aws-appmesh/test/test.virtual-router.ts b/packages/@aws-cdk/aws-appmesh/test/test.virtual-router.ts index 72510c83c4de2..56960564d6981 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.virtual-router.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.virtual-router.ts @@ -106,13 +106,9 @@ export = { const node = mesh.addVirtualNode('test-node', { dnsHostName: 'test', - listener: { - portMapping: - { - port: 8080, - protocol: appmesh.Protocol.HTTP, - }, - }, + listeners: [appmesh.VirtualNodeListener.http({ + port: 8080, + })], backends: [service1], }); @@ -179,39 +175,27 @@ export = { const node = mesh.addVirtualNode('test-node', { dnsHostName: 'test', - listener: { - portMapping: - { - port: 8080, - protocol: appmesh.Protocol.HTTP, - }, - }, + listeners: [appmesh.VirtualNodeListener.http({ + port: 8080, + })], backends: [ service1, ], }); const node2 = mesh.addVirtualNode('test-node2', { dnsHostName: 'test', - listener: { - portMapping: - { - port: 8080, - protocol: appmesh.Protocol.HTTP, - }, - }, + listeners: [appmesh.VirtualNodeListener.http({ + port: 8080, + })], backends: [ service2, ], }); const node3 = mesh.addVirtualNode('test-node3', { dnsHostName: 'test', - listener: { - portMapping: - { - port: 8080, - protocol: appmesh.Protocol.HTTP, - }, - }, + listeners: [appmesh.VirtualNodeListener.http({ + port: 8080, + })], backends: [ service1, ], @@ -337,13 +321,9 @@ export = { const node = mesh.addVirtualNode('test-node', { dnsHostName: 'test', - listener: { - portMapping: - { - port: 8080, - protocol: appmesh.Protocol.HTTP, - }, - }, + listeners: [appmesh.VirtualNodeListener.http({ + port: 8080, + })], backends: [ service1, ],