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