4 # Copyright (C) 2006, 2007 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Script for unittesting the hooks module"""
31 from ganeti import errors
32 from ganeti import opcodes
33 from ganeti import mcpu
34 from ganeti import backend
35 from ganeti import constants
36 from ganeti import cmdlib
37 from ganeti import rpc
38 from ganeti import compat
39 from ganeti.constants import HKR_SUCCESS, HKR_FAIL, HKR_SKIP
41 from mocks import FakeConfig, FakeProc, FakeContext
46 class FakeLU(cmdlib.LogicalUnit):
49 def BuildHooksEnv(self):
52 def BuildHooksNodes(self):
53 return ["localhost"], ["localhost"]
56 class TestHooksRunner(unittest.TestCase):
57 """Testing case for HooksRunner"""
60 self.tmpdir = tempfile.mkdtemp()
61 self.torm.append((self.tmpdir, True))
62 self.logdir = tempfile.mkdtemp()
63 self.torm.append((self.logdir, True))
66 for i in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
67 dname = "%s/%s-%s.d" % (self.tmpdir, self.hpath, i)
69 self.torm.append((dname, True))
70 self.ph_dirs[i] = dname
71 self.hr = backend.HooksRunner(hooks_base_dir=self.tmpdir)
75 for path, kind in self.torm:
81 def _rname(self, fname):
82 return "/".join(fname.split("/")[-2:])
86 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
87 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}), [])
89 def testSkipNonExec(self):
90 """Test skip non-exec file"""
91 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
92 fname = "%s/test" % self.ph_dirs[phase]
95 self.torm.append((fname, False))
96 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
97 [(self._rname(fname), HKR_SKIP, "")])
99 def testSkipInvalidName(self):
100 """Test skip script with invalid name"""
101 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
102 fname = "%s/a.off" % self.ph_dirs[phase]
104 f.write("#!/bin/sh\nexit 0\n")
106 os.chmod(fname, 0700)
107 self.torm.append((fname, False))
108 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
109 [(self._rname(fname), HKR_SKIP, "")])
111 def testSkipDir(self):
112 """Test skip directory"""
113 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
114 fname = "%s/testdir" % self.ph_dirs[phase]
116 self.torm.append((fname, True))
117 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
118 [(self._rname(fname), HKR_SKIP, "")])
120 def testSuccess(self):
121 """Test success execution"""
122 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
123 fname = "%s/success" % self.ph_dirs[phase]
125 f.write("#!/bin/sh\nexit 0\n")
127 self.torm.append((fname, False))
128 os.chmod(fname, 0700)
129 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
130 [(self._rname(fname), HKR_SUCCESS, "")])
132 def testSymlink(self):
133 """Test running a symlink"""
134 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
135 fname = "%s/success" % self.ph_dirs[phase]
136 os.symlink("/bin/true", fname)
137 self.torm.append((fname, False))
138 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
139 [(self._rname(fname), HKR_SUCCESS, "")])
142 """Test success execution"""
143 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
144 fname = "%s/success" % self.ph_dirs[phase]
146 f.write("#!/bin/sh\nexit 1\n")
148 self.torm.append((fname, False))
149 os.chmod(fname, 0700)
150 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
151 [(self._rname(fname), HKR_FAIL, "")])
153 def testCombined(self):
154 """Test success, failure and skip all in one test"""
155 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
157 for fbase, ecode, rs in [("00succ", 0, HKR_SUCCESS),
158 ("10fail", 1, HKR_FAIL),
159 ("20inv.", 0, HKR_SKIP),
161 fname = "%s/%s" % (self.ph_dirs[phase], fbase)
163 f.write("#!/bin/sh\nexit %d\n" % ecode)
165 self.torm.append((fname, False))
166 os.chmod(fname, 0700)
167 expect.append((self._rname(fname), rs, ""))
168 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}), expect)
170 def testOrdering(self):
171 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
173 for fbase in ["10s1",
179 fname = "%s/%s" % (self.ph_dirs[phase], fbase)
180 os.symlink("/bin/true", fname)
181 self.torm.append((fname, False))
182 expect.append((self._rname(fname), HKR_SUCCESS, ""))
184 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}), expect)
187 """Test environment execution"""
188 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
190 fname = "%s/%s" % (self.ph_dirs[phase], fbase)
191 os.symlink("/usr/bin/env", fname)
192 self.torm.append((fname, False))
193 env_snt = {"PHASE": phase}
194 env_exp = "PHASE=%s" % phase
195 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, env_snt),
196 [(self._rname(fname), HKR_SUCCESS, env_exp)])
199 def FakeHooksRpcSuccess(node_list, hpath, phase, env):
200 """Fake call_hooks_runner function.
202 @rtype: dict of node -> L{rpc.RpcResult} with a successful script result
203 @return: script execution from all nodes
207 return dict([(node, rr((True, [("utest", constants.HKR_SUCCESS, "ok")]),
208 node=node, call='FakeScriptOk'))
209 for node in node_list])
212 class TestHooksMaster(unittest.TestCase):
213 """Testing case for HooksMaster"""
215 def _call_false(*args):
216 """Fake call_hooks_runner function which returns False."""
220 def _call_nodes_false(node_list, hpath, phase, env):
221 """Fake call_hooks_runner function.
223 @rtype: dict of node -> L{rpc.RpcResult} with an rpc error
224 @return: rpc failure from all nodes
227 return dict([(node, rpc.RpcResult('error', failed=True,
228 node=node, call='FakeError')) for node in node_list])
231 def _call_script_fail(node_list, hpath, phase, env):
232 """Fake call_hooks_runner function.
234 @rtype: dict of node -> L{rpc.RpcResult} with a failed script result
235 @return: script execution failure from all nodes
239 return dict([(node, rr((True, [("utest", constants.HKR_FAIL, "err")]),
240 node=node, call='FakeScriptFail'))
241 for node in node_list])
244 self.op = opcodes.OpCode()
245 self.context = FakeContext()
246 # WARNING: here we pass None as RpcRunner instance since we know
247 # our usage via HooksMaster will not use lu.rpc
248 self.lu = FakeLU(FakeProc(), self.op, self.context, None)
250 def testTotalFalse(self):
251 """Test complete rpc failure"""
252 hm = mcpu.HooksMaster.BuildFromLu(self._call_false, self.lu)
253 self.failUnlessRaises(errors.HooksFailure,
254 hm.RunPhase, constants.HOOKS_PHASE_PRE)
255 hm.RunPhase(constants.HOOKS_PHASE_POST)
257 def testIndividualFalse(self):
258 """Test individual node failure"""
259 hm = mcpu.HooksMaster.BuildFromLu(self._call_nodes_false, self.lu)
260 hm.RunPhase(constants.HOOKS_PHASE_PRE)
261 #self.failUnlessRaises(errors.HooksFailure,
262 # hm.RunPhase, constants.HOOKS_PHASE_PRE)
263 hm.RunPhase(constants.HOOKS_PHASE_POST)
265 def testScriptFalse(self):
266 """Test individual rpc failure"""
267 hm = mcpu.HooksMaster.BuildFromLu(self._call_script_fail, self.lu)
268 self.failUnlessRaises(errors.HooksAbort,
269 hm.RunPhase, constants.HOOKS_PHASE_PRE)
270 hm.RunPhase(constants.HOOKS_PHASE_POST)
272 def testScriptSucceed(self):
273 """Test individual rpc failure"""
274 hm = mcpu.HooksMaster.BuildFromLu(FakeHooksRpcSuccess, self.lu)
275 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
279 class FakeEnvLU(cmdlib.LogicalUnit):
280 HPATH = "env_test_lu"
281 HTYPE = constants.HTYPE_GROUP
283 def __init__(self, *args):
284 cmdlib.LogicalUnit.__init__(self, *args)
287 def BuildHooksEnv(self):
288 assert self.hook_env is not None
291 def BuildHooksNodes(self):
292 return (["localhost"], ["localhost"])
295 class FakeNoHooksLU(cmdlib.NoHooksLU):
299 class TestHooksRunnerEnv(unittest.TestCase):
303 self.op = opcodes.OpTestDummy(result=False, messages=[], fail=False)
304 self.lu = FakeEnvLU(FakeProc(), self.op, FakeContext(), None)
306 def _HooksRpc(self, *args):
307 self._rpcs.append(args)
308 return FakeHooksRpcSuccess(*args)
310 def _CheckEnv(self, env, phase, hpath):
311 self.assertTrue(env["PATH"].startswith("/sbin"))
312 self.assertEqual(env["GANETI_HOOKS_PHASE"], phase)
313 self.assertEqual(env["GANETI_HOOKS_PATH"], hpath)
314 self.assertEqual(env["GANETI_OP_CODE"], self.op.OP_ID)
315 self.assertEqual(env["GANETI_HOOKS_VERSION"], str(constants.HOOKS_VERSION))
316 self.assertEqual(env["GANETI_DATA_DIR"], constants.DATA_DIR)
317 if "GANETI_OBJECT_TYPE" in env:
318 self.assertEqual(env["GANETI_OBJECT_TYPE"], constants.HTYPE_GROUP)
320 self.assertTrue(self.lu.HTYPE is None)
322 def testEmptyEnv(self):
323 # Check pre-phase hook
324 self.lu.hook_env = {}
325 hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
326 hm.RunPhase(constants.HOOKS_PHASE_PRE)
328 (node_list, hpath, phase, env) = self._rpcs.pop(0)
329 self.assertEqual(node_list, set(["localhost"]))
330 self.assertEqual(hpath, self.lu.HPATH)
331 self.assertEqual(phase, constants.HOOKS_PHASE_PRE)
332 self._CheckEnv(env, constants.HOOKS_PHASE_PRE, self.lu.HPATH)
334 # Check post-phase hook
335 self.lu.hook_env = {}
336 hm.RunPhase(constants.HOOKS_PHASE_POST)
338 (node_list, hpath, phase, env) = self._rpcs.pop(0)
339 self.assertEqual(node_list, set(["localhost"]))
340 self.assertEqual(hpath, self.lu.HPATH)
341 self.assertEqual(phase, constants.HOOKS_PHASE_POST)
342 self._CheckEnv(env, constants.HOOKS_PHASE_POST, self.lu.HPATH)
344 self.assertRaises(IndexError, self._rpcs.pop)
347 # Check pre-phase hook
349 "FOO": "pre-foo-value",
351 hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
352 hm.RunPhase(constants.HOOKS_PHASE_PRE)
354 (node_list, hpath, phase, env) = self._rpcs.pop(0)
355 self.assertEqual(node_list, set(["localhost"]))
356 self.assertEqual(hpath, self.lu.HPATH)
357 self.assertEqual(phase, constants.HOOKS_PHASE_PRE)
358 self.assertEqual(env["GANETI_FOO"], "pre-foo-value")
359 self.assertFalse(compat.any(key.startswith("GANETI_POST") for key in env))
360 self._CheckEnv(env, constants.HOOKS_PHASE_PRE, self.lu.HPATH)
362 # Check post-phase hook
367 hm.RunPhase(constants.HOOKS_PHASE_POST)
369 (node_list, hpath, phase, env) = self._rpcs.pop(0)
370 self.assertEqual(node_list, set(["localhost"]))
371 self.assertEqual(hpath, self.lu.HPATH)
372 self.assertEqual(phase, constants.HOOKS_PHASE_POST)
373 self.assertEqual(env["GANETI_FOO"], "pre-foo-value")
374 self.assertEqual(env["GANETI_POST_FOO"], "post-value")
375 self.assertEqual(env["GANETI_POST_BAR"], "123")
376 self.assertFalse("GANETI_BAR" in env)
377 self._CheckEnv(env, constants.HOOKS_PHASE_POST, self.lu.HPATH)
379 self.assertRaises(IndexError, self._rpcs.pop)
381 # Check configuration update hook
383 (node_list, hpath, phase, env) = self._rpcs.pop(0)
384 self.assertEqual(set(node_list), set([self.lu.cfg.GetMasterNode()]))
385 self.assertEqual(hpath, constants.HOOKS_NAME_CFGUPDATE)
386 self.assertEqual(phase, constants.HOOKS_PHASE_POST)
387 self._CheckEnv(env, constants.HOOKS_PHASE_POST,
388 constants.HOOKS_NAME_CFGUPDATE)
389 self.assertFalse(compat.any(key.startswith("GANETI_POST") for key in env))
390 self.assertEqual(env["GANETI_FOO"], "pre-foo-value")
391 self.assertRaises(IndexError, self._rpcs.pop)
393 def testConflict(self):
394 for name in ["DATA_DIR", "OP_CODE"]:
395 self.lu.hook_env = { name: "value" }
397 # Test using a clean HooksMaster instance
398 hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
400 for phase in [constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST]:
401 self.assertRaises(AssertionError, hm.RunPhase, phase)
402 self.assertRaises(IndexError, self._rpcs.pop)
404 def testNoNodes(self):
405 self.lu.hook_env = {}
406 hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
407 hm.RunPhase(constants.HOOKS_PHASE_PRE, nodes=[])
408 self.assertRaises(IndexError, self._rpcs.pop)
410 def testSpecificNodes(self):
411 self.lu.hook_env = {}
415 "node93782.example.net",
418 hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
420 for phase in [constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST]:
421 hm.RunPhase(phase, nodes=nodes)
423 (node_list, hpath, rpc_phase, env) = self._rpcs.pop(0)
424 self.assertEqual(set(node_list), set(nodes))
425 self.assertEqual(hpath, self.lu.HPATH)
426 self.assertEqual(rpc_phase, phase)
427 self._CheckEnv(env, phase, self.lu.HPATH)
429 self.assertRaises(IndexError, self._rpcs.pop)
431 def testRunConfigUpdateNoPre(self):
436 hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
439 (node_list, hpath, phase, env) = self._rpcs.pop(0)
440 self.assertEqual(set(node_list), set([self.lu.cfg.GetMasterNode()]))
441 self.assertEqual(hpath, constants.HOOKS_NAME_CFGUPDATE)
442 self.assertEqual(phase, constants.HOOKS_PHASE_POST)
443 self.assertEqual(env["GANETI_FOO"], "value")
444 self.assertFalse(compat.any(key.startswith("GANETI_POST") for key in env))
445 self._CheckEnv(env, constants.HOOKS_PHASE_POST,
446 constants.HOOKS_NAME_CFGUPDATE)
448 self.assertRaises(IndexError, self._rpcs.pop)
450 def testNoPreBeforePost(self):
455 hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
456 hm.RunPhase(constants.HOOKS_PHASE_POST)
458 (node_list, hpath, phase, env) = self._rpcs.pop(0)
459 self.assertEqual(node_list, set(["localhost"]))
460 self.assertEqual(hpath, self.lu.HPATH)
461 self.assertEqual(phase, constants.HOOKS_PHASE_POST)
462 self.assertEqual(env["GANETI_FOO"], "value")
463 self.assertEqual(env["GANETI_POST_FOO"], "value")
464 self._CheckEnv(env, constants.HOOKS_PHASE_POST, self.lu.HPATH)
466 self.assertRaises(IndexError, self._rpcs.pop)
468 def testNoHooksLU(self):
469 self.lu = FakeNoHooksLU(FakeProc(), self.op, FakeContext(), None)
470 self.assertRaises(AssertionError, self.lu.BuildHooksEnv)
471 self.assertRaises(AssertionError, self.lu.BuildHooksNodes)
473 hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
474 self.assertEqual(hm.pre_env, {})
475 self.assertRaises(IndexError, self._rpcs.pop)
477 hm.RunPhase(constants.HOOKS_PHASE_PRE)
478 self.assertRaises(IndexError, self._rpcs.pop)
480 hm.RunPhase(constants.HOOKS_PHASE_POST)
481 self.assertRaises(IndexError, self._rpcs.pop)
485 (node_list, hpath, phase, env) = self._rpcs.pop(0)
486 self.assertEqual(set(node_list), set([self.lu.cfg.GetMasterNode()]))
487 self.assertEqual(hpath, constants.HOOKS_NAME_CFGUPDATE)
488 self.assertEqual(phase, constants.HOOKS_PHASE_POST)
489 self.assertFalse(compat.any(key.startswith("GANETI_POST") for key in env))
490 self._CheckEnv(env, constants.HOOKS_PHASE_POST,
491 constants.HOOKS_NAME_CFGUPDATE)
492 self.assertRaises(IndexError, self._rpcs.pop)
494 assert isinstance(self.lu, FakeNoHooksLU), "LU was replaced"
497 if __name__ == '__main__':
498 testutils.GanetiTestProgram()