github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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  	"encoding/json"
     8  	"fmt"
     9  	"net/http"
    10  	"net/url"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/juju/errors"
    18  	"github.com/juju/gomaasapi"
    19  	"github.com/juju/utils"
    20  	"github.com/juju/utils/os"
    21  	"github.com/juju/utils/series"
    22  	"github.com/juju/utils/set"
    23  	"gopkg.in/juju/names.v2"
    24  
    25  	"github.com/juju/juju/cloudconfig/cloudinit"
    26  	"github.com/juju/juju/cloudconfig/instancecfg"
    27  	"github.com/juju/juju/cloudconfig/providerinit"
    28  	"github.com/juju/juju/constraints"
    29  	"github.com/juju/juju/environs"
    30  	"github.com/juju/juju/environs/config"
    31  	"github.com/juju/juju/environs/storage"
    32  	"github.com/juju/juju/environs/tags"
    33  	"github.com/juju/juju/instance"
    34  	"github.com/juju/juju/network"
    35  	"github.com/juju/juju/provider/common"
    36  	"github.com/juju/juju/state/multiwatcher"
    37  	"github.com/juju/juju/status"
    38  	"github.com/juju/juju/tools"
    39  )
    40  
    41  const (
    42  	// The version strings indicating the MAAS API version.
    43  	apiVersion1 = "1.0"
    44  	apiVersion2 = "2.0"
    45  )
    46  
    47  // A request may fail to due "eventual consistency" semantics, which
    48  // should resolve fairly quickly.  A request may also fail due to a slow
    49  // state transition (for instance an instance taking a while to release
    50  // a security group after termination).  The former failure mode is
    51  // dealt with by shortAttempt, the latter by LongAttempt.
    52  var shortAttempt = utils.AttemptStrategy{
    53  	Total: 5 * time.Second,
    54  	Delay: 200 * time.Millisecond,
    55  }
    56  
    57  const statusPollInterval = 5 * time.Second
    58  
    59  var (
    60  	ReleaseNodes         = releaseNodes
    61  	DeploymentStatusCall = deploymentStatusCall
    62  	GetCapabilities      = getCapabilities
    63  	GetMAAS2Controller   = getMAAS2Controller
    64  )
    65  
    66  func getMAAS2Controller(maasServer, apiKey string) (gomaasapi.Controller, error) {
    67  	return gomaasapi.NewController(gomaasapi.ControllerArgs{
    68  		BaseURL: maasServer,
    69  		APIKey:  apiKey,
    70  	})
    71  }
    72  
    73  func releaseNodes(nodes gomaasapi.MAASObject, ids url.Values) error {
    74  	_, err := nodes.CallPost("release", ids)
    75  	return err
    76  }
    77  
    78  type maasEnviron struct {
    79  	name  string
    80  	cloud environs.CloudSpec
    81  	uuid  string
    82  
    83  	// archMutex gates access to supportedArchitectures
    84  	archMutex sync.Mutex
    85  
    86  	// ecfgMutex protects the *Unlocked fields below.
    87  	ecfgMutex sync.Mutex
    88  
    89  	ecfgUnlocked       *maasModelConfig
    90  	maasClientUnlocked *gomaasapi.MAASObject
    91  	storageUnlocked    storage.Storage
    92  
    93  	// maasController provides access to the MAAS 2.0 API.
    94  	maasController gomaasapi.Controller
    95  
    96  	// namespace is used to create the machine and device hostnames.
    97  	namespace instance.Namespace
    98  
    99  	availabilityZonesMutex sync.Mutex
   100  	availabilityZones      []common.AvailabilityZone
   101  
   102  	// apiVersion tells us if we are using the MAAS 1.0 or 2.0 api.
   103  	apiVersion string
   104  }
   105  
   106  var _ environs.Environ = (*maasEnviron)(nil)
   107  
   108  func NewEnviron(cloud environs.CloudSpec, cfg *config.Config) (*maasEnviron, error) {
   109  	env := &maasEnviron{
   110  		name:  cfg.Name(),
   111  		uuid:  cfg.UUID(),
   112  		cloud: cloud,
   113  	}
   114  	err := env.SetConfig(cfg)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	env.storageUnlocked = NewStorage(env)
   119  
   120  	env.namespace, err = instance.NewNamespace(cfg.UUID())
   121  	if err != nil {
   122  		return nil, errors.Trace(err)
   123  	}
   124  	return env, nil
   125  }
   126  
   127  func (env *maasEnviron) usingMAAS2() bool {
   128  	return env.apiVersion == apiVersion2
   129  }
   130  
   131  // PrepareForBootstrap is part of the Environ interface.
   132  func (env *maasEnviron) PrepareForBootstrap(ctx environs.BootstrapContext) error {
   133  	if ctx.ShouldVerifyCredentials() {
   134  		if err := verifyCredentials(env); err != nil {
   135  			return err
   136  		}
   137  	}
   138  	return nil
   139  }
   140  
   141  // Create is part of the Environ interface.
   142  func (env *maasEnviron) Create(environs.CreateParams) error {
   143  	if err := verifyCredentials(env); err != nil {
   144  		return err
   145  	}
   146  	return nil
   147  }
   148  
   149  // Bootstrap is part of the Environ interface.
   150  func (env *maasEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) {
   151  	result, series, finalizer, err := common.BootstrapInstance(ctx, env, args)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	// We want to destroy the started instance if it doesn't transition to Deployed.
   157  	defer func() {
   158  		if err != nil {
   159  			if err := env.StopInstances(result.Instance.Id()); err != nil {
   160  				logger.Errorf("error releasing bootstrap instance: %v", err)
   161  			}
   162  		}
   163  	}()
   164  
   165  	waitingFinalizer := func(
   166  		ctx environs.BootstrapContext,
   167  		icfg *instancecfg.InstanceConfig,
   168  		dialOpts environs.BootstrapDialOpts,
   169  	) error {
   170  		// Wait for bootstrap instance to change to deployed state.
   171  		if err := env.waitForNodeDeployment(result.Instance.Id(), dialOpts.Timeout); err != nil {
   172  			return errors.Annotate(err, "bootstrap instance started but did not change to Deployed state")
   173  		}
   174  		return finalizer(ctx, icfg, dialOpts)
   175  	}
   176  
   177  	bsResult := &environs.BootstrapResult{
   178  		Arch:     *result.Hardware.Arch,
   179  		Series:   series,
   180  		Finalize: waitingFinalizer,
   181  	}
   182  	return bsResult, nil
   183  }
   184  
   185  // BootstrapMessage is part of the Environ interface.
   186  func (env *maasEnviron) BootstrapMessage() string {
   187  	return ""
   188  }
   189  
   190  // ControllerInstances is specified in the Environ interface.
   191  func (env *maasEnviron) ControllerInstances(controllerUUID string) ([]instance.Id, error) {
   192  	if !env.usingMAAS2() {
   193  		return env.controllerInstances1(controllerUUID)
   194  	}
   195  	return env.controllerInstances2(controllerUUID)
   196  }
   197  
   198  func (env *maasEnviron) controllerInstances1(controllerUUID string) ([]instance.Id, error) {
   199  	return common.ProviderStateInstances(env.Storage())
   200  }
   201  
   202  func (env *maasEnviron) controllerInstances2(controllerUUID string) ([]instance.Id, error) {
   203  	instances, err := env.instances2(gomaasapi.MachinesArgs{
   204  		OwnerData: map[string]string{
   205  			tags.JujuIsController: "true",
   206  			tags.JujuController:   controllerUUID,
   207  		},
   208  	})
   209  	if err != nil {
   210  		return nil, errors.Trace(err)
   211  	}
   212  	if len(instances) == 0 {
   213  		return nil, environs.ErrNotBootstrapped
   214  	}
   215  	ids := make([]instance.Id, len(instances))
   216  	for i := range instances {
   217  		ids[i] = instances[i].Id()
   218  	}
   219  	return ids, nil
   220  }
   221  
   222  // ecfg returns the environment's maasModelConfig, and protects it with a
   223  // mutex.
   224  func (env *maasEnviron) ecfg() *maasModelConfig {
   225  	env.ecfgMutex.Lock()
   226  	cfg := *env.ecfgUnlocked
   227  	env.ecfgMutex.Unlock()
   228  	return &cfg
   229  }
   230  
   231  // Config is specified in the Environ interface.
   232  func (env *maasEnviron) Config() *config.Config {
   233  	return env.ecfg().Config
   234  }
   235  
   236  // SetConfig is specified in the Environ interface.
   237  func (env *maasEnviron) SetConfig(cfg *config.Config) error {
   238  	env.ecfgMutex.Lock()
   239  	defer env.ecfgMutex.Unlock()
   240  
   241  	// The new config has already been validated by itself, but now we
   242  	// validate the transition from the old config to the new.
   243  	var oldCfg *config.Config
   244  	if env.ecfgUnlocked != nil {
   245  		oldCfg = env.ecfgUnlocked.Config
   246  	}
   247  	cfg, err := env.Provider().Validate(cfg, oldCfg)
   248  	if err != nil {
   249  		return errors.Trace(err)
   250  	}
   251  
   252  	ecfg, err := providerInstance.newConfig(cfg)
   253  	if err != nil {
   254  		return errors.Trace(err)
   255  	}
   256  
   257  	env.ecfgUnlocked = ecfg
   258  
   259  	maasServer, err := parseCloudEndpoint(env.cloud.Endpoint)
   260  	if err != nil {
   261  		return errors.Trace(err)
   262  	}
   263  	maasOAuth, err := parseOAuthToken(*env.cloud.Credential)
   264  	if err != nil {
   265  		return errors.Trace(err)
   266  	}
   267  
   268  	// We need to know the version of the server we're on. We support 1.9
   269  	// and 2.0. MAAS 1.9 uses the 1.0 api version and 2.0 uses the 2.0 api
   270  	// version.
   271  	apiVersion := apiVersion2
   272  	controller, err := GetMAAS2Controller(maasServer, maasOAuth)
   273  	switch {
   274  	case gomaasapi.IsUnsupportedVersionError(err):
   275  		apiVersion = apiVersion1
   276  		authClient, err := gomaasapi.NewAuthenticatedClient(maasServer, maasOAuth, apiVersion1)
   277  		if err != nil {
   278  			return errors.Trace(err)
   279  		}
   280  		env.maasClientUnlocked = gomaasapi.NewMAAS(*authClient)
   281  		caps, err := GetCapabilities(env.maasClientUnlocked, maasServer)
   282  		if err != nil {
   283  			return errors.Trace(err)
   284  		}
   285  		if !caps.Contains(capNetworkDeploymentUbuntu) {
   286  			return errors.NewNotSupported(nil, "MAAS 1.9 or more recent is required")
   287  		}
   288  	case err != nil:
   289  		return errors.Trace(err)
   290  	default:
   291  		env.maasController = controller
   292  	}
   293  	env.apiVersion = apiVersion
   294  	return nil
   295  }
   296  
   297  func (env *maasEnviron) getSupportedArchitectures() ([]string, error) {
   298  	env.archMutex.Lock()
   299  	defer env.archMutex.Unlock()
   300  	fetchArchitectures := env.allArchitecturesWithFallback
   301  	if env.usingMAAS2() {
   302  		fetchArchitectures = env.allArchitectures2
   303  	}
   304  	return fetchArchitectures()
   305  }
   306  
   307  // SupportsSpaces is specified on environs.Networking.
   308  func (env *maasEnviron) SupportsSpaces() (bool, error) {
   309  	return true, nil
   310  }
   311  
   312  // SupportsSpaceDiscovery is specified on environs.Networking.
   313  func (env *maasEnviron) SupportsSpaceDiscovery() (bool, error) {
   314  	return true, nil
   315  }
   316  
   317  // allArchitectures2 uses the MAAS2 controller to get architectures from boot
   318  // resources.
   319  func (env *maasEnviron) allArchitectures2() ([]string, error) {
   320  	resources, err := env.maasController.BootResources()
   321  	if err != nil {
   322  		return nil, errors.Trace(err)
   323  	}
   324  	architectures := set.NewStrings()
   325  	for _, resource := range resources {
   326  		architectures.Add(strings.Split(resource.Architecture(), "/")[0])
   327  	}
   328  	return architectures.SortedValues(), nil
   329  }
   330  
   331  // allArchitectureWithFallback queries MAAS for all of the boot-images
   332  // across all registered nodegroups and collapses them down to unique
   333  // architectures.
   334  func (env *maasEnviron) allArchitecturesWithFallback() ([]string, error) {
   335  	architectures, err := env.allArchitectures()
   336  	if err != nil || len(architectures) == 0 {
   337  		logger.Debugf("error querying boot-images: %v", err)
   338  		logger.Debugf("falling back to listing nodes")
   339  		architectures, err := env.nodeArchitectures()
   340  		if err != nil {
   341  			return nil, errors.Trace(err)
   342  		}
   343  		return architectures, nil
   344  	} else {
   345  		return architectures, nil
   346  	}
   347  }
   348  
   349  func (env *maasEnviron) allArchitectures() ([]string, error) {
   350  	nodegroups, err := env.getNodegroups()
   351  	if err != nil {
   352  		return nil, err
   353  	}
   354  	architectures := set.NewStrings()
   355  	for _, nodegroup := range nodegroups {
   356  		bootImages, err := env.nodegroupBootImages(nodegroup)
   357  		if err != nil {
   358  			return nil, errors.Annotatef(err, "cannot get boot images for nodegroup %v", nodegroup)
   359  		}
   360  		for _, image := range bootImages {
   361  			architectures.Add(image.architecture)
   362  		}
   363  	}
   364  	return architectures.SortedValues(), nil
   365  }
   366  
   367  // getNodegroups returns the UUID corresponding to each nodegroup
   368  // in the MAAS installation.
   369  func (env *maasEnviron) getNodegroups() ([]string, error) {
   370  	nodegroupsListing := env.getMAASClient().GetSubObject("nodegroups")
   371  	nodegroupsResult, err := nodegroupsListing.CallGet("list", nil)
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  	list, err := nodegroupsResult.GetArray()
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  	nodegroups := make([]string, len(list))
   380  	for i, obj := range list {
   381  		nodegroup, err := obj.GetMap()
   382  		if err != nil {
   383  			return nil, err
   384  		}
   385  		uuid, err := nodegroup["uuid"].GetString()
   386  		if err != nil {
   387  			return nil, err
   388  		}
   389  		nodegroups[i] = uuid
   390  	}
   391  	return nodegroups, nil
   392  }
   393  
   394  type bootImage struct {
   395  	architecture string
   396  	release      string
   397  }
   398  
   399  // nodegroupBootImages returns the set of boot-images for the specified nodegroup.
   400  func (env *maasEnviron) nodegroupBootImages(nodegroupUUID string) ([]bootImage, error) {
   401  	nodegroupObject := env.getMAASClient().GetSubObject("nodegroups").GetSubObject(nodegroupUUID)
   402  	bootImagesObject := nodegroupObject.GetSubObject("boot-images/")
   403  	result, err := bootImagesObject.CallGet("", nil)
   404  	if err != nil {
   405  		return nil, err
   406  	}
   407  	list, err := result.GetArray()
   408  	if err != nil {
   409  		return nil, err
   410  	}
   411  	var bootImages []bootImage
   412  	for _, obj := range list {
   413  		bootimage, err := obj.GetMap()
   414  		if err != nil {
   415  			return nil, err
   416  		}
   417  		arch, err := bootimage["architecture"].GetString()
   418  		if err != nil {
   419  			return nil, err
   420  		}
   421  		release, err := bootimage["release"].GetString()
   422  		if err != nil {
   423  			return nil, err
   424  		}
   425  		bootImages = append(bootImages, bootImage{
   426  			architecture: arch,
   427  			release:      release,
   428  		})
   429  	}
   430  	return bootImages, nil
   431  }
   432  
   433  // nodeArchitectures returns the architectures of all
   434  // available nodes in the system.
   435  //
   436  // Note: this should only be used if we cannot query
   437  // boot-images.
   438  func (env *maasEnviron) nodeArchitectures() ([]string, error) {
   439  	filter := make(url.Values)
   440  	filter.Add("status", gomaasapi.NodeStatusDeclared)
   441  	filter.Add("status", gomaasapi.NodeStatusCommissioning)
   442  	filter.Add("status", gomaasapi.NodeStatusReady)
   443  	filter.Add("status", gomaasapi.NodeStatusReserved)
   444  	filter.Add("status", gomaasapi.NodeStatusAllocated)
   445  	// This is fine - nodeArchitectures is only used in MAAS 1 cases.
   446  	allInstances, err := env.instances1(filter)
   447  	if err != nil {
   448  		return nil, err
   449  	}
   450  	architectures := make(set.Strings)
   451  	for _, inst := range allInstances {
   452  		inst := inst.(*maas1Instance)
   453  		arch, _, err := inst.architecture()
   454  		if err != nil {
   455  			return nil, err
   456  		}
   457  		architectures.Add(arch)
   458  	}
   459  	// TODO(dfc) why is this sorted
   460  	return architectures.SortedValues(), nil
   461  }
   462  
   463  type maasAvailabilityZone struct {
   464  	name string
   465  }
   466  
   467  func (z maasAvailabilityZone) Name() string {
   468  	return z.name
   469  }
   470  
   471  func (z maasAvailabilityZone) Available() bool {
   472  	// MAAS' physical zone attributes only include name and description;
   473  	// there is no concept of availability.
   474  	return true
   475  }
   476  
   477  // AvailabilityZones returns a slice of availability zones
   478  // for the configured region.
   479  func (e *maasEnviron) AvailabilityZones() ([]common.AvailabilityZone, error) {
   480  	e.availabilityZonesMutex.Lock()
   481  	defer e.availabilityZonesMutex.Unlock()
   482  	if e.availabilityZones == nil {
   483  		var availabilityZones []common.AvailabilityZone
   484  		var err error
   485  		if e.usingMAAS2() {
   486  			availabilityZones, err = e.availabilityZones2()
   487  			if err != nil {
   488  				return nil, errors.Trace(err)
   489  			}
   490  		} else {
   491  			availabilityZones, err = e.availabilityZones1()
   492  			if err != nil {
   493  				return nil, errors.Trace(err)
   494  			}
   495  		}
   496  		e.availabilityZones = availabilityZones
   497  	}
   498  	return e.availabilityZones, nil
   499  }
   500  
   501  func (e *maasEnviron) availabilityZones1() ([]common.AvailabilityZone, error) {
   502  	zonesObject := e.getMAASClient().GetSubObject("zones")
   503  	result, err := zonesObject.CallGet("", nil)
   504  	if err, ok := errors.Cause(err).(gomaasapi.ServerError); ok && err.StatusCode == http.StatusNotFound {
   505  		return nil, errors.NewNotImplemented(nil, "the MAAS server does not support zones")
   506  	}
   507  	if err != nil {
   508  		return nil, errors.Annotate(err, "cannot query ")
   509  	}
   510  	list, err := result.GetArray()
   511  	if err != nil {
   512  		return nil, err
   513  	}
   514  	logger.Debugf("availability zones: %+v", list)
   515  	availabilityZones := make([]common.AvailabilityZone, len(list))
   516  	for i, obj := range list {
   517  		zone, err := obj.GetMap()
   518  		if err != nil {
   519  			return nil, err
   520  		}
   521  		name, err := zone["name"].GetString()
   522  		if err != nil {
   523  			return nil, err
   524  		}
   525  		availabilityZones[i] = maasAvailabilityZone{name}
   526  	}
   527  	return availabilityZones, nil
   528  }
   529  
   530  func (e *maasEnviron) availabilityZones2() ([]common.AvailabilityZone, error) {
   531  	zones, err := e.maasController.Zones()
   532  	if err != nil {
   533  		return nil, errors.Trace(err)
   534  	}
   535  	availabilityZones := make([]common.AvailabilityZone, len(zones))
   536  	for i, zone := range zones {
   537  		availabilityZones[i] = maasAvailabilityZone{zone.Name()}
   538  	}
   539  	return availabilityZones, nil
   540  
   541  }
   542  
   543  // InstanceAvailabilityZoneNames returns the availability zone names for each
   544  // of the specified instances.
   545  func (e *maasEnviron) InstanceAvailabilityZoneNames(ids []instance.Id) ([]string, error) {
   546  	instances, err := e.Instances(ids)
   547  	if err != nil && err != environs.ErrPartialInstances {
   548  		return nil, err
   549  	}
   550  	zones := make([]string, len(instances))
   551  	for i, inst := range instances {
   552  		if inst == nil {
   553  			continue
   554  		}
   555  		z, err := inst.(maasInstance).zone()
   556  		if err != nil {
   557  			logger.Errorf("could not get availability zone %v", err)
   558  			continue
   559  		}
   560  		zones[i] = z
   561  	}
   562  	return zones, nil
   563  }
   564  
   565  type maasPlacement struct {
   566  	nodeName string
   567  	zoneName string
   568  }
   569  
   570  func (e *maasEnviron) parsePlacement(placement string) (*maasPlacement, error) {
   571  	pos := strings.IndexRune(placement, '=')
   572  	if pos == -1 {
   573  		// If there's no '=' delimiter, assume it's a node name.
   574  		return &maasPlacement{nodeName: placement}, nil
   575  	}
   576  	switch key, value := placement[:pos], placement[pos+1:]; key {
   577  	case "zone":
   578  		availabilityZone := value
   579  		zones, err := e.AvailabilityZones()
   580  		if err != nil {
   581  			return nil, err
   582  		}
   583  		for _, z := range zones {
   584  			if z.Name() == availabilityZone {
   585  				return &maasPlacement{zoneName: availabilityZone}, nil
   586  			}
   587  		}
   588  		return nil, errors.Errorf("invalid availability zone %q", availabilityZone)
   589  	}
   590  	return nil, errors.Errorf("unknown placement directive: %v", placement)
   591  }
   592  
   593  func (env *maasEnviron) PrecheckInstance(series string, cons constraints.Value, placement string) error {
   594  	if placement == "" {
   595  		return nil
   596  	}
   597  	_, err := env.parsePlacement(placement)
   598  	return err
   599  }
   600  
   601  const (
   602  	capNetworkDeploymentUbuntu = "network-deployment-ubuntu"
   603  )
   604  
   605  // getCapabilities asks the MAAS server for its capabilities, if
   606  // supported by the server.
   607  func getCapabilities(client *gomaasapi.MAASObject, serverURL string) (set.Strings, error) {
   608  	caps := make(set.Strings)
   609  	var result gomaasapi.JSONObject
   610  	var err error
   611  
   612  	for a := shortAttempt.Start(); a.Next(); {
   613  		version := client.GetSubObject("version/")
   614  		result, err = version.CallGet("", nil)
   615  		if err != nil {
   616  			if err, ok := errors.Cause(err).(gomaasapi.ServerError); ok && err.StatusCode == 404 {
   617  				message := "could not connect to MAAS controller - check the endpoint is correct"
   618  				trimmedURL := strings.TrimRight(serverURL, "/")
   619  				if !strings.HasSuffix(trimmedURL, "/MAAS") {
   620  					message += " (it normally ends with /MAAS)"
   621  				}
   622  				return caps, errors.NewNotSupported(nil, message)
   623  			}
   624  		} else {
   625  			break
   626  		}
   627  	}
   628  	if err != nil {
   629  		return caps, err
   630  	}
   631  	info, err := result.GetMap()
   632  	if err != nil {
   633  		return caps, err
   634  	}
   635  	capsObj, ok := info["capabilities"]
   636  	if !ok {
   637  		return caps, fmt.Errorf("MAAS does not report capabilities")
   638  	}
   639  	items, err := capsObj.GetArray()
   640  	if err != nil {
   641  		return caps, err
   642  	}
   643  	for _, item := range items {
   644  		val, err := item.GetString()
   645  		if err != nil {
   646  			return set.NewStrings(), err
   647  		}
   648  		caps.Add(val)
   649  	}
   650  	return caps, nil
   651  }
   652  
   653  // getMAASClient returns a MAAS client object to use for a request, in a
   654  // lock-protected fashion.
   655  func (env *maasEnviron) getMAASClient() *gomaasapi.MAASObject {
   656  	env.ecfgMutex.Lock()
   657  	defer env.ecfgMutex.Unlock()
   658  
   659  	return env.maasClientUnlocked
   660  }
   661  
   662  var dashSuffix = regexp.MustCompile("^(.*)-\\d+$")
   663  
   664  func spaceNamesToSpaceInfo(spaces []string, spaceMap map[string]network.SpaceInfo) ([]network.SpaceInfo, error) {
   665  	spaceInfos := []network.SpaceInfo{}
   666  	for _, name := range spaces {
   667  		info, ok := spaceMap[name]
   668  		if !ok {
   669  			matches := dashSuffix.FindAllStringSubmatch(name, 1)
   670  			if matches == nil {
   671  				return nil, errors.Errorf("unrecognised space in constraint %q", name)
   672  			}
   673  			// A -number was added to the space name when we
   674  			// converted to a juju name, we found
   675  			info, ok = spaceMap[matches[0][1]]
   676  			if !ok {
   677  				return nil, errors.Errorf("unrecognised space in constraint %q", name)
   678  			}
   679  		}
   680  		spaceInfos = append(spaceInfos, info)
   681  	}
   682  	return spaceInfos, nil
   683  }
   684  
   685  func (environ *maasEnviron) buildSpaceMap() (map[string]network.SpaceInfo, error) {
   686  	spaces, err := environ.Spaces()
   687  	if err != nil {
   688  		return nil, errors.Trace(err)
   689  	}
   690  	spaceMap := make(map[string]network.SpaceInfo)
   691  	empty := set.Strings{}
   692  	for _, space := range spaces {
   693  		jujuName := network.ConvertSpaceName(space.Name, empty)
   694  		spaceMap[jujuName] = space
   695  	}
   696  	return spaceMap, nil
   697  }
   698  
   699  func (environ *maasEnviron) spaceNamesToSpaceInfo(positiveSpaces, negativeSpaces []string) ([]network.SpaceInfo, []network.SpaceInfo, error) {
   700  	spaceMap, err := environ.buildSpaceMap()
   701  	if err != nil {
   702  		return nil, nil, errors.Trace(err)
   703  	}
   704  
   705  	positiveSpaceIds, err := spaceNamesToSpaceInfo(positiveSpaces, spaceMap)
   706  	if err != nil {
   707  		return nil, nil, errors.Trace(err)
   708  	}
   709  	negativeSpaceIds, err := spaceNamesToSpaceInfo(negativeSpaces, spaceMap)
   710  	if err != nil {
   711  		return nil, nil, errors.Trace(err)
   712  	}
   713  	return positiveSpaceIds, negativeSpaceIds, nil
   714  }
   715  
   716  // acquireNode2 allocates a machine from MAAS2.
   717  func (environ *maasEnviron) acquireNode2(
   718  	nodeName, zoneName string,
   719  	cons constraints.Value,
   720  	interfaces []interfaceBinding,
   721  	volumes []volumeInfo,
   722  ) (maasInstance, error) {
   723  	acquireParams := convertConstraints2(cons)
   724  	positiveSpaceNames, negativeSpaceNames := convertSpacesFromConstraints(cons.Spaces)
   725  	positiveSpaces, negativeSpaces, err := environ.spaceNamesToSpaceInfo(positiveSpaceNames, negativeSpaceNames)
   726  	// If spaces aren't supported the constraints should be empty anyway.
   727  	if err != nil && !errors.IsNotSupported(err) {
   728  		return nil, errors.Trace(err)
   729  	}
   730  	err = addInterfaces2(&acquireParams, interfaces, positiveSpaces, negativeSpaces)
   731  	if err != nil {
   732  		return nil, errors.Trace(err)
   733  	}
   734  	addStorage2(&acquireParams, volumes)
   735  	acquireParams.AgentName = environ.uuid
   736  	if zoneName != "" {
   737  		acquireParams.Zone = zoneName
   738  	}
   739  	if nodeName != "" {
   740  		acquireParams.Hostname = nodeName
   741  	}
   742  	machine, constraintMatches, err := environ.maasController.AllocateMachine(acquireParams)
   743  
   744  	if err != nil {
   745  		return nil, errors.Trace(err)
   746  	}
   747  	return &maas2Instance{machine, constraintMatches}, nil
   748  }
   749  
   750  // acquireNode allocates a node from the MAAS.
   751  func (environ *maasEnviron) acquireNode(
   752  	nodeName, zoneName string,
   753  	cons constraints.Value,
   754  	interfaces []interfaceBinding,
   755  	volumes []volumeInfo,
   756  ) (gomaasapi.MAASObject, error) {
   757  
   758  	// TODO(axw) 2014-08-18 #1358219
   759  	// We should be requesting preferred architectures if unspecified,
   760  	// like in the other providers.
   761  	//
   762  	// This is slightly complicated in MAAS as there are a finite
   763  	// number of each architecture; preference may also conflict with
   764  	// other constraints, such as tags. Thus, a preference becomes a
   765  	// demand (which may fail) if not handled properly.
   766  
   767  	acquireParams := convertConstraints(cons)
   768  	positiveSpaceNames, negativeSpaceNames := convertSpacesFromConstraints(cons.Spaces)
   769  	positiveSpaces, negativeSpaces, err := environ.spaceNamesToSpaceInfo(positiveSpaceNames, negativeSpaceNames)
   770  	// If spaces aren't supported the constraints should be empty anyway.
   771  	if err != nil && !errors.IsNotSupported(err) {
   772  		return gomaasapi.MAASObject{}, errors.Trace(err)
   773  	}
   774  	err = addInterfaces(acquireParams, interfaces, positiveSpaces, negativeSpaces)
   775  	if err != nil {
   776  		return gomaasapi.MAASObject{}, errors.Trace(err)
   777  	}
   778  	addStorage(acquireParams, volumes)
   779  	acquireParams.Add("agent_name", environ.uuid)
   780  	if zoneName != "" {
   781  		acquireParams.Add("zone", zoneName)
   782  	}
   783  	if nodeName != "" {
   784  		acquireParams.Add("name", nodeName)
   785  	}
   786  
   787  	var result gomaasapi.JSONObject
   788  	for a := shortAttempt.Start(); a.Next(); {
   789  		client := environ.getMAASClient().GetSubObject("nodes/")
   790  		logger.Tracef("calling acquire with params: %+v", acquireParams)
   791  		result, err = client.CallPost("acquire", acquireParams)
   792  		if err == nil {
   793  			break
   794  		}
   795  	}
   796  	if err != nil {
   797  		return gomaasapi.MAASObject{}, err
   798  	}
   799  	node, err := result.GetMAASObject()
   800  	if err != nil {
   801  		err := errors.Annotate(err, "unexpected result from 'acquire' on MAAS API")
   802  		return gomaasapi.MAASObject{}, err
   803  	}
   804  	return node, nil
   805  }
   806  
   807  // startNode installs and boots a node.
   808  func (environ *maasEnviron) startNode(node gomaasapi.MAASObject, series string, userdata []byte) (*gomaasapi.MAASObject, error) {
   809  	params := url.Values{
   810  		"distro_series": {series},
   811  		"user_data":     {string(userdata)},
   812  	}
   813  	// Initialize err to a non-nil value as a sentinel for the following
   814  	// loop.
   815  	err := fmt.Errorf("(no error)")
   816  	var result gomaasapi.JSONObject
   817  	for a := shortAttempt.Start(); a.Next() && err != nil; {
   818  		result, err = node.CallPost("start", params)
   819  		if err == nil {
   820  			break
   821  		}
   822  	}
   823  
   824  	if err == nil {
   825  		var startedNode gomaasapi.MAASObject
   826  		startedNode, err = result.GetMAASObject()
   827  		if err != nil {
   828  			logger.Errorf("cannot process API response after successfully starting node: %v", err)
   829  			return nil, err
   830  		}
   831  		return &startedNode, nil
   832  	}
   833  	return nil, err
   834  }
   835  
   836  func (environ *maasEnviron) startNode2(node maas2Instance, series string, userdata []byte) (*maas2Instance, error) {
   837  	err := node.machine.Start(gomaasapi.StartArgs{DistroSeries: series, UserData: string(userdata)})
   838  	if err != nil {
   839  		return nil, errors.Trace(err)
   840  	}
   841  	// Machine.Start updates the machine in-place when it succeeds.
   842  	return &maas2Instance{machine: node.machine}, nil
   843  
   844  }
   845  
   846  // DistributeInstances implements the state.InstanceDistributor policy.
   847  func (e *maasEnviron) DistributeInstances(candidates, distributionGroup []instance.Id) ([]instance.Id, error) {
   848  	return common.DistributeInstances(e, candidates, distributionGroup)
   849  }
   850  
   851  var availabilityZoneAllocations = common.AvailabilityZoneAllocations
   852  
   853  // MaintainInstance is specified in the InstanceBroker interface.
   854  func (*maasEnviron) MaintainInstance(args environs.StartInstanceParams) error {
   855  	return nil
   856  }
   857  
   858  // StartInstance is specified in the InstanceBroker interface.
   859  func (environ *maasEnviron) StartInstance(args environs.StartInstanceParams) (
   860  	*environs.StartInstanceResult, error,
   861  ) {
   862  	var availabilityZones []string
   863  	var nodeName string
   864  	if args.Placement != "" {
   865  		placement, err := environ.parsePlacement(args.Placement)
   866  		if err != nil {
   867  			return nil, err
   868  		}
   869  		switch {
   870  		case placement.zoneName != "":
   871  			availabilityZones = append(availabilityZones, placement.zoneName)
   872  		default:
   873  			nodeName = placement.nodeName
   874  		}
   875  	}
   876  
   877  	// If no placement is specified, then automatically spread across
   878  	// the known zones for optimal spread across the instance distribution
   879  	// group.
   880  	if args.Placement == "" {
   881  		var group []instance.Id
   882  		var err error
   883  		if args.DistributionGroup != nil {
   884  			group, err = args.DistributionGroup()
   885  			if err != nil {
   886  				return nil, errors.Annotate(err, "cannot get distribution group")
   887  			}
   888  		}
   889  		zoneInstances, err := availabilityZoneAllocations(environ, group)
   890  		// TODO (mfoord): this branch is for old versions of MAAS and
   891  		// can be removed, but this means fixing tests.
   892  		if errors.IsNotImplemented(err) {
   893  			// Availability zones are an extension, so we may get a
   894  			// not implemented error; ignore these.
   895  		} else if err != nil {
   896  			return nil, errors.Annotate(err, "cannot get availability zone allocations")
   897  		} else if len(zoneInstances) > 0 {
   898  			for _, z := range zoneInstances {
   899  				availabilityZones = append(availabilityZones, z.ZoneName)
   900  			}
   901  		}
   902  	}
   903  	if len(availabilityZones) == 0 {
   904  		availabilityZones = []string{""}
   905  	}
   906  
   907  	// Storage.
   908  	volumes, err := buildMAASVolumeParameters(args.Volumes, args.Constraints)
   909  	if err != nil {
   910  		return nil, errors.Annotate(err, "invalid volume parameters")
   911  	}
   912  
   913  	var interfaceBindings []interfaceBinding
   914  	if len(args.EndpointBindings) != 0 {
   915  		for endpoint, spaceProviderID := range args.EndpointBindings {
   916  			interfaceBindings = append(interfaceBindings, interfaceBinding{
   917  				Name:            endpoint,
   918  				SpaceProviderId: string(spaceProviderID),
   919  			})
   920  		}
   921  	}
   922  	snArgs := selectNodeArgs{
   923  		Constraints:       args.Constraints,
   924  		AvailabilityZones: availabilityZones,
   925  		NodeName:          nodeName,
   926  		Interfaces:        interfaceBindings,
   927  		Volumes:           volumes,
   928  	}
   929  	var inst maasInstance
   930  	if !environ.usingMAAS2() {
   931  		selectedNode, err := environ.selectNode(snArgs)
   932  		if err != nil {
   933  			return nil, errors.Errorf("cannot run instances: %v", err)
   934  		}
   935  
   936  		inst = &maas1Instance{
   937  			maasObject:   selectedNode,
   938  			environ:      environ,
   939  			statusGetter: environ.deploymentStatusOne,
   940  		}
   941  	} else {
   942  		inst, err = environ.selectNode2(snArgs)
   943  		if err != nil {
   944  			return nil, errors.Annotatef(err, "cannot run instances")
   945  		}
   946  	}
   947  
   948  	defer func() {
   949  		if err != nil {
   950  			if err := environ.StopInstances(inst.Id()); err != nil {
   951  				logger.Errorf("error releasing failed instance: %v", err)
   952  			}
   953  		}
   954  	}()
   955  
   956  	hc, err := inst.hardwareCharacteristics()
   957  	if err != nil {
   958  		return nil, err
   959  	}
   960  
   961  	series := args.Tools.OneSeries()
   962  	selectedTools, err := args.Tools.Match(tools.Filter{
   963  		Arch: *hc.Arch,
   964  	})
   965  	if err != nil {
   966  		return nil, errors.Trace(err)
   967  	}
   968  	if err := args.InstanceConfig.SetTools(selectedTools); err != nil {
   969  		return nil, errors.Trace(err)
   970  	}
   971  
   972  	hostname, err := inst.hostname()
   973  	if err != nil {
   974  		return nil, err
   975  	}
   976  
   977  	if err := instancecfg.FinishInstanceConfig(args.InstanceConfig, environ.Config()); err != nil {
   978  		return nil, errors.Trace(err)
   979  	}
   980  
   981  	subnetsMap, err := environ.subnetToSpaceIds()
   982  	if err != nil {
   983  		return nil, errors.Trace(err)
   984  	}
   985  
   986  	// We need to extract the names of all interfaces of the selected node,
   987  	// which are both linked to subnets, and have an IP address, in order to
   988  	// pass those to the bridge script.
   989  	interfaceNamesToBridge, err := instanceConfiguredInterfaceNames(environ.usingMAAS2(), inst, subnetsMap)
   990  	if err != nil {
   991  		return nil, errors.Trace(err)
   992  	}
   993  
   994  	cloudcfg, err := environ.newCloudinitConfig(hostname, series, interfaceNamesToBridge)
   995  	if err != nil {
   996  		return nil, errors.Trace(err)
   997  	}
   998  
   999  	userdata, err := providerinit.ComposeUserData(args.InstanceConfig, cloudcfg, MAASRenderer{})
  1000  	if err != nil {
  1001  		return nil, errors.Annotatef(err, "could not compose userdata for bootstrap node")
  1002  	}
  1003  	logger.Debugf("maas user data; %d bytes", len(userdata))
  1004  
  1005  	var interfaces []network.InterfaceInfo
  1006  	if !environ.usingMAAS2() {
  1007  		inst1 := inst.(*maas1Instance)
  1008  		startedNode, err := environ.startNode(*inst1.maasObject, series, userdata)
  1009  		if err != nil {
  1010  			return nil, errors.Trace(err)
  1011  		}
  1012  		// Once the instance has started the response should contain the
  1013  		// assigned IP addresses, even when NICs are set to "auto" instead of
  1014  		// "static". So instead of selectedNode, which only contains the
  1015  		// acquire-time details (no IP addresses for NICs set to "auto" vs
  1016  		// "static"),e we use the up-to-date startedNode response to get the
  1017  		// interfaces.
  1018  		interfaces, err = maasObjectNetworkInterfaces(startedNode, subnetsMap)
  1019  		if err != nil {
  1020  			return nil, errors.Trace(err)
  1021  		}
  1022  		environ.tagInstance1(inst1, args.InstanceConfig)
  1023  	} else {
  1024  		inst2 := inst.(*maas2Instance)
  1025  		startedInst, err := environ.startNode2(*inst2, series, userdata)
  1026  		if err != nil {
  1027  			return nil, errors.Trace(err)
  1028  		}
  1029  		interfaces, err = maas2NetworkInterfaces(startedInst, subnetsMap)
  1030  		if err != nil {
  1031  			return nil, errors.Trace(err)
  1032  		}
  1033  		environ.tagInstance2(inst2, args.InstanceConfig)
  1034  	}
  1035  	logger.Debugf("started instance %q", inst.Id())
  1036  
  1037  	requestedVolumes := make([]names.VolumeTag, len(args.Volumes))
  1038  	for i, v := range args.Volumes {
  1039  		requestedVolumes[i] = v.Tag
  1040  	}
  1041  	resultVolumes, resultAttachments, err := inst.volumes(
  1042  		names.NewMachineTag(args.InstanceConfig.MachineId),
  1043  		requestedVolumes,
  1044  	)
  1045  	if err != nil {
  1046  		return nil, err
  1047  	}
  1048  	if len(resultVolumes) != len(requestedVolumes) {
  1049  		err = errors.Errorf("requested %v storage volumes. %v returned.", len(requestedVolumes), len(resultVolumes))
  1050  		return nil, err
  1051  	}
  1052  
  1053  	return &environs.StartInstanceResult{
  1054  		Instance:          inst,
  1055  		Hardware:          hc,
  1056  		NetworkInfo:       interfaces,
  1057  		Volumes:           resultVolumes,
  1058  		VolumeAttachments: resultAttachments,
  1059  	}, nil
  1060  }
  1061  
  1062  func instanceConfiguredInterfaceNames(usingMAAS2 bool, inst instance.Instance, subnetsMap map[string]network.Id) ([]string, error) {
  1063  	var (
  1064  		interfaces []network.InterfaceInfo
  1065  		err        error
  1066  	)
  1067  	if !usingMAAS2 {
  1068  		inst1 := inst.(*maas1Instance)
  1069  		interfaces, err = maasObjectNetworkInterfaces(inst1.maasObject, subnetsMap)
  1070  		if err != nil {
  1071  			return nil, errors.Trace(err)
  1072  		}
  1073  	} else {
  1074  		inst2 := inst.(*maas2Instance)
  1075  		interfaces, err = maas2NetworkInterfaces(inst2, subnetsMap)
  1076  		if err != nil {
  1077  			return nil, errors.Trace(err)
  1078  		}
  1079  	}
  1080  
  1081  	nameToNumAliases := make(map[string]int)
  1082  	var linkedNames []string
  1083  	for _, iface := range interfaces {
  1084  		if iface.CIDR == "" { // CIDR comes from a linked subnet.
  1085  			continue
  1086  		}
  1087  
  1088  		switch iface.ConfigType {
  1089  		case network.ConfigUnknown, network.ConfigManual:
  1090  			continue // link is unconfigured
  1091  		}
  1092  
  1093  		finalName := iface.InterfaceName
  1094  		numAliases, seen := nameToNumAliases[iface.InterfaceName]
  1095  		if !seen {
  1096  			nameToNumAliases[iface.InterfaceName] = 0
  1097  		} else {
  1098  			numAliases++ // aliases start from 1
  1099  			finalName += fmt.Sprintf(":%d", numAliases)
  1100  			nameToNumAliases[iface.InterfaceName] = numAliases
  1101  		}
  1102  
  1103  		linkedNames = append(linkedNames, finalName)
  1104  	}
  1105  	systemID := extractSystemId(inst.Id())
  1106  	logger.Infof("interface names to bridge for node %q: %v", systemID, linkedNames)
  1107  
  1108  	return linkedNames, nil
  1109  }
  1110  
  1111  func (environ *maasEnviron) tagInstance1(inst *maas1Instance, instanceConfig *instancecfg.InstanceConfig) {
  1112  	if !multiwatcher.AnyJobNeedsState(instanceConfig.Jobs...) {
  1113  		return
  1114  	}
  1115  	err := common.AddStateInstance(environ.Storage(), inst.Id())
  1116  	if err != nil {
  1117  		logger.Errorf("could not record instance in provider-state: %v", err)
  1118  	}
  1119  }
  1120  
  1121  func (environ *maasEnviron) tagInstance2(inst *maas2Instance, instanceConfig *instancecfg.InstanceConfig) {
  1122  	err := inst.machine.SetOwnerData(instanceConfig.Tags)
  1123  	if err != nil {
  1124  		logger.Errorf("could not set owner data for instance: %v", err)
  1125  	}
  1126  }
  1127  
  1128  func (environ *maasEnviron) waitForNodeDeployment(id instance.Id, timeout time.Duration) error {
  1129  	if environ.usingMAAS2() {
  1130  		return environ.waitForNodeDeployment2(id, timeout)
  1131  	}
  1132  	systemId := extractSystemId(id)
  1133  
  1134  	longAttempt := utils.AttemptStrategy{
  1135  		Delay: 10 * time.Second,
  1136  		Total: timeout,
  1137  	}
  1138  
  1139  	for a := longAttempt.Start(); a.Next(); {
  1140  		statusValues, err := environ.deploymentStatus(id)
  1141  		if errors.IsNotImplemented(err) {
  1142  			return nil
  1143  		}
  1144  		if err != nil {
  1145  			return errors.Trace(err)
  1146  		}
  1147  		if statusValues[systemId] == "Deployed" {
  1148  			return nil
  1149  		}
  1150  		if statusValues[systemId] == "Failed deployment" {
  1151  			return errors.Errorf("instance %q failed to deploy", id)
  1152  		}
  1153  	}
  1154  	return errors.Errorf("instance %q is started but not deployed", id)
  1155  }
  1156  
  1157  func (environ *maasEnviron) waitForNodeDeployment2(id instance.Id, timeout time.Duration) error {
  1158  	// TODO(katco): 2016-08-09: lp:1611427
  1159  	longAttempt := utils.AttemptStrategy{
  1160  		Delay: 10 * time.Second,
  1161  		Total: timeout,
  1162  	}
  1163  
  1164  	for a := longAttempt.Start(); a.Next(); {
  1165  		machine, err := environ.getInstance(id)
  1166  		if err != nil {
  1167  			return errors.Trace(err)
  1168  		}
  1169  		stat := machine.Status()
  1170  		if stat.Status == status.Running {
  1171  			return nil
  1172  		}
  1173  		if stat.Status == status.ProvisioningError {
  1174  			return errors.Errorf("instance %q failed to deploy", id)
  1175  
  1176  		}
  1177  	}
  1178  	return errors.Errorf("instance %q is started but not deployed", id)
  1179  }
  1180  
  1181  func (environ *maasEnviron) deploymentStatusOne(id instance.Id) (string, string) {
  1182  	results, err := environ.deploymentStatus(id)
  1183  	if err != nil {
  1184  		return "", ""
  1185  	}
  1186  	systemId := extractSystemId(id)
  1187  	substatus := environ.getDeploymentSubstatus(systemId)
  1188  	return results[systemId], substatus
  1189  }
  1190  
  1191  func (environ *maasEnviron) getDeploymentSubstatus(systemId string) string {
  1192  	nodesAPI := environ.getMAASClient().GetSubObject("nodes")
  1193  	result, err := nodesAPI.CallGet("list", nil)
  1194  	if err != nil {
  1195  		return ""
  1196  	}
  1197  	slices, err := result.GetArray()
  1198  	if err != nil {
  1199  		return ""
  1200  	}
  1201  	for _, slice := range slices {
  1202  		resultMap, err := slice.GetMap()
  1203  		if err != nil {
  1204  			continue
  1205  		}
  1206  		sysId, err := resultMap["system_id"].GetString()
  1207  		if err != nil {
  1208  			continue
  1209  		}
  1210  		if sysId == systemId {
  1211  			message, err := resultMap["substatus_message"].GetString()
  1212  			if err != nil {
  1213  				logger.Warningf("could not get string for substatus_message: %v", resultMap["substatus_message"])
  1214  				return ""
  1215  			}
  1216  			return message
  1217  		}
  1218  	}
  1219  
  1220  	return ""
  1221  }
  1222  
  1223  // deploymentStatus returns the deployment state of MAAS instances with
  1224  // the specified Juju instance ids.
  1225  // Note: the result is a map of MAAS systemId to state.
  1226  func (environ *maasEnviron) deploymentStatus(ids ...instance.Id) (map[string]string, error) {
  1227  	nodesAPI := environ.getMAASClient().GetSubObject("nodes")
  1228  	result, err := DeploymentStatusCall(nodesAPI, ids...)
  1229  	if err != nil {
  1230  		if err, ok := errors.Cause(err).(gomaasapi.ServerError); ok && err.StatusCode == http.StatusBadRequest {
  1231  			return nil, errors.NewNotImplemented(err, "deployment status")
  1232  		}
  1233  		return nil, errors.Trace(err)
  1234  	}
  1235  	resultMap, err := result.GetMap()
  1236  	if err != nil {
  1237  		return nil, errors.Trace(err)
  1238  	}
  1239  	statusValues := make(map[string]string)
  1240  	for systemId, jsonValue := range resultMap {
  1241  		status, err := jsonValue.GetString()
  1242  		if err != nil {
  1243  			return nil, errors.Trace(err)
  1244  		}
  1245  		statusValues[systemId] = status
  1246  	}
  1247  	return statusValues, nil
  1248  }
  1249  
  1250  func deploymentStatusCall(nodes gomaasapi.MAASObject, ids ...instance.Id) (gomaasapi.JSONObject, error) {
  1251  	filter := getSystemIdValues("nodes", ids)
  1252  	return nodes.CallGet("deployment_status", filter)
  1253  }
  1254  
  1255  type selectNodeArgs struct {
  1256  	AvailabilityZones []string
  1257  	NodeName          string
  1258  	Constraints       constraints.Value
  1259  	Interfaces        []interfaceBinding
  1260  	Volumes           []volumeInfo
  1261  }
  1262  
  1263  func (environ *maasEnviron) selectNode(args selectNodeArgs) (*gomaasapi.MAASObject, error) {
  1264  	var err error
  1265  	var node gomaasapi.MAASObject
  1266  
  1267  	for i, zoneName := range args.AvailabilityZones {
  1268  		node, err = environ.acquireNode(
  1269  			args.NodeName,
  1270  			zoneName,
  1271  			args.Constraints,
  1272  			args.Interfaces,
  1273  			args.Volumes,
  1274  		)
  1275  
  1276  		if err, ok := errors.Cause(err).(gomaasapi.ServerError); ok && err.StatusCode == http.StatusConflict {
  1277  			if i+1 < len(args.AvailabilityZones) {
  1278  				logger.Infof("could not acquire a node in zone %q, trying another zone", zoneName)
  1279  				continue
  1280  			}
  1281  		}
  1282  		if err != nil {
  1283  			return nil, errors.Errorf("cannot run instances: %v", err)
  1284  		}
  1285  		// Since a return at the end of the function is required
  1286  		// just break here.
  1287  		break
  1288  	}
  1289  	return &node, nil
  1290  }
  1291  
  1292  func (environ *maasEnviron) selectNode2(args selectNodeArgs) (maasInstance, error) {
  1293  	var err error
  1294  	var inst maasInstance
  1295  
  1296  	for i, zoneName := range args.AvailabilityZones {
  1297  		inst, err = environ.acquireNode2(
  1298  			args.NodeName,
  1299  			zoneName,
  1300  			args.Constraints,
  1301  			args.Interfaces,
  1302  			args.Volumes,
  1303  		)
  1304  
  1305  		if gomaasapi.IsNoMatchError(err) {
  1306  			if i+1 < len(args.AvailabilityZones) {
  1307  				logger.Infof("could not acquire a node in zone %q, trying another zone", zoneName)
  1308  				continue
  1309  			}
  1310  		}
  1311  		if err != nil {
  1312  			return nil, errors.Annotatef(err, "cannot run instance")
  1313  		}
  1314  		// Since a return at the end of the function is required
  1315  		// just break here.
  1316  		break
  1317  	}
  1318  	return inst, nil
  1319  }
  1320  
  1321  // setupJujuNetworking returns a string representing the script to run
  1322  // in order to prepare the Juju-specific networking config on a node.
  1323  func setupJujuNetworking(interfacesToBridge []string) string {
  1324  	// For ubuntu series < xenial we prefer python2 over python3
  1325  	// as we don't want to invalidate lots of testing against
  1326  	// known cloud-image contents. A summary of Ubuntu releases
  1327  	// and python inclusion in the default install of Ubuntu
  1328  	// Server is as follows:
  1329  	//
  1330  	// 12.04 precise:  python 2 (2.7.3)
  1331  	// 14.04 trusty:   python 2 (2.7.5) and python3 (3.4.0)
  1332  	// 14.10 utopic:   python 2 (2.7.8) and python3 (3.4.2)
  1333  	// 15.04 vivid:    python 2 (2.7.9) and python3 (3.4.3)
  1334  	// 15.10 wily:     python 2 (2.7.9) and python3 (3.4.3)
  1335  	// 16.04 xenial:   python 3 only (3.5.1)
  1336  	//
  1337  	// going forward:  python 3 only
  1338  
  1339  	return fmt.Sprintf(`
  1340  trap 'rm -f %[1]q' EXIT
  1341  
  1342  if [ -x /usr/bin/python2 ]; then
  1343      juju_networking_preferred_python_binary=/usr/bin/python2
  1344  elif [ -x /usr/bin/python3 ]; then
  1345      juju_networking_preferred_python_binary=/usr/bin/python3
  1346  elif [ -x /usr/bin/python ]; then
  1347      juju_networking_preferred_python_binary=/usr/bin/python
  1348  fi
  1349  
  1350  if [ ! -z "${juju_networking_preferred_python_binary:-}" ]; then
  1351      if [ -f %[1]q ]; then
  1352  # We are sharing this code between 2.0 and 1.25.
  1353  # For the moment we want 2.0 to bridge all interfaces linked
  1354  # to a subnet, while for 1.25 we only bridge the default route interface.
  1355  # This setting allows us to easily switch the behaviour when merging
  1356  # the code between those various branches.
  1357          juju_bridge_linked_interfaces=1
  1358          if [ $juju_bridge_linked_interfaces -eq 1 ]; then
  1359              $juju_networking_preferred_python_binary %[1]q --bridge-prefix=%[2]q --interfaces-to-bridge=%[5]q --one-time-backup --activate %[4]q
  1360          else
  1361              juju_ipv4_interface_to_bridge=$(ip -4 route list exact default | head -n1 | cut -d' ' -f5)
  1362              $juju_networking_preferred_python_binary %[1]q --bridge-name=%[3]q --interfaces-to-bridge="${juju_ipv4_interface_to_bridge:-unknown}" --one-time-backup --activate %[4]q
  1363          fi
  1364      fi
  1365  else
  1366      echo "error: no Python installation found; cannot run Juju's bridge script"
  1367  fi`,
  1368  		bridgeScriptPath,
  1369  		instancecfg.DefaultBridgePrefix,
  1370  		instancecfg.DefaultBridgeName,
  1371  		"/etc/network/interfaces",
  1372  		strings.Join(interfacesToBridge, " "),
  1373  	)
  1374  }
  1375  
  1376  func renderEtcNetworkInterfacesScript(interfacesToBridge ...string) string {
  1377  	return setupJujuNetworking(interfacesToBridge)
  1378  }
  1379  
  1380  // newCloudinitConfig creates a cloudinit.Config structure suitable as a base
  1381  // for initialising a MAAS node.
  1382  func (environ *maasEnviron) newCloudinitConfig(hostname, forSeries string, interfacesToBridge []string) (cloudinit.CloudConfig, error) {
  1383  	cloudcfg, err := cloudinit.New(forSeries)
  1384  	if err != nil {
  1385  		return nil, err
  1386  	}
  1387  
  1388  	info := machineInfo{hostname}
  1389  	runCmd, err := info.cloudinitRunCmd(cloudcfg)
  1390  	if err != nil {
  1391  		return nil, errors.Trace(err)
  1392  	}
  1393  
  1394  	operatingSystem, err := series.GetOSFromSeries(forSeries)
  1395  	if err != nil {
  1396  		return nil, errors.Trace(err)
  1397  	}
  1398  	switch operatingSystem {
  1399  	case os.Windows:
  1400  		cloudcfg.AddScripts(runCmd)
  1401  	case os.Ubuntu:
  1402  		cloudcfg.SetSystemUpdate(true)
  1403  		cloudcfg.AddScripts("set -xe", runCmd)
  1404  		// DisableNetworkManagement can still disable the bridge(s) creation.
  1405  		if on, set := environ.Config().DisableNetworkManagement(); on && set {
  1406  			logger.Infof(
  1407  				"network management disabled - not using %q bridge for containers",
  1408  				instancecfg.DefaultBridgeName,
  1409  			)
  1410  			break
  1411  		}
  1412  		cloudcfg.AddPackage("bridge-utils")
  1413  		cloudcfg.AddBootTextFile(bridgeScriptPath, bridgeScriptPython, 0755)
  1414  		cloudcfg.AddScripts(setupJujuNetworking(interfacesToBridge))
  1415  	}
  1416  	return cloudcfg, nil
  1417  }
  1418  
  1419  func (environ *maasEnviron) releaseNodes1(nodes gomaasapi.MAASObject, ids url.Values, recurse bool) error {
  1420  	err := ReleaseNodes(nodes, ids)
  1421  	if err == nil {
  1422  		return nil
  1423  	}
  1424  	maasErr, ok := errors.Cause(err).(gomaasapi.ServerError)
  1425  	if !ok {
  1426  		return errors.Annotate(err, "cannot release nodes")
  1427  	}
  1428  
  1429  	// StatusCode 409 means a node couldn't be released due to
  1430  	// a state conflict. Likely it's already released or disk
  1431  	// erasing. We're assuming an error of 409 *only* means it's
  1432  	// safe to assume the instance is already released.
  1433  	// MaaS also releases (or attempts) all nodes, and raises
  1434  	// a single error on failure. So even with an error 409, all
  1435  	// nodes have been released.
  1436  	if maasErr.StatusCode == 409 {
  1437  		logger.Infof("ignoring error while releasing nodes (%v); all nodes released OK", err)
  1438  		return nil
  1439  	}
  1440  
  1441  	// a status code of 400, 403 or 404 means one of the nodes
  1442  	// couldn't be found and none have been released. We have
  1443  	// to release all the ones we can individually.
  1444  	if maasErr.StatusCode != 400 && maasErr.StatusCode != 403 && maasErr.StatusCode != 404 {
  1445  		return errors.Annotate(err, "cannot release nodes")
  1446  	}
  1447  	if !recurse {
  1448  		// this node has already been released and we're golden
  1449  		return nil
  1450  	}
  1451  
  1452  	var lastErr error
  1453  	for _, id := range ids["nodes"] {
  1454  		idFilter := url.Values{}
  1455  		idFilter.Add("nodes", id)
  1456  		err := environ.releaseNodes1(nodes, idFilter, false)
  1457  		if err != nil {
  1458  			lastErr = err
  1459  			logger.Errorf("error while releasing node %v (%v)", id, err)
  1460  		}
  1461  	}
  1462  	return errors.Trace(lastErr)
  1463  
  1464  }
  1465  
  1466  func (environ *maasEnviron) releaseNodes2(ids []instance.Id, recurse bool) error {
  1467  	args := gomaasapi.ReleaseMachinesArgs{
  1468  		SystemIDs: instanceIdsToSystemIDs(ids),
  1469  		Comment:   "Released by Juju MAAS provider",
  1470  	}
  1471  	err := environ.maasController.ReleaseMachines(args)
  1472  
  1473  	switch {
  1474  	case err == nil:
  1475  		return nil
  1476  	case gomaasapi.IsCannotCompleteError(err):
  1477  		// CannotCompleteError means a node couldn't be released due to
  1478  		// a state conflict. Likely it's already released or disk
  1479  		// erasing. We're assuming this error *only* means it's
  1480  		// safe to assume the instance is already released.
  1481  		// MaaS also releases (or attempts) all nodes, and raises
  1482  		// a single error on failure. So even with an error 409, all
  1483  		// nodes have been released.
  1484  		logger.Infof("ignoring error while releasing nodes (%v); all nodes released OK", err)
  1485  		return nil
  1486  	case gomaasapi.IsBadRequestError(err), gomaasapi.IsPermissionError(err):
  1487  		// a status code of 400 or 403 means one of the nodes
  1488  		// couldn't be found and none have been released. We have to
  1489  		// release all the ones we can individually.
  1490  		if !recurse {
  1491  			// this node has already been released and we're golden
  1492  			return nil
  1493  		}
  1494  		return environ.releaseNodesIndividually(ids)
  1495  
  1496  	default:
  1497  		return errors.Annotatef(err, "cannot release nodes")
  1498  	}
  1499  }
  1500  
  1501  func (environ *maasEnviron) releaseNodesIndividually(ids []instance.Id) error {
  1502  	var lastErr error
  1503  	for _, id := range ids {
  1504  		err := environ.releaseNodes2([]instance.Id{id}, false)
  1505  		if err != nil {
  1506  			lastErr = err
  1507  			logger.Errorf("error while releasing node %v (%v)", id, err)
  1508  		}
  1509  	}
  1510  	return errors.Trace(lastErr)
  1511  }
  1512  
  1513  func instanceIdsToSystemIDs(ids []instance.Id) []string {
  1514  	systemIDs := make([]string, len(ids))
  1515  	for index, id := range ids {
  1516  		systemIDs[index] = string(id)
  1517  	}
  1518  	return systemIDs
  1519  }
  1520  
  1521  // StopInstances is specified in the InstanceBroker interface.
  1522  func (environ *maasEnviron) StopInstances(ids ...instance.Id) error {
  1523  	// Shortcut to exit quickly if 'instances' is an empty slice or nil.
  1524  	if len(ids) == 0 {
  1525  		return nil
  1526  	}
  1527  
  1528  	if environ.usingMAAS2() {
  1529  		err := environ.releaseNodes2(ids, true)
  1530  		if err != nil {
  1531  			return errors.Trace(err)
  1532  		}
  1533  	} else {
  1534  		nodes := environ.getMAASClient().GetSubObject("nodes")
  1535  		err := environ.releaseNodes1(nodes, getSystemIdValues("nodes", ids), true)
  1536  		if err != nil {
  1537  			return errors.Trace(err)
  1538  		}
  1539  	}
  1540  	return common.RemoveStateInstances(environ.Storage(), ids...)
  1541  
  1542  }
  1543  
  1544  // acquireInstances calls the MAAS API to list acquired nodes.
  1545  //
  1546  // The "ids" slice is a filter for specific instance IDs.
  1547  // Due to how this works in the HTTP API, an empty "ids"
  1548  // matches all instances (not none as you might expect).
  1549  func (environ *maasEnviron) acquiredInstances(ids []instance.Id) ([]instance.Instance, error) {
  1550  	if !environ.usingMAAS2() {
  1551  		filter := getSystemIdValues("id", ids)
  1552  		filter.Add("agent_name", environ.uuid)
  1553  		return environ.instances1(filter)
  1554  	}
  1555  	args := gomaasapi.MachinesArgs{
  1556  		AgentName: environ.uuid,
  1557  		SystemIDs: instanceIdsToSystemIDs(ids),
  1558  	}
  1559  	return environ.instances2(args)
  1560  }
  1561  
  1562  // instances calls the MAAS API to list nodes matching the given filter.
  1563  func (environ *maasEnviron) instances1(filter url.Values) ([]instance.Instance, error) {
  1564  	nodeListing := environ.getMAASClient().GetSubObject("nodes")
  1565  	listNodeObjects, err := nodeListing.CallGet("list", filter)
  1566  	if err != nil {
  1567  		return nil, err
  1568  	}
  1569  	listNodes, err := listNodeObjects.GetArray()
  1570  	if err != nil {
  1571  		return nil, err
  1572  	}
  1573  	instances := make([]instance.Instance, len(listNodes))
  1574  	for index, nodeObj := range listNodes {
  1575  		node, err := nodeObj.GetMAASObject()
  1576  		if err != nil {
  1577  			return nil, err
  1578  		}
  1579  		instances[index] = &maas1Instance{
  1580  			maasObject:   &node,
  1581  			environ:      environ,
  1582  			statusGetter: environ.deploymentStatusOne,
  1583  		}
  1584  	}
  1585  	return instances, nil
  1586  }
  1587  
  1588  func (environ *maasEnviron) instances2(args gomaasapi.MachinesArgs) ([]instance.Instance, error) {
  1589  	machines, err := environ.maasController.Machines(args)
  1590  	if err != nil {
  1591  		return nil, errors.Trace(err)
  1592  	}
  1593  	instances := make([]instance.Instance, len(machines))
  1594  	for index, machine := range machines {
  1595  		instances[index] = &maas2Instance{machine: machine}
  1596  	}
  1597  	return instances, nil
  1598  }
  1599  
  1600  // Instances returns the instance.Instance objects corresponding to the given
  1601  // slice of instance.Id.  The error is ErrNoInstances if no instances
  1602  // were found.
  1603  func (environ *maasEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) {
  1604  	if len(ids) == 0 {
  1605  		// This would be treated as "return all instances" below, so
  1606  		// treat it as a special case.
  1607  		// The interface requires us to return this particular error
  1608  		// if no instances were found.
  1609  		return nil, environs.ErrNoInstances
  1610  	}
  1611  	instances, err := environ.acquiredInstances(ids)
  1612  	if err != nil {
  1613  		return nil, errors.Trace(err)
  1614  	}
  1615  	if len(instances) == 0 {
  1616  		return nil, environs.ErrNoInstances
  1617  	}
  1618  
  1619  	idMap := make(map[instance.Id]instance.Instance)
  1620  	for _, instance := range instances {
  1621  		idMap[instance.Id()] = instance
  1622  	}
  1623  
  1624  	missing := false
  1625  	result := make([]instance.Instance, len(ids))
  1626  	for index, id := range ids {
  1627  		val, ok := idMap[id]
  1628  		if !ok {
  1629  			missing = true
  1630  			continue
  1631  		}
  1632  		result[index] = val
  1633  	}
  1634  
  1635  	if missing {
  1636  		return result, environs.ErrPartialInstances
  1637  	}
  1638  	return result, nil
  1639  }
  1640  
  1641  // subnetsFromNode fetches all the subnets for a specific node.
  1642  func (environ *maasEnviron) subnetsFromNode(nodeId string) ([]gomaasapi.JSONObject, error) {
  1643  	client := environ.getMAASClient().GetSubObject("nodes").GetSubObject(nodeId)
  1644  	json, err := client.CallGet("", nil)
  1645  	if err != nil {
  1646  		if maasErr, ok := errors.Cause(err).(gomaasapi.ServerError); ok && maasErr.StatusCode == http.StatusNotFound {
  1647  			return nil, errors.NotFoundf("intance %q", nodeId)
  1648  		}
  1649  		return nil, errors.Trace(err)
  1650  	}
  1651  	nodeMap, err := json.GetMap()
  1652  	if err != nil {
  1653  		return nil, errors.Trace(err)
  1654  	}
  1655  	interfacesArray, err := nodeMap["interface_set"].GetArray()
  1656  	if err != nil {
  1657  		return nil, errors.Trace(err)
  1658  	}
  1659  	var subnets []gomaasapi.JSONObject
  1660  	for _, iface := range interfacesArray {
  1661  		ifaceMap, err := iface.GetMap()
  1662  		if err != nil {
  1663  			return nil, errors.Trace(err)
  1664  		}
  1665  		linksArray, err := ifaceMap["links"].GetArray()
  1666  		if err != nil {
  1667  			return nil, errors.Trace(err)
  1668  		}
  1669  		for _, link := range linksArray {
  1670  			linkMap, err := link.GetMap()
  1671  			if err != nil {
  1672  				return nil, errors.Trace(err)
  1673  			}
  1674  			subnet, ok := linkMap["subnet"]
  1675  			if !ok {
  1676  				return nil, errors.New("subnet not found")
  1677  			}
  1678  			subnets = append(subnets, subnet)
  1679  		}
  1680  	}
  1681  	return subnets, nil
  1682  }
  1683  
  1684  // subnetFromJson populates a network.SubnetInfo from a gomaasapi.JSONObject
  1685  // representing a single subnet. This can come from either the subnets api
  1686  // endpoint or the node endpoint.
  1687  func (environ *maasEnviron) subnetFromJson(subnet gomaasapi.JSONObject, spaceId network.Id) (network.SubnetInfo, error) {
  1688  	var subnetInfo network.SubnetInfo
  1689  	fields, err := subnet.GetMap()
  1690  	if err != nil {
  1691  		return subnetInfo, errors.Trace(err)
  1692  	}
  1693  	subnetIdFloat, err := fields["id"].GetFloat64()
  1694  	if err != nil {
  1695  		return subnetInfo, errors.Annotatef(err, "cannot get subnet Id")
  1696  	}
  1697  	subnetId := strconv.Itoa(int(subnetIdFloat))
  1698  	cidr, err := fields["cidr"].GetString()
  1699  	if err != nil {
  1700  		return subnetInfo, errors.Annotatef(err, "cannot get cidr")
  1701  	}
  1702  	vid := 0
  1703  	vidField, ok := fields["vid"]
  1704  	if ok && !vidField.IsNil() {
  1705  		// vid is optional, so assume it's 0 when missing or nil.
  1706  		vidFloat, err := vidField.GetFloat64()
  1707  		if err != nil {
  1708  			return subnetInfo, errors.Errorf("cannot get vlan tag: %v", err)
  1709  		}
  1710  		vid = int(vidFloat)
  1711  	}
  1712  
  1713  	subnetInfo = network.SubnetInfo{
  1714  		ProviderId:      network.Id(subnetId),
  1715  		VLANTag:         vid,
  1716  		CIDR:            cidr,
  1717  		SpaceProviderId: spaceId,
  1718  	}
  1719  	return subnetInfo, nil
  1720  }
  1721  
  1722  // filteredSubnets fetches subnets, filtering optionally by nodeId and/or a
  1723  // slice of subnetIds. If subnetIds is empty then all subnets for that node are
  1724  // fetched. If nodeId is empty, all subnets are returned (filtering by subnetIds
  1725  // first, if set).
  1726  func (environ *maasEnviron) filteredSubnets(nodeId string, subnetIds []network.Id) ([]network.SubnetInfo, error) {
  1727  	var jsonNets []gomaasapi.JSONObject
  1728  	var err error
  1729  	if nodeId != "" {
  1730  		jsonNets, err = environ.subnetsFromNode(nodeId)
  1731  		if err != nil {
  1732  			return nil, errors.Trace(err)
  1733  		}
  1734  	} else {
  1735  		jsonNets, err = environ.fetchAllSubnets()
  1736  		if err != nil {
  1737  			return nil, errors.Trace(err)
  1738  		}
  1739  	}
  1740  	subnetIdSet := make(map[string]bool)
  1741  	for _, netId := range subnetIds {
  1742  		subnetIdSet[string(netId)] = false
  1743  	}
  1744  
  1745  	subnetsMap, err := environ.subnetToSpaceIds()
  1746  	if err != nil {
  1747  		return nil, errors.Trace(err)
  1748  	}
  1749  
  1750  	subnets := []network.SubnetInfo{}
  1751  	for _, jsonNet := range jsonNets {
  1752  		fields, err := jsonNet.GetMap()
  1753  		if err != nil {
  1754  			return nil, err
  1755  		}
  1756  		subnetIdFloat, err := fields["id"].GetFloat64()
  1757  		if err != nil {
  1758  			return nil, errors.Annotatef(err, "cannot get subnet Id: %v")
  1759  		}
  1760  		subnetId := strconv.Itoa(int(subnetIdFloat))
  1761  		// If we're filtering by subnet id check if this subnet is one
  1762  		// we're looking for.
  1763  		if len(subnetIds) != 0 {
  1764  			_, ok := subnetIdSet[subnetId]
  1765  			if !ok {
  1766  				// This id is not what we're looking for.
  1767  				continue
  1768  			}
  1769  			subnetIdSet[subnetId] = true
  1770  		}
  1771  		cidr, err := fields["cidr"].GetString()
  1772  		if err != nil {
  1773  			return nil, errors.Annotatef(err, "cannot get subnet Id")
  1774  		}
  1775  		spaceId, ok := subnetsMap[cidr]
  1776  		if !ok {
  1777  			logger.Warningf("unrecognised subnet: %q, setting empty space id", cidr)
  1778  			spaceId = network.UnknownId
  1779  		}
  1780  
  1781  		subnetInfo, err := environ.subnetFromJson(jsonNet, spaceId)
  1782  		if err != nil {
  1783  			return nil, errors.Trace(err)
  1784  		}
  1785  		subnets = append(subnets, subnetInfo)
  1786  		logger.Tracef("found subnet with info %#v", subnetInfo)
  1787  	}
  1788  	return subnets, checkNotFound(subnetIdSet)
  1789  }
  1790  
  1791  func (environ *maasEnviron) getInstance(instId instance.Id) (instance.Instance, error) {
  1792  	instances, err := environ.acquiredInstances([]instance.Id{instId})
  1793  	if err != nil {
  1794  		// This path can never trigger on MAAS 2, but MAAS 2 doesn't
  1795  		// return an error for a machine not found, it just returns
  1796  		// empty results. The clause below catches that.
  1797  		if maasErr, ok := errors.Cause(err).(gomaasapi.ServerError); ok && maasErr.StatusCode == http.StatusNotFound {
  1798  			return nil, errors.NotFoundf("instance %q", instId)
  1799  		}
  1800  		return nil, errors.Annotatef(err, "getting instance %q", instId)
  1801  	}
  1802  	if len(instances) == 0 {
  1803  		return nil, errors.NotFoundf("instance %q", instId)
  1804  	}
  1805  	inst := instances[0]
  1806  	return inst, nil
  1807  }
  1808  
  1809  // fetchAllSubnets calls the MAAS subnets API to get all subnets and returns the
  1810  // JSON response or an error. If capNetworkDeploymentUbuntu is not available, an
  1811  // error satisfying errors.IsNotSupported will be returned.
  1812  func (environ *maasEnviron) fetchAllSubnets() ([]gomaasapi.JSONObject, error) {
  1813  	client := environ.getMAASClient().GetSubObject("subnets")
  1814  
  1815  	json, err := client.CallGet("", nil)
  1816  	if err != nil {
  1817  		return nil, errors.Trace(err)
  1818  	}
  1819  	return json.GetArray()
  1820  }
  1821  
  1822  // subnetToSpaceIds fetches the spaces from MAAS and builds a map of subnets to
  1823  // space ids.
  1824  func (environ *maasEnviron) subnetToSpaceIds() (map[string]network.Id, error) {
  1825  	subnetsMap := make(map[string]network.Id)
  1826  	spaces, err := environ.Spaces()
  1827  	if err != nil {
  1828  		return subnetsMap, errors.Trace(err)
  1829  	}
  1830  	for _, space := range spaces {
  1831  		for _, subnet := range space.Subnets {
  1832  			subnetsMap[subnet.CIDR] = space.ProviderId
  1833  		}
  1834  	}
  1835  	return subnetsMap, nil
  1836  }
  1837  
  1838  // Spaces returns all the spaces, that have subnets, known to the provider.
  1839  // Space name is not filled in as the provider doesn't know the juju name for
  1840  // the space.
  1841  func (environ *maasEnviron) Spaces() ([]network.SpaceInfo, error) {
  1842  	if !environ.usingMAAS2() {
  1843  		return environ.spaces1()
  1844  	}
  1845  	return environ.spaces2()
  1846  }
  1847  
  1848  func (environ *maasEnviron) spaces1() ([]network.SpaceInfo, error) {
  1849  	spacesClient := environ.getMAASClient().GetSubObject("spaces")
  1850  	spacesJson, err := spacesClient.CallGet("", nil)
  1851  	if err != nil {
  1852  		return nil, errors.Trace(err)
  1853  	}
  1854  	spacesArray, err := spacesJson.GetArray()
  1855  	if err != nil {
  1856  		return nil, errors.Trace(err)
  1857  	}
  1858  	spaces := []network.SpaceInfo{}
  1859  	for _, spaceJson := range spacesArray {
  1860  		spaceMap, err := spaceJson.GetMap()
  1861  		if err != nil {
  1862  			return nil, errors.Trace(err)
  1863  		}
  1864  		providerIdRaw, err := spaceMap["id"].GetFloat64()
  1865  		if err != nil {
  1866  			return nil, errors.Trace(err)
  1867  		}
  1868  		providerId := network.Id(fmt.Sprintf("%.0f", providerIdRaw))
  1869  		name, err := spaceMap["name"].GetString()
  1870  		if err != nil {
  1871  			return nil, errors.Trace(err)
  1872  		}
  1873  
  1874  		space := network.SpaceInfo{Name: name, ProviderId: providerId}
  1875  		subnetsArray, err := spaceMap["subnets"].GetArray()
  1876  		if err != nil {
  1877  			return nil, errors.Trace(err)
  1878  		}
  1879  		for _, subnetJson := range subnetsArray {
  1880  			subnet, err := environ.subnetFromJson(subnetJson, providerId)
  1881  			if err != nil {
  1882  				return nil, errors.Trace(err)
  1883  			}
  1884  			space.Subnets = append(space.Subnets, subnet)
  1885  		}
  1886  		// Skip spaces with no subnets.
  1887  		if len(space.Subnets) > 0 {
  1888  			spaces = append(spaces, space)
  1889  		}
  1890  	}
  1891  	return spaces, nil
  1892  }
  1893  
  1894  func (environ *maasEnviron) spaces2() ([]network.SpaceInfo, error) {
  1895  	spaces, err := environ.maasController.Spaces()
  1896  	if err != nil {
  1897  		return nil, errors.Trace(err)
  1898  	}
  1899  	var result []network.SpaceInfo
  1900  	for _, space := range spaces {
  1901  		if len(space.Subnets()) == 0 {
  1902  			continue
  1903  		}
  1904  		outSpace := network.SpaceInfo{
  1905  			Name:       space.Name(),
  1906  			ProviderId: network.Id(strconv.Itoa(space.ID())),
  1907  			Subnets:    make([]network.SubnetInfo, len(space.Subnets())),
  1908  		}
  1909  		for i, subnet := range space.Subnets() {
  1910  			subnetInfo := network.SubnetInfo{
  1911  				ProviderId:      network.Id(strconv.Itoa(subnet.ID())),
  1912  				VLANTag:         subnet.VLAN().VID(),
  1913  				CIDR:            subnet.CIDR(),
  1914  				SpaceProviderId: network.Id(strconv.Itoa(space.ID())),
  1915  			}
  1916  			outSpace.Subnets[i] = subnetInfo
  1917  		}
  1918  		result = append(result, outSpace)
  1919  	}
  1920  	return result, nil
  1921  }
  1922  
  1923  // Subnets returns basic information about the specified subnets known
  1924  // by the provider for the specified instance. subnetIds must not be
  1925  // empty. Implements NetworkingEnviron.Subnets.
  1926  func (environ *maasEnviron) Subnets(instId instance.Id, subnetIds []network.Id) ([]network.SubnetInfo, error) {
  1927  	if environ.usingMAAS2() {
  1928  		return environ.subnets2(instId, subnetIds)
  1929  	}
  1930  	return environ.subnets1(instId, subnetIds)
  1931  }
  1932  
  1933  func (environ *maasEnviron) subnets1(instId instance.Id, subnetIds []network.Id) ([]network.SubnetInfo, error) {
  1934  	var nodeId string
  1935  	if instId != instance.UnknownId {
  1936  		inst, err := environ.getInstance(instId)
  1937  		if err != nil {
  1938  			return nil, errors.Trace(err)
  1939  		}
  1940  		nodeId, err = environ.nodeIdFromInstance(inst)
  1941  		if err != nil {
  1942  			return nil, errors.Trace(err)
  1943  		}
  1944  	}
  1945  	subnets, err := environ.filteredSubnets(nodeId, subnetIds)
  1946  	if err != nil {
  1947  		return nil, errors.Trace(err)
  1948  	}
  1949  	if instId != instance.UnknownId {
  1950  		logger.Debugf("instance %q has subnets %v", instId, subnets)
  1951  	} else {
  1952  		logger.Debugf("found subnets %v", subnets)
  1953  	}
  1954  
  1955  	return subnets, nil
  1956  }
  1957  
  1958  func (environ *maasEnviron) subnets2(instId instance.Id, subnetIds []network.Id) ([]network.SubnetInfo, error) {
  1959  	subnets := []network.SubnetInfo{}
  1960  	if instId == instance.UnknownId {
  1961  		spaces, err := environ.Spaces()
  1962  		if err != nil {
  1963  			return nil, errors.Trace(err)
  1964  		}
  1965  		for _, space := range spaces {
  1966  			subnets = append(subnets, space.Subnets...)
  1967  		}
  1968  	} else {
  1969  		var err error
  1970  		subnets, err = environ.filteredSubnets2(instId)
  1971  		if err != nil {
  1972  			return nil, errors.Trace(err)
  1973  		}
  1974  	}
  1975  
  1976  	if len(subnetIds) == 0 {
  1977  		return subnets, nil
  1978  	}
  1979  	result := []network.SubnetInfo{}
  1980  	subnetMap := make(map[string]bool)
  1981  	for _, subnetId := range subnetIds {
  1982  		subnetMap[string(subnetId)] = false
  1983  	}
  1984  	for _, subnet := range subnets {
  1985  		_, ok := subnetMap[string(subnet.ProviderId)]
  1986  		if !ok {
  1987  			// This id is not what we're looking for.
  1988  			continue
  1989  		}
  1990  		subnetMap[string(subnet.ProviderId)] = true
  1991  		result = append(result, subnet)
  1992  	}
  1993  
  1994  	return result, checkNotFound(subnetMap)
  1995  }
  1996  
  1997  func (environ *maasEnviron) filteredSubnets2(instId instance.Id) ([]network.SubnetInfo, error) {
  1998  	args := gomaasapi.MachinesArgs{
  1999  		AgentName: environ.uuid,
  2000  		SystemIDs: []string{string(instId)},
  2001  	}
  2002  	machines, err := environ.maasController.Machines(args)
  2003  	if err != nil {
  2004  		return nil, errors.Trace(err)
  2005  	}
  2006  	if len(machines) == 0 {
  2007  		return nil, errors.NotFoundf("machine %v", instId)
  2008  	} else if len(machines) > 1 {
  2009  		return nil, errors.Errorf("unexpected response getting machine details %v: %v", instId, machines)
  2010  	}
  2011  
  2012  	machine := machines[0]
  2013  	spaceMap, err := environ.buildSpaceMap()
  2014  	if err != nil {
  2015  		return nil, errors.Trace(err)
  2016  	}
  2017  	result := []network.SubnetInfo{}
  2018  	for _, iface := range machine.InterfaceSet() {
  2019  		for _, link := range iface.Links() {
  2020  			subnet := link.Subnet()
  2021  			space, ok := spaceMap[subnet.Space()]
  2022  			if !ok {
  2023  				return nil, errors.Errorf("missing space %v on subnet %v", subnet.Space(), subnet.CIDR())
  2024  			}
  2025  			subnetInfo := network.SubnetInfo{
  2026  				ProviderId:      network.Id(strconv.Itoa(subnet.ID())),
  2027  				VLANTag:         subnet.VLAN().VID(),
  2028  				CIDR:            subnet.CIDR(),
  2029  				SpaceProviderId: space.ProviderId,
  2030  			}
  2031  			result = append(result, subnetInfo)
  2032  		}
  2033  	}
  2034  	return result, nil
  2035  }
  2036  
  2037  func checkNotFound(subnetIdSet map[string]bool) error {
  2038  	notFound := []string{}
  2039  	for subnetId, found := range subnetIdSet {
  2040  		if !found {
  2041  			notFound = append(notFound, string(subnetId))
  2042  		}
  2043  	}
  2044  	if len(notFound) != 0 {
  2045  		return errors.Errorf("failed to find the following subnets: %v", strings.Join(notFound, ", "))
  2046  	}
  2047  	return nil
  2048  }
  2049  
  2050  // AllInstances returns all the instance.Instance in this provider.
  2051  func (environ *maasEnviron) AllInstances() ([]instance.Instance, error) {
  2052  	return environ.acquiredInstances(nil)
  2053  }
  2054  
  2055  // Storage is defined by the Environ interface.
  2056  func (env *maasEnviron) Storage() storage.Storage {
  2057  	env.ecfgMutex.Lock()
  2058  	defer env.ecfgMutex.Unlock()
  2059  	return env.storageUnlocked
  2060  }
  2061  
  2062  func (environ *maasEnviron) Destroy() error {
  2063  	if err := common.Destroy(environ); err != nil {
  2064  		return errors.Trace(err)
  2065  	}
  2066  	return environ.Storage().RemoveAll()
  2067  }
  2068  
  2069  // DestroyController implements the Environ interface.
  2070  func (environ *maasEnviron) DestroyController(controllerUUID string) error {
  2071  	// TODO(wallyworld): destroy hosted model resources
  2072  	return environ.Destroy()
  2073  }
  2074  
  2075  // MAAS does not do firewalling so these port methods do nothing.
  2076  func (*maasEnviron) OpenPorts([]network.PortRange) error {
  2077  	logger.Debugf("unimplemented OpenPorts() called")
  2078  	return nil
  2079  }
  2080  
  2081  func (*maasEnviron) ClosePorts([]network.PortRange) error {
  2082  	logger.Debugf("unimplemented ClosePorts() called")
  2083  	return nil
  2084  }
  2085  
  2086  func (*maasEnviron) Ports() ([]network.PortRange, error) {
  2087  	logger.Debugf("unimplemented Ports() called")
  2088  	return nil, nil
  2089  }
  2090  
  2091  func (*maasEnviron) Provider() environs.EnvironProvider {
  2092  	return &providerInstance
  2093  }
  2094  
  2095  func (environ *maasEnviron) nodeIdFromInstance(inst instance.Instance) (string, error) {
  2096  	maasInst := inst.(*maas1Instance)
  2097  	maasObj := maasInst.maasObject
  2098  	nodeId, err := maasObj.GetField("system_id")
  2099  	if err != nil {
  2100  		return "", err
  2101  	}
  2102  	return nodeId, err
  2103  }
  2104  
  2105  func (env *maasEnviron) AllocateContainerAddresses(hostInstanceID instance.Id, containerTag names.MachineTag, preparedInfo []network.InterfaceInfo) ([]network.InterfaceInfo, error) {
  2106  	if len(preparedInfo) == 0 {
  2107  		return nil, errors.Errorf("no prepared info to allocate")
  2108  	}
  2109  	logger.Debugf("using prepared container info: %+v", preparedInfo)
  2110  	if !env.usingMAAS2() {
  2111  		return env.allocateContainerAddresses1(hostInstanceID, containerTag, preparedInfo)
  2112  	}
  2113  	return env.allocateContainerAddresses2(hostInstanceID, containerTag, preparedInfo)
  2114  }
  2115  
  2116  func (env *maasEnviron) allocateContainerAddresses1(hostInstanceID instance.Id, containerTag names.MachineTag, preparedInfo []network.InterfaceInfo) ([]network.InterfaceInfo, error) {
  2117  	subnetCIDRToVLANID := make(map[string]string)
  2118  	subnetsAPI := env.getMAASClient().GetSubObject("subnets")
  2119  	result, err := subnetsAPI.CallGet("", nil)
  2120  	if err != nil {
  2121  		return nil, errors.Annotate(err, "cannot get subnets")
  2122  	}
  2123  	subnetsJSON, err := getJSONBytes(result)
  2124  	if err != nil {
  2125  		return nil, errors.Annotate(err, "cannot get subnets JSON")
  2126  	}
  2127  	var subnets []maasSubnet
  2128  	if err := json.Unmarshal(subnetsJSON, &subnets); err != nil {
  2129  		return nil, errors.Annotate(err, "cannot parse subnets JSON")
  2130  	}
  2131  	for _, subnet := range subnets {
  2132  		subnetCIDRToVLANID[subnet.CIDR] = strconv.Itoa(subnet.VLAN.ID)
  2133  	}
  2134  
  2135  	var primaryNICInfo network.InterfaceInfo
  2136  	for _, nic := range preparedInfo {
  2137  		if nic.InterfaceName == "eth0" {
  2138  			primaryNICInfo = nic
  2139  			break
  2140  		}
  2141  	}
  2142  	if primaryNICInfo.InterfaceName == "" {
  2143  		return nil, errors.Errorf("cannot find primary interface for container")
  2144  	}
  2145  	logger.Debugf("primary device NIC prepared info: %+v", primaryNICInfo)
  2146  
  2147  	deviceName, err := env.namespace.Hostname(containerTag.Id())
  2148  	if err != nil {
  2149  		return nil, errors.Trace(err)
  2150  	}
  2151  	primaryMACAddress := primaryNICInfo.MACAddress
  2152  	containerDevice, err := env.createDevice(hostInstanceID, deviceName, primaryMACAddress)
  2153  	if err != nil {
  2154  		return nil, errors.Annotate(err, "cannot create device for container")
  2155  	}
  2156  	deviceID := instance.Id(containerDevice.ResourceURI)
  2157  	logger.Debugf("created device %q with primary MAC address %q", deviceID, primaryMACAddress)
  2158  
  2159  	interfaces, err := env.deviceInterfaces(deviceID)
  2160  	if err != nil {
  2161  		return nil, errors.Annotate(err, "cannot get device interfaces")
  2162  	}
  2163  	if len(interfaces) != 1 {
  2164  		return nil, errors.Errorf("expected 1 device interface, got %d", len(interfaces))
  2165  	}
  2166  
  2167  	primaryNICName := interfaces[0].Name
  2168  	primaryNICID := strconv.Itoa(interfaces[0].ID)
  2169  	primaryNICSubnetCIDR := primaryNICInfo.CIDR
  2170  	primaryNICVLANID, hasSubnet := subnetCIDRToVLANID[primaryNICSubnetCIDR]
  2171  	if hasSubnet {
  2172  		updatedPrimaryNIC, err := env.updateDeviceInterface(deviceID, primaryNICID, primaryNICName, primaryMACAddress, primaryNICVLANID)
  2173  		if err != nil {
  2174  			return nil, errors.Annotatef(err, "cannot update device interface %q", interfaces[0].Name)
  2175  		}
  2176  		logger.Debugf("device %q primary interface %q updated: %+v", containerDevice.SystemID, primaryNICName, updatedPrimaryNIC)
  2177  	}
  2178  
  2179  	deviceNICIDs := make([]string, len(preparedInfo))
  2180  	nameToParentName := make(map[string]string)
  2181  	for i, nic := range preparedInfo {
  2182  		maasNICID := ""
  2183  		nameToParentName[nic.InterfaceName] = nic.ParentInterfaceName
  2184  		nicVLANID, knownSubnet := subnetCIDRToVLANID[nic.CIDR]
  2185  		if nic.InterfaceName != primaryNICName {
  2186  			if !knownSubnet {
  2187  				logger.Warningf("NIC %v has no subnet - setting to manual and using untagged VLAN", nic.InterfaceName)
  2188  				nicVLANID = primaryNICVLANID
  2189  			} else {
  2190  				logger.Infof("linking NIC %v to subnet %v - using static IP", nic.InterfaceName, nic.CIDR)
  2191  			}
  2192  
  2193  			createdNIC, err := env.createDeviceInterface(deviceID, nic.InterfaceName, nic.MACAddress, nicVLANID)
  2194  			if err != nil {
  2195  				return nil, errors.Annotate(err, "creating device interface")
  2196  			}
  2197  			maasNICID = strconv.Itoa(createdNIC.ID)
  2198  			logger.Debugf("created device interface: %+v", createdNIC)
  2199  		} else {
  2200  			maasNICID = primaryNICID
  2201  		}
  2202  		deviceNICIDs[i] = maasNICID
  2203  		subnetID := string(nic.ProviderSubnetId)
  2204  
  2205  		if !knownSubnet {
  2206  			continue
  2207  		}
  2208  
  2209  		linkedInterface, err := env.linkDeviceInterfaceToSubnet(deviceID, maasNICID, subnetID, modeStatic)
  2210  		if err != nil {
  2211  			logger.Warningf("linking NIC %v to subnet %v failed: %v", nic.InterfaceName, nic.CIDR, err)
  2212  		} else {
  2213  			logger.Debugf("linked device interface to subnet: %+v", linkedInterface)
  2214  		}
  2215  	}
  2216  
  2217  	finalInterfaces, err := env.deviceInterfaceInfo(deviceID, nameToParentName)
  2218  	if err != nil {
  2219  		return nil, errors.Annotate(err, "cannot get device interfaces")
  2220  	}
  2221  	logger.Debugf("allocated device interfaces: %+v", finalInterfaces)
  2222  
  2223  	return finalInterfaces, nil
  2224  }
  2225  
  2226  func (env *maasEnviron) allocateContainerAddresses2(hostInstanceID instance.Id, containerTag names.MachineTag, preparedInfo []network.InterfaceInfo) ([]network.InterfaceInfo, error) {
  2227  	subnetCIDRToSubnet := make(map[string]gomaasapi.Subnet)
  2228  	spaces, err := env.maasController.Spaces()
  2229  	if err != nil {
  2230  		return nil, errors.Trace(err)
  2231  	}
  2232  	for _, space := range spaces {
  2233  		for _, subnet := range space.Subnets() {
  2234  			subnetCIDRToSubnet[subnet.CIDR()] = subnet
  2235  		}
  2236  	}
  2237  
  2238  	var primaryNICInfo network.InterfaceInfo
  2239  	primaryNICName := "eth0"
  2240  	for _, nic := range preparedInfo {
  2241  		if nic.InterfaceName == primaryNICName {
  2242  			primaryNICInfo = nic
  2243  			break
  2244  		}
  2245  	}
  2246  	if primaryNICInfo.InterfaceName == "" {
  2247  		return nil, errors.Errorf("cannot find primary interface for container")
  2248  	}
  2249  	logger.Debugf("primary device NIC prepared info: %+v", primaryNICInfo)
  2250  
  2251  	primaryNICSubnetCIDR := primaryNICInfo.CIDR
  2252  	subnet, hasSubnet := subnetCIDRToSubnet[primaryNICSubnetCIDR]
  2253  	if !hasSubnet {
  2254  		logger.Debugf("primary device NIC %q has no linked subnet - leaving unconfigured", primaryNICInfo.InterfaceName)
  2255  	}
  2256  	primaryMACAddress := primaryNICInfo.MACAddress
  2257  	args := gomaasapi.MachinesArgs{
  2258  		AgentName: env.uuid,
  2259  		SystemIDs: []string{string(hostInstanceID)},
  2260  	}
  2261  	machines, err := env.maasController.Machines(args)
  2262  	if err != nil {
  2263  		return nil, errors.Trace(err)
  2264  	}
  2265  	if len(machines) != 1 {
  2266  		return nil, errors.Errorf("unexpected response fetching machine %v: %v", hostInstanceID, machines)
  2267  	}
  2268  	machine := machines[0]
  2269  	deviceName, err := env.namespace.Hostname(containerTag.Id())
  2270  	if err != nil {
  2271  		return nil, errors.Trace(err)
  2272  	}
  2273  	createDeviceArgs := gomaasapi.CreateMachineDeviceArgs{
  2274  		Hostname:      deviceName,
  2275  		MACAddress:    primaryMACAddress,
  2276  		Subnet:        subnet, // can be nil
  2277  		InterfaceName: primaryNICName,
  2278  	}
  2279  	device, err := machine.CreateDevice(createDeviceArgs)
  2280  	if err != nil {
  2281  		return nil, errors.Trace(err)
  2282  	}
  2283  	interface_set := device.InterfaceSet()
  2284  	if len(interface_set) != 1 {
  2285  		// Shouldn't be possible as machine.CreateDevice always returns us
  2286  		// one interface.
  2287  		return nil, errors.Errorf("unexpected number of interfaces in response from creating device: %v", interface_set)
  2288  	}
  2289  	primaryNICVLAN := interface_set[0].VLAN()
  2290  
  2291  	nameToParentName := make(map[string]string)
  2292  	for _, nic := range preparedInfo {
  2293  		nameToParentName[nic.InterfaceName] = nic.ParentInterfaceName
  2294  		if nic.InterfaceName != primaryNICName {
  2295  			createArgs := gomaasapi.CreateInterfaceArgs{
  2296  				Name:       nic.InterfaceName,
  2297  				MTU:        nic.MTU,
  2298  				MACAddress: nic.MACAddress,
  2299  			}
  2300  
  2301  			subnet, knownSubnet := subnetCIDRToSubnet[nic.CIDR]
  2302  			if !knownSubnet {
  2303  				logger.Warningf("NIC %v has no subnet - setting to manual and using untagged VLAN", nic.InterfaceName)
  2304  				createArgs.VLAN = primaryNICVLAN
  2305  			} else {
  2306  				createArgs.VLAN = subnet.VLAN()
  2307  				logger.Infof("linking NIC %v to subnet %v - using static IP", nic.InterfaceName, subnet.CIDR())
  2308  			}
  2309  
  2310  			createdNIC, err := device.CreateInterface(createArgs)
  2311  			if err != nil {
  2312  				return nil, errors.Annotate(err, "creating device interface")
  2313  			}
  2314  			logger.Debugf("created device interface: %+v", createdNIC)
  2315  
  2316  			if !knownSubnet {
  2317  				continue
  2318  			}
  2319  
  2320  			linkArgs := gomaasapi.LinkSubnetArgs{
  2321  				Mode:   gomaasapi.LinkModeStatic,
  2322  				Subnet: subnet,
  2323  			}
  2324  
  2325  			if err := createdNIC.LinkSubnet(linkArgs); err != nil {
  2326  				logger.Warningf("linking NIC %v to subnet %v failed: %v", nic.InterfaceName, subnet.CIDR(), err)
  2327  			} else {
  2328  				logger.Debugf("linked device interface to subnet: %+v", createdNIC)
  2329  			}
  2330  		}
  2331  	}
  2332  
  2333  	finalInterfaces, err := env.deviceInterfaceInfo2(device.SystemID(), nameToParentName)
  2334  	if err != nil {
  2335  		return nil, errors.Annotate(err, "cannot get device interfaces")
  2336  	}
  2337  	logger.Debugf("allocated device interfaces: %+v", finalInterfaces)
  2338  
  2339  	return finalInterfaces, nil
  2340  }
  2341  
  2342  func (env *maasEnviron) ReleaseContainerAddresses(interfaces []network.ProviderInterfaceInfo) error {
  2343  	macAddresses := make([]string, len(interfaces))
  2344  	for i, info := range interfaces {
  2345  		macAddresses[i] = info.MACAddress
  2346  	}
  2347  	if !env.usingMAAS2() {
  2348  		return env.releaseContainerAddresses1(macAddresses)
  2349  	}
  2350  	return env.releaseContainerAddresses2(macAddresses)
  2351  }
  2352  
  2353  func (env *maasEnviron) releaseContainerAddresses1(macAddresses []string) error {
  2354  	devicesAPI := env.getMAASClient().GetSubObject("devices")
  2355  	values := url.Values{}
  2356  	for _, address := range macAddresses {
  2357  		values.Add("mac_address", address)
  2358  	}
  2359  	result, err := devicesAPI.CallGet("list", values)
  2360  	if err != nil {
  2361  		return errors.Trace(err)
  2362  	}
  2363  	devicesArray, err := result.GetArray()
  2364  	if err != nil {
  2365  		return errors.Trace(err)
  2366  	}
  2367  	deviceIds := make([]string, len(devicesArray))
  2368  	for i, deviceItem := range devicesArray {
  2369  		deviceMap, err := deviceItem.GetMap()
  2370  		if err != nil {
  2371  			return errors.Trace(err)
  2372  		}
  2373  		id, err := deviceMap["system_id"].GetString()
  2374  		if err != nil {
  2375  			return errors.Trace(err)
  2376  		}
  2377  		deviceIds[i] = id
  2378  	}
  2379  
  2380  	// If one device matched on multiple MAC addresses (like for
  2381  	// multi-nic containers) it will be in the slice multiple
  2382  	// times. Skip devices we've seen already.
  2383  	deviceIdSet := set.NewStrings(deviceIds...)
  2384  	deviceIds = deviceIdSet.SortedValues()
  2385  
  2386  	for _, id := range deviceIds {
  2387  		err := devicesAPI.GetSubObject(id).Delete()
  2388  		if err != nil {
  2389  			return errors.Annotatef(err, "deleting device %s", id)
  2390  		}
  2391  	}
  2392  	return nil
  2393  }
  2394  
  2395  func (env *maasEnviron) releaseContainerAddresses2(macAddresses []string) error {
  2396  	devices, err := env.maasController.Devices(gomaasapi.DevicesArgs{MACAddresses: macAddresses})
  2397  	if err != nil {
  2398  		return errors.Trace(err)
  2399  	}
  2400  	// If one device matched on multiple MAC addresses (like for
  2401  	// multi-nic containers) it will be in the slice multiple
  2402  	// times. Skip devices we've seen already.
  2403  	seen := set.NewStrings()
  2404  	for _, device := range devices {
  2405  		if seen.Contains(device.SystemID()) {
  2406  			continue
  2407  		}
  2408  		seen.Add(device.SystemID())
  2409  
  2410  		err = device.Delete()
  2411  		if err != nil {
  2412  			return errors.Annotatef(err, "deleting device %s", device.SystemID())
  2413  		}
  2414  	}
  2415  	return nil
  2416  }