Revision 6bb65e3a

b/lib/backend.py
40 40
import stat
41 41
import errno
42 42
import re
43
import subprocess
44 43
import random
45 44
import logging
46 45
import tempfile
......
2666 2665
    else:
2667 2666
      _Fail("Unknown hooks phase '%s'", phase)
2668 2667

  
2669
    rr = []
2670 2668

  
2671 2669
    subdir = "%s-%s.d" % (hpath, suffix)
2672 2670
    dir_name = "%s/%s" % (self._BASE_DIR, subdir)
2673
    try:
2674
      dir_contents = utils.ListVisibleFiles(dir_name)
2675
    except OSError:
2676
      # FIXME: must log output in case of failures
2677
      return rr
2678

  
2679
    # we use the standard python sort order,
2680
    # so 00name is the recommended naming scheme
2681
    dir_contents.sort()
2682
    for relname in dir_contents:
2683
      fname = os.path.join(dir_name, relname)
2684
      if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
2685
              constants.EXT_PLUGIN_MASK.match(relname) is not None):
2671
    runparts_results = utils.RunParts(dir_name, env=env, reset_env=True)
2672

  
2673
    results = []
2674
    for (relname, relstatus, runresult)  in runparts_results:
2675
      if relstatus == constants.RUNPARTS_SKIP:
2686 2676
        rrval = constants.HKR_SKIP
2687 2677
        output = ""
2688
      else:
2689
        try:
2690
          result = utils.RunCmd([fname], env=env, reset_env=True)
2691
        except (OpExecError, EnvironmentError), err:
2678
      elif relstatus == constants.RUNPARTS_ERR:
2679
        rrval = constants.HKR_FAIL
2680
        output = "Hook script execution error: %s" % runresult
2681
      elif relstatus == constants.RUNPARTS_RUN:
2682
        if runresult.failed:
2692 2683
          rrval = constants.HKR_FAIL
2693
          output = "Hook script error: %s" % str(err)
2694 2684
        else:
2695
          if result.failed:
2696
            rrval = constants.HKR_FAIL
2697
          else:
2698
            rrval = constants.HKR_SUCCESS
2699
        output = utils.SafeEncode(result.output.strip())
2700
      rr.append(("%s/%s" % (subdir, relname), rrval, output))
2701

  
2702
    return rr
2685
          rrval = constants.HKR_SUCCESS
2686
        output = utils.SafeEncode(runresult.output.strip())
2687
      results.append(("%s/%s" % (subdir, relname), rrval, output))
2688

  
2689
    return results
2703 2690

  
2704 2691

  
2705 2692
class IAllocatorRunner(object):
b/lib/constants.py
335 335
DEFAULT_SHUTDOWN_TIMEOUT = 120
336 336
NODE_MAX_CLOCK_SKEW = 150
337 337

  
338
# runparts results
339
(RUNPARTS_SKIP,
340
 RUNPARTS_RUN,
341
 RUNPARTS_ERR) = range(3)
342

  
343
RUNPARTS_STATUS = frozenset([RUNPARTS_SKIP, RUNPARTS_RUN, RUNPARTS_ERR])
344

  
338 345
# RPC constants
339 346
(RPC_ENCODING_NONE,
340 347
 RPC_ENCODING_ZLIB_BASE64) = range(2)
b/lib/utils.py
288 288
  return status
289 289

  
290 290

  
291
def RunParts(dir_name, env=None, reset_env=False):
292
  """Run Scripts or programs in a directory
293

  
294
  @type dir_name: string
295
  @param dir_name: absolute path to a directory
296
  @type env: dict
297
  @param env: The environment to use
298
  @type reset_env: boolean
299
  @param reset_env: whether to reset or keep the default os environment
300
  @rtype: list of tuples
301
  @return: list of (name, (one of RUNDIR_STATUS), RunResult)
302

  
303
  """
304
  rr = []
305

  
306
  try:
