github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/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/constraints" 19 "github.com/juju/juju/environs" 20 "github.com/juju/juju/environs/imagemetadata" 21 "github.com/juju/juju/environs/instances" 22 "github.com/juju/juju/environs/simplestreams" 23 "github.com/juju/juju/instance" 24 "github.com/juju/juju/juju/arch" 25 "github.com/juju/juju/network" 26 "github.com/juju/juju/tools" 27 ) 28 29 var ( 30 vTypeSmartmachine = "smartmachine" 31 vTypeVirtualmachine = "kvm" 32 signedImageDataOnly = false 33 ) 34 35 type joyentCompute struct { 36 sync.Mutex 37 ecfg *environConfig 38 cloudapi *cloudapi.Client 39 } 40 41 func newCompute(cfg *environConfig) (*joyentCompute, error) { 42 creds, err := credentials(cfg) 43 if err != nil { 44 return nil, err 45 } 46 client := client.NewClient(cfg.sdcUrl(), cloudapi.DefaultAPIVersion, creds, &logger) 47 48 return &joyentCompute{ 49 ecfg: cfg, 50 cloudapi: cloudapi.New(client)}, nil 51 } 52 53 func (env *joyentEnviron) machineFullName(machineId string) string { 54 return fmt.Sprintf("juju-%s-%s", env.Name(), names.NewMachineTag(machineId)) 55 } 56 57 var unsupportedConstraints = []string{ 58 constraints.CpuPower, 59 constraints.Tags, 60 } 61 62 // ConstraintsValidator is defined on the Environs interface. 63 func (env *joyentEnviron) ConstraintsValidator() (constraints.Validator, error) { 64 validator := constraints.NewValidator() 65 validator.RegisterUnsupported(unsupportedConstraints) 66 supportedArches, err := env.SupportedArchitectures() 67 if err != nil { 68 return nil, err 69 } 70 validator.RegisterVocabulary(constraints.Arch, supportedArches) 71 packages, err := env.compute.cloudapi.ListPackages(nil) 72 if err != nil { 73 return nil, err 74 } 75 instTypeNames := make([]string, len(packages)) 76 for i, pkg := range packages { 77 instTypeNames[i] = pkg.Name 78 } 79 validator.RegisterVocabulary(constraints.InstanceType, instTypeNames) 80 return validator, nil 81 } 82 83 func (env *joyentEnviron) StartInstance(args environs.StartInstanceParams) (instance.Instance, *instance.HardwareCharacteristics, []network.Info, error) { 84 85 if args.MachineConfig.HasNetworks() { 86 return nil, nil, nil, fmt.Errorf("starting instances with networks is not supported yet.") 87 } 88 89 series := args.Tools.OneSeries() 90 arches := args.Tools.Arches() 91 spec, err := env.FindInstanceSpec(&instances.InstanceConstraint{ 92 Region: env.Ecfg().Region(), 93 Series: series, 94 Arches: arches, 95 Constraints: args.Constraints, 96 }) 97 if err != nil { 98 return nil, nil, nil, err 99 } 100 tools, err := args.Tools.Match(tools.Filter{Arch: spec.Image.Arch}) 101 if err != nil { 102 return nil, nil, nil, fmt.Errorf("chosen architecture %v not present in %v", spec.Image.Arch, arches) 103 } 104 105 args.MachineConfig.Tools = tools[0] 106 107 if err := environs.FinishMachineConfig(args.MachineConfig, env.Config(), args.Constraints); err != nil { 108 return nil, nil, nil, err 109 } 110 userData, err := environs.ComposeUserData(args.MachineConfig, nil) 111 if err != nil { 112 return nil, nil, nil, fmt.Errorf("cannot make user data: %v", err) 113 } 114 115 // Unzipping as Joyent API expects it as string 116 userData, err = utils.Gunzip(userData) 117 if err != nil { 118 return nil, nil, nil, fmt.Errorf("cannot make user data: %v", err) 119 } 120 logger.Debugf("joyent user data: %d bytes", len(userData)) 121 122 var machine *cloudapi.Machine 123 machine, err = env.compute.cloudapi.CreateMachine(cloudapi.CreateMachineOpts{ 124 //Name: env.machineFullName(machineConf.MachineId), 125 Package: spec.InstanceType.Name, 126 Image: spec.Image.Id, 127 Metadata: map[string]string{"metadata.cloud-init:user-data": string(userData)}, 128 Tags: map[string]string{"tag.group": "juju", "tag.env": env.Name()}, 129 }) 130 if err != nil { 131 return nil, nil, nil, fmt.Errorf("cannot create instances: %v", err) 132 } 133 machineId := machine.Id 134 135 logger.Infof("provisioning instance %q", machineId) 136 137 machine, err = env.compute.cloudapi.GetMachine(machineId) 138 if err != nil { 139 return nil, nil, nil, fmt.Errorf("cannot start instances: %v", err) 140 } 141 142 // wait for machine to start 143 for !strings.EqualFold(machine.State, "running") { 144 time.Sleep(1 * time.Second) 145 146 machine, err = env.compute.cloudapi.GetMachine(machineId) 147 if err != nil { 148 return nil, nil, nil, fmt.Errorf("cannot start instances: %v", err) 149 } 150 } 151 152 logger.Infof("started instance %q", machineId) 153 154 inst := &joyentInstance{ 155 machine: machine, 156 env: env, 157 } 158 159 disk64 := uint64(machine.Disk) 160 hc := instance.HardwareCharacteristics{ 161 Arch: &spec.Image.Arch, 162 Mem: &spec.InstanceType.Mem, 163 CpuCores: &spec.InstanceType.CpuCores, 164 CpuPower: spec.InstanceType.CpuPower, 165 RootDisk: &disk64, 166 } 167 168 return inst, &hc, nil, nil 169 } 170 171 func (env *joyentEnviron) AllInstances() ([]instance.Instance, error) { 172 instances := []instance.Instance{} 173 174 filter := cloudapi.NewFilter() 175 filter.Set("tag.group", "juju") 176 filter.Set("tag.env", env.Name()) 177 178 machines, err := env.compute.cloudapi.ListMachines(filter) 179 if err != nil { 180 return nil, fmt.Errorf("cannot retrieve instances: %v", err) 181 } 182 183 for _, m := range machines { 184 if strings.EqualFold(m.State, "provisioning") || strings.EqualFold(m.State, "running") { 185 copy := m 186 instances = append(instances, &joyentInstance{machine: ©, env: env}) 187 } 188 } 189 190 return instances, nil 191 } 192 193 func (env *joyentEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) { 194 if len(ids) == 0 { 195 return nil, nil 196 } 197 198 logger.Debugf("Looking for instances %q", ids) 199 200 instances := make([]instance.Instance, len(ids)) 201 found := 0 202 203 allInstances, err := env.AllInstances() 204 if err != nil { 205 return nil, err 206 } 207 208 for i, id := range ids { 209 for _, instance := range allInstances { 210 if instance.Id() == id { 211 instances[i] = instance 212 found++ 213 } 214 } 215 } 216 217 logger.Debugf("Found %d instances %q", found, instances) 218 219 if found == 0 { 220 return nil, environs.ErrNoInstances 221 } else if found < len(ids) { 222 return instances, environs.ErrPartialInstances 223 } 224 225 return instances, nil 226 } 227 228 // AllocateAddress requests a new address to be allocated for the 229 // given instance on the given network. This is not implemented on the 230 // Joyent provider yet. 231 func (*joyentEnviron) AllocateAddress(_ instance.Id, _ network.Id) (network.Address, error) { 232 return network.Address{}, errors.NotImplementedf("AllocateAddress") 233 } 234 235 // ListNetworks returns basic information about all networks known by 236 // the provider for the environment. They may be unknown to juju yet 237 // (i.e. when called initially or when a new network was created). 238 // This is not implemented on the Joyent provider yet. 239 func (*joyentEnviron) ListNetworks() ([]network.BasicInfo, error) { 240 return nil, errors.NotImplementedf("ListNetworks") 241 } 242 243 func (env *joyentEnviron) StopInstances(ids ...instance.Id) error { 244 // Remove all the instances in parallel so that we incur less round-trips. 245 var wg sync.WaitGroup 246 //var err error 247 wg.Add(len(ids)) 248 errc := make(chan error, len(ids)) 249 for _, id := range ids { 250 id := id // copy to new free var for closure 251 go func() { 252 defer wg.Done() 253 if err := env.stopInstance(string(id)); err != nil { 254 errc <- err 255 } 256 }() 257 } 258 wg.Wait() 259 select { 260 case err := <-errc: 261 return fmt.Errorf("cannot stop all instances: %v", err) 262 default: 263 } 264 265 return nil 266 } 267 268 func (env *joyentEnviron) stopInstance(id string) error { 269 // wait for machine to be running 270 // if machine is still provisioning stop will fail 271 for !env.pollMachineState(id, "running") { 272 time.Sleep(1 * time.Second) 273 } 274 275 err := env.compute.cloudapi.StopMachine(id) 276 if err != nil { 277 return fmt.Errorf("cannot stop instance %s: %v", id, err) 278 } 279 280 // wait for machine to be stopped 281 for !env.pollMachineState(id, "stopped") { 282 time.Sleep(1 * time.Second) 283 } 284 285 err = env.compute.cloudapi.DeleteMachine(id) 286 if err != nil { 287 return fmt.Errorf("cannot delete instance %s: %v", id, err) 288 } 289 290 return nil 291 } 292 293 func (env *joyentEnviron) pollMachineState(machineId, state string) bool { 294 machineConfig, err := env.compute.cloudapi.GetMachine(machineId) 295 if err != nil { 296 return false 297 } 298 return strings.EqualFold(machineConfig.State, state) 299 } 300 301 func (env *joyentEnviron) listInstanceTypes() ([]instances.InstanceType, error) { 302 packages, err := env.compute.cloudapi.ListPackages(nil) 303 if err != nil { 304 return nil, err 305 } 306 allInstanceTypes := []instances.InstanceType{} 307 for _, pkg := range packages { 308 instanceType := instances.InstanceType{ 309 Id: pkg.Id, 310 Name: pkg.Name, 311 Arches: []string{arch.AMD64}, 312 Mem: uint64(pkg.Memory), 313 CpuCores: uint64(pkg.VCPUs), 314 RootDisk: uint64(pkg.Disk * 1024), 315 VirtType: &vTypeVirtualmachine, 316 } 317 allInstanceTypes = append(allInstanceTypes, instanceType) 318 } 319 return allInstanceTypes, nil 320 } 321 322 // FindInstanceSpec returns an InstanceSpec satisfying the supplied instanceConstraint. 323 func (env *joyentEnviron) FindInstanceSpec(ic *instances.InstanceConstraint) (*instances.InstanceSpec, error) { 324 allInstanceTypes, err := env.listInstanceTypes() 325 if err != nil { 326 return nil, err 327 } 328 imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 329 CloudSpec: simplestreams.CloudSpec{ic.Region, env.Ecfg().SdcUrl()}, 330 Series: []string{ic.Series}, 331 Arches: ic.Arches, 332 }) 333 sources, err := imagemetadata.GetMetadataSources(env) 334 if err != nil { 335 return nil, err 336 } 337 338 matchingImages, _, err := imagemetadata.Fetch(sources, simplestreams.DefaultIndexPath, imageConstraint, signedImageDataOnly) 339 if err != nil { 340 return nil, err 341 } 342 images := instances.ImageMetadataToImages(matchingImages) 343 spec, err := instances.FindInstanceSpec(images, ic, allInstanceTypes) 344 if err != nil { 345 return nil, err 346 } 347 return spec, nil 348 }