tuned: move daemon code into submodule, improve forking, signal handling, pidfile handling
This commit is contained in:
parent
6f2c2ca6fd
commit
000e82f258
12 changed files with 226 additions and 267 deletions
49
tuned.py
49
tuned.py
|
|
@ -23,8 +23,17 @@
|
|||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import tuned.application
|
||||
import traceback
|
||||
import tuned.logs
|
||||
import tuned.daemon
|
||||
import tuned.exceptions
|
||||
|
||||
DBUS_BUS = "com.redhat.tuned"
|
||||
DBUS_OBJECT = "/Tuned"
|
||||
DBUS_INTERFACE = "com.redhat.tuned.control"
|
||||
|
||||
def error(message):
|
||||
print >>sys.stderr, message
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Daemon for monitoring and adaptive tuning of system devices.")
|
||||
|
|
@ -35,27 +44,29 @@ if __name__ == "__main__":
|
|||
|
||||
args = parser.parse_args(sys.argv[1:])
|
||||
|
||||
if os.geteuid() != 0:
|
||||
error("Superuser permissions are required to run the daemon.")
|
||||
sys.exit(1)
|
||||
|
||||
log = tuned.logs.get()
|
||||
if (args.debug):
|
||||
if args.debug:
|
||||
log.setLevel("DEBUG")
|
||||
|
||||
if os.geteuid() != 0:
|
||||
try:
|
||||
app = tuned.daemon.Application(args.profile)
|
||||
|
||||
if not args.no_dbus:
|
||||
app.attach_to_dbus(DBUS_BUS, DBUS_OBJECT, DBUS_INTERFACE)
|
||||
|
||||
if args.daemon:
|
||||
log.critical("Superuser permissions are needed.")
|
||||
sys.exit(1)
|
||||
app.daemonize()
|
||||
log.switch_to_file()
|
||||
|
||||
app.run()
|
||||
|
||||
except tuned.exceptions.TunedException as exception:
|
||||
if (args.debug):
|
||||
traceback.print_exc()
|
||||
else:
|
||||
log.warn("Superuser permissions are needed. Most tunings will not work!")
|
||||
|
||||
app = tuned.application.Application(args.profile, not args.no_dbus)
|
||||
|
||||
if args.daemon:
|
||||
log.switch_to_file()
|
||||
if tuned.utils.daemonize(3):
|
||||
log.debug("successfully daemonized")
|
||||
else:
|
||||
log.critical("cannot daemonize")
|
||||
error(str(exception))
|
||||
sys.exit(1)
|
||||
else:
|
||||
tuned.utils.write_pidfile()
|
||||
|
||||
app.run()
|
||||
|
|
|
|||
|
|
@ -1,83 +0,0 @@
|
|||
# Copyright (C) 2008-2012 Red Hat, Inc.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
__all__ = ["Application"]
|
||||
|
||||
import controller
|
||||
import daemon
|
||||
import exports
|
||||
import exports.dbus
|
||||
import monitors
|
||||
import plugins
|
||||
import profiles
|
||||
import signal
|
||||
import storage
|
||||
import units
|
||||
import utils
|
||||
|
||||
DBUS_BUS = "com.redhat.tuned"
|
||||
DBUS_INTERFACE = "com.redhat.tuned.control"
|
||||
DBUS_OBJECT = "/Tuned"
|
||||
|
||||
class Application(object):
|
||||
def __init__(self, profile_name, enable_dbus = True):
|
||||
storage_provider = storage.PickleProvider()
|
||||
storage_factory = storage.Factory(storage_provider)
|
||||
|
||||
unit_factory = units.Factory()
|
||||
device_matcher = units.DeviceMatcher()
|
||||
monitors_repository = monitors.Repository()
|
||||
plugins_repository = plugins.Repository(storage_factory, monitors_repository)
|
||||
unit_manager = units.Manager(plugins_repository, monitors_repository, unit_factory, device_matcher)
|
||||
|
||||
profile_factory = profiles.Factory()
|
||||
profile_merger = profiles.Merger()
|
||||
profile_locator = profiles.Locator(["/usr/lib/tuned", "/etc/tuned"])
|
||||
profile_loader = profiles.Loader(profile_locator, profile_factory, profile_merger)
|
||||
|
||||
self._daemon = daemon.Daemon(unit_manager, profile_loader, profile_name)
|
||||
self._controller = controller.Controller(self._daemon)
|
||||
|
||||
self._dbus_exporter = None
|
||||
if enable_dbus:
|
||||
self._init_dbus()
|
||||
|
||||
self._init_signals()
|
||||
|
||||
def _init_dbus(self):
|
||||
self._dbus_exporter = exports.dbus.DBusExporter(DBUS_BUS, DBUS_INTERFACE, DBUS_OBJECT)
|
||||
exports.register_exporter(self._dbus_exporter)
|
||||
exports.register_object(self._controller)
|
||||
|
||||
def _init_signals(self):
|
||||
utils.handle_signal(signal.SIGHUP, self._controller.reload)
|
||||
utils.handle_signal([signal.SIGINT, signal.SIGTERM], self._controller.terminate)
|
||||
|
||||
@property
|
||||
def daemon(self):
|
||||
return self._daemon
|
||||
|
||||
@property
|
||||
def controller(self):
|
||||
return self._controller
|
||||
|
||||
def run(self):
|
||||
exports.start()
|
||||
result = self._controller.run()
|
||||
exports.stop()
|
||||
|
||||
return result
|
||||
3
tuned/daemon/__init__.py
Normal file
3
tuned/daemon/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from application import *
|
||||
from controller import *
|
||||
from daemon import *
|
||||
180
tuned/daemon/application.py
Normal file
180
tuned/daemon/application.py
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
from tuned import storage, units, monitors, plugins, profiles, exports
|
||||
from tuned.exceptions import TunedException
|
||||
import tuned.logs
|
||||
import controller
|
||||
import daemon
|
||||
import signal
|
||||
import os
|
||||
import sys
|
||||
import select
|
||||
|
||||
PID_FILE = "/run/tuned/tuned.pid"
|
||||
DAEMONIZE_PARENT_TIMEOUT = 5
|
||||
|
||||
log = tuned.logs.get()
|
||||
|
||||
__all__ = ["Application"]
|
||||
|
||||
class Application(object):
|
||||
def __init__(self, profile_name=None):
|
||||
storage_provider = storage.PickleProvider()
|
||||
storage_factory = storage.Factory(storage_provider)
|
||||
|
||||
unit_factory = units.Factory()
|
||||
device_matcher = units.DeviceMatcher()
|
||||
monitors_repository = monitors.Repository()
|
||||
plugins_repository = plugins.Repository(storage_factory, monitors_repository)
|
||||
unit_manager = units.Manager(plugins_repository, monitors_repository, unit_factory, device_matcher)
|
||||
|
||||
profile_factory = profiles.Factory()
|
||||
profile_merger = profiles.Merger()
|
||||
profile_locator = profiles.Locator(["/usr/lib/tuned", "/etc/tuned"])
|
||||
profile_loader = profiles.Loader(profile_locator, profile_factory, profile_merger)
|
||||
|
||||
self._daemon = daemon.Daemon(unit_manager, profile_loader, profile_name)
|
||||
self._controller = controller.Controller(self._daemon)
|
||||
|
||||
self._dbus_exporter = None
|
||||
self._init_signals()
|
||||
|
||||
self._pid_file = None
|
||||
|
||||
def _handle_signal(self, signal_number, handler):
|
||||
def handler_wrapper(_signal_number, _frame):
|
||||
if signal_number == _signal_number:
|
||||
handler()
|
||||
signal.signal(signal_number, handler_wrapper)
|
||||
|
||||
def _init_signals(self):
|
||||
self._handle_signal(signal.SIGHUP, self._controller.reload)
|
||||
self._handle_signal(signal.SIGINT, self._controller.terminate)
|
||||
self._handle_signal(signal.SIGTERM, self._controller.terminate)
|
||||
|
||||
def attach_to_dbus(self, bus_name, object_name, interface_name):
|
||||
if self._dbus_exporter is not None:
|
||||
raise TunedException("DBus interface is already initialized.")
|
||||
|
||||
self._dbus_exporter = exports.dbus.DBusExporter(bus_name, interface_name, object_name)
|
||||
exports.register_exporter(self._dbus_exporter)
|
||||
exports.register_object(self._controller)
|
||||
|
||||
def _daemonize_parent(self, parent_in_fd, child_out_fd):
|
||||
"""
|
||||
Wait till the child signalizes that the initialization is complete by writing
|
||||
some uninteresting data into the pipe.
|
||||
"""
|
||||
os.close(child_out_fd)
|
||||
(read_ready, drop, drop) = select.select([parent_in_fd], [], [], DAEMONIZE_PARENT_TIMEOUT)
|
||||
|
||||
if len(read_ready) != 1:
|
||||
os.close(parent_in_fd)
|
||||
raise TunedException("Cannot daemonize, timeout when waiting for the child process.")
|
||||
|
||||
response = os.read(parent_in_fd, 8)
|
||||
os.close(parent_in_fd)
|
||||
|
||||
if len(response) == 0:
|
||||
raise TunedException("Cannot daemonize, no response from child process received.")
|
||||
|
||||
if response != ("%c" % True):
|
||||
raise TunedException("Cannot daemonize, child process reports failure.")
|
||||
|
||||
def _write_pid_file(self):
|
||||
self._pid_file = PID_FILE
|
||||
self._delete_pid_file()
|
||||
try:
|
||||
dir_name = os.path.dirname(self._pid_file)
|
||||
if not os.path.exists(dir_name):
|
||||
os.makedirs(dir_name)
|
||||
|
||||
fd = os.open(self._pid_file, os.O_CREAT|os.O_TRUNC|os.O_WRONLY , 0644)
|
||||
os.write(fd, "%d" % os.getpid())
|
||||
os.close(fd)
|
||||
except (OSError,IOError) as error:
|
||||
log.critical("cannot write the PID to %s: %s" % (self._pid_file, str(error)))
|
||||
|
||||
def _delete_pid_file(self):
|
||||
if os.path.exists(self._pid_file):
|
||||
try:
|
||||
os.unlink(self._pid_file)
|
||||
except OSError as error:
|
||||
log.warning("cannot remove existing PID file %s, %s" % (self._pid_file, str(error)))
|
||||
|
||||
def _daemonize_child(self, parent_in_fd, child_out_fd):
|
||||
"""
|
||||
Finishes daemonizing process, writes a PID file and signalizes to the parent
|
||||
that the initialization is complete.
|
||||
"""
|
||||
os.close(parent_in_fd)
|
||||
|
||||
os.chdir("/")
|
||||
os.setsid()
|
||||
os.umask(0)
|
||||
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
sys.exit(0)
|
||||
except OSError as error:
|
||||
log.critical("cannot daemonize, fork() error: %s" % str(error))
|
||||
os.write(child_out_fd, "%c" % False)
|
||||
os.close(child_out_fd)
|
||||
raise TunedException("Cannot daemonize, second fork() failed.")
|
||||
|
||||
si = file("/dev/null", "r")
|
||||
so = file("/dev/null", "a+")
|
||||
se = file("/dev/null", "a+", 0)
|
||||
os.dup2(si.fileno(), sys.stdin.fileno())
|
||||
os.dup2(so.fileno(), sys.stdout.fileno())
|
||||
os.dup2(se.fileno(), sys.stderr.fileno())
|
||||
|
||||
self._write_pid_file()
|
||||
|
||||
log.debug("successfully daemonized")
|
||||
os.write(child_out_fd, "%c" % True)
|
||||
os.close(child_out_fd)
|
||||
|
||||
def daemonize(self):
|
||||
"""
|
||||
Daemonizes the application. In case of failure, TunedException is raised
|
||||
in the parent process. If the operation is successfull, the main process
|
||||
is terminated and only child process returns from this method.
|
||||
"""
|
||||
parent_child_fds = os.pipe()
|
||||
try:
|
||||
child_pid = os.fork()
|
||||
except OSError as error:
|
||||
os.close(parent_child_fds[0])
|
||||
os.close(parent_child_fds[1])
|
||||
raise TunedException("Cannot daemonize, fork() failed.")
|
||||
|
||||
try:
|
||||
if child_pid > 0:
|
||||
self._daemonize_parent(*parent_child_fds)
|
||||
sys.exit(0)
|
||||
else:
|
||||
self._daemonize_child(*parent_child_fds)
|
||||
except:
|
||||
# pass exceptions only into parent process
|
||||
if child_pid > 0:
|
||||
raise
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
@property
|
||||
def daemon(self):
|
||||
return self._daemon
|
||||
|
||||
@property
|
||||
def controller(self):
|
||||
return self._controller
|
||||
|
||||
def run(self):
|
||||
exports.start()
|
||||
result = self._controller.run()
|
||||
exports.stop()
|
||||
|
||||
if self._pid_file is not None:
|
||||
self._delete_pid_file()
|
||||
|
||||
return result
|
||||
|
|
@ -17,14 +17,14 @@
|
|||
|
||||
__all__ = ["Controller"]
|
||||
|
||||
import exports
|
||||
import logs
|
||||
import threading
|
||||
from tuned import exports
|
||||
import tuned.logs
|
||||
import tuned.exceptions
|
||||
import threading
|
||||
|
||||
log = logs.get()
|
||||
log = tuned.logs.get()
|
||||
|
||||
class Controller(exports.interfaces.ExportableInterface):
|
||||
class Controller(tuned.exports.interfaces.ExportableInterface):
|
||||
"""
|
||||
Controller's purpose is to keep the program running, start/stop the tuning,
|
||||
and export the controller interface (currently only over D-Bus).
|
||||
|
|
@ -17,10 +17,10 @@
|
|||
|
||||
import os
|
||||
import threading
|
||||
import logs
|
||||
import tuned.logs
|
||||
from tuned.exceptions import TunedException
|
||||
|
||||
log = logs.get()
|
||||
log = tuned.logs.get()
|
||||
|
||||
ACTIVE_PROFILE_FILENAME = "/etc/tuned/active_profile"
|
||||
DEFAULT_PROFILE_NAME = "balanced"
|
||||
|
|
@ -1,22 +1,6 @@
|
|||
# Copyright (C) 2008-2011 Red Hat, Inc.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
import controller
|
||||
import interfaces
|
||||
import controller
|
||||
import dbus_exporter as dbus
|
||||
|
||||
def export(*args, **kwargs):
|
||||
"""Decorator, use to mark exportable methods."""
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@
|
|||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import tuned.exports.interfaces
|
||||
import interfaces
|
||||
import decorator
|
||||
import dbus.service
|
||||
import dbus.mainloop.glib
|
||||
|
|
@ -25,9 +23,7 @@ import gobject
|
|||
import inspect
|
||||
import threading
|
||||
|
||||
gobject.threads_init()
|
||||
|
||||
class DBusExporter(tuned.exports.interfaces.ExporterInterface):
|
||||
class DBusExporter(interfaces.ExporterInterface):
|
||||
"""
|
||||
Export method calls through DBus Interface.
|
||||
|
||||
|
|
@ -38,6 +34,8 @@ class DBusExporter(tuned.exports.interfaces.ExporterInterface):
|
|||
"""
|
||||
|
||||
def __init__(self, bus_name, interface_name, object_name):
|
||||
gobject.threads_init()
|
||||
|
||||
self._dbus_object_cls = None
|
||||
self._dbus_object = None
|
||||
self._dbus_methods = {}
|
||||
|
|
@ -1 +0,0 @@
|
|||
# not implemented yet
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
# Copyright (C) 2008-2011 Red Hat, Inc.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
from signals import *
|
||||
from daemon import *
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
__all__ = ["daemonize", "write_pidfile"]
|
||||
|
||||
import tuned.logs
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
PIDFILE = "/run/tuned/tuned.pid"
|
||||
|
||||
log = tuned.logs.get()
|
||||
|
||||
def handle_signal(signals, handler, pass_args = True):
|
||||
for s in signals:
|
||||
signal.signal(s, handler)
|
||||
|
||||
def daemonize(timeout, pidfile = PIDFILE):
|
||||
"""
|
||||
Perform current process daemonization. Kills current SIGALRM, SIGUSR1, and SIGUSR2 signal handlers.
|
||||
"""
|
||||
parent_pid = os.getpid()
|
||||
handle_signal([signal.SIGALRM, signal.SIGUSR1, signal.SIGUSR2], _daemonize_handle_signal, pass_args=True)
|
||||
|
||||
if _daemonize_fork(timeout, pidfile):
|
||||
os.kill(parent_pid, signal.SIGUSR1)
|
||||
result = True
|
||||
else:
|
||||
os.kill(parent_pid, signal.SIGUSR2)
|
||||
result = False
|
||||
|
||||
handle_signal([signal.SIGALRM, signal.SIGUSR1, signal.SIGUSR2], signal.SIG_DFL)
|
||||
return result
|
||||
|
||||
def write_pidfile(pidfile = PIDFILE):
|
||||
try:
|
||||
if not os.path.exists(os.path.dirname(pidfile)):
|
||||
os.makedirs(os.path.dirname(pidfile))
|
||||
|
||||
if os.path.exists(pidfile):
|
||||
os.unlink(pidfile)
|
||||
|
||||
fd = os.open(pidfile, os.O_CREAT|os.O_TRUNC|os.O_WRONLY , 0644)
|
||||
os.write(fd, "%d" % os.getpid())
|
||||
os.close(fd)
|
||||
except (OSError,IOError) as e:
|
||||
log.critical("Cannot write the PID to %s: %s" % (pidfile, str(e)))
|
||||
|
||||
def _daemonize_fork(timeout, pidfile):
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
_daemonize_wait(timeout)
|
||||
assert False
|
||||
except OSError as e:
|
||||
log.critical("fork: %s", str(e))
|
||||
return False
|
||||
|
||||
os.chdir("/")
|
||||
os.setsid()
|
||||
os.umask(0)
|
||||
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
sys.exit(0)
|
||||
except OSError as e:
|
||||
log.cricital("fork: %s", str(e))
|
||||
return False
|
||||
|
||||
si = file('/dev/null', 'r')
|
||||
so = file('/dev/null', 'a+')
|
||||
se = file('/dev/null', 'a+', 0)
|
||||
os.dup2(si.fileno(), sys.stdin.fileno())
|
||||
os.dup2(so.fileno(), sys.stdout.fileno())
|
||||
os.dup2(se.fileno(), sys.stderr.fileno())
|
||||
|
||||
write_pidfile(pidfile)
|
||||
|
||||
return True
|
||||
|
||||
def _daemonize_wait(timeout):
|
||||
signal.alarm(timeout)
|
||||
while True:
|
||||
signal.pause()
|
||||
|
||||
def _daemonize_handle_signal(signum, frame):
|
||||
if signum == signal.SIGUSR1:
|
||||
log.debug("daemonizing, got signal (success), exit")
|
||||
sys.exit(0)
|
||||
if signum == signal.SIGUSR2 or signum == signal.SIGALRM:
|
||||
log.critical("daemonizing, signal %d (failure), exit" % signum)
|
||||
sys.exit(1)
|
||||
else:
|
||||
log.warn("daemonizing, unknown signal %s, ignoring" % signum)
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
__all__ = ["handle_signal"]
|
||||
|
||||
import signal
|
||||
|
||||
def handle_signal(signals, callback, pass_args = False):
|
||||
"""
|
||||
Set up signal handler for a given signal or a list of signals.
|
||||
"""
|
||||
if type(signals) is not list:
|
||||
signals = [signals]
|
||||
for signum in signals:
|
||||
_handle_signal(signum, callback, pass_args)
|
||||
|
||||
def _handle_signal(signum, callback, pass_args):
|
||||
if pass_args or callback in [signal.SIG_DFL, signal.SIG_IGN]:
|
||||
signal.signal(signum, callback)
|
||||
else:
|
||||
def handler_wrapper(_signum, _frame):
|
||||
if signum == _signum:
|
||||
callback()
|
||||
signal.signal(signum, handler_wrapper)
|
||||
Loading…
Reference in a new issue