aws-rfdk
Version:
Package for core render farm constructs
1,084 lines • 154 kB
JavaScript
"use strict";
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable dot-notation */
const aws_cdk_lib_1 = require("aws-cdk-lib");
const assertions_1 = require("aws-cdk-lib/assertions");
const aws_autoscaling_1 = require("aws-cdk-lib/aws-autoscaling");
const aws_ec2_1 = require("aws-cdk-lib/aws-ec2");
const aws_ecs_1 = require("aws-cdk-lib/aws-ecs");
const lib_1 = require("../../core/lib");
const asset_constants_1 = require("../../core/test/asset-constants");
const tag_helpers_1 = require("../../core/test/tag-helpers");
const lib_2 = require("../lib");
const asset_constants_2 = require("./asset-constants");
const test_helper_1 = require("./test-helper");
let app;
let stack;
let wfstack;
let vpc;
let renderQueue;
let rcsImage;
beforeEach(() => {
app = new aws_cdk_lib_1.App();
stack = new aws_cdk_lib_1.Stack(app, 'infraStack', {
env: {
region: 'us-east-1',
},
});
vpc = new aws_ec2_1.Vpc(stack, 'VPC');
rcsImage = aws_ecs_1.ContainerImage.fromAsset(__dirname);
const version = new lib_2.VersionQuery(stack, 'VersionQuery');
renderQueue = new lib_2.RenderQueue(stack, 'RQ', {
vpc,
images: { remoteConnectionServer: rcsImage },
repository: new lib_2.Repository(stack, 'Repository', {
vpc,
version,
secretsManagementSettings: { enabled: false },
}),
trafficEncryption: { externalTLS: { enabled: false } },
version,
});
wfstack = new aws_cdk_lib_1.Stack(app, 'workerFleetStack', {
env: {
region: 'us-east-1',
},
});
});
test('default worker fleet is created correctly', () => {
// WHEN
const fleet = new lib_2.WorkerInstanceFleet(wfstack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
});
// THEN
assertions_1.Template.fromStack(wfstack).resourceCountIs('AWS::AutoScaling::AutoScalingGroup', 1);
assertions_1.Template.fromStack(wfstack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', {
InstanceType: 't3.large',
IamInstanceProfile: {
Ref: assertions_1.Match.stringLikeRegexp('^workerFleetInstanceProfile.*'),
},
ImageId: 'ami-any',
SecurityGroups: [
{
'Fn::GetAtt': [
assertions_1.Match.stringLikeRegexp('^workerFleetInstanceSecurityGroup.*'),
'GroupId',
],
},
],
spotPrice: assertions_1.Match.absent(),
});
assertions_1.Template.fromStack(wfstack).hasResourceProperties('AWS::EC2::SecurityGroupIngress', {
IpProtocol: 'tcp',
ToPort: parseInt(renderQueue.endpoint.portAsString(), 10),
SourceSecurityGroupId: {
'Fn::GetAtt': [
stack.getLogicalId(fleet.fleet.connections.securityGroups[0].node.defaultChild),
'GroupId',
],
},
GroupId: {
'Fn::ImportValue': 'infraStack:ExportsOutputFnGetAttRQLBSecurityGroupAC643AEDGroupId8F9F7830',
},
});
assertions_1.Template.fromStack(wfstack).hasResourceProperties('Custom::LogRetention', {
RetentionInDays: 3,
LogGroupName: '/renderfarm/workerFleet',
});
assertions_1.Annotations.fromStack(wfstack).hasWarning(`/${fleet.node.path}`, assertions_1.Match.stringLikeRegexp('.*being created without being provided any block devices so the Source AMI\'s devices will be used. Workers can have access to sensitive data so it is recommended to either explicitly encrypt the devices on the worker fleet or to ensure the source AMI\'s Drives are encrypted.'));
assertions_1.Annotations.fromStack(wfstack).hasWarning(`/${fleet.node.path}`, assertions_1.Match.stringLikeRegexp('.*being created without a health monitor attached to it. This means that the fleet will not automatically scale-in to 0 if the workers are unhealthy'));
});
test('security group is added to fleet after its creation', () => {
// WHEN
const fleet = new lib_2.WorkerInstanceFleet(stack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
});
fleet.addSecurityGroup(aws_ec2_1.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'sg-123456789', {
allowAllOutbound: false,
}));
// THEN
assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', {
SecurityGroups: [
{
'Fn::GetAtt': [
stack.getLogicalId(fleet.fleet.connections.securityGroups[0].node.defaultChild),
'GroupId',
],
},
'sg-123456789',
],
});
});
test('WorkerFleet uses given security group', () => {
// WHEN
new lib_2.WorkerInstanceFleet(stack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
securityGroup: aws_ec2_1.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'sg-123456789', {
allowAllOutbound: false,
}),
});
// THEN
assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', {
SecurityGroups: [
'sg-123456789',
],
});
});
describe('allowing log listener port', () => {
test('from CIDR', () => {
// WHEN
const fleet = new lib_2.WorkerInstanceFleet(stack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
});
fleet.allowListenerPortFrom(aws_ec2_1.Peer.ipv4('127.0.0.1/24').connections);
// THEN
assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroup', {
SecurityGroupEgress: [{ CidrIp: '0.0.0.0/0' }],
SecurityGroupIngress: [
{
CidrIp: '127.0.0.1/24',
Description: 'Worker remote command listening port',
FromPort: lib_2.WorkerInstanceConfiguration['DEFAULT_LISTENER_PORT'],
IpProtocol: 'tcp',
ToPort: lib_2.WorkerInstanceConfiguration['DEFAULT_LISTENER_PORT'] + lib_2.WorkerInstanceFleet['MAX_WORKERS_PER_HOST'],
},
],
});
});
test('to CIDR', () => {
// WHEN
const fleet = new lib_2.WorkerInstanceFleet(stack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
});
fleet.allowListenerPortTo(aws_ec2_1.Peer.ipv4('127.0.0.1/24').connections);
// THEN
assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroup', {
SecurityGroupEgress: [{ CidrIp: '0.0.0.0/0' }],
SecurityGroupIngress: [
{
CidrIp: '127.0.0.1/24',
Description: 'Worker remote command listening port',
FromPort: lib_2.WorkerInstanceConfiguration['DEFAULT_LISTENER_PORT'],
IpProtocol: 'tcp',
ToPort: lib_2.WorkerInstanceConfiguration['DEFAULT_LISTENER_PORT'] + lib_2.WorkerInstanceFleet['MAX_WORKERS_PER_HOST'],
},
],
});
});
test('from SecurityGroup', () => {
// WHEN
const fleet = new lib_2.WorkerInstanceFleet(stack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
});
const securityGroup = aws_ec2_1.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'sg-123456789');
fleet.allowListenerPortFrom(securityGroup);
// THEN
assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroupIngress', {
FromPort: lib_2.WorkerInstanceConfiguration['DEFAULT_LISTENER_PORT'],
IpProtocol: 'tcp',
SourceSecurityGroupId: 'sg-123456789',
ToPort: lib_2.WorkerInstanceConfiguration['DEFAULT_LISTENER_PORT'] + lib_2.WorkerInstanceFleet['MAX_WORKERS_PER_HOST'],
});
});
test('to SecurityGroup', () => {
// WHEN
const fleet = new lib_2.WorkerInstanceFleet(stack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
});
const securityGroup = aws_ec2_1.SecurityGroup.fromSecurityGroupId(stack, 'SG', 'sg-123456789');
fleet.allowListenerPortTo(securityGroup);
// THEN
assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroupIngress', {
FromPort: lib_2.WorkerInstanceConfiguration['DEFAULT_LISTENER_PORT'],
IpProtocol: 'tcp',
SourceSecurityGroupId: 'sg-123456789',
ToPort: lib_2.WorkerInstanceConfiguration['DEFAULT_LISTENER_PORT'] + lib_2.WorkerInstanceFleet['MAX_WORKERS_PER_HOST'],
});
});
test('from other stack', () => {
const otherStack = new aws_cdk_lib_1.Stack(app, 'otherStack', {
env: { region: 'us-east-1' },
});
// WHEN
const fleet = new lib_2.WorkerInstanceFleet(stack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
});
const securityGroup = aws_ec2_1.SecurityGroup.fromSecurityGroupId(otherStack, 'SG', 'sg-123456789');
fleet.allowListenerPortFrom(securityGroup);
// THEN
assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroupIngress', {
FromPort: lib_2.WorkerInstanceConfiguration['DEFAULT_LISTENER_PORT'],
IpProtocol: 'tcp',
SourceSecurityGroupId: 'sg-123456789',
ToPort: lib_2.WorkerInstanceConfiguration['DEFAULT_LISTENER_PORT'] + lib_2.WorkerInstanceFleet['MAX_WORKERS_PER_HOST'],
});
});
test('to other stack', () => {
const otherStack = new aws_cdk_lib_1.Stack(app, 'otherStack', {
env: { region: 'us-east-1' },
});
// WHEN
const fleet = new lib_2.WorkerInstanceFleet(stack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
});
const securityGroup = aws_ec2_1.SecurityGroup.fromSecurityGroupId(otherStack, 'SG', 'sg-123456789');
fleet.allowListenerPortTo(securityGroup);
// THEN
assertions_1.Template.fromStack(otherStack).hasResourceProperties('AWS::EC2::SecurityGroupIngress', {
FromPort: lib_2.WorkerInstanceConfiguration['DEFAULT_LISTENER_PORT'],
IpProtocol: 'tcp',
SourceSecurityGroupId: 'sg-123456789',
ToPort: lib_2.WorkerInstanceConfiguration['DEFAULT_LISTENER_PORT'] + lib_2.WorkerInstanceFleet['MAX_WORKERS_PER_HOST'],
});
});
});
test('default worker fleet is created correctly with linux image', () => {
// WHEN
new lib_2.WorkerInstanceFleet(stack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': 'ami-any',
}),
renderQueue,
});
// THEN
// 3 = repository + renderqueue + worker fleet
assertions_1.Template.fromStack(stack).resourceCountIs('AWS::AutoScaling::AutoScalingGroup', 3);
assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', {
InstanceType: 't3.large',
IamInstanceProfile: {
Ref: assertions_1.Match.stringLikeRegexp('^workerFleetInstanceProfile.*'),
},
ImageId: 'ami-any',
SecurityGroups: [
{
'Fn::GetAtt': [
assertions_1.Match.stringLikeRegexp('^workerFleetInstanceSecurityGroup.*'),
'GroupId',
],
},
],
spotPrice: assertions_1.Match.absent(),
});
});
test('default worker fleet is created correctly with spot config', () => {
// WHEN
new lib_2.WorkerInstanceFleet(wfstack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
spotPrice: 2.5,
});
// THEN
assertions_1.Template.fromStack(wfstack).resourceCountIs('AWS::AutoScaling::AutoScalingGroup', 1);
assertions_1.Template.fromStack(wfstack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', {
SpotPrice: '2.5',
});
});
test('default worker fleet is not created with incorrect spot config', () => {
// WHEN
expect(() => {
new lib_2.WorkerInstanceFleet(wfstack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
spotPrice: lib_2.WorkerInstanceFleet.SPOT_PRICE_MAX_LIMIT + 1,
});
}).toThrow(/Invalid value: 256 for property 'spotPrice'. Valid values can be any decimal between 0.001 and 255./);
// WHEN
expect(() => {
new lib_2.WorkerInstanceFleet(wfstack, 'workerFleet2', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
spotPrice: lib_2.WorkerInstanceFleet.SPOT_PRICE_MIN_LIMIT / 2,
});
}).toThrow(/Invalid value: 0.0005 for property 'spotPrice'. Valid values can be any decimal between 0.001 and 255./);
});
test('default worker fleet is created correctly custom Instance type', () => {
// WHEN
new lib_2.WorkerInstanceFleet(stack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
instanceType: aws_ec2_1.InstanceType.of(aws_ec2_1.InstanceClass.T2, aws_ec2_1.InstanceSize.MEDIUM),
});
// THEN
assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', {
InstanceType: 't2.medium',
});
});
test.each([
'test-prefix/',
'',
])('default worker fleet is created correctly with custom LogGroup prefix %s', (testPrefix) => {
// GIVEN
const id = 'workerFleet';
// WHEN
new lib_2.WorkerInstanceFleet(stack, id, {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
logGroupProps: {
logGroupPrefix: testPrefix,
},
});
assertions_1.Template.fromStack(stack).hasResourceProperties('Custom::LogRetention', {
RetentionInDays: 3,
LogGroupName: testPrefix + id,
});
});
test('worker fleet uses given UserData', () => {
// GIVEN
const id = 'workerFleet';
const userData = aws_ec2_1.UserData.forLinux();
// WHEN
const workerFleet = new lib_2.WorkerInstanceFleet(stack, id, {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
userData,
});
// THEN
expect(workerFleet.fleet.userData).toBe(userData);
});
test('default linux worker fleet is created correctly custom subnet values', () => {
vpc = new aws_ec2_1.Vpc(stack, 'VPC1Az', {
maxAzs: 1,
});
// WHEN
new lib_2.WorkerInstanceFleet(stack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
instanceType: aws_ec2_1.InstanceType.of(aws_ec2_1.InstanceClass.T2, aws_ec2_1.InstanceSize.MEDIUM),
vpcSubnets: {
subnetType: aws_ec2_1.SubnetType.PUBLIC,
},
healthCheckConfig: {
port: 6161,
},
});
// THEN
assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', {
VPCZoneIdentifier: [{
Ref: 'VPC1AzPublicSubnet1Subnet9649CC17',
}],
});
assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', {
InstanceType: 't2.medium',
IamInstanceProfile: {
Ref: assertions_1.Match.stringLikeRegexp('workerFleetInstanceProfile.*'),
},
UserData: {
'Fn::Base64': {
'Fn::Join': [
'',
[
'#!/bin/bash\n' +
'function exitTrap(){\nexitCode=$?\n/opt/aws/bin/cfn-signal --stack infraStack --resource workerFleetASG25520D69 --region us-east-1 -e $exitCode || echo \'Failed to send Cloudformation Signal\'\n}\n' +
'trap exitTrap EXIT\n' +
`mkdir -p $(dirname '/tmp/${asset_constants_1.CWA_ASSET_LINUX.Key}.sh')\naws s3 cp 's3://`,
{
'Fn::Sub': asset_constants_1.CWA_ASSET_LINUX.Bucket.replace('${AWS::Region}', 'us-east-1'),
},
`/${asset_constants_1.CWA_ASSET_LINUX.Key}.sh' '/tmp/${asset_constants_1.CWA_ASSET_LINUX.Key}.sh'\n` +
`set -e\nchmod +x '/tmp/${asset_constants_1.CWA_ASSET_LINUX.Key}.sh'\n'/tmp/${asset_constants_1.CWA_ASSET_LINUX.Key}.sh' -i us-east-1 `,
{
Ref: assertions_1.Match.stringLikeRegexp('^workerFleetStringParameter.*'),
},
`\nmkdir -p $(dirname '/tmp/${asset_constants_2.RQ_CONNECTION_ASSET.Key}.py')\naws s3 cp 's3://`,
{
'Fn::Sub': asset_constants_2.RQ_CONNECTION_ASSET.Bucket.replace('${AWS::Region}', 'us-east-1'),
},
`/${asset_constants_2.RQ_CONNECTION_ASSET.Key}.py' '/tmp/${asset_constants_2.RQ_CONNECTION_ASSET.Key}.py'\n` +
'if [ -f "/etc/profile.d/deadlineclient.sh" ]; then\n source "/etc/profile.d/deadlineclient.sh"\nfi\n' +
`"\${DEADLINE_PATH}/deadlinecommand" -executeScriptNoGui "/tmp/${asset_constants_2.RQ_CONNECTION_ASSET.Key}.py" --render-queue "http://`,
{
'Fn::GetAtt': [
'RQLB3B7B1CBC',
'DNSName',
],
},
`:8080" \nrm -f "/tmp/${asset_constants_2.RQ_CONNECTION_ASSET.Key}.py"` +
`\nmkdir -p $(dirname '/tmp/${asset_constants_2.CONFIG_WORKER_PORT_ASSET_LINUX.Key}.py')\naws s3 cp 's3://`,
{
'Fn::Sub': asset_constants_2.CONFIG_WORKER_PORT_ASSET_LINUX.Bucket.replace('${AWS::Region}', 'us-east-1'),
},
`/${asset_constants_2.CONFIG_WORKER_PORT_ASSET_LINUX.Key}.py' '/tmp/${asset_constants_2.CONFIG_WORKER_PORT_ASSET_LINUX.Key}.py'\n` +
`mkdir -p $(dirname '/tmp/${asset_constants_2.CONFIG_WORKER_ASSET_LINUX.Key}.sh')\naws s3 cp 's3://`,
{
'Fn::Sub': asset_constants_2.CONFIG_WORKER_ASSET_LINUX.Bucket.replace('${AWS::Region}', 'us-east-1'),
},
`/${asset_constants_2.CONFIG_WORKER_ASSET_LINUX.Key}.sh' '/tmp/${asset_constants_2.CONFIG_WORKER_ASSET_LINUX.Key}.sh'\n` +
'set -e\n' +
`chmod +x '/tmp/${asset_constants_2.CONFIG_WORKER_ASSET_LINUX.Key}.sh'\n` +
`'/tmp/${asset_constants_2.CONFIG_WORKER_ASSET_LINUX.Key}.sh' '' '' '' '${lib_2.Version.MINIMUM_SUPPORTED_DEADLINE_VERSION.toString()}' ${lib_2.WorkerInstanceConfiguration['DEFAULT_LISTENER_PORT']} /tmp/${asset_constants_2.CONFIG_WORKER_PORT_ASSET_LINUX.Key}.py`,
],
],
},
},
});
});
test('default windows worker fleet is created correctly custom subnet values', () => {
vpc = new aws_ec2_1.Vpc(stack, 'VPC1Az', {
maxAzs: 1,
});
// WHEN
new lib_2.WorkerInstanceFleet(stack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': '123',
}),
renderQueue,
instanceType: aws_ec2_1.InstanceType.of(aws_ec2_1.InstanceClass.T2, aws_ec2_1.InstanceSize.MEDIUM),
vpcSubnets: {
subnetType: aws_ec2_1.SubnetType.PUBLIC,
},
healthCheckConfig: {
port: 6161,
},
});
// THEN
assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::AutoScalingGroup', {
VPCZoneIdentifier: [{
Ref: 'VPC1AzPublicSubnet1Subnet9649CC17',
}],
});
assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', {
InstanceType: 't2.medium',
IamInstanceProfile: {
Ref: assertions_1.Match.stringLikeRegexp('workerFleetInstanceProfile.*'),
},
UserData: {
'Fn::Base64': {
'Fn::Join': [
'',
[
'<powershell>trap {\n$success=($PSItem.Exception.Message -eq "Success")\n' +
'cfn-signal --stack infraStack --resource workerFleetASG25520D69 --region us-east-1 --success ($success.ToString().ToLower())\nbreak\n}\n' +
`mkdir (Split-Path -Path 'C:/temp/${asset_constants_1.CWA_ASSET_WINDOWS.Key}.ps1' ) -ea 0\nRead-S3Object -BucketName '`,
{
'Fn::Sub': asset_constants_1.CWA_ASSET_WINDOWS.Bucket.replace('${AWS::Region}', 'us-east-1'),
},
`' -key '${asset_constants_1.CWA_ASSET_WINDOWS.Key}.ps1' -file 'C:/temp/${asset_constants_1.CWA_ASSET_WINDOWS.Key}.ps1' -ErrorAction Stop\n&'C:/temp/${asset_constants_1.CWA_ASSET_WINDOWS.Key}.ps1' -i us-east-1 `,
{
Ref: assertions_1.Match.stringLikeRegexp('^workerFleetStringParameter.*'),
},
`\nif (!$?) { Write-Error 'Failed to execute the file \"C:/temp/${asset_constants_1.CWA_ASSET_WINDOWS.Key}.ps1\"' -ErrorAction Stop }\n` +
`mkdir (Split-Path -Path 'C:/temp/${asset_constants_2.RQ_CONNECTION_ASSET.Key}.py' ) -ea 0\nRead-S3Object -BucketName '`,
{
'Fn::Sub': asset_constants_2.RQ_CONNECTION_ASSET.Bucket.replace('${AWS::Region}', 'us-east-1'),
},
`' -key '${asset_constants_2.RQ_CONNECTION_ASSET.Key}.py' -file 'C:/temp/${asset_constants_2.RQ_CONNECTION_ASSET.Key}.py' -ErrorAction Stop\n` +
'$ErrorActionPreference = "Stop"\n' +
'$DEADLINE_PATH = (get-item env:"DEADLINE_PATH").Value\n' +
`& "$DEADLINE_PATH/deadlinecommand.exe" -executeScriptNoGui "C:/temp/${asset_constants_2.RQ_CONNECTION_ASSET.Key}.py" --render-queue "http://`,
{
'Fn::GetAtt': [
'RQLB3B7B1CBC',
'DNSName',
],
},
':8080" 2>&1\n' +
`Remove-Item -Path "C:/temp/${asset_constants_2.RQ_CONNECTION_ASSET.Key}.py"\n` +
`mkdir (Split-Path -Path 'C:/temp/${asset_constants_2.CONFIG_WORKER_ASSET_WINDOWS.Key}.py' ) -ea 0\nRead-S3Object -BucketName '`,
{
'Fn::Sub': asset_constants_2.CONFIG_WORKER_ASSET_WINDOWS.Bucket.replace('${AWS::Region}', 'us-east-1'),
},
`' -key '${asset_constants_2.CONFIG_WORKER_ASSET_WINDOWS.Key}.py' -file 'C:/temp/${asset_constants_2.CONFIG_WORKER_ASSET_WINDOWS.Key}.py' -ErrorAction Stop\n` +
`mkdir (Split-Path -Path 'C:/temp/${asset_constants_2.CONFIG_WORKER_PORT_ASSET_WINDOWS.Key}.ps1' ) -ea 0\nRead-S3Object -BucketName '`,
{
'Fn::Sub': asset_constants_2.CONFIG_WORKER_PORT_ASSET_WINDOWS.Bucket.replace('${AWS::Region}', 'us-east-1'),
},
`' -key '${asset_constants_2.CONFIG_WORKER_PORT_ASSET_WINDOWS.Key}.ps1' -file 'C:/temp/${asset_constants_2.CONFIG_WORKER_PORT_ASSET_WINDOWS.Key}.ps1' -ErrorAction Stop\n` +
`&'C:/temp/${asset_constants_2.CONFIG_WORKER_PORT_ASSET_WINDOWS.Key}.ps1' '' '' '' '${lib_2.Version.MINIMUM_SUPPORTED_DEADLINE_VERSION.toString()}' ${lib_2.WorkerInstanceConfiguration['DEFAULT_LISTENER_PORT']} C:/temp/${asset_constants_2.CONFIG_WORKER_ASSET_WINDOWS.Key}.py\n` +
`if (!$?) { Write-Error 'Failed to execute the file \"C:/temp/${asset_constants_2.CONFIG_WORKER_PORT_ASSET_WINDOWS.Key}.ps1\"' -ErrorAction Stop }\n` +
'throw \"Success\"</powershell>',
],
],
},
},
});
});
test('default worker fleet is created correctly with groups, pools and region', () => {
vpc = new aws_ec2_1.Vpc(stack, 'VPC1Az', {
maxAzs: 1,
});
// WHEN
new lib_2.WorkerInstanceFleet(stack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
instanceType: aws_ec2_1.InstanceType.of(aws_ec2_1.InstanceClass.T2, aws_ec2_1.InstanceSize.MEDIUM),
vpcSubnets: {
subnetType: aws_ec2_1.SubnetType.PUBLIC,
},
groups: ['A', 'B'], // We want to make sure that these are converted to lowercase
pools: ['C', 'D'], // We want to make sure that these are converted to lowercase
region: 'E',
});
// THEN
assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', {
InstanceType: 't2.medium',
IamInstanceProfile: {
Ref: assertions_1.Match.stringLikeRegexp('workerFleetInstanceProfile.*'),
},
UserData: {
'Fn::Base64': {
'Fn::Join': [
'',
[
'#!/bin/bash\n' +
'function exitTrap(){\nexitCode=$?\n/opt/aws/bin/cfn-signal --stack infraStack --resource workerFleetASG25520D69 --region us-east-1 -e $exitCode || echo \'Failed to send Cloudformation Signal\'\n}\n' +
'trap exitTrap EXIT\n' +
`mkdir -p $(dirname '/tmp/${asset_constants_1.CWA_ASSET_LINUX.Key}.sh')\naws s3 cp 's3://`,
{
'Fn::Sub': asset_constants_1.CWA_ASSET_LINUX.Bucket.replace('${AWS::Region}', 'us-east-1'),
},
`/${asset_constants_1.CWA_ASSET_LINUX.Key}.sh' '/tmp/${asset_constants_1.CWA_ASSET_LINUX.Key}.sh'\n` +
`set -e\nchmod +x '/tmp/${asset_constants_1.CWA_ASSET_LINUX.Key}.sh'\n'/tmp/${asset_constants_1.CWA_ASSET_LINUX.Key}.sh' -i us-east-1 `,
{
Ref: assertions_1.Match.stringLikeRegexp('^workerFleetStringParameter.*'),
},
`\nmkdir -p $(dirname '/tmp/${asset_constants_2.RQ_CONNECTION_ASSET.Key}.py')\naws s3 cp 's3://`,
{
'Fn::Sub': asset_constants_2.RQ_CONNECTION_ASSET.Bucket.replace('${AWS::Region}', 'us-east-1'),
},
`/${asset_constants_2.RQ_CONNECTION_ASSET.Key}.py' '/tmp/${asset_constants_2.RQ_CONNECTION_ASSET.Key}.py'\n` +
'if [ -f "/etc/profile.d/deadlineclient.sh" ]; then\n source "/etc/profile.d/deadlineclient.sh"\nfi\n' +
`"\${DEADLINE_PATH}/deadlinecommand" -executeScriptNoGui "/tmp/${asset_constants_2.RQ_CONNECTION_ASSET.Key}.py" --render-queue "http://`,
{
'Fn::GetAtt': [
'RQLB3B7B1CBC',
'DNSName',
],
},
`:8080" \nrm -f "/tmp/${asset_constants_2.RQ_CONNECTION_ASSET.Key}.py"` +
`\nmkdir -p $(dirname '/tmp/${asset_constants_2.CONFIG_WORKER_PORT_ASSET_LINUX.Key}.py')\naws s3 cp 's3://`,
{
'Fn::Sub': asset_constants_2.CONFIG_WORKER_PORT_ASSET_LINUX.Bucket.replace('${AWS::Region}', 'us-east-1'),
},
`/${asset_constants_2.CONFIG_WORKER_PORT_ASSET_LINUX.Key}.py' '/tmp/${asset_constants_2.CONFIG_WORKER_PORT_ASSET_LINUX.Key}.py'\n` +
`mkdir -p $(dirname '/tmp/${asset_constants_2.CONFIG_WORKER_ASSET_LINUX.Key}.sh')\naws s3 cp 's3://`,
{
'Fn::Sub': asset_constants_2.CONFIG_WORKER_ASSET_LINUX.Bucket.replace('${AWS::Region}', 'us-east-1'),
},
`/${asset_constants_2.CONFIG_WORKER_ASSET_LINUX.Key}.sh' '/tmp/${asset_constants_2.CONFIG_WORKER_ASSET_LINUX.Key}.sh'\n` +
'set -e\n' +
`chmod +x '/tmp/${asset_constants_2.CONFIG_WORKER_ASSET_LINUX.Key}.sh'\n` +
`'/tmp/${asset_constants_2.CONFIG_WORKER_ASSET_LINUX.Key}.sh' 'a,b' 'c,d' 'E' '${lib_2.Version.MINIMUM_SUPPORTED_DEADLINE_VERSION.toString()}' ${lib_2.WorkerInstanceConfiguration['DEFAULT_LISTENER_PORT']} /tmp/${asset_constants_2.CONFIG_WORKER_PORT_ASSET_LINUX.Key}.py`,
],
],
},
},
});
});
test('worker fleet does validation correctly with groups, pools and region', () => {
vpc = new aws_ec2_1.Vpc(stack, 'VPC1Az', {
maxAzs: 1,
});
// group name as 'none'
expect(() => {
new lib_2.WorkerInstanceFleet(stack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
groups: ['A', 'none'],
});
}).toThrow();
// group name with whitespace
expect(() => {
new lib_2.WorkerInstanceFleet(stack, 'workerFleet1', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
groups: ['A', 'no ne'],
});
}).toThrow(/Invalid value: no ne for property 'groups'/);
// pool name with whitespace
expect(() => {
new lib_2.WorkerInstanceFleet(stack, 'workerFleet2', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
pools: ['A', 'none'],
});
}).toThrow(/Invalid value: none for property 'pools'/);
// pool name as 'none'
expect(() => {
new lib_2.WorkerInstanceFleet(stack, 'workerFleet3', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
pools: ['A', 'none'],
});
}).toThrow(/Invalid value: none for property 'pools'/);
// region as 'none'
expect(() => {
new lib_2.WorkerInstanceFleet(stack, 'workerFleet4', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
region: 'none',
});
}).toThrow(/Invalid value: none for property 'region'/);
// region as 'all'
expect(() => {
new lib_2.WorkerInstanceFleet(stack, 'workerFleet5', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
region: 'all',
});
}).toThrow(/Invalid value: all for property 'region'/);
// region as 'unrecognized'
expect(() => {
new lib_2.WorkerInstanceFleet(stack, 'workerFleet6', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
region: 'unrecognized',
});
}).toThrow(/Invalid value: unrecognized for property 'region'/);
// region with invalid characters
expect(() => {
new lib_2.WorkerInstanceFleet(stack, 'workerFleet7', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
region: 'none@123',
});
}).toThrow(/Invalid value: none@123 for property 'region'/);
// region with reserved name as substring
expect(() => {
new lib_2.WorkerInstanceFleet(stack, 'workerFleet8', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
region: 'none123',
});
}).not.toThrow();
// region with case-insensitive name
expect(() => {
new lib_2.WorkerInstanceFleet(stack, 'workerFleet9', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': '123',
}),
renderQueue,
region: 'None',
});
}).toThrow(/Invalid value: None for property 'region'/);
});
describe('Block Device Tests', () => {
let healthMonitor;
beforeEach(() => {
// create a health monitor so it does not trigger warnings
healthMonitor = new lib_1.HealthMonitor(wfstack, 'healthMonitor', {
vpc,
});
});
test('Warning if no BlockDevices provided', () => {
const fleet = new lib_2.WorkerInstanceFleet(wfstack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
healthMonitor,
});
assertions_1.Annotations.fromStack(wfstack).hasWarning(`/${fleet.node.path}`, assertions_1.Match.stringLikeRegexp('.*being created without being provided any block devices so the Source AMI\'s devices will be used. Workers can have access to sensitive data so it is recommended to either explicitly encrypt the devices on the worker fleet or to ensure the source AMI\'s Drives are encrypted.'));
});
test('No Warnings if Encrypted BlockDevices Provided', () => {
const VOLUME_SIZE = 50;
// WHEN
const fleet = new lib_2.WorkerInstanceFleet(wfstack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
healthMonitor,
blockDevices: [{
deviceName: '/dev/xvda',
volume: aws_autoscaling_1.BlockDeviceVolume.ebs(VOLUME_SIZE, { encrypted: true }),
}],
});
//THEN
assertions_1.Template.fromStack(wfstack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', {
BlockDeviceMappings: [
{
Ebs: {
Encrypted: true,
VolumeSize: VOLUME_SIZE,
},
},
],
});
assertions_1.Annotations.fromStack(wfstack).hasNoInfo(`/${fleet.node.path}`, assertions_1.Match.anyValue());
assertions_1.Annotations.fromStack(wfstack).hasNoWarning(`/${fleet.node.path}`, assertions_1.Match.anyValue());
assertions_1.Annotations.fromStack(wfstack).hasNoError(`/${fleet.node.path}`, assertions_1.Match.anyValue());
});
test('Warnings if non-Encrypted BlockDevices Provided', () => {
const VOLUME_SIZE = 50;
const DEVICE_NAME = '/dev/xvda';
// WHEN
const fleet = new lib_2.WorkerInstanceFleet(wfstack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
healthMonitor,
blockDevices: [{
deviceName: DEVICE_NAME,
volume: aws_autoscaling_1.BlockDeviceVolume.ebs(VOLUME_SIZE, { encrypted: false }),
}],
});
//THEN
assertions_1.Template.fromStack(wfstack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', {
BlockDeviceMappings: [
{
Ebs: {
Encrypted: false,
VolumeSize: VOLUME_SIZE,
},
},
],
});
assertions_1.Annotations.fromStack(wfstack).hasWarning(`/${fleet.node.path}`, assertions_1.Match.stringLikeRegexp(`The BlockDevice \"${DEVICE_NAME}\" on the worker-fleet workerFleet is not encrypted. Workers can have access to sensitive data so it is recommended to encrypt the devices on the worker fleet.`));
});
test('Warnings for BlockDevices without encryption specified', () => {
const VOLUME_SIZE = 50;
const DEVICE_NAME = '/dev/xvda';
// WHEN
const fleet = new lib_2.WorkerInstanceFleet(wfstack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
healthMonitor,
blockDevices: [{
deviceName: DEVICE_NAME,
volume: aws_autoscaling_1.BlockDeviceVolume.ebs(VOLUME_SIZE),
}],
});
//THEN
assertions_1.Template.fromStack(wfstack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', {
BlockDeviceMappings: [
{
Ebs: {
VolumeSize: VOLUME_SIZE,
},
},
],
});
assertions_1.Annotations.fromStack(wfstack).hasWarning(`/${fleet.node.path}`, assertions_1.Match.stringLikeRegexp(`The BlockDevice \"${DEVICE_NAME}\" on the worker-fleet workerFleet is not encrypted. Workers can have access to sensitive data so it is recommended to encrypt the devices on the worker fleet.`));
});
test('No warnings for Ephemeral blockDeviceVolumes', () => {
const DEVICE_NAME = '/dev/xvda';
// WHEN
const fleet = new lib_2.WorkerInstanceFleet(wfstack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
healthMonitor,
blockDevices: [{
deviceName: DEVICE_NAME,
volume: aws_autoscaling_1.BlockDeviceVolume.ephemeral(0),
}],
});
//THEN
assertions_1.Template.fromStack(wfstack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', {
BlockDeviceMappings: [
{
DeviceName: DEVICE_NAME,
VirtualName: 'ephemeral0',
},
],
});
assertions_1.Annotations.fromStack(wfstack).hasNoInfo(`/${fleet.node.path}`, assertions_1.Match.anyValue());
assertions_1.Annotations.fromStack(wfstack).hasNoWarning(`/${fleet.node.path}`, assertions_1.Match.anyValue());
assertions_1.Annotations.fromStack(wfstack).hasNoError(`/${fleet.node.path}`, assertions_1.Match.anyValue());
});
test('No warnings for Suppressed blockDeviceVolumes', () => {
const DEVICE_NAME = '/dev/xvda';
// WHEN
const fleet = new lib_2.WorkerInstanceFleet(wfstack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
healthMonitor,
blockDevices: [{
deviceName: DEVICE_NAME,
volume: aws_autoscaling_1.BlockDeviceVolume.noDevice(),
}],
});
//THEN
assertions_1.Template.fromStack(wfstack).hasResourceProperties('AWS::AutoScaling::LaunchConfiguration', {
BlockDeviceMappings: [
{
DeviceName: DEVICE_NAME,
},
],
});
assertions_1.Annotations.fromStack(wfstack).hasNoInfo(`/${fleet.node.path}`, assertions_1.Match.anyValue());
assertions_1.Annotations.fromStack(wfstack).hasNoWarning(`/${fleet.node.path}`, assertions_1.Match.anyValue());
assertions_1.Annotations.fromStack(wfstack).hasNoError(`/${fleet.node.path}`, assertions_1.Match.anyValue());
});
});
describe('HealthMonitor Tests', () => {
let healthMonitor;
beforeEach(() => {
// create a health monitor so it does not trigger warnings
healthMonitor = new lib_1.HealthMonitor(wfstack, 'healthMonitor', {
vpc,
});
});
test('Monitor is configured for Windows', () => {
// WHEN
const fleet = new lib_2.WorkerInstanceFleet(wfstack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
healthMonitor,
});
const userData = fleet.fleet.userData.render();
// THEN
// Ensure the configuration script is executed with the expected arguments.
expect(userData).toContain(`&'C:/temp/${asset_constants_2.CONFIG_WORKER_HEALTHCHECK_WINDOWS.Key}.ps1' '63415' '${lib_2.Version.MINIMUM_SUPPORTED_DEADLINE_VERSION.toString()}'`);
// Ensure that the health monitor target group has been set up.
// Note: It's sufficient to just check for any resource created by the HealthMonitor registration.
// The HealthMonitor tests cover ensuring that all of the resources are set up.
assertions_1.Template.fromStack(wfstack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', {
HealthCheckIntervalSeconds: 300,
HealthCheckPort: '63415',
HealthCheckProtocol: 'HTTP',
Port: 8081,
Protocol: 'HTTP',
TargetType: 'instance',
});
});
test('Monitor is configured for Linux', () => {
// WHEN
const fleet = new lib_2.WorkerInstanceFleet(wfstack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': 'ami-any',
}),
renderQueue,
healthMonitor,
});
const userData = fleet.fleet.userData.render();
// THEN
// Ensure the configuration script is executed with the expected arguments.
expect(userData).toContain(`'/tmp/${asset_constants_2.CONFIG_WORKER_HEALTHCHECK_LINUX.Key}.sh' '63415' '${lib_2.Version.MINIMUM_SUPPORTED_DEADLINE_VERSION.toString()}'`);
// Ensure that the health monitor target group has been set up.
// Note: It's sufficient to just check for any resource created by the HealthMonitor registration.
// The HealthMonitor tests cover ensuring that all of the resources are set up.
assertions_1.Template.fromStack(wfstack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', {
HealthCheckIntervalSeconds: 300,
HealthCheckPort: '63415',
HealthCheckProtocol: 'HTTP',
Port: 8081,
Protocol: 'HTTP',
TargetType: 'instance',
});
});
test('UserData is added', () => {
// WHEN
class UserDataProvider extends lib_2.InstanceUserDataProvider {
preCloudWatchAgent(host) {
host.userData.addCommands('echo preCloudWatchAgent');
}
preRenderQueueConfiguration(host) {
host.userData.addCommands('echo preRenderQueueConfiguration');
}
preWorkerConfiguration(host) {
host.userData.addCommands('echo preWorkerConfiguration');
}
postWorkerLaunch(host) {
host.userData.addCommands('echo postWorkerLaunch');
}
}
const fleet = new lib_2.WorkerInstanceFleet(wfstack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': 'ami-any',
}),
renderQueue,
healthMonitor,
userDataProvider: new UserDataProvider(wfstack, 'UserDataProvider'),
});
const userData = fleet.fleet.userData.render();
// THEN
expect(userData).toContain('echo preCloudWatchAgent');
expect(userData).toContain('echo preRenderQueueConfiguration');
expect(userData).toContain('echo preWorkerConfiguration');
expect(userData).toContain('echo postWorkerLaunch');
});
});
describe('tagging', () => {
(0, tag_helpers_1.testConstructTags)({
constructName: 'WorkerInstanceFleet',
createConstruct: () => {
// GIVEN
const healthMonitorStack = new aws_cdk_lib_1.Stack(app, 'HealthMonitorStack', {
env: {
region: 'us-east-1',
},
});
const healthMonitor = new lib_1.HealthMonitor(healthMonitorStack, 'healthMonitor', {
vpc,
});
const deviceName = '/dev/xvda';
// WHEN
new lib_2.WorkerInstanceFleet(wfstack, 'WorkerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericLinuxImage({
'us-east-1': 'ami-any',
}),
renderQueue,
healthMonitor,
blockDevices: [{
deviceName,
volume: aws_autoscaling_1.BlockDeviceVolume.noDevice(),
}],
});
return wfstack;
},
resourceTypeCounts: {
'AWS::EC2::SecurityGroup': 1,
'AWS::IAM::Role': 1,
'AWS::AutoScaling::AutoScalingGroup': 1,
'AWS::ElasticLoadBalancingV2::TargetGroup': 1,
'AWS::SSM::Parameter': 1,
},
});
});
test('worker fleet signals when non-zero minCapacity', () => {
// WHEN
const fleet = new lib_2.WorkerInstanceFleet(wfstack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
minCapacity: 1,
});
// WHEN
const userData = fleet.fleet.userData.render();
// THEN
expect(userData).toContain('cfn-signal');
assertions_1.Template.fromStack(wfstack).hasResource('AWS::AutoScaling::AutoScalingGroup', {
CreationPolicy: {
ResourceSignal: {
Count: 1,
},
},
});
assertions_1.Annotations.fromStack(wfstack).hasWarning(`/${fleet.node.path}`, assertions_1.Match.stringLikeRegexp('.*being created without being provided any block devices so the Source AMI\'s devices will be used. Workers can have access to sensitive data so it is recommended to either explicitly encrypt the devices on the worker fleet or to ensure the source AMI\'s Drives are encrypted.'));
assertions_1.Annotations.fromStack(wfstack).hasWarning(`/${fleet.node.path}`, assertions_1.Match.stringLikeRegexp('.*being created without a health monitor attached to it. This means that the fleet will not automatically scale-in to 0 if the workers are unhealthy'));
});
test('worker fleet does not signal when zero minCapacity', () => {
// WHEN
const fleet = new lib_2.WorkerInstanceFleet(wfstack, 'workerFleet', {
vpc,
workerMachineImage: new aws_ec2_1.GenericWindowsImage({
'us-east-1': 'ami-any',
}),
renderQueue,
minCapacity: 0,
});
// WHEN
const userData = fleet.fleet.userData.render();
// THEN
// There should be no cfn-signal call in the UserData.
expect(userData).not.toContain('cfn-signal');
// Make sure we don't have a CreationPolicy
(0, test_helper_1.resourcePropertiesCountIs)(wfstack, 'AWS::AutoScaling::AutoScalingGroup', {
CreationPolicy: assertions_1.Match.anyValue(),
}, 0);
assertions_1.Annotations.fromStack(wfstack).hasWarning(`/${fleet.node.path}`, assertions_1.Match.stringLikeRegexp('.*Depl