github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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  	"bytes"
     8  	"fmt"
     9  	"net/http"
    10  
    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/utils/set"
    17  	gc "gopkg.in/check.v1"
    18  	goyaml "gopkg.in/yaml.v2"
    19  
    20  	"github.com/juju/juju/cloudconfig/cloudinit"
    21  	"github.com/juju/juju/constraints"
    22  	"github.com/juju/juju/environs"
    23  	"github.com/juju/juju/environs/bootstrap"
    24  	"github.com/juju/juju/environs/config"
    25  	envjujutesting "github.com/juju/juju/environs/testing"
    26  	envtools "github.com/juju/juju/environs/tools"
    27  	"github.com/juju/juju/instance"
    28  	jujutesting "github.com/juju/juju/juju/testing"
    29  	"github.com/juju/juju/network"
    30  	"github.com/juju/juju/provider/common"
    31  	corejujutesting "github.com/juju/juju/testing"
    32  	jujuversion "github.com/juju/juju/version"
    33  )
    34  
    35  type maas2EnvironSuite struct {
    36  	maas2Suite
    37  }
    38  
    39  var _ = gc.Suite(&maas2EnvironSuite{})
    40  
    41  func (suite *maas2EnvironSuite) getEnvWithServer(c *gc.C) (*maasEnviron, error) {
    42  	testServer := gomaasapi.NewSimpleServer()
    43  	testServer.AddGetResponse("/api/2.0/version/", http.StatusOK, maas2VersionResponse)
    44  	testServer.AddGetResponse("/api/2.0/users/?op=whoami", http.StatusOK, "{}")
    45  	// Weirdly, rather than returning a 404 when the version is
    46  	// unknown, MAAS2 returns some HTML (the login page).
    47  	testServer.AddGetResponse("/api/1.0/version/", http.StatusOK, "<html></html>")
    48  	testServer.Start()
    49  	suite.AddCleanup(func(*gc.C) { testServer.Close() })
    50  	testAttrs := corejujutesting.Attrs{}
    51  	for k, v := range maasEnvAttrs {
    52  		testAttrs[k] = v
    53  	}
    54  	testAttrs["maas-server"] = testServer.Server.URL
    55  	attrs := corejujutesting.FakeConfig().Merge(testAttrs)
    56  	cfg, err := config.New(config.NoDefaults, attrs)
    57  	c.Assert(err, jc.ErrorIsNil)
    58  	return NewEnviron(cfg)
    59  }
    60  
    61  func (suite *maas2EnvironSuite) TestNewEnvironWithController(c *gc.C) {
    62  	env, err := suite.getEnvWithServer(c)
    63  	c.Assert(err, jc.ErrorIsNil)
    64  	c.Assert(env, gc.NotNil)
    65  }
    66  
    67  func (suite *maas2EnvironSuite) TestSupportedArchitectures(c *gc.C) {
    68  	controller := &fakeController{
    69  		bootResources: []gomaasapi.BootResource{
    70  			&fakeBootResource{name: "wily", architecture: "amd64/blah"},
    71  			&fakeBootResource{name: "wily", architecture: "amd64/something"},
    72  			&fakeBootResource{name: "xenial", architecture: "arm/somethingelse"},
    73  		},
    74  	}
    75  	env := suite.makeEnviron(c, controller)
    76  	result, err := env.SupportedArchitectures()
    77  	c.Assert(err, jc.ErrorIsNil)
    78  	c.Assert(result, gc.DeepEquals, []string{"amd64", "arm"})
    79  }
    80  
    81  func (suite *maas2EnvironSuite) TestSupportedArchitecturesError(c *gc.C) {
    82  	env := suite.makeEnviron(c, &fakeController{bootResourcesError: errors.New("Something terrible!")})
    83  	_, err := env.SupportedArchitectures()
    84  	c.Assert(err, gc.ErrorMatches, "Something terrible!")
    85  }
    86  
    87  func (suite *maas2EnvironSuite) injectControllerWithSpacesAndCheck(c *gc.C, spaces []gomaasapi.Space, expected gomaasapi.AllocateMachineArgs) *maasEnviron {
    88  	var env *maasEnviron
    89  	check := func(args gomaasapi.AllocateMachineArgs) {
    90  		expected.AgentName = env.ecfg().maasAgentName()
    91  		c.Assert(args, gc.DeepEquals, expected)
    92  	}
    93  	controller := &fakeController{
    94  		allocateMachineArgsCheck: check,
    95  		allocateMachine:          newFakeMachine("Bruce Sterling", arch.HostArch(), ""),
    96  		allocateMachineMatches: gomaasapi.ConstraintMatches{
    97  			Storage: map[string][]gomaasapi.BlockDevice{},
    98  		},
    99  		spaces: spaces,
   100  	}
   101  	suite.injectController(controller)
   102  	suite.setupFakeTools(c)
   103  	env = suite.makeEnviron(c, nil)
   104  	return env
   105  }
   106  
   107  func (suite *maas2EnvironSuite) makeEnvironWithMachines(c *gc.C, expectedSystemIDs []string, returnSystemIDs []string) *maasEnviron {
   108  	var env *maasEnviron
   109  	checkArgs := func(args gomaasapi.MachinesArgs) {
   110  		c.Check(args.SystemIDs, gc.DeepEquals, expectedSystemIDs)
   111  		c.Check(args.AgentName, gc.Equals, env.ecfg().maasAgentName())
   112  	}
   113  	machines := make([]gomaasapi.Machine, len(returnSystemIDs))
   114  	for index, id := range returnSystemIDs {
   115  		machines[index] = &fakeMachine{systemID: id}
   116  	}
   117  	controller := &fakeController{
   118  		machines:          machines,
   119  		machinesArgsCheck: checkArgs,
   120  	}
   121  	env = suite.makeEnviron(c, controller)
   122  	return env
   123  }
   124  
   125  func (suite *maas2EnvironSuite) TestAllInstances(c *gc.C) {
   126  	env := suite.makeEnvironWithMachines(
   127  		c, []string{}, []string{"tuco", "tio", "gus"},
   128  	)
   129  	result, err := env.AllInstances()
   130  	c.Assert(err, jc.ErrorIsNil)
   131  	expectedMachines := set.NewStrings("tuco", "tio", "gus")
   132  	actualMachines := set.NewStrings()
   133  	for _, instance := range result {
   134  		actualMachines.Add(string(instance.Id()))
   135  	}
   136  	c.Assert(actualMachines, gc.DeepEquals, expectedMachines)
   137  }
   138  
   139  func (suite *maas2EnvironSuite) TestAllInstancesError(c *gc.C) {
   140  	controller := &fakeController{machinesError: errors.New("Something terrible!")}
   141  	env := suite.makeEnviron(c, controller)
   142  	_, err := env.AllInstances()
   143  	c.Assert(err, gc.ErrorMatches, "Something terrible!")
   144  }
   145  
   146  func (suite *maas2EnvironSuite) TestInstances(c *gc.C) {
   147  	env := suite.makeEnvironWithMachines(
   148  		c, []string{"jake", "bonnibel"}, []string{"jake", "bonnibel"},
   149  	)
   150  	result, err := env.Instances([]instance.Id{"jake", "bonnibel"})
   151  	c.Assert(err, jc.ErrorIsNil)
   152  	expectedMachines := set.NewStrings("jake", "bonnibel")
   153  	actualMachines := set.NewStrings()
   154  	for _, machine := range result {
   155  		actualMachines.Add(string(machine.Id()))
   156  	}
   157  	c.Assert(actualMachines, gc.DeepEquals, expectedMachines)
   158  }
   159  
   160  func (suite *maas2EnvironSuite) TestInstancesPartialResult(c *gc.C) {
   161  	env := suite.makeEnvironWithMachines(
   162  		c, []string{"jake", "bonnibel"}, []string{"tuco", "bonnibel"},
   163  	)
   164  	result, err := env.Instances([]instance.Id{"jake", "bonnibel"})
   165  	c.Check(err, gc.Equals, environs.ErrPartialInstances)
   166  	c.Assert(result, gc.HasLen, 2)
   167  	c.Assert(result[0], gc.IsNil)
   168  	c.Assert(result[1].Id(), gc.Equals, instance.Id("bonnibel"))
   169  }
   170  
   171  func (suite *maas2EnvironSuite) TestAvailabilityZones(c *gc.C) {
   172  	controller := &fakeController{
   173  		zones: []gomaasapi.Zone{
   174  			&fakeZone{name: "mossack"},
   175  			&fakeZone{name: "fonseca"},
   176  		},
   177  	}
   178  	env := suite.makeEnviron(c, controller)
   179  	result, err := env.AvailabilityZones()
   180  	c.Assert(err, jc.ErrorIsNil)
   181  	expectedZones := set.NewStrings("mossack", "fonseca")
   182  	actualZones := set.NewStrings()
   183  	for _, zone := range result {
   184  		actualZones.Add(zone.Name())
   185  	}
   186  	c.Assert(actualZones, gc.DeepEquals, expectedZones)
   187  }
   188  
   189  func (suite *maas2EnvironSuite) TestAvailabilityZonesError(c *gc.C) {
   190  	controller := &fakeController{
   191  		zonesError: errors.New("a bad thing"),
   192  	}
   193  	env := suite.makeEnviron(c, controller)
   194  	_, err := env.AvailabilityZones()
   195  	c.Assert(err, gc.ErrorMatches, "a bad thing")
   196  }
   197  
   198  func (suite *maas2EnvironSuite) TestSpaces(c *gc.C) {
   199  	controller := &fakeController{
   200  		spaces: []gomaasapi.Space{
   201  			fakeSpace{
   202  				name: "pepper",
   203  				id:   1234,
   204  			},
   205  			fakeSpace{
   206  				name: "freckles",
   207  				id:   4567,
   208  				subnets: []gomaasapi.Subnet{
   209  					fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24"},
   210  					fakeSubnet{id: 98, vlan: fakeVLAN{vid: 67}, cidr: "192.168.11.0/24"},
   211  				},
   212  			},
   213  		},
   214  	}
   215  	env := suite.makeEnviron(c, controller)
   216  	result, err := env.Spaces()
   217  	c.Assert(err, jc.ErrorIsNil)
   218  	c.Assert(result, gc.HasLen, 1)
   219  	c.Assert(result[0].Name, gc.Equals, "freckles")
   220  	c.Assert(result[0].ProviderId, gc.Equals, network.Id("4567"))
   221  	subnets := result[0].Subnets
   222  	c.Assert(subnets, gc.HasLen, 2)
   223  	c.Assert(subnets[0].ProviderId, gc.Equals, network.Id("99"))
   224  	c.Assert(subnets[0].VLANTag, gc.Equals, 66)
   225  	c.Assert(subnets[0].CIDR, gc.Equals, "192.168.10.0/24")
   226  	c.Assert(subnets[0].SpaceProviderId, gc.Equals, network.Id("4567"))
   227  	c.Assert(subnets[1].ProviderId, gc.Equals, network.Id("98"))
   228  	c.Assert(subnets[1].VLANTag, gc.Equals, 67)
   229  	c.Assert(subnets[1].CIDR, gc.Equals, "192.168.11.0/24")
   230  	c.Assert(subnets[1].SpaceProviderId, gc.Equals, network.Id("4567"))
   231  }
   232  
   233  func (suite *maas2EnvironSuite) TestSpacesError(c *gc.C) {
   234  	controller := &fakeController{
   235  		spacesError: errors.New("Joe Manginiello"),
   236  	}
   237  	env := suite.makeEnviron(c, controller)
   238  	_, err := env.Spaces()
   239  	c.Assert(err, gc.ErrorMatches, "Joe Manginiello")
   240  }
   241  
   242  func collectReleaseArgs(controller *fakeController) []gomaasapi.ReleaseMachinesArgs {
   243  	args := []gomaasapi.ReleaseMachinesArgs{}
   244  	for _, call := range controller.Stub.Calls() {
   245  		if call.FuncName == "ReleaseMachines" {
   246  			args = append(args, call.Args[0].(gomaasapi.ReleaseMachinesArgs))
   247  		}
   248  	}
   249  	return args
   250  }
   251  
   252  func (suite *maas2EnvironSuite) TestStopInstancesReturnsIfParameterEmpty(c *gc.C) {
   253  	controller := newFakeController()
   254  	err := suite.makeEnviron(c, controller).StopInstances()
   255  	c.Check(err, jc.ErrorIsNil)
   256  	c.Assert(collectReleaseArgs(controller), gc.HasLen, 0)
   257  }
   258  
   259  func (suite *maas2EnvironSuite) TestStopInstancesStopsAndReleasesInstances(c *gc.C) {
   260  	// Return a cannot complete indicating that test1 is in the wrong state.
   261  	// The release operation will still release the others and succeed.
   262  	controller := newFakeControllerWithFiles(&fakeFile{name: "agent-prefix-provider-state"})
   263  	err := suite.makeEnviron(c, controller).StopInstances("test1", "test2", "test3")
   264  	c.Check(err, jc.ErrorIsNil)
   265  	args := collectReleaseArgs(controller)
   266  	c.Assert(args, gc.HasLen, 1)
   267  	c.Assert(args[0].SystemIDs, gc.DeepEquals, []string{"test1", "test2", "test3"})
   268  }
   269  
   270  func (suite *maas2EnvironSuite) TestStopInstancesIgnoresConflict(c *gc.C) {
   271  	// Return a cannot complete indicating that test1 is in the wrong state.
   272  	// The release operation will still release the others and succeed.
   273  	controller := newFakeControllerWithFiles(&fakeFile{name: "agent-prefix-provider-state"})
   274  	controller.SetErrors(gomaasapi.NewCannotCompleteError("test1 not allocated"))
   275  	err := suite.makeEnviron(c, controller).StopInstances("test1", "test2", "test3")
   276  	c.Check(err, jc.ErrorIsNil)
   277  
   278  	args := collectReleaseArgs(controller)
   279  	c.Assert(args, gc.HasLen, 1)
   280  	c.Assert(args[0].SystemIDs, gc.DeepEquals, []string{"test1", "test2", "test3"})
   281  }
   282  
   283  func (suite *maas2EnvironSuite) TestStopInstancesIgnoresMissingNodeAndRecurses(c *gc.C) {
   284  	controller := newFakeControllerWithFiles(&fakeFile{name: "agent-prefix-provider-state"})
   285  	controller.SetErrors(
   286  		gomaasapi.NewBadRequestError("no such machine: test1"),
   287  		gomaasapi.NewBadRequestError("no such machine: test1"),
   288  	)
   289  	err := suite.makeEnviron(c, controller).StopInstances("test1", "test2", "test3")
   290  	c.Check(err, jc.ErrorIsNil)
   291  	args := collectReleaseArgs(controller)
   292  	c.Assert(args, gc.HasLen, 4)
   293  	c.Assert(args[0].SystemIDs, gc.DeepEquals, []string{"test1", "test2", "test3"})
   294  	c.Assert(args[1].SystemIDs, gc.DeepEquals, []string{"test1"})
   295  	c.Assert(args[2].SystemIDs, gc.DeepEquals, []string{"test2"})
   296  	c.Assert(args[3].SystemIDs, gc.DeepEquals, []string{"test3"})
   297  }
   298  
   299  func (suite *maas2EnvironSuite) checkStopInstancesFails(c *gc.C, withError error) {
   300  	controller := newFakeControllerWithFiles(&fakeFile{name: "agent-prefix-provider-state"})
   301  	controller.SetErrors(withError)
   302  	err := suite.makeEnviron(c, controller).StopInstances("test1", "test2", "test3")
   303  	c.Check(err, gc.ErrorMatches, fmt.Sprintf("cannot release nodes: %s", withError))
   304  	// Only tries once.
   305  	c.Assert(collectReleaseArgs(controller), gc.HasLen, 1)
   306  }
   307  
   308  func (suite *maas2EnvironSuite) TestStopInstancesReturnsUnexpectedMAASError(c *gc.C) {
   309  	suite.checkStopInstancesFails(c, gomaasapi.NewNoMatchError("Something else bad!"))
   310  }
   311  
   312  func (suite *maas2EnvironSuite) TestStopInstancesReturnsUnexpectedError(c *gc.C) {
   313  	suite.checkStopInstancesFails(c, errors.New("Something completely unexpected!"))
   314  }
   315  
   316  func (suite *maas2EnvironSuite) TestStartInstanceError(c *gc.C) {
   317  	suite.injectController(&fakeController{
   318  		allocateMachineError: errors.New("Charles Babbage"),
   319  	})
   320  	env := suite.makeEnviron(c, nil)
   321  	_, err := env.StartInstance(environs.StartInstanceParams{})
   322  	c.Assert(err, gc.ErrorMatches, ".* cannot run instance: Charles Babbage")
   323  }
   324  
   325  func (suite *maas2EnvironSuite) TestStartInstance(c *gc.C) {
   326  	env := suite.injectControllerWithSpacesAndCheck(c, nil, gomaasapi.AllocateMachineArgs{})
   327  
   328  	params := environs.StartInstanceParams{}
   329  	result, err := jujutesting.StartInstanceWithParams(env, "1", params)
   330  	c.Assert(err, jc.ErrorIsNil)
   331  	c.Assert(result.Instance.Id(), gc.Equals, instance.Id("Bruce Sterling"))
   332  }
   333  
   334  func (suite *maas2EnvironSuite) TestStartInstanceParams(c *gc.C) {
   335  	var env *maasEnviron
   336  	suite.injectController(&fakeController{
   337  		allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) {
   338  			c.Assert(args, gc.DeepEquals, gomaasapi.AllocateMachineArgs{
   339  				AgentName: env.ecfg().maasAgentName(),
   340  				Zone:      "foo",
   341  				MinMemory: 8192,
   342  			})
   343  		},
   344  		allocateMachine: newFakeMachine("Bruce Sterling", arch.HostArch(), ""),
   345  		allocateMachineMatches: gomaasapi.ConstraintMatches{
   346  			Storage: map[string][]gomaasapi.BlockDevice{},
   347  		},
   348  		zones: []gomaasapi.Zone{&fakeZone{name: "foo"}},
   349  	})
   350  	suite.setupFakeTools(c)
   351  	env = suite.makeEnviron(c, nil)
   352  	params := environs.StartInstanceParams{
   353  		Placement:   "zone=foo",
   354  		Constraints: constraints.MustParse("mem=8G"),
   355  	}
   356  	result, err := jujutesting.StartInstanceWithParams(env, "1", params)
   357  	c.Assert(err, jc.ErrorIsNil)
   358  	c.Assert(result.Instance.Id(), gc.Equals, instance.Id("Bruce Sterling"))
   359  }
   360  
   361  func (suite *maas2EnvironSuite) TestAcquireNodePassedAgentName(c *gc.C) {
   362  	var env *maasEnviron
   363  	suite.injectController(&fakeController{
   364  		allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) {
   365  			c.Assert(args, gc.DeepEquals, gomaasapi.AllocateMachineArgs{
   366  				AgentName: env.ecfg().maasAgentName()})
   367  		},
   368  		allocateMachine: &fakeMachine{
   369  			systemID:     "Bruce Sterling",
   370  			architecture: arch.HostArch(),
   371  		},
   372  	})
   373  	suite.setupFakeTools(c)
   374  	env = suite.makeEnviron(c, nil)
   375  
   376  	_, err := env.acquireNode2("", "", constraints.Value{}, nil, nil)
   377  
   378  	c.Check(err, jc.ErrorIsNil)
   379  }
   380  
   381  func (suite *maas2EnvironSuite) TestAcquireNodePassesPositiveAndNegativeTags(c *gc.C) {
   382  	var env *maasEnviron
   383  	expected := gomaasapi.AllocateMachineArgs{
   384  		Tags:    []string{"tag1", "tag3"},
   385  		NotTags: []string{"tag2", "tag4"},
   386  	}
   387  	env = suite.injectControllerWithSpacesAndCheck(c, nil, expected)
   388  	_, err := env.acquireNode2(
   389  		"", "",
   390  		constraints.Value{Tags: stringslicep("tag1", "^tag2", "tag3", "^tag4")},
   391  		nil, nil,
   392  	)
   393  	c.Check(err, jc.ErrorIsNil)
   394  }
   395  
   396  func getFourSpaces() []gomaasapi.Space {
   397  	return []gomaasapi.Space{
   398  		fakeSpace{
   399  			name:    "space-1",
   400  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24"}},
   401  			id:      5,
   402  		},
   403  		fakeSpace{
   404  			name:    "space-2",
   405  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 100, vlan: fakeVLAN{vid: 66}, cidr: "192.168.11.0/24"}},
   406  			id:      6,
   407  		},
   408  		fakeSpace{
   409  			name:    "space-3",
   410  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 101, vlan: fakeVLAN{vid: 66}, cidr: "192.168.12.0/24"}},
   411  			id:      7,
   412  		},
   413  		fakeSpace{
   414  			name:    "space-4",
   415  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 102, vlan: fakeVLAN{vid: 66}, cidr: "192.168.13.0/24"}},
   416  			id:      8,
   417  		},
   418  	}
   419  
   420  }
   421  
   422  func (suite *maas2EnvironSuite) TestAcquireNodePassesPositiveAndNegativeSpaces(c *gc.C) {
   423  	expected := gomaasapi.AllocateMachineArgs{
   424  		NotSpace: []string{"6", "8"},
   425  		Interfaces: []gomaasapi.InterfaceSpec{
   426  			{Label: "0", Space: "5"},
   427  			{Label: "1", Space: "7"},
   428  		},
   429  	}
   430  	env := suite.injectControllerWithSpacesAndCheck(c, getFourSpaces(), expected)
   431  
   432  	_, err := env.acquireNode2(
   433  		"", "",
   434  		constraints.Value{Spaces: stringslicep("space-1", "^space-2", "space-3", "^space-4")},
   435  		nil, nil,
   436  	)
   437  	c.Check(err, jc.ErrorIsNil)
   438  }
   439  
   440  func (suite *maas2EnvironSuite) TestAcquireNodeDisambiguatesNamedLabelsFromIndexedUpToALimit(c *gc.C) {
   441  	env := suite.injectControllerWithSpacesAndCheck(c, getFourSpaces(), gomaasapi.AllocateMachineArgs{})
   442  	var shortLimit uint = 0
   443  	suite.PatchValue(&numericLabelLimit, shortLimit)
   444  
   445  	_, err := env.acquireNode2(
   446  		"", "",
   447  		constraints.Value{Spaces: stringslicep("space-1", "^space-2", "space-3", "^space-4")},
   448  		[]interfaceBinding{{"0", "first-clash"}, {"1", "final-clash"}},
   449  		nil,
   450  	)
   451  	c.Assert(err, gc.ErrorMatches, `too many conflicting numeric labels, giving up.`)
   452  }
   453  
   454  func (suite *maas2EnvironSuite) TestAcquireNodeStorage(c *gc.C) {
   455  	var env *maasEnviron
   456  	var getStorage func() []gomaasapi.StorageSpec
   457  	suite.injectController(&fakeController{
   458  		allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) {
   459  			c.Assert(args, jc.DeepEquals, gomaasapi.AllocateMachineArgs{
   460  				AgentName: env.ecfg().maasAgentName(),
   461  				Storage:   getStorage(),
   462  			})
   463  		},
   464  		allocateMachine: &fakeMachine{
   465  			systemID:     "Bruce Sterling",
   466  			architecture: arch.HostArch(),
   467  		},
   468  	})
   469  	suite.setupFakeTools(c)
   470  	for i, test := range []struct {
   471  		volumes  []volumeInfo
   472  		expected []gomaasapi.StorageSpec
   473  	}{{
   474  		volumes:  nil,
   475  		expected: []gomaasapi.StorageSpec{},
   476  	}, {
   477  		volumes:  []volumeInfo{{"volume-1", 1234, nil}},
   478  		expected: []gomaasapi.StorageSpec{{"volume-1", 1234, nil}},
   479  	}, {
   480  		volumes:  []volumeInfo{{"", 1234, []string{"tag1", "tag2"}}},
   481  		expected: []gomaasapi.StorageSpec{{"", 1234, []string{"tag1", "tag2"}}},
   482  	}, {
   483  		volumes:  []volumeInfo{{"volume-1", 1234, []string{"tag1", "tag2"}}},
   484  		expected: []gomaasapi.StorageSpec{{"volume-1", 1234, []string{"tag1", "tag2"}}},
   485  	}, {
   486  		volumes: []volumeInfo{
   487  			{"volume-1", 1234, []string{"tag1", "tag2"}},
   488  			{"volume-2", 4567, []string{"tag1", "tag3"}},
   489  		},
   490  		expected: []gomaasapi.StorageSpec{
   491  			{"volume-1", 1234, []string{"tag1", "tag2"}},
   492  			{"volume-2", 4567, []string{"tag1", "tag3"}},
   493  		},
   494  	}} {
   495  		c.Logf("test #%d: volumes=%v", i, test.volumes)
   496  		getStorage = func() []gomaasapi.StorageSpec {
   497  			return test.expected
   498  		}
   499  		env = suite.makeEnviron(c, nil)
   500  		_, err := env.acquireNode2("", "", constraints.Value{}, nil, test.volumes)
   501  		c.Check(err, jc.ErrorIsNil)
   502  	}
   503  }
   504  
   505  func (suite *maas2EnvironSuite) TestAcquireNodeInterfaces(c *gc.C) {
   506  	var env *maasEnviron
   507  	var getNegatives func() []string
   508  	var getPositives func() []gomaasapi.InterfaceSpec
   509  	suite.injectController(&fakeController{
   510  		allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) {
   511  			c.Assert(args, gc.DeepEquals, gomaasapi.AllocateMachineArgs{
   512  				AgentName:  env.ecfg().maasAgentName(),
   513  				Interfaces: getPositives(),
   514  				NotSpace:   getNegatives(),
   515  			})
   516  		},
   517  		allocateMachine: &fakeMachine{
   518  			systemID:     "Bruce Sterling",
   519  			architecture: arch.HostArch(),
   520  		},
   521  		spaces: getTwoSpaces(),
   522  	})
   523  	suite.setupFakeTools(c)
   524  	// Add some constraints, including spaces to verify specified bindings
   525  	// always override any spaces constraints.
   526  	cons := constraints.Value{
   527  		Spaces: stringslicep("foo", "^bar"),
   528  	}
   529  	// In the tests below "space:5" means foo, "space:6" means bar.
   530  	for i, test := range []struct {
   531  		interfaces        []interfaceBinding
   532  		expectedPositives []gomaasapi.InterfaceSpec
   533  		expectedNegatives []string
   534  		expectedError     string
   535  	}{{ // without specified bindings, spaces constraints are used instead.
   536  		interfaces:        nil,
   537  		expectedPositives: []gomaasapi.InterfaceSpec{{"0", "2"}},
   538  		expectedNegatives: []string{"3"},
   539  		expectedError:     "",
   540  	}, {
   541  		interfaces:        []interfaceBinding{{"name-1", "space-1"}},
   542  		expectedPositives: []gomaasapi.InterfaceSpec{{"name-1", "space-1"}, {"0", "2"}},
   543  		expectedNegatives: []string{"3"},
   544  	}, {
   545  		interfaces: []interfaceBinding{
   546  			{"name-1", "7"},
   547  			{"name-2", "8"},
   548  			{"name-3", "9"},
   549  		},
   550  		expectedPositives: []gomaasapi.InterfaceSpec{{"name-1", "7"}, {"name-2", "8"}, {"name-3", "9"}, {"0", "2"}},
   551  		expectedNegatives: []string{"3"},
   552  	}, {
   553  		interfaces:    []interfaceBinding{{"", "anything"}},
   554  		expectedError: "interface bindings cannot have empty names",
   555  	}, {
   556  		interfaces:    []interfaceBinding{{"shared-db", "3"}},
   557  		expectedError: `negative space "bar" from constraints clashes with interface bindings`,
   558  	}, {
   559  		interfaces: []interfaceBinding{
   560  			{"shared-db", "1"},
   561  			{"db", "1"},
   562  		},
   563  		expectedPositives: []gomaasapi.InterfaceSpec{{"shared-db", "1"}, {"db", "1"}, {"0", "2"}},
   564  		expectedNegatives: []string{"3"},
   565  	}, {
   566  		interfaces:    []interfaceBinding{{"", ""}},
   567  		expectedError: "interface bindings cannot have empty names",
   568  	}, {
   569  		interfaces: []interfaceBinding{
   570  			{"valid", "ok"},
   571  			{"", "valid-but-ignored-space"},
   572  			{"valid-name-empty-space", ""},
   573  			{"", ""},
   574  		},
   575  		expectedError: "interface bindings cannot have empty names",
   576  	}, {
   577  		interfaces:    []interfaceBinding{{"foo", ""}},
   578  		expectedError: `invalid interface binding "foo": space provider ID is required`,
   579  	}, {
   580  		interfaces: []interfaceBinding{
   581  			{"bar", ""},
   582  			{"valid", "ok"},
   583  			{"", "valid-but-ignored-space"},
   584  			{"", ""},
   585  		},
   586  		expectedError: `invalid interface binding "bar": space provider ID is required`,
   587  	}, {
   588  		interfaces: []interfaceBinding{
   589  			{"dup-name", "1"},
   590  			{"dup-name", "2"},
   591  		},
   592  		expectedError: `duplicated interface binding "dup-name"`,
   593  	}, {
   594  		interfaces: []interfaceBinding{
   595  			{"valid-1", "0"},
   596  			{"dup-name", "1"},
   597  			{"dup-name", "2"},
   598  			{"valid-2", "3"},
   599  		},
   600  		expectedError: `duplicated interface binding "dup-name"`,
   601  	}} {
   602  		c.Logf("test #%d: interfaces=%v", i, test.interfaces)
   603  		env = suite.makeEnviron(c, nil)
   604  		getNegatives = func() []string {
   605  			return test.expectedNegatives
   606  		}
   607  		getPositives = func() []gomaasapi.InterfaceSpec {
   608  			return test.expectedPositives
   609  		}
   610  		_, err := env.acquireNode2("", "", cons, test.interfaces, nil)
   611  		if test.expectedError != "" {
   612  			c.Check(err, gc.ErrorMatches, test.expectedError)
   613  			c.Check(err, jc.Satisfies, errors.IsNotValid)
   614  			continue
   615  		}
   616  		c.Check(err, jc.ErrorIsNil)
   617  	}
   618  }
   619  
   620  func getTwoSpaces() []gomaasapi.Space {
   621  	return []gomaasapi.Space{
   622  		fakeSpace{
   623  			name:    "foo",
   624  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24"}},
   625  			id:      2,
   626  		},
   627  		fakeSpace{
   628  			name:    "bar",
   629  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 100, vlan: fakeVLAN{vid: 66}, cidr: "192.168.11.0/24"}},
   630  			id:      3,
   631  		},
   632  	}
   633  }
   634  
   635  func (suite *maas2EnvironSuite) TestAcquireNodeConvertsSpaceNames(c *gc.C) {
   636  	expected := gomaasapi.AllocateMachineArgs{
   637  		NotSpace:   []string{"3"},
   638  		Interfaces: []gomaasapi.InterfaceSpec{{Label: "0", Space: "2"}},
   639  	}
   640  	env := suite.injectControllerWithSpacesAndCheck(c, getTwoSpaces(), expected)
   641  	cons := constraints.Value{
   642  		Spaces: stringslicep("foo", "^bar"),
   643  	}
   644  	_, err := env.acquireNode2("", "", cons, nil, nil)
   645  	c.Assert(err, jc.ErrorIsNil)
   646  }
   647  
   648  func (suite *maas2EnvironSuite) TestAcquireNodeTranslatesSpaceNames(c *gc.C) {
   649  	expected := gomaasapi.AllocateMachineArgs{
   650  		NotSpace:   []string{"3"},
   651  		Interfaces: []gomaasapi.InterfaceSpec{{Label: "0", Space: "2"}},
   652  	}
   653  	env := suite.injectControllerWithSpacesAndCheck(c, getTwoSpaces(), expected)
   654  	cons := constraints.Value{
   655  		Spaces: stringslicep("foo-1", "^bar-3"),
   656  	}
   657  	_, err := env.acquireNode2("", "", cons, nil, nil)
   658  	c.Assert(err, jc.ErrorIsNil)
   659  }
   660  
   661  func (suite *maas2EnvironSuite) TestAcquireNodeUnrecognisedSpace(c *gc.C) {
   662  	suite.injectController(&fakeController{})
   663  	env := suite.makeEnviron(c, nil)
   664  	cons := constraints.Value{
   665  		Spaces: stringslicep("baz"),
   666  	}
   667  	_, err := env.acquireNode2("", "", cons, nil, nil)
   668  	c.Assert(err, gc.ErrorMatches, `unrecognised space in constraint "baz"`)
   669  }
   670  
   671  func (suite *maas2EnvironSuite) TestWaitForNodeDeploymentError(c *gc.C) {
   672  	machine := newFakeMachine("Bruce Sterling", arch.HostArch(), "")
   673  	controller := newFakeController()
   674  	controller.allocateMachine = machine
   675  	controller.allocateMachineMatches = gomaasapi.ConstraintMatches{
   676  		Storage: map[string][]gomaasapi.BlockDevice{},
   677  	}
   678  	controller.machines = []gomaasapi.Machine{machine}
   679  	suite.injectController(controller)
   680  	suite.setupFakeTools(c)
   681  	env := suite.makeEnviron(c, nil)
   682  	err := bootstrap.Bootstrap(envjujutesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   683  	c.Assert(err, gc.ErrorMatches, "bootstrap instance started but did not change to Deployed state.*")
   684  }
   685  
   686  func (suite *maas2EnvironSuite) TestWaitForNodeDeploymentSucceeds(c *gc.C) {
   687  	machine := newFakeMachine("Bruce Sterling", arch.HostArch(), "Deployed")
   688  	controller := newFakeController()
   689  	controller.allocateMachine = machine
   690  	controller.allocateMachineMatches = gomaasapi.ConstraintMatches{
   691  		Storage: map[string][]gomaasapi.BlockDevice{},
   692  	}
   693  	controller.machines = []gomaasapi.Machine{machine}
   694  	suite.injectController(controller)
   695  	suite.setupFakeTools(c)
   696  	env := suite.makeEnviron(c, nil)
   697  	err := bootstrap.Bootstrap(envjujutesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   698  	c.Assert(err, jc.ErrorIsNil)
   699  }
   700  
   701  func (suite *maas2EnvironSuite) TestSubnetsNoFilters(c *gc.C) {
   702  	suite.injectController(&fakeController{
   703  		spaces: getFourSpaces(),
   704  	})
   705  	env := suite.makeEnviron(c, nil)
   706  	subnets, err := env.Subnets("", nil)
   707  	c.Assert(err, jc.ErrorIsNil)
   708  	expected := []network.SubnetInfo{
   709  		{CIDR: "192.168.10.0/24", ProviderId: "99", VLANTag: 66, SpaceProviderId: "5"},
   710  		{CIDR: "192.168.11.0/24", ProviderId: "100", VLANTag: 66, SpaceProviderId: "6"},
   711  		{CIDR: "192.168.12.0/24", ProviderId: "101", VLANTag: 66, SpaceProviderId: "7"},
   712  		{CIDR: "192.168.13.0/24", ProviderId: "102", VLANTag: 66, SpaceProviderId: "8"},
   713  	}
   714  	c.Assert(subnets, jc.DeepEquals, expected)
   715  }
   716  
   717  func (suite *maas2EnvironSuite) TestSubnetsNoFiltersError(c *gc.C) {
   718  	suite.injectController(&fakeController{
   719  		spacesError: errors.New("bang"),
   720  	})
   721  	env := suite.makeEnviron(c, nil)
   722  	_, err := env.Subnets("", nil)
   723  	c.Assert(err, gc.ErrorMatches, "bang")
   724  }
   725  
   726  func (suite *maas2EnvironSuite) TestSubnetsSubnetIds(c *gc.C) {
   727  	suite.injectController(&fakeController{
   728  		spaces: getFourSpaces(),
   729  	})
   730  	env := suite.makeEnviron(c, nil)
   731  	subnets, err := env.Subnets("", []network.Id{"99", "100"})
   732  	c.Assert(err, jc.ErrorIsNil)
   733  	expected := []network.SubnetInfo{
   734  		{CIDR: "192.168.10.0/24", ProviderId: "99", VLANTag: 66, SpaceProviderId: "5"},
   735  		{CIDR: "192.168.11.0/24", ProviderId: "100", VLANTag: 66, SpaceProviderId: "6"},
   736  	}
   737  	c.Assert(subnets, jc.DeepEquals, expected)
   738  }
   739  
   740  func (suite *maas2EnvironSuite) TestSubnetsSubnetIdsMissing(c *gc.C) {
   741  	suite.injectController(&fakeController{
   742  		spaces: getFourSpaces(),
   743  	})
   744  	env := suite.makeEnviron(c, nil)
   745  	_, err := env.Subnets("", []network.Id{"99", "missing"})
   746  	msg := "failed to find the following subnets: missing"
   747  	c.Assert(err, gc.ErrorMatches, msg)
   748  }
   749  
   750  func (suite *maas2EnvironSuite) TestSubnetsInstIdNotFound(c *gc.C) {
   751  	suite.injectController(&fakeController{})
   752  	env := suite.makeEnviron(c, nil)
   753  	_, err := env.Subnets("foo", nil)
   754  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   755  }
   756  
   757  func (suite *maas2EnvironSuite) TestSubnetsInstId(c *gc.C) {
   758  	interfaces := []gomaasapi.Interface{
   759  		&fakeInterface{
   760  			links: []gomaasapi.Link{
   761  				&fakeLink{subnet: fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24", space: "space-1"}},
   762  				&fakeLink{subnet: fakeSubnet{id: 100, vlan: fakeVLAN{vid: 0}, cidr: "192.168.11.0/24", space: "space-2"}},
   763  			},
   764  		},
   765  		&fakeInterface{
   766  			links: []gomaasapi.Link{
   767  				&fakeLink{subnet: fakeSubnet{id: 101, vlan: fakeVLAN{vid: 2}, cidr: "192.168.12.0/24", space: "space-3"}},
   768  			},
   769  		},
   770  	}
   771  	machine := &fakeMachine{
   772  		systemID:     "William Gibson",
   773  		interfaceSet: interfaces,
   774  	}
   775  	machine2 := &fakeMachine{systemID: "Bruce Sterling"}
   776  	suite.injectController(&fakeController{
   777  		machines: []gomaasapi.Machine{machine, machine2},
   778  		spaces:   getFourSpaces(),
   779  	})
   780  	env := suite.makeEnviron(c, nil)
   781  	subnets, err := env.Subnets("William Gibson", nil)
   782  	c.Assert(err, jc.ErrorIsNil)
   783  	expected := []network.SubnetInfo{
   784  		{CIDR: "192.168.10.0/24", ProviderId: "99", VLANTag: 66, SpaceProviderId: "5"},
   785  		{CIDR: "192.168.11.0/24", ProviderId: "100", VLANTag: 0, SpaceProviderId: "6"},
   786  		{CIDR: "192.168.12.0/24", ProviderId: "101", VLANTag: 2, SpaceProviderId: "7"},
   787  	}
   788  	c.Assert(subnets, jc.DeepEquals, expected)
   789  }
   790  
   791  func (suite *maas2EnvironSuite) TestStartInstanceNetworkInterfaces(c *gc.C) {
   792  	vlan0 := fakeVLAN{
   793  		id:  5001,
   794  		vid: 0,
   795  		mtu: 1500,
   796  	}
   797  
   798  	vlan50 := fakeVLAN{
   799  		id:  5004,
   800  		vid: 50,
   801  		mtu: 1500,
   802  	}
   803  
   804  	subnetPXE := fakeSubnet{
   805  		id:         3,
   806  		space:      "default",
   807  		vlan:       vlan0,
   808  		gateway:    "10.20.19.2",
   809  		cidr:       "10.20.19.0/24",
   810  		dnsServers: []string{"10.20.19.2", "10.20.19.3"},
   811  	}
   812  
   813  	exampleInterfaces := []gomaasapi.Interface{
   814  		&fakeInterface{
   815  			id:         91,
   816  			name:       "eth0",
   817  			type_:      "physical",
   818  			enabled:    true,
   819  			macAddress: "52:54:00:70:9b:fe",
   820  			vlan:       vlan0,
   821  			links: []gomaasapi.Link{
   822  				&fakeLink{
   823  					id:        436,
   824  					subnet:    &subnetPXE,
   825  					ipAddress: "10.20.19.103",
   826  					mode:      "static",
   827  				},
   828  				&fakeLink{
   829  					id:        437,
   830  					subnet:    &subnetPXE,
   831  					ipAddress: "10.20.19.104",
   832  					mode:      "static",
   833  				},
   834  			},
   835  			parents:  []string{},
   836  			children: []string{"eth0.100", "eth0.250", "eth0.50"},
   837  		},
   838  		&fakeInterface{
   839  			id:         150,
   840  			name:       "eth0.50",
   841  			type_:      "vlan",
   842  			enabled:    true,
   843  			macAddress: "52:54:00:70:9b:fe",
   844  			vlan:       vlan50,
   845  			links: []gomaasapi.Link{
   846  				&fakeLink{
   847  					id: 517,
   848  					subnet: &fakeSubnet{
   849  						id:         5,
   850  						space:      "admin",
   851  						vlan:       vlan50,
   852  						gateway:    "10.50.19.2",
   853  						cidr:       "10.50.19.0/24",
   854  						dnsServers: []string{},
   855  					},
   856  					ipAddress: "10.50.19.103",
   857  					mode:      "static",
   858  				},
   859  			},
   860  			parents:  []string{"eth0"},
   861  			children: []string{},
   862  		},
   863  	}
   864  	var env *maasEnviron
   865  	machine := newFakeMachine("Bruce Sterling", arch.HostArch(), "")
   866  	machine.interfaceSet = exampleInterfaces
   867  	controller := &fakeController{
   868  		allocateMachine: machine,
   869  		allocateMachineMatches: gomaasapi.ConstraintMatches{
   870  			Storage: map[string][]gomaasapi.BlockDevice{},
   871  		},
   872  	}
   873  	suite.injectController(controller)
   874  	suite.setupFakeTools(c)
   875  	env = suite.makeEnviron(c, nil)
   876  
   877  	params := environs.StartInstanceParams{}
   878  	result, err := jujutesting.StartInstanceWithParams(env, "1", params)
   879  	c.Assert(err, jc.ErrorIsNil)
   880  	expected := []network.InterfaceInfo{{
   881  		DeviceIndex:       0,
   882  		MACAddress:        "52:54:00:70:9b:fe",
   883  		CIDR:              "10.20.19.0/24",
   884  		ProviderId:        "91",
   885  		ProviderSubnetId:  "3",
   886  		AvailabilityZones: nil,
   887  		VLANTag:           0,
   888  		ProviderVLANId:    "5001",
   889  		ProviderAddressId: "436",
   890  		InterfaceName:     "eth0",
   891  		InterfaceType:     "ethernet",
   892  		Disabled:          false,
   893  		NoAutoStart:       false,
   894  		ConfigType:        "static",
   895  		Address:           network.NewAddressOnSpace("default", "10.20.19.103"),
   896  		DNSServers:        network.NewAddressesOnSpace("default", "10.20.19.2", "10.20.19.3"),
   897  		DNSSearchDomains:  nil,
   898  		MTU:               1500,
   899  		GatewayAddress:    network.NewAddressOnSpace("default", "10.20.19.2"),
   900  	}, {
   901  		DeviceIndex:       0,
   902  		MACAddress:        "52:54:00:70:9b:fe",
   903  		CIDR:              "10.20.19.0/24",
   904  		ProviderId:        "91",
   905  		ProviderSubnetId:  "3",
   906  		AvailabilityZones: nil,
   907  		VLANTag:           0,
   908  		ProviderVLANId:    "5001",
   909  		ProviderAddressId: "437",
   910  		InterfaceName:     "eth0",
   911  		InterfaceType:     "ethernet",
   912  		Disabled:          false,
   913  		NoAutoStart:       false,
   914  		ConfigType:        "static",
   915  		Address:           network.NewAddressOnSpace("default", "10.20.19.104"),
   916  		DNSServers:        network.NewAddressesOnSpace("default", "10.20.19.2", "10.20.19.3"),
   917  		DNSSearchDomains:  nil,
   918  		MTU:               1500,
   919  		GatewayAddress:    network.NewAddressOnSpace("default", "10.20.19.2"),
   920  	}, {
   921  		DeviceIndex:         1,
   922  		MACAddress:          "52:54:00:70:9b:fe",
   923  		CIDR:                "10.50.19.0/24",
   924  		ProviderId:          "150",
   925  		ProviderSubnetId:    "5",
   926  		AvailabilityZones:   nil,
   927  		VLANTag:             50,
   928  		ProviderVLANId:      "5004",
   929  		ProviderAddressId:   "517",
   930  		InterfaceName:       "eth0.50",
   931  		ParentInterfaceName: "eth0",
   932  		InterfaceType:       "802.1q",
   933  		Disabled:            false,
   934  		NoAutoStart:         false,
   935  		ConfigType:          "static",
   936  		Address:             network.NewAddressOnSpace("admin", "10.50.19.103"),
   937  		DNSServers:          nil,
   938  		DNSSearchDomains:    nil,
   939  		MTU:                 1500,
   940  		GatewayAddress:      network.NewAddressOnSpace("admin", "10.50.19.2"),
   941  	},
   942  	}
   943  	c.Assert(result.NetworkInfo, jc.DeepEquals, expected)
   944  }
   945  
   946  func (suite *maas2EnvironSuite) TestAllocateContainerAddressesSingleNic(c *gc.C) {
   947  	vlan1 := fakeVLAN{
   948  		id:  5001,
   949  		mtu: 1500,
   950  	}
   951  	vlan2 := fakeVLAN{
   952  		id:  5002,
   953  		mtu: 1500,
   954  	}
   955  	subnet1 := fakeSubnet{
   956  		id:         3,
   957  		space:      "default",
   958  		vlan:       vlan1,
   959  		gateway:    "10.20.19.2",
   960  		cidr:       "10.20.19.0/24",
   961  		dnsServers: []string{"10.20.19.2", "10.20.19.3"},
   962  	}
   963  	subnet2 := fakeSubnet{
   964  		id:         4,
   965  		space:      "freckles",
   966  		vlan:       vlan2,
   967  		gateway:    "192.168.1.1",
   968  		cidr:       "192.168.1.0/24",
   969  		dnsServers: []string{"10.20.19.2", "10.20.19.3"},
   970  	}
   971  
   972  	interfaces := []gomaasapi.Interface{
   973  		&fakeInterface{
   974  			id:         91,
   975  			name:       "eth0",
   976  			type_:      "physical",
   977  			enabled:    true,
   978  			macAddress: "52:54:00:70:9b:fe",
   979  			vlan:       vlan1,
   980  			links: []gomaasapi.Link{
   981  				&fakeLink{
   982  					id:        436,
   983  					subnet:    &subnet1,
   984  					ipAddress: "10.20.19.103",
   985  					mode:      "static",
   986  				},
   987  			},
   988  			parents:  []string{},
   989  			children: []string{"eth0.100", "eth0.250", "eth0.50"},
   990  		},
   991  	}
   992  	deviceInterfaces := []gomaasapi.Interface{
   993  		&fakeInterface{
   994  			id:         93,
   995  			name:       "eth1",
   996  			type_:      "physical",
   997  			enabled:    true,
   998  			macAddress: "53:54:00:70:9b:ff",
   999  			vlan:       vlan2,
  1000  			links: []gomaasapi.Link{
  1001  				&fakeLink{
  1002  					id:        480,
  1003  					subnet:    &subnet2,
  1004  					ipAddress: "192.168.1.127",
  1005  					mode:      "static",
  1006  				},
  1007  			},
  1008  			parents:  []string{},
  1009  			children: []string{"eth0.100", "eth0.250", "eth0.50"},
  1010  		},
  1011  	}
  1012  	var env *maasEnviron
  1013  	device := &fakeDevice{
  1014  		interfaceSet: deviceInterfaces,
  1015  		systemID:     "foo",
  1016  	}
  1017  	controller := &fakeController{
  1018  		machines: []gomaasapi.Machine{&fakeMachine{
  1019  			Stub:         &testing.Stub{},
  1020  			systemID:     "1",
  1021  			architecture: arch.HostArch(),
  1022  			interfaceSet: interfaces,
  1023  			createDevice: device,
  1024  		}},
  1025  		spaces: []gomaasapi.Space{
  1026  			fakeSpace{
  1027  				name:    "freckles",
  1028  				id:      4567,
  1029  				subnets: []gomaasapi.Subnet{subnet1, subnet2},
  1030  			},
  1031  		},
  1032  		devices: []gomaasapi.Device{device},
  1033  	}
  1034  	suite.injectController(controller)
  1035  	suite.setupFakeTools(c)
  1036  	env = suite.makeEnviron(c, nil)
  1037  
  1038  	prepared := []network.InterfaceInfo{{
  1039  		MACAddress:    "52:54:00:70:9b:fe",
  1040  		CIDR:          "10.20.19.0/24",
  1041  		InterfaceName: "eth0",
  1042  	}}
  1043  	result, err := env.AllocateContainerAddresses(instance.Id("1"), prepared)
  1044  	c.Assert(err, jc.ErrorIsNil)
  1045  	expected := []network.InterfaceInfo{{
  1046  		DeviceIndex:       0,
  1047  		MACAddress:        "53:54:00:70:9b:ff",
  1048  		CIDR:              "192.168.1.0/24",
  1049  		ProviderId:        "93",
  1050  		ProviderSubnetId:  "4",
  1051  		VLANTag:           0,
  1052  		ProviderVLANId:    "5002",
  1053  		ProviderAddressId: "480",
  1054  		InterfaceName:     "eth1",
  1055  		InterfaceType:     "ethernet",
  1056  		ConfigType:        "static",
  1057  		Address:           network.NewAddressOnSpace("freckles", "192.168.1.127"),
  1058  		DNSServers:        network.NewAddressesOnSpace("freckles", "10.20.19.2", "10.20.19.3"),
  1059  		MTU:               1500,
  1060  		GatewayAddress:    network.NewAddressOnSpace("freckles", "192.168.1.1"),
  1061  	}}
  1062  	c.Assert(result, jc.DeepEquals, expected)
  1063  }
  1064  
  1065  func (suite *maas2EnvironSuite) TestAllocateContainerAddressesDualNic(c *gc.C) {
  1066  	vlan1 := fakeVLAN{
  1067  		id:  5001,
  1068  		mtu: 1500,
  1069  	}
  1070  	vlan2 := fakeVLAN{
  1071  		id:  5002,
  1072  		mtu: 1500,
  1073  	}
  1074  	subnet1 := fakeSubnet{
  1075  		id:         3,
  1076  		space:      "freckles",
  1077  		vlan:       vlan1,
  1078  		gateway:    "10.20.19.2",
  1079  		cidr:       "10.20.19.0/24",
  1080  		dnsServers: []string{"10.20.19.2", "10.20.19.3"},
  1081  	}
  1082  	subnet2 := fakeSubnet{
  1083  		id:         4,
  1084  		space:      "freckles",
  1085  		vlan:       vlan2,
  1086  		gateway:    "192.168.1.1",
  1087  		cidr:       "192.168.1.0/24",
  1088  		dnsServers: []string{"10.20.19.2", "10.20.19.3"},
  1089  	}
  1090  
  1091  	interfaces := []gomaasapi.Interface{
  1092  		&fakeInterface{
  1093  			id:         91,
  1094  			name:       "eth0",
  1095  			type_:      "physical",
  1096  			enabled:    true,
  1097  			macAddress: "52:54:00:70:9b:fe",
  1098  			vlan:       vlan1,
  1099  			links: []gomaasapi.Link{
  1100  				&fakeLink{
  1101  					id:        436,
  1102  					subnet:    &subnet1,
  1103  					ipAddress: "10.20.19.103",
  1104  					mode:      "static",
  1105  				},
  1106  			},
  1107  			parents:  []string{},
  1108  			children: []string{"eth0.100", "eth0.250", "eth0.50"},
  1109  		},
  1110  		&fakeInterface{
  1111  			id:         92,
  1112  			name:       "eth1",
  1113  			type_:      "physical",
  1114  			enabled:    true,
  1115  			macAddress: "52:54:00:70:9b:ff",
  1116  			vlan:       vlan2,
  1117  			links: []gomaasapi.Link{
  1118  				&fakeLink{
  1119  					id:        437,
  1120  					subnet:    &subnet2,
  1121  					ipAddress: "192.168.1.4",
  1122  					mode:      "static",
  1123  				},
  1124  			},
  1125  		},
  1126  	}
  1127  	deviceInterfaces := []gomaasapi.Interface{
  1128  		&fakeInterface{
  1129  			id:         93,
  1130  			name:       "eth0",
  1131  			type_:      "physical",
  1132  			enabled:    true,
  1133  			macAddress: "53:54:00:70:9b:ff",
  1134  			vlan:       vlan1,
  1135  			links: []gomaasapi.Link{
  1136  				&fakeLink{
  1137  					id:        480,
  1138  					subnet:    &subnet1,
  1139  					ipAddress: "10.20.19.127",
  1140  					mode:      "static",
  1141  				},
  1142  			},
  1143  			parents:  []string{},
  1144  			children: []string{"eth0.100", "eth0.250", "eth0.50"},
  1145  			Stub:     &testing.Stub{},
  1146  		},
  1147  	}
  1148  	newInterface := &fakeInterface{
  1149  		id:         94,
  1150  		name:       "eth1",
  1151  		type_:      "physical",
  1152  		enabled:    true,
  1153  		macAddress: "52:54:00:70:9b:f4",
  1154  		vlan:       vlan2,
  1155  		links: []gomaasapi.Link{
  1156  			&fakeLink{
  1157  				id:        481,
  1158  				subnet:    &subnet2,
  1159  				ipAddress: "192.168.1.127",
  1160  				mode:      "static",
  1161  			},
  1162  		},
  1163  		Stub: &testing.Stub{},
  1164  	}
  1165  	device := &fakeDevice{
  1166  		interfaceSet: deviceInterfaces,
  1167  		systemID:     "foo",
  1168  		interface_:   newInterface,
  1169  		Stub:         &testing.Stub{},
  1170  	}
  1171  	controller := &fakeController{
  1172  		machines: []gomaasapi.Machine{&fakeMachine{
  1173  			Stub:         &testing.Stub{},
  1174  			systemID:     "1",
  1175  			architecture: arch.HostArch(),
  1176  			interfaceSet: interfaces,
  1177  			createDevice: device,
  1178  		}},
  1179  		spaces: []gomaasapi.Space{
  1180  			fakeSpace{
  1181  				name:    "freckles",
  1182  				id:      4567,
  1183  				subnets: []gomaasapi.Subnet{subnet1, subnet2},
  1184  			},
  1185  		},
  1186  		devices: []gomaasapi.Device{device},
  1187  	}
  1188  	suite.injectController(controller)
  1189  	env := suite.makeEnviron(c, nil)
  1190  
  1191  	prepared := []network.InterfaceInfo{{
  1192  		MACAddress:    "53:54:00:70:9b:ff",
  1193  		CIDR:          "10.20.19.0/24",
  1194  		InterfaceName: "eth0",
  1195  	}, {
  1196  		MACAddress:    "52:54:00:70:9b:f4",
  1197  		CIDR:          "192.168.1.0/24",
  1198  		InterfaceName: "eth1",
  1199  	}}
  1200  	expected := []network.InterfaceInfo{{
  1201  		DeviceIndex:       0,
  1202  		MACAddress:        "53:54:00:70:9b:ff",
  1203  		CIDR:              "10.20.19.0/24",
  1204  		ProviderId:        "93",
  1205  		ProviderSubnetId:  "3",
  1206  		ProviderVLANId:    "5001",
  1207  		ProviderAddressId: "480",
  1208  		InterfaceName:     "eth0",
  1209  		InterfaceType:     "ethernet",
  1210  		ConfigType:        "static",
  1211  		Address:           network.NewAddressOnSpace("freckles", "10.20.19.127"),
  1212  		DNSServers:        network.NewAddressesOnSpace("freckles", "10.20.19.2", "10.20.19.3"),
  1213  		MTU:               1500,
  1214  		GatewayAddress:    network.NewAddressOnSpace("freckles", "10.20.19.2"),
  1215  	}, {
  1216  		DeviceIndex:       0,
  1217  		MACAddress:        "52:54:00:70:9b:f4",
  1218  		CIDR:              "192.168.1.0/24",
  1219  		ProviderId:        "94",
  1220  		ProviderSubnetId:  "4",
  1221  		ProviderVLANId:    "5002",
  1222  		ProviderAddressId: "481",
  1223  		InterfaceName:     "eth1",
  1224  		InterfaceType:     "ethernet",
  1225  		ConfigType:        "static",
  1226  		Address:           network.NewAddressOnSpace("freckles", "192.168.1.127"),
  1227  		DNSServers:        network.NewAddressesOnSpace("freckles", "10.20.19.2", "10.20.19.3"),
  1228  		MTU:               1500,
  1229  		GatewayAddress:    network.NewAddressOnSpace("freckles", "192.168.1.1"),
  1230  	}}
  1231  	result, err := env.AllocateContainerAddresses(instance.Id("1"), prepared)
  1232  	c.Assert(err, jc.ErrorIsNil)
  1233  	c.Assert(result, jc.DeepEquals, expected)
  1234  }
  1235  
  1236  func (suite *maas2EnvironSuite) assertAllocateContainerAddressesFails(c *gc.C, controller *fakeController, prepared []network.InterfaceInfo, errorMatches string) {
  1237  	if prepared == nil {
  1238  		prepared = []network.InterfaceInfo{{}}
  1239  	}
  1240  	suite.injectController(controller)
  1241  	env := suite.makeEnviron(c, nil)
  1242  	_, err := env.AllocateContainerAddresses(instance.Id("1"), prepared)
  1243  	c.Assert(err, gc.ErrorMatches, errorMatches)
  1244  }
  1245  
  1246  func (suite *maas2EnvironSuite) TestAllocateContainerAddressesSpacesError(c *gc.C) {
  1247  	controller := &fakeController{spacesError: errors.New("boom")}
  1248  	suite.assertAllocateContainerAddressesFails(c, controller, nil, "boom")
  1249  }
  1250  
  1251  func (suite *maas2EnvironSuite) TestAllocateContainerAddressesPrimaryInterfaceMissing(c *gc.C) {
  1252  	controller := &fakeController{}
  1253  	suite.assertAllocateContainerAddressesFails(c, controller, nil, "cannot find primary interface for container")
  1254  }
  1255  
  1256  func (suite *maas2EnvironSuite) TestAllocateContainerAddressesPrimaryInterfaceSubnetMissing(c *gc.C) {
  1257  	controller := &fakeController{}
  1258  	prepared := []network.InterfaceInfo{{InterfaceName: "eth0"}}
  1259  	errorMatches := "primary NIC subnet  not found"
  1260  	suite.assertAllocateContainerAddressesFails(c, controller, prepared, errorMatches)
  1261  }
  1262  
  1263  func makeFakeSubnet(id int) fakeSubnet {
  1264  	return fakeSubnet{
  1265  		id:      id,
  1266  		space:   "freckles",
  1267  		gateway: fmt.Sprintf("10.20.%d.2", 16+id),
  1268  		cidr:    fmt.Sprintf("10.20.%d.0/24", 16+id),
  1269  	}
  1270  }
  1271  func (suite *maas2EnvironSuite) TestAllocateContainerAddressesMachinesError(c *gc.C) {
  1272  	var env *maasEnviron
  1273  	subnet := makeFakeSubnet(3)
  1274  	checkMachinesArgs := func(args gomaasapi.MachinesArgs) {
  1275  		expected := gomaasapi.MachinesArgs{
  1276  			AgentName: env.ecfg().maasAgentName(),
  1277  			SystemIDs: []string{"1"},
  1278  		}
  1279  		c.Assert(args, jc.DeepEquals, expected)
  1280  	}
  1281  	controller := &fakeController{
  1282  		machinesError:     errors.New("boom"),
  1283  		machinesArgsCheck: checkMachinesArgs,
  1284  		spaces: []gomaasapi.Space{
  1285  			fakeSpace{
  1286  				name:    "freckles",
  1287  				id:      4567,
  1288  				subnets: []gomaasapi.Subnet{subnet},
  1289  			},
  1290  		},
  1291  	}
  1292  	suite.injectController(controller)
  1293  	env = suite.makeEnviron(c, nil)
  1294  	prepared := []network.InterfaceInfo{
  1295  		{InterfaceName: "eth0", CIDR: "10.20.19.0/24"},
  1296  	}
  1297  	_, err := env.AllocateContainerAddresses(instance.Id("1"), prepared)
  1298  	c.Assert(err, gc.ErrorMatches, "boom")
  1299  }
  1300  
  1301  func getArgs(c *gc.C, calls []testing.StubCall) interface{} {
  1302  	c.Assert(calls, gc.HasLen, 1)
  1303  	args := calls[0].Args
  1304  	c.Assert(args, gc.HasLen, 1)
  1305  	return args[0]
  1306  }
  1307  
  1308  func (suite *maas2EnvironSuite) TestAllocateContainerAddressesCreateDevicerror(c *gc.C) {
  1309  	subnet := makeFakeSubnet(3)
  1310  	var env *maasEnviron
  1311  	machine := &fakeMachine{
  1312  		Stub:     &testing.Stub{},
  1313  		systemID: "1",
  1314  	}
  1315  	machine.SetErrors(errors.New("boom"))
  1316  	controller := &fakeController{
  1317  		machines: []gomaasapi.Machine{machine},
  1318  		spaces: []gomaasapi.Space{
  1319  			fakeSpace{
  1320  				name:    "freckles",
  1321  				id:      4567,
  1322  				subnets: []gomaasapi.Subnet{subnet},
  1323  			},
  1324  		},
  1325  	}
  1326  	suite.injectController(controller)
  1327  	env = suite.makeEnviron(c, nil)
  1328  	prepared := []network.InterfaceInfo{
  1329  		{InterfaceName: "eth0", CIDR: "10.20.19.0/24", MACAddress: "DEADBEEF"},
  1330  	}
  1331  	_, err := env.AllocateContainerAddresses(instance.Id("1"), prepared)
  1332  	c.Assert(err, gc.ErrorMatches, "boom")
  1333  	args := getArgs(c, machine.Calls())
  1334  	maasArgs, ok := args.(gomaasapi.CreateMachineDeviceArgs)
  1335  	c.Assert(ok, jc.IsTrue)
  1336  	expected := gomaasapi.CreateMachineDeviceArgs{
  1337  		Subnet:        subnet,
  1338  		MACAddress:    "DEADBEEF",
  1339  		InterfaceName: "eth0",
  1340  	}
  1341  	c.Assert(maasArgs, jc.DeepEquals, expected)
  1342  }
  1343  
  1344  func (suite *maas2EnvironSuite) TestAllocateContainerAddressesSecondNICSubnetMissing(c *gc.C) {
  1345  	subnet := makeFakeSubnet(3)
  1346  	var env *maasEnviron
  1347  	device := &fakeDevice{
  1348  		interfaceSet: []gomaasapi.Interface{&fakeInterface{}},
  1349  		systemID:     "foo",
  1350  	}
  1351  	machine := &fakeMachine{
  1352  		Stub:         &testing.Stub{},
  1353  		systemID:     "1",
  1354  		createDevice: device,
  1355  	}
  1356  	controller := &fakeController{
  1357  		machines: []gomaasapi.Machine{machine},
  1358  		spaces: []gomaasapi.Space{
  1359  			fakeSpace{
  1360  				name:    "freckles",
  1361  				id:      4567,
  1362  				subnets: []gomaasapi.Subnet{subnet},
  1363  			},
  1364  		},
  1365  	}
  1366  	suite.injectController(controller)
  1367  	env = suite.makeEnviron(c, nil)
  1368  	prepared := []network.InterfaceInfo{
  1369  		{InterfaceName: "eth0", CIDR: "10.20.19.0/24", MACAddress: "DEADBEEF"},
  1370  		{InterfaceName: "eth1", CIDR: "10.20.20.0/24", MACAddress: "DEADBEEE"},
  1371  	}
  1372  	_, err := env.AllocateContainerAddresses(instance.Id("1"), prepared)
  1373  	c.Assert(err, gc.ErrorMatches, "NIC eth1 subnet 10.20.20.0/24 not found")
  1374  }
  1375  
  1376  func (suite *maas2EnvironSuite) TestAllocateContainerAddressesCreateInterfaceError(c *gc.C) {
  1377  	subnet := makeFakeSubnet(3)
  1378  	subnet2 := makeFakeSubnet(4)
  1379  	subnet2.vlan = fakeVLAN{vid: 66}
  1380  	var env *maasEnviron
  1381  	device := &fakeDevice{
  1382  		Stub:         &testing.Stub{},
  1383  		interfaceSet: []gomaasapi.Interface{&fakeInterface{}},
  1384  		systemID:     "foo",
  1385  	}
  1386  	device.SetErrors(errors.New("boom"))
  1387  	machine := &fakeMachine{
  1388  		Stub:         &testing.Stub{},
  1389  		systemID:     "1",
  1390  		createDevice: device,
  1391  	}
  1392  	controller := &fakeController{
  1393  		machines: []gomaasapi.Machine{machine},
  1394  		spaces: []gomaasapi.Space{
  1395  			fakeSpace{
  1396  				name:    "freckles",
  1397  				id:      4567,
  1398  				subnets: []gomaasapi.Subnet{subnet, subnet2},
  1399  			},
  1400  		},
  1401  	}
  1402  	suite.injectController(controller)
  1403  	env = suite.makeEnviron(c, nil)
  1404  	prepared := []network.InterfaceInfo{
  1405  		{InterfaceName: "eth0", CIDR: "10.20.19.0/24", MACAddress: "DEADBEEF"},
  1406  		{InterfaceName: "eth1", CIDR: "10.20.20.0/24", MACAddress: "DEADBEEE"},
  1407  	}
  1408  	_, err := env.AllocateContainerAddresses(instance.Id("1"), prepared)
  1409  	c.Assert(err, gc.ErrorMatches, "creating device interface: boom")
  1410  	args := getArgs(c, device.Calls())
  1411  	maasArgs, ok := args.(gomaasapi.CreateInterfaceArgs)
  1412  	c.Assert(ok, jc.IsTrue)
  1413  	expected := gomaasapi.CreateInterfaceArgs{
  1414  		MACAddress: "DEADBEEE",
  1415  		Name:       "eth1",
  1416  		VLAN:       subnet2.VLAN(),
  1417  	}
  1418  	c.Assert(maasArgs, jc.DeepEquals, expected)
  1419  }
  1420  
  1421  func (suite *maas2EnvironSuite) TestAllocateContainerAddressesLinkSubnetError(c *gc.C) {
  1422  	subnet := makeFakeSubnet(3)
  1423  	subnet2 := makeFakeSubnet(4)
  1424  	subnet2.vlan = fakeVLAN{vid: 66}
  1425  	var env *maasEnviron
  1426  	interface_ := &fakeInterface{Stub: &testing.Stub{}}
  1427  	interface_.SetErrors(errors.New("boom"))
  1428  	device := &fakeDevice{
  1429  		Stub:         &testing.Stub{},
  1430  		interfaceSet: []gomaasapi.Interface{&fakeInterface{}},
  1431  		interface_:   interface_,
  1432  		systemID:     "foo",
  1433  	}
  1434  	machine := &fakeMachine{
  1435  		Stub:         &testing.Stub{},
  1436  		systemID:     "1",
  1437  		createDevice: device,
  1438  	}
  1439  	controller := &fakeController{
  1440  		machines: []gomaasapi.Machine{machine},
  1441  		spaces: []gomaasapi.Space{
  1442  			fakeSpace{
  1443  				name:    "freckles",
  1444  				id:      4567,
  1445  				subnets: []gomaasapi.Subnet{subnet, subnet2},
  1446  			},
  1447  		},
  1448  	}
  1449  	suite.injectController(controller)
  1450  	env = suite.makeEnviron(c, nil)
  1451  	prepared := []network.InterfaceInfo{
  1452  		{InterfaceName: "eth0", CIDR: "10.20.19.0/24", MACAddress: "DEADBEEF"},
  1453  		{InterfaceName: "eth1", CIDR: "10.20.20.0/24", MACAddress: "DEADBEEE"},
  1454  	}
  1455  	_, err := env.AllocateContainerAddresses(instance.Id("1"), prepared)
  1456  	c.Assert(err, gc.ErrorMatches, "cannot link device interface to subnet: boom")
  1457  	args := getArgs(c, interface_.Calls())
  1458  	maasArgs, ok := args.(gomaasapi.LinkSubnetArgs)
  1459  	c.Assert(ok, jc.IsTrue)
  1460  	expected := gomaasapi.LinkSubnetArgs{
  1461  		Mode:   gomaasapi.LinkModeStatic,
  1462  		Subnet: subnet2,
  1463  	}
  1464  	c.Assert(maasArgs, jc.DeepEquals, expected)
  1465  }
  1466  func (suite *maas2EnvironSuite) TestStorageReturnsStorage(c *gc.C) {
  1467  	controller := newFakeController()
  1468  	env := suite.makeEnviron(c, controller)
  1469  	stor := env.Storage()
  1470  	c.Check(stor, gc.NotNil)
  1471  
  1472  	// The Storage object is really a maas2Storage.
  1473  	specificStorage := stor.(*maas2Storage)
  1474  
  1475  	// Its environment pointer refers back to its environment.
  1476  	c.Check(specificStorage.environ, gc.Equals, env)
  1477  	c.Check(specificStorage.maasController, gc.Equals, controller)
  1478  }
  1479  
  1480  func (suite *maas2EnvironSuite) TestStartInstanceEndToEnd(c *gc.C) {
  1481  	suite.setupFakeTools(c)
  1482  	machine := newFakeMachine("gus", arch.HostArch(), "Deployed")
  1483  	file := &fakeFile{name: "agent-prefix-provider-state"}
  1484  	controller := newFakeControllerWithFiles(file)
  1485  	controller.machines = []gomaasapi.Machine{machine}
  1486  	controller.allocateMachine = machine
  1487  	controller.allocateMachineMatches = gomaasapi.ConstraintMatches{
  1488  		Storage: make(map[string][]gomaasapi.BlockDevice),
  1489  	}
  1490  
  1491  	env := suite.makeEnviron(c, controller)
  1492  	err := bootstrap.Bootstrap(envjujutesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
  1493  	c.Assert(err, jc.ErrorIsNil)
  1494  
  1495  	machine.Stub.CheckCallNames(c, "Start")
  1496  	controller.Stub.CheckCallNames(c, "GetFile", "AddFile")
  1497  	addFileArgs, ok := controller.Stub.Calls()[1].Args[0].(gomaasapi.AddFileArgs)
  1498  	c.Assert(ok, jc.IsTrue)
  1499  
  1500  	// Make it look like the right state was written to the file.
  1501  	buffer := new(bytes.Buffer)
  1502  	buffer.ReadFrom(addFileArgs.Reader)
  1503  	file.contents = buffer.Bytes()
  1504  	c.Check(string(buffer.Bytes()), gc.Equals, "state-instances:\n- gus\n")
  1505  
  1506  	// Test the instance id is correctly recorded for the bootstrap node.
  1507  	// Check that ControllerInstances returns the id of the bootstrap machine.
  1508  	instanceIds, err := env.ControllerInstances()
  1509  	c.Assert(err, jc.ErrorIsNil)
  1510  	c.Assert(instanceIds, gc.HasLen, 1)
  1511  	insts, err := env.AllInstances()
  1512  	c.Assert(err, jc.ErrorIsNil)
  1513  	c.Assert(insts, gc.HasLen, 1)
  1514  	c.Check(insts[0].Id(), gc.Equals, instanceIds[0])
  1515  
  1516  	node1 := newFakeMachine("victor", arch.HostArch(), "Deployed")
  1517  	node1.hostname = "host1"
  1518  	node1.cpuCount = 1
  1519  	node1.memory = 1024
  1520  	node1.zoneName = "test_zone"
  1521  	controller.allocateMachine = node1
  1522  
  1523  	instance, hc := jujutesting.AssertStartInstance(c, env, "1")
  1524  	c.Check(instance, gc.NotNil)
  1525  	c.Assert(hc, gc.NotNil)
  1526  	c.Check(hc.String(), gc.Equals, fmt.Sprintf("arch=%s cpu-cores=1 mem=1024M availability-zone=test_zone", arch.HostArch()))
  1527  
  1528  	node1.Stub.CheckCallNames(c, "Start")
  1529  	startArgs, ok := node1.Stub.Calls()[0].Args[0].(gomaasapi.StartArgs)
  1530  	c.Assert(ok, jc.IsTrue)
  1531  
  1532  	decodedUserData, err := decodeUserData(startArgs.UserData)
  1533  	c.Assert(err, jc.ErrorIsNil)
  1534  	info := machineInfo{"host1"}
  1535  	cloudcfg, err := cloudinit.New("precise")
  1536  	c.Assert(err, jc.ErrorIsNil)
  1537  	cloudinitRunCmd, err := info.cloudinitRunCmd(cloudcfg)
  1538  	c.Assert(err, jc.ErrorIsNil)
  1539  	data, err := goyaml.Marshal(cloudinitRunCmd)
  1540  	c.Assert(err, jc.ErrorIsNil)
  1541  	c.Check(string(decodedUserData), jc.Contains, string(data))
  1542  
  1543  	// Trash the tools and try to start another instance.
  1544  	suite.PatchValue(&envtools.DefaultBaseURL, "")
  1545  	instance, _, _, err = jujutesting.StartInstance(env, "2")
  1546  	c.Check(instance, gc.IsNil)
  1547  	c.Check(err, jc.Satisfies, errors.IsNotFound)
  1548  }
  1549  
  1550  func (suite *maas2EnvironSuite) TestControllerInstances(c *gc.C) {
  1551  	controller := newFakeControllerWithErrors(gomaasapi.NewNoMatchError("state"))
  1552  	env := suite.makeEnviron(c, controller)
  1553  	_, err := env.ControllerInstances()
  1554  	c.Assert(err, gc.Equals, environs.ErrNotBootstrapped)
  1555  
  1556  	tests := [][]instance.Id{{}, {"inst-0"}, {"inst-0", "inst-1"}}
  1557  	for _, expected := range tests {
  1558  		state, err := goyaml.Marshal(&common.BootstrapState{StateInstances: expected})
  1559  		c.Assert(err, jc.ErrorIsNil)
  1560  
  1561  		controller.files = []gomaasapi.File{&fakeFile{
  1562  			name:     "agent-prefix-provider-state",
  1563  			contents: state,
  1564  		}}
  1565  		controllerInstances, err := env.ControllerInstances()
  1566  		c.Assert(err, jc.ErrorIsNil)
  1567  		c.Assert(controllerInstances, jc.SameContents, expected)
  1568  	}
  1569  }
  1570  
  1571  func (suite *maas2EnvironSuite) TestControllerInstancesFailsIfNoStateInstances(c *gc.C) {
  1572  	env := suite.makeEnviron(c,
  1573  		newFakeControllerWithErrors(gomaasapi.NewNoMatchError("state")))
  1574  	_, err := env.ControllerInstances()
  1575  	c.Check(err, gc.Equals, environs.ErrNotBootstrapped)
  1576  }
  1577  
  1578  func (suite *maas2EnvironSuite) TestDestroy(c *gc.C) {
  1579  	file1 := &fakeFile{name: "agent-prefix-provider-state"}
  1580  	file2 := &fakeFile{name: "agent-prefix-horace"}
  1581  	controller := newFakeControllerWithFiles(file1, file2)
  1582  	controller.machines = []gomaasapi.Machine{&fakeMachine{systemID: "pete"}}
  1583  	env := suite.makeEnviron(c, controller)
  1584  	err := env.Destroy()
  1585  	c.Check(err, jc.ErrorIsNil)
  1586  
  1587  	controller.Stub.CheckCallNames(c, "ReleaseMachines", "GetFile", "Files", "GetFile", "GetFile")
  1588  	// Instances have been stopped.
  1589  	controller.Stub.CheckCall(c, 0, "ReleaseMachines", gomaasapi.ReleaseMachinesArgs{
  1590  		SystemIDs: []string{"pete"},
  1591  		Comment:   "Released by Juju MAAS provider",
  1592  	})
  1593  
  1594  	// Files have been cleaned up.
  1595  	c.Check(file1.deleted, jc.IsTrue)
  1596  	c.Check(file2.deleted, jc.IsTrue)
  1597  }
  1598  
  1599  func (suite *maas2EnvironSuite) TestBootstrapFailsIfNoTools(c *gc.C) {
  1600  	env := suite.makeEnviron(c, newFakeController())
  1601  	// Disable auto-uploading by setting the agent version.
  1602  	cfg, err := env.Config().Apply(map[string]interface{}{
  1603  		"agent-version": jujuversion.Current.String(),
  1604  	})
  1605  	c.Assert(err, jc.ErrorIsNil)
  1606  	err = env.SetConfig(cfg)
  1607  	c.Assert(err, jc.ErrorIsNil)
  1608  	err = bootstrap.Bootstrap(envjujutesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
  1609  	c.Check(err, gc.ErrorMatches, "Juju cannot bootstrap because no tools are available for your model(.|\n)*")
  1610  }
  1611  
  1612  func (suite *maas2EnvironSuite) TestBootstrapFailsIfNoNodes(c *gc.C) {
  1613  	suite.setupFakeTools(c)
  1614  	controller := newFakeController()
  1615  	controller.allocateMachineError = gomaasapi.NewNoMatchError("oops")
  1616  	env := suite.makeEnviron(c, controller)
  1617  	err := bootstrap.Bootstrap(envjujutesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
  1618  	// Since there are no nodes, the attempt to allocate one returns a
  1619  	// 409: Conflict.
  1620  	c.Check(err, gc.ErrorMatches, ".*cannot run instances.*")
  1621  }
  1622  
  1623  func (suite *maas2EnvironSuite) TestGetToolsMetadataSources(c *gc.C) {
  1624  	// Add a dummy file to storage so we can use that to check the
  1625  	// obtained source later.
  1626  	env := suite.makeEnviron(c, newFakeControllerWithFiles(
  1627  		&fakeFile{name: "agent-prefix-tools/filename", contents: makeRandomBytes(10)},
  1628  	))
  1629  	sources, err := envtools.GetMetadataSources(env)
  1630  	c.Assert(err, jc.ErrorIsNil)
  1631  	c.Assert(sources, gc.HasLen, 0)
  1632  }
  1633  
  1634  func (suite *maas2EnvironSuite) TestConstraintsValidator(c *gc.C) {
  1635  	controller := newFakeController()
  1636  	controller.bootResources = []gomaasapi.BootResource{&fakeBootResource{name: "trusty", architecture: "amd64"}}
  1637  	env := suite.makeEnviron(c, controller)
  1638  	validator, err := env.ConstraintsValidator()
  1639  	c.Assert(err, jc.ErrorIsNil)
  1640  	cons := constraints.MustParse("arch=amd64 cpu-power=10 instance-type=foo virt-type=kvm")
  1641  	unsupported, err := validator.Validate(cons)
  1642  	c.Assert(err, jc.ErrorIsNil)
  1643  	c.Assert(unsupported, jc.SameContents, []string{"cpu-power", "instance-type", "virt-type"})
  1644  }
  1645  
  1646  func (suite *maas2EnvironSuite) TestConstraintsValidatorVocab(c *gc.C) {
  1647  	controller := newFakeController()
  1648  	controller.bootResources = []gomaasapi.BootResource{
  1649  		&fakeBootResource{name: "trusty", architecture: "amd64"},
  1650  		&fakeBootResource{name: "precise", architecture: "armhf"},
  1651  	}
  1652  	env := suite.makeEnviron(c, controller)
  1653  	validator, err := env.ConstraintsValidator()
  1654  	c.Assert(err, jc.ErrorIsNil)
  1655  	cons := constraints.MustParse("arch=ppc64el")
  1656  	_, err = validator.Validate(cons)
  1657  	c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=ppc64el\nvalid values are: \\[amd64 armhf\\]")
  1658  }