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(ec2): blockDevice property #4781

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
177 changes: 18 additions & 159 deletions packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,29 +448,12 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements
userData: userDataToken,
associatePublicIpAddress: props.associatePublicIpAddress,
spotPrice: props.spotPrice,
blockDeviceMappings: (props.blockDevices !== undefined ? props.blockDevices.map<CfnLaunchConfiguration.BlockDeviceMappingProperty>(
({deviceName, volume, mappingEnabled}) => {
const {virtualName, ebsDevice: ebs} = volume;

if (ebs) {
const {iops, volumeType} = ebs;

if (!iops) {
if (volumeType === EbsDeviceVolumeType.IO1) {
throw new Error('iops property is required with volumeType: EbsDeviceVolumeType.IO1');
}
} else if (volumeType !== EbsDeviceVolumeType.IO1) {
this.node.addWarning('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1');
}
}

return {
deviceName,
ebs,
virtualName,
noDevice: mappingEnabled !== undefined ? !mappingEnabled : undefined,
};
}) : undefined),
blockDeviceMappings: (props.blockDevices !== undefined ?
ec2.synthesizeBlockDeviceMappings(this, props.blockDevices).map<CfnLaunchConfiguration.BlockDeviceMappingProperty>(
({ deviceName, ebs, virtualName, noDevice }) => ({
deviceName, ebs, virtualName, noDevice: noDevice ? true : false
})
) : undefined),
});

launchConfig.node.addDependency(this.role);
Expand Down Expand Up @@ -938,158 +921,34 @@ export interface MetricTargetTrackingProps extends BaseTargetTrackingProps {
readonly targetValue: number;
}

export interface BlockDevice {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not thrilled about this breaking change, but I couldn't find a way to cleanly deprecate asg.BlockDevice, as it would require the following type for AutoScalingGroupProps:

readonly blockDevices?: (asg.BlockDevice | ec2.BlockDevice)[];

Copy link
Contributor

Choose a reason for hiding this comment

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

Couldn't asg.BlockDevice just be more-or-less a copy of ec2.BlockDevice?

I agree, not ideal, but at least not a breaking change.

/**
* The device name exposed to the EC2 instance
*
* @example '/dev/sdh', 'xvdh'
*
* @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/device_naming.html
*/
readonly deviceName: string;

/**
* Defines the block device volume, to be either an Amazon EBS volume or an ephemeral instance store volume
*
* @example BlockDeviceVolume.ebs(15), BlockDeviceVolume.ephemeral(0)
*
*/
readonly volume: BlockDeviceVolume;

/**
* If false, the device mapping will be suppressed.
* If set to false for the root device, the instance might fail the Amazon EC2 health check.
* Amazon EC2 Auto Scaling launches a replacement instance if the instance fails the health check.
*
* @default true - device mapping is left untouched
*/
readonly mappingEnabled?: boolean;
}

export interface EbsDeviceOptionsBase {
/**
* Indicates whether to delete the volume when the instance is terminated.
*
* @default - true for Amazon EC2 Auto Scaling, false otherwise (e.g. EBS)
*/
readonly deleteOnTermination?: boolean;

/**
* The number of I/O operations per second (IOPS) to provision for the volume.
*
* Must only be set for {@link volumeType}: {@link EbsDeviceVolumeType.IO1}
*
* The maximum ratio of IOPS to volume size (in GiB) is 50:1, so for 5,000 provisioned IOPS,
* you need at least 100 GiB storage on the volume.
*
* @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html
*/
readonly iops?: number;

/**
* The EBS volume type
* @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html
*
* @default {@link EbsDeviceVolumeType.GP2}
*/
readonly volumeType?: EbsDeviceVolumeType;
}

export interface EbsDeviceOptions extends EbsDeviceOptionsBase {
/**
* Specifies whether the EBS volume is encrypted.
* Encrypted EBS volumes can only be attached to instances that support Amazon EBS encryption
*
* @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html#EBSEncryption_supported_instances
*
* @default false
*/
readonly encrypted?: boolean;
}

export interface EbsDeviceSnapshotOptions extends EbsDeviceOptionsBase {
/**
* The volume size, in Gibibytes (GiB)
*
* If you specify volumeSize, it must be equal or greater than the size of the snapshot.
*
* @default - The snapshot size
*/
readonly volumeSize?: number;
}

export interface EbsDeviceProps extends EbsDeviceSnapshotOptions {
readonly snapshotId?: string;
}

export class BlockDeviceVolume extends ec2.BlockDeviceVolume {}
export interface BlockDevice extends ec2.BlockDevice {}
export interface EbsDeviceOptions extends ec2.EbsDeviceOptions {}
export interface EbsDeviceOptionsBase extends ec2.EbsDeviceOptionsBase {}
export interface EbsDeviceProps extends ec2.EbsDeviceProps {}
export interface EbsDeviceSnapshotOptions extends ec2.EbsDeviceSnapshotOptions {}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's an issue with the interfaces. JSII complains if I attempt to export them:

