github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/container/lxc/lxc_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lxc_test
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	stdtesting "testing"
    13  	"time"
    14  
    15  	"github.com/juju/loggo"
    16  	jc "github.com/juju/testing/checkers"
    17  	"github.com/juju/utils/proxy"
    18  	gc "launchpad.net/gocheck"
    19  	"launchpad.net/golxc"
    20  	"launchpad.net/goyaml"
    21  
    22  	"github.com/juju/juju/agent"
    23  	"github.com/juju/juju/container"
    24  	"github.com/juju/juju/container/lxc"
    25  	"github.com/juju/juju/container/lxc/mock"
    26  	lxctesting "github.com/juju/juju/container/lxc/testing"
    27  	containertesting "github.com/juju/juju/container/testing"
    28  	instancetest "github.com/juju/juju/instance/testing"
    29  	coretesting "github.com/juju/juju/testing"
    30  )
    31  
    32  func Test(t *stdtesting.T) {
    33  	gc.TestingT(t)
    34  }
    35  
    36  type LxcSuite struct {
    37  	lxctesting.TestSuite
    38  
    39  	events   chan mock.Event
    40  	useClone bool
    41  	useAUFS  bool
    42  }
    43  
    44  var _ = gc.Suite(&LxcSuite{})
    45  
    46  func (s *LxcSuite) SetUpTest(c *gc.C) {
    47  	s.TestSuite.SetUpTest(c)
    48  	loggo.GetLogger("juju.container.lxc").SetLogLevel(loggo.TRACE)
    49  	s.events = make(chan mock.Event, 25)
    50  	s.TestSuite.Factory.AddListener(s.events)
    51  	s.PatchValue(&lxc.TemplateLockDir, c.MkDir())
    52  	s.PatchValue(&lxc.TemplateStopTimeout, 500*time.Millisecond)
    53  }
    54  
    55  func (s *LxcSuite) TearDownTest(c *gc.C) {
    56  	s.TestSuite.Factory.RemoveListener(s.events)
    57  	close(s.events)
    58  	s.TestSuite.TearDownTest(c)
    59  }
    60  
    61  func (t *LxcSuite) TestPreferFastLXC(c *gc.C) {
    62  	for i, test := range []struct {
    63  		message        string
    64  		releaseVersion string
    65  		expected       bool
    66  	}{{
    67  		message: "missing release file",
    68  	}, {
    69  		message:        "precise release",
    70  		releaseVersion: "12.04",
    71  	}, {
    72  		message:        "trusty release",
    73  		releaseVersion: "14.04",
    74  		expected:       true,
    75  	}, {
    76  		message:        "unstable unicorn",
    77  		releaseVersion: "14.10",
    78  		expected:       true,
    79  	}, {
    80  		message:        "lucid",
    81  		releaseVersion: "10.04",
    82  	}} {
    83  		c.Logf("%v: %v", i, test.message)
    84  		value := lxc.PreferFastLXC(test.releaseVersion)
    85  		c.Assert(value, gc.Equals, test.expected)
    86  	}
    87  }
    88  
    89  func (s *LxcSuite) TestContainerManagerLXCClone(c *gc.C) {
    90  	type test struct {
    91  		releaseVersion string
    92  		useClone       string
    93  		expectClone    bool
    94  	}
    95  	tests := []test{{
    96  		releaseVersion: "12.04",
    97  		useClone:       "true",
    98  		expectClone:    true,
    99  	}, {
   100  		releaseVersion: "14.04",
   101  		expectClone:    true,
   102  	}, {
   103  		releaseVersion: "12.04",
   104  		useClone:       "false",
   105  	}, {
   106  		releaseVersion: "14.04",
   107  		useClone:       "false",
   108  	}}
   109  
   110  	for i, test := range tests {
   111  		c.Logf("test %d: %v", i, test)
   112  		s.PatchValue(lxc.ReleaseVersion, func() string { return test.releaseVersion })
   113  
   114  		mgr, err := lxc.NewContainerManager(container.ManagerConfig{
   115  			container.ConfigName: "juju",
   116  			"use-clone":          test.useClone,
   117  		})
   118  		c.Assert(err, gc.IsNil)
   119  		c.Check(lxc.GetCreateWithCloneValue(mgr), gc.Equals, test.expectClone)
   120  	}
   121  }
   122  
   123  func (s *LxcSuite) TestContainerDirFilesystem(c *gc.C) {
   124  	for i, test := range []struct {
   125  		message    string
   126  		output     string
   127  		expected   string
   128  		errorMatch string
   129  	}{{
   130  		message:  "btrfs",
   131  		output:   "Type\nbtrfs\n",
   132  		expected: lxc.Btrfs,
   133  	}, {
   134  		message:  "ext4",
   135  		output:   "Type\next4\n",
   136  		expected: "ext4",
   137  	}, {
   138  		message:    "not enough output",
   139  		output:     "foo",
   140  		errorMatch: "could not determine filesystem type",
   141  	}} {
   142  		c.Logf("%v: %s", i, test.message)
   143  		s.HookCommandOutput(&lxc.FsCommandOutput, []byte(test.output), nil)
   144  		value, err := lxc.ContainerDirFilesystem()
   145  		if test.errorMatch == "" {
   146  			c.Check(err, gc.IsNil)
   147  			c.Check(value, gc.Equals, test.expected)
   148  		} else {
   149  			c.Check(err, gc.ErrorMatches, test.errorMatch)
   150  		}
   151  	}
   152  }
   153  
   154  func (s *LxcSuite) makeManager(c *gc.C, name string) container.Manager {
   155  	params := container.ManagerConfig{
   156  		container.ConfigName: name,
   157  	}
   158  	// Need to ensure use-clone is explicitly set to avoid it
   159  	// being set based on the OS version.
   160  	params["use-clone"] = fmt.Sprintf("%v", s.useClone)
   161  	if s.useAUFS {
   162  		params["use-aufs"] = "true"
   163  	}
   164  	manager, err := lxc.NewContainerManager(params)
   165  	c.Assert(err, gc.IsNil)
   166  	return manager
   167  }
   168  
   169  func (*LxcSuite) TestManagerWarnsAboutUnknownOption(c *gc.C) {
   170  	_, err := lxc.NewContainerManager(container.ManagerConfig{
   171  		container.ConfigName: "BillyBatson",
   172  		"shazam":             "Captain Marvel",
   173  	})
   174  	c.Assert(err, gc.IsNil)
   175  	c.Assert(c.GetTestLog(), jc.Contains, `WARNING juju.container unused config option: "shazam" -> "Captain Marvel"`)
   176  }
   177  
   178  func (s *LxcSuite) TestCreateContainer(c *gc.C) {
   179  	manager := s.makeManager(c, "test")
   180  	instance := containertesting.CreateContainer(c, manager, "1/lxc/0")
   181  
   182  	name := string(instance.Id())
   183  	// Check our container config files.
   184  	lxcConfContents, err := ioutil.ReadFile(filepath.Join(s.ContainerDir, name, "lxc.conf"))
   185  	c.Assert(err, gc.IsNil)
   186  	c.Assert(string(lxcConfContents), jc.Contains, "lxc.network.link = nic42")
   187  
   188  	cloudInitFilename := filepath.Join(s.ContainerDir, name, "cloud-init")
   189  	data := containertesting.AssertCloudInit(c, cloudInitFilename)
   190  
   191  	x := make(map[interface{}]interface{})
   192  	err = goyaml.Unmarshal(data, &x)
   193  	c.Assert(err, gc.IsNil)
   194  
   195  	var scripts []string
   196  	for _, s := range x["runcmd"].([]interface{}) {
   197  		scripts = append(scripts, s.(string))
   198  	}
   199  
   200  	c.Assert(scripts[len(scripts)-2:], gc.DeepEquals, []string{
   201  		"start jujud-machine-1-lxc-0",
   202  		"ifconfig",
   203  	})
   204  
   205  	// Check the mount point has been created inside the container.
   206  	c.Assert(filepath.Join(s.LxcDir, name, "rootfs", agent.DefaultLogDir), jc.IsDirectory)
   207  	// Check that the config file is linked in the restart dir.
   208  	expectedLinkLocation := filepath.Join(s.RestartDir, name+".conf")
   209  	expectedTarget := filepath.Join(s.LxcDir, name, "config")
   210  	linkInfo, err := os.Lstat(expectedLinkLocation)
   211  	c.Assert(err, gc.IsNil)
   212  	c.Assert(linkInfo.Mode()&os.ModeSymlink, gc.Equals, os.ModeSymlink)
   213  
   214  	location, err := os.Readlink(expectedLinkLocation)
   215  	c.Assert(err, gc.IsNil)
   216  	c.Assert(location, gc.Equals, expectedTarget)
   217  }
   218  
   219  func (s *LxcSuite) ensureTemplateStopped(name string) {
   220  	go func() {
   221  		for {
   222  			template := s.Factory.New(name)
   223  			if template.IsRunning() {
   224  				template.Stop()
   225  			}
   226  			time.Sleep(50 * time.Millisecond)
   227  		}
   228  	}()
   229  }
   230  
   231  func (s *LxcSuite) AssertEvent(c *gc.C, event mock.Event, expected mock.Action, id string) {
   232  	c.Assert(event.Action, gc.Equals, expected)
   233  	c.Assert(event.InstanceId, gc.Equals, id)
   234  }
   235  
   236  func (s *LxcSuite) TestCreateContainerEvents(c *gc.C) {
   237  	manager := s.makeManager(c, "test")
   238  	instance := containertesting.CreateContainer(c, manager, "1")
   239  	id := string(instance.Id())
   240  	s.AssertEvent(c, <-s.events, mock.Created, id)
   241  	s.AssertEvent(c, <-s.events, mock.Started, id)
   242  }
   243  
   244  func (s *LxcSuite) TestCreateContainerEventsWithClone(c *gc.C) {
   245  	s.PatchValue(&s.useClone, true)
   246  	// The template containers are created with an upstart job that
   247  	// stops them once cloud init has finished.  We emulate that here.
   248  	template := "juju-series-template"
   249  	s.ensureTemplateStopped(template)
   250  	manager := s.makeManager(c, "test")
   251  	instance := containertesting.CreateContainer(c, manager, "1")
   252  	id := string(instance.Id())
   253  	s.AssertEvent(c, <-s.events, mock.Created, template)
   254  	s.AssertEvent(c, <-s.events, mock.Started, template)
   255  	s.AssertEvent(c, <-s.events, mock.Stopped, template)
   256  	s.AssertEvent(c, <-s.events, mock.Cloned, template)
   257  	s.AssertEvent(c, <-s.events, mock.Started, id)
   258  }
   259  
   260  func (s *LxcSuite) createTemplate(c *gc.C) golxc.Container {
   261  	name := "juju-series-template"
   262  	s.ensureTemplateStopped(name)
   263  	network := container.BridgeNetworkConfig("nic42")
   264  	authorizedKeys := "authorized keys list"
   265  	aptProxy := proxy.Settings{}
   266  	template, err := lxc.EnsureCloneTemplate(
   267  		"ext4", "series", network, authorizedKeys, aptProxy)
   268  	c.Assert(err, gc.IsNil)
   269  	c.Assert(template.Name(), gc.Equals, name)
   270  	s.AssertEvent(c, <-s.events, mock.Created, name)
   271  	s.AssertEvent(c, <-s.events, mock.Started, name)
   272  	s.AssertEvent(c, <-s.events, mock.Stopped, name)
   273  
   274  	autostartLink := lxc.RestartSymlink(name)
   275  	config, err := ioutil.ReadFile(lxc.ContainerConfigFilename(name))
   276  	c.Assert(err, gc.IsNil)
   277  	expected := `
   278  lxc.network.type = veth
   279  lxc.network.link = nic42
   280  lxc.network.flags = up
   281  `
   282  	// NOTE: no autostart, no mounting the log dir
   283  	c.Assert(string(config), gc.Equals, expected)
   284  	c.Assert(autostartLink, jc.DoesNotExist)
   285  
   286  	return template
   287  }
   288  
   289  func (s *LxcSuite) TestCreateContainerEventsWithCloneExistingTemplate(c *gc.C) {
   290  	s.createTemplate(c)
   291  	s.PatchValue(&s.useClone, true)
   292  	manager := s.makeManager(c, "test")
   293  	instance := containertesting.CreateContainer(c, manager, "1")
   294  	name := string(instance.Id())
   295  	cloned := <-s.events
   296  	s.AssertEvent(c, cloned, mock.Cloned, "juju-series-template")
   297  	c.Assert(cloned.Args, gc.IsNil)
   298  	s.AssertEvent(c, <-s.events, mock.Started, name)
   299  }
   300  
   301  func (s *LxcSuite) TestCreateContainerEventsWithCloneExistingTemplateAUFS(c *gc.C) {
   302  	s.createTemplate(c)
   303  	s.PatchValue(&s.useClone, true)
   304  	s.PatchValue(&s.useAUFS, true)
   305  	manager := s.makeManager(c, "test")
   306  	instance := containertesting.CreateContainer(c, manager, "1")
   307  	name := string(instance.Id())
   308  	cloned := <-s.events
   309  	s.AssertEvent(c, cloned, mock.Cloned, "juju-series-template")
   310  	c.Assert(cloned.Args, gc.DeepEquals, []string{"--snapshot", "--backingstore", "aufs"})
   311  	s.AssertEvent(c, <-s.events, mock.Started, name)
   312  }
   313  
   314  func (s *LxcSuite) TestCreateContainerWithCloneMountsAndAutostarts(c *gc.C) {
   315  	s.createTemplate(c)
   316  	s.PatchValue(&s.useClone, true)
   317  	manager := s.makeManager(c, "test")
   318  	instance := containertesting.CreateContainer(c, manager, "1")
   319  	name := string(instance.Id())
   320  
   321  	autostartLink := lxc.RestartSymlink(name)
   322  	config, err := ioutil.ReadFile(lxc.ContainerConfigFilename(name))
   323  	c.Assert(err, gc.IsNil)
   324  	mountLine := "lxc.mount.entry=/var/log/juju var/log/juju none defaults,bind 0 0"
   325  	c.Assert(string(config), jc.Contains, mountLine)
   326  	c.Assert(autostartLink, jc.IsSymlink)
   327  }
   328  
   329  func (s *LxcSuite) TestContainerState(c *gc.C) {
   330  	manager := s.makeManager(c, "test")
   331  	c.Logf("%#v", manager)
   332  	instance := containertesting.CreateContainer(c, manager, "1/lxc/0")
   333  
   334  	// The mock container will be immediately "running".
   335  	c.Assert(instance.Status(), gc.Equals, string(golxc.StateRunning))
   336  
   337  	// DestroyContainer stops and then destroys the container, putting it
   338  	// into "unknown" state.
   339  	err := manager.DestroyContainer(instance.Id())
   340  	c.Assert(err, gc.IsNil)
   341  	c.Assert(instance.Status(), gc.Equals, string(golxc.StateUnknown))
   342  }
   343  
   344  func (s *LxcSuite) TestDestroyContainer(c *gc.C) {
   345  	manager := s.makeManager(c, "test")
   346  	instance := containertesting.CreateContainer(c, manager, "1/lxc/0")
   347  
   348  	err := manager.DestroyContainer(instance.Id())
   349  	c.Assert(err, gc.IsNil)
   350  
   351  	name := string(instance.Id())
   352  	// Check that the container dir is no longer in the container dir
   353  	c.Assert(filepath.Join(s.ContainerDir, name), jc.DoesNotExist)
   354  	// but instead, in the removed container dir
   355  	c.Assert(filepath.Join(s.RemovedDir, name), jc.IsDirectory)
   356  }
   357  
   358  func (s *LxcSuite) TestDestroyContainerNameClash(c *gc.C) {
   359  	manager := s.makeManager(c, "test")
   360  	instance := containertesting.CreateContainer(c, manager, "1/lxc/0")
   361  
   362  	name := string(instance.Id())
   363  	targetDir := filepath.Join(s.RemovedDir, name)
   364  	err := os.MkdirAll(targetDir, 0755)
   365  	c.Assert(err, gc.IsNil)
   366  
   367  	err = manager.DestroyContainer(instance.Id())
   368  	c.Assert(err, gc.IsNil)
   369  
   370  	// Check that the container dir is no longer in the container dir
   371  	c.Assert(filepath.Join(s.ContainerDir, name), jc.DoesNotExist)
   372  	// but instead, in the removed container dir with a ".1" suffix as there was already a directory there.
   373  	c.Assert(filepath.Join(s.RemovedDir, fmt.Sprintf("%s.1", name)), jc.IsDirectory)
   374  }
   375  
   376  func (s *LxcSuite) TestNamedManagerPrefix(c *gc.C) {
   377  	manager := s.makeManager(c, "eric")
   378  	instance := containertesting.CreateContainer(c, manager, "1/lxc/0")
   379  	c.Assert(string(instance.Id()), gc.Equals, "eric-machine-1-lxc-0")
   380  }
   381  
   382  func (s *LxcSuite) TestListContainers(c *gc.C) {
   383  	foo := s.makeManager(c, "foo")
   384  	bar := s.makeManager(c, "bar")
   385  
   386  	foo1 := containertesting.CreateContainer(c, foo, "1/lxc/0")
   387  	foo2 := containertesting.CreateContainer(c, foo, "1/lxc/1")
   388  	foo3 := containertesting.CreateContainer(c, foo, "1/lxc/2")
   389  
   390  	bar1 := containertesting.CreateContainer(c, bar, "1/lxc/0")
   391  	bar2 := containertesting.CreateContainer(c, bar, "1/lxc/1")
   392  
   393  	result, err := foo.ListContainers()
   394  	c.Assert(err, gc.IsNil)
   395  	instancetest.MatchInstances(c, result, foo1, foo2, foo3)
   396  
   397  	result, err = bar.ListContainers()
   398  	c.Assert(err, gc.IsNil)
   399  	instancetest.MatchInstances(c, result, bar1, bar2)
   400  }
   401  
   402  func (s *LxcSuite) TestCreateContainerAutostarts(c *gc.C) {
   403  	manager := s.makeManager(c, "test")
   404  	instance := containertesting.CreateContainer(c, manager, "1/lxc/0")
   405  	autostartLink := lxc.RestartSymlink(string(instance.Id()))
   406  	c.Assert(autostartLink, jc.IsSymlink)
   407  }
   408  
   409  func (s *LxcSuite) TestCreateContainerNoRestartDir(c *gc.C) {
   410  	err := os.Remove(s.RestartDir)
   411  	c.Assert(err, gc.IsNil)
   412  
   413  	manager := s.makeManager(c, "test")
   414  	instance := containertesting.CreateContainer(c, manager, "1/lxc/0")
   415  	name := string(instance.Id())
   416  	autostartLink := lxc.RestartSymlink(name)
   417  	config, err := ioutil.ReadFile(lxc.ContainerConfigFilename(name))
   418  	c.Assert(err, gc.IsNil)
   419  	expected := `
   420  lxc.network.type = veth
   421  lxc.network.link = nic42
   422  lxc.network.flags = up
   423  lxc.start.auto = 1
   424  lxc.mount.entry=/var/log/juju var/log/juju none defaults,bind 0 0
   425  `
   426  	c.Assert(string(config), gc.Equals, expected)
   427  	c.Assert(autostartLink, jc.DoesNotExist)
   428  }
   429  
   430  func (s *LxcSuite) TestDestroyContainerRemovesAutostartLink(c *gc.C) {
   431  	manager := s.makeManager(c, "test")
   432  	instance := containertesting.CreateContainer(c, manager, "1/lxc/0")
   433  	err := manager.DestroyContainer(instance.Id())
   434  	c.Assert(err, gc.IsNil)
   435  	autostartLink := lxc.RestartSymlink(string(instance.Id()))
   436  	c.Assert(autostartLink, jc.SymlinkDoesNotExist)
   437  }
   438  
   439  func (s *LxcSuite) TestDestroyContainerNoRestartDir(c *gc.C) {
   440  	err := os.Remove(s.RestartDir)
   441  	c.Assert(err, gc.IsNil)
   442  
   443  	manager := s.makeManager(c, "test")
   444  	instance := containertesting.CreateContainer(c, manager, "1/lxc/0")
   445  	err = manager.DestroyContainer(instance.Id())
   446  	c.Assert(err, gc.IsNil)
   447  }
   448  
   449  type NetworkSuite struct {
   450  	coretesting.BaseSuite
   451  }
   452  
   453  var _ = gc.Suite(&NetworkSuite{})
   454  
   455  func (*NetworkSuite) TestGenerateNetworkConfig(c *gc.C) {
   456  	for _, test := range []struct {
   457  		config *container.NetworkConfig
   458  		net    string
   459  		link   string
   460  	}{{
   461  		config: nil,
   462  		net:    "veth",
   463  		link:   "lxcbr0",
   464  	}, {
   465  		config: lxc.DefaultNetworkConfig(),
   466  		net:    "veth",
   467  		link:   "lxcbr0",
   468  	}, {
   469  		config: container.BridgeNetworkConfig("foo"),
   470  		net:    "veth",
   471  		link:   "foo",
   472  	}, {
   473  		config: container.PhysicalNetworkConfig("foo"),
   474  		net:    "phys",
   475  		link:   "foo",
   476  	}} {
   477  		config := lxc.GenerateNetworkConfig(test.config)
   478  		c.Assert(config, jc.Contains, fmt.Sprintf("lxc.network.type = %s\n", test.net))
   479  		c.Assert(config, jc.Contains, fmt.Sprintf("lxc.network.link = %s\n", test.link))
   480  	}
   481  }
   482  
   483  func (*NetworkSuite) TestNetworkConfigTemplate(c *gc.C) {
   484  	config := lxc.NetworkConfigTemplate("foo", "bar")
   485  	//In the past, the entire lxc.conf file was just networking. With the addition
   486  	//of the auto start, we now have to have better isolate this test. As such, we
   487  	//parse the conf template results and just get the results that start with
   488  	//'lxc.network' as that is what the test cares about.
   489  	obtained := []string{}
   490  	for _, value := range strings.Split(config, "\n") {
   491  		if strings.HasPrefix(value, "lxc.network") {
   492  			obtained = append(obtained, value)
   493  		}
   494  	}
   495  	expected := []string{
   496  		"lxc.network.type = foo",
   497  		"lxc.network.link = bar",
   498  		"lxc.network.flags = up",
   499  	}
   500  	c.Assert(obtained, gc.DeepEquals, expected)
   501  }