github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/provider/joyent/environ_instance.go (about) 1 // Copyright 2013 Joyent Inc. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package joyent 5 6 import ( 7 "fmt" 8 "strings" 9 "sync" 10 "time" 11 12 "github.com/joyent/gocommon/client" 13 "github.com/joyent/gosdc/cloudapi" 14 "github.com/juju/errors" 15 "github.com/juju/names" 16 "github.com/juju/utils" 17 18 "github.com/juju/juju/cloudinit" 19 "github.com/juju/juju/constraints" 20 "github.com/juju/juju/environs" 21 "github.com/juju/juju/environs/imagemetadata" 22 "github.com/juju/juju/environs/instances" 23 "github.com/juju/juju/environs/simplestreams" 24 "github.com/juju/juju/instance" 25 "github.com/juju/juju/juju/arch" 26 "github.com/juju/juju/provider/common" 27 "github.com/juju/juju/state/multiwatcher" 28 "github.com/juju/juju/tools" 29 ) 30 31 var ( 32 vTypeSmartmachine = "smartmachine" 33 vTypeVirtualmachine = "kvm" 34 signedImageDataOnly = false 35 ) 36 37 type joyentCompute struct { 38 sync.Mutex 39 ecfg *environConfig 40 cloudapi *cloudapi.Client 41 } 42 43 func newCompute(cfg *environConfig) (*joyentCompute, error) { 44 creds, err := credentials(cfg) 45 if err != nil { 46 return nil, err 47 } 48 client := client.NewClient(cfg.sdcUrl(), cloudapi.DefaultAPIVersion, creds, &logger) 49 50 return &joyentCompute{ 51 ecfg: cfg, 52 cloudapi: cloudapi.New(client)}, nil 53 } 54 55 func (env *joyentEnviron) machineFullName(machineId string) string { 56 return fmt.Sprintf("juju-%s-%s", env.Config().Name(), names.NewMachineTag(machineId)) 57 } 58 59 var unsupportedConstraints = []string{ 60 constraints.CpuPower, 61 constraints.Tags, 62 } 63 64 // ConstraintsValidator is defined on the Environs interface. 65 func (env *joyentEnviron) ConstraintsValidator() (constraints.Validator, error) { 66 validator := constraints.NewValidator() 67 validator.RegisterUnsupported(unsupportedConstraints) 68 supportedArches, err := env.SupportedArchitectures() 69 if err != nil { 70 return nil, err 71 } 72 validator.RegisterVocabulary(constraints.Arch, supportedArches) 73 packages, err := env.compute.cloudapi.ListPackages(nil) 74 if err != nil { 75 return nil, err 76 } 77 instTypeNames := make([]string, len(packages)) 78 for i, pkg := range packages { 79 instTypeNames[i] = pkg.Name 80 } 81 validator.RegisterVocabulary(constraints.InstanceType, instTypeNames) 82 return validator, nil 83 } 84 85 func (env *joyentEnviron) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) { 86 87 if args.MachineConfig.HasNetworks() { 88 return nil, errors.New("starting instances with networks is not supported yet") 89 } 90 91 series := args.Tools.OneSeries() 92 arches := args.Tools.Arches() 93 spec, err := env.FindInstanceSpec(&instances.InstanceConstraint{ 94 Region: env.Ecfg().Region(), 95 Series: series, 96 Arches: arches, 97 Constraints: args.Constraints, 98 }) 99 if err != nil { 100 return nil, err 101 } 102 tools, err := args.Tools.Match(tools.Filter{Arch: spec.Image.Arch}) 103 if err != nil { 104 return nil, errors.Errorf("chosen architecture %v not present in %v", spec.Image.Arch, arches) 105 } 106 107 args.MachineConfig.Tools = tools[0] 108 109 if err := environs.FinishMachineConfig(args.MachineConfig, env.Config()); err != nil { 110 return nil, err 111 } 112 113 // This is a hack that ensures that instances can communicate over 114 // the internal network. Joyent sometimes gives instances 115 // different 10.x.x.x/21 networks and adding this route allows 116 // them to talk despite this. See: 117 // https://bugs.launchpad.net/juju-core/+bug/1401130 118 cloudcfg := cloudinit.New() 119 ifupScript := ` 120 #!/bin/bash 121 122 # These guards help to ensure that this hack only runs if Joyent's 123 # internal network still works as it does at time of writing. 124 [ "$IFACE" == "eth1" ] || [ "$IFACE" == "--all" ] || exit 0 125 /sbin/ip -4 --oneline addr show dev eth1 | fgrep --quiet " inet 10." || exit 0 126 127 /sbin/ip route add 10.0.0.0/8 dev eth1 128 `[1:] 129 cloudcfg.AddBootTextFile("/etc/network/if-up.d/joyent", ifupScript, 0755) 130 131 userData, err := environs.ComposeUserData(args.MachineConfig, cloudcfg) 132 if err != nil { 133 return nil, errors.Annotate(err, "cannot make user data") 134 } 135 136 // Unzipping as Joyent API expects it as string 137 userData, err = utils.Gunzip(userData) 138 if err != nil { 139 return nil, errors.Annotate(err, "cannot make user data") 140 } 141 logger.Debugf("joyent user data: %d bytes", len(userData)) 142 143 var machine *cloudapi.Machine 144 machine, err = env.compute.cloudapi.CreateMachine(cloudapi.CreateMachineOpts{ 145 //Name: env.machineFullName(machineConf.MachineId), 146 Package: spec.InstanceType.Name, 147 Image: spec.Image.Id, 148 Metadata: map[string]string{"metadata.cloud-init:user-data": string(userData)}, 149 Tags: map[string]string{"tag.group": "juju", "tag.env": env.Config().Name()}, 150 }) 151 if err != nil { 152 return nil, errors.Annotate(err, "cannot create instances") 153 } 154 machineId := machine.Id 155 156 logger.Infof("provisioning instance %q", machineId) 157 158 machine, err = env.compute.cloudapi.GetMachine(machineId) 159 if err != nil { 160 return nil, errors.Annotate(err, "cannot start instances") 161 } 162 163 // wait for machine to start 164 for !strings.EqualFold(machine.State, "running") { 165 time.Sleep(1 * time.Second) 166 167 machine, err = env.compute.cloudapi.GetMachine(machineId) 168 if err != nil { 169 return nil, errors.Annotate(err, "cannot start instances") 170 } 171 } 172 173 logger.Infof("started instance %q", machineId) 174 175 inst := &joyentInstance{ 176 machine: machine, 177 env: env, 178 } 179 180 if multiwatcher.AnyJobNeedsState(args.MachineConfig.Jobs...) { 181 if err := common.AddStateInstance(env.Storage(), inst.Id()); err != nil { 182 logger.Errorf("could not record instance in provider-state: %v", err) 183 } 184 } 185 186 disk64 := uint64(machine.Disk) 187 hc := instance.HardwareCharacteristics{ 188 Arch: &spec.Image.Arch, 189 Mem: &spec.InstanceType.Mem, 190 CpuCores: &spec.InstanceType.CpuCores, 191 CpuPower: spec.InstanceType.CpuPower, 192 RootDisk: &disk64, 193 } 194 195 return &environs.StartInstanceResult{ 196 Instance: inst, 197 Hardware: &hc, 198 }, nil 199 } 200 201 func (env *joyentEnviron) AllInstances() ([]instance.Instance, error) { 202 instances := []instance.Instance{} 203 204 filter := cloudapi.NewFilter() 205 filter.Set("tag.group", "juju") 206 filter.Set("tag.env", env.Config().Name()) 207 208 machines, err := env.compute.cloudapi.ListMachines(filter) 209 if err != nil { 210 return nil, errors.Annotate(err, "cannot retrieve instances") 211 } 212 213 for _, m := range machines { 214 if strings.EqualFold(m.State, "provisioning") || strings.EqualFold(m.State, "running") { 215 copy := m 216 instances = append(instances, &joyentInstance{machine: ©, env: env}) 217 } 218 } 219 220 return instances, nil 221 } 222 223 func (env *joyentEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) { 224 if len(ids) == 0 { 225 return nil, nil 226 } 227 228 logger.Debugf("Looking for instances %q", ids) 229 230 instances := make([]instance.Instance, len(ids)) 231 found := 0 232 233 allInstances, err := env.AllInstances() 234 if err != nil { 235 return nil, err 236 } 237 238 for i, id := range ids { 239 for _, instance := range allInstances { 240 if instance.Id() == id { 241 instances[i] = instance 242 found++ 243 } 244 } 245 } 246 247 logger.Debugf("Found %d instances %q", found, instances) 248 249 if found == 0 { 250 return nil, environs.ErrNoInstances 251 } else if found < len(ids) { 252 return instances, environs.ErrPartialInstances 253 } 254 255 return instances, nil 256 } 257 258 func (env *joyentEnviron) StopInstances(ids ...instance.Id) error { 259 // Remove all the instances in parallel so that we incur less round-trips. 260 var wg sync.WaitGroup 261 //var err error 262 wg.Add(len(ids)) 263 errc := make(chan error, len(ids)) 264 for _, id := range ids { 265 id := id // copy to new free var for closure 266 go func() { 267 defer wg.Done() 268 if err := env.stopInstance(string(id)); err != nil { 269 errc <- err 270 } 271 }() 272 } 273 wg.Wait() 274 select { 275 case err := <-errc: 276 return errors.Annotate(err, "cannot stop all instances") 277 default: 278 } 279 return common.RemoveStateInstances(env.Storage(), ids...) 280 } 281 282 func (env *joyentEnviron) stopInstance(id string) error { 283 // wait for machine to be running 284 // if machine is still provisioning stop will fail 285 for !env.pollMachineState(id, "running") { 286 time.Sleep(1 * time.Second) 287 } 288 289 err := env.compute.cloudapi.StopMachine(id) 290 if err != nil { 291 return errors.Annotatef(err, "cannot stop instance %v", id) 292 } 293 294 // wait for machine to be stopped 295 for !env.pollMachineState(id, "stopped") { 296 time.Sleep(1 * time.Second) 297 } 298 299 err = env.compute.cloudapi.DeleteMachine(id) 300 if err != nil { 301 return errors.Annotatef(err, "cannot delete instance %v", id) 302 } 303 304 return nil 305 } 306 307 func (env *joyentEnviron) pollMachineState(machineId, state string) bool { 308 machineConfig, err := env.compute.cloudapi.GetMachine(machineId) 309 if err != nil { 310 return false 311 } 312 return strings.EqualFold(machineConfig.State, state) 313 } 314 315 func (env *joyentEnviron) listInstanceTypes() ([]instances.InstanceType, error) { 316 packages, err := env.compute.cloudapi.ListPackages(nil) 317 if err != nil { 318 return nil, err 319 } 320 allInstanceTypes := []instances.InstanceType{} 321 for _, pkg := range packages { 322 instanceType := instances.InstanceType{ 323 Id: pkg.Id, 324 Name: pkg.Name, 325 Arches: []string{arch.AMD64}, 326 Mem: uint64(pkg.Memory), 327 CpuCores: uint64(pkg.VCPUs), 328 RootDisk: uint64(pkg.Disk * 1024), 329 VirtType: &vTypeVirtualmachine, 330 } 331 allInstanceTypes = append(allInstanceTypes, instanceType) 332 } 333 return allInstanceTypes, nil 334 } 335 336 // FindInstanceSpec returns an InstanceSpec satisfying the supplied instanceConstraint. 337 func (env *joyentEnviron) FindInstanceSpec(ic *instances.InstanceConstraint) (*instances.InstanceSpec, error) { 338 allInstanceTypes, err := env.listInstanceTypes() 339 if err != nil { 340 return nil, err 341 } 342 imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 343 CloudSpec: simplestreams.CloudSpec{ic.Region, env.Ecfg().SdcUrl()}, 344 Series: []string{ic.Series}, 345 Arches: ic.Arches, 346 }) 347 sources, err := environs.ImageMetadataSources(env) 348 if err != nil { 349 return nil, err 350 } 351 352 matchingImages, _, err := imagemetadata.Fetch(sources, imageConstraint, signedImageDataOnly) 353 if err != nil { 354 return nil, err 355 } 356 images := instances.ImageMetadataToImages(matchingImages) 357 spec, err := instances.FindInstanceSpec(images, ic, allInstanceTypes) 358 if err != nil { 359 return nil, err 360 } 361 return spec, nil 362 }