Merge pull request #10199 from aleksandrserbin/feature/aws-ebs-io-validation

amazon-ebs: validate IOPS ratio
This commit is contained in:
Megan Marsh 2020-11-20 15:23:59 -08:00 committed by GitHub
commit 392cdc5fe4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 127 additions and 0 deletions

View File

@ -13,6 +13,11 @@ import (
"github.com/hashicorp/packer/packer-plugin-sdk/template/interpolate" "github.com/hashicorp/packer/packer-plugin-sdk/template/interpolate"
) )
const (
minIops = 100
maxIops = 64000
)
// These will be attached when launching your instance. Your // These will be attached when launching your instance. Your
// options here may vary depending on the type of VM you use. // options here may vary depending on the type of VM you use.
// //
@ -154,6 +159,11 @@ func (blockDevice BlockDevice) BuildEC2BlockDeviceMapping() *ec2.BlockDeviceMapp
return mapping return mapping
} }
var iopsRatios = map[string]int64{
"io1": 50,
"io2": 500,
}
func (b *BlockDevice) Prepare(ctx *interpolate.Context) error { func (b *BlockDevice) Prepare(ctx *interpolate.Context) error {
if b.DeviceName == "" { if b.DeviceName == "" {
return fmt.Errorf("The `device_name` must be specified " + return fmt.Errorf("The `device_name` must be specified " +
@ -166,6 +176,18 @@ func (b *BlockDevice) Prepare(ctx *interpolate.Context) error {
"true` when setting a kms_key_id.", b.DeviceName) "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("%s: the maximum ratio of provisioned IOPS to requested volume size "+
"(in GiB) is %v:1 for %s volumes", b.DeviceName, ratio, b.VolumeType)
}
if b.IOPS < minIops || b.IOPS > maxIops {
return fmt.Errorf("IOPS must be between %d and %d for device %s",
minIops, maxIops, b.DeviceName)
}
}
_, err := interpolate.RenderInterface(&b, ctx) _, err := interpolate.RenderInterface(&b, ctx)
return err return err
} }

View File

@ -7,6 +7,7 @@ import (
"github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/hashicorp/packer/packer-plugin-sdk/template/config" "github.com/hashicorp/packer/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer/packer-plugin-sdk/template/interpolate"
) )
func TestBlockDevice(t *testing.T) { func TestBlockDevice(t *testing.T) {
@ -182,3 +183,107 @@ 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: "/dev/sdb: 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: "/dev/sdb: the maximum ratio of provisioned IOPS to requested volume size (in GiB) is 500:1 for io2 volumes",
},
// exceed max iops
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 500,
IOPS: 99999,
},
ok: false,
msg: "IOPS must be between 100 and 64000 for device /dev/sdb",
},
// lower than min iops
{
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 50,
IOPS: 10,
},
ok: false,
msg: "IOPS must be between 100 and 64000 for device /dev/sdb",
},
}
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)
}
}
}
}