diff --git a/packages/@aws-cdk/aws-appmesh/README.md b/packages/@aws-cdk/aws-appmesh/README.md index fe3973e71149e..0adcc52901ba2 100644 --- a/packages/@aws-cdk/aws-appmesh/README.md +++ b/packages/@aws-cdk/aws-appmesh/README.md @@ -306,6 +306,38 @@ const node = mesh.addVirtualNode('virtual-node', { }); ``` +## Adding a connection pool to a listener + +The `connectionPool` property can be added to a Virtual Node listener or Virtual Gateway listener to add a request connection pool. There are different +connection pool properties per listener protocol types. + +```typescript +// A Virtual Node with a gRPC listener with a connection pool set +const node = new appmesh.VirtualNode(stack, 'node', { + mesh, + dnsHostName: 'node', + listeners: [appmesh.VirtualNodeListener.http({ + port: 80, + connectionPool: { + maxConnections: 100, + maxPendingRequests: 10, + }, + })], +}); + +// A Virtual Gateway with a gRPC listener with a connection pool set +const gateway = new appmesh.VirtualGateway(this, 'gateway', { + mesh: mesh, + listeners: [appmesh.VirtualGatewayListener.grpc({ + port: 8080, + connectionPool: { + maxRequests: 10, + }, + })], + virtualGatewayName: 'gateway', +}); +``` + ## Adding a Route A `route` is associated with a virtual router, and it's used to match requests for a virtual router and distribute traffic accordingly to its associated virtual nodes. diff --git a/packages/@aws-cdk/aws-appmesh/lib/private/utils.ts b/packages/@aws-cdk/aws-appmesh/lib/private/utils.ts index 0f63fdaf05253..7b5cfe620de1c 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/private/utils.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/private/utils.ts @@ -38,4 +38,30 @@ const HEALTH_CHECK_PROPERTY_THRESHOLDS: {[key in (keyof AppMeshHealthCheck)]?: [ port: [1, 65535], timeoutMillis: [2000, 60000], unhealthyThreshold: [2, 10], -}; \ No newline at end of file +}; + +/** + * Generated Connection pool config + */ +export interface ConnectionPoolConfig { + /** + * The maximum connections in the pool + * + * @default - none + */ + readonly maxConnections?: number; + + /** + * The maximum pending requests in the pool + * + * @default - none + */ + readonly maxPendingRequests?: number; + + /** + * The maximum requests in the pool + * + * @default - none + */ + readonly maxRequests?: number; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts b/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts index c06a0f40e5a28..5fb77b8cc4145 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts @@ -299,3 +299,58 @@ class VirtualServiceBackend extends Backend { }; } } + +/** + * Connection pool properties for HTTP listeners + */ +export interface HttpConnectionPool { + /** + * The maximum connections in the pool + * + * @default - none + */ + readonly maxConnections: number; + + /** + * The maximum pending requests in the pool + * + * @default - none + */ + readonly maxPendingRequests: number; +} + +/** + * Connection pool properties for TCP listeners + */ +export interface TcpConnectionPool { + /** + * The maximum connections in the pool + * + * @default - none + */ + readonly maxConnections: number; +} + +/** + * Connection pool properties for gRPC listeners + */ +export interface GrpcConnectionPool { + /** + * The maximum requests in the pool + * + * @default - none + */ + readonly maxRequests: number; +} + +/** + * Connection pool properties for HTTP2 listeners + */ +export interface Http2ConnectionPool { + /** + * The maximum requests in the pool + * + * @default - none + */ + readonly maxRequests: number; +} \ No newline at end of file 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 9f081ffdefd60..f4a083e3ea35e 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway-listener.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway-listener.ts @@ -1,7 +1,13 @@ import * as cdk from '@aws-cdk/core'; import { CfnVirtualGateway } from './appmesh.generated'; -import { validateHealthChecks } from './private/utils'; -import { HealthCheck, Protocol } from './shared-interfaces'; +import { validateHealthChecks, ConnectionPoolConfig } from './private/utils'; +import { + GrpcConnectionPool, + HealthCheck, + Http2ConnectionPool, + HttpConnectionPool, + Protocol, +} from './shared-interfaces'; import { TlsCertificate, TlsCertificateConfig } from './tls-certificate'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main @@ -9,9 +15,9 @@ import { TlsCertificate, TlsCertificateConfig } from './tls-certificate'; import { Construct } from '@aws-cdk/core'; /** - * Represents the properties needed to define HTTP Listeners for a VirtualGateway + * Represents the properties needed to define a Listeners for a VirtualGateway */ -export interface HttpGatewayListenerOptions { +interface VirtualGatewayListenerCommonOptions { /** * Port to listen for connections on * @@ -35,29 +41,39 @@ export interface HttpGatewayListenerOptions { } /** - * Represents the properties needed to define GRPC Listeners for a VirtualGateway + * Represents the properties needed to define HTTP Listeners for a VirtualGateway */ -export interface GrpcGatewayListenerOptions { +export interface HttpGatewayListenerOptions extends VirtualGatewayListenerCommonOptions { /** - * Port to listen for connections on + * Connection pool for http listeners * - * @default - 8080 + * @default - None */ - readonly port?: number + readonly connectionPool?: HttpConnectionPool; +} +/** + * Represents the properties needed to define HTTP2 Listeners for a VirtualGateway + */ +export interface Http2GatewayListenerOptions extends VirtualGatewayListenerCommonOptions { /** - * The health check information for the listener + * Connection pool for http listeners * - * @default - no healthcheck + * @default - None */ - readonly healthCheck?: HealthCheck; + readonly connectionPool?: Http2ConnectionPool; +} +/** + * Represents the properties needed to define GRPC Listeners for a VirtualGateway + */ +export interface GrpcGatewayListenerOptions extends VirtualGatewayListenerCommonOptions { /** - * Represents the listener certificate + * Connection pool for http listeners * - * @default - none + * @default - None */ - readonly tlsCertificate?: TlsCertificate; + readonly connectionPool?: GrpcConnectionPool; } /** @@ -78,21 +94,21 @@ export abstract class VirtualGatewayListener { * Returns an HTTP Listener for a VirtualGateway */ public static http(options: HttpGatewayListenerOptions = {}): VirtualGatewayListener { - return new VirtualGatewayListenerImpl(Protocol.HTTP, options.healthCheck, options.port, options.tlsCertificate); + return new VirtualGatewayListenerImpl(Protocol.HTTP, options.healthCheck, options.port, options.tlsCertificate, options.connectionPool); } /** * Returns an HTTP2 Listener for a VirtualGateway */ - public static http2(options: HttpGatewayListenerOptions = {}): VirtualGatewayListener { - return new VirtualGatewayListenerImpl(Protocol.HTTP2, options.healthCheck, options.port, options.tlsCertificate); + public static http2(options: Http2GatewayListenerOptions = {}): VirtualGatewayListener { + return new VirtualGatewayListenerImpl(Protocol.HTTP2, options.healthCheck, options.port, options.tlsCertificate, options.connectionPool); } /** * Returns a GRPC Listener for a VirtualGateway */ public static grpc(options: GrpcGatewayListenerOptions = {}): VirtualGatewayListener { - return new VirtualGatewayListenerImpl(Protocol.GRPC, options.healthCheck, options.port, options.tlsCertificate); + return new VirtualGatewayListenerImpl(Protocol.GRPC, options.healthCheck, options.port, options.tlsCertificate, options.connectionPool); } /** @@ -110,7 +126,8 @@ class VirtualGatewayListenerImpl extends VirtualGatewayListener { constructor(private readonly protocol: Protocol, private readonly healthCheck: HealthCheck | undefined, private readonly port: number = 8080, - private readonly tlsCertificate: TlsCertificate | undefined) { + private readonly tlsCertificate: TlsCertificate | undefined, + private readonly connectionPool: ConnectionPoolConfig | undefined) { super(); } @@ -128,6 +145,7 @@ class VirtualGatewayListenerImpl extends VirtualGatewayListener { }, healthCheck: this.healthCheck ? renderHealthCheck(this.healthCheck, this.protocol, this.port): undefined, tls: tlsConfig ? renderTls(tlsConfig) : undefined, + connectionPool: this.connectionPool ? renderConnectionPool(this.connectionPool, this.protocol) : undefined, }, }; } @@ -171,3 +189,14 @@ function renderHealthCheck(hc: HealthCheck, listenerProtocol: Protocol, return healthCheck; } + +function renderConnectionPool(connectionPool: ConnectionPoolConfig, listenerProtocol: Protocol): +CfnVirtualGateway.VirtualGatewayConnectionPoolProperty { + return ({ + [listenerProtocol]: { + maxRequests: connectionPool?.maxRequests !== undefined ? connectionPool.maxRequests : undefined, + maxConnections: connectionPool?.maxConnections !== undefined ? connectionPool.maxConnections : undefined, + maxPendingRequests: connectionPool?.maxPendingRequests !== undefined ? connectionPool.maxPendingRequests : undefined, + }, + }); +} 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 9ff768d07294c..7a046a138c3ca 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-node-listener.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-node-listener.ts @@ -1,7 +1,10 @@ import * as cdk from '@aws-cdk/core'; import { CfnVirtualNode } from './appmesh.generated'; -import { validateHealthChecks } from './private/utils'; -import { HealthCheck, Protocol, HttpTimeout, GrpcTimeout, TcpTimeout, OutlierDetection } from './shared-interfaces'; +import { validateHealthChecks, ConnectionPoolConfig } from './private/utils'; +import { + GrpcConnectionPool, GrpcTimeout, HealthCheck, Http2ConnectionPool, HttpConnectionPool, + HttpTimeout, OutlierDetection, Protocol, TcpConnectionPool, TcpTimeout, +} from './shared-interfaces'; import { TlsCertificate, TlsCertificateConfig } from './tls-certificate'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main @@ -51,16 +54,38 @@ interface VirtualNodeListenerCommonOptions { readonly outlierDetection?: OutlierDetection; } +interface CommonHttpVirtualNodeListenerOptions extends VirtualNodeListenerCommonOptions { + /** + * Timeout for HTTP protocol + * + * @default - None + */ + readonly timeout?: HttpTimeout; +} + /** * Represent the HTTP Node Listener prorperty */ -export interface HttpVirtualNodeListenerOptions extends VirtualNodeListenerCommonOptions { +export interface HttpVirtualNodeListenerOptions extends CommonHttpVirtualNodeListenerOptions { + /** - * Timeout for HTTP protocol + * Connection pool for http listeners * * @default - None */ - readonly timeout?: HttpTimeout; + readonly connectionPool?: HttpConnectionPool; +} + +/** + * Represent the HTTP2 Node Listener prorperty + */ +export interface Http2VirtualNodeListenerOptions extends CommonHttpVirtualNodeListenerOptions { + /** + * Connection pool for http2 listeners + * + * @default - None + */ + readonly connectionPool?: Http2ConnectionPool; } /** @@ -73,6 +98,13 @@ export interface GrpcVirtualNodeListenerOptions extends VirtualNodeListenerCommo * @default - None */ readonly timeout?: GrpcTimeout; + + /** + * Connection pool for http listeners + * + * @default - None + */ + readonly connectionPool?: GrpcConnectionPool; } /** @@ -85,6 +117,13 @@ export interface TcpVirtualNodeListenerOptions extends VirtualNodeListenerCommon * @default - None */ readonly timeout?: TcpTimeout; + + /** + * Connection pool for http listeners + * + * @default - None + */ + readonly connectionPool?: TcpConnectionPool; } /** @@ -95,35 +134,38 @@ 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, props.tlsCertificate, props.outlierDetection); + return new VirtualNodeListenerImpl(Protocol.HTTP, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection, + props.connectionPool); } /** * Returns an HTTP2 Listener for a VirtualNode */ - public static http2(props: HttpVirtualNodeListenerOptions = {}): VirtualNodeListener { - return new VirtualNodeListenerImpl(Protocol.HTTP2, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection); + public static http2(props: Http2VirtualNodeListenerOptions = {}): VirtualNodeListener { + return new VirtualNodeListenerImpl(Protocol.HTTP2, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection, + props.connectionPool); } /** * Returns an GRPC Listener for a VirtualNode */ public static grpc(props: GrpcVirtualNodeListenerOptions = {}): VirtualNodeListener { - return new VirtualNodeListenerImpl(Protocol.GRPC, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection); + return new VirtualNodeListenerImpl(Protocol.GRPC, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection, + props.connectionPool); } /** * Returns an TCP Listener for a VirtualNode */ public static tcp(props: TcpVirtualNodeListenerOptions = {}): VirtualNodeListener { - return new VirtualNodeListenerImpl(Protocol.TCP, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection); + return new VirtualNodeListenerImpl(Protocol.TCP, props.healthCheck, props.timeout, props.port, props.tlsCertificate, props.outlierDetection, + props.connectionPool); } /** * Binds the current object when adding Listener to a VirtualNode */ public abstract bind(scope: Construct): VirtualNodeListenerConfig; - } class VirtualNodeListenerImpl extends VirtualNodeListener { @@ -132,7 +174,8 @@ class VirtualNodeListenerImpl extends VirtualNodeListener { private readonly timeout: HttpTimeout | undefined, private readonly port: number = 8080, private readonly tlsCertificate: TlsCertificate | undefined, - private readonly outlierDetection: OutlierDetection | undefined) { super(); } + private readonly outlierDetection: OutlierDetection | undefined, + private readonly connectionPool: ConnectionPoolConfig | undefined) { super(); } public bind(scope: Construct): VirtualNodeListenerConfig { const tlsConfig = this.tlsCertificate?.bind(scope); @@ -146,6 +189,7 @@ class VirtualNodeListenerImpl extends VirtualNodeListener { timeout: this.timeout ? this.renderTimeout(this.timeout) : undefined, tls: tlsConfig ? this.renderTls(tlsConfig) : undefined, outlierDetection: this.outlierDetection ? this.renderOutlierDetection(this.outlierDetection) : undefined, + connectionPool: this.connectionPool ? this.renderConnectionPool(this.connectionPool) : undefined, }, }; } @@ -215,5 +259,15 @@ class VirtualNodeListenerImpl extends VirtualNodeListener { maxServerErrors: outlierDetection.maxServerErrors, }; } + + private renderConnectionPool(connectionPool: ConnectionPoolConfig): CfnVirtualNode.VirtualNodeConnectionPoolProperty { + return ({ + [this.protocol]: { + maxRequests: connectionPool?.maxRequests !== undefined ? connectionPool.maxRequests : undefined, + maxConnections: connectionPool?.maxConnections !== undefined ? connectionPool.maxConnections : undefined, + maxPendingRequests: connectionPool?.maxPendingRequests !== undefined ? connectionPool.maxPendingRequests : undefined, + }, + }); + } } 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 f5a5201bb6700..96d3c7cba9210 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts @@ -423,6 +423,131 @@ export = { }, }, + 'Can add an http connection pool to listener'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + new appmesh.VirtualGateway(stack, 'virtual-gateway', { + virtualGatewayName: 'virtual-gateway', + mesh: mesh, + listeners: [ + appmesh.VirtualGatewayListener.http({ + port: 80, + connectionPool: { + maxConnections: 100, + maxPendingRequests: 10, + }, + }), + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualGateway', { + VirtualGatewayName: 'virtual-gateway', + Spec: { + Listeners: [ + { + ConnectionPool: { + HTTP: { + MaxConnections: 100, + MaxPendingRequests: 10, + }, + }, + }, + ], + }, + })); + + test.done(); + }, + + 'Can add an grpc connection pool to listener'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + new appmesh.VirtualGateway(stack, 'virtual-gateway', { + virtualGatewayName: 'virtual-gateway', + mesh: mesh, + listeners: [ + appmesh.VirtualGatewayListener.grpc({ + port: 80, + connectionPool: { + maxRequests: 10, + }, + }), + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualGateway', { + VirtualGatewayName: 'virtual-gateway', + Spec: { + Listeners: [ + { + ConnectionPool: { + GRPC: { + MaxRequests: 10, + }, + }, + }, + ], + }, + })); + + test.done(); + }, + + 'Can add an http2 connection pool to listener'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + new appmesh.VirtualGateway(stack, 'virtual-gateway', { + virtualGatewayName: 'virtual-gateway', + mesh: mesh, + listeners: [ + appmesh.VirtualGatewayListener.http2({ + port: 80, + connectionPool: { + maxRequests: 10, + }, + }), + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualGateway', { + VirtualGatewayName: 'virtual-gateway', + Spec: { + Listeners: [ + { + ConnectionPool: { + HTTP2: { + MaxRequests: 10, + }, + }, + }, + ], + }, + })); + + test.done(); + }, + 'Can import VirtualGateways using an ARN'(test: Test) { const app = new cdk.App(); // GIVEN 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 b025d68581ae9..f143b0025c1db 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts @@ -557,6 +557,164 @@ export = { test.done(); }, }, + + 'Can add an http connection pool to listener'(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, + listeners: [ + appmesh.VirtualNodeListener.http({ + port: 80, + connectionPool: { + maxConnections: 100, + maxPendingRequests: 10, + }, + }), + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualNode', { + Spec: { + Listeners: [ + { + ConnectionPool: { + HTTP: { + MaxConnections: 100, + MaxPendingRequests: 10, + }, + }, + }, + ], + }, + })); + + test.done(); + }, + + 'Can add an tcp connection pool to listener'(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, + listeners: [ + appmesh.VirtualNodeListener.tcp({ + port: 80, + connectionPool: { + maxConnections: 100, + }, + }), + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualNode', { + Spec: { + Listeners: [ + { + ConnectionPool: { + TCP: { + MaxConnections: 100, + }, + }, + }, + ], + }, + })); + + test.done(); + }, + + 'Can add an grpc connection pool to listener'(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, + listeners: [ + appmesh.VirtualNodeListener.grpc({ + port: 80, + connectionPool: { + maxRequests: 10, + }, + }), + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualNode', { + Spec: { + Listeners: [ + { + ConnectionPool: { + GRPC: { + MaxRequests: 10, + }, + }, + }, + ], + }, + })); + + test.done(); + }, + + 'Can add an http2 connection pool to listener'(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, + listeners: [ + appmesh.VirtualNodeListener.http2({ + port: 80, + connectionPool: { + maxRequests: 10, + }, + }), + ], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualNode', { + Spec: { + Listeners: [ + { + ConnectionPool: { + HTTP2: { + MaxRequests: 10, + }, + }, + }, + ], + }, + })); + + test.done(); + }, }, 'Can import Virtual Nodes using an ARN'(test: Test) {