Module: check_mk
Branch: master
Commit: 91cc92e7eee858c6aedaef614772ceaad69fd5c2
URL:
http://git.mathias-kettner.de/git/?p=check_mk.git;a=commit;h=91cc92e7eee858…
Author: Lars Michelsen <lm(a)mathias-kettner.de>
Date: Mon Nov 5 08:42:10 2018 +0100
Add context manager for PID file locking
* Added tests to verify PID locking mechanism
Change-Id: Id8c69cf894e6c1b34b76798af76882c97a1e4c6d
---
cmk/daemon.py | 32 +++++++++++++++++++++++-
tests/unit/cmk/test_daemon.py | 58 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 89 insertions(+), 1 deletion(-)
diff --git a/cmk/daemon.py b/cmk/daemon.py
index aac0e95..20faeb9 100644
--- a/cmk/daemon.py
+++ b/cmk/daemon.py
@@ -30,9 +30,11 @@ from pwd import getpwnam
from grp import getgrnam
import ctypes
import ctypes.util
+from pathlib2 import Path # pylint: disable=unused-import
+from contextlib import contextmanager
import cmk.store
-from .exceptions import MKGeneralException
+from cmk.exceptions import MKGeneralException
def daemonize(user=0, group=0):
@@ -99,7 +101,9 @@ def closefrom(lowfd):
os.closerange(lowfd, highfd)
+# TODO: Change API and call sites to work with Path() objects
def lock_with_pid_file(path):
+ # type: (str) -> None
"""
Use this after daemonizing or in foreground mode to ensure there is only
one process running.
@@ -114,6 +118,32 @@ def lock_with_pid_file(path):
f.write("%d\n" % os.getpid())
+# TODO: Change API and call sites to work with Path() objects
+def _cleanup_locked_pid_file(path):
+ # type: (str) -> None
+ """Cleanup the lock + file acquired by the function
above"""
+ if not cmk.store.have_lock(path):
+ return
+
+ cmk.store.release_lock(path)
+
+ try:
+ os.remove(path)
+ except OSError:
+ pass
+
+
+@contextmanager
+def pid_file_lock(path):
+ # type: (Path) -> None
+ """Context manager for PID file based locking"""
+ lock_with_pid_file("%s" % path)
+ try:
+ yield
+ finally:
+ _cleanup_locked_pid_file("%s" % path)
+
+
def set_cmdline(cmdline):
"""
Change the process name and process command line on of the running process
diff --git a/tests/unit/cmk/test_daemon.py b/tests/unit/cmk/test_daemon.py
new file mode 100644
index 0000000..7ca5599
--- /dev/null
+++ b/tests/unit/cmk/test_daemon.py
@@ -0,0 +1,58 @@
+import pytest
+from pathlib2 import Path
+import os
+
+import cmk.store as store
+import cmk.daemon as daemon
+from cmk.exceptions import MKGeneralException
+
+(a)pytest.fixture(autouse=True)
+def cleanup_locks():
+ yield
+ store.release_all_locks()
+
+
+def test_lock_with_pid_file(tmpdir):
+ pid_file = Path(tmpdir) / "test.pid"
+
+ daemon.lock_with_pid_file("%s" % pid_file)
+
+ assert store.have_lock("%s" % pid_file)
+
+ with pid_file.open() as f:
+ assert int(f.read()) == os.getpid()
+
+
+def test_cleanup_locked_pid_file(tmpdir):
+ pid_file = Path(tmpdir) / "test.pid"
+
+ assert not store.have_lock("%s" % pid_file)
+ daemon.lock_with_pid_file("%s" % pid_file)
+ assert store.have_lock("%s" % pid_file)
+
+ daemon._cleanup_locked_pid_file("%s" % pid_file)
+
+ assert not store.have_lock("%s" % pid_file)
+
+
+def test_pid_file_lock_context_manager(tmpdir):
+ pid_file = Path(tmpdir) / "test.pid"
+
+ assert not store.have_lock("%s" % pid_file)
+
+ with daemon.pid_file_lock(pid_file):
+ assert store.have_lock("%s" % pid_file)
+
+
+def test_pid_file_lock_context_manager_exception(tmpdir):
+ pid_file = Path(tmpdir) / "test.pid"
+
+ assert not store.have_lock("%s" % pid_file)
+ try:
+ with daemon.pid_file_lock(pid_file):
+ assert store.have_lock("%s" % pid_file)
+ raise MKGeneralException("bla")
+ except MKGeneralException:
+ pass
+
+ assert not store.have_lock("%s" % pid_file)