1
// Stub provider for OpenStack, using goose will be implemented here
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"
35
var mgoPortSuffix = fmt.Sprintf(":%d", mgoPort)
36
var apiPortSuffix = fmt.Sprintf(":%d", apiPort)
38
type environProvider struct{}
40
var _ environs.EnvironProvider = (*environProvider)(nil)
42
var providerInstance environProvider
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,
54
var longAttempt = utils.AttemptStrategy{
55
Total: 3 * time.Minute,
56
Delay: 1 * time.Second,
60
environs.RegisterProvider("openstack", environProvider{})
63
func (p environProvider) BoilerplateConfig() string {
65
## https://juju.ubuntu.com/get-started/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)
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
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>
92
## https://juju.ubuntu.com/get-started/hp-cloud/
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)
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
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
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>
128
func (p environProvider) Open(cfg *config.Config) (environs.Environ, error) {
129
log.Infof("environs/openstack: opening environment %q", cfg.Name())
131
err := e.SetConfig(cfg)
138
func (p environProvider) SecretAttrs(cfg *config.Config) (map[string]interface{}, error) {
139
m := make(map[string]interface{})
140
ecfg, err := providerInstance.newConfig(cfg)
144
m["username"] = ecfg.username()
145
m["password"] = ecfg.password()
146
m["tenant-name"] = ecfg.tenantName()
150
func (p environProvider) PublicAddress() (string, error) {
151
if addr, err := fetchMetadata("public-ipv4"); err != nil {
153
} else if addr != "" {
156
return p.PrivateAddress()
159
func (p environProvider) PrivateAddress() (string, error) {
160
return fetchMetadata("local-ipv4")
163
func (p environProvider) InstanceId() (state.InstanceId, error) {
164
str, err := fetchInstanceUUID()
166
str, err = fetchLegacyId()
168
return state.InstanceId(str), err
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"
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)
185
return strings.TrimSpace(string(data)), nil
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)
200
if err := json.Unmarshal(data, &uuid); err != nil {
204
return "", fmt.Errorf("no instance UUID found")
206
return uuid.Uuid, nil
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")
221
if strings.Index(instId, "i-") >= 0 {
222
hex := strings.SplitAfter(instId, "i-")[1]
223
id, err := strconv.ParseInt("0x"+hex, 0, 32)
227
instId = fmt.Sprintf("%d", id)
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)
239
defer resp.Body.Close()
240
if resp.StatusCode != http.StatusOK {
241
err = fmt.Errorf("bad http response %v", resp.Status)
245
data, err = ioutil.ReadAll(resp.Body)
252
return nil, fmt.Errorf("cannot get %q: %v", uri, err)
257
type environ struct {
261
ecfgUnlocked *environConfig
262
novaUnlocked *nova.Client
263
storageUnlocked environs.Storage
264
publicStorageUnlocked environs.Storage // optional.
267
var _ environs.Environ = (*environ)(nil)
269
type instance struct {
275
func (inst *instance) String() string {
276
return inst.ServerDetail.Id
279
var _ environs.Instance = (*instance)(nil)
281
func (inst *instance) Id() state.InstanceId {
282
return state.InstanceId(inst.ServerDetail.Id)
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
298
// Some setups use custom network name, treat as "private"
299
private = address.Address
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
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 != "" {
318
return "", environs.ErrNoDNSName
323
func (inst *instance) DNSName() (string, error) {
324
if inst.address != "" {
325
return inst.address, nil
327
// Fetch the instance information again, in case
328
// the addresses have become available.
329
server, err := inst.e.nova().GetServer(string(inst.Id()))
333
inst.address, err = instanceAddress(server.Addresses)
337
return inst.address, nil
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 {
347
return "", fmt.Errorf("timed out trying to get DNS address for %v", inst.Id())
350
// TODO: following 30 lines nearly verbatim from environs/ec2
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())
357
name := inst.e.machineGroupName(machineId)
358
if err := inst.e.openPortsInGroup(name, ports); err != nil {
361
log.Infof("environs/openstack: opened ports in security group %s: %v", name, ports)
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())
370
name := inst.e.machineGroupName(machineId)
371
if err := inst.e.closePortsInGroup(name, ports); err != nil {
374
log.Infof("environs/openstack: closed ports in security group %s: %v", name, ports)
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())
383
name := inst.e.machineGroupName(machineId)
384
return inst.e.portsInGroup(name)
387
func (e *environ) ecfg() *environConfig {
389
ecfg := e.ecfgUnlocked
394
func (e *environ) nova() *nova.Client {
396
nova := e.novaUnlocked
401
func (e *environ) Name() string {
405
func (e *environ) Storage() environs.Storage {
407
storage := e.storageUnlocked
412
func (e *environ) PublicStorage() environs.StorageReader {
414
defer e.ecfgMutex.Unlock()
415
if e.publicStorageUnlocked == nil {
416
return environs.EmptyStorage
418
return e.publicStorageUnlocked
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.
427
for a := shortAttempt.Start(); a.Next(); {
428
_, err = e.loadState()
434
return fmt.Errorf("environment is already bootstrapped")
436
if _, notFound := err.(*environs.NotFoundError); !notFound {
437
return fmt.Errorf("cannot query old bootstrap state: %v", err)
440
possibleTools, err := environs.FindBootstrapTools(e, cons)
444
inst, err := e.startInstance(&startInstanceParams{
446
machineNonce: state.BootstrapNonce,
447
series: e.Config().DefaultSeries(),
449
possibleTools: possibleTools,
451
withPublicIP: e.ecfg().useFloatingIP(),
454
return fmt.Errorf("cannot start bootstrap instance: %v", err)
456
err = e.saveState(&bootstrapState{
457
StateInstances: []state.InstanceId{inst.Id()},
460
// ignore error on StopInstance because the previous error is
462
e.StopInstances([]environs.Instance{inst})
463
return fmt.Errorf("cannot save state: %v", err)
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.
474
func (e *environ) StateInfo() (*state.Info, *api.Info, error) {
475
st, err := e.loadState()
479
cert, hasCert := e.Config().CACert()
481
return nil, nil, fmt.Errorf("no CA certificate in environment configuration")
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())
494
log.Debugf("started processing instances: %#v", insts)
495
for _, inst := range insts {
499
name, err := inst.(*instance).DNSName()
504
stateAddrs = append(stateAddrs, name+mgoPortSuffix)
505
apiAddrs = append(apiAddrs, name+apiPortSuffix)
509
if len(stateAddrs) == 0 {
510
return nil, nil, fmt.Errorf("timed out waiting for mgo address from %v", st.StateInstances)
521
func (e *environ) Config() *config.Config {
522
return e.ecfg().Config
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(),
533
// authModeCfg has already been validated so we know it's one of the values below.
534
var authMode identity.AuthMode
537
authMode = identity.AuthLegacy
539
authMode = identity.AuthUserPass
541
return client.NewClient(cred, authMode, nil)
544
func (e *environ) publicClient(ecfg *environConfig) client.Client {
545
return client.NewPublicClient(ecfg.publicBucketURL(), nil)
548
func (e *environ) SetConfig(cfg *config.Config) error {
549
ecfg, err := providerInstance.newConfig(cfg)
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
557
defer e.ecfgMutex.Unlock()
559
authModeCfg = AuthMode(ecfg.authMode())
560
e.ecfgUnlocked = ecfg
562
client := e.client(ecfg, authModeCfg)
563
e.novaUnlocked = nova.New(client)
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)}
584
e.publicStorageUnlocked = &storage{
585
containerName: ecfg.publicBucket(),
586
containerACL: swift.PublicRead,
587
swift: swift.New(e.publicClient(ecfg))}
590
e.publicStorageUnlocked = nil
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)
601
return e.startInstance(&startInstanceParams{
602
machineId: machineId,
603
machineNonce: machineNonce,
608
possibleTools: possibleTools,
609
withPublicIP: e.ecfg().useFloatingIP(),
613
type startInstanceParams struct {
617
constraints constraints.Value
620
possibleTools tools.List
623
// withPublicIP, if true, causes a floating IP to be
624
// assigned to the server after starting
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,
637
DataDir: "/var/lib/juju",
640
if err := environs.FinishMachineConfig(mcfg, e.Config(), scfg.constraints); err != nil {
643
cloudcfg, err := cloudinit.New(mcfg)
647
data, err := cloudcfg.Render()
651
cdata := utils.Gzip(data)
652
log.Debugf("environs/openstack: openstack user data; %d bytes", len(cdata))
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()
663
var newfip *nova.FloatingIP
664
for _, fip := range fips {
666
if fip.InstanceId != nil && *fip.InstanceId != "" {
671
// unassigned, we can use it
676
// allocate a new IP and use it
677
newfip, err = e.nova().AllocateFloatingIP()
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) {
689
return fmt.Errorf("cannot assign a nil public IP to %q", serverId)
691
if fip.InstanceId != nil && *fip.InstanceId == serverId {
692
// IP already assigned, nothing to do
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)
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)
713
userData, err := e.userData(scfg, spec.tools)
715
return nil, fmt.Errorf("cannot make user data: %v", err)
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)
723
log.Infof("environs/openstack: allocated public IP %s", publicIP.IP)
726
groups, err := e.setUpGroups(scfg.machineId)
728
return nil, fmt.Errorf("cannot set up groups: %v", err)
730
var groupNames = make([]nova.SecurityGroupName, len(groups))
731
for i, g := range groups {
732
groupNames[i] = nova.SecurityGroupName{g.Name}
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,
742
SecurityGroupNames: groupNames,
744
if err == nil || !gooseerrors.IsNotFound(err) {
749
return nil, fmt.Errorf("cannot run instance: %v", err)
751
detail, err := e.nova().GetServer(server.Id)
753
return nil, fmt.Errorf("cannot get started instance: %v", err)
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)
763
return nil, fmt.Errorf("cannot assign public address %s to instance %q: %v", publicIP.IP, inst.Id(), err)
765
log.Infof("environs/openstack: assigned public IP %s to %q", publicIP.IP, inst.Id())
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)
775
return errors.New("Incompatible environs.Instance supplied")
777
ids[i] = instanceValue.Id()
779
log.Debugf("environs/openstack: terminating instances %v", ids)
780
return e.terminateInstances(ids)
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 {
788
serversById := make(map[string]nova.ServerDetail)
790
// most common case - single instance
791
var server *nova.ServerDetail
792
server, err = e.nova().GetServer(string(ids[0]))
794
serversById[server.Id] = *server
797
var servers []nova.ServerDetail
798
servers, err = e.nova().ListServersDetail(e.machinesFilter())
799
for _, server := range servers {
800
serversById[server.Id] = server
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, ""}
814
missing = append(missing, id)
819
func (e *environ) Instances(ids []state.InstanceId) ([]environs.Instance, error) {
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
828
for a := shortAttempt.Start(); a.Next(); {
829
if missing = e.collectInstances(missing, found); len(missing) == 0 {
834
return nil, environs.ErrNoInstances
836
insts := make([]environs.Instance, len(ids))
838
for i, id := range ids {
839
if inst := found[id]; inst != nil {
842
err = environs.ErrPartialInstances
848
func (e *environ) AllInstances() (insts []environs.Instance, err error) {
849
servers, err := e.nova().ListServersDetail(e.machinesFilter())
853
for _, server := range servers {
854
if server.Status == nova.StatusActive || server.Status == nova.StatusBuild {
856
insts = append(insts, &instance{e, &s, ""})
862
func (e *environ) Destroy(ensureInsts []environs.Instance) error {
863
log.Infof("environs/openstack: destroying environment %q", e.name)
864
insts, err := e.AllInstances()
866
return fmt.Errorf("cannot get instances: %v", err)
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
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())
880
ids = append(ids, id)
884
err = e.terminateInstances(ids)
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()
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
905
func (e *environ) globalGroupName() string {
906
return fmt.Sprintf("%s-global", e.jujuGroupName())
909
func (e *environ) machineGroupName(machineId string) string {
910
return fmt.Sprintf("%s-%s", e.jujuGroupName(), machineId)
913
func (e *environ) jujuGroupName() string {
914
return fmt.Sprintf("juju-%s", e.name)
917
func (e *environ) machineFullName(machineId string) string {
918
return fmt.Sprintf("juju-%s-%s", e.Name(), state.MachineTag(machineId))
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()))
928
func (e *environ) openPortsInGroup(name string, ports []params.Port) error {
929
novaclient := e.nova()
930
group, err := novaclient.SecurityGroupByName(name)
934
for _, port := range ports {
935
_, err := novaclient.CreateSecurityGroupRule(nova.RuleInfo{
936
ParentGroupId: group.Id,
937
FromPort: port.Number,
939
IPProtocol: port.Protocol,
943
// TODO: if err is not rule already exists, raise?
944
log.Debugf("error creating security group rule: %v", err.Error())
950
func (e *environ) closePortsInGroup(name string, ports []params.Port) error {
954
novaclient := e.nova()
955
group, err := novaclient.SecurityGroupByName(name)
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 {
967
err := novaclient.DeleteSecurityGroupRule(p.Id)
977
func (e *environ) portsInGroup(name string) (ports []params.Port, err error) {
978
group, err := e.nova().SecurityGroupByName(name)
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,
990
state.SortPorts(ports)
994
// TODO: following 30 lines nearly verbatim from environs/ec2
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())
1001
if err := e.openPortsInGroup(e.globalGroupName(), ports); err != nil {
1004
log.Infof("environs/openstack: opened ports in global group: %v", ports)
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())
1013
if err := e.closePortsInGroup(e.globalGroupName(), ports); err != nil {
1016
log.Infof("environs/openstack: closed ports in global group: %v", ports)
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())
1025
return e.portsInGroup(e.globalGroupName())
1028
func (e *environ) Provider() environs.EnvironProvider {
1029
return &providerInstance
1032
// setUpGroups creates the security groups for the new machine, and
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(),
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)
1083
return []nova.SecurityGroup{jujuGroup, machineGroup}, nil
1086
// zeroGroup holds the zero security group.
1087
var zeroGroup nova.SecurityGroup
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) {
1094
group, err := nova.CreateSecurityGroup(name, "juju group")
1096
if !gooseerrors.IsDuplicateValue(err) {
1097
return zeroGroup, err
1099
// We just tried to create a duplicate group, so load the existing group.
1100
group, err = nova.SecurityGroupByName(name)
1102
return zeroGroup, err
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
1117
func (e *environ) terminateInstances(ids []state.InstanceId) error {
1123
for _, id := range ids {
1124
err := nova.DeleteServer(string(id))
1125
if gooseerrors.IsNotFound(err) {
1128
if err != nil && firstErr == nil {
1129
log.Debugf("environs/openstack: error terminating instance %q: %v", id, err)