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 }