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