github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/vsphere/environ_availzones.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package vsphere 5 6 import ( 7 "sort" 8 "strings" 9 10 "github.com/juju/errors" 11 "github.com/vmware/govmomi/object" 12 "github.com/vmware/govmomi/vim25/mo" 13 14 "github.com/juju/juju/core/instance" 15 "github.com/juju/juju/core/network" 16 "github.com/juju/juju/environs" 17 "github.com/juju/juju/environs/context" 18 ) 19 20 type vmwareAvailZone struct { 21 r mo.ComputeResource 22 pool *object.ResourcePool 23 name string 24 } 25 26 // Name returns the "name" of the Vsphere availability zone. 27 func (z *vmwareAvailZone) Name() string { 28 return z.name 29 } 30 31 // Available implements common.AvailabilityZone 32 func (z *vmwareAvailZone) Available() bool { 33 return true 34 } 35 36 // AvailabilityZones is part of the common.ZonedEnviron interface. 37 func (env *environ) AvailabilityZones(ctx context.ProviderCallContext) (zones network.AvailabilityZones, err error) { 38 err = env.withSession(ctx, func(env *sessionEnviron) error { 39 zones, err = env.AvailabilityZones(ctx) 40 return err 41 }) 42 return zones, err 43 } 44 45 // AvailabilityZones is part of the common.ZonedEnviron interface. 46 func (env *sessionEnviron) AvailabilityZones(ctx context.ProviderCallContext) (network.AvailabilityZones, error) { 47 if len(env.zones) > 0 { 48 // This is relatively expensive to compute, so cache it on the session 49 return env.zones, nil 50 } 51 52 folders, err := env.client.Folders(env.ctx) 53 if err != nil { 54 HandleCredentialError(err, env, ctx) 55 return nil, errors.Trace(err) 56 } 57 logger.Tracef("host folder InventoryPath=%q, Name=%q", 58 folders.HostFolder.InventoryPath, folders.HostFolder.Name()) 59 hostFolder := folders.HostFolder.InventoryPath 60 61 computeResources, err := env.client.ComputeResources(env.ctx) 62 if err != nil { 63 HandleCredentialError(err, env, ctx) 64 return nil, errors.Trace(err) 65 } 66 var zones network.AvailabilityZones 67 for _, cr := range computeResources { 68 if cr.Resource.Summary.GetComputeResourceSummary().EffectiveCpu == 0 { 69 logger.Debugf("skipping empty compute resource %q", cr.Resource.Name) 70 continue 71 } 72 73 // Add an availability zone for each resource pool under this compute 74 // resource 75 pools, err := env.client.ResourcePools(env.ctx, cr.Path+"/...") 76 if err != nil { 77 HandleCredentialError(err, env, ctx) 78 return nil, errors.Trace(err) 79 } 80 for _, pool := range pools { 81 zone := &vmwareAvailZone{ 82 r: *cr.Resource, 83 pool: pool, 84 name: makeAvailZoneName(hostFolder, cr.Path, pool.InventoryPath), 85 } 86 logger.Tracef("zone: %s (cr.Name=%q pool.InventoryPath=%q)", 87 zone.Name(), zone.r.Name, zone.pool.InventoryPath) 88 zones = append(zones, zone) 89 } 90 } 91 92 if logger.IsDebugEnabled() { 93 zoneNames := make([]string, len(zones)) 94 for i, zone := range zones { 95 zoneNames[i] = zone.Name() 96 } 97 sort.Strings(zoneNames) 98 logger.Debugf("fetched availability zones: %q", zoneNames) 99 } 100 101 env.zones = zones 102 return env.zones, nil 103 } 104 105 // makeAvailZoneName constructs a Vsphere availability zone name from the 106 // given paths. Basically it's the path relative to the host folder without 107 // the extra "Resources" path segment (which doesn't appear in the UI): 108 // 109 // * "/DataCenter1/host/Host1/Resources" becomes "Host1" 110 // * "/DataCenter1/host/Host1/Resources/ResPool1" becomes "Host1/ResPool1" 111 // * "/DataCenter1/host/Host1/Other" becomes "Host1/Other" (shouldn't happen) 112 func makeAvailZoneName(hostFolder, crPath, poolPath string) string { 113 poolPath = strings.TrimRight(poolPath, "/") 114 relCrPath := strings.TrimPrefix(crPath, hostFolder+"/") 115 relPoolPath := strings.TrimPrefix(poolPath, crPath+"/") 116 switch { 117 case relPoolPath == "Resources": 118 return relCrPath 119 case strings.HasPrefix(relPoolPath, "Resources/"): 120 return relCrPath + "/" + relPoolPath[len("Resources/"):] 121 default: 122 return relCrPath + "/" + relPoolPath 123 } 124 } 125 126 // InstanceAvailabilityZoneNames is part of the common.ZonedEnviron interface. 127 func (env *environ) InstanceAvailabilityZoneNames(ctx context.ProviderCallContext, ids []instance.Id) (names map[instance.Id]string, err error) { 128 err = env.withSession(ctx, func(env *sessionEnviron) error { 129 names, err = env.InstanceAvailabilityZoneNames(ctx, ids) 130 return err 131 }) 132 return names, err 133 } 134 135 // InstanceAvailabilityZoneNames is part of the common.ZonedEnviron interface. 136 func (env *sessionEnviron) InstanceAvailabilityZoneNames(ctx context.ProviderCallContext, ids []instance.Id) (map[instance.Id]string, error) { 137 zones, err := env.AvailabilityZones(ctx) 138 if err != nil { 139 return nil, errors.Trace(err) 140 } 141 instances, err := env.Instances(ctx, ids) 142 switch err { 143 case nil, environs.ErrPartialInstances: 144 break 145 case environs.ErrNoInstances: 146 return nil, err 147 default: 148 return nil, errors.Trace(err) 149 } 150 151 results := make(map[instance.Id]string) 152 for _, inst := range instances { 153 if inst == nil { 154 continue 155 } 156 vInst, ok := inst.(*environInstance) 157 if !ok { 158 continue 159 } 160 vm := vInst.base 161 for _, zone := range zones { 162 pool := zone.(*vmwareAvailZone).pool 163 if pool.Reference().Value == vm.ResourcePool.Value { 164 results[inst.Id()] = zone.Name() 165 break 166 } 167 } 168 } 169 // Don't be tempted to change this err to nil, it actually bubbles up 170 // environs.ErrPartialInstances from the above switch. 171 return results, err 172 } 173 174 // DeriveAvailabilityZones is part of the common.ZonedEnviron interface. 175 func (env *environ) DeriveAvailabilityZones(ctx context.ProviderCallContext, args environs.StartInstanceParams) (names []string, err error) { 176 err = env.withSession(ctx, func(env *sessionEnviron) error { 177 names, err = env.DeriveAvailabilityZones(ctx, args) 178 return err 179 }) 180 return names, err 181 } 182 183 // DeriveAvailabilityZones is part of the common.ZonedEnviron interface. 184 func (env *sessionEnviron) DeriveAvailabilityZones(ctx context.ProviderCallContext, args environs.StartInstanceParams) ([]string, error) { 185 if args.Placement != "" { 186 // args.Placement will always be a zone name or empty. 187 placement, err := env.parsePlacement(ctx, args.Placement) 188 if err != nil { 189 return nil, errors.Trace(err) 190 } 191 if placement.Name() != "" { 192 return []string{placement.Name()}, nil 193 } 194 } 195 return nil, nil 196 } 197 198 func (env *sessionEnviron) availZone(ctx context.ProviderCallContext, name string) (*vmwareAvailZone, error) { 199 zones, err := env.AvailabilityZones(ctx) 200 if err != nil { 201 return nil, errors.Trace(err) 202 } 203 for _, z := range zones { 204 if z.Name() == name { 205 return z.(*vmwareAvailZone), nil 206 } 207 } 208 return nil, errors.NotFoundf("availability zone %q", name) 209 }