Skip to content

Commit

Permalink
feat(appmesh): change HealthChecks to use protocol-specific union-lik…
Browse files Browse the repository at this point in the history
…e classes (aws#14432)

BREAKING CHANGE: HealthChecks require use of static factory methods

fixes aws#11640

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
Dominic Fezzie authored and hollanddd committed Aug 26, 2021
1 parent ebd3962 commit ea47e4e
Show file tree
Hide file tree
Showing 14 changed files with 242 additions and 271 deletions.
26 changes: 10 additions & 16 deletions packages/@aws-cdk/aws-appmesh/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
});
Expand All @@ -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),
},
Expand All @@ -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),
},
Expand Down Expand Up @@ -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({
Expand All @@ -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),
},
}),
})],
});
```
Expand Down
189 changes: 189 additions & 0 deletions packages/@aws-cdk/aws-appmesh/lib/health-checks.ts
Original file line number Diff line number Diff line change
@@ -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 '@aws-cdk/core';

/**
* 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,
},
};
}

}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-appmesh/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
42 changes: 0 additions & 42 deletions packages/@aws-cdk/aws-appmesh/lib/private/utils.ts
Original file line number Diff line number Diff line change
@@ -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<keyof AppMeshHealthCheck>)
.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
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-appmesh/lib/route-spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as cdk from '@aws-cdk/core';
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';

// keep this import separate from other imports to reduce chance for merge conflicts with v2-main
Expand Down
59 changes: 2 additions & 57 deletions packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export interface TcpTimeout {

/**
* Enum of supported AppMesh protocols
*
* @deprecated not for use outside package
*/
export enum Protocol {
HTTP = 'http',
Expand All @@ -67,63 +69,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.
*/
Expand Down
Loading

0 comments on commit ea47e4e

Please sign in to comment.