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