Statistics
| Branch: | Tag: | Revision:

root / test / ganeti.hooks_unittest.py @ dd7f6776

History | View | Annotate | Download (14.5 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
  def BuildHooksEnv(self):
49
    return {}, ["localhost"], ["localhost"]
50

    
51

    
52
class TestHooksRunner(unittest.TestCase):
53
  """Testing case for HooksRunner"""
54
  def setUp(self):
55
    self.torm = []
56
    self.tmpdir = tempfile.mkdtemp()
57
    self.torm.append((self.tmpdir, True))
58
    self.logdir = tempfile.mkdtemp()
59
    self.torm.append((self.logdir, True))
60
    self.hpath = "fake"
61
    self.ph_dirs = {}
62
    for i in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
63
      dname = "%s/%s-%s.d" % (self.tmpdir, self.hpath, i)
64
      os.mkdir(dname)
65
      self.torm.append((dname, True))
66
      self.ph_dirs[i] = dname
67
    self.hr = backend.HooksRunner(hooks_base_dir=self.tmpdir)
68

    
69
  def tearDown(self):
70
    self.torm.reverse()
71
    for path, kind in self.torm:
72
      if kind:
73
        os.rmdir(path)
74
      else:
75
        os.unlink(path)
76

    
77
  def _rname(self, fname):
78
    return "/".join(fname.split("/")[-2:])
79

    
80
  def testEmpty(self):
81
    """Test no hooks"""
82
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
83
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}), [])
84

    
85
  def testSkipNonExec(self):
86
    """Test skip non-exec file"""
87
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
88
      fname = "%s/test" % self.ph_dirs[phase]
89
      f = open(fname, "w")
90
      f.close()
91
      self.torm.append((fname, False))
92
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
93
                           [(self._rname(fname), HKR_SKIP, "")])
94

    
95
  def testSkipInvalidName(self):
96
    """Test skip script with invalid name"""
97
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
98
      fname = "%s/a.off" % self.ph_dirs[phase]
99
      f = open(fname, "w")
100
      f.write("#!/bin/sh\nexit 0\n")
101
      f.close()
102
      os.chmod(fname, 0700)
103
      self.torm.append((fname, False))
104
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
105
                           [(self._rname(fname), HKR_SKIP, "")])
106

    
107
  def testSkipDir(self):
108
    """Test skip directory"""
109
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
110
      fname = "%s/testdir" % self.ph_dirs[phase]
111
      os.mkdir(fname)
112
      self.torm.append((fname, True))
113
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
114
                           [(self._rname(fname), HKR_SKIP, "")])
115

    
116
  def testSuccess(self):
117
    """Test success execution"""
118
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
119
      fname = "%s/success" % self.ph_dirs[phase]
120
      f = open(fname, "w")
121
      f.write("#!/bin/sh\nexit 0\n")
122
      f.close()
123
      self.torm.append((fname, False))
124
      os.chmod(fname, 0700)
125
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
126
                           [(self._rname(fname), HKR_SUCCESS, "")])
127

    
128
  def testSymlink(self):
129
    """Test running a symlink"""
130
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
131
      fname = "%s/success" % self.ph_dirs[phase]
132
      os.symlink("/bin/true", fname)
133
      self.torm.append((fname, False))
134
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
135
                           [(self._rname(fname), HKR_SUCCESS, "")])
136

    
137
  def testFail(self):
138
    """Test success execution"""
139
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
140
      fname = "%s/success" % self.ph_dirs[phase]
141
      f = open(fname, "w")
142
      f.write("#!/bin/sh\nexit 1\n")
143
      f.close()
144
      self.torm.append((fname, False))
145
      os.chmod(fname, 0700)
146
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}),
147
                           [(self._rname(fname), HKR_FAIL, "")])
148

    
149
  def testCombined(self):
150
    """Test success, failure and skip all in one test"""
151
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
152
      expect = []
153
      for fbase, ecode, rs in [("00succ", 0, HKR_SUCCESS),
154
                               ("10fail", 1, HKR_FAIL),
155
                               ("20inv.", 0, HKR_SKIP),
156
                               ]:
