Blame SOURCES/sos-bz1937418-add-cmd-timeout.patch

2ed6e8
From 90b6b709e9f4002376b656b155d00d85382f1828 Mon Sep 17 00:00:00 2001
2ed6e8
From: Pavel Moravec <pmoravec@redhat.com>
2ed6e8
Date: Mon, 29 Mar 2021 16:23:01 +0200
2ed6e8
Subject: [PATCH] [report] add --cmd-timeout option
2ed6e8
2ed6e8
Add --cmd-timeout option to configure command timeout. Plugin-specific
2ed6e8
option of the same name (i.e. -k logs.cmd-timeout=60) can control the
2ed6e8
timeout per plugin.
2ed6e8
2ed6e8
Option defaults and global/plugin-specific option preference follows the
2ed6e8
--plugin-timeout rules.
2ed6e8
2ed6e8
Resolves: #2466
2ed6e8
2ed6e8
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
2ed6e8
Signed-off-by: Jake Hunsaker <jhunsake@redhat.com>
2ed6e8
---
2ed6e8
 man/en/sos-report.1            | 18 +++++++++-
2ed6e8
 sos/collector/__init__.py      |  3 ++
2ed6e8
 sos/collector/sosnode.py       |  5 +++
2ed6e8
 sos/options.py                 |  3 +-
2ed6e8
 sos/report/__init__.py         |  5 ++-
2ed6e8
 sos/report/plugins/__init__.py | 63 ++++++++++++++++++++++++----------
2ed6e8
 6 files changed, 76 insertions(+), 21 deletions(-)
2ed6e8
2ed6e8
diff --git a/man/en/sos-report.1 b/man/en/sos-report.1
2ed6e8
index 81005959..51cf3436 100644
2ed6e8
--- a/man/en/sos-report.1
2ed6e8
+++ b/man/en/sos-report.1
2ed6e8
@@ -17,6 +17,7 @@ sosreport \- Collect and package diagnostic and support data
2ed6e8
           [--label label] [--case-id id]\fR
2ed6e8
           [--threads threads]\fR
2ed6e8
           [--plugin-timeout TIMEOUT]\fR
2ed6e8
+          [--cmd-timeout TIMEOUT]\fR
2ed6e8
           [-s|--sysroot SYSROOT]\fR
2ed6e8
           [-c|--chroot {auto|always|never}\fR
2ed6e8
           [--tmp-dir directory]\fR
2ed6e8
@@ -247,7 +248,7 @@ Specify a timeout in seconds to allow each plugin to run for. A value of 0
2ed6e8
 means no timeout will be set. A value of -1 is used to indicate the default
2ed6e8
 timeout of 300 seconds.
2ed6e8
 
2ed6e8
-Note that this options sets the timeout for all plugins. If you want to set
2ed6e8
+Note that this option sets the timeout for all plugins. If you want to set
2ed6e8
 a timeout for a specific plugin, use the 'timeout' plugin option available to
2ed6e8
 all plugins - e.g. '-k logs.timeout=600'.
2ed6e8
 
2ed6e8
@@ -255,6 +256,21 @@ The plugin-specific timeout option will override this option. For example, using
2ed6e8
 \'--plugin-timeout=60 -k logs.timeout=600\' will set a timeout of 600 seconds for
2ed6e8
 the logs plugin and 60 seconds for all other enabled plugins.
2ed6e8
 .TP
2ed6e8
+.B \--cmd-timeout TIMEOUT
2ed6e8
+Specify a timeout limit in seconds for a command execution. Same defaults logic
2ed6e8
+from --plugin-timeout applies here.
2ed6e8
+
2ed6e8
+This option sets the command timeout for all plugins. If you want to set a cmd
2ed6e8
+timeout for a specific plugin, use the 'cmd-timeout' plugin option available to
2ed6e8
+all plugins - e.g. '-k logs.cmd-timeout=600'.
2ed6e8
+
2ed6e8
+Again, the same plugin/global precedence logic as for --plugin-timeout applies
2ed6e8
+here.
2ed6e8
+
2ed6e8
+Note that setting --cmd-timeout (or -k logs.cmd-timeout) high should be followed
2ed6e8
+by increasing the --plugin-timeout equivalent, otherwise the plugin can easily
2ed6e8
+timeout on slow commands execution.
2ed6e8
+.TP
2ed6e8
 .B \--case-id NUMBER
2ed6e8
 Specify a case identifier to associate with the archive.
2ed6e8
 Identifiers may include alphanumeric characters, commas and periods ('.').
2ed6e8
diff --git a/sos/collector/__init__.py b/sos/collector/__init__.py
2ed6e8
index 406c8f35..1ae73508 100644
2ed6e8
--- a/sos/collector/__init__.py
2ed6e8
+++ b/sos/collector/__init__.py
2ed6e8
@@ -82,6 +82,7 @@ class SoSCollector(SoSComponent):
2ed6e8
         'password_per_node': False,
2ed6e8
         'plugin_options': [],
2ed6e8
         'plugin_timeout': None,
2ed6e8
+        'cmd_timeout': None,
2ed6e8
         'preset': '',
2ed6e8
         'save_group': '',
2ed6e8
         'since': '',
2ed6e8
@@ -276,6 +277,8 @@ class SoSCollector(SoSComponent):
2ed6e8
                              help='Do not collect env vars in sosreports')
