~juju-qa/ubuntu/yakkety/juju/2.0-rc3-again

« back to all changes in this revision

Viewing changes to src/launchpad.net/juju-core/environs/openstack/provider.go

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2013-04-24 22:34:47 UTC
  • Revision ID: package-import@ubuntu.com-20130424223447-f0qdji7ubnyo0s71
Tags: upstream-1.10.0.1
ImportĀ upstreamĀ versionĀ 1.10.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Stub provider for OpenStack, using goose will be implemented here
 
2
 
 
3
package openstack
 
4
 
 
5
import (
 
6
        "encoding/json"
 
7
        "errors"
 
8
        "fmt"
 
9
        "io/ioutil"
 
10
        "launchpad.net/goose/client"
 
11
        gooseerrors "launchpad.net/goose/errors"
 
12
        "launchpad.net/goose/identity"
 
13
        "launchpad.net/goose/nova"
 
14
        "launchpad.net/goose/swift"
 
15
        "launchpad.net/juju-core/constraints"
 
16
        "launchpad.net/juju-core/environs"
 
17
        "launchpad.net/juju-core/environs/cloudinit"
 
18
        "launchpad.net/juju-core/environs/config"
 
19
        "launchpad.net/juju-core/environs/tools"
 
20
        "launchpad.net/juju-core/log"
 
21
        "launchpad.net/juju-core/state"
 
22
        "launchpad.net/juju-core/state/api"
 
23
        "launchpad.net/juju-core/state/api/params"
 
24
        "launchpad.net/juju-core/utils"
 
25
        "net/http"
 
26
        "strconv"
 
27
        "strings"
 
28
        "sync"
 
29
        "time"
 
30
)
 
31
 
 
32
const mgoPort = 37017
 
33
const apiPort = 17070
 
34
 
 
35
var mgoPortSuffix = fmt.Sprintf(":%d", mgoPort)
 
36
var apiPortSuffix = fmt.Sprintf(":%d", apiPort)
 
37
 
 
38
type environProvider struct{}
 
39
 
 
40
var _ environs.EnvironProvider = (*environProvider)(nil)
 
41
 
 
42
var providerInstance environProvider
 
43
 
 
44
// A request may fail to due "eventual consistency" semantics, which
 
45
// should resolve fairly quickly.  A request may also fail due to a slow
 
46
// state transition (for instance an instance taking a while to release
 
47
// a security group after termination).  The former failure mode is
 
48
// dealt with by shortAttempt, the latter by longAttempt.
 
49
var shortAttempt = utils.AttemptStrategy{
 
50
        Total: 10 * time.Second, // it seems Nova needs more time than EC2
 
51
        Delay: 200 * time.Millisecond,
 
52
}
 
53
 
 
54
var longAttempt = utils.AttemptStrategy{
 
55
        Total: 3 * time.Minute,
 
56
        Delay: 1 * time.Second,
 
57
}
 
58
 
 
59
func init() {
 
60
        environs.RegisterProvider("openstack", environProvider{})
 
61
}
 
62
 
 
63
func (p environProvider) BoilerplateConfig() string {
 
64
        return `
 
65
## https://juju.ubuntu.com/get-started/openstack/
 
66
openstack:
 
67
  type: openstack
 
68
  # Specifies whether the use of a floating IP address is required to give the nodes
 
69
  # a public IP address. Some installations assign public IP addresses by default without
 
70
  # requiring a floating IP address.
 
71
  # use-floating-ip: false
 
72
  admin-secret: {{rand}}
 
73
  # Globally unique swift bucket name
 
74
  control-bucket: juju-{{rand}}
 
75
  # Usually set via the env variable OS_AUTH_URL, but can be specified here
 
76
  # auth-url: https://yourkeystoneurl:443/v2.0/
 
77
  # override if your workstation is running a different series to which you are deploying
 
78
  # default-series: precise
 
79
  default-image-id: c876e5fe-abb0-41f0-8f29-f0b47481f523
 
80
  default-instance-type: "m1.small"
 
81
  # The following are used for userpass authentication (the default)
 
82
  auth-mode: userpass
 
83
  # Usually set via the env variable OS_USERNAME, but can be specified here
 
84
  # username: <your username>
 
85
  # Usually set via the env variable OS_PASSWORD, but can be specified here
 
86
  # password: <secret>
 
87
  # Usually set via the env variable OS_TENANT_NAME, but can be specified here
 
88
  # tenant-name: <your tenant name>
 
89
  # Usually set via the env variable OS_REGION_NAME, but can be specified here
 
90
  # region: <your region>
 
91
 
 
92
## https://juju.ubuntu.com/get-started/hp-cloud/
 
93
hpcloud:
 
94
  type: openstack
 
95
  # Specifies whether the use of a floating IP address is required to give the nodes
 
96
  # a public IP address. Some installations assign public IP addresses by default without
 
97
  # requiring a floating IP address.
 
98
  use-floating-ip: false
 
99
  admin-secret: {{rand}}
 
100
  # Globally unique swift bucket name
 
101
  control-bucket: juju-{{rand}}
 
102
  # Not required if env variable OS_AUTH_URL is set
 
103
  auth-url: https://yourkeystoneurl:35357/v2.0/
 
104
  # override if your workstation is running a different series to which you are deploying
 
105
  # default-series: precise
 
106
  default-image-id: "75845"
 
107
  default-instance-type: "standard.xsmall"
 
108
  # The following are used for userpass authentication (the default)
 
109
  auth-mode: userpass
 
110
  # Usually set via the env variable OS_USERNAME, but can be specified here
 
111
  # username: <your username>
 
112
  # Usually set via the env variable OS_PASSWORD, but can be specified here
 
113
  # password: <secret>
 
114
  # Usually set via the env variable OS_TENANT_NAME, but can be specified here
 
115
  # tenant-name: <your tenant name>
 
116
  # Usually set via the env variable OS_REGION_NAME, but can be specified here
 
117
  # region: <your region>
 
118
  # The following are used for keypair authentication
 
119
  # auth-mode: keypair
 
120
  # Usually set via the env variable AWS_ACCESS_KEY_ID, but can be specified here
 
121
  # access-key: <secret>
 
122
  # Usually set via the env variable AWS_SECRET_ACCESS_KEY, but can be specified here
 
123
  # secret-key: <secret>
 
124
 
 
125
`[1:]
 
126
}
 
