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