mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 09:01:31 +00:00
feat: implement EC2 DescribeInstanceTypeOfferings (#1025)
This commit is contained in:
@@ -22,7 +22,7 @@ Spinifex serves the AWS APIs below. Every page counts the operations in the pinn
|
||||
| Service | Implemented | Modelled | Coverage |
|
||||
|---|---:|---:|---:|
|
||||
| [ACM](/coverage/acm) | 9 | 15 | 60.0% |
|
||||
| [EC2](/coverage/ec2) | 121 | 625 | 19.4% |
|
||||
| [EC2](/coverage/ec2) | 122 | 625 | 19.5% |
|
||||
| [ECR](/coverage/ecr) | 19 | 47 | 40.4% |
|
||||
| [ECS](/coverage/ecs) | 31 | 56 | 55.4% |
|
||||
| [EKS](/coverage/eks) | 34 | 56 | 60.7% |
|
||||
|
||||
@@ -18,7 +18,7 @@ tags:
|
||||
|
||||
## Overview
|
||||
|
||||
Spinifex implements **121 of the 625** operations (**19.4%**) in the EC2 `2016-11-15` API model.
|
||||
Spinifex implements **122 of the 625** operations (**19.5%**) in the EC2 `2016-11-15` API model.
|
||||
|
||||
### Spot Instances Are a Mock
|
||||
|
||||
@@ -301,7 +301,7 @@ Spot Instance Requests are a mock over the on-demand `RunInstances` path. A requ
|
||||
| `DescribeInstanceEventWindows` | ❌ Not implemented |
|
||||
| `DescribeInstanceStatus` | ✅ Implemented |
|
||||
| `DescribeInstanceTopology` | ❌ Not implemented |
|
||||
| `DescribeInstanceTypeOfferings` | ❌ Not implemented |
|
||||
| `DescribeInstanceTypeOfferings` | ✅ Implemented |
|
||||
| `DescribeInstanceTypes` | ✅ Implemented |
|
||||
| `DescribeInstances` | ✅ Implemented |
|
||||
| `DescribeInternetGateways` | ✅ Implemented |
|
||||
|
||||
@@ -209,6 +209,9 @@ var ec2Actions = map[string]ec2Action{
|
||||
"DescribeInstanceTypes": ec2Handler(func(ctx context.Context, input *ec2.DescribeInstanceTypesInput, gw *GatewayConfig, accountID string) (any, error) {
|
||||
return gateway_ec2_instance.DescribeInstanceTypes(ctx, input, gw.NATSConn, gw.ExpectedNodes, accountID)
|
||||
}),
|
||||
"DescribeInstanceTypeOfferings": ec2Handler(func(ctx context.Context, input *ec2.DescribeInstanceTypeOfferingsInput, gw *GatewayConfig, accountID string) (any, error) {
|
||||
return gateway_ec2_instance.DescribeInstanceTypeOfferings(ctx, input, gw.NATSConn, gw.ExpectedNodes, accountID, gw.Region, gw.AZ)
|
||||
}),
|
||||
"DescribeInstanceStatus": ec2Handler(func(ctx context.Context, input *ec2.DescribeInstanceStatusInput, gw *GatewayConfig, accountID string) (any, error) {
|
||||
return gateway_ec2_instance.DescribeInstanceStatus(ctx, input, gw.NATSConn, gw.DiscoverActiveNodes(ctx), accountID, gw.AZ, gw.InstanceStatus)
|
||||
}),
|
||||
|
||||
@@ -265,6 +265,7 @@ var ec2Scopes = map[string][]*resourceScope{
|
||||
"DescribeInstanceAttribute": unscoped,
|
||||
"DescribeInstanceCreditSpecifications": unscoped,
|
||||
"DescribeInstanceStatus": unscoped,
|
||||
"DescribeInstanceTypeOfferings": unscoped,
|
||||
"DescribeInstanceTypes": unscoped,
|
||||
"DescribeInstances": unscoped,
|
||||
"DescribeInternetGateways": unscoped,
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package gateway_ec2_instance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mulgadc/spinifex/spinifex/awserrors"
|
||||
"github.com/mulgadc/spinifex/spinifex/filterutil"
|
||||
gateway_ec2_zone "github.com/mulgadc/spinifex/spinifex/gateway/ec2/zone"
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
|
||||
// defaultOfferingsPerPage caps a request that names no MaxResults.
|
||||
const defaultOfferingsPerPage = 1000
|
||||
|
||||
var describeInstanceTypeOfferingsValidFilters = map[string]bool{
|
||||
"instance-type": true,
|
||||
"location": true,
|
||||
}
|
||||
|
||||
// DescribeInstanceTypeOfferings projects the supported instance type catalogue
|
||||
// over the location list. Both inputs are read live on every call, so the answer
|
||||
// cannot advertise a type the platform is unable to place.
|
||||
func DescribeInstanceTypeOfferings(ctx context.Context, input *ec2.DescribeInstanceTypeOfferingsInput, natsConn *nats.Conn, expectedNodes int, accountID, region, az string) (*ec2.DescribeInstanceTypeOfferingsOutput, error) {
|
||||
if input == nil {
|
||||
input = &ec2.DescribeInstanceTypeOfferingsInput{}
|
||||
}
|
||||
|
||||
locationType := aws.StringValue(input.LocationType)
|
||||
if locationType == "" {
|
||||
locationType = ec2.LocationTypeRegion
|
||||
}
|
||||
|
||||
locations, err := offeringLocations(locationType, region, az)
|
||||
if err != nil {
|
||||
slog.WarnContext(ctx, "DescribeInstanceTypeOfferings: invalid location type", "location_type", locationType)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parsedFilters, err := filterutil.ParseFilters(input.Filters, describeInstanceTypeOfferingsValidFilters)
|
||||
if err != nil {
|
||||
slog.WarnContext(ctx, "DescribeInstanceTypeOfferings: invalid filter", "err", err)
|
||||
return nil, errors.New(awserrors.ErrorInvalidParameterValue)
|
||||
}
|
||||
|
||||
// No capacity filter: an offering is what the platform supports, not what
|
||||
// has a free slot at this instant.
|
||||
catalogue, err := DescribeInstanceTypes(ctx, &ec2.DescribeInstanceTypesInput{}, natsConn, expectedNodes, accountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
offerings := make([]*ec2.InstanceTypeOffering, 0, len(catalogue.InstanceTypes)*len(locations))
|
||||
for _, instanceType := range catalogue.InstanceTypes {
|
||||
if instanceType == nil || instanceType.InstanceType == nil {
|
||||
continue
|
||||
}
|
||||
if !filterutil.MatchesAny(parsedFilters["instance-type"], *instanceType.InstanceType) {
|
||||
continue
|
||||
}
|
||||
for _, location := range locations {
|
||||
if !filterutil.MatchesAny(parsedFilters["location"], location) {
|
||||
continue
|
||||
}
|
||||
offerings = append(offerings, &ec2.InstanceTypeOffering{
|
||||
InstanceType: aws.String(*instanceType.InstanceType),
|
||||
Location: aws.String(location),
|
||||
LocationType: aws.String(locationType),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Total order so a paged traversal sees every offering exactly once.
|
||||
sort.Slice(offerings, func(i, j int) bool {
|
||||
if *offerings[i].InstanceType != *offerings[j].InstanceType {
|
||||
return *offerings[i].InstanceType < *offerings[j].InstanceType
|
||||
}
|
||||
return *offerings[i].Location < *offerings[j].Location
|
||||
})
|
||||
|
||||
page, nextToken, err := pageOfferings(offerings, input.NextToken, input.MaxResults)
|
||||
if err != nil {
|
||||
slog.WarnContext(ctx, "DescribeInstanceTypeOfferings: invalid NextToken", "next_token", aws.StringValue(input.NextToken))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slog.InfoContext(ctx, "DescribeInstanceTypeOfferings: derived offerings", "location_type", locationType, "total", len(offerings), "returned", len(page))
|
||||
return &ec2.DescribeInstanceTypeOfferingsOutput{InstanceTypeOfferings: page, NextToken: nextToken}, nil
|
||||
}
|
||||
|
||||
// offeringLocations resolves the locations an offering can be reported at. The
|
||||
// zone list is read rather than assumed, so the operation follows the zone model
|
||||
// if it ever widens.
|
||||
func offeringLocations(locationType, region, az string) ([]string, error) {
|
||||
switch locationType {
|
||||
case ec2.LocationTypeRegion:
|
||||
return []string{region}, nil
|
||||
case ec2.LocationTypeAvailabilityZone, ec2.LocationTypeAvailabilityZoneId:
|
||||
default:
|
||||
return nil, errors.New(awserrors.ErrorInvalidParameterValue)
|
||||
}
|
||||
|
||||
zones, err := gateway_ec2_zone.DescribeAvailabilityZones(&ec2.DescribeAvailabilityZonesInput{}, region, az)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
locations := make([]string, 0, len(zones.AvailabilityZones))
|
||||
for _, zone := range zones.AvailabilityZones {
|
||||
if locationType == ec2.LocationTypeAvailabilityZoneId {
|
||||
locations = append(locations, aws.StringValue(zone.ZoneId))
|
||||
continue
|
||||
}
|
||||
locations = append(locations, aws.StringValue(zone.ZoneName))
|
||||
}
|
||||
return locations, nil
|
||||
}
|
||||
|
||||
// pageOfferings slices one page out of a sorted offering set, using an opaque
|
||||
// integer offset as the token. The last page carries a nil token.
|
||||
func pageOfferings(offerings []*ec2.InstanceTypeOffering, token *string, maxResults *int64) ([]*ec2.InstanceTypeOffering, *string, error) {
|
||||
start := 0
|
||||
if aws.StringValue(token) != "" {
|
||||
n, err := strconv.Atoi(*token)
|
||||
if err != nil || n < 0 {
|
||||
return nil, nil, errors.New(awserrors.ErrorInvalidParameterValue)
|
||||
}
|
||||
start = n
|
||||
}
|
||||
if start > len(offerings) {
|
||||
start = len(offerings)
|
||||
}
|
||||
|
||||
size := defaultOfferingsPerPage
|
||||
if maxResults != nil && *maxResults > 0 {
|
||||
size = int(*maxResults)
|
||||
}
|
||||
|
||||
end := start + size
|
||||
if end >= len(offerings) {
|
||||
return offerings[start:], nil, nil
|
||||
}
|
||||
return offerings[start:end], aws.String(strconv.Itoa(end)), nil
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
package gateway_ec2_instance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mulgadc/spinifex/spinifex/awserrors"
|
||||
gateway_ec2_zone "github.com/mulgadc/spinifex/spinifex/gateway/ec2/zone"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
offeringsTestRegion = "ap-southeast-2"
|
||||
offeringsTestAZ = "ap-southeast-2a"
|
||||
)
|
||||
|
||||
// serveInstanceTypes stands in for the node catalogue behind DescribeInstanceTypes.
|
||||
func serveInstanceTypes(t *testing.T, nc *nats.Conn, instanceTypes ...string) {
|
||||
t.Helper()
|
||||
|
||||
infos := make([]*ec2.InstanceTypeInfo, 0, len(instanceTypes))
|
||||
for _, it := range instanceTypes {
|
||||
infos = append(infos, &ec2.InstanceTypeInfo{InstanceType: aws.String(it)})
|
||||
}
|
||||
|
||||
_, err := nc.Subscribe("ec2.DescribeInstanceTypes", func(msg *nats.Msg) {
|
||||
data, _ := json.Marshal(&ec2.DescribeInstanceTypesOutput{InstanceTypes: infos})
|
||||
msg.Respond(data)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, nc.Flush())
|
||||
}
|
||||
|
||||
// TestDescribeInstanceTypeOfferings_AvailabilityZone asserts the result against
|
||||
// DescribeInstanceTypes and DescribeAvailabilityZones rather than a fixture, so
|
||||
// the test fails if the answer ever stops being derived from them.
|
||||
func TestDescribeInstanceTypeOfferings_AvailabilityZone(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, nc := startTestNATSServer(t)
|
||||
serveInstanceTypes(t, nc, "t3.micro", "m5.large")
|
||||
|
||||
types, err := DescribeInstanceTypes(context.Background(), &ec2.DescribeInstanceTypesInput{}, nc, 1, "")
|
||||
require.NoError(t, err)
|
||||
zones, err := gateway_ec2_zone.DescribeAvailabilityZones(&ec2.DescribeAvailabilityZonesInput{}, offeringsTestRegion, offeringsTestAZ)
|
||||
require.NoError(t, err)
|
||||
|
||||
output, err := DescribeInstanceTypeOfferings(context.Background(), &ec2.DescribeInstanceTypeOfferingsInput{
|
||||
LocationType: aws.String(ec2.LocationTypeAvailabilityZone),
|
||||
}, nc, 1, "", offeringsTestRegion, offeringsTestAZ)
|
||||
require.NoError(t, err)
|
||||
|
||||
var want []*ec2.InstanceTypeOffering
|
||||
for _, it := range types.InstanceTypes {
|
||||
for _, zone := range zones.AvailabilityZones {
|
||||
want = append(want, &ec2.InstanceTypeOffering{
|
||||
InstanceType: it.InstanceType,
|
||||
Location: zone.ZoneName,
|
||||
LocationType: aws.String(ec2.LocationTypeAvailabilityZone),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, want, output.InstanceTypeOfferings)
|
||||
assert.Nil(t, output.NextToken)
|
||||
}
|
||||
|
||||
func TestDescribeInstanceTypeOfferings_Region(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, nc := startTestNATSServer(t)
|
||||
serveInstanceTypes(t, nc, "t3.micro", "m5.large")
|
||||
|
||||
output, err := DescribeInstanceTypeOfferings(context.Background(), &ec2.DescribeInstanceTypeOfferingsInput{
|
||||
LocationType: aws.String(ec2.LocationTypeRegion),
|
||||
}, nc, 1, "", offeringsTestRegion, offeringsTestAZ)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, output.InstanceTypeOfferings, 2)
|
||||
for _, offering := range output.InstanceTypeOfferings {
|
||||
assert.Equal(t, offeringsTestRegion, aws.StringValue(offering.Location))
|
||||
assert.Equal(t, ec2.LocationTypeRegion, aws.StringValue(offering.LocationType))
|
||||
}
|
||||
}
|
||||
|
||||
// TestDescribeInstanceTypeOfferings_DefaultLocationTypeIsRegion covers the AWS
|
||||
// default: an omitted LocationType reports the region, not a zone.
|
||||
func TestDescribeInstanceTypeOfferings_DefaultLocationTypeIsRegion(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, nc := startTestNATSServer(t)
|
||||
serveInstanceTypes(t, nc, "t3.micro")
|
||||
|
||||
output, err := DescribeInstanceTypeOfferings(context.Background(), &ec2.DescribeInstanceTypeOfferingsInput{}, nc, 1, "", offeringsTestRegion, offeringsTestAZ)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, output.InstanceTypeOfferings, 1)
|
||||
assert.Equal(t, offeringsTestRegion, aws.StringValue(output.InstanceTypeOfferings[0].Location))
|
||||
assert.Equal(t, ec2.LocationTypeRegion, aws.StringValue(output.InstanceTypeOfferings[0].LocationType))
|
||||
}
|
||||
|
||||
func TestDescribeInstanceTypeOfferings_AvailabilityZoneID(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, nc := startTestNATSServer(t)
|
||||
serveInstanceTypes(t, nc, "t3.micro")
|
||||
|
||||
zones, err := gateway_ec2_zone.DescribeAvailabilityZones(&ec2.DescribeAvailabilityZonesInput{}, offeringsTestRegion, offeringsTestAZ)
|
||||
require.NoError(t, err)
|
||||
zoneID := aws.StringValue(zones.AvailabilityZones[0].ZoneId)
|
||||
require.NotEqual(t, offeringsTestAZ, zoneID, "zone ID must differ from zone name for this assertion to mean anything")
|
||||
|
||||
output, err := DescribeInstanceTypeOfferings(context.Background(), &ec2.DescribeInstanceTypeOfferingsInput{
|
||||
LocationType: aws.String(ec2.LocationTypeAvailabilityZoneId),
|
||||
}, nc, 1, "", offeringsTestRegion, offeringsTestAZ)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, output.InstanceTypeOfferings, 1)
|
||||
assert.Equal(t, zoneID, aws.StringValue(output.InstanceTypeOfferings[0].Location))
|
||||
}
|
||||
|
||||
func TestDescribeInstanceTypeOfferings_UnknownLocationType(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, nc := startTestNATSServer(t)
|
||||
serveInstanceTypes(t, nc, "t3.micro")
|
||||
|
||||
_, err := DescribeInstanceTypeOfferings(context.Background(), &ec2.DescribeInstanceTypeOfferingsInput{
|
||||
LocationType: aws.String("outpost"),
|
||||
}, nc, 1, "", offeringsTestRegion, offeringsTestAZ)
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidParameterValue, err.Error())
|
||||
}
|
||||
|
||||
func TestDescribeInstanceTypeOfferings_Filters(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
filters []*ec2.Filter
|
||||
wantTypes []string
|
||||
}{
|
||||
{
|
||||
name: "instance-type narrows",
|
||||
filters: []*ec2.Filter{{Name: aws.String("instance-type"), Values: []*string{aws.String("t3.micro")}}},
|
||||
wantTypes: []string{"t3.micro"},
|
||||
},
|
||||
{
|
||||
name: "instance-type wildcard narrows",
|
||||
filters: []*ec2.Filter{{Name: aws.String("instance-type"), Values: []*string{aws.String("t3.*")}}},
|
||||
wantTypes: []string{"t3.micro", "t3.small"},
|
||||
},
|
||||
{
|
||||
name: "location narrows to the zone",
|
||||
filters: []*ec2.Filter{{Name: aws.String("location"), Values: []*string{aws.String(offeringsTestAZ)}}},
|
||||
wantTypes: []string{"m5.large", "t3.micro", "t3.small"},
|
||||
},
|
||||
{
|
||||
name: "location matching nothing is empty, not an error",
|
||||
filters: []*ec2.Filter{{Name: aws.String("location"), Values: []*string{aws.String("us-east-1a")}}},
|
||||
wantTypes: nil,
|
||||
},
|
||||
{
|
||||
name: "instance-type matching nothing is empty, not an error",
|
||||
filters: []*ec2.Filter{{Name: aws.String("instance-type"), Values: []*string{aws.String("c9.nonexistent")}}},
|
||||
wantTypes: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, nc := startTestNATSServer(t)
|
||||
serveInstanceTypes(t, nc, "t3.micro", "t3.small", "m5.large")
|
||||
|
||||
output, err := DescribeInstanceTypeOfferings(context.Background(), &ec2.DescribeInstanceTypeOfferingsInput{
|
||||
LocationType: aws.String(ec2.LocationTypeAvailabilityZone),
|
||||
Filters: tc.filters,
|
||||
}, nc, 1, "", offeringsTestRegion, offeringsTestAZ)
|
||||
require.NoError(t, err)
|
||||
|
||||
var got []string
|
||||
for _, offering := range output.InstanceTypeOfferings {
|
||||
got = append(got, aws.StringValue(offering.InstanceType))
|
||||
}
|
||||
assert.Equal(t, tc.wantTypes, got)
|
||||
assert.NotNil(t, output.InstanceTypeOfferings, "empty result must render its container, so the slice must not be nil")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribeInstanceTypeOfferings_UnknownFilter(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, nc := startTestNATSServer(t)
|
||||
serveInstanceTypes(t, nc, "t3.micro")
|
||||
|
||||
_, err := DescribeInstanceTypeOfferings(context.Background(), &ec2.DescribeInstanceTypeOfferingsInput{
|
||||
LocationType: aws.String(ec2.LocationTypeAvailabilityZone),
|
||||
Filters: []*ec2.Filter{{Name: aws.String("not-a-filter"), Values: []*string{aws.String("x")}}},
|
||||
}, nc, 1, "", offeringsTestRegion, offeringsTestAZ)
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidParameterValue, err.Error())
|
||||
}
|
||||
|
||||
// TestDescribeInstanceTypeOfferings_FullTraversal walks every page with a small
|
||||
// MaxResults and asserts each offering appears exactly once, so a paging caller
|
||||
// cannot loop or miss a type.
|
||||
func TestDescribeInstanceTypeOfferings_FullTraversal(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, nc := startTestNATSServer(t)
|
||||
catalogue := []string{"c5.large", "m5.large", "m5.xlarge", "t3.micro", "t3.small"}
|
||||
serveInstanceTypes(t, nc, catalogue...)
|
||||
|
||||
seen := make(map[string]int)
|
||||
var token *string
|
||||
pages := 0
|
||||
for {
|
||||
output, err := DescribeInstanceTypeOfferings(context.Background(), &ec2.DescribeInstanceTypeOfferingsInput{
|
||||
LocationType: aws.String(ec2.LocationTypeAvailabilityZone),
|
||||
MaxResults: aws.Int64(2),
|
||||
NextToken: token,
|
||||
}, nc, 1, "", offeringsTestRegion, offeringsTestAZ)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, offering := range output.InstanceTypeOfferings {
|
||||
seen[aws.StringValue(offering.InstanceType)]++
|
||||
}
|
||||
|
||||
pages++
|
||||
require.Less(t, pages, 10, "traversal did not terminate")
|
||||
if output.NextToken == nil {
|
||||
break
|
||||
}
|
||||
assert.Len(t, output.InstanceTypeOfferings, 2, "a non-final page must be full")
|
||||
token = output.NextToken
|
||||
}
|
||||
|
||||
assert.Equal(t, 3, pages)
|
||||
require.Len(t, seen, len(catalogue))
|
||||
for _, instanceType := range catalogue {
|
||||
assert.Equal(t, 1, seen[instanceType], "offering for %s appeared %d times", instanceType, seen[instanceType])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribeInstanceTypeOfferings_MalformedNextToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, nc := startTestNATSServer(t)
|
||||
serveInstanceTypes(t, nc, "t3.micro")
|
||||
|
||||
_, err := DescribeInstanceTypeOfferings(context.Background(), &ec2.DescribeInstanceTypeOfferingsInput{
|
||||
LocationType: aws.String(ec2.LocationTypeAvailabilityZone),
|
||||
NextToken: aws.String("not-a-token"),
|
||||
}, nc, 1, "", offeringsTestRegion, offeringsTestAZ)
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidParameterValue, err.Error())
|
||||
}
|
||||
|
||||
// TestDescribeInstanceTypeOfferings_TokenPastEnd guards the offset token against
|
||||
// a caller replaying a stale page after the catalogue shrank.
|
||||
func TestDescribeInstanceTypeOfferings_TokenPastEnd(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, nc := startTestNATSServer(t)
|
||||
serveInstanceTypes(t, nc, "t3.micro")
|
||||
|
||||
output, err := DescribeInstanceTypeOfferings(context.Background(), &ec2.DescribeInstanceTypeOfferingsInput{
|
||||
LocationType: aws.String(ec2.LocationTypeAvailabilityZone),
|
||||
NextToken: aws.String("99"),
|
||||
}, nc, 1, "", offeringsTestRegion, offeringsTestAZ)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, output.InstanceTypeOfferings)
|
||||
assert.Nil(t, output.NextToken)
|
||||
}
|
||||
|
||||
func TestDescribeInstanceTypeOfferings_EmptyCatalogue(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, nc := startTestNATSServer(t)
|
||||
|
||||
output, err := DescribeInstanceTypeOfferings(context.Background(), &ec2.DescribeInstanceTypeOfferingsInput{
|
||||
LocationType: aws.String(ec2.LocationTypeAvailabilityZone),
|
||||
}, nc, 0, "", offeringsTestRegion, offeringsTestAZ)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, output.InstanceTypeOfferings)
|
||||
assert.Empty(t, output.InstanceTypeOfferings)
|
||||
assert.Nil(t, output.NextToken)
|
||||
}
|
||||
|
||||
func TestDescribeInstanceTypeOfferings_CatalogueError(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, nc := startTestNATSServer(t)
|
||||
|
||||
closedNC, err := nats.Connect(nc.ConnectedUrl())
|
||||
require.NoError(t, err)
|
||||
closedNC.Close()
|
||||
|
||||
_, err = DescribeInstanceTypeOfferings(context.Background(), &ec2.DescribeInstanceTypeOfferingsInput{
|
||||
LocationType: aws.String(ec2.LocationTypeAvailabilityZone),
|
||||
}, closedNC, 1, "", offeringsTestRegion, offeringsTestAZ)
|
||||
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestDescribeInstanceTypeOfferings_NilInput(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, nc := startTestNATSServer(t)
|
||||
serveInstanceTypes(t, nc, "t3.micro")
|
||||
|
||||
output, err := DescribeInstanceTypeOfferings(context.Background(), nil, nc, 1, "", offeringsTestRegion, offeringsTestAZ)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, output.InstanceTypeOfferings, 1)
|
||||
assert.Equal(t, offeringsTestRegion, aws.StringValue(output.InstanceTypeOfferings[0].Location))
|
||||
}
|
||||
@@ -60,6 +60,9 @@ func TestEC2Describe_EmptyResult_EmitsContainer(t *testing.T) {
|
||||
{"DescribeImages", "DescribeImages", "imagesSet", func(t *testing.T) string {
|
||||
return render[ec2.DescribeImagesInput](t, "DescribeImages", ec2.DescribeImagesOutput{})
|
||||
}},
|
||||
{"DescribeInstanceTypeOfferings", "DescribeInstanceTypeOfferings", "instanceTypeOfferingSet", func(t *testing.T) string {
|
||||
return render[ec2.DescribeInstanceTypeOfferingsInput](t, "DescribeInstanceTypeOfferings", ec2.DescribeInstanceTypeOfferingsOutput{})
|
||||
}},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
||||
Reference in New Issue
Block a user