157
        fname = "%s/%s" % (self.ph_dirs[phase], fbase)
158
        f = open(fname, "w")
159
        f.write("#!/bin/sh\nexit %d\n" % ecode)
160
        f.close()
161
        self.torm.append((fname, False))
162
        os.chmod(fname, 0700)
163
        expect.append((self._rname(fname), rs, ""))
164
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}), expect)
165

    
166
  def testOrdering(self):
167
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
168
      expect = []
169
      for fbase in ["10s1",
170
                    "00s0",
171
                    "10sa",
172
                    "80sc",
173
                    "60sd",
174
                    ]:
175
        fname = "%s/%s" % (self.ph_dirs[phase], fbase)
176
        os.symlink("/bin/true", fname)
177
        self.torm.append((fname, False))
178
        expect.append((self._rname(fname), HKR_SUCCESS, ""))
179
      expect.sort()
180
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, {}), expect)
181

    
182
  def testEnv(self):
183
    """Test environment execution"""
184
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
185
      fbase = "success"
186
      fname = "%s/%s" % (self.ph_dirs[phase], fbase)
187
      os.symlink("/usr/bin/env", fname)
188
      self.torm.append((fname, False))
189
      env_snt = {"PHASE": phase}
190
      env_exp = "PHASE=%s" % phase
191
      self.failUnlessEqual(self.hr.RunHooks(self.hpath, phase, env_snt),
192
                           [(self._rname(fname), HKR_SUCCESS, env_exp)])
193

    
194

    
195
def FakeHooksRpcSuccess(node_list, hpath, phase, env):
196
  """Fake call_hooks_runner function.
197

198
  @rtype: dict of node -> L{rpc.RpcResult} with a successful script result
199
  @return: script execution from all nodes
200

201
  """
202
  rr = rpc.RpcResult
203
  return dict([(node, rr(True, [("utest", constants.HKR_SUCCESS, "ok")],
204
                         node=node, call='FakeScriptOk'))
205
               for node in node_list])
206

    
207

    
208
class TestHooksMaster(unittest.TestCase):
209
  """Testing case for HooksMaster"""
210

    
211
  def _call_false(*args):
212
    """Fake call_hooks_runner function which returns False."""
213
    return False
214

    
215
  @staticmethod
216
  def _call_nodes_false(node_list, hpath, phase, env):
217
    """Fake call_hooks_runner function.
218

219
    @rtype: dict of node -> L{rpc.RpcResult} with an rpc error
220
    @return: rpc failure from all nodes
221

222
    """
223
    return dict([(node, rpc.RpcResult('error', failed=True,
224
                  node=node, call='FakeError')) for node in node_list])
225

    
226
  @staticmethod
227
  def _call_script_fail(node_list, hpath, phase, env):
228
    """Fake call_hooks_runner function.
229

230
    @rtype: dict of node -> L{rpc.RpcResult} with a failed script result
231
    @return: script execution failure from all nodes
232

233
    """
234
    rr = rpc.RpcResult
235
    return dict([(node, rr((True, [("utest", constants.HKR_FAIL, "err")]),
236
                           node=node, call='FakeScriptFail'))
237
                  for node in node_list])
238

    
239
  def setUp(self):
240
    self.op = opcodes.OpCode()
241
    self.context = FakeContext()
242
    # WARNING: here we pass None as RpcRunner instance since we know
243
    # our usage via HooksMaster will not use lu.rpc
244
    self.lu = FakeLU(FakeProc(), self.op, self.context, None)
245

    
246
  def testTotalFalse(self):
247
    """Test complete rpc failure"""
248
    hm = mcpu.HooksMaster(self._call_false, self.lu)
249
    self.failUnlessRaises(errors.HooksFailure,
250
                          hm.RunPhase, constants.HOOKS_PHASE_PRE)
251
    hm.RunPhase(constants.HOOKS_PHASE_POST)
252

    
253
  def testIndividualFalse(self):
254
    """Test individual node failure"""
255
    hm = mcpu.HooksMaster(self._call_nodes_false, self.lu)
256
    hm.RunPhase(constants.HOOKS_PHASE_PRE)
