Update default instance kernel version
[ganeti-local] / test / ganeti.hooks_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2006, 2007 Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20
21
22 """Script for unittesting the hooks module"""
23
24
25 import unittest
26 import os
27 import time
28 import tempfile
29 import os.path
30
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
40
41 from mocks import FakeConfig, FakeProc, FakeContext
42
43 import testutils
44
45
46 class FakeLU(cmdlib.LogicalUnit):
47   HPATH = "test"
48
49   def BuildHooksEnv(self):
50     return {}
51
52   def BuildHooksNodes(self):
53     return ["localhost"], ["localhost"]
54
55
56 class TestHooksRunner(unittest.TestCase):
57   """Testing case for HooksRunner"""
58   def setUp(self):
59     self.torm = []
60     self.tmpdir = tempfile.mkdtemp()
61     self.torm.append((self.tmpdir, True))
62     self.logdir = tempfile.mkdtemp()
63     self.torm.append((self.logdir, True))
64     self.hpath = "fake"
65     self.ph_dirs = {}
66     for i in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
67       dname = "%s/%s-%s.d" % (self.tmpdir, self.hpath, i)
68       os.mkdir(dname)
69       self.torm.append((dname, True))
70       self.ph_dirs[i] = dname
71     self.hr = backend.HooksRunner(hooks_base_dir=self.tmpdir)
72
73   def tearDown(self):
74     self.torm.reverse()
75     for path, kind in self.torm:
76       if kind:
77         os.rmdir(path)
78       else:
79         os.unlink(path)
80
81   def _rname(self, fname):
82     return "/".join(fname.split("/")[-2:])
83
84   def testEmpty(self):
85     """Test no hooks"""
86     for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
87       self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}), [])
88
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]
93       f = open(fname, "w")
94       f.close()
95       self.torm.append((fname, False))
96       self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
97                            [(self._rname(fname), HKR_SKIP, "")])
98
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]
103       f = open(fname, "w")
104       f.write("#!/bin/sh\nexit 0\n")
105       f.close()
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, "")])
110
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]
115       os.mkdir(fname)
116       self.torm.append((fname, True))
117       self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
118                            [(self._rname(fname), HKR_SKIP, "")])
119
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]
124       f = open(fname, "w")
125       f.write("#!/bin/sh\nexit 0\n")
126       f.close()
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, "")])
131
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, "")])
140
141   def testFail(self):
142     """Test success execution"""
143     for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
144       fname = "%s/success" % self.ph_dirs[phase]
145       f = open(fname, "w")
146       f.write("#!/bin/sh\nexit 1\n")
147       f.close()
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, "")])
152
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):
156       expect = []
157       for fbase, ecode, rs in [("00succ", 0, HKR_SUCCESS),
158                                ("10fail", 1, HKR_FAIL),
159                                ("20inv.", 0, HKR_SKIP),
160                                ]:
161         fname = "%s/%s" % (self.ph_dirs[phase], fbase)
162         f = open(fname, "w")
163         f.write("#!/bin/sh\nexit %d\n" % ecode)
164         f.close()
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)
169
170   def testOrdering(self):
171     for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
172       expect = []
173       for fbase in ["10s1",
174                     "00s0",
175                     "10sa",
176                     "80sc",
177                     "60sd",
178                     ]:
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, ""))
183       expect.sort()
184       self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}), expect)
185
186   def testEnv(self):
187     """Test environment execution"""
188     for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
189       fbase = "success"
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)])
197
198
199 def FakeHooksRpcSuccess(node_list, hpath, phase, env):
200   """Fake call_hooks_runner function.
201
202   @rtype: dict of node -> L{rpc.RpcResult} with a successful script result
203   @return: script execution from all nodes
204
205   """
206   rr = rpc.RpcResult
207   return dict([(node, rr((True, [("utest", constants.HKR_SUCCESS, "ok")]),
208                          node=node, call='FakeScriptOk'))
209                for node in node_list])
210
211
212 class TestHooksMaster(unittest.TestCase):
213   """Testing case for HooksMaster"""
214
215   def _call_false(*args):
216     """Fake call_hooks_runner function which returns False."""
217     return False
218
219   @staticmethod
220   def _call_nodes_false(node_list, hpath, phase, env):
221     """Fake call_hooks_runner function.
222
223     @rtype: dict of node -> L{rpc.RpcResult} with an rpc error
224     @return: rpc failure from all nodes
225
226     """
227     return dict([(node, rpc.RpcResult('error', failed=True,
228                   node=node, call='FakeError')) for node in node_list])
229
230   @staticmethod
231   def _call_script_fail(node_list, hpath, phase, env):
232     """Fake call_hooks_runner function.
233
234     @rtype: dict of node -> L{rpc.RpcResult} with a failed script result
235     @return: script execution failure from all nodes
236
237     """
238     rr = rpc.RpcResult
239     return dict([(node, rr((True, [("utest", constants.HKR_FAIL, "err")]),
240                            node=node, call='FakeScriptFail'))
241                   for node in node_list])
242
243   def setUp(self):
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)
249
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)
256
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)
264
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)
271
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):
276       hm.RunPhase(phase)
277
278
279 class FakeEnvLU(cmdlib.LogicalUnit):
280   HPATH = "env_test_lu"
281   HTYPE = constants.HTYPE_GROUP
282
283   def __init__(self, *args):
284     cmdlib.LogicalUnit.__init__(self, *args)
285     self.hook_env = None
286
287   def BuildHooksEnv(self):
288     assert self.hook_env is not None
289     return self.hook_env
290
291   def BuildHooksNodes(self):
292     return (["localhost"], ["localhost"])
293
294
295 class FakeNoHooksLU(cmdlib.NoHooksLU):
296   pass
297
298
299 class TestHooksRunnerEnv(unittest.TestCase):
300   def setUp(self):
301     self._rpcs = []
302
303     self.op = opcodes.OpTestDummy(result=False, messages=[], fail=False)
304     self.lu = FakeEnvLU(FakeProc(), self.op, FakeContext(), None)
305
306   def _HooksRpc(self, *args):
307     self._rpcs.append(args)
308     return FakeHooksRpcSuccess(*args)
309
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)
319     else:
320       self.assertTrue(self.lu.HTYPE is None)
321
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)
327
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)
333
334     # Check post-phase hook
335     self.lu.hook_env = {}
336     hm.RunPhase(constants.HOOKS_PHASE_POST)
337
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)
343
344     self.assertRaises(IndexError, self._rpcs.pop)
345
346   def testEnv(self):
347     # Check pre-phase hook
348     self.lu.hook_env = {
349       "FOO": "pre-foo-value",
350       }
351     hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
352     hm.RunPhase(constants.HOOKS_PHASE_PRE)
353
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)
361
362     # Check post-phase hook
363     self.lu.hook_env = {
364       "FOO": "post-value",
365       "BAR": 123,
366       }
367     hm.RunPhase(constants.HOOKS_PHASE_POST)
368
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)
378
379     self.assertRaises(IndexError, self._rpcs.pop)
380
381     # Check configuration update hook
382     hm.RunConfigUpdate()
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)
392
393   def testConflict(self):
394     for name in ["DATA_DIR", "OP_CODE"]:
395       self.lu.hook_env = { name: "value" }
396
397       # Test using a clean HooksMaster instance
398       hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
399
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)
403
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)
409
410   def testSpecificNodes(self):
411     self.lu.hook_env = {}
412
413     nodes = [
414       "node1.example.com",
415       "node93782.example.net",
416       ]
417
418     hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
419
420     for phase in [constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST]:
421       hm.RunPhase(phase, nodes=nodes)
422
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)
428
429       self.assertRaises(IndexError, self._rpcs.pop)
430
431   def testRunConfigUpdateNoPre(self):
432     self.lu.hook_env = {
433       "FOO": "value",
434       }
435
436     hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
437     hm.RunConfigUpdate()
438
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)
447
448     self.assertRaises(IndexError, self._rpcs.pop)
449
450   def testNoPreBeforePost(self):
451     self.lu.hook_env = {
452       "FOO": "value",
453       }
454
455     hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
456     hm.RunPhase(constants.HOOKS_PHASE_POST)
457
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)
465
466     self.assertRaises(IndexError, self._rpcs.pop)
467
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)
472
473     hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
474     self.assertEqual(hm.pre_env, {})
475     self.assertRaises(IndexError, self._rpcs.pop)
476
477     hm.RunPhase(constants.HOOKS_PHASE_PRE)
478     self.assertRaises(IndexError, self._rpcs.pop)
479
480     hm.RunPhase(constants.HOOKS_PHASE_POST)
481     self.assertRaises(IndexError, self._rpcs.pop)
482
483     hm.RunConfigUpdate()
484
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)
493
494     assert isinstance(self.lu, FakeNoHooksLU), "LU was replaced"
495
496
497 if __name__ == '__main__':
498   testutils.GanetiTestProgram()