127
 
 
128
func (p environProvider) Open(cfg *config.Config) (environs.Environ, error) {
 
129
        log.Infof("environs/openstack: opening environment %q", cfg.Name())
 
130
        e := new(environ)
 
131
        err := e.SetConfig(cfg)
 
132
        if err != nil {
 
133
                return nil, err
 
134
        }
 
135
        return e, nil
 
136
}
 
137
 
 
138
func (p environProvider) SecretAttrs(cfg *config.Config) (map[string]interface{}, error) {
 
139
        m := make(map[string]interface{})
 
140
        ecfg, err := providerInstance.newConfig(cfg)
 
141
        if err != nil {
 
142
                return nil, err
 
143
        }
 
144
        m["username"] = ecfg.username()
 
145
        m["password"] = ecfg.password()
 
146
        m["tenant-name"] = ecfg.tenantName()
 
147
        return m, nil
 
148
}
 
149
 
 
150
func (p environProvider) PublicAddress() (string, error) {
 
151
        if addr, err := fetchMetadata("public-ipv4"); err != nil {
 
152
                return "", err
 
153
        } else if addr != "" {
 
154
                return addr, nil
 
155
        }
 
156
        return p.PrivateAddress()
 
157
}
 
158
 
 
159
func (p environProvider) PrivateAddress() (string, error) {
 
160
        return fetchMetadata("local-ipv4")
 
161
}
 
162
 
 
163
func (p environProvider) InstanceId() (state.InstanceId, error) {
 
164
        str, err := fetchInstanceUUID()
 
165
        if err != nil {
 
166
                str, err = fetchLegacyId()
 
167
        }
 
168
        return state.InstanceId(str), err
 
169
}
 
170
 
 
171
// metadataHost holds the address of the instance metadata service.
 
172
// It is a variable so that tests can change it to refer to a local
 
173
// server when needed.
 
174
var metadataHost = "http://169.254.169.254"
 
175
 
 
176
// fetchMetadata fetches a single atom of data from the openstack instance metadata service.
 
177
// http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html
 
178
// (the same specs is implemented in ec2, hence the reference)
 
179
func fetchMetadata(name string) (value string, err error) {
 
180
        uri := fmt.Sprintf("%s/latest/meta-data/%s", metadataHost, name)
 
181
        data, err := retryGet(uri)
 
182
        if err != nil {
 
183
                return "", err
 
184
        }
 
185
        return strings.TrimSpace(string(data)), nil
 
186
}
 
187
 
 
188
// fetchInstanceUUID fetches the openstack instance UUID, which is not at all
 
189
// the same thing as the "instance-id" in the ec2-style metadata. This only
 
190
// works on openstack Folsom or later.
 
191
func fetchInstanceUUID() (string, error) {
 
192
        uri := fmt.Sprintf("%s/openstack/2012-08-10/meta_data.json", metadataHost)
 
193
        data, err := retryGet(uri)
 
194
        if err != nil {
 
195
                return "", err
 
196
        }
 
197
        var uuid struct {
 
198
                Uuid string
 
199
        }
 
200
        if err := json.Unmarshal(data, &uuid); err != nil {
 
201
                return "", err
 
202
        }
 
203
        if uuid.Uuid == "" {
 
204
                return "", fmt.Errorf("no instance UUID found")
 
205
        }
 
206
        return uuid.Uuid, nil
 
207
}
 
208
 
 
209
// fetchLegacyId fetches the openstack numeric instance Id, which is derived
 
210
// from the "instance-id" in the ec2-style metadata. The ec2 id contains
 
211
// the numeric instance id encoded as hex with a "i-" prefix.
 
212
// This numeric id is required for older versions of Openstack which do
 
213
// not yet support providing UUID's via the metadata. HP Cloud is one such case.
 
214
// Even though using the numeric id is deprecated in favour of using UUID, where
 
215
// UUID is not yet supported, we need to revert to numeric id.
 
216
func fetchLegacyId() (string, error) {
 
217
        instId, err := fetchMetadata("instance-id")
 
218
        if err != nil {
 
219
                return "", err
 
220
        }
 
221
        if strings.Index(instId, "i-") >= 0 {
 
222
                hex := strings.SplitAfter(instId, "i-")[1]
 
223
                id, err := strconv.ParseInt("0x"+hex, 0, 32)
 
224
                if err != nil {
 
225
                        return "", err
 
226
                }
 
227
                instId = fmt.Sprintf("%d", id)
 
228
        }
 
229
        return instId, nil
 
230
}
 
231
 
 
232
func retryGet(uri string) (data []byte, err error) {
 
233
        for a := shortAttempt.Start(); a.Next(); {
 
234
                var resp *http.Response
 
235
                resp, err = http.Get(uri)
 
236
                if err != nil {
 
237
                        continue
 
238
                }
 
239
                defer resp.Body.Close()
 
240
                if resp.StatusCode != http.StatusOK {
 
241
                        err = fmt.Errorf("bad http response %v", resp.Status)
 
242
                        continue
 
243
                }
 
244
                var data []byte
 
245
                data, err = ioutil.ReadAll(resp.Body)
 
246
                if err != nil {
 
247
                        continue
 
248
                }
 
249
                return data, nil
 
250
        }
 
251
        if err != nil {
 
252
                return nil, fmt.Errorf("cannot get %q: %v", uri, err)
 
253
        }
 
254
        return
 
255
}
 
256
 
 
257
type environ struct {
 
258
        name string
 
259
 
 
260
        ecfgMutex             sync.Mutex
 
261
        ecfgUnlocked          *environConfig
 
262
        novaUnlocked          *nova.Client
 
263
        storageUnlocked       environs.Storage
 
264
        publicStorageUnlocked environs.Storage // optional.
 
265
}
 
266
 
 
267
var _ environs.Environ = (*environ)(nil)
 
268
 
 
269
type instance struct {
 
270
        e *environ
 
271
        *nova.ServerDetail
 
272
        address string
 
273
}
 
274
 
 
275
func (inst *instance) String() string {
 
276
        return inst.ServerDetail.Id
 
277
}
 
