Blob Blame History Raw
From 6db459e2b21a798d93cc79e705e8e02f1bbd24c1 Mon Sep 17 00:00:00 2001
From: Jake Hunsaker <jhunsake@redhat.com>
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 <jhunsake@redhat.com>
Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
---
 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 <jhunsake@redhat.com>
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 <jhunsake@redhat.com>
Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
---
 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 <jhunsake@redhat.com>
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 <jhunsake@redhat.com>
---
 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 <pmoravec@redhat.com>
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 <pmoravec@redhat.com>
---
 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):