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 }