github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/container/kvm/kvm_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package kvm_test
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/testing"
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/utils/arch"
    16  	gc "gopkg.in/check.v1"
    17  
    18  	"github.com/juju/juju/constraints"
    19  	"github.com/juju/juju/container"
    20  	"github.com/juju/juju/container/kvm"
    21  	kvmtesting "github.com/juju/juju/container/kvm/testing"
    22  	containertesting "github.com/juju/juju/container/testing"
    23  	"github.com/juju/juju/instance"
    24  	"github.com/juju/juju/network"
    25  	coretesting "github.com/juju/juju/testing"
    26  )
    27  
    28  type KVMSuite struct {
    29  	kvmtesting.TestSuite
    30  	manager container.Manager
    31  }
    32  
    33  var _ = gc.Suite(&KVMSuite{})
    34  
    35  func (s *KVMSuite) SetUpTest(c *gc.C) {
    36  	s.TestSuite.SetUpTest(c)
    37  	var err error
    38  	s.manager, err = kvm.NewContainerManager(
    39  		container.ManagerConfig{container.ConfigModelUUID: coretesting.ModelTag.Id()})
    40  	c.Assert(err, jc.ErrorIsNil)
    41  }
    42  
    43  func (*KVMSuite) TestManagerModelUUIDNeeded(c *gc.C) {
    44  	manager, err := kvm.NewContainerManager(container.ManagerConfig{container.ConfigModelUUID: ""})
    45  	c.Assert(err, gc.ErrorMatches, "model UUID is required")
    46  	c.Assert(manager, gc.IsNil)
    47  }
    48  
    49  func (*KVMSuite) TestManagerWarnsAboutUnknownOption(c *gc.C) {
    50  	_, err := kvm.NewContainerManager(container.ManagerConfig{
    51  		container.ConfigModelUUID: coretesting.ModelTag.Id(),
    52  		"shazam":                  "Captain Marvel",
    53  	})
    54  	c.Assert(err, jc.ErrorIsNil)
    55  	c.Assert(c.GetTestLog(), jc.Contains, `INFO juju.container unused config option: "shazam" -> "Captain Marvel"`)
    56  }
    57  
    58  func (s *KVMSuite) TestListInitiallyEmpty(c *gc.C) {
    59  	containers, err := s.manager.ListContainers()
    60  	c.Assert(err, jc.ErrorIsNil)
    61  	c.Assert(containers, gc.HasLen, 0)
    62  }
    63  
    64  func (s *KVMSuite) createRunningContainer(c *gc.C, name string) kvm.Container {
    65  	kvmContainer := s.ContainerFactory.New(name)
    66  	network := container.BridgeNetworkConfig("testbr0", 0, nil)
    67  	c.Assert(kvmContainer.Start(kvm.StartParams{
    68  		Series:       "quantal",
    69  		Arch:         arch.HostArch(),
    70  		UserDataFile: "userdata.txt",
    71  		Network:      network}), gc.IsNil)
    72  	return kvmContainer
    73  }
    74  
    75  func (s *KVMSuite) TestListMatchesManagerName(c *gc.C) {
    76  	s.createRunningContainer(c, "juju-06f00d-match1")
    77  	s.createRunningContainer(c, "juju-06f00d-match2")
    78  	s.createRunningContainer(c, "testNoMatch")
    79  	s.createRunningContainer(c, "other")
    80  	containers, err := s.manager.ListContainers()
    81  	c.Assert(err, jc.ErrorIsNil)
    82  	c.Assert(containers, gc.HasLen, 2)
    83  	expectedIds := []instance.Id{"juju-06f00d-match1", "juju-06f00d-match2"}
    84  	ids := []instance.Id{containers[0].Id(), containers[1].Id()}
    85  	c.Assert(ids, jc.SameContents, expectedIds)
    86  }
    87  
    88  func (s *KVMSuite) TestListMatchesRunningContainers(c *gc.C) {
    89  	running := s.createRunningContainer(c, "juju-06f00d-running")
    90  	s.ContainerFactory.New("juju-06f00d-stopped")
    91  	containers, err := s.manager.ListContainers()
    92  	c.Assert(err, jc.ErrorIsNil)
    93  	c.Assert(containers, gc.HasLen, 1)
    94  	c.Assert(string(containers[0].Id()), gc.Equals, running.Name())
    95  }
    96  
    97  func (s *KVMSuite) TestCreateContainer(c *gc.C) {
    98  	instance := containertesting.CreateContainer(c, s.manager, "1/kvm/0")
    99  	name := string(instance.Id())
   100  	cloudInitFilename := filepath.Join(s.ContainerDir, name, "cloud-init")
   101  	containertesting.AssertCloudInit(c, cloudInitFilename)
   102  }
   103  
   104  func (s *KVMSuite) TestWriteTemplate(c *gc.C) {
   105  	params := kvm.CreateMachineParams{
   106  		Hostname:      "foo-bar",
   107  		NetworkBridge: "br0",
   108  		Interfaces: []network.InterfaceInfo{{
   109  			InterfaceName:       "eth0",
   110  			MACAddress:          "00:16:3e:20:b0:11",
   111  			ParentInterfaceName: "br-eth0.10",
   112  		}, {
   113  			InterfaceName:       "eth42",
   114  			MACAddress:          "00:16:3e:20:b0:12",
   115  			ParentInterfaceName: "virbr42",
   116  		}},
   117  	}
   118  	tempDir := c.MkDir()
   119  
   120  	templatePath := filepath.Join(tempDir, "kvm.xml")
   121  	err := kvm.WriteTemplate(templatePath, params)
   122  	c.Assert(err, jc.ErrorIsNil)
   123  	templateBytes, err := ioutil.ReadFile(templatePath)
   124  	c.Assert(err, jc.ErrorIsNil)
   125  
   126  	template := string(templateBytes)
   127  
   128  	c.Check(template, jc.Contains, "<name>foo-bar</name>")
   129  	c.Check(strings.Count(template, "<interface type='bridge'>"), gc.Equals, 2)
   130  	c.Check(template, jc.Contains, "<source bridge='br-eth0.10'/>")
   131  	c.Check(template, jc.Contains, "<mac address='00:16:3e:20:b0:11'/>")
   132  	c.Check(template, jc.Contains, "<guest dev='eth0'/>")
   133  	c.Check(template, jc.Contains, "<source bridge='virbr42'/>")
   134  	c.Check(template, jc.Contains, "<mac address='00:16:3e:20:b0:12'/>")
   135  	c.Check(template, jc.Contains, "<guest dev='eth42'/>")
   136  }
   137  
   138  func (s *KVMSuite) TestCreateMachineUsesTemplate(c *gc.C) {
   139  	const uvtKvmBinName = "uvt-kvm"
   140  	testing.PatchExecutableAsEchoArgs(c, s, uvtKvmBinName)
   141  
   142  	tempDir := c.MkDir()
   143  	params := kvm.CreateMachineParams{
   144  		Hostname:      "foo-bar",
   145  		NetworkBridge: "br0",
   146  		Interfaces: []network.InterfaceInfo{
   147  			{MACAddress: "00:16:3e:20:b0:11"},
   148  		},
   149  		UserDataFile: filepath.Join(tempDir, "something"),
   150  	}
   151  
   152  	err := kvm.CreateMachine(params)
   153  	c.Assert(err, jc.ErrorIsNil)
   154  
   155  	expectedArgs := []string{
   156  		"create",
   157  		"--log-console-output",
   158  		"--user-data",
   159  		filepath.Join(tempDir, "something"),
   160  		"--template",
   161  		filepath.Join(tempDir, "kvm-template.xml"),
   162  		"foo-bar",
   163  	}
   164  
   165  	testing.AssertEchoArgs(c, uvtKvmBinName, expectedArgs...)
   166  }
   167  
   168  func (s *KVMSuite) TestDestroyContainer(c *gc.C) {
   169  	instance := containertesting.CreateContainer(c, s.manager, "1/kvm/0")
   170  
   171  	err := s.manager.DestroyContainer(instance.Id())
   172  	c.Assert(err, jc.ErrorIsNil)
   173  
   174  	name := string(instance.Id())
   175  	// Check that the container dir is no longer in the container dir
   176  	c.Assert(filepath.Join(s.ContainerDir, name), jc.DoesNotExist)
   177  	// but instead, in the removed container dir
   178  	c.Assert(filepath.Join(s.RemovedDir, name), jc.IsDirectory)
   179  }
   180  
   181  // Test that CreateContainer creates proper startParams.
   182  func (s *KVMSuite) TestCreateContainerUtilizesReleaseSimpleStream(c *gc.C) {
   183  
   184  	// Mock machineConfig with a mocked simple stream URL.
   185  	instanceConfig, err := containertesting.MockMachineConfig("1/kvm/0")
   186  	c.Assert(err, jc.ErrorIsNil)
   187  
   188  	// CreateContainer sets TestStartParams internally; we call this
   189  	// purely for the side-effect.
   190  	containertesting.CreateContainerWithMachineConfig(c, s.manager, instanceConfig)
   191  
   192  	c.Assert(kvm.TestStartParams.ImageDownloadURL, gc.Equals, "")
   193  }
   194  
   195  // Test that CreateContainer creates proper startParams.
   196  func (s *KVMSuite) TestCreateContainerUtilizesDailySimpleStream(c *gc.C) {
   197  
   198  	// Mock machineConfig with a mocked simple stream URL.
   199  	instanceConfig, err := containertesting.MockMachineConfig("1/kvm/0")
   200  	c.Assert(err, jc.ErrorIsNil)
   201  	instanceConfig.ImageStream = "daily"
   202  
   203  	// CreateContainer sets TestStartParams internally; we call this
   204  	// purely for the side-effect.
   205  	containertesting.CreateContainerWithMachineConfig(c, s.manager, instanceConfig)
   206  
   207  	c.Assert(kvm.TestStartParams.ImageDownloadURL, gc.Equals, "http://cloud-images.ubuntu.com/daily")
   208  }
   209  
   210  func (s *KVMSuite) TestStartContainerUtilizesSimpleStream(c *gc.C) {
   211  
   212  	const libvirtBinName = "uvt-simplestreams-libvirt"
   213  	testing.PatchExecutableAsEchoArgs(c, s, libvirtBinName)
   214  
   215  	startParams := kvm.StartParams{
   216  		Series:           "mocked-series",
   217  		Arch:             "mocked-arch",
   218  		ImageDownloadURL: "mocked-url",
   219  	}
   220  	mockedContainer := kvm.NewEmptyKvmContainer()
   221  	mockedContainer.Start(startParams)
   222  
   223  	expectedArgs := strings.Split(
   224  		fmt.Sprintf(
   225  			"sync arch=%s release=%s --source=%s",
   226  			startParams.Arch,
   227  			startParams.Series,
   228  			startParams.ImageDownloadURL,
   229  		),
   230  		" ",
   231  	)
   232  
   233  	testing.AssertEchoArgs(c, libvirtBinName, expectedArgs...)
   234  }
   235  
   236  type ConstraintsSuite struct {
   237  	coretesting.BaseSuite
   238  }
   239  
   240  var _ = gc.Suite(&ConstraintsSuite{})
   241  
   242  func (s *ConstraintsSuite) TestDefaults(c *gc.C) {
   243  
   244  	for _, test := range []struct {
   245  		cons     string
   246  		expected kvm.StartParams
   247  		infoLog  []string
   248  	}{{
   249  		expected: kvm.StartParams{
   250  			Memory:   kvm.DefaultMemory,
   251  			CpuCores: kvm.DefaultCpu,
   252  			RootDisk: kvm.DefaultDisk,
   253  		},
   254  	}, {
   255  		cons: "mem=256M",
   256  		expected: kvm.StartParams{
   257  			Memory:   kvm.MinMemory,
   258  			CpuCores: kvm.DefaultCpu,
   259  			RootDisk: kvm.DefaultDisk,
   260  		},
   261  	}, {
   262  		cons: "mem=4G",
   263  		expected: kvm.StartParams{
   264  			Memory:   4 * 1024,
   265  			CpuCores: kvm.DefaultCpu,
   266  			RootDisk: kvm.DefaultDisk,
   267  		},
   268  	}, {
   269  		cons: "cores=4",
   270  		expected: kvm.StartParams{
   271  			Memory:   kvm.DefaultMemory,
   272  			CpuCores: 4,
   273  			RootDisk: kvm.DefaultDisk,
   274  		},
   275  	}, {
   276  		cons: "cores=0",
   277  		expected: kvm.StartParams{
   278  			Memory:   kvm.DefaultMemory,
   279  			CpuCores: kvm.MinCpu,
   280  			RootDisk: kvm.DefaultDisk,
   281  		},
   282  	}, {
   283  		cons: "root-disk=512M",
   284  		expected: kvm.StartParams{
   285  			Memory:   kvm.DefaultMemory,
   286  			CpuCores: kvm.DefaultCpu,
   287  			RootDisk: kvm.MinDisk,
   288  		},
   289  	}, {
   290  		cons: "root-disk=4G",
   291  		expected: kvm.StartParams{
   292  			Memory:   kvm.DefaultMemory,
   293  			CpuCores: kvm.DefaultCpu,
   294  			RootDisk: 4,
   295  		},
   296  	}, {
   297  		cons: "arch=armhf",
   298  		expected: kvm.StartParams{
   299  			Memory:   kvm.DefaultMemory,
   300  			CpuCores: kvm.DefaultCpu,
   301  			RootDisk: kvm.DefaultDisk,
   302  		},
   303  		infoLog: []string{
   304  			`arch constraint of "armhf" being ignored as not supported`,
   305  		},
   306  	}, {
   307  		cons: "container=lxd",
   308  		expected: kvm.StartParams{
   309  			Memory:   kvm.DefaultMemory,
   310  			CpuCores: kvm.DefaultCpu,
   311  			RootDisk: kvm.DefaultDisk,
   312  		},
   313  		infoLog: []string{
   314  			`container constraint of "lxd" being ignored as not supported`,
   315  		},
   316  	}, {
   317  		cons: "cpu-power=100",
   318  		expected: kvm.StartParams{
   319  			Memory:   kvm.DefaultMemory,
   320  			CpuCores: kvm.DefaultCpu,
   321  			RootDisk: kvm.DefaultDisk,
   322  		},
   323  		infoLog: []string{
   324  			`cpu-power constraint of 100 being ignored as not supported`,
   325  		},
   326  	}, {
   327  		cons: "tags=foo,bar",
   328  		expected: kvm.StartParams{
   329  			Memory:   kvm.DefaultMemory,
   330  			CpuCores: kvm.DefaultCpu,
   331  			RootDisk: kvm.DefaultDisk,
   332  		},
   333  		infoLog: []string{
   334  			`tags constraint of "foo,bar" being ignored as not supported`,
   335  		},
   336  	}, {
   337  		cons: "mem=4G cores=4 root-disk=20G arch=armhf cpu-power=100 container=lxd tags=foo,bar",
   338  		expected: kvm.StartParams{
   339  			Memory:   4 * 1024,
   340  			CpuCores: 4,
   341  			RootDisk: 20,
   342  		},
   343  		infoLog: []string{
   344  			`arch constraint of "armhf" being ignored as not supported`,
   345  			`container constraint of "lxd" being ignored as not supported`,
   346  			`cpu-power constraint of 100 being ignored as not supported`,
   347  			`tags constraint of "foo,bar" being ignored as not supported`,
   348  		},
   349  	}} {
   350  		var tw loggo.TestWriter
   351  		c.Assert(loggo.RegisterWriter("constraint-tester", &tw), gc.IsNil)
   352  		cons := constraints.MustParse(test.cons)
   353  		params := kvm.ParseConstraintsToStartParams(cons)
   354  		c.Check(params, gc.DeepEquals, test.expected)
   355  		c.Check(tw.Log(), jc.LogMatches, test.infoLog)
   356  		loggo.RemoveWriter("constraint-tester")
   357  	}
   358  }
   359  
   360  // Test the output when no binary can be found.
   361  func (s *KVMSuite) TestIsKVMSupportedKvmOkNotFound(c *gc.C) {
   362  	// With no path, and no backup directory, we should fail.
   363  	s.PatchEnvironment("PATH", "")
   364  	s.PatchValue(kvm.KVMPath, "")
   365  
   366  	supported, err := kvm.IsKVMSupported()
   367  	c.Check(supported, jc.IsFalse)
   368  	c.Assert(err, gc.ErrorMatches, "kvm-ok executable not found")
   369  }
   370  
   371  // Test the output when the binary is found, but errors out.
   372  func (s *KVMSuite) TestIsKVMSupportedBinaryErrorsOut(c *gc.C) {
   373  	// Clear path so real binary is not found.
   374  	s.PatchEnvironment("PATH", "")
   375  
   376  	// Create mocked binary which returns an error and give the test access.
   377  	tmpDir := c.MkDir()
   378  	err := ioutil.WriteFile(filepath.Join(tmpDir, "kvm-ok"), []byte("#!/bin/bash\nexit 127"), 0777)
   379  	c.Assert(err, jc.ErrorIsNil)
   380  	s.PatchValue(kvm.KVMPath, tmpDir)
   381  
   382  	supported, err := kvm.IsKVMSupported()
   383  	c.Check(supported, jc.IsFalse)
   384  	c.Assert(err, gc.ErrorMatches, "exit status 127")
   385  }
   386  
   387  // Test the case where kvm-ok is not in the path, but is in the
   388  // specified directory.
   389  func (s *KVMSuite) TestIsKVMSupportedNoPath(c *gc.C) {
   390  	// Create a mocked binary so that this test does not fail for
   391  	// developers without kvm-ok.
   392  	s.PatchEnvironment("PATH", "")
   393  	tmpDir := c.MkDir()
   394  	err := ioutil.WriteFile(filepath.Join(tmpDir, "kvm-ok"), []byte("#!/bin/bash"), 0777)
   395  	c.Assert(err, jc.ErrorIsNil)
   396  	s.PatchValue(kvm.KVMPath, tmpDir)
   397  
   398  	supported, err := kvm.IsKVMSupported()
   399  	c.Check(supported, jc.IsTrue)
   400  	c.Assert(err, jc.ErrorIsNil)
   401  }
   402  
   403  // Test the case that kvm-ok is found in the path.
   404  func (s *KVMSuite) TestIsKVMSupportedOnlyPath(c *gc.C) {
   405  	// Create a mocked binary so that this test does not fail for
   406  	// developers without kvm-ok.
   407  	tmpDir := c.MkDir()
   408  	err := ioutil.WriteFile(filepath.Join(tmpDir, "kvm-ok"), []byte("#!/bin/bash"), 0777)
   409  	s.PatchEnvironment("PATH", tmpDir)
   410  
   411  	supported, err := kvm.IsKVMSupported()
   412  	c.Check(supported, jc.IsTrue)
   413  	c.Assert(err, jc.ErrorIsNil)
   414  }
   415  
   416  func (s *KVMSuite) TestKVMPathIsCorrect(c *gc.C) {
   417  	c.Assert(*kvm.KVMPath, gc.Equals, "/usr/sbin")
   418  }