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

fix(ec2): volume props validations are incorrect #12821

Merged
merged 3 commits into from
Feb 9, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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