2ed6e8
         sos_grp.add_argument('--plugin-timeout', type=int, default=None,
2ed6e8
                              help='Set the global plugin timeout value')
2ed6e8
+        sos_grp.add_argument('--cmd-timeout', type=int, default=None,
2ed6e8
+                             help='Set the global command timeout value')
2ed6e8
         sos_grp.add_argument('--since', default=None,
2ed6e8
                              help=('Escapes archived files older than date. '
2ed6e8
                                    'This will also affect --all-logs. '
2ed6e8
diff --git a/sos/collector/sosnode.py b/sos/collector/sosnode.py
2ed6e8
index a1679655..dbbee12e 100644
2ed6e8
--- a/sos/collector/sosnode.py
2ed6e8
+++ b/sos/collector/sosnode.py
2ed6e8
@@ -664,6 +664,11 @@ class SosNode():
2ed6e8
                     '--skip-files=%s' % (quote(self.opts.skip_files))
2ed6e8
                 )
2ed6e8
 
2ed6e8
+        if self.check_sos_version('4.2'):
2ed6e8
+            if self.opts.cmd_timeout:
2ed6e8
+                sos_opts.append('--cmd-timeout=%s'
2ed6e8
+                                % quote(str(self.opts.cmd_timeout)))
2ed6e8
+
2ed6e8
         sos_cmd = sos_cmd.replace(
2ed6e8
             'sosreport',
2ed6e8
             os.path.join(self.host.sos_bin_path, self.sos_bin)
2ed6e8
diff --git a/sos/options.py b/sos/options.py
2ed6e8
index b82a7d36..1eda55d6 100644
2ed6e8
--- a/sos/options.py
2ed6e8
+++ b/sos/options.py
2ed6e8
@@ -283,7 +283,8 @@ class SoSOptions():
2ed6e8
             if name in ("add_preset", "del_preset", "desc", "note"):
2ed6e8
                 return False
2ed6e8
             # Exception list for options that still need to be reported when 0
2ed6e8
-            if name in ['log_size', 'plugin_timeout'] and value == 0:
2ed6e8
+            if name in ['log_size', 'plugin_timeout', 'cmd_timeout'] \
2ed6e8
+               and value == 0:
2ed6e8
                 return True
2ed6e8
             return has_value(name, value)
2ed6e8
 
2ed6e8
diff --git a/sos/report/__init__.py b/sos/report/__init__.py
2ed6e8
index 25478ba7..945d0fc1 100644
2ed6e8
--- a/sos/report/__init__.py
2ed6e8
+++ b/sos/report/__init__.py
2ed6e8
@@ -107,6 +107,7 @@ class SoSReport(SoSComponent):
2ed6e8
         'only_plugins': [],
2ed6e8
         'preset': 'auto',
2ed6e8
         'plugin_timeout': 300,
2ed6e8
+        'cmd_timeout': 300,
2ed6e8
         'profiles': [],
2ed6e8
         'since': None,
2ed6e8
         'verify': False,
2ed6e8
@@ -266,6 +267,8 @@ class SoSReport(SoSComponent):
2ed6e8
                                 help="A preset identifier", default="auto")
2ed6e8
         report_grp.add_argument("--plugin-timeout", default=None,
2ed6e8
                                 help="set a timeout for all plugins")
2ed6e8
+        report_grp.add_argument("--cmd-timeout", default=None,
2ed6e8
+                                help="set a command timeout for all plugins")
2ed6e8
         report_grp.add_argument("-p", "--profile", "--profiles",
2ed6e8
                                 action="extend", dest="profiles", type=str,
2ed6e8
                                 default=[],
2ed6e8
@@ -709,7 +712,7 @@ class SoSReport(SoSComponent):
2ed6e8
 
2ed6e8
             self.ui_log.info(_("The following plugin options are available:"))
2ed6e8
             for (plug, plugname, optname, optparm) in self.all_options:
2ed6e8
-                if optname in ('timeout', 'postproc'):
2ed6e8
+                if optname in ('timeout', 'postproc', 'cmd-timeout'):
2ed6e8
                     continue
2ed6e8
                 # format option value based on its type (int or bool)
2ed6e8
                 if type(optparm["enabled"]) == bool:
2ed6e8
diff --git a/sos/report/plugins/__init__.py b/sos/report/plugins/__init__.py
2ed6e8
index 02625eb1..779119af 100644
2ed6e8
--- a/sos/report/plugins/__init__.py
2ed6e8
+++ b/sos/report/plugins/__init__.py
2ed6e8
@@ -472,6 +472,9 @@ class Plugin(object):
2ed6e8
     _default_plug_opts = [
2ed6e8
         ('timeout', 'Timeout in seconds for plugin. The default value (-1) ' +
2ed6e8
             'defers to the general plugin timeout, 300 seconds', 'fast', -1),
2ed6e8
+        ('cmd-timeout', 'Timeout in seconds for a command execution. The ' +
2ed6e8
+            'default value (-1) defers to the general cmd timeout, 300 ' +
2ed6e8
+            'seconds', 'fast', -1),
2ed6e8
         ('postproc', 'Enable post-processing collected plugin data', 'fast',
2ed6e8
          True)
2ed6e8
     ]
2ed6e8
@@ -532,16 +535,15 @@ class Plugin(object):
2ed6e8
         self.manifest.add_list('commands', [])
2ed6e8
         self.manifest.add_list('files', [])
2ed6e8
 
2ed6e8
-    @property
2ed6e8
-    def timeout(self):
2ed6e8
-        """Returns either the default plugin timeout value, the value as
2ed6e8
-        provided on the commandline via -k plugin.timeout=value, or the value
2ed6e8
-        of the global --plugin-timeout option.
2ed6e8
+    def timeout_from_options(self, optname, plugoptname, default_timeout):
2ed6e8
+        """Returns either the default [plugin|cmd] timeout value, the value as
2ed6e8
+        provided on the commandline via -k plugin.[|cmd-]timeout=value, or the
2ed6e8
+        value of the global --[plugin|cmd]-timeout option.
2ed6e8
         """
2ed6e8
         _timeout = None
2ed6e8
         try:
2ed6e8
-            opt_timeout = self.get_option('plugin_timeout')
2ed6e8
-            own_timeout = int(self.get_option('timeout'))
2ed6e8
+            opt_timeout = self.get_option(optname)
2ed6e8
+            own_timeout = int(self.get_option(plugoptname))
2ed6e8
             if opt_timeout is None:
2ed6e8
                 _timeout = own_timeout
2ed6e8
             elif opt_timeout is not None and own_timeout == -1:
2ed6e8
@@ -551,10 +553,30 @@ class Plugin(object):
2ed6e8
             else:
2ed6e8
                 return None
2ed6e8
         except ValueError:
2ed6e8
-            return self.plugin_timeout  # Default to known safe value
2ed6e8
+            return default_timeout  # Default to known safe value
2ed6e8
         if _timeout is not None and _timeout > -1:
2ed6e8
             return _timeout
2ed6e8
-        return self.plugin_timeout
2ed6e8
+        return default_timeout
2ed6e8
+
2ed6e8
+    @property
2ed6e8
+    def timeout(self):
2ed6e8
+        """Returns either the default plugin timeout value, the value as
2ed6e8
+        provided on the commandline via -k plugin.timeout=value, or the value
2ed6e8
+        of the global --plugin-timeout option.
2ed6e8
+        """
2ed6e8
+        _timeout = self.timeout_from_options('plugin_timeout', 'timeout',
2ed6e8
+                                             self.plugin_timeout)
2ed6e8
+        return _timeout
2ed6e8
+
2ed6e8
+    @property
2ed6e8
+    def cmdtimeout(self):
2ed6e8
+        """Returns either the default command timeout value, the value as
2ed6e8
+        provided on the commandline via -k plugin.cmd-timeout=value, or the
2ed6e8
+        value of the global --cmd-timeout option.
2ed6e8
+        """
2ed6e8
+        _cmdtimeout = self.timeout_from_options('cmd_timeout', 'cmd-timeout',
2ed6e8
+                                                self.cmd_timeout)
2ed6e8
+        return _cmdtimeout
2ed6e8
 
2ed6e8
     def set_timeout_hit(self):
2ed6e8
         self._timeout_hit = True
2ed6e8
@@ -1235,8 +1257,8 @@ class Plugin(object):
2ed6e8
         """
2ed6e8
 
2ed6e8
         global_options = (
2ed6e8
-            'all_logs', 'allow_system_changes', 'log_size', 'plugin_timeout',
2ed6e8
-            'since', 'verify'
2ed6e8
+            'all_logs', 'allow_system_changes', 'cmd_timeout', 'log_size',
2ed6e8
+            'plugin_timeout', 'since', 'verify'
2ed6e8
         )
2ed6e8
 
2ed6e8
         if optionname in global_options:
2ed6e8
@@ -1505,7 +1527,7 @@ class Plugin(object):
2ed6e8
                     'tags': _spec_tags
2ed6e8
                 })
