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