github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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/errors"
    11  	"github.com/juju/gomaasapi"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/utils/arch"
    14  	"github.com/juju/utils/set"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	"github.com/juju/juju/constraints"
    18  	"github.com/juju/juju/environs"
    19  	"github.com/juju/juju/environs/bootstrap"
    20  	"github.com/juju/juju/environs/config"
    21  	envtesting "github.com/juju/juju/environs/testing"
    22  	"github.com/juju/juju/instance"
    23  	"github.com/juju/juju/juju/testing"
    24  	"github.com/juju/juju/network"
    25  	coretesting "github.com/juju/juju/testing"
    26  )
    27  
    28  type maas2EnvironSuite struct {
    29  	maas2Suite
    30  }
    31  
    32  var _ = gc.Suite(&maas2EnvironSuite{})
    33  
    34  func (suite *maas2EnvironSuite) getEnvWithServer(c *gc.C) (*maasEnviron, error) {
    35  	testServer := gomaasapi.NewSimpleServer()
    36  	testServer.AddGetResponse("/api/2.0/version/", http.StatusOK, maas2VersionResponse)
    37  	testServer.AddGetResponse("/api/2.0/users/?op=whoami", http.StatusOK, "{}")
    38  	// Weirdly, rather than returning a 404 when the version is
    39  	// unknown, MAAS2 returns some HTML (the login page).
    40  	testServer.AddGetResponse("/api/1.0/version/", http.StatusOK, "<html></html>")
    41  	testServer.Start()
    42  	suite.AddCleanup(func(*gc.C) { testServer.Close() })
    43  	testAttrs := coretesting.Attrs{}
    44  	for k, v := range maasEnvAttrs {
    45  		testAttrs[k] = v
    46  	}
    47  	testAttrs["maas-server"] = testServer.Server.URL
    48  	attrs := coretesting.FakeConfig().Merge(testAttrs)
    49  	cfg, err := config.New(config.NoDefaults, attrs)
    50  	c.Assert(err, jc.ErrorIsNil)
    51  	return NewEnviron(cfg)
    52  }
    53  
    54  func (suite *maas2EnvironSuite) TestNewEnvironWithoutFeatureFlag(c *gc.C) {
    55  	suite.SetFeatureFlags()
    56  	_, err := suite.getEnvWithServer(c)
    57  	c.Assert(err, jc.Satisfies, errors.IsNotSupported)
    58  }
    59  
    60  func (suite *maas2EnvironSuite) TestNewEnvironWithController(c *gc.C) {
    61  	env, err := suite.getEnvWithServer(c)
    62  	c.Assert(err, jc.ErrorIsNil)
    63  	c.Assert(env, gc.NotNil)
    64  }
    65  
    66  func (suite *maas2EnvironSuite) TestSupportedArchitectures(c *gc.C) {
    67  	controller := &fakeController{
    68  		bootResources: []gomaasapi.BootResource{
    69  			&fakeBootResource{name: "wily", architecture: "amd64/blah"},
    70  			&fakeBootResource{name: "wily", architecture: "amd64/something"},
    71  			&fakeBootResource{name: "xenial", architecture: "arm/somethingelse"},
    72  		},
    73  	}
    74  	env := suite.makeEnviron(c, controller)
    75  	result, err := env.SupportedArchitectures()
    76  	c.Assert(err, jc.ErrorIsNil)
    77  	c.Assert(result, gc.DeepEquals, []string{"amd64", "arm"})
    78  }
    79  
    80  func (suite *maas2EnvironSuite) TestSupportedArchitecturesError(c *gc.C) {
    81  	env := suite.makeEnviron(c, &fakeController{bootResourcesError: errors.New("Something terrible!")})
    82  	_, err := env.SupportedArchitectures()
    83  	c.Assert(err, gc.ErrorMatches, "Something terrible!")
    84  }
    85  
    86  func (suite *maas2EnvironSuite) injectControllerWithSpacesAndCheck(c *gc.C, spaces []gomaasapi.Space, expected gomaasapi.AllocateMachineArgs) *maasEnviron {
    87  	var env *maasEnviron
    88  	check := func(args gomaasapi.AllocateMachineArgs) {
    89  		expected.AgentName = env.ecfg().maasAgentName()
    90  		c.Assert(args, gc.DeepEquals, expected)
    91  	}
    92  	controller := &fakeController{
    93  		allocateMachineArgsCheck: check,
    94  		allocateMachine: &fakeMachine{
    95  			systemID:     "Bruce Sterling",
    96  			architecture: arch.HostArch(),
    97  		},
    98  		allocateMachineMatches: gomaasapi.ConstraintMatches{
    99  			Storage: map[string]gomaasapi.BlockDevice{},
   100  		},
   101  		spaces: spaces,
   102  	}
   103  	suite.injectController(controller)
   104  	suite.setupFakeTools(c)
   105  	env = suite.makeEnviron(c, nil)
   106  	return env
   107  }
   108  
   109  func (suite *maas2EnvironSuite) makeEnvironWithMachines(c *gc.C, expectedSystemIDs []string, returnSystemIDs []string) *maasEnviron {
   110  	var env *maasEnviron
   111  	checkArgs := func(args gomaasapi.MachinesArgs) {
   112  		c.Check(args.SystemIDs, gc.DeepEquals, expectedSystemIDs)
   113  		c.Check(args.AgentName, gc.Equals, env.ecfg().maasAgentName())
   114  	}
   115  	machines := make([]gomaasapi.Machine, len(returnSystemIDs))
   116  	for index, id := range returnSystemIDs {
   117  		machines[index] = &fakeMachine{systemID: id}
   118  	}
   119  	controller := &fakeController{
   120  		machines:          machines,
   121  		machinesArgsCheck: checkArgs,
   122  	}
   123  	env = suite.makeEnviron(c, controller)
   124  	return env
   125  }
   126  
   127  func (suite *maas2EnvironSuite) TestAllInstances(c *gc.C) {
   128  	env := suite.makeEnvironWithMachines(
   129  		c, []string{}, []string{"tuco", "tio", "gus"},
   130  	)
   131  	result, err := env.AllInstances()
   132  	c.Assert(err, jc.ErrorIsNil)
   133  	expectedMachines := set.NewStrings("tuco", "tio", "gus")
   134  	actualMachines := set.NewStrings()
   135  	for _, instance := range result {
   136  		actualMachines.Add(string(instance.Id()))
   137  	}
   138  	c.Assert(actualMachines, gc.DeepEquals, expectedMachines)
   139  }
   140  
   141  func (suite *maas2EnvironSuite) TestAllInstancesError(c *gc.C) {
   142  	controller := &fakeController{machinesError: errors.New("Something terrible!")}
   143  	env := suite.makeEnviron(c, controller)
   144  	_, err := env.AllInstances()
   145  	c.Assert(err, gc.ErrorMatches, "Something terrible!")
   146  }
   147  
   148  func (suite *maas2EnvironSuite) TestInstances(c *gc.C) {
   149  	env := suite.makeEnvironWithMachines(
   150  		c, []string{"jake", "bonnibel"}, []string{"jake", "bonnibel"},
   151  	)
   152  	result, err := env.Instances([]instance.Id{"jake", "bonnibel"})
   153  	c.Assert(err, jc.ErrorIsNil)
   154  	expectedMachines := set.NewStrings("jake", "bonnibel")
   155  	actualMachines := set.NewStrings()
   156  	for _, machine := range result {
   157  		actualMachines.Add(string(machine.Id()))
   158  	}
   159  	c.Assert(actualMachines, gc.DeepEquals, expectedMachines)
   160  }
   161  
   162  func (suite *maas2EnvironSuite) TestInstancesPartialResult(c *gc.C) {
   163  	env := suite.makeEnvironWithMachines(
   164  		c, []string{"jake", "bonnibel"}, []string{"tuco", "bonnibel"},
   165  	)
   166  	result, err := env.Instances([]instance.Id{"jake", "bonnibel"})
   167  	c.Check(err, gc.Equals, environs.ErrPartialInstances)
   168  	c.Assert(result, gc.HasLen, 2)
   169  	c.Assert(result[0], gc.IsNil)
   170  	c.Assert(result[1].Id(), gc.Equals, instance.Id("bonnibel"))
   171  }
   172  
   173  func (suite *maas2EnvironSuite) TestAvailabilityZones(c *gc.C) {
   174  	controller := &fakeController{
   175  		zones: []gomaasapi.Zone{
   176  			&fakeZone{name: "mossack"},
   177  			&fakeZone{name: "fonseca"},
   178  		},
   179  	}
   180  	env := suite.makeEnviron(c, controller)
   181  	result, err := env.AvailabilityZones()
   182  	c.Assert(err, jc.ErrorIsNil)
   183  	expectedZones := set.NewStrings("mossack", "fonseca")
   184  	actualZones := set.NewStrings()
   185  	for _, zone := range result {
   186  		actualZones.Add(zone.Name())
   187  	}
   188  	c.Assert(actualZones, gc.DeepEquals, expectedZones)
   189  }
   190  
   191  func (suite *maas2EnvironSuite) TestAvailabilityZonesError(c *gc.C) {
   192  	controller := &fakeController{
   193  		zonesError: errors.New("a bad thing"),
   194  	}
   195  	env := suite.makeEnviron(c, controller)
   196  	_, err := env.AvailabilityZones()
   197  	c.Assert(err, gc.ErrorMatches, "a bad thing")
   198  }
   199  
   200  func (suite *maas2EnvironSuite) TestSpaces(c *gc.C) {
   201  	controller := &fakeController{
   202  		spaces: []gomaasapi.Space{
   203  			fakeSpace{
   204  				name: "pepper",
   205  				id:   1234,
   206  			},
   207  			fakeSpace{
   208  				name: "freckles",
   209  				id:   4567,
   210  				subnets: []gomaasapi.Subnet{
   211  					fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24"},
   212  					fakeSubnet{id: 98, vlan: fakeVLAN{vid: 67}, cidr: "192.168.11.0/24"},
   213  				},
   214  			},
   215  		},
   216  	}
   217  	env := suite.makeEnviron(c, controller)
   218  	result, err := env.Spaces()
   219  	c.Assert(err, jc.ErrorIsNil)
   220  	c.Assert(result, gc.HasLen, 1)
   221  	c.Assert(result[0].Name, gc.Equals, "freckles")
   222  	c.Assert(result[0].ProviderId, gc.Equals, network.Id("4567"))
   223  	subnets := result[0].Subnets
   224  	c.Assert(subnets, gc.HasLen, 2)
   225  	c.Assert(subnets[0].ProviderId, gc.Equals, network.Id("99"))
   226  	c.Assert(subnets[0].VLANTag, gc.Equals, 66)
   227  	c.Assert(subnets[0].CIDR, gc.Equals, "192.168.10.0/24")
   228  	c.Assert(subnets[0].SpaceProviderId, gc.Equals, network.Id("4567"))
   229  	c.Assert(subnets[1].ProviderId, gc.Equals, network.Id("98"))
   230  	c.Assert(subnets[1].VLANTag, gc.Equals, 67)
   231  	c.Assert(subnets[1].CIDR, gc.Equals, "192.168.11.0/24")
   232  	c.Assert(subnets[1].SpaceProviderId, gc.Equals, network.Id("4567"))
   233  }
   234  
   235  func (suite *maas2EnvironSuite) TestSpacesError(c *gc.C) {
   236  	controller := &fakeController{
   237  		spacesError: errors.New("Joe Manginiello"),
   238  	}
   239  	env := suite.makeEnviron(c, controller)
   240  	_, err := env.Spaces()
   241  	c.Assert(err, gc.ErrorMatches, "Joe Manginiello")
   242  }
   243  
   244  func collectReleaseArgs(controller *fakeController) []gomaasapi.ReleaseMachinesArgs {
   245  	args := []gomaasapi.ReleaseMachinesArgs{}
   246  	for _, call := range controller.Stub.Calls() {
   247  		if call.FuncName == "ReleaseMachines" {
   248  			args = append(args, call.Args[0].(gomaasapi.ReleaseMachinesArgs))
   249  		}
   250  	}
   251  	return args
   252  }
   253  
   254  func (suite *maas2EnvironSuite) TestStopInstancesReturnsIfParameterEmpty(c *gc.C) {
   255  	controller := newFakeController()
   256  	err := suite.makeEnviron(c, controller).StopInstances()
   257  	c.Check(err, jc.ErrorIsNil)
   258  	c.Assert(collectReleaseArgs(controller), gc.HasLen, 0)
   259  }
   260  
   261  func (suite *maas2EnvironSuite) TestStopInstancesStopsAndReleasesInstances(c *gc.C) {
   262  	// Return a cannot complete indicating that test1 is in the wrong state.
   263  	// The release operation will still release the others and succeed.
   264  	controller := newFakeControllerWithFiles(&fakeFile{name: "agent-prefix-provider-state"})
   265  	err := suite.makeEnviron(c, controller).StopInstances("test1", "test2", "test3")
   266  	c.Check(err, jc.ErrorIsNil)
   267  	args := collectReleaseArgs(controller)
   268  	c.Assert(args, gc.HasLen, 1)
   269  	c.Assert(args[0].SystemIDs, gc.DeepEquals, []string{"test1", "test2", "test3"})
   270  }
   271  
   272  func (suite *maas2EnvironSuite) TestStopInstancesIgnoresConflict(c *gc.C) {
   273  	// Return a cannot complete indicating that test1 is in the wrong state.
   274  	// The release operation will still release the others and succeed.
   275  	controller := newFakeControllerWithFiles(&fakeFile{name: "agent-prefix-provider-state"})
   276  	controller.SetErrors(gomaasapi.NewCannotCompleteError("test1 not allocated"))
   277  	err := suite.makeEnviron(c, controller).StopInstances("test1", "test2", "test3")
   278  	c.Check(err, jc.ErrorIsNil)
   279  
   280  	args := collectReleaseArgs(controller)
   281  	c.Assert(args, gc.HasLen, 1)
   282  	c.Assert(args[0].SystemIDs, gc.DeepEquals, []string{"test1", "test2", "test3"})
   283  }
   284  
   285  func (suite *maas2EnvironSuite) TestStopInstancesIgnoresMissingNodeAndRecurses(c *gc.C) {
   286  	controller := newFakeControllerWithFiles(&fakeFile{name: "agent-prefix-provider-state"})
   287  	controller.SetErrors(
   288  		gomaasapi.NewBadRequestError("no such machine: test1"),
   289  		gomaasapi.NewBadRequestError("no such machine: test1"),
   290  	)
   291  	err := suite.makeEnviron(c, controller).StopInstances("test1", "test2", "test3")
   292  	c.Check(err, jc.ErrorIsNil)
   293  	args := collectReleaseArgs(controller)
   294  	c.Assert(args, gc.HasLen, 4)
   295  	c.Assert(args[0].SystemIDs, gc.DeepEquals, []string{"test1", "test2", "test3"})
   296  	c.Assert(args[1].SystemIDs, gc.DeepEquals, []string{"test1"})
   297  	c.Assert(args[2].SystemIDs, gc.DeepEquals, []string{"test2"})
   298  	c.Assert(args[3].SystemIDs, gc.DeepEquals, []string{"test3"})
   299  }
   300  
   301  func (suite *maas2EnvironSuite) checkStopInstancesFails(c *gc.C, withError error) {
   302  	controller := newFakeControllerWithFiles(&fakeFile{name: "agent-prefix-provider-state"})
   303  	controller.SetErrors(withError)
   304  	err := suite.makeEnviron(c, controller).StopInstances("test1", "test2", "test3")
   305  	c.Check(err, gc.ErrorMatches, fmt.Sprintf("cannot release nodes: %s", withError))
   306  	// Only tries once.
   307  	c.Assert(collectReleaseArgs(controller), gc.HasLen, 1)
   308  }
   309  
   310  func (suite *maas2EnvironSuite) TestStopInstancesReturnsUnexpectedMAASError(c *gc.C) {
   311  	suite.checkStopInstancesFails(c, gomaasapi.NewNoMatchError("Something else bad!"))
   312  }
   313  
   314  func (suite *maas2EnvironSuite) TestStopInstancesReturnsUnexpectedError(c *gc.C) {
   315  	suite.checkStopInstancesFails(c, errors.New("Something completely unexpected!"))
   316  }
   317  
   318  func (suite *maas2EnvironSuite) TestStartInstanceError(c *gc.C) {
   319  	suite.injectController(&fakeController{
   320  		allocateMachineError: errors.New("Charles Babbage"),
   321  	})
   322  	env := suite.makeEnviron(c, nil)
   323  	_, err := env.StartInstance(environs.StartInstanceParams{})
   324  	c.Assert(err, gc.ErrorMatches, ".* cannot run instance: Charles Babbage")
   325  }
   326  
   327  func (suite *maas2EnvironSuite) TestStartInstance(c *gc.C) {
   328  	env := suite.injectControllerWithSpacesAndCheck(c, nil, gomaasapi.AllocateMachineArgs{})
   329  
   330  	params := environs.StartInstanceParams{}
   331  	result, err := testing.StartInstanceWithParams(env, "1", params)
   332  	c.Assert(err, jc.ErrorIsNil)
   333  	c.Assert(result.Instance.Id(), gc.Equals, instance.Id("Bruce Sterling"))
   334  }
   335  
   336  func (suite *maas2EnvironSuite) TestStartInstanceParams(c *gc.C) {
   337  	var env *maasEnviron
   338  	suite.injectController(&fakeController{
   339  		allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) {
   340  			c.Assert(args, gc.DeepEquals, gomaasapi.AllocateMachineArgs{
   341  				AgentName: env.ecfg().maasAgentName(),
   342  				Zone:      "foo",
   343  				MinMemory: 8192,
   344  			})
   345  		},
   346  		allocateMachine: &fakeMachine{
   347  			systemID:     "Bruce Sterling",
   348  			architecture: arch.HostArch(),
   349  		},
   350  		allocateMachineMatches: gomaasapi.ConstraintMatches{
   351  			Storage: map[string]gomaasapi.BlockDevice{},
   352  		},
   353  		zones: []gomaasapi.Zone{&fakeZone{name: "foo"}},
   354  	})
   355  	suite.setupFakeTools(c)
   356  	env = suite.makeEnviron(c, nil)
   357  	params := environs.StartInstanceParams{
   358  		Placement:   "zone=foo",
   359  		Constraints: constraints.MustParse("mem=8G"),
   360  	}
   361  	result, err := testing.StartInstanceWithParams(env, "1", params)
   362  	c.Assert(err, jc.ErrorIsNil)
   363  	c.Assert(result.Instance.Id(), gc.Equals, instance.Id("Bruce Sterling"))
   364  }
   365  
   366  func (suite *maas2EnvironSuite) TestAcquireNodePassedAgentName(c *gc.C) {
   367  	var env *maasEnviron
   368  	suite.injectController(&fakeController{
   369  		allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) {
   370  			c.Assert(args, gc.DeepEquals, gomaasapi.AllocateMachineArgs{
   371  				AgentName: env.ecfg().maasAgentName()})
   372  		},
   373  		allocateMachine: &fakeMachine{
   374  			systemID:     "Bruce Sterling",
   375  			architecture: arch.HostArch(),
   376  		},
   377  	})
   378  	suite.setupFakeTools(c)
   379  	env = suite.makeEnviron(c, nil)
   380  
   381  	_, err := env.acquireNode2("", "", constraints.Value{}, nil, nil)
   382  
   383  	c.Check(err, jc.ErrorIsNil)
   384  }
   385  
   386  func (suite *maas2EnvironSuite) TestAcquireNodePassesPositiveAndNegativeTags(c *gc.C) {
   387  	var env *maasEnviron
   388  	expected := gomaasapi.AllocateMachineArgs{
   389  		Tags:    []string{"tag1", "tag3"},
   390  		NotTags: []string{"tag2", "tag4"},
   391  	}
   392  	env = suite.injectControllerWithSpacesAndCheck(c, nil, expected)
   393  	_, err := env.acquireNode2(
   394  		"", "",
   395  		constraints.Value{Tags: stringslicep("tag1", "^tag2", "tag3", "^tag4")},
   396  		nil, nil,
   397  	)
   398  	c.Check(err, jc.ErrorIsNil)
   399  }
   400  
   401  func getFourSpaces() []gomaasapi.Space {
   402  	return []gomaasapi.Space{
   403  		fakeSpace{
   404  			name:    "space-1",
   405  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24"}},
   406  			id:      5,
   407  		},
   408  		fakeSpace{
   409  			name:    "space-2",
   410  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 100, vlan: fakeVLAN{vid: 66}, cidr: "192.168.11.0/24"}},
   411  			id:      6,
   412  		},
   413  		fakeSpace{
   414  			name:    "space-3",
   415  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 101, vlan: fakeVLAN{vid: 66}, cidr: "192.168.12.0/24"}},
   416  			id:      7,
   417  		},
   418  		fakeSpace{
   419  			name:    "space-4",
   420  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 102, vlan: fakeVLAN{vid: 66}, cidr: "192.168.13.0/24"}},
   421  			id:      8,
   422  		},
   423  	}
   424  
   425  }
   426  
   427  func (suite *maas2EnvironSuite) TestAcquireNodePassesPositiveAndNegativeSpaces(c *gc.C) {
   428  	expected := gomaasapi.AllocateMachineArgs{
   429  		NotSpace: []string{"6", "8"},
   430  		Interfaces: []gomaasapi.InterfaceSpec{
   431  			{Label: "0", Space: "5"},
   432  			{Label: "1", Space: "7"},
   433  		},
   434  	}
   435  	env := suite.injectControllerWithSpacesAndCheck(c, getFourSpaces(), expected)
   436  
   437  	_, err := env.acquireNode2(
   438  		"", "",
   439  		constraints.Value{Spaces: stringslicep("space-1", "^space-2", "space-3", "^space-4")},
   440  		nil, nil,
   441  	)
   442  	c.Check(err, jc.ErrorIsNil)
   443  }
   444  
   445  func (suite *maas2EnvironSuite) TestAcquireNodeDisambiguatesNamedLabelsFromIndexedUpToALimit(c *gc.C) {
   446  	env := suite.injectControllerWithSpacesAndCheck(c, getFourSpaces(), gomaasapi.AllocateMachineArgs{})
   447  	var shortLimit uint = 0
   448  	suite.PatchValue(&numericLabelLimit, shortLimit)
   449  
   450  	_, err := env.acquireNode2(
   451  		"", "",
   452  		constraints.Value{Spaces: stringslicep("space-1", "^space-2", "space-3", "^space-4")},
   453  		[]interfaceBinding{{"0", "first-clash"}, {"1", "final-clash"}},
   454  		nil,
   455  	)
   456  	c.Assert(err, gc.ErrorMatches, `too many conflicting numeric labels, giving up.`)
   457  }
   458  
   459  func (suite *maas2EnvironSuite) TestAcquireNodeStorage(c *gc.C) {
   460  	var env *maasEnviron
   461  	var getStorage func() []gomaasapi.StorageSpec
   462  	suite.injectController(&fakeController{
   463  		allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) {
   464  			c.Assert(args, jc.DeepEquals, gomaasapi.AllocateMachineArgs{
   465  				AgentName: env.ecfg().maasAgentName(),
   466  				Storage:   getStorage(),
   467  			})
   468  		},
   469  		allocateMachine: &fakeMachine{
   470  			systemID:     "Bruce Sterling",
   471  			architecture: arch.HostArch(),
   472  		},
   473  	})
   474  	suite.setupFakeTools(c)
   475  	for i, test := range []struct {
   476  		volumes  []volumeInfo
   477  		expected []gomaasapi.StorageSpec
   478  	}{{
   479  		volumes:  nil,
   480  		expected: []gomaasapi.StorageSpec{},
   481  	}, {
   482  		volumes:  []volumeInfo{{"volume-1", 1234, nil}},
   483  		expected: []gomaasapi.StorageSpec{{"volume-1", 1234, nil}},
   484  	}, {
   485  		volumes:  []volumeInfo{{"", 1234, []string{"tag1", "tag2"}}},
   486  		expected: []gomaasapi.StorageSpec{{"", 1234, []string{"tag1", "tag2"}}},
   487  	}, {
   488  		volumes:  []volumeInfo{{"volume-1", 1234, []string{"tag1", "tag2"}}},
   489  		expected: []gomaasapi.StorageSpec{{"volume-1", 1234, []string{"tag1", "tag2"}}},
   490  	}, {
   491  		volumes: []volumeInfo{
   492  			{"volume-1", 1234, []string{"tag1", "tag2"}},
   493  			{"volume-2", 4567, []string{"tag1", "tag3"}},
   494  		},
   495  		expected: []gomaasapi.StorageSpec{
   496  			{"volume-1", 1234, []string{"tag1", "tag2"}},
   497  			{"volume-2", 4567, []string{"tag1", "tag3"}},
   498  		},
   499  	}} {
   500  		c.Logf("test #%d: volumes=%v", i, test.volumes)
   501  		getStorage = func() []gomaasapi.StorageSpec {
   502  			return test.expected
   503  		}
   504  		env = suite.makeEnviron(c, nil)
   505  		_, err := env.acquireNode2("", "", constraints.Value{}, nil, test.volumes)
   506  		c.Check(err, jc.ErrorIsNil)
   507  	}
   508  }
   509  
   510  func (suite *maas2EnvironSuite) TestAcquireNodeInterfaces(c *gc.C) {
   511  	var env *maasEnviron
   512  	var getNegatives func() []string
   513  	var getPositives func() []gomaasapi.InterfaceSpec
   514  	suite.injectController(&fakeController{
   515  		allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) {
   516  			c.Assert(args, gc.DeepEquals, gomaasapi.AllocateMachineArgs{
   517  				AgentName:  env.ecfg().maasAgentName(),
   518  				Interfaces: getPositives(),
   519  				NotSpace:   getNegatives(),
   520  			})
   521  		},
   522  		allocateMachine: &fakeMachine{
   523  			systemID:     "Bruce Sterling",
   524  			architecture: arch.HostArch(),
   525  		},
   526  		spaces: getTwoSpaces(),
   527  	})
   528  	suite.setupFakeTools(c)
   529  	// Add some constraints, including spaces to verify specified bindings
   530  	// always override any spaces constraints.
   531  	cons := constraints.Value{
   532  		Spaces: stringslicep("foo", "^bar"),
   533  	}
   534  	// In the tests below "space:5" means foo, "space:6" means bar.
   535  	for i, test := range []struct {
   536  		interfaces        []interfaceBinding
   537  		expectedPositives []gomaasapi.InterfaceSpec
   538  		expectedNegatives []string
   539  		expectedError     string
   540  	}{{ // without specified bindings, spaces constraints are used instead.
   541  		interfaces:        nil,
   542  		expectedPositives: []gomaasapi.InterfaceSpec{{"0", "2"}},
   543  		expectedNegatives: []string{"3"},
   544  		expectedError:     "",
   545  	}, {
   546  		interfaces:        []interfaceBinding{{"name-1", "space-1"}},
   547  		expectedPositives: []gomaasapi.InterfaceSpec{{"name-1", "space-1"}, {"0", "2"}},
   548  		expectedNegatives: []string{"3"},
   549  	}, {
   550  		interfaces: []interfaceBinding{
   551  			{"name-1", "7"},
   552  			{"name-2", "8"},
   553  			{"name-3", "9"},
   554  		},
   555  		expectedPositives: []gomaasapi.InterfaceSpec{{"name-1", "7"}, {"name-2", "8"}, {"name-3", "9"}, {"0", "2"}},
   556  		expectedNegatives: []string{"3"},
   557  	}, {
   558  		interfaces:    []interfaceBinding{{"", "anything"}},
   559  		expectedError: "interface bindings cannot have empty names",
   560  	}, {
   561  		interfaces:    []interfaceBinding{{"shared-db", "3"}},
   562  		expectedError: `negative space "bar" from constraints clashes with interface bindings`,
   563  	}, {
   564  		interfaces: []interfaceBinding{
   565  			{"shared-db", "1"},
   566  			{"db", "1"},
   567  		},
   568  		expectedPositives: []gomaasapi.InterfaceSpec{{"shared-db", "1"}, {"db", "1"}, {"0", "2"}},
   569  		expectedNegatives: []string{"3"},
   570  	}, {
   571  		interfaces:    []interfaceBinding{{"", ""}},
   572  		expectedError: "interface bindings cannot have empty names",
   573  	}, {
   574  		interfaces: []interfaceBinding{
   575  			{"valid", "ok"},
   576  			{"", "valid-but-ignored-space"},
   577  			{"valid-name-empty-space", ""},
   578  			{"", ""},
   579  		},
   580  		expectedError: "interface bindings cannot have empty names",
   581  	}, {
   582  		interfaces:    []interfaceBinding{{"foo", ""}},
   583  		expectedError: `invalid interface binding "foo": space provider ID is required`,
   584  	}, {
   585  		interfaces: []interfaceBinding{
   586  			{"bar", ""},
   587  			{"valid", "ok"},
   588  			{"", "valid-but-ignored-space"},
   589  			{"", ""},
   590  		},
   591  		expectedError: `invalid interface binding "bar": space provider ID is required`,
   592  	}, {
   593  		interfaces: []interfaceBinding{
   594  			{"dup-name", "1"},
   595  			{"dup-name", "2"},
   596  		},
   597  		expectedError: `duplicated interface binding "dup-name"`,
   598  	}, {
   599  		interfaces: []interfaceBinding{
   600  			{"valid-1", "0"},
   601  			{"dup-name", "1"},
   602  			{"dup-name", "2"},
   603  			{"valid-2", "3"},
   604  		},
   605  		expectedError: `duplicated interface binding "dup-name"`,
   606  	}} {
   607  		c.Logf("test #%d: interfaces=%v", i, test.interfaces)
   608  		env = suite.makeEnviron(c, nil)
   609  		getNegatives = func() []string {
   610  			return test.expectedNegatives
   611  		}
   612  		getPositives = func() []gomaasapi.InterfaceSpec {
   613  			return test.expectedPositives
   614  		}
   615  		_, err := env.acquireNode2("", "", cons, test.interfaces, nil)
   616  		if test.expectedError != "" {
   617  			c.Check(err, gc.ErrorMatches, test.expectedError)
   618  			c.Check(err, jc.Satisfies, errors.IsNotValid)
   619  			continue
   620  		}
   621  		c.Check(err, jc.ErrorIsNil)
   622  	}
   623  }
   624  
   625  func getTwoSpaces() []gomaasapi.Space {
   626  	return []gomaasapi.Space{
   627  		fakeSpace{
   628  			name:    "foo",
   629  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24"}},
   630  			id:      2,
   631  		},
   632  		fakeSpace{
   633  			name:    "bar",
   634  			subnets: []gomaasapi.Subnet{fakeSubnet{id: 100, vlan: fakeVLAN{vid: 66}, cidr: "192.168.11.0/24"}},
   635  			id:      3,
   636  		},
   637  	}
   638  }
   639  
   640  func (suite *maas2EnvironSuite) TestAcquireNodeConvertsSpaceNames(c *gc.C) {
   641  	expected := gomaasapi.AllocateMachineArgs{
   642  		NotSpace:   []string{"3"},
   643  		Interfaces: []gomaasapi.InterfaceSpec{{Label: "0", Space: "2"}},
   644  	}
   645  	env := suite.injectControllerWithSpacesAndCheck(c, getTwoSpaces(), expected)
   646  	cons := constraints.Value{
   647  		Spaces: stringslicep("foo", "^bar"),
   648  	}
   649  	_, err := env.acquireNode2("", "", cons, nil, nil)
   650  	c.Assert(err, jc.ErrorIsNil)
   651  }
   652  
   653  func (suite *maas2EnvironSuite) TestAcquireNodeTranslatesSpaceNames(c *gc.C) {
   654  	expected := gomaasapi.AllocateMachineArgs{
   655  		NotSpace:   []string{"3"},
   656  		Interfaces: []gomaasapi.InterfaceSpec{{Label: "0", Space: "2"}},
   657  	}
   658  	env := suite.injectControllerWithSpacesAndCheck(c, getTwoSpaces(), expected)
   659  	cons := constraints.Value{
   660  		Spaces: stringslicep("foo-1", "^bar-3"),
   661  	}
   662  	_, err := env.acquireNode2("", "", cons, nil, nil)
   663  	c.Assert(err, jc.ErrorIsNil)
   664  }
   665  
   666  func (suite *maas2EnvironSuite) TestAcquireNodeUnrecognisedSpace(c *gc.C) {
   667  	suite.injectController(&fakeController{})
   668  	env := suite.makeEnviron(c, nil)
   669  	cons := constraints.Value{
   670  		Spaces: stringslicep("baz"),
   671  	}
   672  	_, err := env.acquireNode2("", "", cons, nil, nil)
   673  	c.Assert(err, gc.ErrorMatches, `unrecognised space in constraint "baz"`)
   674  }
   675  
   676  func (suite *maas2EnvironSuite) TestWaitForNodeDeploymentError(c *gc.C) {
   677  	machine := &fakeMachine{
   678  		systemID:     "Bruce Sterling",
   679  		architecture: arch.HostArch(),
   680  	}
   681  	controller := newFakeController()
   682  	controller.allocateMachine = machine
   683  	controller.allocateMachineMatches = gomaasapi.ConstraintMatches{
   684  		Storage: map[string]gomaasapi.BlockDevice{},
   685  	}
   686  	controller.machines = []gomaasapi.Machine{machine}
   687  	suite.injectController(controller)
   688  	suite.setupFakeTools(c)
   689  	env := suite.makeEnviron(c, nil)
   690  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   691  	c.Assert(err, gc.ErrorMatches, "bootstrap instance started but did not change to Deployed state.*")
   692  }
   693  
   694  func (suite *maas2EnvironSuite) TestWaitForNodeDeploymentSucceeds(c *gc.C) {
   695  	machine := &fakeMachine{
   696  		systemID:     "Bruce Sterling",
   697  		architecture: arch.HostArch(),
   698  		statusName:   "Deployed",
   699  	}
   700  
   701  	controller := newFakeController()
   702  	controller.allocateMachine = machine
   703  	controller.allocateMachineMatches = gomaasapi.ConstraintMatches{
   704  		Storage: map[string]gomaasapi.BlockDevice{},
   705  	}
   706  	controller.machines = []gomaasapi.Machine{machine}
   707  	suite.injectController(controller)
   708  	suite.setupFakeTools(c)
   709  	env := suite.makeEnviron(c, nil)
   710  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   711  	c.Assert(err, jc.ErrorIsNil)
   712  }
   713  
   714  func (suite *maas2EnvironSuite) TestSubnetsNoFilters(c *gc.C) {
   715  	suite.injectController(&fakeController{
   716  		spaces: getFourSpaces(),
   717  	})
   718  	env := suite.makeEnviron(c, nil)
   719  	subnets, err := env.Subnets("", nil)
   720  	c.Assert(err, jc.ErrorIsNil)
   721  	expected := []network.SubnetInfo{
   722  		{CIDR: "192.168.10.0/24", ProviderId: "99", VLANTag: 66, SpaceProviderId: "5"},
   723  		{CIDR: "192.168.11.0/24", ProviderId: "100", VLANTag: 66, SpaceProviderId: "6"},
   724  		{CIDR: "192.168.12.0/24", ProviderId: "101", VLANTag: 66, SpaceProviderId: "7"},
   725  		{CIDR: "192.168.13.0/24", ProviderId: "102", VLANTag: 66, SpaceProviderId: "8"},
   726  	}
   727  	c.Assert(subnets, jc.DeepEquals, expected)
   728  }
   729  
   730  func (suite *maas2EnvironSuite) TestSubnetsNoFiltersError(c *gc.C) {
   731  	suite.injectController(&fakeController{
   732  		spacesError: errors.New("bang"),
   733  	})
   734  	env := suite.makeEnviron(c, nil)
   735  	_, err := env.Subnets("", nil)
   736  	c.Assert(err, gc.ErrorMatches, "bang")
   737  }
   738  
   739  func (suite *maas2EnvironSuite) TestSubnetsSubnetIds(c *gc.C) {
   740  	suite.injectController(&fakeController{
   741  		spaces: getFourSpaces(),
   742  	})
   743  	env := suite.makeEnviron(c, nil)
   744  	subnets, err := env.Subnets("", []network.Id{"99", "100"})
   745  	c.Assert(err, jc.ErrorIsNil)
   746  	expected := []network.SubnetInfo{
   747  		{CIDR: "192.168.10.0/24", ProviderId: "99", VLANTag: 66, SpaceProviderId: "5"},
   748  		{CIDR: "192.168.11.0/24", ProviderId: "100", VLANTag: 66, SpaceProviderId: "6"},
   749  	}
   750  	c.Assert(subnets, jc.DeepEquals, expected)
   751  }
   752  
   753  func (suite *maas2EnvironSuite) TestSubnetsSubnetIdsMissing(c *gc.C) {
   754  	suite.injectController(&fakeController{
   755  		spaces: getFourSpaces(),
   756  	})
   757  	env := suite.makeEnviron(c, nil)
   758  	_, err := env.Subnets("", []network.Id{"99", "missing"})
   759  	msg := "failed to find the following subnets: missing"
   760  	c.Assert(err, gc.ErrorMatches, msg)
   761  }
   762  
   763  func (suite *maas2EnvironSuite) TestSubnetsInstIdNotFound(c *gc.C) {
   764  	suite.injectController(&fakeController{})
   765  	env := suite.makeEnviron(c, nil)
   766  	_, err := env.Subnets("foo", nil)
   767  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   768  }
   769  
   770  func (suite *maas2EnvironSuite) TestSubnetsInstId(c *gc.C) {
   771  	interfaces := []gomaasapi.Interface{
   772  		&fakeInterface{
   773  			links: []gomaasapi.Link{
   774  				&fakeLink{subnet: fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24", space: "space-1"}},
   775  				&fakeLink{subnet: fakeSubnet{id: 100, vlan: fakeVLAN{vid: 0}, cidr: "192.168.11.0/24", space: "space-2"}},
   776  			},
   777  		},
   778  		&fakeInterface{
   779  			links: []gomaasapi.Link{
   780  				&fakeLink{subnet: fakeSubnet{id: 101, vlan: fakeVLAN{vid: 2}, cidr: "192.168.12.0/24", space: "space-3"}},
   781  			},
   782  		},
   783  	}
   784  	machine := &fakeMachine{
   785  		systemID:     "William Gibson",
   786  		interfaceSet: interfaces,
   787  	}
   788  	machine2 := &fakeMachine{systemID: "Bruce Sterling"}
   789  	suite.injectController(&fakeController{
   790  		machines: []gomaasapi.Machine{machine, machine2},
   791  		spaces:   getFourSpaces(),
   792  	})
   793  	env := suite.makeEnviron(c, nil)
   794  	subnets, err := env.Subnets("William Gibson", nil)
   795  	c.Assert(err, jc.ErrorIsNil)
   796  	expected := []network.SubnetInfo{
   797  		{CIDR: "192.168.10.0/24", ProviderId: "99", VLANTag: 66, SpaceProviderId: "5"},
   798  		{CIDR: "192.168.11.0/24", ProviderId: "100", VLANTag: 0, SpaceProviderId: "6"},
   799  		{CIDR: "192.168.12.0/24", ProviderId: "101", VLANTag: 2, SpaceProviderId: "7"},
   800  	}
   801  	c.Assert(subnets, jc.DeepEquals, expected)
   802  }
   803  
   804  func (suite *maas2EnvironSuite) TestStartInstanceNetworkInterfaces(c *gc.C) {
   805  	vlan0 := fakeVLAN{
   806  		id:  5001,
   807  		vid: 0,
   808  		mtu: 1500,
   809  	}
   810  
   811  	vlan50 := fakeVLAN{
   812  		id:  5004,
   813  		vid: 50,
   814  		mtu: 1500,
   815  	}
   816  
   817  	subnetPXE := fakeSubnet{
   818  		id:         3,
   819  		space:      "default",
   820  		vlan:       vlan0,
   821  		gateway:    "10.20.19.2",
   822  		cidr:       "10.20.19.0/24",
   823  		dnsServers: []string{"10.20.19.2", "10.20.19.3"},
   824  	}
   825  
   826  	exampleInterfaces := []gomaasapi.Interface{
   827  		&fakeInterface{
   828  			id:         91,
   829  			name:       "eth0",
   830  			type_:      "physical",
   831  			enabled:    true,
   832  			macAddress: "52:54:00:70:9b:fe",
   833  			vlan:       vlan0,
   834  			links: []gomaasapi.Link{
   835  				&fakeLink{
   836  					id:        436,
   837  					subnet:    &subnetPXE,
   838  					ipAddress: "10.20.19.103",
   839  					mode:      "static",
   840  				},
   841  				&fakeLink{
   842  					id:        437,
   843  					subnet:    &subnetPXE,
   844  					ipAddress: "10.20.19.104",
   845  					mode:      "static",
   846  				},
   847  			},
   848  			parents:  []string{},
   849  			children: []string{"eth0.100", "eth0.250", "eth0.50"},
   850  		},
   851  		&fakeInterface{
   852  			id:         150,
   853  			name:       "eth0.50",
   854  			type_:      "vlan",
   855  			enabled:    true,
   856  			macAddress: "52:54:00:70:9b:fe",
   857  			vlan:       vlan50,
   858  			links: []gomaasapi.Link{
   859  				&fakeLink{
   860  					id: 517,
   861  					subnet: &fakeSubnet{
   862  						id:         5,
   863  						space:      "admin",
   864  						vlan:       vlan50,
   865  						gateway:    "10.50.19.2",
   866  						cidr:       "10.50.19.0/24",
   867  						dnsServers: []string{},
   868  					},
   869  					ipAddress: "10.50.19.103",
   870  					mode:      "static",
   871  				},
   872  			},
   873  			parents:  []string{"eth0"},
   874  			children: []string{},
   875  		},
   876  	}
   877  	var env *maasEnviron
   878  	controller := &fakeController{
   879  		allocateMachine: &fakeMachine{
   880  			systemID:     "Bruce Sterling",
   881  			architecture: arch.HostArch(),
   882  			interfaceSet: exampleInterfaces,
   883  		},
   884  		allocateMachineMatches: gomaasapi.ConstraintMatches{
   885  			Storage: map[string]gomaasapi.BlockDevice{},
   886  		},
   887  	}
   888  	suite.injectController(controller)
   889  	suite.setupFakeTools(c)
   890  	env = suite.makeEnviron(c, nil)
   891  
   892  	params := environs.StartInstanceParams{}
   893  	result, err := testing.StartInstanceWithParams(env, "1", params)
   894  	c.Assert(err, jc.ErrorIsNil)
   895  	expected := []network.InterfaceInfo{{
   896  		DeviceIndex:       0,
   897  		MACAddress:        "52:54:00:70:9b:fe",
   898  		CIDR:              "10.20.19.0/24",
   899  		ProviderId:        "91",
   900  		ProviderSubnetId:  "3",
   901  		AvailabilityZones: nil,
   902  		VLANTag:           0,
   903  		ProviderVLANId:    "5001",
   904  		ProviderAddressId: "436",
   905  		InterfaceName:     "eth0",
   906  		InterfaceType:     "ethernet",
   907  		Disabled:          false,
   908  		NoAutoStart:       false,
   909  		ConfigType:        "static",
   910  		Address:           network.NewAddressOnSpace("default", "10.20.19.103"),
   911  		DNSServers:        network.NewAddressesOnSpace("default", "10.20.19.2", "10.20.19.3"),
   912  		DNSSearchDomains:  nil,
   913  		MTU:               1500,
   914  		GatewayAddress:    network.NewAddressOnSpace("default", "10.20.19.2"),
   915  	}, {
   916  		DeviceIndex:       0,
   917  		MACAddress:        "52:54:00:70:9b:fe",
   918  		CIDR:              "10.20.19.0/24",
   919  		ProviderId:        "91",
   920  		ProviderSubnetId:  "3",
   921  		AvailabilityZones: nil,
   922  		VLANTag:           0,
   923  		ProviderVLANId:    "5001",
   924  		ProviderAddressId: "437",
   925  		InterfaceName:     "eth0",
   926  		InterfaceType:     "ethernet",
   927  		Disabled:          false,
   928  		NoAutoStart:       false,
   929  		ConfigType:        "static",
   930  		Address:           network.NewAddressOnSpace("default", "10.20.19.104"),
   931  		DNSServers:        network.NewAddressesOnSpace("default", "10.20.19.2", "10.20.19.3"),
   932  		DNSSearchDomains:  nil,
   933  		MTU:               1500,
   934  		GatewayAddress:    network.NewAddressOnSpace("default", "10.20.19.2"),
   935  	}, {
   936  		DeviceIndex:         1,
   937  		MACAddress:          "52:54:00:70:9b:fe",
   938  		CIDR:                "10.50.19.0/24",
   939  		ProviderId:          "150",
   940  		ProviderSubnetId:    "5",
   941  		AvailabilityZones:   nil,
   942  		VLANTag:             50,
   943  		ProviderVLANId:      "5004",
   944  		ProviderAddressId:   "517",
   945  		InterfaceName:       "eth0.50",
   946  		ParentInterfaceName: "eth0",
   947  		InterfaceType:       "802.1q",
   948  		Disabled:            false,
   949  		NoAutoStart:         false,
   950  		ConfigType:          "static",
   951  		Address:             network.NewAddressOnSpace("admin", "10.50.19.103"),
   952  		DNSServers:          nil,
   953  		DNSSearchDomains:    nil,
   954  		MTU:                 1500,
   955  		GatewayAddress:      network.NewAddressOnSpace("admin", "10.50.19.2"),
   956  	},
   957  	}
   958  	c.Assert(result.NetworkInfo, jc.DeepEquals, expected)
   959  }