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  }