Merge pull request #10199 from aleksandrserbin/feature/aws-ebs-io-validation
amazon-ebs: validate IOPS ratio
This commit is contained in:
commit
392cdc5fe4
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue