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