github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/provider/openstack/provider.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Stub provider for OpenStack, using goose will be implemented here
     5  
     6  package openstack
     7  
     8  import (
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"regexp"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	jujuerrors "github.com/juju/errors"
    18  	"github.com/juju/loggo"
    19  	"github.com/juju/names"
    20  	"github.com/juju/utils"
    21  	"launchpad.net/goose/client"
    22  	gooseerrors "launchpad.net/goose/errors"
    23  	"launchpad.net/goose/identity"
    24  	"launchpad.net/goose/nova"
    25  	"launchpad.net/goose/swift"
    26  
    27  	"github.com/juju/juju/constraints"
    28  	"github.com/juju/juju/environs"
    29  	"github.com/juju/juju/environs/config"
    30  	"github.com/juju/juju/environs/imagemetadata"
    31  	"github.com/juju/juju/environs/instances"
    32  	"github.com/juju/juju/environs/network"
    33  	"github.com/juju/juju/environs/simplestreams"
    34  	"github.com/juju/juju/environs/storage"
    35  	envtools "github.com/juju/juju/environs/tools"
    36  	"github.com/juju/juju/instance"
    37  	"github.com/juju/juju/juju/arch"
    38  	"github.com/juju/juju/provider/common"
    39  	"github.com/juju/juju/state"
    40  	"github.com/juju/juju/state/api"
    41  	"github.com/juju/juju/tools"
    42  )
    43  
    44  var logger = loggo.GetLogger("juju.provider.openstack")
    45  
    46  type environProvider struct{}
    47  
    48  var _ environs.EnvironProvider = (*environProvider)(nil)
    49  
    50  var providerInstance environProvider
    51  
    52  // Use shortAttempt to poll for short-term events.
    53  // TODO: This was kept to a long timeout because Nova needs more time than EC2.
    54  // For example, HP Cloud takes around 9.1 seconds (10 samples) to return a
    55  // BUILD(spawning) status. But storage delays are handled separately now, and
    56  // perhaps other polling attempts can time out faster.
    57  var shortAttempt = utils.AttemptStrategy{
    58  	Total: 15 * time.Second,
    59  	Delay: 200 * time.Millisecond,
    60  }
    61  
    62  func init() {
    63  	environs.RegisterProvider("openstack", environProvider{})
    64  }
    65  
    66  func (p environProvider) BoilerplateConfig() string {
    67  	return `
    68  # https://juju.ubuntu.com/docs/config-openstack.html
    69  openstack:
    70      type: openstack
    71  
    72      # use-floating-ip specifies whether a floating IP address is
    73      # required to give the nodes a public IP address. Some
    74      # installations assign public IP addresses by default without
    75      # requiring a floating IP address.
    76      #
    77      # use-floating-ip: false
    78  
    79      # use-default-secgroup specifies whether new machine instances
    80      # should have the "default" Openstack security group assigned.
    81      #
    82      # use-default-secgroup: false
    83  
    84      # network specifies the network label or uuid to bring machines up
    85      # on, in the case where multiple networks exist. It may be omitted
    86      # otherwise.
    87      #
    88      # network: <your network label or uuid>
    89  
    90      # tools-metadata-url specifies the location of the Juju tools and
    91      # metadata. It defaults to the global public tools metadata
    92      # location https://streams.canonical.com/tools.
    93      #
    94      # tools-metadata-url:  https://your-tools-metadata-url
    95  
    96      # image-metadata-url specifies the location of Ubuntu cloud image
    97      # metadata. It defaults to the global public image metadata
    98      # location https://cloud-images.ubuntu.com/releases.
    99      #
   100      # image-metadata-url:  https://your-image-metadata-url
   101  
   102      # image-stream chooses a simplestreams stream to select OS images
   103      # from, for example daily or released images (or any other stream
   104      # available on simplestreams).
   105      #
   106      # image-stream: "released"
   107  
   108      # auth-url defaults to the value of the environment variable
   109      # OS_AUTH_URL, but can be specified here.
   110      #
   111      # auth-url: https://yourkeystoneurl:443/v2.0/
   112  
   113      # tenant-name holds the openstack tenant name. It defaults to the
   114      # environment variable OS_TENANT_NAME.
   115      #
   116      # tenant-name: <your tenant name>
   117  
   118      # region holds the openstack region. It defaults to the
   119      # environment variable OS_REGION_NAME.
   120      #
   121      # region: <your region>
   122  
   123      # The auth-mode, username and password attributes are used for
   124      # userpass authentication (the default).
   125      #
   126      # auth-mode holds the authentication mode. For user-password
   127      # authentication, auth-mode should be "userpass" and username and
   128      # password should be set appropriately; they default to the
   129      # environment variables OS_USERNAME and OS_PASSWORD respectively.
   130      #
   131      # auth-mode: userpass
   132      # username: <your username>
   133      # password: <secret>
   134  
   135      # For key-pair authentication, auth-mode should be "keypair" and
   136      # access-key and secret-key should be set appropriately; they
   137      # default to the environment variables OS_ACCESS_KEY and
   138      # OS_SECRET_KEY respectively.
   139      #
   140      # auth-mode: keypair
   141      # access-key: <secret>
   142      # secret-key: <secret>
   143  
   144  # https://juju.ubuntu.com/docs/config-hpcloud.html
   145  hpcloud:
   146      type: openstack
   147  
   148      # use-floating-ip specifies whether a floating IP address is
   149      # required to give the nodes a public IP address. Some
   150      # installations assign public IP addresses by default without
   151      # requiring a floating IP address.
   152      #
   153      # use-floating-ip: false
   154  
   155      # use-default-secgroup specifies whether new machine instances
   156      # should have the "default" Openstack security group assigned.
   157      #
   158      # use-default-secgroup: false
   159  
   160      # tenant-name holds the openstack tenant name. In HPCloud, this is
   161      # synonymous with the project-name It defaults to the environment
   162      # variable OS_TENANT_NAME.
   163      #
   164      # tenant-name: <your tenant name>
   165  
   166      # image-stream chooses a simplestreams stream to select OS images
   167      # from, for example daily or released images (or any other stream
   168      # available on simplestreams).
   169      #
   170      # image-stream: "released"
   171  
   172      # auth-url holds the keystone url for authentication. It defaults
   173      # to the value of the environment variable OS_AUTH_URL.
   174      #
   175      # auth-url: https://region-a.geo-1.identity.hpcloudsvc.com:35357/v2.0/
   176  
   177      # region holds the HP Cloud region (e.g. az-1.region-a.geo-1). It
   178      # defaults to the environment variable OS_REGION_NAME.
   179      #
   180      # region: <your region>
   181  
   182      # auth-mode holds the authentication mode. For user-password
   183      # authentication, auth-mode should be "userpass" and username and
   184      # password should be set appropriately; they default to the
   185      # environment variables OS_USERNAME and OS_PASSWORD respectively.
   186      #
   187      # auth-mode: userpass
   188      # username: <your_username>
   189      # password: <your_password>
   190  
   191      # For key-pair authentication, auth-mode should be "keypair" and
   192      # access-key and secret-key should be set appropriately; they
   193      # default to the environment variables OS_ACCESS_KEY and
   194      # OS_SECRET_KEY respectively.
   195      #
   196      # auth-mode: keypair
   197      # access-key: <secret>
   198      # secret-key: <secret>
   199  
   200  `[1:]
   201  }
   202  
   203  func (p environProvider) Open(cfg *config.Config) (environs.Environ, error) {
   204  	logger.Infof("opening environment %q", cfg.Name())
   205  	e := new(environ)
   206  	err := e.SetConfig(cfg)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	e.name = cfg.Name()
   211  	return e, nil
   212  }
   213  
   214  func (p environProvider) Prepare(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) {
   215  	attrs := cfg.UnknownAttrs()
   216  	if _, ok := attrs["control-bucket"]; !ok {
   217  		uuid, err := utils.NewUUID()
   218  		if err != nil {
   219  			return nil, err
   220  		}
   221  		attrs["control-bucket"] = fmt.Sprintf("%x", uuid.Raw())
   222  	}
   223  	cfg, err := cfg.Apply(attrs)
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  	return p.Open(cfg)
   228  }
   229  
   230  // MetadataLookupParams returns parameters which are used to query image metadata to
   231  // find matching image information.
   232  func (p environProvider) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
   233  	if region == "" {
   234  		return nil, fmt.Errorf("region must be specified")
   235  	}
   236  	return &simplestreams.MetadataLookupParams{
   237  		Region:        region,
   238  		Architectures: arch.AllSupportedArches,
   239  	}, nil
   240  }
   241  
   242  func (p environProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) {
   243  	m := make(map[string]string)
   244  	ecfg, err := providerInstance.newConfig(cfg)
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  	m["username"] = ecfg.username()
   249  	m["password"] = ecfg.password()
   250  	m["tenant-name"] = ecfg.tenantName()
   251  	return m, nil
   252  }
   253  
   254  func retryGet(uri string) (data []byte, err error) {
   255  	for a := shortAttempt.Start(); a.Next(); {
   256  		var resp *http.Response
   257  		resp, err = http.Get(uri)
   258  		if err != nil {
   259  			continue
   260  		}
   261  		defer resp.Body.Close()
   262  		if resp.StatusCode != http.StatusOK {
   263  			err = fmt.Errorf("bad http response %v", resp.Status)
   264  			continue
   265  		}
   266  		var data []byte
   267  		data, err = ioutil.ReadAll(resp.Body)
   268  		if err != nil {
   269  			continue
   270  		}
   271  		return data, nil
   272  	}
   273  	if err != nil {
   274  		return nil, fmt.Errorf("cannot get %q: %v", uri, err)
   275  	}
   276  	return
   277  }
   278  
   279  type environ struct {
   280  	common.SupportsUnitPlacementPolicy
   281  
   282  	name string
   283  
   284  	// archMutex gates access to supportedArchitectures
   285  	archMutex sync.Mutex
   286  	// supportedArchitectures caches the architectures
   287  	// for which images can be instantiated.
   288  	supportedArchitectures []string
   289  
   290  	ecfgMutex       sync.Mutex
   291  	imageBaseMutex  sync.Mutex
   292  	toolsBaseMutex  sync.Mutex
   293  	ecfgUnlocked    *environConfig
   294  	client          client.AuthenticatingClient
   295  	novaUnlocked    *nova.Client
   296  	storageUnlocked storage.Storage
   297  	// An ordered list of sources in which to find the simplestreams index files used to
   298  	// look up image ids.
   299  	imageSources []simplestreams.DataSource
   300  	// An ordered list of paths in which to find the simplestreams index files used to
   301  	// look up tools ids.
   302  	toolsSources []simplestreams.DataSource
   303  }
   304  
   305  var _ environs.Environ = (*environ)(nil)
   306  var _ imagemetadata.SupportsCustomSources = (*environ)(nil)
   307  var _ envtools.SupportsCustomSources = (*environ)(nil)
   308  var _ simplestreams.HasRegion = (*environ)(nil)
   309  var _ state.Prechecker = (*environ)(nil)
   310  
   311  type openstackInstance struct {
   312  	e        *environ
   313  	instType *instances.InstanceType
   314  	arch     *string
   315  
   316  	mu           sync.Mutex
   317  	serverDetail *nova.ServerDetail
   318  }
   319  
   320  func (inst *openstackInstance) String() string {
   321  	return string(inst.Id())
   322  }
   323  
   324  var _ instance.Instance = (*openstackInstance)(nil)
   325  
   326  func (inst *openstackInstance) Refresh() error {
   327  	inst.mu.Lock()
   328  	defer inst.mu.Unlock()
   329  	server, err := inst.e.nova().GetServer(inst.serverDetail.Id)
   330  	if err != nil {
   331  		return err
   332  	}
   333  	inst.serverDetail = server
   334  	return nil
   335  }
   336  
   337  func (inst *openstackInstance) getServerDetail() *nova.ServerDetail {
   338  	inst.mu.Lock()
   339  	defer inst.mu.Unlock()
   340  	return inst.serverDetail
   341  }
   342  
   343  func (inst *openstackInstance) Id() instance.Id {
   344  	return instance.Id(inst.getServerDetail().Id)
   345  }
   346  
   347  func (inst *openstackInstance) Status() string {
   348  	return inst.getServerDetail().Status
   349  }
   350  
   351  func (inst *openstackInstance) hardwareCharacteristics() *instance.HardwareCharacteristics {
   352  	hc := &instance.HardwareCharacteristics{Arch: inst.arch}
   353  	if inst.instType != nil {
   354  		hc.Mem = &inst.instType.Mem
   355  		// openstack is special in that a 0-size root disk means that
   356  		// the root disk will result in an instance with a root disk
   357  		// the same size as the image that created it, so we just set
   358  		// the HardwareCharacteristics to nil to signal that we don't
   359  		// know what the correct size is.
   360  		if inst.instType.RootDisk == 0 {
   361  			hc.RootDisk = nil
   362  		} else {
   363  			hc.RootDisk = &inst.instType.RootDisk
   364  		}
   365  		hc.CpuCores = &inst.instType.CpuCores
   366  		hc.CpuPower = inst.instType.CpuPower
   367  		// tags not currently supported on openstack
   368  	}
   369  	return hc
   370  }
   371  
   372  // getAddresses returns the existing server information on addresses,
   373  // but fetches the details over the api again if no addresses exist.
   374  func (inst *openstackInstance) getAddresses() (map[string][]nova.IPAddress, error) {
   375  	addrs := inst.getServerDetail().Addresses
   376  	if len(addrs) == 0 {
   377  		server, err := inst.e.nova().GetServer(string(inst.Id()))
   378  		if err != nil {
   379  			return nil, err
   380  		}
   381  		addrs = server.Addresses
   382  	}
   383  	return addrs, nil
   384  }
   385  
   386  // Addresses implements instance.Addresses() returning generic address
   387  // details for the instances, and calling the openstack api if needed.
   388  func (inst *openstackInstance) Addresses() ([]instance.Address, error) {
   389  	addresses, err := inst.getAddresses()
   390  	if err != nil {
   391  		return nil, err
   392  	}
   393  	return convertNovaAddresses(addresses), nil
   394  }
   395  
   396  // convertNovaAddresses returns nova addresses in generic format
   397  func convertNovaAddresses(addresses map[string][]nova.IPAddress) []instance.Address {
   398  	// TODO(gz) Network ordering may be significant but is not preserved by
   399  	// the map, see lp:1188126 for example. That could potentially be fixed
   400  	// in goose, or left to be derived by other means.
   401  	var machineAddresses []instance.Address
   402  	for network, ips := range addresses {
   403  		networkscope := instance.NetworkUnknown
   404  		// For canonistack and hpcloud, public floating addresses may
   405  		// be put in networks named something other than public. Rely
   406  		// on address sanity logic to catch and mark them corectly.
   407  		if network == "public" {
   408  			networkscope = instance.NetworkPublic
   409  		}
   410  		for _, address := range ips {
   411  			// Assume ipv4 unless specified otherwise
   412  			addrtype := instance.Ipv4Address
   413  			if address.Version == 6 {
   414  				addrtype = instance.Ipv6Address
   415  			}
   416  			machineAddr := instance.NewAddress(address.Address, networkscope)
   417  			machineAddr.NetworkName = network
   418  			if machineAddr.Type != addrtype {
   419  				logger.Warningf("derived address type %v, nova reports %v", machineAddr.Type, addrtype)
   420  			}
   421  			machineAddresses = append(machineAddresses, machineAddr)
   422  		}
   423  	}
   424  	return machineAddresses
   425  }
   426  
   427  // TODO: following 30 lines nearly verbatim from environs/ec2
   428  
   429  func (inst *openstackInstance) OpenPorts(machineId string, ports []instance.Port) error {
   430  	if inst.e.Config().FirewallMode() != config.FwInstance {
   431  		return fmt.Errorf("invalid firewall mode %q for opening ports on instance",
   432  			inst.e.Config().FirewallMode())
   433  	}
   434  	name := inst.e.machineGroupName(machineId)
   435  	if err := inst.e.openPortsInGroup(name, ports); err != nil {
   436  		return err
   437  	}
   438  	logger.Infof("opened ports in security group %s: %v", name, ports)
   439  	return nil
   440  }
   441  
   442  func (inst *openstackInstance) ClosePorts(machineId string, ports []instance.Port) error {
   443  	if inst.e.Config().FirewallMode() != config.FwInstance {
   444  		return fmt.Errorf("invalid firewall mode %q for closing ports on instance",
   445  			inst.e.Config().FirewallMode())
   446  	}
   447  	name := inst.e.machineGroupName(machineId)
   448  	if err := inst.e.closePortsInGroup(name, ports); err != nil {
   449  		return err
   450  	}
   451  	logger.Infof("closed ports in security group %s: %v", name, ports)
   452  	return nil
   453  }
   454  
   455  func (inst *openstackInstance) Ports(machineId string) ([]instance.Port, error) {
   456  	if inst.e.Config().FirewallMode() != config.FwInstance {
   457  		return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from instance",
   458  			inst.e.Config().FirewallMode())
   459  	}
   460  	name := inst.e.machineGroupName(machineId)
   461  	return inst.e.portsInGroup(name)
   462  }
   463  
   464  func (e *environ) ecfg() *environConfig {
   465  	e.ecfgMutex.Lock()
   466  	ecfg := e.ecfgUnlocked
   467  	e.ecfgMutex.Unlock()
   468  	return ecfg
   469  }
   470  
   471  func (e *environ) nova() *nova.Client {
   472  	e.ecfgMutex.Lock()
   473  	nova := e.novaUnlocked
   474  	e.ecfgMutex.Unlock()
   475  	return nova
   476  }
   477  
   478  func (e *environ) Name() string {
   479  	return e.name
   480  }
   481  
   482  // SupportedArchitectures is specified on the EnvironCapability interface.
   483  func (e *environ) SupportedArchitectures() ([]string, error) {
   484  	e.archMutex.Lock()
   485  	defer e.archMutex.Unlock()
   486  	if e.supportedArchitectures != nil {
   487  		return e.supportedArchitectures, nil
   488  	}
   489  	// Create a filter to get all images from our region and for the correct stream.
   490  	cloudSpec, err := e.Region()
   491  	if err != nil {
   492  		return nil, err
   493  	}
   494  	imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
   495  		CloudSpec: cloudSpec,
   496  		Stream:    e.Config().ImageStream(),
   497  	})
   498  	e.supportedArchitectures, err = common.SupportedArchitectures(e, imageConstraint)
   499  	return e.supportedArchitectures, err
   500  }
   501  
   502  // SupportNetworks is specified on the EnvironCapability interface.
   503  func (e *environ) SupportNetworks() bool {
   504  	// TODO(dimitern) Once we have support for networking, inquire
   505  	// about capabilities and return true if supported.
   506  	return false
   507  }
   508  
   509  var unsupportedConstraints = []string{
   510  	constraints.Tags,
   511  	constraints.CpuPower,
   512  }
   513  
   514  // ConstraintsValidator is defined on the Environs interface.
   515  func (e *environ) ConstraintsValidator() (constraints.Validator, error) {
   516  	validator := constraints.NewValidator()
   517  	validator.RegisterConflicts(
   518  		[]string{constraints.InstanceType},
   519  		[]string{constraints.Mem, constraints.Arch, constraints.RootDisk, constraints.CpuCores})
   520  	validator.RegisterUnsupported(unsupportedConstraints)
   521  	supportedArches, err := e.SupportedArchitectures()
   522  	if err != nil {
   523  		return nil, err
   524  	}
   525  	validator.RegisterVocabulary(constraints.Arch, supportedArches)
   526  	novaClient := e.nova()
   527  	flavors, err := novaClient.ListFlavorsDetail()
   528  	if err != nil {
   529  		return nil, err
   530  	}
   531  	instTypeNames := make([]string, len(flavors))
   532  	for i, flavor := range flavors {
   533  		instTypeNames[i] = flavor.Name
   534  	}
   535  	validator.RegisterVocabulary(constraints.InstanceType, instTypeNames)
   536  	return validator, nil
   537  }
   538  
   539  // PrecheckInstance is defined on the state.Prechecker interface.
   540  func (e *environ) PrecheckInstance(series string, cons constraints.Value, placement string) error {
   541  	if placement != "" {
   542  		return fmt.Errorf("unknown placement directive: %s", placement)
   543  	}
   544  	if !cons.HasInstanceType() {
   545  		return nil
   546  	}
   547  	// Constraint has an instance-type constraint so let's see if it is valid.
   548  	novaClient := e.nova()
   549  	flavors, err := novaClient.ListFlavorsDetail()
   550  	if err != nil {
   551  		return err
   552  	}
   553  	for _, flavor := range flavors {
   554  		if flavor.Name == *cons.InstanceType {
   555  			return nil
   556  		}
   557  	}
   558  	return fmt.Errorf("invalid Openstack flavour %q specified", *cons.InstanceType)
   559  }
   560  
   561  func (e *environ) Storage() storage.Storage {
   562  	e.ecfgMutex.Lock()
   563  	stor := e.storageUnlocked
   564  	e.ecfgMutex.Unlock()
   565  	return stor
   566  }
   567  
   568  func (e *environ) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) error {
   569  	// The client's authentication may have been reset when finding tools if the agent-version
   570  	// attribute was updated so we need to re-authenticate. This will be a no-op if already authenticated.
   571  	// An authenticated client is needed for the URL() call below.
   572  	err := e.client.Authenticate()
   573  	if err != nil {
   574  		return err
   575  	}
   576  	return common.Bootstrap(ctx, e, args)
   577  }
   578  
   579  func (e *environ) StateInfo() (*state.Info, *api.Info, error) {
   580  	return common.StateInfo(e)
   581  }
   582  
   583  func (e *environ) Config() *config.Config {
   584  	return e.ecfg().Config
   585  }
   586  
   587  func (e *environ) authClient(ecfg *environConfig, authModeCfg AuthMode) client.AuthenticatingClient {
   588  	cred := &identity.Credentials{
   589  		User:       ecfg.username(),
   590  		Secrets:    ecfg.password(),
   591  		Region:     ecfg.region(),
   592  		TenantName: ecfg.tenantName(),
   593  		URL:        ecfg.authURL(),
   594  	}
   595  	// authModeCfg has already been validated so we know it's one of the values below.
   596  	var authMode identity.AuthMode
   597  	switch authModeCfg {
   598  	case AuthLegacy:
   599  		authMode = identity.AuthLegacy
   600  	case AuthUserPass:
   601  		authMode = identity.AuthUserPass
   602  	case AuthKeyPair:
   603  		authMode = identity.AuthKeyPair
   604  		cred.User = ecfg.accessKey()
   605  		cred.Secrets = ecfg.secretKey()
   606  	}
   607  	newClient := client.NewClient
   608  	if !ecfg.SSLHostnameVerification() {
   609  		newClient = client.NewNonValidatingClient
   610  	}
   611  	return newClient(cred, authMode, nil)
   612  }
   613  
   614  func (e *environ) SetConfig(cfg *config.Config) error {
   615  	ecfg, err := providerInstance.newConfig(cfg)
   616  	if err != nil {
   617  		return err
   618  	}
   619  	// At this point, the authentication method config value has been validated so we extract it's value here
   620  	// to avoid having to validate again each time when creating the OpenStack client.
   621  	var authModeCfg AuthMode
   622  	e.ecfgMutex.Lock()
   623  	defer e.ecfgMutex.Unlock()
   624  	authModeCfg = AuthMode(ecfg.authMode())
   625  	e.ecfgUnlocked = ecfg
   626  
   627  	e.client = e.authClient(ecfg, authModeCfg)
   628  	e.novaUnlocked = nova.New(e.client)
   629  
   630  	// create new control storage instance, existing instances continue
   631  	// to reference their existing configuration.
   632  	// public storage instance creation is deferred until needed since authenticated
   633  	// access to the identity service is required so that any juju-tools endpoint can be used.
   634  	e.storageUnlocked = &openstackstorage{
   635  		containerName: ecfg.controlBucket(),
   636  		// this is possibly just a hack - if the ACL is swift.Private,
   637  		// the machine won't be able to get the tools (401 error)
   638  		containerACL: swift.PublicRead,
   639  		swift:        swift.New(e.client)}
   640  	return nil
   641  }
   642  
   643  // GetImageSources returns a list of sources which are used to search for simplestreams image metadata.
   644  func (e *environ) GetImageSources() ([]simplestreams.DataSource, error) {
   645  	e.imageBaseMutex.Lock()
   646  	defer e.imageBaseMutex.Unlock()
   647  
   648  	if e.imageSources != nil {
   649  		return e.imageSources, nil
   650  	}
   651  	if !e.client.IsAuthenticated() {
   652  		err := e.client.Authenticate()
   653  		if err != nil {
   654  			return nil, err
   655  		}
   656  	}
   657  	// Add the simplestreams source off the control bucket.
   658  	e.imageSources = append(e.imageSources, storage.NewStorageSimpleStreamsDataSource(
   659  		"cloud storage", e.Storage(), storage.BaseImagesPath))
   660  	// Add the simplestreams base URL from keystone if it is defined.
   661  	productStreamsURL, err := e.client.MakeServiceURL("product-streams", nil)
   662  	if err == nil {
   663  		verify := utils.VerifySSLHostnames
   664  		if !e.Config().SSLHostnameVerification() {
   665  			verify = utils.NoVerifySSLHostnames
   666  		}
   667  		source := simplestreams.NewURLDataSource("keystone catalog", productStreamsURL, verify)
   668  		e.imageSources = append(e.imageSources, source)
   669  	}
   670  	return e.imageSources, nil
   671  }
   672  
   673  // GetToolsSources returns a list of sources which are used to search for simplestreams tools metadata.
   674  func (e *environ) GetToolsSources() ([]simplestreams.DataSource, error) {
   675  	e.toolsBaseMutex.Lock()
   676  	defer e.toolsBaseMutex.Unlock()
   677  
   678  	if e.toolsSources != nil {
   679  		return e.toolsSources, nil
   680  	}
   681  	if !e.client.IsAuthenticated() {
   682  		err := e.client.Authenticate()
   683  		if err != nil {
   684  			return nil, err
   685  		}
   686  	}
   687  	verify := utils.VerifySSLHostnames
   688  	if !e.Config().SSLHostnameVerification() {
   689  		verify = utils.NoVerifySSLHostnames
   690  	}
   691  	// Add the simplestreams source off the control bucket.
   692  	e.toolsSources = append(e.toolsSources, storage.NewStorageSimpleStreamsDataSource(
   693  		"cloud storage", e.Storage(), storage.BaseToolsPath))
   694  	// Add the simplestreams base URL from keystone if it is defined.
   695  	toolsURL, err := e.client.MakeServiceURL("juju-tools", nil)
   696  	if err == nil {
   697  		source := simplestreams.NewURLDataSource("keystone catalog", toolsURL, verify)
   698  		e.toolsSources = append(e.toolsSources, source)
   699  	}
   700  	return e.toolsSources, nil
   701  }
   702  
   703  // TODO(gz): Move this somewhere more reusable
   704  const uuidPattern = "^([a-fA-F0-9]{8})-([a-fA-f0-9]{4})-([1-5][a-fA-f0-9]{3})-([a-fA-f0-9]{4})-([a-fA-f0-9]{12})$"
   705  
   706  var uuidRegexp = regexp.MustCompile(uuidPattern)
   707  
   708  // resolveNetwork takes either a network id or label and returns a network id
   709  func (e *environ) resolveNetwork(networkName string) (string, error) {
   710  	if uuidRegexp.MatchString(networkName) {
   711  		// Network id supplied, assume valid as boot will fail if not
   712  		return networkName, nil
   713  	}
   714  	// Network label supplied, resolve to a network id
   715  	networks, err := e.nova().ListNetworks()
   716  	if err != nil {
   717  		return "", err
   718  	}
   719  	var networkIds = []string{}
   720  	for _, network := range networks {
   721  		if network.Label == networkName {
   722  			networkIds = append(networkIds, network.Id)
   723  		}
   724  	}
   725  	switch len(networkIds) {
   726  	case 1:
   727  		return networkIds[0], nil
   728  	case 0:
   729  		return "", fmt.Errorf("No networks exist with label %q", networkName)
   730  	}
   731  	return "", fmt.Errorf("Multiple networks with label %q: %v", networkName, networkIds)
   732  }
   733  
   734  // allocatePublicIP tries to find an available floating IP address, or
   735  // allocates a new one, returning it, or an error
   736  func (e *environ) allocatePublicIP() (*nova.FloatingIP, error) {
   737  	fips, err := e.nova().ListFloatingIPs()
   738  	if err != nil {
   739  		return nil, err
   740  	}
   741  	var newfip *nova.FloatingIP
   742  	for _, fip := range fips {
   743  		newfip = &fip
   744  		if fip.InstanceId != nil && *fip.InstanceId != "" {
   745  			// unavailable, skip
   746  			newfip = nil
   747  			continue
   748  		} else {
   749  			// unassigned, we can use it
   750  			return newfip, nil
   751  		}
   752  	}
   753  	if newfip == nil {
   754  		// allocate a new IP and use it
   755  		newfip, err = e.nova().AllocateFloatingIP()
   756  		if err != nil {
   757  			return nil, err
   758  		}
   759  	}
   760  	return newfip, nil
   761  }
   762  
   763  // assignPublicIP tries to assign the given floating IP address to the
   764  // specified server, or returns an error.
   765  func (e *environ) assignPublicIP(fip *nova.FloatingIP, serverId string) (err error) {
   766  	if fip == nil {
   767  		return fmt.Errorf("cannot assign a nil public IP to %q", serverId)
   768  	}
   769  	if fip.InstanceId != nil && *fip.InstanceId == serverId {
   770  		// IP already assigned, nothing to do
   771  		return nil
   772  	}
   773  	// At startup nw_info is not yet cached so this may fail
   774  	// temporarily while the server is being built
   775  	for a := common.LongAttempt.Start(); a.Next(); {
   776  		err = e.nova().AddServerFloatingIP(serverId, fip.IP)
   777  		if err == nil {
   778  			return nil
   779  		}
   780  	}
   781  	return err
   782  }
   783  
   784  // StartInstance is specified in the InstanceBroker interface.
   785  func (e *environ) StartInstance(args environs.StartInstanceParams) (instance.Instance, *instance.HardwareCharacteristics, []network.Info, error) {
   786  
   787  	if args.MachineConfig.HasNetworks() {
   788  		return nil, nil, nil, fmt.Errorf("starting instances with networks is not supported yet.")
   789  	}
   790  
   791  	series := args.Tools.OneSeries()
   792  	arches := args.Tools.Arches()
   793  	spec, err := findInstanceSpec(e, &instances.InstanceConstraint{
   794  		Region:      e.ecfg().region(),
   795  		Series:      series,
   796  		Arches:      arches,
   797  		Constraints: args.Constraints,
   798  	})
   799  	if err != nil {
   800  		return nil, nil, nil, err
   801  	}
   802  	tools, err := args.Tools.Match(tools.Filter{Arch: spec.Image.Arch})
   803  	if err != nil {
   804  		return nil, nil, nil, fmt.Errorf("chosen architecture %v not present in %v", spec.Image.Arch, arches)
   805  	}
   806  
   807  	args.MachineConfig.Tools = tools[0]
   808  
   809  	if err := environs.FinishMachineConfig(args.MachineConfig, e.Config(), args.Constraints); err != nil {
   810  		return nil, nil, nil, err
   811  	}
   812  	userData, err := environs.ComposeUserData(args.MachineConfig, nil)
   813  	if err != nil {
   814  		return nil, nil, nil, fmt.Errorf("cannot make user data: %v", err)
   815  	}
   816  	logger.Debugf("openstack user data; %d bytes", len(userData))
   817  	var networks = []nova.ServerNetworks{}
   818  	usingNetwork := e.ecfg().network()
   819  	if usingNetwork != "" {
   820  		networkId, err := e.resolveNetwork(usingNetwork)
   821  		if err != nil {
   822  			return nil, nil, nil, err
   823  		}
   824  		logger.Debugf("using network id %q", networkId)
   825  		networks = append(networks, nova.ServerNetworks{NetworkId: networkId})
   826  	}
   827  	withPublicIP := e.ecfg().useFloatingIP()
   828  	var publicIP *nova.FloatingIP
   829  	if withPublicIP {
   830  		if fip, err := e.allocatePublicIP(); err != nil {
   831  			return nil, nil, nil, fmt.Errorf("cannot allocate a public IP as needed: %v", err)
   832  		} else {
   833  			publicIP = fip
   834  			logger.Infof("allocated public IP %s", publicIP.IP)
   835  		}
   836  	}
   837  	cfg := e.Config()
   838  	groups, err := e.setUpGroups(args.MachineConfig.MachineId, cfg.StatePort(), cfg.APIPort())
   839  	if err != nil {
   840  		return nil, nil, nil, fmt.Errorf("cannot set up groups: %v", err)
   841  	}
   842  	var groupNames = make([]nova.SecurityGroupName, len(groups))
   843  	for i, g := range groups {
   844  		groupNames[i] = nova.SecurityGroupName{g.Name}
   845  	}
   846  	var opts = nova.RunServerOpts{
   847  		Name:               e.machineFullName(args.MachineConfig.MachineId),
   848  		FlavorId:           spec.InstanceType.Id,
   849  		ImageId:            spec.Image.Id,
   850  		UserData:           userData,
   851  		SecurityGroupNames: groupNames,
   852  		Networks:           networks,
   853  	}
   854  	var server *nova.Entity
   855  	for a := shortAttempt.Start(); a.Next(); {
   856  		server, err = e.nova().RunServer(opts)
   857  		if err == nil || !gooseerrors.IsNotFound(err) {
   858  			break
   859  		}
   860  	}
   861  	if err != nil {
   862  		return nil, nil, nil, fmt.Errorf("cannot run instance: %v", err)
   863  	}
   864  	detail, err := e.nova().GetServer(server.Id)
   865  	if err != nil {
   866  		return nil, nil, nil, fmt.Errorf("cannot get started instance: %v", err)
   867  	}
   868  	inst := &openstackInstance{
   869  		e:            e,
   870  		serverDetail: detail,
   871  		arch:         &spec.Image.Arch,
   872  		instType:     &spec.InstanceType,
   873  	}
   874  	logger.Infof("started instance %q", inst.Id())
   875  	if withPublicIP {
   876  		if err := e.assignPublicIP(publicIP, string(inst.Id())); err != nil {
   877  			if err := e.terminateInstances([]instance.Id{inst.Id()}); err != nil {
   878  				// ignore the failure at this stage, just log it
   879  				logger.Debugf("failed to terminate instance %q: %v", inst.Id(), err)
   880  			}
   881  			return nil, nil, nil, fmt.Errorf("cannot assign public address %s to instance %q: %v", publicIP.IP, inst.Id(), err)
   882  		}
   883  		logger.Infof("assigned public IP %s to %q", publicIP.IP, inst.Id())
   884  	}
   885  	return inst, inst.hardwareCharacteristics(), nil, nil
   886  }
   887  
   888  func (e *environ) StopInstances(ids ...instance.Id) error {
   889  	// If in instance firewall mode, gather the security group names.
   890  	var securityGroupNames []string
   891  	if e.Config().FirewallMode() == config.FwInstance {
   892  		instances, err := e.Instances(ids)
   893  		if err == environs.ErrNoInstances {
   894  			return nil
   895  		}
   896  		securityGroupNames = make([]string, 0, len(ids))
   897  		for _, inst := range instances {
   898  			if inst == nil {
   899  				continue
   900  			}
   901  			openstackName := inst.(*openstackInstance).getServerDetail().Name
   902  			lastDashPos := strings.LastIndex(openstackName, "-")
   903  			if lastDashPos == -1 {
   904  				return fmt.Errorf("cannot identify machine ID in openstack server name %q", openstackName)
   905  			}
   906  			securityGroupName := e.machineGroupName(openstackName[lastDashPos+1:])
   907  			securityGroupNames = append(securityGroupNames, securityGroupName)
   908  		}
   909  	}
   910  	logger.Debugf("terminating instances %v", ids)
   911  	if err := e.terminateInstances(ids); err != nil {
   912  		return err
   913  	}
   914  	if securityGroupNames != nil {
   915  		return e.deleteSecurityGroups(securityGroupNames)
   916  	}
   917  	return nil
   918  }
   919  
   920  // collectInstances tries to get information on each instance id in ids.
   921  // It fills the slots in the given map for known servers with status
   922  // either ACTIVE or BUILD. Returns a list of missing ids.
   923  func (e *environ) collectInstances(ids []instance.Id, out map[instance.Id]instance.Instance) []instance.Id {
   924  	var err error
   925  	serversById := make(map[string]nova.ServerDetail)
   926  	if len(ids) == 1 {
   927  		// most common case - single instance
   928  		var server *nova.ServerDetail
   929  		server, err = e.nova().GetServer(string(ids[0]))
   930  		if server != nil {
   931  			serversById[server.Id] = *server
   932  		}
   933  	} else {
   934  		var servers []nova.ServerDetail
   935  		servers, err = e.nova().ListServersDetail(e.machinesFilter())
   936  		for _, server := range servers {
   937  			serversById[server.Id] = server
   938  		}
   939  	}
   940  	if err != nil {
   941  		return ids
   942  	}
   943  	var missing []instance.Id
   944  	for _, id := range ids {
   945  		if server, found := serversById[string(id)]; found {
   946  			// HPCloud uses "BUILD(spawning)" as an intermediate BUILD states once networking is available.
   947  			switch server.Status {
   948  			case nova.StatusActive, nova.StatusBuild, nova.StatusBuildSpawning:
   949  				// TODO(wallyworld): lookup the flavor details to fill in the instance type data
   950  				out[id] = &openstackInstance{e: e, serverDetail: &server}
   951  				continue
   952  			}
   953  		}
   954  		missing = append(missing, id)
   955  	}
   956  	return missing
   957  }
   958  
   959  func (e *environ) Instances(ids []instance.Id) ([]instance.Instance, error) {
   960  	if len(ids) == 0 {
   961  		return nil, nil
   962  	}
   963  	missing := ids
   964  	found := make(map[instance.Id]instance.Instance)
   965  	// Make a series of requests to cope with eventual consistency.
   966  	// Each request will attempt to add more instances to the requested
   967  	// set.
   968  	for a := shortAttempt.Start(); a.Next(); {
   969  		if missing = e.collectInstances(missing, found); len(missing) == 0 {
   970  			break
   971  		}
   972  	}
   973  	if len(found) == 0 {
   974  		return nil, environs.ErrNoInstances
   975  	}
   976  	insts := make([]instance.Instance, len(ids))
   977  	var err error
   978  	for i, id := range ids {
   979  		if inst := found[id]; inst != nil {
   980  			insts[i] = inst
   981  		} else {
   982  			err = environs.ErrPartialInstances
   983  		}
   984  	}
   985  	return insts, err
   986  }
   987  
   988  // AllocateAddress requests a new address to be allocated for the
   989  // given instance on the given network. This is not implemented on the
   990  // OpenStack provider yet.
   991  func (*environ) AllocateAddress(_ instance.Id, _ network.Id) (instance.Address, error) {
   992  	return instance.Address{}, jujuerrors.NotImplementedf("AllocateAddress")
   993  }
   994  
   995  func (e *environ) AllInstances() (insts []instance.Instance, err error) {
   996  	servers, err := e.nova().ListServersDetail(e.machinesFilter())
   997  	if err != nil {
   998  		return nil, err
   999  	}
  1000  	for _, server := range servers {
  1001  		if server.Status == nova.StatusActive || server.Status == nova.StatusBuild {
  1002  			var s = server
  1003  			// TODO(wallyworld): lookup the flavor details to fill in the instance type data
  1004  			insts = append(insts, &openstackInstance{
  1005  				e:            e,
  1006  				serverDetail: &s,
  1007  			})
  1008  		}
  1009  	}
  1010  	return insts, err
  1011  }
  1012  
  1013  func (e *environ) Destroy() error {
  1014  	err := common.Destroy(e)
  1015  	if err != nil {
  1016  		return err
  1017  	}
  1018  	novaClient := e.nova()
  1019  	securityGroups, err := novaClient.ListSecurityGroups()
  1020  	if err != nil {
  1021  		return err
  1022  	}
  1023  	re, err := regexp.Compile(fmt.Sprintf("^%s(-\\d+)?$", e.jujuGroupName()))
  1024  	if err != nil {
  1025  		return err
  1026  	}
  1027  	globalGroupName := e.globalGroupName()
  1028  	for _, group := range securityGroups {
  1029  		if re.MatchString(group.Name) || group.Name == globalGroupName {
  1030  			err = novaClient.DeleteSecurityGroup(group.Id)
  1031  			if err != nil {
  1032  				logger.Warningf("cannot delete security group %q. Used by another environment?", group.Name)
  1033  			}
  1034  		}
  1035  	}
  1036  	return nil
  1037  }
  1038  
  1039  func (e *environ) globalGroupName() string {
  1040  	return fmt.Sprintf("%s-global", e.jujuGroupName())
  1041  }
  1042  
  1043  func (e *environ) machineGroupName(machineId string) string {
  1044  	return fmt.Sprintf("%s-%s", e.jujuGroupName(), machineId)
  1045  }
  1046  
  1047  func (e *environ) jujuGroupName() string {
  1048  	return fmt.Sprintf("juju-%s", e.name)
  1049  }
  1050  
  1051  func (e *environ) machineFullName(machineId string) string {
  1052  	return fmt.Sprintf("juju-%s-%s", e.Name(), names.MachineTag(machineId))
  1053  }
  1054  
  1055  // machinesFilter returns a nova.Filter matching all machines in the environment.
  1056  func (e *environ) machinesFilter() *nova.Filter {
  1057  	filter := nova.NewFilter()
  1058  	filter.Set(nova.FilterServer, fmt.Sprintf("juju-%s-machine-\\d*", e.Name()))
  1059  	return filter
  1060  }
  1061  
  1062  func (e *environ) openPortsInGroup(name string, ports []instance.Port) error {
  1063  	novaclient := e.nova()
  1064  	group, err := novaclient.SecurityGroupByName(name)
  1065  	if err != nil {
  1066  		return err
  1067  	}
  1068  	for _, port := range ports {
  1069  		_, err := novaclient.CreateSecurityGroupRule(nova.RuleInfo{
  1070  			ParentGroupId: group.Id,
  1071  			FromPort:      port.Number,
  1072  			ToPort:        port.Number,
  1073  			IPProtocol:    port.Protocol,
  1074  			Cidr:          "0.0.0.0/0",
  1075  		})
  1076  		if err != nil {
  1077  			// TODO: if err is not rule already exists, raise?
  1078  			logger.Debugf("error creating security group rule: %v", err.Error())
  1079  		}
  1080  	}
  1081  	return nil
  1082  }
  1083  
  1084  func (e *environ) closePortsInGroup(name string, ports []instance.Port) error {
  1085  	if len(ports) == 0 {
  1086  		return nil
  1087  	}
  1088  	novaclient := e.nova()
  1089  	group, err := novaclient.SecurityGroupByName(name)
  1090  	if err != nil {
  1091  		return err
  1092  	}
  1093  	// TODO: Hey look ma, it's quadratic
  1094  	for _, port := range ports {
  1095  		for _, p := range (*group).Rules {
  1096  			if p.IPProtocol == nil || *p.IPProtocol != port.Protocol ||
  1097  				p.FromPort == nil || *p.FromPort != port.Number ||
  1098  				p.ToPort == nil || *p.ToPort != port.Number {
  1099  				continue
  1100  			}
  1101  			err := novaclient.DeleteSecurityGroupRule(p.Id)
  1102  			if err != nil {
  1103  				return err
  1104  			}
  1105  			break
  1106  		}
  1107  	}
  1108  	return nil
  1109  }
  1110  
  1111  func (e *environ) portsInGroup(name string) (ports []instance.Port, err error) {
  1112  	group, err := e.nova().SecurityGroupByName(name)
  1113  	if err != nil {
  1114  		return nil, err
  1115  	}
  1116  	for _, p := range (*group).Rules {
  1117  		for i := *p.FromPort; i <= *p.ToPort; i++ {
  1118  			ports = append(ports, instance.Port{
  1119  				Protocol: *p.IPProtocol,
  1120  				Number:   i,
  1121  			})
  1122  		}
  1123  	}
  1124  	instance.SortPorts(ports)
  1125  	return ports, nil
  1126  }
  1127  
  1128  // TODO: following 30 lines nearly verbatim from environs/ec2
  1129  
  1130  func (e *environ) OpenPorts(ports []instance.Port) error {
  1131  	if e.Config().FirewallMode() != config.FwGlobal {
  1132  		return fmt.Errorf("invalid firewall mode %q for opening ports on environment",
  1133  			e.Config().FirewallMode())
  1134  	}
  1135  	if err := e.openPortsInGroup(e.globalGroupName(), ports); err != nil {
  1136  		return err
  1137  	}
  1138  	logger.Infof("opened ports in global group: %v", ports)
  1139  	return nil
  1140  }
  1141  
  1142  func (e *environ) ClosePorts(ports []instance.Port) error {
  1143  	if e.Config().FirewallMode() != config.FwGlobal {
  1144  		return fmt.Errorf("invalid firewall mode %q for closing ports on environment",
  1145  			e.Config().FirewallMode())
  1146  	}
  1147  	if err := e.closePortsInGroup(e.globalGroupName(), ports); err != nil {
  1148  		return err
  1149  	}
  1150  	logger.Infof("closed ports in global group: %v", ports)
  1151  	return nil
  1152  }
  1153  
  1154  func (e *environ) Ports() ([]instance.Port, error) {
  1155  	if e.Config().FirewallMode() != config.FwGlobal {
  1156  		return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from environment",
  1157  			e.Config().FirewallMode())
  1158  	}
  1159  	return e.portsInGroup(e.globalGroupName())
  1160  }
  1161  
  1162  func (e *environ) Provider() environs.EnvironProvider {
  1163  	return &providerInstance
  1164  }
  1165  
  1166  func (e *environ) setUpGlobalGroup(groupName string, statePort, apiPort int) (nova.SecurityGroup, error) {
  1167  	return e.ensureGroup(groupName,
  1168  		[]nova.RuleInfo{
  1169  			{
  1170  				IPProtocol: "tcp",
  1171  				FromPort:   22,
  1172  				ToPort:     22,
  1173  				Cidr:       "0.0.0.0/0",
  1174  			},
  1175  			{
  1176  				IPProtocol: "tcp",
  1177  				FromPort:   statePort,
  1178  				ToPort:     statePort,
  1179  				Cidr:       "0.0.0.0/0",
  1180  			},
  1181  			{
  1182  				IPProtocol: "tcp",
  1183  				FromPort:   apiPort,
  1184  				ToPort:     apiPort,
  1185  				Cidr:       "0.0.0.0/0",
  1186  			},
  1187  			{
  1188  				IPProtocol: "tcp",
  1189  				FromPort:   1,
  1190  				ToPort:     65535,
  1191  			},
  1192  			{
  1193  				IPProtocol: "udp",
  1194  				FromPort:   1,
  1195  				ToPort:     65535,
  1196  			},
  1197  			{
  1198  				IPProtocol: "icmp",
  1199  				FromPort:   -1,
  1200  				ToPort:     -1,
  1201  			},
  1202  		})
  1203  }
  1204  
  1205  // setUpGroups creates the security groups for the new machine, and
  1206  // returns them.
  1207  //
  1208  // Instances are tagged with a group so they can be distinguished from
  1209  // other instances that might be running on the same OpenStack account.
  1210  // In addition, a specific machine security group is created for each
  1211  // machine, so that its firewall rules can be configured per machine.
  1212  //
  1213  // Note: ideally we'd have a better way to determine group membership so that 2
  1214  // people that happen to share an openstack account and name their environment
  1215  // "openstack" don't end up destroying each other's machines.
  1216  func (e *environ) setUpGroups(machineId string, statePort, apiPort int) ([]nova.SecurityGroup, error) {
  1217  	jujuGroup, err := e.setUpGlobalGroup(e.jujuGroupName(), statePort, apiPort)
  1218  	if err != nil {
  1219  		return nil, err
  1220  	}
  1221  	var machineGroup nova.SecurityGroup
  1222  	switch e.Config().FirewallMode() {
  1223  	case config.FwInstance:
  1224  		machineGroup, err = e.ensureGroup(e.machineGroupName(machineId), nil)
  1225  	case config.FwGlobal:
  1226  		machineGroup, err = e.ensureGroup(e.globalGroupName(), nil)
  1227  	}
  1228  	if err != nil {
  1229  		return nil, err
  1230  	}
  1231  	groups := []nova.SecurityGroup{jujuGroup, machineGroup}
  1232  	if e.ecfg().useDefaultSecurityGroup() {
  1233  		defaultGroup, err := e.nova().SecurityGroupByName("default")
  1234  		if err != nil {
  1235  			return nil, fmt.Errorf("loading default security group: %v", err)
  1236  		}
  1237  		groups = append(groups, *defaultGroup)
  1238  	}
  1239  	return groups, nil
  1240  }
  1241  
  1242  // zeroGroup holds the zero security group.
  1243  var zeroGroup nova.SecurityGroup
  1244  
  1245  // ensureGroup returns the security group with name and perms.
  1246  // If a group with name does not exist, one will be created.
  1247  // If it exists, its permissions are set to perms.
  1248  func (e *environ) ensureGroup(name string, rules []nova.RuleInfo) (nova.SecurityGroup, error) {
  1249  	novaClient := e.nova()
  1250  	// First attempt to look up an existing group by name.
  1251  	group, err := novaClient.SecurityGroupByName(name)
  1252  	if err == nil {
  1253  		// Group exists, so assume it is correctly set up and return it.
  1254  		// TODO(jam): 2013-09-18 http://pad.lv/121795
  1255  		// We really should verify the group is set up correctly,
  1256  		// because deleting and re-creating environments can get us bad
  1257  		// groups (especially if they were set up under Python)
  1258  		return *group, nil
  1259  	}
  1260  	// Doesn't exist, so try and create it.
  1261  	group, err = novaClient.CreateSecurityGroup(name, "juju group")
  1262  	if err != nil {
  1263  		if !gooseerrors.IsDuplicateValue(err) {
  1264  			return zeroGroup, err
  1265  		} else {
  1266  			// We just tried to create a duplicate group, so load the existing group.
  1267  			group, err = novaClient.SecurityGroupByName(name)
  1268  			if err != nil {
  1269  				return zeroGroup, err
  1270  			}
  1271  			return *group, nil
  1272  		}
  1273  	}
  1274  	// The new group is created so now add the rules.
  1275  	group.Rules = make([]nova.SecurityGroupRule, len(rules))
  1276  	for i, rule := range rules {
  1277  		rule.ParentGroupId = group.Id
  1278  		if rule.Cidr == "" {
  1279  			// http://pad.lv/1226996 Rules that don't have a CIDR
  1280  			// are meant to apply only to this group. If you don't
  1281  			// supply CIDR or GroupId then openstack assumes you
  1282  			// mean CIDR=0.0.0.0/0
  1283  			rule.GroupId = &group.Id
  1284  		}
  1285  		groupRule, err := novaClient.CreateSecurityGroupRule(rule)
  1286  		if err != nil && !gooseerrors.IsDuplicateValue(err) {
  1287  			return zeroGroup, err
  1288  		}
  1289  		group.Rules[i] = *groupRule
  1290  	}
  1291  	return *group, nil
  1292  }
  1293  
  1294  // deleteSecurityGroups deletes the given security groups. If a security
  1295  // group is also used by another environment (see bug #1300755), an attempt
  1296  // to delete this group fails. A warning is logged in this case.
  1297  func (e *environ) deleteSecurityGroups(securityGroupNames []string) error {
  1298  	novaclient := e.nova()
  1299  	allSecurityGroups, err := novaclient.ListSecurityGroups()
  1300  	if err != nil {
  1301  		return err
  1302  	}
  1303  	for _, securityGroup := range allSecurityGroups {
  1304  		for _, name := range securityGroupNames {
  1305  			if securityGroup.Name == name {
  1306  				err := novaclient.DeleteSecurityGroup(securityGroup.Id)
  1307  				if err != nil {
  1308  					logger.Warningf("cannot delete security group %q. Used by another environment?", name)
  1309  				}
  1310  				break
  1311  			}
  1312  		}
  1313  	}
  1314  	return nil
  1315  }
  1316  
  1317  func (e *environ) terminateInstances(ids []instance.Id) error {
  1318  	if len(ids) == 0 {
  1319  		return nil
  1320  	}
  1321  	var firstErr error
  1322  	novaClient := e.nova()
  1323  	for _, id := range ids {
  1324  		err := novaClient.DeleteServer(string(id))
  1325  		if gooseerrors.IsNotFound(err) {
  1326  			err = nil
  1327  		}
  1328  		if err != nil && firstErr == nil {
  1329  			logger.Debugf("error terminating instance %q: %v", id, err)
  1330  			firstErr = err
  1331  		}
  1332  	}
  1333  	return firstErr
  1334  }
  1335  
  1336  // MetadataLookupParams returns parameters which are used to query simplestreams metadata.
  1337  func (e *environ) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
  1338  	if region == "" {
  1339  		region = e.ecfg().region()
  1340  	}
  1341  	cloudSpec, err := e.cloudSpec(region)
  1342  	if err != nil {
  1343  		return nil, err
  1344  	}
  1345  	return &simplestreams.MetadataLookupParams{
  1346  		Series:        config.PreferredSeries(e.ecfg()),
  1347  		Region:        cloudSpec.Region,
  1348  		Endpoint:      cloudSpec.Endpoint,
  1349  		Architectures: arch.AllSupportedArches,
  1350  	}, nil
  1351  }
  1352  
  1353  // Region is specified in the HasRegion interface.
  1354  func (e *environ) Region() (simplestreams.CloudSpec, error) {
  1355  	return e.cloudSpec(e.ecfg().region())
  1356  }
  1357  
  1358  func (e *environ) cloudSpec(region string) (simplestreams.CloudSpec, error) {
  1359  	return simplestreams.CloudSpec{
  1360  		Region:   region,
  1361  		Endpoint: e.ecfg().authURL(),
  1362  	}, nil
  1363  }