Lot of refactoring in the daemon class and some more cleanups.

This commit is contained in:
Johannes Findeisen 2022-08-25 19:48:05 +02:00
commit d815508396
4 changed files with 63 additions and 43 deletions

View file

@ -1,30 +1,36 @@
""" # Copyright (c) 2022 Johannes Findeisen <you@hanez.org>
Generic linux daemon base class for python 3.x. #
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is furnished
# to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice (including the next
# paragraph) shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
# OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Original Source: import atexit
https://web.archive.org/web/20131017130434/http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ import os
https://web.archive.org/web/20131017130434/http://www.jejik.com/files/examples/daemon3x.py import signal
See files/daemon3x.py in the uplink source tree. import sys
import time
More Information:
https://franklingu.github.io/programming/2016/03/01/creating-daemon-process-python-example-explanation/
https://dpbl.wordpress.com/2017/02/12/a-tutorial-on-python-daemon/
"""
from logging import getLogger from logging import getLogger
import sys
import os
import time
import atexit
import signal
logger = getLogger(__name__) logger = getLogger(__name__)
class Daemon: class Daemon:
""" """
A generic daemon class.
Usage: subclass the daemon class and override the run() method. Usage: subclass the daemon class and override the run() method.
""" """
@ -33,34 +39,35 @@ class Daemon:
def daemonize(self): def daemonize(self):
""" """
Daemonize class. UNIX double fork mechanism. Daemonize the class using the UNIX double fork mechanism.
""" """
# Do first fork
try: try:
pid = os.fork() pid = os.fork()
if pid > 0: if pid > 0:
# exit first parent # Exit first parent
sys.exit(0) sys.exit(0)
except OSError as err: except OSError as err:
logger.error('fork #1 failed: {0}\n'.format(err)) logger.error(str("fork #1 failed: {0}".format(err)))
sys.exit(1) sys.exit(1)
# decouple from parent environment # Decouple from parent environment
os.chdir('/') os.chdir('/')
os.setsid() os.setsid()
os.umask(0) os.umask(0)
# do second fork # Do second fork
try: try:
pid = os.fork() pid = os.fork()
if pid > 0: if pid > 0:
# exit from second parent # Exit from second parent
sys.exit(0) sys.exit(0)
except OSError as err: except OSError as err:
logger.error('fork #2 failed: {0}\n'.format(err)) logger.error(str("fork #2 failed: {0}".format(err)))
sys.exit(1) sys.exit(1)
# redirect standard file descriptors # Redirect standard file descriptors
sys.stdout.flush() sys.stdout.flush()
sys.stderr.flush() sys.stderr.flush()
si = open(os.devnull, 'r') si = open(os.devnull, 'r')
@ -71,7 +78,7 @@ class Daemon:
os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno()) os.dup2(se.fileno(), sys.stderr.fileno())
# write pidfile # Write pidfile
atexit.register(self.del_pid) atexit.register(self.del_pid)
pid = str(os.getpid()) pid = str(os.getpid())
@ -94,8 +101,8 @@ class Daemon:
pid = None pid = None
if pid: if pid:
message = "pid_file {0} already exist. Daemon already running?" message = "pid_file {0} already exist. daemon already running?"
logger.error(message.format(self.pid_file)) logger.error(str(message.format(self.pid_file)))
sys.exit(1) sys.exit(1)
# Start the daemon # Start the daemon
@ -107,7 +114,7 @@ class Daemon:
Stop the daemon. Stop the daemon.
""" """
# Get the pid from the pidfile # Get the pid from the pid_file
try: try:
with open(self.pid_file, 'r') as pf: with open(self.pid_file, 'r') as pf:
pid = int(pf.read().strip()) pid = int(pf.read().strip())
@ -115,10 +122,9 @@ class Daemon:
pid = None pid = None
if not pid: if not pid:
message = "pid_file {0} does not exist. Daemon not running?" message = "pid_file {0} does not exist. daemon not running?"
logger.error(message.format(self.pid_file)) logger.error(str(message.format(self.pid_file)))
# not an error in a restart return # not an error in a restart
return
# Try killing the daemon process # Try killing the daemon process
try: try:
@ -143,7 +149,8 @@ class Daemon:
def run(self): def run(self):
""" """
Override this method when you subclass Daemon. You should override this method when you subclass Daemon.
It will be called after the process has been daemonized by It will be called after the process has been daemonized by
start() or restart(). start() or restart().
""" """