2ed6e8
 
2ed6e8
-    def add_blockdev_cmd(self, cmds, devices='block', timeout=300,
2ed6e8
+    def add_blockdev_cmd(self, cmds, devices='block', timeout=None,
2ed6e8
                          sizelimit=None, chroot=True, runat=None, env=None,
2ed6e8
                          binary=False, prepend_path=None, whitelist=[],
2ed6e8
                          blacklist=[], tags=[]):
2ed6e8
@@ -1569,7 +1591,7 @@ class Plugin(object):
2ed6e8
                              whitelist=whitelist, blacklist=blacklist,
2ed6e8
                              tags=_dev_tags)
2ed6e8
 
2ed6e8
-    def _add_device_cmd(self, cmds, devices, timeout=300, sizelimit=None,
2ed6e8
+    def _add_device_cmd(self, cmds, devices, timeout=None, sizelimit=None,
2ed6e8
                         chroot=True, runat=None, env=None, binary=False,
2ed6e8
                         prepend_path=None, whitelist=[], blacklist=[],
2ed6e8
                         tags=[]):
2ed6e8
@@ -1627,7 +1649,7 @@ class Plugin(object):
2ed6e8
                                  changes=soscmd.changes)
2ed6e8
 
2ed6e8
     def add_cmd_output(self, cmds, suggest_filename=None,
2ed6e8
-                       root_symlink=None, timeout=cmd_timeout, stderr=True,
2ed6e8
+                       root_symlink=None, timeout=None, stderr=True,
2ed6e8
                        chroot=True, runat=None, env=None, binary=False,
2ed6e8
                        sizelimit=None, pred=None, subdir=None,
2ed6e8
                        changes=False, foreground=False, tags=[]):
