diff options
author | Mauricio Faria de Oliveira <mfo@canonical.com> | 2020-11-11 20:40:02 +0000 |
---|---|---|
committer | Jake Hunsaker <jhunsake@redhat.com> | 2020-11-17 11:16:27 -0500 |
commit | ddc12aedae473c8af33792533afb736f6f5fff7c (patch) | |
tree | 471434aef16beee8e5dae33be11cd9e36a23e88c | |
parent | 7c88d13ee1780c8f351b9cd26df2401c153ccaad (diff) | |
download | sos-ddc12aedae473c8af33792533afb736f6f5fff7c.tar.gz |
[Plugin] exec_cmd(): pass stderr along to sos_get_command_output()
The sosreport of a system with this issue:
```
# ip netns
Error: Peer netns reference is invalid.
Error: Peer netns reference is invalid.
test-ns
```
Shows this difference between the plugins ebpf and networking (callers
of `ip netns`):
```
# ./bin/sos report -o ebpf,networking --batch
# tar tf /tmp/sosreport-*.tar.xz | grep ip_netns_exec
.../sos_commands/ebpf/ip_netns_exec_test-ns_bpftool_net_list
.../sos_commands/networking/ip_netns_exec_Error_ip6tables-save
.../sos_commands/networking/ip_netns_exec_Error_ip6tables-save.1
...
.../sos_commands/networking/ip_netns_exec_test-ns_ip6tables-save
...
```
Only the networking plugin called `ip netns exec <ns> <cmd>` on `Error:`
lines. Hmm.
...
The networking plugin calls `collect_cmd_output()`, which has parameter
stderr=True` as default:
```
def collect_cmd_output(self, cmd, suggest_filename=None,
...
stderr=True, ...
```
The ebpf plugin calls `exec_cmd()`, BUT it has parameter `stderr=True`
as default AS WELL:
```
def exec_cmd(self, cmd, timeout=cmd_timeout, stderr=True, ...
```
So, why the difference?
...
Well, it turns out `exec_cmd()` does NOT pass the `stderr` parameter
along to `sos_get_command_output()`: [but `collect_cmd_output()` does.]
```
return sos_get_command_output(cmd, timeout=timeout, chroot=root,
chdir=runat, binary=binary, env=env,
foreground=foreground)
```
And `sos_get_command_output()` has `stderr=False` as default:
```
def sos_get_command_output(command, timeout=300, stderr=False, ...
```
Thus, despite `exec_cmd()` has `stderr=True` as default it is _ignored_,
as its callee `sos_get_command_output()` has `stderr=False` as default.
This explains the output difference between the ebpf/networking plugins.
...
Looking back for details, the origin of the change is in PR#1807 [1],
with commit e8bb94c (refactor command functions/introduce `exec_cmd()`)
and commit e51d3e6 (update caller plugins).
The PR has no mentions/discussion around `stderr` in comments/review,
so it's apparently an oversight during the refactor, I guess.
Because, previously the refactored callers of `sos_get_command_output()`
(`get_command_output()`, `call_ext_prog()`, `[_]get_cmd_output_now()`)
all had `stderr=True` as parameter/default, passing it along to callee.
(The only exception is in `_collect_cmd_output()` which does the first
call in the chroot with `stderr`, and falls back to non-chroot without
`stderr`, but it was already that way. Thus not changing this case.)
...
So, it seems right and safe and opportune to fix this inconsistency
between default `stderr` in `exec_cmd()` / `sos_get_command_output()`
by just passing it along between them:
Right: because it restores the previous behavior (before refactor)
assumed/accepted by callers that did not specify `stderr=False`.
Safe: because it restores the actual behavior to previous behavior,
and if plugins handled `stderr` lines previously, they should also
handle it now, for the most part (i.e., not any new `stderr` lines,
but they are currently ignoring those, and should get them covered.)
Opportune: because if any plugin breaks _now_ because of this, it is
a new/post-refactor change that decided to use `exec_cmd()` with the
default that is inconsistent with actual behavior, and it seems okay
to take bugs for that, so to identify and fix such cases.
Hopefully those points are reasonable with the project's philosophy.
...
Test-case:
Setup:
```
$ sudo python3
from sos import SoS
from sos.report.plugins import Plugin
sos = SoS(['report'])
plugin = Plugin(sos._component.get_commons())
```
Before:
```
>>> print(plugin.exec_cmd('ip netns')['output'])
test-ns
>>> print(plugin.exec_cmd('ip netns', stderr=True)['output'])
test-ns
>>> print(plugin.exec_cmd('ip netns', stderr=False)['output'])
test-ns
```
After:
```
>>> print(plugin.exec_cmd('ip netns')['output'])
Error: Peer netns reference is invalid.
Error: Peer netns reference is invalid.
test-ns
>>> print(plugin.exec_cmd('ip netns', stderr=True)['output'])
Error: Peer netns reference is invalid.
Error: Peer netns reference is invalid.
test-ns
>>> print(plugin.exec_cmd('ip netns', stderr=False)['output'])
test-ns
```
...
Test-suite:
```
# ./tests/simple.sh
<...>
Summary
failures false time 2 -l
failures false time 3 --list-presets
failures false time 3 --list-profiles
failures false time 17 --batch --build
--no-env-vars
failures false time 3 Size 14.23KiB --batch
--no-report -o hardware
failures false time 20 Size 2.99MiB --batch
--label TEST -a -c never
failures false time 20 Size 2.81MiB --batch
--debug --log-size 0 -c always
failures false time 20 Size 2.84MiB --batch
-z xz --log-size 1
failures false time 19 Size 4.01MiB --batch
-z gzip
failures false time 32 Size 2.76MiB --batch
-t 1 -n hardware
failures false time 19 --batch --quiet
-e opencl -k kernel.with-timer
failures false time 22 Size 4.31MiB --batch
--case-id 10101 --all-logs --since=20201110
failures false time 21 Size 3.06MiB --batch
--verbose --no-postproc
failures false time 39 Size 2.82MiB --batch
--mask
Everything worked!
```
[1] https://github.com/sosreport/sos/pull/1807
Resolves: #2306
Signed-off-by: Mauricio Faria de Oliveira <mfo@canonical.com>
Signed-off-by: Jake Hunsaker <jhunsake@redhat.com>
-rw-r--r-- | sos/report/plugins/__init__.py | 2 |
1 files changed, 1 insertions, 1 deletions
diff --git a/sos/report/plugins/__init__.py b/sos/report/plugins/__init__.py index ed0a07ef..6eeef5c1 100644 --- a/sos/report/plugins/__init__.py +++ b/sos/report/plugins/__init__.py @@ -2099,7 +2099,7 @@ class Plugin(object): return sos_get_command_output(cmd, timeout=timeout, chroot=root, chdir=runat, binary=binary, env=env, - foreground=foreground) + foreground=foreground, stderr=stderr) def _get_container_runtime(self, runtime=None): """Based on policy and request by the plugin, return a usable |