From b6522276819f6e63892d411d6ed9a74a09dbccdb Mon Sep 17 00:00:00 2001 From: Michael Hanselmann <hansmi@google.com> Date: Thu, 8 Dec 2011 15:33:31 +0100 Subject: [PATCH] utils.io.WritePidFile: Improve error reporting If the PID file is already locked by another process, try to read the content and report it as part of the error message. Signed-off-by: Michael Hanselmann <hansmi@google.com> Reviewed-by: Iustin Pop <iustin@google.com> --- lib/errors.py | 6 ++++++ lib/utils/io.py | 31 +++++++++++++++++++++++++------ test/ganeti.utils.io_unittest.py | 19 ++++++++++++++++++- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/lib/errors.py b/lib/errors.py index 181288618..7319cd9f1 100644 --- a/lib/errors.py +++ b/lib/errors.py @@ -82,6 +82,12 @@ class LockError(GenericError): pass +class PidFileLockError(LockError): + """PID file is already locked by another process. + + """ + + class HypervisorError(GenericError): """Hypervisor-related exception. diff --git a/lib/utils/io.py b/lib/utils/io.py index 80dc1f0d3..43f744937 100644 --- a/lib/utils/io.py +++ b/lib/utils/io.py @@ -701,13 +701,24 @@ def ReadPidFile(pidfile): logging.exception("Can't read pid file") return 0 + return _ParsePidFileContents(raw_data) + + +def _ParsePidFileContents(data): + """Tries to extract a process ID from a PID file's content. + + @type data: string + @rtype: int + @return: Zero if nothing could be read, PID otherwise + + """ try: - pid = int(raw_data) - except (TypeError, ValueError), err: + pid = int(data) + except (TypeError, ValueError): logging.info("Can't parse pid file contents", exc_info=True) return 0 - - return pid + else: + return pid def ReadLockedPidFile(path): @@ -834,13 +845,21 @@ def WritePidFile(pidfile): """ # We don't rename nor truncate the file to not drop locks under # existing processes - fd_pidfile = os.open(pidfile, os.O_WRONLY | os.O_CREAT, 0600) + fd_pidfile = os.open(pidfile, os.O_RDWR | os.O_CREAT, 0600) # Lock the PID file (and fail if not possible to do so). Any code # wanting to send a signal to the daemon should try to lock the PID # file before reading it. If acquiring the lock succeeds, the daemon is # no longer running and the signal should not be sent. - filelock.LockFile(fd_pidfile) + try: + filelock.LockFile(fd_pidfile) + except errors.LockError: + msg = ["PID file '%s' is already locked by another process" % pidfile] + # Try to read PID file + pid = _ParsePidFileContents(os.read(fd_pidfile, 100)) + if pid > 0: + msg.append(", PID read from file is %s" % pid) + raise errors.PidFileLockError("".join(msg)) os.write(fd_pidfile, "%d\n" % os.getpid()) diff --git a/test/ganeti.utils.io_unittest.py b/test/ganeti.utils.io_unittest.py index 46bb009d7..53f1c8151 100755 --- a/test/ganeti.utils.io_unittest.py +++ b/test/ganeti.utils.io_unittest.py @@ -711,7 +711,7 @@ class TestPidFileFunctions(unittest.TestCase): read_pid = utils.ReadPidFile(pid_file) self.failUnlessEqual(read_pid, os.getpid()) self.failUnless(utils.IsProcessAlive(read_pid)) - self.failUnlessRaises(errors.LockError, utils.WritePidFile, + self.failUnlessRaises(errors.PidFileLockError, utils.WritePidFile, self.f_dpn('test')) os.close(fd) utils.RemoveFile(self.f_dpn("test")) @@ -747,11 +747,28 @@ class TestPidFileFunctions(unittest.TestCase): read_pid = utils.ReadPidFile(pid_file) self.failUnlessEqual(read_pid, new_pid) self.failUnless(utils.IsProcessAlive(new_pid)) + + # Try writing to locked file + try: + utils.WritePidFile(pid_file) + except errors.PidFileLockError, err: + errmsg = str(err) + self.assertTrue(errmsg.endswith(" %s" % new_pid), + msg=("Error message ('%s') didn't contain correct" + " PID (%s)" % (errmsg, new_pid))) + else: + self.fail("Writing to locked file didn't fail") + utils.KillProcess(new_pid, waitpid=True) self.failIf(utils.IsProcessAlive(new_pid)) utils.RemoveFile(self.f_dpn('child')) self.failUnlessRaises(errors.ProgrammerError, utils.KillProcess, 0) + def testExceptionType(self): + # Make sure the PID lock error is a subclass of LockError in case some code + # depends on it + self.assertTrue(issubclass(errors.PidFileLockError, errors.LockError)) + def tearDown(self): shutil.rmtree(self.dir) -- GitLab