2ed6e8
@@ -1849,7 +1871,7 @@ class Plugin(object):
2ed6e8
         self._log_debug("added string ...'%s' as '%s'" % (summary, filename))
2ed6e8
 
2ed6e8
     def _collect_cmd_output(self, cmd, suggest_filename=None,
2ed6e8
-                            root_symlink=False, timeout=cmd_timeout,
2ed6e8
+                            root_symlink=False, timeout=None,
2ed6e8
                             stderr=True, chroot=True, runat=None, env=None,
2ed6e8
                             binary=False, sizelimit=None, subdir=None,
2ed6e8
                             changes=False, foreground=False, tags=[]):
2ed6e8
@@ -1883,6 +1905,8 @@ class Plugin(object):
2ed6e8
         if self._timeout_hit:
2ed6e8
             return
2ed6e8
 
2ed6e8
+        if timeout is None:
2ed6e8
+            timeout = self.cmdtimeout
2ed6e8
         _tags = []
2ed6e8
 
2ed6e8
         if isinstance(tags, str):
2ed6e8
@@ -1975,7 +1999,7 @@ class Plugin(object):
2ed6e8
         return result
2ed6e8
 
2ed6e8
     def collect_cmd_output(self, cmd, suggest_filename=None,
2ed6e8
-                           root_symlink=False, timeout=cmd_timeout,
2ed6e8
+                           root_symlink=False, timeout=None,
2ed6e8
                            stderr=True, chroot=True, runat=None, env=None,
2ed6e8
                            binary=False, sizelimit=None, pred=None,
2ed6e8
                            subdir=None, tags=[]):
