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