launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/worker/deployer/simple_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package deployer_test
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"path/filepath"
    12  	"regexp"
    13  	"sort"
    14  	"strings"
    15  
    16  	gc "launchpad.net/gocheck"
    17  
    18  	"launchpad.net/juju-core/agent"
    19  	"launchpad.net/juju-core/agent/tools"
    20  	"launchpad.net/juju-core/names"
    21  	"launchpad.net/juju-core/testing"
    22  	jc "launchpad.net/juju-core/testing/checkers"
    23  	"launchpad.net/juju-core/testing/testbase"
    24  	coretools "launchpad.net/juju-core/tools"
    25  	"launchpad.net/juju-core/version"
    26  	"launchpad.net/juju-core/worker/deployer"
    27  )
    28  
    29  type SimpleContextSuite struct {
    30  	SimpleToolsFixture
    31  }
    32  
    33  var _ = gc.Suite(&SimpleContextSuite{})
    34  
    35  func (s *SimpleContextSuite) SetUpTest(c *gc.C) {
    36  	s.SimpleToolsFixture.SetUp(c, c.MkDir())
    37  }
    38  
    39  func (s *SimpleContextSuite) TearDownTest(c *gc.C) {
    40  	s.SimpleToolsFixture.TearDown(c)
    41  }
    42  
    43  func (s *SimpleContextSuite) TestDeployRecall(c *gc.C) {
    44  	mgr0 := s.getContext(c)
    45  	units, err := mgr0.DeployedUnits()
    46  	c.Assert(err, gc.IsNil)
    47  	c.Assert(units, gc.HasLen, 0)
    48  	s.assertUpstartCount(c, 0)
    49  
    50  	err = mgr0.DeployUnit("foo/123", "some-password")
    51  	c.Assert(err, gc.IsNil)
    52  	units, err = mgr0.DeployedUnits()
    53  	c.Assert(err, gc.IsNil)
    54  	c.Assert(units, gc.DeepEquals, []string{"foo/123"})
    55  	s.assertUpstartCount(c, 1)
    56  	s.checkUnitInstalled(c, "foo/123", "some-password")
    57  
    58  	err = mgr0.RecallUnit("foo/123")
    59  	c.Assert(err, gc.IsNil)
    60  	units, err = mgr0.DeployedUnits()
    61  	c.Assert(err, gc.IsNil)
    62  	c.Assert(units, gc.HasLen, 0)
    63  	s.assertUpstartCount(c, 0)
    64  	s.checkUnitRemoved(c, "foo/123")
    65  }
    66  
    67  func (s *SimpleContextSuite) TestOldDeployedUnitsCanBeRecalled(c *gc.C) {
    68  	// After r1347 deployer tag is no longer part of the upstart conf filenames,
    69  	// now only the units' tags are used. This change is with the assumption only
    70  	// one deployer will be running on a machine (in the machine agent as a task,
    71  	// unlike before where there was one in the unit agent as well).
    72  	// This test ensures units deployed previously (or their upstart confs more
    73  	// specifically) can be detected and recalled by the deployer.
    74  
    75  	manager := s.getContext(c)
    76  
    77  	// No deployed units at first.
    78  	units, err := manager.DeployedUnits()
    79  	c.Assert(err, gc.IsNil)
    80  	c.Assert(units, gc.HasLen, 0)
    81  	s.assertUpstartCount(c, 0)
    82  
    83  	// Trying to recall any units will fail.
    84  	err = manager.RecallUnit("principal/1")
    85  	c.Assert(err, gc.ErrorMatches, `unit "principal/1" is not deployed`)
    86  
    87  	// Simulate some previously deployed units with the old
    88  	// upstart conf filename format (+deployer tags).
    89  	s.injectUnit(c, "jujud-machine-0:unit-mysql-0.conf", "unit-mysql-0")
    90  	s.assertUpstartCount(c, 1)
    91  	s.injectUnit(c, "jujud-unit-wordpress-0:unit-nrpe-0.conf", "unit-nrpe-0")
    92  	s.assertUpstartCount(c, 2)
    93  
    94  	// Make sure we can discover them.
    95  	units, err = manager.DeployedUnits()
    96  	c.Assert(err, gc.IsNil)
    97  	c.Assert(units, gc.HasLen, 2)
    98  	sort.Strings(units)
    99  	c.Assert(units, gc.DeepEquals, []string{"mysql/0", "nrpe/0"})
   100  
   101  	// Deploy some units.
   102  	err = manager.DeployUnit("principal/1", "some-password")
   103  	c.Assert(err, gc.IsNil)
   104  	s.checkUnitInstalled(c, "principal/1", "some-password")
   105  	s.assertUpstartCount(c, 3)
   106  	err = manager.DeployUnit("subordinate/2", "fake-password")
   107  	c.Assert(err, gc.IsNil)
   108  	s.checkUnitInstalled(c, "subordinate/2", "fake-password")
   109  	s.assertUpstartCount(c, 4)
   110  
   111  	// Verify the newly deployed units are also discoverable.
   112  	units, err = manager.DeployedUnits()
   113  	c.Assert(err, gc.IsNil)
   114  	c.Assert(units, gc.HasLen, 4)
   115  	sort.Strings(units)
   116  	c.Assert(units, gc.DeepEquals, []string{"mysql/0", "nrpe/0", "principal/1", "subordinate/2"})
   117  
   118  	// Recall all of them - should work ok.
   119  	unitCount := 4
   120  	for _, unitName := range units {
   121  		err = manager.RecallUnit(unitName)
   122  		c.Assert(err, gc.IsNil)
   123  		unitCount--
   124  		s.checkUnitRemoved(c, unitName)
   125  		s.assertUpstartCount(c, unitCount)
   126  	}
   127  
   128  	// Verify they're no longer discoverable.
   129  	units, err = manager.DeployedUnits()
   130  	c.Assert(err, gc.IsNil)
   131  	c.Assert(units, gc.HasLen, 0)
   132  }
   133  
   134  type SimpleToolsFixture struct {
   135  	testbase.LoggingSuite
   136  	dataDir         string
   137  	initDir         string
   138  	logDir          string
   139  	origPath        string
   140  	binDir          string
   141  	syslogConfigDir string
   142  }
   143  
   144  var fakeJujud = "#!/bin/bash --norc\n# fake-jujud\nexit 0\n"
   145  
   146  func (fix *SimpleToolsFixture) SetUp(c *gc.C, dataDir string) {
   147  	fix.LoggingSuite.SetUpTest(c)
   148  	fix.dataDir = dataDir
   149  	fix.initDir = c.MkDir()
   150  	fix.logDir = c.MkDir()
   151  	fix.syslogConfigDir = c.MkDir()
   152  	toolsDir := tools.SharedToolsDir(fix.dataDir, version.Current)
   153  	err := os.MkdirAll(toolsDir, 0755)
   154  	c.Assert(err, gc.IsNil)
   155  	jujudPath := filepath.Join(toolsDir, "jujud")
   156  	err = ioutil.WriteFile(jujudPath, []byte(fakeJujud), 0755)
   157  	c.Assert(err, gc.IsNil)
   158  	toolsPath := filepath.Join(toolsDir, "downloaded-tools.txt")
   159  	testTools := coretools.Tools{Version: version.Current, URL: "http://testing.invalid/tools"}
   160  	data, err := json.Marshal(testTools)
   161  	c.Assert(err, gc.IsNil)
   162  	err = ioutil.WriteFile(toolsPath, data, 0644)
   163  	c.Assert(err, gc.IsNil)
   164  	fix.binDir = c.MkDir()
   165  	fix.origPath = os.Getenv("PATH")
   166  	os.Setenv("PATH", fix.binDir+":"+fix.origPath)
   167  	fix.makeBin(c, "status", `echo "blah stop/waiting"`)
   168  	fix.makeBin(c, "stopped-status", `echo "blah stop/waiting"`)
   169  	fix.makeBin(c, "started-status", `echo "blah start/running, process 666"`)
   170  	fix.makeBin(c, "start", "cp $(which started-status) $(which status)")
   171  	fix.makeBin(c, "stop", "cp $(which stopped-status) $(which status)")
   172  }
   173  
   174  func (fix *SimpleToolsFixture) TearDown(c *gc.C) {
   175  	os.Setenv("PATH", fix.origPath)
   176  	fix.LoggingSuite.TearDownTest(c)
   177  }
   178  
   179  func (fix *SimpleToolsFixture) makeBin(c *gc.C, name, script string) {
   180  	path := filepath.Join(fix.binDir, name)
   181  	err := ioutil.WriteFile(path, []byte("#!/bin/bash --norc\n"+script), 0755)
   182  	c.Assert(err, gc.IsNil)
   183  }
   184  
   185  func (fix *SimpleToolsFixture) assertUpstartCount(c *gc.C, count int) {
   186  	fis, err := ioutil.ReadDir(fix.initDir)
   187  	c.Assert(err, gc.IsNil)
   188  	c.Assert(fis, gc.HasLen, count)
   189  }
   190  
   191  func (fix *SimpleToolsFixture) getContext(c *gc.C) *deployer.SimpleContext {
   192  	config := agentConfig("machine-tag", fix.dataDir)
   193  	return deployer.NewTestSimpleContext(config, fix.initDir, fix.logDir, fix.syslogConfigDir)
   194  }
   195  
   196  func (fix *SimpleToolsFixture) getContextForMachine(c *gc.C, machineTag string) *deployer.SimpleContext {
   197  	config := agentConfig(machineTag, fix.dataDir)
   198  	return deployer.NewTestSimpleContext(config, fix.initDir, fix.logDir, fix.syslogConfigDir)
   199  }
   200  
   201  func (fix *SimpleToolsFixture) paths(tag string) (confPath, agentDir, toolsDir, syslogConfPath string) {
   202  	confName := fmt.Sprintf("jujud-%s.conf", tag)
   203  	confPath = filepath.Join(fix.initDir, confName)
   204  	agentDir = agent.Dir(fix.dataDir, tag)
   205  	toolsDir = tools.ToolsDir(fix.dataDir, tag)
   206  	syslogConfPath = filepath.Join(fix.syslogConfigDir, fmt.Sprintf("26-juju-%s.conf", tag))
   207  	return
   208  }
   209  
   210  var expectedSyslogConf = `
   211  $ModLoad imfile
   212  
   213  $InputFilePersistStateInterval 50
   214  $InputFilePollInterval 5
   215  $InputFileName /var/log/juju/%s.log
   216  $InputFileTag juju-%s:
   217  $InputFileStateFile %s
   218  $InputRunFileMonitor
   219  
   220  $template LongTagForwardFormat,"<%%PRI%%>%%TIMESTAMP:::date-rfc3339%% %%HOSTNAME%% %%syslogtag%%%%msg:::sp-if-no-1st-sp%%%%msg%%"
   221  
   222  :syslogtag, startswith, "juju-" @s1:2345;LongTagForwardFormat
   223  & ~
   224  `
   225  
   226  func (fix *SimpleToolsFixture) checkUnitInstalled(c *gc.C, name, password string) {
   227  	tag := names.UnitTag(name)
   228  	uconfPath, _, toolsDir, syslogConfPath := fix.paths(tag)
   229  	uconfData, err := ioutil.ReadFile(uconfPath)
   230  	c.Assert(err, gc.IsNil)
   231  	uconf := string(uconfData)
   232  	var execLine string
   233  	for _, line := range strings.Split(uconf, "\n") {
   234  		if strings.HasPrefix(line, "exec ") {
   235  			execLine = line
   236  			break
   237  		}
   238  	}
   239  	if execLine == "" {
   240  		c.Fatalf("no command found in %s:\n%s", uconfPath, uconf)
   241  	}
   242  	logPath := filepath.Join(fix.logDir, tag+".log")
   243  	jujudPath := filepath.Join(toolsDir, "jujud")
   244  	for _, pat := range []string{
   245  		"^exec " + jujudPath + " unit ",
   246  		" --unit-name " + name + " ",
   247  		" >> " + logPath + " 2>&1$",
   248  	} {
   249  		match, err := regexp.MatchString(pat, execLine)
   250  		c.Assert(err, gc.IsNil)
   251  		if !match {
   252  			c.Fatalf("failed to match:\n%s\nin:\n%s", pat, execLine)
   253  		}
   254  	}
   255  
   256  	conf, err := agent.ReadConf(fix.dataDir, tag)
   257  	c.Assert(err, gc.IsNil)
   258  	c.Assert(conf.Tag(), gc.Equals, tag)
   259  	c.Assert(conf.DataDir(), gc.Equals, fix.dataDir)
   260  
   261  	jujudData, err := ioutil.ReadFile(jujudPath)
   262  	c.Assert(err, gc.IsNil)
   263  	c.Assert(string(jujudData), gc.Equals, fakeJujud)
   264  
   265  	syslogConfData, err := ioutil.ReadFile(syslogConfPath)
   266  	c.Assert(err, gc.IsNil)
   267  	parts := strings.SplitN(name, "/", 2)
   268  	unitTag := fmt.Sprintf("unit-%s-%s", parts[0], parts[1])
   269  	expectedSyslogConfReplaced := fmt.Sprintf(expectedSyslogConf, unitTag, unitTag, unitTag)
   270  	c.Assert(string(syslogConfData), gc.Equals, expectedSyslogConfReplaced)
   271  
   272  }
   273  
   274  func (fix *SimpleToolsFixture) checkUnitRemoved(c *gc.C, name string) {
   275  	tag := names.UnitTag(name)
   276  	confPath, agentDir, toolsDir, syslogConfPath := fix.paths(tag)
   277  	for _, path := range []string{confPath, agentDir, toolsDir, syslogConfPath} {
   278  		_, err := ioutil.ReadFile(path)
   279  		if err == nil {
   280  			c.Log("Warning: %q not removed as expected", path)
   281  		} else {
   282  			c.Assert(err, jc.Satisfies, os.IsNotExist)
   283  		}
   284  	}
   285  }
   286  
   287  func (fix *SimpleToolsFixture) injectUnit(c *gc.C, upstartConf, unitTag string) {
   288  	confPath := filepath.Join(fix.initDir, upstartConf)
   289  	err := ioutil.WriteFile(confPath, []byte("#!/bin/bash --norc\necho $0"), 0644)
   290  	c.Assert(err, gc.IsNil)
   291  	toolsDir := filepath.Join(fix.dataDir, "tools", unitTag)
   292  	err = os.MkdirAll(toolsDir, 0755)
   293  	c.Assert(err, gc.IsNil)
   294  }
   295  
   296  type mockConfig struct {
   297  	agent.Config
   298  	tag     string
   299  	datadir string
   300  }
   301  
   302  func (mock *mockConfig) Tag() string {
   303  	return mock.tag
   304  }
   305  
   306  func (mock *mockConfig) DataDir() string {
   307  	return mock.datadir
   308  }
   309  
   310  func (mock *mockConfig) CACert() []byte {
   311  	return []byte(testing.CACert)
   312  }
   313  
   314  func (mock *mockConfig) Value(_ string) string {
   315  	return ""
   316  }
   317  
   318  func agentConfig(tag, datadir string) agent.Config {
   319  	return &mockConfig{tag: tag, datadir: datadir}
   320  }