2ed6e8
@@ -2044,7 +2068,7 @@ class Plugin(object):
2ed6e8
             tags=tags
2ed6e8
         )
2ed6e8
 
2ed6e8
-    def exec_cmd(self, cmd, timeout=cmd_timeout, stderr=True, chroot=True,
2ed6e8
+    def exec_cmd(self, cmd, timeout=None, stderr=True, chroot=True,
2ed6e8
                  runat=None, env=None, binary=False, pred=None,
2ed6e8
                  foreground=False, container=False, quotecmd=False):
2ed6e8
         """Execute a command right now and return the output and status, but
2ed6e8
@@ -2095,6 +2119,9 @@ class Plugin(object):
2ed6e8
         if not self.test_predicate(cmd=True, pred=pred):
2ed6e8
             return _default
2ed6e8
 
2ed6e8
+        if timeout is None:
2ed6e8
+            timeout = self.cmdtimeout
2ed6e8
+
2ed6e8
         if chroot or self.commons['cmdlineopts'].chroot == 'always':
2ed6e8
             root = self.sysroot
2ed6e8
         else:
2ed6e8
@@ -2331,7 +2358,7 @@ class Plugin(object):
2ed6e8
 
2ed6e8
     def add_journal(self, units=None, boot=None, since=None, until=None,
2ed6e8
                     lines=None, allfields=False, output=None,
2ed6e8
-                    timeout=cmd_timeout, identifier=None, catalog=None,
2ed6e8
+                    timeout=None, identifier=None, catalog=None,
2ed6e8
                     sizelimit=None, pred=None, tags=[]):
2ed6e8
         """Collect journald logs from one of more units.
2ed6e8
 
2ed6e8
-- 
2ed6e8
2.26.3
2ed6e8