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