1
0
Fork 0

tuned: move daemon code into submodule, improve forking, signal handling, pidfile handling

This commit is contained in:
Jan Vcelak 2012-11-08 18:20:57 +01:00
parent 6f2c2ca6fd
commit 000e82f258
12 changed files with 226 additions and 267 deletions

View file

@ -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()

View file

@ -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
View file

@ -0,0 +1,3 @@
from application import *
from controller import *
from daemon import *

180
tuned/daemon/application.py Normal file
View 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

View file

@ -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).

View file

@ -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"

View file

@ -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."""

View file

@ -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 = {}

View file

@ -1 +0,0 @@
# not implemented yet

View file

@ -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 *

View file

@ -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)

View file

@ -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)