@aws-cdk/aws-autoscaling: error: [awslint:no-unused-type:@aws-cdk/aws-autoscaling.EbsDeviceOptions] type or enum is not used by exported classes (declared at lib/auto-scaling-group.ts:926)
@aws-cdk/aws-autoscaling: error: [awslint:no-unused-type:@aws-cdk/aws-autoscaling.EbsDeviceOptionsBase] type or enum is not used by exported classes (declared at lib/auto-scaling-group.ts:927)
@aws-cdk/aws-autoscaling: error: [awslint:no-unused-type:@aws-cdk/aws-autoscaling.EbsDeviceProps] type or enum is not used by exported classes (declared at lib/auto-scaling-group.ts:928)
@aws-cdk/aws-autoscaling: error: [awslint:no-unused-type:@aws-cdk/aws-autoscaling.EbsDeviceSnapshotOptions] type or enum is not used by exported classes (declared at lib/auto-scaling-group.ts:929)
@aws-cdk/aws-autoscaling: error: [awslint:no-unused-type:@aws-cdk/aws-autoscaling.EbsDeviceVolumeType] type or enum is not used by exported classes (declared at lib/auto-scaling-group.ts:933)

Copy-pasta time?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes. asg.BlockDevice doesn't use asg.EbsDeviceOptions, it uses ec2.EbsDeviceOptions, and so on.

I agree it's not great, but it's practical, and importantly: non-breaking.

/**
* Describes a block device mapping for an Auto Scaling group.
*/
export class BlockDeviceVolume {
/**
* Creates a new Elastic Block Storage device
*
* @param volumeSize The volume size, in Gibibytes (GiB)
* @param options additional device options
*/
public static ebs(volumeSize: number, options: EbsDeviceOptions = {}): BlockDeviceVolume {
return new this({...options, volumeSize});
}

/**
* Creates a new Elastic Block Storage device from an existing snapshot
*
* @param snapshotId The snapshot ID of the volume to use
* @param options additional device options
*/
public static ebsFromSnapshot(snapshotId: string, options: EbsDeviceSnapshotOptions = {}): BlockDeviceVolume {
return new this({...options, snapshotId});
}

/**
* Creates a virtual, ephemeral device.
* The name will be in the form ephemeral{volumeIndex}.
*
* @param volumeIndex the volume index. Must be equal or greater than 0
*/
public static ephemeral(volumeIndex: number) {
if (volumeIndex < 0) {
throw new Error(`volumeIndex must be a number starting from 0, got "${volumeIndex}"`);
}

return new this(undefined, `ephemeral${volumeIndex}`);
}

protected constructor(public readonly ebsDevice?: EbsDeviceProps, public readonly virtualName?: string) {
}
}

/**
* Supported EBS volume types for {@link AutoScalingGroupProps.blockDevices}
* Supported EBS volume types for blockDevices
*/
export enum EbsDeviceVolumeType {
/**
* Magnetic
*/
STANDARD = 'standard',

STANDARD = "standard",
/**
* Provisioned IOPS SSD
*/
IO1 = 'io1',

IO1 = "io1",
/**
* General Purpose SSD
*/
GP2 = 'gp2',

GP2 = "gp2",
/**
* Throughput Optimized HDD
*/
ST1 = 'st1',

ST1 = "st1",
/**
* Cold HDD
*/
SC1 = 'sc1',
SC1 = "sc1"
}
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,8 @@ export = {
},
{
DeviceName: "ephemeral",
VirtualName: "ephemeral0"
VirtualName: "ephemeral0",
NoDevice: false
}
]
}));
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-ec2/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './network-acl-types';
export * from './port';
export * from './security-group';
export * from './peer';
export * from './volume';
export * from './vpc';
export * from './vpc-lookup';
export * from './vpn';
Expand Down
16 changes: 16 additions & 0 deletions packages/@aws-cdk/aws-ec2/lib/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { InstanceType } from './instance-types';
import { IMachineImage, OperatingSystemType } from './machine-image';
import { ISecurityGroup, SecurityGroup } from './security-group';
import { UserData } from './user-data';
import { BlockDevice, synthesizeBlockDeviceMappings } from './volume';
import { IVpc, SubnetSelection } from './vpc';

/**
Expand Down Expand Up @@ -168,6 +169,20 @@ export interface InstanceProps {
* @default true
*/
readonly sourceDestCheck?: boolean;

/**
* Specifies how block devices are exposed to the instance. You can specify virtual devices and EBS volumes.
*
* Each instance that is launched has an associated root device volume,
* either an Amazon EBS volume or an instance store volume.
* You can use block device mappings to specify additional EBS volumes or
* instance store volumes to attach to an instance when it is launched.
*
* @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html
*
* @default - Uses the block device mapping of the AMI
*/
readonly blockDevices?: BlockDevice[];
}

/**
Expand Down Expand Up @@ -285,6 +300,7 @@ export class Instance extends Resource implements IInstance {
subnetId: subnet.subnetId,
availabilityZone: subnet.availabilityZone,
sourceDestCheck: props.sourceDestCheck,
blockDeviceMappings: props.blockDevices !== undefined ? synthesizeBlockDeviceMappings(this, props.blockDevices) : undefined,
});
this.instance.node.addDependency(this.role);

Expand Down
Loading