257
    #self.failUnlessRaises(errors.HooksFailure,
258
    #                      hm.RunPhase, constants.HOOKS_PHASE_PRE)
259
    hm.RunPhase(constants.HOOKS_PHASE_POST)
260

    
261
  def testScriptFalse(self):
262
    """Test individual rpc failure"""
263
    hm = mcpu.HooksMaster(self._call_script_fail, self.lu)
264
    self.failUnlessRaises(errors.HooksAbort,
265
                          hm.RunPhase, constants.HOOKS_PHASE_PRE)
266
    hm.RunPhase(constants.HOOKS_PHASE_POST)
267

    
268
  def testScriptSucceed(self):
269
    """Test individual rpc failure"""
270
    hm = mcpu.HooksMaster(FakeHooksRpcSuccess, self.lu)
271
    for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST):
272
      hm.RunPhase(phase)
273

    
274

    
275
class FakeEnvLU(cmdlib.LogicalUnit):
276
  HPATH = "env_test_lu"
277
  HTYPE = constants.HTYPE_GROUP
278

    
279
  def __init__(self, *args):
280
    cmdlib.LogicalUnit.__init__(self, *args)
281
    self.hook_env = None
282

    
283
  def BuildHooksEnv(self):
284
    assert self.hook_env is not None
285

    
286
    return self.hook_env, ["localhost"], ["localhost"]
287

    
288

    
289
class TestHooksRunnerEnv(unittest.TestCase):
290
  def setUp(self):
291
    self._rpcs = []
292

    
293
    self.op = opcodes.OpTestDummy(result=False, messages=[], fail=False)
294
    self.lu = FakeEnvLU(FakeProc(), self.op, FakeContext(), None)
295
    self.hm = mcpu.HooksMaster(self._HooksRpc, self.lu)
296

    
297
  def _HooksRpc(self, *args):
298
    self._rpcs.append(args)
299
    return FakeHooksRpcSuccess(*args)
300

    
301
  def _CheckEnv(self, env, phase, hpath):
302
    self.assertTrue(env["PATH"].startswith("/sbin"))
303
    self.assertEqual(env["GANETI_HOOKS_PHASE"], phase)
304
    self.assertEqual(env["GANETI_HOOKS_PATH"], hpath)
305
    self.assertEqual(env["GANETI_OP_CODE"], self.op.OP_ID)
306
    self.assertEqual(env["GANETI_OBJECT_TYPE"], constants.HTYPE_GROUP)
307
    self.assertEqual(env["GANETI_HOOKS_VERSION"], str(constants.HOOKS_VERSION))
308
    self.assertEqual(env["GANETI_DATA_DIR"], constants.DATA_DIR)
309

    
310
  def testEmptyEnv(self):
311
    # Check pre-phase hook
312
    self.lu.hook_env = {}
313
    self.hm.RunPhase(constants.HOOKS_PHASE_PRE)
314

    
315
    (node_list, hpath, phase, env) = self._rpcs.pop(0)
316
    self.assertEqual(node_list, set(["localhost"]))
317
    self.assertEqual(hpath, self.lu.HPATH)
318
    self.assertEqual(phase, constants.HOOKS_PHASE_PRE)
319
    self._CheckEnv(env, constants.HOOKS_PHASE_PRE, self.lu.HPATH)
320

    
321
    # Check post-phase hook
322
    self.lu.hook_env = {}
323
    self.hm.RunPhase(constants.HOOKS_PHASE_POST)
324

    
325
    (node_list, hpath, phase, env) = self._rpcs.pop(0)
326
    self.assertEqual(node_list, set(["localhost"]))
327
    self.assertEqual(hpath, self.lu.HPATH)
328
    self.assertEqual(phase, constants.HOOKS_PHASE_POST)
329
    self._CheckEnv(env, constants.HOOKS_PHASE_POST, self.lu.HPATH)
330

    
331
    self.assertRaises(IndexError, self._rpcs.pop)
332

    
333
  def testEnv(self):
334
    # Check pre-phase hook
335
    self.lu.hook_env = {
336
      "FOO": "pre-foo-value",
337
      }
