github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/provider/openstack/provider.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Stub provider for OpenStack, using goose will be implemented here
     5  
     6  package openstack
     7  
     8  import (
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"regexp"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	jujuerrors "github.com/juju/errors"
    18  	"github.com/juju/loggo"
    19  	"github.com/juju/names"
    20  	"github.com/juju/utils"
    21  	"launchpad.net/goose/client"
    22  	gooseerrors "launchpad.net/goose/errors"
    23  	"launchpad.net/goose/identity"
    24  	"launchpad.net/goose/nova"
    25  	"launchpad.net/goose/swift"
    26  
    27  	"github.com/juju/juju/constraints"
    28  	"github.com/juju/juju/environs"
    29  	"github.com/juju/juju/environs/config"
    30  	"github.com/juju/juju/environs/imagemetadata"
    31  	"github.com/juju/juju/environs/instances"
    32  	"github.com/juju/juju/environs/simplestreams"
    33  	"github.com/juju/juju/environs/storage"
    34  	envtools "github.com/juju/juju/environs/tools"
    35  	"github.com/juju/juju/instance"
    36  	"github.com/juju/juju/juju/arch"
    37  	"github.com/juju/juju/network"
    38  	"github.com/juju/juju/provider/common"
    39  	"github.com/juju/juju/state"
    40  	"github.com/juju/juju/state/multiwatcher"
    41  	"github.com/juju/juju/tools"
    42  )
    43  
    44  var logger = loggo.GetLogger("juju.provider.openstack")
    45  
    46  type environProvider struct{}
    47  
    48  var _ environs.EnvironProvider = (*environProvider)(nil)
    49  
    50  var providerInstance environProvider
    51  
    52  var makeServiceURL = client.AuthenticatingClient.MakeServiceURL
    53  
    54  // Use shortAttempt to poll for short-term events.
    55  // TODO: This was kept to a long timeout because Nova needs more time than EC2.
    56  // For example, HP Cloud takes around 9.1 seconds (10 samples) to return a
    57  // BUILD(spawning) status. But storage delays are handled separately now, and
    58  // perhaps other polling attempts can time out faster.
    59  var shortAttempt = utils.AttemptStrategy{
    60  	Total: 15 * time.Second,
    61  	Delay: 200 * time.Millisecond,
    62  }
    63  
    64  func init() {
    65  	environs.RegisterProvider("openstack", environProvider{})
    66  	environs.RegisterImageDataSourceFunc("keystone catalog", getKeystoneImageSource)
    67  	envtools.RegisterToolsDataSourceFunc("keystone catalog", getKeystoneToolsSource)
    68  }
    69  
    70  func (p environProvider) BoilerplateConfig() string {
    71  	return `
    72  # https://juju.ubuntu.com/docs/config-openstack.html
    73  openstack:
    74      type: openstack
    75  
    76      # use-floating-ip specifies whether a floating IP address is
    77      # required to give the nodes a public IP address. Some
    78      # installations assign public IP addresses by default without
    79      # requiring a floating IP address.
    80      #
    81      # use-floating-ip: false
    82  
    83      # use-default-secgroup specifies whether new machine instances
    84      # should have the "default" Openstack security group assigned.
    85      #
    86      # use-default-secgroup: false
    87  
    88      # network specifies the network label or uuid to bring machines up
    89      # on, in the case where multiple networks exist. It may be omitted
    90      # otherwise.
    91      #
    92      # network: <your network label or uuid>
    93  
    94      # agent-metadata-url specifies the location of the Juju tools and
    95      # metadata. It defaults to the global public tools metadata
    96      # location https://streams.canonical.com/tools.
    97      #
    98      # agent-metadata-url:  https://your-agent-metadata-url
    99  
   100      # image-metadata-url specifies the location of Ubuntu cloud image
   101      # metadata. It defaults to the global public image metadata
   102      # location https://cloud-images.ubuntu.com/releases.
   103      #
   104      # image-metadata-url:  https://your-image-metadata-url
   105  
   106      # image-stream chooses a simplestreams stream from which to select
   107      # OS images, for example daily or released images (or any other stream
   108      # available on simplestreams).
   109      #
   110      # image-stream: "released"
   111  
   112      # agent-stream chooses a simplestreams stream from which to select tools,
   113      # for example released or proposed tools (or any other stream available
   114      # on simplestreams).
   115      #
   116      # agent-stream: "released"
   117  
   118      # auth-url defaults to the value of the environment variable
   119      # OS_AUTH_URL, but can be specified here.
   120      #
   121      # auth-url: https://yourkeystoneurl:443/v2.0/
   122  
   123      # tenant-name holds the openstack tenant name. It defaults to the
   124      # environment variable OS_TENANT_NAME.
   125      #
   126      # tenant-name: <your tenant name>
   127  
   128      # region holds the openstack region. It defaults to the
   129      # environment variable OS_REGION_NAME.
   130      #
   131      # region: <your region>
   132  
   133      # The auth-mode, username and password attributes are used for
   134      # userpass authentication (the default).
   135      #
   136      # auth-mode holds the authentication mode. For user-password
   137      # authentication, auth-mode should be "userpass" and username and
   138      # password should be set appropriately; they default to the
   139      # environment variables OS_USERNAME and OS_PASSWORD respectively.
   140      #
   141      # auth-mode: userpass
   142      # username: <your username>
   143      # password: <secret>
   144  
   145      # For key-pair authentication, auth-mode should be "keypair" and
   146      # access-key and secret-key should be set appropriately; they
   147      # default to the environment variables OS_ACCESS_KEY and
   148      # OS_SECRET_KEY respectively.
   149      #
   150      # auth-mode: keypair
   151      # access-key: <secret>
   152      # secret-key: <secret>
   153  
   154      # Whether or not to refresh the list of available updates for an
   155      # OS. The default option of true is recommended for use in
   156      # production systems, but disabling this can speed up local
   157      # deployments for development or testing.
   158      #
   159      # enable-os-refresh-update: true
   160  
   161      # Whether or not to perform OS upgrades when machines are
   162      # provisioned. The default option of true is recommended for use
   163      # in production systems, but disabling this can speed up local
   164      # deployments for development or testing.
   165      #
   166      # enable-os-upgrade: true
   167  
   168  # https://juju.ubuntu.com/docs/config-hpcloud.html
   169  hpcloud:
   170      type: openstack
   171  
   172      # use-floating-ip specifies whether a floating IP address is
   173      # required to give the nodes a public IP address. Some
   174      # installations assign public IP addresses by default without
   175      # requiring a floating IP address.
   176      #
   177      # use-floating-ip: true
   178  
   179      # use-default-secgroup specifies whether new machine instances
   180      # should have the "default" Openstack security group assigned.
   181      #
   182      # use-default-secgroup: false
   183  
   184      # tenant-name holds the openstack tenant name. In HPCloud, this is
   185      # synonymous with the project-name It defaults to the environment
   186      # variable OS_TENANT_NAME.
   187      #
   188      # tenant-name: <your tenant name>
   189  
   190      # image-stream chooses a simplestreams stream from which to select
   191      # OS images, for example daily or released images (or any other stream
   192      # available on simplestreams).
   193      #
   194      # image-stream: "released"
   195  
   196      # agent-stream chooses a simplestreams stream from which to select tools,
   197      # for example released or proposed tools (or any other stream available
   198      # on simplestreams).
   199      #
   200      # agent-stream: "released"
   201  
   202      # auth-url holds the keystone url for authentication. It defaults
   203      # to the value of the environment variable OS_AUTH_URL.
   204      #
   205      # auth-url: https://region-a.geo-1.identity.hpcloudsvc.com:35357/v2.0/
   206  
   207      # region holds the HP Cloud region (e.g. region-a.geo-1). It
   208      # defaults to the environment variable OS_REGION_NAME.
   209      #
   210      # region: <your region>
   211  
   212      # auth-mode holds the authentication mode. For user-password
   213      # authentication, auth-mode should be "userpass" and username and
   214      # password should be set appropriately; they default to the
   215      # environment variables OS_USERNAME and OS_PASSWORD respectively.
   216      #
   217      # auth-mode: userpass
   218      # username: <your_username>
   219      # password: <your_password>
   220  
   221      # For key-pair authentication, auth-mode should be "keypair" and
   222      # access-key and secret-key should be set appropriately; they
   223      # default to the environment variables OS_ACCESS_KEY and
   224      # OS_SECRET_KEY respectively.
   225      #
   226      # auth-mode: keypair
   227      # access-key: <secret>
   228      # secret-key: <secret>
   229  
   230      # Whether or not to refresh the list of available updates for an
   231      # OS. The default option of true is recommended for use in
   232      # production systems, but disabling this can speed up local
   233      # deployments for development or testing.
   234      #
   235      # enable-os-refresh-update: true
   236  
   237      # Whether or not to perform OS upgrades when machines are
   238      # provisioned. The default option of true is recommended for use
   239      # in production systems, but disabling this can speed up local
   240      # deployments for development or testing.
   241      #
   242      # enable-os-upgrade: true
   243  
   244  `[1:]
   245  }
   246  
   247  func (p environProvider) Open(cfg *config.Config) (environs.Environ, error) {
   248  	logger.Infof("opening environment %q", cfg.Name())
   249  	e := new(environ)
   250  	err := e.SetConfig(cfg)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  	e.name = cfg.Name()
   255  	return e, nil
   256  }
   257  
   258  // RestrictedConfigAttributes is specified in the EnvironProvider interface.
   259  func (p environProvider) RestrictedConfigAttributes() []string {
   260  	return []string{"region", "auth-url", "auth-mode"}
   261  }
   262  
   263  // PrepareForCreateEnvironment is specified in the EnvironProvider interface.
   264  func (p environProvider) PrepareForCreateEnvironment(cfg *config.Config) (*config.Config, error) {
   265  	return nil, jujuerrors.NotImplementedf("PrepareForCreateEnvironment")
   266  }
   267  
   268  func (p environProvider) PrepareForBootstrap(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) {
   269  	attrs := cfg.UnknownAttrs()
   270  	if _, ok := attrs["control-bucket"]; !ok {
   271  		uuid, err := utils.NewUUID()
   272  		if err != nil {
   273  			return nil, err
   274  		}
   275  		attrs["control-bucket"] = fmt.Sprintf("%x", uuid.Raw())
   276  	}
   277  	cfg, err := cfg.Apply(attrs)
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  	e, err := p.Open(cfg)
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  	// Verify credentials.
   286  	if err := authenticateClient(e.(*environ)); err != nil {
   287  		return nil, err
   288  	}
   289  	return e, nil
   290  }
   291  
   292  // MetadataLookupParams returns parameters which are used to query image metadata to
   293  // find matching image information.
   294  func (p environProvider) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
   295  	if region == "" {
   296  		return nil, fmt.Errorf("region must be specified")
   297  	}
   298  	return &simplestreams.MetadataLookupParams{
   299  		Region:        region,
   300  		Architectures: arch.AllSupportedArches,
   301  	}, nil
   302  }
   303  
   304  func (p environProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) {
   305  	m := make(map[string]string)
   306  	ecfg, err := providerInstance.newConfig(cfg)
   307  	if err != nil {
   308  		return nil, err
   309  	}
   310  	m["username"] = ecfg.username()
   311  	m["password"] = ecfg.password()
   312  	m["tenant-name"] = ecfg.tenantName()
   313  	return m, nil
   314  }
   315  
   316  func retryGet(uri string) (data []byte, err error) {
   317  	for a := shortAttempt.Start(); a.Next(); {
   318  		var resp *http.Response
   319  		resp, err = http.Get(uri)
   320  		if err != nil {
   321  			continue
   322  		}
   323  		defer resp.Body.Close()
   324  		if resp.StatusCode != http.StatusOK {
   325  			err = fmt.Errorf("bad http response %v", resp.Status)
   326  			continue
   327  		}
   328  		var data []byte
   329  		data, err = ioutil.ReadAll(resp.Body)
   330  		if err != nil {
   331  			continue
   332  		}
   333  		return data, nil
   334  	}
   335  	if err != nil {
   336  		return nil, fmt.Errorf("cannot get %q: %v", uri, err)
   337  	}
   338  	return
   339  }
   340  
   341  type environ struct {
   342  	common.SupportsUnitPlacementPolicy
   343  
   344  	name string
   345  
   346  	// archMutex gates access to supportedArchitectures
   347  	archMutex sync.Mutex
   348  	// supportedArchitectures caches the architectures
   349  	// for which images can be instantiated.
   350  	supportedArchitectures []string
   351  
   352  	ecfgMutex       sync.Mutex
   353  	ecfgUnlocked    *environConfig
   354  	client          client.AuthenticatingClient
   355  	novaUnlocked    *nova.Client
   356  	storageUnlocked storage.Storage
   357  
   358  	// keystoneImageDataSource caches the result of getKeystoneImageSource.
   359  	keystoneImageDataSourceMutex sync.Mutex
   360  	keystoneImageDataSource      simplestreams.DataSource
   361  
   362  	// keystoneToolsDataSource caches the result of getKeystoneToolsSource.
   363  	keystoneToolsDataSourceMutex sync.Mutex
   364  	keystoneToolsDataSource      simplestreams.DataSource
   365  
   366  	availabilityZonesMutex sync.Mutex
   367  	availabilityZones      []common.AvailabilityZone
   368  }
   369  
   370  var _ environs.Environ = (*environ)(nil)
   371  var _ simplestreams.HasRegion = (*environ)(nil)
   372  var _ state.Prechecker = (*environ)(nil)
   373  var _ state.InstanceDistributor = (*environ)(nil)
   374  
   375  type openstackInstance struct {
   376  	e        *environ
   377  	instType *instances.InstanceType
   378  	arch     *string
   379  
   380  	mu           sync.Mutex
   381  	serverDetail *nova.ServerDetail
   382  	// floatingIP is non-nil iff use-floating-ip is true.
   383  	floatingIP *nova.FloatingIP
   384  }
   385  
   386  func (inst *openstackInstance) String() string {
   387  	return string(inst.Id())
   388  }
   389  
   390  var _ instance.Instance = (*openstackInstance)(nil)
   391  
   392  func (inst *openstackInstance) Refresh() error {
   393  	inst.mu.Lock()
   394  	defer inst.mu.Unlock()
   395  	server, err := inst.e.nova().GetServer(inst.serverDetail.Id)
   396  	if err != nil {
   397  		return err
   398  	}
   399  	inst.serverDetail = server
   400  	return nil
   401  }
   402  
   403  func (inst *openstackInstance) getServerDetail() *nova.ServerDetail {
   404  	inst.mu.Lock()
   405  	defer inst.mu.Unlock()
   406  	return inst.serverDetail
   407  }
   408  
   409  func (inst *openstackInstance) Id() instance.Id {
   410  	return instance.Id(inst.getServerDetail().Id)
   411  }
   412  
   413  func (inst *openstackInstance) Status() string {
   414  	return inst.getServerDetail().Status
   415  }
   416  
   417  func (inst *openstackInstance) hardwareCharacteristics() *instance.HardwareCharacteristics {
   418  	hc := &instance.HardwareCharacteristics{Arch: inst.arch}
   419  	if inst.instType != nil {
   420  		hc.Mem = &inst.instType.Mem
   421  		// openstack is special in that a 0-size root disk means that
   422  		// the root disk will result in an instance with a root disk
   423  		// the same size as the image that created it, so we just set
   424  		// the HardwareCharacteristics to nil to signal that we don't
   425  		// know what the correct size is.
   426  		if inst.instType.RootDisk == 0 {
   427  			hc.RootDisk = nil
   428  		} else {
   429  			hc.RootDisk = &inst.instType.RootDisk
   430  		}
   431  		hc.CpuCores = &inst.instType.CpuCores
   432  		hc.CpuPower = inst.instType.CpuPower
   433  		// tags not currently supported on openstack
   434  	}
   435  	hc.AvailabilityZone = &inst.serverDetail.AvailabilityZone
   436  	return hc
   437  }
   438  
   439  // getAddresses returns the existing server information on addresses,
   440  // but fetches the details over the api again if no addresses exist.
   441  func (inst *openstackInstance) getAddresses() (map[string][]nova.IPAddress, error) {
   442  	addrs := inst.getServerDetail().Addresses
   443  	if len(addrs) == 0 {
   444  		server, err := inst.e.nova().GetServer(string(inst.Id()))
   445  		if err != nil {
   446  			return nil, err
   447  		}
   448  		addrs = server.Addresses
   449  	}
   450  	return addrs, nil
   451  }
   452  
   453  // Addresses implements network.Addresses() returning generic address
   454  // details for the instances, and calling the openstack api if needed.
   455  func (inst *openstackInstance) Addresses() ([]network.Address, error) {
   456  	addresses, err := inst.getAddresses()
   457  	if err != nil {
   458  		return nil, err
   459  	}
   460  	var floatingIP string
   461  	if inst.floatingIP != nil && inst.floatingIP.IP != "" {
   462  		floatingIP = inst.floatingIP.IP
   463  		logger.Debugf("instance %v has floating IP address: %v", inst.Id(), floatingIP)
   464  	}
   465  	return convertNovaAddresses(floatingIP, addresses), nil
   466  }
   467  
   468  // convertNovaAddresses returns nova addresses in generic format
   469  func convertNovaAddresses(publicIP string, addresses map[string][]nova.IPAddress) []network.Address {
   470  	var machineAddresses []network.Address
   471  	if publicIP != "" {
   472  		publicAddr := network.NewAddress(publicIP, network.ScopePublic)
   473  		publicAddr.NetworkName = "public"
   474  		machineAddresses = append(machineAddresses, publicAddr)
   475  	}
   476  	// TODO(gz) Network ordering may be significant but is not preserved by
   477  	// the map, see lp:1188126 for example. That could potentially be fixed
   478  	// in goose, or left to be derived by other means.
   479  	for netName, ips := range addresses {
   480  		networkScope := network.ScopeUnknown
   481  		if netName == "public" {
   482  			networkScope = network.ScopePublic
   483  		}
   484  		for _, address := range ips {
   485  			// If this address has already been added as a floating IP, skip it.
   486  			if publicIP == address.Address {
   487  				continue
   488  			}
   489  			// Assume IPv4 unless specified otherwise
   490  			addrtype := network.IPv4Address
   491  			if address.Version == 6 {
   492  				addrtype = network.IPv6Address
   493  			}
   494  			machineAddr := network.NewAddress(address.Address, networkScope)
   495  			machineAddr.NetworkName = netName
   496  			if machineAddr.Type != addrtype {
   497  				logger.Warningf("derived address type %v, nova reports %v", machineAddr.Type, addrtype)
   498  			}
   499  			machineAddresses = append(machineAddresses, machineAddr)
   500  		}
   501  	}
   502  	return machineAddresses
   503  }
   504  
   505  // TODO: following 30 lines nearly verbatim from environs/ec2
   506  
   507  func (inst *openstackInstance) OpenPorts(machineId string, ports []network.PortRange) error {
   508  	if inst.e.Config().FirewallMode() != config.FwInstance {
   509  		return fmt.Errorf("invalid firewall mode %q for opening ports on instance",
   510  			inst.e.Config().FirewallMode())
   511  	}
   512  	name := inst.e.machineGroupName(machineId)
   513  	if err := inst.e.openPortsInGroup(name, ports); err != nil {
   514  		return err
   515  	}
   516  	logger.Infof("opened ports in security group %s: %v", name, ports)
   517  	return nil
   518  }
   519  
   520  func (inst *openstackInstance) ClosePorts(machineId string, ports []network.PortRange) error {
   521  	if inst.e.Config().FirewallMode() != config.FwInstance {
   522  		return fmt.Errorf("invalid firewall mode %q for closing ports on instance",
   523  			inst.e.Config().FirewallMode())
   524  	}
   525  	name := inst.e.machineGroupName(machineId)
   526  	if err := inst.e.closePortsInGroup(name, ports); err != nil {
   527  		return err
   528  	}
   529  	logger.Infof("closed ports in security group %s: %v", name, ports)
   530  	return nil
   531  }
   532  
   533  func (inst *openstackInstance) Ports(machineId string) ([]network.PortRange, error) {
   534  	if inst.e.Config().FirewallMode() != config.FwInstance {
   535  		return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from instance",
   536  			inst.e.Config().FirewallMode())
   537  	}
   538  	name := inst.e.machineGroupName(machineId)
   539  	portRanges, err := inst.e.portsInGroup(name)
   540  	if err != nil {
   541  		return nil, err
   542  	}
   543  	return portRanges, nil
   544  }
   545  
   546  func (e *environ) ecfg() *environConfig {
   547  	e.ecfgMutex.Lock()
   548  	ecfg := e.ecfgUnlocked
   549  	e.ecfgMutex.Unlock()
   550  	return ecfg
   551  }
   552  
   553  func (e *environ) nova() *nova.Client {
   554  	e.ecfgMutex.Lock()
   555  	nova := e.novaUnlocked
   556  	e.ecfgMutex.Unlock()
   557  	return nova
   558  }
   559  
   560  // SupportedArchitectures is specified on the EnvironCapability interface.
   561  func (e *environ) SupportedArchitectures() ([]string, error) {
   562  	e.archMutex.Lock()
   563  	defer e.archMutex.Unlock()
   564  	if e.supportedArchitectures != nil {
   565  		return e.supportedArchitectures, nil
   566  	}
   567  	// Create a filter to get all images from our region and for the correct stream.
   568  	cloudSpec, err := e.Region()
   569  	if err != nil {
   570  		return nil, err
   571  	}
   572  	imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
   573  		CloudSpec: cloudSpec,
   574  		Stream:    e.Config().ImageStream(),
   575  	})
   576  	e.supportedArchitectures, err = common.SupportedArchitectures(e, imageConstraint)
   577  	return e.supportedArchitectures, err
   578  }
   579  
   580  var unsupportedConstraints = []string{
   581  	constraints.Tags,
   582  	constraints.CpuPower,
   583  }
   584  
   585  // ConstraintsValidator is defined on the Environs interface.
   586  func (e *environ) ConstraintsValidator() (constraints.Validator, error) {
   587  	validator := constraints.NewValidator()
   588  	validator.RegisterConflicts(
   589  		[]string{constraints.InstanceType},
   590  		[]string{constraints.Mem, constraints.Arch, constraints.RootDisk, constraints.CpuCores})
   591  	validator.RegisterUnsupported(unsupportedConstraints)
   592  	supportedArches, err := e.SupportedArchitectures()
   593  	if err != nil {
   594  		return nil, err
   595  	}
   596  	validator.RegisterVocabulary(constraints.Arch, supportedArches)
   597  	novaClient := e.nova()
   598  	flavors, err := novaClient.ListFlavorsDetail()
   599  	if err != nil {
   600  		return nil, err
   601  	}
   602  	instTypeNames := make([]string, len(flavors))
   603  	for i, flavor := range flavors {
   604  		instTypeNames[i] = flavor.Name
   605  	}
   606  	validator.RegisterVocabulary(constraints.InstanceType, instTypeNames)
   607  	return validator, nil
   608  }
   609  
   610  var novaListAvailabilityZones = (*nova.Client).ListAvailabilityZones
   611  
   612  type openstackAvailabilityZone struct {
   613  	nova.AvailabilityZone
   614  }
   615  
   616  func (z *openstackAvailabilityZone) Name() string {
   617  	return z.AvailabilityZone.Name
   618  }
   619  
   620  func (z *openstackAvailabilityZone) Available() bool {
   621  	return z.AvailabilityZone.State.Available
   622  }
   623  
   624  // AvailabilityZones returns a slice of availability zones.
   625  func (e *environ) AvailabilityZones() ([]common.AvailabilityZone, error) {
   626  	e.availabilityZonesMutex.Lock()
   627  	defer e.availabilityZonesMutex.Unlock()
   628  	if e.availabilityZones == nil {
   629  		zones, err := novaListAvailabilityZones(e.nova())
   630  		if gooseerrors.IsNotImplemented(err) {
   631  			return nil, jujuerrors.NotImplementedf("availability zones")
   632  		}
   633  		if err != nil {
   634  			return nil, err
   635  		}
   636  		e.availabilityZones = make([]common.AvailabilityZone, len(zones))
   637  		for i, z := range zones {
   638  			e.availabilityZones[i] = &openstackAvailabilityZone{z}
   639  		}
   640  	}
   641  	return e.availabilityZones, nil
   642  }
   643  
   644  // InstanceAvailabilityZoneNames returns the availability zone names for each
   645  // of the specified instances.
   646  func (e *environ) InstanceAvailabilityZoneNames(ids []instance.Id) ([]string, error) {
   647  	instances, err := e.Instances(ids)
   648  	if err != nil && err != environs.ErrPartialInstances {
   649  		return nil, err
   650  	}
   651  	zones := make([]string, len(instances))
   652  	for i, inst := range instances {
   653  		if inst == nil {
   654  			continue
   655  		}
   656  		zones[i] = inst.(*openstackInstance).serverDetail.AvailabilityZone
   657  	}
   658  	return zones, err
   659  }
   660  
   661  type openstackPlacement struct {
   662  	availabilityZone nova.AvailabilityZone
   663  }
   664  
   665  func (e *environ) parsePlacement(placement string) (*openstackPlacement, error) {
   666  	pos := strings.IndexRune(placement, '=')
   667  	if pos == -1 {
   668  		return nil, fmt.Errorf("unknown placement directive: %v", placement)
   669  	}
   670  	switch key, value := placement[:pos], placement[pos+1:]; key {
   671  	case "zone":
   672  		availabilityZone := value
   673  		zones, err := e.AvailabilityZones()
   674  		if err != nil {
   675  			return nil, err
   676  		}
   677  		for _, z := range zones {
   678  			if z.Name() == availabilityZone {
   679  				return &openstackPlacement{
   680  					z.(*openstackAvailabilityZone).AvailabilityZone,
   681  				}, nil
   682  			}
   683  		}
   684  		return nil, fmt.Errorf("invalid availability zone %q", availabilityZone)
   685  	}
   686  	return nil, fmt.Errorf("unknown placement directive: %v", placement)
   687  }
   688  
   689  // PrecheckInstance is defined on the state.Prechecker interface.
   690  func (e *environ) PrecheckInstance(series string, cons constraints.Value, placement string) error {
   691  	if placement != "" {
   692  		if _, err := e.parsePlacement(placement); err != nil {
   693  			return err
   694  		}
   695  	}
   696  	if !cons.HasInstanceType() {
   697  		return nil
   698  	}
   699  	// Constraint has an instance-type constraint so let's see if it is valid.
   700  	novaClient := e.nova()
   701  	flavors, err := novaClient.ListFlavorsDetail()
   702  	if err != nil {
   703  		return err
   704  	}
   705  	for _, flavor := range flavors {
   706  		if flavor.Name == *cons.InstanceType {
   707  			return nil
   708  		}
   709  	}
   710  	return fmt.Errorf("invalid Openstack flavour %q specified", *cons.InstanceType)
   711  }
   712  
   713  func (e *environ) Storage() storage.Storage {
   714  	e.ecfgMutex.Lock()
   715  	stor := e.storageUnlocked
   716  	e.ecfgMutex.Unlock()
   717  	return stor
   718  }
   719  
   720  func (e *environ) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (arch, series string, _ environs.BootstrapFinalizer, _ error) {
   721  	// The client's authentication may have been reset when finding tools if the agent-version
   722  	// attribute was updated so we need to re-authenticate. This will be a no-op if already authenticated.
   723  	// An authenticated client is needed for the URL() call below.
   724  	if err := authenticateClient(e); err != nil {
   725  		return "", "", nil, err
   726  	}
   727  	return common.Bootstrap(ctx, e, args)
   728  }
   729  
   730  func (e *environ) StateServerInstances() ([]instance.Id, error) {
   731  	return common.ProviderStateInstances(e, e.Storage())
   732  }
   733  
   734  func (e *environ) Config() *config.Config {
   735  	return e.ecfg().Config
   736  }
   737  
   738  func (e *environ) authClient(ecfg *environConfig, authModeCfg AuthMode) client.AuthenticatingClient {
   739  	cred := &identity.Credentials{
   740  		User:       ecfg.username(),
   741  		Secrets:    ecfg.password(),
   742  		Region:     ecfg.region(),
   743  		TenantName: ecfg.tenantName(),
   744  		URL:        ecfg.authURL(),
   745  	}
   746  	// authModeCfg has already been validated so we know it's one of the values below.
   747  	var authMode identity.AuthMode
   748  	switch authModeCfg {
   749  	case AuthLegacy:
   750  		authMode = identity.AuthLegacy
   751  	case AuthUserPass:
   752  		authMode = identity.AuthUserPass
   753  	case AuthKeyPair:
   754  		authMode = identity.AuthKeyPair
   755  		cred.User = ecfg.accessKey()
   756  		cred.Secrets = ecfg.secretKey()
   757  	}
   758  	newClient := client.NewClient
   759  	if !ecfg.SSLHostnameVerification() {
   760  		newClient = client.NewNonValidatingClient
   761  	}
   762  	return newClient(cred, authMode, nil)
   763  }
   764  
   765  var authenticateClient = func(e *environ) error {
   766  	err := e.client.Authenticate()
   767  	if err != nil {
   768  		// Log the error in case there are any useful hints,
   769  		// but provide a readable and helpful error message
   770  		// to the user.
   771  		logger.Debugf("authentication failed: %v", err)
   772  		return jujuerrors.New(`authentication failed.
   773  
   774  Please ensure the credentials are correct. A common mistake is
   775  to specify the wrong tenant. Use the OpenStack "project" name
   776  for tenant-name in your environment configuration.`)
   777  	}
   778  	return nil
   779  }
   780  
   781  func (e *environ) SetConfig(cfg *config.Config) error {
   782  	ecfg, err := providerInstance.newConfig(cfg)
   783  	if err != nil {
   784  		return err
   785  	}
   786  	// At this point, the authentication method config value has been validated so we extract it's value here
   787  	// to avoid having to validate again each time when creating the OpenStack client.
   788  	var authModeCfg AuthMode
   789  	e.ecfgMutex.Lock()
   790  	defer e.ecfgMutex.Unlock()
   791  	authModeCfg = AuthMode(ecfg.authMode())
   792  	e.ecfgUnlocked = ecfg
   793  
   794  	e.client = e.authClient(ecfg, authModeCfg)
   795  
   796  	e.novaUnlocked = nova.New(e.client)
   797  
   798  	// create new control storage instance, existing instances continue
   799  	// to reference their existing configuration.
   800  	// public storage instance creation is deferred until needed since authenticated
   801  	// access to the identity service is required so that any juju-tools endpoint can be used.
   802  	e.storageUnlocked = &openstackstorage{
   803  		containerName: ecfg.controlBucket(),
   804  		// this is possibly just a hack - if the ACL is swift.Private,
   805  		// the machine won't be able to get the tools (401 error)
   806  		containerACL: swift.PublicRead,
   807  		swift:        swift.New(e.client)}
   808  	return nil
   809  }
   810  
   811  // getKeystoneImageSource is an imagemetadata.ImageDataSourceFunc that
   812  // returns a DataSource using the "product-streams" keystone URL.
   813  func getKeystoneImageSource(env environs.Environ) (simplestreams.DataSource, error) {
   814  	e, ok := env.(*environ)
   815  	if !ok {
   816  		return nil, jujuerrors.NotSupportedf("non-openstack environment")
   817  	}
   818  	return e.getKeystoneDataSource(&e.keystoneImageDataSourceMutex, &e.keystoneImageDataSource, "product-streams")
   819  }
   820  
   821  // getKeystoneToolsSource is a tools.ToolsDataSourceFunc that
   822  // returns a DataSource using the "juju-tools" keystone URL.
   823  func getKeystoneToolsSource(env environs.Environ) (simplestreams.DataSource, error) {
   824  	e, ok := env.(*environ)
   825  	if !ok {
   826  		return nil, jujuerrors.NotSupportedf("non-openstack environment")
   827  	}
   828  	return e.getKeystoneDataSource(&e.keystoneToolsDataSourceMutex, &e.keystoneToolsDataSource, "juju-tools")
   829  }
   830  
   831  func (e *environ) getKeystoneDataSource(mu *sync.Mutex, datasource *simplestreams.DataSource, keystoneName string) (simplestreams.DataSource, error) {
   832  	mu.Lock()
   833  	defer mu.Unlock()
   834  	if *datasource != nil {
   835  		return *datasource, nil
   836  	}
   837  	if !e.client.IsAuthenticated() {
   838  		if err := authenticateClient(e); err != nil {
   839  			return nil, err
   840  		}
   841  	}
   842  
   843  	url, err := makeServiceURL(e.client, keystoneName, nil)
   844  	if err != nil {
   845  		return nil, jujuerrors.NewNotSupported(err, fmt.Sprintf("cannot make service URL: %v", err))
   846  	}
   847  	verify := utils.VerifySSLHostnames
   848  	if !e.Config().SSLHostnameVerification() {
   849  		verify = utils.NoVerifySSLHostnames
   850  	}
   851  	*datasource = simplestreams.NewURLDataSource("keystone catalog", url, verify)
   852  	return *datasource, nil
   853  }
   854  
   855  // TODO(gz): Move this somewhere more reusable
   856  const uuidPattern = "^([a-fA-F0-9]{8})-([a-fA-f0-9]{4})-([1-5][a-fA-f0-9]{3})-([a-fA-f0-9]{4})-([a-fA-f0-9]{12})$"
   857  
   858  var uuidRegexp = regexp.MustCompile(uuidPattern)
   859  
   860  // resolveNetwork takes either a network id or label and returns a network id
   861  func (e *environ) resolveNetwork(networkName string) (string, error) {
   862  	if uuidRegexp.MatchString(networkName) {
   863  		// Network id supplied, assume valid as boot will fail if not
   864  		return networkName, nil
   865  	}
   866  	// Network label supplied, resolve to a network id
   867  	networks, err := e.nova().ListNetworks()
   868  	if err != nil {
   869  		return "", err
   870  	}
   871  	var networkIds = []string{}
   872  	for _, network := range networks {
   873  		if network.Label == networkName {
   874  			networkIds = append(networkIds, network.Id)
   875  		}
   876  	}
   877  	switch len(networkIds) {
   878  	case 1:
   879  		return networkIds[0], nil
   880  	case 0:
   881  		return "", fmt.Errorf("No networks exist with label %q", networkName)
   882  	}
   883  	return "", fmt.Errorf("Multiple networks with label %q: %v", networkName, networkIds)
   884  }
   885  
   886  // allocatePublicIP tries to find an available floating IP address, or
   887  // allocates a new one, returning it, or an error
   888  func (e *environ) allocatePublicIP() (*nova.FloatingIP, error) {
   889  	fips, err := e.nova().ListFloatingIPs()
   890  	if err != nil {
   891  		return nil, err
   892  	}
   893  	var newfip *nova.FloatingIP
   894  	for _, fip := range fips {
   895  		newfip = &fip
   896  		if fip.InstanceId != nil && *fip.InstanceId != "" {
   897  			// unavailable, skip
   898  			newfip = nil
   899  			continue
   900  		} else {
   901  			logger.Debugf("found unassigned public ip: %v", newfip.IP)
   902  			// unassigned, we can use it
   903  			return newfip, nil
   904  		}
   905  	}
   906  	if newfip == nil {
   907  		// allocate a new IP and use it
   908  		newfip, err = e.nova().AllocateFloatingIP()
   909  		if err != nil {
   910  			return nil, err
   911  		}
   912  		logger.Debugf("allocated new public IP: %v", newfip.IP)
   913  	}
   914  	return newfip, nil
   915  }
   916  
   917  // assignPublicIP tries to assign the given floating IP address to the
   918  // specified server, or returns an error.
   919  func (e *environ) assignPublicIP(fip *nova.FloatingIP, serverId string) (err error) {
   920  	if fip == nil {
   921  		return fmt.Errorf("cannot assign a nil public IP to %q", serverId)
   922  	}
   923  	if fip.InstanceId != nil && *fip.InstanceId == serverId {
   924  		// IP already assigned, nothing to do
   925  		return nil
   926  	}
   927  	// At startup nw_info is not yet cached so this may fail
   928  	// temporarily while the server is being built
   929  	for a := common.LongAttempt.Start(); a.Next(); {
   930  		err = e.nova().AddServerFloatingIP(serverId, fip.IP)
   931  		if err == nil {
   932  			return nil
   933  		}
   934  	}
   935  	return err
   936  }
   937  
   938  // DistributeInstances implements the state.InstanceDistributor policy.
   939  func (e *environ) DistributeInstances(candidates, distributionGroup []instance.Id) ([]instance.Id, error) {
   940  	return common.DistributeInstances(e, candidates, distributionGroup)
   941  }
   942  
   943  var availabilityZoneAllocations = common.AvailabilityZoneAllocations
   944  
   945  // StartInstance is specified in the InstanceBroker interface.
   946  func (e *environ) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) {
   947  	var availabilityZones []string
   948  	if args.Placement != "" {
   949  		placement, err := e.parsePlacement(args.Placement)
   950  		if err != nil {
   951  			return nil, err
   952  		}
   953  		if !placement.availabilityZone.State.Available {
   954  			return nil, fmt.Errorf("availability zone %q is unavailable", placement.availabilityZone.Name)
   955  		}
   956  		availabilityZones = append(availabilityZones, placement.availabilityZone.Name)
   957  	}
   958  
   959  	// If no availability zone is specified, then automatically spread across
   960  	// the known zones for optimal spread across the instance distribution
   961  	// group.
   962  	if len(availabilityZones) == 0 {
   963  		var group []instance.Id
   964  		var err error
   965  		if args.DistributionGroup != nil {
   966  			group, err = args.DistributionGroup()
   967  			if err != nil {
   968  				return nil, err
   969  			}
   970  		}
   971  		zoneInstances, err := availabilityZoneAllocations(e, group)
   972  		if jujuerrors.IsNotImplemented(err) {
   973  			// Availability zones are an extension, so we may get a
   974  			// not implemented error; ignore these.
   975  		} else if err != nil {
   976  			return nil, err
   977  		} else {
   978  			for _, zone := range zoneInstances {
   979  				availabilityZones = append(availabilityZones, zone.ZoneName)
   980  			}
   981  		}
   982  		if len(availabilityZones) == 0 {
   983  			// No explicitly selectable zones available, so use an unspecified zone.
   984  			availabilityZones = []string{""}
   985  		}
   986  	}
   987  
   988  	if args.MachineConfig.HasNetworks() {
   989  		return nil, fmt.Errorf("starting instances with networks is not supported yet.")
   990  	}
   991  
   992  	series := args.Tools.OneSeries()
   993  	arches := args.Tools.Arches()
   994  	spec, err := findInstanceSpec(e, &instances.InstanceConstraint{
   995  		Region:      e.ecfg().region(),
   996  		Series:      series,
   997  		Arches:      arches,
   998  		Constraints: args.Constraints,
   999  	})
  1000  	if err != nil {
  1001  		return nil, err
  1002  	}
  1003  	tools, err := args.Tools.Match(tools.Filter{Arch: spec.Image.Arch})
  1004  	if err != nil {
  1005  		return nil, fmt.Errorf("chosen architecture %v not present in %v", spec.Image.Arch, arches)
  1006  	}
  1007  
  1008  	args.MachineConfig.Tools = tools[0]
  1009  
  1010  	if err := environs.FinishMachineConfig(args.MachineConfig, e.Config()); err != nil {
  1011  		return nil, err
  1012  	}
  1013  	userData, err := environs.ComposeUserData(args.MachineConfig, nil)
  1014  	if err != nil {
  1015  		return nil, fmt.Errorf("cannot make user data: %v", err)
  1016  	}
  1017  	logger.Debugf("openstack user data; %d bytes", len(userData))
  1018  	var networks = []nova.ServerNetworks{}
  1019  	usingNetwork := e.ecfg().network()
  1020  	if usingNetwork != "" {
  1021  		networkId, err := e.resolveNetwork(usingNetwork)
  1022  		if err != nil {
  1023  			return nil, err
  1024  		}
  1025  		logger.Debugf("using network id %q", networkId)
  1026  		networks = append(networks, nova.ServerNetworks{NetworkId: networkId})
  1027  	}
  1028  	withPublicIP := e.ecfg().useFloatingIP()
  1029  	var publicIP *nova.FloatingIP
  1030  	if withPublicIP {
  1031  		logger.Debugf("allocating public IP address for openstack node")
  1032  		if fip, err := e.allocatePublicIP(); err != nil {
  1033  			return nil, fmt.Errorf("cannot allocate a public IP as needed: %v", err)
  1034  		} else {
  1035  			publicIP = fip
  1036  			logger.Infof("allocated public IP %s", publicIP.IP)
  1037  		}
  1038  	}
  1039  	cfg := e.Config()
  1040  	groups, err := e.setUpGroups(args.MachineConfig.MachineId, cfg.APIPort())
  1041  	if err != nil {
  1042  		return nil, fmt.Errorf("cannot set up groups: %v", err)
  1043  	}
  1044  	var groupNames = make([]nova.SecurityGroupName, len(groups))
  1045  	for i, g := range groups {
  1046  		groupNames[i] = nova.SecurityGroupName{g.Name}
  1047  	}
  1048  	var server *nova.Entity
  1049  	for _, availZone := range availabilityZones {
  1050  		var opts = nova.RunServerOpts{
  1051  			Name:               e.machineFullName(args.MachineConfig.MachineId),
  1052  			FlavorId:           spec.InstanceType.Id,
  1053  			ImageId:            spec.Image.Id,
  1054  			UserData:           userData,
  1055  			SecurityGroupNames: groupNames,
  1056  			Networks:           networks,
  1057  			AvailabilityZone:   availZone,
  1058  		}
  1059  		for a := shortAttempt.Start(); a.Next(); {
  1060  			server, err = e.nova().RunServer(opts)
  1061  			if err == nil || !gooseerrors.IsNotFound(err) {
  1062  				break
  1063  			}
  1064  		}
  1065  		if isNoValidHostsError(err) {
  1066  			logger.Infof("no valid hosts available in zone %q, trying another availability zone", availZone)
  1067  		} else {
  1068  			break
  1069  		}
  1070  	}
  1071  	if err != nil {
  1072  		return nil, fmt.Errorf("cannot run instance: %v", err)
  1073  	}
  1074  	detail, err := e.nova().GetServer(server.Id)
  1075  	if err != nil {
  1076  		return nil, fmt.Errorf("cannot get started instance: %v", err)
  1077  	}
  1078  	inst := &openstackInstance{
  1079  		e:            e,
  1080  		serverDetail: detail,
  1081  		arch:         &spec.Image.Arch,
  1082  		instType:     &spec.InstanceType,
  1083  	}
  1084  	logger.Infof("started instance %q", inst.Id())
  1085  	if withPublicIP {
  1086  		if err := e.assignPublicIP(publicIP, string(inst.Id())); err != nil {
  1087  			if err := e.terminateInstances([]instance.Id{inst.Id()}); err != nil {
  1088  				// ignore the failure at this stage, just log it
  1089  				logger.Debugf("failed to terminate instance %q: %v", inst.Id(), err)
  1090  			}
  1091  			return nil, fmt.Errorf("cannot assign public address %s to instance %q: %v", publicIP.IP, inst.Id(), err)
  1092  		}
  1093  		inst.floatingIP = publicIP
  1094  		logger.Infof("assigned public IP %s to %q", publicIP.IP, inst.Id())
  1095  	}
  1096  	if multiwatcher.AnyJobNeedsState(args.MachineConfig.Jobs...) {
  1097  		if err := common.AddStateInstance(e.Storage(), inst.Id()); err != nil {
  1098  			logger.Errorf("could not record instance in provider-state: %v", err)
  1099  		}
  1100  	}
  1101  	return &environs.StartInstanceResult{
  1102  		Instance: inst,
  1103  		Hardware: inst.hardwareCharacteristics(),
  1104  	}, nil
  1105  }
  1106  
  1107  func isNoValidHostsError(err error) bool {
  1108  	gooseErr, ok := err.(gooseerrors.Error)
  1109  	return ok && strings.Contains(gooseErr.Cause().Error(), "No valid host was found")
  1110  }
  1111  
  1112  func (e *environ) StopInstances(ids ...instance.Id) error {
  1113  	// If in instance firewall mode, gather the security group names.
  1114  	var securityGroupNames []string
  1115  	if e.Config().FirewallMode() == config.FwInstance {
  1116  		instances, err := e.Instances(ids)
  1117  		if err == environs.ErrNoInstances {
  1118  			return nil
  1119  		}
  1120  		securityGroupNames = make([]string, 0, len(ids))
  1121  		for _, inst := range instances {
  1122  			if inst == nil {
  1123  				continue
  1124  			}
  1125  			openstackName := inst.(*openstackInstance).getServerDetail().Name
  1126  			lastDashPos := strings.LastIndex(openstackName, "-")
  1127  			if lastDashPos == -1 {
  1128  				return fmt.Errorf("cannot identify machine ID in openstack server name %q", openstackName)
  1129  			}
  1130  			securityGroupName := e.machineGroupName(openstackName[lastDashPos+1:])
  1131  			securityGroupNames = append(securityGroupNames, securityGroupName)
  1132  		}
  1133  	}
  1134  	logger.Debugf("terminating instances %v", ids)
  1135  	if err := e.terminateInstances(ids); err != nil {
  1136  		return err
  1137  	}
  1138  	if securityGroupNames != nil {
  1139  		return e.deleteSecurityGroups(securityGroupNames)
  1140  	}
  1141  	return common.RemoveStateInstances(e.Storage(), ids...)
  1142  }
  1143  
  1144  // collectInstances tries to get information on each instance id in ids.
  1145  // It fills the slots in the given map for known servers with status
  1146  // either ACTIVE or BUILD. Returns a list of missing ids.
  1147  func (e *environ) collectInstances(ids []instance.Id, out map[string]instance.Instance) []instance.Id {
  1148  	var err error
  1149  	serversById := make(map[string]nova.ServerDetail)
  1150  	if len(ids) == 1 {
  1151  		// most common case - single instance
  1152  		var server *nova.ServerDetail
  1153  		server, err = e.nova().GetServer(string(ids[0]))
  1154  		if server != nil {
  1155  			serversById[server.Id] = *server
  1156  		}
  1157  	} else {
  1158  		var servers []nova.ServerDetail
  1159  		servers, err = e.nova().ListServersDetail(e.machinesFilter())
  1160  		for _, server := range servers {
  1161  			serversById[server.Id] = server
  1162  		}
  1163  	}
  1164  	if err != nil {
  1165  		return ids
  1166  	}
  1167  	var missing []instance.Id
  1168  	for _, id := range ids {
  1169  		if server, found := serversById[string(id)]; found {
  1170  			// HPCloud uses "BUILD(spawning)" as an intermediate BUILD states once networking is available.
  1171  			switch server.Status {
  1172  			case nova.StatusActive, nova.StatusBuild, nova.StatusBuildSpawning, nova.StatusShutoff, nova.StatusSuspended:
  1173  				// TODO(wallyworld): lookup the flavor details to fill in the instance type data
  1174  				out[string(id)] = &openstackInstance{e: e, serverDetail: &server}
  1175  				continue
  1176  			}
  1177  		}
  1178  		missing = append(missing, id)
  1179  	}
  1180  	return missing
  1181  }
  1182  
  1183  // updateFloatingIPAddresses updates the instances with any floating IP address
  1184  // that have been assigned to those instances.
  1185  func (e *environ) updateFloatingIPAddresses(instances map[string]instance.Instance) error {
  1186  	fips, err := e.nova().ListFloatingIPs()
  1187  	if err != nil {
  1188  		return err
  1189  	}
  1190  	for _, fip := range fips {
  1191  		if fip.InstanceId != nil && *fip.InstanceId != "" {
  1192  			instId := *fip.InstanceId
  1193  			if inst, ok := instances[instId]; ok {
  1194  				instFip := fip
  1195  				inst.(*openstackInstance).floatingIP = &instFip
  1196  			}
  1197  		}
  1198  	}
  1199  	return nil
  1200  }
  1201  
  1202  func (e *environ) Instances(ids []instance.Id) ([]instance.Instance, error) {
  1203  	if len(ids) == 0 {
  1204  		return nil, nil
  1205  	}
  1206  	missing := ids
  1207  	found := make(map[string]instance.Instance)
  1208  	// Make a series of requests to cope with eventual consistency.
  1209  	// Each request will attempt to add more instances to the requested
  1210  	// set.
  1211  	for a := shortAttempt.Start(); a.Next(); {
  1212  		if missing = e.collectInstances(missing, found); len(missing) == 0 {
  1213  			break
  1214  		}
  1215  	}
  1216  	if len(found) == 0 {
  1217  		return nil, environs.ErrNoInstances
  1218  	}
  1219  
  1220  	// Update the instance structs with any floating IP address that has been assigned to the instance.
  1221  	if e.ecfg().useFloatingIP() {
  1222  		if err := e.updateFloatingIPAddresses(found); err != nil {
  1223  			return nil, err
  1224  		}
  1225  	}
  1226  
  1227  	insts := make([]instance.Instance, len(ids))
  1228  	var err error
  1229  	for i, id := range ids {
  1230  		if inst := found[string(id)]; inst != nil {
  1231  			insts[i] = inst
  1232  		} else {
  1233  			err = environs.ErrPartialInstances
  1234  		}
  1235  	}
  1236  	return insts, err
  1237  }
  1238  
  1239  func (e *environ) AllInstances() (insts []instance.Instance, err error) {
  1240  	servers, err := e.nova().ListServersDetail(e.machinesFilter())
  1241  	if err != nil {
  1242  		return nil, err
  1243  	}
  1244  	instsById := make(map[string]instance.Instance)
  1245  	for _, server := range servers {
  1246  		if server.Status == nova.StatusActive || server.Status == nova.StatusBuild {
  1247  			var s = server
  1248  			// TODO(wallyworld): lookup the flavor details to fill in the instance type data
  1249  			instsById[s.Id] = &openstackInstance{e: e, serverDetail: &s}
  1250  		}
  1251  	}
  1252  
  1253  	if e.ecfg().useFloatingIP() {
  1254  		if err := e.updateFloatingIPAddresses(instsById); err != nil {
  1255  			return nil, err
  1256  		}
  1257  	}
  1258  
  1259  	for _, inst := range instsById {
  1260  		insts = append(insts, inst)
  1261  	}
  1262  	return insts, err
  1263  }
  1264  
  1265  func (e *environ) Destroy() error {
  1266  	err := common.Destroy(e)
  1267  	if err != nil {
  1268  		return jujuerrors.Trace(err)
  1269  	}
  1270  	if err := e.Storage().RemoveAll(); err != nil {
  1271  		return jujuerrors.Trace(err)
  1272  	}
  1273  	novaClient := e.nova()
  1274  	securityGroups, err := novaClient.ListSecurityGroups()
  1275  	if err != nil {
  1276  		return jujuerrors.Annotate(err, "cannot list security groups")
  1277  	}
  1278  	re, err := regexp.Compile(fmt.Sprintf("^%s(-\\d+)?$", e.jujuGroupName()))
  1279  	if err != nil {
  1280  		return jujuerrors.Trace(err)
  1281  	}
  1282  	globalGroupName := e.globalGroupName()
  1283  	for _, group := range securityGroups {
  1284  		if re.MatchString(group.Name) || group.Name == globalGroupName {
  1285  			err = novaClient.DeleteSecurityGroup(group.Id)
  1286  			if err != nil {
  1287  				logger.Warningf("cannot delete security group %q. Used by another environment?", group.Name)
  1288  			}
  1289  		}
  1290  	}
  1291  	return nil
  1292  }
  1293  
  1294  func (e *environ) globalGroupName() string {
  1295  	return fmt.Sprintf("%s-global", e.jujuGroupName())
  1296  }
  1297  
  1298  func (e *environ) machineGroupName(machineId string) string {
  1299  	return fmt.Sprintf("%s-%s", e.jujuGroupName(), machineId)
  1300  }
  1301  
  1302  func (e *environ) jujuGroupName() string {
  1303  	return fmt.Sprintf("juju-%s", e.name)
  1304  }
  1305  
  1306  func (e *environ) machineFullName(machineId string) string {
  1307  	return fmt.Sprintf("juju-%s-%s", e.Config().Name(), names.NewMachineTag(machineId))
  1308  }
  1309  
  1310  // machinesFilter returns a nova.Filter matching all machines in the environment.
  1311  func (e *environ) machinesFilter() *nova.Filter {
  1312  	filter := nova.NewFilter()
  1313  	filter.Set(nova.FilterServer, fmt.Sprintf("juju-%s-machine-\\d*", e.Config().Name()))
  1314  	return filter
  1315  }
  1316  
  1317  // portsToRuleInfo maps port ranges to nova rules
  1318  func portsToRuleInfo(groupId string, ports []network.PortRange) []nova.RuleInfo {
  1319  	rules := make([]nova.RuleInfo, len(ports))
  1320  	for i, portRange := range ports {
  1321  		rules[i] = nova.RuleInfo{
  1322  			ParentGroupId: groupId,
  1323  			FromPort:      portRange.FromPort,
  1324  			ToPort:        portRange.ToPort,
  1325  			IPProtocol:    portRange.Protocol,
  1326  			Cidr:          "0.0.0.0/0",
  1327  		}
  1328  	}
  1329  	return rules
  1330  }
  1331  
  1332  func (e *environ) openPortsInGroup(name string, portRanges []network.PortRange) error {
  1333  	novaclient := e.nova()
  1334  	group, err := novaclient.SecurityGroupByName(name)
  1335  	if err != nil {
  1336  		return err
  1337  	}
  1338  	rules := portsToRuleInfo(group.Id, portRanges)
  1339  	for _, rule := range rules {
  1340  		_, err := novaclient.CreateSecurityGroupRule(rule)
  1341  		if err != nil {
  1342  			// TODO: if err is not rule already exists, raise?
  1343  			logger.Debugf("error creating security group rule: %v", err.Error())
  1344  		}
  1345  	}
  1346  	return nil
  1347  }
  1348  
  1349  // ruleMatchesPortRange checks if supplied nova security group rule matches the port range
  1350  func ruleMatchesPortRange(rule nova.SecurityGroupRule, portRange network.PortRange) bool {
  1351  	if rule.IPProtocol == nil || rule.FromPort == nil || rule.ToPort == nil {
  1352  		return false
  1353  	}
  1354  	return *rule.IPProtocol == portRange.Protocol &&
  1355  		*rule.FromPort == portRange.FromPort &&
  1356  		*rule.ToPort == portRange.ToPort
  1357  }
  1358  
  1359  func (e *environ) closePortsInGroup(name string, portRanges []network.PortRange) error {
  1360  	if len(portRanges) == 0 {
  1361  		return nil
  1362  	}
  1363  	novaclient := e.nova()
  1364  	group, err := novaclient.SecurityGroupByName(name)
  1365  	if err != nil {
  1366  		return err
  1367  	}
  1368  	// TODO: Hey look ma, it's quadratic
  1369  	for _, portRange := range portRanges {
  1370  		for _, p := range (*group).Rules {
  1371  			if !ruleMatchesPortRange(p, portRange) {
  1372  				continue
  1373  			}
  1374  			err := novaclient.DeleteSecurityGroupRule(p.Id)
  1375  			if err != nil {
  1376  				return err
  1377  			}
  1378  			break
  1379  		}
  1380  	}
  1381  	return nil
  1382  }
  1383  
  1384  func (e *environ) portsInGroup(name string) (portRanges []network.PortRange, err error) {
  1385  	group, err := e.nova().SecurityGroupByName(name)
  1386  	if err != nil {
  1387  		return nil, err
  1388  	}
  1389  	for _, p := range (*group).Rules {
  1390  		portRanges = append(portRanges, network.PortRange{
  1391  			Protocol: *p.IPProtocol,
  1392  			FromPort: *p.FromPort,
  1393  			ToPort:   *p.ToPort,
  1394  		})
  1395  	}
  1396  	network.SortPortRanges(portRanges)
  1397  	return portRanges, nil
  1398  }
  1399  
  1400  // TODO: following 30 lines nearly verbatim from environs/ec2
  1401  
  1402  func (e *environ) OpenPorts(ports []network.PortRange) error {
  1403  	if e.Config().FirewallMode() != config.FwGlobal {
  1404  		return fmt.Errorf("invalid firewall mode %q for opening ports on environment",
  1405  			e.Config().FirewallMode())
  1406  	}
  1407  	if err := e.openPortsInGroup(e.globalGroupName(), ports); err != nil {
  1408  		return err
  1409  	}
  1410  	logger.Infof("opened ports in global group: %v", ports)
  1411  	return nil
  1412  }
  1413  
  1414  func (e *environ) ClosePorts(ports []network.PortRange) error {
  1415  	if e.Config().FirewallMode() != config.FwGlobal {
  1416  		return fmt.Errorf("invalid firewall mode %q for closing ports on environment",
  1417  			e.Config().FirewallMode())
  1418  	}
  1419  	if err := e.closePortsInGroup(e.globalGroupName(), ports); err != nil {
  1420  		return err
  1421  	}
  1422  	logger.Infof("closed ports in global group: %v", ports)
  1423  	return nil
  1424  }
  1425  
  1426  func (e *environ) Ports() ([]network.PortRange, error) {
  1427  	if e.Config().FirewallMode() != config.FwGlobal {
  1428  		return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from environment",
  1429  			e.Config().FirewallMode())
  1430  	}
  1431  	return e.portsInGroup(e.globalGroupName())
  1432  }
  1433  
  1434  func (e *environ) Provider() environs.EnvironProvider {
  1435  	return &providerInstance
  1436  }
  1437  
  1438  func (e *environ) setUpGlobalGroup(groupName string, apiPort int) (nova.SecurityGroup, error) {
  1439  	return e.ensureGroup(groupName,
  1440  		[]nova.RuleInfo{
  1441  			{
  1442  				IPProtocol: "tcp",
  1443  				FromPort:   22,
  1444  				ToPort:     22,
  1445  				Cidr:       "0.0.0.0/0",
  1446  			},
  1447  			{
  1448  				IPProtocol: "tcp",
  1449  				FromPort:   apiPort,
  1450  				ToPort:     apiPort,
  1451  				Cidr:       "0.0.0.0/0",
  1452  			},
  1453  			{
  1454  				IPProtocol: "tcp",
  1455  				FromPort:   1,
  1456  				ToPort:     65535,
  1457  			},
  1458  			{
  1459  				IPProtocol: "udp",
  1460  				FromPort:   1,
  1461  				ToPort:     65535,
  1462  			},
  1463  			{
  1464  				IPProtocol: "icmp",
  1465  				FromPort:   -1,
  1466  				ToPort:     -1,
  1467  			},
  1468  		})
  1469  }
  1470  
  1471  // setUpGroups creates the security groups for the new machine, and
  1472  // returns them.
  1473  //
  1474  // Instances are tagged with a group so they can be distinguished from
  1475  // other instances that might be running on the same OpenStack account.
  1476  // In addition, a specific machine security group is created for each
  1477  // machine, so that its firewall rules can be configured per machine.
  1478  //
  1479  // Note: ideally we'd have a better way to determine group membership so that 2
  1480  // people that happen to share an openstack account and name their environment
  1481  // "openstack" don't end up destroying each other's machines.
  1482  func (e *environ) setUpGroups(machineId string, apiPort int) ([]nova.SecurityGroup, error) {
  1483  	jujuGroup, err := e.setUpGlobalGroup(e.jujuGroupName(), apiPort)
  1484  	if err != nil {
  1485  		return nil, err
  1486  	}
  1487  	var machineGroup nova.SecurityGroup
  1488  	switch e.Config().FirewallMode() {
  1489  	case config.FwInstance:
  1490  		machineGroup, err = e.ensureGroup(e.machineGroupName(machineId), nil)
  1491  	case config.FwGlobal:
  1492  		machineGroup, err = e.ensureGroup(e.globalGroupName(), nil)
  1493  	}
  1494  	if err != nil {
  1495  		return nil, err
  1496  	}
  1497  	groups := []nova.SecurityGroup{jujuGroup, machineGroup}
  1498  	if e.ecfg().useDefaultSecurityGroup() {
  1499  		defaultGroup, err := e.nova().SecurityGroupByName("default")
  1500  		if err != nil {
  1501  			return nil, fmt.Errorf("loading default security group: %v", err)
  1502  		}
  1503  		groups = append(groups, *defaultGroup)
  1504  	}
  1505  	return groups, nil
  1506  }
  1507  
  1508  // zeroGroup holds the zero security group.
  1509  var zeroGroup nova.SecurityGroup
  1510  
  1511  // ensureGroup returns the security group with name and perms.
  1512  // If a group with name does not exist, one will be created.
  1513  // If it exists, its permissions are set to perms.
  1514  func (e *environ) ensureGroup(name string, rules []nova.RuleInfo) (nova.SecurityGroup, error) {
  1515  	novaClient := e.nova()
  1516  	// First attempt to look up an existing group by name.
  1517  	group, err := novaClient.SecurityGroupByName(name)
  1518  	if err == nil {
  1519  		// Group exists, so assume it is correctly set up and return it.
  1520  		// TODO(jam): 2013-09-18 http://pad.lv/121795
  1521  		// We really should verify the group is set up correctly,
  1522  		// because deleting and re-creating environments can get us bad
  1523  		// groups (especially if they were set up under Python)
  1524  		return *group, nil
  1525  	}
  1526  	// Doesn't exist, so try and create it.
  1527  	group, err = novaClient.CreateSecurityGroup(name, "juju group")
  1528  	if err != nil {
  1529  		if !gooseerrors.IsDuplicateValue(err) {
  1530  			return zeroGroup, err
  1531  		} else {
  1532  			// We just tried to create a duplicate group, so load the existing group.
  1533  			group, err = novaClient.SecurityGroupByName(name)
  1534  			if err != nil {
  1535  				return zeroGroup, err
  1536  			}
  1537  			return *group, nil
  1538  		}
  1539  	}
  1540  	// The new group is created so now add the rules.
  1541  	group.Rules = make([]nova.SecurityGroupRule, len(rules))
  1542  	for i, rule := range rules {
  1543  		rule.ParentGroupId = group.Id
  1544  		if rule.Cidr == "" {
  1545  			// http://pad.lv/1226996 Rules that don't have a CIDR
  1546  			// are meant to apply only to this group. If you don't
  1547  			// supply CIDR or GroupId then openstack assumes you
  1548  			// mean CIDR=0.0.0.0/0
  1549  			rule.GroupId = &group.Id
  1550  		}
  1551  		groupRule, err := novaClient.CreateSecurityGroupRule(rule)
  1552  		if err != nil && !gooseerrors.IsDuplicateValue(err) {
  1553  			return zeroGroup, err
  1554  		}
  1555  		group.Rules[i] = *groupRule
  1556  	}
  1557  	return *group, nil
  1558  }
  1559  
  1560  // deleteSecurityGroups deletes the given security groups. If a security
  1561  // group is also used by another environment (see bug #1300755), an attempt
  1562  // to delete this group fails. A warning is logged in this case.
  1563  func (e *environ) deleteSecurityGroups(securityGroupNames []string) error {
  1564  	novaclient := e.nova()
  1565  	allSecurityGroups, err := novaclient.ListSecurityGroups()
  1566  	if err != nil {
  1567  		return err
  1568  	}
  1569  	for _, securityGroup := range allSecurityGroups {
  1570  		for _, name := range securityGroupNames {
  1571  			if securityGroup.Name == name {
  1572  				err := novaclient.DeleteSecurityGroup(securityGroup.Id)
  1573  				if err != nil {
  1574  					logger.Warningf("cannot delete security group %q. Used by another environment?", name)
  1575  				}
  1576  				break
  1577  			}
  1578  		}
  1579  	}
  1580  	return nil
  1581  }
  1582  
  1583  func (e *environ) terminateInstances(ids []instance.Id) error {
  1584  	if len(ids) == 0 {
  1585  		return nil
  1586  	}
  1587  	var firstErr error
  1588  	novaClient := e.nova()
  1589  	for _, id := range ids {
  1590  		err := novaClient.DeleteServer(string(id))
  1591  		if gooseerrors.IsNotFound(err) {
  1592  			err = nil
  1593  		}
  1594  		if err != nil && firstErr == nil {
  1595  			logger.Debugf("error terminating instance %q: %v", id, err)
  1596  			firstErr = err
  1597  		}
  1598  	}
  1599  	return firstErr
  1600  }
  1601  
  1602  // MetadataLookupParams returns parameters which are used to query simplestreams metadata.
  1603  func (e *environ) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
  1604  	if region == "" {
  1605  		region = e.ecfg().region()
  1606  	}
  1607  	cloudSpec, err := e.cloudSpec(region)
  1608  	if err != nil {
  1609  		return nil, err
  1610  	}
  1611  	return &simplestreams.MetadataLookupParams{
  1612  		Series:        config.PreferredSeries(e.ecfg()),
  1613  		Region:        cloudSpec.Region,
  1614  		Endpoint:      cloudSpec.Endpoint,
  1615  		Architectures: arch.AllSupportedArches,
  1616  	}, nil
  1617  }
  1618  
  1619  // Region is specified in the HasRegion interface.
  1620  func (e *environ) Region() (simplestreams.CloudSpec, error) {
  1621  	return e.cloudSpec(e.ecfg().region())
  1622  }
  1623  
  1624  func (e *environ) cloudSpec(region string) (simplestreams.CloudSpec, error) {
  1625  	return simplestreams.CloudSpec{
  1626  		Region:   region,
  1627  		Endpoint: e.ecfg().authURL(),
  1628  	}, nil
  1629  }