Statistics
| Branch: | Tag: | Revision:

root / test / ganeti.hooks_unittest.py @ 415feb2e

History | View | Annotate | Download (17.1 kB)

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()