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(ecs): add validation checks to memory cpu combinations of FARGATE compatible task definitions #30166

Merged
merged 13 commits into from
Jul 30, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,8 @@ describe('Application Load Balancer', () => {
// WHEN
new ApplicationMultipleTargetGroupsFargateService(stack, 'myService', {
cluster: new ecs.Cluster(stack, 'EcsCluster', { vpc }),
memoryLimitMiB: 256,
cpu: 256,
memoryLimitMiB: 512,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ describe('ApplicationLoadBalancedFargateService', () => {
// WHEN
const taskDef = new ecs.FargateTaskDefinition(stack1, 'TaskDef', {
cpu: 1024,
memoryLimitMiB: 1024,
memoryLimitMiB: 2048,
});
const container = taskDef.addContainer('Container', {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
Expand Down Expand Up @@ -1816,7 +1816,7 @@ describe('NetworkLoadBalancedFargateService', () => {
});
const taskDef = new ecs.FargateTaskDefinition(stack2, 'TaskDef', {
cpu: 1024,
memoryLimitMiB: 1024,
memoryLimitMiB: 2048,
});
const container = taskDef.addContainer('myContainer', {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ test('Can create a scheduled Fargate Task - with optional props', () => {
scheduledFargateTaskImageOptions: {
image: ecs.ContainerImage.fromRegistry('henk'),
memoryLimitMiB: 512,
cpu: 2,
cpu: 256,
ephemeralStorageGiB: 100,
environment: { TRIGGER: 'CloudWatch Events' },
},
Expand Down
35 changes: 35 additions & 0 deletions packages/aws-cdk-lib/aws-ecs/lib/base/task-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,12 @@ export class TaskDefinition extends TaskDefinitionBase {
this.checkFargateWindowsBasedTasksSize(props.cpu!, props.memoryMiB!, props.runtimePlatform!);
}

// Validate CPU and memory combinations if fargate compatible
if (isFargateCompatible(props.compatibility)) {
// Check the combination as per doc https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html
this.validateFargateTaskDefinitionMemoryCpu(props.cpu!, props.memoryMiB!);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we take the opportunity to remove the !s? It's a bit annoying to see them.

    if (this.isFargateCompatible) {
      if (!props.cpu || !props.memoryMiB) {
        throw new Error(`Fargate-compatible tasks require both CPU (${props.cpu}) and memory (${props.memoryMiB}) specifications`);
      }

      // validate the cpu and memory size for the Windows operation system family.
      if (props.runtimePlatform?.operatingSystemFamily?.isWindows()) {
        this.checkFargateWindowsBasedTasksSize(props.cpu, props.memoryMiB, props.runtimePlatform);
      }

      // Check the combination as per doc https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html
      this.validateFargateTaskDefinitionMemoryCpu(props.cpu, props.memoryMiB);
    }

}

this.runtimePlatform = props.runtimePlatform;
this._cpu = props.cpu;
this._memory = props.memoryMiB;
Expand Down Expand Up @@ -894,6 +900,35 @@ export class TaskDefinition extends TaskDefinitionBase {
throw new Error(`If operatingSystemFamily is ${runtimePlatform.operatingSystemFamily!._operatingSystemFamily}, then cpu must be in 1024 (1 vCPU), 2048 (2 vCPU), or 4096 (4 vCPU). Provided value was: ${cpu}`);
}
};

private validateFargateTaskDefinitionMemoryCpu(cpu: string, memory: string) {
const validCpuMemoryCombinations = [
{ cpu: 256, memory: [512, 1024, 2048] },
{ cpu: 512, memory: this.range(1024, 4096, 1024) },
{ cpu: 1024, memory: this.range(2048, 8192, 1024) },
{ cpu: 2048, memory: this.range(4096, 16384, 1024) },
{ cpu: 4096, memory: this.range(8192, 30720, 1024) },
{ cpu: 8192, memory: this.range(16384, 61440, 4096) },
{ cpu: 16384, memory: this.range(32768, 122880, 8192) },
];

const isValidCombination = validCpuMemoryCombinations.some((combo) => {
return combo.cpu === Number(cpu) && combo.memory.includes(Number(memory));
});

if (!isValidCombination) {
throw new Error('Invalid CPU and memory combinations for FARGATE compatible task definition - https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html');
}
}

private range(start: number, end: number, step: number): number[] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const validCpuMemoryCombinations = [
{ cpu: 256, memory: [512, 1024, 2048] },
{ cpu: 512, memory: this.range(1024, 4096, 1024) },
{ cpu: 1024, memory: this.range(2048, 8192, 1024) },
{ cpu: 2048, memory: this.range(4096, 16384, 1024) },
{ cpu: 4096, memory: this.range(8192, 30720, 1024) },
{ cpu: 8192, memory: this.range(16384, 61440, 4096) },
{ cpu: 16384, memory: this.range(32768, 122880, 8192) },
];
const isValidCombination = validCpuMemoryCombinations.some((combo) => {
return combo.cpu === Number(cpu) && combo.memory.includes(Number(memory));
});
if (!isValidCombination) {
throw new Error('Invalid CPU and memory combinations for FARGATE compatible task definition - https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html');
}
}
private range(start: number, end: number, step: number): number[] {
const validCpuMemoryCombinations = [
{ cpu: '256', memory: ['512', '1024', '2048'] },
{ cpu: '512', memory: this.range(1024, 4096, 1024) },
{ cpu: '1024', memory: this.range(2048, 8192, 1024) },
{ cpu: '2048', memory: this.range(4096, 16384, 1024) },
{ cpu: '4096', memory: this.range(8192, 30720, 1024) },
{ cpu: '8192', memory: this.range(16384, 61440, 4096) },
{ cpu: '16384', memory: this.range(32768, 122880, 8192) },
];
const isValidCombination = validCpuMemoryCombinations.some((combo) => {
return combo.cpu === cpu && combo.memory.includes(memory);
});
if (!isValidCombination) {
throw new Error('Invalid CPU and memory combinations for FARGATE compatible task definition - https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html');
}
}
private range(start: number, end: number, step: number): string[] {

What about avoiding Number -> String casting?

const result = [];
for (let i = start; i <= end; i += step) {
result.push(i);
}
return result;
}

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,24 @@ describe('fargate task definition', () => {

});

test('support lazy cpu and memory values', () => {
test('does not support lazy cpu and memory values', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to support this by adding validation on the node (like this):

this.node.addValidation({ validate: () => this. validateFargateTaskDefinitionMemoryCpu(...);

// GIVEN
const stack = new cdk.Stack();

new ecs.FargateTaskDefinition(stack, 'FargateTaskDef', {
cpu: cdk.Lazy.number({ produce: () => 128 }),
memoryLimitMiB: cdk.Lazy.number({ produce: () => 1024 }),
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', {
Cpu: '128',
Memory: '1024',
});
expect(() => {
new ecs.FargateTaskDefinition(stack, 'FargateTaskDef', {
cpu: cdk.Lazy.number({ produce: () => 256 }),
memoryLimitMiB: cdk.Lazy.number({ produce: () => 1024 }),
});
}).toThrow(/Invalid CPU and memory combinations for FARGATE compatible task definition/);

});

test('with all properties set', () => {
// GIVEN
const stack = new cdk.Stack();
const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef', {
cpu: 128,
cpu: 256,
executionRole: new iam.Role(stack, 'ExecutionRole', {
path: '/',
assumedBy: new iam.CompositePrincipal(
Expand Down Expand Up @@ -72,7 +68,7 @@ describe('fargate task definition', () => {

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', {
Cpu: '128',
Cpu: '256',
ExecutionRoleArn: {
'Fn::GetAtt': [
'ExecutionRole605A040B',
Expand Down Expand Up @@ -195,6 +191,30 @@ describe('fargate task definition', () => {
});
}).toThrow(/'pidMode' can only be set to 'host' for Fargate containers, got: 'task'./);
});

test('throws error when invalid CPU and memory combination is provided', () => {
const stack = new cdk.Stack();

expect(() => {
new ecs.FargateTaskDefinition(stack, 'FargateTaskDef', {
cpu: 256,
memoryLimitMiB: 125,
});
}).toThrow(/Invalid CPU and memory combinations for FARGATE compatible task definition/);
});

test('succesfull when valid CPU and memory combination is provided', () => {
Leo10Gama marked this conversation as resolved.
Show resolved Hide resolved
const stack = new cdk.Stack();
new ecs.FargateTaskDefinition(stack, 'FargateTaskDef', {
cpu: 256,
memoryLimitMiB: 512,
});

Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', {
Cpu: '256',
Memory: '512',
});
});
});
describe('When configuredAtLaunch in the Volume', ()=> {
test('do not throw when configuredAtLaunch is false', () => {
Expand Down
40 changes: 33 additions & 7 deletions packages/aws-cdk-lib/aws-ecs/test/task-definition.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('task definition', () => {

// WHEN
new ecs.TaskDefinition(stack, 'TD', {
cpu: '512',
cpu: '256',
memoryMiB: '512',
compatibility: ecs.Compatibility.EC2_AND_FARGATE,
});
Expand Down Expand Up @@ -51,7 +51,7 @@ describe('task definition', () => {
assumedBy: new iam.AccountRootPrincipal(),
});
const taskDef = new ecs.TaskDefinition(stack, 'TD', {
cpu: '512',
cpu: '256',
memoryMiB: '512',
compatibility: ecs.Compatibility.EC2_AND_FARGATE,
});
Expand Down Expand Up @@ -96,7 +96,7 @@ describe('task definition', () => {
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
});
const taskDef = new ecs.TaskDefinition(stack, 'TD', {
cpu: '512',
cpu: '256',
memoryMiB: '512',
compatibility: ecs.Compatibility.EC2_AND_FARGATE,
executionRole: executionRole,
Expand Down Expand Up @@ -154,7 +154,7 @@ describe('task definition', () => {
},
);
const taskDef = new ecs.TaskDefinition(stack, 'TD', {
cpu: '512',
cpu: '256',
memoryMiB: '512',
compatibility: ecs.Compatibility.EC2_AND_FARGATE,
});
Expand Down Expand Up @@ -387,7 +387,7 @@ describe('task definition', () => {
},
);
const taskDef = new ecs.TaskDefinition(stack, 'TD', {
cpu: '512',
cpu: '256',
memoryMiB: '512',
compatibility: ecs.Compatibility.EC2_AND_FARGATE,
});
Expand Down Expand Up @@ -457,6 +457,32 @@ describe('task definition', () => {
Template.fromStack(stack);
}).toThrow("ECS Container Container must have at least one of 'memoryLimitMiB' or 'memoryReservationMiB' specified");
});

test('throws error when invalid CPU and memory combination is provided with Fargate compatibilities', () => {
const stack = new cdk.Stack();

expect(() => {
new ecs.TaskDefinition(stack, 'TaskDef', {
compatibility: ecs.Compatibility.EC2_AND_FARGATE,
cpu: '122',
memoryMiB: '513',
});
}).toThrow(/Invalid CPU and memory combinations for FARGATE compatible task definition/);
});

test('succesfull when valid CPU and memory combination is provided with Fargate compatibilities', () => {
Leo10Gama marked this conversation as resolved.
Show resolved Hide resolved
const stack = new cdk.Stack();
new ecs.TaskDefinition(stack, 'TaskDef', {
compatibility: ecs.Compatibility.EC2_AND_FARGATE,
cpu: '256',
memoryMiB: '512',
});

Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', {
Cpu: '256',
Memory: '512',
});
});
});

describe('When importing from an existing Task definition', () => {
Expand Down Expand Up @@ -560,7 +586,7 @@ describe('task definition', () => {
const stack = new cdk.Stack();
const taskDefinition = new ecs.TaskDefinition(stack, 'TaskDef', {
cpu: '512',
memoryMiB: '512',
memoryMiB: '1024',
compatibility: ecs.Compatibility.FARGATE,
});

Expand Down Expand Up @@ -610,7 +636,7 @@ describe('task definition', () => {
const stack = new cdk.Stack();
const taskDefinition = new ecs.TaskDefinition(stack, 'TaskDef', {
cpu: '512',
memoryMiB: '512',
memoryMiB: '1024',
compatibility: ecs.Compatibility.FARGATE,
});

Expand Down
Loading