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