278
 
 
279
var _ environs.Instance = (*instance)(nil)
 
280
 
 
281
func (inst *instance) Id() state.InstanceId {
 
282
        return state.InstanceId(inst.ServerDetail.Id)
 
283
}
 
284
 
 
285
// instanceAddress processes a map of networks to lists of IP
 
286
// addresses, as returned by Nova.GetServer(), extracting the proper
 
287
// public (or private, if public is not available) IPv4 address, and
 
288
// returning it, or an error.
 
289
func instanceAddress(addresses map[string][]nova.IPAddress) (string, error) {
 
290
        var private, public, privateNet string
 
291
        for network, ips := range addresses {
 
292
                for _, address := range ips {
 
293
                        if address.Version == 4 {
 
294
                                if network == "public" {
 
295
                                        public = address.Address
 
296
                                } else {
 
297
                                        privateNet = network
 
298
                                        // Some setups use custom network name, treat as "private"
 
299
                                        private = address.Address
 
300
                                }
 
301
                                break
 
302
                        }
 
303
                }
 
304
        }
 
305
        // HP cloud/canonistack specific: public address is 2nd in the private network
 
306
        if prv, ok := addresses[privateNet]; public == "" && ok {
 
307
                if len(prv) > 1 && prv[1].Version == 4 {
 
308
                        public = prv[1].Address
 
309
                }
 
310
        }
 
311
        // Juju assumes it always needs a public address and loops waiting for one.
 
312
        // In fact a private address is generally fine provided it can be sshed to.
 
313
        // (ported from py-juju/providers/openstack)
 
314
        if public == "" && private != "" {
 
315
                public = private
 
316
        }
 
317
        if public == "" {
 
318
                return "", environs.ErrNoDNSName
 
319
        }
 
320
        return public, nil
 
321
}
 
322
 
 
323
func (inst *instance) DNSName() (string, error) {
 
324
        if inst.address != "" {
 
325
                return inst.address, nil
 
326
        }
 
327
        // Fetch the instance information again, in case
 
328
        // the addresses have become available.
 
329
        server, err := inst.e.nova().GetServer(string(inst.Id()))
 
330
        if err != nil {
 
331
                return "", err
 
332
        }
 
333
        inst.address, err = instanceAddress(server.Addresses)
 
334
        if err != nil {
 
335
                return "", err
 
336
        }
 
337
        return inst.address, nil
 
338
}
 
339
 
 
340
func (inst *instance) WaitDNSName() (string, error) {
 
341
        for a := longAttempt.Start(); a.Next(); {
 
342
                addr, err := inst.DNSName()
 
343
                if err == nil || err != environs.ErrNoDNSName {
 
344
                        return addr, err
 
345
                }
 
346
        }
 
347
        return "", fmt.Errorf("timed out trying to get DNS address for %v", inst.Id())
 
348
}
 
349
 
 
350
// TODO: following 30 lines nearly verbatim from environs/ec2
 
351
 
 
352
func (inst *instance) OpenPorts(machineId string, ports []params.Port) error {
 
353
        if inst.e.Config().FirewallMode() != config.FwInstance {
 
354
                return fmt.Errorf("invalid firewall mode for opening ports on instance: %q",
 
355
                        inst.e.Config().FirewallMode())
 
356
        }
 
357
        name := inst.e.machineGroupName(machineId)
 
358
        if err := inst.e.openPortsInGroup(name, ports); err != nil {
 
359
                return err
 
360
        }
 
361
        log.Infof("environs/openstack: opened ports in security group %s: %v", name, ports)
 
362
        return nil
 
363
}
 
364
 
 
365
func (inst *instance) ClosePorts(machineId string, ports []params.Port) error {
 
366
        if inst.e.Config().FirewallMode() != config.FwInstance {
 
367
                return fmt.Errorf("invalid firewall mode for closing ports on instance: %q",
 
368
                        inst.e.Config().FirewallMode())
 
369
        }
 
370
        name := inst.e.machineGroupName(machineId)
 
371
        if err := inst.e.closePortsInGroup(name, ports); err != nil {
 
372
                return err
 
373
        }
 
374
        log.Infof("environs/openstack: closed ports in security group %s: %v", name, ports)
 
375
        return nil
 
376
}
 
377
 
 
378
func (inst *instance) Ports(machineId string) ([]params.Port, error) {
 
379
        if inst.e.Config().FirewallMode() != config.FwInstance {
 
380
                return nil, fmt.Errorf("invalid firewall mode for retrieving ports from instance: %q",
 
381
                        inst.e.Config().FirewallMode())
 
382
        }
 
383
        name := inst.e.machineGroupName(machineId)
 
384
        return inst.e.portsInGroup(name)
 
385
}
 
386
 
 
387
func (e *environ) ecfg() *environConfig {
 
388
        e.ecfgMutex.Lock()
 
389
        ecfg := e.ecfgUnlocked
 
390
        e.ecfgMutex.Unlock()
 
391
        return ecfg
 
392
}
 
393
 
 
394
func (e *environ) nova() *nova.Client {
 
395
        e.ecfgMutex.Lock()
 
396
        nova := e.novaUnlocked
 
397
        e.ecfgMutex.Unlock()
 
398
        return nova
 
399
}
 
400
 
 
401
func (e *environ) Name() string {
 
402
        return e.name
 
403
}
 
404
 
 
405
func (e *environ) Storage() environs.Storage {
 
406
        e.ecfgMutex.Lock()
 
407
        storage := e.storageUnlocked
 
408
        e.ecfgMutex.Unlock()
 
409
        return storage
 
410
}
 
411
 
 
412
func (e *environ) PublicStorage() environs.StorageReader {
 
413
        e.ecfgMutex.Lock()
 
414
        defer e.ecfgMutex.Unlock()
 
415
        if e.publicStorageUnlocked == nil {
 
416
                return environs.EmptyStorage
 
417
        }
 
418
        return e.publicStorageUnlocked
 
419
}
 