307
    dir_contents = ListVisibleFiles(dir_name)
308
  except OSError, err:
309
    logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
310
    return rr
311

  
312
  for relname in sorted(dir_contents):
313
    fname = os.path.join(dir_name, relname)
314
    if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
315
            constants.EXT_PLUGIN_MASK.match(relname) is not None):
316
      rr.append((relname, constants.RUNPARTS_SKIP, None))
317
    else:
318
      try:
319
        result = RunCmd([fname], env=env, reset_env=reset_env)
320
      except Exception, err: # pylint: disable-msg=W0703
321
        rr.append((relname, constants.RUNPARTS_ERR, str(err)))
322
      else:
323
        rr.append((relname, constants.RUNPARTS_RUN, result))
324

  
325
  return rr
326

  
327

  
291 328
def RemoveFile(filename):
292 329
  """Remove a file ignoring some errors.
293 330

  
b/test/ganeti.utils_unittest.py
27 27
import tempfile
28 28
import os.path
29 29
import os
30
import stat
30 31
import md5
31 32
import signal
32 33
import socket
......
46 47
     ShellQuote, ShellQuoteArgs, TcpPing, ListVisibleFiles, \
47 48
     SetEtcHostsEntry, RemoveEtcHostsEntry, FirstFree, OwnIpAddress, \
48 49
     TailFile, ForceDictType, SafeEncode, IsNormAbsPath, FormatTime, \
49
     UnescapeAndSplit
50
     UnescapeAndSplit, RunParts
50 51

  
51 52
from ganeti.errors import LockError, UnitParseError, GenericError, \
52 53
     ProgrammerError
......
236 237
    self.failUnlessEqual(RunCmd(["env"], reset_env=True).stdout.strip(), "")
237 238

  
238 239

  
240
class TestRunParts(unittest.TestCase):
241
  """Testing case for the RunParts function"""
242

  
243
  def setUp(self):
244
    self.rundir = tempfile.mkdtemp(prefix="ganeti-test", suffix=".tmp")
245

  
246
  def tearDown(self):
247
    shutil.rmtree(self.rundir)
248

  
249
  def testEmpty(self):
250
    """Test on an empty dir"""
251
    self.failUnlessEqual(RunParts(self.rundir, reset_env=True), [])
252

  
253
  def testSkipWrongName(self):
254
    """Test that wrong files are skipped"""
255
    fname = os.path.join(self.rundir, "00test.dot")
256
    utils.WriteFile(fname, data="")
257
    os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
258
    relname = os.path.basename(fname)
259
    self.failUnlessEqual(RunParts(self.rundir, reset_env=True),
260
                         [(relname, constants.RUNPARTS_SKIP, None)])
261

  
262
  def testSkipNonExec(self):
263
    """Test that non executable files are skipped"""
264
    fname = os.path.join(self.rundir, "00test")
265
    utils.WriteFile(fname, data="")
266
    relname = os.path.basename(fname)
267
    self.failUnlessEqual(RunParts(self.rundir, reset_env=True),
268
                         [(relname, constants.RUNPARTS_SKIP, None)])
269

  
270
  def testError(self):
271
    """Test error on a broken executable"""
272
    fname = os.path.join(self.rundir, "00test")
273
    utils.WriteFile(fname, data="")
274
    os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
275
    (relname, status, error) = RunParts(self.rundir, reset_env=True)[0]
276
    self.failUnlessEqual(relname, os.path.basename(fname))
277
    self.failUnlessEqual(status, constants.RUNPARTS_ERR)
278
    self.failUnless(error)
279

  
280
  def testSorted(self):
281
    """Test executions are sorted"""
282
    files = []
283
    files.append(os.path.join(self.rundir, "64test"))
284
    files.append(os.path.join(self.rundir, "00test"))
285
    files.append(os.path.join(self.rundir, "42test"))
286

  
287
    for fname in files:
288
      utils.WriteFile(fname, data="")
289

  
290
    results = RunParts(self.rundir, reset_env=True)
291

  
292
    for fname in sorted(files):
293
      self.failUnlessEqual(os.path.basename(fname), results.pop(0)[0])
294

  
295
  def testOk(self):
296
    """Test correct execution"""
297
    fname = os.path.join(self.rundir, "00test")
298
    utils.WriteFile(fname, data="#!/bin/sh\n\necho -n ciao")
299
    os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
300
    (relname, status, runresult) = RunParts(self.rundir, reset_env=True)[0]
301
    self.failUnlessEqual(relname, os.path.basename(fname))
302
    self.failUnlessEqual(status, constants.RUNPARTS_RUN)
303
    self.failUnlessEqual(runresult.stdout, "ciao")
304

  
305
  def testRunFail(self):
306
    """Test correct execution, with run failure"""
307
    fname = os.path.join(self.rundir, "00test")
308
    utils.WriteFile(fname, data="#!/bin/sh\n\nexit 1")
309
    os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
310
    (relname, status, runresult) = RunParts(self.rundir, reset_env=True)[0]
311
    self.failUnlessEqual(relname, os.path.basename(fname))
312
    self.failUnlessEqual(status, constants.RUNPARTS_RUN)
313
    self.failUnlessEqual(runresult.exit_code, 1)
314
    self.failUnless(runresult.failed)
315

  
316
  def testRunMix(self):
317
    files = []
318
    files.append(os.path.join(self.rundir, "00test"))
319
    files.append(os.path.join(self.rundir, "42test"))
320
    files.append(os.path.join(self.rundir, "64test"))
321
    files.append(os.path.join(self.rundir, "99test"))
322

  
323
    files.sort()
324

  
325
    # 1st has errors in execution
326
    utils.WriteFile(files[0], data="#!/bin/sh\n\nexit 1")
327
    os.chmod(files[0], stat.S_IREAD | stat.S_IEXEC)
328

  
329
    # 2nd is skipped
330
    utils.WriteFile(files[1], data="")
331

  
332
    # 3rd cannot execute properly
333
    utils.WriteFile(files[2], data="")
334
    os.chmod(files[2], stat.S_IREAD | stat.S_IEXEC)
335

  
336
    # 4th execs
337
    utils.WriteFile(files[3], data="#!/bin/sh\n\necho -n ciao")
338
    os.chmod(files[3], stat.S_IREAD | stat.S_IEXEC)
339

  
340
    results = RunParts(self.rundir, reset_env=True)
341

  
342
    (relname, status, runresult) = results[0]
343
    self.failUnlessEqual(relname, os.path.basename(files[0]))
344
    self.failUnlessEqual(status, constants.RUNPARTS_RUN)
345
    self.failUnlessEqual(runresult.exit_code, 1)
346
    self.failUnless(runresult.failed)
347

  
348
    (relname, status, runresult) = results[1]
349
    self.failUnlessEqual(relname, os.path.basename(files[1]))
350
    self.failUnlessEqual(status, constants.RUNPARTS_SKIP)
351
    self.failUnlessEqual(runresult, None)
352

  
353
    (relname, status, runresult) = results[2]
354
    self.failUnlessEqual(relname, os.path.basename(files[2]))
355
    self.failUnlessEqual(status, constants.RUNPARTS_ERR)
356
    self.failUnless(runresult)
357

  
358
    (relname, status, runresult) = results[3]
359
    self.failUnlessEqual(relname, os.path.basename(files[3]))
360
    self.failUnlessEqual(status, constants.RUNPARTS_RUN)
361
    self.failUnlessEqual(runresult.output, "ciao")
362
    self.failUnlessEqual(runresult.exit_code, 0)
363
    self.failUnless(not runresult.failed)
364

  
365

  
239 366
class TestRemoveFile(unittest.TestCase):
240 367
  """Test case for the RemoveFile function"""
241 368

  

Also available in: Unified diff