github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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/set"
    23  	gc "gopkg.in/check.v1"
    24  	goyaml "gopkg.in/yaml.v1"
    25  	"launchpad.net/gomaasapi"
    26  
    27  	"github.com/juju/juju/cloudconfig/cloudinit"
    28  	"github.com/juju/juju/constraints"
    29  	"github.com/juju/juju/environs"
    30  	"github.com/juju/juju/environs/bootstrap"
    31  	"github.com/juju/juju/environs/config"
    32  	"github.com/juju/juju/environs/simplestreams"
    33  	envstorage "github.com/juju/juju/environs/storage"
    34  	envtesting "github.com/juju/juju/environs/testing"
    35  	envtools "github.com/juju/juju/environs/tools"
    36  	"github.com/juju/juju/instance"
    37  	"github.com/juju/juju/juju/arch"
    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{maasObject: &node, environ: suite.makeEnviron()}
   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  	interfaces, primaryIface, err := inst.environ.getInstanceNetworkInterfaces(inst)
   963  	c.Assert(err, jc.ErrorIsNil)
   964  	// Both wlan0 and eth0 are disabled in lshw output.
   965  	c.Check(primaryIface, gc.Equals, "vnet1")
   966  	c.Check(interfaces, jc.DeepEquals, templateInterfaces)
   967  }
   968  
   969  func (suite *environSuite) TestSetupNetworks(c *gc.C) {
   970  	testInstance := suite.getInstance("node1")
   971  	templateInterfaces := map[string]ifaceInfo{
   972  		"aa:bb:cc:dd:ee:ff": {0, "wlan0", true},
   973  		"aa:bb:cc:dd:ee:f1": {1, "eth0", true},
   974  		"aa:bb:cc:dd:ee:f2": {2, "vnet1", false},
   975  	}
   976  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
   977  	c.Assert(err, jc.ErrorIsNil)
   978  
   979  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
   980  	suite.getNetwork("LAN", 2, 42)
   981  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1")
   982  	suite.getNetwork("Virt", 3, 0)
   983  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f2")
   984  	suite.getNetwork("WLAN", 1, 0)
   985  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "WLAN", "aa:bb:cc:dd:ee:ff")
   986  	networkInfo, primaryIface, err := suite.makeEnviron().setupNetworks(
   987  		testInstance,
   988  		set.NewStrings("WLAN"), // Disable WLAN only.
   989  	)
   990  	c.Assert(err, jc.ErrorIsNil)
   991  
   992  	// Note: order of networks is based on lshwXML
   993  	c.Check(primaryIface, gc.Equals, "vnet1")
   994  	// Unfortunately, because network.InterfaceInfo is unhashable
   995  	// (contains a map) we can't use jc.SameContents here.
   996  	c.Check(networkInfo, gc.HasLen, 3)
   997  	for _, info := range networkInfo {
   998  		switch info.DeviceIndex {
   999  		case 0:
  1000  			c.Check(info, jc.DeepEquals, network.InterfaceInfo{
  1001  				MACAddress:    "aa:bb:cc:dd:ee:ff",
  1002  				CIDR:          "192.168.1.1/24",
  1003  				NetworkName:   "WLAN",
  1004  				ProviderId:    "WLAN",
  1005  				VLANTag:       0,
  1006  				DeviceIndex:   0,
  1007  				InterfaceName: "wlan0",
  1008  				Disabled:      true, // from networksToDisable("WLAN")
  1009  			})
  1010  		case 1:
  1011  			c.Check(info, jc.DeepEquals, network.InterfaceInfo{
  1012  				DeviceIndex:   1,
  1013  				MACAddress:    "aa:bb:cc:dd:ee:f1",
  1014  				CIDR:          "192.168.2.1/24",
  1015  				NetworkName:   "LAN",
  1016  				ProviderId:    "LAN",
  1017  				VLANTag:       42,
  1018  				InterfaceName: "eth0",
  1019  				Disabled:      true, // from networksToDisable("WLAN")
  1020  			})
  1021  		case 2:
  1022  			c.Check(info, jc.DeepEquals, network.InterfaceInfo{
  1023  				MACAddress:    "aa:bb:cc:dd:ee:f2",
  1024  				CIDR:          "192.168.3.1/24",
  1025  				NetworkName:   "Virt",
  1026  				ProviderId:    "Virt",
  1027  				VLANTag:       0,
  1028  				DeviceIndex:   2,
  1029  				InterfaceName: "vnet1",
  1030  				Disabled:      false,
  1031  			})
  1032  		}
  1033  	}
  1034  }
  1035  
  1036  // The same test, but now "Virt" network does not have matched MAC address
  1037  func (suite *environSuite) TestSetupNetworksPartialMatch(c *gc.C) {
  1038  	testInstance := suite.getInstance("node1")
  1039  	templateInterfaces := map[string]ifaceInfo{
  1040  		"aa:bb:cc:dd:ee:ff": {0, "wlan0", true},
  1041  		"aa:bb:cc:dd:ee:f1": {1, "eth0", false},
  1042  		"aa:bb:cc:dd:ee:f2": {2, "vnet1", false},
  1043  	}
  1044  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
  1045  	c.Assert(err, jc.ErrorIsNil)
  1046  
  1047  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
  1048  	suite.getNetwork("LAN", 2, 42)
  1049  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1")
  1050  	suite.getNetwork("Virt", 3, 0)
  1051  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f3")
  1052  	networkInfo, primaryIface, err := suite.makeEnviron().setupNetworks(
  1053  		testInstance,
  1054  		set.NewStrings(), // All enabled.
  1055  	)
  1056  	c.Assert(err, jc.ErrorIsNil)
  1057  
  1058  	// Note: order of networks is based on lshwXML
  1059  	c.Check(primaryIface, gc.Equals, "eth0")
  1060  	c.Check(networkInfo, jc.DeepEquals, []network.InterfaceInfo{{
  1061  		MACAddress:    "aa:bb:cc:dd:ee:f1",
  1062  		CIDR:          "192.168.2.1/24",
  1063  		NetworkName:   "LAN",
  1064  		ProviderId:    "LAN",
  1065  		VLANTag:       42,
  1066  		DeviceIndex:   1,
  1067  		InterfaceName: "eth0",
  1068  		Disabled:      false,
  1069  	}})
  1070  }
  1071  
  1072  // The same test, but now no networks have matched MAC
  1073  func (suite *environSuite) TestSetupNetworksNoMatch(c *gc.C) {
  1074  	testInstance := suite.getInstance("node1")
  1075  	templateInterfaces := map[string]ifaceInfo{
  1076  		"aa:bb:cc:dd:ee:ff": {0, "wlan0", true},
  1077  		"aa:bb:cc:dd:ee:f1": {1, "eth0", false},
  1078  		"aa:bb:cc:dd:ee:f2": {2, "vnet1", false},
  1079  	}
  1080  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
  1081  	c.Assert(err, jc.ErrorIsNil)
  1082  
  1083  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
  1084  	suite.getNetwork("Virt", 3, 0)
  1085  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f3")
  1086  	networkInfo, primaryIface, err := suite.makeEnviron().setupNetworks(
  1087  		testInstance,
  1088  		set.NewStrings(), // All enabled.
  1089  	)
  1090  	c.Assert(err, jc.ErrorIsNil)
  1091  
  1092  	// Note: order of networks is based on lshwXML
  1093  	c.Check(primaryIface, gc.Equals, "eth0")
  1094  	c.Check(networkInfo, gc.HasLen, 0)
  1095  }
  1096  
  1097  func (suite *environSuite) TestSupportsNetworking(c *gc.C) {
  1098  	env := suite.makeEnviron()
  1099  	_, supported := environs.SupportsNetworking(env)
  1100  	c.Assert(supported, jc.IsTrue)
  1101  }
  1102  
  1103  func (suite *environSuite) TestSupportsAddressAllocation(c *gc.C) {
  1104  	env := suite.makeEnviron()
  1105  	supported, err := env.SupportsAddressAllocation("")
  1106  	c.Assert(err, jc.ErrorIsNil)
  1107  	c.Assert(supported, jc.IsTrue)
  1108  }
  1109  
  1110  func (suite *environSuite) createSubnets(c *gc.C, duplicates bool) instance.Instance {
  1111  	testInstance := suite.getInstance("node1")
  1112  	templateInterfaces := map[string]ifaceInfo{
  1113  		"aa:bb:cc:dd:ee:ff": {0, "wlan0", true},
  1114  		"aa:bb:cc:dd:ee:f1": {1, "eth0", false},
  1115  		"aa:bb:cc:dd:ee:f2": {2, "vnet1", false},
  1116  	}
  1117  	if duplicates {
  1118  		templateInterfaces["aa:bb:cc:dd:ee:f3"] = ifaceInfo{3, "eth1", true}
  1119  		templateInterfaces["aa:bb:cc:dd:ee:f4"] = ifaceInfo{4, "vnet2", false}
  1120  	}
  1121  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
  1122  	c.Assert(err, jc.ErrorIsNil)
  1123  
  1124  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
  1125  	// resulting CIDR 192.168.2.1/24
  1126  	suite.getNetwork("LAN", 2, 42)
  1127  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1")
  1128  	// resulting CIDR 192.168.3.1/24
  1129  	suite.getNetwork("Virt", 3, 0)
  1130  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f2")
  1131  	// resulting CIDR 192.168.1.1/24
  1132  	suite.getNetwork("WLAN", 1, 0)
  1133  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "WLAN", "aa:bb:cc:dd:ee:ff")
  1134  	if duplicates {
  1135  		suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f3")
  1136  		suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f4")
  1137  	}
  1138  
  1139  	// needed for getNodeGroups to work
  1140  	suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "precise"}`)
  1141  	suite.testMAASObject.TestServer.AddBootImage("uuid-1", `{"architecture": "amd64", "release": "precise"}`)
  1142  
  1143  	jsonText1 := `{
  1144  		"ip_range_high":        "192.168.2.255",
  1145  		"ip_range_low":         "192.168.2.128",
  1146  		"broadcast_ip":         "192.168.2.255",
  1147  		"static_ip_range_low":  "192.168.2.0",
  1148  		"name":                 "eth0",
  1149  		"ip":                   "192.168.2.1",
  1150  		"subnet_mask":          "255.255.255.0",
  1151  		"management":           2,
  1152  		"static_ip_range_high": "192.168.2.127",
  1153  		"interface":            "eth0"
  1154  	}`
  1155  	jsonText2 := `{
  1156  		"ip_range_high":        "172.16.0.128",
  1157  		"ip_range_low":         "172.16.0.2",
  1158  		"broadcast_ip":         "172.16.0.255",
  1159  		"static_ip_range_low":  "172.16.0.129",
  1160  		"name":                 "eth1",
  1161  		"ip":                   "172.16.0.2",
  1162  		"subnet_mask":          "255.255.255.0",
  1163  		"management":           2,
  1164  		"static_ip_range_high": "172.16.0.255",
  1165  		"interface":            "eth1"
  1166  	}`
  1167  	jsonText3 := `{
  1168  		"ip_range_high":        "192.168.1.128",
  1169  		"ip_range_low":         "192.168.1.2",
  1170  		"broadcast_ip":         "192.168.1.255",
  1171  		"static_ip_range_low":  "192.168.1.129",
  1172  		"name":                 "eth2",
  1173  		"ip":                   "192.168.1.2",
  1174  		"subnet_mask":          "255.255.255.0",
  1175  		"management":           2,
  1176  		"static_ip_range_high": "192.168.1.255",
  1177  		"interface":            "eth2"
  1178  	}`
  1179  	jsonText4 := `{
  1180  		"ip_range_high":        "172.16.8.128",
  1181  		"ip_range_low":         "172.16.8.2",
  1182  		"broadcast_ip":         "172.16.8.255",
  1183  		"static_ip_range_low":  "172.16.0.129",
  1184  		"name":                 "eth3",
  1185  		"ip":                   "172.16.8.2",
  1186  		"subnet_mask":          "255.255.255.0",
  1187  		"management":           2,
  1188  		"static_ip_range_high": "172.16.8.255",
  1189  		"interface":            "eth3"
  1190  	}`
  1191  	suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-0", jsonText1)
  1192  	suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-0", jsonText2)
  1193  	suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-1", jsonText3)
  1194  	suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-1", jsonText4)
  1195  	return testInstance
  1196  }
  1197  
  1198  func (suite *environSuite) TestNetworkInterfaces(c *gc.C) {
  1199  	testInstance := suite.createSubnets(c, false)
  1200  
  1201  	netInfo, err := suite.makeEnviron().NetworkInterfaces(testInstance.Id())
  1202  	c.Assert(err, jc.ErrorIsNil)
  1203  
  1204  	expectedInfo := []network.InterfaceInfo{{
  1205  		DeviceIndex:      0,
  1206  		MACAddress:       "aa:bb:cc:dd:ee:ff",
  1207  		CIDR:             "192.168.1.1/24",
  1208  		ProviderSubnetId: "WLAN",
  1209  		VLANTag:          0,
  1210  		InterfaceName:    "wlan0",
  1211  		Disabled:         true,
  1212  		NoAutoStart:      true,
  1213  		ConfigType:       network.ConfigDHCP,
  1214  		ExtraConfig:      nil,
  1215  		GatewayAddress:   network.Address{},
  1216  		Address:          network.NewScopedAddress("192.168.1.1", network.ScopeCloudLocal),
  1217  	}, {
  1218  		DeviceIndex:      1,
  1219  		MACAddress:       "aa:bb:cc:dd:ee:f1",
  1220  		CIDR:             "192.168.2.1/24",
  1221  		ProviderSubnetId: "LAN",
  1222  		VLANTag:          42,
  1223  		InterfaceName:    "eth0",
  1224  		Disabled:         false,
  1225  		NoAutoStart:      false,
  1226  		ConfigType:       network.ConfigDHCP,
  1227  		ExtraConfig:      nil,
  1228  		GatewayAddress:   network.Address{},
  1229  		Address:          network.NewScopedAddress("192.168.2.1", network.ScopeCloudLocal),
  1230  	}, {
  1231  		DeviceIndex:      2,
  1232  		MACAddress:       "aa:bb:cc:dd:ee:f2",
  1233  		CIDR:             "192.168.3.1/24",
  1234  		ProviderSubnetId: "Virt",
  1235  		VLANTag:          0,
  1236  		InterfaceName:    "vnet1",
  1237  		Disabled:         false,
  1238  		NoAutoStart:      false,
  1239  		ConfigType:       network.ConfigDHCP,
  1240  		ExtraConfig:      nil,
  1241  		GatewayAddress:   network.Address{},
  1242  		Address:          network.NewScopedAddress("192.168.3.1", network.ScopeCloudLocal),
  1243  	}}
  1244  	network.SortInterfaceInfo(netInfo)
  1245  	c.Assert(netInfo, jc.DeepEquals, expectedInfo)
  1246  }
  1247  
  1248  func (suite *environSuite) TestSubnets(c *gc.C) {
  1249  	testInstance := suite.createSubnets(c, false)
  1250  
  1251  	netInfo, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{"LAN", "Virt", "WLAN"})
  1252  	c.Assert(err, jc.ErrorIsNil)
  1253  
  1254  	expectedInfo := []network.SubnetInfo{
  1255  		{CIDR: "192.168.2.1/24", ProviderId: "LAN", VLANTag: 42, AllocatableIPLow: net.ParseIP("192.168.2.0"), AllocatableIPHigh: net.ParseIP("192.168.2.127")},
  1256  		{CIDR: "192.168.3.1/24", ProviderId: "Virt", VLANTag: 0},
  1257  		{CIDR: "192.168.1.1/24", ProviderId: "WLAN", VLANTag: 0, AllocatableIPLow: net.ParseIP("192.168.1.129"), AllocatableIPHigh: net.ParseIP("192.168.1.255")}}
  1258  	c.Assert(netInfo, jc.DeepEquals, expectedInfo)
  1259  }
  1260  
  1261  func (suite *environSuite) TestSubnetsNoNetIds(c *gc.C) {
  1262  	testInstance := suite.createSubnets(c, false)
  1263  	_, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{})
  1264  	c.Assert(err, gc.ErrorMatches, "subnetIds must not be empty")
  1265  }
  1266  
  1267  func (suite *environSuite) TestSubnetsMissingNetwork(c *gc.C) {
  1268  	testInstance := suite.createSubnets(c, false)
  1269  	_, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{"WLAN", "Missing"})
  1270  	c.Assert(err, gc.ErrorMatches, "failed to find the following subnets: \\[Missing\\]")
  1271  }
  1272  
  1273  func (suite *environSuite) TestSubnetsNoDuplicates(c *gc.C) {
  1274  	testInstance := suite.createSubnets(c, true)
  1275  
  1276  	netInfo, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{"LAN", "Virt", "WLAN"})
  1277  	c.Assert(err, jc.ErrorIsNil)
  1278  
  1279  	expectedInfo := []network.SubnetInfo{
  1280  		{CIDR: "192.168.2.1/24", ProviderId: "LAN", VLANTag: 42, AllocatableIPLow: net.ParseIP("192.168.2.0"), AllocatableIPHigh: net.ParseIP("192.168.2.127")},
  1281  		{CIDR: "192.168.3.1/24", ProviderId: "Virt", VLANTag: 0},
  1282  		{CIDR: "192.168.1.1/24", ProviderId: "WLAN", VLANTag: 0, AllocatableIPLow: net.ParseIP("192.168.1.129"), AllocatableIPHigh: net.ParseIP("192.168.1.255")}}
  1283  	c.Assert(netInfo, jc.DeepEquals, expectedInfo)
  1284  }
  1285  
  1286  func (suite *environSuite) TestAllocateAddress(c *gc.C) {
  1287  	testInstance := suite.createSubnets(c, false)
  1288  	env := suite.makeEnviron()
  1289  
  1290  	// note that the default test server always succeeds if we provide a
  1291  	// valid instance id and net id
  1292  	err := env.AllocateAddress(testInstance.Id(), "LAN", network.Address{Value: "192.168.2.1"}, "foo", "bar")
  1293  	c.Assert(err, jc.ErrorIsNil)
  1294  }
  1295  
  1296  func (suite *environSuite) TestAllocateAddressDevices(c *gc.C) {
  1297  	suite.testMAASObject.TestServer.SetVersionJSON(`{"capabilities": ["networks-management","static-ipaddresses", "devices-management"]}`)
  1298  	testInstance := suite.createSubnets(c, false)
  1299  	env := suite.makeEnviron()
  1300  
  1301  	// note that the default test server always succeeds if we provide a
  1302  	// valid instance id and net id
  1303  	err := env.AllocateAddress(testInstance.Id(), "LAN", network.Address{Value: "192.168.2.1"}, "foo", "bar")
  1304  	c.Assert(err, jc.ErrorIsNil)
  1305  
  1306  	devicesArray := suite.getDeviceArray(c)
  1307  	c.Assert(devicesArray, gc.HasLen, 1)
  1308  
  1309  	device, err := devicesArray[0].GetMap()
  1310  	c.Assert(err, jc.ErrorIsNil)
  1311  
  1312  	hostname, err := device["hostname"].GetString()
  1313  	c.Assert(err, jc.ErrorIsNil)
  1314  	c.Assert(hostname, gc.Equals, "bar")
  1315  
  1316  	parent, err := device["parent"].GetString()
  1317  	c.Assert(err, jc.ErrorIsNil)
  1318  	trimmedId := strings.TrimRight(string(testInstance.Id()), "/")
  1319  	split := strings.Split(trimmedId, "/")
  1320  	maasId := split[len(split)-1]
  1321  	c.Assert(parent, gc.Equals, maasId)
  1322  
  1323  	addressesArray, err := device["ip_addresses"].GetArray()
  1324  	c.Assert(err, jc.ErrorIsNil)
  1325  	c.Assert(addressesArray, gc.HasLen, 1)
  1326  	address, err := addressesArray[0].GetString()
  1327  	c.Assert(err, jc.ErrorIsNil)
  1328  	c.Assert(address, gc.Equals, "192.168.2.1")
  1329  
  1330  	macArray, err := device["macaddress_set"].GetArray()
  1331  	c.Assert(err, jc.ErrorIsNil)
  1332  	c.Assert(macArray, gc.HasLen, 1)
  1333  	macMap, err := macArray[0].GetMap()
  1334  	c.Assert(err, jc.ErrorIsNil)
  1335  	mac, err := macMap["mac_address"].GetString()
  1336  	c.Assert(err, jc.ErrorIsNil)
  1337  	c.Assert(mac, gc.Equals, "foo")
  1338  }
  1339  
  1340  func (suite *environSuite) getDeviceArray(c *gc.C) []gomaasapi.JSONObject {
  1341  	devicesURL := "/api/1.0/devices/?op=list"
  1342  	resp, err := http.Get(suite.testMAASObject.TestServer.Server.URL + devicesURL)
  1343  	c.Assert(err, jc.ErrorIsNil)
  1344  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
  1345  
  1346  	defer resp.Body.Close()
  1347  	content, err := ioutil.ReadAll(resp.Body)
  1348  	c.Assert(err, jc.ErrorIsNil)
  1349  	result, err := gomaasapi.Parse(gomaasapi.Client{}, content)
  1350  	c.Assert(err, jc.ErrorIsNil)
  1351  
  1352  	devicesArray, err := result.GetArray()
  1353  	c.Assert(err, jc.ErrorIsNil)
  1354  	return devicesArray
  1355  }
  1356  
  1357  func (suite *environSuite) TestReleaseAddressDeletesDevice(c *gc.C) {
  1358  	suite.testMAASObject.TestServer.SetVersionJSON(`{"capabilities": ["networks-management","static-ipaddresses", "devices-management"]}`)
  1359  	testInstance := suite.createSubnets(c, false)
  1360  	env := suite.makeEnviron()
  1361  	addr := network.NewAddress("192.168.2.1")
  1362  	err := env.AllocateAddress(testInstance.Id(), "LAN", addr, "foo", "bar")
  1363  	c.Assert(err, jc.ErrorIsNil)
  1364  
  1365  	devicesArray := suite.getDeviceArray(c)
  1366  	c.Assert(devicesArray, gc.HasLen, 1)
  1367  
  1368  	err = env.ReleaseAddress(testInstance.Id(), "LAN", addr, "foo")
  1369  	c.Assert(err, jc.ErrorIsNil)
  1370  
  1371  	devicesArray = suite.getDeviceArray(c)
  1372  	c.Assert(devicesArray, gc.HasLen, 0)
  1373  }
  1374  
  1375  func (suite *environSuite) TestAllocateAddressInvalidInstance(c *gc.C) {
  1376  	env := suite.makeEnviron()
  1377  	addr := network.Address{Value: "192.168.2.1"}
  1378  	instId := instance.Id("foo")
  1379  	err := env.AllocateAddress(instId, "bar", addr, "foo", "bar")
  1380  	expected := fmt.Sprintf("failed to allocate address %q for instance %q.*", addr, instId)
  1381  	c.Assert(err, gc.ErrorMatches, expected)
  1382  }
  1383  
  1384  func (suite *environSuite) TestAllocateAddressMissingSubnet(c *gc.C) {
  1385  	testInstance := suite.createSubnets(c, false)
  1386  	env := suite.makeEnviron()
  1387  	err := env.AllocateAddress(testInstance.Id(), "bar", network.Address{Value: "192.168.2.1"}, "foo", "bar")
  1388  	c.Assert(errors.Cause(err), gc.ErrorMatches, "failed to find the following subnets: \\[bar\\]")
  1389  }
  1390  
  1391  func (suite *environSuite) TestAllocateAddressIPAddressUnavailable(c *gc.C) {
  1392  	testInstance := suite.createSubnets(c, false)
  1393  	env := suite.makeEnviron()
  1394  
  1395  	reserveIPAddress := func(ipaddresses gomaasapi.MAASObject, cidr string, addr network.Address) error {
  1396  		return gomaasapi.ServerError{StatusCode: 404}
  1397  	}
  1398  	suite.PatchValue(&ReserveIPAddress, reserveIPAddress)
  1399  
  1400  	ipAddress := network.Address{Value: "192.168.2.1"}
  1401  	err := env.AllocateAddress(testInstance.Id(), "LAN", ipAddress, "foo", "bar")
  1402  	c.Assert(errors.Cause(err), gc.Equals, environs.ErrIPAddressUnavailable)
  1403  	expected := fmt.Sprintf("failed to allocate address %q for instance %q.*", ipAddress, testInstance.Id())
  1404  	c.Assert(err, gc.ErrorMatches, expected)
  1405  }
  1406  
  1407  func (s *environSuite) TestPrecheckInstanceAvailZone(c *gc.C) {
  1408  	s.testMAASObject.TestServer.AddZone("zone1", "the grass is greener in zone1")
  1409  	env := s.makeEnviron()
  1410  	placement := "zone=zone1"
  1411  	err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement)
  1412  	c.Assert(err, jc.ErrorIsNil)
  1413  }
  1414  
  1415  func (suite *environSuite) TestReleaseAddress(c *gc.C) {
  1416  	testInstance := suite.createSubnets(c, false)
  1417  	env := suite.makeEnviron()
  1418  
  1419  	err := env.AllocateAddress(testInstance.Id(), "LAN", network.Address{Value: "192.168.2.1"}, "foo", "bar")
  1420  	c.Assert(err, jc.ErrorIsNil)
  1421  
  1422  	ipAddress := network.Address{Value: "192.168.2.1"}
  1423  	macAddress := "foobar"
  1424  	err = env.ReleaseAddress(testInstance.Id(), "bar", ipAddress, macAddress)
  1425  	c.Assert(err, jc.ErrorIsNil)
  1426  
  1427  	// by releasing again we can test that the first release worked, *and*
  1428  	// the error handling of ReleaseError
  1429  	err = env.ReleaseAddress(testInstance.Id(), "bar", ipAddress, macAddress)
  1430  	expected := fmt.Sprintf("(?s).*failed to release IP address %q from instance %q.*", ipAddress, testInstance.Id())
  1431  	c.Assert(err, gc.ErrorMatches, expected)
  1432  }
  1433  
  1434  func (suite *environSuite) TestReleaseAddressRetry(c *gc.C) {
  1435  	// Patch short attempt params.
  1436  	suite.PatchValue(&shortAttempt, utils.AttemptStrategy{
  1437  		Min: 5,
  1438  	})
  1439  	// Patch IP address release call to MAAS.
  1440  	retries := 0
  1441  	enoughRetries := 10
  1442  	suite.PatchValue(&ReleaseIPAddress, func(ipaddresses gomaasapi.MAASObject, addr network.Address) error {
  1443  		retries++
  1444  		if retries < enoughRetries {
  1445  			return errors.New("ouch")
  1446  		}
  1447  		return nil
  1448  	})
  1449  
  1450  	testInstance := suite.createSubnets(c, false)
  1451  	env := suite.makeEnviron()
  1452  
  1453  	err := env.AllocateAddress(testInstance.Id(), "LAN", network.Address{Value: "192.168.2.1"}, "foo", "bar")
  1454  	c.Assert(err, jc.ErrorIsNil)
  1455  
  1456  	// ReleaseAddress must fail with 5 retries.
  1457  	ipAddress := network.Address{Value: "192.168.2.1"}
  1458  	macAddress := "foobar"
  1459  	err = env.ReleaseAddress(testInstance.Id(), "bar", ipAddress, macAddress)
  1460  	expected := fmt.Sprintf("(?s).*failed to release IP address %q from instance %q: ouch", ipAddress, testInstance.Id())
  1461  	c.Assert(err, gc.ErrorMatches, expected)
  1462  	c.Assert(retries, gc.Equals, 5)
  1463  
  1464  	// Now let it succeed after 3 retries.
  1465  	retries = 0
  1466  	enoughRetries = 3
  1467  	err = env.ReleaseAddress(testInstance.Id(), "bar", ipAddress, macAddress)
  1468  	c.Assert(err, jc.ErrorIsNil)
  1469  	c.Assert(retries, gc.Equals, 3)
  1470  }
  1471  
  1472  func (s *environSuite) TestPrecheckInstanceAvailZoneUnknown(c *gc.C) {
  1473  	s.testMAASObject.TestServer.AddZone("zone1", "the grass is greener in zone1")
  1474  	env := s.makeEnviron()
  1475  	placement := "zone=zone2"
  1476  	err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement)
  1477  	c.Assert(err, gc.ErrorMatches, `invalid availability zone "zone2"`)
  1478  }
  1479  
  1480  func (s *environSuite) TestPrecheckInstanceAvailZonesUnsupported(c *gc.C) {
  1481  	env := s.makeEnviron()
  1482  	placement := "zone=test-unknown"
  1483  	err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement)
  1484  	c.Assert(err, jc.Satisfies, errors.IsNotImplemented)
  1485  }
  1486  
  1487  func (s *environSuite) TestPrecheckInvalidPlacement(c *gc.C) {
  1488  	env := s.makeEnviron()
  1489  	err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, "notzone=anything")
  1490  	c.Assert(err, gc.ErrorMatches, "unknown placement directive: notzone=anything")
  1491  }
  1492  
  1493  func (s *environSuite) TestPrecheckNodePlacement(c *gc.C) {
  1494  	env := s.makeEnviron()
  1495  	err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, "assumed_node_name")
  1496  	c.Assert(err, jc.ErrorIsNil)
  1497  }
  1498  
  1499  func (s *environSuite) TestStartInstanceAvailZone(c *gc.C) {
  1500  	// Add a node for the started instance.
  1501  	s.newNode(c, "thenode1", "host1", map[string]interface{}{"zone": "test-available"})
  1502  	s.testMAASObject.TestServer.AddZone("test-available", "description")
  1503  	inst, err := s.testStartInstanceAvailZone(c, "test-available")
  1504  	c.Assert(err, jc.ErrorIsNil)
  1505  	c.Assert(inst.(*maasInstance).zone(), gc.Equals, "test-available")
  1506  }
  1507  
  1508  func (s *environSuite) TestStartInstanceAvailZoneUnknown(c *gc.C) {
  1509  	s.testMAASObject.TestServer.AddZone("test-available", "description")
  1510  	_, err := s.testStartInstanceAvailZone(c, "test-unknown")
  1511  	c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`)
  1512  }
  1513  
  1514  func (s *environSuite) testStartInstanceAvailZone(c *gc.C, zone string) (instance.Instance, error) {
  1515  	env := s.bootstrap(c)
  1516  	params := environs.StartInstanceParams{Placement: "zone=" + zone}
  1517  	result, err := testing.StartInstanceWithParams(env, "1", params, nil)
  1518  	if err != nil {
  1519  		return nil, err
  1520  	}
  1521  	return result.Instance, nil
  1522  }
  1523  
  1524  func (s *environSuite) TestStartInstanceUnmetConstraints(c *gc.C) {
  1525  	env := s.bootstrap(c)
  1526  	s.newNode(c, "thenode1", "host1", nil)
  1527  	params := environs.StartInstanceParams{Constraints: constraints.MustParse("mem=8G")}
  1528  	_, err := testing.StartInstanceWithParams(env, "1", params, nil)
  1529  	c.Assert(err, gc.ErrorMatches, "cannot run instances:.* 409.*")
  1530  }
  1531  
  1532  func (s *environSuite) TestStartInstanceConstraints(c *gc.C) {
  1533  	env := s.bootstrap(c)
  1534  	s.newNode(c, "thenode1", "host1", nil)
  1535  	s.newNode(c, "thenode2", "host2", map[string]interface{}{"memory": 8192})
  1536  	params := environs.StartInstanceParams{Constraints: constraints.MustParse("mem=8G")}
  1537  	result, err := testing.StartInstanceWithParams(env, "1", params, nil)
  1538  	c.Assert(err, jc.ErrorIsNil)
  1539  	c.Assert(*result.Hardware.Mem, gc.Equals, uint64(8192))
  1540  }
  1541  
  1542  var nodeStorageAttrs = []map[string]interface{}{
  1543  	{
  1544  		"name":       "sdb",
  1545  		"id":         1,
  1546  		"id_path":    "/dev/disk/by-id/id_for_sda",
  1547  		"path":       "/dev/sdb",
  1548  		"model":      "Samsung_SSD_850_EVO_250GB",
  1549  		"block_size": 4096,
  1550  		"serial":     "S21NNSAFC38075L",
  1551  		"size":       uint64(250059350016),
  1552  	},
  1553  	{
  1554  		"name":       "sda",
  1555  		"id":         2,
  1556  		"path":       "/dev/sda",
  1557  		"model":      "Samsung_SSD_850_EVO_250GB",
  1558  		"block_size": 4096,
  1559  		"serial":     "XXXX",
  1560  		"size":       uint64(250059350016),
  1561  	},
  1562  	{
  1563  		"name":       "sdc",
  1564  		"id":         3,
  1565  		"path":       "/dev/sdc",
  1566  		"model":      "Samsung_SSD_850_EVO_250GB",
  1567  		"block_size": 4096,
  1568  		"serial":     "YYYYYYY",
  1569  		"size":       uint64(250059350016),
  1570  	},
  1571  }
  1572  
  1573  var storageConstraintAttrs = map[string]interface{}{
  1574  	"1": "1",
  1575  	"2": "root",
  1576  	"3": "3",
  1577  }
  1578  
  1579  func (s *environSuite) TestStartInstanceStorage(c *gc.C) {
  1580  	env := s.bootstrap(c)
  1581  	s.newNode(c, "thenode1", "host1", map[string]interface{}{
  1582  		"memory":                  8192,
  1583  		"physicalblockdevice_set": nodeStorageAttrs,
  1584  		"constraint_map":          storageConstraintAttrs,
  1585  	})
  1586  	params := environs.StartInstanceParams{Volumes: []storage.VolumeParams{
  1587  		{Tag: names.NewVolumeTag("1"), Size: 2000000},
  1588  		{Tag: names.NewVolumeTag("3"), Size: 2000000},
  1589  	}}
  1590  	result, err := testing.StartInstanceWithParams(env, "1", params, nil)
  1591  	c.Assert(err, jc.ErrorIsNil)
  1592  	c.Check(result.Volumes, jc.DeepEquals, []storage.Volume{
  1593  		{
  1594  			names.NewVolumeTag("1"),
  1595  			storage.VolumeInfo{
  1596  				Size:       238475,
  1597  				VolumeId:   "volume-1",
  1598  				HardwareId: "id_for_sda",
  1599  			},
  1600  		},
  1601  		{
  1602  			names.NewVolumeTag("3"),
  1603  			storage.VolumeInfo{
  1604  				Size:       238475,
  1605  				VolumeId:   "volume-3",
  1606  				HardwareId: "",
  1607  			},
  1608  		},
  1609  	})
  1610  	c.Assert(result.VolumeAttachments, jc.DeepEquals, []storage.VolumeAttachment{
  1611  		{
  1612  			names.NewVolumeTag("1"),
  1613  			names.NewMachineTag("1"),
  1614  			storage.VolumeAttachmentInfo{
  1615  				DeviceName: "",
  1616  				ReadOnly:   false,
  1617  			},
  1618  		},
  1619  		{
  1620  			names.NewVolumeTag("3"),
  1621  			names.NewMachineTag("1"),
  1622  			storage.VolumeAttachmentInfo{
  1623  				DeviceName: "sdc",
  1624  				ReadOnly:   false,
  1625  			},
  1626  		},
  1627  	})
  1628  }
  1629  
  1630  func (s *environSuite) TestStartInstanceUnsupportedStorage(c *gc.C) {
  1631  	env := s.bootstrap(c)
  1632  	s.newNode(c, "thenode1", "host1", map[string]interface{}{
  1633  		"memory": 8192,
  1634  	})
  1635  	params := environs.StartInstanceParams{Volumes: []storage.VolumeParams{
  1636  		{Tag: names.NewVolumeTag("1"), Size: 2000000},
  1637  		{Tag: names.NewVolumeTag("3"), Size: 2000000},
  1638  	}}
  1639  	_, err := testing.StartInstanceWithParams(env, "1", params, nil)
  1640  	c.Assert(err, gc.ErrorMatches, "the version of MAAS being used does not support Juju storage")
  1641  	operations := s.testMAASObject.TestServer.NodesOperations()
  1642  	c.Check(operations, gc.DeepEquals, []string{"acquire", "acquire", "release"})
  1643  	c.Assert(s.testMAASObject.TestServer.OwnedNodes()["node0"], jc.IsTrue)
  1644  	c.Assert(s.testMAASObject.TestServer.OwnedNodes()["thenode1"], jc.IsFalse)
  1645  }
  1646  
  1647  func (s *environSuite) TestGetAvailabilityZones(c *gc.C) {
  1648  	env := s.makeEnviron()
  1649  
  1650  	zones, err := env.AvailabilityZones()
  1651  	c.Assert(err, jc.Satisfies, errors.IsNotImplemented)
  1652  	c.Assert(zones, gc.IsNil)
  1653  
  1654  	s.testMAASObject.TestServer.AddZone("whatever", "andever")
  1655  	zones, err = env.AvailabilityZones()
  1656  	c.Assert(err, jc.ErrorIsNil)
  1657  	c.Assert(zones, gc.HasLen, 1)
  1658  	c.Assert(zones[0].Name(), gc.Equals, "whatever")
  1659  	c.Assert(zones[0].Available(), jc.IsTrue)
  1660  
  1661  	// A successful result is cached, currently for the lifetime
  1662  	// of the Environ. This will change if/when we have long-lived
  1663  	// Environs to cut down repeated IaaS requests.
  1664  	s.testMAASObject.TestServer.AddZone("somewhere", "outthere")
  1665  	zones, err = env.AvailabilityZones()
  1666  	c.Assert(err, jc.ErrorIsNil)
  1667  	c.Assert(zones, gc.HasLen, 1)
  1668  	c.Assert(zones[0].Name(), gc.Equals, "whatever")
  1669  }
  1670  
  1671  type mockAvailabilityZoneAllocations struct {
  1672  	group  []instance.Id // input param
  1673  	result []common.AvailabilityZoneInstances
  1674  	err    error
  1675  }
  1676  
  1677  func (m *mockAvailabilityZoneAllocations) AvailabilityZoneAllocations(
  1678  	e common.ZonedEnviron, group []instance.Id,
  1679  ) ([]common.AvailabilityZoneInstances, error) {
  1680  	m.group = group
  1681  	return m.result, m.err
  1682  }
  1683  
  1684  func (s *environSuite) newNode(c *gc.C, nodename, hostname string, attrs map[string]interface{}) {
  1685  	allAttrs := map[string]interface{}{
  1686  		"system_id":    nodename,
  1687  		"hostname":     hostname,
  1688  		"architecture": fmt.Sprintf("%s/generic", arch.HostArch()),
  1689  		"memory":       1024,
  1690  		"cpu_count":    1,
  1691  	}
  1692  	for k, v := range attrs {
  1693  		allAttrs[k] = v
  1694  	}
  1695  	data, err := json.Marshal(allAttrs)
  1696  	c.Assert(err, jc.ErrorIsNil)
  1697  	s.testMAASObject.TestServer.NewNode(string(data))
  1698  	lshwXML, err := s.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}})
  1699  	c.Assert(err, jc.ErrorIsNil)
  1700  	s.testMAASObject.TestServer.AddNodeDetails(nodename, lshwXML)
  1701  }
  1702  
  1703  func (s *environSuite) bootstrap(c *gc.C) environs.Environ {
  1704  	s.newNode(c, "node0", "bootstrap-host", nil)
  1705  	s.setupFakeTools(c)
  1706  	env := s.makeEnviron()
  1707  	err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{
  1708  		Placement: "bootstrap-host",
  1709  	})
  1710  	c.Assert(err, jc.ErrorIsNil)
  1711  	return env
  1712  }
  1713  
  1714  func (s *environSuite) TestStartInstanceDistributionParams(c *gc.C) {
  1715  	env := s.bootstrap(c)
  1716  	var mock mockAvailabilityZoneAllocations
  1717  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
  1718  
  1719  	// no distribution group specified
  1720  	s.newNode(c, "node1", "host1", nil)
  1721  	testing.AssertStartInstance(c, env, "1")
  1722  	c.Assert(mock.group, gc.HasLen, 0)
  1723  
  1724  	// distribution group specified: ensure it's passed through to AvailabilityZone.
  1725  	s.newNode(c, "node2", "host2", nil)
  1726  	expectedInstances := []instance.Id{"i-0", "i-1"}
  1727  	params := environs.StartInstanceParams{
  1728  		DistributionGroup: func() ([]instance.Id, error) {
  1729  			return expectedInstances, nil
  1730  		},
  1731  	}
  1732  	_, err := testing.StartInstanceWithParams(env, "1", params, nil)
  1733  	c.Assert(err, jc.ErrorIsNil)
  1734  	c.Assert(mock.group, gc.DeepEquals, expectedInstances)
  1735  }
  1736  
  1737  func (s *environSuite) TestStartInstanceDistributionErrors(c *gc.C) {
  1738  	env := s.bootstrap(c)
  1739  	mock := mockAvailabilityZoneAllocations{
  1740  		err: errors.New("AvailabilityZoneAllocations failed"),
  1741  	}
  1742  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
  1743  	_, _, _, err := testing.StartInstance(env, "1")
  1744  	c.Assert(err, gc.ErrorMatches, "cannot get availability zone allocations: AvailabilityZoneAllocations failed")
  1745  
  1746  	mock.err = nil
  1747  	dgErr := errors.New("DistributionGroup failed")
  1748  	params := environs.StartInstanceParams{
  1749  		DistributionGroup: func() ([]instance.Id, error) {
  1750  			return nil, dgErr
  1751  		},
  1752  	}
  1753  	_, err = testing.StartInstanceWithParams(env, "1", params, nil)
  1754  	c.Assert(err, gc.ErrorMatches, "cannot get distribution group: DistributionGroup failed")
  1755  }
  1756  
  1757  func (s *environSuite) TestStartInstanceDistribution(c *gc.C) {
  1758  	env := s.bootstrap(c)
  1759  	s.testMAASObject.TestServer.AddZone("test-available", "description")
  1760  	s.newNode(c, "node1", "host1", map[string]interface{}{"zone": "test-available"})
  1761  	inst, _ := testing.AssertStartInstance(c, env, "1")
  1762  	c.Assert(inst.(*maasInstance).zone(), gc.Equals, "test-available")
  1763  }
  1764  
  1765  func (s *environSuite) TestStartInstanceDistributionAZNotImplemented(c *gc.C) {
  1766  	env := s.bootstrap(c)
  1767  
  1768  	mock := mockAvailabilityZoneAllocations{err: errors.NotImplementedf("availability zones")}
  1769  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
  1770  
  1771  	// Instance will be created without an availability zone specified.
  1772  	s.newNode(c, "node1", "host1", nil)
  1773  	inst, _ := testing.AssertStartInstance(c, env, "1")
  1774  	c.Assert(inst.(*maasInstance).zone(), gc.Equals, "")
  1775  }
  1776  
  1777  func (s *environSuite) TestStartInstanceDistributionFailover(c *gc.C) {
  1778  	mock := mockAvailabilityZoneAllocations{
  1779  		result: []common.AvailabilityZoneInstances{{
  1780  			ZoneName: "zone1",
  1781  		}, {
  1782  			ZoneName: "zonelord",
  1783  		}, {
  1784  			ZoneName: "zone2",
  1785  		}},
  1786  	}
  1787  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
  1788  	s.testMAASObject.TestServer.AddZone("zone1", "description")
  1789  	s.testMAASObject.TestServer.AddZone("zone2", "description")
  1790  	s.newNode(c, "node2", "host2", map[string]interface{}{"zone": "zone2"})
  1791  
  1792  	env := s.bootstrap(c)
  1793  	inst, _ := testing.AssertStartInstance(c, env, "1")
  1794  	c.Assert(inst.(*maasInstance).zone(), gc.Equals, "zone2")
  1795  	c.Assert(s.testMAASObject.TestServer.NodesOperations(), gc.DeepEquals, []string{
  1796  		// one acquire for the bootstrap, three for StartInstance (with zone failover)
  1797  		"acquire", "acquire", "acquire", "acquire",
  1798  	})
  1799  	c.Assert(s.testMAASObject.TestServer.NodesOperationRequestValues(), gc.DeepEquals, []url.Values{{
  1800  		"name":       []string{"bootstrap-host"},
  1801  		"agent_name": []string{exampleAgentName},
  1802  	}, {
  1803  		"zone":       []string{"zone1"},
  1804  		"agent_name": []string{exampleAgentName},
  1805  	}, {
  1806  		"zone":       []string{"zonelord"},
  1807  		"agent_name": []string{exampleAgentName},
  1808  	}, {
  1809  		"zone":       []string{"zone2"},
  1810  		"agent_name": []string{exampleAgentName},
  1811  	}})
  1812  }
  1813  
  1814  func (s *environSuite) TestStartInstanceDistributionOneAssigned(c *gc.C) {
  1815  	mock := mockAvailabilityZoneAllocations{
  1816  		result: []common.AvailabilityZoneInstances{{
  1817  			ZoneName: "zone1",
  1818  		}, {
  1819  			ZoneName: "zone2",
  1820  		}},
  1821  	}
  1822  	s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations)
  1823  	s.testMAASObject.TestServer.AddZone("zone1", "description")
  1824  	s.testMAASObject.TestServer.AddZone("zone2", "description")
  1825  	s.newNode(c, "node1", "host1", map[string]interface{}{"zone": "zone1"})
  1826  	s.newNode(c, "node2", "host2", map[string]interface{}{"zone": "zone2"})
  1827  
  1828  	env := s.bootstrap(c)
  1829  	testing.AssertStartInstance(c, env, "1")
  1830  	c.Assert(s.testMAASObject.TestServer.NodesOperations(), gc.DeepEquals, []string{
  1831  		// one acquire for the bootstrap, one for StartInstance.
  1832  		"acquire", "acquire",
  1833  	})
  1834  }