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