golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/cloud/fake_aws.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package cloud 6 7 import ( 8 "context" 9 "crypto/rand" 10 "errors" 11 "fmt" 12 mrand "math/rand" 13 "sync" 14 "time" 15 16 "github.com/aws/aws-sdk-go/service/ec2" 17 ) 18 19 func init() { mrand.Seed(time.Now().UnixNano()) } 20 21 // FakeAWSClient provides a fake AWS Client used to test the AWS client 22 // functionality. 23 type FakeAWSClient struct { 24 mu sync.RWMutex 25 instances map[string]*Instance 26 instanceTypes []*InstanceType 27 serviceQuotas map[serviceQuotaKey]int64 28 } 29 30 // serviceQuotaKey should be used as the key in the serviceQuotas map. 31 type serviceQuotaKey struct { 32 code string 33 service string 34 } 35 36 // NewFakeAWSClient crates a fake AWS client. 37 func NewFakeAWSClient() *FakeAWSClient { 38 return &FakeAWSClient{ 39 instances: make(map[string]*Instance), 40 instanceTypes: []*InstanceType{ 41 &InstanceType{"ab.large", 10}, 42 &InstanceType{"ab.xlarge", 20}, 43 &InstanceType{"ab.small", 30}, 44 }, 45 serviceQuotas: map[serviceQuotaKey]int64{ 46 serviceQuotaKey{QuotaCodeCPUOnDemand, QuotaServiceEC2}: 384, 47 }, 48 } 49 } 50 51 // Instance returns the `Instance` record for the requested instance. The instance record will 52 // return records for recently terminated instances. If an instance is not found an error will 53 // be returned. 54 func (f *FakeAWSClient) Instance(ctx context.Context, instID string) (*Instance, error) { 55 if ctx == nil || instID == "" { 56 return nil, errors.New("invalid params") 57 } 58 f.mu.RLock() 59 defer f.mu.RUnlock() 60 61 inst, ok := f.instances[instID] 62 if !ok { 63 return nil, errors.New("instance not found") 64 } 65 return copyInstance(inst), nil 66 } 67 68 // Instances retrieves all EC2 instances in a region which have not been terminated or stopped. 69 func (f *FakeAWSClient) RunningInstances(ctx context.Context) ([]*Instance, error) { 70 if ctx == nil { 71 return nil, errors.New("invalid params") 72 } 73 f.mu.RLock() 74 defer f.mu.RUnlock() 75 76 instances := make([]*Instance, 0, len(f.instances)) 77 for _, inst := range f.instances { 78 if inst.State != ec2.InstanceStateNameRunning && inst.State != ec2.InstanceStateNamePending { 79 continue 80 } 81 instances = append(instances, copyInstance(inst)) 82 } 83 return instances, nil 84 } 85 86 // InstanceTypesARM retrieves all EC2 instance types in a region which support the ARM64 architecture. 87 func (f *FakeAWSClient) InstanceTypesARM(ctx context.Context) ([]*InstanceType, error) { 88 if ctx == nil { 89 return nil, errors.New("invalid params") 90 } 91 f.mu.RLock() 92 defer f.mu.RUnlock() 93 94 instanceTypes := make([]*InstanceType, 0, len(f.instanceTypes)) 95 for _, it := range f.instanceTypes { 96 instanceTypes = append(instanceTypes, &InstanceType{it.Type, it.CPU}) 97 } 98 return instanceTypes, nil 99 } 100 101 // Quota retrieves the requested service quota for the service. 102 func (f *FakeAWSClient) Quota(ctx context.Context, service, code string) (int64, error) { 103 if ctx == nil || service == "" || code == "" { 104 return 0, errors.New("invalid params") 105 } 106 f.mu.RLock() 107 defer f.mu.RUnlock() 108 109 v, ok := f.serviceQuotas[serviceQuotaKey{code, service}] 110 if !ok { 111 return 0, errors.New("service quota not found") 112 } 113 return v, nil 114 } 115 116 // CreateInstance creates an EC2 VM instance. 117 func (f *FakeAWSClient) CreateInstance(ctx context.Context, config *EC2VMConfiguration) (*Instance, error) { 118 if ctx == nil || config == nil { 119 return nil, errors.New("invalid params") 120 } 121 if config.ImageID == "" { 122 return nil, errors.New("invalid Image ID") 123 } 124 if config.Type == "" { 125 return nil, errors.New("invalid Type") 126 } 127 if config.Zone == "" { 128 return nil, errors.New("invalid Zone") 129 } 130 f.mu.Lock() 131 defer f.mu.Unlock() 132 133 inst := &Instance{ 134 CPUCount: 4, 135 CreatedAt: time.Now(), 136 Description: config.Description, 137 ID: fmt.Sprintf("instance-%s", randHex(10)), 138 IPAddressExternal: randIPv4(), 139 IPAddressInternal: randIPv4(), 140 ImageID: config.ImageID, 141 Name: config.Name, 142 SSHKeyID: config.SSHKeyID, 143 SecurityGroups: config.SecurityGroups, 144 State: ec2.InstanceStateNameRunning, 145 Tags: make(map[string]string), 146 Type: config.Type, 147 Zone: config.Zone, 148 } 149 for k, v := range config.Tags { 150 inst.Tags[k] = v 151 } 152 f.instances[inst.ID] = inst 153 return copyInstance(inst), nil 154 } 155 156 // DestroyInstances terminates EC2 VM instances. 157 func (f *FakeAWSClient) DestroyInstances(ctx context.Context, instIDs ...string) error { 158 if ctx == nil || len(instIDs) == 0 { 159 return errors.New("invalid params") 160 } 161 f.mu.Lock() 162 defer f.mu.Unlock() 163 164 for _, id := range instIDs { 165 inst, ok := f.instances[id] 166 if !ok { 167 return errors.New("instance not found") 168 } 169 inst.State = ec2.InstanceStateNameTerminated 170 } 171 return nil 172 } 173 174 // WaitUntilInstanceRunning returns when an instance has transitioned into the running state. 175 func (f *FakeAWSClient) WaitUntilInstanceRunning(ctx context.Context, instID string) error { 176 if ctx == nil || instID == "" { 177 return errors.New("invalid params") 178 } 179 f.mu.RLock() 180 defer f.mu.RUnlock() 181 182 inst, ok := f.instances[instID] 183 if !ok { 184 return errors.New("instance not found") 185 } 186 if inst.State != ec2.InstanceStateNameRunning { 187 return errors.New("timed out waiting for instance to enter running state") 188 } 189 return nil 190 } 191 192 // copyInstance copies the contents of a pointer to an instance and returns a newly created 193 // instance with the same data as the original instance. 194 func copyInstance(inst *Instance) *Instance { 195 i := &Instance{ 196 CPUCount: inst.CPUCount, 197 CreatedAt: inst.CreatedAt, 198 Description: inst.Description, 199 ID: inst.ID, 200 IPAddressExternal: inst.IPAddressExternal, 201 IPAddressInternal: inst.IPAddressInternal, 202 ImageID: inst.ImageID, 203 Name: inst.Name, 204 SSHKeyID: inst.SSHKeyID, 205 SecurityGroups: inst.SecurityGroups, 206 State: inst.State, 207 Tags: make(map[string]string), 208 Type: inst.Type, 209 Zone: inst.Zone, 210 } 211 for k, v := range inst.Tags { 212 i.Tags[k] = v 213 } 214 return i 215 } 216 217 // randHex creates a random hex string of length n. 218 func randHex(n int) string { 219 buf := make([]byte, n/2+1) 220 _, _ = rand.Read(buf) 221 return fmt.Sprintf("%x", buf)[:n] 222 } 223 224 // randIPv4 creates a random IPv4 address. 225 func randIPv4() string { 226 return fmt.Sprintf("%d.%d.%d.%d", mrand.Intn(255), mrand.Intn(255), mrand.Intn(255), mrand.Intn(255)) 227 }