github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/gce/environ_instance.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 "strings" 8 9 "github.com/juju/errors" 10 "github.com/juju/version" 11 12 "github.com/juju/juju/core/constraints" 13 "github.com/juju/juju/core/instance" 14 "github.com/juju/juju/environs" 15 "github.com/juju/juju/environs/context" 16 "github.com/juju/juju/environs/instances" 17 "github.com/juju/juju/environs/tags" 18 "github.com/juju/juju/provider/gce/google" 19 ) 20 21 // instStatus is the list of statuses to accept when filtering 22 // for "alive" instances. 23 var instStatuses = []string{ 24 google.StatusPending, 25 google.StatusStaging, 26 google.StatusRunning, 27 } 28 29 // Instances returns the available instances in the environment that 30 // match the provided instance IDs. For IDs that did not match any 31 // instances, the result at the corresponding index will be nil. In that 32 // case the error will be environs.ErrPartialInstances (or 33 // ErrNoInstances if none of the IDs match an instance). 34 func (env *environ) Instances(ctx context.ProviderCallContext, ids []instance.Id) ([]instances.Instance, error) { 35 if len(ids) == 0 { 36 return nil, environs.ErrNoInstances 37 } 38 39 all, err := getInstances(env, ctx) 40 if err != nil { 41 // We don't return the error since we need to pack one instance 42 // for each ID into the result. If there is a problem then we 43 // will return either ErrPartialInstances or ErrNoInstances. 44 // TODO(ericsnow) Skip returning here only for certain errors? 45 logger.Errorf("failed to get instances from GCE: %v", err) 46 err = errors.Trace(err) 47 } 48 49 // Build the result, matching the provided instance IDs. 50 numFound := 0 // This will never be greater than len(ids). 51 results := make([]instances.Instance, len(ids)) 52 for i, id := range ids { 53 inst := findInst(id, all) 54 if inst != nil { 55 numFound++ 56 } 57 results[i] = inst 58 } 59 60 if numFound == 0 { 61 if err == nil { 62 err = environs.ErrNoInstances 63 } 64 } else if numFound != len(ids) { 65 err = environs.ErrPartialInstances 66 } 67 return results, err 68 } 69 70 var getInstances = func(env *environ, ctx context.ProviderCallContext) ([]instances.Instance, error) { 71 return env.instances(ctx) 72 } 73 74 func (env *environ) gceInstances(ctx context.ProviderCallContext) ([]google.Instance, error) { 75 prefix := env.namespace.Prefix() 76 instances, err := env.gce.Instances(prefix, instStatuses...) 77 return instances, google.HandleCredentialError(errors.Trace(err), ctx) 78 } 79 80 // instances returns a list of all "alive" instances in the environment. 81 // This means only instances where the IDs match 82 // "juju-<env name>-machine-*". This is important because otherwise juju 83 // will see they are not tracked in state, assume they're stale/rogue, 84 // and shut them down. 85 func (env *environ) instances(ctx context.ProviderCallContext) ([]instances.Instance, error) { 86 gceInstances, err := env.gceInstances(ctx) 87 err = errors.Trace(err) 88 89 // Turn google.Instance values into *environInstance values, 90 // whether or not we got an error. 91 var results []instances.Instance 92 for _, base := range gceInstances { 93 // If we don't make a copy then the same pointer is used for the 94 // base of all resulting instances. 95 copied := base 96 inst := newInstance(&copied, env) 97 results = append(results, inst) 98 } 99 100 return results, err 101 } 102 103 // ControllerInstances returns the IDs of the instances corresponding 104 // to juju controllers. 105 func (env *environ) ControllerInstances(ctx context.ProviderCallContext, controllerUUID string) ([]instance.Id, error) { 106 instances, err := env.gceInstances(ctx) 107 if err != nil { 108 return nil, errors.Trace(err) 109 } 110 111 var results []instance.Id 112 for _, inst := range instances { 113 metadata := inst.Metadata() 114 if uuid, ok := metadata[tags.JujuController]; !ok || uuid != controllerUUID { 115 continue 116 } 117 isController, ok := metadata[tags.JujuIsController] 118 if ok && isController == "true" { 119 results = append(results, instance.Id(inst.ID)) 120 } 121 } 122 if len(results) == 0 { 123 return nil, environs.ErrNotBootstrapped 124 } 125 return results, nil 126 } 127 128 // AdoptResources is part of the Environ interface. 129 func (env *environ) AdoptResources(ctx context.ProviderCallContext, controllerUUID string, fromVersion version.Number) error { 130 instances, err := env.AllInstances(ctx) 131 if err != nil { 132 return errors.Annotate(err, "all instances") 133 } 134 135 var stringIds []string 136 for _, id := range instances { 137 stringIds = append(stringIds, string(id.Id())) 138 } 139 err = env.gce.UpdateMetadata(tags.JujuController, controllerUUID, stringIds...) 140 if err != nil { 141 return google.HandleCredentialError(errors.Trace(err), ctx) 142 } 143 return nil 144 } 145 146 // TODO(ericsnow) Turn into an interface. 147 type instPlacement struct { 148 Zone *google.AvailabilityZone 149 } 150 151 // parsePlacement extracts the availability zone from the placement 152 // string and returns it. If no zone is found there then an error is 153 // returned. 154 func (env *environ) parsePlacement(ctx context.ProviderCallContext, placement string) (*instPlacement, error) { 155 if placement == "" { 156 return nil, nil 157 } 158 159 pos := strings.IndexRune(placement, '=') 160 if pos == -1 { 161 return nil, errors.Errorf("unknown placement directive: %v", placement) 162 } 163 164 switch key, value := placement[:pos], placement[pos+1:]; key { 165 case "zone": 166 zone, err := env.availZoneUp(ctx, value) 167 if err != nil { 168 return nil, errors.Trace(err) 169 } 170 return &instPlacement{Zone: zone}, nil 171 } 172 return nil, errors.Errorf("unknown placement directive: %v", placement) 173 } 174 175 // checkInstanceType is used to ensure the the provided constraints 176 // specify a recognized instance type. 177 func checkInstanceType(cons constraints.Value) bool { 178 // Constraint has an instance-type constraint so let's see if it is valid. 179 for _, itype := range allInstanceTypes { 180 if itype.Name == *cons.InstanceType { 181 return true 182 } 183 } 184 return false 185 }