420
 
 
421
func (e *environ) Bootstrap(cons constraints.Value) error {
 
422
        log.Infof("environs/openstack: bootstrapping environment %q", e.name)
 
423
        // If the state file exists, it might actually have just been
 
424
        // removed by Destroy, and eventual consistency has not caught
 
425
        // up yet, so we retry to verify if that is happening.
 
426
        var err error
 
427
        for a := shortAttempt.Start(); a.Next(); {
 
428
                _, err = e.loadState()
 
429
                if err != nil {
 
430
                        break
 
431
                }
 
432
        }
 
433
        if err == nil {
 
434
                return fmt.Errorf("environment is already bootstrapped")
 
435
        }
 
436
        if _, notFound := err.(*environs.NotFoundError); !notFound {
 
437
                return fmt.Errorf("cannot query old bootstrap state: %v", err)
 
438
        }
 
439
 
 
440
        possibleTools, err := environs.FindBootstrapTools(e, cons)
 
441
        if err != nil {
 
442
                return err
 
443
        }
 
444
        inst, err := e.startInstance(&startInstanceParams{
 
445
                machineId:     "0",
 
446
                machineNonce:  state.BootstrapNonce,
 
447
                series:        e.Config().DefaultSeries(),
 
448
                constraints:   cons,
 
449
                possibleTools: possibleTools,
 
450
                stateServer:   true,
 
451
                withPublicIP:  e.ecfg().useFloatingIP(),
 
452
        })
 
453
        if err != nil {
 
454
                return fmt.Errorf("cannot start bootstrap instance: %v", err)
 
455
        }
 
456
        err = e.saveState(&bootstrapState{
 
457
                StateInstances: []state.InstanceId{inst.Id()},
 
458
        })
 
459
        if err != nil {
 
460
                // ignore error on StopInstance because the previous error is
 
461
                // more important.
 
462
                e.StopInstances([]environs.Instance{inst})
 
463
                return fmt.Errorf("cannot save state: %v", err)
 
464
        }
 
465
        // TODO make safe in the case of racing Bootstraps
 
466
        // If two Bootstraps are called concurrently, there's
 
467
        // no way to use Swift to make sure that only one succeeds.
 
468
        // Perhaps consider using SimpleDB for state storage
 
469
        // which would enable that possibility.
 
470
 
 
471
        return nil
 
472
}
 
473
 
 
474
func (e *environ) StateInfo() (*state.Info, *api.Info, error) {
 
475
        st, err := e.loadState()
 
476
        if err != nil {
 
477
                return nil, nil, err
 
478
        }
 
479
        cert, hasCert := e.Config().CACert()
 
480
        if !hasCert {
 
481
                return nil, nil, fmt.Errorf("no CA certificate in environment configuration")
 
482
        }
 
483
        var stateAddrs []string
 
484
        var apiAddrs []string
 
485
        // Wait for the DNS names of any of the instances
 
486
        // to become available.
 
487
        log.Infof("environs/openstack: waiting for DNS name(s) of state server instances %v", st.StateInstances)
 
488
        for a := longAttempt.Start(); len(stateAddrs) == 0 && a.Next(); {
 
489
                insts, err := e.Instances(st.StateInstances)
 
490
                if err != nil && err != environs.ErrPartialInstances {
 
491
                        log.Debugf("error getting state instance: %v", err.Error())
 
492
                        return nil, nil, err
 
493
                }
 
494
                log.Debugf("started processing instances: %#v", insts)
 
495
                for _, inst := range insts {
 
496
                        if inst == nil {
 
497
                                continue
 
498
                        }
 
499
                        name, err := inst.(*instance).DNSName()
 
500
                        if err != nil {
 
501
                                continue
 
502
                        }
 
503
                        if name != "" {
 
504
                                stateAddrs = append(stateAddrs, name+mgoPortSuffix)
 
505
                                apiAddrs = append(apiAddrs, name+apiPortSuffix)
 
506
                        }
 
507
                }
 
508
        }
 
509
        if len(stateAddrs) == 0 {
 
510
                return nil, nil, fmt.Errorf("timed out waiting for mgo address from %v", st.StateInstances)
 
511
        }
 
512
        return &state.Info{
 
513
                        Addrs:  stateAddrs,
 
514
                        CACert: cert,
 
515
                }, &api.Info{
 
516
                        Addrs:  apiAddrs,
 
517
                        CACert: cert,
 
518
                }, nil
 
519
}
 
520
 
 
521
func (e *environ) Config() *config.Config {
 
522
        return e.ecfg().Config
 
523
}
 
524
 
 
525
func (e *environ) client(ecfg *environConfig, authModeCfg AuthMode) client.AuthenticatingClient {
 
526
        cred := &identity.Credentials{
 
527
                User:       ecfg.username(),
 
528
                Secrets:    ecfg.password(),
 
529
                Region:     ecfg.region(),
 
530
                TenantName: ecfg.tenantName(),
 
531
                URL:        ecfg.authURL(),
 
532
        }
 
533
        // authModeCfg has already been validated so we know it's one of the values below.
 
534
        var authMode identity.AuthMode
 
535
        switch authModeCfg {
 
536
        case AuthLegacy:
 
537
                authMode = identity.AuthLegacy
 
538
        case AuthUserPass:
 
539
                authMode = identity.AuthUserPass
 
540
        }
 
541
        return client.NewClient(cred, authMode, nil)
 
542
}
 
543
 
 
544
func (e *environ) publicClient(ecfg *environConfig) client.Client {
 
545
        return client.NewPublicClient(ecfg.publicBucketURL(), nil)
 
546
}
 
