Skip to content

Commit 79ddee6

Browse files
Merge pull request #19 from amd/alex_kernel_mods
Kernel modules plugin
2 parents 5516a9e + f5e3d8c commit 79ddee6

File tree

9 files changed

+760
-1
lines changed

9 files changed

+760
-1
lines changed

nodescraper/cli/helper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ def generate_reference_config(
331331
for obj in results:
332332
if obj.result_data.collection_result.status != ExecutionStatus.OK:
333333
logger.warning(
334-
"Plugin: %s result status is %, skipping",
334+
"Plugin: %s result status is %s, skipping",
335335
obj.source,
336336
obj.result_data.collection_result.status,
337337
)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
###############################################################################
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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 re
27+
28+
from nodescraper.models import AnalyzerArgs
29+
from nodescraper.plugins.inband.kernel_module.kernel_module_data import (
30+
KernelModuleDataModel,
31+
)
32+
33+
34+
class KernelModuleAnalyzerArgs(AnalyzerArgs):
35+
kernel_modules: dict[str, dict] = {}
36+
regex_filter: list[str] = ["amd"]
37+
38+
@classmethod
39+
def build_from_model(cls, datamodel: KernelModuleDataModel) -> "KernelModuleAnalyzerArgs":
40+
"""build analyzer args from data model and filter by regex_filter
41+
42+
Args:
43+
datamodel (KernelModuleDataModel): data model for plugin
44+
45+
Returns:
46+
KernelModuleAnalyzerArgs: instance of analyzer args class
47+
"""
48+
49+
pattern_regex = re.compile("amd", re.IGNORECASE)
50+
filtered_mods = {
51+
name: data
52+
for name, data in datamodel.kernel_modules.items()
53+
if pattern_regex.search(name)
54+
}
55+
56+
return cls(
57+
kernel_modules=filtered_mods,
58+
regex_filter=[],
59+
)
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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 re
27+
from typing import Optional
28+
29+
from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus
30+
from nodescraper.interfaces import DataAnalyzer
31+
from nodescraper.models import TaskResult
32+
33+
from .analyzer_args import KernelModuleAnalyzerArgs
34+
from .kernel_module_data import KernelModuleDataModel
35+
36+
37+
class KernelModuleAnalyzer(DataAnalyzer[KernelModuleDataModel, KernelModuleAnalyzerArgs]):
38+
"""Check kernel matches expected versions"""
39+
40+
DATA_MODEL = KernelModuleDataModel
41+
42+
def filter_modules_by_pattern(
43+
self, modules: dict[str, dict], patterns: list[str] = None
44+
) -> tuple[dict[str, dict], list[str]]:
45+
"""Filter modules by pattern
46+
47+
Args:
48+
modules (dict[str, dict]): modules to be filtered
49+
patterns (list[str], optional): pattern to us. Defaults to None.
50+
51+
Returns:
52+
tuple[dict[str, dict], list[str]]: tuple - dict of modules filtered,
53+
list of unmatched pattern
54+
"""
55+
if patterns is None:
56+
return modules, []
57+
58+
matched_modules = {}
59+
unmatched_patterns = []
60+
61+
pattern_match_flags = {p: False for p in patterns}
62+
63+
for mod_name in modules:
64+
for p in patterns:
65+
if re.search(p, mod_name, re.IGNORECASE):
66+
matched_modules[mod_name] = modules[mod_name]
67+
pattern_match_flags[p] = True
68+
break
69+
70+
unmatched_patterns = [p for p, matched in pattern_match_flags.items() if not matched]
71+
72+
return matched_modules, unmatched_patterns
73+
74+
def filter_modules_by_name_and_param(
75+
self, modules: dict[str, dict], to_match: dict[str, dict]
76+
) -> tuple[dict[str, dict], dict[str, dict]]:
77+
"""Filter modules by name, param and value
78+
79+
Args:
80+
modules (dict[str, dict]): modules to be filtered
81+
to_match (dict[str, dict]): modules to match
82+
83+
Returns:
84+
tuple[dict[str, dict], dict[str, dict]]: tuple - dict of modules filtered,
85+
dict of modules unmatched
86+
"""
87+
if not to_match:
88+
return modules, {}
89+
90+
filtered = {}
91+
unmatched = {}
92+
93+
for mod_name, expected_data in to_match.items():
94+
expected_params = expected_data.get("parameters", {})
95+
actual_data = modules.get(mod_name)
96+
97+
if not actual_data:
98+
unmatched[mod_name] = expected_data
99+
continue
100+
101+
actual_params = actual_data.get("parameters", {})
102+
param_mismatches = {}
103+
104+
for param, expected_val in expected_params.items():
105+
actual_val = actual_params.get(param)
106+
if actual_val != expected_val:
107+
param_mismatches[param] = {
108+
"expected": expected_val,
109+
"actual": actual_val if actual_val is not None else "<missing>",
110+
}
111+
112+
if param_mismatches:
113+
unmatched[mod_name] = {"parameters": param_mismatches}
114+
else:
115+
filtered[mod_name] = actual_data
116+
117+
return filtered, unmatched
118+
119+
def analyze_data(
120+
self, data: KernelModuleDataModel, args: Optional[KernelModuleAnalyzerArgs] = None
121+
) -> TaskResult:
122+
"""Analyze the kernel modules and associated parameters.
123+
124+
Args:
125+
data (KernelModuleDataModel): KernelModule data to analyze.
126+
args (Optional[KernelModuleAnalyzerArgs], optional): KernelModule analyzer args.
127+
128+
Returns:
129+
TaskResult: Result of the analysis containing status and message.
130+
"""
131+
if not args:
132+
args = KernelModuleAnalyzerArgs()
133+
else:
134+
if args.regex_filter and args.kernel_modules:
135+
self.logger.warning(
136+
"Both regex_filter and kernel_modules provided in analyzer args. kernel_modules will be ignored"
137+
)
138+
139+
self.result.status = ExecutionStatus.OK
140+
141+
if args.regex_filter:
142+
try:
143+
filtered_modules, unmatched_pattern = self.filter_modules_by_pattern(
144+
data.kernel_modules, args.regex_filter
145+
)
146+
except re.error:
147+
self._log_event(
148+
category=EventCategory.RUNTIME,
149+
description="KernelModule regex is invalid",
150+
data={"regex_filters": {args.regex_filter}},
151+
priority=EventPriority.ERROR,
152+
)
153+
self.result.message = "Kernel modules failed to match regex"
154+
self.result.status = ExecutionStatus.ERROR
155+
return self.result
156+
157+
if unmatched_pattern:
158+
self._log_event(
159+
category=EventCategory.RUNTIME,
160+
description="KernelModules did not match all patterns",
161+
data={"unmatched_pattern: ": unmatched_pattern},
162+
priority=EventPriority.INFO,
163+
)
164+
self.result.message = "Kernel modules failed to match every pattern"
165+
self.result.status = ExecutionStatus.ERROR
166+
return self.result
167+
168+
self._log_event(
169+
category=EventCategory.OS,
170+
description="KernelModules analyzed",
171+
data={"filtered_modules": filtered_modules},
172+
priority=EventPriority.INFO,
173+
)
174+
return self.result
175+
176+
elif args.kernel_modules:
177+
filtered_modules, not_matched = self.filter_modules_by_name_and_param(
178+
data.kernel_modules, args.kernel_modules
179+
)
180+
181+
# no modules matched
182+
if not filtered_modules and not_matched:
183+
self._log_event(
184+
category=EventCategory.RUNTIME,
185+
description="KernelModules: no modules matched",
186+
data=args.kernel_modules,
187+
priority=EventPriority.ERROR,
188+
)
189+
self.result.message = "Kernel modules not matched"
190+
self.result.status = ExecutionStatus.ERROR
191+
return self.result
192+
# some modules matched
193+
elif filtered_modules and not_matched:
194+
195+
self._log_event(
196+
category=EventCategory.RUNTIME,
197+
description="KernelModules: not all modules matched",
198+
data=not_matched,
199+
priority=EventPriority.ERROR,
200+
)
201+
self.result.message = "Kernel modules not matched"
202+
self.result.status = ExecutionStatus.ERROR
203+
return self.result
204+
else:
205+
self.result.message = (
206+
"No values provided in analysis args for: kernel_modules and regex_match"
207+
)
208+
self.result.status = ExecutionStatus.NOT_RAN
209+
return self.result

0 commit comments

Comments
 (0)