github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/provider/maas/environ_whitebox_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package maas
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/base64"
     9  	"encoding/json"
    10  	"fmt"
    11  	"net/url"
    12  	"regexp"
    13  
    14  	"github.com/juju/errors"
    15  	"github.com/juju/gomaasapi"
    16  	jc "github.com/juju/testing/checkers"
    17  	"github.com/juju/utils"
    18  	"github.com/juju/utils/arch"
    19  	"github.com/juju/utils/series"
    20  	"github.com/juju/version"
    21  	gc "gopkg.in/check.v1"
    22  	"gopkg.in/juju/names.v2"
    23  	goyaml "gopkg.in/yaml.v2"
    24  
    25  	"github.com/juju/juju/cloudconfig/cloudinit"
    26  	"github.com/juju/juju/constraints"
    27  	"github.com/juju/juju/environs"
    28  	"github.com/juju/juju/environs/bootstrap"
    29  	envstorage "github.com/juju/juju/environs/storage"
    30  	envtesting "github.com/juju/juju/environs/testing"
    31  	envtools "github.com/juju/juju/environs/tools"
    32  	"github.com/juju/juju/instance"
    33  	"github.com/juju/juju/juju/testing"
    34  	"github.com/juju/juju/network"
    35  	"github.com/juju/juju/provider/common"
    36  	"github.com/juju/juju/storage"
    37  	coretesting "github.com/juju/juju/testing"
    38  )
    39  
    40  type environSuite struct {
    41  	providerSuite
    42  }
    43  
    44  const (
    45  	allocatedNode = `{"system_id": "test-allocated"}`
    46  )
    47  
    48  var _ = gc.Suite(&environSuite{})
    49  
    50  // ifaceInfo describes an interface to be created on the test server.
    51  type ifaceInfo struct {
    52  	DeviceIndex   int
    53  	InterfaceName string
    54  	Disabled      bool
    55  }
    56  
    57  func (suite *environSuite) addNode(jsonText string) instance.Id {
    58  	node := suite.testMAASObject.TestServer.NewNode(jsonText)
    59  	resourceURI, _ := node.GetField("resource_uri")
    60  	return instance.Id(resourceURI)
    61  }
    62  
    63  func (suite *environSuite) TestInstancesReturnsInstances(c *gc.C) {
    64  	id := suite.addNode(allocatedNode)
    65  	instances, err := suite.makeEnviron().Instances([]instance.Id{id})
    66  
    67  	c.Check(err, jc.ErrorIsNil)
    68  	c.Assert(instances, gc.HasLen, 1)
    69  	c.Assert(instances[0].Id(), gc.Equals, id)
    70  }
    71  
    72  func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfEmptyParameter(c *gc.C) {
    73  	suite.addNode(allocatedNode)
    74  	instances, err := suite.makeEnviron().Instances([]instance.Id{})
    75  
    76  	c.Check(err, gc.Equals, environs.ErrNoInstances)
    77  	c.Check(instances, gc.IsNil)
    78  }
    79  
    80  func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNilParameter(c *gc.C) {
    81  	suite.addNode(allocatedNode)
    82  	instances, err := suite.makeEnviron().Instances(nil)
    83  
    84  	c.Check(err, gc.Equals, environs.ErrNoInstances)
    85  	c.Check(instances, gc.IsNil)
    86  }
    87  
    88  func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNoneFound(c *gc.C) {
    89  	instances, err := suite.makeEnviron().Instances([]instance.Id{"unknown"})
    90  	c.Check(err, gc.Equals, environs.ErrNoInstances)
    91  	c.Check(instances, gc.IsNil)
    92  }
    93  
    94  func (suite *environSuite) TestAllInstances(c *gc.C) {
    95  	id := suite.addNode(allocatedNode)
    96  	instances, err := suite.makeEnviron().AllInstances()
    97  
    98  	c.Check(err, jc.ErrorIsNil)
    99  	c.Assert(instances, gc.HasLen, 1)
   100  	c.Assert(instances[0].Id(), gc.Equals, id)
   101  }
   102  
   103  func (suite *environSuite) TestAllInstancesReturnsEmptySliceIfNoInstance(c *gc.C) {
   104  	instances, err := suite.makeEnviron().AllInstances()
   105  
   106  	c.Check(err, jc.ErrorIsNil)
   107  	c.Check(instances, gc.HasLen, 0)
   108  }
   109  
   110  func (suite *environSuite) TestInstancesReturnsErrorIfPartialInstances(c *gc.C) {
   111  	known := suite.addNode(allocatedNode)
   112  	suite.addNode(`{"system_id": "test2"}`)
   113  	unknown := instance.Id("unknown systemID")
   114  	instances, err := suite.makeEnviron().Instances([]instance.Id{known, unknown})
   115  
   116  	c.Check(err, gc.Equals, environs.ErrPartialInstances)
   117  	c.Assert(instances, gc.HasLen, 2)
   118  	c.Check(instances[0].Id(), gc.Equals, known)
   119  	c.Check(instances[1], gc.IsNil)
   120  }
   121  
   122  func (suite *environSuite) TestStorageReturnsStorage(c *gc.C) {
   123  	env := suite.makeEnviron()
   124  	stor := env.Storage()
   125  	c.Check(stor, gc.NotNil)
   126  	// The Storage object is really a maasStorage.
   127  	specificStorage := stor.(*maas1Storage)
   128  	// Its environment pointer refers back to its environment.
   129  	c.Check(specificStorage.environ, gc.Equals, env)
   130  }
   131  
   132  func decodeUserData(userData string) ([]byte, error) {
   133  	data, err := base64.StdEncoding.DecodeString(userData)
   134  	if err != nil {
   135  		return []byte(""), err
   136  	}
   137  	return utils.Gunzip(data)
   138  }
   139  
   140  func (suite *environSuite) TestStartInstanceStartsInstance(c *gc.C) {
   141  	suite.setupFakeTools(c)
   142  	env := suite.makeEnviron()
   143  	// Create node 0: it will be used as the bootstrap node.
   144  	suite.newNode(c, "node0", "host0", nil)
   145  	suite.addSubnet(c, 9, 9, "node0")
   146  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
   147  		ControllerConfig: coretesting.FakeControllerConfig(),
   148  		AdminSecret:      testing.AdminSecret,
   149  		CAPrivateKey:     coretesting.CAKey,
   150  	})
   151  	c.Assert(err, jc.ErrorIsNil)
   152  	// The bootstrap node has been acquired and started.
   153  	operations := suite.testMAASObject.TestServer.NodeOperations()
   154  	actions, found := operations["node0"]
   155  	c.Check(found, jc.IsTrue)
   156  	c.Check(actions, gc.DeepEquals, []string{"acquire", "start"})
   157  
   158  	// Test the instance id is correctly recorded for the bootstrap node.
   159  	// Check that ControllerInstances returns the id of the bootstrap machine.
   160  	instanceIds, err := env.ControllerInstances(suite.controllerUUID)
   161  	c.Assert(err, jc.ErrorIsNil)
   162  	c.Assert(instanceIds, gc.HasLen, 1)
   163  	insts, err := env.AllInstances()
   164  	c.Assert(err, jc.ErrorIsNil)
   165  	c.Assert(insts, gc.HasLen, 1)
   166  	c.Check(insts[0].Id(), gc.Equals, instanceIds[0])
   167  
   168  	// Create node 1: it will be used as instance number 1.
   169  	suite.newNode(c, "node1", "host1", nil)
   170  	suite.addSubnet(c, 8, 8, "node1")
   171  	instance, hc := testing.AssertStartInstance(c, env, suite.controllerUUID, "1")
   172  	c.Assert(err, jc.ErrorIsNil)
   173  	c.Check(instance, gc.NotNil)
   174  	c.Assert(hc, gc.NotNil)
   175  	c.Check(hc.String(), gc.Equals, fmt.Sprintf("arch=%s cores=1 mem=1024M availability-zone=test_zone", arch.HostArch()))
   176  
   177  	// The instance number 1 has been acquired and started.
   178  	actions, found = operations["node1"]
   179  	c.Assert(found, jc.IsTrue)
   180  	c.Check(actions, gc.DeepEquals, []string{"acquire", "start"})
   181  
   182  	// The value of the "user data" parameter used when starting the node
   183  	// contains the run cmd used to write the machine information onto
   184  	// the node's filesystem.
   185  	requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues()
   186  	nodeRequestValues, found := requestValues["node1"]
   187  	c.Assert(found, jc.IsTrue)
   188  	c.Assert(len(nodeRequestValues), gc.Equals, 2)
   189  	userData := nodeRequestValues[1].Get("user_data")
   190  	decodedUserData, err := decodeUserData(userData)
   191  	c.Assert(err, jc.ErrorIsNil)
   192  	info := machineInfo{"host1"}
   193  	cloudcfg, err := cloudinit.New("precise")
   194  	c.Assert(err, jc.ErrorIsNil)
   195  	cloudinitRunCmd, err := info.cloudinitRunCmd(cloudcfg)
   196  	c.Assert(err, jc.ErrorIsNil)
   197  	data, err := goyaml.Marshal(cloudinitRunCmd)
   198  	c.Assert(err, jc.ErrorIsNil)
   199  	c.Check(string(decodedUserData), jc.Contains, string(data))
   200  
   201  	// Trash the tools and try to start another instance.
   202  	suite.PatchValue(&envtools.DefaultBaseURL, "")
   203  	instance, _, _, err = testing.StartInstance(env, suite.controllerUUID, "2")
   204  	c.Check(instance, gc.IsNil)
   205  	c.Check(err, jc.Satisfies, errors.IsNotFound)
   206  }
   207  
   208  func (suite *environSuite) getInstance(systemId string) *maas1Instance {
   209  	input := fmt.Sprintf(`{"system_id": %q}`, systemId)
   210  	node := suite.testMAASObject.TestServer.NewNode(input)
   211  	statusGetter := func(instance.Id) (string, string) {
   212  		return "unknown", "FAKE"
   213  	}
   214  
   215  	return &maas1Instance{&node, nil, statusGetter}
   216  }
   217  
   218  func (suite *environSuite) TestStopInstancesReturnsIfParameterEmpty(c *gc.C) {
   219  	suite.getInstance("test1")
   220  
   221  	err := suite.makeEnviron().StopInstances()
   222  	c.Check(err, jc.ErrorIsNil)
   223  	operations := suite.testMAASObject.TestServer.NodeOperations()
   224  	c.Check(operations, gc.DeepEquals, map[string][]string{})
   225  }
   226  
   227  func (suite *environSuite) TestStopInstancesStopsAndReleasesInstances(c *gc.C) {
   228  	suite.getInstance("test1")
   229  	suite.getInstance("test2")
   230  	suite.getInstance("test3")
   231  	// mark test1 and test2 as being allocated, but not test3.
   232  	// The release operation will ignore test3.
   233  	suite.testMAASObject.TestServer.OwnedNodes()["test1"] = true
   234  	suite.testMAASObject.TestServer.OwnedNodes()["test2"] = true
   235  
   236  	err := suite.makeEnviron().StopInstances("test1", "test2", "test3")
   237  	c.Check(err, jc.ErrorIsNil)
   238  	operations := suite.testMAASObject.TestServer.NodesOperations()
   239  	c.Check(operations, gc.DeepEquals, []string{"release"})
   240  	c.Assert(suite.testMAASObject.TestServer.OwnedNodes()["test1"], jc.IsFalse)
   241  	c.Assert(suite.testMAASObject.TestServer.OwnedNodes()["test2"], jc.IsFalse)
   242  }
   243  
   244  func (suite *environSuite) TestStopInstancesIgnoresConflict(c *gc.C) {
   245  	releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error {
   246  		return gomaasapi.ServerError{StatusCode: 409}
   247  	}
   248  	suite.PatchValue(&ReleaseNodes, releaseNodes)
   249  	env := suite.makeEnviron()
   250  	err := env.StopInstances("test1")
   251  	c.Assert(err, jc.ErrorIsNil)
   252  }
   253  
   254  func (suite *environSuite) TestStopInstancesIgnoresMissingNodeAndRecurses(c *gc.C) {
   255  	attemptedNodes := [][]string{}
   256  	releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error {
   257  		attemptedNodes = append(attemptedNodes, ids["nodes"])
   258  		return gomaasapi.ServerError{StatusCode: 404}
   259  	}
   260  	suite.PatchValue(&ReleaseNodes, releaseNodes)
   261  	env := suite.makeEnviron()
   262  	err := env.StopInstances("test1", "test2")
   263  	c.Assert(err, jc.ErrorIsNil)
   264  
   265  	expectedNodes := [][]string{{"test1", "test2"}, {"test1"}, {"test2"}}
   266  	c.Assert(attemptedNodes, gc.DeepEquals, expectedNodes)
   267  }
   268  
   269  func (suite *environSuite) TestStopInstancesReturnsUnexpectedMAASError(c *gc.C) {
   270  	releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error {
   271  		return gomaasapi.ServerError{StatusCode: 405}
   272  	}
   273  	suite.PatchValue(&ReleaseNodes, releaseNodes)
   274  	env := suite.makeEnviron()
   275  	err := env.StopInstances("test1")
   276  	c.Assert(err, gc.NotNil)
   277  	maasErr, ok := errors.Cause(err).(gomaasapi.ServerError)
   278  	c.Assert(ok, jc.IsTrue)
   279  	c.Assert(maasErr.StatusCode, gc.Equals, 405)
   280  }
   281  
   282  func (suite *environSuite) TestStopInstancesReturnsUnexpectedError(c *gc.C) {
   283  	releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error {
   284  		return environs.ErrNoInstances
   285  	}
   286  	suite.PatchValue(&ReleaseNodes, releaseNodes)
   287  	env := suite.makeEnviron()
   288  	err := env.StopInstances("test1")
   289  	c.Assert(err, gc.NotNil)
   290  	c.Assert(errors.Cause(err), gc.Equals, environs.ErrNoInstances)
   291  }
   292  
   293  func (suite *environSuite) TestControllerInstances(c *gc.C) {
   294  	env := suite.makeEnviron()
   295  	_, err := env.ControllerInstances(suite.controllerUUID)
   296  	c.Assert(err, gc.Equals, environs.ErrNotBootstrapped)
   297  
   298  	tests := [][]instance.Id{{}, {"inst-0"}, {"inst-0", "inst-1"}}
   299  	for _, expected := range tests {
   300  		err := common.SaveState(env.Storage(), &common.BootstrapState{
   301  			StateInstances: expected,
   302  		})
   303  		c.Assert(err, jc.ErrorIsNil)
   304  		controllerInstances, err := env.ControllerInstances(suite.controllerUUID)
   305  		c.Assert(err, jc.ErrorIsNil)
   306  		c.Assert(controllerInstances, jc.SameContents, expected)
   307  	}
   308  }
   309  
   310  func (suite *environSuite) TestControllerInstancesFailsIfNoStateInstances(c *gc.C) {
   311  	env := suite.makeEnviron()
   312  	_, err := env.ControllerInstances(suite.controllerUUID)
   313  	c.Check(err, gc.Equals, environs.ErrNotBootstrapped)
   314  }
   315  
   316  func (suite *environSuite) TestDestroy(c *gc.C) {
   317  	env := suite.makeEnviron()
   318  	suite.getInstance("test1")
   319  	suite.testMAASObject.TestServer.OwnedNodes()["test1"] = true // simulate acquire
   320  	data := makeRandomBytes(10)
   321  	suite.testMAASObject.TestServer.NewFile("filename", data)
   322  	stor := env.Storage()
   323  
   324  	err := env.Destroy()
   325  	c.Check(err, jc.ErrorIsNil)
   326  
   327  	// Instances have been stopped.
   328  	operations := suite.testMAASObject.TestServer.NodesOperations()
   329  	c.Check(operations, gc.DeepEquals, []string{"release"})
   330  	c.Check(suite.testMAASObject.TestServer.OwnedNodes()["test1"], jc.IsFalse)
   331  	// Files have been cleaned up.
   332  	listing, err := envstorage.List(stor, "")
   333  	c.Assert(err, jc.ErrorIsNil)
   334  	c.Check(listing, gc.DeepEquals, []string{})
   335  }
   336  
   337  func (suite *environSuite) TestBootstrapSucceeds(c *gc.C) {
   338  	suite.setupFakeTools(c)
   339  	env := suite.makeEnviron()
   340  	suite.newNode(c, "thenode", "host", nil)
   341  	suite.addSubnet(c, 9, 9, "thenode")
   342  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
   343  		ControllerConfig: coretesting.FakeControllerConfig(),
   344  		AdminSecret:      testing.AdminSecret,
   345  		CAPrivateKey:     coretesting.CAKey,
   346  	})
   347  	c.Assert(err, jc.ErrorIsNil)
   348  }
   349  
   350  func (suite *environSuite) TestBootstrapNodeNotDeployed(c *gc.C) {
   351  	suite.setupFakeTools(c)
   352  	env := suite.makeEnviron()
   353  	suite.newNode(c, "thenode", "host", nil)
   354  	suite.addSubnet(c, 9, 9, "thenode")
   355  	// Ensure node will not be reported as deployed by changing its status.
   356  	suite.testMAASObject.TestServer.ChangeNode("thenode", "status", "4")
   357  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
   358  		ControllerConfig: coretesting.FakeControllerConfig(),
   359  		AdminSecret:      testing.AdminSecret,
   360  		CAPrivateKey:     coretesting.CAKey,
   361  	})
   362  	c.Assert(err, gc.ErrorMatches, "bootstrap instance started but did not change to Deployed state.*")
   363  }
   364  
   365  func (suite *environSuite) TestBootstrapNodeFailedDeploy(c *gc.C) {
   366  	suite.setupFakeTools(c)
   367  	env := suite.makeEnviron()
   368  	suite.newNode(c, "thenode", "host", nil)
   369  	suite.addSubnet(c, 9, 9, "thenode")
   370  	// Set the node status to "Failed deployment"
   371  	suite.testMAASObject.TestServer.ChangeNode("thenode", "status", "11")
   372  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
   373  		ControllerConfig: coretesting.FakeControllerConfig(),
   374  		AdminSecret:      testing.AdminSecret,
   375  		CAPrivateKey:     coretesting.CAKey,
   376  	})
   377  	c.Assert(err, gc.ErrorMatches, "bootstrap instance started but did not change to Deployed state. instance \"/api/.*/nodes/thenode/\" failed to deploy")
   378  }
   379  
   380  func (suite *environSuite) TestBootstrapFailsIfNoTools(c *gc.C) {
   381  	env := suite.makeEnviron()
   382  	vers := version.MustParse("1.2.3")
   383  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
   384  		ControllerConfig: coretesting.FakeControllerConfig(),
   385  		AdminSecret:      testing.AdminSecret,
   386  		CAPrivateKey:     coretesting.CAKey,
   387  		// Disable auto-uploading by setting the agent version
   388  		// to something that's not the current version.
   389  		AgentVersion: &vers,
   390  	})
   391  	c.Check(err, gc.ErrorMatches, "Juju cannot bootstrap because no agent binaries are available for your model(.|\n)*")
   392  }
   393  
   394  func (suite *environSuite) TestBootstrapFailsIfNoNodes(c *gc.C) {
   395  	suite.setupFakeTools(c)
   396  	env := suite.makeEnviron()
   397  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
   398  		ControllerConfig: coretesting.FakeControllerConfig(),
   399  		AdminSecret:      testing.AdminSecret,
   400  		CAPrivateKey:     coretesting.CAKey,
   401  	})
   402  	// Since there are no nodes, the attempt to allocate one returns a
   403  	// 409: Conflict.
   404  	c.Check(err, gc.ErrorMatches, ".*409.*")
   405  }
   406  
   407  func (suite *environSuite) TestGetToolsMetadataSources(c *gc.C) {
   408  	env := suite.makeEnviron()
   409  	// Add a dummy file to storage so we can use that to check the
   410  	// obtained source later.
   411  	data := makeRandomBytes(10)
   412  	stor := NewStorage(env)
   413  	err := stor.Put("tools/filename", bytes.NewBuffer([]byte(data)), int64(len(data)))
   414  	c.Assert(err, jc.ErrorIsNil)
   415  	sources, err := envtools.GetMetadataSources(env)
   416  	c.Assert(err, jc.ErrorIsNil)
   417  	c.Assert(sources, gc.HasLen, 0)
   418  }
   419  
   420  func (suite *environSuite) TestConstraintsValidator(c *gc.C) {
   421  	suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "trusty"}`)
   422  	env := suite.makeEnviron()
   423  	validator, err := env.ConstraintsValidator()
   424  	c.Assert(err, jc.ErrorIsNil)
   425  	cons := constraints.MustParse("arch=amd64 cpu-power=10 instance-type=foo virt-type=kvm")
   426  	unsupported, err := validator.Validate(cons)
   427  	c.Assert(err, jc.ErrorIsNil)
   428  	c.Assert(unsupported, jc.SameContents, []string{"cpu-power", "instance-type", "virt-type"})
   429  }
   430  
   431  func (suite *environSuite) TestConstraintsValidatorVocab(c *gc.C) {
   432  	suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "trusty"}`)
   433  	suite.testMAASObject.TestServer.AddBootImage("uuid-1", `{"architecture": "armhf", "release": "precise"}`)
   434  	env := suite.makeEnviron()
   435  	validator, err := env.ConstraintsValidator()
   436  	c.Assert(err, jc.ErrorIsNil)
   437  	cons := constraints.MustParse("arch=ppc64el")
   438  	_, err = validator.Validate(cons)
   439  	c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=ppc64el\nvalid values are: \\[amd64 armhf\\]")
   440  }
   441  
   442  func (suite *environSuite) TestSupportsNetworking(c *gc.C) {
   443  	env := suite.makeEnviron()
   444  	_, supported := environs.SupportsNetworking(env)
   445  	c.Assert(supported, jc.IsTrue)
   446  }
   447  
   448  func (suite *environSuite) TestSupportsSpaces(c *gc.C) {
   449  	env := suite.makeEnviron()
   450  	supported, err := env.SupportsSpaces()
   451  	c.Assert(err, jc.ErrorIsNil)
   452  	c.Assert(supported, jc.IsTrue)
   453  }
   454  
   455  func (suite *environSuite) TestSupportsSpaceDiscovery(c *gc.C) {
   456  	env := suite.makeEnviron()
   457  	supported, err := env.SupportsSpaceDiscovery()
   458  	c.Assert(err, jc.ErrorIsNil)
   459  	c.Assert(supported, jc.IsTrue)
   460  }
   461  
   462  func (suite *environSuite) TestSubnetsWithInstanceIdAndSubnetIds(c *gc.C) {
   463  	server := suite.testMAASObject.TestServer
   464  	var subnetIDs []network.Id
   465  	var uintIDs []uint
   466  	for _, i := range []uint{1, 2, 3} {
   467  		server.NewSpace(spaceJSON(gomaasapi.CreateSpace{Name: fmt.Sprintf("space-%d", i)}))
   468  		id := suite.addSubnet(c, i, i, "node1")
   469  		subnetIDs = append(subnetIDs, network.Id(fmt.Sprintf("%v", id)))
   470  		uintIDs = append(uintIDs, id)
   471  		suite.addSubnet(c, i+5, i, "node2")
   472  		suite.addSubnet(c, i+10, i, "") // not linked to a node
   473  	}
   474  	testInstance := suite.getInstance("node1")
   475  	env := suite.makeEnviron()
   476  
   477  	subnetsInfo, err := env.Subnets(testInstance.Id(), subnetIDs)
   478  	c.Assert(err, jc.ErrorIsNil)
   479  	expectedInfo := []network.SubnetInfo{
   480  		createSubnetInfo(uintIDs[0], 2, 1),
   481  		createSubnetInfo(uintIDs[1], 3, 2),
   482  		createSubnetInfo(uintIDs[2], 4, 3),
   483  	}
   484  	c.Assert(subnetsInfo, jc.DeepEquals, expectedInfo)
   485  
   486  	subnetsInfo, err = env.Subnets(testInstance.Id(), subnetIDs[1:])
   487  	c.Assert(err, jc.ErrorIsNil)
   488  	c.Assert(subnetsInfo, jc.DeepEquals, expectedInfo[1:])
   489  }
   490  
   491  func (suite *environSuite) createTwoSpaces() {
   492  	server := suite.testMAASObject.TestServer
   493  	server.NewSpace(spaceJSON(gomaasapi.CreateSpace{Name: "space-1"}))
   494  	server.NewSpace(spaceJSON(gomaasapi.CreateSpace{Name: "space-2"}))
   495  }
   496  
   497  func (suite *environSuite) TestSubnetsWithInstaceIdNoSubnetIds(c *gc.C) {
   498  	suite.createTwoSpaces()
   499  	id1 := suite.addSubnet(c, 1, 1, "node1")
   500  	id2 := suite.addSubnet(c, 2, 2, "node1")
   501  	suite.addSubnet(c, 3, 2, "")      // not linked to a node
   502  	suite.addSubnet(c, 4, 2, "node2") // linked to another node
   503  	testInstance := suite.getInstance("node1")
   504  	env := suite.makeEnviron()
   505  
   506  	subnetsInfo, err := env.Subnets(testInstance.Id(), []network.Id{})
   507  	c.Assert(err, jc.ErrorIsNil)
   508  	expectedInfo := []network.SubnetInfo{
   509  		createSubnetInfo(id1, 2, 1),
   510  		createSubnetInfo(id2, 3, 2),
   511  	}
   512  	c.Assert(subnetsInfo, jc.DeepEquals, expectedInfo)
   513  
   514  	subnetsInfo, err = env.Subnets(testInstance.Id(), nil)
   515  	c.Assert(err, jc.ErrorIsNil)
   516  	c.Assert(subnetsInfo, jc.DeepEquals, expectedInfo)
   517  }
   518  
   519  func (suite *environSuite) TestSubnetsInvalidInstaceIdAnySubnetIds(c *gc.C) {
   520  	suite.createTwoSpaces()
   521  	suite.addSubnet(c, 1, 1, "node1")
   522  	suite.addSubnet(c, 2, 2, "node2")
   523  
   524  	_, err := suite.makeEnviron().Subnets("invalid", []network.Id{"anything"})
   525  	c.Assert(err, gc.ErrorMatches, `instance "invalid" not found`)
   526  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   527  }
   528  
   529  func (suite *environSuite) TestSubnetsNoInstanceIdWithSubnetIds(c *gc.C) {
   530  	suite.createTwoSpaces()
   531  	id1 := suite.addSubnet(c, 1, 1, "node1")
   532  	id2 := suite.addSubnet(c, 2, 2, "node2")
   533  	subnetIDs := []network.Id{
   534  		network.Id(fmt.Sprintf("%v", id1)),
   535  		network.Id(fmt.Sprintf("%v", id2)),
   536  	}
   537  
   538  	subnetsInfo, err := suite.makeEnviron().Subnets(instance.UnknownId, subnetIDs)
   539  	c.Assert(err, jc.ErrorIsNil)
   540  	expectedInfo := []network.SubnetInfo{
   541  		createSubnetInfo(id1, 2, 1),
   542  		createSubnetInfo(id2, 3, 2),
   543  	}
   544  	c.Assert(subnetsInfo, jc.DeepEquals, expectedInfo)
   545  }
   546  
   547  func (suite *environSuite) TestSubnetsNoInstanceIdNoSubnetIds(c *gc.C) {
   548  	suite.createTwoSpaces()
   549  	id1 := suite.addSubnet(c, 1, 1, "node1")
   550  	id2 := suite.addSubnet(c, 2, 2, "node2")
   551  	env := suite.makeEnviron()
   552  
   553  	subnetsInfo, err := suite.makeEnviron().Subnets(instance.UnknownId, []network.Id{})
   554  	c.Assert(err, jc.ErrorIsNil)
   555  	expectedInfo := []network.SubnetInfo{
   556  		createSubnetInfo(id1, 2, 1),
   557  		createSubnetInfo(id2, 3, 2),
   558  	}
   559  	c.Assert(subnetsInfo, jc.DeepEquals, expectedInfo)
   560  
   561  	subnetsInfo, err = env.Subnets(instance.UnknownId, nil)
   562  	c.Assert(err, jc.ErrorIsNil)
   563  	c.Assert(subnetsInfo, jc.DeepEquals, expectedInfo)
   564  }
   565  
   566  func (suite *environSuite) TestSpaces(c *gc.C) {
   567  	suite.createTwoSpaces()
   568  	suite.testMAASObject.TestServer.NewSpace(spaceJSON(gomaasapi.CreateSpace{Name: "space-3"}))
   569  	for _, i := range []uint{1, 2, 3} {
   570  		suite.addSubnet(c, i, i, "node1")
   571  		suite.addSubnet(c, i+5, i, "node1")
   572  	}
   573  
   574  	spaces, err := suite.makeEnviron().Spaces()
   575  	c.Assert(err, jc.ErrorIsNil)
   576  	expectedSpaces := []network.SpaceInfo{{
   577  		Name:       "space-1",
   578  		ProviderId: "2",
   579  		Subnets: []network.SubnetInfo{
   580  			createSubnetInfo(1, 2, 1),
   581  			createSubnetInfo(2, 2, 6),
   582  		},
   583  	}, {
   584  		Name:       "space-2",
   585  		ProviderId: "3",
   586  		Subnets: []network.SubnetInfo{
   587  			createSubnetInfo(3, 3, 2),
   588  			createSubnetInfo(4, 3, 7),
   589  		},
   590  	}, {
   591  		Name:       "space-3",
   592  		ProviderId: "4",
   593  		Subnets: []network.SubnetInfo{
   594  			createSubnetInfo(5, 4, 3),
   595  			createSubnetInfo(6, 4, 8),
   596  		},
   597  	}}
   598  	c.Assert(spaces, jc.DeepEquals, expectedSpaces)
   599  }
   600  
   601  func (suite *environSuite) assertSpaces(c *gc.C, numberOfSubnets int, filters []network.Id) {
   602  	server := suite.testMAASObject.TestServer
   603  	testInstance := suite.getInstance("node1")
   604  	systemID := "node1"
   605  	for i := 1; i <= numberOfSubnets; i++ {
   606  		server.NewSpace(spaceJSON(gomaasapi.CreateSpace{Name: fmt.Sprintf("space-%d", i)}))
   607  		// Put most, but not all, of the subnets on node1.
   608  		if i == 2 {
   609  			systemID = "node2"
   610  		} else {
   611  			systemID = "node1"
   612  		}
   613  		suite.addSubnet(c, uint(i), uint(i), systemID)
   614  	}
   615  
   616  	subnets, err := suite.makeEnviron().Subnets(testInstance.Id(), filters)
   617  	c.Assert(err, jc.ErrorIsNil)
   618  	expectedSubnets := []network.SubnetInfo{
   619  		createSubnetInfo(1, 2, 1),
   620  		createSubnetInfo(3, 4, 3),
   621  	}
   622  	c.Assert(subnets, jc.DeepEquals, expectedSubnets)
   623  
   624  }
   625  
   626  func (suite *environSuite) TestSubnetsAllSubnets(c *gc.C) {
   627  	suite.assertSpaces(c, 3, []network.Id{})
   628  }
   629  
   630  func (suite *environSuite) TestSubnetsFilteredIds(c *gc.C) {
   631  	suite.assertSpaces(c, 4, []network.Id{"1", "3"})
   632  }
   633  
   634  func (suite *environSuite) TestSubnetsMissingSubnet(c *gc.C) {
   635  	testInstance := suite.getInstance("node1")
   636  	for _, i := range []uint{1, 2} {
   637  		suite.addSubnet(c, i, i, "node1")
   638  	}
   639  
   640  	_, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{"1", "3", "6"})
   641  	errorRe := regexp.MustCompile("failed to find the following subnets: (\\d), (\\d)$")
   642  	errorText := err.Error()
   643  	c.Assert(errorRe.MatchString(errorText), jc.IsTrue)
   644  	matches := errorRe.FindStringSubmatch(errorText)
   645  	c.Assert(matches, gc.HasLen, 3)
   646  	c.Assert(matches[1:], jc.SameContents, []string{"3", "6"})
   647  }
   648  
   649  func (s *environSuite) TestPrecheckInstanceAvailZone(c *gc.C) {
   650  	s.testMAASObject.TestServer.AddZone("zone1", "the grass is greener in zone1")
   651  	env := s.makeEnviron()
   652  	placement := "zone=zone1"
   653  	err := env.PrecheckInstance(series.LatestLts(), constraints.Value{}, placement)
   654  	c.Assert(err, jc.ErrorIsNil)
   655  }
   656  
   657  func (s *environSuite) TestPrecheckInstanceAvailZoneUnknown(c *gc.C) {
   658  	s.testMAASObject.TestServer.AddZone("zone1", "the grass is greener in zone1")
   659  	env := s.makeEnviron()
   660  	placement := "zone=zone2"
   661  	err := env.PrecheckInstance(series.LatestLts(), constraints.Value{}, placement)
   662  	c.Assert(err, gc.ErrorMatches, `invalid availability zone "zone2"`)
   663  }
   664  
   665  func (s *environSuite) TestPrecheckInstanceAvailZonesUnsupported(c *gc.C) {
   666  	env := s.makeEnviron()
   667  	placement := "zone=test-unknown"
   668  	err := env.PrecheckInstance(series.LatestLts(), constraints.Value{}, placement)
   669  	c.Assert(err, jc.Satisfies, errors.IsNotImplemented)
   670  }
   671  
   672  func (s *environSuite) TestPrecheckInvalidPlacement(c *gc.C) {
   673  	env := s.makeEnviron()
   674  	err := env.PrecheckInstance(series.LatestLts(), constraints.Value{}, "notzone=anything")
   675  	c.Assert(err, gc.ErrorMatches, "unknown placement directive: notzone=anything")
   676  }
   677  
   678  func (s *environSuite) TestPrecheckNodePlacement(c *gc.C) {
   679  	env := s.makeEnviron()
   680  	err := env.PrecheckInstance(series.LatestLts(), constraints.Value{}, "assumed_node_name")
   681  	c.Assert(err, jc.ErrorIsNil)
   682  }
   683  
   684  func (s *environSuite) TestStartInstanceAvailZone(c *gc.C) {
   685  	// Add a node for the started instance.
   686  	s.newNode(c, "thenode1", "host1", map[string]interface{}{"zone": "test-available"})
   687  	s.addSubnet(c, 1, 1, "thenode1")
   688  	s.testMAASObject.TestServer.AddZone("test-available", "description")
   689  	inst, err := s.testStartInstanceAvailZone(c, "test-available")
   690  	c.Assert(err, jc.ErrorIsNil)
   691  	zone, err := inst.(maasInstance).zone()
   692  	c.Assert(err, jc.ErrorIsNil)
   693  	c.Assert(zone, gc.Equals, "test-available")
   694  }
   695  
   696  func (s *environSuite) TestStartInstanceAvailZoneUnknown(c *gc.C) {
   697  	s.testMAASObject.TestServer.AddZone("test-available", "description")
   698  	_, err := s.testStartInstanceAvailZone(c, "test-unknown")
   699  	c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`)
   700  }
   701  
   702  func (s *environSuite) testStartInstanceAvailZone(c *gc.C, zone string) (instance.Instance, error) {
   703  	env := s.bootstrap(c)
   704  	params := environs.StartInstanceParams{ControllerUUID: s.controllerUUID, Placement: "zone=" + zone}
   705  	result, err := testing.StartInstanceWithParams(env, "1", params)
   706  	if err != nil {
   707  		return nil, err
   708  	}
   709  	return result.Instance, nil
   710  }
   711  
   712  func (s *environSuite) TestStartInstanceUnmetConstraints(c *gc.C) {
   713  	env := s.bootstrap(c)
   714  	s.newNode(c, "thenode1", "host1", nil)
   715  	s.addSubnet(c, 1, 1, "thenode1")
   716  	params := environs.StartInstanceParams{ControllerUUID: s.controllerUUID, Constraints: constraints.MustParse("mem=8G")}
   717  	_, err := testing.StartInstanceWithParams(env, "1", params)
   718  	c.Assert(err, gc.ErrorMatches, "cannot run instances:.* 409.*")
   719  }
   720  
   721  func (s *environSuite) TestStartInstanceConstraints(c *gc.C) {
   722  	env := s.bootstrap(c)
   723  	s.newNode(c, "thenode1", "host1", nil)
   724  	s.addSubnet(c, 1, 1, "thenode1")
   725  	s.newNode(c, "thenode2", "host2", map[string]interface{}{"memory": 8192})
   726  	s.addSubnet(c, 2, 2, "thenode2")
   727  	params := environs.StartInstanceParams{ControllerUUID: s.controllerUUID, Constraints: constraints.MustParse("mem=8G")}
   728  	result, err := testing.StartInstanceWithParams(env, "1", params)
   729  	c.Assert(err, jc.ErrorIsNil)
   730  	c.Assert(*result.Hardware.Mem, gc.Equals, uint64(8192))
   731  }
   732  
   733  var nodeStorageAttrs = []map[string]interface{}{
   734  	{
   735  		"name":       "sdb",
   736  		"id":         1,
   737  		"id_path":    "/dev/disk/by-id/id_for_sda",
   738  		"path":       "/dev/sdb",
   739  		"model":      "Samsung_SSD_850_EVO_250GB",
   740  		"block_size": 4096,
   741  		"serial":     "S21NNSAFC38075L",
   742  		"size":       uint64(250059350016),
   743  	},
   744  	{
   745  		"name":       "sda",
   746  		"id":         2,
   747  		"path":       "/dev/sda",
   748  		"model":      "Samsung_SSD_850_EVO_250GB",
   749  		"block_size": 4096,
   750  		"serial":     "XXXX",
   751  		"size":       uint64(250059350016),
   752  	},
   753  	{
   754  		"name":       "sdc",
   755  		"id":         3,
   756  		"path":       "/dev/sdc",
   757  		"model":      "Samsung_SSD_850_EVO_250GB",
   758  		"block_size": 4096,
   759  		"serial":     "YYYYYYY",
   760  		"size":       uint64(250059350016),
   761  	},
   762  }
   763  
   764  var storageConstraintAttrs = map[string]interface{}{
   765  	"1": "1",
   766  	"2": "root",
   767  	"3": "3",
   768  }
   769  
   770  func (s *environSuite) TestStartInstanceStorage(c *gc.C) {
   771  	env := s.bootstrap(c)
   772  	s.newNode(c, "thenode1", "host1", map[string]interface{}{
   773  		"memory":                  8192,
   774  		"physicalblockdevice_set": nodeStorageAttrs,
   775  		"constraint_map":          storageConstraintAttrs,
   776  	})
   777  	s.addSubnet(c, 1, 1, "thenode1")
   778  	params := environs.StartInstanceParams{ControllerUUID: s.controllerUUID,
   779  		Volumes: []storage.VolumeParams{
   780  			{Tag: names.NewVolumeTag("1"), Size: 2000000},
   781  			{Tag: names.NewVolumeTag("3"), Size: 2000000},
   782  		}}
   783  	result, err := testing.StartInstanceWithParams(env, "1", params)
   784  	c.Assert(err, jc.ErrorIsNil)
   785  	c.Check(result.Volumes, jc.DeepEquals, []storage.Volume{
   786  		{
   787  			names.NewVolumeTag("1"),
   788  			storage.VolumeInfo{
   789  				Size:       238475,
   790  				VolumeId:   "volume-1",
   791  				HardwareId: "id_for_sda",
   792  			},
   793  		},
   794  		{
   795  			names.NewVolumeTag("3"),
   796  			storage.VolumeInfo{
   797  				Size:       238475,
   798  				VolumeId:   "volume-3",
   799  				HardwareId: "",
   800  			},
   801  		},
   802  	})
   803  	c.Assert(result.VolumeAttachments, jc.DeepEquals, []storage.VolumeAttachment{
   804  		{
   805  			names.NewVolumeTag("1"),
   806  			names.NewMachineTag("1"),
   807  			storage.VolumeAttachmentInfo{
   808  				DeviceName: "",
   809  				ReadOnly:   false,
   810  			},
   811  		},
   812  		{
   813  			names.NewVolumeTag("3"),
   814  			names.NewMachineTag("1"),
   815  			storage.VolumeAttachmentInfo{
   816  				DeviceName: "sdc",
   817  				ReadOnly:   false,
   818  			},
   819  		},
   820  	})
   821  }
   822  
   823  func (s *environSuite) TestStartInstanceUnsupportedStorage(c *gc.C) {
   824  	env := s.bootstrap(c)
   825  	s.newNode(c, "thenode1", "host1", map[string]interface{}{
   826  		"memory": 8192,
   827  	})
   828  	s.addSubnet(c, 1, 1, "thenode1")
   829  	params := environs.StartInstanceParams{ControllerUUID: s.controllerUUID,
   830  		Volumes: []storage.VolumeParams{
   831  			{Tag: names.NewVolumeTag("1"), Size: 2000000},
   832  			{Tag: names.NewVolumeTag("3"), Size: 2000000},
   833  		}}
   834  	_, err := testing.StartInstanceWithParams(env, "1", params)
   835  	c.Assert(err, gc.ErrorMatches, "requested 2 storage volumes. 0 returned.")
   836  	operations := s.testMAASObject.TestServer.NodesOperations()
   837  	c.Check(operations, gc.DeepEquals, []string{"acquire", "acquire", "release"})
   838  	c.Assert(s.testMAASObject.TestServer.OwnedNodes()["node0"], jc.IsTrue)
   839  	c.Assert(s.testMAASObject.TestServer.OwnedNodes()["thenode1"], jc.IsFalse)
   840  }
   841  
   842  func (s *environSuite) TestGetAvailabilityZones(c *gc.C) {
   843  	env := s.makeEnviron()
   844  
   845  	zones, err := env.AvailabilityZones()
   846  	c.Assert(err, jc.Satisfies, errors.IsNotImplemented)
   847  	c.Assert(zones, gc.IsNil)
   848  
   849  	s.testMAASObject.TestServer.AddZone("whatever", "andever")
   850  	zones, err = env.AvailabilityZones()
   851  	c.Assert(err, jc.ErrorIsNil)
   852  	c.Assert(zones, gc.HasLen, 1)
   853  	c.Assert(zones[0].Name(), gc.Equals, "whatever")
   854  	c.Assert(zones[0].Available(), jc.IsTrue)
   855  
   856  	// A successful result is cached, currently for the lifetime
   857  	// of the Environ. This will change if/when we have long-lived
   858  	// Environs to cut down repeated IaaS requests.
   859  	s.testMAASObject.TestServer.AddZone("somewhere", "outthere")
   860  	zones, err = env.AvailabilityZones()
   861  	c.Assert(err, jc.ErrorIsNil)
   862  	c.Assert(zones, gc.HasLen, 1)
   863  	c.Assert(zones[0].Name(), gc.Equals, "whatever")
   864  }
   865  
   866  type mockAvailabilityZoneAllocations struct {
   867  	group  []instance.Id // input param
   868  	result []common.AvailabilityZoneInstances
   869  	err    error
   870  }
   871  
   872  func (m *mockAvailabilityZoneAllocations) AvailabilityZoneAllocations(
   873  	e common.ZonedEnviron, group []instance.Id,
   874  ) ([]common.AvailabilityZoneInstances, error) {
   875  	m.group = group
   876  	return m.result, m.err
   877  }
   878  
   879  func (s *environSuite) newNode(c *gc.C, nodename, hostname string, attrs map[string]interface{}) {
   880  	allAttrs := map[string]interface{}{
   881  		"system_id":     nodename,
   882  		"hostname":      hostname,
   883  		"architecture":  fmt.Sprintf("%s/generic", arch.HostArch()),
   884  		"memory":        1024,
   885  		"cpu_count":     1,
   886  		"zone":          map[string]interface{}{"name": "test_zone", "description": "description"},
   887  		"interface_set": exampleParsedInterfaceSetJSON,
   888  	}
   889  	for k, v := range attrs {
   890  		allAttrs[k] = v
   891  	}
   892  	data, err := json.Marshal(allAttrs)
   893  	c.Assert(err, jc.ErrorIsNil)
   894  	s.testMAASObject.TestServer.NewNode(string(data))
   895  	lshwXML, err := s.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}})
   896  	c.Assert(err, jc.ErrorIsNil)
   897  	s.testMAASObject.TestServer.AddNodeDetails(nodename, lshwXML)
   898  }
   899  
   900  func (s *environSuite) bootstrap(c *gc.C) environs.Environ {
   901  	s.newNode(c, "node0", "bootstrap-host", nil)
   902  	s.addSubnet(c, 9, 9, "node0")
   903  	s.setupFakeTools(c)
   904  	env := s.makeEnviron()
   905  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
   906  		ControllerConfig: coretesting.FakeControllerConfig(),
   907  		Placement:        "bootstrap-host",
   908  		AdminSecret:      testing.AdminSecret,
   909  		CAPrivateKey:     coretesting.CAKey,
   910  	})
   911  	c.Assert(err, jc.ErrorIsNil)
   912  	return env
   913  }
   914  
   915  func (s *environSuite) TestStartInstanceDistributionParams(c *gc.C) {
   916  	env := s.bootstrap(c)
   917  	var mock mockAvailabilityZoneAllocations
   918  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
   919  
   920  	// no distribution group specified
   921  	s.newNode(c, "node1", "host1", nil)
   922  	s.addSubnet(c, 1, 1, "node1")
   923  	testing.AssertStartInstance(c, env, s.controllerUUID, "1")
   924  	c.Assert(mock.group, gc.HasLen, 0)
   925  
   926  	// distribution group specified: ensure it's passed through to AvailabilityZone.
   927  	s.newNode(c, "node2", "host2", nil)
   928  	s.addSubnet(c, 2, 2, "node2")
   929  	expectedInstances := []instance.Id{"i-0", "i-1"}
   930  	params := environs.StartInstanceParams{
   931  		ControllerUUID: s.controllerUUID,
   932  		DistributionGroup: func() ([]instance.Id, error) {
   933  			return expectedInstances, nil
   934  		},
   935  	}
   936  	_, err := testing.StartInstanceWithParams(env, "1", params)
   937  	c.Assert(err, jc.ErrorIsNil)
   938  	c.Assert(mock.group, gc.DeepEquals, expectedInstances)
   939  }
   940  
   941  func (s *environSuite) TestStartInstanceDistributionErrors(c *gc.C) {
   942  	env := s.bootstrap(c)
   943  	mock := mockAvailabilityZoneAllocations{
   944  		err: errors.New("AvailabilityZoneAllocations failed"),
   945  	}
   946  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
   947  	_, _, _, err := testing.StartInstance(env, s.controllerUUID, "1")
   948  	c.Assert(err, gc.ErrorMatches, "cannot get availability zone allocations: AvailabilityZoneAllocations failed")
   949  
   950  	mock.err = nil
   951  	dgErr := errors.New("DistributionGroup failed")
   952  	params := environs.StartInstanceParams{
   953  		ControllerUUID: s.controllerUUID,
   954  		DistributionGroup: func() ([]instance.Id, error) {
   955  			return nil, dgErr
   956  		},
   957  	}
   958  	_, err = testing.StartInstanceWithParams(env, "1", params)
   959  	c.Assert(err, gc.ErrorMatches, "cannot get distribution group: DistributionGroup failed")
   960  }
   961  
   962  func (s *environSuite) TestStartInstanceDistribution(c *gc.C) {
   963  	env := s.bootstrap(c)
   964  	s.testMAASObject.TestServer.AddZone("test-available", "description")
   965  	s.newNode(c, "node1", "host1", map[string]interface{}{"zone": "test-available"})
   966  	s.addSubnet(c, 1, 1, "node1")
   967  	inst, _ := testing.AssertStartInstance(c, env, s.controllerUUID, "1")
   968  	zone, err := inst.(*maas1Instance).zone()
   969  	c.Assert(err, jc.ErrorIsNil)
   970  	c.Assert(zone, gc.Equals, "test-available")
   971  }
   972  
   973  func (s *environSuite) TestStartInstanceDistributionFailover(c *gc.C) {
   974  	mock := mockAvailabilityZoneAllocations{
   975  		result: []common.AvailabilityZoneInstances{{
   976  			ZoneName: "zone1",
   977  		}, {
   978  			ZoneName: "zonelord",
   979  		}, {
   980  			ZoneName: "zone2",
   981  		}},
   982  	}
   983  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
   984  	s.testMAASObject.TestServer.AddZone("zone1", "description")
   985  	s.testMAASObject.TestServer.AddZone("zone2", "description")
   986  	s.newNode(c, "node2", "host2", map[string]interface{}{"zone": "zone2"})
   987  	s.addSubnet(c, 1, 1, "node2")
   988  
   989  	env := s.bootstrap(c)
   990  	inst, _ := testing.AssertStartInstance(c, env, s.controllerUUID, "1")
   991  	zone, err := inst.(maasInstance).zone()
   992  	c.Assert(err, jc.ErrorIsNil)
   993  	c.Assert(zone, gc.Equals, "zone2")
   994  	c.Assert(s.testMAASObject.TestServer.NodesOperations(), gc.DeepEquals, []string{
   995  		// one acquire for the bootstrap, three for StartInstance (with zone failover)
   996  		"acquire", "acquire", "acquire", "acquire",
   997  	})
   998  	c.Assert(s.testMAASObject.TestServer.NodesOperationRequestValues(), gc.DeepEquals, []url.Values{{
   999  		"name":       []string{"bootstrap-host"},
  1000  		"agent_name": []string{env.Config().UUID()},
  1001  	}, {
  1002  		"zone":       []string{"zone1"},
  1003  		"agent_name": []string{env.Config().UUID()},
  1004  	}, {
  1005  		"zone":       []string{"zonelord"},
  1006  		"agent_name": []string{env.Config().UUID()},
  1007  	}, {
  1008  		"zone":       []string{"zone2"},
  1009  		"agent_name": []string{env.Config().UUID()},
  1010  	}})
  1011  }
  1012  
  1013  func (s *environSuite) TestStartInstanceDistributionOneAssigned(c *gc.C) {
  1014  	mock := mockAvailabilityZoneAllocations{
  1015  		result: []common.AvailabilityZoneInstances{{
  1016  			ZoneName: "zone1",
  1017  		}, {
  1018  			ZoneName: "zone2",
  1019  		}},
  1020  	}
  1021  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
  1022  	s.testMAASObject.TestServer.AddZone("zone1", "description")
  1023  	s.testMAASObject.TestServer.AddZone("zone2", "description")
  1024  	s.newNode(c, "node1", "host1", map[string]interface{}{"zone": "zone1"})
  1025  	s.addSubnet(c, 1, 1, "node1")
  1026  	s.newNode(c, "node2", "host2", map[string]interface{}{"zone": "zone2"})
  1027  	s.addSubnet(c, 2, 2, "node2")
  1028  
  1029  	env := s.bootstrap(c)
  1030  	testing.AssertStartInstance(c, env, s.controllerUUID, "1")
  1031  	c.Assert(s.testMAASObject.TestServer.NodesOperations(), gc.DeepEquals, []string{
  1032  		// one acquire for the bootstrap, one for StartInstance.
  1033  		"acquire", "acquire",
  1034  	})
  1035  }
  1036  
  1037  func (s *environSuite) TestReleaseContainerAddresses(c *gc.C) {
  1038  	s.testMAASObject.TestServer.AddDevice(&gomaasapi.TestDevice{
  1039  		SystemId:     "device1",
  1040  		MACAddresses: []string{"mac1"},
  1041  	})
  1042  	s.testMAASObject.TestServer.AddDevice(&gomaasapi.TestDevice{
  1043  		SystemId:     "device2",
  1044  		MACAddresses: []string{"mac2"},
  1045  	})
  1046  	s.testMAASObject.TestServer.AddDevice(&gomaasapi.TestDevice{
  1047  		SystemId:     "device3",
  1048  		MACAddresses: []string{"mac3"},
  1049  	})
  1050  
  1051  	env := s.makeEnviron()
  1052  	err := env.ReleaseContainerAddresses([]network.ProviderInterfaceInfo{
  1053  		{MACAddress: "mac1"},
  1054  		{MACAddress: "mac3"},
  1055  		{MACAddress: "mac4"},
  1056  	})
  1057  	c.Assert(err, jc.ErrorIsNil)
  1058  
  1059  	var systemIds []string
  1060  	for systemId, _ := range s.testMAASObject.TestServer.Devices() {
  1061  		systemIds = append(systemIds, systemId)
  1062  	}
  1063  	c.Assert(systemIds, gc.DeepEquals, []string{"device2"})
  1064  }
  1065  
  1066  func (s *environSuite) TestReleaseContainerAddresses_HandlesDupes(c *gc.C) {
  1067  	s.testMAASObject.TestServer.AddDevice(&gomaasapi.TestDevice{
  1068  		SystemId:     "device1",
  1069  		MACAddresses: []string{"mac1", "mac2"},
  1070  	})
  1071  	s.testMAASObject.TestServer.AddDevice(&gomaasapi.TestDevice{
  1072  		SystemId:     "device3",
  1073  		MACAddresses: []string{"mac3"},
  1074  	})
  1075  
  1076  	env := s.makeEnviron()
  1077  	err := env.ReleaseContainerAddresses([]network.ProviderInterfaceInfo{
  1078  		{MACAddress: "mac1"},
  1079  		{MACAddress: "mac2"},
  1080  	})
  1081  	c.Assert(err, jc.ErrorIsNil)
  1082  
  1083  	var systemIds []string
  1084  	for systemId, _ := range s.testMAASObject.TestServer.Devices() {
  1085  		systemIds = append(systemIds, systemId)
  1086  	}
  1087  	c.Assert(systemIds, gc.DeepEquals, []string{"device3"})
  1088  }