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