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