관리-도구
편집 파일: plugin_service.py
from . import base import collections import tuned.consts as consts from .decorators import * import os import re import tuned.logs from tuned.utils.commands import commands log = tuned.logs.get() cmd = commands() class Service(): def __init__(self, start = None, enable = None, cfg_file = None, runlevel = None): self.enable = enable self.start = start self.cfg_file = cfg_file self.runlevel = runlevel class InitHandler(): def runlevel_get(self): (retcode, out) = cmd.execute(["runlevel"]) return out.split()[-1] if retcode == 0 else None def daemon_reload(self): cmd.execute(["telinit", "q"]) def cfg_install(self, name, cfg_file): pass def cfg_uninstall(self, name, cfg_file): pass def cfg_verify(self, name, cfg_file): return None # no enable/disable class SysVBasicHandler(InitHandler): def start(self, name): cmd.execute(["service", name, "start"]) def stop(self, name): cmd.execute(["service", name, "stop"]) def enable(self, name, runlevel): raise NotImplementedError() def disable(self, name, runlevel): raise NotImplementedError() def is_running(self, name): (retcode, out) = cmd.execute(["service", name, "status"], no_errors = [0]) return retcode == 0 def is_enabled(self, name, runlevel): raise NotImplementedError() class SysVHandler(SysVBasicHandler): def enable(self, name, runlevel): cmd.execute(["chkconfig", "--level", runlevel, name, "on"]) def disable(self, name, runlevel): cmd.execute(["chkconfig", "--level", runlevel, name, "off"]) def is_enabled(self, name, runlevel): (retcode, out) = cmd.execute(["chkconfig", "--list", name]) return out.split("%s:" % str(runlevel))[1][:2] == "on" if retcode == 0 else None class SysVRCHandler(SysVBasicHandler): def enable(self, name, runlevel): cmd.execute(["sysv-rc-conf", "--level", runlevel, name, "on"]) def disable(self, name, runlevel): cmd.execute(["sysv-rc-conf", "--level", runlevel, name, "off"]) def is_enabled(self, name, runlevel): (retcode, out) = cmd.execute(["sysv-rc-conf", "--list", name]) return out.split("%s:" % str(runlevel))[1][:2] == "on" if retcode == 0 else None class OpenRCHandler(InitHandler): def runlevel_get(self): (retcode, out) = cmd.execute(["rc-status", "-r"]) return out.strip() if retcode == 0 else None def start(self, name): cmd.execute(["rc-service", name, "start"]) def stop(self, name): cmd.execute(["rc-service", name, "stop"]) def enable(self, name, runlevel): cmd.execute(["rc-update", "add", name, runlevel]) def disable(self, name, runlevel): cmd.execute(["rc-update", "del", name, runlevel]) def is_running(self, name): (retcode, out) = cmd.execute(["rc-service", name, "status"], no_errors = [0]) return retcode == 0 def is_enabled(self, name, runlevel): (retcode, out) = cmd.execute(["rc-update", "show", runlevel]) return bool(re.search(r"\b" + re.escape(name) + r"\b", out)) class SystemdHandler(InitHandler): # runlevel not used def runlevel_get(self): return "" def start(self, name): cmd.execute(["systemctl", "restart", name]) def stop(self, name): cmd.execute(["systemctl", "stop", name]) def enable(self, name, runlevel): cmd.execute(["systemctl", "enable", name]) def disable(self, name, runlevel): cmd.execute(["systemctl", "disable", name]) def is_running(self, name): (retcode, out) = cmd.execute(["systemctl", "is-active", name], no_errors = [0]) return retcode == 0 def is_enabled(self, name, runlevel): (retcode, out) = cmd.execute(["systemctl", "is-enabled", name], no_errors = [0]) status = out.strip() return True if status == "enabled" else False if status =="disabled" else None def cfg_install(self, name, cfg_file): log.info("installing service configuration overlay file '%s' for service '%s'" % (cfg_file, name)) if not os.path.exists(cfg_file): log.error("Unable to find service configuration '%s'" % cfg_file) return dirpath = consts.SERVICE_SYSTEMD_CFG_PATH % name try: os.makedirs(dirpath, consts.DEF_SERVICE_CFG_DIR_MODE, exist_ok=True) except OSError as e: log.error("Unable to create directory '%s': %s" % (dirpath, e)) return cmd.copy(cfg_file, dirpath) self.daemon_reload() def cfg_uninstall(self, name, cfg_file): log.info("uninstalling service configuration overlay file '%s' for service '%s'" % (cfg_file, name)) dirpath = consts.SERVICE_SYSTEMD_CFG_PATH % name path = "%s/%s" % (dirpath, os.path.basename(cfg_file)) cmd.unlink(path) self.daemon_reload() # remove the service dir if empty, do not check for errors try: os.rmdir(dirpath) except (FileNotFoundError, OSError): pass def cfg_verify(self, name, cfg_file): if cfg_file is None: return None path = "%s/%s" % (consts.SERVICE_SYSTEMD_CFG_PATH % name, os.path.basename(cfg_file)) if not os.path.exists(cfg_file): log.error("Unable to find service '%s' configuration '%s'" % (name, cfg_file)) return False if not os.path.exists(path): log.error("Service '%s' configuration not installed in '%s'" % (name, path)) return False sha256sum1 = cmd.sha256sum(cfg_file) sha256sum2 = cmd.sha256sum(path) return sha256sum1 == sha256sum2 class ServicePlugin(base.Plugin): """ Plug-in for handling sysvinit, sysv-rc, openrc and systemd services. The syntax is as follows: [subs="quotes"] ---- [service] service.__service_name__=__commands__[,file:__file__] ---- Supported service-handling `_commands_` are `start`, `stop`, `enable` and `disable`. The optional `file:__file__` directive installs an overlay configuration file `__file__`. Multiple commands must be comma (`,`) or semicolon (`;`) separated. If the directives conflict, the last one is used. The service plugin supports configuration overlays only for systemd. In other init systems, this directive is ignored. The configuration overlay files are copied to `/etc/systemd/system/__service_name__.service.d/` directories. Upon profile unloading, the directory is removed if it is empty. With systemd, the `start` command is implemented by `restart` in order to allow loading of the service configuration file overlay. NOTE: With non-systemd init systems, the plug-in operates on the current runlevel only. .Start and enable the `sendmail` service with an overlay file ==== ---- [service] service.sendmail=start,enable,file:${i:PROFILE_DIR}/tuned-sendmail.conf ---- The internal variable `${i:PROFILE_DIR}` points to the directory from which the profile is loaded. ==== """ def __init__(self, *args, **kwargs): super(ServicePlugin, self).__init__(*args, **kwargs) self._has_dynamic_options = True self._init_handler = self._detect_init_system() def _check_cmd(self, command): (retcode, out) = cmd.execute(command, no_errors = [0]) return retcode == 0 def _detect_init_system(self): if self._check_cmd(["systemctl", "status"]): log.debug("detected systemd") return SystemdHandler() elif self._check_cmd(["chkconfig"]): log.debug("detected generic sysvinit") return SysVHandler() elif self._check_cmd(["update-rc.d", "-h"]): log.debug("detected sysv-rc") return SysVRCHandler() elif self._check_cmd(["rc-update", "-h"]): log.debug("detected openrc") return OpenRCHandler() else: raise exceptions.NotSupportedPluginException("Unable to detect your init system, disabling the plugin.") def _parse_service_options(self, name, val): l = re.split(r"\s*[,;]\s*", val) service = Service() for i in l: if i == "enable": service.enable = True elif i == "disable": service.enable = False elif i == "start": service.start = True elif i == "stop": service.start = False elif i[:5] == "file:": service.cfg_file = i[5:] else: log.error("service '%s': invalid service option: '%s'" % (name, i)) return service def _instance_init(self, instance): instance._has_dynamic_tuning = False instance._has_static_tuning = True self._services = collections.OrderedDict([(option[8:], self._parse_service_options(option[8:], self._variables.expand(value))) for option, value in instance.options.items() if option[:8] == "service." and len(option) > 8]) instance._services_original = {} def _instance_cleanup(self, instance): pass def _process_service(self, name, start, enable, runlevel): if start: self._init_handler.start(name) elif start is not None: self._init_handler.stop(name) if enable: self._init_handler.enable(name, runlevel) elif enable is not None: self._init_handler.disable(name, runlevel) def _instance_apply_static(self, instance): runlevel = self._init_handler.runlevel_get() if runlevel is None: log.error("Cannot detect runlevel") return for service in self._services.items(): is_enabled = self._init_handler.is_enabled(service[0], runlevel) is_running = self._init_handler.is_running(service[0]) instance._services_original[service[0]] = Service(is_running, is_enabled, service[1].cfg_file, runlevel) if service[1].cfg_file: self._init_handler.cfg_install(service[0], service[1].cfg_file) self._process_service(service[0], service[1].start, service[1].enable, runlevel) def _instance_verify_static(self, instance, ignore_missing, devices): runlevel = self._init_handler.runlevel_get() if runlevel is None: log.error(consts.STR_VERIFY_PROFILE_FAIL % "cannot detect runlevel") return False ret = True for service in self._services.items(): ret_cfg_verify = self._init_handler.cfg_verify(service[0], service[1].cfg_file) if ret_cfg_verify: log.info(consts.STR_VERIFY_PROFILE_OK % "service '%s' configuration '%s' matches" % (service[0], service[1].cfg_file)) elif ret_cfg_verify is not None: log.error(consts.STR_VERIFY_PROFILE_FAIL % "service '%s' configuration '%s' differs" % (service[0], service[1].cfg_file)) ret = False else: log.info(consts.STR_VERIFY_PROFILE_VALUE_MISSING % "service '%s' configuration '%s'" % (service[0], service[1].cfg_file)) is_enabled = self._init_handler.is_enabled(service[0], runlevel) is_running = self._init_handler.is_running(service[0]) if self._verify_value("%s running" % service[0], service[1].start, is_running, ignore_missing) is False: ret = False if self._verify_value("%s enabled" % service[0], service[1].enable, is_enabled, ignore_missing) is False: ret = False return ret def _instance_unapply_static(self, instance, rollback = consts.ROLLBACK_SOFT): for name, value in list(instance._services_original.items()): if value.cfg_file: self._init_handler.cfg_uninstall(name, value.cfg_file) self._process_service(name, value.start, value.enable, value.runlevel)