github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/provider/maas/environ_whitebox_test.go (about)

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