Commit 6bb65e3a authored by Guido Trotter's avatar Guido Trotter
Browse files

Implement utils.RunParts and use it for hooks



This function is a generic pythonic version of runparts. We currently
use it in the backend HooksRunner, but we'll use it for running
different directories as well.
Signed-off-by: default avatarGuido Trotter <ultrotter@google.com>
Reviewed-by: default avatarMichael Hanselmann <hansmi@google.com>
parent 8b4fe938
......@@ -40,7 +40,6 @@ import time
import stat
import errno
import re
import subprocess
import random
import logging
import tempfile
......@@ -2666,40 +2665,28 @@ class HooksRunner(object):
else:
_Fail("Unknown hooks phase '%s'", phase)
rr = []
subdir = "%s-%s.d" % (hpath, suffix)
dir_name = "%s/%s" % (self._BASE_DIR, subdir)
try:
dir_contents = utils.ListVisibleFiles(dir_name)
except OSError:
# FIXME: must log output in case of failures
return rr
# we use the standard python sort order,
# so 00name is the recommended naming scheme
dir_contents.sort()
for relname in dir_contents:
fname = os.path.join(dir_name, relname)
if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
constants.EXT_PLUGIN_MASK.match(relname) is not None):
runparts_results = utils.RunParts(dir_name, env=env, reset_env=True)
results = []
for (relname, relstatus, runresult) in runparts_results:
if relstatus == constants.RUNPARTS_SKIP:
rrval = constants.HKR_SKIP
output = ""
else:
try:
result = utils.RunCmd([fname], env=env, reset_env=True)
except (OpExecError, EnvironmentError), err:
elif relstatus == constants.RUNPARTS_ERR:
rrval = constants.HKR_FAIL
output = "Hook script execution error: %s" % runresult
elif relstatus == constants.RUNPARTS_RUN:
if runresult.failed:
rrval = constants.HKR_FAIL
output = "Hook script error: %s" % str(err)
else:
if result.failed:
rrval = constants.HKR_FAIL
else:
rrval = constants.HKR_SUCCESS
output = utils.SafeEncode(result.output.strip())
rr.append(("%s/%s" % (subdir, relname), rrval, output))
return rr
rrval = constants.HKR_SUCCESS
output = utils.SafeEncode(runresult.output.strip())
results.append(("%s/%s" % (subdir, relname), rrval, output))
return results
class IAllocatorRunner(object):
......
......@@ -335,6 +335,13 @@ LVM_STRIPECOUNT = _autoconf.LVM_STRIPECOUNT
DEFAULT_SHUTDOWN_TIMEOUT = 120
NODE_MAX_CLOCK_SKEW = 150
# runparts results
(RUNPARTS_SKIP,
RUNPARTS_RUN,
RUNPARTS_ERR) = range(3)
RUNPARTS_STATUS = frozenset([RUNPARTS_SKIP, RUNPARTS_RUN, RUNPARTS_ERR])
# RPC constants
(RPC_ENCODING_NONE,
RPC_ENCODING_ZLIB_BASE64) = range(2)
......
......@@ -288,6 +288,43 @@ def _RunCmdFile(cmd, env, via_shell, output, cwd):
return status
def RunParts(dir_name, env=None, reset_env=False):
"""Run Scripts or programs in a directory
@type dir_name: string
@param dir_name: absolute path to a directory
@type env: dict
@param env: The environment to use
@type reset_env: boolean
@param reset_env: whether to reset or keep the default os environment
@rtype: list of tuples
@return: list of (name, (one of RUNDIR_STATUS), RunResult)
"""
rr = []
try:
dir_contents = ListVisibleFiles(dir_name)
except OSError, err:
logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
return rr
for relname in sorted(dir_contents):
fname = os.path.join(dir_name, relname)
if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
constants.EXT_PLUGIN_MASK.match(relname) is not None):
rr.append((relname, constants.RUNPARTS_SKIP, None))
else:
try:
result = RunCmd([fname], env=env, reset_env=reset_env)
except Exception, err: # pylint: disable-msg=W0703
rr.append((relname, constants.RUNPARTS_ERR, str(err)))
else:
rr.append((relname, constants.RUNPARTS_RUN, result))
return rr
def RemoveFile(filename):
"""Remove a file ignoring some errors.
......
......@@ -27,6 +27,7 @@ import time
import tempfile
import os.path
import os
import stat
import md5
import signal
import socket
......@@ -46,7 +47,7 @@ from ganeti.utils import IsProcessAlive, RunCmd, \
ShellQuote, ShellQuoteArgs, TcpPing, ListVisibleFiles, \
SetEtcHostsEntry, RemoveEtcHostsEntry, FirstFree, OwnIpAddress, \
TailFile, ForceDictType, SafeEncode, IsNormAbsPath, FormatTime, \
UnescapeAndSplit
UnescapeAndSplit, RunParts
from ganeti.errors import LockError, UnitParseError, GenericError, \
ProgrammerError
......@@ -236,6 +237,132 @@ class TestRunCmd(testutils.GanetiTestCase):
self.failUnlessEqual(RunCmd(["env"], reset_env=True).stdout.strip(), "")
class TestRunParts(unittest.TestCase):
"""Testing case for the RunParts function"""
def setUp(self):
self.rundir = tempfile.mkdtemp(prefix="ganeti-test", suffix=".tmp")
def tearDown(self):
shutil.rmtree(self.rundir)
def testEmpty(self):
"""Test on an empty dir"""
self.failUnlessEqual(RunParts(self.rundir, reset_env=True), [])
def testSkipWrongName(self):
"""Test that wrong files are skipped"""
fname = os.path.join(self.rundir, "00test.dot")
utils.WriteFile(fname, data="")
os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
relname = os.path.basename(fname)
self.failUnlessEqual(RunParts(self.rundir, reset_env=True),
[(relname, constants.RUNPARTS_SKIP, None)])
def testSkipNonExec(self):
"""Test that non executable files are skipped"""
fname = os.path.join(self.rundir, "00test")
utils.WriteFile(fname, data="")
relname = os.path.basename(fname)
self.failUnlessEqual(RunParts(self.rundir, reset_env=True),
[(relname, constants.RUNPARTS_SKIP, None)])
def testError(self):
"""Test error on a broken executable"""
fname = os.path.join(self.rundir, "00test")
utils.WriteFile(fname, data="")
os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
(relname, status, error) = RunParts(self.rundir, reset_env=True)[0]
self.failUnlessEqual(relname, os.path.basename(fname))
self.failUnlessEqual(status, constants.RUNPARTS_ERR)
self.failUnless(error)
def testSorted(self):
"""Test executions are sorted"""
files = []
files.append(os.path.join(self.rundir, "64test"))
files.append(os.path.join(self.rundir, "00test"))
files.append(os.path.join(self.rundir, "42test"))
for fname in files:
utils.WriteFile(fname, data="")
results = RunParts(self.rundir, reset_env=True)
for fname in sorted(files):
self.failUnlessEqual(os.path.basename(fname), results.pop(0)[0])
def testOk(self):
"""Test correct execution"""
fname = os.path.join(self.rundir, "00test")
utils.WriteFile(fname, data="#!/bin/sh\n\necho -n ciao")
os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
(relname, status, runresult) = RunParts(self.rundir, reset_env=True)[0]
self.failUnlessEqual(relname, os.path.basename(fname))
self.failUnlessEqual(status, constants.RUNPARTS_RUN)
self.failUnlessEqual(runresult.stdout, "ciao")
def testRunFail(self):
"""Test correct execution, with run failure"""
fname = os.path.join(self.rundir, "00test")
utils.WriteFile(fname, data="#!/bin/sh\n\nexit 1")
os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
(relname, status, runresult) = RunParts(self.rundir, reset_env=True)[0]
self.failUnlessEqual(relname, os.path.basename(fname))
self.failUnlessEqual(status, constants.RUNPARTS_RUN)
self.failUnlessEqual(runresult.exit_code, 1)
self.failUnless(runresult.failed)
def testRunMix(self):
files = []
files.append(os.path.join(self.rundir, "00test"))
files.append(os.path.join(self.rundir, "42test"))
files.append(os.path.join(self.rundir, "64test"))
files.append(os.path.join(self.rundir, "99test"))
files.sort()
# 1st has errors in execution
utils.WriteFile(files[0], data="#!/bin/sh\n\nexit 1")
os.chmod(files[0], stat.S_IREAD | stat.S_IEXEC)
# 2nd is skipped
utils.WriteFile(files[1], data="")
# 3rd cannot execute properly
utils.WriteFile(files[2], data="")
os.chmod(files[2], stat.S_IREAD | stat.S_IEXEC)
# 4th execs
utils.WriteFile(files[3], data="#!/bin/sh\n\necho -n ciao")
os.chmod(files[3], stat.S_IREAD | stat.S_IEXEC)
results = RunParts(self.rundir, reset_env=True)
(relname, status, runresult) = results[0]
self.failUnlessEqual(relname, os.path.basename(files[0]))
self.failUnlessEqual(status, constants.RUNPARTS_RUN)
self.failUnlessEqual(runresult.exit_code, 1)
self.failUnless(runresult.failed)
(relname, status, runresult) = results[1]
self.failUnlessEqual(relname, os.path.basename(files[1]))
self.failUnlessEqual(status, constants.RUNPARTS_SKIP)
self.failUnlessEqual(runresult, None)
(relname, status, runresult) = results[2]
self.failUnlessEqual(relname, os.path.basename(files[2]))
self.failUnlessEqual(status, constants.RUNPARTS_ERR)
self.failUnless(runresult)
(relname, status, runresult) = results[3]
self.failUnlessEqual(relname, os.path.basename(files[3]))
self.failUnlessEqual(status, constants.RUNPARTS_RUN)
self.failUnlessEqual(runresult.output, "ciao")
self.failUnlessEqual(runresult.exit_code, 0)
self.failUnless(not runresult.failed)
class TestRemoveFile(unittest.TestCase):
"""Test case for the RemoveFile function"""
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment