pkg/fake/ec2api.go (569 lines of code) (raw):
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fake
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/Pallinder/go-randomdata"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/aws/smithy-go"
"github.com/awslabs/operatorpkg/serrors"
"github.com/samber/lo"
"k8s.io/apimachinery/pkg/util/sets"
karpv1 "sigs.k8s.io/karpenter/pkg/apis/v1"
sdk "github.com/aws/karpenter-provider-aws/pkg/aws"
"sigs.k8s.io/karpenter/pkg/test"
"sigs.k8s.io/karpenter/pkg/utils/atomic"
)
type CapacityPool struct {
CapacityType string
InstanceType string
Zone string
ReservationID string
}
// EC2Behavior must be reset between tests otherwise tests will
// pollute each other.
type EC2Behavior struct {
DescribeCapacityReservationsOutput AtomicPtr[ec2.DescribeCapacityReservationsOutput]
DescribeImagesOutput AtomicPtr[ec2.DescribeImagesOutput]
DescribeLaunchTemplatesOutput AtomicPtr[ec2.DescribeLaunchTemplatesOutput]
DescribeInstanceTypesOutput AtomicPtr[ec2.DescribeInstanceTypesOutput]
DescribeInstanceTypeOfferingsOutput AtomicPtr[ec2.DescribeInstanceTypeOfferingsOutput]
DescribeAvailabilityZonesOutput AtomicPtr[ec2.DescribeAvailabilityZonesOutput]
DescribeSubnetsBehavior MockedFunction[ec2.DescribeSubnetsInput, ec2.DescribeSubnetsOutput]
DescribeSecurityGroupsBehavior MockedFunction[ec2.DescribeSecurityGroupsInput, ec2.DescribeSecurityGroupsOutput]
DescribeSpotPriceHistoryBehavior MockedFunction[ec2.DescribeSpotPriceHistoryInput, ec2.DescribeSpotPriceHistoryOutput]
CreateFleetBehavior MockedFunction[ec2.CreateFleetInput, ec2.CreateFleetOutput]
TerminateInstancesBehavior MockedFunction[ec2.TerminateInstancesInput, ec2.TerminateInstancesOutput]
DescribeInstancesBehavior MockedFunction[ec2.DescribeInstancesInput, ec2.DescribeInstancesOutput]
CreateTagsBehavior MockedFunction[ec2.CreateTagsInput, ec2.CreateTagsOutput]
RunInstancesBehavior MockedFunction[ec2.RunInstancesInput, ec2.RunInstancesOutput]
CreateLaunchTemplateBehavior MockedFunction[ec2.CreateLaunchTemplateInput, ec2.CreateLaunchTemplateOutput]
CalledWithDescribeImagesInput AtomicPtrSlice[ec2.DescribeImagesInput]
Instances sync.Map
InsufficientCapacityPools atomic.Slice[CapacityPool]
NextError AtomicError
Subnets sync.Map
LaunchTemplates sync.Map
launchTemplatesToCapacityReservations sync.Map // map[lt-name]cr-id
}
type EC2API struct {
sdk.EC2API
EC2Behavior
}
func NewEC2API() *EC2API {
return &EC2API{}
}
// DefaultSupportedUsageClasses is a var because []*string can't be a const
var DefaultSupportedUsageClasses = []ec2types.UsageClassType{ec2types.UsageClassType("on-demand"), ec2types.UsageClassType("spot")}
// Reset must be called between tests otherwise tests will pollute
// each other.
func (e *EC2API) Reset() {
e.DescribeImagesOutput.Reset()
e.DescribeLaunchTemplatesOutput.Reset()
e.DescribeInstanceTypesOutput.Reset()
e.DescribeInstanceTypeOfferingsOutput.Reset()
e.DescribeAvailabilityZonesOutput.Reset()
e.DescribeSubnetsBehavior.Reset()
e.DescribeSecurityGroupsBehavior.Reset()
e.CreateFleetBehavior.Reset()
e.TerminateInstancesBehavior.Reset()
e.DescribeInstancesBehavior.Reset()
e.CreateLaunchTemplateBehavior.Reset()
e.CalledWithDescribeImagesInput.Reset()
e.DescribeSpotPriceHistoryBehavior.Reset()
e.Subnets.Range(func(k, v any) bool {
e.Subnets.Delete(k)
return true
})
e.Instances.Range(func(k, v any) bool {
e.Instances.Delete(k)
return true
})
e.LaunchTemplates.Range(func(k, v any) bool {
e.LaunchTemplates.Delete(k)
return true
})
e.InsufficientCapacityPools.Reset()
e.NextError.Reset()
e.launchTemplatesToCapacityReservations.Range(func(k, _ any) bool {
e.launchTemplatesToCapacityReservations.Delete(k)
return true
})
}
// nolint: gocyclo
func (e *EC2API) CreateFleet(_ context.Context, input *ec2.CreateFleetInput, _ ...func(*ec2.Options)) (*ec2.CreateFleetOutput, error) {
if input.DryRun != nil && *input.DryRun {
err := e.CreateFleetBehavior.Error.Get()
if err == nil {
return &ec2.CreateFleetOutput{}, &smithy.GenericAPIError{
Code: "DryRunOperation",
Message: "Request would have succeeded, but DryRun flag is set",
}
}
return nil, err
}
return e.CreateFleetBehavior.Invoke(input, func(input *ec2.CreateFleetInput) (*ec2.CreateFleetOutput, error) {
if input.LaunchTemplateConfigs[0].LaunchTemplateSpecification.LaunchTemplateName == nil {
return nil, fmt.Errorf("missing launch template name")
}
var instanceIds []string
var icedPools []CapacityPool
var reservationExceededPools []CapacityPool
var spotInstanceRequestID *string
if string(input.TargetCapacitySpecification.DefaultTargetCapacityType) == karpv1.CapacityTypeSpot {
spotInstanceRequestID = aws.String(test.RandomName())
}
fulfilled := 0
for _, ltc := range input.LaunchTemplateConfigs {
for _, override := range ltc.Overrides {
skipInstance := false
e.InsufficientCapacityPools.Range(func(pool CapacityPool) bool {
if pool.InstanceType == string(override.InstanceType) &&
pool.Zone == aws.ToString(override.AvailabilityZone) &&
pool.CapacityType == string(input.TargetCapacitySpecification.DefaultTargetCapacityType) {
icedPools = append(icedPools, pool)
skipInstance = true
return false
}
return true
})
if skipInstance {
continue
}
if crID, ok := e.launchTemplatesToCapacityReservations.Load(*ltc.LaunchTemplateSpecification.LaunchTemplateName); ok {
if cr, ok := lo.Find(e.DescribeCapacityReservationsOutput.Clone().CapacityReservations, func(cr ec2types.CapacityReservation) bool {
return *cr.CapacityReservationId == crID.(string)
}); !ok || *cr.AvailableInstanceCount == 0 {
reservationExceededPools = append(reservationExceededPools, CapacityPool{
InstanceType: string(override.InstanceType),
Zone: lo.FromPtr(override.AvailabilityZone),
CapacityType: karpv1.CapacityTypeReserved,
ReservationID: crID.(string),
})
continue
}
}
amiID := lo.ToPtr("")
if e.CreateLaunchTemplateBehavior.CalledWithInput.Len() > 0 {
lt := e.CreateLaunchTemplateBehavior.CalledWithInput.Pop()
amiID = lt.LaunchTemplateData.ImageId
e.CreateLaunchTemplateBehavior.CalledWithInput.Add(lt)
}
instanceState := ec2types.InstanceStateNameRunning
for ; fulfilled < int(*input.TargetCapacitySpecification.TotalTargetCapacity); fulfilled++ {
instance := ec2types.Instance{
ImageId: aws.String(*amiID),
InstanceId: aws.String(test.RandomName()),
Placement: &ec2types.Placement{AvailabilityZone: input.LaunchTemplateConfigs[0].Overrides[0].AvailabilityZone},
PrivateDnsName: aws.String(randomdata.IpV4Address()),
InstanceType: input.LaunchTemplateConfigs[0].Overrides[0].InstanceType,
SpotInstanceRequestId: spotInstanceRequestID,
State: &ec2types.InstanceState{
Name: instanceState,
},
}
e.Instances.Store(*instance.InstanceId, instance)
instanceIds = append(instanceIds, *instance.InstanceId)
}
}
if fulfilled == int(*input.TargetCapacitySpecification.TotalTargetCapacity) {
break
}
}
result := &ec2.CreateFleetOutput{Instances: []ec2types.CreateFleetInstance{
{
InstanceIds: instanceIds,
InstanceType: input.LaunchTemplateConfigs[0].Overrides[0].InstanceType,
Lifecycle: ec2types.InstanceLifecycle(input.TargetCapacitySpecification.DefaultTargetCapacityType),
LaunchTemplateAndOverrides: &ec2types.LaunchTemplateAndOverridesResponse{
Overrides: &ec2types.FleetLaunchTemplateOverrides{
SubnetId: input.LaunchTemplateConfigs[0].Overrides[0].SubnetId,
ImageId: input.LaunchTemplateConfigs[0].Overrides[0].ImageId,
InstanceType: input.LaunchTemplateConfigs[0].Overrides[0].InstanceType,
AvailabilityZone: input.LaunchTemplateConfigs[0].Overrides[0].AvailabilityZone,
},
},
},
}}
for _, pool := range icedPools {
result.Errors = append(result.Errors, ec2types.CreateFleetError{
ErrorCode: aws.String("InsufficientInstanceCapacity"),
LaunchTemplateAndOverrides: &ec2types.LaunchTemplateAndOverridesResponse{
Overrides: &ec2types.FleetLaunchTemplateOverrides{
InstanceType: ec2types.InstanceType(pool.InstanceType),
AvailabilityZone: aws.String(pool.Zone),
},
},
})
}
for _, pool := range reservationExceededPools {
result.Errors = append(result.Errors, ec2types.CreateFleetError{
ErrorCode: lo.ToPtr("ReservationCapacityExceeded"),
LaunchTemplateAndOverrides: &ec2types.LaunchTemplateAndOverridesResponse{
Overrides: &ec2types.FleetLaunchTemplateOverrides{
InstanceType: ec2types.InstanceType(pool.InstanceType),
AvailabilityZone: lo.ToPtr(pool.Zone),
},
},
})
}
return result, nil
})
}
func (e *EC2API) TerminateInstances(_ context.Context, input *ec2.TerminateInstancesInput, _ ...func(*ec2.Options)) (*ec2.TerminateInstancesOutput, error) {
return e.TerminateInstancesBehavior.Invoke(input, func(input *ec2.TerminateInstancesInput) (*ec2.TerminateInstancesOutput, error) {
var instanceStateChanges []ec2types.InstanceStateChange
for _, id := range input.InstanceIds {
if _, ok := e.Instances.LoadAndDelete(id); ok {
instanceStateChanges = append(instanceStateChanges, ec2types.InstanceStateChange{
PreviousState: &ec2types.InstanceState{Name: ec2types.InstanceStateNameRunning, Code: aws.Int32(16)},
CurrentState: &ec2types.InstanceState{Name: ec2types.InstanceStateNameShuttingDown, Code: aws.Int32(32)},
InstanceId: aws.String(id),
})
}
}
return &ec2.TerminateInstancesOutput{TerminatingInstances: instanceStateChanges}, nil
})
}
// Then modify the CreateLaunchTemplate method:
func (e *EC2API) CreateLaunchTemplate(ctx context.Context, input *ec2.CreateLaunchTemplateInput, _ ...func(*ec2.Options)) (*ec2.CreateLaunchTemplateOutput, error) {
if input.DryRun != nil && *input.DryRun {
err := e.CreateLaunchTemplateBehavior.Error.Get()
if err == nil {
return &ec2.CreateLaunchTemplateOutput{}, &smithy.GenericAPIError{
Code: "DryRunOperation",
Message: "Request would have succeeded, but DryRun flag is set",
}
}
return nil, err
}
return e.CreateLaunchTemplateBehavior.Invoke(input, func(input *ec2.CreateLaunchTemplateInput) (*ec2.CreateLaunchTemplateOutput, error) {
if !e.NextError.IsNil() {
defer e.NextError.Reset()
return nil, e.NextError.Get()
}
launchTemplate := ec2types.LaunchTemplate{LaunchTemplateName: input.LaunchTemplateName}
e.LaunchTemplates.Store(input.LaunchTemplateName, launchTemplate)
if crs := input.LaunchTemplateData.CapacityReservationSpecification; crs != nil && crs.CapacityReservationPreference == ec2types.CapacityReservationPreferenceCapacityReservationsOnly {
e.launchTemplatesToCapacityReservations.Store(*input.LaunchTemplateName, *crs.CapacityReservationTarget.CapacityReservationId)
}
return &ec2.CreateLaunchTemplateOutput{LaunchTemplate: lo.ToPtr(launchTemplate)}, nil
})
}
func (e *EC2API) CreateTags(_ context.Context, input *ec2.CreateTagsInput, _ ...func(*ec2.Options)) (*ec2.CreateTagsOutput, error) {
return e.CreateTagsBehavior.Invoke(input, func(input *ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error) {
// Update passed in instances with the passed tags
for _, id := range input.Resources {
raw, ok := e.Instances.Load(id)
if !ok {
return nil, serrors.Wrap(fmt.Errorf("instance does not exist"), "instance-id", id)
}
instance := raw.(ec2types.Instance)
// Upsert any tags that have the same key
tagsToMap := func(tag ec2types.Tag) (string, string) {
return *tag.Key, *tag.Value
}
tags := lo.Assign(lo.SliceToMap(instance.Tags, tagsToMap), lo.SliceToMap(input.Tags, tagsToMap))
instance.Tags = lo.MapToSlice(tags, func(key, value string) ec2types.Tag {
return ec2types.Tag{Key: aws.String(key), Value: aws.String(value)}
})
e.Instances.Swap(lo.FromPtr(instance.InstanceId), instance)
}
return nil, nil
})
}
func (e *EC2API) DescribeInstances(_ context.Context, input *ec2.DescribeInstancesInput, _ ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error) {
return e.DescribeInstancesBehavior.Invoke(input, func(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
var instances []ec2types.Instance
// If it's a list call and no instance ids are specified
if len(input.InstanceIds) == 0 {
e.Instances.Range(func(k interface{}, v interface{}) bool {
instances = append(instances, v.(ec2types.Instance))
return true
})
}
for _, instanceID := range input.InstanceIds {
instance, _ := e.Instances.Load(instanceID)
if instance == nil {
continue
}
instances = append(instances, instance.(ec2types.Instance))
}
return &ec2.DescribeInstancesOutput{
Reservations: []ec2types.Reservation{{Instances: filterInstances(instances, input.Filters)}},
}, nil
})
}
//nolint:gocyclo
func filterInstances(instances []ec2types.Instance, filters []ec2types.Filter) []ec2types.Instance {
var ret []ec2types.Instance
for _, instance := range instances {
passesFilter := true
OUTER:
for _, filter := range filters {
switch {
case aws.ToString(filter.Name) == "instance-state-name":
if !sets.New(filter.Values...).Has(string(instance.State.Name)) {
passesFilter = false
break OUTER
}
case aws.ToString(filter.Name) == "tag-key":
values := sets.New(filter.Values...)
if _, ok := lo.Find(instance.Tags, func(t ec2types.Tag) bool {
return values.Has(aws.ToString(t.Key))
}); !ok {
passesFilter = false
break OUTER
}
case strings.HasPrefix(aws.ToString(filter.Name), "tag:"):
k := strings.TrimPrefix(aws.ToString(filter.Name), "tag:")
tag, ok := lo.Find(instance.Tags, func(t ec2types.Tag) bool {
return aws.ToString(t.Key) == k
})
if !ok {
passesFilter = false
break OUTER
}
switch {
case lo.Contains(filter.Values, "*"):
case lo.Contains(filter.Values, aws.ToString(tag.Value)):
default:
passesFilter = false
break OUTER
}
}
}
if passesFilter {
ret = append(ret, instance)
}
}
return ret
}
func (e *EC2API) DescribeCapacityReservations(ctx context.Context, input *ec2.DescribeCapacityReservationsInput, _ ...func(*ec2.Options)) (*ec2.DescribeCapacityReservationsOutput, error) {
if !e.NextError.IsNil() {
defer e.NextError.Reset()
return nil, e.NextError.Get()
}
if !e.DescribeCapacityReservationsOutput.IsNil() {
out := e.DescribeCapacityReservationsOutput.Clone()
out.CapacityReservations = FilterDescribeCapacityReservations(out.CapacityReservations, input.CapacityReservationIds, input.Filters)
return out, nil
}
return &ec2.DescribeCapacityReservationsOutput{}, nil
}
func (e *EC2API) DescribeImages(ctx context.Context, input *ec2.DescribeImagesInput, _ ...func(*ec2.Options)) (*ec2.DescribeImagesOutput, error) {
if !e.NextError.IsNil() {
defer e.NextError.Reset()
return nil, e.NextError.Get()
}
e.CalledWithDescribeImagesInput.Add(input)
if !e.DescribeImagesOutput.IsNil() {
describeImagesOutput := e.DescribeImagesOutput.Clone()
describeImagesOutput.Images = FilterDescribeImages(describeImagesOutput.Images, input.Filters)
return describeImagesOutput, nil
}
if input.Filters != nil && input.Filters[0].Values[0] == "invalid" {
return &ec2.DescribeImagesOutput{}, nil
}
return &ec2.DescribeImagesOutput{
Images: []ec2types.Image{
{
Name: aws.String(test.RandomName()),
ImageId: aws.String(test.RandomName()),
CreationDate: aws.String(time.Now().Format(time.UnixDate)),
Architecture: "x86_64",
State: ec2types.ImageStateAvailable,
},
},
}, nil
}
func (e *EC2API) DescribeLaunchTemplates(_ context.Context, input *ec2.DescribeLaunchTemplatesInput, _ ...func(*ec2.Options)) (*ec2.DescribeLaunchTemplatesOutput, error) {
if !e.NextError.IsNil() {
defer e.NextError.Reset()
return nil, e.NextError.Get()
}
if !e.DescribeLaunchTemplatesOutput.IsNil() {
return e.DescribeLaunchTemplatesOutput.Clone(), nil
}
output := &ec2.DescribeLaunchTemplatesOutput{}
e.LaunchTemplates.Range(func(key, value interface{}) bool {
launchTemplate := value.(ec2types.LaunchTemplate)
if lo.Contains(input.LaunchTemplateNames, lo.FromPtr(launchTemplate.LaunchTemplateName)) || len(input.Filters) != 0 && Filter(input.Filters, aws.ToString(launchTemplate.LaunchTemplateId), aws.ToString(launchTemplate.LaunchTemplateName), "", "", launchTemplate.Tags) {
output.LaunchTemplates = append(output.LaunchTemplates, launchTemplate)
}
return true
})
if len(input.Filters) != 0 {
return output, nil
}
if len(output.LaunchTemplates) == 0 {
return nil, &smithy.GenericAPIError{
Code: "InvalidLaunchTemplateName.NotFoundException",
Message: "At least one of the launch templates specified in the request does not exist.",
}
}
return output, nil
}
func (e *EC2API) DeleteLaunchTemplate(_ context.Context, input *ec2.DeleteLaunchTemplateInput, _ ...func(*ec2.Options)) (*ec2.DeleteLaunchTemplateOutput, error) {
if !e.NextError.IsNil() {
defer e.NextError.Reset()
return nil, e.NextError.Get()
}
e.LaunchTemplates.Delete(input.LaunchTemplateName)
return nil, nil
}
func (e *EC2API) DescribeSubnets(_ context.Context, input *ec2.DescribeSubnetsInput, _ ...func(*ec2.Options)) (*ec2.DescribeSubnetsOutput, error) {
return e.DescribeSubnetsBehavior.Invoke(input, func(input *ec2.DescribeSubnetsInput) (*ec2.DescribeSubnetsOutput, error) {
output := &ec2.DescribeSubnetsOutput{}
e.Subnets.Range(func(key, value any) bool {
subnet := value.(ec2types.Subnet)
if lo.Contains(input.SubnetIds, lo.FromPtr(subnet.SubnetId)) || len(input.Filters) != 0 && len(FilterDescribeSubnets([]ec2types.Subnet{subnet}, input.Filters)) != 0 {
output.Subnets = append(output.Subnets, subnet)
}
return true
})
if len(output.Subnets) != 0 {
return output, nil
}
defaultSubnets := []ec2types.Subnet{
{
SubnetId: aws.String("subnet-test1"),
AvailabilityZone: aws.String("test-zone-1a"),
AvailabilityZoneId: aws.String("tstz1-1a"),
AvailableIpAddressCount: aws.Int32(100),
MapPublicIpOnLaunch: aws.Bool(false),
Tags: []ec2types.Tag{
{Key: aws.String("Name"), Value: aws.String("test-subnet-1")},
{Key: aws.String("foo"), Value: aws.String("bar")},
},
},
{
SubnetId: aws.String("subnet-test2"),
AvailabilityZone: aws.String("test-zone-1b"),
AvailabilityZoneId: aws.String("tstz1-1b"),
AvailableIpAddressCount: aws.Int32(100),
MapPublicIpOnLaunch: aws.Bool(true),
Tags: []ec2types.Tag{
{Key: aws.String("Name"), Value: aws.String("test-subnet-2")},
{Key: aws.String("foo"), Value: aws.String("bar")},
},
},
{
SubnetId: aws.String("subnet-test3"),
AvailabilityZone: aws.String("test-zone-1c"),
AvailabilityZoneId: aws.String("tstz1-1c"),
AvailableIpAddressCount: aws.Int32(100),
Tags: []ec2types.Tag{
{Key: aws.String("Name"), Value: aws.String("test-subnet-3")},
{Key: aws.String("TestTag")},
{Key: aws.String("foo"), Value: aws.String("bar")},
},
},
{
SubnetId: aws.String("subnet-test4"),
AvailabilityZone: aws.String("test-zone-1a-local"),
AvailabilityZoneId: aws.String("tstz1-1alocal"),
AvailableIpAddressCount: aws.Int32(100),
MapPublicIpOnLaunch: aws.Bool(true),
Tags: []ec2types.Tag{
{Key: aws.String("Name"), Value: aws.String("test-subnet-4")},
},
},
}
if len(input.Filters) == 0 {
return nil, fmt.Errorf("InvalidParameterValue: The filter 'null' is invalid")
}
return &ec2.DescribeSubnetsOutput{Subnets: FilterDescribeSubnets(defaultSubnets, input.Filters)}, nil
})
}
func (e *EC2API) DescribeSecurityGroups(_ context.Context, input *ec2.DescribeSecurityGroupsInput, _ ...func(*ec2.Options)) (*ec2.DescribeSecurityGroupsOutput, error) {
return e.DescribeSecurityGroupsBehavior.Invoke(input, func(input *ec2.DescribeSecurityGroupsInput) (*ec2.DescribeSecurityGroupsOutput, error) {
defaultSecurityGroups := []ec2types.SecurityGroup{
{
GroupId: aws.String("sg-test1"),
GroupName: aws.String("securityGroup-test1"),
Tags: []ec2types.Tag{
{Key: aws.String("Name"), Value: aws.String("test-security-group-1")},
{Key: aws.String("foo"), Value: aws.String("bar")},
},
},
{
GroupId: aws.String("sg-test2"),
GroupName: aws.String("securityGroup-test2"),
Tags: []ec2types.Tag{
{Key: aws.String("Name"), Value: aws.String("test-security-group-2")},
{Key: aws.String("foo"), Value: aws.String("bar")},
},
},
{
GroupId: aws.String("sg-test3"),
GroupName: aws.String("securityGroup-test3"),
Tags: []ec2types.Tag{
{Key: aws.String("Name"), Value: aws.String("test-security-group-3")},
{Key: aws.String("TestTag")},
{Key: aws.String("foo"), Value: aws.String("bar")},
},
},
}
if len(input.Filters) == 0 {
return nil, fmt.Errorf("InvalidParameterValue: The filter 'null' is invalid")
}
return &ec2.DescribeSecurityGroupsOutput{SecurityGroups: FilterDescribeSecurtyGroups(defaultSecurityGroups, input.Filters)}, nil
})
}
func (e *EC2API) DescribeAvailabilityZones(context.Context, *ec2.DescribeAvailabilityZonesInput, ...func(*ec2.Options)) (*ec2.DescribeAvailabilityZonesOutput, error) {
if !e.NextError.IsNil() {
defer e.NextError.Reset()
return nil, e.NextError.Get()
}
if !e.DescribeAvailabilityZonesOutput.IsNil() {
return e.DescribeAvailabilityZonesOutput.Clone(), nil
}
return &ec2.DescribeAvailabilityZonesOutput{AvailabilityZones: []ec2types.AvailabilityZone{
{ZoneName: aws.String("test-zone-1a"), ZoneId: aws.String("tstz1-1a"), ZoneType: aws.String("availability-zone")},
{ZoneName: aws.String("test-zone-1b"), ZoneId: aws.String("tstz1-1b"), ZoneType: aws.String("availability-zone")},
{ZoneName: aws.String("test-zone-1c"), ZoneId: aws.String("tstz1-1c"), ZoneType: aws.String("availability-zone")},
{ZoneName: aws.String("test-zone-1a-local"), ZoneId: aws.String("tstz1-1alocal"), ZoneType: aws.String("local-zone")},
}}, nil
}
func (e *EC2API) DescribeInstanceTypes(_ context.Context, _ *ec2.DescribeInstanceTypesInput, _ ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error) {
if !e.NextError.IsNil() {
defer e.NextError.Reset()
return nil, e.NextError.Get()
}
if !e.DescribeInstanceTypesOutput.IsNil() {
return e.DescribeInstanceTypesOutput.Clone(), nil
}
return defaultDescribeInstanceTypesOutput, nil
}
func (e *EC2API) DescribeInstanceTypeOfferings(_ context.Context, _ *ec2.DescribeInstanceTypeOfferingsInput, _ ...func(*ec2.Options)) (*ec2.DescribeInstanceTypeOfferingsOutput, error) {
if !e.NextError.IsNil() {
defer e.NextError.Reset()
return nil, e.NextError.Get()
}
if !e.DescribeInstanceTypeOfferingsOutput.IsNil() {
return e.DescribeInstanceTypeOfferingsOutput.Clone(), nil
}
return defaultDescribeInstanceTypeOfferingsOutput, nil
}
func (e *EC2API) DescribeSpotPriceHistory(_ context.Context, input *ec2.DescribeSpotPriceHistoryInput, _ ...func(*ec2.Options)) (*ec2.DescribeSpotPriceHistoryOutput, error) {
return e.DescribeSpotPriceHistoryBehavior.Invoke(input, func(input *ec2.DescribeSpotPriceHistoryInput) (*ec2.DescribeSpotPriceHistoryOutput, error) {
// fail if the test doesn't provide specific data which causes our pricing provider to use its static price list
return nil, errors.New("no pricing data provided")
})
}
func (e *EC2API) RunInstances(ctx context.Context, input *ec2.RunInstancesInput, optFns ...func(*ec2.Options)) (*ec2.RunInstancesOutput, error) {
return e.RunInstancesBehavior.Invoke(input, func(input *ec2.RunInstancesInput) (*ec2.RunInstancesOutput, error) {
if !e.NextError.IsNil() {
defer e.NextError.Reset()
return nil, e.NextError.Get()
}
if lo.FromPtr(input.DryRun) {
return &ec2.RunInstancesOutput{}, &smithy.GenericAPIError{
Code: "DryRunOperation",
Message: "Request would have succeeded, but DryRun flag is set",
}
}
// Default implementation
instance := ec2types.Instance{
InstanceId: aws.String(test.RandomName()),
InstanceType: input.InstanceType,
State: &ec2types.InstanceState{Name: ec2types.InstanceStateNameRunning},
// Add other required fields
}
return &ec2.RunInstancesOutput{
Instances: []ec2types.Instance{instance},
}, nil
})
}