Commit eb58f7bd authored by Michael Hanselmann's avatar Michael Hanselmann
Browse files

Move RunInSeparateProcess to ganeti.utils



This function could be useful in other places and this
way we can easily unittest it.
Signed-off-by: default avatarMichael Hanselmann <hansmi@google.com>
Reviewed-by: default avatarIustin Pop <iustin@google.com>
parent 055f822b
......@@ -29,7 +29,6 @@ inheritance from parent classes requires it.
# pylint: disable-msg=C0103
# C0103: Invalid name ganeti-masterd
import os
import sys
import SocketServer
import time
......@@ -497,50 +496,6 @@ def CheckAgreementWithRpc():
rpc.Shutdown()
def _RunInSeparateProcess(fn):
"""Runs a function in a separate process.
Note: Only boolean return values are supported.
@type fn: callable
@param fn: Function to be called
@rtype: bool
"""
pid = os.fork()
if pid == 0:
# Child process
try:
# Call function
result = int(bool(fn()))
assert result in (0, 1)
except: # pylint: disable-msg=W0702
logging.exception("Error while calling function in separate process")
# 0 and 1 are reserved for the return value
result = 33
os._exit(result) # pylint: disable-msg=W0212
# Parent process
# Avoid zombies and check exit code
(_, status) = os.waitpid(pid, 0)
if os.WIFSIGNALED(status):
signum = os.WTERMSIG(status)
exitcode = None
else:
signum = None
exitcode = os.WEXITSTATUS(status)
if not (exitcode in (0, 1) and signum is None):
logging.error("Child program failed (code=%s, signal=%s)",
exitcode, signum)
sys.exit(constants.EXIT_FAILURE)
return bool(exitcode)
def CheckMasterd(options, args):
"""Initial checks whether to run or exit with a failure.
......@@ -571,7 +526,7 @@ def CheckMasterd(options, args):
# CheckAgreement uses RPC and threads, hence it needs to be run in a separate
# process before we call utils.Daemonize in the current process.
if not _RunInSeparateProcess(CheckAgreementWithRpc):
if not utils.RunInSeparateProcess(CheckAgreementWithRpc):
sys.exit(constants.EXIT_FAILURE)
......
......@@ -2042,6 +2042,50 @@ def GetFilesystemStats(path):
return (tsize, fsize)
def RunInSeparateProcess(fn):
"""Runs a function in a separate process.
Note: Only boolean return values are supported.
@type fn: callable
@param fn: Function to be called
@rtype: tuple of (int/None, int/None)
@return: Exit code and signal number
"""
pid = os.fork()
if pid == 0:
# Child process
try:
# Call function
result = int(bool(fn()))
assert result in (0, 1)
except: # pylint: disable-msg=W0702
logging.exception("Error while calling function in separate process")
# 0 and 1 are reserved for the return value
result = 33
os._exit(result) # pylint: disable-msg=W0212
# Parent process
# Avoid zombies and check exit code
(_, status) = os.waitpid(pid, 0)
if os.WIFSIGNALED(status):
exitcode = None
signum = os.WTERMSIG(status)
else:
exitcode = os.WEXITSTATUS(status)
signum = None
if not (exitcode in (0, 1) and signum is None):
raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
(exitcode, signum))
return bool(exitcode)
def LockedMethod(fn):
"""Synchronized object access decorator.
......
......@@ -1054,6 +1054,37 @@ class TestFormatTime(unittest.TestCase):
FormatTime(int(time.time()))
class RunInSeparateProcess(unittest.TestCase):
def test(self):
for exp in [True, False]:
def _child():
return exp
self.assertEqual(exp, utils.RunInSeparateProcess(_child))
def testPid(self):
parent_pid = os.getpid()
def _check():
return os.getpid() == parent_pid
self.failIf(utils.RunInSeparateProcess(_check))
def testSignal(self):
def _kill():
os.kill(os.getpid(), signal.SIGTERM)
self.assertRaises(errors.GenericError,
utils.RunInSeparateProcess, _kill)
def testException(self):
def _exc():
raise errors.GenericError("This is a test")
self.assertRaises(errors.GenericError,
utils.RunInSeparateProcess, _exc)
class TestFingerprintFile(unittest.TestCase):
def setUp(self):
self.tmpfile = tempfile.NamedTemporaryFile()
......
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