github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/gce/environ_availzones.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package gce
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  
     9  	"github.com/juju/juju/core/instance"
    10  	"github.com/juju/juju/environs"
    11  	"github.com/juju/juju/environs/context"
    12  	"github.com/juju/juju/provider/common"
    13  	"github.com/juju/juju/provider/gce/google"
    14  	"github.com/juju/juju/storage"
    15  )
    16  
    17  // AvailabilityZones returns all availability zones in the environment.
    18  func (env *environ) AvailabilityZones(ctx context.ProviderCallContext) ([]common.AvailabilityZone, error) {
    19  	zones, err := env.gce.AvailabilityZones(env.cloud.Region)
    20  	if err != nil {
    21  		return nil, google.HandleCredentialError(errors.Trace(err), ctx)
    22  	}
    23  
    24  	var result []common.AvailabilityZone
    25  	for _, zone := range zones {
    26  		if zone.Deprecated() {
    27  			continue
    28  		}
    29  		// We make a copy since the loop var keeps the same pointer.
    30  		zoneCopy := zone
    31  		result = append(result, &zoneCopy)
    32  	}
    33  	return result, nil
    34  }
    35  
    36  // InstanceAvailabilityZoneNames returns the names of the availability
    37  // zones for the specified instances. The error returned follows the same
    38  // rules as Environ.Instances.
    39  func (env *environ) InstanceAvailabilityZoneNames(ctx context.ProviderCallContext, ids []instance.Id) ([]string, error) {
    40  	instances, err := env.Instances(ctx, ids)
    41  	if err != nil && err != environs.ErrPartialInstances && err != environs.ErrNoInstances {
    42  		return nil, errors.Trace(err)
    43  	}
    44  	// We let the two environs errors pass on through. However, we do
    45  	// not use errors.Trace in that case since callers may not call
    46  	// errors.Cause.
    47  
    48  	results := make([]string, len(ids))
    49  	for i, inst := range instances {
    50  		if eInst := inst.(*environInstance); eInst != nil {
    51  			results[i] = eInst.base.ZoneName
    52  		}
    53  	}
    54  
    55  	return results, err
    56  }
    57  
    58  // DeriveAvailabilityZones is part of the common.ZonedEnviron interface.
    59  func (env *environ) DeriveAvailabilityZones(ctx context.ProviderCallContext, args environs.StartInstanceParams) ([]string, error) {
    60  	zone, err := env.deriveAvailabilityZones(ctx, args.Placement, args.VolumeAttachments)
    61  	if zone != "" {
    62  		return []string{zone}, errors.Trace(err)
    63  	}
    64  	return nil, errors.Trace(err)
    65  }
    66  
    67  func (env *environ) availZone(ctx context.ProviderCallContext, name string) (*google.AvailabilityZone, error) {
    68  	zones, err := env.gce.AvailabilityZones(env.cloud.Region)
    69  	if err != nil {
    70  		return nil, google.HandleCredentialError(errors.Trace(err), ctx)
    71  	}
    72  	for _, z := range zones {
    73  		if z.Name() == name {
    74  			return &z, nil
    75  		}
    76  	}
    77  	return nil, errors.NotFoundf("invalid availability zone %q", name)
    78  }
    79  
    80  func (env *environ) availZoneUp(ctx context.ProviderCallContext, name string) (*google.AvailabilityZone, error) {
    81  	zone, err := env.availZone(ctx, name)
    82  	if err != nil {
    83  		return nil, errors.Trace(err)
    84  	}
    85  	if !zone.Available() {
    86  		return nil, errors.Errorf("availability zone %q is %s", zone.Name(), zone.Status())
    87  	}
    88  	return zone, nil
    89  }
    90  
    91  var availabilityZoneAllocations = common.AvailabilityZoneAllocations
    92  
    93  // volumeAttachmentsZone determines the availability zone for each volume
    94  // identified in the volume attachment parameters, checking that they are
    95  // all the same, and returns the availability zone name.
    96  func volumeAttachmentsZone(volumeAttachments []storage.VolumeAttachmentParams) (string, error) {
    97  	var zone string
    98  	for i, a := range volumeAttachments {
    99  		volumeZone, _, err := parseVolumeId(a.VolumeId)
   100  		if err != nil {
   101  			return "", errors.Trace(err)
   102  		}
   103  		if zone == "" {
   104  			zone = volumeZone
   105  		} else if zone != volumeZone {
   106  			return "", errors.Errorf(
   107  				"cannot attach volumes from multiple availability zones: %s is in %s, %s is in %s",
   108  				volumeAttachments[i-1].VolumeId, zone, a.VolumeId, volumeZone,
   109  			)
   110  		}
   111  	}
   112  	return zone, nil
   113  }
   114  
   115  func (env *environ) instancePlacementZone(ctx context.ProviderCallContext, placement string, volumeAttachmentsZone string) (string, error) {
   116  	if placement == "" {
   117  		return volumeAttachmentsZone, nil
   118  	}
   119  	// placement will always be a zone name or empty.
   120  	instPlacement, err := env.parsePlacement(ctx, placement)
   121  	if err != nil {
   122  		return "", errors.Trace(err)
   123  	}
   124  	if volumeAttachmentsZone != "" && instPlacement.Zone.Name() != volumeAttachmentsZone {
   125  		return "", errors.Errorf(
   126  			"cannot create instance with placement %q, as this will prevent attaching the requested disks in zone %q",
   127  			placement, volumeAttachmentsZone,
   128  		)
   129  	}
   130  	return instPlacement.Zone.Name(), nil
   131  }
   132  
   133  func (e *environ) deriveAvailabilityZones(
   134  	ctx context.ProviderCallContext,
   135  	placement string,
   136  	volumeAttachments []storage.VolumeAttachmentParams,
   137  ) (string, error) {
   138  	volumeAttachmentsZone, err := volumeAttachmentsZone(volumeAttachments)
   139  	if err != nil {
   140  		return "", errors.Trace(err)
   141  	}
   142  	if placement == "" {
   143  		return volumeAttachmentsZone, nil
   144  	}
   145  	instPlacement, err := e.parsePlacement(ctx, placement)
   146  	if err != nil {
   147  		return "", err
   148  	}
   149  	instanceZone := instPlacement.Zone.Name()
   150  	if err := validateAvailabilityZoneConsistency(instanceZone, volumeAttachmentsZone); err != nil {
   151  		return "", errors.Annotatef(err, "cannot create instance with placement %q", placement)
   152  	}
   153  	return instanceZone, nil
   154  }
   155  
   156  func validateAvailabilityZoneConsistency(instanceZone, volumeAttachmentsZone string) error {
   157  	if volumeAttachmentsZone != "" && instanceZone != volumeAttachmentsZone {
   158  		return errors.Errorf(
   159  			"cannot create instance in zone %q, as this will prevent attaching the requested disks in zone %q",
   160  			instanceZone, volumeAttachmentsZone,
   161  		)
   162  	}
   163  	return nil
   164  }