github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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  	stdcontext "context"
    10  	"crypto/tls"
    11  	"crypto/x509"
    12  	"fmt"
    13  	"math/rand"
    14  	"net/url"
    15  	"path"
    16  	"sort"
    17  	"strconv"
    18  	"strings"
    19  	"sync"
    20  	"time"
    21  
    22  	"github.com/go-goose/goose/v5/cinder"
    23  	"github.com/go-goose/goose/v5/client"
    24  	gooseerrors "github.com/go-goose/goose/v5/errors"
    25  	"github.com/go-goose/goose/v5/identity"
    26  	"github.com/go-goose/goose/v5/neutron"
    27  	"github.com/go-goose/goose/v5/nova"
    28  	"github.com/juju/clock"
    29  	"github.com/juju/collections/set"
    30  	"github.com/juju/collections/transform"
    31  	"github.com/juju/errors"
    32  	"github.com/juju/http/v2"
    33  	"github.com/juju/jsonschema"
    34  	"github.com/juju/loggo"
    35  	"github.com/juju/names/v5"
    36  	"github.com/juju/retry"
    37  	"github.com/juju/utils/v3"
    38  	"github.com/juju/version/v2"
    39  
    40  	"github.com/juju/juju/cloud"
    41  	"github.com/juju/juju/cloudconfig/cloudinit"
    42  	"github.com/juju/juju/cloudconfig/instancecfg"
    43  	"github.com/juju/juju/cloudconfig/providerinit"
    44  	"github.com/juju/juju/cmd/juju/interact"
    45  	"github.com/juju/juju/core/constraints"
    46  	"github.com/juju/juju/core/instance"
    47  	"github.com/juju/juju/core/network"
    48  	"github.com/juju/juju/core/network/firewall"
    49  	"github.com/juju/juju/core/status"
    50  	"github.com/juju/juju/environs"
    51  	environscloudspec "github.com/juju/juju/environs/cloudspec"
    52  	"github.com/juju/juju/environs/config"
    53  	"github.com/juju/juju/environs/context"
    54  	"github.com/juju/juju/environs/imagemetadata"
    55  	"github.com/juju/juju/environs/instances"
    56  	"github.com/juju/juju/environs/simplestreams"
    57  	"github.com/juju/juju/environs/tags"
    58  	"github.com/juju/juju/provider/common"
    59  	"github.com/juju/juju/storage"
    60  )
    61  
    62  var logger = loggo.GetLogger("juju.provider.openstack")
    63  
    64  type EnvironProvider struct {
    65  	environs.ProviderCredentials
    66  	Configurator      ProviderConfigurator
    67  	FirewallerFactory FirewallerFactory
    68  	FlavorFilter      FlavorFilter
    69  
    70  	// ClientFromEndpoint returns an Openstack client for the given endpoint.
    71  	ClientFromEndpoint func(endpoint string) client.AuthenticatingClient
    72  }
    73  
    74  var (
    75  	_ environs.CloudEnvironProvider = (*EnvironProvider)(nil)
    76  	_ environs.ProviderSchema       = (*EnvironProvider)(nil)
    77  )
    78  
    79  var providerInstance = &EnvironProvider{
    80  	ProviderCredentials: OpenstackCredentials{},
    81  	Configurator:        &defaultConfigurator{},
    82  	FirewallerFactory:   &firewallerFactory{},
    83  	FlavorFilter:        FlavorFilterFunc(AcceptAllFlavors),
    84  	ClientFromEndpoint:  newGooseClient,
    85  }
    86  
    87  var cloudSchema = &jsonschema.Schema{
    88  	Type:     []jsonschema.Type{jsonschema.ObjectType},
    89  	Required: []string{cloud.EndpointKey, cloud.AuthTypesKey, cloud.RegionsKey},
    90  	Order:    []string{cloud.EndpointKey, cloud.CertFilenameKey, cloud.AuthTypesKey, cloud.RegionsKey},
    91  	Properties: map[string]*jsonschema.Schema{
    92  		cloud.EndpointKey: {
    93  			Singular: "the API endpoint url for the cloud",
    94  			Type:     []jsonschema.Type{jsonschema.StringType},
    95  			Format:   jsonschema.FormatURI,
    96  			Default:  "",
    97  			EnvVars:  []string{"OS_AUTH_URL"},
    98  		},
    99  		cloud.CertFilenameKey: {
   100  			Singular:      "a path to the CA certificate for your cloud if one is required to access it. (optional)",
   101  			Type:          []jsonschema.Type{jsonschema.StringType},
   102  			Format:        interact.FormatCertFilename,
   103  			Default:       "",
   104  			PromptDefault: "none",
   105  			EnvVars:       []string{"OS_CACERT"},
   106  		},
   107  		cloud.AuthTypesKey: {
   108  			Singular:    "auth type",
   109  			Plural:      "auth types",
   110  			Type:        []jsonschema.Type{jsonschema.ArrayType},
   111  			UniqueItems: jsonschema.Bool(true),
   112  			Items: &jsonschema.ItemSpec{
   113  				Schemas: []*jsonschema.Schema{{
   114  					Type: []jsonschema.Type{jsonschema.StringType},
   115  					Enum: []interface{}{
   116  						string(cloud.AccessKeyAuthType),
   117  						string(cloud.UserPassAuthType),
   118  					},
   119  				}},
   120  			},
   121  		},
   122  		cloud.RegionsKey: {
   123  			Type:     []jsonschema.Type{jsonschema.ObjectType},
   124  			Singular: "region",
   125  			Plural:   "regions",
   126  			Default:  "",
   127  			EnvVars:  []string{"OS_REGION_NAME"},
   128  			AdditionalProperties: &jsonschema.Schema{
   129  				Type:          []jsonschema.Type{jsonschema.ObjectType},
   130  				Required:      []string{cloud.EndpointKey},
   131  				MaxProperties: jsonschema.Int(1),
   132  				Properties: map[string]*jsonschema.Schema{
   133  					cloud.EndpointKey: {
   134  						Singular:      "the API endpoint url for the region",
   135  						Type:          []jsonschema.Type{jsonschema.StringType},
   136  						Format:        jsonschema.FormatURI,
   137  						Default:       "",
   138  						PromptDefault: "use cloud api url",
   139  					},
   140  				},
   141  			},
   142  		},
   143  	},
   144  }
   145  
   146  var makeServiceURL = client.AuthenticatingClient.MakeServiceURL
   147  
   148  // TODO: shortAttempt was kept to a long timeout because Nova needs
   149  // more time than EC2.  Storage delays are handled separately now, and
   150  // perhaps other polling attempts can time out faster.
   151  
   152  // shortAttempt is used when polling for short-term events in tests.
   153  var shortAttempt = utils.AttemptStrategy{
   154  	Total: 15 * time.Second,
   155  	Delay: 200 * time.Millisecond,
   156  }
   157  
   158  // Version is part of the EnvironProvider interface.
   159  func (EnvironProvider) Version() int {
   160  	return 0
   161  }
   162  
   163  func (p EnvironProvider) Open(ctx stdcontext.Context, args environs.OpenParams) (environs.Environ, error) {
   164  	logger.Infof("opening model %q", args.Config.Name())
   165  	uuid := args.Config.UUID()
   166  	namespace, err := instance.NewNamespace(uuid)
   167  	if err != nil {
   168  		return nil, errors.Annotate(err, "creating instance namespace")
   169  	}
   170  
   171  	e := &Environ{
   172  		name:         args.Config.Name(),
   173  		uuid:         uuid,
   174  		namespace:    namespace,
   175  		clock:        clock.WallClock,
   176  		configurator: p.Configurator,
   177  		flavorFilter: p.FlavorFilter,
   178  	}
   179  
   180  	if err := e.SetConfig(args.Config); err != nil {
   181  		return nil, errors.Trace(err)
   182  	}
   183  	if err := e.SetCloudSpec(ctx, args.Cloud); err != nil {
   184  		return nil, errors.Trace(err)
   185  	}
   186  
   187  	e.networking, e.firewaller, err = p.getEnvironNetworkingFirewaller(e)
   188  	if err != nil {
   189  		return nil, errors.Trace(err)
   190  	}
   191  
   192  	return e, nil
   193  }
   194  
   195  // getEnvironNetworkingFirewaller returns Networking and Firewaller for the
   196  // new Environ.  Both require Neutron to be support by the OpenStack cloud,
   197  // so create together.
   198  func (p EnvironProvider) getEnvironNetworkingFirewaller(e *Environ) (Networking, Firewaller, error) {
   199  	// TODO (hml) 2019-12-05
   200  	// We want to ensure a failure if an old nova networking OpenStack is
   201  	// added as a new model to a multi-cloud controller.  However the
   202  	// current OpenStack testservice does not implement EndpointsForRegions(),
   203  	// thus causing failures and panics in the setup of the majority of
   204  	// provider unit tests.  Or a rewrite of code and/or tests.
   205  	// See LP:1855343
   206  	if err := authenticateClient(e.client()); err != nil {
   207  		return nil, nil, errors.Trace(err)
   208  	}
   209  	if !e.supportsNeutron() {
   210  		// This should turn into a failure, left as an Error message for now to help
   211  		// provide context for failing networking calls by this environ.  Previously
   212  		// this was covered by switchingNetworking{} and switchingFirewaller{}.
   213  		logger.Errorf("Using unsupported OpenStack APIs. Neutron networking " +
   214  			"is not supported by this OpenStack cloud.\n  Please use OpenStack Queens or " +
   215  			"newer to maintain compatibility.")
   216  	}
   217  	networking := newNetworking(e)
   218  	return networking, p.FirewallerFactory.GetFirewaller(e), nil
   219  }
   220  
   221  // DetectRegions implements environs.CloudRegionDetector.
   222  func (EnvironProvider) DetectRegions() ([]cloud.Region, error) {
   223  	// If OS_REGION_NAME and OS_AUTH_URL are both set,
   224  	// return a region using them.
   225  	creds, err := identity.CredentialsFromEnv()
   226  	if err != nil {
   227  		return nil, errors.Errorf("failed to retrieve credential from env : %v", err)
   228  	}
   229  	if creds.Region == "" {
   230  		return nil, errors.NewNotFound(nil, "OS_REGION_NAME environment variable not set")
   231  	}
   232  	if creds.URL == "" {
   233  		return nil, errors.NewNotFound(nil, "OS_AUTH_URL environment variable not set")
   234  	}
   235  	return []cloud.Region{{
   236  		Name:     creds.Region,
   237  		Endpoint: creds.URL,
   238  	}}, nil
   239  }
   240  
   241  // CloudSchema returns the schema for adding new clouds of this type.
   242  func (p EnvironProvider) CloudSchema() *jsonschema.Schema {
   243  	return cloudSchema
   244  }
   245  
   246  // Ping tests the connection to the cloud, to verify the endpoint is valid.
   247  func (p EnvironProvider) Ping(ctx context.ProviderCallContext, endpoint string) error {
   248  	c := p.ClientFromEndpoint(endpoint)
   249  	if _, err := c.IdentityAuthOptions(); err != nil {
   250  		handleCredentialError(err, ctx)
   251  		return errors.Annotatef(err, "No Openstack server running at %s", endpoint)
   252  	}
   253  	return nil
   254  }
   255  
   256  // newGooseClient is the default function in EnvironProvider.ClientFromEndpoint.
   257  func newGooseClient(endpoint string) client.AuthenticatingClient {
   258  	// Use NonValidatingClient, in case the endpoint is behind a cert
   259  	return client.NewNonValidatingClient(&identity.Credentials{URL: endpoint}, 0, nil)
   260  }
   261  
   262  // PrepareConfig is specified in the EnvironProvider interface.
   263  func (p EnvironProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
   264  	if err := validateCloudSpec(args.Cloud); err != nil {
   265  		return nil, errors.Annotate(err, "validating cloud spec")
   266  	}
   267  
   268  	// Set the default block-storage source.
   269  	attrs := make(map[string]interface{})
   270  	if _, ok := args.Config.StorageDefaultBlockSource(); !ok {
   271  		attrs[config.StorageDefaultBlockSourceKey] = CinderProviderType
   272  	}
   273  
   274  	cfg, err := args.Config.Apply(attrs)
   275  	if err != nil {
   276  		return nil, errors.Trace(err)
   277  	}
   278  	return cfg, nil
   279  }
   280  
   281  // AgentMetadataLookupParams returns parameters which are used to query agent metadata to
   282  // find matching image information.
   283  func (p EnvironProvider) AgentMetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
   284  	return p.metadataLookupParams(region)
   285  }
   286  
   287  // ImageMetadataLookupParams returns parameters which are used to query image metadata to
   288  // find matching image information.
   289  func (p EnvironProvider) ImageMetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
   290  	return p.metadataLookupParams(region)
   291  }
   292  
   293  func (p EnvironProvider) metadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
   294  	if region == "" {
   295  		return nil, errors.Errorf("region must be specified")
   296  	}
   297  	return &simplestreams.MetadataLookupParams{
   298  		Region: region,
   299  	}, nil
   300  }
   301  
   302  func (p EnvironProvider) newConfig(cfg *config.Config) (*environConfig, error) {
   303  	valid, err := p.Validate(cfg, nil)
   304  	if err != nil {
   305  		return nil, err
   306  	}
   307  	return &environConfig{valid, valid.UnknownAttrs()}, nil
   308  }
   309  
   310  type Environ struct {
   311  	environs.NoSpaceDiscoveryEnviron
   312  
   313  	name      string
   314  	uuid      string
   315  	namespace instance.Namespace
   316  
   317  	ecfgMutex       sync.Mutex
   318  	ecfgUnlocked    *environConfig
   319  	cloudUnlocked   environscloudspec.CloudSpec
   320  	clientUnlocked  client.AuthenticatingClient
   321  	novaUnlocked    *nova.Client
   322  	neutronUnlocked *neutron.Client
   323  	volumeURL       *url.URL
   324  
   325  	// keystoneImageDataSource caches the result of getKeystoneImageSource.
   326  	keystoneImageDataSourceMutex sync.Mutex
   327  	keystoneImageDataSource      simplestreams.DataSource
   328  
   329  	// keystoneToolsDataSource caches the result of getKeystoneToolsSource.
   330  	keystoneToolsDataSourceMutex sync.Mutex
   331  	keystoneToolsDataSource      simplestreams.DataSource
   332  
   333  	// usingSecurityGroups tracks whether this model is using security groups
   334  	// for firewalling. This will be false if a network has port_security disabled,
   335  	// true otherwise.
   336  	// However, once a model security group is created, it is not removed if such a model
   337  	// is added, this option sticks to true
   338  	usingSecurityGroups bool
   339  
   340  	firewaller   Firewaller
   341  	networking   Networking
   342  	configurator ProviderConfigurator
   343  	flavorFilter FlavorFilter
   344  
   345  	// Clock is defined so it can be replaced for testing
   346  	clock clock.Clock
   347  
   348  	publicIPMutex sync.Mutex
   349  }
   350  
   351  var _ environs.Environ = (*Environ)(nil)
   352  var _ environs.NetworkingEnviron = (*Environ)(nil)
   353  var _ simplestreams.HasRegion = (*Environ)(nil)
   354  var _ context.Distributor = (*Environ)(nil)
   355  var _ environs.InstanceTagger = (*Environ)(nil)
   356  
   357  type openstackInstance struct {
   358  	e        *Environ
   359  	instType *instances.InstanceType
   360  	arch     *string
   361  
   362  	mu           sync.Mutex
   363  	serverDetail *nova.ServerDetail
   364  	// floatingIP is non-nil iff use-floating-ip is true.
   365  	floatingIP *string
   366  
   367  	// runOpts is only set in the response from StartInstance.
   368  	runOpts *nova.RunServerOpts
   369  }
   370  
   371  // NovaInstanceStartedWithOpts exposes run options used to start an instance.
   372  // Used by unit testing.
   373  func (inst *openstackInstance) NovaInstanceStartedWithOpts() *nova.RunServerOpts {
   374  	return inst.runOpts
   375  }
   376  
   377  func (inst *openstackInstance) String() string {
   378  	return string(inst.Id())
   379  }
   380  
   381  var _ instances.Instance = (*openstackInstance)(nil)
   382  
   383  func (inst *openstackInstance) Refresh(ctx context.ProviderCallContext) error {
   384  	inst.mu.Lock()
   385  	defer inst.mu.Unlock()
   386  	server, err := inst.e.nova().GetServer(inst.serverDetail.Id)
   387  	if err != nil {
   388  		handleCredentialError(err, ctx)
   389  		return err
   390  	}
   391  	inst.serverDetail = server
   392  	return nil
   393  }
   394  
   395  func (inst *openstackInstance) getServerDetail() *nova.ServerDetail {
   396  	inst.mu.Lock()
   397  	defer inst.mu.Unlock()
   398  	return inst.serverDetail
   399  }
   400  
   401  func (inst *openstackInstance) Id() instance.Id {
   402  	return instance.Id(inst.getServerDetail().Id)
   403  }
   404  
   405  func (inst *openstackInstance) Status(ctx context.ProviderCallContext) instance.Status {
   406  	instStatus := inst.getServerDetail().Status
   407  	var jujuStatus status.Status
   408  	switch instStatus {
   409  	case nova.StatusActive:
   410  		jujuStatus = status.Running
   411  	case nova.StatusError:
   412  		jujuStatus = status.ProvisioningError
   413  	case nova.StatusBuild, nova.StatusBuildSpawning,
   414  		nova.StatusDeleted, nova.StatusHardReboot,
   415  		nova.StatusPassword, nova.StatusReboot,
   416  		nova.StatusRebuild, nova.StatusRescue,
   417  		nova.StatusResize, nova.StatusShutoff,
   418  		nova.StatusSuspended, nova.StatusVerifyResize:
   419  		jujuStatus = status.Empty
   420  	case nova.StatusUnknown:
   421  		jujuStatus = status.Unknown
   422  	default:
   423  		jujuStatus = status.Empty
   424  	}
   425  	return instance.Status{
   426  		Status:  jujuStatus,
   427  		Message: instStatus,
   428  	}
   429  }
   430  
   431  func (inst *openstackInstance) hardwareCharacteristics() *instance.HardwareCharacteristics {
   432  	hc := &instance.HardwareCharacteristics{Arch: inst.arch}
   433  	if inst.instType != nil {
   434  		hc.Mem = &inst.instType.Mem
   435  		// openstack is special in that a 0-size root disk means that
   436  		// the root disk will result in an instance with a root disk
   437  		// the same size as the image that created it, so we just set
   438  		// the HardwareCharacteristics to nil to signal that we don't
   439  		// know what the correct size is.
   440  		if inst.instType.RootDisk == 0 {
   441  			hc.RootDisk = nil
   442  		} else {
   443  			hc.RootDisk = &inst.instType.RootDisk
   444  		}
   445  		hc.CpuCores = &inst.instType.CpuCores
   446  		hc.CpuPower = inst.instType.CpuPower
   447  		// tags not currently supported on openstack
   448  	}
   449  	hc.AvailabilityZone = &inst.serverDetail.AvailabilityZone
   450  	// If the instance was started with a volume block device mapping, select the first
   451  	// boot disk as the reported RootDisk size.
   452  	if inst.runOpts != nil {
   453  		for _, blockDevice := range inst.runOpts.BlockDeviceMappings {
   454  			if blockDevice.BootIndex == 0 &&
   455  				blockDevice.DestinationType == rootDiskSourceVolume {
   456  				rootDiskSize := uint64(blockDevice.VolumeSize * 1024)
   457  				hc.RootDisk = &rootDiskSize
   458  				break
   459  			}
   460  		}
   461  	}
   462  	return hc
   463  }
   464  
   465  // getAddresses returns the existing server information on addresses,
   466  // but fetches the details over the api again if no addresses exist.
   467  func (inst *openstackInstance) getAddresses(ctx context.ProviderCallContext) (map[string][]nova.IPAddress, error) {
   468  	addrs := inst.getServerDetail().Addresses
   469  	if len(addrs) == 0 {
   470  		server, err := inst.e.nova().GetServer(string(inst.Id()))
   471  		if err != nil {
   472  			handleCredentialError(err, ctx)
   473  			return nil, err
   474  		}
   475  		addrs = server.Addresses
   476  	}
   477  	return addrs, nil
   478  }
   479  
   480  // Addresses implements network.Addresses() returning generic address
   481  // details for the instances, and calling the openstack api if needed.
   482  func (inst *openstackInstance) Addresses(ctx context.ProviderCallContext) (network.ProviderAddresses, error) {
   483  	addresses, err := inst.getAddresses(ctx)
   484  	if err != nil {
   485  		return nil, err
   486  	}
   487  	var floatingIP string
   488  	if inst.floatingIP != nil {
   489  		floatingIP = *inst.floatingIP
   490  		logger.Debugf("instance %v has floating IP address: %v", inst.Id(), floatingIP)
   491  	}
   492  	return convertNovaAddresses(floatingIP, addresses), nil
   493  }
   494  
   495  // convertNovaAddresses returns nova addresses in generic format
   496  func convertNovaAddresses(publicIP string, addresses map[string][]nova.IPAddress) network.ProviderAddresses {
   497  	var machineAddresses []network.ProviderAddress
   498  	if publicIP != "" {
   499  		publicAddr := network.NewMachineAddress(publicIP, network.WithScope(network.ScopePublic)).AsProviderAddress()
   500  		machineAddresses = append(machineAddresses, publicAddr)
   501  	}
   502  	// TODO(gz) Network ordering may be significant but is not preserved by
   503  	// the map, see lp:1188126 for example. That could potentially be fixed
   504  	// in goose, or left to be derived by other means.
   505  	for netName, ips := range addresses {
   506  		networkScope := network.ScopeUnknown
   507  		if netName == "public" {
   508  			networkScope = network.ScopePublic
   509  		}
   510  		for _, address := range ips {
   511  			// If this address has already been added as a floating IP, skip it.
   512  			if publicIP == address.Address {
   513  				continue
   514  			}
   515  			// Assume IPv4 unless specified otherwise
   516  			addrType := network.IPv4Address
   517  			if address.Version == 6 {
   518  				addrType = network.IPv6Address
   519  			}
   520  			machineAddr := network.NewMachineAddress(address.Address, network.WithScope(networkScope)).AsProviderAddress()
   521  			if machineAddr.Type != addrType {
   522  				logger.Warningf("derived address type %v, nova reports %v", machineAddr.Type, addrType)
   523  			}
   524  			machineAddresses = append(machineAddresses, machineAddr)
   525  		}
   526  	}
   527  	return machineAddresses
   528  }
   529  
   530  func (inst *openstackInstance) OpenPorts(ctx context.ProviderCallContext, machineId string, rules firewall.IngressRules) error {
   531  	return inst.e.firewaller.OpenInstancePorts(ctx, inst, machineId, rules)
   532  }
   533  
   534  func (inst *openstackInstance) ClosePorts(ctx context.ProviderCallContext, machineId string, rules firewall.IngressRules) error {
   535  	return inst.e.firewaller.CloseInstancePorts(ctx, inst, machineId, rules)
   536  }
   537  
   538  func (inst *openstackInstance) IngressRules(ctx context.ProviderCallContext, machineId string) (firewall.IngressRules, error) {
   539  	return inst.e.firewaller.InstanceIngressRules(ctx, inst, machineId)
   540  }
   541  
   542  func (e *Environ) ecfg() *environConfig {
   543  	e.ecfgMutex.Lock()
   544  	ecfg := e.ecfgUnlocked
   545  	e.ecfgMutex.Unlock()
   546  	return ecfg
   547  }
   548  
   549  func (e *Environ) cloud() environscloudspec.CloudSpec {
   550  	e.ecfgMutex.Lock()
   551  	cloud := e.cloudUnlocked
   552  	e.ecfgMutex.Unlock()
   553  	return cloud
   554  }
   555  
   556  func (e *Environ) client() client.AuthenticatingClient {
   557  	e.ecfgMutex.Lock()
   558  	client := e.clientUnlocked
   559  	e.ecfgMutex.Unlock()
   560  	return client
   561  }
   562  
   563  func (e *Environ) nova() *nova.Client {
   564  	e.ecfgMutex.Lock()
   565  	nova := e.novaUnlocked
   566  	e.ecfgMutex.Unlock()
   567  	return nova
   568  }
   569  
   570  func (e *Environ) neutron() *neutron.Client {
   571  	e.ecfgMutex.Lock()
   572  	neutron := e.neutronUnlocked
   573  	e.ecfgMutex.Unlock()
   574  	return neutron
   575  }
   576  
   577  var unsupportedConstraints = []string{
   578  	constraints.Tags,
   579  	constraints.CpuPower,
   580  }
   581  
   582  // ConstraintsValidator is defined on the Environs interface.
   583  func (e *Environ) ConstraintsValidator(ctx context.ProviderCallContext) (constraints.Validator, error) {
   584  	validator := constraints.NewValidator()
   585  	validator.RegisterConflicts(
   586  		[]string{constraints.InstanceType},
   587  		// TODO: move to a dynamic conflict for arch when openstack supports defining arch in flavors
   588  		[]string{constraints.Mem, constraints.Cores})
   589  	// NOTE: RootDiskSource and RootDisk constraints are validated in PrecheckInstance.
   590  	validator.RegisterUnsupported(unsupportedConstraints)
   591  	novaClient := e.nova()
   592  	flavors, err := novaClient.ListFlavorsDetail()
   593  	if err != nil {
   594  		handleCredentialError(err, ctx)
   595  		return nil, err
   596  	}
   597  	instTypeNames := make([]string, len(flavors))
   598  	for i, flavor := range flavors {
   599  		instTypeNames[i] = flavor.Name
   600  	}
   601  	sort.Strings(instTypeNames)
   602  	validator.RegisterVocabulary(constraints.InstanceType, instTypeNames)
   603  	validator.RegisterVocabulary(constraints.VirtType, []string{"kvm", "lxd"})
   604  	validator.RegisterVocabulary(constraints.RootDiskSource, []string{rootDiskSourceVolume, rootDiskSourceLocal})
   605  	return validator, nil
   606  }
   607  
   608  var novaListAvailabilityZones = (*nova.Client).ListAvailabilityZones
   609  
   610  type openstackAvailabilityZone struct {
   611  	nova.AvailabilityZone
   612  }
   613  
   614  func (z *openstackAvailabilityZone) Name() string {
   615  	return z.AvailabilityZone.Name
   616  }
   617  
   618  func (z *openstackAvailabilityZone) Available() bool {
   619  	return z.AvailabilityZone.State.Available
   620  }
   621  
   622  // AvailabilityZones returns a slice of availability zones.
   623  func (e *Environ) AvailabilityZones(ctx context.ProviderCallContext) (network.AvailabilityZones, error) {
   624  	zones, err := novaListAvailabilityZones(e.nova())
   625  	if gooseerrors.IsNotImplemented(err) {
   626  		return nil, errors.NotImplementedf("availability zones")
   627  	}
   628  	if err != nil {
   629  		handleCredentialError(err, ctx)
   630  		return nil, err
   631  	}
   632  
   633  	availabilityZones := make(network.AvailabilityZones, len(zones))
   634  	for i, z := range zones {
   635  		availabilityZones[i] = &openstackAvailabilityZone{z}
   636  	}
   637  	return availabilityZones, nil
   638  }
   639  
   640  // InstanceAvailabilityZoneNames returns the availability zone names for each
   641  // of the specified instances.
   642  func (e *Environ) InstanceAvailabilityZoneNames(ctx context.ProviderCallContext, ids []instance.Id) (map[instance.Id]string, error) {
   643  	instances, err := e.Instances(ctx, ids)
   644  	if err != nil && err != environs.ErrPartialInstances {
   645  		handleCredentialError(err, ctx)
   646  		return nil, errors.Trace(err)
   647  	}
   648  	zones := make(map[instance.Id]string)
   649  	for _, inst := range instances {
   650  		if inst == nil {
   651  			continue
   652  		}
   653  		oInst, ok := inst.(*openstackInstance)
   654  		if !ok {
   655  			continue
   656  		}
   657  		zones[inst.Id()] = oInst.serverDetail.AvailabilityZone
   658  	}
   659  	return zones, nil
   660  }
   661  
   662  type openstackPlacement struct {
   663  	zoneName string
   664  }
   665  
   666  // DeriveAvailabilityZones is part of the common.ZonedEnviron interface.
   667  func (e *Environ) DeriveAvailabilityZones(ctx context.ProviderCallContext, args environs.StartInstanceParams) ([]string, error) {
   668  	availabilityZone, err := e.deriveAvailabilityZone(ctx, args.Placement, args.VolumeAttachments)
   669  	if err != nil && !errors.IsNotImplemented(err) {
   670  		handleCredentialError(err, ctx)
   671  		return nil, errors.Trace(err)
   672  	}
   673  	if availabilityZone != "" {
   674  		return []string{availabilityZone}, nil
   675  	}
   676  	return nil, nil
   677  }
   678  
   679  func (e *Environ) parsePlacement(ctx context.ProviderCallContext, placement string) (*openstackPlacement, error) {
   680  	pos := strings.IndexRune(placement, '=')
   681  	if pos == -1 {
   682  		return nil, errors.Errorf("unknown placement directive: %v", placement)
   683  	}
   684  	switch key, value := placement[:pos], placement[pos+1:]; key {
   685  	case "zone":
   686  		zones, err := e.AvailabilityZones(ctx)
   687  		if err != nil {
   688  			handleCredentialError(err, ctx)
   689  			return nil, errors.Trace(err)
   690  		}
   691  		if err := zones.Validate(value); err != nil {
   692  			return nil, errors.Trace(err)
   693  		}
   694  
   695  		return &openstackPlacement{zoneName: value}, nil
   696  	}
   697  	return nil, errors.Errorf("unknown placement directive: %v", placement)
   698  }
   699  
   700  // PrecheckInstance is defined on the environs.InstancePrechecker interface.
   701  func (e *Environ) PrecheckInstance(ctx context.ProviderCallContext, args environs.PrecheckInstanceParams) error {
   702  	if _, err := e.deriveAvailabilityZone(ctx, args.Placement, args.VolumeAttachments); err != nil {
   703  		return errors.Trace(err)
   704  	}
   705  	usingVolumeRootDisk := false
   706  	if args.Constraints.HasRootDiskSource() && args.Constraints.HasRootDisk() &&
   707  		*args.Constraints.RootDiskSource == rootDiskSourceVolume {
   708  		usingVolumeRootDisk = true
   709  	}
   710  	if args.Constraints.HasRootDisk() && args.Constraints.HasInstanceType() && !usingVolumeRootDisk {
   711  		return errors.Errorf("constraint %s cannot be specified with %s unless constraint %s=%s",
   712  			constraints.RootDisk, constraints.InstanceType,
   713  			constraints.RootDiskSource, rootDiskSourceVolume)
   714  	}
   715  	if args.Constraints.HasInstanceType() {
   716  		// Constraint has an instance-type constraint so let's see if it is valid.
   717  		novaClient := e.nova()
   718  		flavors, err := novaClient.ListFlavorsDetail()
   719  		if err != nil {
   720  			handleCredentialError(err, ctx)
   721  			return err
   722  		}
   723  		flavorFound := false
   724  		for _, flavor := range flavors {
   725  			if flavor.Name == *args.Constraints.InstanceType {
   726  				flavorFound = true
   727  				break
   728  			}
   729  		}
   730  		if !flavorFound {
   731  			return errors.Errorf("invalid Openstack flavour %q specified", *args.Constraints.InstanceType)
   732  		}
   733  	}
   734  
   735  	return nil
   736  }
   737  
   738  // PrepareForBootstrap is part of the Environ interface.
   739  func (e *Environ) PrepareForBootstrap(_ environs.BootstrapContext, _ string) error {
   740  	// Verify credentials.
   741  	if err := authenticateClient(e.client()); err != nil {
   742  		return err
   743  	}
   744  	if !e.supportsNeutron() {
   745  		logger.Errorf(`Using unsupported OpenStack APIs.
   746  
   747    Neutron networking is not supported by this OpenStack cloud.
   748  
   749    Please use OpenStack Queens or newer to maintain compatibility.
   750  
   751  `,
   752  		)
   753  		return errors.NewNotFound(nil, "OpenStack Neutron service")
   754  	}
   755  	return nil
   756  }
   757  
   758  // Create is part of the Environ interface.
   759  func (e *Environ) Create(ctx context.ProviderCallContext, args environs.CreateParams) error {
   760  	// Verify credentials.
   761  	if err := authenticateClient(e.client()); err != nil {
   762  		handleCredentialError(err, ctx)
   763  		return err
   764  	}
   765  	// TODO(axw) 2016-08-04 #1609643
   766  	// Create global security group(s) here.
   767  	return nil
   768  }
   769  
   770  func (e *Environ) Bootstrap(ctx environs.BootstrapContext, callCtx context.ProviderCallContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) {
   771  	// The client's authentication may have been reset when finding tools if the agent-version
   772  	// attribute was updated so we need to re-authenticate. This will be a no-op if already authenticated.
   773  	// An authenticated client is needed for the URL() call below.
   774  	if err := authenticateClient(e.client()); err != nil {
   775  		handleCredentialError(err, callCtx)
   776  		return nil, err
   777  	}
   778  	result, err := common.Bootstrap(ctx, e, callCtx, args)
   779  	if err != nil {
   780  		handleCredentialError(err, callCtx)
   781  		return nil, err
   782  	}
   783  	return result, nil
   784  }
   785  
   786  func (e *Environ) supportsNeutron() bool {
   787  	client := e.client()
   788  	endpointMap := client.EndpointsForRegion(e.cloud().Region)
   789  	_, ok := endpointMap["network"]
   790  	return ok
   791  }
   792  
   793  func (e *Environ) ControllerInstances(ctx context.ProviderCallContext, controllerUUID string) ([]instance.Id, error) {
   794  	// Find all instances tagged with tags.JujuIsController.
   795  	instances, err := e.allControllerManagedInstances(ctx, controllerUUID)
   796  	if err != nil {
   797  		return nil, errors.Trace(err)
   798  	}
   799  	ids := make([]instance.Id, 0, 1)
   800  	for _, instance := range instances {
   801  		detail := instance.(*openstackInstance).getServerDetail()
   802  		if detail.Metadata[tags.JujuIsController] == "true" {
   803  			ids = append(ids, instance.Id())
   804  		}
   805  	}
   806  	if len(ids) == 0 {
   807  		return nil, environs.ErrNoInstances
   808  	}
   809  	return ids, nil
   810  }
   811  
   812  func (e *Environ) Config() *config.Config {
   813  	return e.ecfg().Config
   814  }
   815  
   816  func newCredentials(spec environscloudspec.CloudSpec) (identity.Credentials, identity.AuthMode, error) {
   817  	credAttrs := spec.Credential.Attributes()
   818  	cred := identity.Credentials{
   819  		Region:     spec.Region,
   820  		URL:        spec.Endpoint,
   821  		TenantName: credAttrs[CredAttrTenantName],
   822  		TenantID:   credAttrs[CredAttrTenantID],
   823  	}
   824  
   825  	// AuthType is validated when the environment is opened, so it's known
   826  	// to be one of these values.
   827  	var authMode identity.AuthMode
   828  	switch spec.Credential.AuthType() {
   829  	case cloud.UserPassAuthType:
   830  		// TODO(axw) we need a way of saying to use legacy auth.
   831  		cred.User = credAttrs[CredAttrUserName]
   832  		cred.Secrets = credAttrs[CredAttrPassword]
   833  		cred.ProjectDomain = credAttrs[CredAttrProjectDomainName]
   834  		cred.UserDomain = credAttrs[CredAttrUserDomainName]
   835  		cred.Domain = credAttrs[CredAttrDomainName]
   836  		if credAttrs[CredAttrVersion] != "" {
   837  			version, err := strconv.Atoi(credAttrs[CredAttrVersion])
   838  			if err != nil {
   839  				return identity.Credentials{}, 0,
   840  					errors.Errorf("cred.Version is not a valid integer type : %v", err)
   841  			}
   842  			if version < 3 {
   843  				authMode = identity.AuthUserPass
   844  			} else {
   845  				authMode = identity.AuthUserPassV3
   846  			}
   847  			cred.Version = version
   848  		} else if cred.Domain != "" || cred.UserDomain != "" || cred.ProjectDomain != "" {
   849  			authMode = identity.AuthUserPassV3
   850  		} else {
   851  			authMode = identity.AuthUserPass
   852  		}
   853  	case cloud.AccessKeyAuthType:
   854  		cred.User = credAttrs[CredAttrAccessKey]
   855  		cred.Secrets = credAttrs[CredAttrSecretKey]
   856  		authMode = identity.AuthKeyPair
   857  	}
   858  	return cred, authMode, nil
   859  }
   860  
   861  func tlsConfig(certStrs []string) *tls.Config {
   862  	pool := x509.NewCertPool()
   863  	for _, cert := range certStrs {
   864  		pool.AppendCertsFromPEM([]byte(cert))
   865  	}
   866  	tlsConfig := http.SecureTLSConfig()
   867  	tlsConfig.RootCAs = pool
   868  	return tlsConfig
   869  }
   870  
   871  type authenticator interface {
   872  	Authenticate() error
   873  }
   874  
   875  var authenticateClient = func(auth authenticator) error {
   876  	err := auth.Authenticate()
   877  	if err != nil {
   878  		// Log the error in case there are any useful hints,
   879  		// but provide a readable and helpful error message
   880  		// to the user.
   881  		logger.Debugf("Authenticate() failed: %v", err)
   882  		if gooseerrors.IsUnauthorised(err) {
   883  			return errors.Errorf("authentication failed : %v\n"+
   884  				"Please ensure the credentials are correct. A common mistake is\n"+
   885  				"to specify the wrong tenant. Use the OpenStack project name\n"+
   886  				"for tenant-name in your model configuration. \n", err)
   887  		}
   888  		return errors.Annotate(err, "authentication failed.")
   889  	}
   890  	return nil
   891  }
   892  
   893  func (e *Environ) SetConfig(cfg *config.Config) error {
   894  	ecfg, err := providerInstance.newConfig(cfg)
   895  	if err != nil {
   896  		return err
   897  	}
   898  	// At this point, the authentication method config value has been validated so we extract it's value here
   899  	// to avoid having to validate again each time when creating the OpenStack client.
   900  	e.ecfgMutex.Lock()
   901  	defer e.ecfgMutex.Unlock()
   902  	e.ecfgUnlocked = ecfg
   903  
   904  	return nil
   905  }
   906  
   907  // SetCloudSpec is specified in the environs.Environ interface.
   908  func (e *Environ) SetCloudSpec(_ stdcontext.Context, spec environscloudspec.CloudSpec) error {
   909  	e.ecfgMutex.Lock()
   910  	defer e.ecfgMutex.Unlock()
   911  
   912  	if err := validateCloudSpec(spec); err != nil {
   913  		return errors.Annotate(err, "validating cloud spec")
   914  	}
   915  	e.cloudUnlocked = spec
   916  
   917  	// Create a new client factory, that creates the clients according to the
   918  	// auth client version, cloudspec and configuration.
   919  	//
   920  	// In theory we should be able to create one client factory and then every
   921  	// time openstack wants a goose client, we should be just get one from
   922  	// the factory.
   923  	factory := NewClientFactory(spec, e.ecfgUnlocked)
   924  	if err := factory.Init(); err != nil {
   925  		return errors.Trace(err)
   926  	}
   927  	e.clientUnlocked = factory.AuthClient()
   928  
   929  	// The following uses different clients for the different openstack clients
   930  	// and we create them in the factory.
   931  	var err error
   932  	e.novaUnlocked, err = factory.Nova()
   933  	if err != nil {
   934  		return errors.Trace(err)
   935  	}
   936  	e.neutronUnlocked, err = factory.Neutron()
   937  	if err != nil {
   938  		return errors.Trace(err)
   939  	}
   940  	return nil
   941  }
   942  
   943  func identityClientVersion(authURL string) (int, error) {
   944  	url, err := url.Parse(authURL)
   945  	if err != nil {
   946  		// Return 0 as this is the lowest invalid number according to openstack codebase:
   947  		// -1 is reserved and has special handling; 1, 2, 3, etc are valid identity client versions.
   948  		return 0, err
   949  	}
   950  	if url.Path == authURL {
   951  		// This means we could not parse URL into url structure
   952  		// with protocols, domain, port, etc.
   953  		// For example, specifying "keystone.foo" instead of "https://keystone.foo:443/v3/"
   954  		// falls into this category.
   955  		return 0, errors.Errorf("url %s is malformed", authURL)
   956  	}
   957  	if url.Path == "" || url.Path == "/" {
   958  		// User explicitly did not provide any version, it is empty.
   959  		return -1, nil
   960  	}
   961  	// The last part of the path should be the version #, prefixed with a 'v' or 'V'
   962  	// Example: https://keystone.foo:443/v3/
   963  	// Example: https://sharedhost.foo:443/identity/v3/
   964  	var tail string
   965  	urlpath := strings.ToLower(url.Path)
   966  	urlpath, tail = path.Split(urlpath)
   967  	if len(tail) == 0 && len(urlpath) > 2 {
   968  		// trailing /, remove it and split again
   969  		_, tail = path.Split(strings.TrimRight(urlpath, "/"))
   970  	}
   971  	versionNumStr := strings.TrimPrefix(tail, "v")
   972  	logger.Debugf("authURL: %s", authURL)
   973  	major, _, err := version.ParseMajorMinor(versionNumStr)
   974  	if len(tail) < 2 || tail[0] != 'v' || err != nil {
   975  		// There must be a '/v' in the URL path.
   976  		// At this stage only '/Vxxx' and '/vxxx' are valid where xxx is major.minor version.
   977  		// Return 0 as this is the lowest invalid number according to openstack codebase:
   978  		// -1 is reserved and has special handling; 1, 2, 3, etc are valid identity client versions.
   979  		return 0, errors.NotValidf("version part of identity url %s", authURL)
   980  	}
   981  	return major, err
   982  }
   983  
   984  // getKeystoneImageSource is an imagemetadata.ImageDataSourceFunc that
   985  // returns a DataSource using the "product-streams" keystone URL.
   986  func getKeystoneImageSource(env environs.Environ) (simplestreams.DataSource, error) {
   987  	e, ok := env.(*Environ)
   988  	if !ok {
   989  		return nil, errors.NotSupportedf("non-openstack model")
   990  	}
   991  	return e.getKeystoneDataSource(&e.keystoneImageDataSourceMutex, &e.keystoneImageDataSource, "product-streams")
   992  }
   993  
   994  // getKeystoneToolsSource is a tools.ToolsDataSourceFunc that
   995  // returns a DataSource using the "juju-tools" keystone URL.
   996  func getKeystoneToolsSource(env environs.Environ) (simplestreams.DataSource, error) {
   997  	e, ok := env.(*Environ)
   998  	if !ok {
   999  		return nil, errors.NotSupportedf("non-openstack model")
  1000  	}
  1001  	return e.getKeystoneDataSource(&e.keystoneToolsDataSourceMutex, &e.keystoneToolsDataSource, "juju-tools")
  1002  }
  1003  
  1004  func (e *Environ) getKeystoneDataSource(mu *sync.Mutex, datasource *simplestreams.DataSource, keystoneName string) (simplestreams.DataSource, error) {
  1005  	mu.Lock()
  1006  	defer mu.Unlock()
  1007  	if *datasource != nil {
  1008  		return *datasource, nil
  1009  	}
  1010  
  1011  	cl := e.client()
  1012  	if !cl.IsAuthenticated() {
  1013  		if err := authenticateClient(cl); err != nil {
  1014  			return nil, err
  1015  		}
  1016  	}
  1017  
  1018  	serviceURL, err := makeServiceURL(cl, keystoneName, "", nil)
  1019  	if err != nil {
  1020  		return nil, errors.NewNotSupported(err, fmt.Sprintf("cannot make service URL: %v", err))
  1021  	}
  1022  	cfg := simplestreams.Config{
  1023  		Description:          "keystone catalog",
  1024  		BaseURL:              serviceURL,
  1025  		HostnameVerification: e.Config().SSLHostnameVerification(),
  1026  		Priority:             simplestreams.SPECIFIC_CLOUD_DATA,
  1027  		CACertificates:       e.cloudUnlocked.CACertificates,
  1028  	}
  1029  	if err := cfg.Validate(); err != nil {
  1030  		return nil, errors.Annotate(err, "simplestreams config validation failed")
  1031  	}
  1032  	*datasource = simplestreams.NewDataSource(cfg)
  1033  	return *datasource, nil
  1034  }
  1035  
  1036  // assignPublicIP tries to assign the given floating IP address to the
  1037  // specified server, or returns an error.
  1038  func (e *Environ) assignPublicIP(fip *string, serverId string) (err error) {
  1039  	if *fip == "" {
  1040  		return errors.Errorf("cannot assign a nil public IP to %q", serverId)
  1041  	}
  1042  	// At startup nw_info is not yet cached so this may fail
  1043  	// temporarily while the server is being built
  1044  	for a := common.LongAttempt.Start(); a.Next(); {
  1045  		err = e.nova().AddServerFloatingIP(serverId, *fip)
  1046  		if err == nil {
  1047  			return nil
  1048  		}
  1049  	}
  1050  	return err
  1051  }
  1052  
  1053  // DistributeInstances implements the state.InstanceDistributor policy.
  1054  func (e *Environ) DistributeInstances(
  1055  	ctx context.ProviderCallContext, candidates, distributionGroup []instance.Id, limitZones []string,
  1056  ) ([]instance.Id, error) {
  1057  	valid, err := common.DistributeInstances(e, ctx, candidates, distributionGroup, limitZones)
  1058  	if err != nil {
  1059  		handleCredentialError(err, ctx)
  1060  		return valid, err
  1061  	}
  1062  	return valid, nil
  1063  }
  1064  
  1065  // StartInstance is specified in the InstanceBroker interface.
  1066  func (e *Environ) StartInstance(
  1067  	ctx context.ProviderCallContext, args environs.StartInstanceParams,
  1068  ) (*environs.StartInstanceResult, error) {
  1069  	res, err := e.startInstance(ctx, args)
  1070  	handleCredentialError(err, ctx)
  1071  	return res, errors.Trace(err)
  1072  }
  1073  
  1074  func (e *Environ) startInstance(
  1075  	ctx context.ProviderCallContext, args environs.StartInstanceParams,
  1076  ) (_ *environs.StartInstanceResult, err error) {
  1077  	if err := e.validateAvailabilityZone(ctx, args); err != nil {
  1078  		return nil, errors.Trace(err)
  1079  	}
  1080  
  1081  	arch, err := args.Tools.OneArch()
  1082  	if err != nil {
  1083  		return nil, errors.Trace(err)
  1084  	}
  1085  	spec, err := findInstanceSpec(e, instances.InstanceConstraint{
  1086  		Region:      e.cloud().Region,
  1087  		Base:        args.InstanceConfig.Base,
  1088  		Arch:        arch,
  1089  		Constraints: args.Constraints,
  1090  	}, args.ImageMetadata)
  1091  	if err != nil {
  1092  		return nil, environs.ZoneIndependentError(err)
  1093  	}
  1094  	if err := args.InstanceConfig.SetTools(args.Tools); err != nil {
  1095  		return nil, environs.ZoneIndependentError(err)
  1096  	}
  1097  
  1098  	if err := instancecfg.FinishInstanceConfig(args.InstanceConfig, e.Config()); err != nil {
  1099  		return nil, environs.ZoneIndependentError(err)
  1100  	}
  1101  
  1102  	cloudCfg, err := e.configurator.GetCloudConfig(args)
  1103  	if err != nil {
  1104  		return nil, environs.ZoneIndependentError(err)
  1105  	}
  1106  
  1107  	networks, err := e.networksForInstance(args, cloudCfg)
  1108  	if err != nil {
  1109  		return nil, environs.ZoneIndependentError(err)
  1110  	}
  1111  
  1112  	userData, err := providerinit.ComposeUserData(args.InstanceConfig, cloudCfg, OpenstackRenderer{})
  1113  	if err != nil {
  1114  		return nil, environs.ZoneIndependentError(errors.Annotate(err, "cannot make user data"))
  1115  	}
  1116  	logger.Debugf("openstack user data; %d bytes", len(userData))
  1117  
  1118  	machineName := resourceName(
  1119  		e.namespace,
  1120  		e.name,
  1121  		args.InstanceConfig.MachineId,
  1122  	)
  1123  
  1124  	if e.ecfg().useOpenstackGBP() {
  1125  		neutronClient := e.neutron()
  1126  		ptArg := neutron.PolicyTargetV2{
  1127  			Name:                fmt.Sprintf("juju-policytarget-%s", machineName),
  1128  			PolicyTargetGroupId: e.ecfg().policyTargetGroup(),
  1129  		}
  1130  		pt, err := neutronClient.CreatePolicyTargetV2(ptArg)
  1131  		if err != nil {
  1132  			return nil, errors.Trace(err)
  1133  		}
  1134  		networks = append(networks, nova.ServerNetworks{PortId: pt.PortId})
  1135  	}
  1136  
  1137  	// For BUG 1680787: openstack: add support for neutron networks where port
  1138  	// security is disabled.
  1139  	// If any network specified for instance boot has PortSecurityEnabled equals
  1140  	// false, don't create security groups, instance boot will fail.
  1141  	createSecurityGroups := true
  1142  	if len(networks) > 0 && e.supportsNeutron() {
  1143  		neutronClient := e.neutron()
  1144  		for _, n := range networks {
  1145  			if n.NetworkId == "" {
  1146  				// It's a GBP network.
  1147  				continue
  1148  			}
  1149  			net, err := neutronClient.GetNetworkV2(n.NetworkId)
  1150  			if err != nil {
  1151  				return nil, environs.ZoneIndependentError(err)
  1152  			}
  1153  			if net.PortSecurityEnabled != nil && !*net.PortSecurityEnabled {
  1154  				createSecurityGroups = *net.PortSecurityEnabled
  1155  				logger.Infof("network %q has port_security_enabled set to false. Not using security groups.", net.Id)
  1156  				break
  1157  			}
  1158  		}
  1159  	}
  1160  	e.usingSecurityGroups = e.usingSecurityGroups || createSecurityGroups
  1161  
  1162  	var novaGroupNames []nova.SecurityGroupName
  1163  	if createSecurityGroups {
  1164  		groupNames, err := e.firewaller.SetUpGroups(ctx, args.ControllerUUID, args.InstanceConfig.MachineId)
  1165  		if err != nil {
  1166  			return nil, environs.ZoneIndependentError(errors.Annotate(err, "cannot set up groups"))
  1167  		}
  1168  		novaGroupNames = make([]nova.SecurityGroupName, len(groupNames))
  1169  		for i, name := range groupNames {
  1170  			novaGroupNames[i].Name = name
  1171  		}
  1172  	}
  1173  
  1174  	waitForActiveServerDetails := func(
  1175  		client *nova.Client,
  1176  		id string,
  1177  		timeout time.Duration,
  1178  	) (server *nova.ServerDetail, err error) {
  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,
  1196  					fmt.Sprintf("%s, wait 10 seconds before retry, attempt %d", lastError, attempt), nil)
  1197  			},
  1198  			IsFatalError: func(err error) bool {
  1199  				return err != errStillBuilding
  1200  			},
  1201  		})
  1202  		if err != nil {
  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  				break
  1217  			}
  1218  			if server == nil {
  1219  				logger.Warningf("may have lost contact with nova api while creating instances, some stray instances may be around and need to be deleted")
  1220  				break
  1221  			}
  1222  			var serverDetail *nova.ServerDetail
  1223  			serverDetail, err = waitForActiveServerDetails(client, server.Id, 5*time.Minute)
  1224  			if err != nil || serverDetail == nil {
  1225  				// If we got an error back (eg. StillBuilding)
  1226  				// we need to terminate the instance before
  1227  				// retrying to avoid leaking resources.
  1228  				logger.Warningf("Unable to retrieve details for created instance %q: %v; attempting to terminate it", server.Id, err)
  1229  				if termErr := e.terminateInstances(ctx, []instance.Id{instance.Id(server.Id)}); termErr != nil {
  1230  					logger.Errorf("Failed to delete instance %q: %v; manual cleanup required", server.Id, termErr)
  1231  				}
  1232  				server = nil
  1233  				break
  1234  			} else if serverDetail.Status == nova.StatusActive {
  1235  				break
  1236  			} else if serverDetail.Status == nova.StatusError {
  1237  				// Perhaps there is an error case where a retry in the same AZ
  1238  				// is a good idea.
  1239  				faultMsg := " unable to determine fault details"
  1240  				if serverDetail.Fault != nil {
  1241  					faultMsg = fmt.Sprintf(" with fault %q", serverDetail.Fault.Message)
  1242  				} else {
  1243  					logger.Debugf("getting active server details from nova failed without fault details")
  1244  				}
  1245  				logger.Infof("Deleting instance %q in ERROR state%v", server.Id, faultMsg)
  1246  				if err = e.terminateInstances(ctx, []instance.Id{instance.Id(server.Id)}); err != nil {
  1247  					logger.Errorf("Failed to delete instance in ERROR state %q: %v; manual cleanup required", server.Id, err)
  1248  				}
  1249  				server = nil
  1250  				err = errors.New(faultMsg)
  1251  				break
  1252  			}
  1253  		}
  1254  		return server, err
  1255  	}
  1256  
  1257  	opts := nova.RunServerOpts{
  1258  		Name:               machineName,
  1259  		FlavorId:           spec.InstanceType.Id,
  1260  		UserData:           userData,
  1261  		SecurityGroupNames: novaGroupNames,
  1262  		Networks:           networks,
  1263  		Metadata:           args.InstanceConfig.Tags,
  1264  		AvailabilityZone:   args.AvailabilityZone,
  1265  	}
  1266  
  1267  	err = e.configureRootDisk(ctx, args, spec, &opts)
  1268  	if err != nil {
  1269  		return nil, environs.ZoneIndependentError(err)
  1270  	}
  1271  	e.configurator.ModifyRunServerOptions(&opts)
  1272  
  1273  	server, err := tryStartNovaInstance(shortAttempt, e.nova(), opts)
  1274  	if err != nil || server == nil {
  1275  		// Attempt to clean up any security groups we created.
  1276  		if err := e.cleanupGroups(ctx, e.nova(), novaGroupNames); err != nil {
  1277  			// If we failed to clean up the security groups, we need the user
  1278  			// to manually clean them up.
  1279  			logger.Errorf("cannot cleanup security groups: %v", err)
  1280  		}
  1281  
  1282  		logger.Debugf("cannot run instance full error: %q", err)
  1283  		err = errors.Annotate(errors.Cause(err), "cannot run instance")
  1284  		// Improve the error message if there is no valid network.
  1285  		if isInvalidNetworkError(err) {
  1286  			err = e.userFriendlyInvalidNetworkError(err)
  1287  		}
  1288  		// 'No valid host available' is typically a resource error,
  1289  		// let the provisioner know it is a good idea to try another
  1290  		// AZ if available.
  1291  		if !isNoValidHostsError(err) {
  1292  			err = environs.ZoneIndependentError(err)
  1293  		}
  1294  		return nil, err
  1295  	}
  1296  
  1297  	detail, err := e.nova().GetServer(server.Id)
  1298  	if err != nil {
  1299  		return nil, environs.ZoneIndependentError(errors.Annotate(err, "cannot get started instance"))
  1300  	}
  1301  
  1302  	inst := &openstackInstance{
  1303  		e:            e,
  1304  		serverDetail: detail,
  1305  		arch:         &spec.Image.Arch,
  1306  		instType:     &spec.InstanceType,
  1307  		runOpts:      &opts,
  1308  	}
  1309  	logger.Infof("started instance %q", inst.Id())
  1310  	var withPublicIP bool
  1311  	// Any machine constraint for allocating a public IP address
  1312  	// overrides the (deprecated) model config.
  1313  	if args.Constraints.HasAllocatePublicIP() {
  1314  		withPublicIP = *args.Constraints.AllocatePublicIP
  1315  	}
  1316  	if withPublicIP {
  1317  		// If we don't lock here, AllocatePublicIP() can return the same
  1318  		// public IP for 2 different instances. Only one will successfully
  1319  		// be assigned the public IP, the other will not have one.
  1320  		e.publicIPMutex.Lock()
  1321  		defer e.publicIPMutex.Unlock()
  1322  		var publicIP *string
  1323  		logger.Debugf("allocating public IP address for openstack node")
  1324  		if fip, err := e.networking.AllocatePublicIP(inst.Id()); err != nil {
  1325  			if err := e.terminateInstances(ctx, []instance.Id{inst.Id()}); err != nil {
  1326  				// ignore the failure at this stage, just log it
  1327  				logger.Debugf("failed to terminate instance %q: %v", inst.Id(), err)
  1328  			}
  1329  			return nil, environs.ZoneIndependentError(errors.Annotate(err, "cannot allocate a public IP as needed"))
  1330  		} else {
  1331  			publicIP = fip
  1332  			logger.Infof("allocated public IP %s", *publicIP)
  1333  		}
  1334  
  1335  		if err := e.assignPublicIP(publicIP, string(inst.Id())); err != nil {
  1336  			if err := e.terminateInstances(ctx, []instance.Id{inst.Id()}); err != nil {
  1337  				// ignore the failure at this stage, just log it
  1338  				logger.Debugf("failed to terminate instance %q: %v", inst.Id(), err)
  1339  			}
  1340  			return nil, environs.ZoneIndependentError(errors.Annotatef(err,
  1341  				"cannot assign public address %s to instance %q",
  1342  				*publicIP, inst.Id(),
  1343  			))
  1344  		}
  1345  		inst.floatingIP = publicIP
  1346  	}
  1347  
  1348  	return &environs.StartInstanceResult{
  1349  		Instance: inst,
  1350  		Hardware: inst.hardwareCharacteristics(),
  1351  	}, nil
  1352  }
  1353  
  1354  // Clean up any groups that we have created if we fail to start the instance.
  1355  func (e *Environ) cleanupGroups(
  1356  	ctx context.ProviderCallContext,
  1357  	client *nova.Client,
  1358  	groups []nova.SecurityGroupName,
  1359  ) error {
  1360  	names := make([]string, len(groups))
  1361  	for i, group := range groups {
  1362  		names[i] = group.Name
  1363  	}
  1364  	return e.firewaller.DeleteGroups(ctx, names...)
  1365  }
  1366  
  1367  func (e *Environ) userFriendlyInvalidNetworkError(err error) error {
  1368  	msg := fmt.Sprintf("%s\n\t%s\n\t%s", err.Error(),
  1369  		"This error was caused by juju attempting to create an OpenStack instance with no network defined.",
  1370  		"No network has been configured.")
  1371  	networks, err := e.networking.FindNetworks(true)
  1372  	if err != nil {
  1373  		msg += fmt.Sprintf("\n\t%s\n\t\t%s", "Error attempting to find internal networks:", err.Error())
  1374  	} else {
  1375  		msg += fmt.Sprintf(" %s\n\t\t%q", "The following internal networks are available: ", strings.Join(networks.Values(), ", "))
  1376  	}
  1377  	return errors.New(msg)
  1378  }
  1379  
  1380  // validateAvailabilityZone validates AZs supplied in StartInstanceParams.
  1381  // args.AvailabilityZone should only be set if this OpenStack supports zones.
  1382  // We need to validate it if supplied.
  1383  func (e *Environ) validateAvailabilityZone(ctx context.ProviderCallContext, args environs.StartInstanceParams) error {
  1384  	if args.AvailabilityZone == "" {
  1385  		return nil
  1386  	}
  1387  
  1388  	volumeAttachmentsZone, err := e.volumeAttachmentsZone(args.VolumeAttachments)
  1389  	if err != nil {
  1390  		return environs.ZoneIndependentError(err)
  1391  	}
  1392  	if err := validateAvailabilityZoneConsistency(args.AvailabilityZone, volumeAttachmentsZone); err != nil {
  1393  		return environs.ZoneIndependentError(err)
  1394  	}
  1395  
  1396  	zones, err := e.AvailabilityZones(ctx)
  1397  	if err != nil {
  1398  		return errors.Trace(err)
  1399  	}
  1400  	return errors.Trace(zones.Validate(args.AvailabilityZone))
  1401  
  1402  }
  1403  
  1404  // networksForInstance returns networks that will be attached
  1405  // to a new Openstack instance.
  1406  // Network info for all ports created is represented in the input cloud-config
  1407  // reference.
  1408  // This is necessary so that the correct Netplan representation for the
  1409  // associated NICs is rendered in the instance that they will be attached to.
  1410  func (e *Environ) networksForInstance(
  1411  	args environs.StartInstanceParams, cloudCfg cloudinit.NetworkingConfig,
  1412  ) ([]nova.ServerNetworks, error) {
  1413  	networks, err := e.networksForModel()
  1414  	if err != nil {
  1415  		return nil, errors.Trace(err)
  1416  	}
  1417  
  1418  	// If there are no constraints or bindings to accommodate,
  1419  	// the instance will have a NIC for each configured internal network.
  1420  	// Note that uif there is no configured network, this means a NIC in
  1421  	// all *available* networks.
  1422  	if len(args.SubnetsToZones) == 0 {
  1423  		toServerNet := func(n neutron.NetworkV2) nova.ServerNetworks { return nova.ServerNetworks{NetworkId: n.Id} }
  1424  		return transform.Slice(networks, toServerNet), nil
  1425  	}
  1426  
  1427  	if len(networks) == 0 {
  1428  		return nil, errors.New(
  1429  			"space constraints and/or bindings were supplied, but no OpenStack networks can be determined")
  1430  	}
  1431  
  1432  	subnetIDForZone, err := subnetInZone(args.AvailabilityZone, args.SubnetsToZones)
  1433  	if err != nil {
  1434  		return nil, errors.Trace(err)
  1435  	}
  1436  
  1437  	// Set the subnetID on the network for all networks.
  1438  	// For each of the subnetIDs selected, create a port for each one.
  1439  	subnetNetworks := make([]nova.ServerNetworks, 0, len(subnetIDForZone))
  1440  	netInfo := make(network.InterfaceInfos, len(subnetIDForZone))
  1441  	for i, subnetID := range subnetIDForZone {
  1442  		subnetNet, err := networkForSubnet(networks, subnetID)
  1443  		if err != nil {
  1444  			return nil, errors.Trace(err)
  1445  		}
  1446  
  1447  		var port *neutron.PortV2
  1448  		port, err = e.networking.CreatePort(e.uuid, subnetNet.Id, subnetID)
  1449  		if err != nil {
  1450  			break
  1451  		}
  1452  
  1453  		logger.Infof("created new port %q connected to Openstack subnet %q", port.Id, subnetID)
  1454  		subnetNetworks = append(subnetNetworks, nova.ServerNetworks{
  1455  			NetworkId: subnetNet.Id,
  1456  			PortId:    port.Id,
  1457  		})
  1458  
  1459  		// We expect a single address,
  1460  		// but for correctness we add all from the created port.
  1461  		ips := make([]string, len(port.FixedIPs))
  1462  		for j, fixedIP := range port.FixedIPs {
  1463  			ips[j] = fixedIP.IPAddress
  1464  		}
  1465  
  1466  		netInfo[i] = network.InterfaceInfo{
  1467  			InterfaceName: fmt.Sprintf("eth%d", i),
  1468  			MACAddress:    port.MACAddress,
  1469  			Addresses:     network.NewMachineAddresses(ips).AsProviderAddresses(),
  1470  			ConfigType:    network.ConfigDHCP,
  1471  			Origin:        network.OriginProvider,
  1472  		}
  1473  	}
  1474  
  1475  	err = cloudCfg.AddNetworkConfig(netInfo)
  1476  
  1477  	if err != nil {
  1478  		err1 := e.DeletePorts(subnetNetworks)
  1479  		if err1 != nil {
  1480  			logger.Errorf("Unable to delete ports from the provider %+v", subnetNetworks)
  1481  		}
  1482  		return nil, errors.Annotatef(err, "creating ports for instance")
  1483  	}
  1484  
  1485  	return subnetNetworks, nil
  1486  }
  1487  
  1488  // subnetInZone chooses a subnet at random for each entry in the input slice of
  1489  // subnet:zones that is in the input availability zone.
  1490  func subnetInZone(az string, subnetsToZones []map[network.Id][]string) ([]network.Id, error) {
  1491  	// Attempt to filter the constraint/binding subnet IDs for the supplied
  1492  	// availability zone.
  1493  	// The zone is supplied by the provisioner based on its attempt to maintain
  1494  	// distribution of units across zones.
  1495  	// The zones recorded against O7k subnets in Juju are affected by the
  1496  	// `availability_zone_hints` attribute on the network where they reside,
  1497  	// and the default AZ configuration for the project.
  1498  	// They are a read-only attribute.
  1499  	// If a subnet in the subnets-to-zones map has no zones, we assume a basic
  1500  	// O7k set-up where networks are not zone-limited. We log a warning and
  1501  	// consider all the supplied subnets.
  1502  	// See:
  1503  	// - https://docs.openstack.org/neutron/latest/admin/config-az.html#availability-zone-related-attributes
  1504  	// - https://docs.openstack.org/neutron/latest/admin/ovn/availability_zones.html#network-availability-zones
  1505  	subnetIDsForZone := make([][]network.Id, len(subnetsToZones))
  1506  	for i, nic := range subnetsToZones {
  1507  		subnetIDs, err := network.FindSubnetIDsForAvailabilityZone(az, nic)
  1508  		if err != nil {
  1509  			// We found no subnets in the zone.
  1510  			// Add subnets without zone-limited networks.
  1511  			for subnetID, zones := range nic {
  1512  				if len(zones) == 0 {
  1513  					logger.Warningf(
  1514  						"subnet %q is not in a network with availability zones listed; assuming availability in zone %q",
  1515  						subnetID, az)
  1516  					subnetIDs = append(subnetIDs, subnetID)
  1517  				}
  1518  			}
  1519  
  1520  			if len(subnetIDs) == 0 {
  1521  				// If we still have no candidate subnets, then they are all in
  1522  				// networks with availability zones, and none of those zones
  1523  				// match the input one. Return the error we have in hand.
  1524  				return nil, errors.Annotatef(err, "determining subnets in zone %q", az)
  1525  			}
  1526  		}
  1527  
  1528  		subnetIDsForZone[i] = network.FilterInFanNetwork(subnetIDs)
  1529  	}
  1530  
  1531  	// For each list of subnet IDs that satisfy space and zone constraints,
  1532  	// choose a single one at random.
  1533  	subnetIDForZone := make([]network.Id, len(subnetIDsForZone))
  1534  	for i, subnetIDs := range subnetIDsForZone {
  1535  		if len(subnetIDs) == 1 {
  1536  			subnetIDForZone[i] = subnetIDs[0]
  1537  			continue
  1538  		}
  1539  		subnetIDForZone[i] = subnetIDs[rand.Intn(len(subnetIDs))]
  1540  	}
  1541  
  1542  	return subnetIDForZone, nil
  1543  }
  1544  
  1545  // DeletePorts goes through and attempts to delete any ports that have been
  1546  // created during the creation of the networks for the given instance.
  1547  func (e *Environ) DeletePorts(networks []nova.ServerNetworks) error {
  1548  	var errs []error
  1549  	for _, network := range networks {
  1550  		if network.NetworkId != "" && network.PortId != "" {
  1551  			err := e.networking.DeletePortByID(network.PortId)
  1552  			if err != nil {
  1553  				errs = append(errs, err)
  1554  			}
  1555  		}
  1556  	}
  1557  	if len(errs) > 0 {
  1558  		// It would be nice to generalize this so we have the same expected
  1559  		// behaviour from all our slices of errors.
  1560  		for _, err := range errs {
  1561  			logger.Errorf("Unable to delete port with error: %v", err)
  1562  		}
  1563  		return errs[0]
  1564  	}
  1565  	return nil
  1566  }
  1567  
  1568  // networksForModel returns the Openstack network list
  1569  // based on current model configuration.
  1570  func (e *Environ) networksForModel() ([]neutron.NetworkV2, error) {
  1571  	var resolvedNetworks []neutron.NetworkV2
  1572  	networkIDs := set.NewStrings()
  1573  	cfgNets := e.ecfg().networks()
  1574  
  1575  	for _, cfgNet := range cfgNets {
  1576  		networks, err := e.networking.ResolveNetworks(cfgNet, false)
  1577  		if err != nil {
  1578  			logger.Warningf("filtering networks for %q", cfgNet)
  1579  		}
  1580  
  1581  		for _, net := range networks {
  1582  			if networkIDs.Contains(net.Id) {
  1583  				continue
  1584  			}
  1585  
  1586  			resolvedNetworks = append(resolvedNetworks, net)
  1587  			networkIDs.Add(net.Id)
  1588  		}
  1589  	}
  1590  
  1591  	if networkIDs.Size() == 0 {
  1592  		if len(cfgNets) == 1 && cfgNets[0] == "" {
  1593  			return nil, nil
  1594  		}
  1595  		return nil, errors.Errorf("unable to determine networks for configured list: %v", cfgNets)
  1596  	}
  1597  
  1598  	logger.Debugf("using network IDs %v", networkIDs.Values())
  1599  	return resolvedNetworks, nil
  1600  }
  1601  
  1602  func (e *Environ) configureRootDisk(_ context.ProviderCallContext, args environs.StartInstanceParams,
  1603  	spec *instances.InstanceSpec, runOpts *nova.RunServerOpts) error {
  1604  	rootDiskSource := rootDiskSourceLocal
  1605  	if args.Constraints.HasRootDiskSource() {
  1606  		rootDiskSource = *args.Constraints.RootDiskSource
  1607  	}
  1608  	rootDiskMapping := nova.BlockDeviceMapping{
  1609  		BootIndex:  0,
  1610  		UUID:       spec.Image.Id,
  1611  		SourceType: "image",
  1612  		// NB constraints.RootDiskSource in the case of OpenStack represents
  1613  		// the type of block device to use. Either "local" to represent a local
  1614  		// block device or "volume" to represent a block device from the cinder
  1615  		// block storage service.
  1616  		DestinationType:     rootDiskSource,
  1617  		DeleteOnTermination: true,
  1618  	}
  1619  	switch rootDiskSource {
  1620  	case rootDiskSourceLocal:
  1621  		if args.Constraints.HasImageID() {
  1622  			runOpts.ImageId = *args.Constraints.ImageID
  1623  		} else {
  1624  			runOpts.ImageId = spec.Image.Id
  1625  		}
  1626  	case rootDiskSourceVolume:
  1627  		if args.Constraints.HasImageID() {
  1628  			runOpts.ImageId = *args.Constraints.ImageID
  1629  		}
  1630  		size := uint64(0)
  1631  		if args.Constraints.HasRootDisk() {
  1632  			size = *args.Constraints.RootDisk
  1633  		}
  1634  		if size <= 0 {
  1635  			size = defaultRootDiskSize
  1636  		}
  1637  		sizeGB := common.MiBToGiB(size)
  1638  		rootDiskMapping.VolumeSize = int(sizeGB)
  1639  	default:
  1640  		return errors.Errorf("invalid %s %s", constraints.RootDiskSource, rootDiskSource)
  1641  	}
  1642  	runOpts.BlockDeviceMappings = []nova.BlockDeviceMapping{rootDiskMapping}
  1643  	return nil
  1644  }
  1645  
  1646  func (e *Environ) deriveAvailabilityZone(
  1647  	ctx context.ProviderCallContext,
  1648  	placement string,
  1649  	volumeAttachments []storage.VolumeAttachmentParams,
  1650  ) (string, error) {
  1651  	volumeAttachmentsZone, err := e.volumeAttachmentsZone(volumeAttachments)
  1652  	if err != nil {
  1653  		handleCredentialError(err, ctx)
  1654  		return "", errors.Trace(err)
  1655  	}
  1656  	if placement == "" {
  1657  		return volumeAttachmentsZone, nil
  1658  	}
  1659  	instPlacement, err := e.parsePlacement(ctx, placement)
  1660  	if err != nil {
  1661  		return "", err
  1662  	}
  1663  	if err := validateAvailabilityZoneConsistency(instPlacement.zoneName, volumeAttachmentsZone); err != nil {
  1664  		return "", errors.Annotatef(err, "cannot create instance with placement %q", placement)
  1665  	}
  1666  	return instPlacement.zoneName, nil
  1667  }
  1668  
  1669  func validateAvailabilityZoneConsistency(instanceZone, volumeAttachmentsZone string) error {
  1670  	if volumeAttachmentsZone != "" && instanceZone != volumeAttachmentsZone {
  1671  		return errors.Errorf(
  1672  			"cannot create instance in zone %q, as this will prevent attaching the requested disks in zone %q",
  1673  			instanceZone, volumeAttachmentsZone,
  1674  		)
  1675  	}
  1676  	return nil
  1677  }
  1678  
  1679  // volumeAttachmentsZone determines the availability zone for each volume
  1680  // identified in the volume attachment parameters, checking that they are
  1681  // all the same, and returns the availability zone name.
  1682  func (e *Environ) volumeAttachmentsZone(volumeAttachments []storage.VolumeAttachmentParams) (string, error) {
  1683  	if len(volumeAttachments) == 0 {
  1684  		return "", nil
  1685  	}
  1686  	cinderProvider, err := e.cinderProvider()
  1687  	if err != nil {
  1688  		return "", errors.Trace(err)
  1689  	}
  1690  	volumes, err := modelCinderVolumes(cinderProvider.storageAdapter, cinderProvider.modelUUID)
  1691  	if err != nil {
  1692  		return "", errors.Trace(err)
  1693  	}
  1694  	var zone string
  1695  	for i, a := range volumeAttachments {
  1696  		var v *cinder.Volume
  1697  		for i := range volumes {
  1698  			if volumes[i].ID == a.VolumeId {
  1699  				v = &volumes[i]
  1700  				break
  1701  			}
  1702  		}
  1703  		if v == nil {
  1704  			return "", errors.Errorf("cannot find volume %q to attach to new instance", a.VolumeId)
  1705  		}
  1706  		if zone == "" {
  1707  			zone = v.AvailabilityZone
  1708  		} else if v.AvailabilityZone != zone {
  1709  			return "", errors.Errorf(
  1710  				"cannot attach volumes from multiple availability zones: %s is in %s, %s is in %s",
  1711  				volumeAttachments[i-1].VolumeId, zone, a.VolumeId, v.AvailabilityZone,
  1712  			)
  1713  		}
  1714  	}
  1715  	return zone, nil
  1716  }
  1717  
  1718  func isNoValidHostsError(err error) bool {
  1719  	if cause := errors.Cause(err); cause != nil {
  1720  		return strings.Contains(cause.Error(), "No valid host was found")
  1721  	}
  1722  	return false
  1723  }
  1724  
  1725  func isInvalidNetworkError(err error) bool {
  1726  	if cause := errors.Cause(err); cause != nil {
  1727  		return strings.Contains(errors.Cause(err).Error(), "Invalid input for field/attribute networks")
  1728  	}
  1729  	return false
  1730  }
  1731  
  1732  func (e *Environ) StopInstances(ctx context.ProviderCallContext, ids ...instance.Id) error {
  1733  	logger.Debugf("terminating instances %v", ids)
  1734  	if err := e.terminateInstances(ctx, ids); err != nil {
  1735  		handleCredentialError(err, ctx)
  1736  		return err
  1737  	}
  1738  	return nil
  1739  }
  1740  
  1741  func (e *Environ) isAliveServer(server nova.ServerDetail) bool {
  1742  	switch server.Status {
  1743  	case nova.StatusActive, nova.StatusBuild, nova.StatusBuildSpawning, nova.StatusShutoff, nova.StatusSuspended:
  1744  		return true
  1745  	}
  1746  	return false
  1747  }
  1748  
  1749  func (e *Environ) listServers(ctx context.ProviderCallContext, ids []instance.Id) ([]nova.ServerDetail, error) {
  1750  	wantedServers := make([]nova.ServerDetail, 0, len(ids))
  1751  	if len(ids) == 1 {
  1752  		// Common case, single instance, may return NotFound
  1753  		var maybeServer *nova.ServerDetail
  1754  		maybeServer, err := e.nova().GetServer(string(ids[0]))
  1755  		if err != nil {
  1756  			handleCredentialError(err, ctx)
  1757  			return nil, err
  1758  		}
  1759  		// Only return server details if it is currently alive
  1760  		if maybeServer != nil && e.isAliveServer(*maybeServer) {
  1761  			wantedServers = append(wantedServers, *maybeServer)
  1762  		}
  1763  		return wantedServers, nil
  1764  	}
  1765  	// List all instances in the environment.
  1766  	instances, err := e.AllRunningInstances(ctx)
  1767  	if err != nil {
  1768  		handleCredentialError(err, ctx)
  1769  		return nil, err
  1770  	}
  1771  	// Return only servers with the wanted ids that are currently alive
  1772  	for _, inst := range instances {
  1773  		inst := inst.(*openstackInstance)
  1774  		serverDetail := *inst.serverDetail
  1775  		if !e.isAliveServer(serverDetail) {
  1776  			continue
  1777  		}
  1778  		for _, id := range ids {
  1779  			if inst.Id() != id {
  1780  				continue
  1781  			}
  1782  			wantedServers = append(wantedServers, serverDetail)
  1783  			break
  1784  		}
  1785  	}
  1786  	return wantedServers, nil
  1787  }
  1788  
  1789  // updateFloatingIPAddresses updates the instances with any floating IP address
  1790  // that have been assigned to those instances.
  1791  func (e *Environ) updateFloatingIPAddresses(ctx context.ProviderCallContext, instances map[string]instances.Instance) error {
  1792  	servers, err := e.nova().ListServersDetail(jujuMachineFilter())
  1793  	if err != nil {
  1794  		handleCredentialError(err, ctx)
  1795  		return err
  1796  	}
  1797  	for _, server := range servers {
  1798  		// server.Addresses is a map with entries containing []nova.IPAddress
  1799  		for _, net := range server.Addresses {
  1800  			for _, addr := range net {
  1801  				if addr.Type == "floating" {
  1802  					instId := server.Id
  1803  					if inst, ok := instances[instId]; ok {
  1804  						instFip := &addr.Address
  1805  						inst.(*openstackInstance).floatingIP = instFip
  1806  					}
  1807  				}
  1808  			}
  1809  		}
  1810  	}
  1811  	return nil
  1812  }
  1813  
  1814  func (e *Environ) Instances(ctx context.ProviderCallContext, ids []instance.Id) ([]instances.Instance, error) {
  1815  	if len(ids) == 0 {
  1816  		return nil, nil
  1817  	}
  1818  
  1819  	foundServers, err := e.listServers(ctx, ids)
  1820  	if err != nil {
  1821  		logger.Debugf("error listing servers: %v", err)
  1822  		if !IsNotFoundError(err) {
  1823  			handleCredentialError(err, ctx)
  1824  			return nil, err
  1825  		}
  1826  	}
  1827  
  1828  	logger.Tracef("%d/%d live servers found", len(foundServers), len(ids))
  1829  	if len(foundServers) == 0 {
  1830  		return nil, environs.ErrNoInstances
  1831  	}
  1832  
  1833  	instsById := make(map[string]instances.Instance, len(foundServers))
  1834  	for i, server := range foundServers {
  1835  		instsById[server.Id] = &openstackInstance{
  1836  			e:            e,
  1837  			serverDetail: &foundServers[i],
  1838  		}
  1839  	}
  1840  
  1841  	// Update the instance structs with any floating IP address
  1842  	// that has been assigned to the instance.
  1843  	if err = e.updateFloatingIPAddresses(ctx, instsById); err != nil {
  1844  		return nil, err
  1845  	}
  1846  
  1847  	insts := make([]instances.Instance, len(ids))
  1848  	for i, id := range ids {
  1849  		if inst := instsById[string(id)]; inst != nil {
  1850  			insts[i] = inst
  1851  		} else {
  1852  			err = environs.ErrPartialInstances
  1853  		}
  1854  	}
  1855  	return insts, err
  1856  }
  1857  
  1858  // AdoptResources is part of the Environ interface.
  1859  func (e *Environ) AdoptResources(ctx context.ProviderCallContext, controllerUUID string, fromVersion version.Number) error {
  1860  	var failed []string
  1861  	controllerTag := map[string]string{tags.JujuController: controllerUUID}
  1862  
  1863  	instances, err := e.AllInstances(ctx)
  1864  	if err != nil {
  1865  		handleCredentialError(err, ctx)
  1866  		return errors.Trace(err)
  1867  	}
  1868  	for _, instance := range instances {
  1869  		err := e.TagInstance(ctx, instance.Id(), controllerTag)
  1870  		if err != nil {
  1871  			logger.Errorf("error updating controller tag for instance %s: %v", instance.Id(), err)
  1872  			failed = append(failed, string(instance.Id()))
  1873  			if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied {
  1874  				// If we have an invvalid credential, there is no need to proceed: we'll fail 100%.
  1875  				break
  1876  			}
  1877  		}
  1878  	}
  1879  
  1880  	failedVolumes, err := e.adoptVolumes(controllerTag, ctx)
  1881  	if err != nil {
  1882  		handleCredentialError(err, ctx)
  1883  		return errors.Trace(err)
  1884  	}
  1885  	failed = append(failed, failedVolumes...)
  1886  
  1887  	err = e.firewaller.UpdateGroupController(ctx, controllerUUID)
  1888  	if err != nil {
  1889  		handleCredentialError(err, ctx)
  1890  		return errors.Trace(err)
  1891  	}
  1892  	if len(failed) != 0 {
  1893  		return errors.Errorf("error updating controller tag for some resources: %v", failed)
  1894  	}
  1895  	return nil
  1896  }
  1897  
  1898  func (e *Environ) adoptVolumes(controllerTag map[string]string, ctx context.ProviderCallContext) ([]string, error) {
  1899  	cinder, err := e.cinderProvider()
  1900  	if errors.IsNotSupported(err) {
  1901  		logger.Debugf("volumes not supported: not transferring ownership for volumes")
  1902  		return nil, nil
  1903  	}
  1904  	if err != nil {
  1905  		handleCredentialError(err, ctx)
  1906  		return nil, errors.Trace(err)
  1907  	}
  1908  	// TODO(axw): fix the storage API.
  1909  	storageConfig, err := storage.NewConfig("cinder", CinderProviderType, nil)
  1910  	if err != nil {
  1911  		return nil, errors.Trace(err)
  1912  	}
  1913  	volumeSource, err := cinder.VolumeSource(storageConfig)
  1914  	if err != nil {
  1915  		handleCredentialError(err, ctx)
  1916  		return nil, errors.Trace(err)
  1917  	}
  1918  	volumeIds, err := volumeSource.ListVolumes(ctx)
  1919  	if err != nil {
  1920  		handleCredentialError(err, ctx)
  1921  		return nil, errors.Trace(err)
  1922  	}
  1923  
  1924  	var failed []string
  1925  	for _, volumeId := range volumeIds {
  1926  		_, err := cinder.storageAdapter.SetVolumeMetadata(volumeId, controllerTag)
  1927  		if err != nil {
  1928  			logger.Errorf("error updating controller tag for volume %s: %v", volumeId, err)
  1929  			failed = append(failed, volumeId)
  1930  			if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied {
  1931  				// If we have an invvalid credential, there is no need to proceed: we'll fail 100%.
  1932  				break
  1933  			}
  1934  		}
  1935  	}
  1936  	return failed, nil
  1937  }
  1938  
  1939  // AllInstances returns all instances in this environment.
  1940  func (e *Environ) AllInstances(ctx context.ProviderCallContext) ([]instances.Instance, error) {
  1941  	tagFilter := tagValue{tags.JujuModel, e.ecfg().UUID()}
  1942  	instances, err := e.allInstances(ctx, tagFilter)
  1943  	if err != nil {
  1944  		handleCredentialError(err, ctx)
  1945  		return instances, err
  1946  	}
  1947  	return instances, nil
  1948  }
  1949  
  1950  // AllRunningInstances returns all running, available instances in this environment.
  1951  func (e *Environ) AllRunningInstances(ctx context.ProviderCallContext) ([]instances.Instance, error) {
  1952  	// e.allInstances(...) already handles all instances irrespective of the state, so
  1953  	// here 'all' is also 'all running'.
  1954  	return e.AllInstances(ctx)
  1955  }
  1956  
  1957  // allControllerManagedInstances returns all instances managed by this
  1958  // environment's controller, matching the optionally specified filter.
  1959  func (e *Environ) allControllerManagedInstances(ctx context.ProviderCallContext, controllerUUID string) ([]instances.Instance, error) {
  1960  	tagFilter := tagValue{tags.JujuController, controllerUUID}
  1961  	instances, err := e.allInstances(ctx, tagFilter)
  1962  	if err != nil {
  1963  		handleCredentialError(err, ctx)
  1964  		return instances, err
  1965  	}
  1966  	return instances, nil
  1967  }
  1968  
  1969  type tagValue struct {
  1970  	tag, value string
  1971  }
  1972  
  1973  // allControllerManagedInstances returns all instances managed by this
  1974  // environment's controller, matching the optionally specified filter.
  1975  func (e *Environ) allInstances(ctx context.ProviderCallContext, tagFilter tagValue) ([]instances.Instance, error) {
  1976  	servers, err := e.nova().ListServersDetail(jujuMachineFilter())
  1977  	if err != nil {
  1978  		handleCredentialError(err, ctx)
  1979  		return nil, err
  1980  	}
  1981  	instsById := make(map[string]instances.Instance)
  1982  	for _, server := range servers {
  1983  		if server.Metadata[tagFilter.tag] != tagFilter.value {
  1984  			continue
  1985  		}
  1986  		if e.isAliveServer(server) {
  1987  			var s = server
  1988  			// TODO(wallyworld): lookup the flavor details to fill in the instance type data
  1989  			instsById[s.Id] = &openstackInstance{e: e, serverDetail: &s}
  1990  		}
  1991  	}
  1992  	if err := e.updateFloatingIPAddresses(ctx, instsById); err != nil {
  1993  		handleCredentialError(err, ctx)
  1994  		return nil, err
  1995  	}
  1996  	insts := make([]instances.Instance, 0, len(instsById))
  1997  	for _, inst := range instsById {
  1998  		insts = append(insts, inst)
  1999  	}
  2000  	return insts, nil
  2001  }
  2002  
  2003  func (e *Environ) Destroy(ctx context.ProviderCallContext) error {
  2004  	err := common.Destroy(e, ctx)
  2005  	if err != nil {
  2006  		handleCredentialError(err, ctx)
  2007  		return errors.Trace(err)
  2008  	}
  2009  	// Delete all security groups remaining in the model.
  2010  	if err := e.firewaller.DeleteAllModelGroups(ctx); err != nil {
  2011  		handleCredentialError(err, ctx)
  2012  		return errors.Trace(err)
  2013  	}
  2014  	return nil
  2015  }
  2016  
  2017  // DestroyController implements the Environ interface.
  2018  func (e *Environ) DestroyController(ctx context.ProviderCallContext, controllerUUID string) error {
  2019  	if err := e.Destroy(ctx); err != nil {
  2020  		handleCredentialError(err, ctx)
  2021  		return errors.Annotate(err, "destroying controller model")
  2022  	}
  2023  	// In case any hosted environment hasn't been cleaned up yet,
  2024  	// we also attempt to delete their resources when the controller
  2025  	// environment is destroyed.
  2026  	if err := e.destroyControllerManagedEnvirons(ctx, controllerUUID); err != nil {
  2027  		handleCredentialError(err, ctx)
  2028  		return errors.Annotate(err, "destroying managed models")
  2029  	}
  2030  	if err := e.firewaller.DeleteAllControllerGroups(ctx, controllerUUID); err != nil {
  2031  		handleCredentialError(err, ctx)
  2032  		return errors.Trace(err)
  2033  	}
  2034  	return nil
  2035  }
  2036  
  2037  // destroyControllerManagedEnvirons destroys all environments managed by this
  2038  // models's controller.
  2039  func (e *Environ) destroyControllerManagedEnvirons(ctx context.ProviderCallContext, controllerUUID string) error {
  2040  	// Terminate all instances managed by the controller.
  2041  	insts, err := e.allControllerManagedInstances(ctx, controllerUUID)
  2042  	if err != nil {
  2043  		return errors.Annotate(err, "listing instances")
  2044  	}
  2045  	instIds := make([]instance.Id, len(insts))
  2046  	for i, inst := range insts {
  2047  		instIds[i] = inst.Id()
  2048  	}
  2049  	if err := e.terminateInstances(ctx, instIds); err != nil {
  2050  		handleCredentialError(err, ctx)
  2051  		return errors.Annotate(err, "terminating instances")
  2052  	}
  2053  
  2054  	// Delete all volumes managed by the controller.
  2055  	cinder, err := e.cinderProvider()
  2056  	if err == nil {
  2057  		volumes, err := controllerCinderVolumes(cinder.storageAdapter, controllerUUID)
  2058  		if err != nil {
  2059  			handleCredentialError(err, ctx)
  2060  			return errors.Annotate(err, "listing volumes")
  2061  		}
  2062  		volIds := volumeInfoToVolumeIds(cinderToJujuVolumeInfos(volumes))
  2063  		errs := foreachVolume(ctx, cinder.storageAdapter, volIds, destroyVolume)
  2064  		for i, err := range errs {
  2065  			if err == nil {
  2066  				continue
  2067  			}
  2068  			handleCredentialError(err, ctx)
  2069  			return errors.Annotatef(err, "destroying volume %q", volIds[i])
  2070  		}
  2071  	} else if !errors.IsNotSupported(err) {
  2072  		handleCredentialError(err, ctx)
  2073  		return errors.Trace(err)
  2074  	}
  2075  
  2076  	// Security groups for hosted models are destroyed by the
  2077  	// DeleteAllControllerGroups method call from Destroy().
  2078  	return nil
  2079  }
  2080  
  2081  func resourceName(namespace instance.Namespace, envName, resourceId string) string {
  2082  	return namespace.Value(envName + "-" + resourceId)
  2083  }
  2084  
  2085  // jujuMachineFilter returns a nova.Filter matching machines created by Juju.
  2086  // The machines are not filtered to any particular environment. To do that,
  2087  // instance tags must be compared.
  2088  func jujuMachineFilter() *nova.Filter {
  2089  	filter := nova.NewFilter()
  2090  	filter.Set(nova.FilterServer, "juju-.*")
  2091  	return filter
  2092  }
  2093  
  2094  // rulesToRuleInfo maps ingress rules to nova rules
  2095  func rulesToRuleInfo(groupId string, rules firewall.IngressRules) []neutron.RuleInfoV2 {
  2096  	var result []neutron.RuleInfoV2
  2097  	for _, r := range rules {
  2098  		ruleInfo := neutron.RuleInfoV2{
  2099  			Direction:     "ingress",
  2100  			ParentGroupId: groupId,
  2101  			IPProtocol:    r.PortRange.Protocol,
  2102  		}
  2103  		if ruleInfo.IPProtocol != "icmp" {
  2104  			ruleInfo.PortRangeMin = r.PortRange.FromPort
  2105  			ruleInfo.PortRangeMax = r.PortRange.ToPort
  2106  		}
  2107  		sourceCIDRs := r.SourceCIDRs.Values()
  2108  		if len(sourceCIDRs) == 0 {
  2109  			sourceCIDRs = append(sourceCIDRs, firewall.AllNetworksIPV4CIDR, firewall.AllNetworksIPV6CIDR)
  2110  		}
  2111  		for _, sr := range sourceCIDRs {
  2112  			addrType, _ := network.CIDRAddressType(sr)
  2113  			if addrType == network.IPv4Address {
  2114  				ruleInfo.EthernetType = "IPv4"
  2115  			} else if addrType == network.IPv6Address {
  2116  				ruleInfo.EthernetType = "IPv6"
  2117  			} else {
  2118  				// Should never happen; ignore CIDR
  2119  				continue
  2120  			}
  2121  			ruleInfo.RemoteIPPrefix = sr
  2122  			result = append(result, ruleInfo)
  2123  		}
  2124  	}
  2125  	return result
  2126  }
  2127  
  2128  func (e *Environ) OpenPorts(ctx context.ProviderCallContext, rules firewall.IngressRules) error {
  2129  	if err := e.firewaller.OpenPorts(ctx, rules); err != nil {
  2130  		handleCredentialError(err, ctx)
  2131  		return errors.Trace(err)
  2132  	}
  2133  	return nil
  2134  }
  2135  
  2136  func (e *Environ) ClosePorts(ctx context.ProviderCallContext, rules firewall.IngressRules) error {
  2137  	if err := e.firewaller.ClosePorts(ctx, rules); err != nil {
  2138  		handleCredentialError(err, ctx)
  2139  		return errors.Trace(err)
  2140  	}
  2141  	return nil
  2142  }
  2143  
  2144  func (e *Environ) IngressRules(ctx context.ProviderCallContext) (firewall.IngressRules, error) {
  2145  	rules, err := e.firewaller.IngressRules(ctx)
  2146  	if err != nil {
  2147  		handleCredentialError(err, ctx)
  2148  		return rules, errors.Trace(err)
  2149  	}
  2150  	return rules, nil
  2151  }
  2152  
  2153  func (e *Environ) OpenModelPorts(ctx context.ProviderCallContext, rules firewall.IngressRules) error {
  2154  	if err := e.firewaller.OpenModelPorts(ctx, rules); err != nil {
  2155  		handleCredentialError(err, ctx)
  2156  		return errors.Trace(err)
  2157  	}
  2158  	return nil
  2159  }
  2160  
  2161  func (e *Environ) CloseModelPorts(ctx context.ProviderCallContext, rules firewall.IngressRules) error {
  2162  	if err := e.firewaller.CloseModelPorts(ctx, rules); err != nil {
  2163  		handleCredentialError(err, ctx)
  2164  		return errors.Trace(err)
  2165  	}
  2166  	return nil
  2167  }
  2168  
  2169  func (e *Environ) ModelIngressRules(ctx context.ProviderCallContext) (firewall.IngressRules, error) {
  2170  	rules, err := e.firewaller.ModelIngressRules(ctx)
  2171  	if err != nil {
  2172  		handleCredentialError(err, ctx)
  2173  		return rules, errors.Trace(err)
  2174  	}
  2175  	return rules, nil
  2176  }
  2177  
  2178  func (e *Environ) Provider() environs.EnvironProvider {
  2179  	return providerInstance
  2180  }
  2181  
  2182  func (e *Environ) terminateInstances(ctx context.ProviderCallContext, ids []instance.Id) error {
  2183  	if len(ids) == 0 {
  2184  		return nil
  2185  	}
  2186  
  2187  	novaClient := e.nova()
  2188  
  2189  	// If in instance firewall mode, gather the security group names.
  2190  	securityGroupNames, err := e.firewaller.GetSecurityGroups(ctx, ids...)
  2191  	if err == environs.ErrNoInstances {
  2192  		return nil
  2193  	}
  2194  	if err != nil {
  2195  		logger.Debugf("error retrieving security groups for %v: %v", ids, err)
  2196  		if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied {
  2197  			// We'll likely fail all subsequent calls if we have an invalid credential.
  2198  			return errors.Trace(err)
  2199  		}
  2200  	}
  2201  
  2202  	// Record the first error we encounter, as that's the one we're currently
  2203  	// reporting to the user.
  2204  	var firstErr error
  2205  	for _, id := range ids {
  2206  		// Attempt to destroy the ports that could have been created when using
  2207  		// spaces.
  2208  		if err := e.terminateInstanceNetworkPorts(id); err != nil {
  2209  			logger.Errorf("error attempting to remove ports associated with instance %q: %v", id, err)
  2210  			// Unfortunately there is nothing we can do here, there could be
  2211  			// orphan ports left.
  2212  		}
  2213  
  2214  		// Once ports have been deleted, attempt to delete the server.
  2215  		err = novaClient.DeleteServer(string(id))
  2216  		if IsNotFoundError(err) {
  2217  			continue
  2218  		}
  2219  		if err != nil {
  2220  			logger.Debugf("error terminating instance %q: %v", id, err)
  2221  			if firstErr == nil {
  2222  				firstErr = err
  2223  			}
  2224  			if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied {
  2225  				// We'll likely fail all subsequent calls if we have an invalid credential.
  2226  				return errors.Trace(err)
  2227  			}
  2228  		}
  2229  	}
  2230  
  2231  	if len(securityGroupNames) > 0 {
  2232  		logger.Tracef("deleting security groups %v", securityGroupNames)
  2233  		if err := e.firewaller.DeleteGroups(ctx, securityGroupNames...); err != nil {
  2234  			return err
  2235  		}
  2236  	}
  2237  
  2238  	return firstErr
  2239  }
  2240  
  2241  func (e *Environ) terminateInstanceNetworkPorts(id instance.Id) error {
  2242  	novaClient := e.nova()
  2243  	osInterfaces, err := novaClient.ListOSInterfaces(string(id))
  2244  	if err != nil {
  2245  		return errors.Trace(err)
  2246  	}
  2247  
  2248  	client := e.neutron()
  2249  	ports, err := client.ListPortsV2()
  2250  	if err != nil {
  2251  		return errors.Trace(err)
  2252  	}
  2253  
  2254  	// Unfortunately we're unable to bulk delete these ports, so we have to go
  2255  	// over them, one by one.
  2256  	changes := set.NewStrings()
  2257  	for _, port := range ports {
  2258  		if !strings.HasPrefix(port.Name, fmt.Sprintf("juju-%s", e.uuid)) {
  2259  			continue
  2260  		}
  2261  		changes.Add(port.Id)
  2262  	}
  2263  
  2264  	for _, osInterface := range osInterfaces {
  2265  		if osInterface.PortID == "" {
  2266  			continue
  2267  		}
  2268  
  2269  		// Ensure we created the port by first checking the name.
  2270  		port, err := client.PortByIdV2(osInterface.PortID)
  2271  		if err != nil || !strings.HasPrefix(port.Name, "juju-") {
  2272  			continue
  2273  		}
  2274  
  2275  		changes.Add(osInterface.PortID)
  2276  	}
  2277  
  2278  	var errs []error
  2279  	for _, change := range changes.SortedValues() {
  2280  		// Delete a port. If we encounter an error add it to the list of errors
  2281  		// and continue until we've exhausted all the ports to delete.
  2282  		if err := client.DeletePortV2(change); err != nil {
  2283  			errs = append(errs, err)
  2284  			continue
  2285  		}
  2286  	}
  2287  
  2288  	if len(errs) > 0 {
  2289  		return errors.Errorf("error terminating network ports: %v", errs)
  2290  	}
  2291  
  2292  	return nil
  2293  }
  2294  
  2295  // AgentMetadataLookupParams returns parameters which are used to query agent simple-streams metadata.
  2296  func (e *Environ) AgentMetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
  2297  	base := config.PreferredBase(e.ecfg())
  2298  	return e.metadataLookupParams(region, base.OS)
  2299  }
  2300  
  2301  // ImageMetadataLookupParams returns parameters which are used to query image simple-streams metadata.
  2302  func (e *Environ) ImageMetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
  2303  	base := config.PreferredBase(e.ecfg())
  2304  	release, err := imagemetadata.ImageRelease(base)
  2305  	if err != nil {
  2306  		return nil, errors.Trace(err)
  2307  	}
  2308  	return e.metadataLookupParams(region, release)
  2309  }
  2310  
  2311  // MetadataLookupParams returns parameters which are used to query simple-streams metadata.
  2312  func (e *Environ) metadataLookupParams(region, release string) (*simplestreams.MetadataLookupParams, error) {
  2313  	if region == "" {
  2314  		region = e.cloud().Region
  2315  	}
  2316  	cloudSpec, err := e.cloudSpec(region)
  2317  	if err != nil {
  2318  		return nil, err
  2319  	}
  2320  	return &simplestreams.MetadataLookupParams{
  2321  		Release:  release,
  2322  		Region:   cloudSpec.Region,
  2323  		Endpoint: cloudSpec.Endpoint,
  2324  	}, nil
  2325  }
  2326  
  2327  // Region is specified in the HasRegion interface.
  2328  func (e *Environ) Region() (simplestreams.CloudSpec, error) {
  2329  	return e.cloudSpec(e.cloud().Region)
  2330  }
  2331  
  2332  func (e *Environ) cloudSpec(region string) (simplestreams.CloudSpec, error) {
  2333  	return simplestreams.CloudSpec{
  2334  		Region:   region,
  2335  		Endpoint: e.cloud().Endpoint,
  2336  	}, nil
  2337  }
  2338  
  2339  // TagInstance implements environs.InstanceTagger.
  2340  func (e *Environ) TagInstance(ctx context.ProviderCallContext, id instance.Id, tags map[string]string) error {
  2341  	if err := e.nova().SetServerMetadata(string(id), tags); err != nil {
  2342  		handleCredentialError(err, ctx)
  2343  		return errors.Annotate(err, "setting server metadata")
  2344  	}
  2345  	return nil
  2346  }
  2347  
  2348  func (e *Environ) SetClock(clock clock.Clock) {
  2349  	e.clock = clock
  2350  }
  2351  
  2352  func validateCloudSpec(spec environscloudspec.CloudSpec) error {
  2353  	if err := spec.Validate(); err != nil {
  2354  		return errors.Trace(err)
  2355  	}
  2356  	if err := validateAuthURL(spec.Endpoint); err != nil {
  2357  		return errors.Annotate(err, "validating auth-url")
  2358  	}
  2359  	if spec.Credential == nil {
  2360  		return errors.NotValidf("missing credential")
  2361  	}
  2362  	switch authType := spec.Credential.AuthType(); authType {
  2363  	case cloud.UserPassAuthType:
  2364  	case cloud.AccessKeyAuthType:
  2365  	default:
  2366  		return errors.NotSupportedf("%q auth-type", authType)
  2367  	}
  2368  	return nil
  2369  }
  2370  
  2371  func validateAuthURL(authURL string) error {
  2372  	parts, err := url.Parse(authURL)
  2373  	if err != nil || parts.Host == "" || parts.Scheme == "" {
  2374  		return errors.NotValidf("auth-url %q", authURL)
  2375  	}
  2376  	return nil
  2377  }
  2378  
  2379  // Subnets is specified on environs.Networking.
  2380  func (e *Environ) Subnets(
  2381  	ctx context.ProviderCallContext, instId instance.Id, subnetIds []network.Id,
  2382  ) ([]network.SubnetInfo, error) {
  2383  	subnets, err := e.networking.Subnets(instId, subnetIds)
  2384  	if err != nil {
  2385  		handleCredentialError(err, ctx)
  2386  		return subnets, errors.Trace(err)
  2387  	}
  2388  	return subnets, nil
  2389  }
  2390  
  2391  // NetworkInterfaces is specified on environs.Networking.
  2392  func (e *Environ) NetworkInterfaces(ctx context.ProviderCallContext, ids []instance.Id) ([]network.InterfaceInfos, error) {
  2393  	infos, err := e.networking.NetworkInterfaces(ids)
  2394  	if err != nil {
  2395  		handleCredentialError(err, ctx)
  2396  		return infos, errors.Trace(err)
  2397  	}
  2398  
  2399  	return infos, nil
  2400  }
  2401  
  2402  // SupportsSpaces is specified on environs.Networking.
  2403  func (e *Environ) SupportsSpaces(context.ProviderCallContext) (bool, error) {
  2404  	return true, nil
  2405  }
  2406  
  2407  // SupportsContainerAddresses is specified on environs.Networking.
  2408  func (e *Environ) SupportsContainerAddresses(context.ProviderCallContext) (bool, error) {
  2409  	return false, errors.NotSupportedf("container address")
  2410  }
  2411  
  2412  // SuperSubnets is specified on environs.Networking
  2413  func (e *Environ) SuperSubnets(ctx context.ProviderCallContext) ([]string, error) {
  2414  	subnets, err := e.networking.Subnets("", nil)
  2415  	if err != nil {
  2416  		handleCredentialError(err, ctx)
  2417  		return nil, err
  2418  	}
  2419  	cidrs := make([]string, len(subnets))
  2420  	for i, subnet := range subnets {
  2421  		cidrs[i] = subnet.CIDR
  2422  	}
  2423  	return cidrs, nil
  2424  }
  2425  
  2426  // AllocateContainerAddresses is specified on environs.Networking.
  2427  func (e *Environ) AllocateContainerAddresses(ctx context.ProviderCallContext, hostInstanceID instance.Id, containerTag names.MachineTag, preparedInfo network.InterfaceInfos) (network.InterfaceInfos, error) {
  2428  	return nil, errors.NotSupportedf("allocate container address")
  2429  }
  2430  
  2431  // ReleaseContainerAddresses is specified on environs.Networking.
  2432  func (e *Environ) ReleaseContainerAddresses(ctx context.ProviderCallContext, interfaces []network.ProviderInterfaceInfo) error {
  2433  	return errors.NotSupportedf("release container address")
  2434  }
  2435  
  2436  // AreSpacesRoutable is specified on environs.NetworkingEnviron.
  2437  func (*Environ) AreSpacesRoutable(ctx context.ProviderCallContext, space1, space2 *environs.ProviderSpaceInfo) (bool, error) {
  2438  	return false, nil
  2439  }
  2440  
  2441  // SupportsRulesWithIPV6CIDRs returns true if the environment supports ingress
  2442  // rules containing IPV6 CIDRs. It is part of the FirewallFeatureQuerier
  2443  // interface.
  2444  func (e *Environ) SupportsRulesWithIPV6CIDRs(ctx context.ProviderCallContext) (bool, error) {
  2445  	return true, nil
  2446  }
  2447  
  2448  // ValidateCloudEndpoint returns nil if the current model can talk to the openstack
  2449  // endpoint. Used as validation during model upgrades.
  2450  // Implements environs.CloudEndpointChecker
  2451  func (env *Environ) ValidateCloudEndpoint(ctx context.ProviderCallContext) error {
  2452  	err := env.Provider().Ping(ctx, env.cloud().Endpoint)
  2453  	return errors.Trace(err)
  2454  }