github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/provider/gce/environ_broker.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 "fmt" 8 9 "github.com/juju/errors" 10 "github.com/juju/utils" 11 jujuos "github.com/juju/utils/os" 12 "github.com/juju/utils/series" 13 14 "github.com/juju/juju/cloudconfig/instancecfg" 15 "github.com/juju/juju/cloudconfig/providerinit" 16 "github.com/juju/juju/constraints" 17 "github.com/juju/juju/environs" 18 "github.com/juju/juju/environs/imagemetadata" 19 "github.com/juju/juju/environs/instances" 20 "github.com/juju/juju/instance" 21 "github.com/juju/juju/provider/common" 22 "github.com/juju/juju/provider/gce/google" 23 "github.com/juju/juju/state/multiwatcher" 24 "github.com/juju/juju/tools" 25 ) 26 27 func isController(icfg *instancecfg.InstanceConfig) bool { 28 return multiwatcher.AnyJobNeedsState(icfg.Jobs...) 29 } 30 31 // MaintainInstance is specified in the InstanceBroker interface. 32 func (*environ) MaintainInstance(args environs.StartInstanceParams) error { 33 return nil 34 } 35 36 // StartInstance implements environs.InstanceBroker. 37 func (env *environ) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) { 38 // Start a new instance. 39 40 spec, err := buildInstanceSpec(env, args) 41 if err != nil { 42 return nil, errors.Trace(err) 43 } 44 45 if err := env.finishInstanceConfig(args, spec); err != nil { 46 return nil, errors.Trace(err) 47 } 48 49 raw, err := newRawInstance(env, args, spec) 50 if err != nil { 51 return nil, errors.Trace(err) 52 } 53 logger.Infof("started instance %q in zone %q", raw.ID, raw.ZoneName) 54 inst := newInstance(raw, env) 55 56 // Build the result. 57 hwc := getHardwareCharacteristics(env, spec, inst) 58 result := environs.StartInstanceResult{ 59 Instance: inst, 60 Hardware: hwc, 61 } 62 return &result, nil 63 } 64 65 var buildInstanceSpec = func(env *environ, args environs.StartInstanceParams) (*instances.InstanceSpec, error) { 66 return env.buildInstanceSpec(args) 67 } 68 69 var newRawInstance = func(env *environ, args environs.StartInstanceParams, spec *instances.InstanceSpec) (*google.Instance, error) { 70 return env.newRawInstance(args, spec) 71 } 72 73 var getHardwareCharacteristics = func(env *environ, spec *instances.InstanceSpec, inst *environInstance) *instance.HardwareCharacteristics { 74 return env.getHardwareCharacteristics(spec, inst) 75 } 76 77 // finishInstanceConfig updates args.InstanceConfig in place. Setting up 78 // the API, StateServing, and SSHkeys information. 79 func (env *environ) finishInstanceConfig(args environs.StartInstanceParams, spec *instances.InstanceSpec) error { 80 envTools, err := args.Tools.Match(tools.Filter{Arch: spec.Image.Arch}) 81 if err != nil { 82 return errors.Errorf("chosen architecture %v not present in %v", spec.Image.Arch, arches) 83 } 84 85 if err := args.InstanceConfig.SetTools(envTools); err != nil { 86 return errors.Trace(err) 87 } 88 return instancecfg.FinishInstanceConfig(args.InstanceConfig, env.Config()) 89 } 90 91 // buildInstanceSpec builds an instance spec from the provided args 92 // and returns it. This includes pulling the simplestreams data for the 93 // machine type, region, and other constraints. 94 func (env *environ) buildInstanceSpec(args environs.StartInstanceParams) (*instances.InstanceSpec, error) { 95 arches := args.Tools.Arches() 96 series := args.Tools.OneSeries() 97 spec, err := findInstanceSpec( 98 env, &instances.InstanceConstraint{ 99 Region: env.cloud.Region, 100 Series: series, 101 Arches: arches, 102 Constraints: args.Constraints, 103 }, 104 args.ImageMetadata, 105 ) 106 return spec, errors.Trace(err) 107 } 108 109 var findInstanceSpec = func( 110 env *environ, 111 ic *instances.InstanceConstraint, 112 imageMetadata []*imagemetadata.ImageMetadata, 113 ) (*instances.InstanceSpec, error) { 114 return env.findInstanceSpec(ic, imageMetadata) 115 } 116 117 // findInstanceSpec initializes a new instance spec for the given 118 // constraints and returns it. This only covers populating the 119 // initial data for the spec. 120 func (env *environ) findInstanceSpec( 121 ic *instances.InstanceConstraint, 122 imageMetadata []*imagemetadata.ImageMetadata, 123 ) (*instances.InstanceSpec, error) { 124 images := instances.ImageMetadataToImages(imageMetadata) 125 spec, err := instances.FindInstanceSpec(images, ic, allInstanceTypes) 126 return spec, errors.Trace(err) 127 } 128 129 // newRawInstance is where the new physical instance is actually 130 // provisioned, relative to the provided args and spec. Info for that 131 // low-level instance is returned. 132 func (env *environ) newRawInstance(args environs.StartInstanceParams, spec *instances.InstanceSpec) (*google.Instance, error) { 133 hostname, err := env.namespace.Hostname(args.InstanceConfig.MachineId) 134 if err != nil { 135 return nil, errors.Trace(err) 136 } 137 138 os, err := series.GetOSFromSeries(args.InstanceConfig.Series) 139 if err != nil { 140 return nil, errors.Trace(err) 141 } 142 143 metadata, err := getMetadata(args, os) 144 if err != nil { 145 return nil, errors.Trace(err) 146 } 147 tags := []string{ 148 env.globalFirewallName(), 149 hostname, 150 } 151 152 disks, err := getDisks( 153 spec, args.Constraints, args.InstanceConfig.Series, env.Config().UUID(), env.Config().ImageStream() == "daily", 154 ) 155 if err != nil { 156 return nil, errors.Trace(err) 157 } 158 159 // TODO(ericsnow) Use the env ID for the network name (instead of default)? 160 // TODO(ericsnow) Make the network name configurable? 161 // TODO(ericsnow) Support multiple networks? 162 // TODO(ericsnow) Use a different net interface name? Configurable? 163 instSpec := google.InstanceSpec{ 164 ID: hostname, 165 Type: spec.InstanceType.Name, 166 Disks: disks, 167 NetworkInterfaces: []string{"ExternalNAT"}, 168 Metadata: metadata, 169 Tags: tags, 170 // Network is omitted (left empty). 171 } 172 173 zones, err := env.parseAvailabilityZones(args) 174 if err != nil { 175 return nil, errors.Trace(err) 176 } 177 178 inst, err := env.gce.AddInstance(instSpec, zones...) 179 return inst, errors.Trace(err) 180 } 181 182 // getMetadata builds the raw "user-defined" metadata for the new 183 // instance (relative to the provided args) and returns it. 184 func getMetadata(args environs.StartInstanceParams, os jujuos.OSType) (map[string]string, error) { 185 userData, err := providerinit.ComposeUserData(args.InstanceConfig, nil, GCERenderer{}) 186 if err != nil { 187 return nil, errors.Annotate(err, "cannot make user data") 188 } 189 logger.Debugf("GCE user data; %d bytes", len(userData)) 190 191 metadata := make(map[string]string) 192 for tag, value := range args.InstanceConfig.Tags { 193 metadata[tag] = value 194 } 195 switch os { 196 case jujuos.Ubuntu: 197 // We store a gz snapshop of information that is used by 198 // cloud-init and unpacked in to the /var/lib/cloud/instances folder 199 // for the instance. Due to a limitation with GCE and binary blobs 200 // we base64 encode the data before storing it. 201 metadata[metadataKeyCloudInit] = string(userData) 202 // Valid encoding values are determined by the cloudinit GCE data source. 203 // See: http://cloudinit.readthedocs.org 204 metadata[metadataKeyEncoding] = "base64" 205 206 case jujuos.Windows: 207 metadata[metadataKeyWindowsUserdata] = string(userData) 208 209 validChars := append(utils.UpperAlpha, append(utils.LowerAlpha, utils.Digits...)...) 210 211 // The hostname must have maximum 15 characters 212 winHostname := "juju" + utils.RandomString(11, validChars) 213 metadata[metadataKeyWindowsSysprep] = fmt.Sprintf(winSetHostnameScript, winHostname) 214 default: 215 return nil, errors.Errorf("cannot pack metadata for os %s on the gce provider", os.String()) 216 } 217 218 return metadata, nil 219 } 220 221 // getDisks builds the raw spec for the disks that should be attached to 222 // the new instances and returns it. This will always include a root 223 // disk with characteristics determined by the provides args and 224 // constraints. 225 func getDisks(spec *instances.InstanceSpec, cons constraints.Value, ser, eUUID string, daily bool) ([]google.DiskSpec, error) { 226 size := common.MinRootDiskSizeGiB(ser) 227 if cons.RootDisk != nil && *cons.RootDisk > size { 228 size = common.MiBToGiB(*cons.RootDisk) 229 } 230 var imageURL string 231 os, err := series.GetOSFromSeries(ser) 232 if err != nil { 233 return nil, errors.Trace(err) 234 } 235 switch os { 236 case jujuos.Ubuntu: 237 if daily { 238 imageURL = ubuntuDailyImageBasePath 239 } else { 240 imageURL = ubuntuImageBasePath 241 } 242 case jujuos.Windows: 243 imageURL = windowsImageBasePath 244 default: 245 return nil, errors.Errorf("os %s is not supported on the gce provider", os.String()) 246 } 247 dSpec := google.DiskSpec{ 248 Series: ser, 249 SizeHintGB: size, 250 ImageURL: imageURL + spec.Image.Id, 251 Boot: true, 252 AutoDelete: true, 253 Description: eUUID, 254 } 255 if cons.RootDisk != nil && dSpec.TooSmall() { 256 msg := "Ignoring root-disk constraint of %dM because it is smaller than the GCE image size of %dG" 257 logger.Infof(msg, *cons.RootDisk, google.MinDiskSizeGB(ser)) 258 } 259 return []google.DiskSpec{dSpec}, nil 260 } 261 262 // getHardwareCharacteristics compiles hardware-related details about 263 // the given instance and relative to the provided spec and returns it. 264 func (env *environ) getHardwareCharacteristics(spec *instances.InstanceSpec, inst *environInstance) *instance.HardwareCharacteristics { 265 rootDiskMB := inst.base.RootDiskGB() * 1024 266 hwc := instance.HardwareCharacteristics{ 267 Arch: &spec.Image.Arch, 268 Mem: &spec.InstanceType.Mem, 269 CpuCores: &spec.InstanceType.CpuCores, 270 CpuPower: spec.InstanceType.CpuPower, 271 RootDisk: &rootDiskMB, 272 AvailabilityZone: &inst.base.ZoneName, 273 // Tags: not supported in GCE. 274 } 275 return &hwc 276 } 277 278 // AllInstances implements environs.InstanceBroker. 279 func (env *environ) AllInstances() ([]instance.Instance, error) { 280 instances, err := getInstances(env) 281 return instances, errors.Trace(err) 282 } 283 284 // StopInstances implements environs.InstanceBroker. 285 func (env *environ) StopInstances(instances ...instance.Id) error { 286 var ids []string 287 for _, id := range instances { 288 ids = append(ids, string(id)) 289 } 290 291 prefix := env.namespace.Prefix() 292 err := env.gce.RemoveInstances(prefix, ids...) 293 return errors.Trace(err) 294 }