diff --git a/spinifex/awserrors/awserrors.go b/spinifex/awserrors/awserrors.go index 01cfcddbe..33e4339fc 100644 --- a/spinifex/awserrors/awserrors.go +++ b/spinifex/awserrors/awserrors.go @@ -798,7 +798,7 @@ var ErrorLookup = map[string]ErrorMessage{ ErrorInsufficientCapacityOnHost: {HTTPCode: 400, Message: "There is not enough capacity on the Dedicated Host to launch or start the instance."}, ErrorInsufficientFreeAddressesInSubnet: {HTTPCode: 503, Message: "The specified subnet does not contain enough free private IP addresses to fulfill your request. Use the DescribeSubnets request to view how many IP addresses are available (unused) in your subnet. IP addresses associated with stopped instances are considered unavailable."}, ErrorInsufficientHostCapacity: {HTTPCode: 503, Message: "There is not enough capacity to fulfill your Dedicated Host request. Reduce the number of Dedicated Hosts in your request, or wait for additional capacity to become available."}, - ErrorInsufficientInstanceCapacity: {HTTPCode: 400, Message: "There is not enough capacity to fulfill your request. This error can occur if you launch a new instance, restart a stopped instance, create a new Capacity Reservation, or modify an existing Capacity Reservation. Reduce the number of instances in your request, or wait for additional capacity to become available. You can also try launching an instance by selecting different instance types (which you can resize at a later stage). The returned message might also give specific guidance about how to solve the problem."}, + ErrorInsufficientInstanceCapacity: {HTTPCode: 503, Message: "There is not enough capacity to fulfill your request. This error can occur if you launch a new instance, restart a stopped instance, create a new Capacity Reservation, or modify an existing Capacity Reservation. Reduce the number of instances in your request, or wait for additional capacity to become available. You can also try launching an instance by selecting different instance types (which you can resize at a later stage). The returned message might also give specific guidance about how to solve the problem."}, ErrorInsufficientReservedInstanceCapacity: {HTTPCode: 503, Message: "Not enough available Reserved Instances to satisfy your minimum request. Reduce the number of Reserved Instances in your request or wait for additional capacity to become available."}, ErrorInsufficientReservedInstancesCapacity: {HTTPCode: 400, Message: "There is insufficient capacity for the requested Reserved Instances."}, ErrorInsufficientVolumeCapacity: {HTTPCode: 503, Message: "There is not enough capacity to fulfill your EBS volume provision request. You can try to provision a different volume type, EBS volume in a different availability zone, or you can wait for additional capacity to become available."}, diff --git a/spinifex/awserrors/awserrors_test.go b/spinifex/awserrors/awserrors_test.go index 24831cdbc..d3672588b 100644 --- a/spinifex/awserrors/awserrors_test.go +++ b/spinifex/awserrors/awserrors_test.go @@ -34,7 +34,7 @@ func TestErrorLookup_Structure(t *testing.T) { ErrorInvalidGroupNotFound: 404, ErrorInvalidSubnetIDNotFound: 404, ErrorServerInternal: 500, - ErrorInsufficientInstanceCapacity: 400, + ErrorInsufficientInstanceCapacity: 503, ErrorUnauthorizedOperation: 403, } for code, wantHTTP := range critical { diff --git a/spinifex/handlers/eks/nodegroup_test.go b/spinifex/handlers/eks/nodegroup_test.go index 221a3e3a0..f095c67c4 100644 --- a/spinifex/handlers/eks/nodegroup_test.go +++ b/spinifex/handlers/eks/nodegroup_test.go @@ -821,7 +821,7 @@ func TestUpdateNodegroupConfig_ConcurrentScaleUpConverges(t *testing.T) { } // TestUpdateNodegroupConfig_CapacityErrorSurfacesCode proves an out-of-capacity -// scale returns the bare InsufficientInstanceCapacity code (gateway maps to 400), +// scale returns the bare InsufficientInstanceCapacity code (gateway maps to 503), // not a wrapped string the gateway sanitizes to 500. A generic launcher failure // still wraps, staying an opaque 500. func TestUpdateNodegroupConfig_CapacityErrorSurfacesCode(t *testing.T) { diff --git a/tests/integration/assumed_role_test.go b/tests/integration/assumed_role_test.go index 90048689c..6b46a4960 100644 --- a/tests/integration/assumed_role_test.go +++ b/tests/integration/assumed_role_test.go @@ -34,6 +34,16 @@ func requireAWSErrorCode(t *testing.T, err error, code string) { assert.Equal(t, code, awsErr.Code()) } +// requireAWSErrorCodeStatus additionally pins the HTTP status, so a code that +// AWS documents as a client or server error keeps its documented status class. +func requireAWSErrorCodeStatus(t *testing.T, err error, code string, status int) { + t.Helper() + requireAWSErrorCode(t, err, code) + var reqErr awserr.RequestFailure + require.ErrorAs(t, err, &reqErr, "expected awserr.RequestFailure, got %T: %v", err, err) + assert.Equal(t, status, reqErr.StatusCode()) +} + // TestAssumedRoleControlPlaneEnforcement proves the gateway evaluates // assumed-role sessions against managed policies: a zero-policy role is // denied a guarded action, and the same live session is allowed once a diff --git a/tests/integration/conformance-known-findings.md b/tests/integration/conformance-known-findings.md index 08235a8d4..4c2be330d 100644 --- a/tests/integration/conformance-known-findings.md +++ b/tests/integration/conformance-known-findings.md @@ -34,12 +34,13 @@ Twelve responses are reported separately as `errors_unmodelled`: four IAM and ei EC2's model declares no operation errors, so EC2 uses a separate checked-in catalog curated from AWS's official EC2 error-code reference. It contains every documented common and server error plus the documented action-specific codes Spinifex currently emits. The validator checks the EC2 XML envelope, catalog membership, and AWS's documented 4xx client / 5xx server classification. -The same baseline examined 42 EC2 error responses and exposed 16 violations: +The same baseline examined 42 EC2 error responses and exposed 16 violations. The +`InsufficientInstanceCapacity` status-class defect has since been fixed — that +code now returns 503, matching the other capacity errors — leaving 15 open: | Finding | Count | Classification | Implementation cause | |---|---:|---|---| | EC2 authorization failures return `AccessDenied`, which is absent from the EC2 catalog; EC2 documents `UnauthorizedOperation` | 15 | Defect | The shared policy evaluator returns the Query/IAM-oriented `AccessDenied` code for EC2 requests. | -| `RequestSpotInstances` returns `InsufficientInstanceCapacity` with HTTP 400 instead of the documented server-error 5xx class | 1 | Defect | The global error lookup currently assigns HTTP 400 to `InsufficientInstanceCapacity`. | Six additional production-referenced values are deliberately not accepted by the catalog because the chosen EC2 reference does not list them: `ExpiredToken`, `IamInstanceProfileAlreadyAssociated`, `InvalidIamInstanceProfile.NotFound`, `NoSuchAssociation`, `NoSuchEntity`, and `RequestEntityTooLarge`. A runtime occurrence remains a visible violation until it is either backed by an authoritative EC2 source or replaced with a documented EC2 code. diff --git a/tests/integration/spot_instance_test.go b/tests/integration/spot_instance_test.go index 9ec3d3ee1..75ae9d0cb 100644 --- a/tests/integration/spot_instance_test.go +++ b/tests/integration/spot_instance_test.go @@ -6,6 +6,7 @@ import ( "context" "encoding/json" "fmt" + "net/http" "strings" "testing" "time" @@ -228,7 +229,9 @@ func TestRequestSpotInstances_Lifecycle(t *testing.T) { InstanceType: aws.String(instanceType), }, }) - requireAWSErrorCode(t, err, "InsufficientInstanceCapacity") + // AWS documents InsufficientInstanceCapacity as a server error, so the + // status must be 5xx — the EC2 conformance catalog rejects a 4xx here. + requireAWSErrorCodeStatus(t, err, "InsufficientInstanceCapacity", http.StatusServiceUnavailable) if out != nil { assert.Empty(t, out.SpotInstanceRequests, "capacity failure must persist/return no SIRs") }