re-format with black
This commit is contained in:
+2
-2
@@ -37,11 +37,11 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import (absolute_import, unicode_literals)
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
from .daemon import DaemonContext
|
from .daemon import DaemonContext
|
||||||
|
|
||||||
|
|
||||||
# Local variables:
|
# Local variables:
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
# mode: python
|
# mode: python
|
||||||
|
|||||||
+21
-18
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
""" Package metadata for the ‘python-daemon’ distribution. """
|
""" Package metadata for the ‘python-daemon’ distribution. """
|
||||||
|
|
||||||
from __future__ import (absolute_import, unicode_literals)
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
@@ -21,10 +21,11 @@ import datetime
|
|||||||
|
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
|
|
||||||
distribution_name = "python-daemon"
|
distribution_name = "python-daemon"
|
||||||
version_info_filename = "version_info.json"
|
version_info_filename = "version_info.json"
|
||||||
|
|
||||||
|
|
||||||
def get_distribution_version_info(filename=version_info_filename):
|
def get_distribution_version_info(filename=version_info_filename):
|
||||||
""" Get the version info from the installed distribution.
|
""" Get the version info from the installed distribution.
|
||||||
|
|
||||||
@@ -37,10 +38,10 @@ def get_distribution_version_info(filename=version_info_filename):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
version_info = {
|
version_info = {
|
||||||
'release_date': "UNKNOWN",
|
"release_date": "UNKNOWN",
|
||||||
'version': "UNKNOWN",
|
"version": "UNKNOWN",
|
||||||
'maintainer': "UNKNOWN",
|
"maintainer": "UNKNOWN",
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
distribution = pkg_resources.get_distribution(distribution_name)
|
distribution = pkg_resources.get_distribution(distribution_name)
|
||||||
@@ -54,15 +55,16 @@ def get_distribution_version_info(filename=version_info_filename):
|
|||||||
|
|
||||||
return version_info
|
return version_info
|
||||||
|
|
||||||
|
|
||||||
version_info = get_distribution_version_info()
|
version_info = get_distribution_version_info()
|
||||||
|
|
||||||
version_installed = version_info['version']
|
version_installed = version_info["version"]
|
||||||
|
|
||||||
|
|
||||||
rfc822_person_regex = re.compile(
|
|
||||||
"^(?P<name>[^<]+) <(?P<email>[^>]+)>$")
|
|
||||||
|
|
||||||
ParsedPerson = collections.namedtuple('ParsedPerson', ['name', 'email'])
|
rfc822_person_regex = re.compile("^(?P<name>[^<]+) <(?P<email>[^>]+)>$")
|
||||||
|
|
||||||
|
ParsedPerson = collections.namedtuple("ParsedPerson", ["name", "email"])
|
||||||
|
|
||||||
|
|
||||||
def parse_person_field(value):
|
def parse_person_field(value):
|
||||||
""" Parse a person field into name and email address.
|
""" Parse a person field into name and email address.
|
||||||
@@ -79,19 +81,18 @@ def parse_person_field(value):
|
|||||||
match = rfc822_person_regex.match(value)
|
match = rfc822_person_regex.match(value)
|
||||||
if len(value):
|
if len(value):
|
||||||
if match is not None:
|
if match is not None:
|
||||||
result = ParsedPerson(
|
result = ParsedPerson(name=match.group("name"), email=match.group("email"))
|
||||||
name=match.group('name'),
|
|
||||||
email=match.group('email'))
|
|
||||||
else:
|
else:
|
||||||
result = ParsedPerson(name=value, email=None)
|
result = ParsedPerson(name=value, email=None)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
author_name = "Ben Finney"
|
author_name = "Ben Finney"
|
||||||
author_email = "ben+python@benfinney.id.au"
|
author_email = "ben+python@benfinney.id.au"
|
||||||
author = "{name} <{email}>".format(name=author_name, email=author_email)
|
author = "{name} <{email}>".format(name=author_name, email=author_email)
|
||||||
|
|
||||||
|
|
||||||
class YearRange:
|
class YearRange:
|
||||||
""" A range of years spanning a period. """
|
""" A range of years spanning a period. """
|
||||||
|
|
||||||
@@ -135,16 +136,18 @@ def make_year_range(begin_year, end_date=None):
|
|||||||
|
|
||||||
return year_range
|
return year_range
|
||||||
|
|
||||||
|
|
||||||
copyright_year_begin = "2001"
|
copyright_year_begin = "2001"
|
||||||
build_date = version_info['release_date']
|
build_date = version_info["release_date"]
|
||||||
copyright_year_range = make_year_range(copyright_year_begin, build_date)
|
copyright_year_range = make_year_range(copyright_year_begin, build_date)
|
||||||
|
|
||||||
copyright = "Copyright © {year_range} {author} and others".format(
|
copyright = "Copyright © {year_range} {author} and others".format(
|
||||||
year_range=copyright_year_range, author=author)
|
year_range=copyright_year_range, author=author
|
||||||
|
)
|
||||||
license = "Apache-2"
|
license = "Apache-2"
|
||||||
url = "https://alioth.debian.org/projects/python-daemon/"
|
url = "https://alioth.debian.org/projects/python-daemon/"
|
||||||
|
|
||||||
|
|
||||||
# Local variables:
|
# Local variables:
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
# mode: python
|
# mode: python
|
||||||
|
|||||||
+68
-54
@@ -18,7 +18,7 @@
|
|||||||
""" Daemon process behaviour.
|
""" Daemon process behaviour.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import (absolute_import, unicode_literals)
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -27,6 +27,7 @@ import errno
|
|||||||
import signal
|
import signal
|
||||||
import socket
|
import socket
|
||||||
import atexit
|
import atexit
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Python 2 has both ‘str’ (bytes) and ‘unicode’ (text).
|
# Python 2 has both ‘str’ (bytes) and ‘unicode’ (text).
|
||||||
basestring = basestring
|
basestring = basestring
|
||||||
@@ -36,7 +37,7 @@ except NameError:
|
|||||||
basestring = str
|
basestring = str
|
||||||
unicode = str
|
unicode = str
|
||||||
|
|
||||||
|
|
||||||
class DaemonError(Exception):
|
class DaemonError(Exception):
|
||||||
""" Base exception class for errors from this module. """
|
""" Base exception class for errors from this module. """
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ class DaemonOSEnvironmentError(DaemonError, OSError):
|
|||||||
class DaemonProcessDetachError(DaemonError, OSError):
|
class DaemonProcessDetachError(DaemonError, OSError):
|
||||||
""" Exception raised when process detach fails. """
|
""" Exception raised when process detach fails. """
|
||||||
|
|
||||||
|
|
||||||
class DaemonContext:
|
class DaemonContext:
|
||||||
""" Context for turning the current program into a daemon process.
|
""" Context for turning the current program into a daemon process.
|
||||||
|
|
||||||
@@ -231,21 +232,21 @@ class DaemonContext:
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
chroot_directory=None,
|
chroot_directory=None,
|
||||||
working_directory="/",
|
working_directory="/",
|
||||||
umask=0,
|
umask=0,
|
||||||
uid=None,
|
uid=None,
|
||||||
gid=None,
|
gid=None,
|
||||||
prevent_core=True,
|
prevent_core=True,
|
||||||
detach_process=None,
|
detach_process=None,
|
||||||
files_preserve=None,
|
files_preserve=None,
|
||||||
pidfile=None,
|
pidfile=None,
|
||||||
stdin=None,
|
stdin=None,
|
||||||
stdout=None,
|
stdout=None,
|
||||||
stderr=None,
|
stderr=None,
|
||||||
signal_map=None,
|
signal_map=None,
|
||||||
):
|
):
|
||||||
""" Set up a new instance. """
|
""" Set up a new instance. """
|
||||||
self.chroot_directory = chroot_directory
|
self.chroot_directory = chroot_directory
|
||||||
self.working_directory = working_directory
|
self.working_directory = working_directory
|
||||||
@@ -427,8 +428,10 @@ class DaemonContext:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
exception = SystemExit(
|
exception = SystemExit(
|
||||||
"Terminating on signal {signal_number!r}".format(
|
"Terminating on signal {signal_number!r}".format(
|
||||||
signal_number=signal_number))
|
signal_number=signal_number
|
||||||
|
)
|
||||||
|
)
|
||||||
raise exception
|
raise exception
|
||||||
|
|
||||||
def _get_exclude_file_descriptors(self):
|
def _get_exclude_file_descriptors(self):
|
||||||
@@ -454,8 +457,10 @@ class DaemonContext:
|
|||||||
if files_preserve is None:
|
if files_preserve is None:
|
||||||
files_preserve = []
|
files_preserve = []
|
||||||
files_preserve.extend(
|
files_preserve.extend(
|
||||||
item for item in [self.stdin, self.stdout, self.stderr]
|
item
|
||||||
if hasattr(item, 'fileno'))
|
for item in [self.stdin, self.stdout, self.stderr]
|
||||||
|
if hasattr(item, "fileno")
|
||||||
|
)
|
||||||
|
|
||||||
exclude_descriptors = set()
|
exclude_descriptors = set()
|
||||||
for item in files_preserve:
|
for item in files_preserve:
|
||||||
@@ -502,8 +507,9 @@ class DaemonContext:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
signal_handler_map = dict(
|
signal_handler_map = dict(
|
||||||
(signal_number, self._make_signal_handler(target))
|
(signal_number, self._make_signal_handler(target))
|
||||||
for (signal_number, target) in self.signal_map.items())
|
for (signal_number, target) in self.signal_map.items()
|
||||||
|
)
|
||||||
return signal_handler_map
|
return signal_handler_map
|
||||||
|
|
||||||
|
|
||||||
@@ -520,7 +526,7 @@ def _get_file_descriptor(obj):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
file_descriptor = None
|
file_descriptor = None
|
||||||
if hasattr(obj, 'fileno'):
|
if hasattr(obj, "fileno"):
|
||||||
try:
|
try:
|
||||||
file_descriptor = obj.fileno()
|
file_descriptor = obj.fileno()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@@ -529,7 +535,7 @@ def _get_file_descriptor(obj):
|
|||||||
|
|
||||||
return file_descriptor
|
return file_descriptor
|
||||||
|
|
||||||
|
|
||||||
def change_working_directory(directory):
|
def change_working_directory(directory):
|
||||||
""" Change the working directory of this process.
|
""" Change the working directory of this process.
|
||||||
|
|
||||||
@@ -541,7 +547,8 @@ def change_working_directory(directory):
|
|||||||
os.chdir(directory)
|
os.chdir(directory)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
error = DaemonOSEnvironmentError(
|
error = DaemonOSEnvironmentError(
|
||||||
"Unable to change working directory ({exc})".format(exc=exc))
|
"Unable to change working directory ({exc})".format(exc=exc)
|
||||||
|
)
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
|
|
||||||
@@ -561,7 +568,8 @@ def change_root_directory(directory):
|
|||||||
os.chroot(directory)
|
os.chroot(directory)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
error = DaemonOSEnvironmentError(
|
error = DaemonOSEnvironmentError(
|
||||||
"Unable to change root directory ({exc})".format(exc=exc))
|
"Unable to change root directory ({exc})".format(exc=exc)
|
||||||
|
)
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
|
|
||||||
@@ -576,7 +584,8 @@ def change_file_creation_mask(mask):
|
|||||||
os.umask(mask)
|
os.umask(mask)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
error = DaemonOSEnvironmentError(
|
error = DaemonOSEnvironmentError(
|
||||||
"Unable to change file creation mask ({exc})".format(exc=exc))
|
"Unable to change file creation mask ({exc})".format(exc=exc)
|
||||||
|
)
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
|
|
||||||
@@ -597,10 +606,11 @@ def change_process_owner(uid, gid):
|
|||||||
os.setuid(uid)
|
os.setuid(uid)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
error = DaemonOSEnvironmentError(
|
error = DaemonOSEnvironmentError(
|
||||||
"Unable to change process owner ({exc})".format(exc=exc))
|
"Unable to change process owner ({exc})".format(exc=exc)
|
||||||
|
)
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
|
|
||||||
def prevent_core_dump():
|
def prevent_core_dump():
|
||||||
""" Prevent this process from generating a core dump.
|
""" Prevent this process from generating a core dump.
|
||||||
|
|
||||||
@@ -618,15 +628,16 @@ def prevent_core_dump():
|
|||||||
core_limit_prev = resource.getrlimit(core_resource)
|
core_limit_prev = resource.getrlimit(core_resource)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
error = DaemonOSEnvironmentError(
|
error = DaemonOSEnvironmentError(
|
||||||
"System does not support RLIMIT_CORE resource limit"
|
"System does not support RLIMIT_CORE resource limit"
|
||||||
" ({exc})".format(exc=exc))
|
" ({exc})".format(exc=exc)
|
||||||
|
)
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
# Set hard and soft limits to zero, i.e. no core dump at all.
|
# Set hard and soft limits to zero, i.e. no core dump at all.
|
||||||
core_limit = (0, 0)
|
core_limit = (0, 0)
|
||||||
resource.setrlimit(core_resource, core_limit)
|
resource.setrlimit(core_resource, core_limit)
|
||||||
|
|
||||||
|
|
||||||
def detach_process_context():
|
def detach_process_context():
|
||||||
""" Detach the process context from parent and session.
|
""" Detach the process context from parent and session.
|
||||||
|
|
||||||
@@ -656,15 +667,17 @@ def detach_process_context():
|
|||||||
os._exit(0)
|
os._exit(0)
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
error = DaemonProcessDetachError(
|
error = DaemonProcessDetachError(
|
||||||
"{message}: [{exc.errno:d}] {exc.strerror}".format(
|
"{message}: [{exc.errno:d}] {exc.strerror}".format(
|
||||||
message=error_message, exc=exc))
|
message=error_message, exc=exc
|
||||||
|
)
|
||||||
|
)
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
fork_then_exit_parent(error_message="Failed first fork")
|
fork_then_exit_parent(error_message="Failed first fork")
|
||||||
os.setsid()
|
os.setsid()
|
||||||
fork_then_exit_parent(error_message="Failed second fork")
|
fork_then_exit_parent(error_message="Failed second fork")
|
||||||
|
|
||||||
|
|
||||||
def is_process_started_by_init():
|
def is_process_started_by_init():
|
||||||
""" Determine whether the current process is started by `init`.
|
""" Determine whether the current process is started by `init`.
|
||||||
|
|
||||||
@@ -699,8 +712,7 @@ def is_socket(fd):
|
|||||||
file_socket = socket.fromfd(fd, socket.AF_INET, socket.SOCK_RAW)
|
file_socket = socket.fromfd(fd, socket.AF_INET, socket.SOCK_RAW)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
socket_type = file_socket.getsockopt(
|
socket_type = file_socket.getsockopt(socket.SOL_SOCKET, socket.SO_TYPE)
|
||||||
socket.SOL_SOCKET, socket.SO_TYPE)
|
|
||||||
except socket.error as exc:
|
except socket.error as exc:
|
||||||
exc_errno = exc.args[0]
|
exc_errno = exc.args[0]
|
||||||
if exc_errno == errno.ENOTSOCK:
|
if exc_errno == errno.ENOTSOCK:
|
||||||
@@ -759,7 +771,7 @@ def is_detach_process_context_required():
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def close_file_descriptor_if_open(fd):
|
def close_file_descriptor_if_open(fd):
|
||||||
""" Close a file descriptor if already open.
|
""" Close a file descriptor if already open.
|
||||||
|
|
||||||
@@ -778,13 +790,14 @@ def close_file_descriptor_if_open(fd):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
error = DaemonOSEnvironmentError(
|
error = DaemonOSEnvironmentError(
|
||||||
"Failed to close file descriptor {fd:d} ({exc})".format(
|
"Failed to close file descriptor {fd:d} ({exc})".format(fd=fd, exc=exc)
|
||||||
fd=fd, exc=exc))
|
)
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
|
|
||||||
MAXFD = 2048
|
MAXFD = 2048
|
||||||
|
|
||||||
|
|
||||||
def get_maximum_file_descriptors():
|
def get_maximum_file_descriptors():
|
||||||
""" Get the maximum number of open file descriptors for this process.
|
""" Get the maximum number of open file descriptors for this process.
|
||||||
|
|
||||||
@@ -820,7 +833,7 @@ def close_all_open_files(exclude=set()):
|
|||||||
if fd not in exclude:
|
if fd not in exclude:
|
||||||
close_file_descriptor_if_open(fd)
|
close_file_descriptor_if_open(fd)
|
||||||
|
|
||||||
|
|
||||||
def redirect_stream(system_stream, target_stream):
|
def redirect_stream(system_stream, target_stream):
|
||||||
""" Redirect a system stream to a specified file.
|
""" Redirect a system stream to a specified file.
|
||||||
|
|
||||||
@@ -844,7 +857,7 @@ def redirect_stream(system_stream, target_stream):
|
|||||||
target_fd = target_stream.fileno()
|
target_fd = target_stream.fileno()
|
||||||
os.dup2(target_fd, system_stream.fileno())
|
os.dup2(target_fd, system_stream.fileno())
|
||||||
|
|
||||||
|
|
||||||
def make_default_signal_map():
|
def make_default_signal_map():
|
||||||
""" Make the default signal map for this system.
|
""" Make the default signal map for this system.
|
||||||
|
|
||||||
@@ -855,15 +868,16 @@ def make_default_signal_map():
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
name_map = {
|
name_map = {
|
||||||
'SIGTSTP': None,
|
"SIGTSTP": None,
|
||||||
'SIGTTIN': None,
|
"SIGTTIN": None,
|
||||||
'SIGTTOU': None,
|
"SIGTTOU": None,
|
||||||
'SIGTERM': 'terminate',
|
"SIGTERM": "terminate",
|
||||||
}
|
}
|
||||||
signal_map = dict(
|
signal_map = dict(
|
||||||
(getattr(signal, name), target)
|
(getattr(signal, name), target)
|
||||||
for (name, target) in name_map.items()
|
for (name, target) in name_map.items()
|
||||||
if hasattr(signal, name))
|
if hasattr(signal, name)
|
||||||
|
)
|
||||||
|
|
||||||
return signal_map
|
return signal_map
|
||||||
|
|
||||||
@@ -895,7 +909,7 @@ def register_atexit_function(func):
|
|||||||
"""
|
"""
|
||||||
atexit.register(func)
|
atexit.register(func)
|
||||||
|
|
||||||
|
|
||||||
def _chain_exception_from_existing_exception_context(exc, as_cause=False):
|
def _chain_exception_from_existing_exception_context(exc, as_cause=False):
|
||||||
""" Decorate the specified exception with the existing exception context.
|
""" Decorate the specified exception with the existing exception context.
|
||||||
|
|
||||||
@@ -918,7 +932,7 @@ def _chain_exception_from_existing_exception_context(exc, as_cause=False):
|
|||||||
exc.__context__ = existing_exc
|
exc.__context__ = existing_exc
|
||||||
exc.__traceback__ = existing_traceback
|
exc.__traceback__ = existing_traceback
|
||||||
|
|
||||||
|
|
||||||
# Local variables:
|
# Local variables:
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
# mode: python
|
# mode: python
|
||||||
|
|||||||
+3
-3
@@ -13,11 +13,11 @@
|
|||||||
""" Lockfile behaviour implemented via Unix PID files.
|
""" Lockfile behaviour implemented via Unix PID files.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import (absolute_import, unicode_literals)
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
from lockfile.pidlockfile import PIDLockFile
|
from lockfile.pidlockfile import PIDLockFile
|
||||||
|
|
||||||
|
|
||||||
class TimeoutPIDLockFile(PIDLockFile, object):
|
class TimeoutPIDLockFile(PIDLockFile, object):
|
||||||
""" Lockfile with default timeout, implemented as a Unix PID file.
|
""" Lockfile with default timeout, implemented as a Unix PID file.
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ class TimeoutPIDLockFile(PIDLockFile, object):
|
|||||||
timeout = self.acquire_timeout
|
timeout = self.acquire_timeout
|
||||||
super(TimeoutPIDLockFile, self).acquire(timeout, *args, **kwargs)
|
super(TimeoutPIDLockFile, self).acquire(timeout, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
# Local variables:
|
# Local variables:
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
# mode: python
|
# mode: python
|
||||||
|
|||||||
+26
-28
@@ -17,12 +17,13 @@
|
|||||||
""" Daemon runner library.
|
""" Daemon runner library.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import (absolute_import, unicode_literals)
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import errno
|
import errno
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Python 3 standard library.
|
# Python 3 standard library.
|
||||||
ProcessLookupError
|
ProcessLookupError
|
||||||
@@ -33,11 +34,11 @@ except NameError:
|
|||||||
import lockfile
|
import lockfile
|
||||||
|
|
||||||
from . import pidfile
|
from . import pidfile
|
||||||
from .daemon import (basestring, unicode)
|
from .daemon import basestring, unicode
|
||||||
from .daemon import DaemonContext
|
from .daemon import DaemonContext
|
||||||
from .daemon import _chain_exception_from_existing_exception_context
|
from .daemon import _chain_exception_from_existing_exception_context
|
||||||
|
|
||||||
|
|
||||||
class DaemonRunnerError(Exception):
|
class DaemonRunnerError(Exception):
|
||||||
""" Abstract base class for errors from DaemonRunner. """
|
""" Abstract base class for errors from DaemonRunner. """
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ class DaemonRunnerStartFailureError(DaemonRunnerError, RuntimeError):
|
|||||||
class DaemonRunnerStopFailureError(DaemonRunnerError, RuntimeError):
|
class DaemonRunnerStopFailureError(DaemonRunnerError, RuntimeError):
|
||||||
""" Raised when failure stopping DaemonRunner. """
|
""" Raised when failure stopping DaemonRunner. """
|
||||||
|
|
||||||
|
|
||||||
class DaemonRunner:
|
class DaemonRunner:
|
||||||
""" Controller for a callable running in a separate background process.
|
""" Controller for a callable running in a separate background process.
|
||||||
|
|
||||||
@@ -107,15 +108,13 @@ class DaemonRunner:
|
|||||||
self.parse_args()
|
self.parse_args()
|
||||||
self.app = app
|
self.app = app
|
||||||
self.daemon_context = DaemonContext()
|
self.daemon_context = DaemonContext()
|
||||||
self.daemon_context.stdin = open(app.stdin_path, 'rt')
|
self.daemon_context.stdin = open(app.stdin_path, "rt")
|
||||||
self.daemon_context.stdout = open(app.stdout_path, 'w+t')
|
self.daemon_context.stdout = open(app.stdout_path, "w+t")
|
||||||
self.daemon_context.stderr = open(
|
self.daemon_context.stderr = open(app.stderr_path, "w+t", buffering=0)
|
||||||
app.stderr_path, 'w+t', buffering=0)
|
|
||||||
|
|
||||||
self.pidfile = None
|
self.pidfile = None
|
||||||
if app.pidfile_path is not None:
|
if app.pidfile_path is not None:
|
||||||
self.pidfile = make_pidlockfile(
|
self.pidfile = make_pidlockfile(app.pidfile_path, app.pidfile_timeout)
|
||||||
app.pidfile_path, app.pidfile_timeout)
|
|
||||||
self.daemon_context.pidfile = self.pidfile
|
self.daemon_context.pidfile = self.pidfile
|
||||||
|
|
||||||
def _usage_exit(self, argv):
|
def _usage_exit(self, argv):
|
||||||
@@ -130,7 +129,8 @@ class DaemonRunner:
|
|||||||
usage_exit_code = 2
|
usage_exit_code = 2
|
||||||
action_usage = "|".join(self.action_funcs.keys())
|
action_usage = "|".join(self.action_funcs.keys())
|
||||||
message = "usage: {progname} {usage}".format(
|
message = "usage: {progname} {usage}".format(
|
||||||
progname=progname, usage=action_usage)
|
progname=progname, usage=action_usage
|
||||||
|
)
|
||||||
emit_message(message)
|
emit_message(message)
|
||||||
sys.exit(usage_exit_code)
|
sys.exit(usage_exit_code)
|
||||||
|
|
||||||
@@ -175,8 +175,8 @@ class DaemonRunner:
|
|||||||
self.daemon_context.open()
|
self.daemon_context.open()
|
||||||
except lockfile.AlreadyLocked:
|
except lockfile.AlreadyLocked:
|
||||||
error = DaemonRunnerStartFailureError(
|
error = DaemonRunnerStartFailureError(
|
||||||
"PID file {pidfile.path!r} already locked".format(
|
"PID file {pidfile.path!r} already locked".format(pidfile=self.pidfile)
|
||||||
pidfile=self.pidfile))
|
)
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
pid = os.getpid()
|
pid = os.getpid()
|
||||||
@@ -198,8 +198,8 @@ class DaemonRunner:
|
|||||||
os.kill(pid, signal.SIGTERM)
|
os.kill(pid, signal.SIGTERM)
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
error = DaemonRunnerStopFailureError(
|
error = DaemonRunnerStopFailureError(
|
||||||
"Failed to terminate {pid:d}: {exc}".format(
|
"Failed to terminate {pid:d}: {exc}".format(pid=pid, exc=exc)
|
||||||
pid=pid, exc=exc))
|
)
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
def _stop(self):
|
def _stop(self):
|
||||||
@@ -212,8 +212,8 @@ class DaemonRunner:
|
|||||||
"""
|
"""
|
||||||
if not self.pidfile.is_locked():
|
if not self.pidfile.is_locked():
|
||||||
error = DaemonRunnerStopFailureError(
|
error = DaemonRunnerStopFailureError(
|
||||||
"PID file {pidfile.path!r} not locked".format(
|
"PID file {pidfile.path!r} not locked".format(pidfile=self.pidfile)
|
||||||
pidfile=self.pidfile))
|
)
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
if is_pidfile_stale(self.pidfile):
|
if is_pidfile_stale(self.pidfile):
|
||||||
@@ -228,10 +228,10 @@ class DaemonRunner:
|
|||||||
self._start()
|
self._start()
|
||||||
|
|
||||||
action_funcs = {
|
action_funcs = {
|
||||||
'start': _start,
|
"start": _start,
|
||||||
'stop': _stop,
|
"stop": _stop,
|
||||||
'restart': _restart,
|
"restart": _restart,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_action_func(self):
|
def _get_action_func(self):
|
||||||
""" Get the function for the specified action.
|
""" Get the function for the specified action.
|
||||||
@@ -249,8 +249,8 @@ class DaemonRunner:
|
|||||||
func = self.action_funcs[self.action]
|
func = self.action_funcs[self.action]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
error = DaemonRunnerInvalidActionError(
|
error = DaemonRunnerInvalidActionError(
|
||||||
"Unknown action: {action!r}".format(
|
"Unknown action: {action!r}".format(action=self.action)
|
||||||
action=self.action))
|
)
|
||||||
raise error
|
raise error
|
||||||
return func
|
return func
|
||||||
|
|
||||||
@@ -278,12 +278,10 @@ def emit_message(message, stream=None):
|
|||||||
def make_pidlockfile(path, acquire_timeout):
|
def make_pidlockfile(path, acquire_timeout):
|
||||||
""" Make a PIDLockFile instance with the given filesystem path. """
|
""" Make a PIDLockFile instance with the given filesystem path. """
|
||||||
if not isinstance(path, basestring):
|
if not isinstance(path, basestring):
|
||||||
error = ValueError("Not a filesystem path: {path!r}".format(
|
error = ValueError("Not a filesystem path: {path!r}".format(path=path))
|
||||||
path=path))
|
|
||||||
raise error
|
raise error
|
||||||
if not os.path.isabs(path):
|
if not os.path.isabs(path):
|
||||||
error = ValueError("Not an absolute path: {path!r}".format(
|
error = ValueError("Not an absolute path: {path!r}".format(path=path))
|
||||||
path=path))
|
|
||||||
raise error
|
raise error
|
||||||
lockfile = pidfile.TimeoutPIDLockFile(path, acquire_timeout)
|
lockfile = pidfile.TimeoutPIDLockFile(path, acquire_timeout)
|
||||||
|
|
||||||
@@ -316,7 +314,7 @@ def is_pidfile_stale(pidfile):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
# Local variables:
|
# Local variables:
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
# mode: python
|
# mode: python
|
||||||
|
|||||||
+298
-308
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
host and connection class shared between hbd and
|
host and connection class shared between hbd and
|
||||||
the websit's heartbeat.py
|
the websit's heartbeat.py
|
||||||
@@ -13,358 +12,349 @@ num = 0
|
|||||||
|
|
||||||
MAXRTTS = 10
|
MAXRTTS = 10
|
||||||
|
|
||||||
DEBUG=2
|
DEBUG = 2
|
||||||
|
|
||||||
|
|
||||||
def log(host, m):
|
def log(host, m):
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
print("class log: %s %s" % (host, m))
|
print("class log: %s %s" % (host, m))
|
||||||
|
|
||||||
|
|
||||||
class Connection:
|
class Connection:
|
||||||
# map of addrs to names
|
# map of addrs to names
|
||||||
|
|
||||||
htab = {}
|
htab = {}
|
||||||
unknown = "unknown"
|
unknown = "unknown"
|
||||||
up = "up"
|
up = "up"
|
||||||
down = "down"
|
down = "down"
|
||||||
overdue = "overdue"
|
overdue = "overdue"
|
||||||
|
|
||||||
def __init__(self, host, cid, addr, afam):
|
def __init__(self, host, cid, addr, afam):
|
||||||
self.host = host
|
self.host = host
|
||||||
self.cid = cid
|
self.cid = cid
|
||||||
self.addr = addr
|
self.addr = addr
|
||||||
self.afam = afam
|
self.afam = afam
|
||||||
self.rtts = [0]
|
self.rtts = [0]
|
||||||
self.lastbeat = time.time()
|
self.lastbeat = time.time()
|
||||||
self.statetime = self.lastbeat
|
self.statetime = self.lastbeat
|
||||||
self.deltastatetime = 'computed'
|
self.deltastatetime = "computed"
|
||||||
self.state = Connection.unknown
|
self.state = Connection.unknown
|
||||||
|
|
||||||
if host:
|
if host:
|
||||||
r = "new addr %s" % (addr)
|
r = "new addr %s" % (addr)
|
||||||
Connection.htab[addr] = self.host.name
|
Connection.htab[addr] = self.host.name
|
||||||
if self.host.isDynDns():
|
if self.host.isDynDns():
|
||||||
log(self.host.name, "dns update %s" % self.addr)
|
log(self.host.name, "dns update %s" % self.addr)
|
||||||
Host.dnsQ.put((self.host.name, self.addr))
|
Host.dnsQ.put((self.host.name, self.addr))
|
||||||
|
|
||||||
def registerDns(self):
|
def registerDns(self):
|
||||||
Host.dnsQ.put((self.host.name, self.addr))
|
Host.dnsQ.put((self.host.name, self.addr))
|
||||||
|
|
||||||
|
def statedict(self, Null=False):
|
||||||
|
d = {}
|
||||||
|
now = time.time()
|
||||||
|
if not Null:
|
||||||
|
d["addr"] = self.addr
|
||||||
|
if self.rtts[-1]:
|
||||||
|
d["rtt"] = "%0.1f" % self.rtts[-1]
|
||||||
|
elif self.state == Connection.unknown:
|
||||||
|
d["rtt"] = ""
|
||||||
|
else:
|
||||||
|
d["rtt"] = "?"
|
||||||
|
d["lastbeat"] = self.lastbeat
|
||||||
|
if self.state == Connection.overdue:
|
||||||
|
d["state"] = "<b>%s</b>" % self.state
|
||||||
|
else:
|
||||||
|
d["state"] = self.state
|
||||||
|
if self.state == Connection.up:
|
||||||
|
d["rttstate"] = d["rtt"]
|
||||||
|
elif self.state == Connection.overdue:
|
||||||
|
d["rttstate"] = ""
|
||||||
|
else:
|
||||||
|
d["rttstate"] = d["state"]
|
||||||
|
d["statetime"] = time.strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S", time.localtime(self.statetime)
|
||||||
|
)
|
||||||
|
delta = now - self.statetime
|
||||||
|
|
||||||
def statedict(self, Null=False):
|
if self.state == Connection.unknown:
|
||||||
d = {}
|
d["deltastatetime"] = ""
|
||||||
now = time.time()
|
elif delta > 86400:
|
||||||
if not Null:
|
# d['deltastatetime'] = time.strftime("%d %H:%M:%S", time.gmtime(delta))
|
||||||
d['addr'] = self.addr
|
d["deltastatetime"] = "%0.1f days" % (delta / 86400.0)
|
||||||
if self.rtts[-1]:
|
elif delta > 3600:
|
||||||
d['rtt'] = "%0.1f" % self.rtts[-1]
|
# d['deltastatetime'] = time.strftime("%H:%M:%S", time.gmtime(delta))
|
||||||
elif self.state == Connection.unknown:
|
d["deltastatetime"] = time.strftime("%k:%M hrs", time.gmtime(delta))
|
||||||
d['rtt'] = ""
|
# d['deltastatetime'] = "%0.1f hrs" % (delta / 3600.)
|
||||||
else:
|
elif delta > 60:
|
||||||
d['rtt'] = "?"
|
# d['deltastatetime'] = time.strftime("%M:%S", time.gmtime(delta))
|
||||||
d['lastbeat'] = self.lastbeat
|
d["deltastatetime"] = time.strftime("%M:%S mins", time.gmtime(delta))
|
||||||
if self.state == Connection.overdue:
|
# d['deltastatetime'] = "%0.1f mins" % (delta / 60.)
|
||||||
d['state'] = "<b>%s</b>" % self.state
|
else:
|
||||||
else:
|
# d['deltastatetime'] = time.strftime("%S", time.gmtime(delta))
|
||||||
d['state'] = self.state
|
d["deltastatetime"] = "%i secs" % (delta)
|
||||||
if self.state == Connection.up:
|
|
||||||
d['rttstate'] = d['rtt']
|
|
||||||
elif self.state == Connection.overdue:
|
|
||||||
d['rttstate'] = ''
|
|
||||||
else:
|
|
||||||
d['rttstate'] = d['state']
|
|
||||||
d['statetime'] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.statetime))
|
|
||||||
delta = now - self.statetime
|
|
||||||
|
|
||||||
if self.state == Connection.unknown:
|
else:
|
||||||
d['deltastatetime'] = ''
|
d["addr"] = ""
|
||||||
elif delta > 86400:
|
d["rtt"] = ""
|
||||||
# d['deltastatetime'] = time.strftime("%d %H:%M:%S", time.gmtime(delta))
|
d["lastbeat"] = ""
|
||||||
d['deltastatetime'] = "%0.1f days" % (delta / 86400.)
|
d["state"] = ""
|
||||||
elif delta > 3600:
|
d["statetime"] = ""
|
||||||
# d['deltastatetime'] = time.strftime("%H:%M:%S", time.gmtime(delta))
|
d["deltastatetime"] = ""
|
||||||
d['deltastatetime'] = time.strftime("%k:%M hrs", time.gmtime(delta))
|
d["rttstate"] = ""
|
||||||
# d['deltastatetime'] = "%0.1f hrs" % (delta / 3600.)
|
return d
|
||||||
elif delta > 60:
|
|
||||||
# d['deltastatetime'] = time.strftime("%M:%S", time.gmtime(delta))
|
|
||||||
d['deltastatetime'] = time.strftime("%M:%S mins", time.gmtime(delta))
|
|
||||||
# d['deltastatetime'] = "%0.1f mins" % (delta / 60.)
|
|
||||||
else:
|
|
||||||
# d['deltastatetime'] = time.strftime("%S", time.gmtime(delta))
|
|
||||||
d['deltastatetime'] = "%i secs" % (delta)
|
|
||||||
|
|
||||||
else:
|
def headerdict(self, afam):
|
||||||
d['addr'] = ''
|
d = {}
|
||||||
d['rtt'] = ""
|
d["addr"] = "%s Addr" % afam
|
||||||
d['lastbeat'] = ''
|
d["rtt"] = "Latencey"
|
||||||
d['state'] = ''
|
d["lastbeat"] = "Last Contact"
|
||||||
d['statetime'] = ''
|
d["state"] = "State"
|
||||||
d['deltastatetime'] = ''
|
d["statetime"] = "Last State"
|
||||||
d['rttstate'] = ''
|
d["rttstate"] = "Reach"
|
||||||
return d
|
d["deltastatetime"] = "Last State"
|
||||||
|
return d
|
||||||
|
|
||||||
|
def jsons(self):
|
||||||
|
return json.dumps(self.__dict__)
|
||||||
|
|
||||||
def headerdict(self, afam):
|
# set new state, return number of secs in previous state
|
||||||
d = {}
|
def newstate(self, state, now, when=0):
|
||||||
d['addr'] = '%s Addr' % afam
|
self.state = state
|
||||||
d['rtt'] = 'Latencey'
|
delta = now - when
|
||||||
d['lastbeat'] = 'Last Contact'
|
s = delta - self.statetime
|
||||||
d['state'] = 'State'
|
self.statetime = delta
|
||||||
d['statetime'] = 'Last State'
|
return s
|
||||||
d['rttstate'] = 'Reach'
|
|
||||||
d['deltastatetime'] = 'Last State'
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
def getstate(self):
|
||||||
|
return self.state
|
||||||
|
|
||||||
def jsons(self):
|
def newaddr(self, addr, rtt, now):
|
||||||
return(json.dumps(self.__dict__))
|
self.lastbeat = now
|
||||||
|
self.rtts.append(rtt)
|
||||||
|
if len(self.rtts) > MAXRTTS:
|
||||||
|
del self.rtts[0]
|
||||||
|
|
||||||
|
if self.addr == addr:
|
||||||
# set new state, return number of secs in previous state
|
r = None
|
||||||
def newstate(self, state, now, when=0):
|
else:
|
||||||
self.state = state
|
r = "changed from %s to %s" % (self.addr, addr)
|
||||||
delta = now - when
|
try:
|
||||||
s = delta - self.statetime
|
del Connection.htab[self.addr]
|
||||||
self.statetime = delta
|
except:
|
||||||
return s
|
pass
|
||||||
|
self.addr = addr
|
||||||
|
Connection.htab[addr] = self.host.name
|
||||||
def getstate(self):
|
if self.host.isDynDns():
|
||||||
return self.state
|
Host.dnsQ.put((self.host.name, self.addr))
|
||||||
|
return r
|
||||||
|
|
||||||
def newaddr(self, addr, rtt, now):
|
|
||||||
self.lastbeat = now
|
|
||||||
self.rtts.append(rtt)
|
|
||||||
if len(self.rtts) > MAXRTTS:
|
|
||||||
del self.rtts[0]
|
|
||||||
|
|
||||||
if self.addr == addr:
|
|
||||||
r = None
|
|
||||||
else:
|
|
||||||
r = "changed from %s to %s" % (self.addr, addr)
|
|
||||||
try:
|
|
||||||
del Connection.htab[self.addr]
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
self.addr = addr
|
|
||||||
Connection.htab[addr] = self.host.name
|
|
||||||
if self.host.isDynDns():
|
|
||||||
Host.dnsQ.put((self.host.name, self.addr))
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
class Host:
|
class Host:
|
||||||
# Table of Hosts
|
# Table of Hosts
|
||||||
hosts = {}
|
hosts = {}
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
global num
|
global num
|
||||||
self.name = name
|
self.name = name
|
||||||
if name:
|
if name:
|
||||||
num += 1
|
num += 1
|
||||||
Host.hosts[name] = self
|
Host.hosts[name] = self
|
||||||
self.num = num
|
self.num = num
|
||||||
self.dyn = False
|
self.dyn = False
|
||||||
self.watched = False
|
self.watched = False
|
||||||
self.upcount = 0
|
self.upcount = 0
|
||||||
self.interval = 0
|
self.interval = 0
|
||||||
self.doesack = -1
|
self.doesack = -1
|
||||||
self.cmds = []
|
self.cmds = []
|
||||||
self.cver = 0
|
self.cver = 0
|
||||||
self.connections = {}
|
self.connections = {}
|
||||||
self.hdwcounts = [[0,0],[0,0],[0,0]]
|
self.hdwcounts = [[0, 0], [0, 0], [0, 0]]
|
||||||
|
|
||||||
|
def statedict(self):
|
||||||
|
d = {}
|
||||||
|
d["name"] = self.name
|
||||||
|
if self.dyn:
|
||||||
|
d["name"] += "*"
|
||||||
|
if self.watched:
|
||||||
|
d["name"] = "<b>%s</b>" % d["name"]
|
||||||
|
d["dyn"] = str(self.dyn)
|
||||||
|
d["ver"] = str(self.cver)
|
||||||
|
d["num"] = self.num
|
||||||
|
for c in ["IPv4", "IPv6"]:
|
||||||
|
if c in self.connections:
|
||||||
|
cs = self.connections[c].statedict()
|
||||||
|
else:
|
||||||
|
cs = ubConnection.statedict(True)
|
||||||
|
for csv in cs:
|
||||||
|
d["%s.%s" % (c, csv)] = cs[csv]
|
||||||
|
|
||||||
def statedict(self):
|
return d
|
||||||
d = {}
|
|
||||||
d['name'] = self.name
|
|
||||||
if self.dyn:
|
|
||||||
d['name'] += "*"
|
|
||||||
if self.watched:
|
|
||||||
d['name'] = "<b>%s</b>" % d['name']
|
|
||||||
d['dyn'] = str(self.dyn)
|
|
||||||
d['ver'] = str(self.cver)
|
|
||||||
d['num'] = self.num
|
|
||||||
for c in ['IPv4', 'IPv6']:
|
|
||||||
if c in self.connections:
|
|
||||||
cs = self.connections[c].statedict()
|
|
||||||
else:
|
|
||||||
cs = ubConnection.statedict(True)
|
|
||||||
for csv in cs:
|
|
||||||
d['%s.%s' % (c, csv) ] = cs[csv]
|
|
||||||
|
|
||||||
return d
|
def headerdict(self):
|
||||||
|
d = {}
|
||||||
|
d["name"] = "Name"
|
||||||
|
d["dyn"] = "Dyn"
|
||||||
|
d["ver"] = "Ver"
|
||||||
|
d["num"] = "??"
|
||||||
|
for c in ["IPv4", "IPv6"]:
|
||||||
|
cs = ubConnection.headerdict(c)
|
||||||
|
for csv in cs:
|
||||||
|
d["%s.%s" % (c, csv)] = cs[csv]
|
||||||
|
return d
|
||||||
|
|
||||||
|
def registerDns(self):
|
||||||
|
for af in self.connections:
|
||||||
|
self.connections[af].registerDns()
|
||||||
|
|
||||||
def headerdict(self):
|
def jsons(self):
|
||||||
d = {}
|
ddict = {}
|
||||||
d['name'] = 'Name'
|
for d in self.__dict__:
|
||||||
d['dyn'] = 'Dyn'
|
if d == "connections":
|
||||||
d['ver'] = 'Ver'
|
cl = []
|
||||||
d['num'] = '??'
|
for c in self.connections:
|
||||||
for c in ['IPv4', 'IPv6']:
|
# dirty ugly hack: fix conn to host backpointer
|
||||||
cs = ubConnection.headerdict(c)
|
cld = copy.deepcopy(self.connections[c].__dict__)
|
||||||
for csv in cs:
|
cld["host"] = cld["host"].name
|
||||||
d['%s.%s' % (c, csv) ] = cs[csv]
|
cl.append(cld)
|
||||||
return d
|
ddict[d] = cl
|
||||||
|
else:
|
||||||
|
ddict[d] = self.__dict__[d]
|
||||||
|
return json.dumps(ddict)
|
||||||
|
|
||||||
|
def setcver(self, cver):
|
||||||
|
self.cver = cver
|
||||||
|
|
||||||
def registerDns(self):
|
def isDynDns(self):
|
||||||
for af in self.connections:
|
return self.dyn
|
||||||
self.connections[af].registerDns()
|
|
||||||
|
|
||||||
|
def isIPv4(self, addr):
|
||||||
|
if type(addr) == type(()):
|
||||||
|
return addr[0].find(".") > 0
|
||||||
|
else:
|
||||||
|
return addr.find(".") > 0
|
||||||
|
|
||||||
def jsons(self):
|
def conndata(self, cid, addr, rtt, now):
|
||||||
ddict = {}
|
if self.isIPv4(addr):
|
||||||
for d in self.__dict__:
|
afam = "IPv4"
|
||||||
if d == 'connections':
|
else:
|
||||||
cl = []
|
afam = "IPv6"
|
||||||
for c in self.connections:
|
|
||||||
# dirty ugly hack: fix conn to host backpointer
|
|
||||||
cld = copy.deepcopy(self.connections[c].__dict__)
|
|
||||||
cld['host'] = cld['host'].name
|
|
||||||
cl.append(cld)
|
|
||||||
ddict[d] = cl
|
|
||||||
else:
|
|
||||||
ddict[d] = self.__dict__[d]
|
|
||||||
return json.dumps(ddict)
|
|
||||||
|
|
||||||
|
if afam not in self.connections:
|
||||||
|
self.connections[afam] = Connection(self, cid, addr, afam)
|
||||||
|
|
||||||
def setcver(self, cver):
|
conn = self.connections[afam]
|
||||||
self.cver = cver
|
res = conn.newaddr(addr, rtt, now)
|
||||||
|
return conn, res
|
||||||
|
|
||||||
|
# called when reloading class from pickle, add new fields here
|
||||||
|
def fixup(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def isDynDns(self):
|
def dispstate(self):
|
||||||
return self.dyn
|
if self.state in ["down", "overdue"]:
|
||||||
|
state = "<b>%s</b>" % self.state
|
||||||
|
elif self.state in ["up", "UP"]:
|
||||||
|
state = ""
|
||||||
|
for x in list(self.connections.keys()):
|
||||||
|
try:
|
||||||
|
state += " %5.1f" % (self.connections[x].rtts[-1])
|
||||||
|
except:
|
||||||
|
state += " %5s" % (self.connections[x].rtts[-1])
|
||||||
|
elif self.state in ["unknown", "UNKNOWN"]:
|
||||||
|
state = ""
|
||||||
|
else:
|
||||||
|
state = "%s" % self.state
|
||||||
|
return state
|
||||||
|
|
||||||
|
def dispstats(self):
|
||||||
|
if self.doesack != -1:
|
||||||
|
if self.upcount > 0:
|
||||||
|
# return "(%0.1f%%) %s %s %s " % ((self.doesack * 100.0) / self.upcount, self.doesack, self.upcount, self.hdwcounts)
|
||||||
|
r = ""
|
||||||
|
for v in range(3):
|
||||||
|
a, u = self.hdwcounts[v]
|
||||||
|
if (self.upcount - u) != 0:
|
||||||
|
vs = "%0.0f" % (
|
||||||
|
100.0 - (((self.doesack - a) * 100.0) / (self.upcount - u))
|
||||||
|
)
|
||||||
|
if vs == "0":
|
||||||
|
vs = ""
|
||||||
|
else:
|
||||||
|
vs = "-"
|
||||||
|
r += '<td align="right">%s</td>' % vs
|
||||||
|
return r
|
||||||
|
else:
|
||||||
|
return "<td>(%s)</td><td></td><td></td>" % (self.doesack)
|
||||||
|
return '<td align="right">N/A</td><td></td<td></td>>'
|
||||||
|
|
||||||
def isIPv4(self, addr):
|
hostfields_long = [
|
||||||
if type(addr) == type(()):
|
"name",
|
||||||
return addr[0].find('.') > 0
|
"IPv4.addr",
|
||||||
else:
|
"IPv4.state",
|
||||||
return addr.find('.') > 0
|
("IPv4.rtt", 'style="text-align: right;"'),
|
||||||
|
("IPv4.statetime", 'style="text-align: right;"'),
|
||||||
|
"IPv6.addr",
|
||||||
|
"IPv6.state",
|
||||||
|
("IPv6.rtt", 'style="text-align: right;"'),
|
||||||
|
("IPv6.statetime", 'style="text-align: right;"'),
|
||||||
|
"ver",
|
||||||
|
]
|
||||||
|
|
||||||
|
hostfields_short = [
|
||||||
|
"name",
|
||||||
|
("IPv4.rttstate", 'style="text-align: right;"'),
|
||||||
|
("IPv4.deltastatetime", 'style="text-align: right;"'),
|
||||||
|
("IPv6.rttstate", 'style="text-align: right;"'),
|
||||||
|
("IPv6.deltastatetime", 'style="text-align: right;"'),
|
||||||
|
]
|
||||||
|
|
||||||
def conndata(self, cid, addr, rtt, now):
|
def gene(self, tag, v, attrib=None):
|
||||||
if self.isIPv4(addr):
|
if attrib:
|
||||||
afam = "IPv4"
|
a = " %s" % attrib
|
||||||
else:
|
else:
|
||||||
afam = "IPv6"
|
a = ""
|
||||||
|
return "<%s%s>%s</%s>" % (tag, a, v, tag)
|
||||||
|
|
||||||
if afam not in self.connections:
|
def htmltable(self, tag, hd, short):
|
||||||
self.connections[afam] = Connection(self, cid, addr, afam)
|
if short:
|
||||||
|
hostfields = Host.hostfields_short
|
||||||
|
else:
|
||||||
|
hostfields = Host.hostfields_long
|
||||||
|
h = []
|
||||||
|
for f in hostfields:
|
||||||
|
if type(f) == type(()):
|
||||||
|
h.append(self.gene(tag, hd[f[0]], f[1]))
|
||||||
|
else:
|
||||||
|
h.append(self.gene(tag, hd[f]))
|
||||||
|
return self.gene("tr", "\n".join(h))
|
||||||
|
|
||||||
conn = self.connections[afam]
|
def buildhosttable(self, short=False):
|
||||||
res = conn.newaddr(addr, rtt, now)
|
if DEBUG > 1:
|
||||||
return conn, res
|
print("DBG buildhosttable: start")
|
||||||
|
res = []
|
||||||
|
res.append('<table id="ntable" class="sortable">')
|
||||||
# called when reloading class from pickle, add new fields here
|
res.append(ubHost.htmltable("th", ubHost.headerdict(), short))
|
||||||
def fixup(self):
|
hosts_sorted = list(Host.hosts.keys())
|
||||||
pass
|
if len(hosts_sorted):
|
||||||
|
hosts_sorted.sort()
|
||||||
|
for h in hosts_sorted:
|
||||||
def dispstate(self):
|
res.append(ubHost.htmltable("td", Host.hosts[h].statedict(), short))
|
||||||
if self.state in ["down", "overdue"]:
|
res.append("</table>")
|
||||||
state = "<b>%s</b>" % self.state
|
if DEBUG > 1:
|
||||||
elif self.state in ["up", "UP"]:
|
print("DBG buildhosttable: %s" % res)
|
||||||
state = ""
|
return res
|
||||||
for x in list(self.connections.keys()):
|
|
||||||
try:
|
|
||||||
state += " %5.1f" % (self.connections[x].rtts[-1])
|
|
||||||
except:
|
|
||||||
state += " %5s" % (self.connections[x].rtts[-1])
|
|
||||||
elif self.state in ["unknown", "UNKNOWN"]:
|
|
||||||
state = ""
|
|
||||||
else:
|
|
||||||
state = "%s" % self.state
|
|
||||||
return state
|
|
||||||
|
|
||||||
|
|
||||||
def dispstats(self):
|
|
||||||
if self.doesack != -1:
|
|
||||||
if self.upcount > 0:
|
|
||||||
# return "(%0.1f%%) %s %s %s " % ((self.doesack * 100.0) / self.upcount, self.doesack, self.upcount, self.hdwcounts)
|
|
||||||
r = ""
|
|
||||||
for v in range(3):
|
|
||||||
a,u = self.hdwcounts[v]
|
|
||||||
if (self.upcount - u) != 0:
|
|
||||||
vs = "%0.0f" % (100.0 - (((self.doesack - a) * 100.0) / (self.upcount - u)))
|
|
||||||
if vs == "0":
|
|
||||||
vs=""
|
|
||||||
else:
|
|
||||||
vs="-"
|
|
||||||
r+= '<td align="right">%s</td>' % vs
|
|
||||||
return r
|
|
||||||
else:
|
|
||||||
return "<td>(%s)</td><td></td><td></td>" % (self.doesack)
|
|
||||||
return '<td align="right">N/A</td><td></td<td></td>>'
|
|
||||||
|
|
||||||
|
|
||||||
hostfields_long = ['name', 'IPv4.addr',
|
|
||||||
'IPv4.state', ('IPv4.rtt','style="text-align: right;"'),
|
|
||||||
('IPv4.statetime','style="text-align: right;"'), 'IPv6.addr',
|
|
||||||
'IPv6.state', ('IPv6.rtt', 'style="text-align: right;"'),
|
|
||||||
('IPv6.statetime','style="text-align: right;"'), 'ver']
|
|
||||||
|
|
||||||
hostfields_short = ['name',
|
|
||||||
('IPv4.rttstate','style="text-align: right;"'),
|
|
||||||
('IPv4.deltastatetime','style="text-align: right;"'),
|
|
||||||
('IPv6.rttstate','style="text-align: right;"'),
|
|
||||||
('IPv6.deltastatetime','style="text-align: right;"')]
|
|
||||||
|
|
||||||
def gene(self, tag, v, attrib=None):
|
|
||||||
if attrib:
|
|
||||||
a=" %s" % attrib
|
|
||||||
else:
|
|
||||||
a=""
|
|
||||||
return "<%s%s>%s</%s>" % (tag, a, v, tag)
|
|
||||||
|
|
||||||
|
|
||||||
def htmltable(self, tag, hd, short):
|
|
||||||
if short:
|
|
||||||
hostfields = Host.hostfields_short
|
|
||||||
else:
|
|
||||||
hostfields = Host.hostfields_long
|
|
||||||
h = []
|
|
||||||
for f in hostfields:
|
|
||||||
if type(f) == type(()):
|
|
||||||
h.append(self.gene(tag, hd[f[0]], f[1]))
|
|
||||||
else:
|
|
||||||
h.append(self.gene(tag, hd[f]))
|
|
||||||
return self.gene("tr", "\n".join(h))
|
|
||||||
|
|
||||||
|
|
||||||
def buildhosttable(self, short=False):
|
|
||||||
if DEBUG > 1:
|
|
||||||
print("DBG buildhosttable: start")
|
|
||||||
res = []
|
|
||||||
res.append('<table id="ntable" class="sortable">')
|
|
||||||
res.append(ubHost.htmltable('th', ubHost.headerdict(), short))
|
|
||||||
hosts_sorted = list(Host.hosts.keys())
|
|
||||||
if len(hosts_sorted):
|
|
||||||
hosts_sorted.sort()
|
|
||||||
for h in hosts_sorted:
|
|
||||||
res.append(ubHost.htmltable('td', Host.hosts[h].statedict(), short))
|
|
||||||
res.append("</table>")
|
|
||||||
if DEBUG > 1:
|
|
||||||
print("DBG buildhosttable: %s" % res)
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def buildmsgtable(self, msgs):
|
|
||||||
res = []
|
|
||||||
le = max(40 - len(Host.hosts), 3)
|
|
||||||
res.append("<h4>Log of Events</h4>")
|
|
||||||
for m in msgs[len(msgs)-le:]:
|
|
||||||
res.append("%s<BR>" % m)
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
def buildmsgtable(self, msgs):
|
||||||
|
res = []
|
||||||
|
le = max(40 - len(Host.hosts), 3)
|
||||||
|
res.append("<h4>Log of Events</h4>")
|
||||||
|
for m in msgs[len(msgs) - le :]:
|
||||||
|
res.append("%s<BR>" % m)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
# create fake "unbound objects", remove in Python 3.0
|
# create fake "unbound objects", remove in Python 3.0
|
||||||
ubHost = Host(None)
|
ubHost = Host(None)
|
||||||
ubConnection = Connection(None, "", "", "")
|
ubConnection = Connection(None, "", "", "")
|
||||||
|
|
||||||
|
|||||||
+50
-20
@@ -65,10 +65,21 @@ if not hasattr(threading, "current_thread"):
|
|||||||
if not hasattr(threading.Thread, "get_name"):
|
if not hasattr(threading.Thread, "get_name"):
|
||||||
threading.Thread.get_name = threading.Thread.getName
|
threading.Thread.get_name = threading.Thread.getName
|
||||||
|
|
||||||
__all__ = ['Error', 'LockError', 'LockTimeout', 'AlreadyLocked',
|
__all__ = [
|
||||||
'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock',
|
"Error",
|
||||||
'LinkFileLock', 'MkdirFileLock', 'SQLiteFileLock',
|
"LockError",
|
||||||
'LockBase', 'locked']
|
"LockTimeout",
|
||||||
|
"AlreadyLocked",
|
||||||
|
"LockFailed",
|
||||||
|
"UnlockError",
|
||||||
|
"NotLocked",
|
||||||
|
"NotMyLock",
|
||||||
|
"LinkFileLock",
|
||||||
|
"MkdirFileLock",
|
||||||
|
"SQLiteFileLock",
|
||||||
|
"LockBase",
|
||||||
|
"locked",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
@@ -80,6 +91,7 @@ class Error(Exception):
|
|||||||
... except Exception:
|
... except Exception:
|
||||||
... pass
|
... pass
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -92,6 +104,7 @@ class LockError(Error):
|
|||||||
... except Error:
|
... except Error:
|
||||||
... pass
|
... pass
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -103,6 +116,7 @@ class LockTimeout(LockError):
|
|||||||
... except LockError:
|
... except LockError:
|
||||||
... pass
|
... pass
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -114,6 +128,7 @@ class AlreadyLocked(LockError):
|
|||||||
... except LockError:
|
... except LockError:
|
||||||
... pass
|
... pass
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -125,6 +140,7 @@ class LockFailed(LockError):
|
|||||||
... except LockError:
|
... except LockError:
|
||||||
... pass
|
... pass
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -137,6 +153,7 @@ class UnlockError(Error):
|
|||||||
... except Error:
|
... except Error:
|
||||||
... pass
|
... pass
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -148,6 +165,7 @@ class NotLocked(UnlockError):
|
|||||||
... except UnlockError:
|
... except UnlockError:
|
||||||
... pass
|
... pass
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -159,6 +177,7 @@ class NotMyLock(UnlockError):
|
|||||||
... except UnlockError:
|
... except UnlockError:
|
||||||
... pass
|
... pass
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -209,6 +228,7 @@ class _SharedBase(object):
|
|||||||
|
|
||||||
class LockBase(_SharedBase):
|
class LockBase(_SharedBase):
|
||||||
"""Base class for platform-specific lock classes."""
|
"""Base class for platform-specific lock classes."""
|
||||||
|
|
||||||
def __init__(self, path, threaded=True, timeout=None):
|
def __init__(self, path, threaded=True, timeout=None):
|
||||||
"""
|
"""
|
||||||
>>> lock = LockBase('somefile')
|
>>> lock = LockBase('somefile')
|
||||||
@@ -223,7 +243,7 @@ class LockBase(_SharedBase):
|
|||||||
# Thread objects in Python 2.4 and earlier do not have ident
|
# Thread objects in Python 2.4 and earlier do not have ident
|
||||||
# attrs. Worm around that.
|
# attrs. Worm around that.
|
||||||
ident = getattr(t, "ident", hash(t))
|
ident = getattr(t, "ident", hash(t))
|
||||||
self.tname = "-%x" % (ident & 0xffffffff)
|
self.tname = "-%x" % (ident & 0xFFFFFFFF)
|
||||||
else:
|
else:
|
||||||
self.tname = ""
|
self.tname = ""
|
||||||
dirname = os.path.dirname(self.lock_file)
|
dirname = os.path.dirname(self.lock_file)
|
||||||
@@ -235,11 +255,10 @@ class LockBase(_SharedBase):
|
|||||||
# and overwriting the already existing lock-file, then one
|
# and overwriting the already existing lock-file, then one
|
||||||
# gets unlocked, deleting both lock-file and unique file,
|
# gets unlocked, deleting both lock-file and unique file,
|
||||||
# finally the last lock errors out upon releasing.
|
# finally the last lock errors out upon releasing.
|
||||||
self.unique_name = os.path.join(dirname,
|
self.unique_name = os.path.join(
|
||||||
"%s%s.%s%s" % (self.hostname,
|
dirname,
|
||||||
self.tname,
|
"%s%s.%s%s" % (self.hostname, self.tname, self.pid, hash(self.path)),
|
||||||
self.pid,
|
)
|
||||||
hash(self.path)))
|
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
|
||||||
def is_locked(self):
|
def is_locked(self):
|
||||||
@@ -261,13 +280,15 @@ class LockBase(_SharedBase):
|
|||||||
raise NotImplemented("implement in subclass")
|
raise NotImplemented("implement in subclass")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s: %r -- %r>" % (self.__class__.__name__, self.unique_name,
|
return "<%s: %r -- %r>" % (self.__class__.__name__, self.unique_name, self.path)
|
||||||
self.path)
|
|
||||||
|
|
||||||
|
|
||||||
def _fl_helper(cls, mod, *args, **kwds):
|
def _fl_helper(cls, mod, *args, **kwds):
|
||||||
warnings.warn("Import from %s module instead of lockfile package" % mod,
|
warnings.warn(
|
||||||
DeprecationWarning, stacklevel=2)
|
"Import from %s module instead of lockfile package" % mod,
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
# This is a bit funky, but it's only for awhile. The way the unit tests
|
# This is a bit funky, but it's only for awhile. The way the unit tests
|
||||||
# are constructed this function winds up as an unbound method, so it
|
# are constructed this function winds up as an unbound method, so it
|
||||||
# actually takes three args, not two. We want to toss out self.
|
# actually takes three args, not two. We want to toss out self.
|
||||||
@@ -286,8 +307,8 @@ def LinkFileLock(*args, **kwds):
|
|||||||
lockfile.linklockfile module.
|
lockfile.linklockfile module.
|
||||||
"""
|
"""
|
||||||
from . import linklockfile
|
from . import linklockfile
|
||||||
return _fl_helper(linklockfile.LinkLockFile, "lockfile.linklockfile",
|
|
||||||
*args, **kwds)
|
return _fl_helper(linklockfile.LinkLockFile, "lockfile.linklockfile", *args, **kwds)
|
||||||
|
|
||||||
|
|
||||||
def MkdirFileLock(*args, **kwds):
|
def MkdirFileLock(*args, **kwds):
|
||||||
@@ -297,8 +318,10 @@ def MkdirFileLock(*args, **kwds):
|
|||||||
lockfile.mkdirlockfile module.
|
lockfile.mkdirlockfile module.
|
||||||
"""
|
"""
|
||||||
from . import mkdirlockfile
|
from . import mkdirlockfile
|
||||||
return _fl_helper(mkdirlockfile.MkdirLockFile, "lockfile.mkdirlockfile",
|
|
||||||
*args, **kwds)
|
return _fl_helper(
|
||||||
|
mkdirlockfile.MkdirLockFile, "lockfile.mkdirlockfile", *args, **kwds
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def SQLiteFileLock(*args, **kwds):
|
def SQLiteFileLock(*args, **kwds):
|
||||||
@@ -308,8 +331,10 @@ def SQLiteFileLock(*args, **kwds):
|
|||||||
lockfile.mkdirlockfile module.
|
lockfile.mkdirlockfile module.
|
||||||
"""
|
"""
|
||||||
from . import sqlitelockfile
|
from . import sqlitelockfile
|
||||||
return _fl_helper(sqlitelockfile.SQLiteLockFile, "lockfile.sqlitelockfile",
|
|
||||||
*args, **kwds)
|
return _fl_helper(
|
||||||
|
sqlitelockfile.SQLiteLockFile, "lockfile.sqlitelockfile", *args, **kwds
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def locked(path, timeout=None):
|
def locked(path, timeout=None):
|
||||||
@@ -324,6 +349,7 @@ def locked(path, timeout=None):
|
|||||||
def myname(...):
|
def myname(...):
|
||||||
...
|
...
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decor(func):
|
def decor(func):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
@@ -333,15 +359,19 @@ def locked(path, timeout=None):
|
|||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
finally:
|
finally:
|
||||||
lock.release()
|
lock.release()
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
return decor
|
return decor
|
||||||
|
|
||||||
|
|
||||||
if hasattr(os, "link"):
|
if hasattr(os, "link"):
|
||||||
from . import linklockfile as _llf
|
from . import linklockfile as _llf
|
||||||
|
|
||||||
LockFile = _llf.LinkLockFile
|
LockFile = _llf.LinkLockFile
|
||||||
else:
|
else:
|
||||||
from . import mkdirlockfile as _mlf
|
from . import mkdirlockfile as _mlf
|
||||||
|
|
||||||
LockFile = _mlf.MkdirLockFile
|
LockFile = _mlf.MkdirLockFile
|
||||||
|
|
||||||
FileLock = LockFile
|
FileLock = LockFile
|
||||||
|
|||||||
+10
-10
@@ -3,8 +3,7 @@ from __future__ import absolute_import
|
|||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout,
|
from . import LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout, AlreadyLocked
|
||||||
AlreadyLocked)
|
|
||||||
|
|
||||||
|
|
||||||
class LinkLockFile(LockBase):
|
class LinkLockFile(LockBase):
|
||||||
@@ -41,12 +40,11 @@ class LinkLockFile(LockBase):
|
|||||||
if timeout is not None and time.time() > end_time:
|
if timeout is not None and time.time() > end_time:
|
||||||
os.unlink(self.unique_name)
|
os.unlink(self.unique_name)
|
||||||
if timeout > 0:
|
if timeout > 0:
|
||||||
raise LockTimeout("Timeout waiting to acquire"
|
raise LockTimeout(
|
||||||
" lock for %s" %
|
"Timeout waiting to acquire" " lock for %s" % self.path
|
||||||
self.path)
|
)
|
||||||
else:
|
else:
|
||||||
raise AlreadyLocked("%s is already locked" %
|
raise AlreadyLocked("%s is already locked" % self.path)
|
||||||
self.path)
|
|
||||||
time.sleep(timeout is not None and timeout / 10 or 0.1)
|
time.sleep(timeout is not None and timeout / 10 or 0.1)
|
||||||
else:
|
else:
|
||||||
# Link creation succeeded. We're good to go.
|
# Link creation succeeded. We're good to go.
|
||||||
@@ -64,9 +62,11 @@ class LinkLockFile(LockBase):
|
|||||||
return os.path.exists(self.lock_file)
|
return os.path.exists(self.lock_file)
|
||||||
|
|
||||||
def i_am_locking(self):
|
def i_am_locking(self):
|
||||||
return (self.is_locked() and
|
return (
|
||||||
os.path.exists(self.unique_name) and
|
self.is_locked()
|
||||||
os.stat(self.unique_name).st_nlink == 2)
|
and os.path.exists(self.unique_name)
|
||||||
|
and os.stat(self.unique_name).st_nlink == 2
|
||||||
|
)
|
||||||
|
|
||||||
def break_lock(self):
|
def break_lock(self):
|
||||||
if os.path.exists(self.lock_file):
|
if os.path.exists(self.lock_file):
|
||||||
|
|||||||
+10
-13
@@ -5,12 +5,12 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import errno
|
import errno
|
||||||
|
|
||||||
from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout,
|
from . import LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout, AlreadyLocked
|
||||||
AlreadyLocked)
|
|
||||||
|
|
||||||
|
|
||||||
class MkdirLockFile(LockBase):
|
class MkdirLockFile(LockBase):
|
||||||
"""Lock file by creating a directory."""
|
"""Lock file by creating a directory."""
|
||||||
|
|
||||||
def __init__(self, path, threaded=True, timeout=None):
|
def __init__(self, path, threaded=True, timeout=None):
|
||||||
"""
|
"""
|
||||||
>>> lock = MkdirLockFile('somefile')
|
>>> lock = MkdirLockFile('somefile')
|
||||||
@@ -19,10 +19,9 @@ class MkdirLockFile(LockBase):
|
|||||||
LockBase.__init__(self, path, threaded, timeout)
|
LockBase.__init__(self, path, threaded, timeout)
|
||||||
# Lock file itself is a directory. Place the unique file name into
|
# Lock file itself is a directory. Place the unique file name into
|
||||||
# it.
|
# it.
|
||||||
self.unique_name = os.path.join(self.lock_file,
|
self.unique_name = os.path.join(
|
||||||
"%s.%s%s" % (self.hostname,
|
self.lock_file, "%s.%s%s" % (self.hostname, self.tname, self.pid)
|
||||||
self.tname,
|
)
|
||||||
self.pid))
|
|
||||||
|
|
||||||
def acquire(self, timeout=None):
|
def acquire(self, timeout=None):
|
||||||
timeout = timeout if timeout is not None else self.timeout
|
timeout = timeout if timeout is not None else self.timeout
|
||||||
@@ -47,13 +46,12 @@ class MkdirLockFile(LockBase):
|
|||||||
return
|
return
|
||||||
if timeout is not None and time.time() > end_time:
|
if timeout is not None and time.time() > end_time:
|
||||||
if timeout > 0:
|
if timeout > 0:
|
||||||
raise LockTimeout("Timeout waiting to acquire"
|
raise LockTimeout(
|
||||||
" lock for %s" %
|
"Timeout waiting to acquire" " lock for %s" % self.path
|
||||||
self.path)
|
)
|
||||||
else:
|
else:
|
||||||
# Someone else has the lock.
|
# Someone else has the lock.
|
||||||
raise AlreadyLocked("%s is already locked" %
|
raise AlreadyLocked("%s is already locked" % self.path)
|
||||||
self.path)
|
|
||||||
time.sleep(wait)
|
time.sleep(wait)
|
||||||
else:
|
else:
|
||||||
# Couldn't create the lock for some other reason
|
# Couldn't create the lock for some other reason
|
||||||
@@ -74,8 +72,7 @@ class MkdirLockFile(LockBase):
|
|||||||
return os.path.exists(self.lock_file)
|
return os.path.exists(self.lock_file)
|
||||||
|
|
||||||
def i_am_locking(self):
|
def i_am_locking(self):
|
||||||
return (self.is_locked() and
|
return self.is_locked() and os.path.exists(self.unique_name)
|
||||||
os.path.exists(self.unique_name))
|
|
||||||
|
|
||||||
def break_lock(self):
|
def break_lock(self):
|
||||||
if os.path.exists(self.lock_file):
|
if os.path.exists(self.lock_file):
|
||||||
|
|||||||
+9
-11
@@ -18,10 +18,9 @@ import errno
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from . import (LockBase, AlreadyLocked, LockFailed, NotLocked, NotMyLock,
|
from . import LockBase, AlreadyLocked, LockFailed, NotLocked, NotMyLock, LockTimeout
|
||||||
LockTimeout)
|
|
||||||
|
|
||||||
|
|
||||||
class PIDLockFile(LockBase):
|
class PIDLockFile(LockBase):
|
||||||
""" Lockfile implemented as a Unix PID file.
|
""" Lockfile implemented as a Unix PID file.
|
||||||
|
|
||||||
@@ -80,12 +79,11 @@ class PIDLockFile(LockBase):
|
|||||||
# The lock creation failed. Maybe sleep a bit.
|
# The lock creation failed. Maybe sleep a bit.
|
||||||
if time.time() > end_time:
|
if time.time() > end_time:
|
||||||
if timeout is not None and timeout > 0:
|
if timeout is not None and timeout > 0:
|
||||||
raise LockTimeout("Timeout waiting to acquire"
|
raise LockTimeout(
|
||||||
" lock for %s" %
|
"Timeout waiting to acquire" " lock for %s" % self.path
|
||||||
self.path)
|
)
|
||||||
else:
|
else:
|
||||||
raise AlreadyLocked("%s is already locked" %
|
raise AlreadyLocked("%s is already locked" % self.path)
|
||||||
self.path)
|
|
||||||
time.sleep(timeout is not None and timeout / 10 or 0.1)
|
time.sleep(timeout is not None and timeout / 10 or 0.1)
|
||||||
else:
|
else:
|
||||||
raise LockFailed("failed to create %s" % self.path)
|
raise LockFailed("failed to create %s" % self.path)
|
||||||
@@ -125,7 +123,7 @@ def read_pid_from_pidfile(pidfile_path):
|
|||||||
"""
|
"""
|
||||||
pid = None
|
pid = None
|
||||||
try:
|
try:
|
||||||
pidfile = open(pidfile_path, 'r')
|
pidfile = open(pidfile_path, "r")
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@@ -156,10 +154,10 @@ def write_pid_to_pidfile(pidfile_path):
|
|||||||
and write it to the named file as a line of text.
|
and write it to the named file as a line of text.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
open_flags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY)
|
open_flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY
|
||||||
open_mode = 0o644
|
open_mode = 0o644
|
||||||
pidfile_fd = os.open(pidfile_path, open_flags, open_mode)
|
pidfile_fd = os.open(pidfile_path, open_flags, open_mode)
|
||||||
pidfile = os.fdopen(pidfile_fd, 'w')
|
pidfile = os.fdopen(pidfile_fd, "w")
|
||||||
|
|
||||||
# According to the FHS 2.3 section on PID files in /var/run:
|
# According to the FHS 2.3 section on PID files in /var/run:
|
||||||
#
|
#
|
||||||
|
|||||||
+46
-40
@@ -27,6 +27,7 @@ class SQLiteLockFile(LockBase):
|
|||||||
|
|
||||||
if SQLiteLockFile.testdb is None:
|
if SQLiteLockFile.testdb is None:
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
_fd, testdb = tempfile.mkstemp()
|
_fd, testdb = tempfile.mkstemp()
|
||||||
os.close(_fd)
|
os.close(_fd)
|
||||||
os.unlink(testdb)
|
os.unlink(testdb)
|
||||||
@@ -34,20 +35,24 @@ class SQLiteLockFile(LockBase):
|
|||||||
SQLiteLockFile.testdb = testdb
|
SQLiteLockFile.testdb = testdb
|
||||||
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
self.connection = sqlite3.connect(SQLiteLockFile.testdb)
|
self.connection = sqlite3.connect(SQLiteLockFile.testdb)
|
||||||
|
|
||||||
c = self.connection.cursor()
|
c = self.connection.cursor()
|
||||||
try:
|
try:
|
||||||
c.execute("create table locks"
|
c.execute(
|
||||||
"("
|
"create table locks"
|
||||||
" lock_file varchar(32),"
|
"("
|
||||||
" unique_name varchar(32)"
|
" lock_file varchar(32),"
|
||||||
")")
|
" unique_name varchar(32)"
|
||||||
|
")"
|
||||||
|
)
|
||||||
except sqlite3.OperationalError:
|
except sqlite3.OperationalError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.connection.commit()
|
self.connection.commit()
|
||||||
import atexit
|
import atexit
|
||||||
|
|
||||||
atexit.register(os.unlink, SQLiteLockFile.testdb)
|
atexit.register(os.unlink, SQLiteLockFile.testdb)
|
||||||
|
|
||||||
def acquire(self, timeout=None):
|
def acquire(self, timeout=None):
|
||||||
@@ -68,32 +73,35 @@ class SQLiteLockFile(LockBase):
|
|||||||
while True:
|
while True:
|
||||||
if not self.is_locked():
|
if not self.is_locked():
|
||||||
# Not locked. Try to lock it.
|
# Not locked. Try to lock it.
|
||||||
cursor.execute("insert into locks"
|
cursor.execute(
|
||||||
" (lock_file, unique_name)"
|
"insert into locks"
|
||||||
" values"
|
" (lock_file, unique_name)"
|
||||||
" (?, ?)",
|
" values"
|
||||||
(self.lock_file, self.unique_name))
|
" (?, ?)",
|
||||||
|
(self.lock_file, self.unique_name),
|
||||||
|
)
|
||||||
self.connection.commit()
|
self.connection.commit()
|
||||||
|
|
||||||
# Check to see if we are the only lock holder.
|
# Check to see if we are the only lock holder.
|
||||||
cursor.execute("select * from locks"
|
cursor.execute(
|
||||||
" where unique_name = ?",
|
"select * from locks" " where unique_name = ?", (self.unique_name,)
|
||||||
(self.unique_name,))
|
)
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
if len(rows) > 1:
|
if len(rows) > 1:
|
||||||
# Nope. Someone else got there. Remove our lock.
|
# Nope. Someone else got there. Remove our lock.
|
||||||
cursor.execute("delete from locks"
|
cursor.execute(
|
||||||
" where unique_name = ?",
|
"delete from locks" " where unique_name = ?",
|
||||||
(self.unique_name,))
|
(self.unique_name,),
|
||||||
|
)
|
||||||
self.connection.commit()
|
self.connection.commit()
|
||||||
else:
|
else:
|
||||||
# Yup. We're done, so go home.
|
# Yup. We're done, so go home.
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
# Check to see if we are the only lock holder.
|
# Check to see if we are the only lock holder.
|
||||||
cursor.execute("select * from locks"
|
cursor.execute(
|
||||||
" where unique_name = ?",
|
"select * from locks" " where unique_name = ?", (self.unique_name,)
|
||||||
(self.unique_name,))
|
)
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
if len(rows) == 1:
|
if len(rows) == 1:
|
||||||
# We're the locker, so go home.
|
# We're the locker, so go home.
|
||||||
@@ -103,9 +111,9 @@ class SQLiteLockFile(LockBase):
|
|||||||
if timeout is not None and time.time() > end_time:
|
if timeout is not None and time.time() > end_time:
|
||||||
if timeout > 0:
|
if timeout > 0:
|
||||||
# No more waiting.
|
# No more waiting.
|
||||||
raise LockTimeout("Timeout waiting to acquire"
|
raise LockTimeout(
|
||||||
" lock for %s" %
|
"Timeout waiting to acquire" " lock for %s" % self.path
|
||||||
self.path)
|
)
|
||||||
else:
|
else:
|
||||||
# Someone else has the lock and we are impatient..
|
# Someone else has the lock and we are impatient..
|
||||||
raise AlreadyLocked("%s is already locked" % self.path)
|
raise AlreadyLocked("%s is already locked" % self.path)
|
||||||
@@ -117,40 +125,38 @@ class SQLiteLockFile(LockBase):
|
|||||||
if not self.is_locked():
|
if not self.is_locked():
|
||||||
raise NotLocked("%s is not locked" % self.path)
|
raise NotLocked("%s is not locked" % self.path)
|
||||||
if not self.i_am_locking():
|
if not self.i_am_locking():
|
||||||
raise NotMyLock("%s is locked, but not by me (by %s)" %
|
raise NotMyLock(
|
||||||
(self.unique_name, self._who_is_locking()))
|
"%s is locked, but not by me (by %s)"
|
||||||
|
% (self.unique_name, self._who_is_locking())
|
||||||
|
)
|
||||||
cursor = self.connection.cursor()
|
cursor = self.connection.cursor()
|
||||||
cursor.execute("delete from locks"
|
cursor.execute(
|
||||||
" where unique_name = ?",
|
"delete from locks" " where unique_name = ?", (self.unique_name,)
|
||||||
(self.unique_name,))
|
)
|
||||||
self.connection.commit()
|
self.connection.commit()
|
||||||
|
|
||||||
def _who_is_locking(self):
|
def _who_is_locking(self):
|
||||||
cursor = self.connection.cursor()
|
cursor = self.connection.cursor()
|
||||||
cursor.execute("select unique_name from locks"
|
cursor.execute(
|
||||||
" where lock_file = ?",
|
"select unique_name from locks" " where lock_file = ?", (self.lock_file,)
|
||||||
(self.lock_file,))
|
)
|
||||||
return cursor.fetchone()[0]
|
return cursor.fetchone()[0]
|
||||||
|
|
||||||
def is_locked(self):
|
def is_locked(self):
|
||||||
cursor = self.connection.cursor()
|
cursor = self.connection.cursor()
|
||||||
cursor.execute("select * from locks"
|
cursor.execute("select * from locks" " where lock_file = ?", (self.lock_file,))
|
||||||
" where lock_file = ?",
|
|
||||||
(self.lock_file,))
|
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
return not not rows
|
return not not rows
|
||||||
|
|
||||||
def i_am_locking(self):
|
def i_am_locking(self):
|
||||||
cursor = self.connection.cursor()
|
cursor = self.connection.cursor()
|
||||||
cursor.execute("select * from locks"
|
cursor.execute(
|
||||||
" where lock_file = ?"
|
"select * from locks" " where lock_file = ?" " and unique_name = ?",
|
||||||
" and unique_name = ?",
|
(self.lock_file, self.unique_name),
|
||||||
(self.lock_file, self.unique_name))
|
)
|
||||||
return not not cursor.fetchall()
|
return not not cursor.fetchall()
|
||||||
|
|
||||||
def break_lock(self):
|
def break_lock(self):
|
||||||
cursor = self.connection.cursor()
|
cursor = self.connection.cursor()
|
||||||
cursor.execute("delete from locks"
|
cursor.execute("delete from locks" " where lock_file = ?", (self.lock_file,))
|
||||||
" where lock_file = ?",
|
|
||||||
(self.lock_file,))
|
|
||||||
self.connection.commit()
|
self.connection.commit()
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ from __future__ import absolute_import
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from . import (LockBase, NotLocked, NotMyLock, LockTimeout,
|
from . import LockBase, NotLocked, NotMyLock, LockTimeout, AlreadyLocked
|
||||||
AlreadyLocked)
|
|
||||||
|
|
||||||
|
|
||||||
class SymlinkLockFile(LockBase):
|
class SymlinkLockFile(LockBase):
|
||||||
@@ -40,12 +39,11 @@ class SymlinkLockFile(LockBase):
|
|||||||
# Otherwise the lock creation failed.
|
# Otherwise the lock creation failed.
|
||||||
if timeout is not None and time.time() > end_time:
|
if timeout is not None and time.time() > end_time:
|
||||||
if timeout > 0:
|
if timeout > 0:
|
||||||
raise LockTimeout("Timeout waiting to acquire"
|
raise LockTimeout(
|
||||||
" lock for %s" %
|
"Timeout waiting to acquire" " lock for %s" % self.path
|
||||||
self.path)
|
)
|
||||||
else:
|
else:
|
||||||
raise AlreadyLocked("%s is already locked" %
|
raise AlreadyLocked("%s is already locked" % self.path)
|
||||||
self.path)
|
|
||||||
time.sleep(timeout / 10 if timeout is not None else 0.1)
|
time.sleep(timeout / 10 if timeout is not None else 0.1)
|
||||||
else:
|
else:
|
||||||
# Link creation succeeded. We're good to go.
|
# Link creation succeeded. We're good to go.
|
||||||
@@ -62,8 +60,10 @@ class SymlinkLockFile(LockBase):
|
|||||||
return os.path.islink(self.lock_file)
|
return os.path.islink(self.lock_file)
|
||||||
|
|
||||||
def i_am_locking(self):
|
def i_am_locking(self):
|
||||||
return (os.path.islink(self.lock_file)
|
return (
|
||||||
and os.readlink(self.lock_file) == self.unique_name)
|
os.path.islink(self.lock_file)
|
||||||
|
and os.readlink(self.lock_file) == self.unique_name
|
||||||
|
)
|
||||||
|
|
||||||
def break_lock(self):
|
def break_lock(self):
|
||||||
if os.path.islink(self.lock_file): # exists && link
|
if os.path.islink(self.lock_file): # exists && link
|
||||||
|
|||||||
Reference in New Issue
Block a user