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