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 hooksmaster
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 import pathutils
40 from ganeti.constants import HKR_SUCCESS, HKR_FAIL, HKR_SKIP
42 from mocks import FakeConfig, FakeProc, FakeContext
47 class FakeLU(cmdlib.LogicalUnit):
50 def BuildHooksEnv(self):
53 def BuildHooksNodes(self):
54 return ["localhost"], ["localhost"]
57 class TestHooksRunner(unittest.TestCase):
58 """Testing case for HooksRunner"""
61 self.tmpdir = tempfile.mkdtemp()
62 self.torm.append((self.tmpdir, True))
63 self.logdir = tempfile.mkdtemp()
64 self.torm.append((self.logdir, True))
67 for i in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
68 dname = "%s/%s-%s.d" % (self.tmpdir, self.hpath, i)
70 self.torm.append((dname, True))
71 self.ph_dirs[i] = dname
72 self.hr = backend.HooksRunner(hooks_base_dir=self.tmpdir)
76 for path, kind in self.torm:
82 def _rname(self, fname):
83 return "/".join(fname.split("/")[-2:])
87 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
88 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}), [])
90 def testSkipNonExec(self):
91 """Test skip non-exec file"""
92 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
93 fname = "%s/test" % self.ph_dirs[phase]
96 self.torm.append((fname, False))
97 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
98 [(self._rname(fname), HKR_SKIP, "")])
100 def testSkipInvalidName(self):
101 """Test skip script with invalid name"""
102 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
103 fname = "%s/a.off" % self.ph_dirs[phase]
105 f.write("#!/bin/sh\nexit 0\n")
107 os.chmod(fname, 0700)
108 self.torm.append((fname, False))
109 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
110 [(self._rname(fname), HKR_SKIP, "")])
112 def testSkipDir(self):
113 """Test skip directory"""
114 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
115 fname = "%s/testdir" % self.ph_dirs[phase]
117 self.torm.append((fname, True))
118 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
119 [(self._rname(fname), HKR_SKIP, "")])
121 def testSuccess(self):
122 """Test success execution"""
123 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
124 fname = "%s/success" % self.ph_dirs[phase]
126 f.write("#!/bin/sh\nexit 0\n")
128 self.torm.append((fname, False))
129 os.chmod(fname, 0700)
130 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
131 [(self._rname(fname), HKR_SUCCESS, "")])
133 def testSymlink(self):
134 """Test running a symlink"""
135 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
136 fname = "%s/success" % self.ph_dirs[phase]
137 os.symlink("/bin/true", fname)
138 self.torm.append((fname, False))
139 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
140 [(self._rname(fname), HKR_SUCCESS, "")])
143 """Test success execution"""
144 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
145 fname = "%s/success" % self.ph_dirs[phase]
147 f.write("#!/bin/sh\nexit 1\n")
149 self.torm.append((fname, False))
150 os.chmod(fname, 0700)
151 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
152 [(self._rname(fname), HKR_FAIL, "")])
154 def testCombined(self):
155 """Test success, failure and skip all in one test"""
156 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
158 for fbase, ecode, rs in [("00succ", 0, HKR_SUCCESS),
159 ("10fail", 1, HKR_FAIL),
160 ("20inv.", 0, HKR_SKIP),
162 fname = "%s/%s" % (self.ph_dirs[phase], fbase)
164 f.write("#!/bin/sh\nexit %d\n" % ecode)
166 self.torm.append((fname, False))
167 os.chmod(fname, 0700)
168 expect.append((self._rname(fname), rs, ""))
169 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}), expect)
171 def testOrdering(self):
172 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
174 for fbase in ["10s1",
180 fname = "%s/%s" % (self.ph_dirs[phase], fbase)
181 os.symlink("/bin/true", fname)
182 self.torm.append((fname, False))
183 expect.append((self._rname(fname), HKR_SUCCESS, ""))
185 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}), expect)
188 """Test environment execution"""
189 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
191 fname = "%s/%s" % (self.ph_dirs[phase], fbase)
192 os.symlink("/usr/bin/env", fname)
193 self.torm.append((fname, False))
194 env_snt = {"PHASE": phase}
195 env_exp = "PHASE=%s" % phase
196 self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, env_snt),
197 [(self._rname(fname), HKR_SUCCESS, env_exp)])
200 def FakeHooksRpcSuccess(node_list, hpath, phase, env):
201 """Fake call_hooks_runner function.
203 @rtype: dict of node -> L{rpc.RpcResult} with a successful script result
204 @return: script execution from all nodes
208 return dict([(node, rr((True, [("utest", constants.HKR_SUCCESS, "ok")]),
209 node=node, call="FakeScriptOk"))
210 for node in node_list])
213 class TestHooksMaster(unittest.TestCase):
214 """Testing case for HooksMaster"""
216 def _call_false(*args):
217 """Fake call_hooks_runner function which returns False."""
221 def _call_nodes_false(node_list, hpath, phase, env):
222 """Fake call_hooks_runner function.
224 @rtype: dict of node -> L{rpc.RpcResult} with an rpc error
225 @return: rpc failure from all nodes
228 return dict([(node, rpc.RpcResult("error", failed=True,
229 node=node, call="FakeError")) for node in node_list])
232 def _call_script_fail(node_list, hpath, phase, env):
233 """Fake call_hooks_runner function.
235 @rtype: dict of node -> L{rpc.RpcResult} with a failed script result
236 @return: script execution failure from all nodes
240 return dict([(node, rr((True, [("utest", constants.HKR_FAIL, "err")]),
241 node=node, call="FakeScriptFail"))
242 for node in node_list])
245 self.op = opcodes.OpCode()
246 self.context = FakeContext()
247 # WARNING: here we pass None as RpcRunner instance since we know
248 # our usage via HooksMaster will not use lu.rpc
249 self.lu = FakeLU(FakeProc(), self.op, self.context, None)
251 def testTotalFalse(self):
252 """Test complete rpc failure"""
253 hm = hooksmaster.HooksMaster.BuildFromLu(self._call_false, self.lu)
254 self.failUnlessRaises(errors.HooksFailure,
255 hm.RunPhase, constants.HOOKS_PHASE_PRE)
256 hm.RunPhase(constants.HOOKS_PHASE_POST)
258 def testIndividualFalse(self):
259 """Test individual node failure"""
260 hm = hooksmaster.HooksMaster.BuildFromLu(self._call_nodes_false, self.lu)
261 hm.RunPhase(constants.HOOKS_PHASE_PRE)
262 #self.failUnlessRaises(errors.HooksFailure,
263 # hm.RunPhase, constants.HOOKS_PHASE_PRE)
264 hm.RunPhase(constants.HOOKS_PHASE_POST)
266 def testScriptFalse(self):
267 """Test individual rpc failure"""
268 hm = hooksmaster.HooksMaster.BuildFromLu(self._call_script_fail, self.lu)
269 self.failUnlessRaises(errors.HooksAbort,
270 hm.RunPhase, constants.HOOKS_PHASE_PRE)
271 hm.RunPhase(constants.HOOKS_PHASE_POST)
273 def testScriptSucceed(self):
274 """Test individual rpc failure"""
275 hm = hooksmaster.HooksMaster.BuildFromLu(FakeHooksRpcSuccess, self.lu)
276 for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
280 class FakeEnvLU(cmdlib.LogicalUnit):
281 HPATH = "env_test_lu"
282 HTYPE = constants.HTYPE_GROUP
284 def __init__(self, *args):
285 cmdlib.LogicalUnit.__init__(self, *args)
288 def BuildHooksEnv(self):
289 assert self.hook_env is not None
292 def BuildHooksNodes(self):
293 return (["localhost"], ["localhost"])
296 class FakeNoHooksLU(cmdlib.NoHooksLU):
300 class TestHooksRunnerEnv(unittest.TestCase):
304 self.op = opcodes.OpTestDummy(result=False, messages=[], fail=False)
305 self.lu = FakeEnvLU(FakeProc(), self.op, FakeContext(), None)
307 def _HooksRpc(self, *args):
308 self._rpcs.append(args)
309 return FakeHooksRpcSuccess(*args)
311 def _CheckEnv(self, env, phase, hpath):
312 self.assertTrue(env["PATH"].startswith("/sbin"))
313 self.assertEqual(env["GANETI_HOOKS_PHASE"], phase)
314 self.assertEqual(env["GANETI_HOOKS_PATH"], hpath)
315 self.assertEqual(env["GANETI_OP_CODE"], self.op.OP_ID)
316 self.assertEqual(env["GANETI_HOOKS_VERSION"], str(constants.HOOKS_VERSION))
317 self.assertEqual(env["GANETI_DATA_DIR"], pathutils.DATA_DIR)
318 if "GANETI_OBJECT_TYPE" in env:
319 self.assertEqual(env["GANETI_OBJECT_TYPE"], constants.HTYPE_GROUP)
321 self.assertTrue(self.lu.HTYPE is None)
323 def testEmptyEnv(self):
324 # Check pre-phase hook
325 self.lu.hook_env = {}
326 hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
327 hm.RunPhase(constants.HOOKS_PHASE_PRE)
329 (node_list, hpath, phase, env) = self._rpcs.pop(0)
330 self.assertEqual(node_list, set(["localhost"]))
331 self.assertEqual(hpath, self.lu.HPATH)
332 self.assertEqual(phase, constants.HOOKS_PHASE_PRE)
333 self._CheckEnv(env, constants.HOOKS_PHASE_PRE, self.lu.HPATH)
335 # Check post-phase hook
336 self.lu.hook_env = {}
337 hm.RunPhase(constants.HOOKS_PHASE_POST)
339 (node_list, hpath, phase, env) = self._rpcs.pop(0)
340 self.assertEqual(node_list, set(["localhost"]))
341 self.assertEqual(hpath, self.lu.HPATH)
342 self.assertEqual(phase, constants.HOOKS_PHASE_POST)
343 self._CheckEnv(env, constants.HOOKS_PHASE_POST, self.lu.HPATH)
345 self.assertRaises(IndexError, self._rpcs.pop)
348 # Check pre-phase hook
350 "FOO": "pre-foo-value",
352 hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
353 hm.RunPhase(constants.HOOKS_PHASE_PRE)
355 (node_list, hpath, phase, env) = self._rpcs.pop(0)
356 self.assertEqual(node_list, set(["localhost"]))
357 self.assertEqual(hpath, self.lu.HPATH)
358 self.assertEqual(phase, constants.HOOKS_PHASE_PRE)
359 self.assertEqual(env["GANETI_FOO"], "pre-foo-value")
360 self.assertFalse(compat.any(key.startswith("GANETI_POST") for key in env))
361 self._CheckEnv(env, constants.HOOKS_PHASE_PRE, self.lu.HPATH)
363 # Check post-phase hook
368 hm.RunPhase(constants.HOOKS_PHASE_POST)
370 (node_list, hpath, phase, env) = self._rpcs.pop(0)
371 self.assertEqual(node_list, set(["localhost"]))
372 self.assertEqual(hpath, self.lu.HPATH)
373 self.assertEqual(phase, constants.HOOKS_PHASE_POST)
374 self.assertEqual(env["GANETI_FOO"], "pre-foo-value")
375 self.assertEqual(env["GANETI_POST_FOO"], "post-value")
376 self.assertEqual(env["GANETI_POST_BAR"], "123")
377 self.assertFalse("GANETI_BAR" in env)
378 self._CheckEnv(env, constants.HOOKS_PHASE_POST, self.lu.HPATH)
380 self.assertRaises(IndexError, self._rpcs.pop)
382 # Check configuration update hook
384 (node_list, hpath, phase, env) = self._rpcs.pop(0)
385 self.assertEqual(set(node_list), set([self.lu.cfg.GetMasterNode()]))
386 self.assertEqual(hpath, constants.HOOKS_NAME_CFGUPDATE)
387 self.assertEqual(phase, constants.HOOKS_PHASE_POST)
388 self._CheckEnv(env, constants.HOOKS_PHASE_POST,
389 constants.HOOKS_NAME_CFGUPDATE)
390 self.assertFalse(compat.any(key.startswith("GANETI_POST") for key in env))
391 self.assertEqual(env["GANETI_FOO"], "pre-foo-value")
392 self.assertRaises(IndexError, self._rpcs.pop)
394 def testConflict(self):
395 for name in ["DATA_DIR", "OP_CODE"]:
396 self.lu.hook_env = { name: "value" }
398 # Test using a clean HooksMaster instance
399 hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
401 for phase in [constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST]:
402 self.assertRaises(AssertionError, hm.RunPhase, phase)
403 self.assertRaises(IndexError, self._rpcs.pop)
405 def testNoNodes(self):
406 self.lu.hook_env = {}
407 hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
408 hm.RunPhase(constants.HOOKS_PHASE_PRE, nodes=[])
409 self.assertRaises(IndexError, self._rpcs.pop)
411 def testSpecificNodes(self):
412 self.lu.hook_env = {}
416 "node93782.example.net",
419 hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
421 for phase in [constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST]:
422 hm.RunPhase(phase, nodes=nodes)
424 (node_list, hpath, rpc_phase, env) = self._rpcs.pop(0)
425 self.assertEqual(set(node_list), set(nodes))
426 self.assertEqual(hpath, self.lu.HPATH)
427 self.assertEqual(rpc_phase, phase)
428 self._CheckEnv(env, phase, self.lu.HPATH)
430 self.assertRaises(IndexError, self._rpcs.pop)
432 def testRunConfigUpdateNoPre(self):
437 hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
440 (node_list, hpath, phase, env) = self._rpcs.pop(0)
441 self.assertEqual(set(node_list), set([self.lu.cfg.GetMasterNode()]))
442 self.assertEqual(hpath, constants.HOOKS_NAME_CFGUPDATE)
443 self.assertEqual(phase, constants.HOOKS_PHASE_POST)
444 self.assertEqual(env["GANETI_FOO"], "value")
445 self.assertFalse(compat.any(key.startswith("GANETI_POST") for key in env))
446 self._CheckEnv(env, constants.HOOKS_PHASE_POST,
447 constants.HOOKS_NAME_CFGUPDATE)
449 self.assertRaises(IndexError, self._rpcs.pop)
451 def testNoPreBeforePost(self):
456 hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
457 hm.RunPhase(constants.HOOKS_PHASE_POST)
459 (node_list, hpath, phase, env) = self._rpcs.pop(0)
460 self.assertEqual(node_list, set(["localhost"]))
461 self.assertEqual(hpath, self.lu.HPATH)
462 self.assertEqual(phase, constants.HOOKS_PHASE_POST)
463 self.assertEqual(env["GANETI_FOO"], "value")
464 self.assertEqual(env["GANETI_POST_FOO"], "value")
465 self._CheckEnv(env, constants.HOOKS_PHASE_POST, self.lu.HPATH)
467 self.assertRaises(IndexError, self._rpcs.pop)
469 def testNoHooksLU(self):
470 self.lu = FakeNoHooksLU(FakeProc(), self.op, FakeContext(), None)
471 self.assertRaises(AssertionError, self.lu.BuildHooksEnv)
472 self.assertRaises(AssertionError, self.lu.BuildHooksNodes)
474 hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
475 self.assertEqual(hm.pre_env, {})
476 self.assertRaises(IndexError, self._rpcs.pop)
478 hm.RunPhase(constants.HOOKS_PHASE_PRE)
479 self.assertRaises(IndexError, self._rpcs.pop)
481 hm.RunPhase(constants.HOOKS_PHASE_POST)
482 self.assertRaises(IndexError, self._rpcs.pop)
486 (node_list, hpath, phase, env) = self._rpcs.pop(0)
487 self.assertEqual(set(node_list), set([self.lu.cfg.GetMasterNode()]))
488 self.assertEqual(hpath, constants.HOOKS_NAME_CFGUPDATE)
489 self.assertEqual(phase, constants.HOOKS_PHASE_POST)
490 self.assertFalse(compat.any(key.startswith("GANETI_POST") for key in env))
491 self._CheckEnv(env, constants.HOOKS_PHASE_POST,
492 constants.HOOKS_NAME_CFGUPDATE)
493 self.assertRaises(IndexError, self._rpcs.pop)
495 assert isinstance(self.lu, FakeNoHooksLU), "LU was replaced"
498 if __name__ == "__main__":
499 testutils.GanetiTestProgram()