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