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