Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(appmesh): add listener timeout to Virtual Nodes #10793

Merged
merged 27 commits into from Nov 11, 2020
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c0073e6
feat(aws-appmesh): adds listener timeout to Virtual Nodes
sshver Oct 5, 2020
97e1275
Update README.md to include listener timeout for Virtual Nodes
sshver Oct 8, 2020
03e012f
Added check to set timeout only for those protocol the listener uses
sshver Oct 13, 2020
3d3e09f
WIP - Adding VirtualNodeListener Class
sshver Oct 19, 2020
0046aa1
Added descriptive comments
sshver Oct 25, 2020
d4f136c
minor edits: added/updated comments and added/removed spaces where ev…
sshver Oct 26, 2020
c520b01
Fixed test after cherry-pick
sshver Oct 26, 2020
b348e42
Fixed code to make sure the build does not fail.
sshver Oct 26, 2020
2fe07eb
Fixed code to make sure the build does not fail.
sshver Oct 26, 2020
9d4f03b
Revert "Fixed code to make sure the build does not fail."
sshver Oct 27, 2020
b012bbb
addressed comments and removed dupilcations
sshver Oct 30, 2020
8c8c2ad
addressed comment
sshver Nov 2, 2020
8373e3c
Updated appmesh.ts to add listener based on protocol
sshver Nov 2, 2020
f220deb
Removing unused file from directory
sshver Nov 2, 2020
ae6ad41
Merge remote-tracking branch 'upstream/master' into feature/listenert…
sshver Nov 3, 2020
03b4c1f
- Updated test to remove unnecessary changes.
sshver Nov 5, 2020
6ff4b82
- Moved bind implementation to VirtualNodeListener.
sshver Nov 6, 2020
a167ae8
Removed addListeners and moved addListener and addBackends from IVirt…
sshver Nov 6, 2020
588b56b
Create addListener() and addBackend() method to accept a single liste…
sshver Nov 9, 2020
0d70cd7
Merge remote-tracking branch 'upstream/master' into feature/listenert…
sshver Nov 10, 2020
b67d48e
Removed unnecessary whitespaces
sshver Nov 10, 2020
a2138cd
Make the fields in VirtualNode private instead of protected
skinny85 Nov 10, 2020
caf3e7c
Merge branch 'master' into feature/listenertimeout
sshver Nov 10, 2020
be69829
Merge branch 'master' into feature/listenertimeout
skinny85 Nov 10, 2020
d35705f
Revert unnecessarily removed empty line in virtual-node.ts
skinny85 Nov 10, 2020
d8b049c
Merge branch 'master' into feature/listenertimeout
skinny85 Nov 11, 2020
cf15f6e
Merge branch 'master' into feature/listenertimeout
mergify[bot] Nov 11, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
25 changes: 11 additions & 14 deletions packages/@aws-cdk/aws-appmesh/README.md
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -177,15 +171,18 @@ const node = new VirtualNode(this, 'node', {
protocol: Protocol.HTTP,
timeout: Duration.seconds(2), // min
unhealthyThreshold: 2,
},
timeout: {
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
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

Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-appmesh/lib/index.ts
Expand Up @@ -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';
Expand Down
38 changes: 0 additions & 38 deletions packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts
Expand Up @@ -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;
}

skinny85 marked this conversation as resolved.
Show resolved Hide resolved
/**
* All Properties for Envoy Access logs for mesh endpoints
*/
Expand Down
219 changes: 219 additions & 0 deletions 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 {
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
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,
},
});
}
}