From 07ce820c7094f6ea522cbac55a85a3a2af784068 Mon Sep 17 00:00:00 2001 From: Sargun Dhillon Date: Tue, 4 Sep 2018 16:56:17 -0700 Subject: [PATCH 1/3] Enable setting the volume type when building with the Amazon Chroot builder --- builder/amazon/chroot/builder.go | 2 + builder/amazon/chroot/step_create_volume.go | 50 +++++++++---- .../amazon/chroot/step_create_volume_test.go | 73 +++++++++++++++++++ 3 files changed, 109 insertions(+), 16 deletions(-) create mode 100644 builder/amazon/chroot/step_create_volume_test.go diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go index c0453ba6b..fb385dc33 100644 --- a/builder/amazon/chroot/builder.go +++ b/builder/amazon/chroot/builder.go @@ -42,6 +42,7 @@ type Config struct { PreMountCommands []string `mapstructure:"pre_mount_commands"` RootDeviceName string `mapstructure:"root_device_name"` RootVolumeSize int64 `mapstructure:"root_volume_size"` + RootVolumeType string `mapstructure:"root_volume_type"` SourceAmi string `mapstructure:"source_ami"` SourceAmiFilter awscommon.AmiFilterOptions `mapstructure:"source_ami_filter"` RootVolumeTags awscommon.TagMap `mapstructure:"root_volume_tags"` @@ -233,6 +234,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &StepFlock{}, &StepPrepareDevice{}, &StepCreateVolume{ + RootVolumeType: b.config.RootVolumeType, RootVolumeSize: b.config.RootVolumeSize, RootVolumeTags: b.config.RootVolumeTags, Ctx: b.config.ctx, diff --git a/builder/amazon/chroot/step_create_volume.go b/builder/amazon/chroot/step_create_volume.go index 4adf3074f..34468ff7e 100644 --- a/builder/amazon/chroot/step_create_volume.go +++ b/builder/amazon/chroot/step_create_volume.go @@ -21,6 +21,7 @@ import ( type StepCreateVolume struct { volumeId string RootVolumeSize int64 + RootVolumeType string RootVolumeTags awscommon.TagMap Ctx interpolate.Context } @@ -70,26 +71,13 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu } } - if rootDevice == nil { - err := fmt.Errorf("Couldn't find root device!") + ui.Say("Creating the root volume...") + createVolume, err = s.buildCreateVolumeInput(*instance.Placement.AvailabilityZone, rootDevice) + if err != nil { state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - - ui.Say("Creating the root volume...") - vs := *rootDevice.Ebs.VolumeSize - if s.RootVolumeSize > *rootDevice.Ebs.VolumeSize { - vs = s.RootVolumeSize - } - - createVolume = &ec2.CreateVolumeInput{ - AvailabilityZone: instance.Placement.AvailabilityZone, - Size: aws.Int64(vs), - SnapshotId: rootDevice.Ebs.SnapshotId, - VolumeType: rootDevice.Ebs.VolumeType, - Iops: rootDevice.Ebs.Iops, - } } if len(tagSpecs) > 0 { @@ -137,3 +125,33 @@ func (s *StepCreateVolume) Cleanup(state multistep.StateBag) { ui.Error(fmt.Sprintf("Error deleting EBS volume: %s", err)) } } + +func (s *StepCreateVolume) buildCreateVolumeInput(az string, rootDevice *ec2.BlockDeviceMapping) (*ec2.CreateVolumeInput, error) { + if rootDevice == nil { + return nil, fmt.Errorf("Couldn't find root device!") + } + createVolumeInput := &ec2.CreateVolumeInput{ + AvailabilityZone: aws.String(az), + Size: rootDevice.Ebs.VolumeSize, + SnapshotId: rootDevice.Ebs.SnapshotId, + VolumeType: rootDevice.Ebs.VolumeType, + Iops: rootDevice.Ebs.Iops, + } + if s.RootVolumeSize > *rootDevice.Ebs.VolumeSize { + createVolumeInput.Size = aws.Int64(s.RootVolumeSize) + } + + if s.RootVolumeType == "" || s.RootVolumeType == *rootDevice.Ebs.VolumeType { + return createVolumeInput, nil + } + + if s.RootVolumeType == "io1" { + return nil, fmt.Errorf("Root volume type cannot be io1, because existing root volume type was %s", *rootDevice.Ebs.VolumeType) + } + + createVolumeInput.VolumeType = aws.String(s.RootVolumeType) + // non io1 cannot set iops + createVolumeInput.Iops = nil + + return createVolumeInput, nil +} diff --git a/builder/amazon/chroot/step_create_volume_test.go b/builder/amazon/chroot/step_create_volume_test.go new file mode 100644 index 000000000..03a34bfd5 --- /dev/null +++ b/builder/amazon/chroot/step_create_volume_test.go @@ -0,0 +1,73 @@ +package chroot + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/stretchr/testify/assert" + "testing" +) + +func buildTestRootDevice() *ec2.BlockDeviceMapping { + return &ec2.BlockDeviceMapping{ + Ebs: &ec2.EbsBlockDevice{ + VolumeSize: aws.Int64(10), + SnapshotId: aws.String("snap-1234"), + VolumeType: aws.String("gp2"), + }, + } +} + +func TestCreateVolume_Default(t *testing.T) { + stepCreateVolume := new(StepCreateVolume) + _, err := stepCreateVolume.buildCreateVolumeInput("test-az", buildTestRootDevice()) + assert.NoError(t, err) +} + +func TestCreateVolume_Shrink(t *testing.T) { + stepCreateVolume := StepCreateVolume{RootVolumeSize: 1} + testRootDevice := buildTestRootDevice() + ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice) + assert.NoError(t, err) + // Ensure that the new value is equal to the size of the old root device + assert.Equal(t, *ret.Size, *testRootDevice.Ebs.VolumeSize) +} + +func TestCreateVolume_Expand(t *testing.T) { + stepCreateVolume := StepCreateVolume{RootVolumeSize: 25} + testRootDevice := buildTestRootDevice() + ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice) + assert.NoError(t, err) + // Ensure that the new value is equal to the size of the value passed in + assert.Equal(t, *ret.Size, stepCreateVolume.RootVolumeSize) +} + +func TestCreateVolume_io1_to_io1(t *testing.T) { + stepCreateVolume := StepCreateVolume{RootVolumeType: "io1"} + testRootDevice := buildTestRootDevice() + testRootDevice.Ebs.VolumeType = aws.String("io1") + testRootDevice.Ebs.Iops = aws.Int64(1000) + ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice) + assert.NoError(t, err) + assert.Equal(t, *ret.VolumeType, stepCreateVolume.RootVolumeType) + assert.Equal(t, *ret.Iops, *testRootDevice.Ebs.Iops) +} + +func TestCreateVolume_io1_to_gp2(t *testing.T) { + stepCreateVolume := StepCreateVolume{RootVolumeType: "gp2"} + testRootDevice := buildTestRootDevice() + testRootDevice.Ebs.VolumeType = aws.String("io1") + testRootDevice.Ebs.Iops = aws.Int64(1000) + + ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice) + assert.NoError(t, err) + assert.Equal(t, *ret.VolumeType, stepCreateVolume.RootVolumeType) + assert.Nil(t, ret.Iops) +} + +func TestCreateVolume_gp2_to_io1(t *testing.T) { + stepCreateVolume := StepCreateVolume{RootVolumeType: "io1"} + testRootDevice := buildTestRootDevice() + + _, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice) + assert.Error(t, err) +} From 63d784023f4f1de530b5bbd60be51c0b32a3ddde Mon Sep 17 00:00:00 2001 From: Sargun Dhillon Date: Thu, 6 Sep 2018 13:20:26 -0700 Subject: [PATCH 2/3] Add support to support non-gp2 volume types for building from_scratch --- builder/amazon/chroot/step_create_volume.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/builder/amazon/chroot/step_create_volume.go b/builder/amazon/chroot/step_create_volume.go index 34468ff7e..72f1c270f 100644 --- a/builder/amazon/chroot/step_create_volume.go +++ b/builder/amazon/chroot/step_create_volume.go @@ -2,6 +2,7 @@ package chroot import ( "context" + "errors" "fmt" "log" @@ -54,11 +55,21 @@ func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) mu var createVolume *ec2.CreateVolumeInput if config.FromScratch { + rootVolumeType := ec2.VolumeTypeGp2 + if s.RootVolumeType == "io1" { + err := errors.New("Cannot use io1 volume when building from scratch") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } else if s.RootVolumeType != "" { + rootVolumeType = s.RootVolumeType + } createVolume = &ec2.CreateVolumeInput{ AvailabilityZone: instance.Placement.AvailabilityZone, Size: aws.Int64(s.RootVolumeSize), - VolumeType: aws.String(ec2.VolumeTypeGp2), + VolumeType: aws.String(rootVolumeType), } + } else { // Determine the root device snapshot image := state.Get("source_image").(*ec2.Image) From 3ef15260a94639e087578311a32e1d78370b1b44 Mon Sep 17 00:00:00 2001 From: Sargun Dhillon Date: Tue, 4 Sep 2018 17:01:54 -0700 Subject: [PATCH 3/3] Add documentation for root_volume_type --- website/source/docs/builders/amazon-chroot.html.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/website/source/docs/builders/amazon-chroot.html.md b/website/source/docs/builders/amazon-chroot.html.md index 82d88a053..42acdcfda 100644 --- a/website/source/docs/builders/amazon-chroot.html.md +++ b/website/source/docs/builders/amazon-chroot.html.md @@ -254,6 +254,11 @@ each category, the available configuration keys are alphabetized. of the `source_ami` unless `from_scratch` is `true`, in which case this field must be defined. +- `root_volume_type` (string) - The type of EBS volume for the chroot environment + and resulting AMI. The default value is the type of the `source_ami`, unless + `from_scratch` is true, in which case the default value is `gp2`. You can only + specify `io1` if building based on top of a `source_ami` which is also `io1`. + - `root_volume_tags` (object of key/value strings) - Tags to apply to the volumes that are *launched*. This is a [template engine](/docs/templates/engine.html),