github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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  	"fmt"
    10  	"io/ioutil"
    11  	"net/url"
    12  	"strings"
    13  	"text/template"
    14  
    15  	"github.com/juju/errors"
    16  	jc "github.com/juju/testing/checkers"
    17  	"github.com/juju/utils"
    18  	"github.com/juju/utils/set"
    19  	gc "launchpad.net/gocheck"
    20  	"launchpad.net/gomaasapi"
    21  	"launchpad.net/goyaml"
    22  
    23  	"github.com/juju/juju/constraints"
    24  	"github.com/juju/juju/environs"
    25  	"github.com/juju/juju/environs/bootstrap"
    26  	"github.com/juju/juju/environs/config"
    27  	"github.com/juju/juju/environs/imagemetadata"
    28  	"github.com/juju/juju/environs/network"
    29  	"github.com/juju/juju/environs/simplestreams"
    30  	"github.com/juju/juju/environs/storage"
    31  	envtesting "github.com/juju/juju/environs/testing"
    32  	envtools "github.com/juju/juju/environs/tools"
    33  	"github.com/juju/juju/instance"
    34  	"github.com/juju/juju/juju/testing"
    35  	coretesting "github.com/juju/juju/testing"
    36  	"github.com/juju/juju/tools"
    37  	"github.com/juju/juju/version"
    38  )
    39  
    40  type environSuite struct {
    41  	providerSuite
    42  }
    43  
    44  const (
    45  	allocatedNode = `{"system_id": "test-allocated"}`
    46  )
    47  
    48  var _ = gc.Suite(&environSuite{})
    49  
    50  // getTestConfig creates a customized sample MAAS provider configuration.
    51  func getTestConfig(name, server, oauth, secret string) *config.Config {
    52  	ecfg, err := newConfig(map[string]interface{}{
    53  		"name":            name,
    54  		"maas-server":     server,
    55  		"maas-oauth":      oauth,
    56  		"admin-secret":    secret,
    57  		"authorized-keys": "I-am-not-a-real-key",
    58  	})
    59  	if err != nil {
    60  		panic(err)
    61  	}
    62  	return ecfg.Config
    63  }
    64  
    65  func (suite *environSuite) setupFakeProviderStateFile(c *gc.C) {
    66  	suite.testMAASObject.TestServer.NewFile(bootstrap.StateFile, []byte("test file content"))
    67  }
    68  
    69  func (suite *environSuite) setupFakeTools(c *gc.C) {
    70  	stor := NewStorage(suite.makeEnviron())
    71  	envtesting.UploadFakeTools(c, stor)
    72  }
    73  
    74  func (suite *environSuite) setupFakeImageMetadata(c *gc.C) {
    75  	stor := NewStorage(suite.makeEnviron())
    76  	UseTestImageMetadata(c, stor)
    77  }
    78  
    79  func (suite *environSuite) addNode(jsonText string) instance.Id {
    80  	node := suite.testMAASObject.TestServer.NewNode(jsonText)
    81  	resourceURI, _ := node.GetField("resource_uri")
    82  	return instance.Id(resourceURI)
    83  }
    84  
    85  func (suite *environSuite) TestInstancesReturnsInstances(c *gc.C) {
    86  	id := suite.addNode(allocatedNode)
    87  	instances, err := suite.makeEnviron().Instances([]instance.Id{id})
    88  
    89  	c.Check(err, gc.IsNil)
    90  	c.Assert(instances, gc.HasLen, 1)
    91  	c.Assert(instances[0].Id(), gc.Equals, id)
    92  }
    93  
    94  func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfEmptyParameter(c *gc.C) {
    95  	suite.addNode(allocatedNode)
    96  	instances, err := suite.makeEnviron().Instances([]instance.Id{})
    97  
    98  	c.Check(err, gc.Equals, environs.ErrNoInstances)
    99  	c.Check(instances, gc.IsNil)
   100  }
   101  
   102  func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNilParameter(c *gc.C) {
   103  	suite.addNode(allocatedNode)
   104  	instances, err := suite.makeEnviron().Instances(nil)
   105  
   106  	c.Check(err, gc.Equals, environs.ErrNoInstances)
   107  	c.Check(instances, gc.IsNil)
   108  }
   109  
   110  func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNoneFound(c *gc.C) {
   111  	instances, err := suite.makeEnviron().Instances([]instance.Id{"unknown"})
   112  	c.Check(err, gc.Equals, environs.ErrNoInstances)
   113  	c.Check(instances, gc.IsNil)
   114  }
   115  
   116  func (suite *environSuite) TestAllInstances(c *gc.C) {
   117  	id := suite.addNode(allocatedNode)
   118  	instances, err := suite.makeEnviron().AllInstances()
   119  
   120  	c.Check(err, gc.IsNil)
   121  	c.Assert(instances, gc.HasLen, 1)
   122  	c.Assert(instances[0].Id(), gc.Equals, id)
   123  }
   124  
   125  func (suite *environSuite) TestAllInstancesReturnsEmptySliceIfNoInstance(c *gc.C) {
   126  	instances, err := suite.makeEnviron().AllInstances()
   127  
   128  	c.Check(err, gc.IsNil)
   129  	c.Check(instances, gc.HasLen, 0)
   130  }
   131  
   132  func (suite *environSuite) TestInstancesReturnsErrorIfPartialInstances(c *gc.C) {
   133  	known := suite.addNode(allocatedNode)
   134  	suite.addNode(`{"system_id": "test2"}`)
   135  	unknown := instance.Id("unknown systemID")
   136  	instances, err := suite.makeEnviron().Instances([]instance.Id{known, unknown})
   137  
   138  	c.Check(err, gc.Equals, environs.ErrPartialInstances)
   139  	c.Assert(instances, gc.HasLen, 2)
   140  	c.Check(instances[0].Id(), gc.Equals, known)
   141  	c.Check(instances[1], gc.IsNil)
   142  }
   143  
   144  func (suite *environSuite) TestStorageReturnsStorage(c *gc.C) {
   145  	env := suite.makeEnviron()
   146  	stor := env.Storage()
   147  	c.Check(stor, gc.NotNil)
   148  	// The Storage object is really a maasStorage.
   149  	specificStorage := stor.(*maasStorage)
   150  	// Its environment pointer refers back to its environment.
   151  	c.Check(specificStorage.environUnlocked, gc.Equals, env)
   152  }
   153  
   154  func decodeUserData(userData string) ([]byte, error) {
   155  	data, err := base64.StdEncoding.DecodeString(userData)
   156  	if err != nil {
   157  		return []byte(""), err
   158  	}
   159  	return utils.Gunzip(data)
   160  }
   161  
   162  const lshwXMLTemplate = `
   163  <?xml version="1.0" standalone="yes" ?>
   164  <!-- generated by lshw-B.02.16 -->
   165  <list>
   166  <node id="node1" claimed="true" class="system" handle="DMI:0001">
   167   <description>Computer</description>
   168   <product>VirtualBox ()</product>
   169   <width units="bits">64</width>
   170    <node id="core" claimed="true" class="bus" handle="DMI:0008">
   171     <description>Motherboard</description>
   172      <node id="pci" claimed="true" class="bridge" handle="PCIBUS:0000:00">
   173       <description>Host bridge</description>{{range $m, $n := .}}
   174        <node id="network:0" claimed="true" class="network" handle="PCI:0000:00:03.0">
   175         <description>Ethernet interface</description>
   176         <product>82540EM Gigabit Ethernet Controller</product>
   177         <logicalname>{{$n}}</logicalname>
   178         <serial>{{$m}}</serial>
   179        </node>{{end}}
   180      </node>
   181    </node>
   182  </node>
   183  </list>
   184  </list>
   185  `
   186  
   187  func (suite *environSuite) generateHWTemplate(netMacs map[string]string) (string, error) {
   188  	tmpl, err := template.New("test").Parse(lshwXMLTemplate)
   189  	if err != nil {
   190  		return "", err
   191  	}
   192  	var buf bytes.Buffer
   193  	err = tmpl.Execute(&buf, netMacs)
   194  	if err != nil {
   195  		return "", err
   196  	}
   197  	return string(buf.Bytes()), nil
   198  }
   199  
   200  func (suite *environSuite) TestStartInstanceStartsInstance(c *gc.C) {
   201  	suite.setupFakeTools(c)
   202  	env := suite.makeEnviron()
   203  	// Create node 0: it will be used as the bootstrap node.
   204  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`)
   205  	lshwXML, err := suite.generateHWTemplate(map[string]string{"aa:bb:cc:dd:ee:f0": "eth0"})
   206  	c.Assert(err, gc.IsNil)
   207  	suite.testMAASObject.TestServer.AddNodeDetails("node0", lshwXML)
   208  	err = bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{})
   209  	c.Assert(err, gc.IsNil)
   210  	// The bootstrap node has been acquired and started.
   211  	operations := suite.testMAASObject.TestServer.NodeOperations()
   212  	actions, found := operations["node0"]
   213  	c.Check(found, gc.Equals, true)
   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 the state holds the id of the bootstrap machine.
   218  	stateData, err := bootstrap.LoadState(env.Storage())
   219  	c.Assert(err, gc.IsNil)
   220  	c.Assert(stateData.StateInstances, gc.HasLen, 1)
   221  	insts, err := env.AllInstances()
   222  	c.Assert(err, gc.IsNil)
   223  	c.Assert(insts, gc.HasLen, 1)
   224  	c.Check(insts[0].Id(), gc.Equals, stateData.StateInstances[0])
   225  
   226  	// Create node 1: it will be used as instance number 1.
   227  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node1", "hostname": "host1"}`)
   228  	lshwXML, err = suite.generateHWTemplate(map[string]string{"aa:bb:cc:dd:ee:f1": "eth0"})
   229  	c.Assert(err, gc.IsNil)
   230  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
   231  	// TODO(wallyworld) - test instance metadata
   232  	instance, _ := testing.AssertStartInstance(c, env, "1")
   233  	c.Assert(err, gc.IsNil)
   234  	c.Check(instance, gc.NotNil)
   235  
   236  	// The instance number 1 has been acquired and started.
   237  	actions, found = operations["node1"]
   238  	c.Assert(found, gc.Equals, true)
   239  	c.Check(actions, gc.DeepEquals, []string{"acquire", "start"})
   240  
   241  	// The value of the "user data" parameter used when starting the node
   242  	// contains the run cmd used to write the machine information onto
   243  	// the node's filesystem.
   244  	requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues()
   245  	nodeRequestValues, found := requestValues["node1"]
   246  	c.Assert(found, gc.Equals, true)
   247  	c.Assert(len(nodeRequestValues), gc.Equals, 2)
   248  	userData := nodeRequestValues[1].Get("user_data")
   249  	decodedUserData, err := decodeUserData(userData)
   250  	c.Assert(err, gc.IsNil)
   251  	info := machineInfo{"host1"}
   252  	cloudinitRunCmd, err := info.cloudinitRunCmd()
   253  	c.Assert(err, gc.IsNil)
   254  	data, err := goyaml.Marshal(cloudinitRunCmd)
   255  	c.Assert(err, gc.IsNil)
   256  	c.Check(string(decodedUserData), gc.Matches, "(.|\n)*"+string(data)+"(\n|.)*")
   257  
   258  	// Trash the tools and try to start another instance.
   259  	envtesting.RemoveTools(c, env.Storage())
   260  	instance, _, _, err = testing.StartInstance(env, "2")
   261  	c.Check(instance, gc.IsNil)
   262  	c.Check(err, jc.Satisfies, errors.IsNotFound)
   263  }
   264  
   265  func uint64p(val uint64) *uint64 {
   266  	return &val
   267  }
   268  
   269  func stringp(val string) *string {
   270  	return &val
   271  }
   272  
   273  func (suite *environSuite) TestAcquireNode(c *gc.C) {
   274  	stor := NewStorage(suite.makeEnviron())
   275  	fakeTools := envtesting.MustUploadFakeToolsVersions(stor, version.Current)[0]
   276  	env := suite.makeEnviron()
   277  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`)
   278  
   279  	_, _, err := env.acquireNode("", constraints.Value{}, nil, nil, tools.List{fakeTools})
   280  
   281  	c.Check(err, gc.IsNil)
   282  	operations := suite.testMAASObject.TestServer.NodeOperations()
   283  	actions, found := operations["node0"]
   284  	c.Assert(found, gc.Equals, true)
   285  	c.Check(actions, gc.DeepEquals, []string{"acquire"})
   286  
   287  	// no "name" parameter should have been passed through
   288  	values := suite.testMAASObject.TestServer.NodeOperationRequestValues()["node0"][0]
   289  	_, found = values["name"]
   290  	c.Assert(found, jc.IsFalse)
   291  }
   292  
   293  func (suite *environSuite) TestAcquireNodeByName(c *gc.C) {
   294  	stor := NewStorage(suite.makeEnviron())
   295  	fakeTools := envtesting.MustUploadFakeToolsVersions(stor, version.Current)[0]
   296  	env := suite.makeEnviron()
   297  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`)
   298  
   299  	_, _, err := env.acquireNode("host0", constraints.Value{}, nil, nil, tools.List{fakeTools})
   300  
   301  	c.Check(err, gc.IsNil)
   302  	operations := suite.testMAASObject.TestServer.NodeOperations()
   303  	actions, found := operations["node0"]
   304  	c.Assert(found, gc.Equals, true)
   305  	c.Check(actions, gc.DeepEquals, []string{"acquire"})
   306  
   307  	// no "name" parameter should have been passed through
   308  	values := suite.testMAASObject.TestServer.NodeOperationRequestValues()["node0"][0]
   309  	nodeName := values.Get("name")
   310  	c.Assert(nodeName, gc.Equals, "host0")
   311  }
   312  
   313  func (suite *environSuite) TestAcquireNodeTakesConstraintsIntoAccount(c *gc.C) {
   314  	stor := NewStorage(suite.makeEnviron())
   315  	fakeTools := envtesting.MustUploadFakeToolsVersions(stor, version.Current)[0]
   316  	env := suite.makeEnviron()
   317  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`)
   318  	constraints := constraints.Value{Arch: stringp("arm"), Mem: uint64p(1024)}
   319  
   320  	_, _, err := env.acquireNode("", constraints, nil, nil, tools.List{fakeTools})
   321  
   322  	c.Check(err, gc.IsNil)
   323  	requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues()
   324  	nodeRequestValues, found := requestValues["node0"]
   325  	c.Assert(found, gc.Equals, true)
   326  	c.Assert(nodeRequestValues[0].Get("arch"), gc.Equals, "arm")
   327  	c.Assert(nodeRequestValues[0].Get("mem"), gc.Equals, "1024")
   328  }
   329  
   330  func (suite *environSuite) TestAcquireNodePassedAgentName(c *gc.C) {
   331  	stor := NewStorage(suite.makeEnviron())
   332  	fakeTools := envtesting.MustUploadFakeToolsVersions(stor, version.Current)[0]
   333  	env := suite.makeEnviron()
   334  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`)
   335  
   336  	_, _, err := env.acquireNode("", constraints.Value{}, nil, nil, tools.List{fakeTools})
   337  
   338  	c.Check(err, gc.IsNil)
   339  	requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues()
   340  	nodeRequestValues, found := requestValues["node0"]
   341  	c.Assert(found, gc.Equals, true)
   342  	c.Assert(nodeRequestValues[0].Get("agent_name"), gc.Equals, exampleAgentName)
   343  }
   344  
   345  var testValues = []struct {
   346  	constraints    constraints.Value
   347  	expectedResult url.Values
   348  }{
   349  	{constraints.Value{Arch: stringp("arm")}, url.Values{"arch": {"arm"}}},
   350  	{constraints.Value{CpuCores: uint64p(4)}, url.Values{"cpu_count": {"4"}}},
   351  	{constraints.Value{Mem: uint64p(1024)}, url.Values{"mem": {"1024"}}},
   352  
   353  	// CpuPower is ignored.
   354  	{constraints.Value{CpuPower: uint64p(1024)}, url.Values{}},
   355  
   356  	// RootDisk is ignored.
   357  	{constraints.Value{RootDisk: uint64p(8192)}, url.Values{}},
   358  	{constraints.Value{Tags: &[]string{"foo", "bar"}}, url.Values{"tags": {"foo,bar"}}},
   359  	{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"}}},
   360  }
   361  
   362  func (*environSuite) TestConvertConstraints(c *gc.C) {
   363  	for _, test := range testValues {
   364  		c.Check(convertConstraints(test.constraints), gc.DeepEquals, test.expectedResult)
   365  	}
   366  }
   367  
   368  var testNetworkValues = []struct {
   369  	includeNetworks []string
   370  	excludeNetworks []string
   371  	expectedResult  url.Values
   372  }{
   373  	{
   374  		nil,
   375  		nil,
   376  		url.Values{},
   377  	},
   378  	{
   379  		[]string{"included_net_1"},
   380  		nil,
   381  		url.Values{"networks": {"included_net_1"}},
   382  	},
   383  	{
   384  		nil,
   385  		[]string{"excluded_net_1"},
   386  		url.Values{"not_networks": {"excluded_net_1"}},
   387  	},
   388  	{
   389  		[]string{"included_net_1", "included_net_2"},
   390  		[]string{"excluded_net_1", "excluded_net_2"},
   391  		url.Values{
   392  			"networks":     {"included_net_1", "included_net_2"},
   393  			"not_networks": {"excluded_net_1", "excluded_net_2"},
   394  		},
   395  	},
   396  }
   397  
   398  func (*environSuite) TestConvertNetworks(c *gc.C) {
   399  	for _, test := range testNetworkValues {
   400  		var vals = url.Values{}
   401  		addNetworks(vals, test.includeNetworks, test.excludeNetworks)
   402  		c.Check(vals, gc.DeepEquals, test.expectedResult)
   403  	}
   404  }
   405  
   406  func (suite *environSuite) getInstance(systemId string) *maasInstance {
   407  	input := fmt.Sprintf(`{"system_id": %q}`, systemId)
   408  	node := suite.testMAASObject.TestServer.NewNode(input)
   409  	return &maasInstance{maasObject: &node, environ: suite.makeEnviron()}
   410  }
   411  
   412  func (suite *environSuite) getNetwork(name string, id int, vlanTag int) *gomaasapi.MAASObject {
   413  	var vlan string
   414  	if vlanTag == 0 {
   415  		vlan = "null"
   416  	} else {
   417  		vlan = fmt.Sprintf("%d", vlanTag)
   418  	}
   419  	var input string
   420  	input = fmt.Sprintf(`{"name": %q, "ip":"192.168.%d.1", "netmask": "255.255.255.0",`+
   421  		`"vlan_tag": %s, "description": "%s_%d_%d" }`, name, id, vlan, name, id, vlanTag)
   422  	network := suite.testMAASObject.TestServer.NewNetwork(input)
   423  	return &network
   424  }
   425  
   426  func (suite *environSuite) TestStopInstancesReturnsIfParameterEmpty(c *gc.C) {
   427  	suite.getInstance("test1")
   428  
   429  	err := suite.makeEnviron().StopInstances()
   430  	c.Check(err, gc.IsNil)
   431  	operations := suite.testMAASObject.TestServer.NodeOperations()
   432  	c.Check(operations, gc.DeepEquals, map[string][]string{})
   433  }
   434  
   435  func (suite *environSuite) TestStopInstancesStopsAndReleasesInstances(c *gc.C) {
   436  	suite.getInstance("test1")
   437  	suite.getInstance("test2")
   438  	suite.getInstance("test3")
   439  	// mark test1 and test2 as being allocated, but not test3.
   440  	// The release operation will ignore test3.
   441  	suite.testMAASObject.TestServer.OwnedNodes()["test1"] = true
   442  	suite.testMAASObject.TestServer.OwnedNodes()["test2"] = true
   443  
   444  	err := suite.makeEnviron().StopInstances("test1", "test2", "test3")
   445  	c.Check(err, gc.IsNil)
   446  	operations := suite.testMAASObject.TestServer.NodesOperations()
   447  	c.Check(operations, gc.DeepEquals, []string{"release"})
   448  	c.Assert(suite.testMAASObject.TestServer.OwnedNodes()["test1"], jc.IsFalse)
   449  	c.Assert(suite.testMAASObject.TestServer.OwnedNodes()["test2"], jc.IsFalse)
   450  }
   451  
   452  func (suite *environSuite) TestStateInfo(c *gc.C) {
   453  	env := suite.makeEnviron()
   454  	hostname := "test"
   455  	input := `{"system_id": "system_id", "hostname": "` + hostname + `"}`
   456  	node := suite.testMAASObject.TestServer.NewNode(input)
   457  	testInstance := &maasInstance{maasObject: &node, environ: suite.makeEnviron()}
   458  	err := bootstrap.SaveState(
   459  		env.Storage(),
   460  		&bootstrap.BootstrapState{StateInstances: []instance.Id{testInstance.Id()}})
   461  	c.Assert(err, gc.IsNil)
   462  
   463  	stateInfo, apiInfo, err := env.StateInfo()
   464  	c.Assert(err, gc.IsNil)
   465  
   466  	cfg := env.Config()
   467  	statePortSuffix := fmt.Sprintf(":%d", cfg.StatePort())
   468  	apiPortSuffix := fmt.Sprintf(":%d", cfg.APIPort())
   469  	c.Assert(stateInfo.Addrs, gc.DeepEquals, []string{hostname + statePortSuffix})
   470  	c.Assert(apiInfo.Addrs, gc.DeepEquals, []string{hostname + apiPortSuffix})
   471  }
   472  
   473  func (suite *environSuite) TestStateInfoFailsIfNoStateInstances(c *gc.C) {
   474  	env := suite.makeEnviron()
   475  
   476  	_, _, err := env.StateInfo()
   477  
   478  	c.Check(err, gc.Equals, environs.ErrNotBootstrapped)
   479  }
   480  
   481  func (suite *environSuite) TestDestroy(c *gc.C) {
   482  	env := suite.makeEnviron()
   483  	suite.getInstance("test1")
   484  	suite.testMAASObject.TestServer.OwnedNodes()["test1"] = true // simulate acquire
   485  	data := makeRandomBytes(10)
   486  	suite.testMAASObject.TestServer.NewFile("filename", data)
   487  	stor := env.Storage()
   488  
   489  	err := env.Destroy()
   490  	c.Check(err, gc.IsNil)
   491  
   492  	// Instances have been stopped.
   493  	operations := suite.testMAASObject.TestServer.NodesOperations()
   494  	c.Check(operations, gc.DeepEquals, []string{"release"})
   495  	c.Check(suite.testMAASObject.TestServer.OwnedNodes()["test1"], jc.IsFalse)
   496  	// Files have been cleaned up.
   497  	listing, err := storage.List(stor, "")
   498  	c.Assert(err, gc.IsNil)
   499  	c.Check(listing, gc.DeepEquals, []string{})
   500  }
   501  
   502  func (suite *environSuite) TestBootstrapSucceeds(c *gc.C) {
   503  	suite.setupFakeTools(c)
   504  	env := suite.makeEnviron()
   505  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "thenode", "hostname": "host"}`)
   506  	lshwXML, err := suite.generateHWTemplate(map[string]string{"aa:bb:cc:dd:ee:f0": "eth0"})
   507  	c.Assert(err, gc.IsNil)
   508  	suite.testMAASObject.TestServer.AddNodeDetails("thenode", lshwXML)
   509  	err = bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{})
   510  	c.Assert(err, gc.IsNil)
   511  }
   512  
   513  func (suite *environSuite) TestBootstrapFailsIfNoTools(c *gc.C) {
   514  	suite.setupFakeTools(c)
   515  	env := suite.makeEnviron()
   516  	// Can't RemoveAllTools, no public storage.
   517  	envtesting.RemoveTools(c, env.Storage())
   518  	// Disable auto-uploading by setting the agent version.
   519  	cfg, err := env.Config().Apply(map[string]interface{}{
   520  		"agent-version": version.Current.Number.String(),
   521  	})
   522  	c.Assert(err, gc.IsNil)
   523  	err = env.SetConfig(cfg)
   524  	c.Assert(err, gc.IsNil)
   525  	err = bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{})
   526  	stripped := strings.Replace(err.Error(), "\n", "", -1)
   527  	c.Check(stripped,
   528  		gc.Matches,
   529  		"cannot upload bootstrap tools: Juju cannot bootstrap because no tools are available for your environment.*")
   530  }
   531  
   532  func (suite *environSuite) TestBootstrapFailsIfNoNodes(c *gc.C) {
   533  	suite.setupFakeTools(c)
   534  	env := suite.makeEnviron()
   535  	err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{})
   536  	// Since there are no nodes, the attempt to allocate one returns a
   537  	// 409: Conflict.
   538  	c.Check(err, gc.ErrorMatches, ".*409.*")
   539  }
   540  
   541  func assertSourceContents(c *gc.C, source simplestreams.DataSource, filename string, content []byte) {
   542  	rc, _, err := source.Fetch(filename)
   543  	c.Assert(err, gc.IsNil)
   544  	defer rc.Close()
   545  	retrieved, err := ioutil.ReadAll(rc)
   546  	c.Assert(err, gc.IsNil)
   547  	c.Assert(retrieved, gc.DeepEquals, content)
   548  }
   549  
   550  func (suite *environSuite) assertGetImageMetadataSources(c *gc.C, stream, officialSourcePath string) {
   551  	// Make an env configured with the stream.
   552  	testAttrs := maasEnvAttrs
   553  	testAttrs = testAttrs.Merge(coretesting.Attrs{
   554  		"maas-server": suite.testMAASObject.TestServer.URL,
   555  	})
   556  	if stream != "" {
   557  		testAttrs = testAttrs.Merge(coretesting.Attrs{
   558  			"image-stream": stream,
   559  		})
   560  	}
   561  	attrs := coretesting.FakeConfig().Merge(testAttrs)
   562  	cfg, err := config.New(config.NoDefaults, attrs)
   563  	c.Assert(err, gc.IsNil)
   564  	env, err := NewEnviron(cfg)
   565  	c.Assert(err, gc.IsNil)
   566  
   567  	// Add a dummy file to storage so we can use that to check the
   568  	// obtained source later.
   569  	data := makeRandomBytes(10)
   570  	stor := NewStorage(env)
   571  	err = stor.Put("images/filename", bytes.NewBuffer([]byte(data)), int64(len(data)))
   572  	c.Assert(err, gc.IsNil)
   573  	sources, err := imagemetadata.GetMetadataSources(env)
   574  	c.Assert(err, gc.IsNil)
   575  	c.Assert(len(sources), gc.Equals, 2)
   576  	assertSourceContents(c, sources[0], "filename", data)
   577  	url, err := sources[1].URL("")
   578  	c.Assert(err, gc.IsNil)
   579  	c.Assert(url, gc.Equals, fmt.Sprintf("http://cloud-images.ubuntu.com/%s/", officialSourcePath))
   580  }
   581  
   582  func (suite *environSuite) TestGetImageMetadataSources(c *gc.C) {
   583  	suite.assertGetImageMetadataSources(c, "", "releases")
   584  	suite.assertGetImageMetadataSources(c, "released", "releases")
   585  	suite.assertGetImageMetadataSources(c, "daily", "daily")
   586  }
   587  
   588  func (suite *environSuite) TestGetToolsMetadataSources(c *gc.C) {
   589  	env := suite.makeEnviron()
   590  	// Add a dummy file to storage so we can use that to check the
   591  	// obtained source later.
   592  	data := makeRandomBytes(10)
   593  	stor := NewStorage(env)
   594  	err := stor.Put("tools/filename", bytes.NewBuffer([]byte(data)), int64(len(data)))
   595  	c.Assert(err, gc.IsNil)
   596  	sources, err := envtools.GetMetadataSources(env)
   597  	c.Assert(err, gc.IsNil)
   598  	c.Assert(len(sources), gc.Equals, 1)
   599  	assertSourceContents(c, sources[0], "filename", data)
   600  }
   601  
   602  func (suite *environSuite) TestSupportedArchitectures(c *gc.C) {
   603  	suite.setupFakeImageMetadata(c)
   604  	env := suite.makeEnviron()
   605  	a, err := env.SupportedArchitectures()
   606  	c.Assert(err, gc.IsNil)
   607  	c.Assert(a, jc.SameContents, []string{"amd64"})
   608  }
   609  
   610  func (suite *environSuite) TestConstraintsValidator(c *gc.C) {
   611  	suite.setupFakeImageMetadata(c)
   612  	env := suite.makeEnviron()
   613  	validator, err := env.ConstraintsValidator()
   614  	c.Assert(err, gc.IsNil)
   615  	cons := constraints.MustParse("arch=amd64 cpu-power=10 instance-type=foo")
   616  	unsupported, err := validator.Validate(cons)
   617  	c.Assert(err, gc.IsNil)
   618  	c.Assert(unsupported, jc.SameContents, []string{"cpu-power", "instance-type"})
   619  }
   620  
   621  func (suite *environSuite) TestConstraintsValidatorVocab(c *gc.C) {
   622  	suite.setupFakeImageMetadata(c)
   623  	env := suite.makeEnviron()
   624  	validator, err := env.ConstraintsValidator()
   625  	c.Assert(err, gc.IsNil)
   626  	cons := constraints.MustParse("arch=ppc64")
   627  	_, err = validator.Validate(cons)
   628  	c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=ppc64\nvalid values are:.*")
   629  }
   630  
   631  func (suite *environSuite) TestGetNetworkMACs(c *gc.C) {
   632  	suite.setupFakeTools(c)
   633  	env := suite.makeEnviron()
   634  
   635  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node_1"}`)
   636  	suite.testMAASObject.TestServer.NewNode(`{"system_id": "node_2"}`)
   637  	suite.testMAASObject.TestServer.NewNetwork(`{"name": "net_1"}`)
   638  	suite.testMAASObject.TestServer.NewNetwork(`{"name": "net_2"}`)
   639  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_2", "net_2", "aa:bb:cc:dd:ee:22")
   640  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_1", "net_1", "aa:bb:cc:dd:ee:11")
   641  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_2", "net_1", "aa:bb:cc:dd:ee:21")
   642  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_1", "net_2", "aa:bb:cc:dd:ee:12")
   643  
   644  	networks, err := env.getNetworkMACs("net_1")
   645  	c.Assert(err, gc.IsNil)
   646  	c.Check(networks, jc.SameContents, []string{"aa:bb:cc:dd:ee:11", "aa:bb:cc:dd:ee:21"})
   647  
   648  	networks, err = env.getNetworkMACs("net_2")
   649  	c.Assert(err, gc.IsNil)
   650  	c.Check(networks, jc.SameContents, []string{"aa:bb:cc:dd:ee:12", "aa:bb:cc:dd:ee:22"})
   651  
   652  	networks, err = env.getNetworkMACs("net_3")
   653  	c.Check(networks, gc.HasLen, 0)
   654  	c.Assert(err, gc.IsNil)
   655  }
   656  
   657  func (suite *environSuite) TestGetInstanceNetworks(c *gc.C) {
   658  	suite.getNetwork("test_network", 123, 321)
   659  	test_instance := suite.getInstance("instance_for_network")
   660  	suite.testMAASObject.TestServer.ConnectNodeToNetwork("instance_for_network", "test_network")
   661  	networks, err := suite.makeEnviron().getInstanceNetworks(test_instance)
   662  	c.Assert(err, gc.IsNil)
   663  	c.Check(networks, gc.DeepEquals, []networkDetails{
   664  		{Name: "test_network", IP: "192.168.123.1", Mask: "255.255.255.0", VLANTag: 321,
   665  			Description: "test_network_123_321"},
   666  	})
   667  }
   668  
   669  // A typical lshw XML dump with lots of things left out.
   670  const lshwXMLTestExtractInterfaces = `
   671  <?xml version="1.0" standalone="yes" ?>
   672  <!-- generated by lshw-B.02.16 -->
   673  <list>
   674  <node id="machine" claimed="true" class="system" handle="DMI:0001">
   675   <description>Notebook</description>
   676   <product>MyMachine</product>
   677   <version>1.0</version>
   678   <width units="bits">64</width>
   679    <node id="core" claimed="true" class="bus" handle="DMI:0002">
   680     <description>Motherboard</description>
   681      <node id="cpu" claimed="true" class="processor" handle="DMI:0004">
   682       <description>CPU</description>
   683        <node id="pci:2" claimed="true" class="bridge" handle="PCIBUS:0000:03">
   684          <node id="network" claimed="true" class="network" handle="PCI:0000:03:00.0">
   685           <logicalname>wlan0</logicalname>
   686           <serial>aa:bb:cc:dd:ee:ff</serial>
   687          </node>
   688          <node id="network" claimed="true" class="network" handle="PCI:0000:04:00.0">
   689           <logicalname>eth0</logicalname>
   690           <serial>aa:bb:cc:dd:ee:f1</serial>
   691          </node>
   692        </node>
   693      </node>
   694    </node>
   695    <node id="network:0" claimed="true" class="network" handle="">
   696     <logicalname>vnet1</logicalname>
   697     <serial>aa:bb:cc:dd:ee:f2</serial>
   698    </node>
   699  </node>
   700  </list>
   701  `
   702  
   703  func (suite *environSuite) TestExtractInterfaces(c *gc.C) {
   704  	inst := suite.getInstance("testInstance")
   705  	interfaces, err := extractInterfaces(inst, []byte(lshwXMLTestExtractInterfaces))
   706  	c.Assert(err, gc.IsNil)
   707  	c.Check(interfaces, jc.DeepEquals, map[string]string{
   708  		"aa:bb:cc:dd:ee:ff": "wlan0",
   709  		"aa:bb:cc:dd:ee:f1": "eth0",
   710  		"aa:bb:cc:dd:ee:f2": "vnet1",
   711  	})
   712  }
   713  
   714  func (suite *environSuite) TestGetInstanceNetworkInterfaces(c *gc.C) {
   715  	inst := suite.getInstance("testInstance")
   716  	templateInterfaces := map[string]string{
   717  		"aa:bb:cc:dd:ee:ff": "wlan0",
   718  		"aa:bb:cc:dd:ee:f1": "eth0",
   719  		"aa:bb:cc:dd:ee:f2": "vnet1",
   720  	}
   721  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
   722  	c.Assert(err, gc.IsNil)
   723  
   724  	suite.testMAASObject.TestServer.AddNodeDetails("testInstance", lshwXML)
   725  	interfaces, err := inst.environ.getInstanceNetworkInterfaces(inst)
   726  	c.Assert(err, gc.IsNil)
   727  	c.Check(interfaces, jc.DeepEquals, templateInterfaces)
   728  }
   729  
   730  func (suite *environSuite) TestSetupNetworks(c *gc.C) {
   731  	test_instance := suite.getInstance("node1")
   732  	templateInterfaces := map[string]string{
   733  		"aa:bb:cc:dd:ee:ff": "wlan0",
   734  		"aa:bb:cc:dd:ee:f1": "eth0",
   735  		"aa:bb:cc:dd:ee:f2": "vnet1",
   736  	}
   737  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
   738  	c.Assert(err, gc.IsNil)
   739  
   740  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
   741  	suite.getNetwork("LAN", 2, 42)
   742  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1")
   743  	suite.getNetwork("Virt", 3, 0)
   744  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f2")
   745  	suite.getNetwork("WLAN", 1, 0)
   746  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "WLAN", "aa:bb:cc:dd:ee:ff")
   747  	networkInfo, err := suite.makeEnviron().setupNetworks(test_instance, set.NewStrings("LAN", "Virt"))
   748  	c.Assert(err, gc.IsNil)
   749  
   750  	// Note: order of networks is based on lshwXML
   751  	c.Check(networkInfo, jc.SameContents, []network.Info{
   752  		network.Info{
   753  			MACAddress:    "aa:bb:cc:dd:ee:ff",
   754  			CIDR:          "192.168.1.1/24",
   755  			NetworkName:   "WLAN",
   756  			ProviderId:    "WLAN",
   757  			VLANTag:       0,
   758  			InterfaceName: "wlan0",
   759  			IsVirtual:     false,
   760  			Disabled:      true,
   761  		},
   762  		network.Info{
   763  			MACAddress:    "aa:bb:cc:dd:ee:f1",
   764  			CIDR:          "192.168.2.1/24",
   765  			NetworkName:   "LAN",
   766  			ProviderId:    "LAN",
   767  			VLANTag:       42,
   768  			InterfaceName: "eth0",
   769  			IsVirtual:     true,
   770  			Disabled:      false,
   771  		},
   772  		network.Info{
   773  			MACAddress:    "aa:bb:cc:dd:ee:f2",
   774  			CIDR:          "192.168.3.1/24",
   775  			NetworkName:   "Virt",
   776  			ProviderId:    "Virt",
   777  			VLANTag:       0,
   778  			InterfaceName: "vnet1",
   779  			IsVirtual:     false,
   780  			Disabled:      false,
   781  		},
   782  	})
   783  }
   784  
   785  // The same test, but now "Virt" network does not have matched MAC address
   786  func (suite *environSuite) TestSetupNetworksPartialMatch(c *gc.C) {
   787  	test_instance := suite.getInstance("node1")
   788  	templateInterfaces := map[string]string{
   789  		"aa:bb:cc:dd:ee:ff": "wlan0",
   790  		"aa:bb:cc:dd:ee:f1": "eth0",
   791  		"aa:bb:cc:dd:ee:f2": "vnet1",
   792  	}
   793  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
   794  	c.Assert(err, gc.IsNil)
   795  
   796  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
   797  	suite.getNetwork("LAN", 2, 42)
   798  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1")
   799  	suite.getNetwork("Virt", 3, 0)
   800  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f3")
   801  	networkInfo, err := suite.makeEnviron().setupNetworks(test_instance, set.NewStrings("LAN"))
   802  	c.Assert(err, gc.IsNil)
   803  
   804  	// Note: order of networks is based on lshwXML
   805  	c.Check(networkInfo, jc.SameContents, []network.Info{
   806  		network.Info{
   807  			MACAddress:    "aa:bb:cc:dd:ee:f1",
   808  			CIDR:          "192.168.2.1/24",
   809  			NetworkName:   "LAN",
   810  			ProviderId:    "LAN",
   811  			VLANTag:       42,
   812  			InterfaceName: "eth0",
   813  			IsVirtual:     true,
   814  			Disabled:      false,
   815  		},
   816  	})
   817  }
   818  
   819  // The same test, but now no networks have matched MAC
   820  func (suite *environSuite) TestSetupNetworksNoMatch(c *gc.C) {
   821  	test_instance := suite.getInstance("node1")
   822  	templateInterfaces := map[string]string{
   823  		"aa:bb:cc:dd:ee:ff": "wlan0",
   824  		"aa:bb:cc:dd:ee:f1": "eth0",
   825  		"aa:bb:cc:dd:ee:f2": "vnet1",
   826  	}
   827  	lshwXML, err := suite.generateHWTemplate(templateInterfaces)
   828  	c.Assert(err, gc.IsNil)
   829  
   830  	suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML)
   831  	suite.getNetwork("Virt", 3, 0)
   832  	suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f3")
   833  	networkInfo, err := suite.makeEnviron().setupNetworks(test_instance, set.NewStrings("Virt"))
   834  	c.Assert(err, gc.IsNil)
   835  
   836  	// Note: order of networks is based on lshwXML
   837  	c.Check(networkInfo, gc.HasLen, 0)
   838  }
   839  
   840  func (suite *environSuite) TestSupportNetworks(c *gc.C) {
   841  	env := suite.makeEnviron()
   842  	c.Assert(env.SupportNetworks(), jc.IsTrue)
   843  }