View file

@ -1,4 +1,15 @@
"""Generic linux daemon base class for python 3.x.""" """
Generic linux daemon base class for python 3.x.
Original Source:
https://web.archive.org/web/20131017130434/http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
https://web.archive.org/web/20131017130434/http://www.jejik.com/files/examples/daemon3x.py
See files/daemon3x.py in the uplink source tree.
More Information:
https://franklingu.github.io/programming/2016/03/01/creating-daemon-process-python-example-explanation/
https://dpbl.wordpress.com/2017/02/12/a-tutorial-on-python-daemon/
"""
import sys, os, time, atexit, signal import sys, os, time, atexit, signal

12
uplink
View file

@ -31,13 +31,17 @@ import time
from uplink import Uplink from uplink import Uplink
# Version format: MAJOR.FEATURE.FIXES # Version format: MAJOR.FEATURE.FIXES
__version__ = "0.6.0" __version__ = "0.6.1"
# TODO: CHECK ALL ERROR HANDLING!!! # TODO: CHECK ALL ERROR HANDLING!!!
# TODO: IDEA: Implement a small webserver inline to get statistics and graphs over the network? # TODO: IDEA: Implement a small webserver inline to get statistics and graphs over the network?
# Or maybe better as a separate daemon # Or maybe better as a separate daemon?
# TODO: Make use of more args passed to the script # TODO: Make use of more args passed to the script
# TODO: Implement a speedtest that will also run regularly but in an individual interval # TODO: Implement a speedtest that will also run regularly but in an individual interval
# TODO: become more verbose in each log level and log only to error when it really is an error
# else log to info
# TODO: Maybe make path to pid_file an arg to use /var/run/$user/uplink.pid instead of
# /tmp/uplink.pid
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -139,7 +143,7 @@ def main():
config_data = configfile.read() config_data = configfile.read()
config = json.loads(config_data) config = json.loads(config_data)
except Exception as err: except Exception as err:
logger.error(str("uplink: configuration error!\n" + str(err))) logger.error(str("uplink: configuration error! {0}".format(err)))
sys.exit(1) sys.exit(1)
try: try:
@ -175,7 +179,7 @@ def main():
t.start() t.start()
try: try:
time.sleep(config["interval"]) time.sleep(config["interval"])
except KeyboardInterrupt as err: except KeyboardInterrupt:
logger.info(str("uplink: program terminated by user!")) logger.info(str("uplink: program terminated by user!"))
sys.exit(0) sys.exit(0)
else: else:

View file

@ -30,7 +30,6 @@ from fritzconnection.lib.fritzstatus import FritzStatus
from logging import getLogger from logging import getLogger
from threading import Thread from threading import Thread
logger = getLogger(__name__) logger = getLogger(__name__)
@ -38,7 +37,6 @@ class Uplink(Daemon):
def __init__(self, pid_file, config): def __init__(self, pid_file, config):
super().__init__(pid_file) super().__init__(pid_file)
self.pid_file = pid_file
self.config = config self.config = config
self.date = None self.date = None
self.time = None self.time = None
@ -53,7 +51,7 @@ class Uplink(Daemon):
password=config["database_password"], password=config["database_password"],
database=config["database"]) database=config["database"])
except Exception as err: except Exception as err:
logger.error(str("uplink: Database connection failed: " + str(err))) logger.error(str("uplink: Database connection failed: {0}".format(err)))
sys.exit(1) sys.exit(1)
timestamp = calendar.timegm(time.gmtime()) timestamp = calendar.timegm(time.gmtime())