From 6db459e2b21a798d93cc79e705e8e02f1bbd24c1 Mon Sep 17 00:00:00 2001 From: Jake Hunsaker Date: Tue, 24 Jul 2018 17:40:25 -0400 Subject: [PATCH] [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 82fef18e5..252de4d05 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 @@ def is_installed(self, package_name): '''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 @@ def check_enabled(self): 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 @@ def check_enabled(self): 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 @@ def check_enabled(self): 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 @@ def check_enabled(self): 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 @@ def have_kmod(kmod): 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 65d8aac63..d6255d3ee 100644 --- a/sos/policies/__init__.py +++ b/sos/policies/__init__.py @@ -13,7 +13,8 @@ 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 5bfbade28..b494de3c4 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) From 7b9284e948f2e9076c92741ed5b95fec7934af8d Mon Sep 17 00:00:00 2001 From: Jake Hunsaker Date: Fri, 15 Feb 2019 16:03:53 -0500 Subject: [PATCH] [policy|plugin] Add 'is_running' check for services Adds a method to the InitSystem class used by policies and plugins to check if a given service name is running. Plugins can make use of this through the new self.service_is_running() method. For policies that use the base InitSystem class, this method will always return True as the service_is_running() method is likely to be used when determining if we should run commands or not, and we do not want to incorrectly stop running those commands where they would collect meaningful output today. The SystemD init system for policies properly checks to see if the given service is active or not when reporting is the service is running. Resolves: #1567 Signed-off-by: Jake Hunsaker Signed-off-by: Bryn M. Reeves --- sos/plugins/__init__.py | 6 +++++- sos/policies/__init__.py | 22 ++++++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/sos/plugins/__init__.py b/sos/plugins/__init__.py index 47b028a85..030e7a305 100644 --- a/sos/plugins/__init__.py +++ b/sos/plugins/__init__.py @@ -215,9 +215,13 @@ def service_is_disabled(self, name): '''Is the service $name disabled?''' return self.policy.init_system.is_disabled(name) + def service_is_running(self, name): + '''Is the service $name currently running?''' + return self.policy.init_system.is_running(name) + def get_service_status(self, name): '''Return the reported status for service $name''' - return self.policy.init_system.get_service_status(name) + return self.policy.init_system.get_service_status(name)['status'] def do_cmd_private_sub(self, cmd): '''Remove certificate and key output archived by sosreport. cmd diff --git a/sos/policies/__init__.py b/sos/policies/__init__.py index d6255d3ee..d0b180152 100644 --- a/sos/policies/__init__.py +++ b/sos/policies/__init__.py @@ -86,6 +86,17 @@ def is_service(self, name): """ return name in self.services + def is_running(self, name): + """Checks if the given service name is in a running state. + + This should be overridden by initsystems that subclass InitSystem + """ + # This is going to be primarily used in gating if service related + # commands are going to be run or not. Default to always returning + # True when an actual init system is not specified by policy so that + # we don't inadvertantly restrict sosreports on those systems + return True + 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 @@ -96,10 +107,9 @@ def load_all_services(self): 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: + try: + return sos_get_command_output("%s %s" % (self.query_cmd, name)) + except Exception: return None return None @@ -156,6 +166,10 @@ def load_all_services(self): except IndexError: pass + def is_running(self, name): + svc = self.get_service_status(name) + return svc['status'] == 'active' + class PackageManager(object): """Encapsulates a package manager. If you provide a query_command to the From 3e736ad53d254aba8795b3d5d8ce0ec4f827ab1c Mon Sep 17 00:00:00 2001 From: Jake Hunsaker Date: Fri, 8 Feb 2019 13:19:56 -0500 Subject: [PATCH] [postgresql] Use postgres 10 scl if installed Updates the plugin to check if the specified SCL is running, as some systems may have multiple SCL versions installed, but only one will be running at a time. We now use the running version for a pgdump. This is primarily aimed at RHV environments as 4.3 and later use version 10. Signed-off-by: Jake Hunsaker --- sos/plugins/postgresql.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/sos/plugins/postgresql.py b/sos/plugins/postgresql.py index aef431f8a..e641c3b44 100644 --- a/sos/plugins/postgresql.py +++ b/sos/plugins/postgresql.py @@ -80,14 +80,25 @@ def setup(self): class RedHatPostgreSQL(PostgreSQL, SCLPlugin): - packages = ('postgresql', 'rh-postgresql95-postgresql-server', ) + packages = ( + 'postgresql', + 'rh-postgresql95-postgresql-server', + 'rh-postgresql10-postgresql-server' + ) def setup(self): super(RedHatPostgreSQL, self).setup() - scl = "rh-postgresql95" pghome = self.get_option("pghome") + scl = None + for pkg in self.packages[1:]: + # The scl name, package name, and service name all differ slightly + # but is at least consistent in doing so across versions, so we + # need to do some mangling here + if self.service_is_running(pkg.replace('-server', '')): + scl = pkg.split('-postgresql-')[0] + # Copy PostgreSQL log files. for filename in find("*.log", pghome): self.add_copy_spec(filename) @@ -111,7 +122,7 @@ def setup(self): ) ) - if scl in self.scls_matched: + if scl and scl in self.scls_matched: self.do_pg_dump(scl=scl, filename="pgdump-scl-%s.tar" % scl) From 0ba743bbf9df335dd47ec45a450e63d72d7ce494 Mon Sep 17 00:00:00 2001 From: Pavel Moravec Date: Wed, 5 Sep 2018 12:34:48 +0200 Subject: [PATCH] [plugins] fix 6db459e for SCL services Calling _files_pkgs_or_cmds_present for SCLs lacks "services" argument that was added in 6db459e commit. Also it is worth renaming the method to more generic _check_plugin_triggers . Resolves: #1416 Signed-off-by: Pavel Moravec --- sos/plugins/__init__.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/sos/plugins/__init__.py b/sos/plugins/__init__.py index 97f3cc592..3abe29db6 100644 --- a/sos/plugins/__init__.py +++ b/sos/plugins/__init__.py @@ -1033,16 +1033,18 @@ def check_enabled(self): files = [f % {"scl_name": scl} for f in self.files] packages = [p % {"scl_name": scl} for p in self.packages] commands = [c % {"scl_name": scl} for c in self.commands] - if self._files_pkgs_or_cmds_present(files, - packages, - commands): + services = [s % {"scl_name": scl} for s in self.services] + if self._check_plugin_triggers(files, + packages, + commands, + services): type(self)._scls_matched.append(scl) return len(type(self)._scls_matched) > 0 - return self._files_pkgs_or_cmds_present(self.files, - self.packages, - self.commands, - self.services) + return self._check_plugin_triggers(self.files, + self.packages, + self.commands, + self.services) if isinstance(self, SCLPlugin): # if files and packages weren't specified, we take all SCLs @@ -1050,7 +1052,7 @@ def check_enabled(self): return True - def _files_pkgs_or_cmds_present(self, files, packages, commands, services): + def _check_plugin_triggers(self, files, packages, commands, services): kernel_mods = self.policy.lsmod() def have_kmod(kmod):