github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"crypto/tls"
    10  	"crypto/x509"
    11  	"fmt"
    12  	"net/url"
    13  	"path"
    14  	"strconv"
    15  	"strings"
    16  	"sync"
    17  	"time"
    18  
    19  	"github.com/juju/clock"
    20  	"github.com/juju/errors"
    21  	"github.com/juju/jsonschema"
    22  	"github.com/juju/loggo"
    23  	"github.com/juju/retry"
    24  	"github.com/juju/utils"
    25  	"github.com/juju/version"
    26  	"gopkg.in/goose.v2/cinder"
    27  	"gopkg.in/goose.v2/client"
    28  	gooseerrors "gopkg.in/goose.v2/errors"
    29  	"gopkg.in/goose.v2/identity"
    30  	gooselogging "gopkg.in/goose.v2/logging"
    31  	"gopkg.in/goose.v2/neutron"
    32  	"gopkg.in/goose.v2/nova"
    33  	"gopkg.in/juju/names.v2"
    34  
    35  	"github.com/juju/juju/cloud"
    36  	"github.com/juju/juju/cloudconfig/instancecfg"
    37  	"github.com/juju/juju/cloudconfig/providerinit"
    38  	"github.com/juju/juju/cmd/juju/interact"
    39  	"github.com/juju/juju/core/constraints"
    40  	"github.com/juju/juju/core/instance"
    41  	"github.com/juju/juju/core/status"
    42  	"github.com/juju/juju/environs"
    43  	"github.com/juju/juju/environs/config"
    44  	"github.com/juju/juju/environs/context"
    45  	"github.com/juju/juju/environs/instances"
    46  	"github.com/juju/juju/environs/simplestreams"
    47  	"github.com/juju/juju/environs/tags"
    48  	"github.com/juju/juju/network"
    49  	"github.com/juju/juju/provider/common"
    50  	"github.com/juju/juju/storage"
    51  	"github.com/juju/juju/tools"
    52  )
    53  
    54  var logger = loggo.GetLogger("juju.provider.openstack")
    55  
    56  type EnvironProvider struct {
    57  	environs.ProviderCredentials
    58  	Configurator      ProviderConfigurator
    59  	FirewallerFactory FirewallerFactory
    60  	FlavorFilter      FlavorFilter
    61  
    62  	// NetworkingDecorator, if non-nil, will be used to
    63  	// decorate the default networking implementation.
    64  	// This can be used to override behaviour.
    65  	NetworkingDecorator NetworkingDecorator
    66  
    67  	// ClientFromEndpoint returns an Openstack client for the given endpoint.
    68  	ClientFromEndpoint func(endpoint string) client.AuthenticatingClient
    69  }
    70  
    71  var (
    72  	_ environs.CloudEnvironProvider = (*EnvironProvider)(nil)
    73  	_ environs.ProviderSchema       = (*EnvironProvider)(nil)
    74  )
    75  
    76  var providerInstance *EnvironProvider = &EnvironProvider{
    77  	ProviderCredentials: OpenstackCredentials{},
    78  	Configurator:        &defaultConfigurator{},
    79  	FirewallerFactory:   &firewallerFactory{},
    80  	FlavorFilter:        FlavorFilterFunc(AcceptAllFlavors),
    81  	NetworkingDecorator: nil,
    82  	ClientFromEndpoint:  newGooseClient,
    83  }
    84  
    85  var cloudSchema = &jsonschema.Schema{
    86  	Type:     []jsonschema.Type{jsonschema.ObjectType},
    87  	Required: []string{cloud.EndpointKey, cloud.AuthTypesKey, cloud.RegionsKey},
    88  	Order:    []string{cloud.EndpointKey, cloud.CertFilenameKey, cloud.AuthTypesKey, cloud.RegionsKey},
    89  	Properties: map[string]*jsonschema.Schema{
    90  		cloud.EndpointKey: {
    91  			Singular: "the API endpoint url for the cloud",
    92  			Type:     []jsonschema.Type{jsonschema.StringType},
    93  			Format:   jsonschema.FormatURI,
    94  			Default:  "",
    95  			EnvVars:  []string{"OS_AUTH_URL"},
    96  		},
    97  		cloud.CertFilenameKey: {
    98  			Singular:      "a path to the CA certificate for your cloud if one is required to access it. (optional)",
    99  			Type:          []jsonschema.Type{jsonschema.StringType},
   100  			Format:        interact.FormatCertFilename,
   101  			Default:       "",
   102  			PromptDefault: "none",
   103  			EnvVars:       []string{"OS_CACERT"},
   104  		},
   105  		cloud.AuthTypesKey: {
   106  			Singular:    "auth type",
   107  			Plural:      "auth types",
   108  			Type:        []jsonschema.Type{jsonschema.ArrayType},
   109  			UniqueItems: jsonschema.Bool(true),
   110  			Items: &jsonschema.ItemSpec{
   111  				Schemas: []*jsonschema.Schema{{
   112  					Type: []jsonschema.Type{jsonschema.StringType},
   113  					Enum: []interface{}{
   114  						string(cloud.AccessKeyAuthType),
   115  						string(cloud.UserPassAuthType),
   116  					},
   117  				}},
   118  			},
   119  		},
   120  		cloud.RegionsKey: {
   121  			Type:     []jsonschema.Type{jsonschema.ObjectType},
   122  			Singular: "region",
   123  			Plural:   "regions",
   124  			Default:  "",
   125  			EnvVars:  []string{"OS_REGION_NAME"},
   126  			AdditionalProperties: &jsonschema.Schema{
   127  				Type:          []jsonschema.Type{jsonschema.ObjectType},
   128  				Required:      []string{cloud.EndpointKey},
   129  				MaxProperties: jsonschema.Int(1),
   130  				Properties: map[string]*jsonschema.Schema{
   131  					cloud.EndpointKey: {
   132  						Singular:      "the API endpoint url for the region",
   133  						Type:          []jsonschema.Type{jsonschema.StringType},
   134  						Format:        jsonschema.FormatURI,
   135  						Default:       "",
   136  						PromptDefault: "use cloud api url",
   137  					},
   138  				},
   139  			},
   140  		},
   141  	},
   142  }
   143  
   144  var makeServiceURL = client.AuthenticatingClient.MakeServiceURL
   145  
   146  // TODO: shortAttempt was kept to a long timeout because Nova needs
   147  // more time than EC2.  Storage delays are handled separately now, and
   148  // perhaps other polling attempts can time out faster.
   149  
   150  // shortAttempt is used when polling for short-term events in tests.
   151  var shortAttempt = utils.AttemptStrategy{
   152  	Total: 15 * time.Second,
   153  	Delay: 200 * time.Millisecond,
   154  }
   155  
   156  // Version is part of the EnvironProvider interface.
   157  func (EnvironProvider) Version() int {
   158  	return 0
   159  }
   160  
   161  func (p EnvironProvider) Open(args environs.OpenParams) (environs.Environ, error) {
   162  	logger.Infof("opening model %q", args.Config.Name())
   163  	if err := validateCloudSpec(args.Cloud); err != nil {
   164  		return nil, errors.Annotate(err, "validating cloud spec")
   165  	}
   166  	uuid := args.Config.UUID()
   167  	namespace, err := instance.NewNamespace(uuid)
   168  	if err != nil {
   169  		return nil, errors.Annotate(err, "creating instance namespace")
   170  	}
   171  
   172  	e := &Environ{
   173  		name:         args.Config.Name(),
   174  		uuid:         uuid,
   175  		cloud:        args.Cloud,
   176  		namespace:    namespace,
   177  		clock:        clock.WallClock,
   178  		configurator: p.Configurator,
   179  		flavorFilter: p.FlavorFilter,
   180  	}
   181  	e.firewaller = p.FirewallerFactory.GetFirewaller(e)
   182  
   183  	var networking Networking = &switchingNetworking{env: e}
   184  	if p.NetworkingDecorator != nil {
   185  		var err error
   186  		networking, err = p.NetworkingDecorator.DecorateNetworking(networking)
   187  		if err != nil {
   188  			return nil, errors.Trace(err)
   189  		}
   190  	}
   191  	e.networking = networking
   192  
   193  	if err := e.SetConfig(args.Config); err != nil {
   194  		return nil, err
   195  	}
   196  
   197  	e.ecfgMutex.Lock()
   198  	defer e.ecfgMutex.Unlock()
   199  	client, err := authClient(e.cloud, e.ecfgUnlocked)
   200  	if err != nil {
   201  		return nil, errors.Annotate(err, "cannot set config")
   202  	}
   203  	e.clientUnlocked = client
   204  	e.novaUnlocked = nova.New(e.clientUnlocked)
   205  	e.neutronUnlocked = neutron.New(e.clientUnlocked)
   206  
   207  	return e, nil
   208  }
   209  
   210  // DetectRegions implements environs.CloudRegionDetector.
   211  func (EnvironProvider) DetectRegions() ([]cloud.Region, error) {
   212  	// If OS_REGION_NAME and OS_AUTH_URL are both set,
   213  	// return return a region using them.
   214  	creds, err := identity.CredentialsFromEnv()
   215  	if err != nil {
   216  		return nil, errors.Errorf("failed to retrive cred from env : %v", err)
   217  	}
   218  	if creds.Region == "" {
   219  		return nil, errors.NewNotFound(nil, "OS_REGION_NAME environment variable not set")
   220  	}
   221  	if creds.URL == "" {
   222  		return nil, errors.NewNotFound(nil, "OS_AUTH_URL environment variable not set")
   223  	}
   224  	return []cloud.Region{{
   225  		Name:     creds.Region,
   226  		Endpoint: creds.URL,
   227  	}}, nil
   228  }
   229  
   230  // CloudSchema returns the schema for adding new clouds of this type.
   231  func (p EnvironProvider) CloudSchema() *jsonschema.Schema {
   232  	return cloudSchema
   233  }
   234  
   235  // Ping tests the connection to the cloud, to verify the endpoint is valid.
   236  func (p EnvironProvider) Ping(ctx context.ProviderCallContext, endpoint string) error {
   237  	c := p.ClientFromEndpoint(endpoint)
   238  	if _, err := c.IdentityAuthOptions(); err != nil {
   239  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   240  		return errors.Wrap(err, errors.Errorf("No Openstack server running at %s", endpoint))
   241  	}
   242  	return nil
   243  }
   244  
   245  // newGooseClient is the default function in EnvironProvider.ClientFromEndpoint.
   246  func newGooseClient(endpoint string) client.AuthenticatingClient {
   247  	// Use NonValidatingClient, in case the endpoint is behind a cert
   248  	return client.NewNonValidatingClient(&identity.Credentials{URL: endpoint}, 0, nil)
   249  }
   250  
   251  // PrepareConfig is specified in the EnvironProvider interface.
   252  func (p EnvironProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
   253  	if err := validateCloudSpec(args.Cloud); err != nil {
   254  		return nil, errors.Annotate(err, "validating cloud spec")
   255  	}
   256  
   257  	// Set the default block-storage source.
   258  	attrs := make(map[string]interface{})
   259  	if _, ok := args.Config.StorageDefaultBlockSource(); !ok {
   260  		attrs[config.StorageDefaultBlockSourceKey] = CinderProviderType
   261  	}
   262  
   263  	cfg, err := args.Config.Apply(attrs)
   264  	if err != nil {
   265  		return nil, errors.Trace(err)
   266  	}
   267  	return cfg, nil
   268  }
   269  
   270  // MetadataLookupParams returns parameters which are used to query image metadata to
   271  // find matching image information.
   272  func (p EnvironProvider) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
   273  	if region == "" {
   274  		return nil, errors.Errorf("region must be specified")
   275  	}
   276  	return &simplestreams.MetadataLookupParams{
   277  		Region: region,
   278  	}, nil
   279  }
   280  
   281  func (p EnvironProvider) newConfig(cfg *config.Config) (*environConfig, error) {
   282  	valid, err := p.Validate(cfg, nil)
   283  	if err != nil {
   284  		return nil, err
   285  	}
   286  	return &environConfig{valid, valid.UnknownAttrs()}, nil
   287  }
   288  
   289  type Environ struct {
   290  	name      string
   291  	uuid      string
   292  	cloud     environs.CloudSpec
   293  	namespace instance.Namespace
   294  
   295  	ecfgMutex       sync.Mutex
   296  	ecfgUnlocked    *environConfig
   297  	clientUnlocked  client.AuthenticatingClient
   298  	novaUnlocked    *nova.Client
   299  	neutronUnlocked *neutron.Client
   300  	volumeURL       *url.URL
   301  
   302  	// keystoneImageDataSource caches the result of getKeystoneImageSource.
   303  	keystoneImageDataSourceMutex sync.Mutex
   304  	keystoneImageDataSource      simplestreams.DataSource
   305  
   306  	// keystoneToolsDataSource caches the result of getKeystoneToolsSource.
   307  	keystoneToolsDataSourceMutex sync.Mutex
   308  	keystoneToolsDataSource      simplestreams.DataSource
   309  
   310  	availabilityZonesMutex sync.Mutex
   311  	availabilityZones      []common.AvailabilityZone
   312  	firewaller             Firewaller
   313  	networking             Networking
   314  	configurator           ProviderConfigurator
   315  	flavorFilter           FlavorFilter
   316  
   317  	// Clock is defined so it can be replaced for testing
   318  	clock clock.Clock
   319  
   320  	publicIPMutex sync.Mutex
   321  }
   322  
   323  var _ environs.Environ = (*Environ)(nil)
   324  var _ environs.NetworkingEnviron = (*Environ)(nil)
   325  var _ simplestreams.HasRegion = (*Environ)(nil)
   326  var _ context.Distributor = (*Environ)(nil)
   327  var _ environs.InstanceTagger = (*Environ)(nil)
   328  
   329  type openstackInstance struct {
   330  	e        *Environ
   331  	instType *instances.InstanceType
   332  	arch     *string
   333  
   334  	mu           sync.Mutex
   335  	serverDetail *nova.ServerDetail
   336  	// floatingIP is non-nil iff use-floating-ip is true.
   337  	floatingIP *string
   338  }
   339  
   340  func (inst *openstackInstance) String() string {
   341  	return string(inst.Id())
   342  }
   343  
   344  var _ instances.Instance = (*openstackInstance)(nil)
   345  
   346  func (inst *openstackInstance) Refresh(ctx context.ProviderCallContext) error {
   347  	inst.mu.Lock()
   348  	defer inst.mu.Unlock()
   349  	server, err := inst.e.nova().GetServer(inst.serverDetail.Id)
   350  	if err != nil {
   351  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   352  		return err
   353  	}
   354  	inst.serverDetail = server
   355  	return nil
   356  }
   357  
   358  func (inst *openstackInstance) getServerDetail() *nova.ServerDetail {
   359  	inst.mu.Lock()
   360  	defer inst.mu.Unlock()
   361  	return inst.serverDetail
   362  }
   363  
   364  func (inst *openstackInstance) Id() instance.Id {
   365  	return instance.Id(inst.getServerDetail().Id)
   366  }
   367  
   368  func (inst *openstackInstance) Status(ctx context.ProviderCallContext) instance.Status {
   369  	instStatus := inst.getServerDetail().Status
   370  	jujuStatus := status.Pending
   371  	switch instStatus {
   372  	case nova.StatusActive:
   373  		jujuStatus = status.Running
   374  	case nova.StatusError:
   375  		jujuStatus = status.ProvisioningError
   376  	case nova.StatusBuild, nova.StatusBuildSpawning,
   377  		nova.StatusDeleted, nova.StatusHardReboot,
   378  		nova.StatusPassword, nova.StatusReboot,
   379  		nova.StatusRebuild, nova.StatusRescue,
   380  		nova.StatusResize, nova.StatusShutoff,
   381  		nova.StatusSuspended, nova.StatusVerifyResize:
   382  		jujuStatus = status.Empty
   383  	case nova.StatusUnknown:
   384  		jujuStatus = status.Unknown
   385  	default:
   386  		jujuStatus = status.Empty
   387  	}
   388  	return instance.Status{
   389  		Status:  jujuStatus,
   390  		Message: instStatus,
   391  	}
   392  }
   393  
   394  func (inst *openstackInstance) hardwareCharacteristics() *instance.HardwareCharacteristics {
   395  	hc := &instance.HardwareCharacteristics{Arch: inst.arch}
   396  	if inst.instType != nil {
   397  		hc.Mem = &inst.instType.Mem
   398  		// openstack is special in that a 0-size root disk means that
   399  		// the root disk will result in an instance with a root disk
   400  		// the same size as the image that created it, so we just set
   401  		// the HardwareCharacteristics to nil to signal that we don't
   402  		// know what the correct size is.
   403  		if inst.instType.RootDisk == 0 {
   404  			hc.RootDisk = nil
   405  		} else {
   406  			hc.RootDisk = &inst.instType.RootDisk
   407  		}
   408  		hc.CpuCores = &inst.instType.CpuCores
   409  		hc.CpuPower = inst.instType.CpuPower
   410  		// tags not currently supported on openstack
   411  	}
   412  	hc.AvailabilityZone = &inst.serverDetail.AvailabilityZone
   413  	return hc
   414  }
   415  
   416  // getAddresses returns the existing server information on addresses,
   417  // but fetches the details over the api again if no addresses exist.
   418  func (inst *openstackInstance) getAddresses(ctx context.ProviderCallContext) (map[string][]nova.IPAddress, error) {
   419  	addrs := inst.getServerDetail().Addresses
   420  	if len(addrs) == 0 {
   421  		server, err := inst.e.nova().GetServer(string(inst.Id()))
   422  		if err != nil {
   423  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   424  			return nil, err
   425  		}
   426  		addrs = server.Addresses
   427  	}
   428  	return addrs, nil
   429  }
   430  
   431  // Addresses implements network.Addresses() returning generic address
   432  // details for the instances, and calling the openstack api if needed.
   433  func (inst *openstackInstance) Addresses(ctx context.ProviderCallContext) ([]network.Address, error) {
   434  	addresses, err := inst.getAddresses(ctx)
   435  	if err != nil {
   436  		return nil, err
   437  	}
   438  	var floatingIP string
   439  	if inst.floatingIP != nil {
   440  		floatingIP = *inst.floatingIP
   441  		logger.Debugf("instance %v has floating IP address: %v", inst.Id(), floatingIP)
   442  	}
   443  	return convertNovaAddresses(floatingIP, addresses), nil
   444  }
   445  
   446  // convertNovaAddresses returns nova addresses in generic format
   447  func convertNovaAddresses(publicIP string, addresses map[string][]nova.IPAddress) []network.Address {
   448  	var machineAddresses []network.Address
   449  	if publicIP != "" {
   450  		publicAddr := network.NewScopedAddress(publicIP, network.ScopePublic)
   451  		machineAddresses = append(machineAddresses, publicAddr)
   452  	}
   453  	// TODO(gz) Network ordering may be significant but is not preserved by
   454  	// the map, see lp:1188126 for example. That could potentially be fixed
   455  	// in goose, or left to be derived by other means.
   456  	for netName, ips := range addresses {
   457  		networkScope := network.ScopeUnknown
   458  		if netName == "public" {
   459  			networkScope = network.ScopePublic
   460  		}
   461  		for _, address := range ips {
   462  			// If this address has already been added as a floating IP, skip it.
   463  			if publicIP == address.Address {
   464  				continue
   465  			}
   466  			// Assume IPv4 unless specified otherwise
   467  			addrtype := network.IPv4Address
   468  			if address.Version == 6 {
   469  				addrtype = network.IPv6Address
   470  			}
   471  			machineAddr := network.NewScopedAddress(address.Address, networkScope)
   472  			if machineAddr.Type != addrtype {
   473  				logger.Warningf("derived address type %v, nova reports %v", machineAddr.Type, addrtype)
   474  			}
   475  			machineAddresses = append(machineAddresses, machineAddr)
   476  		}
   477  	}
   478  	return machineAddresses
   479  }
   480  
   481  func (inst *openstackInstance) OpenPorts(ctx context.ProviderCallContext, machineId string, rules []network.IngressRule) error {
   482  	return inst.e.firewaller.OpenInstancePorts(ctx, inst, machineId, rules)
   483  }
   484  
   485  func (inst *openstackInstance) ClosePorts(ctx context.ProviderCallContext, machineId string, rules []network.IngressRule) error {
   486  	return inst.e.firewaller.CloseInstancePorts(ctx, inst, machineId, rules)
   487  }
   488  
   489  func (inst *openstackInstance) IngressRules(ctx context.ProviderCallContext, machineId string) ([]network.IngressRule, error) {
   490  	return inst.e.firewaller.InstanceIngressRules(ctx, inst, machineId)
   491  }
   492  
   493  func (e *Environ) ecfg() *environConfig {
   494  	e.ecfgMutex.Lock()
   495  	ecfg := e.ecfgUnlocked
   496  	e.ecfgMutex.Unlock()
   497  	return ecfg
   498  }
   499  
   500  func (e *Environ) client() client.AuthenticatingClient {
   501  	e.ecfgMutex.Lock()
   502  	client := e.clientUnlocked
   503  	e.ecfgMutex.Unlock()
   504  	return client
   505  }
   506  
   507  func (e *Environ) nova() *nova.Client {
   508  	e.ecfgMutex.Lock()
   509  	nova := e.novaUnlocked
   510  	e.ecfgMutex.Unlock()
   511  	return nova
   512  }
   513  
   514  func (e *Environ) neutron() *neutron.Client {
   515  	e.ecfgMutex.Lock()
   516  	neutron := e.neutronUnlocked
   517  	e.ecfgMutex.Unlock()
   518  	return neutron
   519  }
   520  
   521  var unsupportedConstraints = []string{
   522  	constraints.Tags,
   523  	constraints.CpuPower,
   524  }
   525  
   526  // ConstraintsValidator is defined on the Environs interface.
   527  func (e *Environ) ConstraintsValidator(ctx context.ProviderCallContext) (constraints.Validator, error) {
   528  	validator := constraints.NewValidator()
   529  	validator.RegisterConflicts(
   530  		[]string{constraints.InstanceType},
   531  		[]string{constraints.Mem, constraints.RootDisk, constraints.Cores})
   532  	validator.RegisterUnsupported(unsupportedConstraints)
   533  	novaClient := e.nova()
   534  	flavors, err := novaClient.ListFlavorsDetail()
   535  	if err != nil {
   536  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   537  		return nil, err
   538  	}
   539  	instTypeNames := make([]string, len(flavors))
   540  	for i, flavor := range flavors {
   541  		instTypeNames[i] = flavor.Name
   542  	}
   543  	validator.RegisterVocabulary(constraints.InstanceType, instTypeNames)
   544  	validator.RegisterVocabulary(constraints.VirtType, []string{"kvm", "lxd"})
   545  	return validator, nil
   546  }
   547  
   548  var novaListAvailabilityZones = (*nova.Client).ListAvailabilityZones
   549  
   550  type openstackAvailabilityZone struct {
   551  	nova.AvailabilityZone
   552  }
   553  
   554  func (z *openstackAvailabilityZone) Name() string {
   555  	return z.AvailabilityZone.Name
   556  }
   557  
   558  func (z *openstackAvailabilityZone) Available() bool {
   559  	return z.AvailabilityZone.State.Available
   560  }
   561  
   562  // AvailabilityZones returns a slice of availability zones.
   563  func (e *Environ) AvailabilityZones(ctx context.ProviderCallContext) ([]common.AvailabilityZone, error) {
   564  	e.availabilityZonesMutex.Lock()
   565  	defer e.availabilityZonesMutex.Unlock()
   566  	if e.availabilityZones == nil {
   567  		zones, err := novaListAvailabilityZones(e.nova())
   568  		if gooseerrors.IsNotImplemented(err) {
   569  			return nil, errors.NotImplementedf("availability zones")
   570  		}
   571  		if err != nil {
   572  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   573  			return nil, err
   574  		}
   575  		e.availabilityZones = make([]common.AvailabilityZone, len(zones))
   576  		for i, z := range zones {
   577  			e.availabilityZones[i] = &openstackAvailabilityZone{z}
   578  		}
   579  	}
   580  	return e.availabilityZones, nil
   581  }
   582  
   583  // InstanceAvailabilityZoneNames returns the availability zone names for each
   584  // of the specified instances.
   585  func (e *Environ) InstanceAvailabilityZoneNames(ctx context.ProviderCallContext, ids []instance.Id) ([]string, error) {
   586  	instances, err := e.Instances(ctx, ids)
   587  	if err != nil && err != environs.ErrPartialInstances {
   588  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   589  		return nil, err
   590  	}
   591  	zones := make([]string, len(instances))
   592  	for i, inst := range instances {
   593  		if inst == nil {
   594  			continue
   595  		}
   596  		zones[i] = inst.(*openstackInstance).serverDetail.AvailabilityZone
   597  	}
   598  	return zones, err
   599  }
   600  
   601  type openstackPlacement struct {
   602  	zoneName string
   603  }
   604  
   605  // DeriveAvailabilityZones is part of the common.ZonedEnviron interface.
   606  func (e *Environ) DeriveAvailabilityZones(ctx context.ProviderCallContext, args environs.StartInstanceParams) ([]string, error) {
   607  	availabilityZone, err := e.deriveAvailabilityZone(ctx, args.Placement, args.VolumeAttachments)
   608  	if err != nil && !errors.IsNotImplemented(err) {
   609  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   610  		return nil, errors.Trace(err)
   611  	}
   612  	if availabilityZone != "" {
   613  		return []string{availabilityZone}, nil
   614  	}
   615  	return nil, nil
   616  }
   617  
   618  func (e *Environ) parsePlacement(ctx context.ProviderCallContext, placement string) (*openstackPlacement, error) {
   619  	pos := strings.IndexRune(placement, '=')
   620  	if pos == -1 {
   621  		return nil, errors.Errorf("unknown placement directive: %v", placement)
   622  	}
   623  	switch key, value := placement[:pos], placement[pos+1:]; key {
   624  	case "zone":
   625  		availabilityZone := value
   626  		err := common.ValidateAvailabilityZone(e, ctx, availabilityZone)
   627  		if err != nil {
   628  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   629  			return nil, err
   630  		}
   631  		return &openstackPlacement{zoneName: availabilityZone}, nil
   632  	}
   633  	return nil, errors.Errorf("unknown placement directive: %v", placement)
   634  }
   635  
   636  // PrecheckInstance is defined on the environs.InstancePrechecker interface.
   637  func (e *Environ) PrecheckInstance(ctx context.ProviderCallContext, args environs.PrecheckInstanceParams) error {
   638  	if _, err := e.deriveAvailabilityZone(ctx, args.Placement, args.VolumeAttachments); err != nil {
   639  		return errors.Trace(err)
   640  	}
   641  	if !args.Constraints.HasInstanceType() {
   642  		return nil
   643  	}
   644  	// Constraint has an instance-type constraint so let's see if it is valid.
   645  	novaClient := e.nova()
   646  	flavors, err := novaClient.ListFlavorsDetail()
   647  	if err != nil {
   648  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   649  		return err
   650  	}
   651  	for _, flavor := range flavors {
   652  		if flavor.Name == *args.Constraints.InstanceType {
   653  			return nil
   654  		}
   655  	}
   656  	return errors.Errorf("invalid Openstack flavour %q specified", *args.Constraints.InstanceType)
   657  }
   658  
   659  // PrepareForBootstrap is part of the Environ interface.
   660  func (e *Environ) PrepareForBootstrap(ctx environs.BootstrapContext) error {
   661  	// Verify credentials.
   662  	if err := authenticateClient(e.client()); err != nil {
   663  		return err
   664  	}
   665  	if !e.supportsNeutron() {
   666  		logger.Warningf(`Using deprecated OpenStack APIs.
   667  
   668    Neutron networking is not supported by this OpenStack cloud.
   669    Falling back to deprecated Nova networking.
   670  
   671    Support for deprecated Nova networking APIs will be removed
   672    in a future Juju release. Please upgrade to OpenStack Icehouse
   673    or newer to maintain compatibility.
   674  
   675  `,
   676  		)
   677  	}
   678  	return nil
   679  }
   680  
   681  // Create is part of the Environ interface.
   682  func (e *Environ) Create(ctx context.ProviderCallContext, args environs.CreateParams) error {
   683  	// Verify credentials.
   684  	if err := authenticateClient(e.client()); err != nil {
   685  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   686  		return err
   687  	}
   688  	// TODO(axw) 2016-08-04 #1609643
   689  	// Create global security group(s) here.
   690  	return nil
   691  }
   692  
   693  func (e *Environ) Bootstrap(ctx environs.BootstrapContext, callCtx context.ProviderCallContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) {
   694  	// The client's authentication may have been reset when finding tools if the agent-version
   695  	// attribute was updated so we need to re-authenticate. This will be a no-op if already authenticated.
   696  	// An authenticated client is needed for the URL() call below.
   697  	if err := authenticateClient(e.client()); err != nil {
   698  		common.HandleCredentialError(IsAuthorisationFailure, err, callCtx)
   699  		return nil, err
   700  	}
   701  	result, err := common.Bootstrap(ctx, e, callCtx, args)
   702  	if err != nil {
   703  		common.HandleCredentialError(IsAuthorisationFailure, err, callCtx)
   704  		return nil, err
   705  	}
   706  	return result, nil
   707  }
   708  
   709  func (e *Environ) supportsNeutron() bool {
   710  	client := e.client()
   711  	endpointMap := client.EndpointsForRegion(e.cloud.Region)
   712  	_, ok := endpointMap["network"]
   713  	return ok
   714  }
   715  
   716  func (e *Environ) ControllerInstances(ctx context.ProviderCallContext, controllerUUID string) ([]instance.Id, error) {
   717  	// Find all instances tagged with tags.JujuIsController.
   718  	instances, err := e.allControllerManagedInstances(ctx, controllerUUID, e.ecfg().useFloatingIP())
   719  	if err != nil {
   720  		return nil, errors.Trace(err)
   721  	}
   722  	ids := make([]instance.Id, 0, 1)
   723  	for _, instance := range instances {
   724  		detail := instance.(*openstackInstance).getServerDetail()
   725  		if detail.Metadata[tags.JujuIsController] == "true" {
   726  			ids = append(ids, instance.Id())
   727  		}
   728  	}
   729  	if len(ids) == 0 {
   730  		return nil, environs.ErrNoInstances
   731  	}
   732  	return ids, nil
   733  }
   734  
   735  func (e *Environ) Config() *config.Config {
   736  	return e.ecfg().Config
   737  }
   738  
   739  func newCredentials(spec environs.CloudSpec) (identity.Credentials, identity.AuthMode, error) {
   740  	credAttrs := spec.Credential.Attributes()
   741  	cred := identity.Credentials{
   742  		Region:     spec.Region,
   743  		URL:        spec.Endpoint,
   744  		TenantName: credAttrs[CredAttrTenantName],
   745  		TenantID:   credAttrs[CredAttrTenantID],
   746  	}
   747  
   748  	// AuthType is validated when the environment is opened, so it's known
   749  	// to be one of these values.
   750  	var authMode identity.AuthMode
   751  	switch spec.Credential.AuthType() {
   752  	case cloud.UserPassAuthType:
   753  		// TODO(axw) we need a way of saying to use legacy auth.
   754  		cred.User = credAttrs[CredAttrUserName]
   755  		cred.Secrets = credAttrs[CredAttrPassword]
   756  		cred.ProjectDomain = credAttrs[CredAttrProjectDomainName]
   757  		cred.UserDomain = credAttrs[CredAttrUserDomainName]
   758  		cred.Domain = credAttrs[CredAttrDomainName]
   759  		if credAttrs[CredAttrVersion] != "" {
   760  			version, err := strconv.Atoi(credAttrs[CredAttrVersion])
   761  			if err != nil {
   762  				return identity.Credentials{}, 0,
   763  					errors.Errorf("cred.Version is not a valid integer type : %v", err)
   764  			}
   765  			if version < 3 {
   766  				authMode = identity.AuthUserPass
   767  			} else {
   768  				authMode = identity.AuthUserPassV3
   769  			}
   770  			cred.Version = version
   771  		} else if cred.Domain != "" || cred.UserDomain != "" || cred.ProjectDomain != "" {
   772  			authMode = identity.AuthUserPassV3
   773  		} else {
   774  			authMode = identity.AuthUserPass
   775  		}
   776  	case cloud.AccessKeyAuthType:
   777  		cred.User = credAttrs[CredAttrAccessKey]
   778  		cred.Secrets = credAttrs[CredAttrSecretKey]
   779  		authMode = identity.AuthKeyPair
   780  	}
   781  	return cred, authMode, nil
   782  }
   783  
   784  func authClient(spec environs.CloudSpec, ecfg *environConfig) (client.AuthenticatingClient, error) {
   785  	identityClientVersion, err := identityClientVersion(spec.Endpoint)
   786  	if err != nil {
   787  		return nil, errors.Annotate(err, "cannot create a client")
   788  	}
   789  	cred, authMode, err := newCredentials(spec)
   790  	if err != nil {
   791  		return nil, errors.Annotate(err, "cannot create credential")
   792  	}
   793  	gooseLogger := gooselogging.LoggoLogger{loggo.GetLogger("goose")}
   794  
   795  	cl, _ := newClientByType(cred, authMode, gooseLogger, ecfg.SSLHostnameVerification(), spec.CACertificates)
   796  
   797  	// before returning, lets make sure that we want to have AuthMode
   798  	// AuthUserPass instead of its V3 counterpart.
   799  	if authMode == identity.AuthUserPass && (identityClientVersion == -1 || identityClientVersion == 3) {
   800  		options, err := cl.IdentityAuthOptions()
   801  		if err != nil {
   802  			logger.Errorf("cannot determine available auth versions %v", err)
   803  		}
   804  		for _, option := range options {
   805  			if option.Mode != identity.AuthUserPassV3 {
   806  				continue
   807  			}
   808  			cred.URL = option.Endpoint
   809  			v3Cl, err := newClientByType(cred, identity.AuthUserPassV3, gooseLogger, ecfg.SSLHostnameVerification(), spec.CACertificates)
   810  			if err != nil {
   811  				return nil, err
   812  			}
   813  			// if the v3 client can authenticate, use it, otherwise fallback to the v2 client.
   814  			if err = v3Cl.Authenticate(); err == nil {
   815  				cl = v3Cl
   816  				break
   817  			}
   818  		}
   819  	}
   820  
   821  	// Juju requires "compute" at a minimum. We'll use "network" if it's
   822  	// available in preference to the Neutron network APIs; and "volume" or
   823  	// "volume2" for storage if either one is available.
   824  	cl.SetRequiredServiceTypes([]string{"compute"})
   825  	return cl, nil
   826  }
   827  
   828  // newClientByType returns an authenticating client to talk to the
   829  // OpenStack cloud.  CACertificate and SSLHostnameVerification == false
   830  // config options are mutually exclusive here.
   831  func newClientByType(
   832  	cred identity.Credentials,
   833  	authMode identity.AuthMode,
   834  	gooseLogger gooselogging.CompatLogger,
   835  	sslHostnameVerification bool,
   836  	certs []string,
   837  ) (client.AuthenticatingClient, error) {
   838  	switch {
   839  	case len(certs) > 0:
   840  		tlsConfig := tlsConfig(certs)
   841  		logger.Tracef("using NewClientTLSConfig")
   842  		return client.NewClientTLSConfig(&cred, authMode, gooseLogger, tlsConfig), nil
   843  	case sslHostnameVerification == false:
   844  		logger.Tracef("using NewNonValidatingClient")
   845  		return client.NewNonValidatingClient(&cred, authMode, gooseLogger), nil
   846  	default:
   847  		logger.Tracef("using NewClient")
   848  		return client.NewClient(&cred, authMode, gooseLogger), nil
   849  	}
   850  }
   851  
   852  func tlsConfig(certStrs []string) *tls.Config {
   853  	pool := x509.NewCertPool()
   854  	for _, cert := range certStrs {
   855  		pool.AppendCertsFromPEM([]byte(cert))
   856  	}
   857  	tlsConfig := utils.SecureTLSConfig()
   858  	tlsConfig.RootCAs = pool
   859  	return tlsConfig
   860  }
   861  
   862  type authenticator interface {
   863  	Authenticate() error
   864  }
   865  
   866  var authenticateClient = func(auth authenticator) error {
   867  	err := auth.Authenticate()
   868  	if err != nil {
   869  		// Log the error in case there are any useful hints,
   870  		// but provide a readable and helpful error message
   871  		// to the user.
   872  		logger.Debugf("Authenticate() failed: %v", err)
   873  		if gooseerrors.IsUnauthorised(err) {
   874  			return errors.Errorf("authentication failed : %v\n"+
   875  				"Please ensure the credentials are correct. A common mistake is\n"+
   876  				"to specify the wrong tenant. Use the OpenStack project name\n"+
   877  				"for tenant-name in your model configuration. \n", err)
   878  		} else {
   879  			return errors.Annotate(err, "authentication failed.")
   880  		}
   881  	}
   882  	return nil
   883  }
   884  
   885  func (e *Environ) SetConfig(cfg *config.Config) error {
   886  	ecfg, err := providerInstance.newConfig(cfg)
   887  	if err != nil {
   888  		return err
   889  	}
   890  	// At this point, the authentication method config value has been validated so we extract it's value here
   891  	// to avoid having to validate again each time when creating the OpenStack client.
   892  	e.ecfgMutex.Lock()
   893  	defer e.ecfgMutex.Unlock()
   894  	e.ecfgUnlocked = ecfg
   895  
   896  	return nil
   897  }
   898  
   899  func identityClientVersion(authURL string) (int, error) {
   900  	url, err := url.Parse(authURL)
   901  	if err != nil {
   902  		// Return 0 as this is the lowest invalid number according to openstack codebase:
   903  		// -1 is reserved and has special handling; 1, 2, 3, etc are valid identity client versions.
   904  		return 0, err
   905  	}
   906  	if url.Path == authURL {
   907  		// This means we could not parse URL into url structure
   908  		// with protocols, domain, port, etc.
   909  		// For example, specifying "keystone.foo" instead of "https://keystone.foo:443/v3/"
   910  		// falls into this category.
   911  		return 0, errors.Errorf("url %s is malformed", authURL)
   912  	}
   913  	if url.Path == "" || url.Path == "/" {
   914  		// User explicitly did not provide any version, it is empty.
   915  		return -1, nil
   916  	}
   917  	// The last part of the path should be the version #, prefixed with a 'v' or 'V'
   918  	// Example: https://keystone.foo:443/v3/
   919  	// Example: https://sharedhost.foo:443/identity/v3/
   920  	urlpath := strings.ToLower(url.Path)
   921  	urlpath, tail := path.Split(urlpath)
   922  	if len(tail) == 0 && len(urlpath) > 2 {
   923  		// trailing /, remove it and split again
   924  		urlpath, tail = path.Split(strings.TrimRight(urlpath, "/"))
   925  	}
   926  	versionNumStr := strings.TrimPrefix(tail, "v")
   927  	logger.Tracef("authURL: %s", authURL)
   928  	major, _, err := version.ParseMajorMinor(versionNumStr)
   929  	if len(tail) < 2 || tail[0] != 'v' || err != nil {
   930  		// There must be a '/v' in the URL path.
   931  		// At this stage only '/Vxxx' and '/vxxx' are valid where xxx is major.minor version.
   932  		// Return 0 as this is the lowest invalid number according to openstack codebase:
   933  		// -1 is reserved and has special handling; 1, 2, 3, etc are valid identity client versions.
   934  		return 0, errors.NotValidf("version part of identity url %s", authURL)
   935  	}
   936  	return major, err
   937  }
   938  
   939  // getKeystoneImageSource is an imagemetadata.ImageDataSourceFunc that
   940  // returns a DataSource using the "product-streams" keystone URL.
   941  func getKeystoneImageSource(env environs.Environ) (simplestreams.DataSource, error) {
   942  	e, ok := env.(*Environ)
   943  	if !ok {
   944  		return nil, errors.NotSupportedf("non-openstack model")
   945  	}
   946  	return e.getKeystoneDataSource(&e.keystoneImageDataSourceMutex, &e.keystoneImageDataSource, "product-streams")
   947  }
   948  
   949  // getKeystoneToolsSource is a tools.ToolsDataSourceFunc that
   950  // returns a DataSource using the "juju-tools" keystone URL.
   951  func getKeystoneToolsSource(env environs.Environ) (simplestreams.DataSource, error) {
   952  	e, ok := env.(*Environ)
   953  	if !ok {
   954  		return nil, errors.NotSupportedf("non-openstack model")
   955  	}
   956  	return e.getKeystoneDataSource(&e.keystoneToolsDataSourceMutex, &e.keystoneToolsDataSource, "juju-tools")
   957  }
   958  
   959  func (e *Environ) getKeystoneDataSource(mu *sync.Mutex, datasource *simplestreams.DataSource, keystoneName string) (simplestreams.DataSource, error) {
   960  	mu.Lock()
   961  	defer mu.Unlock()
   962  	if *datasource != nil {
   963  		return *datasource, nil
   964  	}
   965  
   966  	client := e.client()
   967  	if !client.IsAuthenticated() {
   968  		if err := authenticateClient(client); err != nil {
   969  			return nil, err
   970  		}
   971  	}
   972  
   973  	url, err := makeServiceURL(client, keystoneName, "", nil)
   974  	if err != nil {
   975  		return nil, errors.NewNotSupported(err, fmt.Sprintf("cannot make service URL: %v", err))
   976  	}
   977  	verify := utils.VerifySSLHostnames
   978  	if !e.Config().SSLHostnameVerification() {
   979  		verify = utils.NoVerifySSLHostnames
   980  	}
   981  	*datasource = simplestreams.NewURLDataSource("keystone catalog", url, verify, simplestreams.SPECIFIC_CLOUD_DATA, false)
   982  	return *datasource, nil
   983  }
   984  
   985  // assignPublicIP tries to assign the given floating IP address to the
   986  // specified server, or returns an error.
   987  func (e *Environ) assignPublicIP(fip *string, serverId string) (err error) {
   988  	if *fip == "" {
   989  		return errors.Errorf("cannot assign a nil public IP to %q", serverId)
   990  	}
   991  	// At startup nw_info is not yet cached so this may fail
   992  	// temporarily while the server is being built
   993  	for a := common.LongAttempt.Start(); a.Next(); {
   994  		err = e.nova().AddServerFloatingIP(serverId, *fip)
   995  		if err == nil {
   996  			return nil
   997  		}
   998  	}
   999  	return err
  1000  }
  1001  
  1002  // DistributeInstances implements the state.InstanceDistributor policy.
  1003  func (e *Environ) DistributeInstances(
  1004  	ctx context.ProviderCallContext, candidates, distributionGroup []instance.Id, limitZones []string,
  1005  ) ([]instance.Id, error) {
  1006  	valid, err := common.DistributeInstances(e, ctx, candidates, distributionGroup, limitZones)
  1007  	if err != nil {
  1008  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1009  		return valid, err
  1010  	}
  1011  	return valid, nil
  1012  }
  1013  
  1014  var availabilityZoneAllocations = common.AvailabilityZoneAllocations
  1015  
  1016  // MaintainInstance is specified in the InstanceBroker interface.
  1017  func (*Environ) MaintainInstance(ctx context.ProviderCallContext, args environs.StartInstanceParams) error {
  1018  	return nil
  1019  }
  1020  
  1021  // StartInstance is specified in the InstanceBroker interface.
  1022  func (e *Environ) StartInstance(ctx context.ProviderCallContext, args environs.StartInstanceParams) (_ *environs.StartInstanceResult, err error) {
  1023  	if args.AvailabilityZone != "" {
  1024  		// args.AvailabilityZone should only be set if this OpenStack
  1025  		// supports zones; validate the zone.
  1026  		volumeAttachmentsZone, err := e.volumeAttachmentsZone(args.VolumeAttachments)
  1027  		if err != nil {
  1028  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1029  			return nil, common.ZoneIndependentError(err)
  1030  		}
  1031  		if err := validateAvailabilityZoneConsistency(args.AvailabilityZone, volumeAttachmentsZone); err != nil {
  1032  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1033  			return nil, common.ZoneIndependentError(err)
  1034  		}
  1035  		if err := common.ValidateAvailabilityZone(e, ctx, args.AvailabilityZone); err != nil {
  1036  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1037  			return nil, errors.Trace(err)
  1038  		}
  1039  	}
  1040  
  1041  	series := args.Tools.OneSeries()
  1042  	arches := args.Tools.Arches()
  1043  	spec, err := findInstanceSpec(e, &instances.InstanceConstraint{
  1044  		Region:      e.cloud.Region,
  1045  		Series:      series,
  1046  		Arches:      arches,
  1047  		Constraints: args.Constraints,
  1048  	}, args.ImageMetadata)
  1049  	if err != nil {
  1050  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1051  		return nil, common.ZoneIndependentError(err)
  1052  	}
  1053  	tools, err := args.Tools.Match(tools.Filter{Arch: spec.Image.Arch})
  1054  	if err != nil {
  1055  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1056  		return nil, common.ZoneIndependentError(
  1057  			errors.Errorf("chosen architecture %v not present in %v", spec.Image.Arch, arches),
  1058  		)
  1059  	}
  1060  
  1061  	if err := args.InstanceConfig.SetTools(tools); err != nil {
  1062  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1063  		return nil, common.ZoneIndependentError(err)
  1064  	}
  1065  
  1066  	if err := instancecfg.FinishInstanceConfig(args.InstanceConfig, e.Config()); err != nil {
  1067  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1068  		return nil, common.ZoneIndependentError(err)
  1069  	}
  1070  	cloudcfg, err := e.configurator.GetCloudConfig(args)
  1071  	if err != nil {
  1072  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1073  		return nil, common.ZoneIndependentError(err)
  1074  	}
  1075  	userData, err := providerinit.ComposeUserData(args.InstanceConfig, cloudcfg, OpenstackRenderer{})
  1076  	if err != nil {
  1077  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1078  		return nil, common.ZoneIndependentError(errors.Annotate(err, "cannot make user data"))
  1079  	}
  1080  	logger.Debugf("openstack user data; %d bytes", len(userData))
  1081  
  1082  	networks, err := e.networking.DefaultNetworks()
  1083  	if err != nil {
  1084  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1085  		return nil, common.ZoneIndependentError(errors.Annotate(err, "getting initial networks"))
  1086  	}
  1087  	usingNetwork := e.ecfg().network()
  1088  	networkId, err := e.networking.ResolveNetwork(usingNetwork, false)
  1089  	if err != nil {
  1090  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1091  		if usingNetwork == "" {
  1092  			// If there is no network configured, we only throw out when the
  1093  			// error reports multiple Openstack networks.
  1094  			// If there are no Openstack networks at all (such as Canonistack),
  1095  			// having no network config is not an error condition.
  1096  			if strings.HasPrefix(err.Error(), "multiple networks") {
  1097  				return nil, common.ZoneIndependentError(errors.New(noNetConfigMsg(err)))
  1098  			}
  1099  		} else {
  1100  			return nil, common.ZoneIndependentError(err)
  1101  		}
  1102  	} else {
  1103  		logger.Debugf("using network id %q", networkId)
  1104  		networks = append(networks, nova.ServerNetworks{NetworkId: networkId})
  1105  	}
  1106  
  1107  	machineName := resourceName(
  1108  		e.namespace,
  1109  		e.name,
  1110  		args.InstanceConfig.MachineId,
  1111  	)
  1112  
  1113  	if e.ecfg().useOpenstackGBP() {
  1114  		client := e.neutron()
  1115  		ptArg := neutron.PolicyTargetV2{
  1116  			Name:                fmt.Sprintf("juju-policytarget-%s", machineName),
  1117  			PolicyTargetGroupId: e.ecfg().policyTargetGroup(),
  1118  		}
  1119  		pt, err := client.CreatePolicyTargetV2(ptArg)
  1120  		if err != nil {
  1121  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1122  			return nil, errors.Trace(err)
  1123  		}
  1124  		networks = append(networks, nova.ServerNetworks{PortId: pt.PortId})
  1125  	}
  1126  
  1127  	// For BUG 1680787: openstack: add support for neutron networks where port
  1128  	// security is disabled.
  1129  	// If any network specified for instance boot has PortSecurityEnabled equals
  1130  	// false, don't create security groups, instance boot will fail.
  1131  	createSecurityGroups := true
  1132  	if len(networks) > 0 && e.supportsNeutron() {
  1133  		client := e.neutron()
  1134  		for _, n := range networks {
  1135  			if n.NetworkId == "" {
  1136  				// It's a GBP network.
  1137  				continue
  1138  			}
  1139  			net, err := client.GetNetworkV2(n.NetworkId)
  1140  			if err != nil {
  1141  				common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1142  				return nil, common.ZoneIndependentError(err)
  1143  			}
  1144  			if net.PortSecurityEnabled != nil &&
  1145  				*net.PortSecurityEnabled == false {
  1146  				createSecurityGroups = *net.PortSecurityEnabled
  1147  				logger.Infof("network %q has port_security_enabled set to false. Not using security groups.", net.Id)
  1148  				break
  1149  			}
  1150  		}
  1151  	}
  1152  
  1153  	var novaGroupNames = []nova.SecurityGroupName{}
  1154  	if createSecurityGroups {
  1155  		var apiPort int
  1156  		if args.InstanceConfig.Controller != nil {
  1157  			apiPort = args.InstanceConfig.Controller.Config.APIPort()
  1158  		} else {
  1159  			// All ports are the same so pick the first.
  1160  			apiPort = args.InstanceConfig.APIInfo.Ports()[0]
  1161  		}
  1162  		groupNames, err := e.firewaller.SetUpGroups(ctx, args.ControllerUUID, args.InstanceConfig.MachineId, apiPort)
  1163  		if err != nil {
  1164  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1165  			return nil, common.ZoneIndependentError(errors.Annotate(err, "cannot set up groups"))
  1166  		}
  1167  		novaGroupNames = make([]nova.SecurityGroupName, len(groupNames))
  1168  		for i, name := range groupNames {
  1169  			novaGroupNames[i].Name = name
  1170  		}
  1171  	}
  1172  
  1173  	waitForActiveServerDetails := func(
  1174  		client *nova.Client,
  1175  		id string,
  1176  		timeout time.Duration,
  1177  	) (server *nova.ServerDetail, err error) {
  1178  
  1179  		var errStillBuilding = errors.Errorf("instance %q has status BUILD", id)
  1180  		err = retry.Call(retry.CallArgs{
  1181  			Clock:       e.clock,
  1182  			Delay:       10 * time.Second,
  1183  			MaxDuration: timeout,
  1184  			Func: func() error {
  1185  				server, err = client.GetServer(id)
  1186  				if err != nil {
  1187  					return err
  1188  				}
  1189  				if server.Status == nova.StatusBuild {
  1190  					return errStillBuilding
  1191  				}
  1192  				return nil
  1193  			},
  1194  			NotifyFunc: func(lastError error, attempt int) {
  1195  				args.StatusCallback(status.Provisioning, fmt.Sprintf("%s, wait 10 seconds before retry, attempt %d", lastError, attempt), nil)
  1196  			},
  1197  			IsFatalError: func(err error) bool {
  1198  				return err != errStillBuilding
  1199  			},
  1200  		})
  1201  		if err != nil {
  1202  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1203  			return nil, err
  1204  		}
  1205  		return server, nil
  1206  	}
  1207  
  1208  	tryStartNovaInstance := func(
  1209  		attempts utils.AttemptStrategy,
  1210  		client *nova.Client,
  1211  		instanceOpts nova.RunServerOpts,
  1212  	) (server *nova.Entity, err error) {
  1213  		for a := attempts.Start(); a.Next(); {
  1214  			server, err = client.RunServer(instanceOpts)
  1215  			if err != nil {
  1216  				common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1217  				break
  1218  			}
  1219  			if server == nil {
  1220  				logger.Warningf("may have lost contact with nova api while creating instances, some stray instances may be around and need to be deleted")
  1221  				break
  1222  			}
  1223  			var serverDetail *nova.ServerDetail
  1224  			serverDetail, err = waitForActiveServerDetails(client, server.Id, 5*time.Minute)
  1225  			if err != nil || serverDetail == nil {
  1226  				server = nil
  1227  				break
  1228  			} else if serverDetail.Status == nova.StatusActive {
  1229  				break
  1230  			} else if serverDetail.Status == nova.StatusError {
  1231  				// Perhaps there is an error case where a retry in the same AZ
  1232  				// is a good idea.
  1233  				faultMsg := " unable to determine fault details"
  1234  				if serverDetail.Fault != nil {
  1235  					faultMsg = fmt.Sprintf(" with fault %q", serverDetail.Fault.Message)
  1236  				} else {
  1237  					logger.Debugf("getting active server details from nova failed without fault details")
  1238  				}
  1239  				logger.Infof("Deleting instance %q in ERROR state%v", server.Id, faultMsg)
  1240  				if err = e.terminateInstances(ctx, []instance.Id{instance.Id(server.Id)}); err != nil {
  1241  					logger.Debugf("Failed to delete instance in ERROR state, %q", err)
  1242  				}
  1243  				server = nil
  1244  				err = errors.New(faultMsg)
  1245  				break
  1246  			}
  1247  		}
  1248  		return server, err
  1249  	}
  1250  
  1251  	var opts = nova.RunServerOpts{
  1252  		Name:               machineName,
  1253  		FlavorId:           spec.InstanceType.Id,
  1254  		ImageId:            spec.Image.Id,
  1255  		UserData:           userData,
  1256  		SecurityGroupNames: novaGroupNames,
  1257  		Networks:           networks,
  1258  		Metadata:           args.InstanceConfig.Tags,
  1259  		AvailabilityZone:   args.AvailabilityZone,
  1260  	}
  1261  	e.configurator.ModifyRunServerOptions(&opts)
  1262  
  1263  	server, err := tryStartNovaInstance(shortAttempt, e.nova(), opts)
  1264  	if err != nil || server == nil {
  1265  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1266  		// 'No valid host available' is typically a resource error,
  1267  		// let the provisioner know it is a good idea to try another
  1268  		// AZ if available.
  1269  		err := errors.Annotate(err, "cannot run instance")
  1270  		zoneSpecific := isNoValidHostsError(err)
  1271  		if !zoneSpecific {
  1272  			err = common.ZoneIndependentError(err)
  1273  		}
  1274  		return nil, err
  1275  	}
  1276  
  1277  	detail, err := e.nova().GetServer(server.Id)
  1278  	if err != nil {
  1279  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1280  		return nil, common.ZoneIndependentError(errors.Annotate(err, "cannot get started instance"))
  1281  	}
  1282  
  1283  	inst := &openstackInstance{
  1284  		e:            e,
  1285  		serverDetail: detail,
  1286  		arch:         &spec.Image.Arch,
  1287  		instType:     &spec.InstanceType,
  1288  	}
  1289  	logger.Infof("started instance %q", inst.Id())
  1290  	withPublicIP := e.ecfg().useFloatingIP()
  1291  	if withPublicIP {
  1292  		// If we don't lock here, AllocatePublicIP() can return the same
  1293  		// public IP for 2 different instances.  Only one will successfully
  1294  		// be assigned the public IP, the other will not have one.
  1295  		e.publicIPMutex.Lock()
  1296  		defer e.publicIPMutex.Unlock()
  1297  		var publicIP *string
  1298  		logger.Debugf("allocating public IP address for openstack node")
  1299  		if fip, err := e.networking.AllocatePublicIP(inst.Id()); err != nil {
  1300  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1301  			return nil, common.ZoneIndependentError(errors.Annotate(err, "cannot allocate a public IP as needed"))
  1302  		} else {
  1303  			publicIP = fip
  1304  			logger.Infof("allocated public IP %s", *publicIP)
  1305  		}
  1306  		if err := e.assignPublicIP(publicIP, string(inst.Id())); err != nil {
  1307  			if err := e.terminateInstances(ctx, []instance.Id{inst.Id()}); err != nil {
  1308  				common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1309  				// ignore the failure at this stage, just log it
  1310  				logger.Debugf("failed to terminate instance %q: %v", inst.Id(), err)
  1311  			}
  1312  			return nil, common.ZoneIndependentError(errors.Annotatef(err,
  1313  				"cannot assign public address %s to instance %q",
  1314  				*publicIP, inst.Id(),
  1315  			))
  1316  		}
  1317  		inst.floatingIP = publicIP
  1318  	}
  1319  
  1320  	return &environs.StartInstanceResult{
  1321  		Instance: inst,
  1322  		Hardware: inst.hardwareCharacteristics(),
  1323  	}, nil
  1324  }
  1325  
  1326  func (e *Environ) deriveAvailabilityZone(
  1327  	ctx context.ProviderCallContext,
  1328  	placement string,
  1329  	volumeAttachments []storage.VolumeAttachmentParams,
  1330  ) (string, error) {
  1331  	volumeAttachmentsZone, err := e.volumeAttachmentsZone(volumeAttachments)
  1332  	if err != nil {
  1333  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1334  		return "", errors.Trace(err)
  1335  	}
  1336  	if placement == "" {
  1337  		return volumeAttachmentsZone, nil
  1338  	}
  1339  	instPlacement, err := e.parsePlacement(ctx, placement)
  1340  	if err != nil {
  1341  		return "", err
  1342  	}
  1343  	if err := validateAvailabilityZoneConsistency(instPlacement.zoneName, volumeAttachmentsZone); err != nil {
  1344  		return "", errors.Annotatef(err, "cannot create instance with placement %q", placement)
  1345  	}
  1346  	return instPlacement.zoneName, nil
  1347  }
  1348  
  1349  func validateAvailabilityZoneConsistency(instanceZone, volumeAttachmentsZone string) error {
  1350  	if volumeAttachmentsZone != "" && instanceZone != volumeAttachmentsZone {
  1351  		return errors.Errorf(
  1352  			"cannot create instance in zone %q, as this will prevent attaching the requested disks in zone %q",
  1353  			instanceZone, volumeAttachmentsZone,
  1354  		)
  1355  	}
  1356  	return nil
  1357  }
  1358  
  1359  // volumeAttachmentsZone determines the availability zone for each volume
  1360  // identified in the volume attachment parameters, checking that they are
  1361  // all the same, and returns the availability zone name.
  1362  func (e *Environ) volumeAttachmentsZone(volumeAttachments []storage.VolumeAttachmentParams) (string, error) {
  1363  	if len(volumeAttachments) == 0 {
  1364  		return "", nil
  1365  	}
  1366  	cinderProvider, err := e.cinderProvider()
  1367  	if err != nil {
  1368  		return "", errors.Trace(err)
  1369  	}
  1370  	volumes, err := modelCinderVolumes(cinderProvider.storageAdapter, cinderProvider.modelUUID)
  1371  	if err != nil {
  1372  		return "", errors.Trace(err)
  1373  	}
  1374  	var zone string
  1375  	for i, a := range volumeAttachments {
  1376  		var v *cinder.Volume
  1377  		for i := range volumes {
  1378  			if volumes[i].ID == a.VolumeId {
  1379  				v = &volumes[i]
  1380  				break
  1381  			}
  1382  		}
  1383  		if v == nil {
  1384  			return "", errors.Errorf("cannot find volume %q to attach to new instance", a.VolumeId)
  1385  		}
  1386  		if zone == "" {
  1387  			zone = v.AvailabilityZone
  1388  		} else if v.AvailabilityZone != zone {
  1389  			return "", errors.Errorf(
  1390  				"cannot attach volumes from multiple availability zones: %s is in %s, %s is in %s",
  1391  				volumeAttachments[i-1].VolumeId, zone, a.VolumeId, v.AvailabilityZone,
  1392  			)
  1393  		}
  1394  	}
  1395  	return zone, nil
  1396  }
  1397  
  1398  func isNoValidHostsError(err error) bool {
  1399  	if cause := errors.Cause(err); cause != nil {
  1400  		return strings.Contains(cause.Error(), "No valid host was found")
  1401  	}
  1402  	return false
  1403  }
  1404  
  1405  func (e *Environ) StopInstances(ctx context.ProviderCallContext, ids ...instance.Id) error {
  1406  	// If in instance firewall mode, gather the security group names.
  1407  	securityGroupNames, err := e.firewaller.GetSecurityGroups(ctx, ids...)
  1408  	if err == environs.ErrNoInstances {
  1409  		return nil
  1410  	}
  1411  	if err != nil {
  1412  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1413  		return err
  1414  	}
  1415  	logger.Debugf("terminating instances %v", ids)
  1416  	if err := e.terminateInstances(ctx, ids); err != nil {
  1417  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1418  		return err
  1419  	}
  1420  	if securityGroupNames != nil {
  1421  		if err := e.firewaller.DeleteGroups(ctx, securityGroupNames...); err != nil {
  1422  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1423  			return err
  1424  		}
  1425  	}
  1426  	return nil
  1427  }
  1428  
  1429  func (e *Environ) isAliveServer(server nova.ServerDetail) bool {
  1430  	switch server.Status {
  1431  	case nova.StatusActive, nova.StatusBuild, nova.StatusBuildSpawning, nova.StatusShutoff, nova.StatusSuspended:
  1432  		return true
  1433  	}
  1434  	return false
  1435  }
  1436  
  1437  func (e *Environ) listServers(ctx context.ProviderCallContext, ids []instance.Id) ([]nova.ServerDetail, error) {
  1438  	wantedServers := make([]nova.ServerDetail, 0, len(ids))
  1439  	if len(ids) == 1 {
  1440  		// Common case, single instance, may return NotFound
  1441  		var maybeServer *nova.ServerDetail
  1442  		maybeServer, err := e.nova().GetServer(string(ids[0]))
  1443  		if err != nil {
  1444  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1445  			return nil, err
  1446  		}
  1447  		// Only return server details if it is currently alive
  1448  		if maybeServer != nil && e.isAliveServer(*maybeServer) {
  1449  			wantedServers = append(wantedServers, *maybeServer)
  1450  		}
  1451  		return wantedServers, nil
  1452  	}
  1453  	// List all instances in the environment.
  1454  	instances, err := e.AllInstances(ctx)
  1455  	if err != nil {
  1456  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1457  		return nil, err
  1458  	}
  1459  	// Return only servers with the wanted ids that are currently alive
  1460  	for _, inst := range instances {
  1461  		inst := inst.(*openstackInstance)
  1462  		serverDetail := *inst.serverDetail
  1463  		if !e.isAliveServer(serverDetail) {
  1464  			continue
  1465  		}
  1466  		for _, id := range ids {
  1467  			if inst.Id() != id {
  1468  				continue
  1469  			}
  1470  			wantedServers = append(wantedServers, serverDetail)
  1471  			break
  1472  		}
  1473  	}
  1474  	return wantedServers, nil
  1475  }
  1476  
  1477  // updateFloatingIPAddresses updates the instances with any floating IP address
  1478  // that have been assigned to those instances.
  1479  func (e *Environ) updateFloatingIPAddresses(ctx context.ProviderCallContext, instances map[string]instances.Instance) error {
  1480  	servers, err := e.nova().ListServersDetail(jujuMachineFilter())
  1481  	if err != nil {
  1482  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1483  		return err
  1484  	}
  1485  	for _, server := range servers {
  1486  		// server.Addresses is a map with entries containing []nova.IPAddress
  1487  		for _, net := range server.Addresses {
  1488  			for _, addr := range net {
  1489  				if addr.Type == "floating" {
  1490  					instId := server.Id
  1491  					if inst, ok := instances[instId]; ok {
  1492  						instFip := &addr.Address
  1493  						inst.(*openstackInstance).floatingIP = instFip
  1494  					}
  1495  				}
  1496  			}
  1497  		}
  1498  	}
  1499  	return nil
  1500  }
  1501  
  1502  func (e *Environ) Instances(ctx context.ProviderCallContext, ids []instance.Id) ([]instances.Instance, error) {
  1503  	if len(ids) == 0 {
  1504  		return nil, nil
  1505  	}
  1506  	// Make a series of requests to cope with eventual consistency.
  1507  	// Each request will attempt to add more instances to the requested
  1508  	// set.
  1509  	var foundServers []nova.ServerDetail
  1510  	for a := shortAttempt.Start(); a.Next(); {
  1511  		var err error
  1512  		foundServers, err = e.listServers(ctx, ids)
  1513  		if err != nil {
  1514  			logger.Debugf("error listing servers: %v", err)
  1515  			if !gooseerrors.IsNotFound(err) {
  1516  				common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1517  				return nil, err
  1518  			}
  1519  		}
  1520  		if len(foundServers) == len(ids) {
  1521  			break
  1522  		}
  1523  	}
  1524  	logger.Tracef("%d/%d live servers found", len(foundServers), len(ids))
  1525  	if len(foundServers) == 0 {
  1526  		return nil, environs.ErrNoInstances
  1527  	}
  1528  
  1529  	instsById := make(map[string]instances.Instance, len(foundServers))
  1530  	for i, server := range foundServers {
  1531  		// TODO(wallyworld): lookup the flavor details to fill in the
  1532  		// instance type data
  1533  		instsById[server.Id] = &openstackInstance{
  1534  			e:            e,
  1535  			serverDetail: &foundServers[i],
  1536  		}
  1537  	}
  1538  
  1539  	// Update the instance structs with any floating IP address that has been assigned to the instance.
  1540  	if e.ecfg().useFloatingIP() {
  1541  		if err := e.updateFloatingIPAddresses(ctx, instsById); err != nil {
  1542  			return nil, err
  1543  		}
  1544  	}
  1545  
  1546  	insts := make([]instances.Instance, len(ids))
  1547  	var err error
  1548  	for i, id := range ids {
  1549  		if inst := instsById[string(id)]; inst != nil {
  1550  			insts[i] = inst
  1551  		} else {
  1552  			err = environs.ErrPartialInstances
  1553  		}
  1554  	}
  1555  	return insts, err
  1556  }
  1557  
  1558  // AdoptResources is part of the Environ interface.
  1559  func (e *Environ) AdoptResources(ctx context.ProviderCallContext, controllerUUID string, fromVersion version.Number) error {
  1560  	var failed []string
  1561  	controllerTag := map[string]string{tags.JujuController: controllerUUID}
  1562  
  1563  	instances, err := e.AllInstances(ctx)
  1564  	if err != nil {
  1565  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1566  		return errors.Trace(err)
  1567  	}
  1568  	for _, instance := range instances {
  1569  		err := e.TagInstance(ctx, instance.Id(), controllerTag)
  1570  		if err != nil {
  1571  			logger.Errorf("error updating controller tag for instance %s: %v", instance.Id(), err)
  1572  			failed = append(failed, string(instance.Id()))
  1573  			if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied {
  1574  				// If we have an invvalid credential, there is no need to proceed: we'll fail 100%.
  1575  				break
  1576  			}
  1577  		}
  1578  	}
  1579  
  1580  	failedVolumes, err := e.adoptVolumes(controllerTag, ctx)
  1581  	if err != nil {
  1582  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1583  		return errors.Trace(err)
  1584  	}
  1585  	failed = append(failed, failedVolumes...)
  1586  
  1587  	err = e.firewaller.UpdateGroupController(ctx, controllerUUID)
  1588  	if err != nil {
  1589  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1590  		return errors.Trace(err)
  1591  	}
  1592  	if len(failed) != 0 {
  1593  		return errors.Errorf("error updating controller tag for some resources: %v", failed)
  1594  	}
  1595  	return nil
  1596  }
  1597  
  1598  func (e *Environ) adoptVolumes(controllerTag map[string]string, ctx context.ProviderCallContext) ([]string, error) {
  1599  	cinder, err := e.cinderProvider()
  1600  	if errors.IsNotSupported(err) {
  1601  		logger.Debugf("volumes not supported: not transferring ownership for volumes")
  1602  		return nil, nil
  1603  	}
  1604  	if err != nil {
  1605  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1606  		return nil, errors.Trace(err)
  1607  	}
  1608  	// TODO(axw): fix the storage API.
  1609  	storageConfig, err := storage.NewConfig("cinder", CinderProviderType, nil)
  1610  	if err != nil {
  1611  		return nil, errors.Trace(err)
  1612  	}
  1613  	volumeSource, err := cinder.VolumeSource(storageConfig)
  1614  	if err != nil {
  1615  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1616  		return nil, errors.Trace(err)
  1617  	}
  1618  	volumeIds, err := volumeSource.ListVolumes(ctx)
  1619  	if err != nil {
  1620  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1621  		return nil, errors.Trace(err)
  1622  	}
  1623  
  1624  	var failed []string
  1625  	for _, volumeId := range volumeIds {
  1626  		_, err := cinder.storageAdapter.SetVolumeMetadata(volumeId, controllerTag)
  1627  		if err != nil {
  1628  			logger.Errorf("error updating controller tag for volume %s: %v", volumeId, err)
  1629  			failed = append(failed, volumeId)
  1630  			if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied {
  1631  				// If we have an invvalid credential, there is no need to proceed: we'll fail 100%.
  1632  				break
  1633  			}
  1634  		}
  1635  	}
  1636  	return failed, nil
  1637  }
  1638  
  1639  // AllInstances returns all instances in this environment.
  1640  func (e *Environ) AllInstances(ctx context.ProviderCallContext) ([]instances.Instance, error) {
  1641  	tagFilter := tagValue{tags.JujuModel, e.ecfg().UUID()}
  1642  	instances, err := e.allInstances(ctx, tagFilter, e.ecfg().useFloatingIP())
  1643  	if err != nil {
  1644  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1645  		return instances, err
  1646  	}
  1647  	return instances, nil
  1648  }
  1649  
  1650  // allControllerManagedInstances returns all instances managed by this
  1651  // environment's controller, matching the optionally specified filter.
  1652  func (e *Environ) allControllerManagedInstances(ctx context.ProviderCallContext, controllerUUID string, updateFloatingIPAddresses bool) ([]instances.Instance, error) {
  1653  	tagFilter := tagValue{tags.JujuController, controllerUUID}
  1654  	instances, err := e.allInstances(ctx, tagFilter, updateFloatingIPAddresses)
  1655  	if err != nil {
  1656  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1657  		return instances, err
  1658  	}
  1659  	return instances, nil
  1660  }
  1661  
  1662  type tagValue struct {
  1663  	tag, value string
  1664  }
  1665  
  1666  // allControllerManagedInstances returns all instances managed by this
  1667  // environment's controller, matching the optionally specified filter.
  1668  func (e *Environ) allInstances(ctx context.ProviderCallContext, tagFilter tagValue, updateFloatingIPAddresses bool) ([]instances.Instance, error) {
  1669  	servers, err := e.nova().ListServersDetail(jujuMachineFilter())
  1670  	if err != nil {
  1671  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1672  		return nil, err
  1673  	}
  1674  	instsById := make(map[string]instances.Instance)
  1675  	for _, server := range servers {
  1676  		if server.Metadata[tagFilter.tag] != tagFilter.value {
  1677  			continue
  1678  		}
  1679  		if e.isAliveServer(server) {
  1680  			var s = server
  1681  			// TODO(wallyworld): lookup the flavor details to fill in the instance type data
  1682  			instsById[s.Id] = &openstackInstance{e: e, serverDetail: &s}
  1683  		}
  1684  	}
  1685  	if updateFloatingIPAddresses {
  1686  		if err := e.updateFloatingIPAddresses(ctx, instsById); err != nil {
  1687  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1688  			return nil, err
  1689  		}
  1690  	}
  1691  	insts := make([]instances.Instance, 0, len(instsById))
  1692  	for _, inst := range instsById {
  1693  		insts = append(insts, inst)
  1694  	}
  1695  	return insts, nil
  1696  }
  1697  
  1698  func (e *Environ) Destroy(ctx context.ProviderCallContext) error {
  1699  	err := common.Destroy(e, ctx)
  1700  	if err != nil {
  1701  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1702  		return errors.Trace(err)
  1703  	}
  1704  	// Delete all security groups remaining in the model.
  1705  	if err := e.firewaller.DeleteAllModelGroups(ctx); err != nil {
  1706  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1707  		return errors.Trace(err)
  1708  	}
  1709  	return nil
  1710  }
  1711  
  1712  // DestroyController implements the Environ interface.
  1713  func (e *Environ) DestroyController(ctx context.ProviderCallContext, controllerUUID string) error {
  1714  	if err := e.Destroy(ctx); err != nil {
  1715  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1716  		return errors.Annotate(err, "destroying controller model")
  1717  	}
  1718  	// In case any hosted environment hasn't been cleaned up yet,
  1719  	// we also attempt to delete their resources when the controller
  1720  	// environment is destroyed.
  1721  	if err := e.destroyControllerManagedEnvirons(ctx, controllerUUID); err != nil {
  1722  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1723  		return errors.Annotate(err, "destroying managed models")
  1724  	}
  1725  	if err := e.firewaller.DeleteAllControllerGroups(ctx, controllerUUID); err != nil {
  1726  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1727  		return errors.Trace(err)
  1728  	}
  1729  	return nil
  1730  }
  1731  
  1732  // destroyControllerManagedEnvirons destroys all environments managed by this
  1733  // models's controller.
  1734  func (e *Environ) destroyControllerManagedEnvirons(ctx context.ProviderCallContext, controllerUUID string) error {
  1735  	// Terminate all instances managed by the controller.
  1736  	insts, err := e.allControllerManagedInstances(ctx, controllerUUID, false)
  1737  	if err != nil {
  1738  		return errors.Annotate(err, "listing instances")
  1739  	}
  1740  	instIds := make([]instance.Id, len(insts))
  1741  	for i, inst := range insts {
  1742  		instIds[i] = inst.Id()
  1743  	}
  1744  	if err := e.terminateInstances(ctx, instIds); err != nil {
  1745  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1746  		return errors.Annotate(err, "terminating instances")
  1747  	}
  1748  
  1749  	// Delete all volumes managed by the controller.
  1750  	cinder, err := e.cinderProvider()
  1751  	if err == nil {
  1752  		volumes, err := controllerCinderVolumes(cinder.storageAdapter, controllerUUID)
  1753  		if err != nil {
  1754  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1755  			return errors.Annotate(err, "listing volumes")
  1756  		}
  1757  		volIds := volumeInfoToVolumeIds(cinderToJujuVolumeInfos(volumes))
  1758  		errs := foreachVolume(ctx, cinder.storageAdapter, volIds, destroyVolume)
  1759  		for i, err := range errs {
  1760  			if err == nil {
  1761  				continue
  1762  			}
  1763  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1764  			return errors.Annotatef(err, "destroying volume %q", volIds[i])
  1765  		}
  1766  	} else if !errors.IsNotSupported(err) {
  1767  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1768  		return errors.Trace(err)
  1769  	}
  1770  
  1771  	// Security groups for hosted models are destroyed by the
  1772  	// DeleteAllControllerGroups method call from Destroy().
  1773  	return nil
  1774  }
  1775  
  1776  func resourceName(namespace instance.Namespace, envName, resourceId string) string {
  1777  	return namespace.Value(envName + "-" + resourceId)
  1778  }
  1779  
  1780  // jujuMachineFilter returns a nova.Filter matching machines created by Juju.
  1781  // The machines are not filtered to any particular environment. To do that,
  1782  // instance tags must be compared.
  1783  func jujuMachineFilter() *nova.Filter {
  1784  	filter := nova.NewFilter()
  1785  	filter.Set(nova.FilterServer, "juju-.*")
  1786  	return filter
  1787  }
  1788  
  1789  // rulesToRuleInfo maps ingress rules to nova rules
  1790  func rulesToRuleInfo(groupId string, rules []network.IngressRule) []neutron.RuleInfoV2 {
  1791  	var result []neutron.RuleInfoV2
  1792  	for _, r := range rules {
  1793  		ruleInfo := neutron.RuleInfoV2{
  1794  			Direction:     "ingress",
  1795  			ParentGroupId: groupId,
  1796  			PortRangeMin:  r.FromPort,
  1797  			PortRangeMax:  r.ToPort,
  1798  			IPProtocol:    r.Protocol,
  1799  		}
  1800  		sourceCIDRs := r.SourceCIDRs
  1801  		if len(sourceCIDRs) == 0 {
  1802  			sourceCIDRs = []string{"0.0.0.0/0"}
  1803  		}
  1804  		for _, sr := range sourceCIDRs {
  1805  			ruleInfo.RemoteIPPrefix = sr
  1806  			result = append(result, ruleInfo)
  1807  		}
  1808  	}
  1809  	return result
  1810  }
  1811  
  1812  func (e *Environ) OpenPorts(ctx context.ProviderCallContext, rules []network.IngressRule) error {
  1813  	if err := e.firewaller.OpenPorts(ctx, rules); err != nil {
  1814  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1815  		return errors.Trace(err)
  1816  	}
  1817  	return nil
  1818  }
  1819  
  1820  func (e *Environ) ClosePorts(ctx context.ProviderCallContext, rules []network.IngressRule) error {
  1821  	if err := e.firewaller.ClosePorts(ctx, rules); err != nil {
  1822  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1823  		return errors.Trace(err)
  1824  	}
  1825  	return nil
  1826  }
  1827  
  1828  func (e *Environ) IngressRules(ctx context.ProviderCallContext) ([]network.IngressRule, error) {
  1829  	rules, err := e.firewaller.IngressRules(ctx)
  1830  	if err != nil {
  1831  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1832  		return rules, errors.Trace(err)
  1833  	}
  1834  	return rules, nil
  1835  }
  1836  
  1837  func (e *Environ) Provider() environs.EnvironProvider {
  1838  	return providerInstance
  1839  }
  1840  
  1841  func (e *Environ) terminateInstances(ctx context.ProviderCallContext, ids []instance.Id) error {
  1842  	if len(ids) == 0 {
  1843  		return nil
  1844  	}
  1845  	var firstErr error
  1846  	novaClient := e.nova()
  1847  	for _, id := range ids {
  1848  		err := novaClient.DeleteServer(string(id))
  1849  		if gooseerrors.IsNotFound(err) {
  1850  			err = nil
  1851  		}
  1852  		if err != nil && firstErr == nil {
  1853  			logger.Debugf("error terminating instance %q: %v", id, err)
  1854  			firstErr = err
  1855  			if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied {
  1856  				// We'll 100% fail all subsequent calls if we have an invalid credential.
  1857  				break
  1858  			}
  1859  		}
  1860  	}
  1861  	return firstErr
  1862  }
  1863  
  1864  // MetadataLookupParams returns parameters which are used to query simplestreams metadata.
  1865  func (e *Environ) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
  1866  	if region == "" {
  1867  		region = e.cloud.Region
  1868  	}
  1869  	cloudSpec, err := e.cloudSpec(region)
  1870  	if err != nil {
  1871  		return nil, err
  1872  	}
  1873  	return &simplestreams.MetadataLookupParams{
  1874  		Series:   config.PreferredSeries(e.ecfg()),
  1875  		Region:   cloudSpec.Region,
  1876  		Endpoint: cloudSpec.Endpoint,
  1877  	}, nil
  1878  }
  1879  
  1880  // Region is specified in the HasRegion interface.
  1881  func (e *Environ) Region() (simplestreams.CloudSpec, error) {
  1882  	return e.cloudSpec(e.cloud.Region)
  1883  }
  1884  
  1885  func (e *Environ) cloudSpec(region string) (simplestreams.CloudSpec, error) {
  1886  	return simplestreams.CloudSpec{
  1887  		Region:   region,
  1888  		Endpoint: e.cloud.Endpoint,
  1889  	}, nil
  1890  }
  1891  
  1892  // TagInstance implements environs.InstanceTagger.
  1893  func (e *Environ) TagInstance(ctx context.ProviderCallContext, id instance.Id, tags map[string]string) error {
  1894  	if err := e.nova().SetServerMetadata(string(id), tags); err != nil {
  1895  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1896  		return errors.Annotate(err, "setting server metadata")
  1897  	}
  1898  	return nil
  1899  }
  1900  
  1901  func (e *Environ) SetClock(clock clock.Clock) {
  1902  	e.clock = clock
  1903  }
  1904  
  1905  func validateCloudSpec(spec environs.CloudSpec) error {
  1906  	if err := spec.Validate(); err != nil {
  1907  		return errors.Trace(err)
  1908  	}
  1909  	if err := validateAuthURL(spec.Endpoint); err != nil {
  1910  		return errors.Annotate(err, "validating auth-url")
  1911  	}
  1912  	if spec.Credential == nil {
  1913  		return errors.NotValidf("missing credential")
  1914  	}
  1915  	switch authType := spec.Credential.AuthType(); authType {
  1916  	case cloud.UserPassAuthType:
  1917  	case cloud.AccessKeyAuthType:
  1918  	default:
  1919  		return errors.NotSupportedf("%q auth-type", authType)
  1920  	}
  1921  	return nil
  1922  }
  1923  
  1924  func validateAuthURL(authURL string) error {
  1925  	parts, err := url.Parse(authURL)
  1926  	if err != nil || parts.Host == "" || parts.Scheme == "" {
  1927  		return errors.NotValidf("auth-url %q", authURL)
  1928  	}
  1929  	return nil
  1930  }
  1931  
  1932  // Subnets is specified on environs.Networking.
  1933  func (e *Environ) Subnets(ctx context.ProviderCallContext, instId instance.Id, subnetIds []network.Id) ([]network.SubnetInfo, error) {
  1934  	subnets, err := e.networking.Subnets(instId, subnetIds)
  1935  	if err != nil {
  1936  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1937  		return subnets, errors.Trace(err)
  1938  	}
  1939  	return subnets, nil
  1940  }
  1941  
  1942  // NetworkInterfaces is specified on environs.Networking.
  1943  func (e *Environ) NetworkInterfaces(ctx context.ProviderCallContext, instId instance.Id) ([]network.InterfaceInfo, error) {
  1944  	infos, err := e.networking.NetworkInterfaces(instId)
  1945  	if err != nil {
  1946  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1947  		return infos, errors.Trace(err)
  1948  	}
  1949  	return infos, nil
  1950  }
  1951  
  1952  // SupportsSpaces is specified on environs.Networking.
  1953  func (e *Environ) SupportsSpaces(ctx context.ProviderCallContext) (bool, error) {
  1954  	return false, nil
  1955  }
  1956  
  1957  // SupportsSpaceDiscovery is specified on environs.Networking.
  1958  func (e *Environ) SupportsSpaceDiscovery(ctx context.ProviderCallContext) (bool, error) {
  1959  	return false, nil
  1960  }
  1961  
  1962  // Spaces is specified on environs.Networking.
  1963  func (e *Environ) Spaces(ctx context.ProviderCallContext) ([]network.SpaceInfo, error) {
  1964  	return nil, errors.NotSupportedf("spaces")
  1965  }
  1966  
  1967  // SupportsContainerAddresses is specified on environs.Networking.
  1968  func (e *Environ) SupportsContainerAddresses(ctx context.ProviderCallContext) (bool, error) {
  1969  	return false, errors.NotSupportedf("container address")
  1970  }
  1971  
  1972  // SuperSubnets is specified on environs.Networking
  1973  func (e *Environ) SuperSubnets(ctx context.ProviderCallContext) ([]string, error) {
  1974  	subnets, err := e.networking.Subnets("", nil)
  1975  	if err != nil {
  1976  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1977  		return nil, err
  1978  	}
  1979  	cidrs := make([]string, len(subnets))
  1980  	for i, subnet := range subnets {
  1981  		cidrs[i] = subnet.CIDR
  1982  	}
  1983  	return cidrs, nil
  1984  }
  1985  
  1986  // AllocateContainerAddresses is specified on environs.Networking.
  1987  func (e *Environ) AllocateContainerAddresses(ctx context.ProviderCallContext, hostInstanceID instance.Id, containerTag names.MachineTag, preparedInfo []network.InterfaceInfo) ([]network.InterfaceInfo, error) {
  1988  	return nil, errors.NotSupportedf("allocate container address")
  1989  }
  1990  
  1991  // ReleaseContainerAddresses is specified on environs.Networking.
  1992  func (e *Environ) ReleaseContainerAddresses(ctx context.ProviderCallContext, interfaces []network.ProviderInterfaceInfo) error {
  1993  	return errors.NotSupportedf("release container address")
  1994  }
  1995  
  1996  // ProviderSpaceInfo is specified on environs.NetworkingEnviron.
  1997  func (*Environ) ProviderSpaceInfo(ctx context.ProviderCallContext, space *network.SpaceInfo) (*environs.ProviderSpaceInfo, error) {
  1998  	return nil, errors.NotSupportedf("provider space info")
  1999  }
  2000  
  2001  // AreSpacesRoutable is specified on environs.NetworkingEnviron.
  2002  func (*Environ) AreSpacesRoutable(ctx context.ProviderCallContext, space1, space2 *environs.ProviderSpaceInfo) (bool, error) {
  2003  	return false, nil
  2004  }
  2005  
  2006  // SSHAddresses is specified on environs.SSHAddresses.
  2007  func (*Environ) SSHAddresses(ctx context.ProviderCallContext, addresses []network.Address) ([]network.Address, error) {
  2008  	return addresses, nil
  2009  }