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