github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  	"log"
    11  	"net/url"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/juju/errors"
    17  	"github.com/juju/loggo"
    18  	"github.com/juju/names"
    19  	"github.com/juju/utils"
    20  	"github.com/juju/utils/arch"
    21  	"github.com/juju/version"
    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  
    27  	"github.com/juju/juju/cloud"
    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/tags"
    37  	"github.com/juju/juju/instance"
    38  	"github.com/juju/juju/network"
    39  	"github.com/juju/juju/provider/common"
    40  	"github.com/juju/juju/state"
    41  	"github.com/juju/juju/status"
    42  	"github.com/juju/juju/tools"
    43  )
    44  
    45  var logger = loggo.GetLogger("juju.provider.openstack")
    46  
    47  type EnvironProvider struct {
    48  	environs.ProviderCredentials
    49  	Configurator      ProviderConfigurator
    50  	FirewallerFactory FirewallerFactory
    51  }
    52  
    53  var _ environs.EnvironProvider = (*EnvironProvider)(nil)
    54  
    55  var providerInstance *EnvironProvider = &EnvironProvider{
    56  	OpenstackCredentials{},
    57  	&defaultConfigurator{},
    58  	&firewallerFactory{},
    59  }
    60  
    61  var makeServiceURL = client.AuthenticatingClient.MakeServiceURL
    62  
    63  // Use shortAttempt to poll for short-term events.
    64  // TODO: This was kept to a long timeout because Nova needs more time than EC2.
    65  // For example, HP Cloud takes around 9.1 seconds (10 samples) to return a
    66  // BUILD(spawning) status. But storage delays are handled separately now, and
    67  // perhaps other polling attempts can time out faster.
    68  var shortAttempt = utils.AttemptStrategy{
    69  	Total: 15 * time.Second,
    70  	Delay: 200 * time.Millisecond,
    71  }
    72  
    73  func (p EnvironProvider) Open(cfg *config.Config) (environs.Environ, error) {
    74  	logger.Infof("opening model %q", cfg.Name())
    75  	e := new(Environ)
    76  
    77  	e.firewaller = p.FirewallerFactory.GetFirewaller(e)
    78  	e.configurator = p.Configurator
    79  	err := e.SetConfig(cfg)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	e.name = cfg.Name()
    84  	return e, nil
    85  }
    86  
    87  // RestrictedConfigAttributes is specified in the EnvironProvider interface.
    88  func (p EnvironProvider) RestrictedConfigAttributes() []string {
    89  	return []string{"region", "auth-url", "auth-mode"}
    90  }
    91  
    92  // DetectRegions implements environs.CloudRegionDetector.
    93  func (EnvironProvider) DetectRegions() ([]cloud.Region, error) {
    94  	// If OS_REGION_NAME and OS_AUTH_URL are both set,
    95  	// return return a region using them.
    96  	creds := identity.CredentialsFromEnv()
    97  	if creds.Region == "" {
    98  		return nil, errors.NewNotFound(nil, "OS_REGION_NAME environment variable not set")
    99  	}
   100  	if creds.URL == "" {
   101  		return nil, errors.NewNotFound(nil, "OS_AUTH_URL environment variable not set")
   102  	}
   103  	return []cloud.Region{{
   104  		Name:     creds.Region,
   105  		Endpoint: creds.URL,
   106  	}}, nil
   107  }
   108  
   109  // PrepareForCreateEnvironment is specified in the EnvironProvider interface.
   110  func (p EnvironProvider) PrepareForCreateEnvironment(cfg *config.Config) (*config.Config, error) {
   111  	return cfg, nil
   112  }
   113  
   114  // BootstrapConfig is specified in the EnvironProvider interface.
   115  func (p EnvironProvider) BootstrapConfig(args environs.BootstrapConfigParams) (*config.Config, error) {
   116  	// Add credentials to the configuration.
   117  	attrs := map[string]interface{}{
   118  		"region":   args.CloudRegion,
   119  		"auth-url": args.CloudEndpoint,
   120  	}
   121  	credentialAttrs := args.Credentials.Attributes()
   122  	switch authType := args.Credentials.AuthType(); authType {
   123  	case cloud.UserPassAuthType:
   124  		// TODO(axw) we need a way of saying to use legacy auth.
   125  		attrs["username"] = credentialAttrs["username"]
   126  		attrs["password"] = credentialAttrs["password"]
   127  		attrs["tenant-name"] = credentialAttrs["tenant-name"]
   128  		attrs["domain-name"] = credentialAttrs["domain-name"]
   129  		attrs["auth-mode"] = AuthUserPass
   130  	case cloud.AccessKeyAuthType:
   131  		attrs["access-key"] = credentialAttrs["access-key"]
   132  		attrs["secret-key"] = credentialAttrs["secret-key"]
   133  		attrs["tenant-name"] = credentialAttrs["tenant-name"]
   134  		attrs["auth-mode"] = AuthKeyPair
   135  	default:
   136  		return nil, errors.NotSupportedf("%q auth-type", authType)
   137  	}
   138  
   139  	// Set the default block-storage source.
   140  	if _, ok := args.Config.StorageDefaultBlockSource(); !ok {
   141  		attrs[config.StorageDefaultBlockSourceKey] = CinderProviderType
   142  	}
   143  
   144  	cfg, err := args.Config.Apply(attrs)
   145  	if err != nil {
   146  		return nil, errors.Trace(err)
   147  	}
   148  	return p.PrepareForCreateEnvironment(cfg)
   149  }
   150  
   151  // PrepareForBootstrap is specified in the EnvironProvider interface.
   152  func (p EnvironProvider) PrepareForBootstrap(
   153  	ctx environs.BootstrapContext,
   154  	cfg *config.Config,
   155  ) (environs.Environ, error) {
   156  	e, err := p.Open(cfg)
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  	// Verify credentials.
   161  	if err := authenticateClient(e.(*Environ)); err != nil {
   162  		return nil, err
   163  	}
   164  	return e, nil
   165  }
   166  
   167  // MetadataLookupParams returns parameters which are used to query image metadata to
   168  // find matching image information.
   169  func (p EnvironProvider) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
   170  	if region == "" {
   171  		return nil, errors.Errorf("region must be specified")
   172  	}
   173  	return &simplestreams.MetadataLookupParams{
   174  		Region:        region,
   175  		Architectures: arch.AllSupportedArches,
   176  	}, nil
   177  }
   178  
   179  func (p EnvironProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) {
   180  	m := make(map[string]string)
   181  	ecfg, err := p.newConfig(cfg)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  	m["username"] = ecfg.username()
   186  	m["password"] = ecfg.password()
   187  	m["tenant-name"] = ecfg.tenantName()
   188  	return m, nil
   189  }
   190  
   191  func (p EnvironProvider) newConfig(cfg *config.Config) (*environConfig, error) {
   192  	valid, err := p.Validate(cfg, nil)
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  	return &environConfig{valid, valid.UnknownAttrs()}, nil
   197  }
   198  
   199  type Environ struct {
   200  	common.SupportsUnitPlacementPolicy
   201  
   202  	name string
   203  
   204  	// archMutex gates access to supportedArchitectures
   205  	archMutex sync.Mutex
   206  	// supportedArchitectures caches the architectures
   207  	// for which images can be instantiated.
   208  	supportedArchitectures []string
   209  
   210  	ecfgMutex    sync.Mutex
   211  	ecfgUnlocked *environConfig
   212  	client       client.AuthenticatingClient
   213  	novaUnlocked *nova.Client
   214  
   215  	// keystoneImageDataSource caches the result of getKeystoneImageSource.
   216  	keystoneImageDataSourceMutex sync.Mutex
   217  	keystoneImageDataSource      simplestreams.DataSource
   218  
   219  	// keystoneToolsDataSource caches the result of getKeystoneToolsSource.
   220  	keystoneToolsDataSourceMutex sync.Mutex
   221  	keystoneToolsDataSource      simplestreams.DataSource
   222  
   223  	availabilityZonesMutex sync.Mutex
   224  	availabilityZones      []common.AvailabilityZone
   225  	firewaller             Firewaller
   226  	configurator           ProviderConfigurator
   227  }
   228  
   229  var _ environs.Environ = (*Environ)(nil)
   230  var _ simplestreams.HasRegion = (*Environ)(nil)
   231  var _ state.Prechecker = (*Environ)(nil)
   232  var _ state.InstanceDistributor = (*Environ)(nil)
   233  var _ environs.InstanceTagger = (*Environ)(nil)
   234  
   235  type openstackInstance struct {
   236  	e        *Environ
   237  	instType *instances.InstanceType
   238  	arch     *string
   239  
   240  	mu           sync.Mutex
   241  	serverDetail *nova.ServerDetail
   242  	// floatingIP is non-nil iff use-floating-ip is true.
   243  	floatingIP *nova.FloatingIP
   244  }
   245  
   246  func (inst *openstackInstance) String() string {
   247  	return string(inst.Id())
   248  }
   249  
   250  var _ instance.Instance = (*openstackInstance)(nil)
   251  
   252  func (inst *openstackInstance) Refresh() error {
   253  	inst.mu.Lock()
   254  	defer inst.mu.Unlock()
   255  	server, err := inst.e.nova().GetServer(inst.serverDetail.Id)
   256  	if err != nil {
   257  		return err
   258  	}
   259  	inst.serverDetail = server
   260  	return nil
   261  }
   262  
   263  func (inst *openstackInstance) getServerDetail() *nova.ServerDetail {
   264  	inst.mu.Lock()
   265  	defer inst.mu.Unlock()
   266  	return inst.serverDetail
   267  }
   268  
   269  func (inst *openstackInstance) Id() instance.Id {
   270  	return instance.Id(inst.getServerDetail().Id)
   271  }
   272  
   273  func (inst *openstackInstance) Status() instance.InstanceStatus {
   274  	instStatus := inst.getServerDetail().Status
   275  	jujuStatus := status.StatusPending
   276  	switch instStatus {
   277  	case nova.StatusActive:
   278  		jujuStatus = status.StatusRunning
   279  	case nova.StatusError:
   280  		jujuStatus = status.StatusProvisioningError
   281  	case nova.StatusBuild, nova.StatusBuildSpawning,
   282  		nova.StatusDeleted, nova.StatusHardReboot,
   283  		nova.StatusPassword, nova.StatusReboot,
   284  		nova.StatusRebuild, nova.StatusRescue,
   285  		nova.StatusResize, nova.StatusShutoff,
   286  		nova.StatusSuspended, nova.StatusVerifyResize:
   287  		jujuStatus = status.StatusEmpty
   288  	case nova.StatusUnknown:
   289  		jujuStatus = status.StatusUnknown
   290  	default:
   291  		jujuStatus = status.StatusEmpty
   292  	}
   293  	return instance.InstanceStatus{
   294  		Status:  jujuStatus,
   295  		Message: instStatus,
   296  	}
   297  }
   298  
   299  func (inst *openstackInstance) hardwareCharacteristics() *instance.HardwareCharacteristics {
   300  	hc := &instance.HardwareCharacteristics{Arch: inst.arch}
   301  	if inst.instType != nil {
   302  		hc.Mem = &inst.instType.Mem
   303  		// openstack is special in that a 0-size root disk means that
   304  		// the root disk will result in an instance with a root disk
   305  		// the same size as the image that created it, so we just set
   306  		// the HardwareCharacteristics to nil to signal that we don't
   307  		// know what the correct size is.
   308  		if inst.instType.RootDisk == 0 {
   309  			hc.RootDisk = nil
   310  		} else {
   311  			hc.RootDisk = &inst.instType.RootDisk
   312  		}
   313  		hc.CpuCores = &inst.instType.CpuCores
   314  		hc.CpuPower = inst.instType.CpuPower
   315  		// tags not currently supported on openstack
   316  	}
   317  	hc.AvailabilityZone = &inst.serverDetail.AvailabilityZone
   318  	return hc
   319  }
   320  
   321  // getAddresses returns the existing server information on addresses,
   322  // but fetches the details over the api again if no addresses exist.
   323  func (inst *openstackInstance) getAddresses() (map[string][]nova.IPAddress, error) {
   324  	addrs := inst.getServerDetail().Addresses
   325  	if len(addrs) == 0 {
   326  		server, err := inst.e.nova().GetServer(string(inst.Id()))
   327  		if err != nil {
   328  			return nil, err
   329  		}
   330  		addrs = server.Addresses
   331  	}
   332  	return addrs, nil
   333  }
   334  
   335  // Addresses implements network.Addresses() returning generic address
   336  // details for the instances, and calling the openstack api if needed.
   337  func (inst *openstackInstance) Addresses() ([]network.Address, error) {
   338  	addresses, err := inst.getAddresses()
   339  	if err != nil {
   340  		return nil, err
   341  	}
   342  	var floatingIP string
   343  	if inst.floatingIP != nil && inst.floatingIP.IP != "" {
   344  		floatingIP = inst.floatingIP.IP
   345  		logger.Debugf("instance %v has floating IP address: %v", inst.Id(), floatingIP)
   346  	}
   347  	return convertNovaAddresses(floatingIP, addresses), nil
   348  }
   349  
   350  // convertNovaAddresses returns nova addresses in generic format
   351  func convertNovaAddresses(publicIP string, addresses map[string][]nova.IPAddress) []network.Address {
   352  	var machineAddresses []network.Address
   353  	if publicIP != "" {
   354  		publicAddr := network.NewScopedAddress(publicIP, network.ScopePublic)
   355  		machineAddresses = append(machineAddresses, publicAddr)
   356  	}
   357  	// TODO(gz) Network ordering may be significant but is not preserved by
   358  	// the map, see lp:1188126 for example. That could potentially be fixed
   359  	// in goose, or left to be derived by other means.
   360  	for netName, ips := range addresses {
   361  		networkScope := network.ScopeUnknown
   362  		if netName == "public" {
   363  			networkScope = network.ScopePublic
   364  		}
   365  		for _, address := range ips {
   366  			// If this address has already been added as a floating IP, skip it.
   367  			if publicIP == address.Address {
   368  				continue
   369  			}
   370  			// Assume IPv4 unless specified otherwise
   371  			addrtype := network.IPv4Address
   372  			if address.Version == 6 {
   373  				addrtype = network.IPv6Address
   374  			}
   375  			machineAddr := network.NewScopedAddress(address.Address, networkScope)
   376  			if machineAddr.Type != addrtype {
   377  				logger.Warningf("derived address type %v, nova reports %v", machineAddr.Type, addrtype)
   378  			}
   379  			machineAddresses = append(machineAddresses, machineAddr)
   380  		}
   381  	}
   382  	return machineAddresses
   383  }
   384  
   385  func (inst *openstackInstance) OpenPorts(machineId string, ports []network.PortRange) error {
   386  	return inst.e.firewaller.OpenInstancePorts(inst, machineId, ports)
   387  }
   388  
   389  func (inst *openstackInstance) ClosePorts(machineId string, ports []network.PortRange) error {
   390  	return inst.e.firewaller.CloseInstancePorts(inst, machineId, ports)
   391  }
   392  
   393  func (inst *openstackInstance) Ports(machineId string) ([]network.PortRange, error) {
   394  	return inst.e.firewaller.InstancePorts(inst, machineId)
   395  }
   396  
   397  func (e *Environ) ecfg() *environConfig {
   398  	e.ecfgMutex.Lock()
   399  	ecfg := e.ecfgUnlocked
   400  	e.ecfgMutex.Unlock()
   401  	return ecfg
   402  }
   403  
   404  func (e *Environ) nova() *nova.Client {
   405  	e.ecfgMutex.Lock()
   406  	nova := e.novaUnlocked
   407  	e.ecfgMutex.Unlock()
   408  	return nova
   409  }
   410  
   411  // SupportedArchitectures is specified on the EnvironCapability interface.
   412  func (e *Environ) SupportedArchitectures() ([]string, error) {
   413  	e.archMutex.Lock()
   414  	defer e.archMutex.Unlock()
   415  	if e.supportedArchitectures != nil {
   416  		return e.supportedArchitectures, nil
   417  	}
   418  	// Create a filter to get all images from our region and for the correct stream.
   419  	cloudSpec, err := e.Region()
   420  	if err != nil {
   421  		return nil, err
   422  	}
   423  	imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
   424  		CloudSpec: cloudSpec,
   425  		Stream:    e.Config().ImageStream(),
   426  	})
   427  	e.supportedArchitectures, err = common.SupportedArchitectures(e, imageConstraint)
   428  	return e.supportedArchitectures, err
   429  }
   430  
   431  var unsupportedConstraints = []string{
   432  	constraints.Tags,
   433  	constraints.CpuPower,
   434  }
   435  
   436  // ConstraintsValidator is defined on the Environs interface.
   437  func (e *Environ) ConstraintsValidator() (constraints.Validator, error) {
   438  	validator := constraints.NewValidator()
   439  	validator.RegisterConflicts(
   440  		[]string{constraints.InstanceType},
   441  		[]string{constraints.Mem, constraints.Arch, constraints.RootDisk, constraints.CpuCores})
   442  	validator.RegisterUnsupported(unsupportedConstraints)
   443  	supportedArches, err := e.SupportedArchitectures()
   444  	if err != nil {
   445  		return nil, err
   446  	}
   447  	validator.RegisterVocabulary(constraints.Arch, supportedArches)
   448  	novaClient := e.nova()
   449  	flavors, err := novaClient.ListFlavorsDetail()
   450  	if err != nil {
   451  		return nil, err
   452  	}
   453  	instTypeNames := make([]string, len(flavors))
   454  	for i, flavor := range flavors {
   455  		instTypeNames[i] = flavor.Name
   456  	}
   457  	validator.RegisterVocabulary(constraints.InstanceType, instTypeNames)
   458  	validator.RegisterVocabulary(constraints.VirtType, []string{"kvm", "lxd"})
   459  	return validator, nil
   460  }
   461  
   462  var novaListAvailabilityZones = (*nova.Client).ListAvailabilityZones
   463  
   464  type openstackAvailabilityZone struct {
   465  	nova.AvailabilityZone
   466  }
   467  
   468  func (z *openstackAvailabilityZone) Name() string {
   469  	return z.AvailabilityZone.Name
   470  }
   471  
   472  func (z *openstackAvailabilityZone) Available() bool {
   473  	return z.AvailabilityZone.State.Available
   474  }
   475  
   476  // AvailabilityZones returns a slice of availability zones.
   477  func (e *Environ) AvailabilityZones() ([]common.AvailabilityZone, error) {
   478  	e.availabilityZonesMutex.Lock()
   479  	defer e.availabilityZonesMutex.Unlock()
   480  	if e.availabilityZones == nil {
   481  		zones, err := novaListAvailabilityZones(e.nova())
   482  		if gooseerrors.IsNotImplemented(err) {
   483  			return nil, errors.NotImplementedf("availability zones")
   484  		}
   485  		if err != nil {
   486  			return nil, err
   487  		}
   488  		e.availabilityZones = make([]common.AvailabilityZone, len(zones))
   489  		for i, z := range zones {
   490  			e.availabilityZones[i] = &openstackAvailabilityZone{z}
   491  		}
   492  	}
   493  	return e.availabilityZones, nil
   494  }
   495  
   496  // InstanceAvailabilityZoneNames returns the availability zone names for each
   497  // of the specified instances.
   498  func (e *Environ) InstanceAvailabilityZoneNames(ids []instance.Id) ([]string, error) {
   499  	instances, err := e.Instances(ids)
   500  	if err != nil && err != environs.ErrPartialInstances {
   501  		return nil, err
   502  	}
   503  	zones := make([]string, len(instances))
   504  	for i, inst := range instances {
   505  		if inst == nil {
   506  			continue
   507  		}
   508  		zones[i] = inst.(*openstackInstance).serverDetail.AvailabilityZone
   509  	}
   510  	return zones, err
   511  }
   512  
   513  type openstackPlacement struct {
   514  	availabilityZone nova.AvailabilityZone
   515  }
   516  
   517  func (e *Environ) parsePlacement(placement string) (*openstackPlacement, error) {
   518  	pos := strings.IndexRune(placement, '=')
   519  	if pos == -1 {
   520  		return nil, errors.Errorf("unknown placement directive: %v", placement)
   521  	}
   522  	switch key, value := placement[:pos], placement[pos+1:]; key {
   523  	case "zone":
   524  		availabilityZone := value
   525  		zones, err := e.AvailabilityZones()
   526  		if err != nil {
   527  			return nil, err
   528  		}
   529  		for _, z := range zones {
   530  			if z.Name() == availabilityZone {
   531  				return &openstackPlacement{
   532  					z.(*openstackAvailabilityZone).AvailabilityZone,
   533  				}, nil
   534  			}
   535  		}
   536  		return nil, errors.Errorf("invalid availability zone %q", availabilityZone)
   537  	}
   538  	return nil, errors.Errorf("unknown placement directive: %v", placement)
   539  }
   540  
   541  // PrecheckInstance is defined on the state.Prechecker interface.
   542  func (e *Environ) PrecheckInstance(series string, cons constraints.Value, placement string) error {
   543  	if placement != "" {
   544  		if _, err := e.parsePlacement(placement); err != nil {
   545  			return err
   546  		}
   547  	}
   548  	if !cons.HasInstanceType() {
   549  		return nil
   550  	}
   551  	// Constraint has an instance-type constraint so let's see if it is valid.
   552  	novaClient := e.nova()
   553  	flavors, err := novaClient.ListFlavorsDetail()
   554  	if err != nil {
   555  		return err
   556  	}
   557  	for _, flavor := range flavors {
   558  		if flavor.Name == *cons.InstanceType {
   559  			return nil
   560  		}
   561  	}
   562  	return errors.Errorf("invalid Openstack flavour %q specified", *cons.InstanceType)
   563  }
   564  
   565  func (e *Environ) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) {
   566  	// The client's authentication may have been reset when finding tools if the agent-version
   567  	// attribute was updated so we need to re-authenticate. This will be a no-op if already authenticated.
   568  	// An authenticated client is needed for the URL() call below.
   569  	if err := authenticateClient(e); err != nil {
   570  		return nil, err
   571  	}
   572  	return common.Bootstrap(ctx, e, args)
   573  }
   574  
   575  func (e *Environ) ControllerInstances() ([]instance.Id, error) {
   576  	// Find all instances tagged with tags.JujuIsController.
   577  	instances, err := e.AllInstances()
   578  	if err != nil {
   579  		return nil, errors.Trace(err)
   580  	}
   581  	ids := make([]instance.Id, 0, 1)
   582  	for _, instance := range instances {
   583  		detail := instance.(*openstackInstance).getServerDetail()
   584  		if detail.Metadata[tags.JujuIsController] == "true" {
   585  			ids = append(ids, instance.Id())
   586  		}
   587  	}
   588  	if len(ids) == 0 {
   589  		return nil, environs.ErrNoInstances
   590  	}
   591  	return ids, nil
   592  }
   593  
   594  func (e *Environ) Config() *config.Config {
   595  	return e.ecfg().Config
   596  }
   597  
   598  func newCredentials(ecfg *environConfig) (identity.Credentials, identity.AuthMode) {
   599  	cred := identity.Credentials{
   600  		User:       ecfg.username(),
   601  		Secrets:    ecfg.password(),
   602  		Region:     ecfg.region(),
   603  		TenantName: ecfg.tenantName(),
   604  		URL:        ecfg.authURL(),
   605  		DomainName: ecfg.domainName(),
   606  	}
   607  	// authModeCfg has already been validated so we know it's one of the values below.
   608  	var authMode identity.AuthMode
   609  	switch AuthMode(ecfg.authMode()) {
   610  	case AuthLegacy:
   611  		authMode = identity.AuthLegacy
   612  	case AuthUserPass:
   613  		authMode = identity.AuthUserPass
   614  		if cred.DomainName != "" {
   615  			authMode = identity.AuthUserPassV3
   616  		}
   617  	case AuthKeyPair:
   618  		authMode = identity.AuthKeyPair
   619  		cred.User = ecfg.accessKey()
   620  		cred.Secrets = ecfg.secretKey()
   621  	}
   622  
   623  	return cred, authMode
   624  }
   625  
   626  func determineBestClient(
   627  	options identity.AuthOptions,
   628  	client client.AuthenticatingClient,
   629  	cred identity.Credentials,
   630  	newClient func(*identity.Credentials, identity.AuthMode, *log.Logger) client.AuthenticatingClient,
   631  ) client.AuthenticatingClient {
   632  	for _, option := range options {
   633  		if option.Mode != identity.AuthUserPassV3 {
   634  			continue
   635  		}
   636  		cred.URL = option.Endpoint
   637  		v3client := newClient(&cred, identity.AuthUserPassV3, nil)
   638  		// V3 being advertised is not necessaritly a guarantee that it will
   639  		// work.
   640  		err := v3client.Authenticate()
   641  		if err == nil {
   642  			return v3client
   643  		}
   644  	}
   645  	return client
   646  }
   647  
   648  func authClient(ecfg *environConfig) (client.AuthenticatingClient, error) {
   649  
   650  	identityClientVersion, err := identityClientVersion(ecfg.authURL())
   651  	if err != nil {
   652  		return nil, errors.Annotate(err, "cannot create a client")
   653  	}
   654  	cred, authMode := newCredentials(ecfg)
   655  
   656  	newClient := client.NewClient
   657  	if ecfg.SSLHostnameVerification() == false {
   658  		newClient = client.NewNonValidatingClient
   659  	}
   660  	client := newClient(&cred, authMode, nil)
   661  
   662  	// before returning, lets make sure that we want to have AuthMode
   663  	// AuthUserPass instead of its V3 counterpart.
   664  	if authMode == identity.AuthUserPass && (identityClientVersion == -1 || identityClientVersion == 3) {
   665  		options, err := client.IdentityAuthOptions()
   666  		if err != nil {
   667  			logger.Errorf("cannot determine available auth versions %v", err)
   668  		} else {
   669  			client = determineBestClient(options, client, cred, newClient)
   670  		}
   671  	}
   672  
   673  	// By default, the client requires "compute" and
   674  	// "object-store". Juju only requires "compute".
   675  	client.SetRequiredServiceTypes([]string{"compute"})
   676  	return client, nil
   677  }
   678  
   679  var authenticateClient = func(e *Environ) error {
   680  	err := e.client.Authenticate()
   681  	if err != nil {
   682  		// Log the error in case there are any useful hints,
   683  		// but provide a readable and helpful error message
   684  		// to the user.
   685  		logger.Debugf("authentication failed: %v", err)
   686  		return errors.New(`authentication failed.
   687  
   688  Please ensure the credentials are correct. A common mistake is
   689  to specify the wrong tenant. Use the OpenStack "project" name
   690  for tenant-name in your model configuration.`)
   691  	}
   692  	return nil
   693  }
   694  
   695  func (e *Environ) SetConfig(cfg *config.Config) error {
   696  	ecfg, err := providerInstance.newConfig(cfg)
   697  	if err != nil {
   698  		return err
   699  	}
   700  	// At this point, the authentication method config value has been validated so we extract it's value here
   701  	// to avoid having to validate again each time when creating the OpenStack client.
   702  	e.ecfgMutex.Lock()
   703  	defer e.ecfgMutex.Unlock()
   704  	e.ecfgUnlocked = ecfg
   705  
   706  	client, err := authClient(ecfg)
   707  	if err != nil {
   708  		return errors.Annotate(err, "cannot set config")
   709  	}
   710  	e.client = client
   711  	e.novaUnlocked = nova.New(e.client)
   712  	return nil
   713  }
   714  
   715  func identityClientVersion(authURL string) (int, error) {
   716  	url, err := url.Parse(authURL)
   717  	if err != nil {
   718  		return -1, err
   719  	} else if url.Path == "" {
   720  		return -1, err
   721  	}
   722  	// The last part of the path should be the version #.
   723  	// Example: https://keystone.foo:443/v3/
   724  	logger.Debugf("authURL: %s", authURL)
   725  	versionNumStr := url.Path[2:]
   726  	if versionNumStr[len(versionNumStr)-1] == '/' {
   727  		versionNumStr = versionNumStr[:len(versionNumStr)-1]
   728  	}
   729  	major, _, err := version.ParseMajorMinor(versionNumStr)
   730  	return major, err
   731  }
   732  
   733  // getKeystoneImageSource is an imagemetadata.ImageDataSourceFunc that
   734  // returns a DataSource using the "product-streams" keystone URL.
   735  func getKeystoneImageSource(env environs.Environ) (simplestreams.DataSource, error) {
   736  	e, ok := env.(*Environ)
   737  	if !ok {
   738  		return nil, errors.NotSupportedf("non-openstack model")
   739  	}
   740  	return e.getKeystoneDataSource(&e.keystoneImageDataSourceMutex, &e.keystoneImageDataSource, "product-streams")
   741  }
   742  
   743  // getKeystoneToolsSource is a tools.ToolsDataSourceFunc that
   744  // returns a DataSource using the "juju-tools" keystone URL.
   745  func getKeystoneToolsSource(env environs.Environ) (simplestreams.DataSource, error) {
   746  	e, ok := env.(*Environ)
   747  	if !ok {
   748  		return nil, errors.NotSupportedf("non-openstack model")
   749  	}
   750  	return e.getKeystoneDataSource(&e.keystoneToolsDataSourceMutex, &e.keystoneToolsDataSource, "juju-tools")
   751  }
   752  
   753  func (e *Environ) getKeystoneDataSource(mu *sync.Mutex, datasource *simplestreams.DataSource, keystoneName string) (simplestreams.DataSource, error) {
   754  	mu.Lock()
   755  	defer mu.Unlock()
   756  	if *datasource != nil {
   757  		return *datasource, nil
   758  	}
   759  	if !e.client.IsAuthenticated() {
   760  		if err := authenticateClient(e); err != nil {
   761  			return nil, err
   762  		}
   763  	}
   764  
   765  	url, err := makeServiceURL(e.client, keystoneName, nil)
   766  	if err != nil {
   767  		return nil, errors.NewNotSupported(err, fmt.Sprintf("cannot make service URL: %v", err))
   768  	}
   769  	verify := utils.VerifySSLHostnames
   770  	if !e.Config().SSLHostnameVerification() {
   771  		verify = utils.NoVerifySSLHostnames
   772  	}
   773  	*datasource = simplestreams.NewURLDataSource("keystone catalog", url, verify, simplestreams.SPECIFIC_CLOUD_DATA, false)
   774  	return *datasource, nil
   775  }
   776  
   777  // resolveNetwork takes either a network id or label and returns a network id
   778  func (e *Environ) resolveNetwork(networkName string) (string, error) {
   779  	if utils.IsValidUUIDString(networkName) {
   780  		// Network id supplied, assume valid as boot will fail if not
   781  		return networkName, nil
   782  	}
   783  	// Network label supplied, resolve to a network id
   784  	networks, err := e.nova().ListNetworks()
   785  	if err != nil {
   786  		return "", err
   787  	}
   788  	var networkIds = []string{}
   789  	for _, network := range networks {
   790  		if network.Label == networkName {
   791  			networkIds = append(networkIds, network.Id)
   792  		}
   793  	}
   794  	switch len(networkIds) {
   795  	case 1:
   796  		return networkIds[0], nil
   797  	case 0:
   798  		return "", errors.Errorf("No networks exist with label %q", networkName)
   799  	}
   800  	return "", errors.Errorf("Multiple networks with label %q: %v", networkName, networkIds)
   801  }
   802  
   803  // allocatePublicIP tries to find an available floating IP address, or
   804  // allocates a new one, returning it, or an error
   805  func (e *Environ) allocatePublicIP() (*nova.FloatingIP, error) {
   806  	fips, err := e.nova().ListFloatingIPs()
   807  	if err != nil {
   808  		return nil, err
   809  	}
   810  	var newfip *nova.FloatingIP
   811  	for _, fip := range fips {
   812  		newfip = &fip
   813  		if fip.InstanceId != nil && *fip.InstanceId != "" {
   814  			// unavailable, skip
   815  			newfip = nil
   816  			continue
   817  		} else {
   818  			logger.Debugf("found unassigned public ip: %v", newfip.IP)
   819  			// unassigned, we can use it
   820  			return newfip, nil
   821  		}
   822  	}
   823  	if newfip == nil {
   824  		// allocate a new IP and use it
   825  		newfip, err = e.nova().AllocateFloatingIP()
   826  		if err != nil {
   827  			return nil, err
   828  		}
   829  		logger.Debugf("allocated new public IP: %v", newfip.IP)
   830  	}
   831  	return newfip, nil
   832  }
   833  
   834  // assignPublicIP tries to assign the given floating IP address to the
   835  // specified server, or returns an error.
   836  func (e *Environ) assignPublicIP(fip *nova.FloatingIP, serverId string) (err error) {
   837  	if fip == nil {
   838  		return errors.Errorf("cannot assign a nil public IP to %q", serverId)
   839  	}
   840  	if fip.InstanceId != nil && *fip.InstanceId == serverId {
   841  		// IP already assigned, nothing to do
   842  		return nil
   843  	}
   844  	// At startup nw_info is not yet cached so this may fail
   845  	// temporarily while the server is being built
   846  	for a := common.LongAttempt.Start(); a.Next(); {
   847  		err = e.nova().AddServerFloatingIP(serverId, fip.IP)
   848  		if err == nil {
   849  			return nil
   850  		}
   851  	}
   852  	return err
   853  }
   854  
   855  // DistributeInstances implements the state.InstanceDistributor policy.
   856  func (e *Environ) DistributeInstances(candidates, distributionGroup []instance.Id) ([]instance.Id, error) {
   857  	return common.DistributeInstances(e, candidates, distributionGroup)
   858  }
   859  
   860  var availabilityZoneAllocations = common.AvailabilityZoneAllocations
   861  
   862  // MaintainInstance is specified in the InstanceBroker interface.
   863  func (*Environ) MaintainInstance(args environs.StartInstanceParams) error {
   864  	return nil
   865  }
   866  
   867  // StartInstance is specified in the InstanceBroker interface.
   868  func (e *Environ) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) {
   869  	var availabilityZones []string
   870  	if args.Placement != "" {
   871  		placement, err := e.parsePlacement(args.Placement)
   872  		if err != nil {
   873  			return nil, err
   874  		}
   875  		if !placement.availabilityZone.State.Available {
   876  			return nil, errors.Errorf("availability zone %q is unavailable", placement.availabilityZone.Name)
   877  		}
   878  		availabilityZones = append(availabilityZones, placement.availabilityZone.Name)
   879  	}
   880  
   881  	// If no availability zone is specified, then automatically spread across
   882  	// the known zones for optimal spread across the instance distribution
   883  	// group.
   884  	if len(availabilityZones) == 0 {
   885  		var group []instance.Id
   886  		var err error
   887  		if args.DistributionGroup != nil {
   888  			group, err = args.DistributionGroup()
   889  			if err != nil {
   890  				return nil, err
   891  			}
   892  		}
   893  		zoneInstances, err := availabilityZoneAllocations(e, group)
   894  		if errors.IsNotImplemented(err) {
   895  			// Availability zones are an extension, so we may get a
   896  			// not implemented error; ignore these.
   897  		} else if err != nil {
   898  			return nil, err
   899  		} else {
   900  			for _, zone := range zoneInstances {
   901  				availabilityZones = append(availabilityZones, zone.ZoneName)
   902  			}
   903  		}
   904  		if len(availabilityZones) == 0 {
   905  			// No explicitly selectable zones available, so use an unspecified zone.
   906  			availabilityZones = []string{""}
   907  		}
   908  	}
   909  
   910  	series := args.Tools.OneSeries()
   911  	arches := args.Tools.Arches()
   912  	spec, err := findInstanceSpec(e, &instances.InstanceConstraint{
   913  		Region:      e.ecfg().region(),
   914  		Series:      series,
   915  		Arches:      arches,
   916  		Constraints: args.Constraints,
   917  	}, args.ImageMetadata)
   918  	if err != nil {
   919  		return nil, err
   920  	}
   921  	tools, err := args.Tools.Match(tools.Filter{Arch: spec.Image.Arch})
   922  	if err != nil {
   923  		return nil, errors.Errorf("chosen architecture %v not present in %v", spec.Image.Arch, arches)
   924  	}
   925  
   926  	if err := args.InstanceConfig.SetTools(tools); err != nil {
   927  		return nil, errors.Trace(err)
   928  	}
   929  
   930  	if err := instancecfg.FinishInstanceConfig(args.InstanceConfig, e.Config()); err != nil {
   931  		return nil, err
   932  	}
   933  	cloudcfg, err := e.configurator.GetCloudConfig(args)
   934  	if err != nil {
   935  		return nil, errors.Trace(err)
   936  	}
   937  	userData, err := providerinit.ComposeUserData(args.InstanceConfig, cloudcfg, OpenstackRenderer{})
   938  	if err != nil {
   939  		return nil, errors.Annotate(err, "cannot make user data")
   940  	}
   941  	logger.Debugf("openstack user data; %d bytes", len(userData))
   942  
   943  	var networks = e.firewaller.InitialNetworks()
   944  	usingNetwork := e.ecfg().network()
   945  	if usingNetwork != "" {
   946  		networkId, err := e.resolveNetwork(usingNetwork)
   947  		if err != nil {
   948  			return nil, err
   949  		}
   950  		logger.Debugf("using network id %q", networkId)
   951  		networks = append(networks, nova.ServerNetworks{NetworkId: networkId})
   952  	}
   953  	withPublicIP := e.ecfg().useFloatingIP()
   954  	var publicIP *nova.FloatingIP
   955  	if withPublicIP {
   956  		logger.Debugf("allocating public IP address for openstack node")
   957  		if fip, err := e.allocatePublicIP(); err != nil {
   958  			return nil, errors.Annotate(err, "cannot allocate a public IP as needed")
   959  		} else {
   960  			publicIP = fip
   961  			logger.Infof("allocated public IP %s", publicIP.IP)
   962  		}
   963  	}
   964  
   965  	cfg := e.Config()
   966  	var groupNames = make([]nova.SecurityGroupName, 0)
   967  	groups, err := e.firewaller.SetUpGroups(args.InstanceConfig.MachineId, cfg.APIPort())
   968  	if err != nil {
   969  		return nil, errors.Annotate(err, "cannot set up groups")
   970  	}
   971  
   972  	for _, g := range groups {
   973  		groupNames = append(groupNames, nova.SecurityGroupName{g.Name})
   974  	}
   975  	machineName := resourceName(
   976  		names.NewMachineTag(args.InstanceConfig.MachineId),
   977  		e.Config().UUID(),
   978  	)
   979  
   980  	tryStartNovaInstance := func(
   981  		attempts utils.AttemptStrategy,
   982  		client *nova.Client,
   983  		instanceOpts nova.RunServerOpts,
   984  	) (server *nova.Entity, err error) {
   985  		for a := attempts.Start(); a.Next(); {
   986  			server, err = client.RunServer(instanceOpts)
   987  			if err == nil || gooseerrors.IsNotFound(err) == false {
   988  				break
   989  			}
   990  		}
   991  		return server, err
   992  	}
   993  
   994  	tryStartNovaInstanceAcrossAvailZones := func(
   995  		attempts utils.AttemptStrategy,
   996  		client *nova.Client,
   997  		instanceOpts nova.RunServerOpts,
   998  		availabilityZones []string,
   999  	) (server *nova.Entity, err error) {
  1000  		for _, zone := range availabilityZones {
  1001  			instanceOpts.AvailabilityZone = zone
  1002  			e.configurator.ModifyRunServerOptions(&instanceOpts)
  1003  			server, err = tryStartNovaInstance(attempts, client, instanceOpts)
  1004  			if err == nil || isNoValidHostsError(err) == false {
  1005  				break
  1006  			}
  1007  
  1008  			logger.Infof("no valid hosts available in zone %q, trying another availability zone", zone)
  1009  		}
  1010  
  1011  		if err != nil {
  1012  			err = errors.Annotate(err, "cannot run instance")
  1013  		}
  1014  
  1015  		return server, err
  1016  	}
  1017  
  1018  	var opts = nova.RunServerOpts{
  1019  		Name:               machineName,
  1020  		FlavorId:           spec.InstanceType.Id,
  1021  		ImageId:            spec.Image.Id,
  1022  		UserData:           userData,
  1023  		SecurityGroupNames: groupNames,
  1024  		Networks:           networks,
  1025  		Metadata:           args.InstanceConfig.Tags,
  1026  	}
  1027  	server, err := tryStartNovaInstanceAcrossAvailZones(shortAttempt, e.nova(), opts, availabilityZones)
  1028  	if err != nil {
  1029  		return nil, errors.Trace(err)
  1030  	}
  1031  
  1032  	detail, err := e.nova().GetServer(server.Id)
  1033  	if err != nil {
  1034  		return nil, errors.Annotate(err, "cannot get started instance")
  1035  	}
  1036  
  1037  	inst := &openstackInstance{
  1038  		e:            e,
  1039  		serverDetail: detail,
  1040  		arch:         &spec.Image.Arch,
  1041  		instType:     &spec.InstanceType,
  1042  	}
  1043  	logger.Infof("started instance %q", inst.Id())
  1044  	if withPublicIP {
  1045  		if err := e.assignPublicIP(publicIP, string(inst.Id())); err != nil {
  1046  			if err := e.terminateInstances([]instance.Id{inst.Id()}); err != nil {
  1047  				// ignore the failure at this stage, just log it
  1048  				logger.Debugf("failed to terminate instance %q: %v", inst.Id(), err)
  1049  			}
  1050  			return nil, errors.Annotatef(err, "cannot assign public address %s to instance %q", publicIP.IP, inst.Id())
  1051  		}
  1052  		inst.floatingIP = publicIP
  1053  		logger.Infof("assigned public IP %s to %q", publicIP.IP, inst.Id())
  1054  	}
  1055  	return &environs.StartInstanceResult{
  1056  		Instance: inst,
  1057  		Hardware: inst.hardwareCharacteristics(),
  1058  	}, nil
  1059  }
  1060  
  1061  func isNoValidHostsError(err error) bool {
  1062  	if gooseErr, ok := err.(gooseerrors.Error); ok {
  1063  		if cause := gooseErr.Cause(); cause != nil {
  1064  			return strings.Contains(cause.Error(), "No valid host was found")
  1065  		}
  1066  	}
  1067  	return false
  1068  }
  1069  
  1070  func (e *Environ) StopInstances(ids ...instance.Id) error {
  1071  	// If in instance firewall mode, gather the security group names.
  1072  	securityGroupNames, err := e.firewaller.GetSecurityGroups(ids...)
  1073  	if err == environs.ErrNoInstances {
  1074  		return nil
  1075  	}
  1076  	if err != nil {
  1077  		return err
  1078  	}
  1079  	logger.Debugf("terminating instances %v", ids)
  1080  	if err := e.terminateInstances(ids); err != nil {
  1081  		return err
  1082  	}
  1083  	if securityGroupNames != nil {
  1084  		return e.deleteSecurityGroups(securityGroupNames)
  1085  	}
  1086  	return nil
  1087  }
  1088  
  1089  func (e *Environ) isAliveServer(server nova.ServerDetail) bool {
  1090  	switch server.Status {
  1091  	// HPCloud uses "BUILD(spawning)" as an intermediate BUILD state
  1092  	// once networking is available.
  1093  	case nova.StatusActive, nova.StatusBuild, nova.StatusBuildSpawning, nova.StatusShutoff, nova.StatusSuspended:
  1094  		return true
  1095  	}
  1096  	return false
  1097  }
  1098  
  1099  func (e *Environ) listServers(ids []instance.Id) ([]nova.ServerDetail, error) {
  1100  	wantedServers := make([]nova.ServerDetail, 0, len(ids))
  1101  	if len(ids) == 1 {
  1102  		// Common case, single instance, may return NotFound
  1103  		var maybeServer *nova.ServerDetail
  1104  		maybeServer, err := e.nova().GetServer(string(ids[0]))
  1105  		if err != nil {
  1106  			return nil, err
  1107  		}
  1108  		// Only return server details if it is currently alive
  1109  		if maybeServer != nil && e.isAliveServer(*maybeServer) {
  1110  			wantedServers = append(wantedServers, *maybeServer)
  1111  		}
  1112  		return wantedServers, nil
  1113  	}
  1114  	// List all servers that may be in the environment
  1115  	servers, err := e.nova().ListServersDetail(e.machinesFilter())
  1116  	if err != nil {
  1117  		return nil, err
  1118  	}
  1119  	// Create a set of the ids of servers that are wanted
  1120  	idSet := make(map[string]struct{}, len(ids))
  1121  	for _, id := range ids {
  1122  		idSet[string(id)] = struct{}{}
  1123  	}
  1124  	// Return only servers with the wanted ids that are currently alive
  1125  	for _, server := range servers {
  1126  		if _, ok := idSet[server.Id]; ok && e.isAliveServer(server) {
  1127  			wantedServers = append(wantedServers, server)
  1128  		}
  1129  	}
  1130  	return wantedServers, nil
  1131  }
  1132  
  1133  // updateFloatingIPAddresses updates the instances with any floating IP address
  1134  // that have been assigned to those instances.
  1135  func (e *Environ) updateFloatingIPAddresses(instances map[string]instance.Instance) error {
  1136  	fips, err := e.nova().ListFloatingIPs()
  1137  	if err != nil {
  1138  		return err
  1139  	}
  1140  	for _, fip := range fips {
  1141  		if fip.InstanceId != nil && *fip.InstanceId != "" {
  1142  			instId := *fip.InstanceId
  1143  			if inst, ok := instances[instId]; ok {
  1144  				instFip := fip
  1145  				inst.(*openstackInstance).floatingIP = &instFip
  1146  			}
  1147  		}
  1148  	}
  1149  	return nil
  1150  }
  1151  
  1152  func (e *Environ) Instances(ids []instance.Id) ([]instance.Instance, error) {
  1153  	if len(ids) == 0 {
  1154  		return nil, nil
  1155  	}
  1156  	// Make a series of requests to cope with eventual consistency.
  1157  	// Each request will attempt to add more instances to the requested
  1158  	// set.
  1159  	var foundServers []nova.ServerDetail
  1160  	for a := shortAttempt.Start(); a.Next(); {
  1161  		var err error
  1162  		foundServers, err = e.listServers(ids)
  1163  		if err != nil {
  1164  			logger.Debugf("error listing servers: %v", err)
  1165  			if !gooseerrors.IsNotFound(err) {
  1166  				return nil, err
  1167  			}
  1168  		}
  1169  		if len(foundServers) == len(ids) {
  1170  			break
  1171  		}
  1172  	}
  1173  	logger.Tracef("%d/%d live servers found", len(foundServers), len(ids))
  1174  	if len(foundServers) == 0 {
  1175  		return nil, environs.ErrNoInstances
  1176  	}
  1177  
  1178  	instsById := make(map[string]instance.Instance, len(foundServers))
  1179  	for i, server := range foundServers {
  1180  		// TODO(wallyworld): lookup the flavor details to fill in the
  1181  		// instance type data
  1182  		instsById[server.Id] = &openstackInstance{
  1183  			e:            e,
  1184  			serverDetail: &foundServers[i],
  1185  		}
  1186  	}
  1187  
  1188  	// Update the instance structs with any floating IP address that has been assigned to the instance.
  1189  	if e.ecfg().useFloatingIP() {
  1190  		if err := e.updateFloatingIPAddresses(instsById); err != nil {
  1191  			return nil, err
  1192  		}
  1193  	}
  1194  
  1195  	insts := make([]instance.Instance, len(ids))
  1196  	var err error
  1197  	for i, id := range ids {
  1198  		if inst := instsById[string(id)]; inst != nil {
  1199  			insts[i] = inst
  1200  		} else {
  1201  			err = environs.ErrPartialInstances
  1202  		}
  1203  	}
  1204  	return insts, err
  1205  }
  1206  
  1207  func (e *Environ) AllInstances() (insts []instance.Instance, err error) {
  1208  	servers, err := e.nova().ListServersDetail(e.machinesFilter())
  1209  	if err != nil {
  1210  		return nil, err
  1211  	}
  1212  	instsById := make(map[string]instance.Instance)
  1213  	cfg := e.Config()
  1214  	eUUID := cfg.UUID()
  1215  	for _, server := range servers {
  1216  		modelUUID, ok := server.Metadata[tags.JujuModel]
  1217  		if !ok || modelUUID != eUUID {
  1218  			continue
  1219  		}
  1220  		if e.isAliveServer(server) {
  1221  			var s = server
  1222  			// TODO(wallyworld): lookup the flavor details to fill in the instance type data
  1223  			instsById[s.Id] = &openstackInstance{e: e, serverDetail: &s}
  1224  		}
  1225  	}
  1226  
  1227  	if e.ecfg().useFloatingIP() {
  1228  		if err := e.updateFloatingIPAddresses(instsById); err != nil {
  1229  			return nil, err
  1230  		}
  1231  	}
  1232  
  1233  	for _, inst := range instsById {
  1234  		insts = append(insts, inst)
  1235  	}
  1236  	return insts, err
  1237  }
  1238  
  1239  func (e *Environ) Destroy() error {
  1240  	err := common.Destroy(e)
  1241  	if err != nil {
  1242  		return errors.Trace(err)
  1243  	}
  1244  	return e.firewaller.DeleteGlobalGroups()
  1245  }
  1246  
  1247  func resourceName(tag names.Tag, envName string) string {
  1248  	return fmt.Sprintf("juju-%s-%s", envName, tag)
  1249  }
  1250  
  1251  // machinesFilter returns a nova.Filter matching all machines in the environment.
  1252  func (e *Environ) machinesFilter() *nova.Filter {
  1253  	filter := nova.NewFilter()
  1254  	eUUID := e.Config().UUID()
  1255  	filter.Set(nova.FilterServer, fmt.Sprintf("juju-%s-machine-\\d*", eUUID))
  1256  	return filter
  1257  }
  1258  
  1259  // portsToRuleInfo maps port ranges to nova rules
  1260  func portsToRuleInfo(groupId string, ports []network.PortRange) []nova.RuleInfo {
  1261  	rules := make([]nova.RuleInfo, len(ports))
  1262  	for i, portRange := range ports {
  1263  		rules[i] = nova.RuleInfo{
  1264  			ParentGroupId: groupId,
  1265  			FromPort:      portRange.FromPort,
  1266  			ToPort:        portRange.ToPort,
  1267  			IPProtocol:    portRange.Protocol,
  1268  			Cidr:          "0.0.0.0/0",
  1269  		}
  1270  	}
  1271  	return rules
  1272  }
  1273  
  1274  func (e *Environ) OpenPorts(ports []network.PortRange) error {
  1275  	return e.firewaller.OpenPorts(ports)
  1276  }
  1277  
  1278  func (e *Environ) ClosePorts(ports []network.PortRange) error {
  1279  	return e.firewaller.ClosePorts(ports)
  1280  }
  1281  
  1282  func (e *Environ) Ports() ([]network.PortRange, error) {
  1283  	return e.firewaller.Ports()
  1284  }
  1285  
  1286  func (e *Environ) Provider() environs.EnvironProvider {
  1287  	return providerInstance
  1288  }
  1289  
  1290  // deleteSecurityGroups deletes the given security groups. If a security
  1291  // group is also used by another environment (see bug #1300755), an attempt
  1292  // to delete this group fails. A warning is logged in this case.
  1293  func (e *Environ) deleteSecurityGroups(securityGroupNames []string) error {
  1294  	novaclient := e.nova()
  1295  	allSecurityGroups, err := novaclient.ListSecurityGroups()
  1296  	if err != nil {
  1297  		return err
  1298  	}
  1299  	for _, securityGroup := range allSecurityGroups {
  1300  		for _, name := range securityGroupNames {
  1301  			if securityGroup.Name == name {
  1302  				deleteSecurityGroup(novaclient, name, securityGroup.Id)
  1303  				break
  1304  			}
  1305  		}
  1306  	}
  1307  	return nil
  1308  }
  1309  
  1310  func (e *Environ) terminateInstances(ids []instance.Id) error {
  1311  	if len(ids) == 0 {
  1312  		return nil
  1313  	}
  1314  	var firstErr error
  1315  	novaClient := e.nova()
  1316  	for _, id := range ids {
  1317  		err := novaClient.DeleteServer(string(id))
  1318  		if gooseerrors.IsNotFound(err) {
  1319  			err = nil
  1320  		}
  1321  		if err != nil && firstErr == nil {
  1322  			logger.Debugf("error terminating instance %q: %v", id, err)
  1323  			firstErr = err
  1324  		}
  1325  	}
  1326  	return firstErr
  1327  }
  1328  
  1329  // MetadataLookupParams returns parameters which are used to query simplestreams metadata.
  1330  func (e *Environ) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
  1331  	if region == "" {
  1332  		region = e.ecfg().region()
  1333  	}
  1334  	cloudSpec, err := e.cloudSpec(region)
  1335  	if err != nil {
  1336  		return nil, err
  1337  	}
  1338  	return &simplestreams.MetadataLookupParams{
  1339  		Series:        config.PreferredSeries(e.ecfg()),
  1340  		Region:        cloudSpec.Region,
  1341  		Endpoint:      cloudSpec.Endpoint,
  1342  		Architectures: arch.AllSupportedArches,
  1343  	}, nil
  1344  }
  1345  
  1346  // Region is specified in the HasRegion interface.
  1347  func (e *Environ) Region() (simplestreams.CloudSpec, error) {
  1348  	return e.cloudSpec(e.ecfg().region())
  1349  }
  1350  
  1351  func (e *Environ) cloudSpec(region string) (simplestreams.CloudSpec, error) {
  1352  	return simplestreams.CloudSpec{
  1353  		Region:   region,
  1354  		Endpoint: e.ecfg().authURL(),
  1355  	}, nil
  1356  }
  1357  
  1358  // TagInstance implements environs.InstanceTagger.
  1359  func (e *Environ) TagInstance(id instance.Id, tags map[string]string) error {
  1360  	if err := e.nova().SetServerMetadata(string(id), tags); err != nil {
  1361  		return errors.Annotate(err, "setting server metadata")
  1362  	}
  1363  	return nil
  1364  }