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

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package maas
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/base64"
     9  	"encoding/xml"
    10  	"fmt"
    11  	"net"
    12  	"net/http"
    13  	"net/url"
    14  	"strconv"
    15  	"strings"
    16  	"sync"
    17  	"text/template"
    18  	"time"
    19  
    20  	"github.com/juju/errors"
    21  	"github.com/juju/utils"
    22  	"github.com/juju/utils/set"
    23  	"gopkg.in/mgo.v2/bson"
    24  	"launchpad.net/gomaasapi"
    25  
    26  	"github.com/juju/juju/agent"
    27  	"github.com/juju/juju/cloudinit"
    28  	"github.com/juju/juju/constraints"
    29  	"github.com/juju/juju/environs"
    30  	"github.com/juju/juju/environs/config"
    31  	"github.com/juju/juju/environs/storage"
    32  	"github.com/juju/juju/instance"
    33  	"github.com/juju/juju/network"
    34  	"github.com/juju/juju/provider/common"
    35  	"github.com/juju/juju/state/multiwatcher"
    36  	"github.com/juju/juju/tools"
    37  	"github.com/juju/juju/version"
    38  )
    39  
    40  const (
    41  	// We're using v1.0 of the MAAS API.
    42  	apiVersion = "1.0"
    43  )
    44  
    45  // A request may fail to due "eventual consistency" semantics, which
    46  // should resolve fairly quickly.  A request may also fail due to a slow
    47  // state transition (for instance an instance taking a while to release
    48  // a security group after termination).  The former failure mode is
    49  // dealt with by shortAttempt, the latter by LongAttempt.
    50  var shortAttempt = utils.AttemptStrategy{
    51  	Total: 5 * time.Second,
    52  	Delay: 200 * time.Millisecond,
    53  }
    54  
    55  // longAttempt is used when we are polling for changes to
    56  // instance state. Such changes may involve a reboot so we
    57  // want to allow sufficient time for that to happen.
    58  var longAttempt = utils.AttemptStrategy{
    59  	Min:   50,
    60  	Delay: 5 * time.Second,
    61  }
    62  
    63  var (
    64  	ReleaseNodes         = releaseNodes
    65  	ReserveIPAddress     = reserveIPAddress
    66  	ReleaseIPAddress     = releaseIPAddress
    67  	DeploymentStatusCall = deploymentStatusCall
    68  )
    69  
    70  func releaseNodes(nodes gomaasapi.MAASObject, ids url.Values) error {
    71  	_, err := nodes.CallPost("release", ids)
    72  	return err
    73  }
    74  
    75  func reserveIPAddress(ipaddresses gomaasapi.MAASObject, cidr string, addr network.Address) error {
    76  	params := url.Values{}
    77  	params.Add("network", cidr)
    78  	params.Add("ip", addr.Value)
    79  	_, err := ipaddresses.CallPost("reserve", params)
    80  	return err
    81  }
    82  
    83  func releaseIPAddress(ipaddresses gomaasapi.MAASObject, addr network.Address) error {
    84  	params := url.Values{}
    85  	params.Add("ip", addr.Value)
    86  	_, err := ipaddresses.CallPost("release", params)
    87  	return err
    88  }
    89  
    90  type maasEnviron struct {
    91  	common.SupportsUnitPlacementPolicy
    92  
    93  	name string
    94  
    95  	// archMutex gates access to supportedArchitectures
    96  	archMutex sync.Mutex
    97  	// supportedArchitectures caches the architectures
    98  	// for which images can be instantiated.
    99  	supportedArchitectures []string
   100  
   101  	// ecfgMutex protects the *Unlocked fields below.
   102  	ecfgMutex sync.Mutex
   103  
   104  	ecfgUnlocked       *maasEnvironConfig
   105  	maasClientUnlocked *gomaasapi.MAASObject
   106  	storageUnlocked    storage.Storage
   107  
   108  	availabilityZonesMutex sync.Mutex
   109  	availabilityZones      []common.AvailabilityZone
   110  }
   111  
   112  var _ environs.Environ = (*maasEnviron)(nil)
   113  
   114  func NewEnviron(cfg *config.Config) (*maasEnviron, error) {
   115  	env := new(maasEnviron)
   116  	err := env.SetConfig(cfg)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	env.name = cfg.Name()
   121  	env.storageUnlocked = NewStorage(env)
   122  	return env, nil
   123  }
   124  
   125  // Bootstrap is specified in the Environ interface.
   126  func (env *maasEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (arch, series string, _ environs.BootstrapFinalizer, _ error) {
   127  	// Override the network bridge device used for both LXC and KVM
   128  	// containers, because we'll be creating juju-br0 at bootstrap
   129  	// time.
   130  	args.ContainerBridgeName = environs.DefaultBridgeName
   131  	result, series, finalizer, err := common.BootstrapInstance(ctx, env, args)
   132  	if err != nil {
   133  		return "", "", nil, err
   134  	}
   135  
   136  	// We want to destroy the started instance if it doesn't transition to Deployed.
   137  	defer func() {
   138  		if err != nil {
   139  			if err := env.StopInstances(result.Instance.Id()); err != nil {
   140  				logger.Errorf("error releasing bootstrap instance: %v", err)
   141  			}
   142  		}
   143  	}()
   144  	// Wait for bootstrap instance to change to deployed state.
   145  	if err := env.waitForNodeDeployment(result.Instance.Id()); err != nil {
   146  		return "", "", nil, errors.Annotate(err, "bootstrap instance started but did not change to Deployed state")
   147  	}
   148  	return *result.Hardware.Arch, series, finalizer, nil
   149  }
   150  
   151  // StateServerInstances is specified in the Environ interface.
   152  func (env *maasEnviron) StateServerInstances() ([]instance.Id, error) {
   153  	return common.ProviderStateInstances(env, env.Storage())
   154  }
   155  
   156  // ecfg returns the environment's maasEnvironConfig, and protects it with a
   157  // mutex.
   158  func (env *maasEnviron) ecfg() *maasEnvironConfig {
   159  	env.ecfgMutex.Lock()
   160  	defer env.ecfgMutex.Unlock()
   161  	return env.ecfgUnlocked
   162  }
   163  
   164  // Config is specified in the Environ interface.
   165  func (env *maasEnviron) Config() *config.Config {
   166  	return env.ecfg().Config
   167  }
   168  
   169  // SetConfig is specified in the Environ interface.
   170  func (env *maasEnviron) SetConfig(cfg *config.Config) error {
   171  	env.ecfgMutex.Lock()
   172  	defer env.ecfgMutex.Unlock()
   173  
   174  	// The new config has already been validated by itself, but now we
   175  	// validate the transition from the old config to the new.
   176  	var oldCfg *config.Config
   177  	if env.ecfgUnlocked != nil {
   178  		oldCfg = env.ecfgUnlocked.Config
   179  	}
   180  	cfg, err := env.Provider().Validate(cfg, oldCfg)
   181  	if err != nil {
   182  		return err
   183  	}
   184  
   185  	ecfg, err := providerInstance.newConfig(cfg)
   186  	if err != nil {
   187  		return err
   188  	}
   189  
   190  	env.ecfgUnlocked = ecfg
   191  
   192  	authClient, err := gomaasapi.NewAuthenticatedClient(ecfg.maasServer(), ecfg.maasOAuth(), apiVersion)
   193  	if err != nil {
   194  		return err
   195  	}
   196  	env.maasClientUnlocked = gomaasapi.NewMAAS(*authClient)
   197  
   198  	return nil
   199  }
   200  
   201  // SupportedArchitectures is specified on the EnvironCapability interface.
   202  func (env *maasEnviron) SupportedArchitectures() ([]string, error) {
   203  	env.archMutex.Lock()
   204  	defer env.archMutex.Unlock()
   205  	if env.supportedArchitectures != nil {
   206  		return env.supportedArchitectures, nil
   207  	}
   208  	bootImages, err := env.allBootImages()
   209  	if err != nil || len(bootImages) == 0 {
   210  		logger.Debugf("error querying boot-images: %v", err)
   211  		logger.Debugf("falling back to listing nodes")
   212  		supportedArchitectures, err := env.nodeArchitectures()
   213  		if err != nil {
   214  			return nil, err
   215  		}
   216  		env.supportedArchitectures = supportedArchitectures
   217  	} else {
   218  		architectures := make(set.Strings)
   219  		for _, image := range bootImages {
   220  			architectures.Add(image.architecture)
   221  		}
   222  		env.supportedArchitectures = architectures.SortedValues()
   223  	}
   224  	return env.supportedArchitectures, nil
   225  }
   226  
   227  // SupportsAddressAllocation is specified on environs.Networking.
   228  func (env *maasEnviron) SupportsAddressAllocation(subnetId network.Id) (bool, error) {
   229  	caps, err := env.getCapabilities()
   230  	if err != nil {
   231  		return false, errors.Annotatef(err, "getCapabilities failed")
   232  	}
   233  	return caps.Contains(capStaticIPAddresses), nil
   234  }
   235  
   236  // allBootImages queries MAAS for all of the boot-images across
   237  // all registered nodegroups.
   238  func (env *maasEnviron) allBootImages() ([]bootImage, error) {
   239  	nodegroups, err := env.getNodegroups()
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  	var allBootImages []bootImage
   244  	seen := make(set.Strings)
   245  	for _, nodegroup := range nodegroups {
   246  		bootImages, err := env.nodegroupBootImages(nodegroup)
   247  		if err != nil {
   248  			return nil, errors.Annotatef(err, "cannot get boot images for nodegroup %v", nodegroup)
   249  		}
   250  		for _, image := range bootImages {
   251  			str := fmt.Sprint(image)
   252  			if seen.Contains(str) {
   253  				continue
   254  			}
   255  			seen.Add(str)
   256  			allBootImages = append(allBootImages, image)
   257  		}
   258  	}
   259  	return allBootImages, nil
   260  }
   261  
   262  // getNodegroups returns the UUID corresponding to each nodegroup
   263  // in the MAAS installation.
   264  func (env *maasEnviron) getNodegroups() ([]string, error) {
   265  	nodegroupsListing := env.getMAASClient().GetSubObject("nodegroups")
   266  	nodegroupsResult, err := nodegroupsListing.CallGet("list", nil)
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  	list, err := nodegroupsResult.GetArray()
   271  	if err != nil {
   272  		return nil, err
   273  	}
   274  	nodegroups := make([]string, len(list))
   275  	for i, obj := range list {
   276  		nodegroup, err := obj.GetMap()
   277  		if err != nil {
   278  			return nil, err
   279  		}
   280  		uuid, err := nodegroup["uuid"].GetString()
   281  		if err != nil {
   282  			return nil, err
   283  		}
   284  		nodegroups[i] = uuid
   285  	}
   286  	return nodegroups, nil
   287  }
   288  
   289  func (env *maasEnviron) getNodegroupInterfaces(nodegroups []string) map[string][]net.IP {
   290  	nodegroupsObject := env.getMAASClient().GetSubObject("nodegroups")
   291  
   292  	nodegroupsInterfacesMap := make(map[string][]net.IP)
   293  	for _, uuid := range nodegroups {
   294  		interfacesObject := nodegroupsObject.GetSubObject(uuid).GetSubObject("interfaces")
   295  		interfacesResult, err := interfacesObject.CallGet("list", nil)
   296  		if err != nil {
   297  			logger.Warningf("could not fetch nodegroup-interfaces for nodegroup %v: %v", uuid, err)
   298  			continue
   299  		}
   300  		interfaces, err := interfacesResult.GetArray()
   301  		if err != nil {
   302  			logger.Warningf("could not fetch nodegroup-interfaces for nodegroup %v: %v", uuid, err)
   303  			continue
   304  		}
   305  		for _, interfaceResult := range interfaces {
   306  			nic, err := interfaceResult.GetMap()
   307  			if err != nil {
   308  				logger.Warningf("could not fetch interface %v for nodegroup %v: %v", nic, uuid, err)
   309  				continue
   310  			}
   311  			ip, err := nic["ip"].GetString()
   312  			if err != nil {
   313  				logger.Warningf("could not fetch interface %v for nodegroup %v: %v", nic, uuid, err)
   314  				continue
   315  			}
   316  			static_low, err := nic["static_ip_range_low"].GetString()
   317  			if err != nil {
   318  				logger.Warningf("could not fetch static IP range lower bound for interface %v on nodegroup %v: %v", nic, uuid, err)
   319  				continue
   320  			}
   321  			static_high, err := nic["static_ip_range_high"].GetString()
   322  			if err != nil {
   323  				logger.Warningf("could not fetch static IP range higher bound for interface %v on nodegroup %v: %v", nic, uuid, err)
   324  				continue
   325  			}
   326  			static_low_ip := net.ParseIP(static_low)
   327  			static_high_ip := net.ParseIP(static_high)
   328  			if static_low_ip == nil || static_high_ip == nil {
   329  				logger.Warningf("invalid IP in static range for interface %v on nodegroup %v: %q %q", nic, uuid, static_low_ip, static_high_ip)
   330  				continue
   331  			}
   332  			nodegroupsInterfacesMap[ip] = []net.IP{static_low_ip, static_high_ip}
   333  		}
   334  	}
   335  	return nodegroupsInterfacesMap
   336  }
   337  
   338  type bootImage struct {
   339  	architecture string
   340  	release      string
   341  }
   342  
   343  // nodegroupBootImages returns the set of boot-images for the specified nodegroup.
   344  func (env *maasEnviron) nodegroupBootImages(nodegroupUUID string) ([]bootImage, error) {
   345  	nodegroupObject := env.getMAASClient().GetSubObject("nodegroups").GetSubObject(nodegroupUUID)
   346  	bootImagesObject := nodegroupObject.GetSubObject("boot-images/")
   347  	result, err := bootImagesObject.CallGet("", nil)
   348  	if err != nil {
   349  		return nil, err
   350  	}
   351  	list, err := result.GetArray()
   352  	if err != nil {
   353  		return nil, err
   354  	}
   355  	var bootImages []bootImage
   356  	for _, obj := range list {
   357  		bootimage, err := obj.GetMap()
   358  		if err != nil {
   359  			return nil, err
   360  		}
   361  		arch, err := bootimage["architecture"].GetString()
   362  		if err != nil {
   363  			return nil, err
   364  		}
   365  		release, err := bootimage["release"].GetString()
   366  		if err != nil {
   367  			return nil, err
   368  		}
   369  		bootImages = append(bootImages, bootImage{
   370  			architecture: arch,
   371  			release:      release,
   372  		})
   373  	}
   374  	return bootImages, nil
   375  }
   376  
   377  // nodeArchitectures returns the architectures of all
   378  // available nodes in the system.
   379  //
   380  // Note: this should only be used if we cannot query
   381  // boot-images.
   382  func (env *maasEnviron) nodeArchitectures() ([]string, error) {
   383  	filter := make(url.Values)
   384  	filter.Add("status", gomaasapi.NodeStatusDeclared)
   385  	filter.Add("status", gomaasapi.NodeStatusCommissioning)
   386  	filter.Add("status", gomaasapi.NodeStatusReady)
   387  	filter.Add("status", gomaasapi.NodeStatusReserved)
   388  	filter.Add("status", gomaasapi.NodeStatusAllocated)
   389  	allInstances, err := env.instances(filter)
   390  	if err != nil {
   391  		return nil, err
   392  	}
   393  	architectures := make(set.Strings)
   394  	for _, inst := range allInstances {
   395  		inst := inst.(*maasInstance)
   396  		arch, _, err := inst.architecture()
   397  		if err != nil {
   398  			return nil, err
   399  		}
   400  		architectures.Add(arch)
   401  	}
   402  	// TODO(dfc) why is this sorted
   403  	return architectures.SortedValues(), nil
   404  }
   405  
   406  type maasAvailabilityZone struct {
   407  	name string
   408  }
   409  
   410  func (z maasAvailabilityZone) Name() string {
   411  	return z.name
   412  }
   413  
   414  func (z maasAvailabilityZone) Available() bool {
   415  	// MAAS' physical zone attributes only include name and description;
   416  	// there is no concept of availability.
   417  	return true
   418  }
   419  
   420  // AvailabilityZones returns a slice of availability zones
   421  // for the configured region.
   422  func (e *maasEnviron) AvailabilityZones() ([]common.AvailabilityZone, error) {
   423  	e.availabilityZonesMutex.Lock()
   424  	defer e.availabilityZonesMutex.Unlock()
   425  	if e.availabilityZones == nil {
   426  		zonesObject := e.getMAASClient().GetSubObject("zones")
   427  		result, err := zonesObject.CallGet("", nil)
   428  		if err, ok := err.(gomaasapi.ServerError); ok && err.StatusCode == http.StatusNotFound {
   429  			return nil, errors.NewNotImplemented(nil, "the MAAS server does not support zones")
   430  		}
   431  		if err != nil {
   432  			return nil, errors.Annotate(err, "cannot query ")
   433  		}
   434  		list, err := result.GetArray()
   435  		if err != nil {
   436  			return nil, err
   437  		}
   438  		logger.Debugf("availability zones: %+v", list)
   439  		availabilityZones := make([]common.AvailabilityZone, len(list))
   440  		for i, obj := range list {
   441  			zone, err := obj.GetMap()
   442  			if err != nil {
   443  				return nil, err
   444  			}
   445  			name, err := zone["name"].GetString()
   446  			if err != nil {
   447  				return nil, err
   448  			}
   449  			availabilityZones[i] = maasAvailabilityZone{name}
   450  		}
   451  		e.availabilityZones = availabilityZones
   452  	}
   453  	return e.availabilityZones, nil
   454  }
   455  
   456  // InstanceAvailabilityZoneNames returns the availability zone names for each
   457  // of the specified instances.
   458  func (e *maasEnviron) InstanceAvailabilityZoneNames(ids []instance.Id) ([]string, error) {
   459  	instances, err := e.Instances(ids)
   460  	if err != nil && err != environs.ErrPartialInstances {
   461  		return nil, err
   462  	}
   463  	zones := make([]string, len(instances))
   464  	for i, inst := range instances {
   465  		if inst == nil {
   466  			continue
   467  		}
   468  		zones[i] = inst.(*maasInstance).zone()
   469  	}
   470  	return zones, nil
   471  }
   472  
   473  type maasPlacement struct {
   474  	nodeName string
   475  	zoneName string
   476  }
   477  
   478  func (e *maasEnviron) parsePlacement(placement string) (*maasPlacement, error) {
   479  	pos := strings.IndexRune(placement, '=')
   480  	if pos == -1 {
   481  		// If there's no '=' delimiter, assume it's a node name.
   482  		return &maasPlacement{nodeName: placement}, nil
   483  	}
   484  	switch key, value := placement[:pos], placement[pos+1:]; key {
   485  	case "zone":
   486  		availabilityZone := value
   487  		zones, err := e.AvailabilityZones()
   488  		if err != nil {
   489  			return nil, err
   490  		}
   491  		for _, z := range zones {
   492  			if z.Name() == availabilityZone {
   493  				return &maasPlacement{zoneName: availabilityZone}, nil
   494  			}
   495  		}
   496  		return nil, errors.Errorf("invalid availability zone %q", availabilityZone)
   497  	}
   498  	return nil, errors.Errorf("unknown placement directive: %v", placement)
   499  }
   500  
   501  func (env *maasEnviron) PrecheckInstance(series string, cons constraints.Value, placement string) error {
   502  	if placement == "" {
   503  		return nil
   504  	}
   505  	_, err := env.parsePlacement(placement)
   506  	return err
   507  }
   508  
   509  const (
   510  	capNetworksManagement = "networks-management"
   511  	capStaticIPAddresses  = "static-ipaddresses"
   512  )
   513  
   514  // getCapabilities asks the MAAS server for its capabilities, if
   515  // supported by the server.
   516  func (env *maasEnviron) getCapabilities() (set.Strings, error) {
   517  	caps := make(set.Strings)
   518  	var result gomaasapi.JSONObject
   519  	var err error
   520  
   521  	for a := shortAttempt.Start(); a.Next(); {
   522  		client := env.getMAASClient().GetSubObject("version/")
   523  		result, err = client.CallGet("", nil)
   524  		if err != nil {
   525  			if err, ok := err.(gomaasapi.ServerError); ok && err.StatusCode == 404 {
   526  				return caps, fmt.Errorf("MAAS does not support version info")
   527  			}
   528  			return caps, err
   529  		}
   530  	}
   531  	if err != nil {
   532  		return caps, err
   533  	}
   534  	info, err := result.GetMap()
   535  	if err != nil {
   536  		return caps, err
   537  	}
   538  	capsObj, ok := info["capabilities"]
   539  	if !ok {
   540  		return caps, fmt.Errorf("MAAS does not report capabilities")
   541  	}
   542  	items, err := capsObj.GetArray()
   543  	if err != nil {
   544  		return caps, err
   545  	}
   546  	for _, item := range items {
   547  		val, err := item.GetString()
   548  		if err != nil {
   549  			return set.NewStrings(), err
   550  		}
   551  		caps.Add(val)
   552  	}
   553  	return caps, nil
   554  }
   555  
   556  // getMAASClient returns a MAAS client object to use for a request, in a
   557  // lock-protected fashion.
   558  func (env *maasEnviron) getMAASClient() *gomaasapi.MAASObject {
   559  	env.ecfgMutex.Lock()
   560  	defer env.ecfgMutex.Unlock()
   561  
   562  	return env.maasClientUnlocked
   563  }
   564  
   565  // convertConstraints converts the given constraints into an url.Values
   566  // object suitable to pass to MAAS when acquiring a node.
   567  // CpuPower is ignored because it cannot translated into something
   568  // meaningful for MAAS right now.
   569  func convertConstraints(cons constraints.Value) url.Values {
   570  	params := url.Values{}
   571  	if cons.Arch != nil {
   572  		// Note: Juju and MAAS use the same architecture names.
   573  		// MAAS also accepts a subarchitecture (e.g. "highbank"
   574  		// for ARM), which defaults to "generic" if unspecified.
   575  		params.Add("arch", *cons.Arch)
   576  	}
   577  	if cons.CpuCores != nil {
   578  		params.Add("cpu_count", fmt.Sprintf("%d", *cons.CpuCores))
   579  	}
   580  	if cons.Mem != nil {
   581  		params.Add("mem", fmt.Sprintf("%d", *cons.Mem))
   582  	}
   583  	if cons.Tags != nil && len(*cons.Tags) > 0 {
   584  		tags, notTags := parseTags(*cons.Tags)
   585  		if len(tags) > 0 {
   586  			params.Add("tags", strings.Join(tags, ","))
   587  		}
   588  		if len(notTags) > 0 {
   589  			params.Add("not_tags", strings.Join(notTags, ","))
   590  		}
   591  	}
   592  	// TODO(bug 1212689): ignore root-disk constraint for now.
   593  	if cons.RootDisk != nil {
   594  		logger.Warningf("ignoring unsupported constraint 'root-disk'")
   595  	}
   596  	if cons.CpuPower != nil {
   597  		logger.Warningf("ignoring unsupported constraint 'cpu-power'")
   598  	}
   599  	return params
   600  }
   601  
   602  // parseTags parses a tags constraints, splitting it into a positive
   603  // and negative tags to pass to MAAS. Positive tags have no prefix,
   604  // negative tags have a "^" prefix. All spaces inside the rawTags are
   605  // stripped before parsing.
   606  func parseTags(rawTags []string) (tags, notTags []string) {
   607  	for _, tag := range rawTags {
   608  		tag = strings.Replace(tag, " ", "", -1)
   609  		if len(tag) == 0 {
   610  			continue
   611  		}
   612  		if strings.HasPrefix(tag, "^") {
   613  			notTags = append(notTags, strings.TrimPrefix(tag, "^"))
   614  		} else {
   615  			tags = append(tags, tag)
   616  		}
   617  	}
   618  	return tags, notTags
   619  }
   620  
   621  // addNetworks converts networks include/exclude information into
   622  // url.Values object suitable to pass to MAAS when acquiring a node.
   623  func addNetworks(params url.Values, includeNetworks, excludeNetworks []string) {
   624  	// Network Inclusion/Exclusion setup
   625  	if len(includeNetworks) > 0 {
   626  		for _, name := range includeNetworks {
   627  			params.Add("networks", name)
   628  		}
   629  	}
   630  	if len(excludeNetworks) > 0 {
   631  		for _, name := range excludeNetworks {
   632  			params.Add("not_networks", name)
   633  		}
   634  	}
   635  }
   636  
   637  // acquireNode allocates a node from the MAAS.
   638  func (environ *maasEnviron) acquireNode(nodeName, zoneName string, cons constraints.Value, includeNetworks, excludeNetworks []string) (gomaasapi.MAASObject, error) {
   639  	acquireParams := convertConstraints(cons)
   640  	addNetworks(acquireParams, includeNetworks, excludeNetworks)
   641  	acquireParams.Add("agent_name", environ.ecfg().maasAgentName())
   642  	if zoneName != "" {
   643  		acquireParams.Add("zone", zoneName)
   644  	}
   645  	if nodeName != "" {
   646  		acquireParams.Add("name", nodeName)
   647  	} else if cons.Arch == nil {
   648  		// TODO(axw) 2014-08-18 #1358219
   649  		// We should be requesting preferred
   650  		// architectures if unspecified, like
   651  		// in the other providers.
   652  		//
   653  		// This is slightly complicated in MAAS
   654  		// as there are a finite number of each
   655  		// architecture; preference may also
   656  		// conflict with other constraints, such
   657  		// as tags. Thus, a preference becomes a
   658  		// demand (which may fail) if not handled
   659  		// properly.
   660  		logger.Warningf(
   661  			"no architecture was specified, acquiring an arbitrary node",
   662  		)
   663  	}
   664  
   665  	var result gomaasapi.JSONObject
   666  	var err error
   667  	for a := shortAttempt.Start(); a.Next(); {
   668  		client := environ.getMAASClient().GetSubObject("nodes/")
   669  		result, err = client.CallPost("acquire", acquireParams)
   670  		if err == nil {
   671  			break
   672  		}
   673  	}
   674  	if err != nil {
   675  		return gomaasapi.MAASObject{}, err
   676  	}
   677  	node, err := result.GetMAASObject()
   678  	if err != nil {
   679  		err := errors.Annotate(err, "unexpected result from 'acquire' on MAAS API")
   680  		return gomaasapi.MAASObject{}, err
   681  	}
   682  	return node, nil
   683  }
   684  
   685  // startNode installs and boots a node.
   686  func (environ *maasEnviron) startNode(node gomaasapi.MAASObject, series string, userdata []byte) error {
   687  	userDataParam := base64.StdEncoding.EncodeToString(userdata)
   688  	params := url.Values{
   689  		"distro_series": {series},
   690  		"user_data":     {userDataParam},
   691  	}
   692  	// Initialize err to a non-nil value as a sentinel for the following
   693  	// loop.
   694  	err := fmt.Errorf("(no error)")
   695  	for a := shortAttempt.Start(); a.Next() && err != nil; {
   696  		_, err = node.CallPost("start", params)
   697  	}
   698  	return err
   699  }
   700  
   701  const bridgeConfigTemplate = `cat >> {{.Config}} << EOF
   702  
   703  iface {{.PrimaryNIC}} inet manual
   704  
   705  auto {{.Bridge}}
   706  iface {{.Bridge}} inet dhcp
   707      bridge_ports {{.PrimaryNIC}}
   708  EOF
   709  grep -q 'iface {{.PrimaryNIC}} inet dhcp' {{.Config}} && \
   710  sed -i 's/iface {{.PrimaryNIC}} inet dhcp//' {{.Config}}`
   711  
   712  // setupJujuNetworking returns a string representing the script to run
   713  // in order to prepare the Juju-specific networking config on a node.
   714  func setupJujuNetworking(primaryIface string) (string, error) {
   715  	parsedTemplate := template.Must(
   716  		template.New("BridgeConfig").Parse(bridgeConfigTemplate),
   717  	)
   718  	var buf bytes.Buffer
   719  	err := parsedTemplate.Execute(&buf, map[string]interface{}{
   720  		"Config":     "/etc/network/interfaces",
   721  		"Bridge":     environs.DefaultBridgeName,
   722  		"PrimaryNIC": primaryIface,
   723  	})
   724  	if err != nil {
   725  		return "", errors.Annotate(err, "bridge config template error")
   726  	}
   727  	return buf.String(), nil
   728  }
   729  
   730  var unsupportedConstraints = []string{
   731  	constraints.CpuPower,
   732  	constraints.InstanceType,
   733  }
   734  
   735  // ConstraintsValidator is defined on the Environs interface.
   736  func (environ *maasEnviron) ConstraintsValidator() (constraints.Validator, error) {
   737  	validator := constraints.NewValidator()
   738  	validator.RegisterUnsupported(unsupportedConstraints)
   739  	supportedArches, err := environ.SupportedArchitectures()
   740  	if err != nil {
   741  		return nil, err
   742  	}
   743  	validator.RegisterVocabulary(constraints.Arch, supportedArches)
   744  	return validator, nil
   745  }
   746  
   747  // setupNetworks prepares a []network.InterfaceInfo for the given
   748  // instance. Any networks in networksToDisable will be configured as
   749  // disabled on the machine. Any disabled network interfaces (as
   750  // discovered from the lshw output for the node) will stay disabled.
   751  // The interface name discovered as primary is also returned.
   752  func (environ *maasEnviron) setupNetworks(inst instance.Instance, networksToDisable set.Strings) ([]network.InterfaceInfo, string, error) {
   753  	// Get the instance network interfaces first.
   754  	interfaces, primaryIface, err := environ.getInstanceNetworkInterfaces(inst)
   755  	if err != nil {
   756  		return nil, "", errors.Annotatef(err, "getInstanceNetworkInterfaces failed")
   757  	}
   758  	logger.Debugf("node %q has network interfaces %v", inst.Id(), interfaces)
   759  	networks, err := environ.getInstanceNetworks(inst)
   760  	if err != nil {
   761  		return nil, "", errors.Annotatef(err, "getInstanceNetworks failed")
   762  	}
   763  	logger.Debugf("node %q has networks %v", inst.Id(), networks)
   764  	var tempInterfaceInfo []network.InterfaceInfo
   765  	for _, netw := range networks {
   766  		disabled := networksToDisable.Contains(netw.Name)
   767  		netCIDR := &net.IPNet{
   768  			IP:   net.ParseIP(netw.IP),
   769  			Mask: net.IPMask(net.ParseIP(netw.Mask)),
   770  		}
   771  		macs, err := environ.getNetworkMACs(netw.Name)
   772  		if err != nil {
   773  			return nil, "", errors.Annotatef(err, "getNetworkMACs failed")
   774  		}
   775  		logger.Debugf("network %q has MACs: %v", netw.Name, macs)
   776  		for _, mac := range macs {
   777  			if ifinfo, ok := interfaces[mac]; ok {
   778  				tempInterfaceInfo = append(tempInterfaceInfo, network.InterfaceInfo{
   779  					MACAddress:    mac,
   780  					InterfaceName: ifinfo.InterfaceName,
   781  					DeviceIndex:   ifinfo.DeviceIndex,
   782  					CIDR:          netCIDR.String(),
   783  					VLANTag:       netw.VLANTag,
   784  					ProviderId:    network.Id(netw.Name),
   785  					NetworkName:   netw.Name,
   786  					Disabled:      disabled || ifinfo.Disabled,
   787  				})
   788  			}
   789  		}
   790  	}
   791  	// Verify we filled-in everything for all networks/interfaces
   792  	// and drop incomplete records.
   793  	var interfaceInfo []network.InterfaceInfo
   794  	for _, info := range tempInterfaceInfo {
   795  		if info.ProviderId == "" || info.NetworkName == "" || info.CIDR == "" {
   796  			logger.Warningf("ignoring network interface %q: missing network information", info.InterfaceName)
   797  			continue
   798  		}
   799  		if info.MACAddress == "" || info.InterfaceName == "" {
   800  			logger.Warningf("ignoring network %q: missing network interface information", info.ProviderId)
   801  			continue
   802  		}
   803  		interfaceInfo = append(interfaceInfo, info)
   804  	}
   805  	logger.Debugf("node %q network information: %#v", inst.Id(), interfaceInfo)
   806  	return interfaceInfo, primaryIface, nil
   807  }
   808  
   809  // DistributeInstances implements the state.InstanceDistributor policy.
   810  func (e *maasEnviron) DistributeInstances(candidates, distributionGroup []instance.Id) ([]instance.Id, error) {
   811  	return common.DistributeInstances(e, candidates, distributionGroup)
   812  }
   813  
   814  var availabilityZoneAllocations = common.AvailabilityZoneAllocations
   815  
   816  // StartInstance is specified in the InstanceBroker interface.
   817  func (environ *maasEnviron) StartInstance(args environs.StartInstanceParams) (
   818  	*environs.StartInstanceResult, error,
   819  ) {
   820  	var availabilityZones []string
   821  	var nodeName string
   822  	if args.Placement != "" {
   823  		placement, err := environ.parsePlacement(args.Placement)
   824  		if err != nil {
   825  			return nil, err
   826  		}
   827  		switch {
   828  		case placement.zoneName != "":
   829  			availabilityZones = append(availabilityZones, placement.zoneName)
   830  		default:
   831  			nodeName = placement.nodeName
   832  		}
   833  	}
   834  
   835  	// If no placement is specified, then automatically spread across
   836  	// the known zones for optimal spread across the instance distribution
   837  	// group.
   838  	if args.Placement == "" {
   839  		var group []instance.Id
   840  		var err error
   841  		if args.DistributionGroup != nil {
   842  			group, err = args.DistributionGroup()
   843  			if err != nil {
   844  				return nil, errors.Annotate(err, "cannot get distribution group")
   845  			}
   846  		}
   847  		zoneInstances, err := availabilityZoneAllocations(environ, group)
   848  		if errors.IsNotImplemented(err) {
   849  			// Availability zones are an extension, so we may get a
   850  			// not implemented error; ignore these.
   851  		} else if err != nil {
   852  			return nil, errors.Annotate(err, "cannot get availability zone allocations")
   853  		} else if len(zoneInstances) > 0 {
   854  			for _, z := range zoneInstances {
   855  				availabilityZones = append(availabilityZones, z.ZoneName)
   856  			}
   857  		}
   858  	}
   859  	if len(availabilityZones) == 0 {
   860  		availabilityZones = []string{""}
   861  	}
   862  
   863  	requestedNetworks := args.MachineConfig.Networks
   864  	includeNetworks := append(args.Constraints.IncludeNetworks(), requestedNetworks...)
   865  	excludeNetworks := args.Constraints.ExcludeNetworks()
   866  
   867  	snArgs := selectNodeArgs{
   868  		Constraints:       args.Constraints,
   869  		AvailabilityZones: availabilityZones,
   870  		NodeName:          nodeName,
   871  		IncludeNetworks:   includeNetworks,
   872  		ExcludeNetworks:   excludeNetworks,
   873  	}
   874  	node, err := environ.selectNode(snArgs)
   875  	if err != nil {
   876  		return nil, errors.Errorf("cannot run instances: %v", err)
   877  	}
   878  
   879  	inst := &maasInstance{maasObject: node, environ: environ}
   880  	defer func() {
   881  		if err != nil {
   882  			if err := environ.StopInstances(inst.Id()); err != nil {
   883  				logger.Errorf("error releasing failed instance: %v", err)
   884  			}
   885  		}
   886  	}()
   887  
   888  	hc, err := inst.hardwareCharacteristics()
   889  	if err != nil {
   890  		return nil, err
   891  	}
   892  
   893  	selectedTools, err := args.Tools.Match(tools.Filter{
   894  		Arch: *hc.Arch,
   895  	})
   896  	if err != nil {
   897  		return nil, err
   898  	}
   899  	args.MachineConfig.Tools = selectedTools[0]
   900  
   901  	var networkInfo []network.InterfaceInfo
   902  	networkInfo, primaryIface, err := environ.setupNetworks(inst, set.NewStrings(excludeNetworks...))
   903  	if err != nil {
   904  		return nil, err
   905  	}
   906  
   907  	hostname, err := inst.hostname()
   908  	if err != nil {
   909  		return nil, err
   910  	}
   911  	// Override the network bridge to use for both LXC and KVM
   912  	// containers on the new instance.
   913  	if args.MachineConfig.AgentEnvironment == nil {
   914  		args.MachineConfig.AgentEnvironment = make(map[string]string)
   915  	}
   916  	args.MachineConfig.AgentEnvironment[agent.LxcBridge] = environs.DefaultBridgeName
   917  	if err := environs.FinishMachineConfig(args.MachineConfig, environ.Config()); err != nil {
   918  		return nil, err
   919  	}
   920  	series := args.MachineConfig.Tools.Version.Series
   921  
   922  	cloudcfg, err := environ.newCloudinitConfig(hostname, primaryIface, series)
   923  	if err != nil {
   924  		return nil, err
   925  	}
   926  	userdata, err := environs.ComposeUserData(args.MachineConfig, cloudcfg)
   927  	if err != nil {
   928  		msg := fmt.Errorf("could not compose userdata for bootstrap node: %v", err)
   929  		return nil, msg
   930  	}
   931  	logger.Debugf("maas user data; %d bytes", len(userdata))
   932  
   933  	if err := environ.startNode(*inst.maasObject, series, userdata); err != nil {
   934  		return nil, err
   935  	}
   936  	logger.Debugf("started instance %q", inst.Id())
   937  
   938  	if multiwatcher.AnyJobNeedsState(args.MachineConfig.Jobs...) {
   939  		if err := common.AddStateInstance(environ.Storage(), inst.Id()); err != nil {
   940  			logger.Errorf("could not record instance in provider-state: %v", err)
   941  		}
   942  	}
   943  
   944  	return &environs.StartInstanceResult{
   945  		Instance:    inst,
   946  		Hardware:    hc,
   947  		NetworkInfo: networkInfo,
   948  	}, nil
   949  }
   950  
   951  func (environ *maasEnviron) waitForNodeDeployment(id instance.Id) error {
   952  	systemId := extractSystemId(id)
   953  	for a := longAttempt.Start(); a.Next(); {
   954  		statusValues, err := environ.deploymentStatus(id)
   955  		if errors.IsNotImplemented(err) {
   956  			return nil
   957  		}
   958  		if err != nil {
   959  			return errors.Trace(err)
   960  		}
   961  		if statusValues[systemId] == "Deployed" {
   962  			return nil
   963  		}
   964  	}
   965  	return errors.Errorf("instance %q is started but not deployed", id)
   966  }
   967  
   968  // deploymentStatus returns the deployment state of MAAS instances with
   969  // the specified Juju instance ids.
   970  // Note: the result is a map of MAAS systemId to state.
   971  func (environ *maasEnviron) deploymentStatus(ids ...instance.Id) (map[string]string, error) {
   972  	nodesAPI := environ.getMAASClient().GetSubObject("nodes")
   973  	result, err := DeploymentStatusCall(nodesAPI, ids...)
   974  	if err != nil {
   975  		if err, ok := err.(gomaasapi.ServerError); ok && err.StatusCode == http.StatusBadRequest {
   976  			return nil, errors.NewNotImplemented(err, "deployment status")
   977  		}
   978  		return nil, errors.Trace(err)
   979  	}
   980  	resultMap, err := result.GetMap()
   981  	if err != nil {
   982  		return nil, errors.Trace(err)
   983  	}
   984  	statusValues := make(map[string]string)
   985  	for systemId, jsonValue := range resultMap {
   986  		status, err := jsonValue.GetString()
   987  		if err != nil {
   988  			return nil, errors.Trace(err)
   989  		}
   990  		statusValues[systemId] = status
   991  	}
   992  	return statusValues, nil
   993  }
   994  
   995  func deploymentStatusCall(nodes gomaasapi.MAASObject, ids ...instance.Id) (gomaasapi.JSONObject, error) {
   996  	filter := getSystemIdValues("nodes", ids)
   997  	return nodes.CallGet("deployment_status", filter)
   998  }
   999  
  1000  type selectNodeArgs struct {
  1001  	AvailabilityZones []string
  1002  	NodeName          string
  1003  	Constraints       constraints.Value
  1004  	IncludeNetworks   []string
  1005  	ExcludeNetworks   []string
  1006  }
  1007  
  1008  func (environ *maasEnviron) selectNode(args selectNodeArgs) (*gomaasapi.MAASObject, error) {
  1009  	var err error
  1010  	var node gomaasapi.MAASObject
  1011  
  1012  	for i, zoneName := range args.AvailabilityZones {
  1013  		node, err = environ.acquireNode(
  1014  			args.NodeName,
  1015  			zoneName,
  1016  			args.Constraints,
  1017  			args.IncludeNetworks,
  1018  			args.ExcludeNetworks,
  1019  		)
  1020  
  1021  		if err, ok := err.(gomaasapi.ServerError); ok && err.StatusCode == http.StatusConflict {
  1022  			if i+1 < len(args.AvailabilityZones) {
  1023  				logger.Infof("could not acquire a node in zone %q, trying another zone", zoneName)
  1024  				continue
  1025  			}
  1026  		}
  1027  		if err != nil {
  1028  			return nil, errors.Errorf("cannot run instances: %v", err)
  1029  		}
  1030  		// Since a return at the end of the function is required
  1031  		// just break here.
  1032  		break
  1033  	}
  1034  	return &node, nil
  1035  }
  1036  
  1037  // newCloudinitConfig creates a cloudinit.Config structure
  1038  // suitable as a base for initialising a MAAS node.
  1039  func (environ *maasEnviron) newCloudinitConfig(hostname, primaryIface, series string) (*cloudinit.Config, error) {
  1040  	info := machineInfo{hostname}
  1041  	runCmd, err := info.cloudinitRunCmd(series)
  1042  
  1043  	if err != nil {
  1044  		return nil, err
  1045  	}
  1046  
  1047  	cloudcfg := cloudinit.New()
  1048  	operatingSystem, err := version.GetOSFromSeries(series)
  1049  	if err != nil {
  1050  		return nil, err
  1051  	}
  1052  
  1053  	switch operatingSystem {
  1054  	case version.Windows:
  1055  		cloudcfg.AddScripts(
  1056  			runCmd,
  1057  		)
  1058  	case version.Ubuntu:
  1059  		cloudcfg.SetAptUpdate(true)
  1060  		if on, set := environ.Config().DisableNetworkManagement(); on && set {
  1061  			logger.Infof("network management disabled - setting up br0, eth0 disabled")
  1062  			cloudcfg.AddScripts("set -xe", runCmd)
  1063  		} else {
  1064  			bridgeScript, err := setupJujuNetworking(primaryIface)
  1065  			if err != nil {
  1066  				return nil, errors.Trace(err)
  1067  			}
  1068  			cloudcfg.AddPackage("bridge-utils")
  1069  			cloudcfg.AddScripts(
  1070  				"set -xe",
  1071  				runCmd,
  1072  				"ifdown "+primaryIface,
  1073  				bridgeScript,
  1074  				"ifup "+environs.DefaultBridgeName,
  1075  			)
  1076  		}
  1077  	}
  1078  	return cloudcfg, nil
  1079  }
  1080  
  1081  func (environ *maasEnviron) releaseNodes(nodes gomaasapi.MAASObject, ids url.Values, recurse bool) error {
  1082  	err := ReleaseNodes(nodes, ids)
  1083  	if err == nil {
  1084  		return nil
  1085  	}
  1086  	maasErr, ok := err.(gomaasapi.ServerError)
  1087  	if !ok {
  1088  		return errors.Annotate(err, "cannot release nodes")
  1089  	}
  1090  
  1091  	// StatusCode 409 means a node couldn't be released due to
  1092  	// a state conflict. Likely it's already released or disk
  1093  	// erasing. We're assuming an error of 409 *only* means it's
  1094  	// safe to assume the instance is already released.
  1095  	// MaaS also releases (or attempts) all nodes, and raises
  1096  	// a single error on failure. So even with an error 409, all
  1097  	// nodes have been released.
  1098  	if maasErr.StatusCode == 409 {
  1099  		logger.Infof("ignoring error while releasing nodes (%v); all nodes released OK", err)
  1100  		return nil
  1101  	}
  1102  
  1103  	// a status code of 400, 403 or 404 means one of the nodes
  1104  	// couldn't be found and none have been released. We have
  1105  	// to release all the ones we can individually.
  1106  	if maasErr.StatusCode != 400 && maasErr.StatusCode != 403 && maasErr.StatusCode != 404 {
  1107  		return errors.Annotate(err, "cannot release nodes")
  1108  	}
  1109  	if !recurse {
  1110  		// this node has already been released and we're golden
  1111  		return nil
  1112  	}
  1113  
  1114  	var lastErr error
  1115  	for _, id := range ids["nodes"] {
  1116  		idFilter := url.Values{}
  1117  		idFilter.Add("nodes", id)
  1118  		err := environ.releaseNodes(nodes, idFilter, false)
  1119  		if err != nil {
  1120  			lastErr = err
  1121  			logger.Errorf("error while releasing node %v (%v)", id, err)
  1122  		}
  1123  	}
  1124  	return errors.Trace(lastErr)
  1125  
  1126  }
  1127  
  1128  // StopInstances is specified in the InstanceBroker interface.
  1129  func (environ *maasEnviron) StopInstances(ids ...instance.Id) error {
  1130  	// Shortcut to exit quickly if 'instances' is an empty slice or nil.
  1131  	if len(ids) == 0 {
  1132  		return nil
  1133  	}
  1134  	nodes := environ.getMAASClient().GetSubObject("nodes")
  1135  	err := environ.releaseNodes(nodes, getSystemIdValues("nodes", ids), true)
  1136  	if err != nil {
  1137  		// error will already have been wrapped
  1138  		return err
  1139  	}
  1140  	return common.RemoveStateInstances(environ.Storage(), ids...)
  1141  
  1142  }
  1143  
  1144  // acquireInstances calls the MAAS API to list acquired nodes.
  1145  //
  1146  // The "ids" slice is a filter for specific instance IDs.
  1147  // Due to how this works in the HTTP API, an empty "ids"
  1148  // matches all instances (not none as you might expect).
  1149  func (environ *maasEnviron) acquiredInstances(ids []instance.Id) ([]instance.Instance, error) {
  1150  	filter := getSystemIdValues("id", ids)
  1151  	filter.Add("agent_name", environ.ecfg().maasAgentName())
  1152  	return environ.instances(filter)
  1153  }
  1154  
  1155  // instances calls the MAAS API to list nodes matching the given filter.
  1156  func (environ *maasEnviron) instances(filter url.Values) ([]instance.Instance, error) {
  1157  	nodeListing := environ.getMAASClient().GetSubObject("nodes")
  1158  	listNodeObjects, err := nodeListing.CallGet("list", filter)
  1159  	if err != nil {
  1160  		return nil, err
  1161  	}
  1162  	listNodes, err := listNodeObjects.GetArray()
  1163  	if err != nil {
  1164  		return nil, err
  1165  	}
  1166  	instances := make([]instance.Instance, len(listNodes))
  1167  	for index, nodeObj := range listNodes {
  1168  		node, err := nodeObj.GetMAASObject()
  1169  		if err != nil {
  1170  			return nil, err
  1171  		}
  1172  		instances[index] = &maasInstance{
  1173  			maasObject: &node,
  1174  			environ:    environ,
  1175  		}
  1176  	}
  1177  	return instances, nil
  1178  }
  1179  
  1180  // Instances returns the instance.Instance objects corresponding to the given
  1181  // slice of instance.Id.  The error is ErrNoInstances if no instances
  1182  // were found.
  1183  func (environ *maasEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) {
  1184  	if len(ids) == 0 {
  1185  		// This would be treated as "return all instances" below, so
  1186  		// treat it as a special case.
  1187  		// The interface requires us to return this particular error
  1188  		// if no instances were found.
  1189  		return nil, environs.ErrNoInstances
  1190  	}
  1191  	instances, err := environ.acquiredInstances(ids)
  1192  	if err != nil {
  1193  		return nil, err
  1194  	}
  1195  	if len(instances) == 0 {
  1196  		return nil, environs.ErrNoInstances
  1197  	}
  1198  
  1199  	idMap := make(map[instance.Id]instance.Instance)
  1200  	for _, instance := range instances {
  1201  		idMap[instance.Id()] = instance
  1202  	}
  1203  
  1204  	result := make([]instance.Instance, len(ids))
  1205  	for index, id := range ids {
  1206  		result[index] = idMap[id]
  1207  	}
  1208  
  1209  	if len(instances) < len(ids) {
  1210  		return result, environs.ErrPartialInstances
  1211  	}
  1212  	return result, nil
  1213  }
  1214  
  1215  // AllocateAddress requests an address to be allocated for the
  1216  // given instance on the given network.
  1217  func (environ *maasEnviron) AllocateAddress(instId instance.Id, subnetId network.Id, addr network.Address) error {
  1218  	subnets, err := environ.Subnets(instId, []network.Id{subnetId})
  1219  	if err != nil {
  1220  		return errors.Trace(err)
  1221  	}
  1222  	if len(subnets) != 1 {
  1223  		return errors.Errorf("could not find network matching %v", subnetId)
  1224  	}
  1225  	foundSub := subnets[0]
  1226  
  1227  	cidr := foundSub.CIDR
  1228  	ipaddresses := environ.getMAASClient().GetSubObject("ipaddresses")
  1229  	err = ReserveIPAddress(ipaddresses, cidr, addr)
  1230  	if err == nil {
  1231  		return nil
  1232  	}
  1233  
  1234  	maasErr, ok := err.(gomaasapi.ServerError)
  1235  	if !ok {
  1236  		return errors.Trace(err)
  1237  	}
  1238  	// For an "out of range" IP address, maas raises
  1239  	// StaticIPAddressOutOfRange - an error 403
  1240  	// If there are no more addresses we get
  1241  	// StaticIPAddressExhaustion - an error 503
  1242  	// For an address already in use we get
  1243  	// StaticIPAddressUnavailable - an error 404
  1244  	if maasErr.StatusCode == 404 {
  1245  		return environs.ErrIPAddressUnavailable
  1246  	} else if maasErr.StatusCode == 503 {
  1247  		return environs.ErrIPAddressesExhausted
  1248  	}
  1249  	// any error other than a 404 or 503 is "unexpected" and should
  1250  	// be returned directly.
  1251  	return errors.Trace(err)
  1252  }
  1253  
  1254  // ReleaseAddress releases a specific address previously allocated with
  1255  // AllocateAddress.
  1256  func (environ *maasEnviron) ReleaseAddress(_ instance.Id, _ network.Id, addr network.Address) error {
  1257  	ipaddresses := environ.getMAASClient().GetSubObject("ipaddresses")
  1258  	// This can return a 404 error if the address has already been released
  1259  	// or is unknown by maas. However this, like any other error, would be
  1260  	// unexpected - so we don't treat it specially and just return it to
  1261  	// the caller.
  1262  	err := ReleaseIPAddress(ipaddresses, addr)
  1263  	if err != nil {
  1264  		return errors.Annotatef(err, "failed to release IP address %v", addr.Value)
  1265  	}
  1266  	return nil
  1267  }
  1268  
  1269  // NetworkInterfaces implements Environ.NetworkInterfaces.
  1270  func (environ *maasEnviron) NetworkInterfaces(instId instance.Id) ([]network.InterfaceInfo, error) {
  1271  	instances, err := environ.acquiredInstances([]instance.Id{instId})
  1272  	if err != nil {
  1273  		return nil, errors.Annotatef(err, "could not find instance %v", instId)
  1274  	}
  1275  	if len(instances) == 0 {
  1276  		return nil, errors.NotFoundf("instance %v", instId)
  1277  	}
  1278  	inst := instances[0]
  1279  	interfaces, _, err := environ.getInstanceNetworkInterfaces(inst)
  1280  	if err != nil {
  1281  		return nil, errors.Trace(err)
  1282  	}
  1283  
  1284  	networks, err := environ.getInstanceNetworks(inst)
  1285  	if err != nil {
  1286  		return nil, errors.Annotatef(err, "getInstanceNetworks failed")
  1287  	}
  1288  
  1289  	macToNetworkMap := make(map[string]networkDetails)
  1290  	for _, network := range networks {
  1291  		macs, err := environ.listConnectedMacs(network)
  1292  		if err != nil {
  1293  			return nil, errors.Trace(err)
  1294  		}
  1295  		for _, mac := range macs {
  1296  			macToNetworkMap[mac] = network
  1297  		}
  1298  	}
  1299  
  1300  	result := []network.InterfaceInfo{}
  1301  	for serial, iface := range interfaces {
  1302  		deviceIndex := iface.DeviceIndex
  1303  		interfaceName := iface.InterfaceName
  1304  		disabled := iface.Disabled
  1305  
  1306  		ifaceInfo := network.InterfaceInfo{
  1307  			DeviceIndex:   deviceIndex,
  1308  			InterfaceName: interfaceName,
  1309  			Disabled:      disabled,
  1310  			MACAddress:    serial,
  1311  		}
  1312  		details, ok := macToNetworkMap[serial]
  1313  		if ok {
  1314  			ifaceInfo.VLANTag = details.VLANTag
  1315  			ifaceInfo.ProviderSubnetId = network.Id(details.Name)
  1316  			mask := net.IPMask(net.ParseIP(details.Mask))
  1317  			cidr := net.IPNet{net.ParseIP(details.IP), mask}
  1318  			ifaceInfo.CIDR = cidr.String()
  1319  		}
  1320  		result = append(result, ifaceInfo)
  1321  	}
  1322  	return result, nil
  1323  }
  1324  
  1325  // listConnectedMacs calls the MAAS list_connected_macs API to fetch all the
  1326  // the MAC addresses attached to a specific network.
  1327  func (environ *maasEnviron) listConnectedMacs(network networkDetails) ([]string, error) {
  1328  	client := environ.getMAASClient().GetSubObject("networks").GetSubObject(network.Name)
  1329  	json, err := client.CallGet("list_connected_macs", nil)
  1330  	if err != nil {
  1331  		return nil, err
  1332  	}
  1333  
  1334  	macs, err := json.GetArray()
  1335  	if err != nil {
  1336  		return nil, err
  1337  	}
  1338  	result := []string{}
  1339  	for _, macObj := range macs {
  1340  		macMap, err := macObj.GetMap()
  1341  		if err != nil {
  1342  			return nil, err
  1343  		}
  1344  		mac, err := macMap["mac_address"].GetString()
  1345  		if err != nil {
  1346  			return nil, err
  1347  		}
  1348  
  1349  		result = append(result, mac)
  1350  	}
  1351  	return result, nil
  1352  }
  1353  
  1354  // Subnets returns basic information about the specified subnets for a specific
  1355  // instance.
  1356  func (environ *maasEnviron) Subnets(instId instance.Id, netIds []network.Id) ([]network.SubnetInfo, error) {
  1357  	// At some point in the future an empty netIds may mean "fetch all subnets"
  1358  	// but until that functionality is needed it's an error.
  1359  	if len(netIds) == 0 {
  1360  		return nil, errors.Errorf("netIds must not be empty")
  1361  	}
  1362  	instances, err := environ.acquiredInstances([]instance.Id{instId})
  1363  	if err != nil {
  1364  		return nil, errors.Annotatef(err, "could not find instance %v", instId)
  1365  	}
  1366  	if len(instances) == 0 {
  1367  		return nil, errors.NotFoundf("instance %v", instId)
  1368  	}
  1369  	inst := instances[0]
  1370  	networks, err := environ.getInstanceNetworks(inst)
  1371  	if err != nil {
  1372  		return nil, errors.Annotatef(err, "getInstanceNetworks failed")
  1373  	}
  1374  	logger.Debugf("node %q has networks %v", instId, networks)
  1375  
  1376  	nodegroups, err := environ.getNodegroups()
  1377  	if err != nil {
  1378  		return nil, errors.Annotatef(err, "getNodegroups failed")
  1379  	}
  1380  	nodegroupInterfaces := environ.getNodegroupInterfaces(nodegroups)
  1381  
  1382  	netIdSet := make(map[network.Id]bool)
  1383  	for _, netId := range netIds {
  1384  		netIdSet[netId] = false
  1385  	}
  1386  
  1387  	var networkInfo []network.SubnetInfo
  1388  	for _, netw := range networks {
  1389  		_, ok := netIdSet[network.Id(netw.Name)]
  1390  		if !ok {
  1391  			continue
  1392  		}
  1393  		// mark that we've found this subnet
  1394  		netIdSet[network.Id(netw.Name)] = true
  1395  		netCIDR := &net.IPNet{
  1396  			IP:   net.ParseIP(netw.IP),
  1397  			Mask: net.IPMask(net.ParseIP(netw.Mask)),
  1398  		}
  1399  		var allocatableHigh, allocatableLow net.IP
  1400  		for ip, bounds := range nodegroupInterfaces {
  1401  			contained := netCIDR.Contains(net.ParseIP(ip))
  1402  			if contained {
  1403  				allocatableLow = bounds[0]
  1404  				allocatableHigh = bounds[1]
  1405  				break
  1406  			}
  1407  		}
  1408  		netInfo := network.SubnetInfo{
  1409  			CIDR:              netCIDR.String(),
  1410  			VLANTag:           netw.VLANTag,
  1411  			ProviderId:        network.Id(netw.Name),
  1412  			AllocatableIPLow:  allocatableLow,
  1413  			AllocatableIPHigh: allocatableHigh,
  1414  		}
  1415  
  1416  		// Verify we filled-in everything for all networks
  1417  		// and drop incomplete records.
  1418  		if netInfo.ProviderId == "" || netInfo.CIDR == "" {
  1419  			logger.Warningf("ignoring network  %q: missing information (%#v)", netw.Name, netInfo)
  1420  			continue
  1421  		}
  1422  
  1423  		networkInfo = append(networkInfo, netInfo)
  1424  	}
  1425  	logger.Debugf("available networks for instance %v: %#v", inst.Id(), networkInfo)
  1426  
  1427  	notFound := []network.Id{}
  1428  	for netId, found := range netIdSet {
  1429  		if !found {
  1430  			notFound = append(notFound, netId)
  1431  		}
  1432  	}
  1433  	if len(notFound) != 0 {
  1434  		return nil, errors.Errorf("failed to find the following networks: %v", notFound)
  1435  	}
  1436  
  1437  	return networkInfo, nil
  1438  }
  1439  
  1440  // AllInstances returns all the instance.Instance in this provider.
  1441  func (environ *maasEnviron) AllInstances() ([]instance.Instance, error) {
  1442  	return environ.acquiredInstances(nil)
  1443  }
  1444  
  1445  // Storage is defined by the Environ interface.
  1446  func (env *maasEnviron) Storage() storage.Storage {
  1447  	env.ecfgMutex.Lock()
  1448  	defer env.ecfgMutex.Unlock()
  1449  	return env.storageUnlocked
  1450  }
  1451  
  1452  func (environ *maasEnviron) Destroy() error {
  1453  	if err := common.Destroy(environ); err != nil {
  1454  		return errors.Trace(err)
  1455  	}
  1456  	return environ.Storage().RemoveAll()
  1457  }
  1458  
  1459  // MAAS does not do firewalling so these port methods do nothing.
  1460  func (*maasEnviron) OpenPorts([]network.PortRange) error {
  1461  	logger.Debugf("unimplemented OpenPorts() called")
  1462  	return nil
  1463  }
  1464  
  1465  func (*maasEnviron) ClosePorts([]network.PortRange) error {
  1466  	logger.Debugf("unimplemented ClosePorts() called")
  1467  	return nil
  1468  }
  1469  
  1470  func (*maasEnviron) Ports() ([]network.PortRange, error) {
  1471  	logger.Debugf("unimplemented Ports() called")
  1472  	return nil, nil
  1473  }
  1474  
  1475  func (*maasEnviron) Provider() environs.EnvironProvider {
  1476  	return &providerInstance
  1477  }
  1478  
  1479  // networkDetails holds information about a MAAS network.
  1480  type networkDetails struct {
  1481  	Name        string
  1482  	IP          string
  1483  	Mask        string
  1484  	VLANTag     int
  1485  	Description string
  1486  }
  1487  
  1488  // getInstanceNetworks returns a list of all MAAS networks for a given node.
  1489  func (environ *maasEnviron) getInstanceNetworks(inst instance.Instance) ([]networkDetails, error) {
  1490  	maasInst := inst.(*maasInstance)
  1491  	maasObj := maasInst.maasObject
  1492  	client := environ.getMAASClient().GetSubObject("networks")
  1493  	nodeId, err := maasObj.GetField("system_id")
  1494  	if err != nil {
  1495  		return nil, err
  1496  	}
  1497  	params := url.Values{"node": {nodeId}}
  1498  	json, err := client.CallGet("", params)
  1499  	if err != nil {
  1500  		return nil, err
  1501  	}
  1502  	jsonNets, err := json.GetArray()
  1503  	if err != nil {
  1504  		return nil, err
  1505  	}
  1506  
  1507  	networks := make([]networkDetails, len(jsonNets))
  1508  	for i, jsonNet := range jsonNets {
  1509  		fields, err := jsonNet.GetMap()
  1510  		if err != nil {
  1511  			return nil, err
  1512  		}
  1513  		name, err := fields["name"].GetString()
  1514  		if err != nil {
  1515  			return nil, fmt.Errorf("cannot get name: %v", err)
  1516  		}
  1517  		ip, err := fields["ip"].GetString()
  1518  		if err != nil {
  1519  			return nil, fmt.Errorf("cannot get ip: %v", err)
  1520  		}
  1521  		netmask, err := fields["netmask"].GetString()
  1522  		if err != nil {
  1523  			return nil, fmt.Errorf("cannot get netmask: %v", err)
  1524  		}
  1525  		vlanTag := 0
  1526  		vlanTagField, ok := fields["vlan_tag"]
  1527  		if ok && !vlanTagField.IsNil() {
  1528  			// vlan_tag is optional, so assume it's 0 when missing or nil.
  1529  			vlanTagFloat, err := vlanTagField.GetFloat64()
  1530  			if err != nil {
  1531  				return nil, fmt.Errorf("cannot get vlan_tag: %v", err)
  1532  			}
  1533  			vlanTag = int(vlanTagFloat)
  1534  		}
  1535  		description, err := fields["description"].GetString()
  1536  		if err != nil {
  1537  			return nil, fmt.Errorf("cannot get description: %v", err)
  1538  		}
  1539  
  1540  		networks[i] = networkDetails{
  1541  			Name:        name,
  1542  			IP:          ip,
  1543  			Mask:        netmask,
  1544  			VLANTag:     vlanTag,
  1545  			Description: description,
  1546  		}
  1547  	}
  1548  	return networks, nil
  1549  }
  1550  
  1551  // getNetworkMACs returns all MAC addresses connected to the given
  1552  // network.
  1553  func (environ *maasEnviron) getNetworkMACs(networkName string) ([]string, error) {
  1554  	client := environ.getMAASClient().GetSubObject("networks").GetSubObject(networkName)
  1555  	json, err := client.CallGet("list_connected_macs", nil)
  1556  	if err != nil {
  1557  		return nil, err
  1558  	}
  1559  	jsonMACs, err := json.GetArray()
  1560  	if err != nil {
  1561  		return nil, err
  1562  	}
  1563  
  1564  	macs := make([]string, len(jsonMACs))
  1565  	for i, jsonMAC := range jsonMACs {
  1566  		fields, err := jsonMAC.GetMap()
  1567  		if err != nil {
  1568  			return nil, err
  1569  		}
  1570  		macAddress, err := fields["mac_address"].GetString()
  1571  		if err != nil {
  1572  			return nil, fmt.Errorf("cannot get mac_address: %v", err)
  1573  		}
  1574  		macs[i] = macAddress
  1575  	}
  1576  	return macs, nil
  1577  }
  1578  
  1579  // getInstanceNetworkInterfaces returns a map of interface MAC address
  1580  // to ifaceInfo for each network interface of the given instance, as
  1581  // discovered during the commissioning phase. In addition, it also
  1582  // returns the interface name discovered as primary.
  1583  func (environ *maasEnviron) getInstanceNetworkInterfaces(inst instance.Instance) (map[string]ifaceInfo, string, error) {
  1584  	maasInst := inst.(*maasInstance)
  1585  	maasObj := maasInst.maasObject
  1586  	result, err := maasObj.CallGet("details", nil)
  1587  	if err != nil {
  1588  		return nil, "", errors.Trace(err)
  1589  	}
  1590  	// Get the node's lldp / lshw details discovered at commissioning.
  1591  	data, err := result.GetBytes()
  1592  	if err != nil {
  1593  		return nil, "", errors.Trace(err)
  1594  	}
  1595  	var parsed map[string]interface{}
  1596  	if err := bson.Unmarshal(data, &parsed); err != nil {
  1597  		return nil, "", errors.Trace(err)
  1598  	}
  1599  	lshwData, ok := parsed["lshw"]
  1600  	if !ok {
  1601  		return nil, "", errors.Errorf("no hardware information available for node %q", inst.Id())
  1602  	}
  1603  	lshwXML, ok := lshwData.([]byte)
  1604  	if !ok {
  1605  		return nil, "", errors.Errorf("invalid hardware information for node %q", inst.Id())
  1606  	}
  1607  	// Now we have the lshw XML data, parse it to extract and return NICs.
  1608  	return extractInterfaces(inst, lshwXML)
  1609  }
  1610  
  1611  type ifaceInfo struct {
  1612  	DeviceIndex   int
  1613  	InterfaceName string
  1614  	Disabled      bool
  1615  }
  1616  
  1617  // extractInterfaces parses the XML output of lswh and extracts all
  1618  // network interfaces, returing a map MAC address to ifaceInfo, as
  1619  // well as the interface name discovered as primary.
  1620  func extractInterfaces(inst instance.Instance, lshwXML []byte) (map[string]ifaceInfo, string, error) {
  1621  	type Node struct {
  1622  		Id          string `xml:"id,attr"`
  1623  		Disabled    bool   `xml:"disabled,attr,omitempty"`
  1624  		Description string `xml:"description"`
  1625  		Serial      string `xml:"serial"`
  1626  		LogicalName string `xml:"logicalname"`
  1627  		Children    []Node `xml:"node"`
  1628  	}
  1629  	type List struct {
  1630  		Nodes []Node `xml:"node"`
  1631  	}
  1632  	var lshw List
  1633  	if err := xml.Unmarshal(lshwXML, &lshw); err != nil {
  1634  		return nil, "", errors.Annotatef(err, "cannot parse lshw XML details for node %q", inst.Id())
  1635  	}
  1636  	primaryIface := ""
  1637  	interfaces := make(map[string]ifaceInfo)
  1638  	var processNodes func(nodes []Node) error
  1639  	processNodes = func(nodes []Node) error {
  1640  		for _, node := range nodes {
  1641  			if strings.HasPrefix(node.Id, "network") {
  1642  				// If there's a single interface, the ID won't have an
  1643  				// index suffix.
  1644  				index := 0
  1645  				if strings.HasPrefix(node.Id, "network:") {
  1646  					// There is an index suffix, parse it.
  1647  					var err error
  1648  					index, err = strconv.Atoi(strings.TrimPrefix(node.Id, "network:"))
  1649  					if err != nil {
  1650  						return errors.Annotatef(err, "lshw output for node %q has invalid ID suffix for %q", inst.Id(), node.Id)
  1651  					}
  1652  				}
  1653  				if primaryIface == "" && !node.Disabled {
  1654  					primaryIface = node.LogicalName
  1655  					logger.Debugf("node %q primary network interface is %q", inst.Id(), primaryIface)
  1656  				}
  1657  				interfaces[node.Serial] = ifaceInfo{
  1658  					DeviceIndex:   index,
  1659  					InterfaceName: node.LogicalName,
  1660  					Disabled:      node.Disabled,
  1661  				}
  1662  				if node.Disabled {
  1663  					logger.Debugf("node %q skipping disabled network interface %q", inst.Id(), node.LogicalName)
  1664  				}
  1665  
  1666  			}
  1667  			if err := processNodes(node.Children); err != nil {
  1668  				return err
  1669  			}
  1670  		}
  1671  		return nil
  1672  	}
  1673  	err := processNodes(lshw.Nodes)
  1674  	return interfaces, primaryIface, err
  1675  }