github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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  	"io/ioutil"
    12  	"net"
    13  	"net/url"
    14  	"text/template"
    15  
    16  	"github.com/juju/errors"
    17  	jc "github.com/juju/testing/checkers"
    18  	"github.com/juju/utils"
    19  	"github.com/juju/utils/set"
    20  	gc "gopkg.in/check.v1"
    21  	goyaml "gopkg.in/yaml.v1"
    22  	"launchpad.net/gomaasapi"
    23  
    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  	"github.com/juju/juju/environs/simplestreams"
    29  	"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  	coretesting "github.com/juju/juju/testing"
    37  	"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  // getTestConfig creates a customized sample MAAS provider configuration.
    51  func getTestConfig(name, server, oauth, secret string) *config.Config {
    52  	ecfg, err := newConfig(map[string]interface{}{
    53  		"name":            name,
    54  		"maas-server":     server,
    55  		"maas-oauth":      oauth,
    56  		"admin-secret":    secret,
    57  		"authorized-keys": "I-am-not-a-real-key",
    58  	})
    59  	if err != nil {
    60  		panic(err)
    61  	}
    62  	return ecfg.Config
    63  }
    64  
    65  func (suite *environSuite) setupFakeTools(c *gc.C) {
    66  	storageDir := c.MkDir()
    67  	suite.PatchValue(&envtools.DefaultBaseURL, "file://"+storageDir+"/tools")
    68  	suite.UploadFakeToolsToDirectory(c, storageDir, "released", "released")
    69  }
    70  
    71  func (suite *environSuite) addNode(jsonText string) instance.Id {
    72  	node := suite.testMAASObject.TestServer.NewNode(jsonText)
    73  	resourceURI, _ := node.GetField("resource_uri")
    74  	return instance.Id(resourceURI)
    75  }
    76  
    77  func (suite *environSuite) TestInstancesReturnsInstances(c *gc.C) {
    78  	id := suite.addNode(allocatedNode)
    79  	instances, err := suite.makeEnviron().Instances([]instance.Id{id})
    80  
    81  	c.Check(err, jc.ErrorIsNil)
    82  	c.Assert(instances, gc.HasLen, 1)
    83  	c.Assert(instances[0].Id(), gc.Equals, id)
    84  }
    85  
    86  func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfEmptyParameter(c *gc.C) {
    87  	suite.addNode(allocatedNode)
    88  	instances, err := suite.makeEnviron().Instances([]instance.Id{})
    89  
    90  	c.Check(err, gc.Equals, environs.ErrNoInstances)
    91  	c.Check(instances, gc.IsNil)
    92  }
    93  
    94  func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNilParameter(c *gc.C) {
    95  	suite.addNode(allocatedNode)
    96  	instances, err := suite.makeEnviron().Instances(nil)
    97  
    98  	c.Check(err, gc.Equals, environs.ErrNoInstances)
    99  	c.Check(instances, gc.IsNil)
   100  }
   101  
   102  func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNoneFound(c *gc.C) {
   103  	instances, err := suite.makeEnviron().Instances([]instance.Id{"unknown"})
   104  	c.Check(err, gc.Equals, environs.ErrNoInstances)
   105  	c.Check(instances, gc.IsNil)
   106  }
   107  
   108  func (suite *environSuite) TestAllInstances(c *gc.C) {
   109  	id := suite.addNode(allocatedNode)
   110  	instances, err := suite.makeEnviron().AllInstances()
   111  
   112  	c.Check(err, jc.ErrorIsNil)
   113  	c.Assert(instances, gc.HasLen, 1)
   114  	c.Assert(instances[0].Id(), gc.Equals, id)
   115  }
   116  
   117  func (suite *environSuite) TestAllInstancesReturnsEmptySliceIfNoInstance(c *gc.C) {
   118  	instances, err := suite.makeEnviron().AllInstances()
   119  
   120  	c.Check(err, jc.ErrorIsNil)
   121  	c.Check(instances, gc.HasLen, 0)
   122  }
   123  
   124  func (suite *environSuite) TestInstancesReturnsErrorIfPartialInstances(c *gc.C) {
   125  	known := suite.addNode(allocatedNode)
   126  	suite.addNode(`{"system_id": "test2"}`)
   127  	unknown := instance.Id("unknown systemID")
   128  	instances, err := suite.makeEnviron().Instances([]instance.Id{known, unknown})
   129  
   130  	c.Check(err, gc.Equals, environs.ErrPartialInstances)
   131  	c.Assert(instances, gc.HasLen, 2)
   132  	c.Check(instances[0].Id(), gc.Equals, known)
   133  	c.Check(instances[1], gc.IsNil)
   134  }
   135  
   136  func (suite *environSuite) TestStorageReturnsStorage(c *gc.C) {
   137  	env := suite.makeEnviron()
   138  	stor := env.Storage()
   139  	c.Check(stor, gc.NotNil)
   140  	// The Storage object is really a maasStorage.
   141  	specificStorage := stor.(*maasStorage)
   142  	// Its environment pointer refers back to its environment.
   143  	c.Check(specificStorage.environUnlocked, gc.Equals, env)
   144  }
   145  
   146  func decodeUserData(userData string) ([]byte, error) {
   147  	data, err := base64.StdEncoding.DecodeString(userData)
   148  	if err != nil {
   149  		return []byte(""), err
   150  	}
   151  	return utils.Gunzip(data)
   152  }
   153  
   154  const lshwXMLTemplate = `
   155  <?xml version="1.0" standalone="yes" ?>
   156  <!-- generated by lshw-B.02.16 -->
   157  <list>
   158  <node id="node1" claimed="true" class="system" handle="DMI:0001">
   159   <description>Computer</description>
   160   <product>VirtualBox ()</product>
   161   <width units="bits">64</width>
   162    <node id="core" claimed="true" class="bus" handle="DMI:0008">
   163     <description>Motherboard</description>
   164      <node id="pci" claimed="true" class="bridge" handle="PCIBUS:0000:00">
   165       <description>Host bridge</description>{{$list := .}}{{range $mac, $ifi := $list}}
   166        <node id="network{{if gt (len $list) 1}}:{{$ifi.DeviceIndex}}{{end}}"{{if $ifi.Disabled}} disabled="true"{{end}} claimed="true" class="network" handle="PCI:0000:00:03.0">
   167         <description>Ethernet interface</description>
   168         <product>82540EM Gigabit Ethernet Controller</product>
   169         <logicalname>{{$ifi.InterfaceName}}</logicalname>
   170         <serial>{{$mac}}</serial>
   171        </node>{{end}}
   172      </node>
   173    </node>
   174  </node>
   175  </list>
   176  `
   177  
   178  func (suite *environSuite) generateHWTemplate(netMacs map[string]ifaceInfo) (string, error) {
   179  	tmpl, err := template.New("test").Parse(lshwXMLTemplate)
   180  	if err != nil {
   181  		return "", err
   182  	}
   183  	var buf bytes.Buffer
   184  	err = tmpl.Execute(&buf, netMacs)
   185  	if err != nil {
   186  		return "", err
   187  	}
   188  	return string(buf.Bytes()), nil
   189  }
   190  
   191  func (suite *environSuite) TestStartInstanceStartsInstance(c *gc.C) {
   192  	suite.setupFakeTools(c)
   193  	env := suite.makeEnviron()
   194  	// Create node 0: it will be used as the bootstrap node.
   195  	suite.testMAASObject.TestServer.NewNode(fmt.Sprintf(
   196  		`{"system_id": "node0", "hostname": "host0", "architecture": "%s/generic", "memory": 1024, "cpu_count": 1}`,
   197  		version.Current.Arch),
   198  	)
   199  	lshwXML, err := suite.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}})
   200  	c.Assert(err, jc.ErrorIsNil)
   201  	suite.testMAASObject.TestServer.AddNodeDetails("node0", lshwXML)
   202  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   203  	c.Assert(err, jc.ErrorIsNil)
   204  	// The bootstrap node has been acquired and started.
   205  	operations := suite.testMAASObject.TestServer.NodeOperations()
   206  	actions, found := operations["node0"]
   207  	c.Check(found, jc.IsTrue)
   208  	c.Check(actions, gc.DeepEquals, []string{"acquire", "start"})
   209  
   210  	// Test the instance id is correctly recorded for the bootstrap node.
   211  	// Check that StateServerInstances returns the id of the bootstrap machine.
   212  	instanceIds, err := env.StateServerInstances()
   213  	c.Assert(err, jc.ErrorIsNil)
   214  	c.Assert(instanceIds, gc.HasLen, 1)
   215  	insts, err := env.AllInstances()
   216  	c.Assert(err, jc.ErrorIsNil)
   217  	c.Assert(insts, gc.HasLen, 1)
   218  	c.Check(insts[0].Id(), gc.Equals, instanceIds[0])
   219  
   220  	// Create node 1: it will be used as instance number 1.
   221  	suite.testMAASObject.TestServer.NewNode(fmt.Sprintf(
   222  		`{"system_id": "node1", "hostname": "host1", "architecture": "%s/generic", "memory": 1024, "cpu_count": 1}`,
   223  		version.Current.Arch),
   224  	)
   225  	lshwXML, err = suite.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f1": {0, "eth0", false}})
   226  	c.Assert(err, jc.ErrorIsNil)
   227  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
   228  	instance, hc := testing.AssertStartInstance(c, env, "1")
   229  	c.Assert(err, jc.ErrorIsNil)
   230  	c.Check(instance, gc.NotNil)
   231  	c.Assert(hc, gc.NotNil)
   232  	c.Check(hc.String(), gc.Equals, fmt.Sprintf("arch=%s cpu-cores=1 mem=1024M", version.Current.Arch))
   233  
   234  	// The instance number 1 has been acquired and started.
   235  	actions, found = operations["node1"]
   236  	c.Assert(found, jc.IsTrue)
   237  	c.Check(actions, gc.DeepEquals, []string{"acquire", "start"})
   238  
   239  	// The value of the "user data" parameter used when starting the node
   240  	// contains the run cmd used to write the machine information onto
   241  	// the node's filesystem.
   242  	requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues()
   243  	nodeRequestValues, found := requestValues["node1"]
   244  	c.Assert(found, jc.IsTrue)
   245  	c.Assert(len(nodeRequestValues), gc.Equals, 2)
   246  	userData := nodeRequestValues[1].Get("user_data")
   247  	decodedUserData, err := decodeUserData(userData)
   248  	c.Assert(err, jc.ErrorIsNil)
   249  	info := machineInfo{"host1"}
   250  	cloudinitRunCmd, err := info.cloudinitRunCmd("precise")
   251  	c.Assert(err, jc.ErrorIsNil)
   252  	data, err := goyaml.Marshal(cloudinitRunCmd)
   253  	c.Assert(err, jc.ErrorIsNil)
   254  	c.Check(string(decodedUserData), jc.Contains, string(data))
   255  
   256  	// Trash the tools and try to start another instance.
   257  	suite.PatchValue(&envtools.DefaultBaseURL, "")
   258  	instance, _, _, err = testing.StartInstance(env, "2")
   259  	c.Check(instance, gc.IsNil)
   260  	c.Check(err, jc.Satisfies, errors.IsNotFound)
   261  }
   262  
   263  func uint64p(val uint64) *uint64 {
   264  	return &val
   265  }
   266  
   267  func stringp(val string) *string {
   268  	return &val
   269  }
   270  
   271  func (suite *environSuite) TestSelectNodeValidZone(c *gc.C) {
   272  	env := suite.makeEnviron()
   273  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0", "zone": "bar"}`)
   274  
   275  	snArgs := selectNodeArgs{
   276  		AvailabilityZones: []string{"foo", "bar"},
   277  		Constraints:       constraints.Value{},
   278  		IncludeNetworks:   nil,
   279  		ExcludeNetworks:   nil,
   280  	}
   281  
   282  	node, err := env.selectNode(snArgs)
   283  	c.Assert(err, jc.ErrorIsNil)
   284  	c.Assert(node, gc.NotNil)
   285  }
   286  
   287  func (suite *environSuite) TestSelectNodeInvalidZone(c *gc.C) {
   288  	env := suite.makeEnviron()
   289  
   290  	snArgs := selectNodeArgs{
   291  		AvailabilityZones: []string{"foo", "bar"},
   292  		Constraints:       constraints.Value{},
   293  		IncludeNetworks:   nil,
   294  		ExcludeNetworks:   nil,
   295  	}
   296  
   297  	_, err := env.selectNode(snArgs)
   298  	c.Assert(fmt.Sprintf("%s", err), gc.Equals, "cannot run instances: gomaasapi: got error back from server: 409 Conflict ()")
   299  }
   300  
   301  func (suite *environSuite) TestAcquireNode(c *gc.C) {
   302  	env := suite.makeEnviron()
   303  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`)
   304  
   305  	_, err := env.acquireNode("", "", constraints.Value{}, nil, nil)
   306  
   307  	c.Check(err, jc.ErrorIsNil)
   308  	operations := suite.testMAASObject.TestServer.NodeOperations()
   309  	actions, found := operations["node0"]
   310  	c.Assert(found, jc.IsTrue)
   311  	c.Check(actions, gc.DeepEquals, []string{"acquire"})
   312  
   313  	// no "name" parameter should have been passed through
   314  	values := suite.testMAASObject.TestServer.NodeOperationRequestValues()["node0"][0]
   315  	_, found = values["name"]
   316  	c.Assert(found, jc.IsFalse)
   317  }
   318  
   319  func (suite *environSuite) TestAcquireNodeByName(c *gc.C) {
   320  	env := suite.makeEnviron()
   321  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`)
   322  
   323  	_, err := env.acquireNode("host0", "", constraints.Value{}, nil, nil)
   324  
   325  	c.Check(err, jc.ErrorIsNil)
   326  	operations := suite.testMAASObject.TestServer.NodeOperations()
   327  	actions, found := operations["node0"]
   328  	c.Assert(found, jc.IsTrue)
   329  	c.Check(actions, gc.DeepEquals, []string{"acquire"})
   330  
   331  	// no "name" parameter should have been passed through
   332  	values := suite.testMAASObject.TestServer.NodeOperationRequestValues()["node0"][0]
   333  	nodeName := values.Get("name")
   334  	c.Assert(nodeName, gc.Equals, "host0")
   335  }
   336  
   337  func (suite *environSuite) TestAcquireNodeTakesConstraintsIntoAccount(c *gc.C) {
   338  	env := suite.makeEnviron()
   339  	suite.testMAASObject.TestServer.NewNode(
   340  		`{"system_id": "node0", "hostname": "host0", "architecture": "arm/generic", "memory": 2048}`,
   341  	)
   342  	constraints := constraints.Value{Arch: stringp("arm"), Mem: uint64p(1024)}
   343  
   344  	_, err := env.acquireNode("", "", constraints, nil, nil)
   345  
   346  	c.Check(err, jc.ErrorIsNil)
   347  	requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues()
   348  	nodeRequestValues, found := requestValues["node0"]
   349  	c.Assert(found, jc.IsTrue)
   350  	c.Assert(nodeRequestValues[0].Get("arch"), gc.Equals, "arm")
   351  	c.Assert(nodeRequestValues[0].Get("mem"), gc.Equals, "1024")
   352  }
   353  
   354  func (suite *environSuite) TestParseTags(c *gc.C) {
   355  	tests := []struct {
   356  		about         string
   357  		input         []string
   358  		tags, notTags []string
   359  	}{{
   360  		about:   "nil input",
   361  		input:   nil,
   362  		tags:    []string{},
   363  		notTags: []string{},
   364  	}, {
   365  		about:   "empty input",
   366  		input:   []string{},
   367  		tags:    []string{},
   368  		notTags: []string{},
   369  	}, {
   370  		about:   "tag list with embedded spaces",
   371  		input:   []string{"   tag1  ", " tag2", " ^ not Tag 3  ", "  ", " ", "", "", " ^notTag4   "},
   372  		tags:    []string{"tag1", "tag2"},
   373  		notTags: []string{"notTag3", "notTag4"},
   374  	}, {
   375  		about:   "only positive tags",
   376  		input:   []string{"tag1", "tag2", "tag3"},
   377  		tags:    []string{"tag1", "tag2", "tag3"},
   378  		notTags: []string{},
   379  	}, {
   380  		about:   "only negative tags",
   381  		input:   []string{"^tag1", "^tag2", "^tag3"},
   382  		tags:    []string{},
   383  		notTags: []string{"tag1", "tag2", "tag3"},
   384  	}, {
   385  		about:   "both positive and negative tags",
   386  		input:   []string{"^tag1", "tag2", "^tag3", "tag4"},
   387  		tags:    []string{"tag2", "tag4"},
   388  		notTags: []string{"tag1", "tag3"},
   389  	}, {
   390  		about:   "single positive tag",
   391  		input:   []string{"tag1"},
   392  		tags:    []string{"tag1"},
   393  		notTags: []string{},
   394  	}, {
   395  		about:   "single negative tag",
   396  		input:   []string{"^tag1"},
   397  		tags:    []string{},
   398  		notTags: []string{"tag1"},
   399  	}}
   400  	for i, test := range tests {
   401  		c.Logf("test %d: %s", i, test.about)
   402  		tags, notTags := parseTags(test.input)
   403  		c.Check(tags, jc.DeepEquals, test.tags)
   404  		c.Check(notTags, jc.DeepEquals, test.notTags)
   405  	}
   406  }
   407  
   408  func (suite *environSuite) TestAcquireNodePassedAgentName(c *gc.C) {
   409  	env := suite.makeEnviron()
   410  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`)
   411  
   412  	_, err := env.acquireNode("", "", constraints.Value{}, nil, nil)
   413  
   414  	c.Check(err, jc.ErrorIsNil)
   415  	requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues()
   416  	nodeRequestValues, found := requestValues["node0"]
   417  	c.Assert(found, jc.IsTrue)
   418  	c.Assert(nodeRequestValues[0].Get("agent_name"), gc.Equals, exampleAgentName)
   419  }
   420  
   421  func (suite *environSuite) TestAcquireNodePassesPositiveAndNegativeTags(c *gc.C) {
   422  	env := suite.makeEnviron()
   423  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0"}`)
   424  
   425  	_, err := env.acquireNode(
   426  		"", "",
   427  		constraints.Value{Tags: &[]string{"tag1", "^tag2", "tag3", "^tag4"}},
   428  		nil, nil,
   429  	)
   430  
   431  	c.Check(err, jc.ErrorIsNil)
   432  	requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues()
   433  	nodeValues, found := requestValues["node0"]
   434  	c.Assert(found, jc.IsTrue)
   435  	c.Assert(nodeValues[0].Get("tags"), gc.Equals, "tag1,tag3")
   436  	c.Assert(nodeValues[0].Get("not_tags"), gc.Equals, "tag2,tag4")
   437  }
   438  
   439  var testValues = []struct {
   440  	constraints    constraints.Value
   441  	expectedResult url.Values
   442  }{
   443  	{constraints.Value{Arch: stringp("arm")}, url.Values{"arch": {"arm"}}},
   444  	{constraints.Value{CpuCores: uint64p(4)}, url.Values{"cpu_count": {"4"}}},
   445  	{constraints.Value{Mem: uint64p(1024)}, url.Values{"mem": {"1024"}}},
   446  	{constraints.Value{Tags: &[]string{"tag1", "tag2", "^tag3", "^tag4"}}, url.Values{"tags": {"tag1,tag2"}, "not_tags": {"tag3,tag4"}}},
   447  
   448  	// CpuPower is ignored.
   449  	{constraints.Value{CpuPower: uint64p(1024)}, url.Values{}},
   450  
   451  	// RootDisk is ignored.
   452  	{constraints.Value{RootDisk: uint64p(8192)}, url.Values{}},
   453  	{constraints.Value{Tags: &[]string{"foo", "bar"}}, url.Values{"tags": {"foo,bar"}}},
   454  	{constraints.Value{Arch: stringp("arm"), CpuCores: uint64p(4), Mem: uint64p(1024), CpuPower: uint64p(1024), RootDisk: uint64p(8192), Tags: &[]string{"foo", "bar"}}, url.Values{"arch": {"arm"}, "cpu_count": {"4"}, "mem": {"1024"}, "tags": {"foo,bar"}}},
   455  }
   456  
   457  func (*environSuite) TestConvertConstraints(c *gc.C) {
   458  	for _, test := range testValues {
   459  		c.Check(convertConstraints(test.constraints), gc.DeepEquals, test.expectedResult)
   460  	}
   461  }
   462  
   463  var testNetworkValues = []struct {
   464  	includeNetworks []string
   465  	excludeNetworks []string
   466  	expectedResult  url.Values
   467  }{
   468  	{
   469  		nil,
   470  		nil,
   471  		url.Values{},
   472  	},
   473  	{
   474  		[]string{"included_net_1"},
   475  		nil,
   476  		url.Values{"networks": {"included_net_1"}},
   477  	},
   478  	{
   479  		nil,
   480  		[]string{"excluded_net_1"},
   481  		url.Values{"not_networks": {"excluded_net_1"}},
   482  	},
   483  	{
   484  		[]string{"included_net_1", "included_net_2"},
   485  		[]string{"excluded_net_1", "excluded_net_2"},
   486  		url.Values{
   487  			"networks":     {"included_net_1", "included_net_2"},
   488  			"not_networks": {"excluded_net_1", "excluded_net_2"},
   489  		},
   490  	},
   491  }
   492  
   493  func (*environSuite) TestConvertNetworks(c *gc.C) {
   494  	for _, test := range testNetworkValues {
   495  		var vals = url.Values{}
   496  		addNetworks(vals, test.includeNetworks, test.excludeNetworks)
   497  		c.Check(vals, gc.DeepEquals, test.expectedResult)
   498  	}
   499  }
   500  
   501  func (suite *environSuite) getInstance(systemId string) *maasInstance {
   502  	input := fmt.Sprintf(`{"system_id": %q}`, systemId)
   503  	node := suite.testMAASObject.TestServer.NewNode(input)
   504  	return &maasInstance{maasObject: &node, environ: suite.makeEnviron()}
   505  }
   506  
   507  func (suite *environSuite) getNetwork(name string, id int, vlanTag int) *gomaasapi.MAASObject {
   508  	var vlan string
   509  	if vlanTag == 0 {
   510  		vlan = "null"
   511  	} else {
   512  		vlan = fmt.Sprintf("%d", vlanTag)
   513  	}
   514  	var input string
   515  	input = fmt.Sprintf(`{"name": %q, "ip":"192.168.%d.1", "netmask": "255.255.255.0",`+
   516  		`"vlan_tag": %s, "description": "%s_%d_%d" }`, name, id, vlan, name, id, vlanTag)
   517  	network := suite.testMAASObject.TestServer.NewNetwork(input)
   518  	return &network
   519  }
   520  
   521  func (suite *environSuite) TestStopInstancesReturnsIfParameterEmpty(c *gc.C) {
   522  	suite.getInstance("test1")
   523  
   524  	err := suite.makeEnviron().StopInstances()
   525  	c.Check(err, jc.ErrorIsNil)
   526  	operations := suite.testMAASObject.TestServer.NodeOperations()
   527  	c.Check(operations, gc.DeepEquals, map[string][]string{})
   528  }
   529  
   530  func (suite *environSuite) TestStopInstancesStopsAndReleasesInstances(c *gc.C) {
   531  	suite.getInstance("test1")
   532  	suite.getInstance("test2")
   533  	suite.getInstance("test3")
   534  	// mark test1 and test2 as being allocated, but not test3.
   535  	// The release operation will ignore test3.
   536  	suite.testMAASObject.TestServer.OwnedNodes()["test1"] = true
   537  	suite.testMAASObject.TestServer.OwnedNodes()["test2"] = true
   538  
   539  	err := suite.makeEnviron().StopInstances("test1", "test2", "test3")
   540  	c.Check(err, jc.ErrorIsNil)
   541  	operations := suite.testMAASObject.TestServer.NodesOperations()
   542  	c.Check(operations, gc.DeepEquals, []string{"release"})
   543  	c.Assert(suite.testMAASObject.TestServer.OwnedNodes()["test1"], jc.IsFalse)
   544  	c.Assert(suite.testMAASObject.TestServer.OwnedNodes()["test2"], jc.IsFalse)
   545  }
   546  
   547  func (suite *environSuite) TestStopInstancesIgnoresConflict(c *gc.C) {
   548  	releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error {
   549  		return gomaasapi.ServerError{StatusCode: 409}
   550  	}
   551  	suite.PatchValue(&ReleaseNodes, releaseNodes)
   552  	env := suite.makeEnviron()
   553  	err := env.StopInstances("test1")
   554  	c.Assert(err, jc.ErrorIsNil)
   555  }
   556  
   557  func (suite *environSuite) TestStopInstancesIgnoresMissingNodeAndRecurses(c *gc.C) {
   558  	attemptedNodes := [][]string{}
   559  	releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error {
   560  		attemptedNodes = append(attemptedNodes, ids["nodes"])
   561  		return gomaasapi.ServerError{StatusCode: 404}
   562  	}
   563  	suite.PatchValue(&ReleaseNodes, releaseNodes)
   564  	env := suite.makeEnviron()
   565  	err := env.StopInstances("test1", "test2")
   566  	c.Assert(err, jc.ErrorIsNil)
   567  
   568  	expectedNodes := [][]string{{"test1", "test2"}, {"test1"}, {"test2"}}
   569  	c.Assert(attemptedNodes, gc.DeepEquals, expectedNodes)
   570  }
   571  
   572  func (suite *environSuite) TestStopInstancesReturnsUnexpectedMAASError(c *gc.C) {
   573  	releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error {
   574  		return gomaasapi.ServerError{StatusCode: 405}
   575  	}
   576  	suite.PatchValue(&ReleaseNodes, releaseNodes)
   577  	env := suite.makeEnviron()
   578  	err := env.StopInstances("test1")
   579  	c.Assert(err, gc.NotNil)
   580  	maasErr, ok := errors.Cause(err).(gomaasapi.ServerError)
   581  	c.Assert(ok, jc.IsTrue)
   582  	c.Assert(maasErr.StatusCode, gc.Equals, 405)
   583  }
   584  
   585  func (suite *environSuite) TestStopInstancesReturnsUnexpectedError(c *gc.C) {
   586  	releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error {
   587  		return environs.ErrNoInstances
   588  	}
   589  	suite.PatchValue(&ReleaseNodes, releaseNodes)
   590  	env := suite.makeEnviron()
   591  	err := env.StopInstances("test1")
   592  	c.Assert(err, gc.NotNil)
   593  	c.Assert(errors.Cause(err), gc.Equals, environs.ErrNoInstances)
   594  }
   595  
   596  func (suite *environSuite) TestStateServerInstances(c *gc.C) {
   597  	env := suite.makeEnviron()
   598  	_, err := env.StateServerInstances()
   599  	c.Assert(err, gc.Equals, environs.ErrNotBootstrapped)
   600  
   601  	tests := [][]instance.Id{{}, {"inst-0"}, {"inst-0", "inst-1"}}
   602  	for _, expected := range tests {
   603  		err := common.SaveState(env.Storage(), &common.BootstrapState{
   604  			StateInstances: expected,
   605  		})
   606  		c.Assert(err, jc.ErrorIsNil)
   607  		stateServerInstances, err := env.StateServerInstances()
   608  		c.Assert(err, jc.ErrorIsNil)
   609  		c.Assert(stateServerInstances, jc.SameContents, expected)
   610  	}
   611  }
   612  
   613  func (suite *environSuite) TestStateServerInstancesFailsIfNoStateInstances(c *gc.C) {
   614  	env := suite.makeEnviron()
   615  	_, err := env.StateServerInstances()
   616  	c.Check(err, gc.Equals, environs.ErrNotBootstrapped)
   617  }
   618  
   619  func (suite *environSuite) TestDestroy(c *gc.C) {
   620  	env := suite.makeEnviron()
   621  	suite.getInstance("test1")
   622  	suite.testMAASObject.TestServer.OwnedNodes()["test1"] = true // simulate acquire
   623  	data := makeRandomBytes(10)
   624  	suite.testMAASObject.TestServer.NewFile("filename", data)
   625  	stor := env.Storage()
   626  
   627  	err := env.Destroy()
   628  	c.Check(err, jc.ErrorIsNil)
   629  
   630  	// Instances have been stopped.
   631  	operations := suite.testMAASObject.TestServer.NodesOperations()
   632  	c.Check(operations, gc.DeepEquals, []string{"release"})
   633  	c.Check(suite.testMAASObject.TestServer.OwnedNodes()["test1"], jc.IsFalse)
   634  	// Files have been cleaned up.
   635  	listing, err := storage.List(stor, "")
   636  	c.Assert(err, jc.ErrorIsNil)
   637  	c.Check(listing, gc.DeepEquals, []string{})
   638  }
   639  
   640  func (suite *environSuite) TestBootstrapSucceeds(c *gc.C) {
   641  	suite.setupFakeTools(c)
   642  	env := suite.makeEnviron()
   643  	suite.testMAASObject.TestServer.NewNode(fmt.Sprintf(
   644  		`{"system_id": "thenode", "hostname": "host", "architecture": "%s/generic", "memory": 256, "cpu_count": 8}`,
   645  		version.Current.Arch),
   646  	)
   647  	lshwXML, err := suite.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}})
   648  	c.Assert(err, jc.ErrorIsNil)
   649  	suite.testMAASObject.TestServer.AddNodeDetails("thenode", lshwXML)
   650  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   651  	c.Assert(err, jc.ErrorIsNil)
   652  }
   653  
   654  func (suite *environSuite) TestBootstrapNodeNotDeployed(c *gc.C) {
   655  	suite.setupFakeTools(c)
   656  	env := suite.makeEnviron()
   657  	suite.testMAASObject.TestServer.NewNode(fmt.Sprintf(
   658  		`{"system_id": "thenode", "hostname": "host", "architecture": "%s/generic", "memory": 256, "cpu_count": 8}`,
   659  		version.Current.Arch),
   660  	)
   661  	lshwXML, err := suite.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}})
   662  	c.Assert(err, jc.ErrorIsNil)
   663  	suite.testMAASObject.TestServer.AddNodeDetails("thenode", lshwXML)
   664  	// Ensure node will not be reported as deployed by changing its status.
   665  	suite.testMAASObject.TestServer.ChangeNode("thenode", "status", "4")
   666  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   667  	c.Assert(err, gc.ErrorMatches, "bootstrap instance started but did not change to Deployed state.*")
   668  }
   669  
   670  func (suite *environSuite) TestBootstrapFailsIfNoTools(c *gc.C) {
   671  	env := suite.makeEnviron()
   672  	// Disable auto-uploading by setting the agent version.
   673  	cfg, err := env.Config().Apply(map[string]interface{}{
   674  		"agent-version": version.Current.Number.String(),
   675  	})
   676  	c.Assert(err, jc.ErrorIsNil)
   677  	err = env.SetConfig(cfg)
   678  	c.Assert(err, jc.ErrorIsNil)
   679  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   680  	c.Check(err, gc.ErrorMatches, "Juju cannot bootstrap because no tools are available for your environment(.|\n)*")
   681  }
   682  
   683  func (suite *environSuite) TestBootstrapFailsIfNoNodes(c *gc.C) {
   684  	suite.setupFakeTools(c)
   685  	env := suite.makeEnviron()
   686  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   687  	// Since there are no nodes, the attempt to allocate one returns a
   688  	// 409: Conflict.
   689  	c.Check(err, gc.ErrorMatches, ".*409.*")
   690  }
   691  
   692  func assertSourceContents(c *gc.C, source simplestreams.DataSource, filename string, content []byte) {
   693  	rc, _, err := source.Fetch(filename)
   694  	c.Assert(err, jc.ErrorIsNil)
   695  	defer rc.Close()
   696  	retrieved, err := ioutil.ReadAll(rc)
   697  	c.Assert(err, jc.ErrorIsNil)
   698  	c.Assert(retrieved, gc.DeepEquals, content)
   699  }
   700  
   701  func (suite *environSuite) TestGetToolsMetadataSources(c *gc.C) {
   702  	env := suite.makeEnviron()
   703  	// Add a dummy file to storage so we can use that to check the
   704  	// obtained source later.
   705  	data := makeRandomBytes(10)
   706  	stor := NewStorage(env)
   707  	err := stor.Put("tools/filename", bytes.NewBuffer([]byte(data)), int64(len(data)))
   708  	c.Assert(err, jc.ErrorIsNil)
   709  	sources, err := envtools.GetMetadataSources(env)
   710  	c.Assert(err, jc.ErrorIsNil)
   711  	c.Assert(sources, gc.HasLen, 0)
   712  }
   713  
   714  func (suite *environSuite) TestSupportedArchitectures(c *gc.C) {
   715  	suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "precise"}`)
   716  	suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "trusty"}`)
   717  	suite.testMAASObject.TestServer.AddBootImage("uuid-1", `{"architecture": "amd64", "release": "precise"}`)
   718  	suite.testMAASObject.TestServer.AddBootImage("uuid-1", `{"architecture": "ppc64el", "release": "trusty"}`)
   719  	env := suite.makeEnviron()
   720  	a, err := env.SupportedArchitectures()
   721  	c.Assert(err, jc.ErrorIsNil)
   722  	c.Assert(a, jc.SameContents, []string{"amd64", "ppc64el"})
   723  }
   724  
   725  func (suite *environSuite) TestSupportedArchitecturesFallback(c *gc.C) {
   726  	// If we cannot query boot-images (e.g. MAAS server version 1.4),
   727  	// then Juju will fall over to listing all the available nodes.
   728  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "architecture": "amd64/generic"}`)
   729  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node1", "architecture": "armhf"}`)
   730  	env := suite.makeEnviron()
   731  	a, err := env.SupportedArchitectures()
   732  	c.Assert(err, jc.ErrorIsNil)
   733  	c.Assert(a, jc.SameContents, []string{"amd64", "armhf"})
   734  }
   735  
   736  func (suite *environSuite) TestConstraintsValidator(c *gc.C) {
   737  	suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "trusty"}`)
   738  	env := suite.makeEnviron()
   739  	validator, err := env.ConstraintsValidator()
   740  	c.Assert(err, jc.ErrorIsNil)
   741  	cons := constraints.MustParse("arch=amd64 cpu-power=10 instance-type=foo")
   742  	unsupported, err := validator.Validate(cons)
   743  	c.Assert(err, jc.ErrorIsNil)
   744  	c.Assert(unsupported, jc.SameContents, []string{"cpu-power", "instance-type"})
   745  }
   746  
   747  func (suite *environSuite) TestConstraintsValidatorVocab(c *gc.C) {
   748  	suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "trusty"}`)
   749  	suite.testMAASObject.TestServer.AddBootImage("uuid-1", `{"architecture": "armhf", "release": "precise"}`)
   750  	env := suite.makeEnviron()
   751  	validator, err := env.ConstraintsValidator()
   752  	c.Assert(err, jc.ErrorIsNil)
   753  	cons := constraints.MustParse("arch=ppc64el")
   754  	_, err = validator.Validate(cons)
   755  	c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=ppc64el\nvalid values are: \\[amd64 armhf\\]")
   756  }
   757  
   758  func (suite *environSuite) TestGetNetworkMACs(c *gc.C) {
   759  	env := suite.makeEnviron()
   760  
   761  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node_1"}`)
   762  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node_2"}`)
   763  	suite.testMAASObject.TestServer.NewNetwork(
   764  		`{"name": "net_1","ip":"0.1.2.0","netmask":"255.255.255.0"}`,
   765  	)
   766  	suite.testMAASObject.TestServer.NewNetwork(
   767  		`{"name": "net_2","ip":"0.2.2.0","netmask":"255.255.255.0"}`,
   768  	)
   769  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_2", "net_2", "aa:bb:cc:dd:ee:22")
   770  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_1", "net_1", "aa:bb:cc:dd:ee:11")
   771  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_2", "net_1", "aa:bb:cc:dd:ee:21")
   772  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_1", "net_2", "aa:bb:cc:dd:ee:12")
   773  
   774  	networks, err := env.getNetworkMACs("net_1")
   775  	c.Assert(err, jc.ErrorIsNil)
   776  	c.Check(networks, jc.SameContents, []string{"aa:bb:cc:dd:ee:11", "aa:bb:cc:dd:ee:21"})
   777  
   778  	networks, err = env.getNetworkMACs("net_2")
   779  	c.Assert(err, jc.ErrorIsNil)
   780  	c.Check(networks, jc.SameContents, []string{"aa:bb:cc:dd:ee:12", "aa:bb:cc:dd:ee:22"})
   781  
   782  	networks, err = env.getNetworkMACs("net_3")
   783  	c.Check(networks, gc.HasLen, 0)
   784  	c.Assert(err, jc.ErrorIsNil)
   785  }
   786  
   787  func (suite *environSuite) TestGetInstanceNetworks(c *gc.C) {
   788  	suite.getNetwork("test_network", 123, 321)
   789  	test_instance := suite.getInstance("instance_for_network")
   790  	suite.testMAASObject.TestServer.ConnectNodeToNetwork("instance_for_network", "test_network")
   791  	networks, err := suite.makeEnviron().getInstanceNetworks(test_instance)
   792  	c.Assert(err, jc.ErrorIsNil)
   793  	c.Check(networks, gc.DeepEquals, []networkDetails{
   794  		{Name: "test_network", IP: "192.168.123.1", Mask: "255.255.255.0", VLANTag: 321,
   795  			Description: "test_network_123_321"},
   796  	})
   797  }
   798  
   799  // A typical lshw XML dump with lots of things left out.
   800  const lshwXMLTestExtractInterfaces = `
   801  <?xml version="1.0" standalone="yes" ?>
   802  <!-- generated by lshw-B.02.16 -->
   803  <list>
   804  <node id="machine" claimed="true" class="system" handle="DMI:0001">
   805   <description>Notebook</description>
   806   <product>MyMachine</product>
   807   <version>1.0</version>
   808   <width units="bits">64</width>
   809    <node id="core" claimed="true" class="bus" handle="DMI:0002">
   810     <description>Motherboard</description>
   811      <node id="cpu" claimed="true" class="processor" handle="DMI:0004">
   812       <description>CPU</description>
   813        <node id="pci:2" claimed="true" class="bridge" handle="PCIBUS:0000:03">
   814          <node id="network:0" claimed="true" disabled="true" class="network" handle="PCI:0000:03:00.0">
   815           <logicalname>wlan0</logicalname>
   816           <serial>aa:bb:cc:dd:ee:ff</serial>
   817          </node>
   818          <node id="network:1" claimed="true" class="network" handle="PCI:0000:04:00.0">
   819           <logicalname>eth0</logicalname>
   820           <serial>aa:bb:cc:dd:ee:f1</serial>
   821          </node>
   822        </node>
   823      </node>
   824    </node>
   825    <node id="network:2" claimed="true" class="network" handle="">
   826     <logicalname>vnet1</logicalname>
   827     <serial>aa:bb:cc:dd:ee:f2</serial>
   828    </node>
   829  </node>
   830  </list>
   831  `
   832  
   833  func (suite *environSuite) TestExtractInterfaces(c *gc.C) {
   834  	inst := suite.getInstance("testInstance")
   835  	interfaces, primaryIface, err := extractInterfaces(inst, []byte(lshwXMLTestExtractInterfaces))
   836  	c.Assert(err, jc.ErrorIsNil)
   837  	c.Check(primaryIface, gc.Equals, "eth0")
   838  	c.Check(interfaces, jc.DeepEquals, map[string]ifaceInfo{
   839  		"aa:bb:cc:dd:ee:ff": {0, "wlan0", true},
   840  		"aa:bb:cc:dd:ee:f1": {1, "eth0", false},
   841  		"aa:bb:cc:dd:ee:f2": {2, "vnet1", false},
   842  	})
   843  }
   844  
   845  func (suite *environSuite) TestGetInstanceNetworkInterfaces(c *gc.C) {
   846  	inst := suite.getInstance("testInstance")
   847  	templateInterfaces := map[string]ifaceInfo{
   848  		"aa:bb:cc:dd:ee:ff": {0, "wlan0", true},
   849  		"aa:bb:cc:dd:ee:f1": {1, "eth0", true},
   850  		"aa:bb:cc:dd:ee:f2": {2, "vnet1", false},
   851  	}
   852  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
   853  	c.Assert(err, jc.ErrorIsNil)
   854  
   855  	suite.testMAASObject.TestServer.AddNodeDetails("testInstance", lshwXML)
   856  	interfaces, primaryIface, err := inst.environ.getInstanceNetworkInterfaces(inst)
   857  	c.Assert(err, jc.ErrorIsNil)
   858  	// Both wlan0 and eth0 are disabled in lshw output.
   859  	c.Check(primaryIface, gc.Equals, "vnet1")
   860  	c.Check(interfaces, jc.DeepEquals, templateInterfaces)
   861  }
   862  
   863  func (suite *environSuite) TestSetupNetworks(c *gc.C) {
   864  	test_instance := suite.getInstance("node1")
   865  	templateInterfaces := map[string]ifaceInfo{
   866  		"aa:bb:cc:dd:ee:ff": {0, "wlan0", true},
   867  		"aa:bb:cc:dd:ee:f1": {1, "eth0", true},
   868  		"aa:bb:cc:dd:ee:f2": {2, "vnet1", false},
   869  	}
   870  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
   871  	c.Assert(err, jc.ErrorIsNil)
   872  
   873  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
   874  	suite.getNetwork("LAN", 2, 42)
   875  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1")
   876  	suite.getNetwork("Virt", 3, 0)
   877  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f2")
   878  	suite.getNetwork("WLAN", 1, 0)
   879  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "WLAN", "aa:bb:cc:dd:ee:ff")
   880  	networkInfo, primaryIface, err := suite.makeEnviron().setupNetworks(
   881  		test_instance,
   882  		set.NewStrings("WLAN"), // Disable WLAN only.
   883  	)
   884  	c.Assert(err, jc.ErrorIsNil)
   885  
   886  	// Note: order of networks is based on lshwXML
   887  	c.Check(primaryIface, gc.Equals, "vnet1")
   888  	// Unfortunately, because network.InterfaceInfo is unhashable
   889  	// (contains a map) we can't use jc.SameContents here.
   890  	c.Check(networkInfo, gc.HasLen, 3)
   891  	for _, info := range networkInfo {
   892  		switch info.DeviceIndex {
   893  		case 0:
   894  			c.Check(info, jc.DeepEquals, network.InterfaceInfo{
   895  				MACAddress:    "aa:bb:cc:dd:ee:ff",
   896  				CIDR:          "192.168.1.1/24",
   897  				NetworkName:   "WLAN",
   898  				ProviderId:    "WLAN",
   899  				VLANTag:       0,
   900  				DeviceIndex:   0,
   901  				InterfaceName: "wlan0",
   902  				Disabled:      true, // from networksToDisable("WLAN")
   903  			})
   904  		case 1:
   905  			c.Check(info, jc.DeepEquals, network.InterfaceInfo{
   906  				DeviceIndex:   1,
   907  				MACAddress:    "aa:bb:cc:dd:ee:f1",
   908  				CIDR:          "192.168.2.1/24",
   909  				NetworkName:   "LAN",
   910  				ProviderId:    "LAN",
   911  				VLANTag:       42,
   912  				InterfaceName: "eth0",
   913  				Disabled:      true, // from networksToDisable("WLAN")
   914  			})
   915  		case 2:
   916  			c.Check(info, jc.DeepEquals, network.InterfaceInfo{
   917  				MACAddress:    "aa:bb:cc:dd:ee:f2",
   918  				CIDR:          "192.168.3.1/24",
   919  				NetworkName:   "Virt",
   920  				ProviderId:    "Virt",
   921  				VLANTag:       0,
   922  				DeviceIndex:   2,
   923  				InterfaceName: "vnet1",
   924  				Disabled:      false,
   925  			})
   926  		}
   927  	}
   928  }
   929  
   930  // The same test, but now "Virt" network does not have matched MAC address
   931  func (suite *environSuite) TestSetupNetworksPartialMatch(c *gc.C) {
   932  	test_instance := suite.getInstance("node1")
   933  	templateInterfaces := map[string]ifaceInfo{
   934  		"aa:bb:cc:dd:ee:ff": {0, "wlan0", true},
   935  		"aa:bb:cc:dd:ee:f1": {1, "eth0", false},
   936  		"aa:bb:cc:dd:ee:f2": {2, "vnet1", false},
   937  	}
   938  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
   939  	c.Assert(err, jc.ErrorIsNil)
   940  
   941  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
   942  	suite.getNetwork("LAN", 2, 42)
   943  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1")
   944  	suite.getNetwork("Virt", 3, 0)
   945  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f3")
   946  	networkInfo, primaryIface, err := suite.makeEnviron().setupNetworks(
   947  		test_instance,
   948  		set.NewStrings(), // All enabled.
   949  	)
   950  	c.Assert(err, jc.ErrorIsNil)
   951  
   952  	// Note: order of networks is based on lshwXML
   953  	c.Check(primaryIface, gc.Equals, "eth0")
   954  	c.Check(networkInfo, jc.DeepEquals, []network.InterfaceInfo{{
   955  		MACAddress:    "aa:bb:cc:dd:ee:f1",
   956  		CIDR:          "192.168.2.1/24",
   957  		NetworkName:   "LAN",
   958  		ProviderId:    "LAN",
   959  		VLANTag:       42,
   960  		DeviceIndex:   1,
   961  		InterfaceName: "eth0",
   962  		Disabled:      false,
   963  	}})
   964  }
   965  
   966  // The same test, but now no networks have matched MAC
   967  func (suite *environSuite) TestSetupNetworksNoMatch(c *gc.C) {
   968  	test_instance := suite.getInstance("node1")
   969  	templateInterfaces := map[string]ifaceInfo{
   970  		"aa:bb:cc:dd:ee:ff": {0, "wlan0", true},
   971  		"aa:bb:cc:dd:ee:f1": {1, "eth0", false},
   972  		"aa:bb:cc:dd:ee:f2": {2, "vnet1", false},
   973  	}
   974  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
   975  	c.Assert(err, jc.ErrorIsNil)
   976  
   977  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
   978  	suite.getNetwork("Virt", 3, 0)
   979  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f3")
   980  	networkInfo, primaryIface, err := suite.makeEnviron().setupNetworks(
   981  		test_instance,
   982  		set.NewStrings(), // All enabled.
   983  	)
   984  	c.Assert(err, jc.ErrorIsNil)
   985  
   986  	// Note: order of networks is based on lshwXML
   987  	c.Check(primaryIface, gc.Equals, "eth0")
   988  	c.Check(networkInfo, gc.HasLen, 0)
   989  }
   990  
   991  func (suite *environSuite) TestSupportsNetworking(c *gc.C) {
   992  	env := suite.makeEnviron()
   993  	_, supported := environs.SupportsNetworking(env)
   994  	c.Assert(supported, jc.IsTrue)
   995  }
   996  
   997  func (suite *environSuite) TestSupportsAddressAllocation(c *gc.C) {
   998  	env := suite.makeEnviron()
   999  	supported, err := env.SupportsAddressAllocation("")
  1000  	c.Assert(err, jc.ErrorIsNil)
  1001  	c.Assert(supported, jc.IsTrue)
  1002  }
  1003  
  1004  func (suite *environSuite) createSubnets(c *gc.C) instance.Instance {
  1005  	test_instance := suite.getInstance("node1")
  1006  	templateInterfaces := map[string]ifaceInfo{
  1007  		"aa:bb:cc:dd:ee:ff": {0, "wlan0", true},
  1008  		"aa:bb:cc:dd:ee:f1": {1, "eth0", false},
  1009  		"aa:bb:cc:dd:ee:f2": {2, "vnet1", false},
  1010  	}
  1011  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
  1012  	c.Assert(err, jc.ErrorIsNil)
  1013  
  1014  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
  1015  	// resulting CIDR 192.168.2.1/24
  1016  	suite.getNetwork("LAN", 2, 42)
  1017  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1")
  1018  	// resulting CIDR 192.168.3.1/24
  1019  	suite.getNetwork("Virt", 3, 0)
  1020  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f2")
  1021  	// resulting CIDR 192.168.1.1/24
  1022  	suite.getNetwork("WLAN", 1, 0)
  1023  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "WLAN", "aa:bb:cc:dd:ee:ff")
  1024  
  1025  	// needed for getNodeGroups to work
  1026  	suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "precise"}`)
  1027  	suite.testMAASObject.TestServer.AddBootImage("uuid-1", `{"architecture": "amd64", "release": "precise"}`)
  1028  
  1029  	jsonText1 := `{
  1030  		"ip_range_high":        "192.168.2.255",
  1031  		"ip_range_low":         "192.168.2.128",
  1032  		"broadcast_ip":         "192.168.2.255",
  1033  		"static_ip_range_low":  "192.168.2.0",
  1034  		"name":                 "eth0",
  1035  		"ip":                   "192.168.2.1",
  1036  		"subnet_mask":          "255.255.255.0",
  1037  		"management":           2,
  1038  		"static_ip_range_high": "192.168.2.127",
  1039  		"interface":            "eth0"
  1040  	}`
  1041  	jsonText2 := `{
  1042  		"ip_range_high":        "172.16.0.128",
  1043  		"ip_range_low":         "172.16.0.2",
  1044  		"broadcast_ip":         "172.16.0.255",
  1045  		"static_ip_range_low":  "172.16.0.129",
  1046  		"name":                 "eth0",
  1047  		"ip":                   "172.16.0.2",
  1048  		"subnet_mask":          "255.255.255.0",
  1049  		"management":           2,
  1050  		"static_ip_range_high": "172.16.0.255",
  1051  		"interface":            "eth0"
  1052  	}`
  1053  	jsonText3 := `{
  1054  		"ip_range_high":        "192.168.1.128",
  1055  		"ip_range_low":         "192.168.1.2",
  1056  		"broadcast_ip":         "192.168.1.255",
  1057  		"static_ip_range_low":  "192.168.1.129",
  1058  		"name":                 "eth0",
  1059  		"ip":                   "192.168.1.2",
  1060  		"subnet_mask":          "255.255.255.0",
  1061  		"management":           2,
  1062  		"static_ip_range_high": "192.168.1.255",
  1063  		"interface":            "eth0"
  1064  	}`
  1065  	jsonText4 := `{
  1066  		"ip_range_high":        "172.16.8.128",
  1067  		"ip_range_low":         "172.16.8.2",
  1068  		"broadcast_ip":         "172.16.8.255",
  1069  		"static_ip_range_low":  "172.16.0.129",
  1070  		"name":                 "eth0",
  1071  		"ip":                   "172.16.8.2",
  1072  		"subnet_mask":          "255.255.255.0",
  1073  		"management":           2,
  1074  		"static_ip_range_high": "172.16.8.255",
  1075  		"interface":            "eth0"
  1076  	}`
  1077  	suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-0", jsonText1)
  1078  	suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-0", jsonText2)
  1079  	suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-1", jsonText3)
  1080  	suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-1", jsonText4)
  1081  	return test_instance
  1082  }
  1083  
  1084  func (suite *environSuite) TestNetworkInterfaces(c *gc.C) {
  1085  	test_instance := suite.createSubnets(c)
  1086  
  1087  	netInfo, err := suite.makeEnviron().NetworkInterfaces(test_instance.Id())
  1088  	c.Assert(err, jc.ErrorIsNil)
  1089  
  1090  	expectedInfo := []network.InterfaceInfo{
  1091  		{DeviceIndex: 0, MACAddress: "aa:bb:cc:dd:ee:ff", CIDR: "192.168.1.1/24", ProviderSubnetId: "WLAN", InterfaceName: "wlan0", Disabled: true},
  1092  		{DeviceIndex: 1, MACAddress: "aa:bb:cc:dd:ee:f1", CIDR: "192.168.2.1/24", ProviderSubnetId: "LAN", VLANTag: 42, InterfaceName: "eth0"},
  1093  		{DeviceIndex: 2, MACAddress: "aa:bb:cc:dd:ee:f2", CIDR: "192.168.3.1/24", ProviderSubnetId: "Virt", InterfaceName: "vnet1"},
  1094  	}
  1095  	network.SortInterfaceInfo(netInfo)
  1096  	c.Assert(netInfo, jc.DeepEquals, expectedInfo)
  1097  }
  1098  
  1099  func (suite *environSuite) TestSubnets(c *gc.C) {
  1100  	test_instance := suite.createSubnets(c)
  1101  
  1102  	netInfo, err := suite.makeEnviron().Subnets(test_instance.Id(), []network.Id{"LAN", "Virt", "WLAN"})
  1103  	c.Assert(err, jc.ErrorIsNil)
  1104  
  1105  	expectedInfo := []network.SubnetInfo{
  1106  		{CIDR: "192.168.2.1/24", ProviderId: "LAN", VLANTag: 42, AllocatableIPLow: net.ParseIP("192.168.2.0"), AllocatableIPHigh: net.ParseIP("192.168.2.127")},
  1107  		{CIDR: "192.168.3.1/24", ProviderId: "Virt", VLANTag: 0},
  1108  		{CIDR: "192.168.1.1/24", ProviderId: "WLAN", VLANTag: 0, AllocatableIPLow: net.ParseIP("192.168.1.129"), AllocatableIPHigh: net.ParseIP("192.168.1.255")}}
  1109  	c.Assert(netInfo, jc.DeepEquals, expectedInfo)
  1110  }
  1111  
  1112  func (suite *environSuite) TestSubnetsNoNetIds(c *gc.C) {
  1113  	test_instance := suite.createSubnets(c)
  1114  	_, err := suite.makeEnviron().Subnets(test_instance.Id(), []network.Id{})
  1115  	c.Assert(err, gc.ErrorMatches, "netIds must not be empty")
  1116  }
  1117  
  1118  func (suite *environSuite) TestSubnetsMissingNetwork(c *gc.C) {
  1119  	test_instance := suite.createSubnets(c)
  1120  	_, err := suite.makeEnviron().Subnets(test_instance.Id(), []network.Id{"WLAN", "Missing"})
  1121  	c.Assert(err, gc.ErrorMatches, "failed to find the following networks: \\[Missing\\]")
  1122  }
  1123  
  1124  func (suite *environSuite) TestAllocateAddress(c *gc.C) {
  1125  	test_instance := suite.createSubnets(c)
  1126  	env := suite.makeEnviron()
  1127  
  1128  	// note that the default test server always succeeds if we provide a
  1129  	// valid instance id and net id
  1130  	err := env.AllocateAddress(test_instance.Id(), "LAN", network.Address{Value: "192.168.2.1"})
  1131  	c.Assert(err, jc.ErrorIsNil)
  1132  }
  1133  
  1134  func (suite *environSuite) TestAllocateAddressInvalidInstance(c *gc.C) {
  1135  	env := suite.makeEnviron()
  1136  	err := env.AllocateAddress("foo", "bar", network.Address{Value: "192.168.2.1"})
  1137  	c.Assert(err, gc.ErrorMatches, "instance foo not found")
  1138  }
  1139  
  1140  func (suite *environSuite) TestAllocateAddressMissingSubnet(c *gc.C) {
  1141  	test_instance := suite.createSubnets(c)
  1142  	env := suite.makeEnviron()
  1143  	err := env.AllocateAddress(test_instance.Id(), "bar", network.Address{Value: "192.168.2.1"})
  1144  	c.Assert(errors.Cause(err), gc.ErrorMatches, "failed to find the following networks: \\[bar\\]")
  1145  }
  1146  
  1147  func (suite *environSuite) TestAllocateAddressIPAddressUnavailable(c *gc.C) {
  1148  	test_instance := suite.createSubnets(c)
  1149  	env := suite.makeEnviron()
  1150  
  1151  	reserveIPAddress := func(ipaddresses gomaasapi.MAASObject, cidr string, addr network.Address) error {
  1152  		return gomaasapi.ServerError{StatusCode: 404}
  1153  	}
  1154  	suite.PatchValue(&ReserveIPAddress, reserveIPAddress)
  1155  
  1156  	err := env.AllocateAddress(test_instance.Id(), "LAN", network.Address{Value: "192.168.2.1"})
  1157  	c.Assert(err, gc.Equals, environs.ErrIPAddressUnavailable)
  1158  }
  1159  
  1160  func (s *environSuite) TestPrecheckInstanceAvailZone(c *gc.C) {
  1161  	s.testMAASObject.TestServer.AddZone("zone1", "the grass is greener in zone1")
  1162  	env := s.makeEnviron()
  1163  	placement := "zone=zone1"
  1164  	err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement)
  1165  	c.Assert(err, jc.ErrorIsNil)
  1166  }
  1167  
  1168  func (suite *environSuite) TestReleaseAddress(c *gc.C) {
  1169  	test_instance := suite.createSubnets(c)
  1170  	env := suite.makeEnviron()
  1171  
  1172  	err := env.AllocateAddress(test_instance.Id(), "LAN", network.Address{Value: "192.168.2.1"})
  1173  	c.Assert(err, jc.ErrorIsNil)
  1174  
  1175  	err = env.ReleaseAddress("foo", "bar", network.Address{Value: "192.168.2.1"})
  1176  	c.Assert(err, jc.ErrorIsNil)
  1177  
  1178  	// by releasing again we can test that the first release worked, *and*
  1179  	// the error handling of ReleaseError
  1180  	err = env.ReleaseAddress("foo", "bar", network.Address{Value: "192.168.2.1"})
  1181  	c.Assert(err, gc.ErrorMatches, "(.|\n)*failed to release IP address 192\\.168\\.2\\.1(.|\n)*")
  1182  }
  1183  
  1184  func (s *environSuite) TestPrecheckInstanceAvailZoneUnknown(c *gc.C) {
  1185  	s.testMAASObject.TestServer.AddZone("zone1", "the grass is greener in zone1")
  1186  	env := s.makeEnviron()
  1187  	placement := "zone=zone2"
  1188  	err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement)
  1189  	c.Assert(err, gc.ErrorMatches, `invalid availability zone "zone2"`)
  1190  }
  1191  
  1192  func (s *environSuite) TestPrecheckInstanceAvailZonesUnsupported(c *gc.C) {
  1193  	env := s.makeEnviron()
  1194  	placement := "zone=test-unknown"
  1195  	err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement)
  1196  	c.Assert(err, jc.Satisfies, errors.IsNotImplemented)
  1197  }
  1198  
  1199  func (s *environSuite) TestPrecheckInvalidPlacement(c *gc.C) {
  1200  	env := s.makeEnviron()
  1201  	err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, "notzone=anything")
  1202  	c.Assert(err, gc.ErrorMatches, "unknown placement directive: notzone=anything")
  1203  }
  1204  
  1205  func (s *environSuite) TestPrecheckNodePlacement(c *gc.C) {
  1206  	env := s.makeEnviron()
  1207  	err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, "assumed_node_name")
  1208  	c.Assert(err, jc.ErrorIsNil)
  1209  }
  1210  
  1211  func (s *environSuite) TestStartInstanceAvailZone(c *gc.C) {
  1212  	// Add a node for the started instance.
  1213  	s.newNode(c, "thenode1", "host1", map[string]interface{}{"zone": "test-available"})
  1214  	s.testMAASObject.TestServer.AddZone("test-available", "description")
  1215  	inst, err := s.testStartInstanceAvailZone(c, "test-available")
  1216  	c.Assert(err, jc.ErrorIsNil)
  1217  	c.Assert(inst.(*maasInstance).zone(), gc.Equals, "test-available")
  1218  }
  1219  
  1220  func (s *environSuite) TestStartInstanceAvailZoneUnknown(c *gc.C) {
  1221  	s.testMAASObject.TestServer.AddZone("test-available", "description")
  1222  	_, err := s.testStartInstanceAvailZone(c, "test-unknown")
  1223  	c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`)
  1224  }
  1225  
  1226  func (s *environSuite) testStartInstanceAvailZone(c *gc.C, zone string) (instance.Instance, error) {
  1227  	env := s.bootstrap(c)
  1228  	params := environs.StartInstanceParams{Placement: "zone=" + zone}
  1229  	result, err := testing.StartInstanceWithParams(env, "1", params, nil)
  1230  	if err != nil {
  1231  		return nil, err
  1232  	}
  1233  	return result.Instance, nil
  1234  }
  1235  
  1236  func (s *environSuite) TestStartInstanceUnmetConstraints(c *gc.C) {
  1237  	env := s.bootstrap(c)
  1238  	s.newNode(c, "thenode1", "host1", nil)
  1239  	params := environs.StartInstanceParams{Constraints: constraints.MustParse("mem=8G")}
  1240  	_, err := testing.StartInstanceWithParams(env, "1", params, nil)
  1241  	c.Assert(err, gc.ErrorMatches, "cannot run instances:.* 409.*")
  1242  }
  1243  
  1244  func (s *environSuite) TestStartInstanceConstraints(c *gc.C) {
  1245  	env := s.bootstrap(c)
  1246  	s.newNode(c, "thenode1", "host1", nil)
  1247  	s.newNode(c, "thenode2", "host2", map[string]interface{}{"memory": 8192})
  1248  	params := environs.StartInstanceParams{Constraints: constraints.MustParse("mem=8G")}
  1249  	result, err := testing.StartInstanceWithParams(env, "1", params, nil)
  1250  	c.Assert(err, jc.ErrorIsNil)
  1251  	c.Assert(*result.Hardware.Mem, gc.Equals, uint64(8192))
  1252  }
  1253  
  1254  func (s *environSuite) TestGetAvailabilityZones(c *gc.C) {
  1255  	env := s.makeEnviron()
  1256  
  1257  	zones, err := env.AvailabilityZones()
  1258  	c.Assert(err, jc.Satisfies, errors.IsNotImplemented)
  1259  	c.Assert(zones, gc.IsNil)
  1260  
  1261  	s.testMAASObject.TestServer.AddZone("whatever", "andever")
  1262  	zones, err = env.AvailabilityZones()
  1263  	c.Assert(err, jc.ErrorIsNil)
  1264  	c.Assert(zones, gc.HasLen, 1)
  1265  	c.Assert(zones[0].Name(), gc.Equals, "whatever")
  1266  	c.Assert(zones[0].Available(), jc.IsTrue)
  1267  
  1268  	// A successful result is cached, currently for the lifetime
  1269  	// of the Environ. This will change if/when we have long-lived
  1270  	// Environs to cut down repeated IaaS requests.
  1271  	s.testMAASObject.TestServer.AddZone("somewhere", "outthere")
  1272  	zones, err = env.AvailabilityZones()
  1273  	c.Assert(err, jc.ErrorIsNil)
  1274  	c.Assert(zones, gc.HasLen, 1)
  1275  	c.Assert(zones[0].Name(), gc.Equals, "whatever")
  1276  }
  1277  
  1278  type mockAvailabilityZoneAllocations struct {
  1279  	group  []instance.Id // input param
  1280  	result []common.AvailabilityZoneInstances
  1281  	err    error
  1282  }
  1283  
  1284  func (m *mockAvailabilityZoneAllocations) AvailabilityZoneAllocations(
  1285  	e common.ZonedEnviron, group []instance.Id,
  1286  ) ([]common.AvailabilityZoneInstances, error) {
  1287  	m.group = group
  1288  	return m.result, m.err
  1289  }
  1290  
  1291  func (s *environSuite) newNode(c *gc.C, nodename, hostname string, attrs map[string]interface{}) {
  1292  	allAttrs := map[string]interface{}{
  1293  		"system_id":    nodename,
  1294  		"hostname":     hostname,
  1295  		"architecture": fmt.Sprintf("%s/generic", version.Current.Arch),
  1296  		"memory":       1024,
  1297  		"cpu_count":    1,
  1298  	}
  1299  	for k, v := range attrs {
  1300  		allAttrs[k] = v
  1301  	}
  1302  	data, err := json.Marshal(allAttrs)
  1303  	c.Assert(err, jc.ErrorIsNil)
  1304  	s.testMAASObject.TestServer.NewNode(string(data))
  1305  	lshwXML, err := s.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}})
  1306  	c.Assert(err, jc.ErrorIsNil)
  1307  	s.testMAASObject.TestServer.AddNodeDetails(nodename, lshwXML)
  1308  }
  1309  
  1310  func (s *environSuite) bootstrap(c *gc.C) environs.Environ {
  1311  	s.newNode(c, "node0", "bootstrap-host", nil)
  1312  	s.setupFakeTools(c)
  1313  	env := s.makeEnviron()
  1314  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
  1315  		Placement: "bootstrap-host",
  1316  	})
  1317  	c.Assert(err, jc.ErrorIsNil)
  1318  	return env
  1319  }
  1320  
  1321  func (s *environSuite) TestStartInstanceDistributionParams(c *gc.C) {
  1322  	env := s.bootstrap(c)
  1323  	var mock mockAvailabilityZoneAllocations
  1324  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
  1325  
  1326  	// no distribution group specified
  1327  	s.newNode(c, "node1", "host1", nil)
  1328  	testing.AssertStartInstance(c, env, "1")
  1329  	c.Assert(mock.group, gc.HasLen, 0)
  1330  
  1331  	// distribution group specified: ensure it's passed through to AvailabilityZone.
  1332  	s.newNode(c, "node2", "host2", nil)
  1333  	expectedInstances := []instance.Id{"i-0", "i-1"}
  1334  	params := environs.StartInstanceParams{
  1335  		DistributionGroup: func() ([]instance.Id, error) {
  1336  			return expectedInstances, nil
  1337  		},
  1338  	}
  1339  	_, err := testing.StartInstanceWithParams(env, "1", params, nil)
  1340  	c.Assert(err, jc.ErrorIsNil)
  1341  	c.Assert(mock.group, gc.DeepEquals, expectedInstances)
  1342  }
  1343  
  1344  func (s *environSuite) TestStartInstanceDistributionErrors(c *gc.C) {
  1345  	env := s.bootstrap(c)
  1346  	mock := mockAvailabilityZoneAllocations{
  1347  		err: errors.New("AvailabilityZoneAllocations failed"),
  1348  	}
  1349  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
  1350  	_, _, _, err := testing.StartInstance(env, "1")
  1351  	c.Assert(err, gc.ErrorMatches, "cannot get availability zone allocations: AvailabilityZoneAllocations failed")
  1352  
  1353  	mock.err = nil
  1354  	dgErr := errors.New("DistributionGroup failed")
  1355  	params := environs.StartInstanceParams{
  1356  		DistributionGroup: func() ([]instance.Id, error) {
  1357  			return nil, dgErr
  1358  		},
  1359  	}
  1360  	_, err = testing.StartInstanceWithParams(env, "1", params, nil)
  1361  	c.Assert(err, gc.ErrorMatches, "cannot get distribution group: DistributionGroup failed")
  1362  }
  1363  
  1364  func (s *environSuite) TestStartInstanceDistribution(c *gc.C) {
  1365  	env := s.bootstrap(c)
  1366  	s.testMAASObject.TestServer.AddZone("test-available", "description")
  1367  	s.newNode(c, "node1", "host1", map[string]interface{}{"zone": "test-available"})
  1368  	inst, _ := testing.AssertStartInstance(c, env, "1")
  1369  	c.Assert(inst.(*maasInstance).zone(), gc.Equals, "test-available")
  1370  }
  1371  
  1372  func (s *environSuite) TestStartInstanceDistributionAZNotImplemented(c *gc.C) {
  1373  	env := s.bootstrap(c)
  1374  
  1375  	mock := mockAvailabilityZoneAllocations{err: errors.NotImplementedf("availability zones")}
  1376  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
  1377  
  1378  	// Instance will be created without an availability zone specified.
  1379  	s.newNode(c, "node1", "host1", nil)
  1380  	inst, _ := testing.AssertStartInstance(c, env, "1")
  1381  	c.Assert(inst.(*maasInstance).zone(), gc.Equals, "")
  1382  }
  1383  
  1384  func (s *environSuite) TestStartInstanceDistributionFailover(c *gc.C) {
  1385  	mock := mockAvailabilityZoneAllocations{
  1386  		result: []common.AvailabilityZoneInstances{{
  1387  			ZoneName: "zone1",
  1388  		}, {
  1389  			ZoneName: "zonelord",
  1390  		}, {
  1391  			ZoneName: "zone2",
  1392  		}},
  1393  	}
  1394  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
  1395  	s.testMAASObject.TestServer.AddZone("zone1", "description")
  1396  	s.testMAASObject.TestServer.AddZone("zone2", "description")
  1397  	s.newNode(c, "node2", "host2", map[string]interface{}{"zone": "zone2"})
  1398  
  1399  	env := s.bootstrap(c)
  1400  	inst, _ := testing.AssertStartInstance(c, env, "1")
  1401  	c.Assert(inst.(*maasInstance).zone(), gc.Equals, "zone2")
  1402  	c.Assert(s.testMAASObject.TestServer.NodesOperations(), gc.DeepEquals, []string{
  1403  		// one acquire for the bootstrap, three for StartInstance (with zone failover)
  1404  		"acquire", "acquire", "acquire", "acquire",
  1405  	})
  1406  	c.Assert(s.testMAASObject.TestServer.NodesOperationRequestValues(), gc.DeepEquals, []url.Values{{
  1407  		"name":       []string{"bootstrap-host"},
  1408  		"agent_name": []string{exampleAgentName},
  1409  	}, {
  1410  		"zone":       []string{"zone1"},
  1411  		"agent_name": []string{exampleAgentName},
  1412  	}, {
  1413  		"zone":       []string{"zonelord"},
  1414  		"agent_name": []string{exampleAgentName},
  1415  	}, {
  1416  		"zone":       []string{"zone2"},
  1417  		"agent_name": []string{exampleAgentName},
  1418  	}})
  1419  }
  1420  
  1421  func (s *environSuite) TestStartInstanceDistributionOneAssigned(c *gc.C) {
  1422  	mock := mockAvailabilityZoneAllocations{
  1423  		result: []common.AvailabilityZoneInstances{{
  1424  			ZoneName: "zone1",
  1425  		}, {
  1426  			ZoneName: "zone2",
  1427  		}},
  1428  	}
  1429  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
  1430  	s.testMAASObject.TestServer.AddZone("zone1", "description")
  1431  	s.testMAASObject.TestServer.AddZone("zone2", "description")
  1432  	s.newNode(c, "node1", "host1", map[string]interface{}{"zone": "zone1"})
  1433  	s.newNode(c, "node2", "host2", map[string]interface{}{"zone": "zone2"})
  1434  
  1435  	env := s.bootstrap(c)
  1436  	testing.AssertStartInstance(c, env, "1")
  1437  	c.Assert(s.testMAASObject.TestServer.NodesOperations(), gc.DeepEquals, []string{
  1438  		// one acquire for the bootstrap, one for StartInstance.
  1439  		"acquire", "acquire",
  1440  	})
  1441  }