547
 
 
548
func (e *environ) SetConfig(cfg *config.Config) error {
 
549
        ecfg, err := providerInstance.newConfig(cfg)
 
550
        if err != nil {
 
551
                return err
 
552
        }
 
553
        // At this point, the authentication method config value has been validated so we extract it's value here
 
554
        // to avoid having to validate again each time when creating the OpenStack client.
 
555
        var authModeCfg AuthMode
 
556
        e.ecfgMutex.Lock()
 
557
        defer e.ecfgMutex.Unlock()
 
558
        e.name = ecfg.Name()
 
559
        authModeCfg = AuthMode(ecfg.authMode())
 
560
        e.ecfgUnlocked = ecfg
 
561
 
 
562
        client := e.client(ecfg, authModeCfg)
 
563
        e.novaUnlocked = nova.New(client)
 
564
 
 
565
        // create new storage instances, existing instances continue
 
566
        // to reference their existing configuration.
 
567
        e.storageUnlocked = &storage{
 
568
                containerName: ecfg.controlBucket(),
 
569
                // this is possibly just a hack - if the ACL is swift.Private,
 
570
                // the machine won't be able to get the tools (401 error)
 
571
                containerACL: swift.PublicRead,
 
572
                swift:        swift.New(client)}
 
573
        if ecfg.publicBucket() != "" {
 
574
                // If no public bucket URL is specified, we will instead create the public bucket
 
575
                // using the user's credentials on the authenticated client.
 
576
                if ecfg.publicBucketURL() == "" {
 
577
                        e.publicStorageUnlocked = &storage{
 
578
                                containerName: ecfg.publicBucket(),
 
579
                                // this is possibly just a hack - if the ACL is swift.Private,
 
580
                                // the machine won't be able to get the tools (401 error)
 
581
                                containerACL: swift.PublicRead,
 
582
                                swift:        swift.New(client)}
 
583
                } else {
 
584
                        e.publicStorageUnlocked = &storage{
 
585
                                containerName: ecfg.publicBucket(),
 
586
                                containerACL:  swift.PublicRead,
 
587
                                swift:         swift.New(e.publicClient(ecfg))}
 
588
                }
 
589
        } else {
 
590
                e.publicStorageUnlocked = nil
 
591
        }
 
592
 
 
593
        return nil
 
594
}
 
595
 
 
596
func (e *environ) StartInstance(machineId, machineNonce string, series string, cons constraints.Value, info *state.Info, apiInfo *api.Info) (environs.Instance, error) {
 
597
        possibleTools, err := environs.FindInstanceTools(e, series, cons)
 
598
        if err != nil {
 
599
                return nil, err
 
600
        }
 
601
        return e.startInstance(&startInstanceParams{
 
602
                machineId:     machineId,
 
603
                machineNonce:  machineNonce,
 
604
                series:        series,
 
605
                constraints:   cons,
 
606
                info:          info,
 
607
                apiInfo:       apiInfo,
 
608
                possibleTools: possibleTools,
 
609
                withPublicIP:  e.ecfg().useFloatingIP(),
 
610
        })
 
611
}
 
612
 
 
613
type startInstanceParams struct {
 
614
        machineId     string
 
615
        machineNonce  string
 
616
        series        string
 
617
        constraints   constraints.Value
 
618
        info          *state.Info
 
619
        apiInfo       *api.Info
 
620
        possibleTools tools.List
 
621
        stateServer   bool
 
622
 
 
623
        // withPublicIP, if true, causes a floating IP to be
 
624
        // assigned to the server after starting
 
625
        withPublicIP bool
 
626
}
 
627
 
 
628
func (e *environ) userData(scfg *startInstanceParams, tools *state.Tools) ([]byte, error) {
 
629
        mcfg := &cloudinit.MachineConfig{
 
630
                MachineId:    scfg.machineId,
 
631
                MachineNonce: scfg.machineNonce,
 
632
                StateServer:  scfg.stateServer,
 
633
                StateInfo:    scfg.info,
 
634
                APIInfo:      scfg.apiInfo,
 
635
                MongoPort:    mgoPort,
 
636
                APIPort:      apiPort,
 
637
                DataDir:      "/var/lib/juju",
 
638
                Tools:        tools,
 
639
        }
 
640
        if err := environs.FinishMachineConfig(mcfg, e.Config(), scfg.constraints); err != nil {
 
641
                return nil, err
 
642
        }
 
643
        cloudcfg, err := cloudinit.New(mcfg)
 
644
        if err != nil {
 
645
                return nil, err
 
646
        }
 
647
        data, err := cloudcfg.Render()
 
648
        if err != nil {
 
649
                return nil, err
 
650
        }
 
651
        cdata := utils.Gzip(data)
 
652
        log.Debugf("environs/openstack: openstack user data; %d bytes", len(cdata))
 
653
        return cdata, nil
 
654
}
 
655
 
 
656
// allocatePublicIP tries to find an available floating IP address, or
 
657
// allocates a new one, returning it, or an error
 
658
func (e *environ) allocatePublicIP() (*nova.FloatingIP, error) {
 
659
        fips, err := e.nova().ListFloatingIPs()
 
660
        if err != nil {
 
661
                return nil, err
 
662
        }
 
663
        var newfip *nova.FloatingIP
 
664
        for _, fip := range fips {
 
665
                newfip = &fip
 
666
                if fip.InstanceId != nil && *fip.InstanceId != "" {
 
667
                        // unavailable, skip
 
668
                        newfip = nil
 
669
                        continue
 
670
                } else {
 
671
                        // unassigned, we can use it
 
672
                        return newfip, nil
 
673
                }
 
674
        }
 
675
        if newfip == nil {
 
676
                // allocate a new IP and use it
 
677
                newfip, err = e.nova().AllocateFloatingIP()
 
678
                if err != nil {
 
679
                        return nil, err
 
680
                }
 
681
        }
 
682
        return newfip, nil
 
683
}
 
684
 
 
685
// assignPublicIP tries to assign the given floating IP address to the
 
686
// specified server, or returns an error.
 
687
func (e *environ) assignPublicIP(fip *nova.FloatingIP, serverId string) (err error) {
 
688
        if fip == nil {
 
689
                return fmt.Errorf("cannot assign a nil public IP to %q", serverId)
 
690
        }
 
691
        if fip.InstanceId != nil && *fip.InstanceId == serverId {
 
692
                // IP already assigned, nothing to do
 
693
                return nil
 
694
        }
 
695
        // At startup nw_info is not yet cached so this may fail
 
696
        // temporarily while the server is being built
 
697
        for a := longAttempt.Start(); a.Next(); {
 
698
                err = e.nova().AddServerFloatingIP(serverId, fip.IP)
 
699
                if err == nil {
 
700
                        return nil
 
701
                }
 
702
        }
 
703
        return err
 
704
}
 
705
 
 
706
// startInstance is the internal version of StartInstance, used by Bootstrap
 
707
// as well as via StartInstance itself.
 
