github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"regexp"
    12  	"runtime"
    13  	"sort"
    14  
    15  	"github.com/juju/names"
    16  	jc "github.com/juju/testing/checkers"
    17  	gc "gopkg.in/check.v1"
    18  
    19  	"github.com/juju/juju/agent"
    20  	"github.com/juju/juju/agent/tools"
    21  	svctesting "github.com/juju/juju/service/common/testing"
    22  	"github.com/juju/juju/service/upstart"
    23  	"github.com/juju/juju/state/multiwatcher"
    24  	"github.com/juju/juju/testing"
    25  	coretools "github.com/juju/juju/tools"
    26  	"github.com/juju/juju/version"
    27  	"github.com/juju/juju/worker/deployer"
    28  )
    29  
    30  var quote, cmdSuffix string
    31  
    32  func init() {
    33  	quote = "'"
    34  	if runtime.GOOS == "windows" {
    35  		cmdSuffix = ".exe"
    36  		quote = `"`
    37  	}
    38  }
    39  
    40  type SimpleContextSuite struct {
    41  	SimpleToolsFixture
    42  }
    43  
    44  var _ = gc.Suite(&SimpleContextSuite{})
    45  
    46  func (s *SimpleContextSuite) SetUpTest(c *gc.C) {
    47  	s.SimpleToolsFixture.SetUp(c, c.MkDir())
    48  }
    49  
    50  func (s *SimpleContextSuite) TearDownTest(c *gc.C) {
    51  	s.SimpleToolsFixture.TearDown(c)
    52  }
    53  
    54  func (s *SimpleContextSuite) TestDeployRecall(c *gc.C) {
    55  	mgr0 := s.getContext(c)
    56  	units, err := mgr0.DeployedUnits()
    57  	c.Assert(err, jc.ErrorIsNil)
    58  	c.Assert(units, gc.HasLen, 0)
    59  	s.assertUpstartCount(c, 0)
    60  
    61  	err = mgr0.DeployUnit("foo/123", "some-password")
    62  	c.Assert(err, jc.ErrorIsNil)
    63  	units, err = mgr0.DeployedUnits()
    64  	c.Assert(err, jc.ErrorIsNil)
    65  	c.Assert(units, gc.DeepEquals, []string{"foo/123"})
    66  	s.assertUpstartCount(c, 1)
    67  	s.checkUnitInstalled(c, "foo/123", "some-password")
    68  
    69  	err = mgr0.RecallUnit("foo/123")
    70  	c.Assert(err, jc.ErrorIsNil)
    71  	units, err = mgr0.DeployedUnits()
    72  	c.Assert(err, jc.ErrorIsNil)
    73  	c.Assert(units, gc.HasLen, 0)
    74  	s.assertUpstartCount(c, 0)
    75  	s.checkUnitRemoved(c, "foo/123")
    76  }
    77  
    78  func (s *SimpleContextSuite) TestOldDeployedUnitsCanBeRecalled(c *gc.C) {
    79  	// After r1347 deployer tag is no longer part of the upstart conf filenames,
    80  	// now only the units' tags are used. This change is with the assumption only
    81  	// one deployer will be running on a machine (in the machine agent as a task,
    82  	// unlike before where there was one in the unit agent as well).
    83  	// This test ensures units deployed previously (or their upstart confs more
    84  	// specifically) can be detected and recalled by the deployer.
    85  
    86  	manager := s.getContext(c)
    87  
    88  	// No deployed units at first.
    89  	units, err := manager.DeployedUnits()
    90  	c.Assert(err, jc.ErrorIsNil)
    91  	c.Assert(units, gc.HasLen, 0)
    92  	s.assertUpstartCount(c, 0)
    93  
    94  	// Trying to recall any units will fail.
    95  	err = manager.RecallUnit("principal/1")
    96  	c.Assert(err, gc.ErrorMatches, `unit "principal/1" is not deployed`)
    97  
    98  	// Simulate some previously deployed units with the old
    99  	// upstart conf filename format (+deployer tags).
   100  	s.injectUnit(c, "jujud-machine-0:unit-mysql-0", "unit-mysql-0")
   101  	s.assertUpstartCount(c, 1)
   102  	s.injectUnit(c, "jujud-unit-wordpress-0:unit-nrpe-0", "unit-nrpe-0")
   103  	s.assertUpstartCount(c, 2)
   104  
   105  	// Make sure we can discover them.
   106  	units, err = manager.DeployedUnits()
   107  	c.Assert(err, jc.ErrorIsNil)
   108  	c.Assert(units, gc.HasLen, 2)
   109  	sort.Strings(units)
   110  	c.Assert(units, gc.DeepEquals, []string{"mysql/0", "nrpe/0"})
   111  
   112  	// Deploy some units.
   113  	err = manager.DeployUnit("principal/1", "some-password")
   114  	c.Assert(err, jc.ErrorIsNil)
   115  	s.checkUnitInstalled(c, "principal/1", "some-password")
   116  	s.assertUpstartCount(c, 3)
   117  	err = manager.DeployUnit("subordinate/2", "fake-password")
   118  	c.Assert(err, jc.ErrorIsNil)
   119  	s.checkUnitInstalled(c, "subordinate/2", "fake-password")
   120  	s.assertUpstartCount(c, 4)
   121  
   122  	// Verify the newly deployed units are also discoverable.
   123  	units, err = manager.DeployedUnits()
   124  	c.Assert(err, jc.ErrorIsNil)
   125  	c.Assert(units, gc.HasLen, 4)
   126  	sort.Strings(units)
   127  	c.Assert(units, gc.DeepEquals, []string{"mysql/0", "nrpe/0", "principal/1", "subordinate/2"})
   128  
   129  	// Recall all of them - should work ok.
   130  	unitCount := 4
   131  	for _, unitName := range units {
   132  		err = manager.RecallUnit(unitName)
   133  		c.Assert(err, jc.ErrorIsNil)
   134  		unitCount--
   135  		s.checkUnitRemoved(c, unitName)
   136  		s.assertUpstartCount(c, unitCount)
   137  	}
   138  
   139  	// Verify they're no longer discoverable.
   140  	units, err = manager.DeployedUnits()
   141  	c.Assert(err, jc.ErrorIsNil)
   142  	c.Assert(units, gc.HasLen, 0)
   143  }
   144  
   145  type SimpleToolsFixture struct {
   146  	dataDir  string
   147  	logDir   string
   148  	origPath string
   149  	binDir   string
   150  
   151  	data *svctesting.FakeServiceData
   152  }
   153  
   154  var fakeJujud = "#!/bin/bash --norc\n# fake-jujud\nexit 0\n"
   155  
   156  func (fix *SimpleToolsFixture) SetUp(c *gc.C, dataDir string) {
   157  	fix.dataDir = dataDir
   158  	fix.logDir = c.MkDir()
   159  	toolsDir := tools.SharedToolsDir(fix.dataDir, version.Current)
   160  	err := os.MkdirAll(toolsDir, 0755)
   161  	c.Assert(err, jc.ErrorIsNil)
   162  	jujudPath := filepath.Join(toolsDir, "jujud")
   163  	err = ioutil.WriteFile(jujudPath, []byte(fakeJujud), 0755)
   164  	c.Assert(err, jc.ErrorIsNil)
   165  	toolsPath := filepath.Join(toolsDir, "downloaded-tools.txt")
   166  	testTools := coretools.Tools{Version: version.Current, URL: "http://testing.invalid/tools"}
   167  	data, err := json.Marshal(testTools)
   168  	c.Assert(err, jc.ErrorIsNil)
   169  	err = ioutil.WriteFile(toolsPath, data, 0644)
   170  	c.Assert(err, jc.ErrorIsNil)
   171  	fix.binDir = c.MkDir()
   172  	fix.origPath = os.Getenv("PATH")
   173  	os.Setenv("PATH", fix.binDir+":"+fix.origPath)
   174  	fix.makeBin(c, "status", `echo "blah stop/waiting"`)
   175  	fix.makeBin(c, "stopped-status", `echo "blah stop/waiting"`)
   176  	fix.makeBin(c, "started-status", `echo "blah start/running, process 666"`)
   177  	fix.makeBin(c, "start", "cp $(which started-status) $(which status)")
   178  	fix.makeBin(c, "stop", "cp $(which stopped-status) $(which status)")
   179  
   180  	fix.data = svctesting.NewFakeServiceData()
   181  }
   182  
   183  func (fix *SimpleToolsFixture) TearDown(c *gc.C) {
   184  	os.Setenv("PATH", fix.origPath)
   185  }
   186  
   187  func (fix *SimpleToolsFixture) makeBin(c *gc.C, name, script string) {
   188  	path := filepath.Join(fix.binDir, name)
   189  	err := ioutil.WriteFile(path, []byte("#!/bin/bash --norc\n"+script), 0755)
   190  	c.Assert(err, jc.ErrorIsNil)
   191  }
   192  
   193  func (fix *SimpleToolsFixture) assertUpstartCount(c *gc.C, count int) {
   194  	c.Assert(fix.data.InstalledNames(), gc.HasLen, count)
   195  }
   196  
   197  func (fix *SimpleToolsFixture) getContext(c *gc.C) *deployer.SimpleContext {
   198  	config := agentConfig(names.NewMachineTag("99"), fix.dataDir, fix.logDir)
   199  	return deployer.NewTestSimpleContext(config, fix.logDir, fix.data)
   200  }
   201  
   202  func (fix *SimpleToolsFixture) getContextForMachine(c *gc.C, machineTag names.Tag) *deployer.SimpleContext {
   203  	config := agentConfig(machineTag, fix.dataDir, fix.logDir)
   204  	return deployer.NewTestSimpleContext(config, fix.logDir, fix.data)
   205  }
   206  
   207  func (fix *SimpleToolsFixture) paths(tag names.Tag) (agentDir, toolsDir string) {
   208  	agentDir = agent.Dir(fix.dataDir, tag)
   209  	toolsDir = tools.ToolsDir(fix.dataDir, tag.String())
   210  	return
   211  }
   212  
   213  func (fix *SimpleToolsFixture) checkUnitInstalled(c *gc.C, name, password string) {
   214  	tag := names.NewUnitTag(name)
   215  
   216  	svcName := "jujud-" + tag.String()
   217  	assertContains(c, fix.data.InstalledNames(), svcName)
   218  
   219  	svcConf := fix.data.GetInstalled(svcName).Conf()
   220  	// TODO(ericsnow) For now we just use upstart serialization.
   221  	uconfData, err := upstart.Serialize(svcName, svcConf)
   222  	c.Assert(err, jc.ErrorIsNil)
   223  	uconf := string(uconfData)
   224  
   225  	regex := regexp.MustCompile("(?m)(?:^\\s)*exec\\s.+$")
   226  	execs := regex.FindAllString(uconf, -1)
   227  
   228  	if nil == execs {
   229  		c.Fatalf("no command found in conf:\n%s", uconf)
   230  	} else if 1 > len(execs) {
   231  		c.Fatalf("Test is not built to handle more than one exec line.")
   232  	}
   233  
   234  	_, toolsDir := fix.paths(tag)
   235  	jujudPath := filepath.Join(toolsDir, "jujud"+cmdSuffix)
   236  
   237  	logPath := filepath.Join(fix.logDir, tag.String()+".log")
   238  
   239  	for _, pat := range []string{
   240  		"^exec " + quote + jujudPath + quote + " unit ",
   241  		" --unit-name " + name + " ",
   242  		" >> " + logPath + " 2>&1$",
   243  	} {
   244  		match, err := regexp.MatchString(pat, execs[0])
   245  		c.Assert(err, jc.ErrorIsNil)
   246  		if !match {
   247  			c.Fatalf("failed to match:\n%s\nin:\n%s", pat, execs[0])
   248  		}
   249  	}
   250  
   251  	conf, err := agent.ReadConfig(agent.ConfigPath(fix.dataDir, tag))
   252  	c.Assert(err, jc.ErrorIsNil)
   253  	c.Assert(conf.Tag(), gc.Equals, tag)
   254  	c.Assert(conf.DataDir(), gc.Equals, fix.dataDir)
   255  
   256  	jujudData, err := ioutil.ReadFile(jujudPath)
   257  	c.Assert(err, jc.ErrorIsNil)
   258  	c.Assert(string(jujudData), gc.Equals, fakeJujud)
   259  }
   260  
   261  func (fix *SimpleToolsFixture) checkUnitRemoved(c *gc.C, name string) {
   262  	assertNotContains(c, fix.data.InstalledNames(), name)
   263  
   264  	tag := names.NewUnitTag(name)
   265  	agentDir, toolsDir := fix.paths(tag)
   266  	for _, path := range []string{agentDir, toolsDir} {
   267  		_, err := ioutil.ReadFile(path)
   268  		if err == nil {
   269  			c.Logf("Warning: %q not removed as expected", path)
   270  		} else {
   271  			c.Assert(err, jc.Satisfies, os.IsNotExist)
   272  		}
   273  	}
   274  }
   275  
   276  func (fix *SimpleToolsFixture) injectUnit(c *gc.C, name, unitTag string) {
   277  	fix.data.SetStatus(name, "installed")
   278  
   279  	toolsDir := filepath.Join(fix.dataDir, "tools", unitTag)
   280  	err := os.MkdirAll(toolsDir, 0755)
   281  	c.Assert(err, jc.ErrorIsNil)
   282  }
   283  
   284  type mockConfig struct {
   285  	agent.Config
   286  	tag               names.Tag
   287  	datadir           string
   288  	logdir            string
   289  	upgradedToVersion version.Number
   290  	jobs              []multiwatcher.MachineJob
   291  }
   292  
   293  func (mock *mockConfig) Tag() names.Tag {
   294  	return mock.tag
   295  }
   296  
   297  func (mock *mockConfig) DataDir() string {
   298  	return mock.datadir
   299  }
   300  
   301  func (mock *mockConfig) LogDir() string {
   302  	return mock.logdir
   303  }
   304  
   305  func (mock *mockConfig) Jobs() []multiwatcher.MachineJob {
   306  	return mock.jobs
   307  }
   308  
   309  func (mock *mockConfig) UpgradedToVersion() version.Number {
   310  	return mock.upgradedToVersion
   311  }
   312  
   313  func (mock *mockConfig) WriteUpgradedToVersion(newVersion version.Number) error {
   314  	mock.upgradedToVersion = newVersion
   315  	return nil
   316  }
   317  
   318  func (mock *mockConfig) Environment() names.EnvironTag {
   319  	return testing.EnvironmentTag
   320  }
   321  
   322  func (mock *mockConfig) CACert() string {
   323  	return testing.CACert
   324  }
   325  
   326  func (mock *mockConfig) Value(_ string) string {
   327  	return ""
   328  }
   329  
   330  func agentConfig(tag names.Tag, datadir, logdir string) agent.Config {
   331  	return &mockConfig{tag: tag, datadir: datadir, logdir: logdir}
   332  }
   333  
   334  // assertContains asserts a needle is contained within haystack
   335  func assertContains(c *gc.C, haystack []string, needle string) {
   336  	c.Assert(contains(haystack, needle), jc.IsTrue)
   337  }
   338  
   339  // assertNotContains asserts a needle is not contained within haystack
   340  func assertNotContains(c *gc.C, haystack []string, needle string) {
   341  	c.Assert(contains(haystack, needle), gc.Not(jc.IsTrue))
   342  }
   343  
   344  func contains(haystack []string, needle string) bool {
   345  	for _, e := range haystack {
   346  		if e == needle {
   347  			return true
   348  		}
   349  	}
   350  	return false
   351  }