github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/provisioner/container_initialisation_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provisioner_test
     5  
     6  import (
     7  	"fmt"
     8  	"os/exec"
     9  	"runtime"
    10  	"sync/atomic"
    11  	"time"
    12  
    13  	"github.com/juju/mutex"
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/utils/arch"
    16  	"github.com/juju/utils/clock"
    17  	jujuos "github.com/juju/utils/os"
    18  	"github.com/juju/utils/packaging/manager"
    19  	"github.com/juju/utils/series"
    20  	"github.com/juju/version"
    21  	gc "gopkg.in/check.v1"
    22  	"gopkg.in/juju/names.v2"
    23  
    24  	"github.com/juju/juju/agent"
    25  	apiprovisioner "github.com/juju/juju/api/provisioner"
    26  	"github.com/juju/juju/container"
    27  	"github.com/juju/juju/environs"
    28  	"github.com/juju/juju/instance"
    29  	"github.com/juju/juju/state"
    30  	coretesting "github.com/juju/juju/testing"
    31  	"github.com/juju/juju/tools"
    32  	jujuversion "github.com/juju/juju/version"
    33  	"github.com/juju/juju/watcher"
    34  	"github.com/juju/juju/worker"
    35  	"github.com/juju/juju/worker/provisioner"
    36  )
    37  
    38  type ContainerSetupSuite struct {
    39  	CommonProvisionerSuite
    40  	p           provisioner.Provisioner
    41  	agentConfig agent.ConfigSetter
    42  	// Record the apt commands issued as part of container initialisation
    43  	aptCmdChan <-chan *exec.Cmd
    44  	lockName   string
    45  }
    46  
    47  var _ = gc.Suite(&ContainerSetupSuite{})
    48  
    49  func (s *ContainerSetupSuite) SetUpSuite(c *gc.C) {
    50  	// TODO(bogdanteleaga): Fix this on windows
    51  	if runtime.GOOS == "windows" {
    52  		c.Skip("bug 1403084: Skipping container tests on windows")
    53  	}
    54  	s.CommonProvisionerSuite.SetUpSuite(c)
    55  }
    56  
    57  func (s *ContainerSetupSuite) TearDownSuite(c *gc.C) {
    58  	s.CommonProvisionerSuite.TearDownSuite(c)
    59  }
    60  
    61  func allFatal(error) bool {
    62  	return true
    63  }
    64  
    65  func noImportance(err0, err1 error) bool {
    66  	return false
    67  }
    68  
    69  func (s *ContainerSetupSuite) SetUpTest(c *gc.C) {
    70  	s.CommonProvisionerSuite.SetUpTest(c)
    71  	aptCmdChan := s.HookCommandOutput(&manager.CommandOutput, []byte{}, nil)
    72  	s.aptCmdChan = aptCmdChan
    73  
    74  	// Set up provisioner for the state machine.
    75  	s.agentConfig = s.AgentConfigForTag(c, names.NewMachineTag("0"))
    76  	var err error
    77  	s.p, err = provisioner.NewEnvironProvisioner(s.provisioner, s.agentConfig, s.Environ)
    78  	c.Assert(err, jc.ErrorIsNil)
    79  	s.lockName = "provisioner-test"
    80  }
    81  
    82  func (s *ContainerSetupSuite) TearDownTest(c *gc.C) {
    83  	if s.p != nil {
    84  		stop(c, s.p)
    85  	}
    86  	s.CommonProvisionerSuite.TearDownTest(c)
    87  }
    88  
    89  func (s *ContainerSetupSuite) setupContainerWorker(c *gc.C, tag names.MachineTag) (watcher.StringsHandler, worker.Runner) {
    90  	runner := worker.NewRunner(allFatal, noImportance, worker.RestartDelay)
    91  	pr := apiprovisioner.NewState(s.st)
    92  	machine, err := pr.Machine(tag)
    93  	c.Assert(err, jc.ErrorIsNil)
    94  	err = machine.SetSupportedContainers(instance.ContainerTypes...)
    95  	c.Assert(err, jc.ErrorIsNil)
    96  	cfg := s.AgentConfigForTag(c, tag)
    97  
    98  	watcherName := fmt.Sprintf("%s-container-watcher", machine.Id())
    99  	params := provisioner.ContainerSetupParams{
   100  		Runner:              runner,
   101  		WorkerName:          watcherName,
   102  		SupportedContainers: instance.ContainerTypes,
   103  		Machine:             machine,
   104  		Provisioner:         pr,
   105  		Config:              cfg,
   106  		InitLockName:        s.lockName,
   107  	}
   108  	handler := provisioner.NewContainerSetupHandler(params)
   109  	runner.StartWorker(watcherName, func() (worker.Worker, error) {
   110  		return watcher.NewStringsWorker(watcher.StringsConfig{
   111  			Handler: handler,
   112  		})
   113  	})
   114  	return handler, runner
   115  }
   116  
   117  func (s *ContainerSetupSuite) createContainer(c *gc.C, host *state.Machine, ctype instance.ContainerType) {
   118  	inst := s.checkStartInstance(c, host)
   119  	s.setupContainerWorker(c, host.Tag().(names.MachineTag))
   120  
   121  	// make a container on the host machine
   122  	template := state.MachineTemplate{
   123  		Series: series.LatestLts(),
   124  		Jobs:   []state.MachineJob{state.JobHostUnits},
   125  	}
   126  	container, err := s.State.AddMachineInsideMachine(template, host.Id(), ctype)
   127  	c.Assert(err, jc.ErrorIsNil)
   128  
   129  	// the host machine agent should not attempt to create the container
   130  	s.checkNoOperations(c)
   131  
   132  	// cleanup
   133  	c.Assert(container.EnsureDead(), gc.IsNil)
   134  	c.Assert(container.Remove(), gc.IsNil)
   135  	c.Assert(host.EnsureDead(), gc.IsNil)
   136  	s.checkStopInstances(c, inst)
   137  	s.waitForRemovalMark(c, host)
   138  }
   139  
   140  func (s *ContainerSetupSuite) assertContainerProvisionerStarted(
   141  	c *gc.C, host *state.Machine, ctype instance.ContainerType) {
   142  
   143  	// A stub worker callback to record what happens.
   144  	var provisionerStarted uint32
   145  	startProvisionerWorker := func(runner worker.Runner, containerType instance.ContainerType,
   146  		pr *apiprovisioner.State, cfg agent.Config, broker environs.InstanceBroker,
   147  		toolsFinder provisioner.ToolsFinder) error {
   148  		c.Assert(containerType, gc.Equals, ctype)
   149  		c.Assert(cfg.Tag(), gc.Equals, host.Tag())
   150  		atomic.StoreUint32(&provisionerStarted, 1)
   151  		return nil
   152  	}
   153  	s.PatchValue(&provisioner.StartProvisioner, startProvisionerWorker)
   154  
   155  	s.createContainer(c, host, ctype)
   156  	// Consume the apt command used to initialise the container.
   157  	select {
   158  	case <-s.aptCmdChan:
   159  	case <-time.After(coretesting.LongWait):
   160  		c.Fatalf("took too long to get command from channel")
   161  	}
   162  	// the container worker should have created the provisioner
   163  	c.Assert(atomic.LoadUint32(&provisionerStarted) > 0, jc.IsTrue)
   164  }
   165  
   166  func (s *ContainerSetupSuite) TestContainerProvisionerStarted(c *gc.C) {
   167  	// Specifically ignore LXD here, if present in instance.ContainerTypes.
   168  	containerTypes := []instance.ContainerType{instance.KVM}
   169  	for _, ctype := range containerTypes {
   170  		// create a machine to host the container.
   171  		m, err := s.BackingState.AddOneMachine(state.MachineTemplate{
   172  			Series:      series.LatestLts(),
   173  			Jobs:        []state.MachineJob{state.JobHostUnits},
   174  			Constraints: s.defaultConstraints,
   175  		})
   176  		c.Assert(err, jc.ErrorIsNil)
   177  		err = m.SetSupportedContainers(containerTypes)
   178  		c.Assert(err, jc.ErrorIsNil)
   179  		current := version.Binary{
   180  			Number: jujuversion.Current,
   181  			Arch:   arch.HostArch(),
   182  			Series: series.HostSeries(),
   183  		}
   184  		err = m.SetAgentVersion(current)
   185  		c.Assert(err, jc.ErrorIsNil)
   186  		s.assertContainerProvisionerStarted(c, m, ctype)
   187  	}
   188  }
   189  
   190  func (s *ContainerSetupSuite) TestKvmContainerUsesHostArch(c *gc.C) {
   191  	// KVM should do what it's told, and use the architecture in
   192  	// constraints.
   193  	s.PatchValue(&arch.HostArch, func() string { return arch.PPC64EL })
   194  	s.testContainerConstraintsArch(c, instance.KVM, arch.AMD64)
   195  }
   196  
   197  func (s *ContainerSetupSuite) testContainerConstraintsArch(c *gc.C, containerType instance.ContainerType, expectArch string) {
   198  	var called uint32
   199  	s.PatchValue(provisioner.GetToolsFinder, func(*apiprovisioner.State) provisioner.ToolsFinder {
   200  		return toolsFinderFunc(func(v version.Number, series string, arch string) (tools.List, error) {
   201  			atomic.StoreUint32(&called, 1)
   202  			c.Assert(arch, gc.Equals, expectArch)
   203  			result := version.Binary{
   204  				Number: v,
   205  				Arch:   arch,
   206  				Series: series,
   207  			}
   208  			return tools.List{{Version: result}}, nil
   209  		})
   210  	})
   211  
   212  	s.PatchValue(&provisioner.StartProvisioner, func(runner worker.Runner, containerType instance.ContainerType,
   213  		pr *apiprovisioner.State, cfg agent.Config, broker environs.InstanceBroker,
   214  		toolsFinder provisioner.ToolsFinder) error {
   215  		toolsFinder.FindTools(jujuversion.Current, series.HostSeries(), arch.AMD64)
   216  		return nil
   217  	})
   218  
   219  	// create a machine to host the container.
   220  	m, err := s.BackingState.AddOneMachine(state.MachineTemplate{
   221  		Series:      series.LatestLts(),
   222  		Jobs:        []state.MachineJob{state.JobHostUnits},
   223  		Constraints: s.defaultConstraints,
   224  	})
   225  	c.Assert(err, jc.ErrorIsNil)
   226  	err = m.SetSupportedContainers([]instance.ContainerType{containerType})
   227  	c.Assert(err, jc.ErrorIsNil)
   228  	current := version.Binary{
   229  		Number: jujuversion.Current,
   230  		Arch:   arch.HostArch(),
   231  		Series: series.HostSeries(),
   232  	}
   233  	err = m.SetAgentVersion(current)
   234  	c.Assert(err, jc.ErrorIsNil)
   235  
   236  	s.createContainer(c, m, containerType)
   237  	select {
   238  	case <-s.aptCmdChan:
   239  	case <-time.After(coretesting.LongWait):
   240  		c.Fatalf("took too long to get command from channel")
   241  	}
   242  	c.Assert(atomic.LoadUint32(&called) > 0, jc.IsTrue)
   243  }
   244  
   245  func (s *ContainerSetupSuite) TestContainerManagerConfigName(c *gc.C) {
   246  	pr := apiprovisioner.NewState(s.st)
   247  	cfg, err := provisioner.ContainerManagerConfig(instance.KVM, pr, s.agentConfig)
   248  	c.Assert(err, jc.ErrorIsNil)
   249  	c.Assert(cfg[container.ConfigModelUUID], gc.Equals, coretesting.ModelTag.Id())
   250  }
   251  
   252  type ContainerInstance struct {
   253  	ctype    instance.ContainerType
   254  	packages [][]string
   255  }
   256  
   257  func (s *ContainerSetupSuite) assertContainerInitialised(c *gc.C, cont ContainerInstance) {
   258  	// A noop worker callback.
   259  	startProvisionerWorker := func(runner worker.Runner, containerType instance.ContainerType,
   260  		pr *apiprovisioner.State, cfg agent.Config, broker environs.InstanceBroker,
   261  		toolsFinder provisioner.ToolsFinder) error {
   262  		return nil
   263  	}
   264  	s.PatchValue(&provisioner.StartProvisioner, startProvisionerWorker)
   265  
   266  	current_os, err := series.GetOSFromSeries(series.HostSeries())
   267  	c.Assert(err, jc.ErrorIsNil)
   268  
   269  	var ser string
   270  	var expected_initial []string
   271  	switch current_os {
   272  	case jujuos.CentOS:
   273  		ser = "centos7"
   274  		expected_initial = []string{
   275  			"yum", "--assumeyes", "--debuglevel=1", "install"}
   276  	default:
   277  		ser = "precise"
   278  		expected_initial = []string{
   279  			"apt-get", "--option=Dpkg::Options::=--force-confold",
   280  			"--option=Dpkg::options::=--force-unsafe-io", "--assume-yes", "--quiet",
   281  			"install"}
   282  	}
   283  
   284  	// create a machine to host the container.
   285  	m, err := s.BackingState.AddOneMachine(state.MachineTemplate{
   286  		Series:      ser, // precise requires special apt parameters, so we use that series here.
   287  		Jobs:        []state.MachineJob{state.JobHostUnits},
   288  		Constraints: s.defaultConstraints,
   289  	})
   290  	c.Assert(err, jc.ErrorIsNil)
   291  	err = m.SetSupportedContainers([]instance.ContainerType{instance.LXD, instance.KVM})
   292  	c.Assert(err, jc.ErrorIsNil)
   293  	current := version.Binary{
   294  		Number: jujuversion.Current,
   295  		Arch:   arch.HostArch(),
   296  		Series: series.HostSeries(),
   297  	}
   298  	err = m.SetAgentVersion(current)
   299  	c.Assert(err, jc.ErrorIsNil)
   300  
   301  	s.createContainer(c, m, cont.ctype)
   302  
   303  	for _, pack := range cont.packages {
   304  		select {
   305  		case cmd := <-s.aptCmdChan:
   306  			expected := append(expected_initial, pack...)
   307  			c.Assert(cmd.Args, gc.DeepEquals, expected)
   308  		case <-time.After(coretesting.LongWait):
   309  			c.Fatalf("took too long to get command from channel")
   310  		}
   311  	}
   312  }
   313  
   314  func (s *ContainerSetupSuite) TestContainerInitialised(c *gc.C) {
   315  	cont, err := getContainerInstance()
   316  	c.Assert(err, jc.ErrorIsNil)
   317  
   318  	for _, test := range cont {
   319  		s.assertContainerInitialised(c, test)
   320  	}
   321  }
   322  
   323  func (s *ContainerSetupSuite) TestContainerInitLockError(c *gc.C) {
   324  	spec := mutex.Spec{
   325  		Name:  s.lockName,
   326  		Clock: clock.WallClock,
   327  		Delay: coretesting.ShortWait,
   328  	}
   329  	releaser, err := mutex.Acquire(spec)
   330  	c.Assert(err, jc.ErrorIsNil)
   331  	defer releaser.Release()
   332  
   333  	m, err := s.BackingState.AddOneMachine(state.MachineTemplate{
   334  		Series:      series.LatestLts(),
   335  		Jobs:        []state.MachineJob{state.JobHostUnits},
   336  		Constraints: s.defaultConstraints,
   337  	})
   338  	c.Assert(err, jc.ErrorIsNil)
   339  	current := version.Binary{
   340  		Number: jujuversion.Current,
   341  		Arch:   arch.HostArch(),
   342  		Series: series.HostSeries(),
   343  	}
   344  	err = m.SetAgentVersion(current)
   345  	c.Assert(err, jc.ErrorIsNil)
   346  
   347  	handler, runner := s.setupContainerWorker(c, m.Tag().(names.MachineTag))
   348  	runner.Kill()
   349  	err = runner.Wait()
   350  	c.Assert(err, jc.ErrorIsNil)
   351  
   352  	_, err = handler.SetUp()
   353  	c.Assert(err, jc.ErrorIsNil)
   354  	abort := make(chan struct{})
   355  	close(abort)
   356  	err = handler.Handle(abort, []string{"0/lxd/0"})
   357  	c.Assert(err, gc.ErrorMatches, ".*failed to acquire initialization lock:.*")
   358  
   359  }
   360  
   361  type toolsFinderFunc func(v version.Number, series string, arch string) (tools.List, error)
   362  
   363  func (t toolsFinderFunc) FindTools(v version.Number, series string, arch string) (tools.List, error) {
   364  	return t(v, series, arch)
   365  }
   366  
   367  func getContainerInstance() (cont []ContainerInstance, err error) {
   368  	cont = []ContainerInstance{
   369  		{instance.KVM, [][]string{
   370  			{"uvtool-libvirt"},
   371  			{"uvtool"},
   372  		}},
   373  	}
   374  	return cont, nil
   375  }