From 6db459e2b21a798d93cc79e705e8e02f1bbd24c1 Mon Sep 17 00:00:00 2001 From: Jake Hunsaker Date: Tue, 24 Jul 2018 17:40:25 -0400 Subject: [Policies|Plugins] Add services member Adds a services member to facilitate plugin enablement. This is tied to a new InitSystem class that gets attached to policies. The InitSystem class is used to determine services that are present on the system and what those service statuses currently are (e.g. enabled/disable). Plugins can now specify a set of services to enable the plugin on if that service exists on the system, similar to the file, command, and package checks. Additionally, the Plugin class now has methods to check on service states, and make decisions based off of. For example: def setup(self): if self.is_service('foobar'): self.add_cmd_output('barfoo') Currently, only systemd has actual functionality for this. The base InitSystem inherited by policies by default will always return False for service checks, thus resulting in the same behavior as before this change. The Red Hat family of distributions has been set to systemd, as all current versions of those distributions use systemd. Closes: #83 Resolves: #1387 Signed-off-by: Jake Hunsaker Signed-off-by: Bryn M. Reeves --- sos/plugins/__init__.py | 31 +++++++++++-- sos/policies/__init__.py | 115 ++++++++++++++++++++++++++++++++++++++++++++++- sos/policies/redhat.py | 1 + 3 files changed, 142 insertions(+), 5 deletions(-) diff --git a/sos/plugins/__init__.py b/sos/plugins/__init__.py index 82fef18e..252de4d0 100644 --- a/sos/plugins/__init__.py +++ b/sos/plugins/__init__.py @@ -123,6 +123,7 @@ class Plugin(object): files = () commands = () kernel_mods = () + services = () archive = None profiles = () sysroot = '/' @@ -202,6 +203,22 @@ class Plugin(object): '''Is the package $package_name installed?''' return self.policy.pkg_by_name(package_name) is not None + def is_service(self, name): + '''Does the service $name exist on the system?''' + return self.policy.init_system.is_service(name) + + def service_is_enabled(self, name): + '''Is the service $name enabled?''' + return self.policy.init_system.is_enabled(name) + + def service_is_disabled(self, name): + '''Is the service $name disabled?''' + return self.policy.init_system.is_disabled(name) + + def get_service_status(self, name): + '''Return the reported status for service $name''' + return self.policy.init_system.get_service_status(name) + def do_cmd_private_sub(self, cmd): '''Remove certificate and key output archived by sosreport. cmd is the command name from which output is collected (i.e. exlcuding @@ -977,7 +994,8 @@ class Plugin(object): overridden. """ # some files or packages have been specified for this package - if any([self.files, self.packages, self.commands, self.kernel_mods]): + if any([self.files, self.packages, self.commands, self.kernel_mods, + self.services]): if isinstance(self.files, six.string_types): self.files = [self.files] @@ -990,6 +1008,9 @@ class Plugin(object): if isinstance(self.kernel_mods, six.string_types): self.kernel_mods = [self.kernel_mods] + if isinstance(self.services, six.string_types): + self.services = [self.services] + if isinstance(self, SCLPlugin): # save SCLs that match files or packages type(self)._scls_matched = [] @@ -1005,7 +1026,8 @@ class Plugin(object): return self._files_pkgs_or_cmds_present(self.files, self.packages, - self.commands) + self.commands, + self.services) if isinstance(self, SCLPlugin): # if files and packages weren't specified, we take all SCLs @@ -1013,7 +1035,7 @@ class Plugin(object): return True - def _files_pkgs_or_cmds_present(self, files, packages, commands): + def _files_pkgs_or_cmds_present(self, files, packages, commands, services): kernel_mods = self.policy.lsmod() def have_kmod(kmod): @@ -1022,7 +1044,8 @@ class Plugin(object): return (any(os.path.exists(fname) for fname in files) or any(self.is_installed(pkg) for pkg in packages) or any(is_executable(cmd) for cmd in commands) or - any(have_kmod(kmod) for kmod in self.kernel_mods)) + any(have_kmod(kmod) for kmod in self.kernel_mods) or + any(self.is_service(svc) for svc in services)) def default_enabled(self): """This decides whether a plugin should be automatically loaded or diff --git a/sos/policies/__init__.py b/sos/policies/__init__.py index 65d8aac6..d6255d3e 100644 --- a/sos/policies/__init__.py +++ b/sos/policies/__init__.py @@ -13,7 +13,8 @@ from os import environ from sos.utilities import (ImporterHelper, import_module, - shell_out) + shell_out, + sos_get_command_output) from sos.plugins import IndependentPlugin, ExperimentalPlugin from sos import _sos as _ from sos import SoSOptions, _arg_names @@ -49,6 +50,113 @@ def load(cache={}, sysroot=None): return cache['policy'] +class InitSystem(object): + """Encapsulates an init system to provide service-oriented functions to + sos. + + This should be used to query the status of services, such as if they are + enabled or disabled on boot, or if the service is currently running. + """ + + def __init__(self, init_cmd=None, list_cmd=None, query_cmd=None): + + self.services = {} + + self.init_cmd = init_cmd + self.list_cmd = "%s %s" % (self.init_cmd, list_cmd) or None + self.query_cmd = "%s %s" % (self.init_cmd, query_cmd) or None + + self.load_all_services() + + def is_enabled(self, name): + """Check if given service name is enabled """ + if self.services and name in self.services: + return self.services[name]['config'] == 'enabled' + return False + + def is_disabled(self, name): + """Check if a given service name is disabled """ + if self.services and name in self.services: + return self.services[name]['config'] == 'disabled' + return False + + def is_service(self, name): + """Checks if the given service name exists on the system at all, this + does not check for the service status + """ + return name in self.services + + def load_all_services(self): + """This loads all services known to the init system into a dict. + The dict should be keyed by the service name, and contain a dict of the + name and service status + """ + pass + + def _query_service(self, name): + """Query an individual service""" + if self.query_cmd: + res = sos_get_command_output("%s %s" % (self.query_cmd, name)) + if res['status'] == 0: + return res + else: + return None + return None + + def parse_query(self, output): + """Parses the output returned by the query command to make a + determination of what the state of the service is + + This should be overriden by anything that subclasses InitSystem + """ + return output + + def get_service_status(self, name): + """Returns the status for the given service name along with the output + of the query command + """ + svc = self._query_service(name) + if svc is not None: + return {'name': name, + 'status': self.parse_query(svc['output']), + 'output': svc['output'] + } + else: + return {'name': name, + 'status': 'missing', + 'output': '' + } + + +class SystemdInit(InitSystem): + + def __init__(self): + super(SystemdInit, self).__init__( + init_cmd='systemctl', + list_cmd='list-unit-files --type=service', + query_cmd='status' + ) + + def parse_query(self, output): + for line in output.splitlines(): + if line.strip().startswith('Active:'): + return line.split()[1] + return 'unknown' + + def load_all_services(self): + svcs = shell_out(self.list_cmd).splitlines() + for line in svcs: + try: + name = line.split('.service')[0] + config = line.split()[1] + self.services[name] = { + 'name': name, + 'config': config + } + except IndexError: + pass + + class PackageManager(object): """Encapsulates a package manager. If you provide a query_command to the constructor it should print each package on the system in the following @@ -676,11 +784,16 @@ class LinuxPolicy(Policy): distro = "Linux" vendor = "None" PATH = "/bin:/sbin:/usr/bin:/usr/sbin" + init = None _preferred_hash_name = None def __init__(self, sysroot=None): super(LinuxPolicy, self).__init__(sysroot=sysroot) + if self.init == 'systemd': + self.init_system = SystemdInit() + else: + self.init_system = InitSystem() def get_preferred_hash_name(self): diff --git a/sos/policies/redhat.py b/sos/policies/redhat.py index 5bfbade2..b494de3c 100644 --- a/sos/policies/redhat.py +++ b/sos/policies/redhat.py @@ -45,6 +45,7 @@ class RedHatPolicy(LinuxPolicy): _host_sysroot = '/' default_scl_prefix = '/opt/rh' name_pattern = 'friendly' + init = 'systemd' def __init__(self, sysroot=None): super(RedHatPolicy, self).__init__(sysroot=sysroot) -- cgit