github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/gce/instance_information.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package gce
     5  
     6  import (
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/juju/clock"
    11  	"github.com/juju/errors"
    12  
    13  	"github.com/juju/juju/core/arch"
    14  	"github.com/juju/juju/core/constraints"
    15  	"github.com/juju/juju/environs"
    16  	"github.com/juju/juju/environs/context"
    17  	"github.com/juju/juju/environs/instances"
    18  	"github.com/juju/juju/provider/gce/google"
    19  )
    20  
    21  var (
    22  	_ environs.InstanceTypesFetcher = (*environ)(nil)
    23  
    24  	virtType = "kvm"
    25  
    26  	// minCpuCores is the assumed minimum CPU cores we prefer in order to run a server.
    27  	minCpuCores uint64 = 2
    28  )
    29  
    30  // ensureDefaultConstraints adds the minimum number of cpu cores value to the
    31  // constraints if the user has not provided a cpu-cores constraint.
    32  // This function exists only so that the minimum cpu cores takes precedence
    33  // over the default cpu-cores and memory values implemented in
    34  // instances.MatchingInstanceTypes()
    35  func ensureDefaultConstraints(c constraints.Value) constraints.Value {
    36  	if c.HasInstanceType() || c.HasCpuCores() {
    37  		return c
    38  	}
    39  	c.CpuCores = &minCpuCores
    40  	return c
    41  }
    42  
    43  // InstanceTypes implements InstanceTypesFetcher
    44  func (env *environ) InstanceTypes(ctx context.ProviderCallContext, c constraints.Value) (instances.InstanceTypesWithCostMetadata, error) {
    45  	allInstanceTypes, err := env.getAllInstanceTypes(ctx, clock.WallClock)
    46  	if err != nil {
    47  		return instances.InstanceTypesWithCostMetadata{}, errors.Trace(err)
    48  	}
    49  	matches, err := instances.MatchingInstanceTypes(allInstanceTypes, "", ensureDefaultConstraints(c))
    50  	if err != nil {
    51  		return instances.InstanceTypesWithCostMetadata{}, errors.Trace(err)
    52  	}
    53  	return instances.InstanceTypesWithCostMetadata{InstanceTypes: matches}, nil
    54  }
    55  
    56  // getAllInstanceTypes fetches and memoizes the list of available GCE instances
    57  // for the AZs associated with the current region.
    58  func (env *environ) getAllInstanceTypes(ctx context.ProviderCallContext, clock clock.Clock) ([]instances.InstanceType, error) {
    59  	env.instTypeListLock.Lock()
    60  	defer env.instTypeListLock.Unlock()
    61  
    62  	if len(env.cachedInstanceTypes) != 0 && clock.Now().Before(env.instCacheExpireAt) {
    63  		return env.cachedInstanceTypes, nil
    64  	}
    65  
    66  	reg, err := env.Region()
    67  	if err != nil {
    68  		return nil, errors.Trace(err)
    69  	}
    70  	zones, err := env.gce.AvailabilityZones(reg.Region)
    71  	if err != nil {
    72  		return nil, google.HandleCredentialError(errors.Trace(err), ctx)
    73  	}
    74  	resultUnique := map[string]instances.InstanceType{}
    75  
    76  	for _, z := range zones {
    77  		if !z.Available() {
    78  			continue
    79  		}
    80  		machines, err := env.gce.ListMachineTypes(z.Name())
    81  		if err != nil {
    82  			return nil, google.HandleCredentialError(errors.Trace(err), ctx)
    83  		}
    84  		for _, m := range machines {
    85  			i := instances.InstanceType{
    86  				Id:       strconv.FormatUint(m.Id, 10),
    87  				Name:     m.Name,
    88  				CpuCores: uint64(m.GuestCpus),
    89  				Mem:      uint64(m.MemoryMb),
    90  				// TODO: support arm64 once the API can report arch.
    91  				Arch:     arch.AMD64,
    92  				VirtType: &virtType,
    93  			}
    94  			resultUnique[m.Name] = i
    95  		}
    96  	}
    97  
    98  	env.cachedInstanceTypes = make([]instances.InstanceType, 0, len(resultUnique))
    99  	for _, it := range resultUnique {
   100  		env.cachedInstanceTypes = append(env.cachedInstanceTypes, it)
   101  	}
   102  
   103  	// Keep the instance data in the cache for 10 minutes. This is probably
   104  	// long enough to exploit temporal locality when deploying bundles etc
   105  	// and short enough to allow the use of new machines a few moments after
   106  	// they are published by the GCE.
   107  	env.instCacheExpireAt = clock.Now().Add(10 * time.Minute)
   108  	return env.cachedInstanceTypes, nil
   109  }