github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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  	stdcontext "context"
     8  	"fmt"
     9  	"net/http"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/juju/clock"
    17  	"github.com/juju/collections/set"
    18  	"github.com/juju/errors"
    19  	"github.com/juju/gomaasapi/v2"
    20  	"github.com/juju/names/v5"
    21  	"github.com/juju/retry"
    22  	"github.com/juju/version/v2"
    23  
    24  	"github.com/juju/juju/cloudconfig/cloudinit"
    25  	"github.com/juju/juju/cloudconfig/instancecfg"
    26  	"github.com/juju/juju/cloudconfig/providerinit"
    27  	corebase "github.com/juju/juju/core/base"
    28  	"github.com/juju/juju/core/constraints"
    29  	"github.com/juju/juju/core/instance"
    30  	corenetwork "github.com/juju/juju/core/network"
    31  	"github.com/juju/juju/core/os/ostype"
    32  	"github.com/juju/juju/core/status"
    33  	"github.com/juju/juju/environs"
    34  	environscloudspec "github.com/juju/juju/environs/cloudspec"
    35  	"github.com/juju/juju/environs/config"
    36  	"github.com/juju/juju/environs/context"
    37  	"github.com/juju/juju/environs/instances"
    38  	"github.com/juju/juju/environs/storage"
    39  	"github.com/juju/juju/environs/tags"
    40  	"github.com/juju/juju/provider/common"
    41  	"github.com/juju/juju/tools"
    42  )
    43  
    44  const (
    45  	// The version strings indicating the MAAS API version.
    46  	apiVersion2 = "2.0"
    47  )
    48  
    49  var defaultShortRetryStrategy = retry.CallArgs{
    50  	Clock:       clock.WallClock,
    51  	Delay:       200 * time.Millisecond,
    52  	MaxDuration: 5 * time.Second,
    53  }
    54  var defaultLongRetryStrategy = retry.CallArgs{
    55  	Clock:       clock.WallClock,
    56  	Delay:       10 * time.Second,
    57  	MaxDuration: 1200 * time.Second,
    58  }
    59  
    60  var (
    61  	DeploymentStatusCall = deploymentStatusCall
    62  	GetMAASController    = getMAASController
    63  )
    64  
    65  func getMAASController(maasServer, apiKey string) (gomaasapi.Controller, error) {
    66  	return gomaasapi.NewController(gomaasapi.ControllerArgs{
    67  		BaseURL: maasServer,
    68  		APIKey:  apiKey,
    69  	})
    70  }
    71  
    72  type maasEnviron struct {
    73  	name string
    74  	uuid string
    75  
    76  	// archMutex gates access to supportedArchitectures
    77  	archMutex sync.Mutex
    78  
    79  	// ecfgMutex protects the *Unlocked fields below.
    80  	ecfgMutex sync.Mutex
    81  
    82  	ecfgUnlocked    *maasModelConfig
    83  	storageUnlocked storage.Storage
    84  
    85  	// maasController provides access to the MAAS 2.0 API.
    86  	maasController gomaasapi.Controller
    87  
    88  	// namespace is used to create the machine and device hostnames.
    89  	namespace instance.Namespace
    90  
    91  	// apiVersion tells us if we are using the MAAS 1.0 or 2.0 api.
    92  	apiVersion string
    93  
    94  	// GetCapabilities is a function that connects to MAAS to return its set of
    95  	// capabilities.
    96  	GetCapabilities Capabilities
    97  
    98  	// A request may fail to due "eventual consistency" semantics, which
    99  	// should resolve fairly quickly.  A request may also fail due to a slow
   100  	// state transition (for instance an instance taking a while to release
   101  	// a security group after termination).  The former failure mode is
   102  	// dealt with by shortRetryStrategy, the latter by longRetryStrategy
   103  	shortRetryStrategy retry.CallArgs
   104  	longRetryStrategy  retry.CallArgs
   105  }
   106  
   107  var _ environs.Environ = (*maasEnviron)(nil)
   108  var _ environs.Networking = (*maasEnviron)(nil)
   109  
   110  // Capabilities is an alias for a function that gets
   111  // the capabilities of a MAAS installation.
   112  type Capabilities = func(client *gomaasapi.MAASObject, serverURL string) (set.Strings, error)
   113  
   114  func NewEnviron(cloud environscloudspec.CloudSpec, cfg *config.Config, getCaps Capabilities) (*maasEnviron, error) {
   115  	if getCaps == nil {
   116  		getCaps = getCapabilities
   117  	}
   118  	env := &maasEnviron{
   119  		name:               cfg.Name(),
   120  		uuid:               cfg.UUID(),
   121  		GetCapabilities:    getCaps,
   122  		shortRetryStrategy: defaultShortRetryStrategy,
   123  		longRetryStrategy:  defaultLongRetryStrategy,
   124  	}
   125  	if err := env.SetConfig(cfg); err != nil {
   126  		return nil, errors.Trace(err)
   127  	}
   128  	if err := env.SetCloudSpec(stdcontext.TODO(), cloud); err != nil {
   129  		return nil, errors.Trace(err)
   130  	}
   131  
   132  	var err error
   133  	env.namespace, err = instance.NewNamespace(cfg.UUID())
   134  	if err != nil {
   135  		return nil, errors.Trace(err)
   136  	}
   137  	return env, nil
   138  }
   139  
   140  // PrepareForBootstrap is part of the Environ interface.
   141  func (env *maasEnviron) PrepareForBootstrap(_ environs.BootstrapContext, _ string) error {
   142  	return nil
   143  }
   144  
   145  // Create is part of the Environ interface.
   146  func (env *maasEnviron) Create(_ context.ProviderCallContext, _ environs.CreateParams) error {
   147  	return nil
   148  }
   149  
   150  // Bootstrap is part of the Environ interface.
   151  func (env *maasEnviron) Bootstrap(
   152  	ctx environs.BootstrapContext, callCtx context.ProviderCallContext, args environs.BootstrapParams,
   153  ) (*environs.BootstrapResult, error) {
   154  	result, base, finalizer, err := common.BootstrapInstance(ctx, env, callCtx, args)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	// We want to destroy the started instance if it doesn't transition to Deployed.
   160  	defer func() {
   161  		if err != nil {
   162  			if err := env.StopInstances(callCtx, result.Instance.Id()); err != nil {
   163  				logger.Errorf("error releasing bootstrap instance: %v", err)
   164  			}
   165  		}
   166  	}()
   167  
   168  	waitingFinalizer := func(
   169  		ctx environs.BootstrapContext,
   170  		icfg *instancecfg.InstanceConfig,
   171  		dialOpts environs.BootstrapDialOpts,
   172  	) error {
   173  		// Wait for bootstrap instance to change to deployed state.
   174  		if err := env.waitForNodeDeployment(callCtx, result.Instance.Id(), dialOpts.Timeout); err != nil {
   175  			return errors.Annotate(err, "bootstrap instance started but did not change to Deployed state")
   176  		}
   177  		return finalizer(ctx, icfg, dialOpts)
   178  	}
   179  
   180  	bsResult := &environs.BootstrapResult{
   181  		Arch:                    *result.Hardware.Arch,
   182  		Base:                    *base,
   183  		CloudBootstrapFinalizer: waitingFinalizer,
   184  	}
   185  	return bsResult, nil
   186  }
   187  
   188  // ControllerInstances is specified in the Environ interface.
   189  func (env *maasEnviron) ControllerInstances(ctx context.ProviderCallContext, controllerUUID string) ([]instance.Id, error) {
   190  	instances, err := env.instances(ctx, gomaasapi.MachinesArgs{
   191  		OwnerData: map[string]string{
   192  			tags.JujuIsController: "true",
   193  			tags.JujuController:   controllerUUID,
   194  		},
   195  	})
   196  	if err != nil {
   197  		return nil, errors.Trace(err)
   198  	}
   199  	if len(instances) == 0 {
   200  		return nil, environs.ErrNotBootstrapped
   201  	}
   202  	ids := make([]instance.Id, len(instances))
   203  	for i := range instances {
   204  		ids[i] = instances[i].Id()
   205  	}
   206  	return ids, nil
   207  }
   208  
   209  // ecfg returns the environment's maasModelConfig, and protects it with a
   210  // mutex.
   211  func (env *maasEnviron) ecfg() *maasModelConfig {
   212  	env.ecfgMutex.Lock()
   213  	cfg := *env.ecfgUnlocked
   214  	env.ecfgMutex.Unlock()
   215  	return &cfg
   216  }
   217  
   218  // Config is specified in the Environ interface.
   219  func (env *maasEnviron) Config() *config.Config {
   220  	return env.ecfg().Config
   221  }
   222  
   223  // SetConfig is specified in the Environ interface.
   224  func (env *maasEnviron) SetConfig(cfg *config.Config) error {
   225  	env.ecfgMutex.Lock()
   226  	defer env.ecfgMutex.Unlock()
   227  
   228  	// The new config has already been validated by itself, but now we
   229  	// validate the transition from the old config to the new.
   230  	var oldCfg *config.Config
   231  	if env.ecfgUnlocked != nil {
   232  		oldCfg = env.ecfgUnlocked.Config
   233  	}
   234  	cfg, err := env.Provider().Validate(cfg, oldCfg)
   235  	if err != nil {
   236  		return errors.Trace(err)
   237  	}
   238  
   239  	ecfg, err := providerInstance.newConfig(cfg)
   240  	if err != nil {
   241  		return errors.Trace(err)
   242  	}
   243  
   244  	env.ecfgUnlocked = ecfg
   245  
   246  	return nil
   247  }
   248  
   249  // SetCloudSpec is specified in the environs.Environ interface.
   250  func (env *maasEnviron) SetCloudSpec(_ stdcontext.Context, spec environscloudspec.CloudSpec) error {
   251  	env.ecfgMutex.Lock()
   252  	defer env.ecfgMutex.Unlock()
   253  
   254  	maasServer, err := parseCloudEndpoint(spec.Endpoint)
   255  	if err != nil {
   256  		return errors.Trace(err)
   257  	}
   258  	maasOAuth, err := parseOAuthToken(*spec.Credential)
   259  	if err != nil {
   260  		return errors.Trace(err)
   261  	}
   262  
   263  	apiVersion := apiVersion2
   264  	controller, err := GetMAASController(maasServer, maasOAuth)
   265  	if err != nil {
   266  		return errors.Trace(err)
   267  	}
   268  
   269  	env.maasController = controller
   270  	env.apiVersion = apiVersion
   271  	env.storageUnlocked = NewStorage(env)
   272  
   273  	return nil
   274  }
   275  
   276  // ValidateCloudEndpoint returns nil if the current model can talk to the maas
   277  // endpoint.  Used as validation during model upgrades.
   278  // Implements environs.CloudEndpointChecker
   279  func (env *maasEnviron) ValidateCloudEndpoint(ctx context.ProviderCallContext) error {
   280  	_, _, err := env.maasController.APIVersionInfo()
   281  	return errors.Trace(err)
   282  }
   283  
   284  func (env *maasEnviron) getSupportedArchitectures(ctx context.ProviderCallContext) ([]string, error) {
   285  	env.archMutex.Lock()
   286  	defer env.archMutex.Unlock()
   287  
   288  	resources, err := env.maasController.BootResources()
   289  	if err != nil {
   290  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   291  		return nil, errors.Trace(err)
   292  	}
   293  	architectures := set.NewStrings()
   294  	for _, resource := range resources {
   295  		architectures.Add(strings.Split(resource.Architecture(), "/")[0])
   296  	}
   297  	return architectures.SortedValues(), nil
   298  }
   299  
   300  // SupportsSpaces is specified on environs.Networking.
   301  func (env *maasEnviron) SupportsSpaces(ctx context.ProviderCallContext) (bool, error) {
   302  	return true, nil
   303  }
   304  
   305  // SupportsSpaceDiscovery is specified on environs.Networking.
   306  func (env *maasEnviron) SupportsSpaceDiscovery(ctx context.ProviderCallContext) (bool, error) {
   307  	return true, nil
   308  }
   309  
   310  // SupportsContainerAddresses is specified on environs.Networking.
   311  func (env *maasEnviron) SupportsContainerAddresses(ctx context.ProviderCallContext) (bool, error) {
   312  	return true, nil
   313  }
   314  
   315  type maasAvailabilityZone struct {
   316  	name string
   317  }
   318  
   319  func (z maasAvailabilityZone) Name() string {
   320  	return z.name
   321  }
   322  
   323  func (z maasAvailabilityZone) Available() bool {
   324  	// MAAS' physical zone attributes only include name and description;
   325  	// there is no concept of availability.
   326  	return true
   327  }
   328  
   329  // AvailabilityZones returns a slice of availability zones
   330  // for the configured region.
   331  func (env *maasEnviron) AvailabilityZones(ctx context.ProviderCallContext) (corenetwork.AvailabilityZones, error) {
   332  	zones, err := env.availabilityZones(ctx)
   333  	return zones, errors.Trace(err)
   334  }
   335  
   336  func (env *maasEnviron) availabilityZones(ctx context.ProviderCallContext) (corenetwork.AvailabilityZones, error) {
   337  	zones, err := env.maasController.Zones()
   338  	if err != nil {
   339  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   340  		return nil, errors.Trace(err)
   341  	}
   342  	availabilityZones := make(corenetwork.AvailabilityZones, len(zones))
   343  	for i, zone := range zones {
   344  		availabilityZones[i] = maasAvailabilityZone{zone.Name()}
   345  	}
   346  	return availabilityZones, nil
   347  }
   348  
   349  // InstanceAvailabilityZoneNames returns the availability zone names for each
   350  // of the specified instances.
   351  func (env *maasEnviron) InstanceAvailabilityZoneNames(ctx context.ProviderCallContext, ids []instance.Id) (map[instance.Id]string, error) {
   352  	inst, err := env.Instances(ctx, ids)
   353  	if err != nil && err != environs.ErrPartialInstances {
   354  		return nil, err
   355  	}
   356  	zones := make(map[instance.Id]string, 0)
   357  	for _, inst := range inst {
   358  		if inst == nil {
   359  			continue
   360  		}
   361  		mInst, ok := inst.(*maasInstance)
   362  		if !ok {
   363  			continue
   364  		}
   365  		z, err := mInst.zone()
   366  		if err != nil {
   367  			logger.Errorf("could not get availability zone %v", err)
   368  			continue
   369  		}
   370  		zones[inst.Id()] = z
   371  	}
   372  	return zones, nil
   373  }
   374  
   375  // DeriveAvailabilityZones is part of the common.ZonedEnviron interface.
   376  func (env *maasEnviron) DeriveAvailabilityZones(ctx context.ProviderCallContext, args environs.StartInstanceParams) ([]string, error) {
   377  	if args.Placement != "" {
   378  		placement, err := env.parsePlacement(ctx, args.Placement)
   379  		if err != nil {
   380  			return nil, errors.Trace(err)
   381  		}
   382  		if placement.zoneName != "" {
   383  			return []string{placement.zoneName}, nil
   384  		}
   385  	}
   386  	return nil, nil
   387  }
   388  
   389  type maasPlacement struct {
   390  	nodeName string
   391  	zoneName string
   392  	systemId string
   393  }
   394  
   395  func (env *maasEnviron) parsePlacement(ctx context.ProviderCallContext, placement string) (*maasPlacement, error) {
   396  	pos := strings.IndexRune(placement, '=')
   397  	if pos == -1 {
   398  		// If there's no '=' delimiter, assume it's a node name.
   399  		return &maasPlacement{nodeName: placement}, nil
   400  	}
   401  	switch key, value := placement[:pos], placement[pos+1:]; key {
   402  	case "zone":
   403  		zones, err := env.AvailabilityZones(ctx)
   404  		if err != nil {
   405  			return nil, errors.Trace(err)
   406  		}
   407  		if err := zones.Validate(value); err != nil {
   408  			return nil, errors.Trace(err)
   409  		}
   410  
   411  		return &maasPlacement{zoneName: value}, nil
   412  	case "system-id":
   413  		return &maasPlacement{systemId: value}, nil
   414  	}
   415  
   416  	return nil, errors.Errorf("unknown placement directive: %v", placement)
   417  }
   418  
   419  func (env *maasEnviron) PrecheckInstance(ctx context.ProviderCallContext, args environs.PrecheckInstanceParams) error {
   420  	if args.Placement == "" {
   421  		return nil
   422  	}
   423  	_, err := env.parsePlacement(ctx, args.Placement)
   424  	return err
   425  }
   426  
   427  // getCapabilities asks the MAAS server for its capabilities, if
   428  // supported by the server.
   429  func getCapabilities(client *gomaasapi.MAASObject, serverURL string) (set.Strings, error) {
   430  	caps := make(set.Strings)
   431  	var result gomaasapi.JSONObject
   432  
   433  	retryStrategy := defaultShortRetryStrategy
   434  	retryStrategy.IsFatalError = func(err error) bool {
   435  		if err, ok := errors.Cause(err).(gomaasapi.ServerError); ok && err.StatusCode == 404 {
   436  			return true
   437  		}
   438  		return false
   439  	}
   440  	retryStrategy.Func = func() error {
   441  		var err error
   442  		version := client.GetSubObject("version/")
   443  		result, err = version.CallGet("", nil)
   444  		return err
   445  	}
   446  	err := retry.Call(retryStrategy)
   447  
   448  	if retry.IsAttemptsExceeded(err) || retry.IsDurationExceeded(err) {
   449  		logger.Debugf("Can't connect to maas server at endpoint %q: %v", serverURL, err)
   450  		err = retry.LastError(err)
   451  		return caps, err
   452  	}
   453  	if err != nil {
   454  		err, _ := errors.Cause(err).(gomaasapi.ServerError)
   455  		logger.Debugf("Failed attempting to get capabilities from maas endpoint %q: %v", serverURL, err)
   456  
   457  		message := "could not connect to MAAS controller - check the endpoint is correct"
   458  		trimmedURL := strings.TrimRight(serverURL, "/")
   459  		if !strings.HasSuffix(trimmedURL, "/MAAS") {
   460  			message += " (it normally ends with /MAAS)"
   461  		}
   462  		return caps, errors.NewNotSupported(nil, message)
   463  	}
   464  
   465  	info, err := result.GetMap()
   466  	if err != nil {
   467  		logger.Debugf("Invalid data returned from maas endpoint %q: %v", serverURL, err)
   468  		// invalid data of some sort, probably not a MAAS server.
   469  		return caps, errors.New("failed to get expected data from server")
   470  	}
   471  	capsObj, ok := info["capabilities"]
   472  	if !ok {
   473  		return caps, fmt.Errorf("MAAS does not report capabilities")
   474  	}
   475  	items, err := capsObj.GetArray()
   476  	if err != nil {
   477  		logger.Debugf("Invalid data returned from maas endpoint %q: %v", serverURL, err)
   478  		return caps, errors.New("failed to get expected data from server")
   479  	}
   480  	for _, item := range items {
   481  		val, err := item.GetString()
   482  		if err != nil {
   483  			logger.Debugf("Invalid data returned from maas endpoint %q: %v", serverURL, err)
   484  			return set.NewStrings(), errors.New("failed to get expected data from server")
   485  		}
   486  		caps.Add(val)
   487  	}
   488  	return caps, nil
   489  }
   490  
   491  var dashSuffix = regexp.MustCompile("^(.*)-\\d+$")
   492  
   493  func spaceNamesToSpaceInfo(
   494  	spaces []string, spaceMap map[string]corenetwork.SpaceInfo,
   495  ) ([]corenetwork.SpaceInfo, error) {
   496  	var spaceInfos []corenetwork.SpaceInfo
   497  	for _, name := range spaces {
   498  		info, ok := spaceMap[name]
   499  		if !ok {
   500  			matches := dashSuffix.FindAllStringSubmatch(name, 1)
   501  			if matches == nil {
   502  				return nil, errors.Errorf("unrecognised space in constraint %q", name)
   503  			}
   504  			// A -number was added to the space name when we
   505  			// converted to a juju name, we found
   506  			info, ok = spaceMap[matches[0][1]]
   507  			if !ok {
   508  				return nil, errors.Errorf("unrecognised space in constraint %q", name)
   509  			}
   510  		}
   511  		spaceInfos = append(spaceInfos, info)
   512  	}
   513  	return spaceInfos, nil
   514  }
   515  
   516  func (env *maasEnviron) buildSpaceMap(ctx context.ProviderCallContext) (map[string]corenetwork.SpaceInfo, error) {
   517  	spaces, err := env.Spaces(ctx)
   518  	if err != nil {
   519  		return nil, errors.Trace(err)
   520  	}
   521  	spaceMap := make(map[string]corenetwork.SpaceInfo)
   522  	empty := set.Strings{}
   523  	for _, space := range spaces {
   524  		jujuName := corenetwork.ConvertSpaceName(string(space.Name), empty)
   525  		spaceMap[jujuName] = space
   526  	}
   527  	return spaceMap, nil
   528  }
   529  
   530  func (env *maasEnviron) spaceNamesToSpaceInfo(
   531  	ctx context.ProviderCallContext, positiveSpaces, negativeSpaces []string,
   532  ) ([]corenetwork.SpaceInfo, []corenetwork.SpaceInfo, error) {
   533  	spaceMap, err := env.buildSpaceMap(ctx)
   534  	if err != nil {
   535  		return nil, nil, errors.Trace(err)
   536  	}
   537  
   538  	positiveSpaceIds, err := spaceNamesToSpaceInfo(positiveSpaces, spaceMap)
   539  	if err != nil {
   540  		return nil, nil, errors.Trace(err)
   541  	}
   542  	negativeSpaceIds, err := spaceNamesToSpaceInfo(negativeSpaces, spaceMap)
   543  	if err != nil {
   544  		return nil, nil, errors.Trace(err)
   545  	}
   546  	return positiveSpaceIds, negativeSpaceIds, nil
   547  }
   548  
   549  // networkSpaceRequirements combines the space requirements for the application
   550  // bindings and the specified constraints and returns a set of provider
   551  // space IDs for which a NIC needs to be provisioned in the instance we are
   552  // about to launch and a second (negative) set of space IDs that must not be
   553  // present in the launched instance NICs.
   554  func (env *maasEnviron) networkSpaceRequirements(ctx context.ProviderCallContext, endpointToProviderSpaceID map[string]corenetwork.Id, cons constraints.Value) (set.Strings, set.Strings, error) {
   555  	positiveSpaceIds := set.NewStrings()
   556  	negativeSpaceIds := set.NewStrings()
   557  
   558  	// Iterate the application bindings and add each bound space ID to the
   559  	// positive space set.
   560  	for _, providerSpaceID := range endpointToProviderSpaceID {
   561  		// The alpha space is not part of the MAAS space list. When the
   562  		// code that maps between space IDs and provider space IDs
   563  		// encounters a space that it cannot map, it passes the space
   564  		// name through.
   565  		if providerSpaceID == corenetwork.AlphaSpaceName {
   566  			continue
   567  		}
   568  
   569  		positiveSpaceIds.Add(string(providerSpaceID))
   570  	}
   571  
   572  	// Convert space constraints into a list of space IDs to include and
   573  	// a list of space IDs to omit.
   574  	positiveSpaceNames, negativeSpaceNames := convertSpacesFromConstraints(cons.Spaces)
   575  	positiveSpaceInfo, negativeSpaceInfo, err := env.spaceNamesToSpaceInfo(ctx, positiveSpaceNames, negativeSpaceNames)
   576  	if err != nil {
   577  		// Spaces are not supported by this MAAS instance.
   578  		if errors.IsNotSupported(err) {
   579  			return nil, nil, nil
   580  		}
   581  
   582  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   583  		return nil, nil, errors.Trace(err)
   584  	}
   585  
   586  	// Append required space IDs from constraints.
   587  	for _, si := range positiveSpaceInfo {
   588  		if si.ProviderId == "" {
   589  			continue
   590  		}
   591  		positiveSpaceIds.Add(string(si.ProviderId))
   592  	}
   593  
   594  	// Calculate negative space ID set and check for clashes with the positive set.
   595  	for _, si := range negativeSpaceInfo {
   596  		if si.ProviderId == "" {
   597  			continue
   598  		}
   599  
   600  		if positiveSpaceIds.Contains(string(si.ProviderId)) {
   601  			return nil, nil, errors.NewNotValid(nil, fmt.Sprintf("negative space %q from constraints clashes with required spaces for instance NICs", si.Name))
   602  		}
   603  
   604  		negativeSpaceIds.Add(string(si.ProviderId))
   605  	}
   606  
   607  	return positiveSpaceIds, negativeSpaceIds, nil
   608  }
   609  
   610  // acquireNode allocates a machine from MAAS.
   611  func (env *maasEnviron) acquireNode(
   612  	ctx context.ProviderCallContext,
   613  	nodeName, zoneName, systemId string,
   614  	cons constraints.Value,
   615  	positiveSpaceIDs set.Strings,
   616  	negativeSpaceIDs set.Strings,
   617  	volumes []volumeInfo,
   618  ) (*maasInstance, error) {
   619  	acquireParams := convertConstraints(cons)
   620  	addInterfaces(&acquireParams, positiveSpaceIDs, negativeSpaceIDs)
   621  	addStorage(&acquireParams, volumes)
   622  	acquireParams.AgentName = env.uuid
   623  	if zoneName != "" {
   624  		acquireParams.Zone = zoneName
   625  	}
   626  	if nodeName != "" {
   627  		acquireParams.Hostname = nodeName
   628  	}
   629  	if systemId != "" {
   630  		acquireParams.SystemId = systemId
   631  	}
   632  	machine, constraintMatches, err := env.maasController.AllocateMachine(acquireParams)
   633  
   634  	if err != nil {
   635  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   636  		return nil, errors.Trace(err)
   637  	}
   638  	return &maasInstance{
   639  		machine:           machine,
   640  		constraintMatches: constraintMatches,
   641  		environ:           env,
   642  	}, nil
   643  }
   644  
   645  // DistributeInstances implements the state.InstanceDistributor policy.
   646  func (env *maasEnviron) DistributeInstances(
   647  	ctx context.ProviderCallContext, candidates, distributionGroup []instance.Id, limitZones []string,
   648  ) ([]instance.Id, error) {
   649  	return common.DistributeInstances(env, ctx, candidates, distributionGroup, limitZones)
   650  }
   651  
   652  // StartInstance is specified in the InstanceBroker interface.
   653  func (env *maasEnviron) StartInstance(
   654  	ctx context.ProviderCallContext,
   655  	args environs.StartInstanceParams,
   656  ) (_ *environs.StartInstanceResult, err error) {
   657  
   658  	availabilityZone := args.AvailabilityZone
   659  	var nodeName, systemId string
   660  	if args.Placement != "" {
   661  		placement, err := env.parsePlacement(ctx, args.Placement)
   662  		if err != nil {
   663  			return nil, environs.ZoneIndependentError(err)
   664  		}
   665  		// NOTE(axw) we wipe out args.AvailabilityZone if the
   666  		// user specified a specific node or system ID via
   667  		// placement, as placement must always take precedence.
   668  		switch {
   669  		case placement.systemId != "":
   670  			availabilityZone = ""
   671  			systemId = placement.systemId
   672  		case placement.nodeName != "":
   673  			availabilityZone = ""
   674  			nodeName = placement.nodeName
   675  		}
   676  	}
   677  	if availabilityZone != "" {
   678  		zones, err := env.AvailabilityZones(ctx)
   679  		if err != nil {
   680  			return nil, errors.Trace(err)
   681  		}
   682  		if err := errors.Trace(zones.Validate(availabilityZone)); err != nil {
   683  			return nil, errors.Trace(err)
   684  		}
   685  		logger.Debugf("attempting to acquire node in zone %q", availabilityZone)
   686  	}
   687  
   688  	// Storage.
   689  	volumes, err := buildMAASVolumeParameters(args.Volumes, args.Constraints)
   690  	if err != nil {
   691  		return nil, environs.ZoneIndependentError(errors.Annotate(err, "invalid volume parameters"))
   692  	}
   693  
   694  	// Calculate network space requirements.
   695  	positiveSpaceIDs, negativeSpaceIDs, err := env.networkSpaceRequirements(ctx, args.EndpointBindings, args.Constraints)
   696  	if err != nil {
   697  		return nil, errors.Trace(err)
   698  	}
   699  
   700  	inst, selectNodeErr := env.selectNode(ctx,
   701  		selectNodeArgs{
   702  			Constraints:      args.Constraints,
   703  			AvailabilityZone: availabilityZone,
   704  			NodeName:         nodeName,
   705  			SystemId:         systemId,
   706  			PositiveSpaceIDs: positiveSpaceIDs,
   707  			NegativeSpaceIDs: negativeSpaceIDs,
   708  			Volumes:          volumes,
   709  		})
   710  	if selectNodeErr != nil {
   711  		err := errors.Annotate(selectNodeErr, "failed to acquire node")
   712  		if selectNodeErr.noMatch && availabilityZone != "" {
   713  			// The error was due to MAAS not being able to
   714  			// find provide a machine matching the specified
   715  			// constraints in the zone; try again in another.
   716  			return nil, errors.Trace(err)
   717  		}
   718  		return nil, environs.ZoneIndependentError(err)
   719  	}
   720  
   721  	defer func() {
   722  		if err != nil {
   723  			if err := env.StopInstances(ctx, inst.Id()); err != nil {
   724  				logger.Errorf("error releasing failed instance: %v", err)
   725  			}
   726  		}
   727  	}()
   728  
   729  	hc, err := inst.hardwareCharacteristics()
   730  	if err != nil {
   731  		return nil, environs.ZoneIndependentError(err)
   732  	}
   733  
   734  	selectedTools, err := args.Tools.Match(tools.Filter{
   735  		Arch: *hc.Arch,
   736  	})
   737  	if err != nil {
   738  		return nil, environs.ZoneIndependentError(err)
   739  	}
   740  	if err := args.InstanceConfig.SetTools(selectedTools); err != nil {
   741  		return nil, environs.ZoneIndependentError(err)
   742  	}
   743  
   744  	hostname, err := inst.hostname()
   745  	if err != nil {
   746  		return nil, environs.ZoneIndependentError(err)
   747  	}
   748  
   749  	if err := instancecfg.FinishInstanceConfig(args.InstanceConfig, env.Config()); err != nil {
   750  		return nil, environs.ZoneIndependentError(err)
   751  	}
   752  
   753  	subnetsMap, err := env.subnetToSpaceIds(ctx)
   754  	if err != nil {
   755  		return nil, environs.ZoneIndependentError(err)
   756  	}
   757  
   758  	cloudcfg, err := env.newCloudinitConfig(hostname, args.InstanceConfig.Base.OS)
   759  	if err != nil {
   760  		return nil, environs.ZoneIndependentError(err)
   761  	}
   762  
   763  	userdata, err := providerinit.ComposeUserData(args.InstanceConfig, cloudcfg, MAASRenderer{})
   764  	if err != nil {
   765  		return nil, environs.ZoneIndependentError(errors.Annotate(
   766  			err, "could not compose userdata for bootstrap node",
   767  		))
   768  	}
   769  	logger.Debugf("maas user data; %d bytes", len(userdata))
   770  
   771  	distroSeries, err := env.distroSeries(args)
   772  	if err != nil {
   773  		return nil, environs.ZoneIndependentError(err)
   774  	}
   775  	err = inst.machine.Start(gomaasapi.StartArgs{
   776  		DistroSeries: distroSeries,
   777  		UserData:     string(userdata),
   778  	})
   779  	if err != nil {
   780  		return nil, environs.ZoneIndependentError(err)
   781  	}
   782  
   783  	domains, err := env.Domains(ctx)
   784  	if err != nil {
   785  		return nil, errors.Trace(err)
   786  	}
   787  	interfaces, err := maasNetworkInterfaces(ctx, inst, subnetsMap, domains...)
   788  	if err != nil {
   789  		return nil, environs.ZoneIndependentError(err)
   790  	}
   791  	env.tagInstance(inst, args.InstanceConfig)
   792  
   793  	displayName, err := inst.displayName()
   794  	if err != nil {
   795  		return nil, environs.ZoneIndependentError(err)
   796  	}
   797  	logger.Debugf("started instance %q", inst.Id())
   798  
   799  	requestedVolumes := make([]names.VolumeTag, len(args.Volumes))
   800  	for i, v := range args.Volumes {
   801  		requestedVolumes[i] = v.Tag
   802  	}
   803  	resultVolumes, resultAttachments, err := inst.volumes(
   804  		names.NewMachineTag(args.InstanceConfig.MachineId),
   805  		requestedVolumes,
   806  	)
   807  	if err != nil {
   808  		return nil, environs.ZoneIndependentError(err)
   809  	}
   810  	if len(resultVolumes) != len(requestedVolumes) {
   811  		return nil, environs.ZoneIndependentError(errors.Errorf(
   812  			"requested %v storage volumes. %v returned",
   813  			len(requestedVolumes), len(resultVolumes),
   814  		))
   815  	}
   816  
   817  	return &environs.StartInstanceResult{
   818  		DisplayName:       displayName,
   819  		Instance:          inst,
   820  		Hardware:          hc,
   821  		NetworkInfo:       interfaces,
   822  		Volumes:           resultVolumes,
   823  		VolumeAttachments: resultAttachments,
   824  	}, nil
   825  }
   826  
   827  func (env *maasEnviron) tagInstance(inst *maasInstance, instanceConfig *instancecfg.InstanceConfig) {
   828  	err := inst.machine.SetOwnerData(instanceConfig.Tags)
   829  	if err != nil {
   830  		logger.Errorf("could not set owner data for instance: %v", err)
   831  	}
   832  }
   833  
   834  func (env *maasEnviron) distroSeries(args environs.StartInstanceParams) (string, error) {
   835  	if args.Constraints.HasImageID() {
   836  		return *args.Constraints.ImageID, nil
   837  	}
   838  	return corebase.GetSeriesFromBase(args.InstanceConfig.Base)
   839  }
   840  
   841  func (env *maasEnviron) waitForNodeDeployment(ctx context.ProviderCallContext, id instance.Id, timeout time.Duration) error {
   842  	retryStrategy := env.longRetryStrategy
   843  	retryStrategy.MaxDuration = timeout
   844  	retryStrategy.IsFatalError = func(err error) bool {
   845  		if errors.IsNotProvisioned(err) {
   846  			return true
   847  		}
   848  		if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied {
   849  			return true
   850  		}
   851  		return false
   852  	}
   853  	retryStrategy.NotifyFunc = func(lastErr error, attempts int) {
   854  		if errors.IsNotFound(lastErr) {
   855  			logger.Warningf("failed to get instance from provider attempt %d", attempts)
   856  		}
   857  	}
   858  	retryStrategy.Func = func() error {
   859  		machine, err := env.getInstance(ctx, id)
   860  		if err != nil {
   861  			return err
   862  		}
   863  		stat := machine.Status(ctx)
   864  		if stat.Status == status.Running {
   865  			return nil
   866  		}
   867  		if stat.Status == status.ProvisioningError {
   868  			return errors.NewNotProvisioned(nil, fmt.Sprintf("instance %q failed to deploy", id))
   869  		}
   870  		return errors.NewNotYetAvailable(nil, "Not yet provisioned")
   871  	}
   872  	err := retry.Call(retryStrategy)
   873  	if retry.IsAttemptsExceeded(err) || retry.IsDurationExceeded(err) {
   874  		return errors.Errorf("instance %q is started but not deployed", id)
   875  	}
   876  	return errors.Trace(err)
   877  }
   878  
   879  func deploymentStatusCall(nodes gomaasapi.MAASObject, ids ...instance.Id) (gomaasapi.JSONObject, error) {
   880  	filter := getSystemIdValues("nodes", ids)
   881  	return nodes.CallGet("deployment_status", filter)
   882  }
   883  
   884  type selectNodeArgs struct {
   885  	AvailabilityZone string
   886  	NodeName         string
   887  	SystemId         string
   888  	Constraints      constraints.Value
   889  	PositiveSpaceIDs set.Strings
   890  	NegativeSpaceIDs set.Strings
   891  	Volumes          []volumeInfo
   892  }
   893  
   894  type selectNodeError struct {
   895  	error
   896  	noMatch bool
   897  }
   898  
   899  func (env *maasEnviron) selectNode(ctx context.ProviderCallContext, args selectNodeArgs) (*maasInstance, *selectNodeError) {
   900  	inst, err := env.acquireNode(
   901  		ctx,
   902  		args.NodeName,
   903  		args.AvailabilityZone,
   904  		args.SystemId,
   905  		args.Constraints,
   906  		args.PositiveSpaceIDs,
   907  		args.NegativeSpaceIDs,
   908  		args.Volumes,
   909  	)
   910  	if err != nil {
   911  		return nil, &selectNodeError{
   912  			error:   errors.Trace(err),
   913  			noMatch: gomaasapi.IsNoMatchError(err),
   914  		}
   915  	}
   916  	return inst, nil
   917  }
   918  
   919  // newCloudinitConfig creates a cloudinit.Config structure suitable as a base
   920  // for initialising a MAAS node.
   921  func (env *maasEnviron) newCloudinitConfig(hostname, osname string) (cloudinit.CloudConfig, error) {
   922  	cloudcfg, err := cloudinit.New(osname)
   923  	if err != nil {
   924  		return nil, err
   925  	}
   926  
   927  	info := machineInfo{hostname}
   928  	runCmd, err := info.cloudinitRunCmd(cloudcfg)
   929  	if err != nil {
   930  		return nil, errors.Trace(err)
   931  	}
   932  
   933  	operatingSystem := ostype.OSTypeForName(osname)
   934  	switch operatingSystem {
   935  	case ostype.Ubuntu:
   936  		cloudcfg.SetSystemUpdate(true)
   937  		cloudcfg.AddScripts("set -xe", runCmd)
   938  		// DisableNetworkManagement can still disable the bridge(s) creation.
   939  		if on, set := env.Config().DisableNetworkManagement(); on && set {
   940  			logger.Infof(
   941  				"network management disabled - not using %q bridge for containers",
   942  				instancecfg.DefaultBridgeName,
   943  			)
   944  			break
   945  		}
   946  		cloudcfg.AddPackage("bridge-utils")
   947  	}
   948  	return cloudcfg, nil
   949  }
   950  
   951  func (env *maasEnviron) releaseNodes(ctx context.ProviderCallContext, ids []instance.Id, recurse bool) error {
   952  	args := gomaasapi.ReleaseMachinesArgs{
   953  		SystemIDs: instanceIdsToSystemIDs(ids),
   954  		Comment:   "Released by Juju MAAS provider",
   955  	}
   956  	err := env.maasController.ReleaseMachines(args)
   957  
   958  	denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx)
   959  	switch {
   960  	case err == nil:
   961  		return nil
   962  	case gomaasapi.IsCannotCompleteError(err):
   963  		// CannotCompleteError means a node couldn't be released due to
   964  		// a state conflict. Likely it's already released or disk
   965  		// erasing. We're assuming this error *only* means it's
   966  		// safe to assume the instance is already released.
   967  		// MaaS also releases (or attempts) all nodes, and raises
   968  		// a single error on failure. So even with an error 409, all
   969  		// nodes have been released.
   970  		logger.Infof("ignoring error while releasing nodes (%v); all nodes released OK", err)
   971  		return nil
   972  	case gomaasapi.IsBadRequestError(err), denied:
   973  		// a status code of 400 or 403 means one of the nodes
   974  		// couldn't be found and none have been released. We have to
   975  		// release all the ones we can individually.
   976  		if !recurse {
   977  			// this node has already been released and we're golden
   978  			return nil
   979  		}
   980  		return env.releaseNodesIndividually(ctx, ids)
   981  
   982  	default:
   983  		return errors.Annotatef(err, "cannot release nodes")
   984  	}
   985  }
   986  
   987  func (env *maasEnviron) releaseNodesIndividually(ctx context.ProviderCallContext, ids []instance.Id) error {
   988  	var lastErr error
   989  	for _, id := range ids {
   990  		err := env.releaseNodes(ctx, []instance.Id{id}, false)
   991  		if err != nil {
   992  			lastErr = err
   993  			logger.Errorf("error while releasing node %v (%v)", id, err)
   994  			if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied {
   995  				break
   996  			}
   997  		}
   998  	}
   999  	return errors.Trace(lastErr)
  1000  }
  1001  
  1002  func instanceIdsToSystemIDs(ids []instance.Id) []string {
  1003  	systemIDs := make([]string, len(ids))
  1004  	for index, id := range ids {
  1005  		systemIDs[index] = string(id)
  1006  	}
  1007  	return systemIDs
  1008  }
  1009  
  1010  // StopInstances is specified in the InstanceBroker interface.
  1011  func (env *maasEnviron) StopInstances(ctx context.ProviderCallContext, ids ...instance.Id) error {
  1012  	// Shortcut to exit quickly if 'instances' is an empty slice or nil.
  1013  	if len(ids) == 0 {
  1014  		return nil
  1015  	}
  1016  
  1017  	err := env.releaseNodes(ctx, ids, true)
  1018  	if err != nil {
  1019  		return errors.Trace(err)
  1020  	}
  1021  	return common.RemoveStateInstances(env.Storage(), ids...)
  1022  
  1023  }
  1024  
  1025  // Instances returns the instances.Instance objects corresponding to the given
  1026  // slice of instance.Id.  The error is ErrNoInstances if no instances
  1027  // were found.
  1028  func (env *maasEnviron) Instances(ctx context.ProviderCallContext, ids []instance.Id) ([]instances.Instance, error) {
  1029  	if len(ids) == 0 {
  1030  		// This would be treated as "return all instances" below, so
  1031  		// treat it as a special case.
  1032  		// The interface requires us to return this particular error
  1033  		// if no instances were found.
  1034  		return nil, environs.ErrNoInstances
  1035  	}
  1036  	acquired, err := env.acquiredInstances(ctx, ids)
  1037  	if err != nil {
  1038  		return nil, errors.Trace(err)
  1039  	}
  1040  	if len(acquired) == 0 {
  1041  		return nil, environs.ErrNoInstances
  1042  	}
  1043  
  1044  	idMap := make(map[instance.Id]instances.Instance)
  1045  	for _, inst := range acquired {
  1046  		idMap[inst.Id()] = inst
  1047  	}
  1048  
  1049  	missing := false
  1050  	result := make([]instances.Instance, len(ids))
  1051  	for index, id := range ids {
  1052  		val, ok := idMap[id]
  1053  		if !ok {
  1054  			missing = true
  1055  			continue
  1056  		}
  1057  		result[index] = val
  1058  	}
  1059  
  1060  	if missing {
  1061  		return result, environs.ErrPartialInstances
  1062  	}
  1063  	return result, nil
  1064  }
  1065  
  1066  // acquireInstances calls the MAAS API to list acquired nodes.
  1067  //
  1068  // The "ids" slice is a filter for specific instance IDs.
  1069  // Due to how this works in the HTTP API, an empty "ids"
  1070  // matches all instances (not none as you might expect).
  1071  func (env *maasEnviron) acquiredInstances(ctx context.ProviderCallContext, ids []instance.Id) ([]instances.Instance, error) {
  1072  	args := gomaasapi.MachinesArgs{
  1073  		AgentName: env.uuid,
  1074  		SystemIDs: instanceIdsToSystemIDs(ids),
  1075  	}
  1076  
  1077  	maasInstances, err := env.instances(ctx, args)
  1078  	if err != nil {
  1079  		return nil, errors.Trace(err)
  1080  	}
  1081  
  1082  	inst := make([]instances.Instance, len(maasInstances))
  1083  	for i, mi := range maasInstances {
  1084  		inst[i] = mi
  1085  	}
  1086  	return inst, nil
  1087  }
  1088  
  1089  func (env *maasEnviron) instances(ctx context.ProviderCallContext, args gomaasapi.MachinesArgs) ([]*maasInstance, error) {
  1090  	machines, err := env.maasController.Machines(args)
  1091  	if err != nil {
  1092  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1093  		return nil, errors.Trace(err)
  1094  	}
  1095  
  1096  	inst := make([]*maasInstance, len(machines))
  1097  	for index, machine := range machines {
  1098  		inst[index] = &maasInstance{machine: machine, environ: env}
  1099  	}
  1100  	return inst, nil
  1101  }
  1102  
  1103  func (env *maasEnviron) getInstance(ctx context.ProviderCallContext, instId instance.Id) (instances.Instance, error) {
  1104  	instances, err := env.acquiredInstances(ctx, []instance.Id{instId})
  1105  	if err != nil {
  1106  		// This path can never trigger on MAAS 2, but MAAS 2 doesn't
  1107  		// return an error for a machine not found, it just returns
  1108  		// empty results. The clause below catches that.
  1109  		if maasErr, ok := errors.Cause(err).(gomaasapi.ServerError); ok && maasErr.StatusCode == http.StatusNotFound {
  1110  			return nil, errors.NotFoundf("instance %q", instId)
  1111  		}
  1112  		return nil, errors.Annotatef(err, "getting instance %q", instId)
  1113  	}
  1114  	if len(instances) == 0 {
  1115  		return nil, errors.NotFoundf("instance %q", instId)
  1116  	}
  1117  	inst := instances[0]
  1118  	return inst, nil
  1119  }
  1120  
  1121  // subnetToSpaceIds fetches the spaces from MAAS and builds a map of subnets to
  1122  // space ids.
  1123  func (env *maasEnviron) subnetToSpaceIds(ctx context.ProviderCallContext) (map[string]corenetwork.Id, error) {
  1124  	subnetsMap := make(map[string]corenetwork.Id)
  1125  	spaces, err := env.Spaces(ctx)
  1126  	if err != nil {
  1127  		return subnetsMap, errors.Trace(err)
  1128  	}
  1129  	for _, space := range spaces {
  1130  		for _, subnet := range space.Subnets {
  1131  			subnetsMap[subnet.CIDR] = space.ProviderId
  1132  		}
  1133  	}
  1134  	return subnetsMap, nil
  1135  }
  1136  
  1137  // Spaces returns all the spaces, that have subnets, known to the provider.
  1138  // Space name is not filled in as the provider doesn't know the juju name for
  1139  // the space.
  1140  func (env *maasEnviron) Spaces(ctx context.ProviderCallContext) (corenetwork.SpaceInfos, error) {
  1141  	spaces, err := env.maasController.Spaces()
  1142  	if err != nil {
  1143  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1144  		return nil, errors.Trace(err)
  1145  	}
  1146  	var result []corenetwork.SpaceInfo
  1147  	for _, space := range spaces {
  1148  		if len(space.Subnets()) == 0 {
  1149  			continue
  1150  		}
  1151  		outSpace := corenetwork.SpaceInfo{
  1152  			Name:       corenetwork.SpaceName(space.Name()),
  1153  			ProviderId: corenetwork.Id(strconv.Itoa(space.ID())),
  1154  			Subnets:    make([]corenetwork.SubnetInfo, len(space.Subnets())),
  1155  		}
  1156  		for i, subnet := range space.Subnets() {
  1157  			subnetInfo := corenetwork.SubnetInfo{
  1158  				ProviderId:      corenetwork.Id(strconv.Itoa(subnet.ID())),
  1159  				VLANTag:         subnet.VLAN().VID(),
  1160  				CIDR:            subnet.CIDR(),
  1161  				ProviderSpaceId: corenetwork.Id(strconv.Itoa(space.ID())),
  1162  			}
  1163  			outSpace.Subnets[i] = subnetInfo
  1164  		}
  1165  		result = append(result, outSpace)
  1166  	}
  1167  	return result, nil
  1168  }
  1169  
  1170  // Subnets returns basic information about the specified subnets known
  1171  // by the provider for the specified instance. subnetIds must not be
  1172  // empty. Implements NetworkingEnviron.Subnets.
  1173  func (env *maasEnviron) Subnets(
  1174  	ctx context.ProviderCallContext, instId instance.Id, subnetIds []corenetwork.Id,
  1175  ) ([]corenetwork.SubnetInfo, error) {
  1176  	var subnets []corenetwork.SubnetInfo
  1177  	if instId == instance.UnknownId {
  1178  		spaces, err := env.Spaces(ctx)
  1179  		if err != nil {
  1180  			return nil, errors.Trace(err)
  1181  		}
  1182  		for _, space := range spaces {
  1183  			subnets = append(subnets, space.Subnets...)
  1184  		}
  1185  	} else {
  1186  		var err error
  1187  		subnets, err = env.filteredSubnets2(ctx, instId)
  1188  		if err != nil {
  1189  			return nil, errors.Trace(err)
  1190  		}
  1191  	}
  1192  
  1193  	if len(subnetIds) == 0 {
  1194  		return subnets, nil
  1195  	}
  1196  	var result []corenetwork.SubnetInfo
  1197  	subnetMap := make(map[string]bool)
  1198  	for _, subnetId := range subnetIds {
  1199  		subnetMap[string(subnetId)] = false
  1200  	}
  1201  	for _, subnet := range subnets {
  1202  		_, ok := subnetMap[string(subnet.ProviderId)]
  1203  		if !ok {
  1204  			// This id is not what we're looking for.
  1205  			continue
  1206  		}
  1207  		subnetMap[string(subnet.ProviderId)] = true
  1208  		result = append(result, subnet)
  1209  	}
  1210  
  1211  	return result, checkNotFound(subnetMap)
  1212  }
  1213  
  1214  func (env *maasEnviron) filteredSubnets2(
  1215  	ctx context.ProviderCallContext, instId instance.Id,
  1216  ) ([]corenetwork.SubnetInfo, error) {
  1217  	args := gomaasapi.MachinesArgs{
  1218  		AgentName: env.uuid,
  1219  		SystemIDs: []string{string(instId)},
  1220  	}
  1221  	machines, err := env.maasController.Machines(args)
  1222  	if err != nil {
  1223  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1224  		return nil, errors.Trace(err)
  1225  	}
  1226  	if len(machines) == 0 {
  1227  		return nil, errors.NotFoundf("machine %v", instId)
  1228  	} else if len(machines) > 1 {
  1229  		return nil, errors.Errorf("unexpected response getting machine details %v: %v", instId, machines)
  1230  	}
  1231  
  1232  	machine := machines[0]
  1233  	spaceMap, err := env.buildSpaceMap(ctx)
  1234  	if err != nil {
  1235  		return nil, errors.Trace(err)
  1236  	}
  1237  	var result []corenetwork.SubnetInfo
  1238  	for _, iface := range machine.InterfaceSet() {
  1239  		for _, link := range iface.Links() {
  1240  			subnet := link.Subnet()
  1241  			space, ok := spaceMap[subnet.Space()]
  1242  			if !ok {
  1243  				return nil, errors.Errorf("missing space %v on subnet %v", subnet.Space(), subnet.CIDR())
  1244  			}
  1245  			subnetInfo := corenetwork.SubnetInfo{
  1246  				ProviderId:      corenetwork.Id(strconv.Itoa(subnet.ID())),
  1247  				VLANTag:         subnet.VLAN().VID(),
  1248  				CIDR:            subnet.CIDR(),
  1249  				ProviderSpaceId: space.ProviderId,
  1250  			}
  1251  			result = append(result, subnetInfo)
  1252  		}
  1253  	}
  1254  	return result, nil
  1255  }
  1256  
  1257  func checkNotFound(subnetIdSet map[string]bool) error {
  1258  	var notFound []string
  1259  	for subnetId, found := range subnetIdSet {
  1260  		if !found {
  1261  			notFound = append(notFound, subnetId)
  1262  		}
  1263  	}
  1264  	if len(notFound) != 0 {
  1265  		return errors.Errorf("failed to find the following subnets: %v", strings.Join(notFound, ", "))
  1266  	}
  1267  	return nil
  1268  }
  1269  
  1270  // AllInstances implements environs.InstanceBroker.
  1271  func (env *maasEnviron) AllInstances(ctx context.ProviderCallContext) ([]instances.Instance, error) {
  1272  	return env.acquiredInstances(ctx, nil)
  1273  }
  1274  
  1275  // AllRunningInstances implements environs.InstanceBroker.
  1276  func (env *maasEnviron) AllRunningInstances(ctx context.ProviderCallContext) ([]instances.Instance, error) {
  1277  	// We always get all instances here, so "all" is the same as "running".
  1278  	return env.AllInstances(ctx)
  1279  }
  1280  
  1281  // Storage is defined by the Environ interface.
  1282  func (env *maasEnviron) Storage() storage.Storage {
  1283  	env.ecfgMutex.Lock()
  1284  	defer env.ecfgMutex.Unlock()
  1285  	return env.storageUnlocked
  1286  }
  1287  
  1288  func (env *maasEnviron) Destroy(ctx context.ProviderCallContext) error {
  1289  	if err := common.Destroy(env, ctx); err != nil {
  1290  		return errors.Trace(err)
  1291  	}
  1292  	return env.Storage().RemoveAll()
  1293  }
  1294  
  1295  // DestroyController implements the Environ interface.
  1296  func (env *maasEnviron) DestroyController(ctx context.ProviderCallContext, controllerUUID string) error {
  1297  	// TODO(wallyworld): destroy hosted model resources
  1298  	return env.Destroy(ctx)
  1299  }
  1300  
  1301  func (*maasEnviron) Provider() environs.EnvironProvider {
  1302  	return &providerInstance
  1303  }
  1304  
  1305  func (env *maasEnviron) AllocateContainerAddresses(ctx context.ProviderCallContext, hostInstanceID instance.Id, containerTag names.MachineTag, preparedInfo corenetwork.InterfaceInfos) (corenetwork.InterfaceInfos, error) {
  1306  	if len(preparedInfo) == 0 {
  1307  		return nil, errors.Errorf("no prepared info to allocate")
  1308  	}
  1309  
  1310  	logger.Debugf("using prepared container info: %+v", preparedInfo)
  1311  	args := gomaasapi.MachinesArgs{
  1312  		AgentName: env.uuid,
  1313  		SystemIDs: []string{string(hostInstanceID)},
  1314  	}
  1315  	machines, err := env.maasController.Machines(args)
  1316  	if err != nil {
  1317  		return nil, errors.Trace(err)
  1318  	}
  1319  	if len(machines) != 1 {
  1320  		return nil, errors.Errorf("failed to identify unique machine with ID %q; got %v", hostInstanceID, machines)
  1321  	}
  1322  	machine := machines[0]
  1323  	deviceName, err := env.namespace.Hostname(containerTag.Id())
  1324  	if err != nil {
  1325  		return nil, errors.Trace(err)
  1326  	}
  1327  	params, err := env.prepareDeviceDetails(deviceName, machine, preparedInfo)
  1328  	if err != nil {
  1329  		return nil, errors.Trace(err)
  1330  	}
  1331  
  1332  	// Check to see if we've already tried to allocate information for this device:
  1333  	device, err := env.checkForExistingDevice(params)
  1334  	if err != nil {
  1335  		return nil, errors.Trace(err)
  1336  	}
  1337  	if device == nil {
  1338  		device, err = env.createAndPopulateDevice(params)
  1339  		if err != nil {
  1340  			return nil, errors.Annotatef(err,
  1341  				"failed to create MAAS device for %q",
  1342  				params.Name)
  1343  		}
  1344  	}
  1345  
  1346  	// TODO(jam): the old code used to reload the device from its SystemID()
  1347  	nameToParentName := make(map[string]string)
  1348  	for _, nic := range preparedInfo {
  1349  		nameToParentName[nic.InterfaceName] = nic.ParentInterfaceName
  1350  	}
  1351  	interfaces, err := env.deviceInterfaceInfo(device, nameToParentName, params.CIDRToStaticRoutes)
  1352  	if err != nil {
  1353  		return nil, errors.Annotate(err, "cannot get device interfaces")
  1354  	}
  1355  	return interfaces, nil
  1356  }
  1357  
  1358  func (env *maasEnviron) ReleaseContainerAddresses(ctx context.ProviderCallContext, interfaces []corenetwork.ProviderInterfaceInfo) error {
  1359  	hwAddresses := make([]string, len(interfaces))
  1360  	for i, info := range interfaces {
  1361  		hwAddresses[i] = info.HardwareAddress
  1362  	}
  1363  
  1364  	devices, err := env.maasController.Devices(gomaasapi.DevicesArgs{MACAddresses: hwAddresses})
  1365  	if err != nil {
  1366  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1367  		return errors.Trace(err)
  1368  	}
  1369  	// If one device matched on multiple MAC addresses (like for
  1370  	// multi-nic containers) it will be in the slice multiple
  1371  	// times. Skip devices we've seen already.
  1372  	seen := set.NewStrings()
  1373  	for _, device := range devices {
  1374  		if seen.Contains(device.SystemID()) {
  1375  			continue
  1376  		}
  1377  		seen.Add(device.SystemID())
  1378  
  1379  		err = device.Delete()
  1380  		if err != nil {
  1381  			return errors.Annotatef(err, "deleting device %s", device.SystemID())
  1382  		}
  1383  	}
  1384  	return nil
  1385  }
  1386  
  1387  // AdoptResources updates all the instances to indicate they
  1388  // are now associated with the specified controller.
  1389  func (env *maasEnviron) AdoptResources(ctx context.ProviderCallContext, controllerUUID string, _ version.Number) error {
  1390  	allInstances, err := env.AllInstances(ctx)
  1391  	if err != nil {
  1392  		return errors.Trace(err)
  1393  	}
  1394  	var failed []instance.Id
  1395  	for _, inst := range allInstances {
  1396  		maasInst, ok := inst.(*maasInstance)
  1397  		if !ok {
  1398  			// This should never happen.
  1399  			return errors.Errorf("instance %q wasn't a maasInstance", inst.Id())
  1400  		}
  1401  		// From the MAAS docs: "[SetOwnerData] will not remove any
  1402  		// previous keys unless explicitly passed with an empty
  1403  		// string." So not passing all of the keys here is fine.
  1404  		// https://maas.ubuntu.com/docs2.0/api.html#machine
  1405  		err := maasInst.machine.SetOwnerData(map[string]string{tags.JujuController: controllerUUID})
  1406  		if err != nil {
  1407  			logger.Errorf("error setting controller uuid tag for %q: %v", inst.Id(), err)
  1408  			failed = append(failed, inst.Id())
  1409  		}
  1410  	}
  1411  
  1412  	if failed != nil {
  1413  		return errors.Errorf("failed to update controller for some instances: %v", failed)
  1414  	}
  1415  	return nil
  1416  }
  1417  
  1418  // ProviderSpaceInfo implements environs.NetworkingEnviron.
  1419  func (*maasEnviron) ProviderSpaceInfo(
  1420  	ctx context.ProviderCallContext, space *corenetwork.SpaceInfo,
  1421  ) (*environs.ProviderSpaceInfo, error) {
  1422  	return nil, errors.NotSupportedf("provider space info")
  1423  }
  1424  
  1425  // AreSpacesRoutable implements environs.NetworkingEnviron.
  1426  func (*maasEnviron) AreSpacesRoutable(ctx context.ProviderCallContext, space1, space2 *environs.ProviderSpaceInfo) (bool, error) {
  1427  	return false, nil
  1428  }
  1429  
  1430  // SuperSubnets implements environs.SuperSubnets
  1431  func (*maasEnviron) SuperSubnets(ctx context.ProviderCallContext) ([]string, error) {
  1432  	return nil, errors.NotSupportedf("super subnets")
  1433  }
  1434  
  1435  // Domains gets the domains managed by MAAS. We only need the name of the
  1436  // domain at present. If more information is needed this function can be
  1437  // updated to parse and return a structure. Client code would need to be
  1438  // updated.
  1439  func (env *maasEnviron) Domains(ctx context.ProviderCallContext) ([]string, error) {
  1440  	maasDomains, err := env.maasController.Domains()
  1441  	if err != nil {
  1442  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
  1443  		return nil, errors.Trace(err)
  1444  	}
  1445  	var result []string
  1446  	for _, domain := range maasDomains {
  1447  		result = append(result, domain.Name())
  1448  	}
  1449  	return result, nil
  1450  }