708
func (e *environ) startInstance(scfg *startInstanceParams) (environs.Instance, error) {
 
709
        spec, err := findInstanceSpec(e, scfg.possibleTools)
 
710
        if err != nil {
 
711
                return nil, err
 
712
        }
 
713
        userData, err := e.userData(scfg, spec.tools)
 
714
        if err != nil {
 
715
                return nil, fmt.Errorf("cannot make user data: %v", err)
 
716
        }
 
717
        var publicIP *nova.FloatingIP
 
718
        if scfg.withPublicIP {
 
719
                if fip, err := e.allocatePublicIP(); err != nil {
 
720
                        return nil, fmt.Errorf("cannot allocate a public IP as needed: %v", err)
 
721
                } else {
 
722
                        publicIP = fip
 
723
                        log.Infof("environs/openstack: allocated public IP %s", publicIP.IP)
 
724
                }
 
725
        }
 
726
        groups, err := e.setUpGroups(scfg.machineId)
 
727
        if err != nil {
 
728
                return nil, fmt.Errorf("cannot set up groups: %v", err)
 
729
        }
 
730
        var groupNames = make([]nova.SecurityGroupName, len(groups))
 
731
        for i, g := range groups {
 
732
                groupNames[i] = nova.SecurityGroupName{g.Name}
 
733
        }
 
734
 
 
735
        var server *nova.Entity
 
736
        for a := shortAttempt.Start(); a.Next(); {
 
737
                server, err = e.nova().RunServer(nova.RunServerOpts{
 
738
                        Name:               e.machineFullName(scfg.machineId),
 
739
                        FlavorId:           spec.flavorId,
 
740
                        ImageId:            spec.imageId,
 
741
                        UserData:           userData,
 
742
                        SecurityGroupNames: groupNames,
 
743
                })
 
744
                if err == nil || !gooseerrors.IsNotFound(err) {
 
745
                        break
 
746
                }
 
747
        }
 
748
        if err != nil {
 
749
                return nil, fmt.Errorf("cannot run instance: %v", err)
 
750
        }
 
751
        detail, err := e.nova().GetServer(server.Id)
 
752
        if err != nil {
 
753
                return nil, fmt.Errorf("cannot get started instance: %v", err)
 
754
        }
 
755
        inst := &instance{e, detail, ""}
 
756
        log.Infof("environs/openstack: started instance %q", inst.Id())
 
757
        if scfg.withPublicIP {
 
758
                if err := e.assignPublicIP(publicIP, string(inst.Id())); err != nil {
 
759
                        if err := e.terminateInstances([]state.InstanceId{inst.Id()}); err != nil {
 
760
                                // ignore the failure at this stage, just log it
 
761
                                log.Debugf("environs/openstack: failed to terminate instance %q: %v", inst.Id(), err)
 
762
                        }
 
763
                        return nil, fmt.Errorf("cannot assign public address %s to instance %q: %v", publicIP.IP, inst.Id(), err)
 
764
                }
 
765
                log.Infof("environs/openstack: assigned public IP %s to %q", publicIP.IP, inst.Id())
 
766
        }
 
767
        return inst, nil
 
768
}
 
769
 
 
770
func (e *environ) StopInstances(insts []environs.Instance) error {
 
771
        ids := make([]state.InstanceId, len(insts))
 
772
        for i, inst := range insts {
 
773
                instanceValue, ok := inst.(*instance)
 
774
                if !ok {
 
775
                        return errors.New("Incompatible environs.Instance supplied")
 
776
                }
 
777
                ids[i] = instanceValue.Id()
 
778
        }
 
779
        log.Debugf("environs/openstack: terminating instances %v", ids)
 
780
        return e.terminateInstances(ids)
 
781
}
 
782
 
 
783
// collectInstances tries to get information on each instance id in ids.
 
784
// It fills the slots in the given map for known servers with status
 
785
// either ACTIVE or BUILD. Returns a list of missing ids.
 
786
func (e *environ) collectInstances(ids []state.InstanceId, out map[state.InstanceId]environs.Instance) []state.InstanceId {
 
787
        var err error
 
788
        serversById := make(map[string]nova.ServerDetail)
 
789
        if len(ids) == 1 {
 
790
                // most common case - single instance
 
791
                var server *nova.ServerDetail
 
792
                server, err = e.nova().GetServer(string(ids[0]))
 
793
                if server != nil {
 
794
                        serversById[server.Id] = *server
 
795
                }
 
796
        } else {
 
797
                var servers []nova.ServerDetail
 
798
                servers, err = e.nova().ListServersDetail(e.machinesFilter())
 
799
                for _, server := range servers {
 
800
                        serversById[server.Id] = server
 
801
                }
 
802
        }
 
803
        if err != nil {
 
804
                return ids
 
805
        }
 
806
        var missing []state.InstanceId
 
807
        for _, id := range ids {
 
808
                if server, found := serversById[string(id)]; found {
 
809
                        if server.Status == nova.StatusActive || server.Status == nova.StatusBuild {
 
810
                                out[id] = &instance{e, &server, ""}
 
811
                        }
 
812
                        continue
 
813
                }
 
814
                missing = append(missing, id)
 
815
        }
 
816
        return missing
 
817
}
 
818
 
 
819
func (e *environ) Instances(ids []state.InstanceId) ([]environs.Instance, error) {
 
820
        if len(ids) == 0 {
 
821
                return nil, nil
 
822
        }
 
823
        missing := ids
 
824
        found := make(map[state.InstanceId]environs.Instance)
 
825
        // Make a series of requests to cope with eventual consistency.
 
826
        // Each request will attempt to add more instances to the requested
 
827
        // set.
 
828
        for a := shortAttempt.Start(); a.Next(); {
 
829
                if missing = e.collectInstances(missing, found); len(missing) == 0 {
 
830
                        break
 
831
                }
 
832
        }
 
833
        if len(found) == 0 {
 
834
                return nil, environs.ErrNoInstances
 
835
        }
 
836
        insts := make([]environs.Instance, len(ids))
 
837
        var err error
 
838
        for i, id := range ids {
 
839
                if inst := found[id]; inst != nil {
 
840
                        insts[i] = inst
 
841
                } else {
 
842
                        err = environs.ErrPartialInstances
 
843
                }
 
844
        }
 
845
        return insts, err
 
846
}
 
