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