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  }