Skip to content

Commit 93a97a0

Browse files
committed
added pytest
1 parent 0e5cd17 commit 93a97a0

File tree

2 files changed

+183
-7
lines changed

2 files changed

+183
-7
lines changed

nodescraper/plugins/inband/syslog/syslog_collector.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,29 +40,39 @@ class SyslogCollector(InBandDataCollector[SyslogData, None]):
4040

4141
DATA_MODEL = SyslogData
4242

43-
DMESG_LOGS_CMD = r"ls -1 /var/log/syslog* 2>/dev/null | grep -E '^/var/log/syslog(\.[0-9]+(\.gz)?)?$' || true"
43+
SYSLOG_CMD = r"ls -1 /var/log/syslog* 2>/dev/null | grep -E '^/var/log/syslog(\.[0-9]+(\.gz)?)?$' || true"
4444

4545
def _shell_quote(self, s: str) -> str:
4646
"""single-quote fix."""
4747
return "'" + s.replace("'", "'\"'\"'") + "'"
4848

4949
def _nice_syslog_name(self, path: str) -> str:
50-
"""Map path to filename."""
50+
"""Map path to filename
51+
Args:
52+
path (str): file path
53+
Returns:
54+
str: new local filename
55+
"""
56+
prefix = "rotated_"
5157
base = path.rstrip("/").rsplit("/", 1)[-1]
58+
5259
if base == "syslog":
53-
return "syslog_log.log"
60+
return f"{prefix}syslog.log"
61+
5462
m = re.fullmatch(r"syslog\.(\d+)\.gz", base)
5563
if m:
56-
return f"syslog.{m.group(1)}.gz.log"
64+
return f"{prefix}syslog.{m.group(1)}.gz.log"
65+
5766
m = re.fullmatch(r"syslog\.(\d+)", base)
5867
if m:
59-
return f"syslog.{m.group(1)}.log"
68+
return f"{prefix}syslog.{m.group(1)}.log"
6069

61-
return (base[:-3] if base.endswith(".gz") else base) + ".log"
70+
middle = base[:-3] if base.endswith(".gz") else base
71+
return f"{prefix}{middle}.log"
6272

