github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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  	"github.com/juju/errors"
    18  	"github.com/juju/loggo"
    19  	"github.com/juju/names"
    20  	"github.com/juju/utils"
    21  	"github.com/juju/utils/arch"
    22  	"gopkg.in/goose.v1/client"
    23  	gooseerrors "gopkg.in/goose.v1/errors"
    24  	"gopkg.in/goose.v1/identity"
    25  	"gopkg.in/goose.v1/nova"
    26  	"gopkg.in/goose.v1/swift"
    27  
    28  	"github.com/juju/juju/cloudconfig/instancecfg"
    29  	"github.com/juju/juju/cloudconfig/providerinit"
    30  	"github.com/juju/juju/constraints"
    31  	"github.com/juju/juju/environs"
    32  	"github.com/juju/juju/environs/config"
    33  	"github.com/juju/juju/environs/imagemetadata"
    34  	"github.com/juju/juju/environs/instances"
    35  	"github.com/juju/juju/environs/simplestreams"
    36  	"github.com/juju/juju/environs/storage"
    37  	"github.com/juju/juju/environs/tags"
    38  	"github.com/juju/juju/instance"
    39  	"github.com/juju/juju/network"
    40  	"github.com/juju/juju/provider/common"
    41  	"github.com/juju/juju/state"
    42  	"github.com/juju/juju/tools"
    43  )
    44  
    45  var logger = loggo.GetLogger("juju.provider.openstack")
    46  
    47  type environProvider struct{}
    48  
    49  var _ environs.EnvironProvider = (*environProvider)(nil)
    50  
    51  var providerInstance environProvider
    52  
    53  var makeServiceURL = client.AuthenticatingClient.MakeServiceURL
    54  
    55  // Use shortAttempt to poll for short-term events.
    56  // TODO: This was kept to a long timeout because Nova needs more time than EC2.
    57  // For example, HP Cloud takes around 9.1 seconds (10 samples) to return a
    58  // BUILD(spawning) status. But storage delays are handled separately now, and
    59  // perhaps other polling attempts can time out faster.
    60  var shortAttempt = utils.AttemptStrategy{
    61  	Total: 15 * time.Second,
    62  	Delay: 200 * time.Millisecond,
    63  }
    64  
    65  func (p environProvider) BoilerplateConfig() string {
    66  	return `
    67  # https://juju.ubuntu.com/docs/config-openstack.html
    68  openstack:
    69      type: openstack
    70  
    71      # use-floating-ip specifies whether a floating IP address is
    72      # required to give the nodes a public IP address. Some
    73      # installations assign public IP addresses by default without
    74      # requiring a floating IP address.
    75      #
    76      # use-floating-ip: false
    77  
    78      # use-default-secgroup specifies whether new machine instances
    79      # should have the "default" Openstack security group assigned.
    80      #
    81      # use-default-secgroup: false
    82  
    83      # network specifies the network label or uuid to bring machines up
    84      # on, in the case where multiple networks exist. It may be omitted
    85      # otherwise.
    86      #
    87      # network: <your network label or uuid>
    88  
    89      # agent-metadata-url specifies the location of the Juju tools and
    90      # metadata. It defaults to the global public tools metadata
    91      # location https://streams.canonical.com/tools.
    92      #
    93      # agent-metadata-url:  https://your-agent-metadata-url
    94  
    95      # image-metadata-url specifies the location of Ubuntu cloud image
    96      # metadata. It defaults to the global public image metadata
    97      # location https://cloud-images.ubuntu.com/releases.
    98      #
    99      # image-metadata-url:  https://your-image-metadata-url
   100  
   101      # image-stream chooses a simplestreams stream from which to select
   102      # OS images, for example daily or released images (or any other stream
   103      # available on simplestreams).
   104      #
   105      # image-stream: "released"
   106  
   107      # agent-stream chooses a simplestreams stream from which to select tools,
   108      # for example released or proposed tools (or any other stream available
   109      # on simplestreams).
   110      #
   111      # agent-stream: "released"
   112  
   113      # auth-url defaults to the value of the environment variable
   114      # OS_AUTH_URL, but can be specified here.
   115      #
   116      # auth-url: https://yourkeystoneurl:443/v2.0/
   117  
   118      # tenant-name holds the openstack tenant name. It defaults to the
   119      # environment variable OS_TENANT_NAME.
   120      #
   121      # tenant-name: <your tenant name>
   122  
   123      # region holds the openstack region. It defaults to the
   124      # environment variable OS_REGION_NAME.
   125      #
   126      # region: <your region>
   127  
   128      # The auth-mode, username and password attributes are used for
   129      # userpass authentication (the default).
   130      #
   131      # auth-mode holds the authentication mode. For user-password
   132      # authentication, auth-mode should be "userpass" and username and
   133      # password should be set appropriately; they default to the
   134      # environment variables OS_USERNAME and OS_PASSWORD respectively.
   135      #
   136      # auth-mode: userpass
   137      # username: <your username>
   138      # password: <secret>
   139  
   140      # For key-pair authentication, auth-mode should be "keypair" and
   141      # access-key and secret-key should be set appropriately; they
   142      # default to the environment variables OS_ACCESS_KEY and
   143      # OS_SECRET_KEY respectively.
   144      #
   145      # auth-mode: keypair
   146      # access-key: <secret>
   147      # secret-key: <secret>
   148  
   149      # Whether or not to refresh the list of available updates for an
   150      # OS. The default option of true is recommended for use in
   151      # production systems, but disabling this can speed up local
   152      # deployments for development or testing.
   153      #
   154      # enable-os-refresh-update: true
   155  
   156      # Whether or not to perform OS upgrades when machines are
   157      # provisioned. The default option of true is recommended for use
   158      # in production systems, but disabling this can speed up local
   159      # deployments for development or testing.
   160      #
   161      # enable-os-upgrade: true
   162  
   163  # https://juju.ubuntu.com/docs/config-hpcloud.html
   164  hpcloud:
   165      type: openstack
   166  
   167      # use-floating-ip specifies whether a floating IP address is
   168      # required to give the nodes a public IP address. Some
   169      # installations assign public IP addresses by default without
   170      # requiring a floating IP address.
   171      #
   172      # use-floating-ip: true
   173  
   174      # use-default-secgroup specifies whether new machine instances
   175      # should have the "default" Openstack security group assigned.
   176      #
   177      # use-default-secgroup: false
   178  
   179      # tenant-name holds the openstack tenant name. In HPCloud, this is
   180      # synonymous with the project-name It defaults to the environment
   181      # variable OS_TENANT_NAME.
   182      #
   183      # tenant-name: <your tenant name>
   184  
   185      # image-stream chooses a simplestreams stream from which to select
   186      # OS images, for example daily or released images (or any other stream
   187      # available on simplestreams).
   188      #
   189      # image-stream: "released"
   190  
   191      # agent-stream chooses a simplestreams stream from which to select tools,
   192      # for example released or proposed tools (or any other stream available
   193      # on simplestreams).
   194      #
   195      # agent-stream: "released"
   196  
   197      # auth-url holds the keystone url for authentication. It defaults
   198      # to the value of the environment variable OS_AUTH_URL.
   199      #
   200      # auth-url: https://region-a.geo-1.identity.hpcloudsvc.com:35357/v2.0/
   201  
   202      # region holds the HP Cloud region (e.g. region-a.geo-1). It
   203      # defaults to the environment variable OS_REGION_NAME.
   204      #
   205      # region: <your region>
   206  
   207      # auth-mode holds the authentication mode. For user-password
   208      # authentication, auth-mode should be "userpass" and username and
   209      # password should be set appropriately; they default to the
   210      # environment variables OS_USERNAME and OS_PASSWORD respectively.
   211      #
   212      # auth-mode: userpass
   213      # username: <your_username>
   214      # password: <your_password>
   215  
   216      # For key-pair authentication, auth-mode should be "keypair" and
   217      # access-key and secret-key should be set appropriately; they
   218      # default to the environment variables OS_ACCESS_KEY and
   219      # OS_SECRET_KEY respectively.
   220      #
   221      # auth-mode: keypair
   222      # access-key: <secret>
   223      # secret-key: <secret>
   224  
   225      # Whether or not to refresh the list of available updates for an
   226      # OS. The default option of true is recommended for use in
   227      # production systems, but disabling this can speed up local
   228      # deployments for development or testing.
   229      #
   230      # enable-os-refresh-update: true
   231  
   232      # Whether or not to perform OS upgrades when machines are
   233      # provisioned. The default option of true is recommended for use
   234      # in production systems, but disabling this can speed up local
   235      # deployments for development or testing.
   236      #
   237      # enable-os-upgrade: true
   238  
   239  `[1:]
   240  }
   241  
   242  func (p environProvider) Open(cfg *config.Config) (environs.Environ, error) {
   243  	logger.Infof("opening environment %q", cfg.Name())
   244  	e := new(environ)
   245  	err := e.SetConfig(cfg)
   246  	if err != nil {
   247  		return nil, err
   248  	}
   249  	e.name = cfg.Name()
   250  	return e, nil
   251  }
   252  
   253  // RestrictedConfigAttributes is specified in the EnvironProvider interface.
   254  func (p environProvider) RestrictedConfigAttributes() []string {
   255  	return []string{"region", "auth-url", "auth-mode"}
   256  }
   257  
   258  // PrepareForCreateEnvironment is specified in the EnvironProvider interface.
   259  func (p environProvider) PrepareForCreateEnvironment(cfg *config.Config) (*config.Config, error) {
   260  	attrs := cfg.UnknownAttrs()
   261  	if _, ok := attrs["control-bucket"]; !ok {
   262  		uuid, err := utils.NewUUID()
   263  		if err != nil {
   264  			return nil, errors.Trace(err)
   265  		}
   266  		attrs["control-bucket"] = fmt.Sprintf("%x", uuid.Raw())
   267  	}
   268  	return cfg.Apply(attrs)
   269  }
   270  
   271  func (p environProvider) PrepareForBootstrap(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) {
   272  	cfg, err := p.PrepareForCreateEnvironment(cfg)
   273  	if err != nil {
   274  		return nil, err
   275  	}
   276  	e, err := p.Open(cfg)
   277  	if err != nil {
   278  		return nil, err
   279  	}
   280  	// Verify credentials.
   281  	if err := authenticateClient(e.(*environ)); err != nil {
   282  		return nil, err
   283  	}
   284  	return e, nil
   285  }
   286  
   287  // MetadataLookupParams returns parameters which are used to query image metadata to
   288  // find matching image information.
   289  func (p environProvider) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
   290  	if region == "" {
   291  		return nil, fmt.Errorf("region must be specified")
   292  	}
   293  	return &simplestreams.MetadataLookupParams{
   294  		Region:        region,
   295  		Architectures: arch.AllSupportedArches,
   296  	}, nil
   297  }
   298  
   299  func (p environProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) {
   300  	m := make(map[string]string)
   301  	ecfg, err := providerInstance.newConfig(cfg)
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  	m["username"] = ecfg.username()
   306  	m["password"] = ecfg.password()
   307  	m["tenant-name"] = ecfg.tenantName()
   308  	return m, nil
   309  }
   310  
   311  func retryGet(uri string) (data []byte, err error) {
   312  	for a := shortAttempt.Start(); a.Next(); {
   313  		var resp *http.Response
   314  		resp, err = http.Get(uri)
   315  		if err != nil {
   316  			continue
   317  		}
   318  		defer resp.Body.Close()
   319  		if resp.StatusCode != http.StatusOK {
   320  			err = fmt.Errorf("bad http response %v", resp.Status)
   321  			continue
   322  		}
   323  		var data []byte
   324  		data, err = ioutil.ReadAll(resp.Body)
   325  		if err != nil {
   326  			continue
   327  		}
   328  		return data, nil
   329  	}
   330  	if err != nil {
   331  		return nil, fmt.Errorf("cannot get %q: %v", uri, err)
   332  	}
   333  	return
   334  }
   335  
   336  type environ struct {
   337  	common.SupportsUnitPlacementPolicy
   338  
   339  	name string
   340  
   341  	// archMutex gates access to supportedArchitectures
   342  	archMutex sync.Mutex
   343  	// supportedArchitectures caches the architectures
   344  	// for which images can be instantiated.
   345  	supportedArchitectures []string
   346  
   347  	ecfgMutex       sync.Mutex
   348  	ecfgUnlocked    *environConfig
   349  	client          client.AuthenticatingClient
   350  	novaUnlocked    *nova.Client
   351  	storageUnlocked storage.Storage
   352  
   353  	// keystoneImageDataSource caches the result of getKeystoneImageSource.
   354  	keystoneImageDataSourceMutex sync.Mutex
   355  	keystoneImageDataSource      simplestreams.DataSource
   356  
   357  	// keystoneToolsDataSource caches the result of getKeystoneToolsSource.
   358  	keystoneToolsDataSourceMutex sync.Mutex
   359  	keystoneToolsDataSource      simplestreams.DataSource
   360  
   361  	availabilityZonesMutex sync.Mutex
   362  	availabilityZones      []common.AvailabilityZone
   363  }
   364  
   365  var _ environs.Environ = (*environ)(nil)
   366  var _ simplestreams.HasRegion = (*environ)(nil)
   367  var _ state.Prechecker = (*environ)(nil)
   368  var _ state.InstanceDistributor = (*environ)(nil)
   369  var _ environs.InstanceTagger = (*environ)(nil)
   370  
   371  type openstackInstance struct {
   372  	e        *environ
   373  	instType *instances.InstanceType
   374  	arch     *string
   375  
   376  	mu           sync.Mutex
   377  	serverDetail *nova.ServerDetail
   378  	// floatingIP is non-nil iff use-floating-ip is true.
   379  	floatingIP *nova.FloatingIP
   380  }
   381  
   382  func (inst *openstackInstance) String() string {
   383  	return string(inst.Id())
   384  }
   385  
   386  var _ instance.Instance = (*openstackInstance)(nil)
   387  
   388  func (inst *openstackInstance) Refresh() error {
   389  	inst.mu.Lock()
   390  	defer inst.mu.Unlock()
   391  	server, err := inst.e.nova().GetServer(inst.serverDetail.Id)
   392  	if err != nil {
   393  		return err
   394  	}
   395  	inst.serverDetail = server
   396  	return nil
   397  }
   398  
   399  func (inst *openstackInstance) getServerDetail() *nova.ServerDetail {
   400  	inst.mu.Lock()
   401  	defer inst.mu.Unlock()
   402  	return inst.serverDetail
   403  }
   404  
   405  func (inst *openstackInstance) Id() instance.Id {
   406  	return instance.Id(inst.getServerDetail().Id)
   407  }
   408  
   409  func (inst *openstackInstance) Status() string {
   410  	return inst.getServerDetail().Status
   411  }
   412  
   413  func (inst *openstackInstance) hardwareCharacteristics() *instance.HardwareCharacteristics {
   414  	hc := &instance.HardwareCharacteristics{Arch: inst.arch}
   415  	if inst.instType != nil {
   416  		hc.Mem = &inst.instType.Mem
   417  		// openstack is special in that a 0-size root disk means that
   418  		// the root disk will result in an instance with a root disk
   419  		// the same size as the image that created it, so we just set
   420  		// the HardwareCharacteristics to nil to signal that we don't
   421  		// know what the correct size is.
   422  		if inst.instType.RootDisk == 0 {
   423  			hc.RootDisk = nil
   424  		} else {
   425  			hc.RootDisk = &inst.instType.RootDisk
   426  		}
   427  		hc.CpuCores = &inst.instType.CpuCores
   428  		hc.CpuPower = inst.instType.CpuPower
   429  		// tags not currently supported on openstack
   430  	}
   431  	hc.AvailabilityZone = &inst.serverDetail.AvailabilityZone
   432  	return hc
   433  }
   434  
   435  // getAddresses returns the existing server information on addresses,
   436  // but fetches the details over the api again if no addresses exist.
   437  func (inst *openstackInstance) getAddresses() (map[string][]nova.IPAddress, error) {
   438  	addrs := inst.getServerDetail().Addresses
   439  	if len(addrs) == 0 {
   440  		server, err := inst.e.nova().GetServer(string(inst.Id()))
   441  		if err != nil {
   442  			return nil, err
   443  		}
   444  		addrs = server.Addresses
   445  	}
   446  	return addrs, nil
   447  }
   448  
   449  // Addresses implements network.Addresses() returning generic address
   450  // details for the instances, and calling the openstack api if needed.
   451  func (inst *openstackInstance) Addresses() ([]network.Address, error) {
   452  	addresses, err := inst.getAddresses()
   453  	if err != nil {
   454  		return nil, err
   455  	}
   456  	var floatingIP string
   457  	if inst.floatingIP != nil && inst.floatingIP.IP != "" {
   458  		floatingIP = inst.floatingIP.IP
   459  		logger.Debugf("instance %v has floating IP address: %v", inst.Id(), floatingIP)
   460  	}
   461  	return convertNovaAddresses(floatingIP, addresses), nil
   462  }
   463  
   464  // convertNovaAddresses returns nova addresses in generic format
   465  func convertNovaAddresses(publicIP string, addresses map[string][]nova.IPAddress) []network.Address {
   466  	var machineAddresses []network.Address
   467  	if publicIP != "" {
   468  		publicAddr := network.NewScopedAddress(publicIP, network.ScopePublic)
   469  		publicAddr.NetworkName = "public"
   470  		machineAddresses = append(machineAddresses, publicAddr)
   471  	}
   472  	// TODO(gz) Network ordering may be significant but is not preserved by
   473  	// the map, see lp:1188126 for example. That could potentially be fixed
   474  	// in goose, or left to be derived by other means.
   475  	for netName, ips := range addresses {
   476  		networkScope := network.ScopeUnknown
   477  		if netName == "public" {
   478  			networkScope = network.ScopePublic
   479  		}
   480  		for _, address := range ips {
   481  			// If this address has already been added as a floating IP, skip it.
   482  			if publicIP == address.Address {
   483  				continue
   484  			}
   485  			// Assume IPv4 unless specified otherwise
   486  			addrtype := network.IPv4Address
   487  			if address.Version == 6 {
   488  				addrtype = network.IPv6Address
   489  			}
   490  			machineAddr := network.NewScopedAddress(address.Address, networkScope)
   491  			machineAddr.NetworkName = netName
   492  			if machineAddr.Type != addrtype {
   493  				logger.Warningf("derived address type %v, nova reports %v", machineAddr.Type, addrtype)
   494  			}
   495  			machineAddresses = append(machineAddresses, machineAddr)
   496  		}
   497  	}
   498  	return machineAddresses
   499  }
   500  
   501  // TODO: following 30 lines nearly verbatim from environs/ec2
   502  
   503  func (inst *openstackInstance) OpenPorts(machineId string, ports []network.PortRange) error {
   504  	if inst.e.Config().FirewallMode() != config.FwInstance {
   505  		return fmt.Errorf("invalid firewall mode %q for opening ports on instance",
   506  			inst.e.Config().FirewallMode())
   507  	}
   508  	name := inst.e.machineGroupName(machineId)
   509  	if err := inst.e.openPortsInGroup(name, ports); err != nil {
   510  		return err
   511  	}
   512  	logger.Infof("opened ports in security group %s: %v", name, ports)
   513  	return nil
   514  }
   515  
   516  func (inst *openstackInstance) ClosePorts(machineId string, ports []network.PortRange) error {
   517  	if inst.e.Config().FirewallMode() != config.FwInstance {
   518  		return fmt.Errorf("invalid firewall mode %q for closing ports on instance",
   519  			inst.e.Config().FirewallMode())
   520  	}
   521  	name := inst.e.machineGroupName(machineId)
   522  	if err := inst.e.closePortsInGroup(name, ports); err != nil {
   523  		return err
   524  	}
   525  	logger.Infof("closed ports in security group %s: %v", name, ports)
   526  	return nil
   527  }
   528  
   529  func (inst *openstackInstance) Ports(machineId string) ([]network.PortRange, error) {
   530  	if inst.e.Config().FirewallMode() != config.FwInstance {
   531  		return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from instance",
   532  			inst.e.Config().FirewallMode())
   533  	}
   534  	name := inst.e.machineGroupName(machineId)
   535  	portRanges, err := inst.e.portsInGroup(name)
   536  	if err != nil {
   537  		return nil, err
   538  	}
   539  	return portRanges, nil
   540  }
   541  
   542  func (e *environ) ecfg() *environConfig {
   543  	e.ecfgMutex.Lock()
   544  	ecfg := e.ecfgUnlocked
   545  	e.ecfgMutex.Unlock()
   546  	return ecfg
   547  }
   548  
   549  func (e *environ) nova() *nova.Client {
   550  	e.ecfgMutex.Lock()
   551  	nova := e.novaUnlocked
   552  	e.ecfgMutex.Unlock()
   553  	return nova
   554  }
   555  
   556  // SupportedArchitectures is specified on the EnvironCapability interface.
   557  func (e *environ) SupportedArchitectures() ([]string, error) {
   558  	e.archMutex.Lock()
   559  	defer e.archMutex.Unlock()
   560  	if e.supportedArchitectures != nil {
   561  		return e.supportedArchitectures, nil
   562  	}
   563  	// Create a filter to get all images from our region and for the correct stream.
   564  	cloudSpec, err := e.Region()
   565  	if err != nil {
   566  		return nil, err
   567  	}
   568  	imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
   569  		CloudSpec: cloudSpec,
   570  		Stream:    e.Config().ImageStream(),
   571  	})
   572  	e.supportedArchitectures, err = common.SupportedArchitectures(e, imageConstraint)
   573  	return e.supportedArchitectures, err
   574  }
   575  
   576  var unsupportedConstraints = []string{
   577  	constraints.Tags,
   578  	constraints.CpuPower,
   579  }
   580  
   581  // ConstraintsValidator is defined on the Environs interface.
   582  func (e *environ) ConstraintsValidator() (constraints.Validator, error) {
   583  	validator := constraints.NewValidator()
   584  	validator.RegisterConflicts(
   585  		[]string{constraints.InstanceType},
   586  		[]string{constraints.Mem, constraints.Arch, constraints.RootDisk, constraints.CpuCores})
   587  	validator.RegisterUnsupported(unsupportedConstraints)
   588  	supportedArches, err := e.SupportedArchitectures()
   589  	if err != nil {
   590  		return nil, err
   591  	}
   592  	validator.RegisterVocabulary(constraints.Arch, supportedArches)
   593  	novaClient := e.nova()
   594  	flavors, err := novaClient.ListFlavorsDetail()
   595  	if err != nil {
   596  		return nil, err
   597  	}
   598  	instTypeNames := make([]string, len(flavors))
   599  	for i, flavor := range flavors {
   600  		instTypeNames[i] = flavor.Name
   601  	}
   602  	validator.RegisterVocabulary(constraints.InstanceType, instTypeNames)
   603  	return validator, nil
   604  }
   605  
   606  var novaListAvailabilityZones = (*nova.Client).ListAvailabilityZones
   607  
   608  type openstackAvailabilityZone struct {
   609  	nova.AvailabilityZone
   610  }
   611  
   612  func (z *openstackAvailabilityZone) Name() string {
   613  	return z.AvailabilityZone.Name
   614  }
   615  
   616  func (z *openstackAvailabilityZone) Available() bool {
   617  	return z.AvailabilityZone.State.Available
   618  }
   619  
   620  // AvailabilityZones returns a slice of availability zones.
   621  func (e *environ) AvailabilityZones() ([]common.AvailabilityZone, error) {
   622  	e.availabilityZonesMutex.Lock()
   623  	defer e.availabilityZonesMutex.Unlock()
   624  	if e.availabilityZones == nil {
   625  		zones, err := novaListAvailabilityZones(e.nova())
   626  		if gooseerrors.IsNotImplemented(err) {
   627  			return nil, errors.NotImplementedf("availability zones")
   628  		}
   629  		if err != nil {
   630  			return nil, err
   631  		}
   632  		e.availabilityZones = make([]common.AvailabilityZone, len(zones))
   633  		for i, z := range zones {
   634  			e.availabilityZones[i] = &openstackAvailabilityZone{z}
   635  		}
   636  	}
   637  	return e.availabilityZones, nil
   638  }
   639  
   640  // InstanceAvailabilityZoneNames returns the availability zone names for each
   641  // of the specified instances.
   642  func (e *environ) InstanceAvailabilityZoneNames(ids []instance.Id) ([]string, error) {
   643  	instances, err := e.Instances(ids)
   644  	if err != nil && err != environs.ErrPartialInstances {
   645  		return nil, err
   646  	}
   647  	zones := make([]string, len(instances))
   648  	for i, inst := range instances {
   649  		if inst == nil {
   650  			continue
   651  		}
   652  		zones[i] = inst.(*openstackInstance).serverDetail.AvailabilityZone
   653  	}
   654  	return zones, err
   655  }
   656  
   657  type openstackPlacement struct {
   658  	availabilityZone nova.AvailabilityZone
   659  }
   660  
   661  func (e *environ) parsePlacement(placement string) (*openstackPlacement, error) {
   662  	pos := strings.IndexRune(placement, '=')
   663  	if pos == -1 {
   664  		return nil, fmt.Errorf("unknown placement directive: %v", placement)
   665  	}
   666  	switch key, value := placement[:pos], placement[pos+1:]; key {
   667  	case "zone":
   668  		availabilityZone := value
   669  		zones, err := e.AvailabilityZones()
   670  		if err != nil {
   671  			return nil, err
   672  		}
   673  		for _, z := range zones {
   674  			if z.Name() == availabilityZone {
   675  				return &openstackPlacement{
   676  					z.(*openstackAvailabilityZone).AvailabilityZone,
   677  				}, nil
   678  			}
   679  		}
   680  		return nil, fmt.Errorf("invalid availability zone %q", availabilityZone)
   681  	}
   682  	return nil, fmt.Errorf("unknown placement directive: %v", placement)
   683  }
   684  
   685  // PrecheckInstance is defined on the state.Prechecker interface.
   686  func (e *environ) PrecheckInstance(series string, cons constraints.Value, placement string) error {
   687  	if placement != "" {
   688  		if _, err := e.parsePlacement(placement); err != nil {
   689  			return err
   690  		}
   691  	}
   692  	if !cons.HasInstanceType() {
   693  		return nil
   694  	}
   695  	// Constraint has an instance-type constraint so let's see if it is valid.
   696  	novaClient := e.nova()
   697  	flavors, err := novaClient.ListFlavorsDetail()
   698  	if err != nil {
   699  		return err
   700  	}
   701  	for _, flavor := range flavors {
   702  		if flavor.Name == *cons.InstanceType {
   703  			return nil
   704  		}
   705  	}
   706  	return fmt.Errorf("invalid Openstack flavour %q specified", *cons.InstanceType)
   707  }
   708  
   709  func (e *environ) Storage() storage.Storage {
   710  	e.ecfgMutex.Lock()
   711  	stor := e.storageUnlocked
   712  	e.ecfgMutex.Unlock()
   713  	return stor
   714  }
   715  
   716  func (e *environ) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (arch, series string, _ environs.BootstrapFinalizer, _ error) {
   717  	// The client's authentication may have been reset when finding tools if the agent-version
   718  	// attribute was updated so we need to re-authenticate. This will be a no-op if already authenticated.
   719  	// An authenticated client is needed for the URL() call below.
   720  	if err := authenticateClient(e); err != nil {
   721  		return "", "", nil, err
   722  	}
   723  	return common.Bootstrap(ctx, e, args)
   724  }
   725  
   726  func (e *environ) StateServerInstances() ([]instance.Id, error) {
   727  	// Find all instances tagged with tags.JujuStateServer.
   728  	instances, err := e.AllInstances()
   729  	if err != nil {
   730  		return nil, errors.Trace(err)
   731  	}
   732  	ids := make([]instance.Id, 0, 1)
   733  	for _, instance := range instances {
   734  		detail := instance.(*openstackInstance).getServerDetail()
   735  		if detail.Metadata[tags.JujuStateServer] == "true" {
   736  			ids = append(ids, instance.Id())
   737  		}
   738  	}
   739  	if len(ids) == 0 {
   740  		return nil, environs.ErrNoInstances
   741  	}
   742  	return ids, nil
   743  }
   744  
   745  func (e *environ) Config() *config.Config {
   746  	return e.ecfg().Config
   747  }
   748  
   749  func authClient(ecfg *environConfig) client.AuthenticatingClient {
   750  	cred := &identity.Credentials{
   751  		User:       ecfg.username(),
   752  		Secrets:    ecfg.password(),
   753  		Region:     ecfg.region(),
   754  		TenantName: ecfg.tenantName(),
   755  		URL:        ecfg.authURL(),
   756  	}
   757  	// authModeCfg has already been validated so we know it's one of the values below.
   758  	var authMode identity.AuthMode
   759  	switch AuthMode(ecfg.authMode()) {
   760  	case AuthLegacy:
   761  		authMode = identity.AuthLegacy
   762  	case AuthUserPass:
   763  		authMode = identity.AuthUserPass
   764  	case AuthKeyPair:
   765  		authMode = identity.AuthKeyPair
   766  		cred.User = ecfg.accessKey()
   767  		cred.Secrets = ecfg.secretKey()
   768  	}
   769  	newClient := client.NewClient
   770  	if !ecfg.SSLHostnameVerification() {
   771  		newClient = client.NewNonValidatingClient
   772  	}
   773  	client := newClient(cred, authMode, nil)
   774  	// By default, the client requires "compute" and
   775  	// "object-store". Juju only requires "compute".
   776  	client.SetRequiredServiceTypes([]string{"compute"})
   777  	return client
   778  }
   779  
   780  var authenticateClient = func(e *environ) error {
   781  	err := e.client.Authenticate()
   782  	if err != nil {
   783  		// Log the error in case there are any useful hints,
   784  		// but provide a readable and helpful error message
   785  		// to the user.
   786  		logger.Debugf("authentication failed: %v", err)
   787  		return errors.New(`authentication failed.
   788  
   789  Please ensure the credentials are correct. A common mistake is
   790  to specify the wrong tenant. Use the OpenStack "project" name
   791  for tenant-name in your environment configuration.`)
   792  	}
   793  	return nil
   794  }
   795  
   796  func (e *environ) SetConfig(cfg *config.Config) error {
   797  	ecfg, err := providerInstance.newConfig(cfg)
   798  	if err != nil {
   799  		return err
   800  	}
   801  	// At this point, the authentication method config value has been validated so we extract it's value here
   802  	// to avoid having to validate again each time when creating the OpenStack client.
   803  	e.ecfgMutex.Lock()
   804  	defer e.ecfgMutex.Unlock()
   805  	e.ecfgUnlocked = ecfg
   806  
   807  	e.client = authClient(ecfg)
   808  
   809  	e.novaUnlocked = nova.New(e.client)
   810  
   811  	// To support upgrading from old environments, we continue to interface
   812  	// with Swift object storage. We do not use it except for upgrades, so
   813  	// new environments will work with OpenStack deployments that lack Swift.
   814  	e.storageUnlocked = &openstackstorage{
   815  		containerName: ecfg.controlBucket(),
   816  		// this is possibly just a hack - if the ACL is swift.Private,
   817  		// the machine won't be able to get the tools (401 error)
   818  		containerACL: swift.PublicRead,
   819  		swift:        swift.New(e.client)}
   820  	return nil
   821  }
   822  
   823  // getKeystoneImageSource is an imagemetadata.ImageDataSourceFunc that
   824  // returns a DataSource using the "product-streams" keystone URL.
   825  func getKeystoneImageSource(env environs.Environ) (simplestreams.DataSource, error) {
   826  	e, ok := env.(*environ)
   827  	if !ok {
   828  		return nil, errors.NotSupportedf("non-openstack environment")
   829  	}
   830  	return e.getKeystoneDataSource(&e.keystoneImageDataSourceMutex, &e.keystoneImageDataSource, "product-streams")
   831  }
   832  
   833  // getKeystoneToolsSource is a tools.ToolsDataSourceFunc that
   834  // returns a DataSource using the "juju-tools" keystone URL.
   835  func getKeystoneToolsSource(env environs.Environ) (simplestreams.DataSource, error) {
   836  	e, ok := env.(*environ)
   837  	if !ok {
   838  		return nil, errors.NotSupportedf("non-openstack environment")
   839  	}
   840  	return e.getKeystoneDataSource(&e.keystoneToolsDataSourceMutex, &e.keystoneToolsDataSource, "juju-tools")
   841  }
   842  
   843  func (e *environ) getKeystoneDataSource(mu *sync.Mutex, datasource *simplestreams.DataSource, keystoneName string) (simplestreams.DataSource, error) {
   844  	mu.Lock()
   845  	defer mu.Unlock()
   846  	if *datasource != nil {
   847  		return *datasource, nil
   848  	}
   849  	if !e.client.IsAuthenticated() {
   850  		if err := authenticateClient(e); err != nil {
   851  			return nil, err
   852  		}
   853  	}
   854  
   855  	url, err := makeServiceURL(e.client, keystoneName, nil)
   856  	if err != nil {
   857  		return nil, errors.NewNotSupported(err, fmt.Sprintf("cannot make service URL: %v", err))
   858  	}
   859  	verify := utils.VerifySSLHostnames
   860  	if !e.Config().SSLHostnameVerification() {
   861  		verify = utils.NoVerifySSLHostnames
   862  	}
   863  	*datasource = simplestreams.NewURLDataSource("keystone catalog", url, verify)
   864  	return *datasource, nil
   865  }
   866  
   867  // TODO(gz): Move this somewhere more reusable
   868  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})$"
   869  
   870  var uuidRegexp = regexp.MustCompile(uuidPattern)
   871  
   872  // resolveNetwork takes either a network id or label and returns a network id
   873  func (e *environ) resolveNetwork(networkName string) (string, error) {
   874  	if uuidRegexp.MatchString(networkName) {
   875  		// Network id supplied, assume valid as boot will fail if not
   876  		return networkName, nil
   877  	}
   878  	// Network label supplied, resolve to a network id
   879  	networks, err := e.nova().ListNetworks()
   880  	if err != nil {
   881  		return "", err
   882  	}
   883  	var networkIds = []string{}
   884  	for _, network := range networks {
   885  		if network.Label == networkName {
   886  			networkIds = append(networkIds, network.Id)
   887  		}
   888  	}
   889  	switch len(networkIds) {
   890  	case 1:
   891  		return networkIds[0], nil
   892  	case 0:
   893  		return "", fmt.Errorf("No networks exist with label %q", networkName)
   894  	}
   895  	return "", fmt.Errorf("Multiple networks with label %q: %v", networkName, networkIds)
   896  }
   897  
   898  // allocatePublicIP tries to find an available floating IP address, or
   899  // allocates a new one, returning it, or an error
   900  func (e *environ) allocatePublicIP() (*nova.FloatingIP, error) {
   901  	fips, err := e.nova().ListFloatingIPs()
   902  	if err != nil {
   903  		return nil, err
   904  	}
   905  	var newfip *nova.FloatingIP
   906  	for _, fip := range fips {
   907  		newfip = &fip
   908  		if fip.InstanceId != nil && *fip.InstanceId != "" {
   909  			// unavailable, skip
   910  			newfip = nil
   911  			continue
   912  		} else {
   913  			logger.Debugf("found unassigned public ip: %v", newfip.IP)
   914  			// unassigned, we can use it
   915  			return newfip, nil
   916  		}
   917  	}
   918  	if newfip == nil {
   919  		// allocate a new IP and use it
   920  		newfip, err = e.nova().AllocateFloatingIP()
   921  		if err != nil {
   922  			return nil, err
   923  		}
   924  		logger.Debugf("allocated new public IP: %v", newfip.IP)
   925  	}
   926  	return newfip, nil
   927  }
   928  
   929  // assignPublicIP tries to assign the given floating IP address to the
   930  // specified server, or returns an error.
   931  func (e *environ) assignPublicIP(fip *nova.FloatingIP, serverId string) (err error) {
   932  	if fip == nil {
   933  		return fmt.Errorf("cannot assign a nil public IP to %q", serverId)
   934  	}
   935  	if fip.InstanceId != nil && *fip.InstanceId == serverId {
   936  		// IP already assigned, nothing to do
   937  		return nil
   938  	}
   939  	// At startup nw_info is not yet cached so this may fail
   940  	// temporarily while the server is being built
   941  	for a := common.LongAttempt.Start(); a.Next(); {
   942  		err = e.nova().AddServerFloatingIP(serverId, fip.IP)
   943  		if err == nil {
   944  			return nil
   945  		}
   946  	}
   947  	return err
   948  }
   949  
   950  // DistributeInstances implements the state.InstanceDistributor policy.
   951  func (e *environ) DistributeInstances(candidates, distributionGroup []instance.Id) ([]instance.Id, error) {
   952  	return common.DistributeInstances(e, candidates, distributionGroup)
   953  }
   954  
   955  var availabilityZoneAllocations = common.AvailabilityZoneAllocations
   956  
   957  // MaintainInstance is specified in the InstanceBroker interface.
   958  func (*environ) MaintainInstance(args environs.StartInstanceParams) error {
   959  	return nil
   960  }
   961  
   962  // StartInstance is specified in the InstanceBroker interface.
   963  func (e *environ) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) {
   964  	var availabilityZones []string
   965  	if args.Placement != "" {
   966  		placement, err := e.parsePlacement(args.Placement)
   967  		if err != nil {
   968  			return nil, err
   969  		}
   970  		if !placement.availabilityZone.State.Available {
   971  			return nil, fmt.Errorf("availability zone %q is unavailable", placement.availabilityZone.Name)
   972  		}
   973  		availabilityZones = append(availabilityZones, placement.availabilityZone.Name)
   974  	}
   975  
   976  	// If no availability zone is specified, then automatically spread across
   977  	// the known zones for optimal spread across the instance distribution
   978  	// group.
   979  	if len(availabilityZones) == 0 {
   980  		var group []instance.Id
   981  		var err error
   982  		if args.DistributionGroup != nil {
   983  			group, err = args.DistributionGroup()
   984  			if err != nil {
   985  				return nil, err
   986  			}
   987  		}
   988  		zoneInstances, err := availabilityZoneAllocations(e, group)
   989  		if errors.IsNotImplemented(err) {
   990  			// Availability zones are an extension, so we may get a
   991  			// not implemented error; ignore these.
   992  		} else if err != nil {
   993  			return nil, err
   994  		} else {
   995  			for _, zone := range zoneInstances {
   996  				availabilityZones = append(availabilityZones, zone.ZoneName)
   997  			}
   998  		}
   999  		if len(availabilityZones) == 0 {
  1000  			// No explicitly selectable zones available, so use an unspecified zone.
  1001  			availabilityZones = []string{""}
  1002  		}
  1003  	}
  1004  
  1005  	if args.InstanceConfig.HasNetworks() {
  1006  		return nil, fmt.Errorf("starting instances with networks is not supported yet.")
  1007  	}
  1008  
  1009  	series := args.Tools.OneSeries()
  1010  	arches := args.Tools.Arches()
  1011  	spec, err := findInstanceSpec(e, &instances.InstanceConstraint{
  1012  		Region:      e.ecfg().region(),
  1013  		Series:      series,
  1014  		Arches:      arches,
  1015  		Constraints: args.Constraints,
  1016  	})
  1017  	if err != nil {
  1018  		return nil, err
  1019  	}
  1020  	tools, err := args.Tools.Match(tools.Filter{Arch: spec.Image.Arch})
  1021  	if err != nil {
  1022  		return nil, fmt.Errorf("chosen architecture %v not present in %v", spec.Image.Arch, arches)
  1023  	}
  1024  
  1025  	args.InstanceConfig.Tools = tools[0]
  1026  
  1027  	if err := instancecfg.FinishInstanceConfig(args.InstanceConfig, e.Config()); err != nil {
  1028  		return nil, err
  1029  	}
  1030  	userData, err := providerinit.ComposeUserData(args.InstanceConfig, nil, OpenstackRenderer{})
  1031  	if err != nil {
  1032  		return nil, fmt.Errorf("cannot make user data: %v", err)
  1033  	}
  1034  	logger.Debugf("openstack user data; %d bytes", len(userData))
  1035  
  1036  	var networks = []nova.ServerNetworks{}
  1037  	usingNetwork := e.ecfg().network()
  1038  	if usingNetwork != "" {
  1039  		networkId, err := e.resolveNetwork(usingNetwork)
  1040  		if err != nil {
  1041  			return nil, err
  1042  		}
  1043  		logger.Debugf("using network id %q", networkId)
  1044  		networks = append(networks, nova.ServerNetworks{NetworkId: networkId})
  1045  	}
  1046  	withPublicIP := e.ecfg().useFloatingIP()
  1047  	var publicIP *nova.FloatingIP
  1048  	if withPublicIP {
  1049  		logger.Debugf("allocating public IP address for openstack node")
  1050  		if fip, err := e.allocatePublicIP(); err != nil {
  1051  			return nil, fmt.Errorf("cannot allocate a public IP as needed: %v", err)
  1052  		} else {
  1053  			publicIP = fip
  1054  			logger.Infof("allocated public IP %s", publicIP.IP)
  1055  		}
  1056  	}
  1057  
  1058  	cfg := e.Config()
  1059  	groups, err := e.setUpGroups(args.InstanceConfig.MachineId, cfg.APIPort())
  1060  	if err != nil {
  1061  		return nil, fmt.Errorf("cannot set up groups: %v", err)
  1062  	}
  1063  	var groupNames = make([]nova.SecurityGroupName, len(groups))
  1064  	for i, g := range groups {
  1065  		groupNames[i] = nova.SecurityGroupName{g.Name}
  1066  	}
  1067  
  1068  	machineName := resourceName(
  1069  		names.NewMachineTag(args.InstanceConfig.MachineId),
  1070  		e.Config().Name(),
  1071  	)
  1072  
  1073  	var server *nova.Entity
  1074  	for _, availZone := range availabilityZones {
  1075  		var opts = nova.RunServerOpts{
  1076  			Name:               machineName,
  1077  			FlavorId:           spec.InstanceType.Id,
  1078  			ImageId:            spec.Image.Id,
  1079  			UserData:           userData,
  1080  			SecurityGroupNames: groupNames,
  1081  			Networks:           networks,
  1082  			AvailabilityZone:   availZone,
  1083  			Metadata:           args.InstanceConfig.Tags,
  1084  		}
  1085  		for a := shortAttempt.Start(); a.Next(); {
  1086  			server, err = e.nova().RunServer(opts)
  1087  			if err == nil || !gooseerrors.IsNotFound(err) {
  1088  				break
  1089  			}
  1090  		}
  1091  		if isNoValidHostsError(err) {
  1092  			logger.Infof("no valid hosts available in zone %q, trying another availability zone", availZone)
  1093  		} else {
  1094  			break
  1095  		}
  1096  	}
  1097  	if err != nil {
  1098  		return nil, fmt.Errorf("cannot run instance: %v", err)
  1099  	}
  1100  	detail, err := e.nova().GetServer(server.Id)
  1101  	if err != nil {
  1102  		return nil, fmt.Errorf("cannot get started instance: %v", err)
  1103  	}
  1104  	inst := &openstackInstance{
  1105  		e:            e,
  1106  		serverDetail: detail,
  1107  		arch:         &spec.Image.Arch,
  1108  		instType:     &spec.InstanceType,
  1109  	}
  1110  	logger.Infof("started instance %q", inst.Id())
  1111  	if withPublicIP {
  1112  		if err := e.assignPublicIP(publicIP, string(inst.Id())); err != nil {
  1113  			if err := e.terminateInstances([]instance.Id{inst.Id()}); err != nil {
  1114  				// ignore the failure at this stage, just log it
  1115  				logger.Debugf("failed to terminate instance %q: %v", inst.Id(), err)
  1116  			}
  1117  			return nil, fmt.Errorf("cannot assign public address %s to instance %q: %v", publicIP.IP, inst.Id(), err)
  1118  		}
  1119  		inst.floatingIP = publicIP
  1120  		logger.Infof("assigned public IP %s to %q", publicIP.IP, inst.Id())
  1121  	}
  1122  	return &environs.StartInstanceResult{
  1123  		Instance: inst,
  1124  		Hardware: inst.hardwareCharacteristics(),
  1125  	}, nil
  1126  }
  1127  
  1128  func isNoValidHostsError(err error) bool {
  1129  	gooseErr, ok := err.(gooseerrors.Error)
  1130  	return ok && strings.Contains(gooseErr.Cause().Error(), "No valid host was found")
  1131  }
  1132  
  1133  func (e *environ) StopInstances(ids ...instance.Id) error {
  1134  	// If in instance firewall mode, gather the security group names.
  1135  	var securityGroupNames []string
  1136  	if e.Config().FirewallMode() == config.FwInstance {
  1137  		instances, err := e.Instances(ids)
  1138  		if err == environs.ErrNoInstances {
  1139  			return nil
  1140  		}
  1141  		securityGroupNames = make([]string, 0, len(ids))
  1142  		for _, inst := range instances {
  1143  			if inst == nil {
  1144  				continue
  1145  			}
  1146  			openstackName := inst.(*openstackInstance).getServerDetail().Name
  1147  			lastDashPos := strings.LastIndex(openstackName, "-")
  1148  			if lastDashPos == -1 {
  1149  				return fmt.Errorf("cannot identify machine ID in openstack server name %q", openstackName)
  1150  			}
  1151  			securityGroupName := e.machineGroupName(openstackName[lastDashPos+1:])
  1152  			securityGroupNames = append(securityGroupNames, securityGroupName)
  1153  		}
  1154  	}
  1155  	logger.Debugf("terminating instances %v", ids)
  1156  	if err := e.terminateInstances(ids); err != nil {
  1157  		return err
  1158  	}
  1159  	if securityGroupNames != nil {
  1160  		return e.deleteSecurityGroups(securityGroupNames)
  1161  	}
  1162  	return nil
  1163  }
  1164  
  1165  func (e *environ) isAliveServer(server nova.ServerDetail) bool {
  1166  	switch server.Status {
  1167  	// HPCloud uses "BUILD(spawning)" as an intermediate BUILD state
  1168  	// once networking is available.
  1169  	case nova.StatusActive, nova.StatusBuild, nova.StatusBuildSpawning, nova.StatusShutoff, nova.StatusSuspended:
  1170  		return true
  1171  	}
  1172  	return false
  1173  }
  1174  
  1175  func (e *environ) listServers(ids []instance.Id) ([]nova.ServerDetail, error) {
  1176  	wantedServers := make([]nova.ServerDetail, 0, len(ids))
  1177  	if len(ids) == 1 {
  1178  		// Common case, single instance, may return NotFound
  1179  		var maybeServer *nova.ServerDetail
  1180  		maybeServer, err := e.nova().GetServer(string(ids[0]))
  1181  		if err != nil {
  1182  			return nil, err
  1183  		}
  1184  		// Only return server details if it is currently alive
  1185  		if maybeServer != nil && e.isAliveServer(*maybeServer) {
  1186  			wantedServers = append(wantedServers, *maybeServer)
  1187  		}
  1188  		return wantedServers, nil
  1189  	}
  1190  	// List all servers that may be in the environment
  1191  	servers, err := e.nova().ListServersDetail(e.machinesFilter())
  1192  	if err != nil {
  1193  		return nil, err
  1194  	}
  1195  	// Create a set of the ids of servers that are wanted
  1196  	idSet := make(map[string]struct{}, len(ids))
  1197  	for _, id := range ids {
  1198  		idSet[string(id)] = struct{}{}
  1199  	}
  1200  	// Return only servers with the wanted ids that are currently alive
  1201  	for _, server := range servers {
  1202  		if _, ok := idSet[server.Id]; ok && e.isAliveServer(server) {
  1203  			wantedServers = append(wantedServers, server)
  1204  		}
  1205  	}
  1206  	return wantedServers, nil
  1207  }
  1208  
  1209  // updateFloatingIPAddresses updates the instances with any floating IP address
  1210  // that have been assigned to those instances.
  1211  func (e *environ) updateFloatingIPAddresses(instances map[string]instance.Instance) error {
  1212  	fips, err := e.nova().ListFloatingIPs()
  1213  	if err != nil {
  1214  		return err
  1215  	}
  1216  	for _, fip := range fips {
  1217  		if fip.InstanceId != nil && *fip.InstanceId != "" {
  1218  			instId := *fip.InstanceId
  1219  			if inst, ok := instances[instId]; ok {
  1220  				instFip := fip
  1221  				inst.(*openstackInstance).floatingIP = &instFip
  1222  			}
  1223  		}
  1224  	}
  1225  	return nil
  1226  }
  1227  
  1228  func (e *environ) Instances(ids []instance.Id) ([]instance.Instance, error) {
  1229  	if len(ids) == 0 {
  1230  		return nil, nil
  1231  	}
  1232  	// Make a series of requests to cope with eventual consistency.
  1233  	// Each request will attempt to add more instances to the requested
  1234  	// set.
  1235  	var foundServers []nova.ServerDetail
  1236  	for a := shortAttempt.Start(); a.Next(); {
  1237  		var err error
  1238  		foundServers, err = e.listServers(ids)
  1239  		if err != nil {
  1240  			logger.Debugf("error listing servers: %v", err)
  1241  			if !gooseerrors.IsNotFound(err) {
  1242  				return nil, err
  1243  			}
  1244  		}
  1245  		if len(foundServers) == len(ids) {
  1246  			break
  1247  		}
  1248  	}
  1249  	logger.Tracef("%d/%d live servers found", len(foundServers), len(ids))
  1250  	if len(foundServers) == 0 {
  1251  		return nil, environs.ErrNoInstances
  1252  	}
  1253  
  1254  	instsById := make(map[string]instance.Instance, len(foundServers))
  1255  	for i, server := range foundServers {
  1256  		// TODO(wallyworld): lookup the flavor details to fill in the
  1257  		// instance type data
  1258  		instsById[server.Id] = &openstackInstance{
  1259  			e:            e,
  1260  			serverDetail: &foundServers[i],
  1261  		}
  1262  	}
  1263  
  1264  	// Update the instance structs with any floating IP address that has been assigned to the instance.
  1265  	if e.ecfg().useFloatingIP() {
  1266  		if err := e.updateFloatingIPAddresses(instsById); err != nil {
  1267  			return nil, err
  1268  		}
  1269  	}
  1270  
  1271  	insts := make([]instance.Instance, len(ids))
  1272  	var err error
  1273  	for i, id := range ids {
  1274  		if inst := instsById[string(id)]; inst != nil {
  1275  			insts[i] = inst
  1276  		} else {
  1277  			err = environs.ErrPartialInstances
  1278  		}
  1279  	}
  1280  	return insts, err
  1281  }
  1282  
  1283  func (e *environ) AllInstances() (insts []instance.Instance, err error) {
  1284  	servers, err := e.nova().ListServersDetail(e.machinesFilter())
  1285  	if err != nil {
  1286  		return nil, err
  1287  	}
  1288  	instsById := make(map[string]instance.Instance)
  1289  	for _, server := range servers {
  1290  		if e.isAliveServer(server) {
  1291  			var s = server
  1292  			// TODO(wallyworld): lookup the flavor details to fill in the instance type data
  1293  			instsById[s.Id] = &openstackInstance{e: e, serverDetail: &s}
  1294  		}
  1295  	}
  1296  
  1297  	if e.ecfg().useFloatingIP() {
  1298  		if err := e.updateFloatingIPAddresses(instsById); err != nil {
  1299  			return nil, err
  1300  		}
  1301  	}
  1302  
  1303  	for _, inst := range instsById {
  1304  		insts = append(insts, inst)
  1305  	}
  1306  	return insts, err
  1307  }
  1308  
  1309  func (e *environ) Destroy() error {
  1310  	err := common.Destroy(e)
  1311  	if err != nil {
  1312  		return errors.Trace(err)
  1313  	}
  1314  	novaClient := e.nova()
  1315  	securityGroups, err := novaClient.ListSecurityGroups()
  1316  	if err != nil {
  1317  		return errors.Annotate(err, "cannot list security groups")
  1318  	}
  1319  	re, err := regexp.Compile(fmt.Sprintf("^%s(-\\d+)?$", e.jujuGroupName()))
  1320  	if err != nil {
  1321  		return errors.Trace(err)
  1322  	}
  1323  	globalGroupName := e.globalGroupName()
  1324  	for _, group := range securityGroups {
  1325  		if re.MatchString(group.Name) || group.Name == globalGroupName {
  1326  			deleteSecurityGroup(novaClient, group.Name, group.Id)
  1327  		}
  1328  	}
  1329  	return nil
  1330  }
  1331  
  1332  func (e *environ) globalGroupName() string {
  1333  	return fmt.Sprintf("%s-global", e.jujuGroupName())
  1334  }
  1335  
  1336  func (e *environ) machineGroupName(machineId string) string {
  1337  	return fmt.Sprintf("%s-%s", e.jujuGroupName(), machineId)
  1338  }
  1339  
  1340  func (e *environ) jujuGroupName() string {
  1341  	return fmt.Sprintf("juju-%s", e.name)
  1342  }
  1343  
  1344  func resourceName(tag names.Tag, envName string) string {
  1345  	return fmt.Sprintf("juju-%s-%s", envName, tag)
  1346  }
  1347  
  1348  // machinesFilter returns a nova.Filter matching all machines in the environment.
  1349  func (e *environ) machinesFilter() *nova.Filter {
  1350  	filter := nova.NewFilter()
  1351  	filter.Set(nova.FilterServer, fmt.Sprintf("juju-%s-machine-\\d*", e.Config().Name()))
  1352  	return filter
  1353  }
  1354  
  1355  // portsToRuleInfo maps port ranges to nova rules
  1356  func portsToRuleInfo(groupId string, ports []network.PortRange) []nova.RuleInfo {
  1357  	rules := make([]nova.RuleInfo, len(ports))
  1358  	for i, portRange := range ports {
  1359  		rules[i] = nova.RuleInfo{
  1360  			ParentGroupId: groupId,
  1361  			FromPort:      portRange.FromPort,
  1362  			ToPort:        portRange.ToPort,
  1363  			IPProtocol:    portRange.Protocol,
  1364  			Cidr:          "0.0.0.0/0",
  1365  		}
  1366  	}
  1367  	return rules
  1368  }
  1369  
  1370  func (e *environ) openPortsInGroup(name string, portRanges []network.PortRange) error {
  1371  	novaclient := e.nova()
  1372  	group, err := novaclient.SecurityGroupByName(name)
  1373  	if err != nil {
  1374  		return err
  1375  	}
  1376  	rules := portsToRuleInfo(group.Id, portRanges)
  1377  	for _, rule := range rules {
  1378  		_, err := novaclient.CreateSecurityGroupRule(rule)
  1379  		if err != nil {
  1380  			// TODO: if err is not rule already exists, raise?
  1381  			logger.Debugf("error creating security group rule: %v", err.Error())
  1382  		}
  1383  	}
  1384  	return nil
  1385  }
  1386  
  1387  // ruleMatchesPortRange checks if supplied nova security group rule matches the port range
  1388  func ruleMatchesPortRange(rule nova.SecurityGroupRule, portRange network.PortRange) bool {
  1389  	if rule.IPProtocol == nil || rule.FromPort == nil || rule.ToPort == nil {
  1390  		return false
  1391  	}
  1392  	return *rule.IPProtocol == portRange.Protocol &&
  1393  		*rule.FromPort == portRange.FromPort &&
  1394  		*rule.ToPort == portRange.ToPort
  1395  }
  1396  
  1397  func (e *environ) closePortsInGroup(name string, portRanges []network.PortRange) error {
  1398  	if len(portRanges) == 0 {
  1399  		return nil
  1400  	}
  1401  	novaclient := e.nova()
  1402  	group, err := novaclient.SecurityGroupByName(name)
  1403  	if err != nil {
  1404  		return err
  1405  	}
  1406  	// TODO: Hey look ma, it's quadratic
  1407  	for _, portRange := range portRanges {
  1408  		for _, p := range (*group).Rules {
  1409  			if !ruleMatchesPortRange(p, portRange) {
  1410  				continue
  1411  			}
  1412  			err := novaclient.DeleteSecurityGroupRule(p.Id)
  1413  			if err != nil {
  1414  				return err
  1415  			}
  1416  			break
  1417  		}
  1418  	}
  1419  	return nil
  1420  }
  1421  
  1422  func (e *environ) portsInGroup(name string) (portRanges []network.PortRange, err error) {
  1423  	group, err := e.nova().SecurityGroupByName(name)
  1424  	if err != nil {
  1425  		return nil, err
  1426  	}
  1427  	for _, p := range (*group).Rules {
  1428  		portRanges = append(portRanges, network.PortRange{
  1429  			Protocol: *p.IPProtocol,
  1430  			FromPort: *p.FromPort,
  1431  			ToPort:   *p.ToPort,
  1432  		})
  1433  	}
  1434  	network.SortPortRanges(portRanges)
  1435  	return portRanges, nil
  1436  }
  1437  
  1438  // TODO: following 30 lines nearly verbatim from environs/ec2
  1439  
  1440  func (e *environ) OpenPorts(ports []network.PortRange) error {
  1441  	if e.Config().FirewallMode() != config.FwGlobal {
  1442  		return fmt.Errorf("invalid firewall mode %q for opening ports on environment",
  1443  			e.Config().FirewallMode())
  1444  	}
  1445  	if err := e.openPortsInGroup(e.globalGroupName(), ports); err != nil {
  1446  		return err
  1447  	}
  1448  	logger.Infof("opened ports in global group: %v", ports)
  1449  	return nil
  1450  }
  1451  
  1452  func (e *environ) ClosePorts(ports []network.PortRange) error {
  1453  	if e.Config().FirewallMode() != config.FwGlobal {
  1454  		return fmt.Errorf("invalid firewall mode %q for closing ports on environment",
  1455  			e.Config().FirewallMode())
  1456  	}
  1457  	if err := e.closePortsInGroup(e.globalGroupName(), ports); err != nil {
  1458  		return err
  1459  	}
  1460  	logger.Infof("closed ports in global group: %v", ports)
  1461  	return nil
  1462  }
  1463  
  1464  func (e *environ) Ports() ([]network.PortRange, error) {
  1465  	if e.Config().FirewallMode() != config.FwGlobal {
  1466  		return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from environment",
  1467  			e.Config().FirewallMode())
  1468  	}
  1469  	return e.portsInGroup(e.globalGroupName())
  1470  }
  1471  
  1472  func (e *environ) Provider() environs.EnvironProvider {
  1473  	return &providerInstance
  1474  }
  1475  
  1476  func (e *environ) setUpGlobalGroup(groupName string, apiPort int) (nova.SecurityGroup, error) {
  1477  	return e.ensureGroup(groupName,
  1478  		[]nova.RuleInfo{
  1479  			{
  1480  				IPProtocol: "tcp",
  1481  				FromPort:   22,
  1482  				ToPort:     22,
  1483  				Cidr:       "0.0.0.0/0",
  1484  			},
  1485  			{
  1486  				IPProtocol: "tcp",
  1487  				FromPort:   apiPort,
  1488  				ToPort:     apiPort,
  1489  				Cidr:       "0.0.0.0/0",
  1490  			},
  1491  			{
  1492  				IPProtocol: "tcp",
  1493  				FromPort:   1,
  1494  				ToPort:     65535,
  1495  			},
  1496  			{
  1497  				IPProtocol: "udp",
  1498  				FromPort:   1,
  1499  				ToPort:     65535,
  1500  			},
  1501  			{
  1502  				IPProtocol: "icmp",
  1503  				FromPort:   -1,
  1504  				ToPort:     -1,
  1505  			},
  1506  		})
  1507  }
  1508  
  1509  // setUpGroups creates the security groups for the new machine, and
  1510  // returns them.
  1511  //
  1512  // Instances are tagged with a group so they can be distinguished from
  1513  // other instances that might be running on the same OpenStack account.
  1514  // In addition, a specific machine security group is created for each
  1515  // machine, so that its firewall rules can be configured per machine.
  1516  //
  1517  // Note: ideally we'd have a better way to determine group membership so that 2
  1518  // people that happen to share an openstack account and name their environment
  1519  // "openstack" don't end up destroying each other's machines.
  1520  func (e *environ) setUpGroups(machineId string, apiPort int) ([]nova.SecurityGroup, error) {
  1521  	jujuGroup, err := e.setUpGlobalGroup(e.jujuGroupName(), apiPort)
  1522  	if err != nil {
  1523  		return nil, err
  1524  	}
  1525  	var machineGroup nova.SecurityGroup
  1526  	switch e.Config().FirewallMode() {
  1527  	case config.FwInstance:
  1528  		machineGroup, err = e.ensureGroup(e.machineGroupName(machineId), nil)
  1529  	case config.FwGlobal:
  1530  		machineGroup, err = e.ensureGroup(e.globalGroupName(), nil)
  1531  	}
  1532  	if err != nil {
  1533  		return nil, err
  1534  	}
  1535  	groups := []nova.SecurityGroup{jujuGroup, machineGroup}
  1536  	if e.ecfg().useDefaultSecurityGroup() {
  1537  		defaultGroup, err := e.nova().SecurityGroupByName("default")
  1538  		if err != nil {
  1539  			return nil, fmt.Errorf("loading default security group: %v", err)
  1540  		}
  1541  		groups = append(groups, *defaultGroup)
  1542  	}
  1543  	return groups, nil
  1544  }
  1545  
  1546  // zeroGroup holds the zero security group.
  1547  var zeroGroup nova.SecurityGroup
  1548  
  1549  // ensureGroup returns the security group with name and perms.
  1550  // If a group with name does not exist, one will be created.
  1551  // If it exists, its permissions are set to perms.
  1552  func (e *environ) ensureGroup(name string, rules []nova.RuleInfo) (nova.SecurityGroup, error) {
  1553  	novaClient := e.nova()
  1554  	// First attempt to look up an existing group by name.
  1555  	group, err := novaClient.SecurityGroupByName(name)
  1556  	if err == nil {
  1557  		// Group exists, so assume it is correctly set up and return it.
  1558  		// TODO(jam): 2013-09-18 http://pad.lv/121795
  1559  		// We really should verify the group is set up correctly,
  1560  		// because deleting and re-creating environments can get us bad
  1561  		// groups (especially if they were set up under Python)
  1562  		return *group, nil
  1563  	}
  1564  	// Doesn't exist, so try and create it.
  1565  	group, err = novaClient.CreateSecurityGroup(name, "juju group")
  1566  	if err != nil {
  1567  		if !gooseerrors.IsDuplicateValue(err) {
  1568  			return zeroGroup, err
  1569  		} else {
  1570  			// We just tried to create a duplicate group, so load the existing group.
  1571  			group, err = novaClient.SecurityGroupByName(name)
  1572  			if err != nil {
  1573  				return zeroGroup, err
  1574  			}
  1575  			return *group, nil
  1576  		}
  1577  	}
  1578  	// The new group is created so now add the rules.
  1579  	group.Rules = make([]nova.SecurityGroupRule, len(rules))
  1580  	for i, rule := range rules {
  1581  		rule.ParentGroupId = group.Id
  1582  		if rule.Cidr == "" {
  1583  			// http://pad.lv/1226996 Rules that don't have a CIDR
  1584  			// are meant to apply only to this group. If you don't
  1585  			// supply CIDR or GroupId then openstack assumes you
  1586  			// mean CIDR=0.0.0.0/0
  1587  			rule.GroupId = &group.Id
  1588  		}
  1589  		groupRule, err := novaClient.CreateSecurityGroupRule(rule)
  1590  		if err != nil && !gooseerrors.IsDuplicateValue(err) {
  1591  			return zeroGroup, err
  1592  		}
  1593  		group.Rules[i] = *groupRule
  1594  	}
  1595  	return *group, nil
  1596  }
  1597  
  1598  // deleteSecurityGroups deletes the given security groups. If a security
  1599  // group is also used by another environment (see bug #1300755), an attempt
  1600  // to delete this group fails. A warning is logged in this case.
  1601  func (e *environ) deleteSecurityGroups(securityGroupNames []string) error {
  1602  	novaclient := e.nova()
  1603  	allSecurityGroups, err := novaclient.ListSecurityGroups()
  1604  	if err != nil {
  1605  		return err
  1606  	}
  1607  	for _, securityGroup := range allSecurityGroups {
  1608  		for _, name := range securityGroupNames {
  1609  			if securityGroup.Name == name {
  1610  				deleteSecurityGroup(novaclient, name, securityGroup.Id)
  1611  				break
  1612  			}
  1613  		}
  1614  	}
  1615  	return nil
  1616  }
  1617  
  1618  // deleteSecurityGroup attempts to delete the security group. Should it fail,
  1619  // the deletion is retried due to timing issues in openstack. A security group
  1620  // cannot be deleted while it is in use. Theoretically we terminate all the
  1621  // instances before we attempt to delete the associated security groups, but
  1622  // in practice nova hasn't always finished with the instance before it
  1623  // returns, so there is a race condition where we think the instance is
  1624  // terminated and hence attempt to delete the security groups but nova still
  1625  // has it around internally. To attempt to catch this timing issue, deletion
  1626  // of the groups is tried multiple times.
  1627  func deleteSecurityGroup(novaclient *nova.Client, name, id string) {
  1628  	attempts := utils.AttemptStrategy{
  1629  		Total: 30 * time.Second,
  1630  		Delay: time.Second,
  1631  	}
  1632  	logger.Debugf("deleting security group %q", name)
  1633  	i := 0
  1634  	for attempt := attempts.Start(); attempt.Next(); {
  1635  		err := novaclient.DeleteSecurityGroup(id)
  1636  		if err == nil {
  1637  			return
  1638  		}
  1639  		i++
  1640  		if i%4 == 0 {
  1641  			message := fmt.Sprintf("waiting to delete security group %q", name)
  1642  			if i != 4 {
  1643  				message = "still " + message
  1644  			}
  1645  			logger.Debugf(message)
  1646  		}
  1647  	}
  1648  	logger.Warningf("cannot delete security group %q. Used by another environment?", name)
  1649  }
  1650  
  1651  func (e *environ) terminateInstances(ids []instance.Id) error {
  1652  	if len(ids) == 0 {
  1653  		return nil
  1654  	}
  1655  	var firstErr error
  1656  	novaClient := e.nova()
  1657  	for _, id := range ids {
  1658  		err := novaClient.DeleteServer(string(id))
  1659  		if gooseerrors.IsNotFound(err) {
  1660  			err = nil
  1661  		}
  1662  		if err != nil && firstErr == nil {
  1663  			logger.Debugf("error terminating instance %q: %v", id, err)
  1664  			firstErr = err
  1665  		}
  1666  	}
  1667  	return firstErr
  1668  }
  1669  
  1670  // MetadataLookupParams returns parameters which are used to query simplestreams metadata.
  1671  func (e *environ) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
  1672  	if region == "" {
  1673  		region = e.ecfg().region()
  1674  	}
  1675  	cloudSpec, err := e.cloudSpec(region)
  1676  	if err != nil {
  1677  		return nil, err
  1678  	}
  1679  	return &simplestreams.MetadataLookupParams{
  1680  		Series:        config.PreferredSeries(e.ecfg()),
  1681  		Region:        cloudSpec.Region,
  1682  		Endpoint:      cloudSpec.Endpoint,
  1683  		Architectures: arch.AllSupportedArches,
  1684  	}, nil
  1685  }
  1686  
  1687  // Region is specified in the HasRegion interface.
  1688  func (e *environ) Region() (simplestreams.CloudSpec, error) {
  1689  	return e.cloudSpec(e.ecfg().region())
  1690  }
  1691  
  1692  func (e *environ) cloudSpec(region string) (simplestreams.CloudSpec, error) {
  1693  	return simplestreams.CloudSpec{
  1694  		Region:   region,
  1695  		Endpoint: e.ecfg().authURL(),
  1696  	}, nil
  1697  }
  1698  
  1699  // TagInstance implements environs.InstanceTagger.
  1700  func (e *environ) TagInstance(id instance.Id, tags map[string]string) error {
  1701  	if err := e.nova().SetServerMetadata(string(id), tags); err != nil {
  1702  		return errors.Annotate(err, "setting server metadata")
  1703  	}
  1704  	return nil
  1705  }