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 }