Skip to content

Commit

Permalink
feat(appmesh): eagerly validate healthCheck settings (#4221)
Browse files Browse the repository at this point in the history
* feat(appmesh): throw if healthCheck thresholds aren't met

* feat(appmesh): throw if healthCheck path with Protocol.TCP

* chore: tslint fix

* chore: better threshold hint

* fix: check isUnresolved

* chore: remove trailing whitespace
  • Loading branch information
Jimmy Gaussen authored and mergify[bot] committed Sep 26, 2019
1 parent eb0a269 commit 84a1b45
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 1 deletion.
39 changes: 38 additions & 1 deletion packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,27 @@ abstract class VirtualNodeBase extends cdk.Resource implements IVirtualNode {
}
}

/**
* 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 CfnVirtualNode.HealthCheckProperty)]?: [number, number]} = {
healthyThreshold: [2, 10],
intervalMillis: [5000, 300000],
port: [1, 65535],
timeoutMillis: [2000, 60000],
unhealthyThreshold: [2, 10],
};

function renderHealthCheck(hc: HealthCheck | undefined, pm: PortMapping): CfnVirtualNode.HealthCheckProperty | undefined {
if (hc === undefined) { return undefined; }
return {

if (hc.protocol === Protocol.TCP && hc.path) {
throw new Error('The path property cannot be set with Protocol.TCP');
}

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),
Expand All @@ -159,6 +177,25 @@ function renderHealthCheck(hc: HealthCheck | undefined, pm: PortMapping): CfnVir
timeoutMillis: (hc.timeout || cdk.Duration.seconds(2)).toMilliseconds(),
unhealthyThreshold: hc.unhealthyThreshold || 2,
};

(Object.keys(healthCheck) as Array<keyof CfnVirtualNode.HealthCheckProperty>)
.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})`);
}
});

return healthCheck;
}

/**
Expand Down
131 changes: 131 additions & 0 deletions packages/@aws-cdk/aws-appmesh/test/test.health-check.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import cdk = require('@aws-cdk/core');
import {Test} from 'nodeunit';

import appmesh = require('../lib');

let idCounter = 0;
const getNode = (stack: cdk.Stack) => {
const mesh = new appmesh.Mesh(stack, `mesh-${++idCounter}`, {
meshName: 'test-mesh',
});
return mesh.addVirtualNode(`virtual-node-${idCounter}`, {
dnsHostName: 'test-node',
});
};

export = {
'interval'(test: Test) {
// GIVEN
const stack = new cdk.Stack();

const [min, max] = [5000, 300000];

// WHEN
const toThrow = (millis: number) => getNode(stack).addListeners({
healthCheck: {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.done();
},
'timeout'(test: Test) {
// GIVEN
const stack = new cdk.Stack();

const [min, max] = [2000, 60000];

// WHEN
const toThrow = (millis: number) => getNode(stack).addListeners({
healthCheck: {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).addListeners({
healthCheck: {port}
});

// THEN
test.doesNotThrow(() => toThrow(min));
test.doesNotThrow(() => toThrow(max));
// falsy, falls back to portMapping.port
// test.throws(() => toThrow(min - 1), /below the minimum threshold/);
test.throws(() => toThrow(max + 1), /above the maximum threshold/);

test.done();
},
'healthyThreshold'(test: Test) {
// GIVEN
const stack = new cdk.Stack();

const [min, max] = [2, 10];

// WHEN
const toThrow = (healthyThreshold: number) => getNode(stack).addListeners({
healthCheck: {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.done();
},
'unhealthyThreshold'(test: Test) {
// GIVEN
const stack = new cdk.Stack();

const [min, max] = [2, 10];

// WHEN
const toThrow = (unhealthyThreshold: number) => getNode(stack).addListeners({
healthCheck: {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.done();
},
'throws if path and Protocol.TCP'(test: Test) {
// GIVEN
const stack = new cdk.Stack();

// WHEN
const toThrow = (protocol: appmesh.Protocol) => getNode(stack).addListeners({
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();
},
};

0 comments on commit 84a1b45

Please sign in to comment.