From 6dde01bd3e01e1ea46534ea1b095da9c64cea385 Mon Sep 17 00:00:00 2001 From: Ian Kerins Date: Wed, 22 May 2024 22:02:53 -0400 Subject: [PATCH] feat(ec2): support throughput on LaunchTemplate EBS volumes This support was simply not included in ec2 when it was added to autoscaling in #22441. I have copied that PR's implementation implementation to ec2 and similarly and adapted its tests. Fixes #24341. --- .../aws-ec2/lib/private/ebs-util.ts | 26 ++++++++- packages/aws-cdk-lib/aws-ec2/lib/volume.ts | 8 +++ .../aws-ec2/test/launch-template.test.ts | 56 +++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-ec2/lib/private/ebs-util.ts b/packages/aws-cdk-lib/aws-ec2/lib/private/ebs-util.ts index 51a020fa1036f..6586f657d83c8 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/private/ebs-util.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/private/ebs-util.ts @@ -25,7 +25,30 @@ function synthesizeBlockDeviceMappings(construct: Construct, blockDevic if (ebs) { - const { iops, volumeType, kmsKey, ...rest } = ebs; + const { iops, throughput, volumeType, kmsKey, ...rest } = ebs; + + if (throughput) { + const throughputRange = { Min: 125, Max: 1000 }; + const { Min, Max } = throughputRange; + + if (volumeType != EbsDeviceVolumeType.GP3) { + throw new Error('throughput property requires volumeType: EbsDeviceVolumeType.GP3'); + } + + if (throughput < Min || throughput > Max) { + throw new Error( + `throughput property takes a minimum of ${Min} and a maximum of ${Max}`, + ); + } + + const maximumThroughputRatio = 0.25; + if (iops) { + const iopsRatio = (throughput / iops); + if (iopsRatio > maximumThroughputRatio) { + throw new Error(`Throughput (MiBps) to iops ratio of ${iopsRatio} is too high; maximum is ${maximumThroughputRatio} MiBps per iops`); + } + } + } if (!iops) { if (volumeType === EbsDeviceVolumeType.IO1 || volumeType === EbsDeviceVolumeType.IO2) { @@ -43,6 +66,7 @@ function synthesizeBlockDeviceMappings(construct: Construct, blockDevic finalEbs = { ...rest, iops, + throughput, volumeType, kmsKeyId: kmsKey?.keyArn, }; diff --git a/packages/aws-cdk-lib/aws-ec2/lib/volume.ts b/packages/aws-cdk-lib/aws-ec2/lib/volume.ts index 70d19b119f717..73ffed0d5b1f8 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/volume.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/volume.ts @@ -70,6 +70,14 @@ export interface EbsDeviceOptionsBase { * `@aws-cdk/aws-ec2:ebsDefaultGp3Volume` is enabled. */ readonly volumeType?: EbsDeviceVolumeType; + + /** + * The throughput that the volume supports, in MiB/s + * Takes a minimum of 125 and maximum of 1000. + * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html + * @default - 125 MiB/s. Only valid on gp3 volumes. + */ + readonly throughput?: number; } /** diff --git a/packages/aws-cdk-lib/aws-ec2/test/launch-template.test.ts b/packages/aws-cdk-lib/aws-ec2/test/launch-template.test.ts index 227eb1c1b0d84..932c0fa08fc4c 100644 --- a/packages/aws-cdk-lib/aws-ec2/test/launch-template.test.ts +++ b/packages/aws-cdk-lib/aws-ec2/test/launch-template.test.ts @@ -314,6 +314,12 @@ describe('LaunchTemplate', () => { }, { deviceName: 'ephemeral', volume: BlockDeviceVolume.ephemeral(0), + }, { + deviceName: 'gp3-with-throughput', + volume: BlockDeviceVolume.ebs(15, { + volumeType: EbsDeviceVolumeType.GP3, + throughput: 350, + }), }, ]; @@ -366,10 +372,60 @@ describe('LaunchTemplate', () => { DeviceName: 'ephemeral', VirtualName: 'ephemeral0', }, + { + DeviceName: 'gp3-with-throughput', + Ebs: { + VolumeSize: 15, + VolumeType: 'gp3', + Throughput: 350, + }, + }, ], }, }); }); + test.each([124, 1001])('throws if throughput is set less than 125 or more than 1000', (throughput) => { + expect(() => { + new LaunchTemplate(stack, 'LaunchTemplate', { + blockDevices: [{ + deviceName: 'ebs', + volume: BlockDeviceVolume.ebs(15, { + volumeType: EbsDeviceVolumeType.GP3, + throughput, + }), + }], + }); + }).toThrow(/throughput property takes a minimum of 125 and a maximum of 1000/); + }); + test.each([ + ...Object.values(EbsDeviceVolumeType).filter((v) => v !== 'gp3'), + ])('throws if throughput is set on any volume type other than GP3', (volumeType) => { + expect(() => { + new LaunchTemplate(stack, 'LaunchTemplate', { + blockDevices: [{ + deviceName: 'ebs', + volume: BlockDeviceVolume.ebs(15, { + volumeType: volumeType, + throughput: 150, + }), + }], + }); + }).toThrow(/throughput property requires volumeType: EbsDeviceVolumeType.GP3/); + }); + test('throws if throughput / iops ratio is greater than 0.25', () => { + expect(() => { + new LaunchTemplate(stack, 'LaunchTemplate', { + blockDevices: [{ + deviceName: 'ebs', + volume: BlockDeviceVolume.ebs(15, { + volumeType: EbsDeviceVolumeType.GP3, + throughput: 751, + iops: 3000, + }), + }], + }); + }).toThrow('Throughput (MiBps) to iops ratio of 0.25033333333333335 is too high; maximum is 0.25 MiBps per iops'); + }); test('Given instance profile', () => { // GIVEN