diff --git a/builder/amazon/common/block_device.go b/builder/amazon/common/block_device.go index 6dc35d719..8b7fe5246 100644 --- a/builder/amazon/common/block_device.go +++ b/builder/amazon/common/block_device.go @@ -154,6 +154,11 @@ func (blockDevice BlockDevice) BuildEC2BlockDeviceMapping() *ec2.BlockDeviceMapp return mapping } +var iopsRatios = map[string]int64{ + "io1": 50, + "io2": 500, +} + func (b *BlockDevice) Prepare(ctx *interpolate.Context) error { if b.DeviceName == "" { return fmt.Errorf("The `device_name` must be specified " + @@ -166,6 +171,13 @@ func (b *BlockDevice) Prepare(ctx *interpolate.Context) error { "true` when setting a kms_key_id.", b.DeviceName) } + if ratio, ok := iopsRatios[b.VolumeType]; b.VolumeSize != 0 && ok { + if b.IOPS/b.VolumeSize > ratio { + return fmt.Errorf("The maximum ratio of provisioned IOPS to requested volume size "+ + "(in GiB) is %v:1 for %s volumes", ratio, b.VolumeType) + } + } + _, err := interpolate.RenderInterface(&b, ctx) return err } diff --git a/builder/amazon/common/block_device_test.go b/builder/amazon/common/block_device_test.go index b434ca02b..d6f9e665a 100644 --- a/builder/amazon/common/block_device_test.go +++ b/builder/amazon/common/block_device_test.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/google/go-cmp/cmp" "github.com/hashicorp/packer/helper/config" + "github.com/hashicorp/packer/template/interpolate" ) func TestBlockDevice(t *testing.T) { @@ -182,3 +183,85 @@ func TestBlockDevice(t *testing.T) { } } } + +func TestIOPSValidation(t *testing.T) { + + cases := []struct { + device BlockDevice + ok bool + msg string + }{ + // volume size unknown + { + device: BlockDevice{ + DeviceName: "/dev/sdb", + VolumeType: "io1", + IOPS: 1000, + }, + ok: true, + }, + { + device: BlockDevice{ + DeviceName: "/dev/sdb", + VolumeType: "io2", + IOPS: 1000, + }, + ok: true, + }, + // ratio requirement satisfied + { + device: BlockDevice{ + DeviceName: "/dev/sdb", + VolumeType: "io1", + VolumeSize: 50, + IOPS: 1000, + }, + ok: true, + }, + { + device: BlockDevice{ + DeviceName: "/dev/sdb", + VolumeType: "io2", + VolumeSize: 100, + IOPS: 1000, + }, + ok: true, + }, + // ratio requirement not satisfied + { + device: BlockDevice{ + DeviceName: "/dev/sdb", + VolumeType: "io1", + VolumeSize: 10, + IOPS: 2000, + }, + ok: false, + msg: "The maximum ratio of provisioned IOPS to requested volume size (in GiB) is 50:1 for io1 volumes", + }, + { + device: BlockDevice{ + DeviceName: "/dev/sdb", + VolumeType: "io2", + VolumeSize: 50, + IOPS: 30000, + }, + ok: false, + msg: "The maximum ratio of provisioned IOPS to requested volume size (in GiB) is 500:1 for io2 volumes", + }, + } + + ctx := interpolate.Context{} + for _, testCase := range cases { + err := testCase.device.Prepare(&ctx) + if testCase.ok && err != nil { + t.Fatalf("should not error, but: %v", err) + } + if !testCase.ok { + if err == nil { + t.Fatalf("should error") + } else if err.Error() != testCase.msg { + t.Fatalf("wrong error: expected %s, found: %v", testCase.msg, err) + } + } + } +}