338
    self.hm.RunPhase(constants.HOOKS_PHASE_PRE)
339

    
340
    (node_list, hpath, phase, env) = self._rpcs.pop(0)
341
    self.assertEqual(node_list, set(["localhost"]))
342
    self.assertEqual(hpath, self.lu.HPATH)
343
    self.assertEqual(phase, constants.HOOKS_PHASE_PRE)
344
    self.assertEqual(env["GANETI_FOO"], "pre-foo-value")
345
    self.assertFalse(compat.any(key.startswith("GANETI_POST") for key in env))
346
    self._CheckEnv(env, constants.HOOKS_PHASE_PRE, self.lu.HPATH)
347

    
348
    # Check post-phase hook
349
    self.lu.hook_env = {
350
      "FOO": "post-value",
351
      "BAR": 123,
352
      }
353
    self.hm.RunPhase(constants.HOOKS_PHASE_POST)
354

    
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_POST)
359
    self.assertEqual(env["GANETI_FOO"], "pre-foo-value")
360
    self.assertEqual(env["GANETI_POST_FOO"], "post-value")
361
    self.assertEqual(env["GANETI_POST_BAR"], "123")
362
    self.assertFalse("GANETI_BAR" in env)
363
    self._CheckEnv(env, constants.HOOKS_PHASE_POST, self.lu.HPATH)
364

    
365
    self.assertRaises(IndexError, self._rpcs.pop)
366

    
367
    # Check configuration update hook
368
    self.hm.RunConfigUpdate()
369
    (node_list, hpath, phase, env) = self._rpcs.pop(0)
370
    self.assertEqual(set(node_list), set([self.lu.cfg.GetMasterNode()]))
371
    self.assertEqual(hpath, constants.HOOKS_NAME_CFGUPDATE)
372
    self.assertEqual(phase, constants.HOOKS_PHASE_POST)
373
    self._CheckEnv(env, constants.HOOKS_PHASE_POST,
374
                   constants.HOOKS_NAME_CFGUPDATE)
375
    self.assertFalse(compat.any(key.startswith("GANETI_POST") for key in env))
376
    self.assertEqual(env["GANETI_FOO"], "pre-foo-value")
377
    self.assertRaises(IndexError, self._rpcs.pop)
378

    
379
  def testConflict(self):
380
    for name in ["DATA_DIR", "OP_CODE"]:
381
      self.lu.hook_env = { name: "value" }
382
      for phase in [constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST]:
383
        # Test using a clean HooksMaster instance
384
        self.assertRaises(AssertionError,
385
                          mcpu.HooksMaster(self._HooksRpc, self.lu).RunPhase,
386
                          phase)
387
        self.assertRaises(IndexError, self._rpcs.pop)
388

    
389
  def testNoNodes(self):
390
    self.lu.hook_env = {}
391
    self.hm.RunPhase(constants.HOOKS_PHASE_PRE, nodes=[])
392
    self.assertRaises(IndexError, self._rpcs.pop)
393

    
394
  def testSpecificNodes(self):
395
    self.lu.hook_env = {}
396

    
397
    nodes = [
398
      "node1.example.com",
399
      "node93782.example.net",
400
      ]
401

    
402
    for phase in [constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST]:
403
      self.hm.RunPhase(phase, nodes=nodes)
404

    
405
      (node_list, hpath, rpc_phase, env) = self._rpcs.pop(0)
406
      self.assertEqual(set(node_list), set(nodes))
407
      self.assertEqual(hpath, self.lu.HPATH)
408
      self.assertEqual(rpc_phase, phase)
409
      self._CheckEnv(env, phase, self.lu.HPATH)
410

    
411
      self.assertRaises(IndexError, self._rpcs.pop)
412

    
413
  def testRunConfigUpdateNoPre(self):
414
    self.lu.hook_env = {}
415
    self.assertRaises(AssertionError, self.hm.RunConfigUpdate)
416
    self.assertRaises(IndexError, self._rpcs.pop)
417

    
418

    
419
if __name__ == '__main__':
420
  testutils.GanetiTestProgram()