feat: honor gp3 Iops on CreateVolume + clear UnknownVolumeType error (#451)

CreateVolume hardcoded iops to 3000, silently dropping a caller-supplied Iops,
and rejected non-gp3 VolumeType with the generic InvalidParameterValue. Honor
input.Iops within the gp3 envelope (3000 baseline, 500 IOPS/GiB, 16000 ceiling)
and return the volume-type-specific UnknownVolumeType for unsupported types.
Closes EBS CSI API gaps #3-4 (siv-380); gp3 Throughput (gap #2) deferred.
This commit is contained in:
Josh Siviour
2026-06-30 15:09:04 +10:00
committed by Julian Sommer
parent c9629d3bf6
commit 9934be33c3
4 changed files with 61 additions and 7 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ func ValidateCreateVolumeInput(input *ec2.CreateVolumeInput) error {
}
if input.VolumeType != nil && *input.VolumeType != "" && *input.VolumeType != "gp3" {
return errors.New(awserrors.ErrorInvalidParameterValue)
return errors.New(awserrors.ErrorUnknownVolumeType)
}
return nil
@@ -105,7 +105,7 @@ func TestValidateCreateVolumeInput(t *testing.T) {
VolumeType: aws.String("io1"),
},
wantErr: true,
errMsg: awserrors.ErrorInvalidParameterValue,
errMsg: awserrors.ErrorUnknownVolumeType,
},
{
name: "InvalidVolumeType_GP2",
@@ -115,7 +115,7 @@ func TestValidateCreateVolumeInput(t *testing.T) {
VolumeType: aws.String("gp2"),
},
wantErr: true,
errMsg: awserrors.ErrorInvalidParameterValue,
errMsg: awserrors.ErrorUnknownVolumeType,
},
{
name: "ValidInput_EmptyVolumeType",
+18 -2
View File
@@ -28,7 +28,13 @@ import (
"github.com/nats-io/nats.go"
)
const defaultGP3IOPS = 3000
const (
// gp3 IOPS envelope (AWS): 3000 baseline on any size, up to 500 IOPS/GiB,
// capped at 16000.
defaultGP3IOPS = 3000
maxGP3IOPS = 16000
gp3IOPSPerGiB = 500
)
// Ensure VolumeServiceImpl implements VolumeService
var _ VolumeService = (*VolumeServiceImpl)(nil)
@@ -92,7 +98,7 @@ func (s *VolumeServiceImpl) CreateVolume(input *ec2.CreateVolumeInput, accountID
// Validate volume type: only gp3 supported (or empty defaults to gp3)
if input.VolumeType != nil && *input.VolumeType != "" && *input.VolumeType != "gp3" {
return nil, errors.New(awserrors.ErrorInvalidParameterValue)
return nil, errors.New(awserrors.ErrorUnknownVolumeType)
}
volumeType := "gp3"
@@ -141,7 +147,17 @@ func (s *VolumeServiceImpl) CreateVolume(input *ec2.CreateVolumeInput, accountID
now := time.Now()
volumeID := utils.GenerateResourceID("vol")
// Honor caller-supplied Iops for gp3, else the 3000 baseline. The ceiling is
// min(16000, 500*size) but never below the free baseline, so small volumes
// still get 3000.
iops := defaultGP3IOPS
if input.Iops != nil {
iops = int(*input.Iops)
}
maxIOPS := min(max(int(size)*gp3IOPSPerGiB, defaultGP3IOPS), maxGP3IOPS)
if iops < defaultGP3IOPS || iops > maxIOPS {
return nil, errors.New(awserrors.ErrorInvalidParameterValue)
}
slog.Info("CreateVolume", "volumeId", volumeID, "size", size, "type", volumeType,
"az", *input.AvailabilityZone, "snapshotId", snapshotID)
@@ -92,7 +92,7 @@ func TestCreateVolume_Validation(t *testing.T) {
AvailabilityZone: aws.String("ap-southeast-2a"),
VolumeType: aws.String("io1"),
},
wantErr: awserrors.ErrorInvalidParameterValue,
wantErr: awserrors.ErrorUnknownVolumeType,
},
{
name: "UnsupportedVolumeType_GP2",
@@ -102,7 +102,7 @@ func TestCreateVolume_Validation(t *testing.T) {
AvailabilityZone: aws.String("ap-southeast-2a"),
VolumeType: aws.String("gp2"),
},
wantErr: awserrors.ErrorInvalidParameterValue,
wantErr: awserrors.ErrorUnknownVolumeType,
},
{
name: "UnsupportedVolumeType_ST1",
@@ -112,6 +112,36 @@ func TestCreateVolume_Validation(t *testing.T) {
AvailabilityZone: aws.String("ap-southeast-2a"),
VolumeType: aws.String("st1"),
},
wantErr: awserrors.ErrorUnknownVolumeType,
},
{
name: "Iops_BelowBaseline",
az: "ap-southeast-2a",
input: &ec2.CreateVolumeInput{
Size: aws.Int64(80),
AvailabilityZone: aws.String("ap-southeast-2a"),
Iops: aws.Int64(2999),
},
wantErr: awserrors.ErrorInvalidParameterValue,
},
{
name: "Iops_AboveCeiling",
az: "ap-southeast-2a",
input: &ec2.CreateVolumeInput{
Size: aws.Int64(80),
AvailabilityZone: aws.String("ap-southeast-2a"),
Iops: aws.Int64(16001),
},
wantErr: awserrors.ErrorInvalidParameterValue,
},
{
name: "Iops_AboveRatioForSmallVolume",
az: "ap-southeast-2a",
input: &ec2.CreateVolumeInput{
Size: aws.Int64(10),
AvailabilityZone: aws.String("ap-southeast-2a"),
Iops: aws.Int64(6000),
},
wantErr: awserrors.ErrorInvalidParameterValue,
},
{
@@ -180,6 +210,14 @@ func TestCreateVolume_PassesValidation(t *testing.T) {
AvailabilityZone: aws.String("ap-southeast-2a"),
},
},
{
name: "ExplicitIopsInRange",
input: &ec2.CreateVolumeInput{
Size: aws.Int64(80),
AvailabilityZone: aws.String("ap-southeast-2a"),
Iops: aws.Int64(8000),
},
},
}
for _, tt := range tests {