github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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/http"
    14  	"net/url"
    15  	"strings"
    16  	"text/template"
    17  
    18  	"github.com/juju/errors"
    19  	"github.com/juju/names"
    20  	jc "github.com/juju/testing/checkers"
    21  	"github.com/juju/utils"
    22  	"github.com/juju/utils/arch"
    23  	"github.com/juju/utils/set"
    24  	gc "gopkg.in/check.v1"
    25  	goyaml "gopkg.in/yaml.v1"
    26  	"launchpad.net/gomaasapi"
    27  
    28  	"github.com/juju/juju/cloudconfig/cloudinit"
    29  	"github.com/juju/juju/constraints"
    30  	"github.com/juju/juju/environs"
    31  	"github.com/juju/juju/environs/bootstrap"
    32  	"github.com/juju/juju/environs/config"
    33  	"github.com/juju/juju/environs/simplestreams"
    34  	envstorage "github.com/juju/juju/environs/storage"
    35  	envtesting "github.com/juju/juju/environs/testing"
    36  	envtools "github.com/juju/juju/environs/tools"
    37  	"github.com/juju/juju/instance"
    38  	"github.com/juju/juju/juju/testing"
    39  	"github.com/juju/juju/network"
    40  	"github.com/juju/juju/provider/common"
    41  	"github.com/juju/juju/storage"
    42  	coretesting "github.com/juju/juju/testing"
    43  	"github.com/juju/juju/version"
    44  )
    45  
    46  type environSuite struct {
    47  	providerSuite
    48  }
    49  
    50  const (
    51  	allocatedNode = `{"system_id": "test-allocated"}`
    52  )
    53  
    54  var _ = gc.Suite(&environSuite{})
    55  
    56  // getTestConfig creates a customized sample MAAS provider configuration.
    57  func getTestConfig(name, server, oauth, secret string) *config.Config {
    58  	ecfg, err := newConfig(map[string]interface{}{
    59  		"name":            name,
    60  		"maas-server":     server,
    61  		"maas-oauth":      oauth,
    62  		"admin-secret":    secret,
    63  		"authorized-keys": "I-am-not-a-real-key",
    64  	})
    65  	if err != nil {
    66  		panic(err)
    67  	}
    68  	return ecfg.Config
    69  }
    70  
    71  func (suite *environSuite) setupFakeTools(c *gc.C) {
    72  	storageDir := c.MkDir()
    73  	suite.PatchValue(&envtools.DefaultBaseURL, "file://"+storageDir+"/tools")
    74  	suite.UploadFakeToolsToDirectory(c, storageDir, "released", "released")
    75  }
    76  
    77  func (suite *environSuite) addNode(jsonText string) instance.Id {
    78  	node := suite.testMAASObject.TestServer.NewNode(jsonText)
    79  	resourceURI, _ := node.GetField("resource_uri")
    80  	return instance.Id(resourceURI)
    81  }
    82  
    83  func (suite *environSuite) TestInstancesReturnsInstances(c *gc.C) {
    84  	id := suite.addNode(allocatedNode)
    85  	instances, err := suite.makeEnviron().Instances([]instance.Id{id})
    86  
    87  	c.Check(err, jc.ErrorIsNil)
    88  	c.Assert(instances, gc.HasLen, 1)
    89  	c.Assert(instances[0].Id(), gc.Equals, id)
    90  }
    91  
    92  func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfEmptyParameter(c *gc.C) {
    93  	suite.addNode(allocatedNode)
    94  	instances, err := suite.makeEnviron().Instances([]instance.Id{})
    95  
    96  	c.Check(err, gc.Equals, environs.ErrNoInstances)
    97  	c.Check(instances, gc.IsNil)
    98  }
    99  
   100  func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNilParameter(c *gc.C) {
   101  	suite.addNode(allocatedNode)
   102  	instances, err := suite.makeEnviron().Instances(nil)
   103  
   104  	c.Check(err, gc.Equals, environs.ErrNoInstances)
   105  	c.Check(instances, gc.IsNil)
   106  }
   107  
   108  func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNoneFound(c *gc.C) {
   109  	instances, err := suite.makeEnviron().Instances([]instance.Id{"unknown"})
   110  	c.Check(err, gc.Equals, environs.ErrNoInstances)
   111  	c.Check(instances, gc.IsNil)
   112  }
   113  
   114  func (suite *environSuite) TestAllInstances(c *gc.C) {
   115  	id := suite.addNode(allocatedNode)
   116  	instances, err := suite.makeEnviron().AllInstances()
   117  
   118  	c.Check(err, jc.ErrorIsNil)
   119  	c.Assert(instances, gc.HasLen, 1)
   120  	c.Assert(instances[0].Id(), gc.Equals, id)
   121  }
   122  
   123  func (suite *environSuite) TestAllInstancesReturnsEmptySliceIfNoInstance(c *gc.C) {
   124  	instances, err := suite.makeEnviron().AllInstances()
   125  
   126  	c.Check(err, jc.ErrorIsNil)
   127  	c.Check(instances, gc.HasLen, 0)
   128  }
   129  
   130  func (suite *environSuite) TestInstancesReturnsErrorIfPartialInstances(c *gc.C) {
   131  	known := suite.addNode(allocatedNode)
   132  	suite.addNode(`{"system_id": "test2"}`)
   133  	unknown := instance.Id("unknown systemID")
   134  	instances, err := suite.makeEnviron().Instances([]instance.Id{known, unknown})
   135  
   136  	c.Check(err, gc.Equals, environs.ErrPartialInstances)
   137  	c.Assert(instances, gc.HasLen, 2)
   138  	c.Check(instances[0].Id(), gc.Equals, known)
   139  	c.Check(instances[1], gc.IsNil)
   140  }
   141  
   142  func (suite *environSuite) TestStorageReturnsStorage(c *gc.C) {
   143  	env := suite.makeEnviron()
   144  	stor := env.Storage()
   145  	c.Check(stor, gc.NotNil)
   146  	// The Storage object is really a maasStorage.
   147  	specificStorage := stor.(*maasStorage)
   148  	// Its environment pointer refers back to its environment.
   149  	c.Check(specificStorage.environUnlocked, gc.Equals, env)
   150  }
   151  
   152  func decodeUserData(userData string) ([]byte, error) {
   153  	data, err := base64.StdEncoding.DecodeString(userData)
   154  	if err != nil {
   155  		return []byte(""), err
   156  	}
   157  	return utils.Gunzip(data)
   158  }
   159  
   160  const lshwXMLTemplate = `
   161  <?xml version="1.0" standalone="yes" ?>
   162  <!-- generated by lshw-B.02.16 -->
   163  <list>
   164  <node id="node1" claimed="true" class="system" handle="DMI:0001">
   165   <description>Computer</description>
   166   <product>VirtualBox ()</product>
   167   <width units="bits">64</width>
   168    <node id="core" claimed="true" class="bus" handle="DMI:0008">
   169     <description>Motherboard</description>
   170      <node id="pci" claimed="true" class="bridge" handle="PCIBUS:0000:00">
   171       <description>Host bridge</description>{{$list := .}}{{range $mac, $ifi := $list}}
   172        <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">
   173         <description>Ethernet interface</description>
   174         <product>82540EM Gigabit Ethernet Controller</product>
   175         <logicalname>{{$ifi.InterfaceName}}</logicalname>
   176         <serial>{{$mac}}</serial>
   177        </node>{{end}}
   178      </node>
   179    </node>
   180  </node>
   181  </list>
   182  `
   183  
   184  func (suite *environSuite) generateHWTemplate(netMacs map[string]ifaceInfo) (string, error) {
   185  	tmpl, err := template.New("test").Parse(lshwXMLTemplate)
   186  	if err != nil {
   187  		return "", err
   188  	}
   189  	var buf bytes.Buffer
   190  	err = tmpl.Execute(&buf, netMacs)
   191  	if err != nil {
   192  		return "", err
   193  	}
   194  	return string(buf.Bytes()), nil
   195  }
   196  
   197  func (suite *environSuite) TestStartInstanceStartsInstance(c *gc.C) {
   198  	suite.setupFakeTools(c)
   199  	env := suite.makeEnviron()
   200  	// Create node 0: it will be used as the bootstrap node.
   201  	suite.testMAASObject.TestServer.NewNode(fmt.Sprintf(
   202  		`{"system_id": "node0", "hostname": "host0", "architecture": "%s/generic", "memory": 1024, "cpu_count": 1}`,
   203  		arch.HostArch()),
   204  	)
   205  	lshwXML, err := suite.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}})
   206  	c.Assert(err, jc.ErrorIsNil)
   207  	suite.testMAASObject.TestServer.AddNodeDetails("node0", lshwXML)
   208  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   209  	c.Assert(err, jc.ErrorIsNil)
   210  	// The bootstrap node has been acquired and started.
   211  	operations := suite.testMAASObject.TestServer.NodeOperations()
   212  	actions, found := operations["node0"]
   213  	c.Check(found, jc.IsTrue)
   214  	c.Check(actions, gc.DeepEquals, []string{"acquire", "start"})
   215  
   216  	// Test the instance id is correctly recorded for the bootstrap node.
   217  	// Check that StateServerInstances returns the id of the bootstrap machine.
   218  	instanceIds, err := env.StateServerInstances()
   219  	c.Assert(err, jc.ErrorIsNil)
   220  	c.Assert(instanceIds, gc.HasLen, 1)
   221  	insts, err := env.AllInstances()
   222  	c.Assert(err, jc.ErrorIsNil)
   223  	c.Assert(insts, gc.HasLen, 1)
   224  	c.Check(insts[0].Id(), gc.Equals, instanceIds[0])
   225  
   226  	// Create node 1: it will be used as instance number 1.
   227  	suite.testMAASObject.TestServer.NewNode(fmt.Sprintf(
   228  		`{"system_id": "node1", "hostname": "host1", "architecture": "%s/generic", "memory": 1024, "cpu_count": 1}`,
   229  		arch.HostArch()),
   230  	)
   231  	lshwXML, err = suite.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f1": {0, "eth0", false}})
   232  	c.Assert(err, jc.ErrorIsNil)
   233  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
   234  	instance, hc := testing.AssertStartInstance(c, env, "1")
   235  	c.Assert(err, jc.ErrorIsNil)
   236  	c.Check(instance, gc.NotNil)
   237  	c.Assert(hc, gc.NotNil)
   238  	c.Check(hc.String(), gc.Equals, fmt.Sprintf("arch=%s cpu-cores=1 mem=1024M", arch.HostArch()))
   239  
   240  	// The instance number 1 has been acquired and started.
   241  	actions, found = operations["node1"]
   242  	c.Assert(found, jc.IsTrue)
   243  	c.Check(actions, gc.DeepEquals, []string{"acquire", "start"})
   244  
   245  	// The value of the "user data" parameter used when starting the node
   246  	// contains the run cmd used to write the machine information onto
   247  	// the node's filesystem.
   248  	requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues()
   249  	nodeRequestValues, found := requestValues["node1"]
   250  	c.Assert(found, jc.IsTrue)
   251  	c.Assert(len(nodeRequestValues), gc.Equals, 2)
   252  	userData := nodeRequestValues[1].Get("user_data")
   253  	decodedUserData, err := decodeUserData(userData)
   254  	c.Assert(err, jc.ErrorIsNil)
   255  	info := machineInfo{"host1"}
   256  	cloudcfg, err := cloudinit.New("precise")
   257  	c.Assert(err, jc.ErrorIsNil)
   258  	cloudinitRunCmd, err := info.cloudinitRunCmd(cloudcfg)
   259  	c.Assert(err, jc.ErrorIsNil)
   260  	data, err := goyaml.Marshal(cloudinitRunCmd)
   261  	c.Assert(err, jc.ErrorIsNil)
   262  	c.Check(string(decodedUserData), jc.Contains, string(data))
   263  
   264  	// Trash the tools and try to start another instance.
   265  	suite.PatchValue(&envtools.DefaultBaseURL, "")
   266  	instance, _, _, err = testing.StartInstance(env, "2")
   267  	c.Check(instance, gc.IsNil)
   268  	c.Check(err, jc.Satisfies, errors.IsNotFound)
   269  }
   270  
   271  func uint64p(val uint64) *uint64 {
   272  	return &val
   273  }
   274  
   275  func stringp(val string) *string {
   276  	return &val
   277  }
   278  
   279  func (suite *environSuite) TestSelectNodeValidZone(c *gc.C) {
   280  	env := suite.makeEnviron()
   281  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0", "zone": "bar"}`)
   282  
   283  	snArgs := selectNodeArgs{
   284  		AvailabilityZones: []string{"foo", "bar"},
   285  		Constraints:       constraints.Value{},
   286  		IncludeNetworks:   nil,
   287  		ExcludeNetworks:   nil,
   288  	}
   289  
   290  	node, err := env.selectNode(snArgs)
   291  	c.Assert(err, jc.ErrorIsNil)
   292  	c.Assert(node, gc.NotNil)
   293  }
   294  
   295  func (suite *environSuite) TestSelectNodeInvalidZone(c *gc.C) {
   296  	env := suite.makeEnviron()
   297  
   298  	snArgs := selectNodeArgs{
   299  		AvailabilityZones: []string{"foo", "bar"},
   300  		Constraints:       constraints.Value{},
   301  		IncludeNetworks:   nil,
   302  		ExcludeNetworks:   nil,
   303  	}
   304  
   305  	_, err := env.selectNode(snArgs)
   306  	c.Assert(fmt.Sprintf("%s", err), gc.Equals, "cannot run instances: gomaasapi: got error back from server: 409 Conflict ()")
   307  }
   308  
   309  func (suite *environSuite) TestAcquireNode(c *gc.C) {
   310  	env := suite.makeEnviron()
   311  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`)
   312  
   313  	_, err := env.acquireNode("", "", constraints.Value{}, nil, nil, nil)
   314  
   315  	c.Check(err, jc.ErrorIsNil)
   316  	operations := suite.testMAASObject.TestServer.NodeOperations()
   317  	actions, found := operations["node0"]
   318  	c.Assert(found, jc.IsTrue)
   319  	c.Check(actions, gc.DeepEquals, []string{"acquire"})
   320  
   321  	// no "name" parameter should have been passed through
   322  	values := suite.testMAASObject.TestServer.NodeOperationRequestValues()["node0"][0]
   323  	_, found = values["name"]
   324  	c.Assert(found, jc.IsFalse)
   325  }
   326  
   327  func (suite *environSuite) TestAcquireNodeByName(c *gc.C) {
   328  	env := suite.makeEnviron()
   329  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`)
   330  
   331  	_, err := env.acquireNode("host0", "", constraints.Value{}, nil, nil, nil)
   332  
   333  	c.Check(err, jc.ErrorIsNil)
   334  	operations := suite.testMAASObject.TestServer.NodeOperations()
   335  	actions, found := operations["node0"]
   336  	c.Assert(found, jc.IsTrue)
   337  	c.Check(actions, gc.DeepEquals, []string{"acquire"})
   338  
   339  	// no "name" parameter should have been passed through
   340  	values := suite.testMAASObject.TestServer.NodeOperationRequestValues()["node0"][0]
   341  	nodeName := values.Get("name")
   342  	c.Assert(nodeName, gc.Equals, "host0")
   343  }
   344  
   345  func (suite *environSuite) TestAcquireNodeTakesConstraintsIntoAccount(c *gc.C) {
   346  	env := suite.makeEnviron()
   347  	suite.testMAASObject.TestServer.NewNode(
   348  		`{"system_id": "node0", "hostname": "host0", "architecture": "arm/generic", "memory": 2048}`,
   349  	)
   350  	constraints := constraints.Value{Arch: stringp("arm"), Mem: uint64p(1024)}
   351  
   352  	_, err := env.acquireNode("", "", constraints, nil, nil, nil)
   353  
   354  	c.Check(err, jc.ErrorIsNil)
   355  	requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues()
   356  	nodeRequestValues, found := requestValues["node0"]
   357  	c.Assert(found, jc.IsTrue)
   358  	c.Assert(nodeRequestValues[0].Get("arch"), gc.Equals, "arm")
   359  	c.Assert(nodeRequestValues[0].Get("mem"), gc.Equals, "1024")
   360  }
   361  
   362  func (suite *environSuite) TestParseTags(c *gc.C) {
   363  	tests := []struct {
   364  		about         string
   365  		input         []string
   366  		tags, notTags []string
   367  	}{{
   368  		about:   "nil input",
   369  		input:   nil,
   370  		tags:    []string{},
   371  		notTags: []string{},
   372  	}, {
   373  		about:   "empty input",
   374  		input:   []string{},
   375  		tags:    []string{},
   376  		notTags: []string{},
   377  	}, {
   378  		about:   "tag list with embedded spaces",
   379  		input:   []string{"   tag1  ", " tag2", " ^ not Tag 3  ", "  ", " ", "", "", " ^notTag4   "},
   380  		tags:    []string{"tag1", "tag2"},
   381  		notTags: []string{"notTag3", "notTag4"},
   382  	}, {
   383  		about:   "only positive tags",
   384  		input:   []string{"tag1", "tag2", "tag3"},
   385  		tags:    []string{"tag1", "tag2", "tag3"},
   386  		notTags: []string{},
   387  	}, {
   388  		about:   "only negative tags",
   389  		input:   []string{"^tag1", "^tag2", "^tag3"},
   390  		tags:    []string{},
   391  		notTags: []string{"tag1", "tag2", "tag3"},
   392  	}, {
   393  		about:   "both positive and negative tags",
   394  		input:   []string{"^tag1", "tag2", "^tag3", "tag4"},
   395  		tags:    []string{"tag2", "tag4"},
   396  		notTags: []string{"tag1", "tag3"},
   397  	}, {
   398  		about:   "single positive tag",
   399  		input:   []string{"tag1"},
   400  		tags:    []string{"tag1"},
   401  		notTags: []string{},
   402  	}, {
   403  		about:   "single negative tag",
   404  		input:   []string{"^tag1"},
   405  		tags:    []string{},
   406  		notTags: []string{"tag1"},
   407  	}}
   408  	for i, test := range tests {
   409  		c.Logf("test %d: %s", i, test.about)
   410  		tags, notTags := parseTags(test.input)
   411  		c.Check(tags, jc.DeepEquals, test.tags)
   412  		c.Check(notTags, jc.DeepEquals, test.notTags)
   413  	}
   414  }
   415  
   416  func (suite *environSuite) TestAcquireNodePassedAgentName(c *gc.C) {
   417  	env := suite.makeEnviron()
   418  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`)
   419  
   420  	_, err := env.acquireNode("", "", constraints.Value{}, nil, nil, nil)
   421  
   422  	c.Check(err, jc.ErrorIsNil)
   423  	requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues()
   424  	nodeRequestValues, found := requestValues["node0"]
   425  	c.Assert(found, jc.IsTrue)
   426  	c.Assert(nodeRequestValues[0].Get("agent_name"), gc.Equals, exampleAgentName)
   427  }
   428  
   429  func (suite *environSuite) TestAcquireNodePassesPositiveAndNegativeTags(c *gc.C) {
   430  	env := suite.makeEnviron()
   431  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0"}`)
   432  
   433  	_, err := env.acquireNode(
   434  		"", "",
   435  		constraints.Value{Tags: &[]string{"tag1", "^tag2", "tag3", "^tag4"}},
   436  		nil, nil, nil,
   437  	)
   438  
   439  	c.Check(err, jc.ErrorIsNil)
   440  	requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues()
   441  	nodeValues, found := requestValues["node0"]
   442  	c.Assert(found, jc.IsTrue)
   443  	c.Assert(nodeValues[0].Get("tags"), gc.Equals, "tag1,tag3")
   444  	c.Assert(nodeValues[0].Get("not_tags"), gc.Equals, "tag2,tag4")
   445  }
   446  
   447  func (suite *environSuite) TestAcquireNodeStorage(c *gc.C) {
   448  	for i, test := range []struct {
   449  		volumes  []volumeInfo
   450  		expected string
   451  	}{
   452  		{
   453  			nil,
   454  			"",
   455  		},
   456  		{
   457  			[]volumeInfo{{"volume-1", 1234, nil}},
   458  			"volume-1:1234",
   459  		},
   460  		{
   461  			[]volumeInfo{{"", 1234, []string{"tag1", "tag2"}}},
   462  			"1234(tag1,tag2)",
   463  		},
   464  		{
   465  			[]volumeInfo{{"volume-1", 1234, []string{"tag1", "tag2"}}},
   466  			"volume-1:1234(tag1,tag2)",
   467  		},
   468  		{
   469  			[]volumeInfo{
   470  				{"volume-1", 1234, []string{"tag1", "tag2"}},
   471  				{"volume-2", 4567, []string{"tag1", "tag3"}},
   472  			},
   473  			"volume-1:1234(tag1,tag2),volume-2:4567(tag1,tag3)",
   474  		},
   475  	} {
   476  		c.Logf("test %d", i)
   477  		env := suite.makeEnviron()
   478  		suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`)
   479  		_, err := env.acquireNode("", "", constraints.Value{}, nil, nil, test.volumes)
   480  		c.Check(err, jc.ErrorIsNil)
   481  		requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues()
   482  		nodeRequestValues, found := requestValues["node0"]
   483  		c.Check(found, jc.IsTrue)
   484  		c.Check(nodeRequestValues[0].Get("storage"), gc.Equals, test.expected)
   485  		suite.testMAASObject.TestServer.Clear()
   486  	}
   487  }
   488  
   489  var testValues = []struct {
   490  	constraints    constraints.Value
   491  	expectedResult url.Values
   492  }{
   493  	{constraints.Value{Arch: stringp("arm")}, url.Values{"arch": {"arm"}}},
   494  	{constraints.Value{CpuCores: uint64p(4)}, url.Values{"cpu_count": {"4"}}},
   495  	{constraints.Value{Mem: uint64p(1024)}, url.Values{"mem": {"1024"}}},
   496  	{constraints.Value{Tags: &[]string{"tag1", "tag2", "^tag3", "^tag4"}}, url.Values{"tags": {"tag1,tag2"}, "not_tags": {"tag3,tag4"}}},
   497  
   498  	// CpuPower is ignored.
   499  	{constraints.Value{CpuPower: uint64p(1024)}, url.Values{}},
   500  
   501  	// RootDisk is ignored.
   502  	{constraints.Value{RootDisk: uint64p(8192)}, url.Values{}},
   503  	{constraints.Value{Tags: &[]string{"foo", "bar"}}, url.Values{"tags": {"foo,bar"}}},
   504  	{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"}}},
   505  }
   506  
   507  func (*environSuite) TestConvertConstraints(c *gc.C) {
   508  	for _, test := range testValues {
   509  		c.Check(convertConstraints(test.constraints), gc.DeepEquals, test.expectedResult)
   510  	}
   511  }
   512  
   513  var testNetworkValues = []struct {
   514  	includeNetworks []string
   515  	excludeNetworks []string
   516  	expectedResult  url.Values
   517  }{
   518  	{
   519  		nil,
   520  		nil,
   521  		url.Values{},
   522  	},
   523  	{
   524  		[]string{"included_net_1"},
   525  		nil,
   526  		url.Values{"networks": {"included_net_1"}},
   527  	},
   528  	{
   529  		nil,
   530  		[]string{"excluded_net_1"},
   531  		url.Values{"not_networks": {"excluded_net_1"}},
   532  	},
   533  	{
   534  		[]string{"included_net_1", "included_net_2"},
   535  		[]string{"excluded_net_1", "excluded_net_2"},
   536  		url.Values{
   537  			"networks":     {"included_net_1", "included_net_2"},
   538  			"not_networks": {"excluded_net_1", "excluded_net_2"},
   539  		},
   540  	},
   541  }
   542  
   543  func (*environSuite) TestConvertNetworks(c *gc.C) {
   544  	for _, test := range testNetworkValues {
   545  		var vals = url.Values{}
   546  		addNetworks(vals, test.includeNetworks, test.excludeNetworks)
   547  		c.Check(vals, gc.DeepEquals, test.expectedResult)
   548  	}
   549  }
   550  
   551  func (suite *environSuite) getInstance(systemId string) *maasInstance {
   552  	input := fmt.Sprintf(`{"system_id": %q}`, systemId)
   553  	node := suite.testMAASObject.TestServer.NewNode(input)
   554  	return &maasInstance{&node}
   555  }
   556  
   557  func (suite *environSuite) getNetwork(name string, id int, vlanTag int) *gomaasapi.MAASObject {
   558  	var vlan string
   559  	if vlanTag == 0 {
   560  		vlan = "null"
   561  	} else {
   562  		vlan = fmt.Sprintf("%d", vlanTag)
   563  	}
   564  	var input string
   565  	input = fmt.Sprintf(`{"name": %q, "ip":"192.168.%d.1", "netmask": "255.255.255.0",`+
   566  		`"vlan_tag": %s, "description": "%s_%d_%d" }`, name, id, vlan, name, id, vlanTag)
   567  	network := suite.testMAASObject.TestServer.NewNetwork(input)
   568  	return &network
   569  }
   570  
   571  func (suite *environSuite) TestStopInstancesReturnsIfParameterEmpty(c *gc.C) {
   572  	suite.getInstance("test1")
   573  
   574  	err := suite.makeEnviron().StopInstances()
   575  	c.Check(err, jc.ErrorIsNil)
   576  	operations := suite.testMAASObject.TestServer.NodeOperations()
   577  	c.Check(operations, gc.DeepEquals, map[string][]string{})
   578  }
   579  
   580  func (suite *environSuite) TestStopInstancesStopsAndReleasesInstances(c *gc.C) {
   581  	suite.getInstance("test1")
   582  	suite.getInstance("test2")
   583  	suite.getInstance("test3")
   584  	// mark test1 and test2 as being allocated, but not test3.
   585  	// The release operation will ignore test3.
   586  	suite.testMAASObject.TestServer.OwnedNodes()["test1"] = true
   587  	suite.testMAASObject.TestServer.OwnedNodes()["test2"] = true
   588  
   589  	err := suite.makeEnviron().StopInstances("test1", "test2", "test3")
   590  	c.Check(err, jc.ErrorIsNil)
   591  	operations := suite.testMAASObject.TestServer.NodesOperations()
   592  	c.Check(operations, gc.DeepEquals, []string{"release"})
   593  	c.Assert(suite.testMAASObject.TestServer.OwnedNodes()["test1"], jc.IsFalse)
   594  	c.Assert(suite.testMAASObject.TestServer.OwnedNodes()["test2"], jc.IsFalse)
   595  }
   596  
   597  func (suite *environSuite) TestStopInstancesIgnoresConflict(c *gc.C) {
   598  	releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error {
   599  		return gomaasapi.ServerError{StatusCode: 409}
   600  	}
   601  	suite.PatchValue(&ReleaseNodes, releaseNodes)
   602  	env := suite.makeEnviron()
   603  	err := env.StopInstances("test1")
   604  	c.Assert(err, jc.ErrorIsNil)
   605  }
   606  
   607  func (suite *environSuite) TestStopInstancesIgnoresMissingNodeAndRecurses(c *gc.C) {
   608  	attemptedNodes := [][]string{}
   609  	releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error {
   610  		attemptedNodes = append(attemptedNodes, ids["nodes"])
   611  		return gomaasapi.ServerError{StatusCode: 404}
   612  	}
   613  	suite.PatchValue(&ReleaseNodes, releaseNodes)
   614  	env := suite.makeEnviron()
   615  	err := env.StopInstances("test1", "test2")
   616  	c.Assert(err, jc.ErrorIsNil)
   617  
   618  	expectedNodes := [][]string{{"test1", "test2"}, {"test1"}, {"test2"}}
   619  	c.Assert(attemptedNodes, gc.DeepEquals, expectedNodes)
   620  }
   621  
   622  func (suite *environSuite) TestStopInstancesReturnsUnexpectedMAASError(c *gc.C) {
   623  	releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error {
   624  		return gomaasapi.ServerError{StatusCode: 405}
   625  	}
   626  	suite.PatchValue(&ReleaseNodes, releaseNodes)
   627  	env := suite.makeEnviron()
   628  	err := env.StopInstances("test1")
   629  	c.Assert(err, gc.NotNil)
   630  	maasErr, ok := errors.Cause(err).(gomaasapi.ServerError)
   631  	c.Assert(ok, jc.IsTrue)
   632  	c.Assert(maasErr.StatusCode, gc.Equals, 405)
   633  }
   634  
   635  func (suite *environSuite) TestStopInstancesReturnsUnexpectedError(c *gc.C) {
   636  	releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error {
   637  		return environs.ErrNoInstances
   638  	}
   639  	suite.PatchValue(&ReleaseNodes, releaseNodes)
   640  	env := suite.makeEnviron()
   641  	err := env.StopInstances("test1")
   642  	c.Assert(err, gc.NotNil)
   643  	c.Assert(errors.Cause(err), gc.Equals, environs.ErrNoInstances)
   644  }
   645  
   646  func (suite *environSuite) TestStateServerInstances(c *gc.C) {
   647  	env := suite.makeEnviron()
   648  	_, err := env.StateServerInstances()
   649  	c.Assert(err, gc.Equals, environs.ErrNotBootstrapped)
   650  
   651  	tests := [][]instance.Id{{}, {"inst-0"}, {"inst-0", "inst-1"}}
   652  	for _, expected := range tests {
   653  		err := common.SaveState(env.Storage(), &common.BootstrapState{
   654  			StateInstances: expected,
   655  		})
   656  		c.Assert(err, jc.ErrorIsNil)
   657  		stateServerInstances, err := env.StateServerInstances()
   658  		c.Assert(err, jc.ErrorIsNil)
   659  		c.Assert(stateServerInstances, jc.SameContents, expected)
   660  	}
   661  }
   662  
   663  func (suite *environSuite) TestStateServerInstancesFailsIfNoStateInstances(c *gc.C) {
   664  	env := suite.makeEnviron()
   665  	_, err := env.StateServerInstances()
   666  	c.Check(err, gc.Equals, environs.ErrNotBootstrapped)
   667  }
   668  
   669  func (suite *environSuite) TestDestroy(c *gc.C) {
   670  	env := suite.makeEnviron()
   671  	suite.getInstance("test1")
   672  	suite.testMAASObject.TestServer.OwnedNodes()["test1"] = true // simulate acquire
   673  	data := makeRandomBytes(10)
   674  	suite.testMAASObject.TestServer.NewFile("filename", data)
   675  	stor := env.Storage()
   676  
   677  	err := env.Destroy()
   678  	c.Check(err, jc.ErrorIsNil)
   679  
   680  	// Instances have been stopped.
   681  	operations := suite.testMAASObject.TestServer.NodesOperations()
   682  	c.Check(operations, gc.DeepEquals, []string{"release"})
   683  	c.Check(suite.testMAASObject.TestServer.OwnedNodes()["test1"], jc.IsFalse)
   684  	// Files have been cleaned up.
   685  	listing, err := envstorage.List(stor, "")
   686  	c.Assert(err, jc.ErrorIsNil)
   687  	c.Check(listing, gc.DeepEquals, []string{})
   688  }
   689  
   690  func (suite *environSuite) TestBootstrapSucceeds(c *gc.C) {
   691  	suite.setupFakeTools(c)
   692  	env := suite.makeEnviron()
   693  	suite.testMAASObject.TestServer.NewNode(fmt.Sprintf(
   694  		`{"system_id": "thenode", "hostname": "host", "architecture": "%s/generic", "memory": 256, "cpu_count": 8}`,
   695  		arch.HostArch()),
   696  	)
   697  	lshwXML, err := suite.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}})
   698  	c.Assert(err, jc.ErrorIsNil)
   699  	suite.testMAASObject.TestServer.AddNodeDetails("thenode", lshwXML)
   700  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   701  	c.Assert(err, jc.ErrorIsNil)
   702  }
   703  
   704  func (suite *environSuite) TestBootstrapNodeNotDeployed(c *gc.C) {
   705  	suite.setupFakeTools(c)
   706  	env := suite.makeEnviron()
   707  	suite.testMAASObject.TestServer.NewNode(fmt.Sprintf(
   708  		`{"system_id": "thenode", "hostname": "host", "architecture": "%s/generic", "memory": 256, "cpu_count": 8}`,
   709  		arch.HostArch()),
   710  	)
   711  	lshwXML, err := suite.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}})
   712  	c.Assert(err, jc.ErrorIsNil)
   713  	suite.testMAASObject.TestServer.AddNodeDetails("thenode", lshwXML)
   714  	// Ensure node will not be reported as deployed by changing its status.
   715  	suite.testMAASObject.TestServer.ChangeNode("thenode", "status", "4")
   716  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   717  	c.Assert(err, gc.ErrorMatches, "bootstrap instance started but did not change to Deployed state.*")
   718  }
   719  
   720  func (suite *environSuite) TestBootstrapNodeFailedDeploy(c *gc.C) {
   721  	suite.setupFakeTools(c)
   722  	env := suite.makeEnviron()
   723  	suite.testMAASObject.TestServer.NewNode(fmt.Sprintf(
   724  		`{"system_id": "thenode", "hostname": "host", "architecture": "%s/generic", "memory": 256, "cpu_count": 8}`,
   725  		arch.HostArch()),
   726  	)
   727  	lshwXML, err := suite.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}})
   728  	c.Assert(err, jc.ErrorIsNil)
   729  	suite.testMAASObject.TestServer.AddNodeDetails("thenode", lshwXML)
   730  	// Set the node status to "Failed deployment"
   731  	suite.testMAASObject.TestServer.ChangeNode("thenode", "status", "11")
   732  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   733  	c.Assert(err, gc.ErrorMatches, "bootstrap instance started but did not change to Deployed state. instance \"/api/.*/nodes/thenode/\" failed to deploy")
   734  }
   735  
   736  func (suite *environSuite) TestBootstrapFailsIfNoTools(c *gc.C) {
   737  	env := suite.makeEnviron()
   738  	// Disable auto-uploading by setting the agent version.
   739  	cfg, err := env.Config().Apply(map[string]interface{}{
   740  		"agent-version": version.Current.Number.String(),
   741  	})
   742  	c.Assert(err, jc.ErrorIsNil)
   743  	err = env.SetConfig(cfg)
   744  	c.Assert(err, jc.ErrorIsNil)
   745  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   746  	c.Check(err, gc.ErrorMatches, "Juju cannot bootstrap because no tools are available for your environment(.|\n)*")
   747  }
   748  
   749  func (suite *environSuite) TestBootstrapFailsIfNoNodes(c *gc.C) {
   750  	suite.setupFakeTools(c)
   751  	env := suite.makeEnviron()
   752  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
   753  	// Since there are no nodes, the attempt to allocate one returns a
   754  	// 409: Conflict.
   755  	c.Check(err, gc.ErrorMatches, ".*409.*")
   756  }
   757  
   758  func assertSourceContents(c *gc.C, source simplestreams.DataSource, filename string, content []byte) {
   759  	rc, _, err := source.Fetch(filename)
   760  	c.Assert(err, jc.ErrorIsNil)
   761  	defer rc.Close()
   762  	retrieved, err := ioutil.ReadAll(rc)
   763  	c.Assert(err, jc.ErrorIsNil)
   764  	c.Assert(retrieved, gc.DeepEquals, content)
   765  }
   766  
   767  func (suite *environSuite) TestGetToolsMetadataSources(c *gc.C) {
   768  	env := suite.makeEnviron()
   769  	// Add a dummy file to storage so we can use that to check the
   770  	// obtained source later.
   771  	data := makeRandomBytes(10)
   772  	stor := NewStorage(env)
   773  	err := stor.Put("tools/filename", bytes.NewBuffer([]byte(data)), int64(len(data)))
   774  	c.Assert(err, jc.ErrorIsNil)
   775  	sources, err := envtools.GetMetadataSources(env)
   776  	c.Assert(err, jc.ErrorIsNil)
   777  	c.Assert(sources, gc.HasLen, 0)
   778  }
   779  
   780  func (suite *environSuite) TestSupportedArchitectures(c *gc.C) {
   781  	suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "precise"}`)
   782  	suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "trusty"}`)
   783  	suite.testMAASObject.TestServer.AddBootImage("uuid-1", `{"architecture": "amd64", "release": "precise"}`)
   784  	suite.testMAASObject.TestServer.AddBootImage("uuid-1", `{"architecture": "ppc64el", "release": "trusty"}`)
   785  	env := suite.makeEnviron()
   786  	a, err := env.SupportedArchitectures()
   787  	c.Assert(err, jc.ErrorIsNil)
   788  	c.Assert(a, jc.SameContents, []string{"amd64", "ppc64el"})
   789  }
   790  
   791  func (suite *environSuite) TestSupportedArchitecturesFallback(c *gc.C) {
   792  	// If we cannot query boot-images (e.g. MAAS server version 1.4),
   793  	// then Juju will fall over to listing all the available nodes.
   794  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "architecture": "amd64/generic"}`)
   795  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node1", "architecture": "armhf"}`)
   796  	env := suite.makeEnviron()
   797  	a, err := env.SupportedArchitectures()
   798  	c.Assert(err, jc.ErrorIsNil)
   799  	c.Assert(a, jc.SameContents, []string{"amd64", "armhf"})
   800  }
   801  
   802  func (suite *environSuite) TestConstraintsValidator(c *gc.C) {
   803  	suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "trusty"}`)
   804  	env := suite.makeEnviron()
   805  	validator, err := env.ConstraintsValidator()
   806  	c.Assert(err, jc.ErrorIsNil)
   807  	cons := constraints.MustParse("arch=amd64 cpu-power=10 instance-type=foo")
   808  	unsupported, err := validator.Validate(cons)
   809  	c.Assert(err, jc.ErrorIsNil)
   810  	c.Assert(unsupported, jc.SameContents, []string{"cpu-power", "instance-type"})
   811  }
   812  
   813  func (suite *environSuite) TestConstraintsValidatorVocab(c *gc.C) {
   814  	suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "trusty"}`)
   815  	suite.testMAASObject.TestServer.AddBootImage("uuid-1", `{"architecture": "armhf", "release": "precise"}`)
   816  	env := suite.makeEnviron()
   817  	validator, err := env.ConstraintsValidator()
   818  	c.Assert(err, jc.ErrorIsNil)
   819  	cons := constraints.MustParse("arch=ppc64el")
   820  	_, err = validator.Validate(cons)
   821  	c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=ppc64el\nvalid values are: \\[amd64 armhf\\]")
   822  }
   823  
   824  func (suite *environSuite) TestGetNetworkMACs(c *gc.C) {
   825  	env := suite.makeEnviron()
   826  
   827  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node_1"}`)
   828  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node_2"}`)
   829  	suite.testMAASObject.TestServer.NewNetwork(
   830  		`{"name": "net_1","ip":"0.1.2.0","netmask":"255.255.255.0"}`,
   831  	)
   832  	suite.testMAASObject.TestServer.NewNetwork(
   833  		`{"name": "net_2","ip":"0.2.2.0","netmask":"255.255.255.0"}`,
   834  	)
   835  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_2", "net_2", "aa:bb:cc:dd:ee:22")
   836  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_1", "net_1", "aa:bb:cc:dd:ee:11")
   837  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_2", "net_1", "aa:bb:cc:dd:ee:21")
   838  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_1", "net_2", "aa:bb:cc:dd:ee:12")
   839  
   840  	networks, err := env.getNetworkMACs("net_1")
   841  	c.Assert(err, jc.ErrorIsNil)
   842  	c.Check(networks, jc.SameContents, []string{"aa:bb:cc:dd:ee:11", "aa:bb:cc:dd:ee:21"})
   843  
   844  	networks, err = env.getNetworkMACs("net_2")
   845  	c.Assert(err, jc.ErrorIsNil)
   846  	c.Check(networks, jc.SameContents, []string{"aa:bb:cc:dd:ee:12", "aa:bb:cc:dd:ee:22"})
   847  
   848  	networks, err = env.getNetworkMACs("net_3")
   849  	c.Check(networks, gc.HasLen, 0)
   850  	c.Assert(err, jc.ErrorIsNil)
   851  }
   852  
   853  func (suite *environSuite) TestGetInstanceNetworks(c *gc.C) {
   854  	suite.getNetwork("test_network", 123, 321)
   855  	testInstance := suite.getInstance("instance_for_network")
   856  	suite.testMAASObject.TestServer.ConnectNodeToNetwork("instance_for_network", "test_network")
   857  	networks, err := suite.makeEnviron().getInstanceNetworks(testInstance)
   858  	c.Assert(err, jc.ErrorIsNil)
   859  	c.Check(networks, gc.DeepEquals, []networkDetails{
   860  		{Name: "test_network", IP: "192.168.123.1", Mask: "255.255.255.0", VLANTag: 321,
   861  			Description: "test_network_123_321"},
   862  	})
   863  }
   864  
   865  // A typical lshw XML dump with lots of things left out.
   866  const lshwXMLTestExtractInterfaces = `
   867  <?xml version="1.0" standalone="yes" ?>
   868  <!-- generated by lshw-B.02.16 -->
   869  <list>
   870  <node id="machine" claimed="true" class="system" handle="DMI:0001">
   871   <description>Notebook</description>
   872   <product>MyMachine</product>
   873   <version>1.0</version>
   874   <width units="bits">64</width>
   875    <node id="core" claimed="true" class="bus" handle="DMI:0002">
   876     <description>Motherboard</description>
   877      <node id="cpu" claimed="true" class="processor" handle="DMI:0004">
   878       <description>CPU</description>
   879        <node id="pci:2" claimed="true" class="bridge" handle="PCIBUS:0000:03">
   880          <node id="network:0" claimed="true" disabled="true" class="network" handle="PCI:0000:03:00.0">
   881           <logicalname>wlan0</logicalname>
   882           <serial>aa:bb:cc:dd:ee:ff</serial>
   883          </node>
   884          <node id="network:1" claimed="true" class="network" handle="PCI:0000:04:00.0">
   885           <logicalname>eth0</logicalname>
   886           <serial>aa:bb:cc:dd:ee:f1</serial>
   887          </node>
   888        </node>
   889      </node>
   890    </node>
   891    <node id="network:2" claimed="true" class="network" handle="">
   892     <logicalname>vnet1</logicalname>
   893     <serial>aa:bb:cc:dd:ee:f2</serial>
   894    </node>
   895  </node>
   896  </list>
   897  `
   898  
   899  // An lshw XML dump with implicit network interface indexes.
   900  const lshwXMLTestExtractInterfacesImplicitIndexes = `
   901  <?xml version="1.0" standalone="yes" ?>
   902  <!-- generated by lshw-B.02.16 -->
   903  <list>
   904  <node id="machine" claimed="true" class="system" handle="DMI:0001">
   905   <description>Notebook</description>
   906   <product>MyMachine</product>
   907   <version>1.0</version>
   908   <width units="bits">64</width>
   909    <node id="core" claimed="true" class="bus" handle="DMI:0002">
   910     <description>Motherboard</description>
   911      <node id="cpu" claimed="true" class="processor" handle="DMI:0004">
   912       <description>CPU</description>
   913        <node id="pci:2" claimed="true" class="bridge" handle="PCIBUS:0000:03">
   914          <node id="network" claimed="true" disabled="true" class="network" handle="PCI:0000:03:00.0">
   915           <logicalname>wlan0</logicalname>
   916           <serial>aa:bb:cc:dd:ee:ff</serial>
   917          </node>
   918          <node id="network" claimed="true" class="network" handle="PCI:0000:04:00.0">
   919           <logicalname>eth0</logicalname>
   920           <serial>aa:bb:cc:dd:ee:f1</serial>
   921          </node>
   922        </node>
   923      </node>
   924    </node>
   925    <node id="network" claimed="true" class="network" handle="">
   926     <logicalname>vnet1</logicalname>
   927     <serial>aa:bb:cc:dd:ee:f2</serial>
   928    </node>
   929  </node>
   930  </list>
   931  `
   932  
   933  func (suite *environSuite) TestExtractInterfaces(c *gc.C) {
   934  	rawData := []string{
   935  		lshwXMLTestExtractInterfaces,
   936  		lshwXMLTestExtractInterfacesImplicitIndexes,
   937  	}
   938  	for _, data := range rawData {
   939  		inst := suite.getInstance("testInstance")
   940  		interfaces, primaryIface, err := extractInterfaces(inst, []byte(data))
   941  		c.Assert(err, jc.ErrorIsNil)
   942  		c.Check(primaryIface, gc.Equals, "eth0")
   943  		c.Check(interfaces, jc.DeepEquals, map[string]ifaceInfo{
   944  			"aa:bb:cc:dd:ee:ff": {0, "wlan0", true},
   945  			"aa:bb:cc:dd:ee:f1": {1, "eth0", false},
   946  			"aa:bb:cc:dd:ee:f2": {2, "vnet1", false},
   947  		})
   948  	}
   949  }
   950  
   951  func (suite *environSuite) TestGetInstanceNetworkInterfaces(c *gc.C) {
   952  	inst := suite.getInstance("testInstance")
   953  	templateInterfaces := map[string]ifaceInfo{
   954  		"aa:bb:cc:dd:ee:ff": {0, "wlan0", true},
   955  		"aa:bb:cc:dd:ee:f1": {1, "eth0", true},
   956  		"aa:bb:cc:dd:ee:f2": {2, "vnet1", false},
   957  	}
   958  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
   959  	c.Assert(err, jc.ErrorIsNil)
   960  
   961  	suite.testMAASObject.TestServer.AddNodeDetails("testInstance", lshwXML)
   962  	env := suite.makeEnviron()
   963  	interfaces, primaryIface, err := env.getInstanceNetworkInterfaces(inst)
   964  	c.Assert(err, jc.ErrorIsNil)
   965  	// Both wlan0 and eth0 are disabled in lshw output.
   966  	c.Check(primaryIface, gc.Equals, "vnet1")
   967  	c.Check(interfaces, jc.DeepEquals, templateInterfaces)
   968  }
   969  
   970  func (suite *environSuite) TestSetupNetworks(c *gc.C) {
   971  	testInstance := suite.getInstance("node1")
   972  	templateInterfaces := map[string]ifaceInfo{
   973  		"aa:bb:cc:dd:ee:ff": {0, "wlan0", true},
   974  		"aa:bb:cc:dd:ee:f1": {1, "eth0", true},
   975  		"aa:bb:cc:dd:ee:f2": {2, "vnet1", false},
   976  	}
   977  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
   978  	c.Assert(err, jc.ErrorIsNil)
   979  
   980  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
   981  	suite.getNetwork("LAN", 2, 42)
   982  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1")
   983  	suite.getNetwork("Virt", 3, 0)
   984  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f2")
   985  	suite.getNetwork("WLAN", 1, 0)
   986  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "WLAN", "aa:bb:cc:dd:ee:ff")
   987  	networkInfo, primaryIface, err := suite.makeEnviron().setupNetworks(
   988  		testInstance,
   989  		set.NewStrings("WLAN"), // Disable WLAN only.
   990  	)
   991  	c.Assert(err, jc.ErrorIsNil)
   992  
   993  	// Note: order of networks is based on lshwXML
   994  	c.Check(primaryIface, gc.Equals, "vnet1")
   995  	// Unfortunately, because network.InterfaceInfo is unhashable
   996  	// (contains a map) we can't use jc.SameContents here.
   997  	c.Check(networkInfo, gc.HasLen, 3)
   998  	for _, info := range networkInfo {
   999  		switch info.DeviceIndex {
  1000  		case 0:
  1001  			c.Check(info, jc.DeepEquals, network.InterfaceInfo{
  1002  				MACAddress:    "aa:bb:cc:dd:ee:ff",
  1003  				CIDR:          "192.168.1.1/24",
  1004  				NetworkName:   "WLAN",
  1005  				ProviderId:    "WLAN",
  1006  				VLANTag:       0,
  1007  				DeviceIndex:   0,
  1008  				InterfaceName: "wlan0",
  1009  				Disabled:      true, // from networksToDisable("WLAN")
  1010  			})
  1011  		case 1:
  1012  			c.Check(info, jc.DeepEquals, network.InterfaceInfo{
  1013  				DeviceIndex:   1,
  1014  				MACAddress:    "aa:bb:cc:dd:ee:f1",
  1015  				CIDR:          "192.168.2.1/24",
  1016  				NetworkName:   "LAN",
  1017  				ProviderId:    "LAN",
  1018  				VLANTag:       42,
  1019  				InterfaceName: "eth0",
  1020  				Disabled:      true, // from networksToDisable("WLAN")
  1021  			})
  1022  		case 2:
  1023  			c.Check(info, jc.DeepEquals, network.InterfaceInfo{
  1024  				MACAddress:    "aa:bb:cc:dd:ee:f2",
  1025  				CIDR:          "192.168.3.1/24",
  1026  				NetworkName:   "Virt",
  1027  				ProviderId:    "Virt",
  1028  				VLANTag:       0,
  1029  				DeviceIndex:   2,
  1030  				InterfaceName: "vnet1",
  1031  				Disabled:      false,
  1032  			})
  1033  		}
  1034  	}
  1035  }
  1036  
  1037  // The same test, but now "Virt" network does not have matched MAC address
  1038  func (suite *environSuite) TestSetupNetworksPartialMatch(c *gc.C) {
  1039  	testInstance := suite.getInstance("node1")
  1040  	templateInterfaces := map[string]ifaceInfo{
  1041  		"aa:bb:cc:dd:ee:ff": {0, "wlan0", true},
  1042  		"aa:bb:cc:dd:ee:f1": {1, "eth0", false},
  1043  		"aa:bb:cc:dd:ee:f2": {2, "vnet1", false},
  1044  	}
  1045  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
  1046  	c.Assert(err, jc.ErrorIsNil)
  1047  
  1048  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
  1049  	suite.getNetwork("LAN", 2, 42)
  1050  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1")
  1051  	suite.getNetwork("Virt", 3, 0)
  1052  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f3")
  1053  	networkInfo, primaryIface, err := suite.makeEnviron().setupNetworks(
  1054  		testInstance,
  1055  		set.NewStrings(), // All enabled.
  1056  	)
  1057  	c.Assert(err, jc.ErrorIsNil)
  1058  
  1059  	// Note: order of networks is based on lshwXML
  1060  	c.Check(primaryIface, gc.Equals, "eth0")
  1061  	c.Check(networkInfo, jc.DeepEquals, []network.InterfaceInfo{{
  1062  		MACAddress:    "aa:bb:cc:dd:ee:f1",
  1063  		CIDR:          "192.168.2.1/24",
  1064  		NetworkName:   "LAN",
  1065  		ProviderId:    "LAN",
  1066  		VLANTag:       42,
  1067  		DeviceIndex:   1,
  1068  		InterfaceName: "eth0",
  1069  		Disabled:      false,
  1070  	}})
  1071  }
  1072  
  1073  // The same test, but now no networks have matched MAC
  1074  func (suite *environSuite) TestSetupNetworksNoMatch(c *gc.C) {
  1075  	testInstance := suite.getInstance("node1")
  1076  	templateInterfaces := map[string]ifaceInfo{
  1077  		"aa:bb:cc:dd:ee:ff": {0, "wlan0", true},
  1078  		"aa:bb:cc:dd:ee:f1": {1, "eth0", false},
  1079  		"aa:bb:cc:dd:ee:f2": {2, "vnet1", false},
  1080  	}
  1081  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
  1082  	c.Assert(err, jc.ErrorIsNil)
  1083  
  1084  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
  1085  	suite.getNetwork("Virt", 3, 0)
  1086  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f3")
  1087  	networkInfo, primaryIface, err := suite.makeEnviron().setupNetworks(
  1088  		testInstance,
  1089  		set.NewStrings(), // All enabled.
  1090  	)
  1091  	c.Assert(err, jc.ErrorIsNil)
  1092  
  1093  	// Note: order of networks is based on lshwXML
  1094  	c.Check(primaryIface, gc.Equals, "eth0")
  1095  	c.Check(networkInfo, gc.HasLen, 0)
  1096  }
  1097  
  1098  func (suite *environSuite) TestSupportsNetworking(c *gc.C) {
  1099  	env := suite.makeEnviron()
  1100  	_, supported := environs.SupportsNetworking(env)
  1101  	c.Assert(supported, jc.IsTrue)
  1102  }
  1103  
  1104  func (suite *environSuite) TestSupportsAddressAllocation(c *gc.C) {
  1105  	env := suite.makeEnviron()
  1106  	supported, err := env.SupportsAddressAllocation("")
  1107  	c.Assert(err, jc.ErrorIsNil)
  1108  	c.Assert(supported, jc.IsTrue)
  1109  }
  1110  
  1111  func (suite *environSuite) createSubnets(c *gc.C, duplicates bool) instance.Instance {
  1112  	testInstance := suite.getInstance("node1")
  1113  	templateInterfaces := map[string]ifaceInfo{
  1114  		"aa:bb:cc:dd:ee:ff": {0, "wlan0", true},
  1115  		"aa:bb:cc:dd:ee:f1": {1, "eth0", false},
  1116  		"aa:bb:cc:dd:ee:f2": {2, "vnet1", false},
  1117  	}
  1118  	if duplicates {
  1119  		templateInterfaces["aa:bb:cc:dd:ee:f3"] = ifaceInfo{3, "eth1", true}
  1120  		templateInterfaces["aa:bb:cc:dd:ee:f4"] = ifaceInfo{4, "vnet2", false}
  1121  	}
  1122  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
  1123  	c.Assert(err, jc.ErrorIsNil)
  1124  
  1125  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
  1126  	// resulting CIDR 192.168.2.1/24
  1127  	suite.getNetwork("LAN", 2, 42)
  1128  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1")
  1129  	// resulting CIDR 192.168.3.1/24
  1130  	suite.getNetwork("Virt", 3, 0)
  1131  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f2")
  1132  	// resulting CIDR 192.168.1.1/24
  1133  	suite.getNetwork("WLAN", 1, 0)
  1134  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "WLAN", "aa:bb:cc:dd:ee:ff")
  1135  	if duplicates {
  1136  		suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f3")
  1137  		suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f4")
  1138  	}
  1139  
  1140  	// needed for getNodeGroups to work
  1141  	suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "precise"}`)
  1142  	suite.testMAASObject.TestServer.AddBootImage("uuid-1", `{"architecture": "amd64", "release": "precise"}`)
  1143  
  1144  	jsonText1 := `{
  1145  		"ip_range_high":        "192.168.2.255",
  1146  		"ip_range_low":         "192.168.2.128",
  1147  		"broadcast_ip":         "192.168.2.255",
  1148  		"static_ip_range_low":  "192.168.2.0",
  1149  		"name":                 "eth0",
  1150  		"ip":                   "192.168.2.1",
  1151  		"subnet_mask":          "255.255.255.0",
  1152  		"management":           2,
  1153  		"static_ip_range_high": "192.168.2.127",
  1154  		"interface":            "eth0"
  1155  	}`
  1156  	jsonText2 := `{
  1157  		"ip_range_high":        "172.16.0.128",
  1158  		"ip_range_low":         "172.16.0.2",
  1159  		"broadcast_ip":         "172.16.0.255",
  1160  		"static_ip_range_low":  "172.16.0.129",
  1161  		"name":                 "eth1",
  1162  		"ip":                   "172.16.0.2",
  1163  		"subnet_mask":          "255.255.255.0",
  1164  		"management":           2,
  1165  		"static_ip_range_high": "172.16.0.255",
  1166  		"interface":            "eth1"
  1167  	}`
  1168  	jsonText3 := `{
  1169  		"ip_range_high":        "192.168.1.128",
  1170  		"ip_range_low":         "192.168.1.2",
  1171  		"broadcast_ip":         "192.168.1.255",
  1172  		"static_ip_range_low":  "192.168.1.129",
  1173  		"name":                 "eth2",
  1174  		"ip":                   "192.168.1.2",
  1175  		"subnet_mask":          "255.255.255.0",
  1176  		"management":           2,
  1177  		"static_ip_range_high": "192.168.1.255",
  1178  		"interface":            "eth2"
  1179  	}`
  1180  	jsonText4 := `{
  1181  		"ip_range_high":        "172.16.8.128",
  1182  		"ip_range_low":         "172.16.8.2",
  1183  		"broadcast_ip":         "172.16.8.255",
  1184  		"static_ip_range_low":  "172.16.0.129",
  1185  		"name":                 "eth3",
  1186  		"ip":                   "172.16.8.2",
  1187  		"subnet_mask":          "255.255.255.0",
  1188  		"management":           2,
  1189  		"static_ip_range_high": "172.16.8.255",
  1190  		"interface":            "eth3"
  1191  	}`
  1192  	suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-0", jsonText1)
  1193  	suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-0", jsonText2)
  1194  	suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-1", jsonText3)
  1195  	suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-1", jsonText4)
  1196  	return testInstance
  1197  }
  1198  
  1199  func (suite *environSuite) TestNetworkInterfaces(c *gc.C) {
  1200  	testInstance := suite.createSubnets(c, false)
  1201  
  1202  	netInfo, err := suite.makeEnviron().NetworkInterfaces(testInstance.Id())
  1203  	c.Assert(err, jc.ErrorIsNil)
  1204  
  1205  	expectedInfo := []network.InterfaceInfo{{
  1206  		DeviceIndex:      0,
  1207  		MACAddress:       "aa:bb:cc:dd:ee:ff",
  1208  		CIDR:             "192.168.1.1/24",
  1209  		ProviderSubnetId: "WLAN",
  1210  		VLANTag:          0,
  1211  		InterfaceName:    "wlan0",
  1212  		Disabled:         true,
  1213  		NoAutoStart:      true,
  1214  		ConfigType:       network.ConfigDHCP,
  1215  		ExtraConfig:      nil,
  1216  		GatewayAddress:   network.Address{},
  1217  		Address:          network.NewScopedAddress("192.168.1.1", network.ScopeCloudLocal),
  1218  	}, {
  1219  		DeviceIndex:      1,
  1220  		MACAddress:       "aa:bb:cc:dd:ee:f1",
  1221  		CIDR:             "192.168.2.1/24",
  1222  		ProviderSubnetId: "LAN",
  1223  		VLANTag:          42,
  1224  		InterfaceName:    "eth0",
  1225  		Disabled:         false,
  1226  		NoAutoStart:      false,
  1227  		ConfigType:       network.ConfigDHCP,
  1228  		ExtraConfig:      nil,
  1229  		GatewayAddress:   network.Address{},
  1230  		Address:          network.NewScopedAddress("192.168.2.1", network.ScopeCloudLocal),
  1231  	}, {
  1232  		DeviceIndex:      2,
  1233  		MACAddress:       "aa:bb:cc:dd:ee:f2",
  1234  		CIDR:             "192.168.3.1/24",
  1235  		ProviderSubnetId: "Virt",
  1236  		VLANTag:          0,
  1237  		InterfaceName:    "vnet1",
  1238  		Disabled:         false,
  1239  		NoAutoStart:      false,
  1240  		ConfigType:       network.ConfigDHCP,
  1241  		ExtraConfig:      nil,
  1242  		GatewayAddress:   network.Address{},
  1243  		Address:          network.NewScopedAddress("192.168.3.1", network.ScopeCloudLocal),
  1244  	}}
  1245  	network.SortInterfaceInfo(netInfo)
  1246  	c.Assert(netInfo, jc.DeepEquals, expectedInfo)
  1247  }
  1248  
  1249  func (suite *environSuite) TestSubnets(c *gc.C) {
  1250  	testInstance := suite.createSubnets(c, false)
  1251  
  1252  	netInfo, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{"LAN", "Virt", "WLAN"})
  1253  	c.Assert(err, jc.ErrorIsNil)
  1254  
  1255  	expectedInfo := []network.SubnetInfo{
  1256  		{CIDR: "192.168.2.1/24", ProviderId: "LAN", VLANTag: 42, AllocatableIPLow: net.ParseIP("192.168.2.0"), AllocatableIPHigh: net.ParseIP("192.168.2.127")},
  1257  		{CIDR: "192.168.3.1/24", ProviderId: "Virt", VLANTag: 0},
  1258  		{CIDR: "192.168.1.1/24", ProviderId: "WLAN", VLANTag: 0, AllocatableIPLow: net.ParseIP("192.168.1.129"), AllocatableIPHigh: net.ParseIP("192.168.1.255")}}
  1259  	c.Assert(netInfo, jc.DeepEquals, expectedInfo)
  1260  }
  1261  
  1262  func (suite *environSuite) TestSubnetsNoNetIds(c *gc.C) {
  1263  	testInstance := suite.createSubnets(c, false)
  1264  	_, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{})
  1265  	c.Assert(err, gc.ErrorMatches, "subnetIds must not be empty")
  1266  }
  1267  
  1268  func (suite *environSuite) TestSubnetsMissingNetwork(c *gc.C) {
  1269  	testInstance := suite.createSubnets(c, false)
  1270  	_, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{"WLAN", "Missing"})
  1271  	c.Assert(err, gc.ErrorMatches, "failed to find the following subnets: \\[Missing\\]")
  1272  }
  1273  
  1274  func (suite *environSuite) TestSubnetsNoDuplicates(c *gc.C) {
  1275  	testInstance := suite.createSubnets(c, true)
  1276  
  1277  	netInfo, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{"LAN", "Virt", "WLAN"})
  1278  	c.Assert(err, jc.ErrorIsNil)
  1279  
  1280  	expectedInfo := []network.SubnetInfo{
  1281  		{CIDR: "192.168.2.1/24", ProviderId: "LAN", VLANTag: 42, AllocatableIPLow: net.ParseIP("192.168.2.0"), AllocatableIPHigh: net.ParseIP("192.168.2.127")},
  1282  		{CIDR: "192.168.3.1/24", ProviderId: "Virt", VLANTag: 0},
  1283  		{CIDR: "192.168.1.1/24", ProviderId: "WLAN", VLANTag: 0, AllocatableIPLow: net.ParseIP("192.168.1.129"), AllocatableIPHigh: net.ParseIP("192.168.1.255")}}
  1284  	c.Assert(netInfo, jc.DeepEquals, expectedInfo)
  1285  }
  1286  
  1287  func (suite *environSuite) TestAllocateAddress(c *gc.C) {
  1288  	testInstance := suite.createSubnets(c, false)
  1289  	env := suite.makeEnviron()
  1290  
  1291  	// note that the default test server always succeeds if we provide a
  1292  	// valid instance id and net id
  1293  	err := env.AllocateAddress(testInstance.Id(), "LAN", network.Address{Value: "192.168.2.1"}, "foo", "bar")
  1294  	c.Assert(err, jc.ErrorIsNil)
  1295  }
  1296  
  1297  func (suite *environSuite) TestAllocateAddressDevices(c *gc.C) {
  1298  	suite.testMAASObject.TestServer.SetVersionJSON(`{"capabilities": ["networks-management","static-ipaddresses", "devices-management"]}`)
  1299  	testInstance := suite.createSubnets(c, false)
  1300  	env := suite.makeEnviron()
  1301  
  1302  	// note that the default test server always succeeds if we provide a
  1303  	// valid instance id and net id
  1304  	err := env.AllocateAddress(testInstance.Id(), "LAN", network.Address{Value: "192.168.2.1"}, "foo", "bar")
  1305  	c.Assert(err, jc.ErrorIsNil)
  1306  
  1307  	devicesArray := suite.getDeviceArray(c)
  1308  	c.Assert(devicesArray, gc.HasLen, 1)
  1309  
  1310  	device, err := devicesArray[0].GetMap()
  1311  	c.Assert(err, jc.ErrorIsNil)
  1312  
  1313  	hostname, err := device["hostname"].GetString()
  1314  	c.Assert(err, jc.ErrorIsNil)
  1315  	c.Assert(hostname, gc.Equals, "bar")
  1316  
  1317  	parent, err := device["parent"].GetString()
  1318  	c.Assert(err, jc.ErrorIsNil)
  1319  	trimmedId := strings.TrimRight(string(testInstance.Id()), "/")
  1320  	split := strings.Split(trimmedId, "/")
  1321  	maasId := split[len(split)-1]
  1322  	c.Assert(parent, gc.Equals, maasId)
  1323  
  1324  	addressesArray, err := device["ip_addresses"].GetArray()
  1325  	c.Assert(err, jc.ErrorIsNil)
  1326  	c.Assert(addressesArray, gc.HasLen, 1)
  1327  	address, err := addressesArray[0].GetString()
  1328  	c.Assert(err, jc.ErrorIsNil)
  1329  	c.Assert(address, gc.Equals, "192.168.2.1")
  1330  
  1331  	macArray, err := device["macaddress_set"].GetArray()
  1332  	c.Assert(err, jc.ErrorIsNil)
  1333  	c.Assert(macArray, gc.HasLen, 1)
  1334  	macMap, err := macArray[0].GetMap()
  1335  	c.Assert(err, jc.ErrorIsNil)
  1336  	mac, err := macMap["mac_address"].GetString()
  1337  	c.Assert(err, jc.ErrorIsNil)
  1338  	c.Assert(mac, gc.Equals, "foo")
  1339  }
  1340  
  1341  func (suite *environSuite) getDeviceArray(c *gc.C) []gomaasapi.JSONObject {
  1342  	devicesURL := "/api/1.0/devices/?op=list"
  1343  	resp, err := http.Get(suite.testMAASObject.TestServer.Server.URL + devicesURL)
  1344  	c.Assert(err, jc.ErrorIsNil)
  1345  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
  1346  
  1347  	defer resp.Body.Close()
  1348  	content, err := ioutil.ReadAll(resp.Body)
  1349  	c.Assert(err, jc.ErrorIsNil)
  1350  	result, err := gomaasapi.Parse(gomaasapi.Client{}, content)
  1351  	c.Assert(err, jc.ErrorIsNil)
  1352  
  1353  	devicesArray, err := result.GetArray()
  1354  	c.Assert(err, jc.ErrorIsNil)
  1355  	return devicesArray
  1356  }
  1357  
  1358  func (suite *environSuite) TestReleaseAddressDeletesDevice(c *gc.C) {
  1359  	suite.testMAASObject.TestServer.SetVersionJSON(`{"capabilities": ["networks-management","static-ipaddresses", "devices-management"]}`)
  1360  	testInstance := suite.createSubnets(c, false)
  1361  	env := suite.makeEnviron()
  1362  	addr := network.NewAddress("192.168.2.1")
  1363  	err := env.AllocateAddress(testInstance.Id(), "LAN", addr, "foo", "bar")
  1364  	c.Assert(err, jc.ErrorIsNil)
  1365  
  1366  	devicesArray := suite.getDeviceArray(c)
  1367  	c.Assert(devicesArray, gc.HasLen, 1)
  1368  
  1369  	err = env.ReleaseAddress(testInstance.Id(), "LAN", addr, "foo")
  1370  	c.Assert(err, jc.ErrorIsNil)
  1371  
  1372  	devicesArray = suite.getDeviceArray(c)
  1373  	c.Assert(devicesArray, gc.HasLen, 0)
  1374  }
  1375  
  1376  func (suite *environSuite) TestAllocateAddressInvalidInstance(c *gc.C) {
  1377  	env := suite.makeEnviron()
  1378  	addr := network.Address{Value: "192.168.2.1"}
  1379  	instId := instance.Id("foo")
  1380  	err := env.AllocateAddress(instId, "bar", addr, "foo", "bar")
  1381  	expected := fmt.Sprintf("failed to allocate address %q for instance %q.*", addr, instId)
  1382  	c.Assert(err, gc.ErrorMatches, expected)
  1383  }
  1384  
  1385  func (suite *environSuite) TestAllocateAddressMissingSubnet(c *gc.C) {
  1386  	testInstance := suite.createSubnets(c, false)
  1387  	env := suite.makeEnviron()
  1388  	err := env.AllocateAddress(testInstance.Id(), "bar", network.Address{Value: "192.168.2.1"}, "foo", "bar")
  1389  	c.Assert(errors.Cause(err), gc.ErrorMatches, "failed to find the following subnets: \\[bar\\]")
  1390  }
  1391  
  1392  func (suite *environSuite) TestAllocateAddressIPAddressUnavailable(c *gc.C) {
  1393  	testInstance := suite.createSubnets(c, false)
  1394  	env := suite.makeEnviron()
  1395  
  1396  	reserveIPAddress := func(ipaddresses gomaasapi.MAASObject, cidr string, addr network.Address) error {
  1397  		return gomaasapi.ServerError{StatusCode: 404}
  1398  	}
  1399  	suite.PatchValue(&ReserveIPAddress, reserveIPAddress)
  1400  
  1401  	ipAddress := network.Address{Value: "192.168.2.1"}
  1402  	err := env.AllocateAddress(testInstance.Id(), "LAN", ipAddress, "foo", "bar")
  1403  	c.Assert(errors.Cause(err), gc.Equals, environs.ErrIPAddressUnavailable)
  1404  	expected := fmt.Sprintf("failed to allocate address %q for instance %q.*", ipAddress, testInstance.Id())
  1405  	c.Assert(err, gc.ErrorMatches, expected)
  1406  }
  1407  
  1408  func (s *environSuite) TestPrecheckInstanceAvailZone(c *gc.C) {
  1409  	s.testMAASObject.TestServer.AddZone("zone1", "the grass is greener in zone1")
  1410  	env := s.makeEnviron()
  1411  	placement := "zone=zone1"
  1412  	err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement)
  1413  	c.Assert(err, jc.ErrorIsNil)
  1414  }
  1415  
  1416  func (suite *environSuite) TestReleaseAddress(c *gc.C) {
  1417  	testInstance := suite.createSubnets(c, false)
  1418  	env := suite.makeEnviron()
  1419  
  1420  	err := env.AllocateAddress(testInstance.Id(), "LAN", network.Address{Value: "192.168.2.1"}, "foo", "bar")
  1421  	c.Assert(err, jc.ErrorIsNil)
  1422  
  1423  	ipAddress := network.Address{Value: "192.168.2.1"}
  1424  	macAddress := "foobar"
  1425  	err = env.ReleaseAddress(testInstance.Id(), "bar", ipAddress, macAddress)
  1426  	c.Assert(err, jc.ErrorIsNil)
  1427  
  1428  	// by releasing again we can test that the first release worked, *and*
  1429  	// the error handling of ReleaseError
  1430  	err = env.ReleaseAddress(testInstance.Id(), "bar", ipAddress, macAddress)
  1431  	expected := fmt.Sprintf("(?s).*failed to release IP address %q from instance %q.*", ipAddress, testInstance.Id())
  1432  	c.Assert(err, gc.ErrorMatches, expected)
  1433  }
  1434  
  1435  func (suite *environSuite) TestReleaseAddressRetry(c *gc.C) {
  1436  	// Patch short attempt params.
  1437  	suite.PatchValue(&shortAttempt, utils.AttemptStrategy{
  1438  		Min: 5,
  1439  	})
  1440  	// Patch IP address release call to MAAS.
  1441  	retries := 0
  1442  	enoughRetries := 10
  1443  	suite.PatchValue(&ReleaseIPAddress, func(ipaddresses gomaasapi.MAASObject, addr network.Address) error {
  1444  		retries++
  1445  		if retries < enoughRetries {
  1446  			return errors.New("ouch")
  1447  		}
  1448  		return nil
  1449  	})
  1450  
  1451  	testInstance := suite.createSubnets(c, false)
  1452  	env := suite.makeEnviron()
  1453  
  1454  	err := env.AllocateAddress(testInstance.Id(), "LAN", network.Address{Value: "192.168.2.1"}, "foo", "bar")
  1455  	c.Assert(err, jc.ErrorIsNil)
  1456  
  1457  	// ReleaseAddress must fail with 5 retries.
  1458  	ipAddress := network.Address{Value: "192.168.2.1"}
  1459  	macAddress := "foobar"
  1460  	err = env.ReleaseAddress(testInstance.Id(), "bar", ipAddress, macAddress)
  1461  	expected := fmt.Sprintf("(?s).*failed to release IP address %q from instance %q: ouch", ipAddress, testInstance.Id())
  1462  	c.Assert(err, gc.ErrorMatches, expected)
  1463  	c.Assert(retries, gc.Equals, 5)
  1464  
  1465  	// Now let it succeed after 3 retries.
  1466  	retries = 0
  1467  	enoughRetries = 3
  1468  	err = env.ReleaseAddress(testInstance.Id(), "bar", ipAddress, macAddress)
  1469  	c.Assert(err, jc.ErrorIsNil)
  1470  	c.Assert(retries, gc.Equals, 3)
  1471  }
  1472  
  1473  func (s *environSuite) TestPrecheckInstanceAvailZoneUnknown(c *gc.C) {
  1474  	s.testMAASObject.TestServer.AddZone("zone1", "the grass is greener in zone1")
  1475  	env := s.makeEnviron()
  1476  	placement := "zone=zone2"
  1477  	err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement)
  1478  	c.Assert(err, gc.ErrorMatches, `invalid availability zone "zone2"`)
  1479  }
  1480  
  1481  func (s *environSuite) TestPrecheckInstanceAvailZonesUnsupported(c *gc.C) {
  1482  	env := s.makeEnviron()
  1483  	placement := "zone=test-unknown"
  1484  	err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement)
  1485  	c.Assert(err, jc.Satisfies, errors.IsNotImplemented)
  1486  }
  1487  
  1488  func (s *environSuite) TestPrecheckInvalidPlacement(c *gc.C) {
  1489  	env := s.makeEnviron()
  1490  	err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, "notzone=anything")
  1491  	c.Assert(err, gc.ErrorMatches, "unknown placement directive: notzone=anything")
  1492  }
  1493  
  1494  func (s *environSuite) TestPrecheckNodePlacement(c *gc.C) {
  1495  	env := s.makeEnviron()
  1496  	err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, "assumed_node_name")
  1497  	c.Assert(err, jc.ErrorIsNil)
  1498  }
  1499  
  1500  func (s *environSuite) TestStartInstanceAvailZone(c *gc.C) {
  1501  	// Add a node for the started instance.
  1502  	s.newNode(c, "thenode1", "host1", map[string]interface{}{"zone": "test-available"})
  1503  	s.testMAASObject.TestServer.AddZone("test-available", "description")
  1504  	inst, err := s.testStartInstanceAvailZone(c, "test-available")
  1505  	c.Assert(err, jc.ErrorIsNil)
  1506  	c.Assert(inst.(*maasInstance).zone(), gc.Equals, "test-available")
  1507  }
  1508  
  1509  func (s *environSuite) TestStartInstanceAvailZoneUnknown(c *gc.C) {
  1510  	s.testMAASObject.TestServer.AddZone("test-available", "description")
  1511  	_, err := s.testStartInstanceAvailZone(c, "test-unknown")
  1512  	c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`)
  1513  }
  1514  
  1515  func (s *environSuite) testStartInstanceAvailZone(c *gc.C, zone string) (instance.Instance, error) {
  1516  	env := s.bootstrap(c)
  1517  	params := environs.StartInstanceParams{Placement: "zone=" + zone}
  1518  	result, err := testing.StartInstanceWithParams(env, "1", params, nil)
  1519  	if err != nil {
  1520  		return nil, err
  1521  	}
  1522  	return result.Instance, nil
  1523  }
  1524  
  1525  func (s *environSuite) TestStartInstanceUnmetConstraints(c *gc.C) {
  1526  	env := s.bootstrap(c)
  1527  	s.newNode(c, "thenode1", "host1", nil)
  1528  	params := environs.StartInstanceParams{Constraints: constraints.MustParse("mem=8G")}
  1529  	_, err := testing.StartInstanceWithParams(env, "1", params, nil)
  1530  	c.Assert(err, gc.ErrorMatches, "cannot run instances:.* 409.*")
  1531  }
  1532  
  1533  func (s *environSuite) TestStartInstanceConstraints(c *gc.C) {
  1534  	env := s.bootstrap(c)
  1535  	s.newNode(c, "thenode1", "host1", nil)
  1536  	s.newNode(c, "thenode2", "host2", map[string]interface{}{"memory": 8192})
  1537  	params := environs.StartInstanceParams{Constraints: constraints.MustParse("mem=8G")}
  1538  	result, err := testing.StartInstanceWithParams(env, "1", params, nil)
  1539  	c.Assert(err, jc.ErrorIsNil)
  1540  	c.Assert(*result.Hardware.Mem, gc.Equals, uint64(8192))
  1541  }
  1542  
  1543  var nodeStorageAttrs = []map[string]interface{}{
  1544  	{
  1545  		"name":       "sdb",
  1546  		"id":         1,
  1547  		"id_path":    "/dev/disk/by-id/id_for_sda",
  1548  		"path":       "/dev/sdb",
  1549  		"model":      "Samsung_SSD_850_EVO_250GB",
  1550  		"block_size": 4096,
  1551  		"serial":     "S21NNSAFC38075L",
  1552  		"size":       uint64(250059350016),
  1553  	},
  1554  	{
  1555  		"name":       "sda",
  1556  		"id":         2,
  1557  		"path":       "/dev/sda",
  1558  		"model":      "Samsung_SSD_850_EVO_250GB",
  1559  		"block_size": 4096,
  1560  		"serial":     "XXXX",
  1561  		"size":       uint64(250059350016),
  1562  	},
  1563  	{
  1564  		"name":       "sdc",
  1565  		"id":         3,
  1566  		"path":       "/dev/sdc",
  1567  		"model":      "Samsung_SSD_850_EVO_250GB",
  1568  		"block_size": 4096,
  1569  		"serial":     "YYYYYYY",
  1570  		"size":       uint64(250059350016),
  1571  	},
  1572  }
  1573  
  1574  var storageConstraintAttrs = map[string]interface{}{
  1575  	"1": "1",
  1576  	"2": "root",
  1577  	"3": "3",
  1578  }
  1579  
  1580  func (s *environSuite) TestStartInstanceStorage(c *gc.C) {
  1581  	env := s.bootstrap(c)
  1582  	s.newNode(c, "thenode1", "host1", map[string]interface{}{
  1583  		"memory":                  8192,
  1584  		"physicalblockdevice_set": nodeStorageAttrs,
  1585  		"constraint_map":          storageConstraintAttrs,
  1586  	})
  1587  	params := environs.StartInstanceParams{Volumes: []storage.VolumeParams{
  1588  		{Tag: names.NewVolumeTag("1"), Size: 2000000},
  1589  		{Tag: names.NewVolumeTag("3"), Size: 2000000},
  1590  	}}
  1591  	result, err := testing.StartInstanceWithParams(env, "1", params, nil)
  1592  	c.Assert(err, jc.ErrorIsNil)
  1593  	c.Check(result.Volumes, jc.DeepEquals, []storage.Volume{
  1594  		{
  1595  			names.NewVolumeTag("1"),
  1596  			storage.VolumeInfo{
  1597  				Size:       238475,
  1598  				VolumeId:   "volume-1",
  1599  				HardwareId: "id_for_sda",
  1600  			},
  1601  		},
  1602  		{
  1603  			names.NewVolumeTag("3"),
  1604  			storage.VolumeInfo{
  1605  				Size:       238475,
  1606  				VolumeId:   "volume-3",
  1607  				HardwareId: "",
  1608  			},
  1609  		},
  1610  	})
  1611  	c.Assert(result.VolumeAttachments, jc.DeepEquals, []storage.VolumeAttachment{
  1612  		{
  1613  			names.NewVolumeTag("1"),
  1614  			names.NewMachineTag("1"),
  1615  			storage.VolumeAttachmentInfo{
  1616  				DeviceName: "",
  1617  				ReadOnly:   false,
  1618  			},
  1619  		},
  1620  		{
  1621  			names.NewVolumeTag("3"),
  1622  			names.NewMachineTag("1"),
  1623  			storage.VolumeAttachmentInfo{
  1624  				DeviceName: "sdc",
  1625  				ReadOnly:   false,
  1626  			},
  1627  		},
  1628  	})
  1629  }
  1630  
  1631  func (s *environSuite) TestStartInstanceUnsupportedStorage(c *gc.C) {
  1632  	env := s.bootstrap(c)
  1633  	s.newNode(c, "thenode1", "host1", map[string]interface{}{
  1634  		"memory": 8192,
  1635  	})
  1636  	params := environs.StartInstanceParams{Volumes: []storage.VolumeParams{
  1637  		{Tag: names.NewVolumeTag("1"), Size: 2000000},
  1638  		{Tag: names.NewVolumeTag("3"), Size: 2000000},
  1639  	}}
  1640  	_, err := testing.StartInstanceWithParams(env, "1", params, nil)
  1641  	c.Assert(err, gc.ErrorMatches, "the version of MAAS being used does not support Juju storage")
  1642  	operations := s.testMAASObject.TestServer.NodesOperations()
  1643  	c.Check(operations, gc.DeepEquals, []string{"acquire", "acquire", "release"})
  1644  	c.Assert(s.testMAASObject.TestServer.OwnedNodes()["node0"], jc.IsTrue)
  1645  	c.Assert(s.testMAASObject.TestServer.OwnedNodes()["thenode1"], jc.IsFalse)
  1646  }
  1647  
  1648  func (s *environSuite) TestGetAvailabilityZones(c *gc.C) {
  1649  	env := s.makeEnviron()
  1650  
  1651  	zones, err := env.AvailabilityZones()
  1652  	c.Assert(err, jc.Satisfies, errors.IsNotImplemented)
  1653  	c.Assert(zones, gc.IsNil)
  1654  
  1655  	s.testMAASObject.TestServer.AddZone("whatever", "andever")
  1656  	zones, err = env.AvailabilityZones()
  1657  	c.Assert(err, jc.ErrorIsNil)
  1658  	c.Assert(zones, gc.HasLen, 1)
  1659  	c.Assert(zones[0].Name(), gc.Equals, "whatever")
  1660  	c.Assert(zones[0].Available(), jc.IsTrue)
  1661  
  1662  	// A successful result is cached, currently for the lifetime
  1663  	// of the Environ. This will change if/when we have long-lived
  1664  	// Environs to cut down repeated IaaS requests.
  1665  	s.testMAASObject.TestServer.AddZone("somewhere", "outthere")
  1666  	zones, err = env.AvailabilityZones()
  1667  	c.Assert(err, jc.ErrorIsNil)
  1668  	c.Assert(zones, gc.HasLen, 1)
  1669  	c.Assert(zones[0].Name(), gc.Equals, "whatever")
  1670  }
  1671  
  1672  type mockAvailabilityZoneAllocations struct {
  1673  	group  []instance.Id // input param
  1674  	result []common.AvailabilityZoneInstances
  1675  	err    error
  1676  }
  1677  
  1678  func (m *mockAvailabilityZoneAllocations) AvailabilityZoneAllocations(
  1679  	e common.ZonedEnviron, group []instance.Id,
  1680  ) ([]common.AvailabilityZoneInstances, error) {
  1681  	m.group = group
  1682  	return m.result, m.err
  1683  }
  1684  
  1685  func (s *environSuite) newNode(c *gc.C, nodename, hostname string, attrs map[string]interface{}) {
  1686  	allAttrs := map[string]interface{}{
  1687  		"system_id":    nodename,
  1688  		"hostname":     hostname,
  1689  		"architecture": fmt.Sprintf("%s/generic", arch.HostArch()),
  1690  		"memory":       1024,
  1691  		"cpu_count":    1,
  1692  	}
  1693  	for k, v := range attrs {
  1694  		allAttrs[k] = v
  1695  	}
  1696  	data, err := json.Marshal(allAttrs)
  1697  	c.Assert(err, jc.ErrorIsNil)
  1698  	s.testMAASObject.TestServer.NewNode(string(data))
  1699  	lshwXML, err := s.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}})
  1700  	c.Assert(err, jc.ErrorIsNil)
  1701  	s.testMAASObject.TestServer.AddNodeDetails(nodename, lshwXML)
  1702  }
  1703  
  1704  func (s *environSuite) bootstrap(c *gc.C) environs.Environ {
  1705  	s.newNode(c, "node0", "bootstrap-host", nil)
  1706  	s.setupFakeTools(c)
  1707  	env := s.makeEnviron()
  1708  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
  1709  		Placement: "bootstrap-host",
  1710  	})
  1711  	c.Assert(err, jc.ErrorIsNil)
  1712  	return env
  1713  }
  1714  
  1715  func (s *environSuite) TestStartInstanceDistributionParams(c *gc.C) {
  1716  	env := s.bootstrap(c)
  1717  	var mock mockAvailabilityZoneAllocations
  1718  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
  1719  
  1720  	// no distribution group specified
  1721  	s.newNode(c, "node1", "host1", nil)
  1722  	testing.AssertStartInstance(c, env, "1")
  1723  	c.Assert(mock.group, gc.HasLen, 0)
  1724  
  1725  	// distribution group specified: ensure it's passed through to AvailabilityZone.
  1726  	s.newNode(c, "node2", "host2", nil)
  1727  	expectedInstances := []instance.Id{"i-0", "i-1"}
  1728  	params := environs.StartInstanceParams{
  1729  		DistributionGroup: func() ([]instance.Id, error) {
  1730  			return expectedInstances, nil
  1731  		},
  1732  	}
  1733  	_, err := testing.StartInstanceWithParams(env, "1", params, nil)
  1734  	c.Assert(err, jc.ErrorIsNil)
  1735  	c.Assert(mock.group, gc.DeepEquals, expectedInstances)
  1736  }
  1737  
  1738  func (s *environSuite) TestStartInstanceDistributionErrors(c *gc.C) {
  1739  	env := s.bootstrap(c)
  1740  	mock := mockAvailabilityZoneAllocations{
  1741  		err: errors.New("AvailabilityZoneAllocations failed"),
  1742  	}
  1743  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
  1744  	_, _, _, err := testing.StartInstance(env, "1")
  1745  	c.Assert(err, gc.ErrorMatches, "cannot get availability zone allocations: AvailabilityZoneAllocations failed")
  1746  
  1747  	mock.err = nil
  1748  	dgErr := errors.New("DistributionGroup failed")
  1749  	params := environs.StartInstanceParams{
  1750  		DistributionGroup: func() ([]instance.Id, error) {
  1751  			return nil, dgErr
  1752  		},
  1753  	}
  1754  	_, err = testing.StartInstanceWithParams(env, "1", params, nil)
  1755  	c.Assert(err, gc.ErrorMatches, "cannot get distribution group: DistributionGroup failed")
  1756  }
  1757  
  1758  func (s *environSuite) TestStartInstanceDistribution(c *gc.C) {
  1759  	env := s.bootstrap(c)
  1760  	s.testMAASObject.TestServer.AddZone("test-available", "description")
  1761  	s.newNode(c, "node1", "host1", map[string]interface{}{"zone": "test-available"})
  1762  	inst, _ := testing.AssertStartInstance(c, env, "1")
  1763  	c.Assert(inst.(*maasInstance).zone(), gc.Equals, "test-available")
  1764  }
  1765  
  1766  func (s *environSuite) TestStartInstanceDistributionAZNotImplemented(c *gc.C) {
  1767  	env := s.bootstrap(c)
  1768  
  1769  	mock := mockAvailabilityZoneAllocations{err: errors.NotImplementedf("availability zones")}
  1770  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
  1771  
  1772  	// Instance will be created without an availability zone specified.
  1773  	s.newNode(c, "node1", "host1", nil)
  1774  	inst, _ := testing.AssertStartInstance(c, env, "1")
  1775  	c.Assert(inst.(*maasInstance).zone(), gc.Equals, "")
  1776  }
  1777  
  1778  func (s *environSuite) TestStartInstanceDistributionFailover(c *gc.C) {
  1779  	mock := mockAvailabilityZoneAllocations{
  1780  		result: []common.AvailabilityZoneInstances{{
  1781  			ZoneName: "zone1",
  1782  		}, {
  1783  			ZoneName: "zonelord",
  1784  		}, {
  1785  			ZoneName: "zone2",
  1786  		}},
  1787  	}
  1788  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
  1789  	s.testMAASObject.TestServer.AddZone("zone1", "description")
  1790  	s.testMAASObject.TestServer.AddZone("zone2", "description")
  1791  	s.newNode(c, "node2", "host2", map[string]interface{}{"zone": "zone2"})
  1792  
  1793  	env := s.bootstrap(c)
  1794  	inst, _ := testing.AssertStartInstance(c, env, "1")
  1795  	c.Assert(inst.(*maasInstance).zone(), gc.Equals, "zone2")
  1796  	c.Assert(s.testMAASObject.TestServer.NodesOperations(), gc.DeepEquals, []string{
  1797  		// one acquire for the bootstrap, three for StartInstance (with zone failover)
  1798  		"acquire", "acquire", "acquire", "acquire",
  1799  	})
  1800  	c.Assert(s.testMAASObject.TestServer.NodesOperationRequestValues(), gc.DeepEquals, []url.Values{{
  1801  		"name":       []string{"bootstrap-host"},
  1802  		"agent_name": []string{exampleAgentName},
  1803  	}, {
  1804  		"zone":       []string{"zone1"},
  1805  		"agent_name": []string{exampleAgentName},
  1806  	}, {
  1807  		"zone":       []string{"zonelord"},
  1808  		"agent_name": []string{exampleAgentName},
  1809  	}, {
  1810  		"zone":       []string{"zone2"},
  1811  		"agent_name": []string{exampleAgentName},
  1812  	}})
  1813  }
  1814  
  1815  func (s *environSuite) TestStartInstanceDistributionOneAssigned(c *gc.C) {
  1816  	mock := mockAvailabilityZoneAllocations{
  1817  		result: []common.AvailabilityZoneInstances{{
  1818  			ZoneName: "zone1",
  1819  		}, {
  1820  			ZoneName: "zone2",
  1821  		}},
  1822  	}
  1823  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
  1824  	s.testMAASObject.TestServer.AddZone("zone1", "description")
  1825  	s.testMAASObject.TestServer.AddZone("zone2", "description")
  1826  	s.newNode(c, "node1", "host1", map[string]interface{}{"zone": "zone1"})
  1827  	s.newNode(c, "node2", "host2", map[string]interface{}{"zone": "zone2"})
  1828  
  1829  	env := s.bootstrap(c)
  1830  	testing.AssertStartInstance(c, env, "1")
  1831  	c.Assert(s.testMAASObject.TestServer.NodesOperations(), gc.DeepEquals, []string{
  1832  		// one acquire for the bootstrap, one for StartInstance.
  1833  		"acquire", "acquire",
  1834  	})
  1835  }