Skip to content

Commit ef68b40

Browse files
committed
added utest
1 parent 9053667 commit ef68b40

File tree

2 files changed

+180
-32
lines changed

2 files changed

+180
-32
lines changed

nodescraper/plugins/inband/dmesg/dmesg_collector.py

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ class DmesgCollector(InBandDataCollector[DmesgData, None]):
4242

4343
DMESG_CMD = "dmesg --time-format iso -x"
4444

45-
DMESG_LIST_CMD = (
45+
DMESG_LOGS_CMD = (
4646
r"ls -1 /var/log/dmesg /var/log/dmesg.1 /var/log/dmesg.[0-9]*.gz 2>/dev/null || true"
4747
)
4848

4949
def _shell_quote(self, s: str) -> str:
50-
"""POSIX single-quote."""
50+
"""single-quote fix."""
5151
return "'" + s.replace("'", "'\"'\"'") + "'"
5252

5353
def _nice_dmesg_name(self, path: str) -> str:
@@ -58,20 +58,15 @@ def _nice_dmesg_name(self, path: str) -> str:
5858
return "dmesg.1.log"
5959
m = re.search(r"/dmesg\.(\d+)\.gz$", path)
6060
if m:
61-
return f"dmesg.{m.group(1)}.log"
61+
return f"dmesg.{m.group(1)}.gz.log"
6262
base = path.rsplit("/", 1)[-1]
6363
return base.replace(".gz", "") + ".log"
6464

6565
def _collect_dmesg_rotations(self) -> int:
66-
"""
67-
Collect /var/log/dmesg, /var/log/dmesg.1, and /var/log/dmesg.N.gz (decompressed).
68-
Attaches each as a text artifact.
69-
70-
Returns:
71-
list: list of logs collected.
72-
73-
"""
74-
list_res = self._run_sut_cmd(self.DMESG_LIST_CMD)
66+
list_res = self._run_sut_cmd(
67+
r"ls -1 /var/log/dmesg /var/log/dmesg.1 /var/log/dmesg.[0-9]*.gz 2>/dev/null || true",
68+
sudo=True,
69+
)
7570
paths = [p.strip() for p in (list_res.stdout or "").splitlines() if p.strip()]
7671
if not paths:
7772
self._log_event(
@@ -82,43 +77,56 @@ def _collect_dmesg_rotations(self) -> int:
8277
)
8378
return 0
8479

85-
collected, failed = [], []
80+
collected_logs, failed_logs = [], []
8681
for p in paths:
8782
qp = self._shell_quote(p)
8883
if p.endswith(".gz"):
89-
cmd = f"(command -v gzip >/dev/null && gzip -dc {qp}) || (command -v zcat >/dev/null && zcat {qp}) || cat {qp}"
84+
cmd = f"gzip -dc {qp} 2>/dev/null || zcat {qp} 2>/dev/null"
85+
res = self._run_sut_cmd(cmd, sudo=True)
86+
if res.exit_code == 0 and res.stdout is not None:
87+
fname = self._nice_dmesg_name(p)
88+
self.logger.info("Collected dmesg log: %s", fname)
89+
self.result.artifacts.append(FileArtifact(filename=fname, contents=res.stdout))
90+
collected_logs.append(
91+
{"path": p, "as": fname, "bytes": len(res.stdout.encode("utf-8", "ignore"))}
92+
)
93+
else:
94+
failed_logs.append(
95+
{"path": p, "exit_code": res.exit_code, "stderr": res.stderr, "cmd": cmd}
96+
)
9097
else:
9198
cmd = f"cat {qp}"
92-
93-
res = self._run_sut_cmd(cmd)
94-
if res.exit_code == 0 and res.stdout is not None:
95-
fname = self._nice_dmesg_name(p)
96-
self.result.artifacts.append(FileArtifact(filename=fname, contents=res.stdout))
97-
collected.append(
98-
{"path": p, "as": fname, "bytes": len(res.stdout.encode("utf-8", "ignore"))}
99-
)
100-
else:
101-
failed.append({"path": p, "exit_code": res.exit_code, "stderr": res.stderr})
102-
103-
if collected:
99+
res = self._run_sut_cmd(cmd, sudo=True)
100+
if res.exit_code == 0 and res.stdout is not None:
101+
fname = self._nice_dmesg_name(p)
102+
self.logger.info("Collected dmesg log: %s", fname)
103+
self.result.artifacts.append(FileArtifact(filename=fname, contents=res.stdout))
104+
collected_logs.append(
105+
{"path": p, "as": fname, "bytes": len(res.stdout.encode("utf-8", "ignore"))}
106+
)
107+
else:
108+
failed_logs.append(
109+
{"path": p, "exit_code": res.exit_code, "stderr": res.stderr, "cmd": cmd}
110+
)
111+
112+
if collected_logs:
104113
self._log_event(
105114
category=EventCategory.OS,
106115
description="Collected dmesg rotated files",
107-
data={"collected": collected},
116+
data={"collected": collected_logs},
108117
priority=EventPriority.INFO,
109118
)
110-
# self.result.status = self.result.status or ExecutionStatus.OK
111119
self.result.message = self.result.message or "dmesg rotated files collected"
112120

113-
if failed:
121+
if failed_logs:
114122
self._log_event(
115123
category=EventCategory.OS,
116124
description="Some dmesg files could not be collected.",
117-
data={"failed": failed},
125+
data={"failed": failed_logs},
118126
priority=EventPriority.WARNING,
119127
)
120128

121-
return collected
129+
return collected_logs
122130

123131
def _get_dmesg_content(self) -> str:
124132
"""run dmesg command on system and return output

test/unit/plugin/test_dmesg_collector.py

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
# SOFTWARE.
2424
#
2525
###############################################################################
26+
import types
27+
2628
import pytest
2729

2830
from nodescraper.connection.inband.inband import CommandArtifact
@@ -115,7 +117,7 @@ def test_bad_exit_code(conn_mock, system_info):
115117

116118
res, _ = collector.collect_data()
117119
assert res.status == ExecutionStatus.ERROR
118-
assert len(res.events) == 1
120+
assert len(res.events) == 2
119121
assert res.events[0].description == "Error reading dmesg"
120122

121123

@@ -141,3 +143,141 @@ def test_data_model():
141143
assert dmesg_data2.dmesg_content == (
142144
"2023-06-01T01:00:00,685236-05:00 test message1\n2023-06-01T02:30:00,685106-05:00 test message2"
143145
)
146+
147+
148+
class DummyRes:
149+
def __init__(self, command="", stdout="", exit_code=0, stderr=""):
150+
self.command = command
151+
self.stdout = stdout
152+
self.exit_code = exit_code
153+
self.stderr = stderr
154+
155+
156+
def get_collector(monkeypatch, run_map, system_info, conn_mock):
157+
c = DmesgCollector(
158+
system_info=system_info,
159+
system_interaction_level=SystemInteractionLevel.INTERACTIVE,
160+
connection=conn_mock,
161+
)
162+
c.result = types.SimpleNamespace(artifacts=[], message=None) # minimal fields we use
163+
c._events = []
164+
165+
def _log_event(**kw):
166+
c._events.append(kw)
167+
168+
def _run_sut_cmd(cmd, *args, **kwargs):
169+
return run_map(cmd, *args, **kwargs)
170+
171+
monkeypatch.setattr(c, "_log_event", _log_event, raising=True)
172+
monkeypatch.setattr(c, "_run_sut_cmd", _run_sut_cmd, raising=True)
173+
return c
174+
175+
176+
def test_collect_rotations_good_path(monkeypatch, system_info, conn_mock):
177+
ls_out = (
178+
"\n".join(
179+
[
180+
"/var/log/dmesg",
181+
"/var/log/dmesg.1",
182+
"/var/log/dmesg.2.gz",
183+
"/var/log/dmesg.10.gz",
184+
]
185+
)
186+
+ "\n"
187+
)
188+
189+
def run_map(cmd, **kwargs):
190+
if cmd.startswith("ls -1 /var/log/dmesg"):
191+
return DummyRes(command=cmd, stdout=ls_out, exit_code=0)
192+
if cmd.startswith("cat '"):
193+
if "/var/log/dmesg.1'" in cmd:
194+
return DummyRes(command=cmd, stdout="dmesg.1 content\n", exit_code=0)
195+
if "/var/log/dmesg'" in cmd:
196+
return DummyRes(command=cmd, stdout="dmesg content\n", exit_code=0)
197+
if "gzip -dc" in cmd and "/var/log/dmesg.2.gz" in cmd:
198+
return DummyRes(command=cmd, stdout="gz2 content\n", exit_code=0)
199+
if "gzip -dc" in cmd and "/var/log/dmesg.10.gz" in cmd:
200+
return DummyRes(command=cmd, stdout="gz10 content\n", exit_code=0)
201+
return DummyRes(command=cmd, stdout="", exit_code=1, stderr="unexpected")
202+
203+
c = get_collector(monkeypatch, run_map, system_info, conn_mock)
204+
205+
collected = c._collect_dmesg_rotations()
206+
207+
names = {a.filename for a in c.result.artifacts}
208+
assert names == {"dmesg.log", "dmesg.1.log", "dmesg.2.gz.log", "dmesg.10.gz.log"}
209+
210+
assert isinstance(collected, list)
211+
assert len(collected) == 4
212+
213+
descs = [e["description"] for e in c._events]
214+
assert "Collected dmesg rotated files" in descs
215+
216+
217+
def test_collect_rotations_no_files(monkeypatch, system_info, conn_mock):
218+
def run_map(cmd, **kwargs):
219+
if cmd.startswith("ls -1 /var/log/dmesg"):
220+
return DummyRes(command=cmd, stdout="", exit_code=0)
221+
return DummyRes(command=cmd, stdout="", exit_code=1)
222+
223+
c = get_collector(monkeypatch, run_map, system_info, conn_mock)
224+
225+
collected = c._collect_dmesg_rotations()
226+
227+
assert collected == 0
228+
assert c.result.artifacts == []
229+
230+
events = c._events
231+
assert any(
232+
e["description"].startswith("No /var/log/dmesg files found")
233+
and e["priority"].name == "WARNING"
234+
for e in events
235+
)
236+
237+
238+
def test_collect_rotations_gz_failure(monkeypatch, system_info, conn_mock):
239+
ls_out = "/var/log/dmesg.2.gz\n"
240+
241+
def run_map(cmd, **kwargs):
242+
if cmd.startswith("ls -1 /var/log/dmesg"):
243+
return DummyRes(command=cmd, stdout=ls_out, exit_code=0)
244+
if "gzip -dc" in cmd and "/var/log/dmesg.2.gz" in cmd:
245+
return DummyRes(command=cmd, stdout="", exit_code=1, stderr="gzip: not found")
246+
return DummyRes(command=cmd, stdout="", exit_code=1)
247+
248+
c = get_collector(monkeypatch, run_map, system_info, conn_mock)
249+
250+
collected = c._collect_dmesg_rotations()
251+
252+
assert isinstance(collected, list)
253+
assert len(collected) == 0
254+
assert c.result.artifacts == []
255+
256+
fail_events = [
257+
e for e in c._events if e["description"] == "Some dmesg files could not be collected."
258+
]
259+
assert fail_events, "Expected a failure event"
260+
failed = fail_events[-1]["data"]["failed"]
261+
assert any(item["path"].endswith("/var/log/dmesg.2.gz") for item in failed)
262+
263+
264+
def test_collect_data_integration(monkeypatch, system_info, conn_mock):
265+
def run_map(cmd, **kwargs):
266+
if cmd == DmesgCollector.DMESG_CMD:
267+
return DummyRes(command=cmd, stdout="DMESG OUTPUT\n", exit_code=0)
268+
if cmd.startswith("ls -1 /var/log/dmesg"):
269+
return DummyRes(command=cmd, stdout="/var/log/dmesg\n", exit_code=0)
270+
if cmd.startswith("cat '") and "/var/log/dmesg'" in cmd:
271+
return DummyRes(command=cmd, stdout="dmesg file content\n", exit_code=0)
272+
return DummyRes(command=cmd, stdout="", exit_code=1)
273+
274+
c = get_collector(monkeypatch, run_map, system_info, conn_mock)
275+
276+
result, data = c.collect_data()
277+
278+
assert isinstance(data, DmesgData)
279+
assert data.dmesg_content == "DMESG OUTPUT\n"
280+
281+
assert len(c.result.artifacts) == 1
282+
assert c.result.artifacts[0].filename == "dmesg.log"
283+
assert c.result.message == "Dmesg data collected"

0 commit comments

Comments
 (0)