847
 
 
848
func (e *environ) AllInstances() (insts []environs.Instance, err error) {
 
849
        servers, err := e.nova().ListServersDetail(e.machinesFilter())
 
850
        if err != nil {
 
851
                return nil, err
 
852
        }
 
853
        for _, server := range servers {
 
854
                if server.Status == nova.StatusActive || server.Status == nova.StatusBuild {
 
855
                        var s = server
 
856
                        insts = append(insts, &instance{e, &s, ""})
 
857
                }
 
858
        }
 
859
        return insts, err
 
860
}
 
861
 
 
862
func (e *environ) Destroy(ensureInsts []environs.Instance) error {
 
863
        log.Infof("environs/openstack: destroying environment %q", e.name)
 
864
        insts, err := e.AllInstances()
 
865
        if err != nil {
 
866
                return fmt.Errorf("cannot get instances: %v", err)
 
867
        }
 
868
        found := make(map[state.InstanceId]bool)
 
869
        var ids []state.InstanceId
 
870
        for _, inst := range insts {
 
871
                ids = append(ids, inst.Id())
 
872
                found[inst.Id()] = true
 
873
        }
 
874
 
 
875
        // Add any instances we've been told about but haven't yet shown
 
876
        // up in the instance list.
 
877
        for _, inst := range ensureInsts {
 
878
                id := state.InstanceId(inst.(*instance).Id())
 
879
                if !found[id] {
 
880
                        ids = append(ids, id)
 
881
                        found[id] = true
 
882
                }
 
883
        }
 
884
        err = e.terminateInstances(ids)
 
885
        if err != nil {
 
886
                return err
 
887
        }
 
888
 
 
889
        // To properly observe e.storageUnlocked we need to get its value while
 
890
        // holding e.ecfgMutex. e.Storage() does this for us, then we convert
 
891
        // back to the (*storage) to access the private deleteAll() method.
 
892
        st := e.Storage().(*storage)
 
893
        return st.deleteAll()
 
894
}
 
895
 
 
896
func (e *environ) AssignmentPolicy() state.AssignmentPolicy {
 
897
        // Until we get proper containers to install units into, we shouldn't
 
898
        // reuse dirty machines, as we cannot guarantee that when units were
 
899
        // removed, it was left in a clean state.  Once we have good
 
900
        // containerisation for the units, we should be able to have the ability
 
901
        // to assign back to unused machines.
 
902
        return state.AssignNew
 
903
}
 
904
 
 
905
func (e *environ) globalGroupName() string {
 
906
        return fmt.Sprintf("%s-global", e.jujuGroupName())
 
907
}
 
908
 
 
909
func (e *environ) machineGroupName(machineId string) string {
 
910
        return fmt.Sprintf("%s-%s", e.jujuGroupName(), machineId)
 
911
}
 
912
 
 
913
func (e *environ) jujuGroupName() string {
 
914
        return fmt.Sprintf("juju-%s", e.name)
 
915
}
 
916
 
 
917
func (e *environ) machineFullName(machineId string) string {
 
918
        return fmt.Sprintf("juju-%s-%s", e.Name(), state.MachineTag(machineId))
 
919
}
 
920
 
 
921
// machinesFilter returns a nova.Filter matching all machines in the environment.
 
922
func (e *environ) machinesFilter() *nova.Filter {
 
923
        filter := nova.NewFilter()
 
924
        filter.Set(nova.FilterServer, fmt.Sprintf("juju-%s-.*", e.Name()))
 
925
        return filter
 
926
}
 
927
 
 
928
func (e *environ) openPortsInGroup(name string, ports []params.Port) error {
 
929
        novaclient := e.nova()
 
930
        group, err := novaclient.SecurityGroupByName(name)
 
931
        if err != nil {
 
932
                return err
 
933
        }
 
934
        for _, port := range ports {
 
935
                _, err := novaclient.CreateSecurityGroupRule(nova.RuleInfo{
 
936
                        ParentGroupId: group.Id,
 
937
                        FromPort:      port.Number,
 
938
                        ToPort:        port.Number,
 
939
                        IPProtocol:    port.Protocol,
 
940
                        Cidr:          "0.0.0.0/0",
 
941
                })
 
942
                if err != nil {
 
943
                        // TODO: if err is not rule already exists, raise?
 
944
                        log.Debugf("error creating security group rule: %v", err.Error())
 
945
                }
 
946
        }
 
947
        return nil
 
948
}
 
949
 
 
950
func (e *environ) closePortsInGroup(name string, ports []params.Port) error {
 
951
        if len(ports) == 0 {
 
952
                return nil
 
953
        }
 
954
        novaclient := e.nova()
 
955
        group, err := novaclient.SecurityGroupByName(name)
 
956
        if err != nil {
 
957
                return err
 
958
        }
 
959
        // TODO: Hey look ma, it's quadratic
 
960
        for _, port := range ports {
 
961
                for _, p := range (*group).Rules {
 
962
                        if p.IPProtocol == nil || *p.IPProtocol != port.Protocol ||
 
963
                                p.FromPort == nil || *p.FromPort != port.Number ||
 
964
                                p.ToPort == nil || *p.ToPort != port.Number {
 
965
                                continue
 
966
                        }
 
967
                        err := novaclient.DeleteSecurityGroupRule(p.Id)
 
968
                        if err != nil {
 
969
                                return err
 
970
                        }
 
971
                        break
 
972
                }
 
973
        }
 
974
        return nil
 
975
}
 
976
 
 
977
func (e *environ) portsInGroup(name string) (ports []params.Port, err error) {
 
978
        group, err := e.nova().SecurityGroupByName(name)
 
979
        if err != nil {
 
980
                return nil, err
 
981
        }
 
982
        for _, p := range (*group).Rules {
 
983
                for i := *p.FromPort; i <= *p.ToPort; i++ {
 
984
                        ports = append(ports, params.Port{
 
985
                                Protocol: *p.IPProtocol,
 
986
                                Number:   i,
 
987
                        })
 
988
                }
 
989
        }
 
990
        state.SortPorts(ports)
 
991
        return ports, nil
 
992
}
 
993
 
 
994
// TODO: following 30 lines nearly verbatim from environs/ec2
 