6373
def _collect_syslog_rotations(self) -> int:
6474
ret = 0
65-
list_res = self._run_sut_cmd(self.DMESG_LOGS_CMD, sudo=True)
75+
list_res = self._run_sut_cmd(self.SYSLOG_CMD, sudo=True)
6676
paths = [p.strip() for p in (list_res.stdout or "").splitlines() if p.strip()]
6777
if not paths:
6878
self._log_event(
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
###############################################################################
2+
#
3+
# MIT License
4+
#
5+
# Copyright (c) 2025 Advanced Micro Devices, Inc.
6+
#
7+
# Permission is hereby granted, free of charge, to any person obtaining a copy
8+
# of this software and associated documentation files (the "Software"), to deal
9+
# in the Software without restriction, including without limitation the rights
10+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
# copies of the Software, and to permit persons to whom the Software is
12+
# furnished to do so, subject to the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be included in all
15+
# copies or substantial portions of the Software.
16+
#
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
# SOFTWARE.
24+
#
25+
###############################################################################
26+
import types
27+
28+
from nodescraper.enums.systeminteraction import SystemInteractionLevel
29+
from nodescraper.plugins.inband.syslog.syslog_collector import SyslogCollector
30+
from nodescraper.plugins.inband.syslog.syslogdata import SyslogData
31+
32+
33+
class DummyRes:
34+
def __init__(self, command="", stdout="", exit_code=0, stderr=""):
35+
self.command = command
36+
self.stdout = stdout
37+
self.exit_code = exit_code
38+
self.stderr = stderr
39+
40+
41+
def get_collector(monkeypatch, run_map, system_info, conn_mock):
42+
c = SyslogCollector(
43+
system_info=system_info,
44+
system_interaction_level=SystemInteractionLevel.INTERACTIVE,
45+
connection=conn_mock,
46+
)
47+
c.result = types.SimpleNamespace(artifacts=[], message=None)
48+
c._events = []
49+
50+
def _log_event(**kw):
51+
c._events.append(kw)
52+
53+
def _run_sut_cmd(cmd, *args, **kwargs):
54+
return run_map(cmd, *args, **kwargs)
55+
56+
monkeypatch.setattr(c, "_log_event", _log_event, raising=True)
57+
monkeypatch.setattr(c, "_run_sut_cmd", _run_sut_cmd, raising=True)
58+
return c
59+
60+
61+
def test_collect_rotations_good_path(monkeypatch, system_info, conn_mock):
62+
ls_out = (
63+
"\n".join(
64+
[
65+
"/var/log/syslog",
66+
"/var/log/syslog.1",
67+
"/var/log/syslog.2.gz",
68+
"/var/log/syslog.10.gz",
69+
]
70+
)
71+
+ "\n"
72+
)
73+
74+
def run_map(cmd, **kwargs):
75+
if cmd.startswith("ls -1 /var/log/syslog"):
76+
return DummyRes(command=cmd, stdout=ls_out, exit_code=0)
77+
78+
if cmd.startswith("cat "):
79+
if "/var/log/syslog.1" in cmd:
80+
return DummyRes(command=cmd, stdout="syslog.1 content\n", exit_code=0)
81+
if "/var/log/syslog" in cmd:
82+
return DummyRes(command=cmd, stdout="syslog content\n", exit_code=0)
83+
84+
if "gzip -dc" in cmd and "/var/log/syslog.2.gz" in cmd:
85+
return DummyRes(command=cmd, stdout="gz2 content\n", exit_code=0)
86+
if "gzip -dc" in cmd and "/var/log/syslog.10.gz" in cmd:
87+
return DummyRes(command=cmd, stdout="gz10 content\n", exit_code=0)
88+
89+
return DummyRes(command=cmd, stdout="", exit_code=1, stderr="unexpected")
90+
91+
c = get_collector(monkeypatch, run_map, system_info, conn_mock)
92+
93+
n = c._collect_syslog_rotations()
94+
assert n == 4
95+
96+
names = {a.filename for a in c.result.artifacts}
97+
assert names == {
98+
"rotated_syslog.log",
99+
"rotated_syslog.1.log",
100+
"rotated_syslog.2.gz.log",
101+
"rotated_syslog.10.gz.log",
102+
}
103+
104+
descs = [e["description"] for e in c._events]
105+
assert "Collected syslog rotated files" in descs
106+
107+
108+
def test_collect_rotations_no_files(monkeypatch, system_info, conn_mock):
109+
def run_map(cmd, **kwargs):
110+
if cmd.startswith("ls -1 /var/log/syslog"):
111+
return DummyRes(command=cmd, stdout="", exit_code=0)
112+
return DummyRes(command=cmd, stdout="", exit_code=1)
113+
114+
c = get_collector(monkeypatch, run_map, system_info, conn_mock)
115+
116+
n = c._collect_syslog_rotations()
117+
assert n == 0
118+
assert c.result.artifacts == []
119+
120+
assert any(
121+
e["description"].startswith("No /var/log/syslog files found")
122+
and getattr(e["priority"], "name", str(e["priority"])) == "WARNING"
123+
for e in c._events
124+
)
125+
126+
127+
def test_collect_rotations_gz_failure(monkeypatch, system_info, conn_mock):
128+
ls_out = "/var/log/syslog.2.gz\n"
129+
130+
def run_map(cmd, **kwargs):
131+
if cmd.startswith("ls -1 /var/log/syslog"):
132+
return DummyRes(command=cmd, stdout=ls_out, exit_code=0)
133+
if "gzip -dc" in cmd and "/var/log/syslog.2.gz" in cmd:
134+
return DummyRes(command=cmd, stdout="", exit_code=1, stderr="gzip: not found")
135+
return DummyRes(command=cmd, stdout="", exit_code=1)
136+
137+
c = get_collector(monkeypatch, run_map, system_info, conn_mock)
138+
139+
n = c._collect_syslog_rotations()
140+
assert n == 0
141+
assert c.result.artifacts == []
142+
143+
fail_events = [
144+
e for e in c._events if e["description"] == "Some syslog files could not be collected."
145+
]
146+
assert fail_events, "Expected a failure event"
147+
failed = fail_events[-1]["data"]["failed"]
148+
assert any(item["path"].endswith("/var/log/syslog.2.gz") for item in failed)
149+
150+
151+
def test_collect_data_integration(monkeypatch, system_info, conn_mock):
152+
ls_out = "/var/log/syslog\n"
153+
154+
def run_map(cmd, **kwargs):
155+
if cmd.startswith("ls -1 /var/log/syslog"):
156+
return DummyRes(command=cmd, stdout=ls_out, exit_code=0)
157+
if cmd.startswith("cat ") and "/var/log/syslog" in cmd:
158+
return DummyRes(command=cmd, stdout="syslog file content\n", exit_code=0)
159+
return DummyRes(command=cmd, stdout="", exit_code=1)
160+
161+
c = get_collector(monkeypatch, run_map, system_info, conn_mock)
162+
163+
result, data = c.collect_data()
164+
assert isinstance(data, SyslogData)
165+
assert data.syslog_logs == 1
166+
assert c.result.message == "Syslog data collected"

0 commit comments

Comments
 (0)