github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/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/simplestreams"
    33  	"github.com/juju/juju/environs/storage"
    34  	envtools "github.com/juju/juju/environs/tools"
    35  	"github.com/juju/juju/instance"
    36  	"github.com/juju/juju/juju/arch"
    37  	"github.com/juju/juju/network"
    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: true
   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. 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  	availabilityZonesMutex sync.Mutex
   305  	availabilityZones      []common.AvailabilityZone
   306  }
   307  
   308  var _ environs.Environ = (*environ)(nil)
   309  var _ imagemetadata.SupportsCustomSources = (*environ)(nil)
   310  var _ envtools.SupportsCustomSources = (*environ)(nil)
   311  var _ simplestreams.HasRegion = (*environ)(nil)
   312  var _ state.Prechecker = (*environ)(nil)
   313  var _ state.InstanceDistributor = (*environ)(nil)
   314  
   315  type openstackInstance struct {
   316  	e        *environ
   317  	instType *instances.InstanceType
   318  	arch     *string
   319  
   320  	mu           sync.Mutex
   321  	serverDetail *nova.ServerDetail
   322  }
   323  
   324  func (inst *openstackInstance) String() string {
   325  	return string(inst.Id())
   326  }
   327  
   328  var _ instance.Instance = (*openstackInstance)(nil)
   329  
   330  func (inst *openstackInstance) Refresh() error {
   331  	inst.mu.Lock()
   332  	defer inst.mu.Unlock()
   333  	server, err := inst.e.nova().GetServer(inst.serverDetail.Id)
   334  	if err != nil {
   335  		return err
   336  	}
   337  	inst.serverDetail = server
   338  	return nil
   339  }
   340  
   341  func (inst *openstackInstance) getServerDetail() *nova.ServerDetail {
   342  	inst.mu.Lock()
   343  	defer inst.mu.Unlock()
   344  	return inst.serverDetail
   345  }
   346  
   347  func (inst *openstackInstance) Id() instance.Id {
   348  	return instance.Id(inst.getServerDetail().Id)
   349  }
   350  
   351  func (inst *openstackInstance) Status() string {
   352  	return inst.getServerDetail().Status
   353  }
   354  
   355  func (inst *openstackInstance) hardwareCharacteristics() *instance.HardwareCharacteristics {
   356  	hc := &instance.HardwareCharacteristics{Arch: inst.arch}
   357  	if inst.instType != nil {
   358  		hc.Mem = &inst.instType.Mem
   359  		// openstack is special in that a 0-size root disk means that
   360  		// the root disk will result in an instance with a root disk
   361  		// the same size as the image that created it, so we just set
   362  		// the HardwareCharacteristics to nil to signal that we don't
   363  		// know what the correct size is.
   364  		if inst.instType.RootDisk == 0 {
   365  			hc.RootDisk = nil
   366  		} else {
   367  			hc.RootDisk = &inst.instType.RootDisk
   368  		}
   369  		hc.CpuCores = &inst.instType.CpuCores
   370  		hc.CpuPower = inst.instType.CpuPower
   371  		// tags not currently supported on openstack
   372  	}
   373  	return hc
   374  }
   375  
   376  // getAddresses returns the existing server information on addresses,
   377  // but fetches the details over the api again if no addresses exist.
   378  func (inst *openstackInstance) getAddresses() (map[string][]nova.IPAddress, error) {
   379  	addrs := inst.getServerDetail().Addresses
   380  	if len(addrs) == 0 {
   381  		server, err := inst.e.nova().GetServer(string(inst.Id()))
   382  		if err != nil {
   383  			return nil, err
   384  		}
   385  		addrs = server.Addresses
   386  	}
   387  	return addrs, nil
   388  }
   389  
   390  // Addresses implements network.Addresses() returning generic address
   391  // details for the instances, and calling the openstack api if needed.
   392  func (inst *openstackInstance) Addresses() ([]network.Address, error) {
   393  	addresses, err := inst.getAddresses()
   394  	if err != nil {
   395  		return nil, err
   396  	}
   397  	return convertNovaAddresses(addresses), nil
   398  }
   399  
   400  // convertNovaAddresses returns nova addresses in generic format
   401  func convertNovaAddresses(addresses map[string][]nova.IPAddress) []network.Address {
   402  	// TODO(gz) Network ordering may be significant but is not preserved by
   403  	// the map, see lp:1188126 for example. That could potentially be fixed
   404  	// in goose, or left to be derived by other means.
   405  	var machineAddresses []network.Address
   406  	for netName, ips := range addresses {
   407  		networkScope := network.ScopeUnknown
   408  		// For canonistack and hpcloud, public floating addresses may
   409  		// be put in networks named something other than public. Rely
   410  		// on address sanity logic to catch and mark them corectly.
   411  		if netName == "public" {
   412  			networkScope = network.ScopePublic
   413  		}
   414  		for _, address := range ips {
   415  			// Assume IPv4 unless specified otherwise
   416  			addrtype := network.IPv4Address
   417  			if address.Version == 6 {
   418  				addrtype = network.IPv6Address
   419  			}
   420  			machineAddr := network.NewAddress(address.Address, networkScope)
   421  			machineAddr.NetworkName = netName
   422  			if machineAddr.Type != addrtype {
   423  				logger.Warningf("derived address type %v, nova reports %v", machineAddr.Type, addrtype)
   424  			}
   425  			machineAddresses = append(machineAddresses, machineAddr)
   426  		}
   427  	}
   428  	return machineAddresses
   429  }
   430  
   431  // TODO: following 30 lines nearly verbatim from environs/ec2
   432  
   433  func (inst *openstackInstance) OpenPorts(machineId string, ports []network.Port) error {
   434  	if inst.e.Config().FirewallMode() != config.FwInstance {
   435  		return fmt.Errorf("invalid firewall mode %q for opening ports on instance",
   436  			inst.e.Config().FirewallMode())
   437  	}
   438  	name := inst.e.machineGroupName(machineId)
   439  	if err := inst.e.openPortsInGroup(name, ports); err != nil {
   440  		return err
   441  	}
   442  	logger.Infof("opened ports in security group %s: %v", name, ports)
   443  	return nil
   444  }
   445  
   446  func (inst *openstackInstance) ClosePorts(machineId string, ports []network.Port) error {
   447  	if inst.e.Config().FirewallMode() != config.FwInstance {
   448  		return fmt.Errorf("invalid firewall mode %q for closing ports on instance",
   449  			inst.e.Config().FirewallMode())
   450  	}
   451  	name := inst.e.machineGroupName(machineId)
   452  	if err := inst.e.closePortsInGroup(name, ports); err != nil {
   453  		return err
   454  	}
   455  	logger.Infof("closed ports in security group %s: %v", name, ports)
   456  	return nil
   457  }
   458  
   459  func (inst *openstackInstance) Ports(machineId string) ([]network.Port, error) {
   460  	if inst.e.Config().FirewallMode() != config.FwInstance {
   461  		return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from instance",
   462  			inst.e.Config().FirewallMode())
   463  	}
   464  	name := inst.e.machineGroupName(machineId)
   465  	return inst.e.portsInGroup(name)
   466  }
   467  
   468  func (e *environ) ecfg() *environConfig {
   469  	e.ecfgMutex.Lock()
   470  	ecfg := e.ecfgUnlocked
   471  	e.ecfgMutex.Unlock()
   472  	return ecfg
   473  }
   474  
   475  func (e *environ) nova() *nova.Client {
   476  	e.ecfgMutex.Lock()
   477  	nova := e.novaUnlocked
   478  	e.ecfgMutex.Unlock()
   479  	return nova
   480  }
   481  
   482  func (e *environ) Name() string {
   483  	return e.name
   484  }
   485  
   486  // SupportedArchitectures is specified on the EnvironCapability interface.
   487  func (e *environ) SupportedArchitectures() ([]string, error) {
   488  	e.archMutex.Lock()
   489  	defer e.archMutex.Unlock()
   490  	if e.supportedArchitectures != nil {
   491  		return e.supportedArchitectures, nil
   492  	}
   493  	// Create a filter to get all images from our region and for the correct stream.
   494  	cloudSpec, err := e.Region()
   495  	if err != nil {
   496  		return nil, err
   497  	}
   498  	imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
   499  		CloudSpec: cloudSpec,
   500  		Stream:    e.Config().ImageStream(),
   501  	})
   502  	e.supportedArchitectures, err = common.SupportedArchitectures(e, imageConstraint)
   503  	return e.supportedArchitectures, err
   504  }
   505  
   506  // SupportNetworks is specified on the EnvironCapability interface.
   507  func (e *environ) SupportNetworks() bool {
   508  	// TODO(dimitern) Once we have support for networking, inquire
   509  	// about capabilities and return true if supported.
   510  	return false
   511  }
   512  
   513  var unsupportedConstraints = []string{
   514  	constraints.Tags,
   515  	constraints.CpuPower,
   516  }
   517  
   518  // ConstraintsValidator is defined on the Environs interface.
   519  func (e *environ) ConstraintsValidator() (constraints.Validator, error) {
   520  	validator := constraints.NewValidator()
   521  	validator.RegisterConflicts(
   522  		[]string{constraints.InstanceType},
   523  		[]string{constraints.Mem, constraints.Arch, constraints.RootDisk, constraints.CpuCores})
   524  	validator.RegisterUnsupported(unsupportedConstraints)
   525  	supportedArches, err := e.SupportedArchitectures()
   526  	if err != nil {
   527  		return nil, err
   528  	}
   529  	validator.RegisterVocabulary(constraints.Arch, supportedArches)
   530  	novaClient := e.nova()
   531  	flavors, err := novaClient.ListFlavorsDetail()
   532  	if err != nil {
   533  		return nil, err
   534  	}
   535  	instTypeNames := make([]string, len(flavors))
   536  	for i, flavor := range flavors {
   537  		instTypeNames[i] = flavor.Name
   538  	}
   539  	validator.RegisterVocabulary(constraints.InstanceType, instTypeNames)
   540  	return validator, nil
   541  }
   542  
   543  var novaListAvailabilityZones = (*nova.Client).ListAvailabilityZones
   544  
   545  type openstackAvailabilityZone struct {
   546  	nova.AvailabilityZone
   547  }
   548  
   549  func (z *openstackAvailabilityZone) Name() string {
   550  	return z.AvailabilityZone.Name
   551  }
   552  
   553  func (z *openstackAvailabilityZone) Available() bool {
   554  	return z.AvailabilityZone.State.Available
   555  }
   556  
   557  // AvailabilityZones returns a slice of availability zones.
   558  func (e *environ) AvailabilityZones() ([]common.AvailabilityZone, error) {
   559  	e.availabilityZonesMutex.Lock()
   560  	defer e.availabilityZonesMutex.Unlock()
   561  	if e.availabilityZones == nil {
   562  		zones, err := novaListAvailabilityZones(e.nova())
   563  		if err != nil {
   564  			return nil, err
   565  		}
   566  		e.availabilityZones = make([]common.AvailabilityZone, len(zones))
   567  		for i, z := range zones {
   568  			e.availabilityZones[i] = &openstackAvailabilityZone{z}
   569  		}
   570  	}
   571  	return e.availabilityZones, nil
   572  }
   573  
   574  // InstanceAvailabilityZoneNames returns the availability zone names for each
   575  // of the specified instances.
   576  func (e *environ) InstanceAvailabilityZoneNames(ids []instance.Id) ([]string, error) {
   577  	instances, err := e.Instances(ids)
   578  	if err != nil && err != environs.ErrPartialInstances {
   579  		return nil, err
   580  	}
   581  	zones := make([]string, len(instances))
   582  	for i, inst := range instances {
   583  		if inst == nil {
   584  			continue
   585  		}
   586  		zones[i] = inst.(*openstackInstance).serverDetail.AvailabilityZone
   587  	}
   588  	return zones, err
   589  }
   590  
   591  type openstackPlacement struct {
   592  	availabilityZone nova.AvailabilityZone
   593  }
   594  
   595  func (e *environ) parsePlacement(placement string) (*openstackPlacement, error) {
   596  	pos := strings.IndexRune(placement, '=')
   597  	if pos == -1 {
   598  		return nil, fmt.Errorf("unknown placement directive: %v", placement)
   599  	}
   600  	switch key, value := placement[:pos], placement[pos+1:]; key {
   601  	case "zone":
   602  		availabilityZone := value
   603  		zones, err := e.AvailabilityZones()
   604  		if err != nil {
   605  			return nil, err
   606  		}
   607  		for _, z := range zones {
   608  			if z.Name() == availabilityZone {
   609  				return &openstackPlacement{
   610  					z.(*openstackAvailabilityZone).AvailabilityZone,
   611  				}, nil
   612  			}
   613  		}
   614  		return nil, fmt.Errorf("invalid availability zone %q", availabilityZone)
   615  	}
   616  	return nil, fmt.Errorf("unknown placement directive: %v", placement)
   617  }
   618  
   619  // PrecheckInstance is defined on the state.Prechecker interface.
   620  func (e *environ) PrecheckInstance(series string, cons constraints.Value, placement string) error {
   621  	if placement != "" {
   622  		if _, err := e.parsePlacement(placement); err != nil {
   623  			return err
   624  		}
   625  	}
   626  	if !cons.HasInstanceType() {
   627  		return nil
   628  	}
   629  	// Constraint has an instance-type constraint so let's see if it is valid.
   630  	novaClient := e.nova()
   631  	flavors, err := novaClient.ListFlavorsDetail()
   632  	if err != nil {
   633  		return err
   634  	}
   635  	for _, flavor := range flavors {
   636  		if flavor.Name == *cons.InstanceType {
   637  			return nil
   638  		}
   639  	}
   640  	return fmt.Errorf("invalid Openstack flavour %q specified", *cons.InstanceType)
   641  }
   642  
   643  func (e *environ) Storage() storage.Storage {
   644  	e.ecfgMutex.Lock()
   645  	stor := e.storageUnlocked
   646  	e.ecfgMutex.Unlock()
   647  	return stor
   648  }
   649  
   650  func (e *environ) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) error {
   651  	// The client's authentication may have been reset when finding tools if the agent-version
   652  	// attribute was updated so we need to re-authenticate. This will be a no-op if already authenticated.
   653  	// An authenticated client is needed for the URL() call below.
   654  	err := e.client.Authenticate()
   655  	if err != nil {
   656  		return err
   657  	}
   658  	return common.Bootstrap(ctx, e, args)
   659  }
   660  
   661  func (e *environ) StateInfo() (*state.Info, *api.Info, error) {
   662  	return common.StateInfo(e)
   663  }
   664  
   665  func (e *environ) Config() *config.Config {
   666  	return e.ecfg().Config
   667  }
   668  
   669  func (e *environ) authClient(ecfg *environConfig, authModeCfg AuthMode) client.AuthenticatingClient {
   670  	cred := &identity.Credentials{
   671  		User:       ecfg.username(),
   672  		Secrets:    ecfg.password(),
   673  		Region:     ecfg.region(),
   674  		TenantName: ecfg.tenantName(),
   675  		URL:        ecfg.authURL(),
   676  	}
   677  	// authModeCfg has already been validated so we know it's one of the values below.
   678  	var authMode identity.AuthMode
   679  	switch authModeCfg {
   680  	case AuthLegacy:
   681  		authMode = identity.AuthLegacy
   682  	case AuthUserPass:
   683  		authMode = identity.AuthUserPass
   684  	case AuthKeyPair:
   685  		authMode = identity.AuthKeyPair
   686  		cred.User = ecfg.accessKey()
   687  		cred.Secrets = ecfg.secretKey()
   688  	}
   689  	newClient := client.NewClient
   690  	if !ecfg.SSLHostnameVerification() {
   691  		newClient = client.NewNonValidatingClient
   692  	}
   693  	return newClient(cred, authMode, nil)
   694  }
   695  
   696  func (e *environ) SetConfig(cfg *config.Config) error {
   697  	ecfg, err := providerInstance.newConfig(cfg)
   698  	if err != nil {
   699  		return err
   700  	}
   701  	// At this point, the authentication method config value has been validated so we extract it's value here
   702  	// to avoid having to validate again each time when creating the OpenStack client.
   703  	var authModeCfg AuthMode
   704  	e.ecfgMutex.Lock()
   705  	defer e.ecfgMutex.Unlock()
   706  	authModeCfg = AuthMode(ecfg.authMode())
   707  	e.ecfgUnlocked = ecfg
   708  
   709  	e.client = e.authClient(ecfg, authModeCfg)
   710  	e.novaUnlocked = nova.New(e.client)
   711  
   712  	// create new control storage instance, existing instances continue
   713  	// to reference their existing configuration.
   714  	// public storage instance creation is deferred until needed since authenticated
   715  	// access to the identity service is required so that any juju-tools endpoint can be used.
   716  	e.storageUnlocked = &openstackstorage{
   717  		containerName: ecfg.controlBucket(),
   718  		// this is possibly just a hack - if the ACL is swift.Private,
   719  		// the machine won't be able to get the tools (401 error)
   720  		containerACL: swift.PublicRead,
   721  		swift:        swift.New(e.client)}
   722  	return nil
   723  }
   724  
   725  // GetImageSources returns a list of sources which are used to search for simplestreams image metadata.
   726  func (e *environ) GetImageSources() ([]simplestreams.DataSource, error) {
   727  	e.imageBaseMutex.Lock()
   728  	defer e.imageBaseMutex.Unlock()
   729  
   730  	if e.imageSources != nil {
   731  		return e.imageSources, nil
   732  	}
   733  	if !e.client.IsAuthenticated() {
   734  		err := e.client.Authenticate()
   735  		if err != nil {
   736  			return nil, err
   737  		}
   738  	}
   739  	// Add the simplestreams source off the control bucket.
   740  	e.imageSources = append(e.imageSources, storage.NewStorageSimpleStreamsDataSource(
   741  		"cloud storage", e.Storage(), storage.BaseImagesPath))
   742  	// Add the simplestreams base URL from keystone if it is defined.
   743  	productStreamsURL, err := e.client.MakeServiceURL("product-streams", nil)
   744  	if err == nil {
   745  		verify := utils.VerifySSLHostnames
   746  		if !e.Config().SSLHostnameVerification() {
   747  			verify = utils.NoVerifySSLHostnames
   748  		}
   749  		source := simplestreams.NewURLDataSource("keystone catalog", productStreamsURL, verify)
   750  		e.imageSources = append(e.imageSources, source)
   751  	}
   752  	return e.imageSources, nil
   753  }
   754  
   755  // GetToolsSources returns a list of sources which are used to search for simplestreams tools metadata.
   756  func (e *environ) GetToolsSources() ([]simplestreams.DataSource, error) {
   757  	e.toolsBaseMutex.Lock()
   758  	defer e.toolsBaseMutex.Unlock()
   759  
   760  	if e.toolsSources != nil {
   761  		return e.toolsSources, nil
   762  	}
   763  	if !e.client.IsAuthenticated() {
   764  		err := e.client.Authenticate()
   765  		if err != nil {
   766  			return nil, err
   767  		}
   768  	}
   769  	verify := utils.VerifySSLHostnames
   770  	if !e.Config().SSLHostnameVerification() {
   771  		verify = utils.NoVerifySSLHostnames
   772  	}
   773  	// Add the simplestreams source off the control bucket.
   774  	e.toolsSources = append(e.toolsSources, storage.NewStorageSimpleStreamsDataSource(
   775  		"cloud storage", e.Storage(), storage.BaseToolsPath))
   776  	// Add the simplestreams base URL from keystone if it is defined.
   777  	toolsURL, err := e.client.MakeServiceURL("juju-tools", nil)
   778  	if err == nil {
   779  		source := simplestreams.NewURLDataSource("keystone catalog", toolsURL, verify)
   780  		e.toolsSources = append(e.toolsSources, source)
   781  	}
   782  	return e.toolsSources, nil
   783  }
   784  
   785  // TODO(gz): Move this somewhere more reusable
   786  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})$"
   787  
   788  var uuidRegexp = regexp.MustCompile(uuidPattern)
   789  
   790  // resolveNetwork takes either a network id or label and returns a network id
   791  func (e *environ) resolveNetwork(networkName string) (string, error) {
   792  	if uuidRegexp.MatchString(networkName) {
   793  		// Network id supplied, assume valid as boot will fail if not
   794  		return networkName, nil
   795  	}
   796  	// Network label supplied, resolve to a network id
   797  	networks, err := e.nova().ListNetworks()
   798  	if err != nil {
   799  		return "", err
   800  	}
   801  	var networkIds = []string{}
   802  	for _, network := range networks {
   803  		if network.Label == networkName {
   804  			networkIds = append(networkIds, network.Id)
   805  		}
   806  	}
   807  	switch len(networkIds) {
   808  	case 1:
   809  		return networkIds[0], nil
   810  	case 0:
   811  		return "", fmt.Errorf("No networks exist with label %q", networkName)
   812  	}
   813  	return "", fmt.Errorf("Multiple networks with label %q: %v", networkName, networkIds)
   814  }
   815  
   816  // allocatePublicIP tries to find an available floating IP address, or
   817  // allocates a new one, returning it, or an error
   818  func (e *environ) allocatePublicIP() (*nova.FloatingIP, error) {
   819  	fips, err := e.nova().ListFloatingIPs()
   820  	if err != nil {
   821  		return nil, err
   822  	}
   823  	var newfip *nova.FloatingIP
   824  	for _, fip := range fips {
   825  		newfip = &fip
   826  		if fip.InstanceId != nil && *fip.InstanceId != "" {
   827  			// unavailable, skip
   828  			newfip = nil
   829  			continue
   830  		} else {
   831  			// unassigned, we can use it
   832  			return newfip, nil
   833  		}
   834  	}
   835  	if newfip == nil {
   836  		// allocate a new IP and use it
   837  		newfip, err = e.nova().AllocateFloatingIP()
   838  		if err != nil {
   839  			return nil, err
   840  		}
   841  	}
   842  	return newfip, nil
   843  }
   844  
   845  // assignPublicIP tries to assign the given floating IP address to the
   846  // specified server, or returns an error.
   847  func (e *environ) assignPublicIP(fip *nova.FloatingIP, serverId string) (err error) {
   848  	if fip == nil {
   849  		return fmt.Errorf("cannot assign a nil public IP to %q", serverId)
   850  	}
   851  	if fip.InstanceId != nil && *fip.InstanceId == serverId {
   852  		// IP already assigned, nothing to do
   853  		return nil
   854  	}
   855  	// At startup nw_info is not yet cached so this may fail
   856  	// temporarily while the server is being built
   857  	for a := common.LongAttempt.Start(); a.Next(); {
   858  		err = e.nova().AddServerFloatingIP(serverId, fip.IP)
   859  		if err == nil {
   860  			return nil
   861  		}
   862  	}
   863  	return err
   864  }
   865  
   866  // DistributeInstances implements the state.InstanceDistributor policy.
   867  func (e *environ) DistributeInstances(candidates, distributionGroup []instance.Id) ([]instance.Id, error) {
   868  	return common.DistributeInstances(e, candidates, distributionGroup)
   869  }
   870  
   871  var bestAvailabilityZoneAllocations = common.BestAvailabilityZoneAllocations
   872  
   873  // StartInstance is specified in the InstanceBroker interface.
   874  func (e *environ) StartInstance(args environs.StartInstanceParams) (instance.Instance, *instance.HardwareCharacteristics, []network.Info, error) {
   875  	var availabilityZone string
   876  	if args.Placement != "" {
   877  		placement, err := e.parsePlacement(args.Placement)
   878  		if err != nil {
   879  			return nil, nil, nil, err
   880  		}
   881  		if !placement.availabilityZone.State.Available {
   882  			return nil, nil, nil, fmt.Errorf("availability zone %q is unavailable", placement.availabilityZone.Name)
   883  		}
   884  		availabilityZone = placement.availabilityZone.Name
   885  	}
   886  
   887  	// If no availability zone is specified, then automatically spread across
   888  	// the known zones for optimal spread across the instance distribution
   889  	// group.
   890  	if availabilityZone == "" {
   891  		var group []instance.Id
   892  		var err error
   893  		if args.DistributionGroup != nil {
   894  			group, err = args.DistributionGroup()
   895  			if err != nil {
   896  				return nil, nil, nil, err
   897  			}
   898  		}
   899  		bestAvailabilityZones, err := bestAvailabilityZoneAllocations(e, group)
   900  		if err != nil {
   901  			return nil, nil, nil, err
   902  		}
   903  		for availabilityZone = range bestAvailabilityZones {
   904  			break
   905  		}
   906  	}
   907  
   908  	if args.MachineConfig.HasNetworks() {
   909  		return nil, nil, nil, fmt.Errorf("starting instances with networks is not supported yet.")
   910  	}
   911  
   912  	series := args.Tools.OneSeries()
   913  	arches := args.Tools.Arches()
   914  	spec, err := findInstanceSpec(e, &instances.InstanceConstraint{
   915  		Region:      e.ecfg().region(),
   916  		Series:      series,
   917  		Arches:      arches,
   918  		Constraints: args.Constraints,
   919  	})
   920  	if err != nil {
   921  		return nil, nil, nil, err
   922  	}
   923  	tools, err := args.Tools.Match(tools.Filter{Arch: spec.Image.Arch})
   924  	if err != nil {
   925  		return nil, nil, nil, fmt.Errorf("chosen architecture %v not present in %v", spec.Image.Arch, arches)
   926  	}
   927  
   928  	args.MachineConfig.Tools = tools[0]
   929  
   930  	if err := environs.FinishMachineConfig(args.MachineConfig, e.Config(), args.Constraints); err != nil {
   931  		return nil, nil, nil, err
   932  	}
   933  	userData, err := environs.ComposeUserData(args.MachineConfig, nil)
   934  	if err != nil {
   935  		return nil, nil, nil, fmt.Errorf("cannot make user data: %v", err)
   936  	}
   937  	logger.Debugf("openstack user data; %d bytes", len(userData))
   938  	var networks = []nova.ServerNetworks{}
   939  	usingNetwork := e.ecfg().network()
   940  	if usingNetwork != "" {
   941  		networkId, err := e.resolveNetwork(usingNetwork)
   942  		if err != nil {
   943  			return nil, nil, nil, err
   944  		}
   945  		logger.Debugf("using network id %q", networkId)
   946  		networks = append(networks, nova.ServerNetworks{NetworkId: networkId})
   947  	}
   948  	withPublicIP := e.ecfg().useFloatingIP()
   949  	var publicIP *nova.FloatingIP
   950  	if withPublicIP {
   951  		if fip, err := e.allocatePublicIP(); err != nil {
   952  			return nil, nil, nil, fmt.Errorf("cannot allocate a public IP as needed: %v", err)
   953  		} else {
   954  			publicIP = fip
   955  			logger.Infof("allocated public IP %s", publicIP.IP)
   956  		}
   957  	}
   958  	cfg := e.Config()
   959  	groups, err := e.setUpGroups(args.MachineConfig.MachineId, cfg.StatePort(), cfg.APIPort())
   960  	if err != nil {
   961  		return nil, nil, nil, fmt.Errorf("cannot set up groups: %v", err)
   962  	}
   963  	var groupNames = make([]nova.SecurityGroupName, len(groups))
   964  	for i, g := range groups {
   965  		groupNames[i] = nova.SecurityGroupName{g.Name}
   966  	}
   967  	var opts = nova.RunServerOpts{
   968  		Name:               e.machineFullName(args.MachineConfig.MachineId),
   969  		FlavorId:           spec.InstanceType.Id,
   970  		ImageId:            spec.Image.Id,
   971  		UserData:           userData,
   972  		SecurityGroupNames: groupNames,
   973  		Networks:           networks,
   974  		AvailabilityZone:   availabilityZone,
   975  	}
   976  	var server *nova.Entity
   977  	for a := shortAttempt.Start(); a.Next(); {
   978  		server, err = e.nova().RunServer(opts)
   979  		if err == nil || !gooseerrors.IsNotFound(err) {
   980  			break
   981  		}
   982  	}
   983  	if err != nil {
   984  		return nil, nil, nil, fmt.Errorf("cannot run instance: %v", err)
   985  	}
   986  	detail, err := e.nova().GetServer(server.Id)
   987  	if err != nil {
   988  		return nil, nil, nil, fmt.Errorf("cannot get started instance: %v", err)
   989  	}
   990  	inst := &openstackInstance{
   991  		e:            e,
   992  		serverDetail: detail,
   993  		arch:         &spec.Image.Arch,
   994  		instType:     &spec.InstanceType,
   995  	}
   996  	logger.Infof("started instance %q", inst.Id())
   997  	if withPublicIP {
   998  		if err := e.assignPublicIP(publicIP, string(inst.Id())); err != nil {
   999  			if err := e.terminateInstances([]instance.Id{inst.Id()}); err != nil {
  1000  				// ignore the failure at this stage, just log it
  1001  				logger.Debugf("failed to terminate instance %q: %v", inst.Id(), err)
  1002  			}
  1003  			return nil, nil, nil, fmt.Errorf("cannot assign public address %s to instance %q: %v", publicIP.IP, inst.Id(), err)
  1004  		}
  1005  		logger.Infof("assigned public IP %s to %q", publicIP.IP, inst.Id())
  1006  	}
  1007  	return inst, inst.hardwareCharacteristics(), nil, nil
  1008  }
  1009  
  1010  func (e *environ) StopInstances(ids ...instance.Id) error {
  1011  	// If in instance firewall mode, gather the security group names.
  1012  	var securityGroupNames []string
  1013  	if e.Config().FirewallMode() == config.FwInstance {
  1014  		instances, err := e.Instances(ids)
  1015  		if err == environs.ErrNoInstances {
  1016  			return nil
  1017  		}
  1018  		securityGroupNames = make([]string, 0, len(ids))
  1019  		for _, inst := range instances {
  1020  			if inst == nil {
  1021  				continue
  1022  			}
  1023  			openstackName := inst.(*openstackInstance).getServerDetail().Name
  1024  			lastDashPos := strings.LastIndex(openstackName, "-")
  1025  			if lastDashPos == -1 {
  1026  				return fmt.Errorf("cannot identify machine ID in openstack server name %q", openstackName)
  1027  			}
  1028  			securityGroupName := e.machineGroupName(openstackName[lastDashPos+1:])
  1029  			securityGroupNames = append(securityGroupNames, securityGroupName)
  1030  		}
  1031  	}
  1032  	logger.Debugf("terminating instances %v", ids)
  1033  	if err := e.terminateInstances(ids); err != nil {
  1034  		return err
  1035  	}
  1036  	if securityGroupNames != nil {
  1037  		return e.deleteSecurityGroups(securityGroupNames)
  1038  	}
  1039  	return nil
  1040  }
  1041  
  1042  // collectInstances tries to get information on each instance id in ids.
  1043  // It fills the slots in the given map for known servers with status
  1044  // either ACTIVE or BUILD. Returns a list of missing ids.
  1045  func (e *environ) collectInstances(ids []instance.Id, out map[instance.Id]instance.Instance) []instance.Id {
  1046  	var err error
  1047  	serversById := make(map[string]nova.ServerDetail)
  1048  	if len(ids) == 1 {
  1049  		// most common case - single instance
  1050  		var server *nova.ServerDetail
  1051  		server, err = e.nova().GetServer(string(ids[0]))
  1052  		if server != nil {
  1053  			serversById[server.Id] = *server
  1054  		}
  1055  	} else {
  1056  		var servers []nova.ServerDetail
  1057  		servers, err = e.nova().ListServersDetail(e.machinesFilter())
  1058  		for _, server := range servers {
  1059  			serversById[server.Id] = server
  1060  		}
  1061  	}
  1062  	if err != nil {
  1063  		return ids
  1064  	}
  1065  	var missing []instance.Id
  1066  	for _, id := range ids {
  1067  		if server, found := serversById[string(id)]; found {
  1068  			// HPCloud uses "BUILD(spawning)" as an intermediate BUILD states once networking is available.
  1069  			switch server.Status {
  1070  			case nova.StatusActive, nova.StatusBuild, nova.StatusBuildSpawning:
  1071  				// TODO(wallyworld): lookup the flavor details to fill in the instance type data
  1072  				out[id] = &openstackInstance{e: e, serverDetail: &server}
  1073  				continue
  1074  			}
  1075  		}
  1076  		missing = append(missing, id)
  1077  	}
  1078  	return missing
  1079  }
  1080  
  1081  func (e *environ) Instances(ids []instance.Id) ([]instance.Instance, error) {
  1082  	if len(ids) == 0 {
  1083  		return nil, nil
  1084  	}
  1085  	missing := ids
  1086  	found := make(map[instance.Id]instance.Instance)
  1087  	// Make a series of requests to cope with eventual consistency.
  1088  	// Each request will attempt to add more instances to the requested
  1089  	// set.
  1090  	for a := shortAttempt.Start(); a.Next(); {
  1091  		if missing = e.collectInstances(missing, found); len(missing) == 0 {
  1092  			break
  1093  		}
  1094  	}
  1095  	if len(found) == 0 {
  1096  		return nil, environs.ErrNoInstances
  1097  	}
  1098  	insts := make([]instance.Instance, len(ids))
  1099  	var err error
  1100  	for i, id := range ids {
  1101  		if inst := found[id]; inst != nil {
  1102  			insts[i] = inst
  1103  		} else {
  1104  			err = environs.ErrPartialInstances
  1105  		}
  1106  	}
  1107  	return insts, err
  1108  }
  1109  
  1110  // AllocateAddress requests a new address to be allocated for the
  1111  // given instance on the given network. This is not implemented on the
  1112  // OpenStack provider yet.
  1113  func (*environ) AllocateAddress(_ instance.Id, _ network.Id) (network.Address, error) {
  1114  	return network.Address{}, jujuerrors.NotImplementedf("AllocateAddress")
  1115  }
  1116  
  1117  // ListNetworks returns basic information about all networks known
  1118  // by the provider for the environment. They may be unknown to juju
  1119  // yet (i.e. when called initially or when a new network was created).
  1120  // This is not implemented by the OpenStack provider yet.
  1121  func (*environ) ListNetworks() ([]network.BasicInfo, error) {
  1122  	return nil, jujuerrors.NotImplementedf("ListNetworks")
  1123  }
  1124  
  1125  func (e *environ) AllInstances() (insts []instance.Instance, err error) {
  1126  	servers, err := e.nova().ListServersDetail(e.machinesFilter())
  1127  	if err != nil {
  1128  		return nil, err
  1129  	}
  1130  	for _, server := range servers {
  1131  		if server.Status == nova.StatusActive || server.Status == nova.StatusBuild {
  1132  			var s = server
  1133  			// TODO(wallyworld): lookup the flavor details to fill in the instance type data
  1134  			insts = append(insts, &openstackInstance{
  1135  				e:            e,
  1136  				serverDetail: &s,
  1137  			})
  1138  		}
  1139  	}
  1140  	return insts, err
  1141  }
  1142  
  1143  func (e *environ) Destroy() error {
  1144  	err := common.Destroy(e)
  1145  	if err != nil {
  1146  		return err
  1147  	}
  1148  	novaClient := e.nova()
  1149  	securityGroups, err := novaClient.ListSecurityGroups()
  1150  	if err != nil {
  1151  		return err
  1152  	}
  1153  	re, err := regexp.Compile(fmt.Sprintf("^%s(-\\d+)?$", e.jujuGroupName()))
  1154  	if err != nil {
  1155  		return err
  1156  	}
  1157  	globalGroupName := e.globalGroupName()
  1158  	for _, group := range securityGroups {
  1159  		if re.MatchString(group.Name) || group.Name == globalGroupName {
  1160  			err = novaClient.DeleteSecurityGroup(group.Id)
  1161  			if err != nil {
  1162  				logger.Warningf("cannot delete security group %q. Used by another environment?", group.Name)
  1163  			}
  1164  		}
  1165  	}
  1166  	return nil
  1167  }
  1168  
  1169  func (e *environ) globalGroupName() string {
  1170  	return fmt.Sprintf("%s-global", e.jujuGroupName())
  1171  }
  1172  
  1173  func (e *environ) machineGroupName(machineId string) string {
  1174  	return fmt.Sprintf("%s-%s", e.jujuGroupName(), machineId)
  1175  }
  1176  
  1177  func (e *environ) jujuGroupName() string {
  1178  	return fmt.Sprintf("juju-%s", e.name)
  1179  }
  1180  
  1181  func (e *environ) machineFullName(machineId string) string {
  1182  	return fmt.Sprintf("juju-%s-%s", e.Name(), names.NewMachineTag(machineId))
  1183  }
  1184  
  1185  // machinesFilter returns a nova.Filter matching all machines in the environment.
  1186  func (e *environ) machinesFilter() *nova.Filter {
  1187  	filter := nova.NewFilter()
  1188  	filter.Set(nova.FilterServer, fmt.Sprintf("juju-%s-machine-\\d*", e.Name()))
  1189  	return filter
  1190  }
  1191  
  1192  func (e *environ) openPortsInGroup(name string, ports []network.Port) error {
  1193  	novaclient := e.nova()
  1194  	group, err := novaclient.SecurityGroupByName(name)
  1195  	if err != nil {
  1196  		return err
  1197  	}
  1198  	for _, port := range ports {
  1199  		_, err := novaclient.CreateSecurityGroupRule(nova.RuleInfo{
  1200  			ParentGroupId: group.Id,
  1201  			FromPort:      port.Number,
  1202  			ToPort:        port.Number,
  1203  			IPProtocol:    port.Protocol,
  1204  			Cidr:          "0.0.0.0/0",
  1205  		})
  1206  		if err != nil {
  1207  			// TODO: if err is not rule already exists, raise?
  1208  			logger.Debugf("error creating security group rule: %v", err.Error())
  1209  		}
  1210  	}
  1211  	return nil
  1212  }
  1213  
  1214  func (e *environ) closePortsInGroup(name string, ports []network.Port) error {
  1215  	if len(ports) == 0 {
  1216  		return nil
  1217  	}
  1218  	novaclient := e.nova()
  1219  	group, err := novaclient.SecurityGroupByName(name)
  1220  	if err != nil {
  1221  		return err
  1222  	}
  1223  	// TODO: Hey look ma, it's quadratic
  1224  	for _, port := range ports {
  1225  		for _, p := range (*group).Rules {
  1226  			if p.IPProtocol == nil || *p.IPProtocol != port.Protocol ||
  1227  				p.FromPort == nil || *p.FromPort != port.Number ||
  1228  				p.ToPort == nil || *p.ToPort != port.Number {
  1229  				continue
  1230  			}
  1231  			err := novaclient.DeleteSecurityGroupRule(p.Id)
  1232  			if err != nil {
  1233  				return err
  1234  			}
  1235  			break
  1236  		}
  1237  	}
  1238  	return nil
  1239  }
  1240  
  1241  func (e *environ) portsInGroup(name string) (ports []network.Port, err error) {
  1242  	group, err := e.nova().SecurityGroupByName(name)
  1243  	if err != nil {
  1244  		return nil, err
  1245  	}
  1246  	for _, p := range (*group).Rules {
  1247  		for i := *p.FromPort; i <= *p.ToPort; i++ {
  1248  			ports = append(ports, network.Port{
  1249  				Protocol: *p.IPProtocol,
  1250  				Number:   i,
  1251  			})
  1252  		}
  1253  	}
  1254  	network.SortPorts(ports)
  1255  	return ports, nil
  1256  }
  1257  
  1258  // TODO: following 30 lines nearly verbatim from environs/ec2
  1259  
  1260  func (e *environ) OpenPorts(ports []network.Port) error {
  1261  	if e.Config().FirewallMode() != config.FwGlobal {
  1262  		return fmt.Errorf("invalid firewall mode %q for opening ports on environment",
  1263  			e.Config().FirewallMode())
  1264  	}
  1265  	if err := e.openPortsInGroup(e.globalGroupName(), ports); err != nil {
  1266  		return err
  1267  	}
  1268  	logger.Infof("opened ports in global group: %v", ports)
  1269  	return nil
  1270  }
  1271  
  1272  func (e *environ) ClosePorts(ports []network.Port) error {
  1273  	if e.Config().FirewallMode() != config.FwGlobal {
  1274  		return fmt.Errorf("invalid firewall mode %q for closing ports on environment",
  1275  			e.Config().FirewallMode())
  1276  	}
  1277  	if err := e.closePortsInGroup(e.globalGroupName(), ports); err != nil {
  1278  		return err
  1279  	}
  1280  	logger.Infof("closed ports in global group: %v", ports)
  1281  	return nil
  1282  }
  1283  
  1284  func (e *environ) Ports() ([]network.Port, error) {
  1285  	if e.Config().FirewallMode() != config.FwGlobal {
  1286  		return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from environment",
  1287  			e.Config().FirewallMode())
  1288  	}
  1289  	return e.portsInGroup(e.globalGroupName())
  1290  }
  1291  
  1292  func (e *environ) Provider() environs.EnvironProvider {
  1293  	return &providerInstance
  1294  }
  1295  
  1296  func (e *environ) setUpGlobalGroup(groupName string, statePort, apiPort int) (nova.SecurityGroup, error) {
  1297  	return e.ensureGroup(groupName,
  1298  		[]nova.RuleInfo{
  1299  			{
  1300  				IPProtocol: "tcp",
  1301  				FromPort:   22,
  1302  				ToPort:     22,
  1303  				Cidr:       "0.0.0.0/0",
  1304  			},
  1305  			{
  1306  				IPProtocol: "tcp",
  1307  				FromPort:   statePort,
  1308  				ToPort:     statePort,
  1309  				Cidr:       "0.0.0.0/0",
  1310  			},
  1311  			{
  1312  				IPProtocol: "tcp",
  1313  				FromPort:   apiPort,
  1314  				ToPort:     apiPort,
  1315  				Cidr:       "0.0.0.0/0",
  1316  			},
  1317  			{
  1318  				IPProtocol: "tcp",
  1319  				FromPort:   1,
  1320  				ToPort:     65535,
  1321  			},
  1322  			{
  1323  				IPProtocol: "udp",
  1324  				FromPort:   1,
  1325  				ToPort:     65535,
  1326  			},
  1327  			{
  1328  				IPProtocol: "icmp",
  1329  				FromPort:   -1,
  1330  				ToPort:     -1,
  1331  			},
  1332  		})
  1333  }
  1334  
  1335  // setUpGroups creates the security groups for the new machine, and
  1336  // returns them.
  1337  //
  1338  // Instances are tagged with a group so they can be distinguished from
  1339  // other instances that might be running on the same OpenStack account.
  1340  // In addition, a specific machine security group is created for each
  1341  // machine, so that its firewall rules can be configured per machine.
  1342  //
  1343  // Note: ideally we'd have a better way to determine group membership so that 2
  1344  // people that happen to share an openstack account and name their environment
  1345  // "openstack" don't end up destroying each other's machines.
  1346  func (e *environ) setUpGroups(machineId string, statePort, apiPort int) ([]nova.SecurityGroup, error) {
  1347  	jujuGroup, err := e.setUpGlobalGroup(e.jujuGroupName(), statePort, apiPort)
  1348  	if err != nil {
  1349  		return nil, err
  1350  	}
  1351  	var machineGroup nova.SecurityGroup
  1352  	switch e.Config().FirewallMode() {
  1353  	case config.FwInstance:
  1354  		machineGroup, err = e.ensureGroup(e.machineGroupName(machineId), nil)
  1355  	case config.FwGlobal:
  1356  		machineGroup, err = e.ensureGroup(e.globalGroupName(), nil)
  1357  	}
  1358  	if err != nil {
  1359  		return nil, err
  1360  	}
  1361  	groups := []nova.SecurityGroup{jujuGroup, machineGroup}
  1362  	if e.ecfg().useDefaultSecurityGroup() {
  1363  		defaultGroup, err := e.nova().SecurityGroupByName("default")
  1364  		if err != nil {
  1365  			return nil, fmt.Errorf("loading default security group: %v", err)
  1366  		}
  1367  		groups = append(groups, *defaultGroup)
  1368  	}
  1369  	return groups, nil
  1370  }
  1371  
  1372  // zeroGroup holds the zero security group.
  1373  var zeroGroup nova.SecurityGroup
  1374  
  1375  // ensureGroup returns the security group with name and perms.
  1376  // If a group with name does not exist, one will be created.
  1377  // If it exists, its permissions are set to perms.
  1378  func (e *environ) ensureGroup(name string, rules []nova.RuleInfo) (nova.SecurityGroup, error) {
  1379  	novaClient := e.nova()
  1380  	// First attempt to look up an existing group by name.
  1381  	group, err := novaClient.SecurityGroupByName(name)
  1382  	if err == nil {
  1383  		// Group exists, so assume it is correctly set up and return it.
  1384  		// TODO(jam): 2013-09-18 http://pad.lv/121795
  1385  		// We really should verify the group is set up correctly,
  1386  		// because deleting and re-creating environments can get us bad
  1387  		// groups (especially if they were set up under Python)
  1388  		return *group, nil
  1389  	}
  1390  	// Doesn't exist, so try and create it.
  1391  	group, err = novaClient.CreateSecurityGroup(name, "juju group")
  1392  	if err != nil {
  1393  		if !gooseerrors.IsDuplicateValue(err) {
  1394  			return zeroGroup, err
  1395  		} else {
  1396  			// We just tried to create a duplicate group, so load the existing group.
  1397  			group, err = novaClient.SecurityGroupByName(name)
  1398  			if err != nil {
  1399  				return zeroGroup, err
  1400  			}
  1401  			return *group, nil
  1402  		}
  1403  	}
  1404  	// The new group is created so now add the rules.
  1405  	group.Rules = make([]nova.SecurityGroupRule, len(rules))
  1406  	for i, rule := range rules {
  1407  		rule.ParentGroupId = group.Id
  1408  		if rule.Cidr == "" {
  1409  			// http://pad.lv/1226996 Rules that don't have a CIDR
  1410  			// are meant to apply only to this group. If you don't
  1411  			// supply CIDR or GroupId then openstack assumes you
  1412  			// mean CIDR=0.0.0.0/0
  1413  			rule.GroupId = &group.Id
  1414  		}
  1415  		groupRule, err := novaClient.CreateSecurityGroupRule(rule)
  1416  		if err != nil && !gooseerrors.IsDuplicateValue(err) {
  1417  			return zeroGroup, err
  1418  		}
  1419  		group.Rules[i] = *groupRule
  1420  	}
  1421  	return *group, nil
  1422  }
  1423  
  1424  // deleteSecurityGroups deletes the given security groups. If a security
  1425  // group is also used by another environment (see bug #1300755), an attempt
  1426  // to delete this group fails. A warning is logged in this case.
  1427  func (e *environ) deleteSecurityGroups(securityGroupNames []string) error {
  1428  	novaclient := e.nova()
  1429  	allSecurityGroups, err := novaclient.ListSecurityGroups()
  1430  	if err != nil {
  1431  		return err
  1432  	}
  1433  	for _, securityGroup := range allSecurityGroups {
  1434  		for _, name := range securityGroupNames {
  1435  			if securityGroup.Name == name {
  1436  				err := novaclient.DeleteSecurityGroup(securityGroup.Id)
  1437  				if err != nil {
  1438  					logger.Warningf("cannot delete security group %q. Used by another environment?", name)
  1439  				}
  1440  				break
  1441  			}
  1442  		}
  1443  	}
  1444  	return nil
  1445  }
  1446  
  1447  func (e *environ) terminateInstances(ids []instance.Id) error {
  1448  	if len(ids) == 0 {
  1449  		return nil
  1450  	}
  1451  	var firstErr error
  1452  	novaClient := e.nova()
  1453  	for _, id := range ids {
  1454  		err := novaClient.DeleteServer(string(id))
  1455  		if gooseerrors.IsNotFound(err) {
  1456  			err = nil
  1457  		}
  1458  		if err != nil && firstErr == nil {
  1459  			logger.Debugf("error terminating instance %q: %v", id, err)
  1460  			firstErr = err
  1461  		}
  1462  	}
  1463  	return firstErr
  1464  }
  1465  
  1466  // MetadataLookupParams returns parameters which are used to query simplestreams metadata.
  1467  func (e *environ) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
  1468  	if region == "" {
  1469  		region = e.ecfg().region()
  1470  	}
  1471  	cloudSpec, err := e.cloudSpec(region)
  1472  	if err != nil {
  1473  		return nil, err
  1474  	}
  1475  	return &simplestreams.MetadataLookupParams{
  1476  		Series:        config.PreferredSeries(e.ecfg()),
  1477  		Region:        cloudSpec.Region,
  1478  		Endpoint:      cloudSpec.Endpoint,
  1479  		Architectures: arch.AllSupportedArches,
  1480  	}, nil
  1481  }
  1482  
  1483  // Region is specified in the HasRegion interface.
  1484  func (e *environ) Region() (simplestreams.CloudSpec, error) {
  1485  	return e.cloudSpec(e.ecfg().region())
  1486  }
  1487  
  1488  func (e *environ) cloudSpec(region string) (simplestreams.CloudSpec, error) {
  1489  	return simplestreams.CloudSpec{
  1490  		Region:   region,
  1491  		Endpoint: e.ecfg().authURL(),
  1492  	}, nil
  1493  }