995
 
 
996
func (e *environ) OpenPorts(ports []params.Port) error {
 
997
        if e.Config().FirewallMode() != config.FwGlobal {
 
998
                return fmt.Errorf("invalid firewall mode for opening ports on environment: %q",
 
999
                        e.Config().FirewallMode())
 
1000
        }
 
1001
        if err := e.openPortsInGroup(e.globalGroupName(), ports); err != nil {
 
1002
                return err
 
1003
        }
 
1004
        log.Infof("environs/openstack: opened ports in global group: %v", ports)
 
1005
        return nil
 
1006
}
 
1007
 
 
1008
func (e *environ) ClosePorts(ports []params.Port) error {
 
1009
        if e.Config().FirewallMode() != config.FwGlobal {
 
1010
                return fmt.Errorf("invalid firewall mode for closing ports on environment: %q",
 
1011
                        e.Config().FirewallMode())
 
1012
        }
 
1013
        if err := e.closePortsInGroup(e.globalGroupName(), ports); err != nil {
 
1014
                return err
 
1015
        }
 
1016
        log.Infof("environs/openstack: closed ports in global group: %v", ports)
 
1017
        return nil
 
1018
}
 
1019
 
 
1020
func (e *environ) Ports() ([]params.Port, error) {
 
1021
        if e.Config().FirewallMode() != config.FwGlobal {
 
1022
                return nil, fmt.Errorf("invalid firewall mode for retrieving ports from environment: %q",
 
1023
                        e.Config().FirewallMode())
 
1024
        }
 
1025
        return e.portsInGroup(e.globalGroupName())
 
1026
}
 
1027
 
 
1028
func (e *environ) Provider() environs.EnvironProvider {
 
1029
        return &providerInstance
 
1030
}
 
1031
 
 
1032
// setUpGroups creates the security groups for the new machine, and
 
1033
// returns them.
 
1034
//
 
1035
// Instances are tagged with a group so they can be distinguished from
 
1036
// other instances that might be running on the same OpenStack account.
 
1037
// In addition, a specific machine security group is created for each
 
1038
// machine, so that its firewall rules can be configured per machine.
 
1039
func (e *environ) setUpGroups(machineId string) ([]nova.SecurityGroup, error) {
 
1040
        jujuGroup, err := e.ensureGroup(e.jujuGroupName(),
 
1041
                []nova.RuleInfo{
 
1042
                        {
 
1043
                                IPProtocol: "tcp",
 
1044
                                FromPort:   22,
 
1045
                                ToPort:     22,
 
1046
                                Cidr:       "0.0.0.0/0",
 
1047
                        },
 
1048
                        {
 
1049
                                IPProtocol: "tcp",
 
1050
                                FromPort:   mgoPort,
 
1051
                                ToPort:     mgoPort,
 
1052
                                Cidr:       "0.0.0.0/0",
 
1053
                        },
 
1054
                        {
 
1055
                                IPProtocol: "tcp",
 
1056
                                FromPort:   1,
 
1057
                                ToPort:     65535,
 
1058
                        },
 
1059
                        {
 
1060
                                IPProtocol: "udp",
 
1061
                                FromPort:   1,
 
1062
                                ToPort:     65535,
 
1063
                        },
 
1064
                        {
 
1065
                                IPProtocol: "icmp",
 
1066
                                FromPort:   -1,
 
1067
                                ToPort:     -1,
 
1068
                        },
 
1069
                })
 
1070
        if err != nil {
 
1071
                return nil, err
 
1072
        }
 
1073
        var machineGroup nova.SecurityGroup
 
1074
        switch e.Config().FirewallMode() {
 
1075
        case config.FwInstance:
 
1076
                machineGroup, err = e.ensureGroup(e.machineGroupName(machineId), nil)
 
1077
        case config.FwGlobal:
 
1078
                machineGroup, err = e.ensureGroup(e.globalGroupName(), nil)
 
1079
        }
 
1080
        if err != nil {
 
1081
                return nil, err
 
1082
        }
 
1083
        return []nova.SecurityGroup{jujuGroup, machineGroup}, nil
 
1084
}
 
1085
 
 
1086
// zeroGroup holds the zero security group.
 
1087
var zeroGroup nova.SecurityGroup
 
1088
 
 
1089
// ensureGroup returns the security group with name and perms.
 
1090
// If a group with name does not exist, one will be created.
 
1091
// If it exists, its permissions are set to perms.
 
1092
func (e *environ) ensureGroup(name string, rules []nova.RuleInfo) (nova.SecurityGroup, error) {
 
1093
        nova := e.nova()
 
1094
        group, err := nova.CreateSecurityGroup(name, "juju group")
 
1095
        if err != nil {
 
1096
                if !gooseerrors.IsDuplicateValue(err) {
 
1097
                        return zeroGroup, err
 
1098
                } else {
 
1099
                        // We just tried to create a duplicate group, so load the existing group.
 
1100
                        group, err = nova.SecurityGroupByName(name)
 
1101
                        if err != nil {
 
1102
                                return zeroGroup, err
 
1103
                        }
 
1104
                }
 
1105
        }
 
1106
        // The group is created so now add the rules.
 
1107
        for _, rule := range rules {
 
1108
                rule.ParentGroupId = group.Id
 
1109
                _, err := nova.CreateSecurityGroupRule(rule)
 
1110
                if err != nil && !gooseerrors.IsDuplicateValue(err) {
 
1111
                        return zeroGroup, err
 
1112
                }
 
1113
        }
 
1114
        return *group, nil
 
1115
}
 
1116
 
 
1117
func (e *environ) terminateInstances(ids []state.InstanceId) error {
 
1118
        if len(ids) == 0 {
 
1119
                return nil
 
1120
        }
 
1121
        var firstErr error
 
1122
        nova := e.nova()
 
1123
        for _, id := range ids {
 
1124
                err := nova.DeleteServer(string(id))
 
1125
                if gooseerrors.IsNotFound(err) {
 
1126
                        err = nil
 
1127
                }
 
1128
                if err != nil && firstErr == nil {
 
1129
                        log.Debugf("environs/openstack: error terminating instance %q: %v", id, err)
 
1130
                        firstErr = err
 
1131
                }
 
1132
        }
 
1133
        return firstErr
 
1134
}