Lot of refactoring in the daemon class and some more cleanups.
This commit is contained in:
parent
ec3a61e019
commit
d815508396
4 changed files with 63 additions and 43 deletions
77
daemon.py
77
daemon.py
|
|
@ -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().
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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
12
uplink
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue