From 7831fc5fbe3a80d590a9215b73e58ed259bd0582 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann <hansmi@google.com> Date: Mon, 10 Jan 2011 17:51:46 +0100 Subject: [PATCH] utils: Move wrappers into separate file Signed-off-by: Michael Hanselmann <hansmi@google.com> Reviewed-by: Iustin Pop <iustin@google.com> --- Makefile.am | 4 +- lib/utils/__init__.py | 140 +-------------------- lib/utils/wrapper.py | 170 ++++++++++++++++++++++++++ test/ganeti.utils.wrapper_unittest.py | 126 +++++++++++++++++++ test/ganeti.utils_unittest.py | 90 +------------- 5 files changed, 301 insertions(+), 229 deletions(-) create mode 100644 lib/utils/wrapper.py create mode 100755 test/ganeti.utils.wrapper_unittest.py diff --git a/Makefile.am b/Makefile.am index 7704dd97f..bbec35280 100644 --- a/Makefile.am +++ b/Makefile.am @@ -218,7 +218,8 @@ utils_PYTHON = \ lib/utils/log.py \ lib/utils/mlock.py \ lib/utils/retry.py \ - lib/utils/text.py + lib/utils/text.py \ + lib/utils/wrapper.py docrst = \ doc/admin.rst \ @@ -489,6 +490,7 @@ python_tests = \ test/ganeti.utils.mlock_unittest.py \ test/ganeti.utils.retry_unittest.py \ test/ganeti.utils.text_unittest.py \ + test/ganeti.utils.wrapper_unittest.py \ test/ganeti.utils_unittest.py \ test/ganeti.workerpool_unittest.py \ test/cfgupgrade_unittest.py \ diff --git a/lib/utils/__init__.py b/lib/utils/__init__.py index 061456fa0..eb8d1b028 100644 --- a/lib/utils/__init__.py +++ b/lib/utils/__init__.py @@ -59,6 +59,7 @@ from ganeti.utils.text import * # pylint: disable-msg=W0401 from ganeti.utils.mlock import * # pylint: disable-msg=W0401 from ganeti.utils.log import * # pylint: disable-msg=W0401 from ganeti.utils.hash import * # pylint: disable-msg=W0401 +from ganeti.utils.wrapper import * # pylint: disable-msg=W0401 #: when set to True, L{RunCmd} is disabled @@ -662,61 +663,6 @@ def _RunCmdFile(cmd, env, via_shell, output, cwd): return status -def SetCloseOnExecFlag(fd, enable): - """Sets or unsets the close-on-exec flag on a file descriptor. - - @type fd: int - @param fd: File descriptor - @type enable: bool - @param enable: Whether to set or unset it. - - """ - flags = fcntl.fcntl(fd, fcntl.F_GETFD) - - if enable: - flags |= fcntl.FD_CLOEXEC - else: - flags &= ~fcntl.FD_CLOEXEC - - fcntl.fcntl(fd, fcntl.F_SETFD, flags) - - -def SetNonblockFlag(fd, enable): - """Sets or unsets the O_NONBLOCK flag on on a file descriptor. - - @type fd: int - @param fd: File descriptor - @type enable: bool - @param enable: Whether to set or unset it - - """ - flags = fcntl.fcntl(fd, fcntl.F_GETFL) - - if enable: - flags |= os.O_NONBLOCK - else: - flags &= ~os.O_NONBLOCK - - fcntl.fcntl(fd, fcntl.F_SETFL, flags) - - -def RetryOnSignal(fn, *args, **kwargs): - """Calls a function again if it failed due to EINTR. - - """ - while True: - try: - return fn(*args, **kwargs) - except EnvironmentError, err: - if err.errno != errno.EINTR: - raise - except (socket.error, select.error), err: - # In python 2.6 and above select.error is an IOError, so it's handled - # above, in 2.5 and below it's not, and it's handled here. - if not (err.args and err.args[0] == errno.EINTR): - raise - - def RunParts(dir_name, env=None, reset_env=False): """Run Scripts or programs in a directory @@ -1876,41 +1822,6 @@ def WaitForFdCondition(fdobj, event, timeout): return result -def TestDelay(duration): - """Sleep for a fixed amount of time. - - @type duration: float - @param duration: the sleep duration - @rtype: boolean - @return: False for negative value, True otherwise - - """ - if duration < 0: - return False, "Invalid sleep duration" - time.sleep(duration) - return True, None - - -def CloseFdNoError(fd, retries=5): - """Close a file descriptor ignoring errors. - - @type fd: int - @param fd: the file descriptor - @type retries: int - @param retries: how many retries to make, in case we get any - other error than EBADF - - """ - try: - os.close(fd) - except OSError, err: - if err.errno != errno.EBADF: - if retries > 0: - CloseFdNoError(fd, retries - 1) - # else either it's closed already or we're out of retries, so we - # ignore this and go on - - def CloseFDs(noclose_fds=None): """Close file descriptors. @@ -2638,46 +2549,6 @@ def RunInSeparateProcess(fn, *args): return bool(exitcode) -def IgnoreProcessNotFound(fn, *args, **kwargs): - """Ignores ESRCH when calling a process-related function. - - ESRCH is raised when a process is not found. - - @rtype: bool - @return: Whether process was found - - """ - try: - fn(*args, **kwargs) - except EnvironmentError, err: - # Ignore ESRCH - if err.errno == errno.ESRCH: - return False - raise - - return True - - -def IgnoreSignals(fn, *args, **kwargs): - """Tries to call a function ignoring failures due to EINTR. - - """ - try: - return fn(*args, **kwargs) - except EnvironmentError, err: - if err.errno == errno.EINTR: - return None - else: - raise - except (select.error, socket.error), err: - # In python 2.6 and above select.error is an IOError, so it's handled - # above, in 2.5 and below it's not, and it's handled here. - if err.args and err.args[0] == errno.EINTR: - return None - else: - raise - - def LockFile(fd): """Locks a file using POSIX locks. @@ -2736,15 +2607,6 @@ def ReadWatcherPauseFile(filename, now=None, remove_after=3600): return value -def GetClosedTempfile(*args, **kwargs): - """Creates a temporary file and returns its path. - - """ - (fd, path) = tempfile.mkstemp(*args, **kwargs) - CloseFdNoError(fd) - return path - - def GenerateSelfSignedX509Cert(common_name, validity): """Generates a self-signed X509 certificate. diff --git a/lib/utils/wrapper.py b/lib/utils/wrapper.py new file mode 100644 index 000000000..b87f69c35 --- /dev/null +++ b/lib/utils/wrapper.py @@ -0,0 +1,170 @@ +# +# + +# Copyright (C) 2006, 2007, 2010, 2011 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + +"""Utility functions wrapping other functions. + +""" + +import time +import socket +import errno +import tempfile +import fcntl +import os +import select + + +def TestDelay(duration): + """Sleep for a fixed amount of time. + + @type duration: float + @param duration: the sleep duration + @rtype: boolean + @return: False for negative value, True otherwise + + """ + if duration < 0: + return False, "Invalid sleep duration" + time.sleep(duration) + return True, None + + +def CloseFdNoError(fd, retries=5): + """Close a file descriptor ignoring errors. + + @type fd: int + @param fd: the file descriptor + @type retries: int + @param retries: how many retries to make, in case we get any + other error than EBADF + + """ + try: + os.close(fd) + except OSError, err: + if err.errno != errno.EBADF: + if retries > 0: + CloseFdNoError(fd, retries - 1) + # else either it's closed already or we're out of retries, so we + # ignore this and go on + + +def SetCloseOnExecFlag(fd, enable): + """Sets or unsets the close-on-exec flag on a file descriptor. + + @type fd: int + @param fd: File descriptor + @type enable: bool + @param enable: Whether to set or unset it. + + """ + flags = fcntl.fcntl(fd, fcntl.F_GETFD) + + if enable: + flags |= fcntl.FD_CLOEXEC + else: + flags &= ~fcntl.FD_CLOEXEC + + fcntl.fcntl(fd, fcntl.F_SETFD, flags) + + +def SetNonblockFlag(fd, enable): + """Sets or unsets the O_NONBLOCK flag on on a file descriptor. + + @type fd: int + @param fd: File descriptor + @type enable: bool + @param enable: Whether to set or unset it + + """ + flags = fcntl.fcntl(fd, fcntl.F_GETFL) + + if enable: + flags |= os.O_NONBLOCK + else: + flags &= ~os.O_NONBLOCK + + fcntl.fcntl(fd, fcntl.F_SETFL, flags) + + +def RetryOnSignal(fn, *args, **kwargs): + """Calls a function again if it failed due to EINTR. + + """ + while True: + try: + return fn(*args, **kwargs) + except EnvironmentError, err: + if err.errno != errno.EINTR: + raise + except (socket.error, select.error), err: + # In python 2.6 and above select.error is an IOError, so it's handled + # above, in 2.5 and below it's not, and it's handled here. + if not (err.args and err.args[0] == errno.EINTR): + raise + + +def IgnoreProcessNotFound(fn, *args, **kwargs): + """Ignores ESRCH when calling a process-related function. + + ESRCH is raised when a process is not found. + + @rtype: bool + @return: Whether process was found + + """ + try: + fn(*args, **kwargs) + except EnvironmentError, err: + # Ignore ESRCH + if err.errno == errno.ESRCH: + return False + raise + + return True + + +def IgnoreSignals(fn, *args, **kwargs): + """Tries to call a function ignoring failures due to EINTR. + + """ + try: + return fn(*args, **kwargs) + except EnvironmentError, err: + if err.errno == errno.EINTR: + return None + else: + raise + except (select.error, socket.error), err: + # In python 2.6 and above select.error is an IOError, so it's handled + # above, in 2.5 and below it's not, and it's handled here. + if err.args and err.args[0] == errno.EINTR: + return None + else: + raise + + +def GetClosedTempfile(*args, **kwargs): + """Creates a temporary file and returns its path. + + """ + (fd, path) = tempfile.mkstemp(*args, **kwargs) + CloseFdNoError(fd) + return path diff --git a/test/ganeti.utils.wrapper_unittest.py b/test/ganeti.utils.wrapper_unittest.py new file mode 100755 index 000000000..16633f7a2 --- /dev/null +++ b/test/ganeti.utils.wrapper_unittest.py @@ -0,0 +1,126 @@ +#!/usr/bin/python +# + +# Copyright (C) 2006, 2007, 2010, 2011 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + + +"""Script for testing ganeti.utils.wrapper""" + +import errno +import fcntl +import os +import socket +import tempfile +import unittest + +from ganeti import constants +from ganeti import utils + +import testutils + + +class TestSetCloseOnExecFlag(unittest.TestCase): + """Tests for SetCloseOnExecFlag""" + + def setUp(self): + self.tmpfile = tempfile.TemporaryFile() + + def testEnable(self): + utils.SetCloseOnExecFlag(self.tmpfile.fileno(), True) + self.failUnless(fcntl.fcntl(self.tmpfile.fileno(), fcntl.F_GETFD) & + fcntl.FD_CLOEXEC) + + def testDisable(self): + utils.SetCloseOnExecFlag(self.tmpfile.fileno(), False) + self.failIf(fcntl.fcntl(self.tmpfile.fileno(), fcntl.F_GETFD) & + fcntl.FD_CLOEXEC) + + +class TestSetNonblockFlag(unittest.TestCase): + def setUp(self): + self.tmpfile = tempfile.TemporaryFile() + + def testEnable(self): + utils.SetNonblockFlag(self.tmpfile.fileno(), True) + self.failUnless(fcntl.fcntl(self.tmpfile.fileno(), fcntl.F_GETFL) & + os.O_NONBLOCK) + + def testDisable(self): + utils.SetNonblockFlag(self.tmpfile.fileno(), False) + self.failIf(fcntl.fcntl(self.tmpfile.fileno(), fcntl.F_GETFL) & + os.O_NONBLOCK) + + +class TestIgnoreProcessNotFound(unittest.TestCase): + @staticmethod + def _WritePid(fd): + os.write(fd, str(os.getpid())) + os.close(fd) + return True + + def test(self): + (pid_read_fd, pid_write_fd) = os.pipe() + + # Start short-lived process which writes its PID to pipe + self.assert_(utils.RunInSeparateProcess(self._WritePid, pid_write_fd)) + os.close(pid_write_fd) + + # Read PID from pipe + pid = int(os.read(pid_read_fd, 1024)) + os.close(pid_read_fd) + + # Try to send signal to process which exited recently + self.assertFalse(utils.IgnoreProcessNotFound(os.kill, pid, 0)) + + +class TestIgnoreSignals(unittest.TestCase): + """Test the IgnoreSignals decorator""" + + @staticmethod + def _Raise(exception): + raise exception + + @staticmethod + def _Return(rval): + return rval + + def testIgnoreSignals(self): + sock_err_intr = socket.error(errno.EINTR, "Message") + sock_err_inval = socket.error(errno.EINVAL, "Message") + + env_err_intr = EnvironmentError(errno.EINTR, "Message") + env_err_inval = EnvironmentError(errno.EINVAL, "Message") + + self.assertRaises(socket.error, self._Raise, sock_err_intr) + self.assertRaises(socket.error, self._Raise, sock_err_inval) + self.assertRaises(EnvironmentError, self._Raise, env_err_intr) + self.assertRaises(EnvironmentError, self._Raise, env_err_inval) + + self.assertEquals(utils.IgnoreSignals(self._Raise, sock_err_intr), None) + self.assertEquals(utils.IgnoreSignals(self._Raise, env_err_intr), None) + self.assertRaises(socket.error, utils.IgnoreSignals, self._Raise, + sock_err_inval) + self.assertRaises(EnvironmentError, utils.IgnoreSignals, self._Raise, + env_err_inval) + + self.assertEquals(utils.IgnoreSignals(self._Return, True), True) + self.assertEquals(utils.IgnoreSignals(self._Return, 33), 33) + + +if __name__ == "__main__": + testutils.GanetiTestProgram() diff --git a/test/ganeti.utils_unittest.py b/test/ganeti.utils_unittest.py index a62feb52b..baaca37be 100755 --- a/test/ganeti.utils_unittest.py +++ b/test/ganeti.utils_unittest.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -# Copyright (C) 2006, 2007, 2010 Google Inc. +# Copyright (C) 2006, 2007, 2010, 2011 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -627,38 +627,6 @@ class TestStartDaemon(testutils.GanetiTestCase): os.close(fd) -class TestSetCloseOnExecFlag(unittest.TestCase): - """Tests for SetCloseOnExecFlag""" - - def setUp(self): - self.tmpfile = tempfile.TemporaryFile() - - def testEnable(self): - utils.SetCloseOnExecFlag(self.tmpfile.fileno(), True) - self.failUnless(fcntl.fcntl(self.tmpfile.fileno(), fcntl.F_GETFD) & - fcntl.FD_CLOEXEC) - - def testDisable(self): - utils.SetCloseOnExecFlag(self.tmpfile.fileno(), False) - self.failIf(fcntl.fcntl(self.tmpfile.fileno(), fcntl.F_GETFD) & - fcntl.FD_CLOEXEC) - - -class TestSetNonblockFlag(unittest.TestCase): - def setUp(self): - self.tmpfile = tempfile.TemporaryFile() - - def testEnable(self): - utils.SetNonblockFlag(self.tmpfile.fileno(), True) - self.failUnless(fcntl.fcntl(self.tmpfile.fileno(), fcntl.F_GETFL) & - os.O_NONBLOCK) - - def testDisable(self): - utils.SetNonblockFlag(self.tmpfile.fileno(), False) - self.failIf(fcntl.fcntl(self.tmpfile.fileno(), fcntl.F_GETFL) & - os.O_NONBLOCK) - - class TestRemoveFile(unittest.TestCase): """Test case for the RemoveFile function""" @@ -1801,40 +1769,6 @@ class TestVerifyCertificateInner(unittest.TestCase): self.assertEqual(errcode, utils.CERT_ERROR) -class TestIgnoreSignals(unittest.TestCase): - """Test the IgnoreSignals decorator""" - - @staticmethod - def _Raise(exception): - raise exception - - @staticmethod - def _Return(rval): - return rval - - def testIgnoreSignals(self): - sock_err_intr = socket.error(errno.EINTR, "Message") - sock_err_inval = socket.error(errno.EINVAL, "Message") - - env_err_intr = EnvironmentError(errno.EINTR, "Message") - env_err_inval = EnvironmentError(errno.EINVAL, "Message") - - self.assertRaises(socket.error, self._Raise, sock_err_intr) - self.assertRaises(socket.error, self._Raise, sock_err_inval) - self.assertRaises(EnvironmentError, self._Raise, env_err_intr) - self.assertRaises(EnvironmentError, self._Raise, env_err_inval) - - self.assertEquals(utils.IgnoreSignals(self._Raise, sock_err_intr), None) - self.assertEquals(utils.IgnoreSignals(self._Raise, env_err_intr), None) - self.assertRaises(socket.error, utils.IgnoreSignals, self._Raise, - sock_err_inval) - self.assertRaises(EnvironmentError, utils.IgnoreSignals, self._Raise, - env_err_inval) - - self.assertEquals(utils.IgnoreSignals(self._Return, True), True) - self.assertEquals(utils.IgnoreSignals(self._Return, 33), 33) - - class TestEnsureDirs(unittest.TestCase): """Tests for EnsureDirs""" @@ -1857,28 +1791,6 @@ class TestEnsureDirs(unittest.TestCase): os.umask(self.old_umask) -class TestIgnoreProcessNotFound(unittest.TestCase): - @staticmethod - def _WritePid(fd): - os.write(fd, str(os.getpid())) - os.close(fd) - return True - - def test(self): - (pid_read_fd, pid_write_fd) = os.pipe() - - # Start short-lived process which writes its PID to pipe - self.assert_(utils.RunInSeparateProcess(self._WritePid, pid_write_fd)) - os.close(pid_write_fd) - - # Read PID from pipe - pid = int(os.read(pid_read_fd, 1024)) - os.close(pid_read_fd) - - # Try to send signal to process which exited recently - self.assertFalse(utils.IgnoreProcessNotFound(os.kill, pid, 0)) - - class TestFindMatch(unittest.TestCase): def test(self): data = { -- GitLab