github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/maas/maas_environ_whitebox_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package maas
     5  
     6  import (
     7  	"encoding/base64"
     8  	"fmt"
     9  	"math/rand"
    10  	"net/http"
    11  
    12  	"github.com/juju/collections/set"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/gomaasapi/v2"
    15  	"github.com/juju/names/v5"
    16  	"github.com/juju/testing"
    17  	jc "github.com/juju/testing/checkers"
    18  	"github.com/juju/utils/v3"
    19  	"github.com/juju/version/v2"
    20  	gc "gopkg.in/check.v1"
    21  	goyaml "gopkg.in/yaml.v2"
    22  
    23  	"github.com/juju/juju/cloud"
    24  	"github.com/juju/juju/cloudconfig/cloudinit"
    25  	"github.com/juju/juju/core/arch"
    26  	"github.com/juju/juju/core/constraints"
    27  	"github.com/juju/juju/core/instance"
    28  	"github.com/juju/juju/core/network"
    29  	"github.com/juju/juju/environs"
    30  	"github.com/juju/juju/environs/bootstrap"
    31  	environscloudspec "github.com/juju/juju/environs/cloudspec"
    32  	"github.com/juju/juju/environs/config"
    33  	"github.com/juju/juju/environs/simplestreams"
    34  	sstesting "github.com/juju/juju/environs/simplestreams/testing"
    35  	"github.com/juju/juju/environs/tags"
    36  	envtesting "github.com/juju/juju/environs/testing"
    37  	envtools "github.com/juju/juju/environs/tools"
    38  	jujutesting "github.com/juju/juju/juju/testing"
    39  	coretesting "github.com/juju/juju/testing"
    40  )
    41  
    42  const maasVersionResponse = `{"version": "unknown", "subversion": "", "capabilities": ["networks-management", "static-ipaddresses", "ipv6-deployment-ubuntu", "devices-management", "storage-deployment-ubuntu", "network-deployment-ubuntu"]}`
    43  
    44  const maasDomainsResponse = `
    45  [
    46      {
    47          "authoritative": "true",
    48          "resource_uri": "/MAAS/api/2.0/domains/0/",
    49          "name": "maas",
    50          "id": 0,
    51          "ttl": null,
    52          "resource_record_count": 3
    53      }
    54  ]
    55  `
    56  
    57  type maasEnvironSuite struct {
    58  	maasSuite
    59  }
    60  
    61  var _ = gc.Suite(&maasEnvironSuite{})
    62  
    63  func (suite *maasEnvironSuite) getEnvWithServer(c *gc.C) (*maasEnviron, error) {
    64  	testServer := gomaasapi.NewSimpleServer()
    65  	testServer.AddGetResponse("/api/2.0/version/", http.StatusOK, maasVersionResponse)
    66  	testServer.AddGetResponse("/api/2.0/users/?op=whoami", http.StatusOK, "{}")
    67  	testServer.AddGetResponse("/api/2.0/domains", http.StatusOK, maasDomainsResponse)
    68  	// Weirdly, rather than returning a 404 when the version is
    69  	// unknown, MAAS2 returns some HTML (the login page).
    70  	testServer.AddGetResponse("/api/1.0/version/", http.StatusOK, "<html></html>")
    71  	testServer.Start()
    72  	suite.AddCleanup(func(*gc.C) { testServer.Close() })
    73  	cred := cloud.NewCredential(cloud.OAuth1AuthType, map[string]string{
    74  		"maas-oauth": "a:b:c",
    75  	})
    76  	cloud := environscloudspec.CloudSpec{
    77  		Type:       "maas",
    78  		Name:       "maas",
    79  		Endpoint:   testServer.Server.URL,
    80  		Credential: &cred,
    81  	}
    82  	attrs := coretesting.FakeConfig().Merge(maasEnvAttrs)
    83  	cfg, err := config.New(config.NoDefaults, attrs)
    84  	c.Assert(err, jc.ErrorIsNil)
    85  	return NewEnviron(cloud, cfg, nil)
    86  }
    87  
    88  func (suite *maasEnvironSuite) TestNewEnvironWithController(c *gc.C) {
    89  	env, err := suite.getEnvWithServer(c)
    90  	c.Assert(err, jc.ErrorIsNil)
    91  	c.Assert(env, gc.NotNil)
    92  }
    93  
    94  func (suite *maasEnvironSuite) injectControllerWithSpacesAndCheck(c *gc.C, spaces []gomaasapi.Space, expected gomaasapi.AllocateMachineArgs) (*maasEnviron, *fakeController) {
    95  	machine := newFakeMachine("Bruce Sterling", arch.HostArch(), "")
    96  	return suite.injectControllerWithMachine(c, machine, spaces, expected)
    97  }
    98  
    99  func (suite *maasEnvironSuite) injectControllerWithMachine(c *gc.C, machine *fakeMachine, spaces []gomaasapi.Space, expected gomaasapi.AllocateMachineArgs) (*maasEnviron, *fakeController) {
   100  	var env *maasEnviron
   101  	check := func(args gomaasapi.AllocateMachineArgs) {
   102  		expected.AgentName = env.Config().UUID()
   103  		c.Assert(args, gc.DeepEquals, expected)
   104  	}
   105  
   106  	controller := &fakeController{
   107  		allocateMachineArgsCheck: check,
   108  		allocateMachine:          machine,
   109  		allocateMachineMatches: gomaasapi.ConstraintMatches{
   110  			Storage: map[string][]gomaasapi.StorageDevice{},
   111  		},
   112  		spaces: spaces,
   113  	}
   114  	suite.injectController(controller)
   115  	suite.setupFakeTools(c)
   116  	env = suite.makeEnviron(c, nil)
   117  	return env, controller
   118  }
   119  
   120  func (suite *maasEnvironSuite) makeEnvironWithMachines(c *gc.C, expectedSystemIDs []string, returnSystemIDs []string) *maasEnviron {
   121  	var env *maasEnviron
   122  	checkArgs := func(args gomaasapi.MachinesArgs) {
   123  		c.Check(args.SystemIDs, gc.DeepEquals, expectedSystemIDs)
   124  		c.Check(args.AgentName, gc.Equals, env.Config().UUID())
   125  	}
   126  	machines := make([]gomaasapi.Machine, len(returnSystemIDs))
   127  	for index, id := range returnSystemIDs {
   128  		machines[index] = &fakeMachine{systemID: id}
   129  	}
   130  	controller := &fakeController{
   131  		machines:          machines,
   132  		machinesArgsCheck: checkArgs,
   133  	}
   134  	env = suite.makeEnviron(c, controller)
   135  	return env
   136  }
   137  
   138  func (suite *maasEnvironSuite) TestAllRunningInstances(c *gc.C) {
   139  	env := suite.makeEnvironWithMachines(
   140  		c, []string{}, []string{"tuco", "tio", "gus"},
   141  	)
   142  	result, err := env.AllRunningInstances(suite.callCtx)
   143  	c.Assert(err, jc.ErrorIsNil)
   144  	expectedMachines := set.NewStrings("tuco", "tio", "gus")
   145  	actualMachines := set.NewStrings()
   146  	for _, instance := range result {
   147  		actualMachines.Add(string(instance.Id()))
   148  	}
   149  	c.Assert(actualMachines, gc.DeepEquals, expectedMachines)
   150  }
   151  
   152  func (suite *maasEnvironSuite) TestAllRunningInstancesError(c *gc.C) {
   153  	controller := &fakeController{machinesError: errors.New("Something terrible!")}
   154  	env := suite.makeEnviron(c, controller)
   155  	_, err := env.AllRunningInstances(suite.callCtx)
   156  	c.Assert(err, gc.ErrorMatches, "Something terrible!")
   157  }
   158  
   159  func (suite *maasEnvironSuite) TestInstances(c *gc.C) {
   160  	env := suite.makeEnvironWithMachines(
   161  		c, []string{"jake", "bonnibel"}, []string{"jake", "bonnibel"},
   162  	)
   163  	result, err := env.Instances(suite.callCtx, []instance.Id{"jake", "bonnibel"})
   164  	c.Assert(err, jc.ErrorIsNil)
   165  	expectedMachines := set.NewStrings("jake", "bonnibel")
   166  	actualMachines := set.NewStrings()
   167  	for _, machine := range result {
   168  		actualMachines.Add(string(machine.Id()))
   169  	}
   170  	c.Assert(actualMachines, gc.DeepEquals, expectedMachines)
   171  }
   172  
   173  func (suite *maasEnvironSuite) TestInstancesInvalidCredential(c *gc.C) {
   174  	controller := &fakeController{
   175  		machinesError: gomaasapi.NewPermissionError("fail auth here"),
   176  	}
   177  	env := suite.makeEnviron(c, controller)
   178  	c.Assert(suite.invalidCredential, jc.IsFalse)
   179  	_, err := env.Instances(suite.callCtx, []instance.Id{"jake", "bonnibel"})
   180  	c.Assert(err, gc.NotNil)
   181  	c.Assert(suite.invalidCredential, jc.IsTrue)
   182  }
   183  
   184  func (suite *maasEnvironSuite) TestInstancesPartialResult(c *gc.C) {
   185  	env := suite.makeEnvironWithMachines(
   186  		c, []string{"jake", "bonnibel"}, []string{"tuco", "bonnibel"},
   187  	)
   188  	result, err := env.Instances(suite.callCtx, []instance.Id{"jake", "bonnibel"})
   189  	c.Check(err, gc.Equals, environs.ErrPartialInstances)
   190  	c.Assert(result, gc.HasLen, 2)
   191  	c.Assert(result[0], gc.IsNil)
   192  	c.Assert(result[1].Id(), gc.Equals, instance.Id("bonnibel"))
   193  }
   194  
   195  func (suite *maasEnvironSuite) TestAvailabilityZones(c *gc.C) {
   196  	env := suite.makeEnviron(c, newFakeController())
   197  	result, err := env.AvailabilityZones(suite.callCtx)
   198  	c.Assert(err, jc.ErrorIsNil)
   199  	expectedZones := set.NewStrings("mossack", "fonseca")
   200  	actualZones := set.NewStrings()
   201  	for _, zone := range result {
   202  		actualZones.Add(zone.Name())
   203  	}
   204  	c.Assert(actualZones, gc.DeepEquals, expectedZones)
   205  }
   206  
   207  func (suite *maasEnvironSuite) TestAvailabilityZonesError(c *gc.C) {
   208  	controller := &fakeController{
   209  		zonesError: errors.New("a bad thing"),
   210  	}
   211  	env := suite.makeEnviron(c, controller)
   212  	_, err := env.AvailabilityZones(suite.callCtx)
   213  	c.Assert(err, gc.ErrorMatches, "a bad thing")
   214  }
   215  
   216  func (suite *maasEnvironSuite) TestAvailabilityZonesInvalidCredential(c *gc.C) {
   217  	controller := &fakeController{
   218  		zonesError: gomaasapi.NewPermissionError("fail auth here"),
   219  	}
   220  	env := suite.makeEnviron(c, controller)
   221  	c.Assert(suite.invalidCredential, jc.IsFalse)
   222  	_, err := env.AvailabilityZones(suite.callCtx)
   223  	c.Assert(err, gc.NotNil)
   224  	c.Assert(suite.invalidCredential, jc.IsTrue)
   225  }
   226  
   227  func (suite *maasEnvironSuite) TestSpaces(c *gc.C) {
   228  	controller := &fakeController{
   229  		spaces: []gomaasapi.Space{
   230  			fakeSpace{
   231  				name: "pepper",
   232  				id:   1234,
   233  			},
   234  			fakeSpace{
   235  				name: "freckles",
   236  				id:   4567,
   237  				subnets: []gomaasapi.Subnet{
   238  					fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24"},
   239  					fakeSubnet{id: 98, vlan: fakeVLAN{vid: 67}, cidr: "192.168.11.0/24"},
   240  				},
   241  			},
   242  		},
   243  	}
   244  	env := suite.makeEnviron(c, controller)
   245  	result, err := env.Spaces(suite.callCtx)
   246  	c.Assert(err, jc.ErrorIsNil)
   247  	c.Assert(result, gc.HasLen, 1)
   248  	c.Assert(result[0].Name, gc.Equals, network.SpaceName("freckles"))
   249  	c.Assert(result[0].ProviderId, gc.Equals, network.Id("4567"))
   250  	subnets := result[0].Subnets
   251  	c.Assert(subnets, gc.HasLen, 2)
   252  	c.Assert(subnets[0].ProviderId, gc.Equals, network.Id("99"))
   253  	c.Assert(subnets[0].VLANTag, gc.Equals, 66)
   254  	c.Assert(subnets[0].CIDR, gc.Equals, "192.168.10.0/24")
   255  	c.Assert(subnets[0].ProviderSpaceId, gc.Equals, network.Id("4567"))
   256  	c.Assert(subnets[1].ProviderId, gc.Equals, network.Id("98"))
   257  	c.Assert(subnets[1].VLANTag, gc.Equals, 67)
   258  	c.Assert(subnets[1].CIDR, gc.Equals, "192.168.11.0/24")
   259  	c.Assert(subnets[1].ProviderSpaceId, gc.Equals, network.Id("4567"))
   260  }
   261  
   262  func (suite *maasEnvironSuite) TestSpacesError(c *gc.C) {
   263  	controller := &fakeController{
   264  		spacesError: errors.New("Joe Manginiello"),
   265  	}
   266  	env := suite.makeEnviron(c, controller)
   267  	_, err := env.Spaces(suite.callCtx)
   268  	c.Assert(err, gc.ErrorMatches, "Joe Manginiello")
   269  }
   270  
   271  func (suite *maasEnvironSuite) TestSpacesInvalidCredential(c *gc.C) {
   272  	controller := &fakeController{
   273  		spacesError: gomaasapi.NewPermissionError("fail auth here"),
   274  	}
   275  	env := suite.makeEnviron(c, controller)
   276  	c.Assert(suite.invalidCredential, jc.IsFalse)
   277  	_, err := env.Spaces(suite.callCtx)
   278  	c.Assert(err, gc.NotNil)
   279  	c.Assert(suite.invalidCredential, jc.IsTrue)
   280  }
   281  
   282  func collectReleaseArgs(controller *fakeController) []gomaasapi.ReleaseMachinesArgs {
   283  	args := []gomaasapi.ReleaseMachinesArgs{}
   284  	for _, call := range controller.Stub.Calls() {
   285  		if call.FuncName == "ReleaseMachines" {
   286  			args = append(args, call.Args[0].(gomaasapi.ReleaseMachinesArgs))
   287  		}
   288  	}
   289  	return args
   290  }
   291  
   292  func (suite *maasEnvironSuite) TestStopInstancesReturnsIfParameterEmpty(c *gc.C) {
   293  	controller := newFakeController()
   294  	err := suite.makeEnviron(c, controller).StopInstances(suite.callCtx)
   295  	c.Check(err, jc.ErrorIsNil)
   296  	c.Assert(collectReleaseArgs(controller), gc.HasLen, 0)
   297  }
   298  
   299  func (suite *maasEnvironSuite) TestStopInstancesStopsAndReleasesInstances(c *gc.C) {
   300  	// Return a cannot complete indicating that test1 is in the wrong state.
   301  	// The release operation will still release the others and succeed.
   302  	controller := newFakeControllerWithFiles(&fakeFile{name: coretesting.ModelTag.Id() + "-provider-state"})
   303  	err := suite.makeEnviron(c, controller).StopInstances(suite.callCtx, "test1", "test2", "test3")
   304  	c.Check(err, jc.ErrorIsNil)
   305  	args := collectReleaseArgs(controller)
   306  	c.Assert(args, gc.HasLen, 1)
   307  	c.Assert(args[0].SystemIDs, gc.DeepEquals, []string{"test1", "test2", "test3"})
   308  }
   309  
   310  func (suite *maasEnvironSuite) TestStopInstancesIgnoresConflict(c *gc.C) {
   311  	// Return a cannot complete indicating that test1 is in the wrong state.
   312  	// The release operation will still release the others and succeed.
   313  	controller := newFakeControllerWithFiles(&fakeFile{name: coretesting.ModelTag.Id() + "-provider-state"})
   314  	controller.SetErrors(gomaasapi.NewCannotCompleteError("test1 not allocated"))
   315  	err := suite.makeEnviron(c, controller).StopInstances(suite.callCtx, "test1", "test2", "test3")
   316  	c.Check(err, jc.ErrorIsNil)
   317  
   318  	args := collectReleaseArgs(controller)
   319  	c.Assert(args, gc.HasLen, 1)
   320  	c.Assert(args[0].SystemIDs, gc.DeepEquals, []string{"test1", "test2", "test3"})
   321  }
   322  
   323  func (suite *maasEnvironSuite) TestStopInstancesIgnoresMissingNodeAndRecurses(c *gc.C) {
   324  	controller := newFakeControllerWithFiles(&fakeFile{name: coretesting.ModelTag.Id() + "-provider-state"})
   325  	controller.SetErrors(
   326  		gomaasapi.NewBadRequestError("no such machine: test1"),
   327  		gomaasapi.NewBadRequestError("no such machine: test1"),
   328  	)
   329  	err := suite.makeEnviron(c, controller).StopInstances(suite.callCtx, "test1", "test2", "test3")
   330  	c.Check(err, jc.ErrorIsNil)
   331  	args := collectReleaseArgs(controller)
   332  	c.Assert(args, gc.HasLen, 4)
   333  	c.Assert(args[0].SystemIDs, gc.DeepEquals, []string{"test1", "test2", "test3"})
   334  	c.Assert(args[1].SystemIDs, gc.DeepEquals, []string{"test1"})
   335  	c.Assert(args[2].SystemIDs, gc.DeepEquals, []string{"test2"})
   336  	c.Assert(args[3].SystemIDs, gc.DeepEquals, []string{"test3"})
   337  }
   338  
   339  func (suite *maasEnvironSuite) checkStopInstancesFails(c *gc.C, withError error) {
   340  	controller := newFakeControllerWithFiles(&fakeFile{name: coretesting.ModelTag.Id() + "-provider-state"})
   341  	controller.SetErrors(withError)
   342  	err := suite.makeEnviron(c, controller).StopInstances(suite.callCtx, "test1", "test2", "test3")
   343  	c.Check(err, gc.ErrorMatches, fmt.Sprintf("cannot release nodes: %s", withError))
   344  	// Only tries once.
   345  	c.Assert(collectReleaseArgs(controller), gc.HasLen, 1)
   346  }
   347  
   348  func (suite *maasEnvironSuite) TestStopInstancesReturnsUnexpectedMAASError(c *gc.C) {
   349  	suite.checkStopInstancesFails(c, gomaasapi.NewNoMatchError("Something else bad!"))
   350  }
   351  
   352  func (suite *maasEnvironSuite) TestStopInstancesReturnsUnexpectedError(c *gc.C) {
   353  	suite.checkStopInstancesFails(c, errors.New("Something completely unexpected!"))
   354  }
   355  
   356  func (suite *maasEnvironSuite) TestStartInstanceError(c *gc.C) {
   357  	suite.injectController(&fakeController{
   358  		allocateMachineError: errors.New("Charles Babbage"),
   359  	})
   360  	env := suite.makeEnviron(c, nil)
   361  	_, err := env.StartInstance(suite.callCtx, environs.StartInstanceParams{})
   362  	c.Assert(err, gc.ErrorMatches, "failed to acquire node: Charles Babbage")
   363  }
   364  
   365  func (suite *maasEnvironSuite) TestStartInstance(c *gc.C) {
   366  	env, _ := suite.injectControllerWithSpacesAndCheck(c, nil, gomaasapi.AllocateMachineArgs{})
   367  
   368  	params := environs.StartInstanceParams{ControllerUUID: suite.controllerUUID}
   369  	result, err := jujutesting.StartInstanceWithParams(env, suite.callCtx, "1", params)
   370  	c.Assert(err, jc.ErrorIsNil)
   371  	c.Assert(result.Instance.Id(), gc.Equals, instance.Id("Bruce Sterling"))
   372  	c.Assert(result.DisplayName, gc.Equals, "example.com.")
   373  }
   374  
   375  func (suite *maasEnvironSuite) TestStartInstanceReturnsHostnameAsDisplayName(c *gc.C) {
   376  	machine := &fakeMachine{
   377  		systemID:     "Bruce Sterling",
   378  		architecture: arch.HostArch(),
   379  		hostname:     "mirrorshades.author",
   380  		Stub:         &testing.Stub{},
   381  		statusName:   "",
   382  	}
   383  	env, _ := suite.injectControllerWithMachine(c, machine, nil, gomaasapi.AllocateMachineArgs{})
   384  	params := environs.StartInstanceParams{ControllerUUID: suite.controllerUUID}
   385  	result, err := jujutesting.StartInstanceWithParams(env, suite.callCtx, "0", params)
   386  	c.Assert(err, jc.ErrorIsNil)
   387  	c.Assert(result.Instance.Id(), gc.Equals, instance.Id("Bruce Sterling"))
   388  	c.Assert(result.DisplayName, gc.Equals, machine.Hostname())
   389  }
   390  
   391  func (suite *maasEnvironSuite) TestStartInstanceReturnsFQDNAsDisplayNameWhenHostnameUnavailable(c *gc.C) {
   392  	machine := &fakeMachine{
   393  		systemID:     "Bruce Sterling",
   394  		architecture: arch.HostArch(),
   395  		hostname:     "",
   396  		Stub:         &testing.Stub{},
   397  		statusName:   "",
   398  	}
   399  	env, _ := suite.injectControllerWithMachine(c, machine, nil, gomaasapi.AllocateMachineArgs{})
   400  	params := environs.StartInstanceParams{ControllerUUID: suite.controllerUUID}
   401  	result, err := jujutesting.StartInstanceWithParams(env, suite.callCtx, "0", params)
   402  	c.Assert(err, jc.ErrorIsNil)
   403  	c.Assert(result.Instance.Id(), gc.Equals, instance.Id("Bruce Sterling"))
   404  	c.Assert(result.DisplayName, gc.Equals, machine.FQDN())
   405  }
   406  
   407  func (suite *maasEnvironSuite) TestStartInstanceAppliesResourceTags(c *gc.C) {
   408  	env, controller := suite.injectControllerWithSpacesAndCheck(c, nil, gomaasapi.AllocateMachineArgs{})
   409  	config := env.Config()
   410  	_, ok := config.ResourceTags()
   411  	c.Assert(ok, jc.IsTrue)
   412  	params := environs.StartInstanceParams{ControllerUUID: suite.controllerUUID}
   413  	_, err := jujutesting.StartInstanceWithParams(env, suite.callCtx, "1", params)
   414  	c.Assert(err, jc.ErrorIsNil)
   415  
   416  	machine := controller.allocateMachine.(*fakeMachine)
   417  	machine.CheckCallNames(c, "Start", "SetOwnerData")
   418  	c.Assert(machine.Calls()[1].Args[0], gc.DeepEquals, map[string]string{
   419  		"claude":            "rains",
   420  		tags.JujuController: suite.controllerUUID,
   421  		tags.JujuModel:      config.UUID(),
   422  	})
   423  }
   424  
   425  func (suite *maasEnvironSuite) TestStartInstanceParams(c *gc.C) {
   426  	var env *maasEnviron
   427  	suite.injectController(&fakeController{
   428  		allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) {
   429  			c.Assert(args, gc.DeepEquals, gomaasapi.AllocateMachineArgs{
   430  				AgentName: env.Config().UUID(),
   431  				Zone:      "foo",
   432  				MinMemory: 8192,
   433  			})
   434  		},
   435  		allocateMachine: newFakeMachine("Bruce Sterling", arch.HostArch(), ""),
   436  		allocateMachineMatches: gomaasapi.ConstraintMatches{
   437  			Storage: map[string][]gomaasapi.StorageDevice{},
   438  		},
   439  		zones: []gomaasapi.Zone{&fakeZone{name: "foo"}},
   440  	})
   441  	suite.setupFakeTools(c)
   442  	env = suite.makeEnviron(c, nil)
   443  	params := environs.StartInstanceParams{
   444  		ControllerUUID:   suite.controllerUUID,
   445  		AvailabilityZone: "foo",
   446  		Constraints:      constraints.MustParse("mem=8G"),
   447  	}
   448  	result, err := jujutesting.StartInstanceWithParams(env, suite.callCtx, "1", params)
   449  	c.Assert(err, jc.ErrorIsNil)
   450  	c.Assert(result.Instance.Id(), gc.Equals, instance.Id("Bruce Sterling"))
   451  }
   452  
   453  func (suite *maasEnvironSuite) TestAcquireNodePassedAgentName(c *gc.C) {
   454  	var env *maasEnviron
   455  	suite.injectController(&fakeController{
   456  		allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) {
   457  			c.Assert(args, gc.DeepEquals, gomaasapi.AllocateMachineArgs{
   458  				AgentName: env.Config().UUID()})
   459  		},
   460  		allocateMachine: &fakeMachine{
   461  			systemID:     "Bruce Sterling",
   462  			architecture: arch.HostArch(),
   463  		},
   464  	})
   465  	suite.setupFakeTools(c)
   466  	env = suite.makeEnviron(c, nil)
   467  
   468  	_, err := env.acquireNode(suite.callCtx, "", "", "", constraints.Value{}, nil, nil, nil)
   469  
   470  	c.Check(err, jc.ErrorIsNil)
   471  }
   472  
   473  func (suite *maasEnvironSuite) TestAcquireNodePassesPositiveAndNegativeTags(c *gc.C) {
   474  	var env *maasEnviron
   475  	expected := gomaasapi.AllocateMachineArgs{
   476  		Tags:    []string{"tag1", "tag3"},
   477  		NotTags: []string{"tag2", "tag4"},
   478  	}
   479  	env, _ = suite.injectControllerWithSpacesAndCheck(c, nil, expected)
   480  	_, err := env.acquireNode(suite.callCtx,
   481  		"", "", "",
   482  		constraints.Value{Tags: stringslicep("tag1", "^tag2", "tag3", "^tag4")},
   483  		nil, nil, nil,
   484  	)
   485  	c.Check(err, jc.ErrorIsNil)
   486  }
   487  
   488  func getFourSpaces() []gomaasapi.Space {
   489  	return []gomaasapi.Space{
   490  		fakeSpace{
   491  			name:    "space-1",
   492  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24"}},
   493  			id:      5,
   494  		},
   495  		fakeSpace{
   496  			name:    "space-2",
   497  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 100, vlan: fakeVLAN{vid: 66}, cidr: "192.168.11.0/24"}},
   498  			id:      6,
   499  		},
   500  		fakeSpace{
   501  			name:    "space-3",
   502  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 101, vlan: fakeVLAN{vid: 66}, cidr: "192.168.12.0/24"}},
   503  			id:      7,
   504  		},
   505  		fakeSpace{
   506  			name:    "space-4",
   507  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 102, vlan: fakeVLAN{vid: 66}, cidr: "192.168.13.0/24"}},
   508  			id:      8,
   509  		},
   510  	}
   511  }
   512  
   513  func (suite *maasEnvironSuite) TestAcquireNodePassesPositiveAndNegativeSpaces(c *gc.C) {
   514  	expected := gomaasapi.AllocateMachineArgs{
   515  		NotSpace: []string{"6", "8"},
   516  		Interfaces: []gomaasapi.InterfaceSpec{
   517  			{Label: "5", Space: "5"},
   518  			{Label: "7", Space: "7"},
   519  		},
   520  	}
   521  	env, _ := suite.injectControllerWithSpacesAndCheck(c, getFourSpaces(), expected)
   522  
   523  	cons := constraints.Value{Spaces: stringslicep("space-1", "^space-2", "space-3", "^space-4")}
   524  	positiveSpaceIDs, negativeSpaceIDs, err := env.networkSpaceRequirements(suite.callCtx, nil, cons)
   525  	c.Check(err, jc.ErrorIsNil)
   526  
   527  	_, err = env.acquireNode(suite.callCtx,
   528  		"", "", "",
   529  		cons,
   530  		positiveSpaceIDs, negativeSpaceIDs,
   531  		nil,
   532  	)
   533  	c.Check(err, jc.ErrorIsNil)
   534  }
   535  
   536  func (suite *maasEnvironSuite) TestAcquireNodeStorage(c *gc.C) {
   537  	var env *maasEnviron
   538  	var getStorage func() []gomaasapi.StorageSpec
   539  	suite.injectController(&fakeController{
   540  		allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) {
   541  			c.Assert(args, jc.DeepEquals, gomaasapi.AllocateMachineArgs{
   542  				AgentName: env.Config().UUID(),
   543  				Storage:   getStorage(),
   544  			})
   545  		},
   546  		allocateMachine: &fakeMachine{
   547  			systemID:     "Bruce Sterling",
   548  			architecture: arch.HostArch(),
   549  		},
   550  	})
   551  	suite.setupFakeTools(c)
   552  	for i, test := range []struct {
   553  		volumes  []volumeInfo
   554  		expected []gomaasapi.StorageSpec
   555  	}{{
   556  		volumes:  nil,
   557  		expected: []gomaasapi.StorageSpec{},
   558  	}, {
   559  		volumes:  []volumeInfo{{"volume-1", 1234, nil}},
   560  		expected: []gomaasapi.StorageSpec{{"volume-1", 1234, nil}},
   561  	}, {
   562  		volumes:  []volumeInfo{{"", 1234, []string{"tag1", "tag2"}}},
   563  		expected: []gomaasapi.StorageSpec{{"", 1234, []string{"tag1", "tag2"}}},
   564  	}, {
   565  		volumes:  []volumeInfo{{"volume-1", 1234, []string{"tag1", "tag2"}}},
   566  		expected: []gomaasapi.StorageSpec{{"volume-1", 1234, []string{"tag1", "tag2"}}},
   567  	}, {
   568  		volumes: []volumeInfo{
   569  			{"volume-1", 1234, []string{"tag1", "tag2"}},
   570  			{"volume-2", 4567, []string{"tag1", "tag3"}},
   571  		},
   572  		expected: []gomaasapi.StorageSpec{
   573  			{"volume-1", 1234, []string{"tag1", "tag2"}},
   574  			{"volume-2", 4567, []string{"tag1", "tag3"}},
   575  		},
   576  	}} {
   577  		c.Logf("test #%d: volumes=%v", i, test.volumes)
   578  		getStorage = func() []gomaasapi.StorageSpec {
   579  			return test.expected
   580  		}
   581  		env = suite.makeEnviron(c, nil)
   582  		_, err := env.acquireNode(suite.callCtx, "", "", "", constraints.Value{}, nil, nil, test.volumes)
   583  		c.Check(err, jc.ErrorIsNil)
   584  	}
   585  }
   586  
   587  func (suite *maasEnvironSuite) TestAcquireNodeInterfaces(c *gc.C) {
   588  	var env *maasEnviron
   589  	var getNegatives func() []string
   590  	var getPositives func() []gomaasapi.InterfaceSpec
   591  	suite.injectController(&fakeController{
   592  		allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) {
   593  			c.Assert(args, gc.DeepEquals, gomaasapi.AllocateMachineArgs{
   594  				AgentName:  env.Config().UUID(),
   595  				Interfaces: getPositives(),
   596  				NotSpace:   getNegatives(),
   597  			})
   598  		},
   599  		allocateMachine: &fakeMachine{
   600  			systemID:     "Bruce Sterling",
   601  			architecture: arch.HostArch(),
   602  		},
   603  		spaces: getTwoSpaces(),
   604  	})
   605  	suite.setupFakeTools(c)
   606  
   607  	// Add some constraints, including spaces to verify specified bindings
   608  	// always override any spaces constraints.
   609  	cons := constraints.Value{
   610  		Spaces: stringslicep("foo", "^bar"),
   611  	}
   612  	// In the tests below Space 2 means foo, Space 3 means bar.
   613  	for i, test := range []struct {
   614  		descr             string
   615  		endpointBindings  map[string]network.Id
   616  		expectedPositives []gomaasapi.InterfaceSpec
   617  		expectedNegatives []string
   618  		expectedError     string
   619  	}{{
   620  		descr:             "no bindings and space constraints",
   621  		expectedPositives: []gomaasapi.InterfaceSpec{{Label: "2", Space: "2"}},
   622  		expectedNegatives: []string{"3"},
   623  		expectedError:     "",
   624  	}, {
   625  		descr:            "bindings and no space constraints",
   626  		endpointBindings: map[string]network.Id{"name-1": "space-1"},
   627  		expectedPositives: []gomaasapi.InterfaceSpec{
   628  			{Label: "2", Space: "2"},
   629  			{Label: "space-1", Space: "space-1"},
   630  		},
   631  		expectedNegatives: []string{"3"},
   632  	}, {
   633  		descr: "bindings (to the same provider space ID) and space constraints",
   634  		endpointBindings: map[string]network.Id{
   635  			"":         "999", // we should get a NIC in this space even if none of the endpoints are bound to it
   636  			"name-1":   "1",
   637  			"name-2":   "1",
   638  			"name-3":   "2",
   639  			"name-4":   "42",
   640  			"to-alpha": network.AlphaSpaceName, // alpha space is not present on maas and is skipped
   641  		},
   642  		expectedPositives: []gomaasapi.InterfaceSpec{
   643  			{Label: "1", Space: "1"},
   644  			{Label: "2", Space: "2"},
   645  			{Label: "42", Space: "42"},
   646  			{Label: "999", Space: "999"},
   647  		},
   648  		expectedNegatives: []string{"3"},
   649  	}} {
   650  		c.Logf("test #%d: %s", i, test.descr)
   651  
   652  		env = suite.makeEnviron(c, nil)
   653  		getNegatives = func() []string {
   654  			return test.expectedNegatives
   655  		}
   656  		getPositives = func() []gomaasapi.InterfaceSpec {
   657  			return test.expectedPositives
   658  		}
   659  
   660  		positiveSpaceIDs, negativeSpaceIDs, err := env.networkSpaceRequirements(suite.callCtx, test.endpointBindings, cons)
   661  		c.Check(err, jc.ErrorIsNil)
   662  
   663  		_, err = env.acquireNode(suite.callCtx, "", "", "", cons, positiveSpaceIDs, negativeSpaceIDs, nil)
   664  		if test.expectedError != "" {
   665  			c.Check(err, gc.ErrorMatches, test.expectedError)
   666  			c.Check(err, jc.Satisfies, errors.IsNotValid)
   667  			continue
   668  		}
   669  		c.Check(err, jc.ErrorIsNil)
   670  	}
   671  }
   672  
   673  func getTwoSpaces() []gomaasapi.Space {
   674  	return []gomaasapi.Space{
   675  		fakeSpace{
   676  			name:    "foo",
   677  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24"}},
   678  			id:      2,
   679  		},
   680  		fakeSpace{
   681  			name:    "bar",
   682  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 100, vlan: fakeVLAN{vid: 66}, cidr: "192.168.11.0/24"}},
   683  			id:      3,
   684  		},
   685  	}
   686  }
   687  
   688  func (suite *maasEnvironSuite) TestWaitForNodeDeploymentError(c *gc.C) {
   689  	machine := newFakeMachine("Bruce Sterling", arch.HostArch(), "")
   690  	controller := newFakeController()
   691  	controller.allocateMachine = machine
   692  	controller.allocateMachineMatches = gomaasapi.ConstraintMatches{
   693  		Storage: map[string][]gomaasapi.StorageDevice{},
   694  	}
   695  	controller.machines = []gomaasapi.Machine{machine}
   696  	suite.injectController(controller)
   697  	suite.setupFakeTools(c)
   698  	env := suite.makeEnviron(c, nil)
   699  	err := bootstrap.Bootstrap(envtesting.BootstrapTODOContext(c), env,
   700  		suite.callCtx, bootstrap.BootstrapParams{
   701  			ControllerConfig:        coretesting.FakeControllerConfig(),
   702  			AdminSecret:             jujutesting.AdminSecret,
   703  			CAPrivateKey:            coretesting.CAKey,
   704  			SupportedBootstrapBases: coretesting.FakeSupportedJujuBases,
   705  			DialOpts: environs.BootstrapDialOpts{
   706  				Timeout: coretesting.LongWait,
   707  			},
   708  		})
   709  	c.Assert(err, gc.ErrorMatches, "bootstrap instance started but did not change to Deployed state.*")
   710  }
   711  
   712  func (suite *maasEnvironSuite) TestWaitForNodeDeploymentRetry(c *gc.C) {
   713  	machine := newFakeMachine("Inaccessible machine", arch.HostArch(), "")
   714  	controller := newFakeController()
   715  	controller.allocateMachine = machine
   716  	controller.allocateMachineMatches = gomaasapi.ConstraintMatches{
   717  		Storage: map[string][]gomaasapi.StorageDevice{},
   718  	}
   719  	controller.machines = []gomaasapi.Machine{}
   720  	suite.injectController(controller)
   721  	suite.setupFakeTools(c)
   722  	env := suite.makeEnviron(c, nil)
   723  	bootstrap.Bootstrap(envtesting.BootstrapTODOContext(c), env,
   724  		suite.callCtx, bootstrap.BootstrapParams{
   725  			ControllerConfig:        coretesting.FakeControllerConfig(),
   726  			AdminSecret:             jujutesting.AdminSecret,
   727  			CAPrivateKey:            coretesting.CAKey,
   728  			SupportedBootstrapBases: coretesting.FakeSupportedJujuBases,
   729  			DialOpts: environs.BootstrapDialOpts{
   730  				Timeout: coretesting.LongWait,
   731  			},
   732  		})
   733  	c.Check(c.GetTestLog(), jc.Contains, "WARNING juju.provider.maas failed to get instance from provider attempt")
   734  }
   735  
   736  func (suite *maasEnvironSuite) TestWaitForNodeDeploymentSucceeds(c *gc.C) {
   737  	machine := newFakeMachine("Bruce Sterling", arch.HostArch(), "Deployed")
   738  	controller := newFakeController()
   739  	controller.allocateMachine = machine
   740  	controller.allocateMachineMatches = gomaasapi.ConstraintMatches{
   741  		Storage: map[string][]gomaasapi.StorageDevice{},
   742  	}
   743  	controller.machines = []gomaasapi.Machine{machine}
   744  	suite.injectController(controller)
   745  	suite.setupFakeTools(c)
   746  	env := suite.makeEnviron(c, nil)
   747  	err := bootstrap.Bootstrap(envtesting.BootstrapTODOContext(c), env,
   748  		suite.callCtx, bootstrap.BootstrapParams{
   749  			ControllerConfig:        coretesting.FakeControllerConfig(),
   750  			AdminSecret:             jujutesting.AdminSecret,
   751  			CAPrivateKey:            coretesting.CAKey,
   752  			SupportedBootstrapBases: coretesting.FakeSupportedJujuBases,
   753  			DialOpts: environs.BootstrapDialOpts{
   754  				Timeout: coretesting.LongWait,
   755  			},
   756  		})
   757  	c.Assert(err, jc.ErrorIsNil)
   758  }
   759  
   760  func (suite *maasEnvironSuite) TestSubnetsNoFilters(c *gc.C) {
   761  	suite.injectController(&fakeController{
   762  		spaces: getFourSpaces(),
   763  	})
   764  	env := suite.makeEnviron(c, nil)
   765  	subnets, err := env.Subnets(suite.callCtx, "", nil)
   766  	c.Assert(err, jc.ErrorIsNil)
   767  	expected := []network.SubnetInfo{
   768  		{CIDR: "192.168.10.0/24", ProviderId: "99", VLANTag: 66, ProviderSpaceId: "5"},
   769  		{CIDR: "192.168.11.0/24", ProviderId: "100", VLANTag: 66, ProviderSpaceId: "6"},
   770  		{CIDR: "192.168.12.0/24", ProviderId: "101", VLANTag: 66, ProviderSpaceId: "7"},
   771  		{CIDR: "192.168.13.0/24", ProviderId: "102", VLANTag: 66, ProviderSpaceId: "8"},
   772  	}
   773  	c.Assert(subnets, jc.DeepEquals, expected)
   774  }
   775  
   776  func (suite *maasEnvironSuite) TestSubnetsNoFiltersError(c *gc.C) {
   777  	suite.injectController(&fakeController{
   778  		spacesError: errors.New("bang"),
   779  	})
   780  	env := suite.makeEnviron(c, nil)
   781  	_, err := env.Subnets(suite.callCtx, "", nil)
   782  	c.Assert(err, gc.ErrorMatches, "bang")
   783  }
   784  
   785  func (suite *maasEnvironSuite) TestSubnetsSubnetIds(c *gc.C) {
   786  	suite.injectController(&fakeController{
   787  		spaces: getFourSpaces(),
   788  	})
   789  	env := suite.makeEnviron(c, nil)
   790  	subnets, err := env.Subnets(suite.callCtx, "", []network.Id{"99", "100"})
   791  	c.Assert(err, jc.ErrorIsNil)
   792  	expected := []network.SubnetInfo{
   793  		{CIDR: "192.168.10.0/24", ProviderId: "99", VLANTag: 66, ProviderSpaceId: "5"},
   794  		{CIDR: "192.168.11.0/24", ProviderId: "100", VLANTag: 66, ProviderSpaceId: "6"},
   795  	}
   796  	c.Assert(subnets, jc.DeepEquals, expected)
   797  }
   798  
   799  func (suite *maasEnvironSuite) TestSubnetsSubnetIdsMissing(c *gc.C) {
   800  	suite.injectController(&fakeController{
   801  		spaces: getFourSpaces(),
   802  	})
   803  	env := suite.makeEnviron(c, nil)
   804  	_, err := env.Subnets(suite.callCtx, "", []network.Id{"99", "missing"})
   805  	msg := "failed to find the following subnets: missing"
   806  	c.Assert(err, gc.ErrorMatches, msg)
   807  }
   808  
   809  func (suite *maasEnvironSuite) TestSubnetsInstIdNotFound(c *gc.C) {
   810  	suite.injectController(&fakeController{})
   811  	env := suite.makeEnviron(c, nil)
   812  	_, err := env.Subnets(suite.callCtx, "foo", nil)
   813  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   814  }
   815  
   816  func (suite *maasEnvironSuite) TestSubnetsInstId(c *gc.C) {
   817  	interfaces := []gomaasapi.Interface{
   818  		&fakeInterface{
   819  			links: []gomaasapi.Link{
   820  				&fakeLink{subnet: fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24", space: "space-1"}},
   821  				&fakeLink{subnet: fakeSubnet{id: 100, vlan: fakeVLAN{vid: 0}, cidr: "192.168.11.0/24", space: "space-2"}},
   822  			},
   823  		},
   824  		&fakeInterface{
   825  			links: []gomaasapi.Link{
   826  				&fakeLink{subnet: fakeSubnet{id: 101, vlan: fakeVLAN{vid: 2}, cidr: "192.168.12.0/24", space: "space-3"}},
   827  			},
   828  		},
   829  	}
   830  	machine := &fakeMachine{
   831  		systemID:     "William Gibson",
   832  		interfaceSet: interfaces,
   833  	}
   834  	machine2 := &fakeMachine{systemID: "Bruce Sterling"}
   835  	suite.injectController(&fakeController{
   836  		machines: []gomaasapi.Machine{machine, machine2},
   837  		spaces:   getFourSpaces(),
   838  	})
   839  	env := suite.makeEnviron(c, nil)
   840  	subnets, err := env.Subnets(suite.callCtx, "William Gibson", nil)
   841  	c.Assert(err, jc.ErrorIsNil)
   842  	expected := []network.SubnetInfo{
   843  		{CIDR: "192.168.10.0/24", ProviderId: "99", VLANTag: 66, ProviderSpaceId: "5"},
   844  		{CIDR: "192.168.11.0/24", ProviderId: "100", VLANTag: 0, ProviderSpaceId: "6"},
   845  		{CIDR: "192.168.12.0/24", ProviderId: "101", VLANTag: 2, ProviderSpaceId: "7"},
   846  	}
   847  	c.Assert(subnets, jc.DeepEquals, expected)
   848  }
   849  
   850  func (suite *maasEnvironSuite) TestStartInstanceNetworkInterfaces(c *gc.C) {
   851  	vlan0 := fakeVLAN{
   852  		id:  5001,
   853  		vid: 0,
   854  		mtu: 1500,
   855  	}
   856  
   857  	vlan50 := fakeVLAN{
   858  		id:  5004,
   859  		vid: 50,
   860  		mtu: 1500,
   861  	}
   862  
   863  	subnetPXE := fakeSubnet{
   864  		id:         3,
   865  		space:      "default",
   866  		vlan:       vlan0,
   867  		gateway:    "10.20.19.2",
   868  		cidr:       "10.20.19.0/24",
   869  		dnsServers: []string{"10.20.19.2", "10.20.19.3"},
   870  	}
   871  
   872  	exampleInterfaces := []gomaasapi.Interface{
   873  		&fakeInterface{
   874  			id:         91,
   875  			name:       "eth0",
   876  			type_:      "physical",
   877  			enabled:    true,
   878  			macAddress: "52:54:00:70:9b:fe",
   879  			vlan:       vlan0,
   880  			links: []gomaasapi.Link{
   881  				&fakeLink{
   882  					id:        436,
   883  					subnet:    &subnetPXE,
   884  					ipAddress: "10.20.19.103",
   885  					mode:      "static",
   886  				},
   887  				&fakeLink{
   888  					id:        437,
   889  					subnet:    &subnetPXE,
   890  					ipAddress: "10.20.19.104",
   891  					mode:      "static",
   892  				},
   893  			},
   894  			parents:  []string{},
   895  			children: []string{"eth0.100", "eth0.250", "eth0.50"},
   896  		},
   897  		&fakeInterface{
   898  			id:         150,
   899  			name:       "eth0.50",
   900  			type_:      "vlan",
   901  			enabled:    true,
   902  			macAddress: "52:54:00:70:9b:fe",
   903  			vlan:       vlan50,
   904  			links: []gomaasapi.Link{
   905  				&fakeLink{
   906  					id: 517,
   907  					subnet: &fakeSubnet{
   908  						id:         5,
   909  						space:      "admin",
   910  						vlan:       vlan50,
   911  						gateway:    "10.50.19.2",
   912  						cidr:       "10.50.19.0/24",
   913  						dnsServers: []string{},
   914  					},
   915  					ipAddress: "10.50.19.103",
   916  					mode:      "static",
   917  				},
   918  			},
   919  			parents:  []string{"eth0"},
   920  			children: []string{},
   921  		},
   922  	}
   923  	var env *maasEnviron
   924  	machine := newFakeMachine("Bruce Sterling", arch.HostArch(), "")
   925  	machine.interfaceSet = exampleInterfaces
   926  	controller := &fakeController{
   927  		allocateMachine: machine,
   928  		allocateMachineMatches: gomaasapi.ConstraintMatches{
   929  			Storage: map[string][]gomaasapi.StorageDevice{},
   930  		},
   931  	}
   932  	suite.injectController(controller)
   933  	suite.setupFakeTools(c)
   934  	env = suite.makeEnviron(c, nil)
   935  
   936  	params := environs.StartInstanceParams{ControllerUUID: suite.controllerUUID}
   937  	result, err := jujutesting.StartInstanceWithParams(env, suite.callCtx, "1", params)
   938  	c.Assert(err, jc.ErrorIsNil)
   939  	expected := network.InterfaceInfos{{
   940  		DeviceIndex:       0,
   941  		MACAddress:        "52:54:00:70:9b:fe",
   942  		ProviderId:        "91",
   943  		ProviderSubnetId:  "3",
   944  		AvailabilityZones: nil,
   945  		VLANTag:           0,
   946  		ProviderVLANId:    "5001",
   947  		ProviderAddressId: "436",
   948  		InterfaceName:     "eth0",
   949  		InterfaceType:     "ethernet",
   950  		Disabled:          false,
   951  		NoAutoStart:       false,
   952  		Addresses: network.ProviderAddresses{
   953  			network.NewMachineAddress(
   954  				"10.20.19.103", network.WithCIDR("10.20.19.0/24"), network.WithConfigType(network.ConfigStatic),
   955  			).AsProviderAddress(network.WithSpaceName("default")),
   956  		},
   957  		DNSServers: network.NewMachineAddresses([]string{
   958  			"10.20.19.2",
   959  			"10.20.19.3",
   960  		}).AsProviderAddresses(network.WithSpaceName("default")),
   961  		DNSSearchDomains: nil,
   962  		MTU:              1500,
   963  		GatewayAddress:   network.NewMachineAddress("10.20.19.2").AsProviderAddress(network.WithSpaceName("default")),
   964  		Origin:           network.OriginProvider,
   965  	}, {
   966  		DeviceIndex:       0,
   967  		MACAddress:        "52:54:00:70:9b:fe",
   968  		ProviderId:        "91",
   969  		ProviderSubnetId:  "3",
   970  		AvailabilityZones: nil,
   971  		VLANTag:           0,
   972  		ProviderVLANId:    "5001",
   973  		ProviderAddressId: "437",
   974  		InterfaceName:     "eth0",
   975  		InterfaceType:     "ethernet",
   976  		Disabled:          false,
   977  		NoAutoStart:       false,
   978  		Addresses: network.ProviderAddresses{
   979  			network.NewMachineAddress(
   980  				"10.20.19.104", network.WithCIDR("10.20.19.0/24"), network.WithConfigType(network.ConfigStatic),
   981  			).AsProviderAddress(network.WithSpaceName("default")),
   982  		},
   983  		DNSServers: network.NewMachineAddresses([]string{
   984  			"10.20.19.2",
   985  			"10.20.19.3",
   986  		}).AsProviderAddresses(network.WithSpaceName("default")),
   987  		DNSSearchDomains: nil,
   988  		MTU:              1500,
   989  		GatewayAddress:   network.NewMachineAddress("10.20.19.2").AsProviderAddress(network.WithSpaceName("default")),
   990  		Origin:           network.OriginProvider,
   991  	}, {
   992  		DeviceIndex:         1,
   993  		MACAddress:          "52:54:00:70:9b:fe",
   994  		ProviderId:          "150",
   995  		ProviderSubnetId:    "5",
   996  		AvailabilityZones:   nil,
   997  		VLANTag:             50,
   998  		ProviderVLANId:      "5004",
   999  		ProviderAddressId:   "517",
  1000  		InterfaceName:       "eth0.50",
  1001  		ParentInterfaceName: "eth0",
  1002  		InterfaceType:       "802.1q",
  1003  		Disabled:            false,
  1004  		NoAutoStart:         false,
  1005  		Addresses: network.ProviderAddresses{
  1006  			network.NewMachineAddress(
  1007  				"10.50.19.103", network.WithCIDR("10.50.19.0/24"), network.WithConfigType(network.ConfigStatic),
  1008  			).AsProviderAddress(network.WithSpaceName("admin")),
  1009  		},
  1010  		DNSServers:       nil,
  1011  		DNSSearchDomains: nil,
  1012  		MTU:              1500,
  1013  		GatewayAddress:   network.NewMachineAddress("10.50.19.2").AsProviderAddress(network.WithSpaceName("admin")),
  1014  		Origin:           network.OriginProvider,
  1015  	}}
  1016  	c.Assert(result.NetworkInfo, jc.DeepEquals, expected)
  1017  }
  1018  
  1019  func (suite *maasEnvironSuite) TestAllocateContainerAddressesSingleNic(c *gc.C) {
  1020  	vlan1 := fakeVLAN{
  1021  		id:  5001,
  1022  		mtu: 1500,
  1023  	}
  1024  	vlan2 := fakeVLAN{
  1025  		id:  5002,
  1026  		mtu: 1500,
  1027  	}
  1028  	subnet1 := fakeSubnet{
  1029  		id:         3,
  1030  		space:      "default",
  1031  		vlan:       vlan1,
  1032  		gateway:    "10.20.19.2",
  1033  		cidr:       "10.20.19.0/24",
  1034  		dnsServers: []string{"10.20.19.2", "10.20.19.3"},
  1035  	}
  1036  	subnet2 := fakeSubnet{
  1037  		id:         4,
  1038  		space:      "freckles",
  1039  		vlan:       vlan2,
  1040  		gateway:    "192.168.1.1",
  1041  		cidr:       "192.168.1.0/24",
  1042  		dnsServers: []string{"10.20.19.2", "10.20.19.3"},
  1043  	}
  1044  	staticRoute2to1 := fakeStaticRoute{
  1045  		id:          1,
  1046  		source:      subnet2,
  1047  		destination: subnet1,
  1048  		gatewayIP:   "192.168.1.1",
  1049  		metric:      100,
  1050  	}
  1051  
  1052  	interfaces := []gomaasapi.Interface{
  1053  		&fakeInterface{
  1054  			id:         91,
  1055  			name:       "eth0",
  1056  			type_:      "physical",
  1057  			enabled:    true,
  1058  			macAddress: "52:54:00:70:9b:fe",
  1059  			vlan:       vlan1,
  1060  			links: []gomaasapi.Link{
  1061  				&fakeLink{
  1062  					id:        436,
  1063  					subnet:    &subnet1,
  1064  					ipAddress: "10.20.19.103",
  1065  					mode:      "static",
  1066  				},
  1067  			},
  1068  			parents:  []string{},
  1069  			children: []string{"eth0.100", "eth0.250", "eth0.50"},
  1070  		},
  1071  	}
  1072  	deviceInterfaces := []gomaasapi.Interface{
  1073  		&fakeInterface{
  1074  			id:         93,
  1075  			name:       "eth1",
  1076  			type_:      "physical",
  1077  			enabled:    true,
  1078  			macAddress: "53:54:00:70:9b:ff",
  1079  			vlan:       vlan2,
  1080  			links: []gomaasapi.Link{
  1081  				&fakeLink{
  1082  					id:        480,
  1083  					subnet:    &subnet2,
  1084  					ipAddress: "192.168.1.127",
  1085  					mode:      "static",
  1086  				},
  1087  			},
  1088  			parents:  []string{},
  1089  			children: []string{"eth0.100", "eth0.250", "eth0.50"},
  1090  		},
  1091  	}
  1092  	var env *maasEnviron
  1093  	device := &fakeDevice{
  1094  		interfaceSet: deviceInterfaces,
  1095  		systemID:     "foo",
  1096  	}
  1097  	controller := &fakeController{
  1098  		Stub: &testing.Stub{},
  1099  		machines: []gomaasapi.Machine{&fakeMachine{
  1100  			Stub:         &testing.Stub{},
  1101  			systemID:     "1",
  1102  			architecture: arch.HostArch(),
  1103  			interfaceSet: interfaces,
  1104  			createDevice: device,
  1105  		}},
  1106  		spaces: []gomaasapi.Space{
  1107  			fakeSpace{
  1108  				name:    "freckles",
  1109  				id:      4567,
  1110  				subnets: []gomaasapi.Subnet{subnet1, subnet2},
  1111  			},
  1112  		},
  1113  		devices:      []gomaasapi.Device{device},
  1114  		staticRoutes: []gomaasapi.StaticRoute{staticRoute2to1},
  1115  	}
  1116  	suite.injectController(controller)
  1117  	suite.setupFakeTools(c)
  1118  	env = suite.makeEnviron(c, nil)
  1119  
  1120  	prepared := network.InterfaceInfos{{
  1121  		MACAddress:    "52:54:00:70:9b:fe",
  1122  		Addresses:     network.ProviderAddresses{network.NewMachineAddress("", network.WithCIDR("10.20.19.0/24")).AsProviderAddress()},
  1123  		InterfaceName: "eth0",
  1124  	}}
  1125  	ignored := names.NewMachineTag("1/lxd/0")
  1126  	result, err := env.AllocateContainerAddresses(suite.callCtx, "1", ignored, prepared)
  1127  	c.Assert(err, jc.ErrorIsNil)
  1128  	expected := network.InterfaceInfos{{
  1129  		DeviceIndex:       0,
  1130  		MACAddress:        "53:54:00:70:9b:ff",
  1131  		ProviderId:        "93",
  1132  		ProviderSubnetId:  "4",
  1133  		VLANTag:           0,
  1134  		ProviderVLANId:    "5002",
  1135  		ProviderAddressId: "480",
  1136  		InterfaceName:     "eth1",
  1137  		InterfaceType:     "ethernet",
  1138  		Addresses: network.ProviderAddresses{
  1139  			network.NewMachineAddress(
  1140  				"192.168.1.127", network.WithCIDR("192.168.1.0/24"), network.WithConfigType(network.ConfigStatic),
  1141  			).AsProviderAddress(network.WithSpaceName("freckles")),
  1142  		},
  1143  		DNSServers: network.NewMachineAddresses([]string{
  1144  			"10.20.19.2",
  1145  			"10.20.19.3",
  1146  		}).AsProviderAddresses(network.WithSpaceName("freckles")),
  1147  		MTU:            1500,
  1148  		GatewayAddress: network.NewMachineAddress("192.168.1.1").AsProviderAddress(network.WithSpaceName("freckles")),
  1149  		Routes: []network.Route{{
  1150  			DestinationCIDR: subnet1.CIDR(),
  1151  			GatewayIP:       "192.168.1.1",
  1152  			Metric:          100,
  1153  		}},
  1154  		Origin: network.OriginProvider,
  1155  	}}
  1156  	c.Assert(result, jc.DeepEquals, expected)
  1157  }
  1158  
  1159  func (suite *maasEnvironSuite) TestAllocateContainerAddressesSingleNicWithNoVLAN(c *gc.C) {
  1160  	vlan1 := fakeVLAN{
  1161  		id:  5001,
  1162  		mtu: 1500,
  1163  	}
  1164  	vlan2 := fakeVLAN{
  1165  		id:  5002,
  1166  		mtu: 1500,
  1167  	}
  1168  	subnet1 := fakeSubnet{
  1169  		id:         3,
  1170  		space:      "default",
  1171  		vlan:       vlan1,
  1172  		gateway:    "10.20.19.2",
  1173  		cidr:       "10.20.19.0/24",
  1174  		dnsServers: []string{"10.20.19.2", "10.20.19.3"},
  1175  	}
  1176  	subnet2 := fakeSubnet{
  1177  		id:         4,
  1178  		space:      "freckles",
  1179  		vlan:       vlan2,
  1180  		gateway:    "192.168.1.1",
  1181  		cidr:       "192.168.1.0/24",
  1182  		dnsServers: []string{"10.20.19.2", "10.20.19.3"},
  1183  	}
  1184  	staticRoute2to1 := fakeStaticRoute{
  1185  		id:          1,
  1186  		source:      subnet2,
  1187  		destination: subnet1,
  1188  		gatewayIP:   "192.168.1.1",
  1189  		metric:      100,
  1190  	}
  1191  
  1192  	interfaces := []gomaasapi.Interface{
  1193  		&fakeInterface{
  1194  			id:         91,
  1195  			name:       "eth0",
  1196  			type_:      "physical",
  1197  			enabled:    true,
  1198  			macAddress: "52:54:00:70:9b:fe",
  1199  			vlan:       vlan1,
  1200  			links: []gomaasapi.Link{
  1201  				&fakeLink{
  1202  					id:        436,
  1203  					subnet:    &subnet1,
  1204  					ipAddress: "10.20.19.103",
  1205  					mode:      "static",
  1206  				},
  1207  			},
  1208  			parents:  []string{},
  1209  			children: []string{"eth0.100", "eth0.250", "eth0.50"},
  1210  		},
  1211  	}
  1212  	deviceInterfaces := []gomaasapi.Interface{
  1213  		&fakeInterface{
  1214  			id:         93,
  1215  			name:       "eth1",
  1216  			type_:      "physical",
  1217  			enabled:    true,
  1218  			macAddress: "53:54:00:70:9b:ff",
  1219  			links: []gomaasapi.Link{
  1220  				&fakeLink{
  1221  					id:        480,
  1222  					subnet:    &subnet2,
  1223  					ipAddress: "192.168.1.127",
  1224  					mode:      "static",
  1225  				},
  1226  			},
  1227  			parents:  []string{},
  1228  			children: []string{"eth0.100", "eth0.250", "eth0.50"},
  1229  		},
  1230  	}
  1231  	var env *maasEnviron
  1232  	device := &fakeDevice{
  1233  		interfaceSet: deviceInterfaces,
  1234  		systemID:     "foo",
  1235  	}
  1236  	controller := &fakeController{
  1237  		Stub: &testing.Stub{},
  1238  		machines: []gomaasapi.Machine{&fakeMachine{
  1239  			Stub:         &testing.Stub{},
  1240  			systemID:     "1",
  1241  			architecture: arch.HostArch(),
  1242  			interfaceSet: interfaces,
  1243  			createDevice: device,
  1244  		}},
  1245  		spaces: []gomaasapi.Space{
  1246  			fakeSpace{
  1247  				name:    "freckles",
  1248  				id:      4567,
  1249  				subnets: []gomaasapi.Subnet{subnet1, subnet2},
  1250  			},
  1251  		},
  1252  		devices:      []gomaasapi.Device{device},
  1253  		staticRoutes: []gomaasapi.StaticRoute{staticRoute2to1},
  1254  	}
  1255  	suite.injectController(controller)
  1256  	suite.setupFakeTools(c)
  1257  	env = suite.makeEnviron(c, nil)
  1258  
  1259  	prepared := network.InterfaceInfos{{
  1260  		MACAddress:    "52:54:00:70:9b:fe",
  1261  		Addresses:     network.ProviderAddresses{network.NewMachineAddress("", network.WithCIDR("10.20.19.0/24")).AsProviderAddress()},
  1262  		InterfaceName: "eth0",
  1263  	}}
  1264  	ignored := names.NewMachineTag("1/lxd/0")
  1265  	result, err := env.AllocateContainerAddresses(suite.callCtx, "1", ignored, prepared)
  1266  	c.Assert(err, jc.ErrorIsNil)
  1267  	expected := network.InterfaceInfos{{
  1268  		DeviceIndex:       0,
  1269  		MACAddress:        "53:54:00:70:9b:ff",
  1270  		ProviderId:        "93",
  1271  		ProviderSubnetId:  "4",
  1272  		VLANTag:           0,
  1273  		ProviderVLANId:    "0",
  1274  		ProviderAddressId: "480",
  1275  		InterfaceName:     "eth1",
  1276  		InterfaceType:     "ethernet",
  1277  		Addresses: network.ProviderAddresses{
  1278  			network.NewMachineAddress(
  1279  				"192.168.1.127", network.WithCIDR("192.168.1.0/24"), network.WithConfigType(network.ConfigStatic),
  1280  			).AsProviderAddress(network.WithSpaceName("freckles")),
  1281  		},
  1282  		DNSServers: network.NewMachineAddresses([]string{
  1283  			"10.20.19.2",
  1284  			"10.20.19.3",
  1285  		}).AsProviderAddresses(network.WithSpaceName("freckles")),
  1286  		MTU:            1500,
  1287  		GatewayAddress: network.NewMachineAddress("192.168.1.1").AsProviderAddress(network.WithSpaceName("freckles")),
  1288  		Routes: []network.Route{{
  1289  			DestinationCIDR: subnet1.CIDR(),
  1290  			GatewayIP:       "192.168.1.1",
  1291  			Metric:          100,
  1292  		}},
  1293  		Origin: network.OriginProvider,
  1294  	}}
  1295  	c.Assert(result, jc.DeepEquals, expected)
  1296  }
  1297  
  1298  func (suite *maasEnvironSuite) TestAllocateContainerAddressesNoStaticRoutesAPI(c *gc.C) {
  1299  	// MAAS 2.0 doesn't have support for static routes, and generates an Error
  1300  	vlan1 := fakeVLAN{
  1301  		id:  5001,
  1302  		mtu: 1500,
  1303  	}
  1304  	subnet1 := fakeSubnet{
  1305  		id:         3,
  1306  		space:      "freckles",
  1307  		vlan:       vlan1,
  1308  		gateway:    "10.20.19.2",
  1309  		cidr:       "10.20.19.0/24",
  1310  		dnsServers: []string{"10.20.19.2", "10.20.19.3"},
  1311  	}
  1312  
  1313  	interfaces := []gomaasapi.Interface{
  1314  		&fakeInterface{
  1315  			id:         91,
  1316  			name:       "eth0",
  1317  			type_:      "physical",
  1318  			enabled:    true,
  1319  			macAddress: "52:54:00:70:9b:fe",
  1320  			vlan:       vlan1,
  1321  			links: []gomaasapi.Link{
  1322  				&fakeLink{
  1323  					id:        436,
  1324  					subnet:    &subnet1,
  1325  					ipAddress: "10.20.19.103",
  1326  					mode:      "static",
  1327  				},
  1328  			},
  1329  			parents:  []string{},
  1330  			children: []string{"eth0.100", "eth0.250", "eth0.50"},
  1331  		},
  1332  	}
  1333  	// This will be returned by the fakeController when we call CreateDevice
  1334  	deviceInterfaces := []gomaasapi.Interface{
  1335  		&fakeInterface{
  1336  			id:         93,
  1337  			name:       "eth0",
  1338  			type_:      "physical",
  1339  			enabled:    true,
  1340  			macAddress: "53:54:00:70:9b:ff",
  1341  			vlan:       vlan1,
  1342  			links: []gomaasapi.Link{
  1343  				&fakeLink{
  1344  					id:        480,
  1345  					subnet:    &subnet1,
  1346  					ipAddress: "10.20.19.104",
  1347  					mode:      "static",
  1348  				},
  1349  			},
  1350  			parents:  []string{},
  1351  			children: []string{},
  1352  		},
  1353  	}
  1354  	stub := &testing.Stub{}
  1355  	device := &fakeDevice{
  1356  		Stub:         stub,
  1357  		interfaceSet: deviceInterfaces,
  1358  		systemID:     "foo",
  1359  	}
  1360  	// MAAS 2.0 gives us this kind of error back, I'm not sure of the conten of
  1361  	// the Headers or BodyMessage, but it is a 404 with a particular error
  1362  	// string that we've seen.
  1363  	body := "Unknown API endpoint: /MAAS/api/2.0/static-routes/."
  1364  	notFound := gomaasapi.ServerError{
  1365  		StatusCode:  http.StatusNotFound,
  1366  		BodyMessage: body,
  1367  	}
  1368  	wrap1 := errors.Annotatef(notFound, "ServerError: 404 NOT FOUND (%s)", body)
  1369  	staticRoutesErr := gomaasapi.NewUnexpectedError(wrap1)
  1370  	var env *maasEnviron
  1371  	controller := &fakeController{
  1372  		Stub: stub,
  1373  		machines: []gomaasapi.Machine{&fakeMachine{
  1374  			Stub:         stub,
  1375  			systemID:     "1",
  1376  			architecture: arch.HostArch(),
  1377  			interfaceSet: interfaces,
  1378  			createDevice: device,
  1379  		}},
  1380  		spaces: []gomaasapi.Space{
  1381  			fakeSpace{
  1382  				name:    "freckles",
  1383  				id:      4567,
  1384  				subnets: []gomaasapi.Subnet{subnet1},
  1385  			},
  1386  		},
  1387  		devices:           []gomaasapi.Device{device},
  1388  		staticRoutesError: staticRoutesErr,
  1389  	}
  1390  	suite.injectController(controller)
  1391  	suite.setupFakeTools(c)
  1392  	env = suite.makeEnviron(c, nil)
  1393  
  1394  	prepared := network.InterfaceInfos{{
  1395  		MACAddress:    "52:54:00:70:9b:fe",
  1396  		Addresses:     network.ProviderAddresses{network.NewMachineAddress("", network.WithCIDR("10.20.19.0/24")).AsProviderAddress()},
  1397  		InterfaceName: "eth0",
  1398  	}}
  1399  	ignored := names.NewMachineTag("1/lxd/0")
  1400  	result, err := env.AllocateContainerAddresses(suite.callCtx, instance.Id("1"), ignored, prepared)
  1401  	c.Assert(err, jc.ErrorIsNil)
  1402  	expected := network.InterfaceInfos{{
  1403  		DeviceIndex:       0,
  1404  		MACAddress:        "53:54:00:70:9b:ff",
  1405  		ProviderId:        "93",
  1406  		ProviderSubnetId:  "3",
  1407  		VLANTag:           0,
  1408  		ProviderVLANId:    "5001",
  1409  		ProviderAddressId: "480",
  1410  		InterfaceName:     "eth0",
  1411  		InterfaceType:     "ethernet",
  1412  		Addresses: network.ProviderAddresses{
  1413  			network.NewMachineAddress(
  1414  				"10.20.19.104", network.WithCIDR("10.20.19.0/24"), network.WithConfigType(network.ConfigStatic),
  1415  			).AsProviderAddress(network.WithSpaceName("freckles")),
  1416  		},
  1417  		DNSServers: network.NewMachineAddresses([]string{
  1418  			"10.20.19.2",
  1419  			"10.20.19.3",
  1420  		}).AsProviderAddresses(network.WithSpaceName("freckles")),
  1421  		MTU:            1500,
  1422  		GatewayAddress: network.NewMachineAddress("10.20.19.2").AsProviderAddress(network.WithSpaceName("freckles")),
  1423  		Routes:         []network.Route{},
  1424  		Origin:         network.OriginProvider,
  1425  	}}
  1426  	c.Assert(result, jc.DeepEquals, expected)
  1427  }
  1428  
  1429  func (suite *maasEnvironSuite) TestAllocateContainerAddressesStaticRoutesDenied(c *gc.C) {
  1430  	// I don't have a specific error that we've triggered, but we want to make
  1431  	// sure that we don't suppress all error responses from MAAS just because
  1432  	// we know we want to skip 404
  1433  	vlan1 := fakeVLAN{
  1434  		id:  5001,
  1435  		mtu: 1500,
  1436  	}
  1437  	subnet1 := fakeSubnet{
  1438  		id:         3,
  1439  		space:      "freckles",
  1440  		vlan:       vlan1,
  1441  		gateway:    "10.20.19.2",
  1442  		cidr:       "10.20.19.0/24",
  1443  		dnsServers: []string{"10.20.19.2", "10.20.19.3"},
  1444  	}
  1445  
  1446  	interfaces := []gomaasapi.Interface{
  1447  		&fakeInterface{
  1448  			id:         91,
  1449  			name:       "eth0",
  1450  			type_:      "physical",
  1451  			enabled:    true,
  1452  			macAddress: "52:54:00:70:9b:fe",
  1453  			vlan:       vlan1,
  1454  			links: []gomaasapi.Link{
  1455  				&fakeLink{
  1456  					id:        436,
  1457  					subnet:    &subnet1,
  1458  					ipAddress: "10.20.19.103",
  1459  					mode:      "static",
  1460  				},
  1461  			},
  1462  			parents:  []string{},
  1463  			children: []string{"eth0.100", "eth0.250", "eth0.50"},
  1464  		},
  1465  	}
  1466  	body := "I have failed you"
  1467  	internalError := gomaasapi.ServerError{
  1468  		StatusCode:  http.StatusInternalServerError,
  1469  		BodyMessage: body,
  1470  	}
  1471  	staticRoutesErr := errors.Annotatef(internalError, "ServerError: %v (%s)", http.StatusInternalServerError, body)
  1472  	var env *maasEnviron
  1473  	controller := &fakeController{
  1474  		Stub: &testing.Stub{},
  1475  		machines: []gomaasapi.Machine{&fakeMachine{
  1476  			Stub:         &testing.Stub{},
  1477  			systemID:     "1",
  1478  			architecture: arch.HostArch(),
  1479  			interfaceSet: interfaces,
  1480  		}},
  1481  		spaces: []gomaasapi.Space{
  1482  			fakeSpace{
  1483  				name:    "freckles",
  1484  				id:      4567,
  1485  				subnets: []gomaasapi.Subnet{subnet1},
  1486  			},
  1487  		},
  1488  		staticRoutesError: staticRoutesErr,
  1489  	}
  1490  	suite.injectController(controller)
  1491  	suite.setupFakeTools(c)
  1492  	env = suite.makeEnviron(c, nil)
  1493  
  1494  	prepared := network.InterfaceInfos{{
  1495  		MACAddress:    "52:54:00:70:9b:fe",
  1496  		Addresses:     network.ProviderAddresses{network.NewMachineAddress("", network.WithCIDR("10.20.19.0/24")).AsProviderAddress()},
  1497  		InterfaceName: "eth0",
  1498  	}}
  1499  	ignored := names.NewMachineTag("1/lxd/0")
  1500  	_, err := env.AllocateContainerAddresses(suite.callCtx, "1", ignored, prepared)
  1501  	c.Assert(err, gc.NotNil)
  1502  	c.Assert(err, gc.ErrorMatches, ".*ServerError: 500 \\(I have failed you\\).*")
  1503  }
  1504  
  1505  func (suite *maasEnvironSuite) TestAllocateContainerAddressesDualNic(c *gc.C) {
  1506  	vlan1 := fakeVLAN{
  1507  		id:  5001,
  1508  		mtu: 1500,
  1509  	}
  1510  	vlan2 := fakeVLAN{
  1511  		id:  5002,
  1512  		mtu: 1500,
  1513  	}
  1514  	subnet1 := fakeSubnet{
  1515  		id:         3,
  1516  		space:      "freckles",
  1517  		vlan:       vlan1,
  1518  		gateway:    "10.20.19.2",
  1519  		cidr:       "10.20.19.0/24",
  1520  		dnsServers: []string{"10.20.19.2", "10.20.19.3"},
  1521  	}
  1522  	subnet2 := fakeSubnet{
  1523  		id:         4,
  1524  		space:      "freckles",
  1525  		vlan:       vlan2,
  1526  		gateway:    "192.168.1.1",
  1527  		cidr:       "192.168.1.0/24",
  1528  		dnsServers: []string{"10.20.19.2", "10.20.19.3"},
  1529  	}
  1530  	staticRoute2to1 := fakeStaticRoute{
  1531  		id:          1,
  1532  		source:      subnet2,
  1533  		destination: subnet1,
  1534  		gatewayIP:   "192.168.1.1",
  1535  		metric:      100,
  1536  	}
  1537  
  1538  	interfaces := []gomaasapi.Interface{
  1539  		&fakeInterface{
  1540  			id:         91,
  1541  			name:       "eth0",
  1542  			type_:      "physical",
  1543  			enabled:    true,
  1544  			macAddress: "52:54:00:70:9b:fe",
  1545  			vlan:       vlan1,
  1546  			links: []gomaasapi.Link{
  1547  				&fakeLink{
  1548  					id:        436,
  1549  					subnet:    &subnet1,
  1550  					ipAddress: "10.20.19.103",
  1551  					mode:      "static",
  1552  				},
  1553  			},
  1554  			parents:  []string{},
  1555  			children: []string{"eth0.100", "eth0.250", "eth0.50"},
  1556  		},
  1557  		&fakeInterface{
  1558  			id:         92,
  1559  			name:       "eth1",
  1560  			type_:      "physical",
  1561  			enabled:    true,
  1562  			macAddress: "52:54:00:70:9b:ff",
  1563  			vlan:       vlan2,
  1564  			links: []gomaasapi.Link{
  1565  				&fakeLink{
  1566  					id:        437,
  1567  					subnet:    &subnet2,
  1568  					ipAddress: "192.168.1.4",
  1569  					mode:      "static",
  1570  				},
  1571  			},
  1572  		},
  1573  	}
  1574  	deviceInterfaces := []gomaasapi.Interface{
  1575  		&fakeInterface{
  1576  			id:         93,
  1577  			name:       "eth0",
  1578  			type_:      "physical",
  1579  			enabled:    true,
  1580  			macAddress: "53:54:00:70:9b:ff",
  1581  			vlan:       vlan1,
  1582  			links: []gomaasapi.Link{
  1583  				&fakeLink{
  1584  					id:        480,
  1585  					subnet:    &subnet1,
  1586  					ipAddress: "10.20.19.127",
  1587  					mode:      "static",
  1588  				},
  1589  			},
  1590  			parents:  []string{},
  1591  			children: []string{"eth0.100", "eth0.250", "eth0.50"},
  1592  			Stub:     &testing.Stub{},
  1593  		},
  1594  	}
  1595  	newInterface := &fakeInterface{
  1596  		id:         94,
  1597  		name:       "eth1",
  1598  		type_:      "physical",
  1599  		enabled:    true,
  1600  		macAddress: "52:54:00:70:9b:f4",
  1601  		vlan:       vlan2,
  1602  		links: []gomaasapi.Link{
  1603  			&fakeLink{
  1604  				id:        481,
  1605  				subnet:    &subnet2,
  1606  				ipAddress: "192.168.1.127",
  1607  				mode:      "static",
  1608  			},
  1609  		},
  1610  		Stub: &testing.Stub{},
  1611  	}
  1612  	device := &fakeDevice{
  1613  		interfaceSet: deviceInterfaces,
  1614  		systemID:     "foo",
  1615  		interface_:   newInterface,
  1616  		Stub:         &testing.Stub{},
  1617  	}
  1618  	controller := &fakeController{
  1619  		Stub: &testing.Stub{},
  1620  		machines: []gomaasapi.Machine{&fakeMachine{
  1621  			Stub:         &testing.Stub{},
  1622  			systemID:     "1",
  1623  			architecture: arch.HostArch(),
  1624  			interfaceSet: interfaces,
  1625  			createDevice: device,
  1626  		}},
  1627  		spaces: []gomaasapi.Space{
  1628  			fakeSpace{
  1629  				name:    "freckles",
  1630  				id:      4567,
  1631  				subnets: []gomaasapi.Subnet{subnet1, subnet2},
  1632  			},
  1633  		},
  1634  		devices:      []gomaasapi.Device{device},
  1635  		staticRoutes: []gomaasapi.StaticRoute{staticRoute2to1},
  1636  	}
  1637  	suite.injectController(controller)
  1638  	env := suite.makeEnviron(c, nil)
  1639  
  1640  	prepared := network.InterfaceInfos{{
  1641  		MACAddress:    "53:54:00:70:9b:ff",
  1642  		Addresses:     network.ProviderAddresses{network.NewMachineAddress("", network.WithCIDR("10.20.19.0/24")).AsProviderAddress()},
  1643  		InterfaceName: "eth0",
  1644  	}, {
  1645  		MACAddress:    "52:54:00:70:9b:f4",
  1646  		Addresses:     network.ProviderAddresses{network.NewMachineAddress("", network.WithCIDR("192.168.1.0/24")).AsProviderAddress()},
  1647  		InterfaceName: "eth1",
  1648  	}}
  1649  	expected := network.InterfaceInfos{{
  1650  		DeviceIndex:       0,
  1651  		MACAddress:        "53:54:00:70:9b:ff",
  1652  		ProviderId:        "93",
  1653  		ProviderSubnetId:  "3",
  1654  		ProviderVLANId:    "5001",
  1655  		ProviderAddressId: "480",
  1656  		InterfaceName:     "eth0",
  1657  		InterfaceType:     "ethernet",
  1658  		Addresses: network.ProviderAddresses{
  1659  			network.NewMachineAddress(
  1660  				"10.20.19.127", network.WithCIDR("10.20.19.0/24"), network.WithConfigType(network.ConfigStatic),
  1661  			).AsProviderAddress(network.WithSpaceName("freckles")),
  1662  		},
  1663  		DNSServers: network.NewMachineAddresses([]string{
  1664  			"10.20.19.2",
  1665  			"10.20.19.3",
  1666  		}).AsProviderAddresses(network.WithSpaceName("freckles")),
  1667  		MTU:            1500,
  1668  		GatewayAddress: network.NewMachineAddress("10.20.19.2").AsProviderAddress(network.WithSpaceName("freckles")),
  1669  		Origin:         network.OriginProvider,
  1670  	}, {
  1671  		DeviceIndex:       1,
  1672  		MACAddress:        "52:54:00:70:9b:f4",
  1673  		ProviderId:        "94",
  1674  		ProviderSubnetId:  "4",
  1675  		ProviderVLANId:    "5002",
  1676  		ProviderAddressId: "481",
  1677  		InterfaceName:     "eth1",
  1678  		InterfaceType:     "ethernet",
  1679  		Addresses: network.ProviderAddresses{
  1680  			network.NewMachineAddress(
  1681  				"192.168.1.127", network.WithCIDR("192.168.1.0/24"), network.WithConfigType(network.ConfigStatic),
  1682  			).AsProviderAddress(network.WithSpaceName("freckles")),
  1683  		},
  1684  		DNSServers: network.NewMachineAddresses([]string{
  1685  			"10.20.19.2",
  1686  			"10.20.19.3",
  1687  		}).AsProviderAddresses(network.WithSpaceName("freckles")),
  1688  		MTU:            1500,
  1689  		GatewayAddress: network.NewMachineAddress("192.168.1.1").AsProviderAddress(network.WithSpaceName("freckles")),
  1690  		Routes: []network.Route{{
  1691  			DestinationCIDR: "10.20.19.0/24",
  1692  			GatewayIP:       "192.168.1.1",
  1693  			Metric:          100,
  1694  		}},
  1695  		Origin: network.OriginProvider,
  1696  	}}
  1697  	ignored := names.NewMachineTag("1/lxd/0")
  1698  	result, err := env.AllocateContainerAddresses(suite.callCtx, instance.Id("1"), ignored, prepared)
  1699  	c.Assert(err, jc.ErrorIsNil)
  1700  	c.Assert(result, jc.DeepEquals, expected)
  1701  }
  1702  
  1703  func (suite *maasEnvironSuite) assertAllocateContainerAddressesFails(c *gc.C, controller *fakeController, prepared network.InterfaceInfos, errorMatches string) {
  1704  	if prepared == nil {
  1705  		prepared = network.InterfaceInfos{{}}
  1706  	}
  1707  	suite.injectController(controller)
  1708  	env := suite.makeEnviron(c, nil)
  1709  	ignored := names.NewMachineTag("1/lxd/0")
  1710  	_, err := env.AllocateContainerAddresses(suite.callCtx, instance.Id("1"), ignored, prepared)
  1711  	c.Assert(err, gc.ErrorMatches, errorMatches)
  1712  }
  1713  
  1714  func (suite *maasEnvironSuite) TestAllocateContainerAddressesSpacesError(c *gc.C) {
  1715  	machine := &fakeMachine{
  1716  		Stub:     &testing.Stub{},
  1717  		systemID: "1",
  1718  	}
  1719  	controller := &fakeController{
  1720  		machines:    []gomaasapi.Machine{machine},
  1721  		spacesError: errors.New("boom"),
  1722  	}
  1723  	suite.assertAllocateContainerAddressesFails(c, controller, nil, "boom")
  1724  }
  1725  
  1726  func (suite *maasEnvironSuite) TestAllocateContainerAddressesPrimaryInterfaceMissing(c *gc.C) {
  1727  	machine := &fakeMachine{
  1728  		Stub:     &testing.Stub{},
  1729  		systemID: "1",
  1730  	}
  1731  	controller := &fakeController{
  1732  		machines: []gomaasapi.Machine{machine},
  1733  	}
  1734  	suite.assertAllocateContainerAddressesFails(c, controller, nil, "cannot find primary interface for container")
  1735  }
  1736  
  1737  func makeFakeSubnet(id int) fakeSubnet {
  1738  	return fakeSubnet{
  1739  		id:      id,
  1740  		space:   "freckles",
  1741  		gateway: fmt.Sprintf("10.20.%d.2", 16+id),
  1742  		cidr:    fmt.Sprintf("10.20.%d.0/24", 16+id),
  1743  	}
  1744  }
  1745  func (suite *maasEnvironSuite) TestAllocateContainerAddressesMachinesError(c *gc.C) {
  1746  	var env *maasEnviron
  1747  	subnet := makeFakeSubnet(3)
  1748  	checkMachinesArgs := func(args gomaasapi.MachinesArgs) {
  1749  		expected := gomaasapi.MachinesArgs{
  1750  			AgentName: env.Config().UUID(),
  1751  			SystemIDs: []string{"1"},
  1752  		}
  1753  		c.Assert(args, jc.DeepEquals, expected)
  1754  	}
  1755  	controller := &fakeController{
  1756  		machinesError:     errors.New("boom"),
  1757  		machinesArgsCheck: checkMachinesArgs,
  1758  		spaces: []gomaasapi.Space{
  1759  			fakeSpace{
  1760  				name:    "freckles",
  1761  				id:      4567,
  1762  				subnets: []gomaasapi.Subnet{subnet},
  1763  			},
  1764  		},
  1765  	}
  1766  	suite.injectController(controller)
  1767  	env = suite.makeEnviron(c, nil)
  1768  	prepared := network.InterfaceInfos{{
  1769  		InterfaceName: "eth0",
  1770  		Addresses:     network.ProviderAddresses{network.NewMachineAddress("", network.WithCIDR("10.20.19.0/24")).AsProviderAddress()},
  1771  	}}
  1772  	ignored := names.NewMachineTag("1/lxd/0")
  1773  	_, err := env.AllocateContainerAddresses(suite.callCtx, instance.Id("1"), ignored, prepared)
  1774  	c.Assert(err, gc.ErrorMatches, "boom")
  1775  }
  1776  
  1777  func getArgs(c *gc.C, calls []testing.StubCall, callNum, argNum int) interface{} {
  1778  	c.Assert(len(calls), gc.Not(jc.LessThan), callNum)
  1779  	args := calls[callNum].Args
  1780  	c.Assert(len(args), gc.Not(jc.LessThan), argNum)
  1781  	return args[argNum]
  1782  }
  1783  
  1784  func (suite *maasEnvironSuite) TestAllocateContainerAddressesCreateDeviceError(c *gc.C) {
  1785  	subnet := makeFakeSubnet(3)
  1786  	var env *maasEnviron
  1787  	machine := &fakeMachine{
  1788  		Stub:     &testing.Stub{},
  1789  		systemID: "1",
  1790  	}
  1791  	machine.SetErrors(nil, errors.New("bad device call"))
  1792  	controller := &fakeController{
  1793  		machines: []gomaasapi.Machine{machine},
  1794  		spaces: []gomaasapi.Space{
  1795  			fakeSpace{
  1796  				name:    "freckles",
  1797  				id:      4567,
  1798  				subnets: []gomaasapi.Subnet{subnet},
  1799  			},
  1800  		},
  1801  	}
  1802  	suite.injectController(controller)
  1803  	env = suite.makeEnviron(c, nil)
  1804  	prepared := network.InterfaceInfos{{
  1805  		InterfaceName: "eth0",
  1806  		Addresses:     network.ProviderAddresses{network.NewMachineAddress("", network.WithCIDR("10.20.19.0/24")).AsProviderAddress()},
  1807  		MACAddress:    "DEADBEEF",
  1808  	}}
  1809  	ignored := names.NewMachineTag("1/lxd/0")
  1810  	_, err := env.AllocateContainerAddresses(suite.callCtx, instance.Id("1"), ignored, prepared)
  1811  	c.Assert(err, gc.ErrorMatches, `failed to create MAAS device for "juju-06f00d-1-lxd-0": bad device call`)
  1812  	machine.CheckCall(c, 0, "Devices", gomaasapi.DevicesArgs{
  1813  		Hostname: []string{"juju-06f00d-1-lxd-0"},
  1814  	})
  1815  	machine.CheckCall(c, 1, "CreateDevice", gomaasapi.CreateMachineDeviceArgs{
  1816  		Hostname:      "juju-06f00d-1-lxd-0",
  1817  		Subnet:        subnet,
  1818  		MACAddress:    "DEADBEEF",
  1819  		InterfaceName: "eth0",
  1820  	})
  1821  }
  1822  
  1823  func (suite *maasEnvironSuite) TestAllocateContainerAddressesSubnetMissing(c *gc.C) {
  1824  	subnet := makeFakeSubnet(3)
  1825  	var env *maasEnviron
  1826  	device := &fakeDevice{
  1827  		Stub: &testing.Stub{},
  1828  		interfaceSet: []gomaasapi.Interface{
  1829  			&fakeInterface{
  1830  				id:         93,
  1831  				name:       "eth0",
  1832  				type_:      "physical",
  1833  				enabled:    true,
  1834  				macAddress: "53:54:00:70:9b:ff",
  1835  				vlan:       &fakeVLAN{vid: 0},
  1836  				links: []gomaasapi.Link{
  1837  					&fakeLink{
  1838  						id:   480,
  1839  						mode: "link_up",
  1840  					},
  1841  				},
  1842  				parents:  []string{},
  1843  				children: []string{},
  1844  				Stub:     &testing.Stub{},
  1845  			},
  1846  		},
  1847  		interface_: &fakeInterface{
  1848  			id:         94,
  1849  			name:       "eth1",
  1850  			type_:      "physical",
  1851  			enabled:    true,
  1852  			macAddress: "53:54:00:70:9b:f1",
  1853  			vlan:       &fakeVLAN{vid: 0},
  1854  			links: []gomaasapi.Link{
  1855  				&fakeLink{
  1856  					id:   481,
  1857  					mode: "link_up",
  1858  				},
  1859  			},
  1860  			parents:  []string{},
  1861  			children: []string{},
  1862  			Stub:     &testing.Stub{},
  1863  		},
  1864  		systemID: "foo",
  1865  	}
  1866  	machine := &fakeMachine{
  1867  		Stub:         &testing.Stub{},
  1868  		systemID:     "1",
  1869  		createDevice: device,
  1870  	}
  1871  	controller := &fakeController{
  1872  		Stub:     &testing.Stub{},
  1873  		machines: []gomaasapi.Machine{machine},
  1874  		spaces: []gomaasapi.Space{
  1875  			fakeSpace{
  1876  				name:    "freckles",
  1877  				id:      4567,
  1878  				subnets: []gomaasapi.Subnet{subnet},
  1879  			},
  1880  		},
  1881  		devices: []gomaasapi.Device{device},
  1882  	}
  1883  	suite.injectController(controller)
  1884  	env = suite.makeEnviron(c, nil)
  1885  	prepared := network.InterfaceInfos{
  1886  		{InterfaceName: "eth0", MACAddress: "DEADBEEF"},
  1887  		{InterfaceName: "eth1", MACAddress: "DEADBEEE"},
  1888  	}
  1889  	ignored := names.NewMachineTag("1/lxd/0")
  1890  	allocated, err := env.AllocateContainerAddresses(suite.callCtx, "1", ignored, prepared)
  1891  	c.Assert(err, jc.ErrorIsNil)
  1892  	c.Assert(allocated, jc.DeepEquals, network.InterfaceInfos{{
  1893  		DeviceIndex:    0,
  1894  		MACAddress:     "53:54:00:70:9b:ff",
  1895  		ProviderId:     "93",
  1896  		ProviderVLANId: "0",
  1897  		VLANTag:        0,
  1898  		InterfaceName:  "eth0",
  1899  		InterfaceType:  "ethernet",
  1900  		Disabled:       false,
  1901  		NoAutoStart:    false,
  1902  		MTU:            1500,
  1903  		Origin:         network.OriginProvider,
  1904  	}, {
  1905  		DeviceIndex:    1,
  1906  		MACAddress:     "53:54:00:70:9b:f1",
  1907  		ProviderId:     "94",
  1908  		ProviderVLANId: "0",
  1909  		VLANTag:        0,
  1910  		InterfaceName:  "eth1",
  1911  		InterfaceType:  "ethernet",
  1912  		Disabled:       false,
  1913  		NoAutoStart:    false,
  1914  		MTU:            1500,
  1915  		Origin:         network.OriginProvider,
  1916  	}})
  1917  }
  1918  
  1919  func (suite *maasEnvironSuite) TestAllocateContainerAddressesCreateInterfaceError(c *gc.C) {
  1920  	subnet := makeFakeSubnet(3)
  1921  	subnet2 := makeFakeSubnet(4)
  1922  	subnet2.vlan = fakeVLAN{vid: 66}
  1923  	var env *maasEnviron
  1924  	device := &fakeDevice{
  1925  		Stub:         &testing.Stub{},
  1926  		interfaceSet: []gomaasapi.Interface{&fakeInterface{}},
  1927  		systemID:     "foo",
  1928  	}
  1929  	device.SetErrors(errors.New("boom"))
  1930  	machine := &fakeMachine{
  1931  		Stub:         &testing.Stub{},
  1932  		systemID:     "1",
  1933  		createDevice: device,
  1934  	}
  1935  	controller := &fakeController{
  1936  		machines: []gomaasapi.Machine{machine},
  1937  		spaces: []gomaasapi.Space{
  1938  			fakeSpace{
  1939  				name:    "freckles",
  1940  				id:      4567,
  1941  				subnets: []gomaasapi.Subnet{subnet, subnet2},
  1942  			},
  1943  		},
  1944  	}
  1945  	suite.injectController(controller)
  1946  	env = suite.makeEnviron(c, nil)
  1947  	prepared := network.InterfaceInfos{
  1948  		{
  1949  			InterfaceName: "eth0",
  1950  			Addresses:     network.ProviderAddresses{network.NewMachineAddress("", network.WithCIDR("10.20.19.0/24")).AsProviderAddress()},
  1951  			MACAddress:    "DEADBEEF",
  1952  		},
  1953  		{
  1954  			InterfaceName: "eth1",
  1955  			Addresses:     network.ProviderAddresses{network.NewMachineAddress("", network.WithCIDR("10.20.20.0/24")).AsProviderAddress()},
  1956  			MACAddress:    "DEADBEEE",
  1957  		},
  1958  	}
  1959  	ignored := names.NewMachineTag("1/lxd/0")
  1960  	_, err := env.AllocateContainerAddresses(suite.callCtx, instance.Id("1"), ignored, prepared)
  1961  	c.Assert(err, gc.ErrorMatches, `failed to create MAAS device for "juju-06f00d-1-lxd-0": creating device interface: boom`)
  1962  	args := getArgs(c, device.Calls(), 0, 0)
  1963  	maasArgs, ok := args.(gomaasapi.CreateInterfaceArgs)
  1964  	c.Assert(ok, jc.IsTrue)
  1965  	expected := gomaasapi.CreateInterfaceArgs{
  1966  		MACAddress: "DEADBEEE",
  1967  		Name:       "eth1",
  1968  		VLAN:       subnet2.VLAN(),
  1969  	}
  1970  	c.Assert(maasArgs, jc.DeepEquals, expected)
  1971  }
  1972  
  1973  func (suite *maasEnvironSuite) TestAllocateContainerAddressesLinkSubnetError(c *gc.C) {
  1974  	subnet := makeFakeSubnet(3)
  1975  	subnet2 := makeFakeSubnet(4)
  1976  	subnet2.vlan = fakeVLAN{vid: 66}
  1977  	var env *maasEnviron
  1978  	interface_ := &fakeInterface{Stub: &testing.Stub{}}
  1979  	interface_.SetErrors(errors.New("boom"))
  1980  	device := &fakeDevice{
  1981  		Stub:         &testing.Stub{},
  1982  		interfaceSet: []gomaasapi.Interface{&fakeInterface{}},
  1983  		interface_:   interface_,
  1984  		systemID:     "foo",
  1985  	}
  1986  	machine := &fakeMachine{
  1987  		Stub:         &testing.Stub{},
  1988  		systemID:     "1",
  1989  		createDevice: device,
  1990  	}
  1991  	controller := &fakeController{
  1992  		Stub:     &testing.Stub{},
  1993  		machines: []gomaasapi.Machine{machine},
  1994  		spaces: []gomaasapi.Space{
  1995  			fakeSpace{
  1996  				name:    "freckles",
  1997  				id:      4567,
  1998  				subnets: []gomaasapi.Subnet{subnet, subnet2},
  1999  			},
  2000  		},
  2001  		devices: []gomaasapi.Device{device},
  2002  	}
  2003  	suite.injectController(controller)
  2004  	env = suite.makeEnviron(c, nil)
  2005  	prepared := network.InterfaceInfos{
  2006  		{
  2007  			InterfaceName: "eth0",
  2008  			Addresses:     network.ProviderAddresses{network.NewMachineAddress("", network.WithCIDR("10.20.19.0/24")).AsProviderAddress()},
  2009  			MACAddress:    "DEADBEEF",
  2010  		},
  2011  		{
  2012  			InterfaceName: "eth1",
  2013  			Addresses:     network.ProviderAddresses{network.NewMachineAddress("", network.WithCIDR("10.20.20.0/24")).AsProviderAddress()},
  2014  			MACAddress:    "DEADBEEE",
  2015  		},
  2016  	}
  2017  	ignored := names.NewMachineTag("1/lxd/0")
  2018  	_, err := env.AllocateContainerAddresses(suite.callCtx, "1", ignored, prepared)
  2019  	c.Assert(err, gc.ErrorMatches, "failed to create MAAS device.*boom")
  2020  	args := getArgs(c, interface_.Calls(), 0, 0)
  2021  	maasArgs, ok := args.(gomaasapi.LinkSubnetArgs)
  2022  	c.Assert(ok, jc.IsTrue)
  2023  	expected := gomaasapi.LinkSubnetArgs{
  2024  		Mode:   gomaasapi.LinkModeStatic,
  2025  		Subnet: subnet2,
  2026  	}
  2027  	c.Assert(maasArgs, jc.DeepEquals, expected)
  2028  }
  2029  
  2030  func (suite *maasEnvironSuite) TestStorageReturnsStorage(c *gc.C) {
  2031  	controller := newFakeController()
  2032  	env := suite.makeEnviron(c, controller)
  2033  	stor := env.Storage()
  2034  	c.Check(stor, gc.NotNil)
  2035  
  2036  	// The Storage object is really a maasStorage.
  2037  	specificStorage := stor.(*maasStorage)
  2038  
  2039  	// Its environment pointer refers back to its environment.
  2040  	c.Check(specificStorage.environ, gc.Equals, env)
  2041  	c.Check(specificStorage.maasController, gc.Equals, controller)
  2042  }
  2043  
  2044  func (suite *maasEnvironSuite) TestAllocateContainerReuseExistingDevice(c *gc.C) {
  2045  	stub := &testing.Stub{}
  2046  	vlan1 := fakeVLAN{
  2047  		id:  5001,
  2048  		mtu: 1500,
  2049  	}
  2050  	subnet1 := fakeSubnet{
  2051  		id:         3,
  2052  		space:      "space-1",
  2053  		vlan:       vlan1,
  2054  		gateway:    "10.20.19.2",
  2055  		cidr:       "10.20.19.0/24",
  2056  		dnsServers: []string{"10.20.19.2", "10.20.19.3"},
  2057  	}
  2058  	interfaces := []gomaasapi.Interface{
  2059  		&fakeInterface{
  2060  			id:         91,
  2061  			name:       "eth0",
  2062  			type_:      "physical",
  2063  			enabled:    true,
  2064  			macAddress: "52:54:00:70:9b:fe",
  2065  			vlan:       vlan1,
  2066  			links: []gomaasapi.Link{
  2067  				&fakeLink{
  2068  					id:        436,
  2069  					subnet:    &subnet1,
  2070  					ipAddress: "10.20.19.103",
  2071  					mode:      "static",
  2072  				},
  2073  			},
  2074  			parents: []string{},
  2075  		},
  2076  	}
  2077  	deviceInterfaces := []gomaasapi.Interface{
  2078  		&fakeInterface{
  2079  			id:         93,
  2080  			name:       "eth0",
  2081  			type_:      "physical",
  2082  			enabled:    true,
  2083  			macAddress: "53:54:00:70:9b:ff",
  2084  			vlan:       vlan1,
  2085  			links: []gomaasapi.Link{
  2086  				&fakeLink{
  2087  					id:        480,
  2088  					subnet:    &subnet1,
  2089  					ipAddress: "10.20.19.105",
  2090  					mode:      "static",
  2091  				},
  2092  			},
  2093  			parents: []string{},
  2094  		},
  2095  	}
  2096  	var env *maasEnviron
  2097  	device := &fakeDevice{
  2098  		Stub:         stub,
  2099  		interfaceSet: deviceInterfaces,
  2100  		systemID:     "foo",
  2101  	}
  2102  	controller := &fakeController{
  2103  		Stub: stub,
  2104  		machines: []gomaasapi.Machine{&fakeMachine{
  2105  			Stub:         stub,
  2106  			systemID:     "1",
  2107  			architecture: arch.HostArch(),
  2108  			interfaceSet: interfaces,
  2109  			// Instead of having createDevice return it, Devices()
  2110  			// returns it from the beginning
  2111  			createDevice: nil,
  2112  			devices:      []gomaasapi.Device{device},
  2113  		}},
  2114  		spaces: []gomaasapi.Space{
  2115  			fakeSpace{
  2116  				name:    "space-1",
  2117  				id:      4567,
  2118  				subnets: []gomaasapi.Subnet{subnet1},
  2119  			},
  2120  		},
  2121  		devices: []gomaasapi.Device{device},
  2122  	}
  2123  	suite.injectController(controller)
  2124  	suite.setupFakeTools(c)
  2125  	env = suite.makeEnviron(c, nil)
  2126  
  2127  	prepared := network.InterfaceInfos{{
  2128  		MACAddress:    "53:54:00:70:9b:ff",
  2129  		Addresses:     network.ProviderAddresses{network.NewMachineAddress("", network.WithCIDR("10.20.19.0/24")).AsProviderAddress()},
  2130  		InterfaceName: "eth0",
  2131  	}}
  2132  	containerTag := names.NewMachineTag("1/lxd/0")
  2133  	result, err := env.AllocateContainerAddresses(suite.callCtx, "1", containerTag, prepared)
  2134  	c.Assert(err, jc.ErrorIsNil)
  2135  	expected := network.InterfaceInfos{{
  2136  		DeviceIndex:       0,
  2137  		MACAddress:        "53:54:00:70:9b:ff",
  2138  		ProviderId:        "93",
  2139  		ProviderSubnetId:  "3",
  2140  		VLANTag:           0,
  2141  		ProviderVLANId:    "5001",
  2142  		ProviderAddressId: "480",
  2143  		InterfaceName:     "eth0",
  2144  		InterfaceType:     "ethernet",
  2145  		Addresses: network.ProviderAddresses{
  2146  			network.NewMachineAddress(
  2147  				"10.20.19.105", network.WithCIDR("10.20.19.0/24"), network.WithConfigType(network.ConfigStatic),
  2148  			).AsProviderAddress(network.WithSpaceName("space-1")),
  2149  		},
  2150  		DNSServers: network.NewMachineAddresses([]string{
  2151  			"10.20.19.2",
  2152  			"10.20.19.3",
  2153  		}).AsProviderAddresses(network.WithSpaceName("space-1")),
  2154  		MTU:            1500,
  2155  		GatewayAddress: network.NewMachineAddress("10.20.19.2").AsProviderAddress(network.WithSpaceName("space-1")),
  2156  		Routes:         []network.Route{},
  2157  		Origin:         network.OriginProvider,
  2158  	}}
  2159  	c.Assert(result, jc.DeepEquals, expected)
  2160  }
  2161  
  2162  func (suite *maasEnvironSuite) TestAllocateContainerRefusesReuseInvalidNIC(c *gc.C) {
  2163  	vlan1 := fakeVLAN{
  2164  		id:  5001,
  2165  		mtu: 1500,
  2166  	}
  2167  	vlan2 := fakeVLAN{
  2168  		id:  5002,
  2169  		mtu: 1500,
  2170  	}
  2171  	subnet1 := fakeSubnet{
  2172  		id:         3,
  2173  		space:      "freckles",
  2174  		vlan:       vlan1,
  2175  		gateway:    "10.20.19.2",
  2176  		cidr:       "10.20.19.0/24",
  2177  		dnsServers: []string{"10.20.19.2", "10.20.19.3"},
  2178  	}
  2179  	subnet2 := fakeSubnet{
  2180  		id:         4,
  2181  		space:      "freckles",
  2182  		vlan:       vlan2,
  2183  		gateway:    "192.168.1.1",
  2184  		cidr:       "192.168.1.0/24",
  2185  		dnsServers: []string{"192.168.1.2"},
  2186  	}
  2187  	subnet3 := fakeSubnet{
  2188  		id:         5,
  2189  		space:      "freckles",
  2190  		vlan:       vlan2,
  2191  		gateway:    "192.168.1.1",
  2192  		cidr:       "192.168.2.0/24",
  2193  		dnsServers: []string{"192.168.1.2"},
  2194  	}
  2195  	interfaces := []gomaasapi.Interface{
  2196  		&fakeInterface{
  2197  			id:         91,
  2198  			name:       "eth0",
  2199  			type_:      "physical",
  2200  			enabled:    true,
  2201  			macAddress: "52:54:00:70:9b:fe",
  2202  			vlan:       vlan1,
  2203  			links: []gomaasapi.Link{
  2204  				&fakeLink{
  2205  					id:        436,
  2206  					subnet:    &subnet1,
  2207  					ipAddress: "10.20.19.103",
  2208  					mode:      "static",
  2209  				},
  2210  			},
  2211  			parents: []string{},
  2212  		},
  2213  		&fakeInterface{
  2214  			id:         92,
  2215  			name:       "eth1",
  2216  			type_:      "physical",
  2217  			enabled:    true,
  2218  			macAddress: "52:54:00:70:9b:ff",
  2219  			vlan:       vlan2,
  2220  			links: []gomaasapi.Link{
  2221  				&fakeLink{
  2222  					id:        437,
  2223  					subnet:    &subnet2,
  2224  					ipAddress: "192.168.1.100",
  2225  					mode:      "static",
  2226  				},
  2227  			},
  2228  			parents: []string{},
  2229  		},
  2230  	}
  2231  	badDeviceInterfaces := []gomaasapi.Interface{
  2232  		&fakeInterface{
  2233  			id:         93,
  2234  			name:       "eth0",
  2235  			type_:      "physical",
  2236  			enabled:    true,
  2237  			macAddress: "53:54:00:70:88:aa",
  2238  			vlan:       vlan1,
  2239  			links: []gomaasapi.Link{
  2240  				&fakeLink{
  2241  					id:        480,
  2242  					subnet:    &subnet1,
  2243  					ipAddress: "10.20.19.105",
  2244  					mode:      "static",
  2245  				},
  2246  			},
  2247  			parents: []string{},
  2248  		},
  2249  		// This interface is linked to the wrong subnet
  2250  		&fakeInterface{
  2251  			id:         94,
  2252  			name:       "eth1",
  2253  			type_:      "physical",
  2254  			enabled:    true,
  2255  			macAddress: "53:54:00:70:88:bb",
  2256  			vlan:       vlan1,
  2257  			links: []gomaasapi.Link{
  2258  				&fakeLink{
  2259  					id:        481,
  2260  					subnet:    &subnet3,
  2261  					ipAddress: "192.168.2.100",
  2262  					mode:      "static",
  2263  				},
  2264  			},
  2265  			parents: []string{},
  2266  		},
  2267  	}
  2268  	goodSecondInterface := &fakeInterface{
  2269  		id:         94,
  2270  		name:       "eth1",
  2271  		type_:      "physical",
  2272  		enabled:    true,
  2273  		macAddress: "53:54:00:70:88:bb",
  2274  		vlan:       vlan2,
  2275  		links: []gomaasapi.Link{
  2276  			&fakeLink{
  2277  				id:        481,
  2278  				subnet:    &subnet2,
  2279  				ipAddress: "192.168.1.101",
  2280  				mode:      "static",
  2281  			},
  2282  		},
  2283  		parents: []string{},
  2284  	}
  2285  	goodDeviceInterfaces := []gomaasapi.Interface{
  2286  		badDeviceInterfaces[0],
  2287  	}
  2288  	var env *maasEnviron
  2289  	stub := &testing.Stub{}
  2290  	badDevice := &fakeDevice{
  2291  		Stub:         stub,
  2292  		interfaceSet: badDeviceInterfaces,
  2293  		systemID:     "foo",
  2294  	}
  2295  	goodDevice := &fakeDevice{
  2296  		Stub:         stub,
  2297  		interfaceSet: goodDeviceInterfaces,
  2298  		systemID:     "foo",
  2299  		interface_:   goodSecondInterface,
  2300  	}
  2301  	machine := &fakeMachine{
  2302  		Stub:         stub,
  2303  		systemID:     "1",
  2304  		architecture: arch.HostArch(),
  2305  		interfaceSet: interfaces,
  2306  		createDevice: goodDevice,
  2307  		// Devices will first list the bad device, and then
  2308  		// createDevice will create the right one
  2309  		devices: []gomaasapi.Device{badDevice},
  2310  	}
  2311  	badDevice.deleteCB = func() { machine.devices = machine.devices[:0] }
  2312  	controller := &fakeController{
  2313  		Stub:     stub,
  2314  		machines: []gomaasapi.Machine{machine},
  2315  		spaces: []gomaasapi.Space{
  2316  			fakeSpace{
  2317  				name:    "space-1",
  2318  				id:      4567,
  2319  				subnets: []gomaasapi.Subnet{subnet1},
  2320  			},
  2321  		},
  2322  		devices: []gomaasapi.Device{goodDevice},
  2323  	}
  2324  	suite.injectController(controller)
  2325  	suite.setupFakeTools(c)
  2326  	env = suite.makeEnviron(c, nil)
  2327  
  2328  	prepared := network.InterfaceInfos{{
  2329  		MACAddress:    "53:54:00:70:88:aa",
  2330  		Addresses:     network.ProviderAddresses{network.NewMachineAddress("", network.WithCIDR("10.20.19.0/24")).AsProviderAddress()},
  2331  		InterfaceName: "eth0",
  2332  	}, {
  2333  		MACAddress:    "53:54:00:70:88:bb",
  2334  		Addresses:     network.ProviderAddresses{network.NewMachineAddress("", network.WithCIDR("192.168.1.0/24")).AsProviderAddress()},
  2335  		InterfaceName: "eth1",
  2336  	}}
  2337  	containerTag := names.NewMachineTag("1/lxd/0")
  2338  	result, err := env.AllocateContainerAddresses(suite.callCtx, instance.Id("1"), containerTag, prepared)
  2339  	c.Assert(err, jc.ErrorIsNil)
  2340  	expected := network.InterfaceInfos{{
  2341  		DeviceIndex:       0,
  2342  		MACAddress:        "53:54:00:70:88:aa",
  2343  		ProviderId:        "93",
  2344  		ProviderSubnetId:  "3",
  2345  		VLANTag:           0,
  2346  		ProviderVLANId:    "5001",
  2347  		ProviderAddressId: "480",
  2348  		InterfaceName:     "eth0",
  2349  		InterfaceType:     "ethernet",
  2350  		Addresses: network.ProviderAddresses{
  2351  			network.NewMachineAddress(
  2352  				"10.20.19.105", network.WithCIDR("10.20.19.0/24"), network.WithConfigType(network.ConfigStatic),
  2353  			).AsProviderAddress(network.WithSpaceName("freckles")),
  2354  		},
  2355  		DNSServers: network.NewMachineAddresses([]string{
  2356  			"10.20.19.2",
  2357  			"10.20.19.3",
  2358  		}).AsProviderAddresses(network.WithSpaceName("freckles")),
  2359  		MTU:            1500,
  2360  		GatewayAddress: network.NewMachineAddress("10.20.19.2").AsProviderAddress(network.WithSpaceName("freckles")),
  2361  		Routes:         []network.Route{},
  2362  		Origin:         network.OriginProvider,
  2363  	}, {
  2364  		DeviceIndex:       1,
  2365  		MACAddress:        "53:54:00:70:88:bb",
  2366  		ProviderId:        "94",
  2367  		ProviderSubnetId:  "4",
  2368  		VLANTag:           0,
  2369  		ProviderVLANId:    "5002",
  2370  		ProviderAddressId: "481",
  2371  		InterfaceName:     "eth1",
  2372  		InterfaceType:     "ethernet",
  2373  		Addresses: network.ProviderAddresses{
  2374  			network.NewMachineAddress(
  2375  				"192.168.1.101", network.WithCIDR("192.168.1.0/24"), network.WithConfigType(network.ConfigStatic),
  2376  			).AsProviderAddress(network.WithSpaceName("freckles")),
  2377  		},
  2378  		DNSServers: network.NewMachineAddresses([]string{
  2379  			"192.168.1.2",
  2380  		}).AsProviderAddresses(network.WithSpaceName("freckles")),
  2381  		MTU:            1500,
  2382  		GatewayAddress: network.NewMachineAddress("192.168.1.1").AsProviderAddress(network.WithSpaceName("freckles")),
  2383  		Routes:         []network.Route{},
  2384  		Origin:         network.OriginProvider,
  2385  	}}
  2386  	c.Assert(result, jc.DeepEquals, expected)
  2387  }
  2388  
  2389  func (suite *maasEnvironSuite) TestStartInstanceEndToEnd(c *gc.C) {
  2390  	suite.setupFakeTools(c)
  2391  	machine := newFakeMachine("gus", arch.HostArch(), "Deployed")
  2392  	file := &fakeFile{name: coretesting.ModelTag.Id() + "-provider-state"}
  2393  	controller := newFakeControllerWithFiles(file)
  2394  	controller.machines = []gomaasapi.Machine{machine}
  2395  	controller.allocateMachine = machine
  2396  	controller.allocateMachineMatches = gomaasapi.ConstraintMatches{
  2397  		Storage: make(map[string][]gomaasapi.StorageDevice),
  2398  	}
  2399  
  2400  	env := suite.makeEnviron(c, controller)
  2401  	err := bootstrap.Bootstrap(envtesting.BootstrapTODOContext(c), env,
  2402  		suite.callCtx, bootstrap.BootstrapParams{
  2403  			ControllerConfig:        coretesting.FakeControllerConfig(),
  2404  			AdminSecret:             jujutesting.AdminSecret,
  2405  			CAPrivateKey:            coretesting.CAKey,
  2406  			SupportedBootstrapBases: coretesting.FakeSupportedJujuBases,
  2407  			DialOpts: environs.BootstrapDialOpts{
  2408  				Timeout: coretesting.LongWait,
  2409  			},
  2410  		})
  2411  	c.Assert(err, jc.ErrorIsNil)
  2412  
  2413  	machine.Stub.CheckCallNames(c, "Start", "SetOwnerData")
  2414  	ownerData, ok := machine.Stub.Calls()[1].Args[0].(map[string]string)
  2415  	c.Assert(ok, jc.IsTrue)
  2416  	c.Assert(ownerData, gc.DeepEquals, map[string]string{
  2417  		"claude":              "rains",
  2418  		tags.JujuController:   suite.controllerUUID,
  2419  		tags.JujuIsController: "true",
  2420  		tags.JujuModel:        env.Config().UUID(),
  2421  	})
  2422  
  2423  	// Test the instance id is correctly recorded for the bootstrap node.
  2424  	// Check that ControllerInstances returns the id of the bootstrap machine.
  2425  	instanceIds, err := env.ControllerInstances(suite.callCtx, suite.controllerUUID)
  2426  	c.Assert(err, jc.ErrorIsNil)
  2427  	c.Assert(instanceIds, gc.HasLen, 1)
  2428  	insts, err := env.AllRunningInstances(suite.callCtx)
  2429  	c.Assert(err, jc.ErrorIsNil)
  2430  	c.Assert(insts, gc.HasLen, 1)
  2431  	c.Check(insts[0].Id(), gc.Equals, instanceIds[0])
  2432  
  2433  	node1 := newFakeMachine("victor", arch.HostArch(), "Deployed")
  2434  	node1.hostname = "host1"
  2435  	node1.cpuCount = 1
  2436  	node1.memory = 1024
  2437  	node1.zoneName = "test_zone"
  2438  	controller.allocateMachine = node1
  2439  
  2440  	instance, hc := jujutesting.AssertStartInstanceWithConstraints(
  2441  		c,
  2442  		env,
  2443  		suite.callCtx,
  2444  		suite.controllerUUID,
  2445  		"1",
  2446  		constraints.Value{
  2447  			ImageID: stringp("ubuntu-bf2"),
  2448  		})
  2449  	c.Check(instance, gc.NotNil)
  2450  	c.Assert(hc, gc.NotNil)
  2451  	c.Check(hc.String(), gc.Equals, fmt.Sprintf("arch=%s cores=1 mem=1024M availability-zone=test_zone", arch.HostArch()))
  2452  
  2453  	node1.Stub.CheckCallNames(c, "Start", "SetOwnerData")
  2454  	startArgs, ok := node1.Stub.Calls()[0].Args[0].(gomaasapi.StartArgs)
  2455  	c.Assert(ok, jc.IsTrue)
  2456  
  2457  	c.Assert(startArgs.DistroSeries, gc.Equals, "ubuntu-bf2")
  2458  
  2459  	decodedUserData, err := decodeUserData(startArgs.UserData)
  2460  	c.Assert(err, jc.ErrorIsNil)
  2461  	info := machineInfo{"host1"}
  2462  	cloudcfg, err := cloudinit.New("ubuntu")
  2463  	c.Assert(err, jc.ErrorIsNil)
  2464  	cloudinitRunCmd, err := info.cloudinitRunCmd(cloudcfg)
  2465  	c.Assert(err, jc.ErrorIsNil)
  2466  	data, err := goyaml.Marshal(cloudinitRunCmd)
  2467  	c.Assert(err, jc.ErrorIsNil)
  2468  	c.Check(string(decodedUserData), jc.Contains, string(data))
  2469  
  2470  	// Trash the tools and try to start another instance.
  2471  	suite.PatchValue(&envtools.DefaultBaseURL, "")
  2472  	instance, _, _, err = jujutesting.StartInstance(env, suite.callCtx, suite.controllerUUID, "2")
  2473  	c.Check(instance, gc.IsNil)
  2474  	c.Check(err, jc.Satisfies, errors.IsNotFound)
  2475  }
  2476  
  2477  func (suite *maasEnvironSuite) TestControllerInstances(c *gc.C) {
  2478  	controller := newFakeControllerWithErrors(gomaasapi.NewNoMatchError("state"))
  2479  	env := suite.makeEnviron(c, controller)
  2480  	_, err := env.ControllerInstances(suite.callCtx, suite.controllerUUID)
  2481  	c.Assert(err, gc.Equals, environs.ErrNotBootstrapped)
  2482  
  2483  	controller.machinesArgsCheck = func(args gomaasapi.MachinesArgs) {
  2484  		c.Assert(args, gc.DeepEquals, gomaasapi.MachinesArgs{
  2485  			OwnerData: map[string]string{
  2486  				tags.JujuIsController: "true",
  2487  				tags.JujuController:   suite.controllerUUID,
  2488  			},
  2489  		})
  2490  	}
  2491  
  2492  	tests := [][]instance.Id{{"inst-0"}, {"inst-0", "inst-1"}}
  2493  	for _, expected := range tests {
  2494  		controller.machines = make([]gomaasapi.Machine, len(expected))
  2495  		for i := range expected {
  2496  			controller.machines[i] = newFakeMachine(string(expected[i]), "", "")
  2497  		}
  2498  		controllerInstances, err := env.ControllerInstances(suite.callCtx, suite.controllerUUID)
  2499  		c.Assert(err, jc.ErrorIsNil)
  2500  		c.Assert(controllerInstances, jc.SameContents, expected)
  2501  	}
  2502  }
  2503  
  2504  func (suite *maasEnvironSuite) TestControllerInstancesInvalidCredential(c *gc.C) {
  2505  	controller := &fakeController{
  2506  		machinesError: gomaasapi.NewPermissionError("fail auth here"),
  2507  	}
  2508  	env := suite.makeEnviron(c, controller)
  2509  
  2510  	c.Assert(suite.invalidCredential, jc.IsFalse)
  2511  	_, err := env.ControllerInstances(suite.callCtx, suite.controllerUUID)
  2512  	c.Assert(err, gc.NotNil)
  2513  	c.Assert(suite.invalidCredential, jc.IsTrue)
  2514  }
  2515  
  2516  func (suite *maasEnvironSuite) TestDestroy(c *gc.C) {
  2517  	file1 := &fakeFile{name: coretesting.ModelTag.Id() + "-provider-state"}
  2518  	file2 := &fakeFile{name: coretesting.ModelTag.Id() + "-horace"}
  2519  	controller := newFakeControllerWithFiles(file1, file2)
  2520  	controller.machines = []gomaasapi.Machine{&fakeMachine{systemID: "pete"}}
  2521  	env := suite.makeEnviron(c, controller)
  2522  	err := env.Destroy(suite.callCtx)
  2523  	c.Check(err, jc.ErrorIsNil)
  2524  
  2525  	controller.Stub.CheckCallNames(c, "ReleaseMachines", "GetFile", "Files", "GetFile", "GetFile")
  2526  	// Instances have been stopped.
  2527  	controller.Stub.CheckCall(c, 0, "ReleaseMachines", gomaasapi.ReleaseMachinesArgs{
  2528  		SystemIDs: []string{"pete"},
  2529  		Comment:   "Released by Juju MAAS provider",
  2530  	})
  2531  
  2532  	// Files have been cleaned up.
  2533  	c.Check(file1.deleted, jc.IsTrue)
  2534  	c.Check(file2.deleted, jc.IsTrue)
  2535  }
  2536  
  2537  func (suite *maasEnvironSuite) TestBootstrapFailsIfNoTools(c *gc.C) {
  2538  	env := suite.makeEnviron(c, newFakeController())
  2539  	vers := version.MustParse("1.2.3")
  2540  	err := bootstrap.Bootstrap(envtesting.BootstrapTODOContext(c), env,
  2541  		suite.callCtx, bootstrap.BootstrapParams{
  2542  			ControllerConfig: coretesting.FakeControllerConfig(),
  2543  			AdminSecret:      jujutesting.AdminSecret,
  2544  			CAPrivateKey:     coretesting.CAKey,
  2545  			// Disable auto-uploading by setting the agent version
  2546  			// to something that's not the current version.
  2547  			AgentVersion:            &vers,
  2548  			SupportedBootstrapBases: coretesting.FakeSupportedJujuBases,
  2549  			DialOpts: environs.BootstrapDialOpts{
  2550  				Timeout: coretesting.LongWait,
  2551  			},
  2552  		})
  2553  	c.Check(err, gc.ErrorMatches, "Juju cannot bootstrap because no agent binaries are available for your model(.|\n)*")
  2554  }
  2555  
  2556  func (suite *maasEnvironSuite) TestBootstrapFailsIfNoNodes(c *gc.C) {
  2557  	suite.setupFakeTools(c)
  2558  	controller := newFakeController()
  2559  	controller.allocateMachineError = gomaasapi.NewNoMatchError("oops")
  2560  	env := suite.makeEnviron(c, controller)
  2561  	err := bootstrap.Bootstrap(envtesting.BootstrapTODOContext(c), env,
  2562  		suite.callCtx, bootstrap.BootstrapParams{
  2563  			ControllerConfig:        coretesting.FakeControllerConfig(),
  2564  			AdminSecret:             jujutesting.AdminSecret,
  2565  			CAPrivateKey:            coretesting.CAKey,
  2566  			SupportedBootstrapBases: coretesting.FakeSupportedJujuBases,
  2567  			DialOpts: environs.BootstrapDialOpts{
  2568  				Timeout: coretesting.LongWait,
  2569  			},
  2570  		})
  2571  	// Since there are no nodes, the attempt to allocate one returns a
  2572  	// 409: Conflict.
  2573  	c.Check(err, gc.ErrorMatches, "(?ms)cannot start bootstrap instance in any availability zone \\(mossack, fonseca\\).*")
  2574  }
  2575  
  2576  func (suite *maasEnvironSuite) TestGetToolsMetadataSources(c *gc.C) {
  2577  	ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory())
  2578  	// Add a dummy file to storage so we can use that to check the
  2579  	// obtained source later.
  2580  	env := suite.makeEnviron(c, newFakeControllerWithFiles(
  2581  		&fakeFile{name: coretesting.ModelTag.Id() + "-tools/filename", contents: makeRandomBytes(10)},
  2582  	))
  2583  	sources, err := envtools.GetMetadataSources(env, ss)
  2584  	c.Assert(err, jc.ErrorIsNil)
  2585  	c.Assert(sources, gc.HasLen, 0)
  2586  }
  2587  
  2588  func (suite *maasEnvironSuite) TestConstraintsValidator(c *gc.C) {
  2589  	controller := newFakeController()
  2590  	controller.bootResources = []gomaasapi.BootResource{&fakeBootResource{name: "jammy", architecture: "amd64"}}
  2591  	env := suite.makeEnviron(c, controller)
  2592  	validator, err := env.ConstraintsValidator(suite.callCtx)
  2593  	c.Assert(err, jc.ErrorIsNil)
  2594  	cons := constraints.MustParse("arch=amd64 cpu-power=10 instance-type=foo virt-type=kvm")
  2595  	unsupported, err := validator.Validate(cons)
  2596  	c.Assert(err, jc.ErrorIsNil)
  2597  	c.Assert(unsupported, jc.SameContents, []string{"cpu-power", "instance-type", "virt-type"})
  2598  }
  2599  
  2600  func (suite *maasEnvironSuite) TestConstraintsValidatorWithUnsupportedArch(c *gc.C) {
  2601  	controller := newFakeController()
  2602  	controller.bootResources = []gomaasapi.BootResource{
  2603  		&fakeBootResource{name: "jammy", architecture: "i386"},
  2604  		&fakeBootResource{name: "jammy", architecture: "amd64"},
  2605  	}
  2606  	env := suite.makeEnviron(c, controller)
  2607  	validator, err := env.ConstraintsValidator(suite.callCtx)
  2608  	c.Assert(err, jc.ErrorIsNil)
  2609  	cons := constraints.MustParse("arch=amd64 cpu-power=10 instance-type=foo virt-type=kvm")
  2610  	unsupported, err := validator.Validate(cons)
  2611  	c.Assert(err, jc.ErrorIsNil)
  2612  	c.Assert(unsupported, jc.SameContents, []string{"cpu-power", "instance-type", "virt-type"})
  2613  }
  2614  
  2615  func (suite *maasEnvironSuite) TestConstraintsValidatorInvalidCredential(c *gc.C) {
  2616  	controller := &fakeController{
  2617  		bootResources:      []gomaasapi.BootResource{&fakeBootResource{name: "jammy", architecture: "amd64"}},
  2618  		bootResourcesError: gomaasapi.NewPermissionError("fail auth here"),
  2619  	}
  2620  	env := suite.makeEnviron(c, controller)
  2621  	c.Assert(suite.invalidCredential, jc.IsFalse)
  2622  	_, err := env.ConstraintsValidator(suite.callCtx)
  2623  	c.Assert(err, gc.NotNil)
  2624  	c.Assert(suite.invalidCredential, jc.IsTrue)
  2625  }
  2626  
  2627  func (suite *maasEnvironSuite) TestDomainsInvalidCredential(c *gc.C) {
  2628  	controller := &fakeController{
  2629  		domainsError: gomaasapi.NewPermissionError("fail auth here"),
  2630  	}
  2631  	env := suite.makeEnviron(c, controller)
  2632  	c.Assert(suite.invalidCredential, jc.IsFalse)
  2633  	_, err := env.Domains(suite.callCtx)
  2634  	c.Assert(err, gc.NotNil)
  2635  	c.Assert(suite.invalidCredential, jc.IsTrue)
  2636  }
  2637  
  2638  func (suite *maasEnvironSuite) TestConstraintsValidatorVocab(c *gc.C) {
  2639  	controller := newFakeController()
  2640  	controller.bootResources = []gomaasapi.BootResource{
  2641  		&fakeBootResource{name: "jammy", architecture: "amd64"},
  2642  		&fakeBootResource{name: "focal", architecture: "arm64"},
  2643  	}
  2644  	env := suite.makeEnviron(c, controller)
  2645  	validator, err := env.ConstraintsValidator(suite.callCtx)
  2646  	c.Assert(err, jc.ErrorIsNil)
  2647  	cons := constraints.MustParse("arch=ppc64el")
  2648  	_, err = validator.Validate(cons)
  2649  	c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=ppc64el\nvalid values are: \\[amd64 arm64\\]")
  2650  }
  2651  
  2652  func (suite *maasEnvironSuite) TestReleaseContainerAddresses(c *gc.C) {
  2653  	dev1 := newFakeDevice("a", "eleven")
  2654  	dev2 := newFakeDevice("b", "will")
  2655  	controller := newFakeController()
  2656  	controller.devices = []gomaasapi.Device{dev1, dev2}
  2657  
  2658  	env := suite.makeEnviron(c, controller)
  2659  	err := env.ReleaseContainerAddresses(suite.callCtx, []network.ProviderInterfaceInfo{
  2660  		{HardwareAddress: "will"},
  2661  		{HardwareAddress: "dustin"},
  2662  		{HardwareAddress: "eleven"},
  2663  	})
  2664  	c.Assert(err, jc.ErrorIsNil)
  2665  
  2666  	args, ok := getArgs(c, controller.Calls(), 0, 0).(gomaasapi.DevicesArgs)
  2667  	c.Assert(ok, jc.IsTrue)
  2668  	expected := gomaasapi.DevicesArgs{MACAddresses: []string{"will", "dustin", "eleven"}}
  2669  	c.Assert(args, gc.DeepEquals, expected)
  2670  
  2671  	dev1.CheckCallNames(c, "Delete")
  2672  	dev2.CheckCallNames(c, "Delete")
  2673  }
  2674  
  2675  func (suite *maasEnvironSuite) TestReleaseContainerAddresses_HandlesDupes(c *gc.C) {
  2676  	dev1 := newFakeDevice("a", "eleven")
  2677  	controller := newFakeController()
  2678  	controller.devices = []gomaasapi.Device{dev1, dev1}
  2679  
  2680  	env := suite.makeEnviron(c, controller)
  2681  	err := env.ReleaseContainerAddresses(suite.callCtx, []network.ProviderInterfaceInfo{
  2682  		{HardwareAddress: "will"},
  2683  		{HardwareAddress: "eleven"},
  2684  	})
  2685  	c.Assert(err, jc.ErrorIsNil)
  2686  
  2687  	args, ok := getArgs(c, controller.Calls(), 0, 0).(gomaasapi.DevicesArgs)
  2688  	c.Assert(ok, jc.IsTrue)
  2689  	expected := gomaasapi.DevicesArgs{MACAddresses: []string{"will", "eleven"}}
  2690  	c.Assert(args, gc.DeepEquals, expected)
  2691  
  2692  	dev1.CheckCallNames(c, "Delete")
  2693  }
  2694  
  2695  func (suite *maasEnvironSuite) TestReleaseContainerAddressesErrorGettingDevices(c *gc.C) {
  2696  	controller := newFakeControllerWithErrors(errors.New("Everything done broke"))
  2697  	env := suite.makeEnviron(c, controller)
  2698  	err := env.ReleaseContainerAddresses(suite.callCtx, []network.ProviderInterfaceInfo{{HardwareAddress: "anything"}})
  2699  	c.Assert(err, gc.ErrorMatches, "Everything done broke")
  2700  }
  2701  
  2702  func (suite *maasEnvironSuite) TestReleaseContainerAddressesErrorDeletingDevice(c *gc.C) {
  2703  	dev1 := newFakeDevice("a", "eleven")
  2704  	dev1.systemID = "hopper"
  2705  	dev1.SetErrors(errors.New("don't delete me"))
  2706  	controller := newFakeController()
  2707  	controller.devices = []gomaasapi.Device{dev1}
  2708  
  2709  	env := suite.makeEnviron(c, controller)
  2710  	err := env.ReleaseContainerAddresses(suite.callCtx, []network.ProviderInterfaceInfo{
  2711  		{HardwareAddress: "eleven"},
  2712  	})
  2713  	c.Assert(err, gc.ErrorMatches, "deleting device hopper: don't delete me")
  2714  
  2715  	_, ok := getArgs(c, controller.Calls(), 0, 0).(gomaasapi.DevicesArgs)
  2716  	c.Assert(ok, jc.IsTrue)
  2717  
  2718  	dev1.CheckCallNames(c, "Delete")
  2719  }
  2720  
  2721  func (suite *maasEnvironSuite) TestAdoptResources(c *gc.C) {
  2722  	machine1 := newFakeMachine("big-fig-wasp", "gaudi", "good")
  2723  	machine2 := newFakeMachine("robot-stop", "hundertwasser", "fine")
  2724  	machine3 := newFakeMachine("gamma-knife", "von-neumann", "acceptable")
  2725  	controller := newFakeController()
  2726  	controller.machines = append(controller.machines, machine1, machine3)
  2727  	env := suite.makeEnviron(c, controller)
  2728  
  2729  	err := env.AdoptResources(suite.callCtx, "some-other-controller", version.MustParse("1.2.3"))
  2730  	c.Assert(err, jc.ErrorIsNil)
  2731  
  2732  	machine1.CheckCallNames(c, "SetOwnerData")
  2733  	c.Assert(machine1.Calls()[0].Args[0], gc.DeepEquals, map[string]string{
  2734  		tags.JujuController: "some-other-controller",
  2735  	})
  2736  	machine2.CheckCallNames(c)
  2737  	machine3.CheckCallNames(c, "SetOwnerData")
  2738  	c.Assert(machine3.Calls()[0].Args[0], gc.DeepEquals, map[string]string{
  2739  		tags.JujuController: "some-other-controller",
  2740  	})
  2741  }
  2742  
  2743  func (suite *maasEnvironSuite) TestAdoptResourcesError(c *gc.C) {
  2744  	machine1 := newFakeMachine("evil-death-roll", "frank-lloyd-wright", "ok")
  2745  	machine2 := newFakeMachine("people-vultures", "gehry", "adequate")
  2746  	controller := newFakeController()
  2747  	controller.machines = append(controller.machines, machine1, machine2)
  2748  	env := suite.makeEnviron(c, controller)
  2749  
  2750  	machine1.SetErrors(errors.New("blorp"))
  2751  
  2752  	err := env.AdoptResources(suite.callCtx, "some-other-controller", version.MustParse("3.2.1"))
  2753  	c.Assert(err, gc.ErrorMatches, `failed to update controller for some instances: \[evil-death-roll\]`)
  2754  
  2755  	machine1.CheckCallNames(c, "SetOwnerData")
  2756  	c.Assert(machine1.Calls()[0].Args[0], gc.DeepEquals, map[string]string{
  2757  		tags.JujuController: "some-other-controller",
  2758  	})
  2759  	machine2.CheckCallNames(c, "SetOwnerData")
  2760  	c.Assert(machine2.Calls()[0].Args[0], gc.DeepEquals, map[string]string{
  2761  		tags.JujuController: "some-other-controller",
  2762  	})
  2763  }
  2764  
  2765  func newFakeDevice(systemID, macAddress string) *fakeDevice {
  2766  	return &fakeDevice{
  2767  		Stub:     &testing.Stub{},
  2768  		systemID: systemID,
  2769  		interface_: &fakeInterface{
  2770  			Stub:       &testing.Stub{},
  2771  			macAddress: macAddress,
  2772  		},
  2773  	}
  2774  }
  2775  
  2776  // makeRandomBytes returns an array of arbitrary byte values.
  2777  func makeRandomBytes(length int) []byte {
  2778  	data := make([]byte, length)
  2779  	for index := range data {
  2780  		data[index] = byte(rand.Intn(256))
  2781  	}
  2782  	return data
  2783  }
  2784  
  2785  func decodeUserData(userData string) ([]byte, error) {
  2786  	data, err := base64.StdEncoding.DecodeString(userData)
  2787  	if err != nil {
  2788  		return []byte(""), err
  2789  	}
  2790  	return utils.Gunzip(data)
  2791  }