Skip to content

Commit

Permalink
fix(ec2): volume props validations are incorrect (#12821)
Browse files Browse the repository at this point in the history
Fixes #12816.

* add validation: must specify `iops` if `volumeType` is `io1` or `io2`
* fix validation: `iops` may only be specified if the `volumeType` is `io1`, `io2` or `gp3`
* fix validation: `iops` minimum & maximum for `io1`, `io2` and `gp3` respectively
* fix validation: `iops` maximum ratio (IOPS/Gib) for `io1`, `io2` and `gp3` respectively
* fix validation: `multi-attach` is supported exclusively on `io1` and `io2` volumes.
* fix validation: `size` minimum & maximum for all `volumeType` (including `gp3` and `io2` which was a bug specified in #12816)

Unit tests are either added / fixed for above changes.

References:
#12074
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ebs-volume.html
https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
kirintw committed Feb 9, 2021
1 parent fbe7e89 commit 12cddff
Show file tree
Hide file tree
Showing 2 changed files with 252 additions and 81 deletions.
88 changes: 67 additions & 21 deletions packages/@aws-cdk/aws-ec2/lib/volume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ export interface VolumeProps {

/**
* The size of the volume, in GiBs. You must specify either a snapshot ID or a volume size.
* See {@link https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html#ebs-volume-characteristics|Volume Characteristics}
* See {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ebs-volume.html}
* for details on the allowable size for each type of volume.
*
* @default If you're creating the volume from a snapshot and don't specify a volume size, the default is the snapshot size.
Expand Down Expand Up @@ -421,13 +421,14 @@ export interface VolumeProps {
readonly volumeType?: EbsDeviceVolumeType;

/**
* The number of I/O operations per second (IOPS) to provision for the volume, with a maximum ratio of 50 IOPS/GiB.
* See {@link https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html#EBSVolumeTypes_piops|Provisioned IOPS SSD (io1) volumes}
* The number of I/O operations per second (IOPS) to provision for the volume. The maximum ratio is 50 IOPS/GiB for PROVISIONED_IOPS_SSD,
* and 500 IOPS/GiB for both PROVISIONED_IOPS_SSD_IO2 and GENERAL_PURPOSE_SSD_GP3.
* See {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ebs-volume.html}
* for more information.
*
* This parameter is valid only for PROVISIONED_IOPS_SSD volumes.
* This parameter is valid only for PROVISIONED_IOPS_SSD, PROVISIONED_IOPS_SSD_IO2 and GENERAL_PURPOSE_SSD_GP3 volumes.
*
* @default None -- Required for {@link EbsDeviceVolumeType.PROVISIONED_IOPS_SSD}
* @default None -- Required for io1 and io2 volumes. The default for gp3 volumes is 3,000 IOPS if omitted.
*/
readonly iops?: number;
}
Expand Down Expand Up @@ -642,34 +643,79 @@ export class Volume extends VolumeBase {
throw new Error('`encrypted` must be true when providing an `encryptionKey`.');
}

if (
props.volumeType &&
[
EbsDeviceVolumeType.PROVISIONED_IOPS_SSD,
EbsDeviceVolumeType.PROVISIONED_IOPS_SSD_IO2,
].includes(props.volumeType) &&
!props.iops
) {
throw new Error(
'`iops` must be specified if the `volumeType` is `PROVISIONED_IOPS_SSD` or `PROVISIONED_IOPS_SSD_IO2`.',
);
}

if (props.iops) {
if (props.volumeType !== EbsDeviceVolumeType.PROVISIONED_IOPS_SSD) {
throw new Error('`iops` may only be specified if the `volumeType` is `PROVISIONED_IOPS_SSD`/`IO1`');
const volumeType = props.volumeType ?? EbsDeviceVolumeType.GENERAL_PURPOSE_SSD;
if (
![
EbsDeviceVolumeType.PROVISIONED_IOPS_SSD,
EbsDeviceVolumeType.PROVISIONED_IOPS_SSD_IO2,
EbsDeviceVolumeType.GENERAL_PURPOSE_SSD_GP3,
].includes(volumeType)
) {
throw new Error(
'`iops` may only be specified if the `volumeType` is `PROVISIONED_IOPS_SSD`, `PROVISIONED_IOPS_SSD_IO2` or `GENERAL_PURPOSE_SSD_GP3`.',
);
}

if (props.iops < 100 || props.iops > 64000) {
throw new Error('`iops` must be in the range 100 to 64,000, inclusive.');
// Enforce minimum & maximum IOPS:
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ebs-volume.html
const iopsRanges: { [key: string]: { Min: number, Max: number } } = {};
iopsRanges[EbsDeviceVolumeType.GENERAL_PURPOSE_SSD_GP3] = { Min: 3000, Max: 16000 };
iopsRanges[EbsDeviceVolumeType.PROVISIONED_IOPS_SSD] = { Min: 100, Max: 64000 };
iopsRanges[EbsDeviceVolumeType.PROVISIONED_IOPS_SSD_IO2] = { Min: 100, Max: 64000 };
const { Min, Max } = iopsRanges[volumeType];
if (props.iops < Min || props.iops > Max) {
throw new Error(`\`${volumeType}\` volumes iops must be between ${Min} and ${Max}.`);
}

if (props.size && (props.iops > 50 * props.size.toGibibytes({ rounding: SizeRoundingBehavior.FAIL }))) {
throw new Error('`iops` has a maximum ratio of 50 IOPS/GiB.');
// Enforce maximum ratio of IOPS/GiB:
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html
const maximumRatios: { [key: string]: number } = {};
maximumRatios[EbsDeviceVolumeType.GENERAL_PURPOSE_SSD_GP3] = 500;
maximumRatios[EbsDeviceVolumeType.PROVISIONED_IOPS_SSD] = 50;
maximumRatios[EbsDeviceVolumeType.PROVISIONED_IOPS_SSD_IO2] = 500;
const maximumRatio = maximumRatios[volumeType];
if (props.size && (props.iops > maximumRatio * props.size.toGibibytes({ rounding: SizeRoundingBehavior.FAIL }))) {
throw new Error(`\`${volumeType}\` volumes iops has a maximum ratio of ${maximumRatio} IOPS/GiB.`);
}
}

if (props.enableMultiAttach && props.volumeType !== EbsDeviceVolumeType.PROVISIONED_IOPS_SSD) {
throw new Error('multi-attach is supported exclusively on `PROVISIONED_IOPS_SSD` volumes.');
if (props.enableMultiAttach) {
const volumeType = props.volumeType ?? EbsDeviceVolumeType.GENERAL_PURPOSE_SSD;
if (
![
EbsDeviceVolumeType.PROVISIONED_IOPS_SSD,
EbsDeviceVolumeType.PROVISIONED_IOPS_SSD_IO2,
].includes(volumeType)
) {
throw new Error('multi-attach is supported exclusively on `PROVISIONED_IOPS_SSD` and `PROVISIONED_IOPS_SSD_IO2` volumes.');
}
}

if (props.size) {
const size = props.size.toGibibytes({ rounding: SizeRoundingBehavior.FAIL });
// Enforce maximum volume size:
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html#ebs-volume-characteristics
// Enforce minimum & maximum volume size:
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-ebs-volume.html
const sizeRanges: { [key: string]: { Min: number, Max: number } } = {};
sizeRanges[EbsDeviceVolumeType.GENERAL_PURPOSE_SSD] = { Min: 1, Max: 16000 };
sizeRanges[EbsDeviceVolumeType.PROVISIONED_IOPS_SSD] = { Min: 4, Max: 16000 };
sizeRanges[EbsDeviceVolumeType.THROUGHPUT_OPTIMIZED_HDD] = { Min: 500, Max: 16000 };
sizeRanges[EbsDeviceVolumeType.COLD_HDD] = { Min: 500, Max: 16000 };
sizeRanges[EbsDeviceVolumeType.MAGNETIC] = { Min: 1, Max: 1000 };
sizeRanges[EbsDeviceVolumeType.GENERAL_PURPOSE_SSD] = { Min: 1, Max: 16384 };
sizeRanges[EbsDeviceVolumeType.GENERAL_PURPOSE_SSD_GP3] = { Min: 1, Max: 16384 };
sizeRanges[EbsDeviceVolumeType.PROVISIONED_IOPS_SSD] = { Min: 4, Max: 16384 };
sizeRanges[EbsDeviceVolumeType.PROVISIONED_IOPS_SSD_IO2] = { Min: 4, Max: 16384 };
sizeRanges[EbsDeviceVolumeType.THROUGHPUT_OPTIMIZED_HDD] = { Min: 125, Max: 16384 };
sizeRanges[EbsDeviceVolumeType.COLD_HDD] = { Min: 125, Max: 16384 };
sizeRanges[EbsDeviceVolumeType.MAGNETIC] = { Min: 1, Max: 1024 };
const volumeType = props.volumeType ?? EbsDeviceVolumeType.GENERAL_PURPOSE_SSD;
const { Min, Max } = sizeRanges[volumeType];
if (size < Min || size > Max) {
Expand Down
Loading

0 comments on commit 12cddff

Please sign in to comment.