From e19398cc3e452d43065d3fc7562448242eee5341 Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Tue, 5 Aug 2025 11:16:23 -0500 Subject: [PATCH 1/5] added sysctl plugin, collection only --- nodescraper/plugins/inband/sysctl/__init__.py | 29 +++++ .../plugins/inband/sysctl/analyzer_args.py | 55 ++++++++++ .../plugins/inband/sysctl/sysctl_analyzer.py | 56 ++++++++++ .../plugins/inband/sysctl/sysctl_collector.py | 100 ++++++++++++++++++ .../plugins/inband/sysctl/sysctl_plugin.py | 43 ++++++++ .../plugins/inband/sysctl/sysctldata.py | 42 ++++++++ 6 files changed, 325 insertions(+) create mode 100644 nodescraper/plugins/inband/sysctl/__init__.py create mode 100644 nodescraper/plugins/inband/sysctl/analyzer_args.py create mode 100644 nodescraper/plugins/inband/sysctl/sysctl_analyzer.py create mode 100644 nodescraper/plugins/inband/sysctl/sysctl_collector.py create mode 100644 nodescraper/plugins/inband/sysctl/sysctl_plugin.py create mode 100644 nodescraper/plugins/inband/sysctl/sysctldata.py diff --git a/nodescraper/plugins/inband/sysctl/__init__.py b/nodescraper/plugins/inband/sysctl/__init__.py new file mode 100644 index 0000000..b4ba1e4 --- /dev/null +++ b/nodescraper/plugins/inband/sysctl/__init__.py @@ -0,0 +1,29 @@ +############################################################################### +# +# MIT License +# +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +############################################################################### +from .analyzer_args import SysctlAnalyzerArgs +from .sysctl_plugin import SysctlPlugin + +__all__ = ["SysctlPlugin", "SysctlAnalyzerArgs"] diff --git a/nodescraper/plugins/inband/sysctl/analyzer_args.py b/nodescraper/plugins/inband/sysctl/analyzer_args.py new file mode 100644 index 0000000..f69ce3a --- /dev/null +++ b/nodescraper/plugins/inband/sysctl/analyzer_args.py @@ -0,0 +1,55 @@ +############################################################################### +# +# MIT License +# +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +############################################################################### +from typing import Optional + +from nodescraper.models import AnalyzerArgs +from nodescraper.plugins.inband.sysctl.sysctldata import SysctlDataModel + + +class SysctlAnalyzerArgs(AnalyzerArgs): + exp_vm_swappiness: Optional[int] = None + exp_vm_numa_balancing: Optional[int] = None + exp_vm_oom_kill_allocating_task: Optional[int] = None + exp_vm_compaction_proactiveness: Optional[int] = None + exp_vm_compact_unevictable_allowed: Optional[int] = None + exp_vm_extfrag_threshold: Optional[int] = None + exp_vm_zone_reclaim_mode: Optional[int] = None + exp_vm_dirty_background_ratio: Optional[int] = None + exp_vm_dirty_ratio: Optional[int] = None + exp_vm_dirty_writeback_centisecs: Optional[int] = None + exp_kernel_numa_balancing: Optional[int] = None + + @classmethod + def build_from_model(cls, datamodel: SysctlDataModel) -> "SysctlAnalyzerArgs": + """build analyzer args from data model + + Args: + datamodel (SysctlDataModel): data model for plugin + + Returns: + SysctlAnalyzerArgs: instance of analyzer args class + """ + return cls(exp_vm_swappiness=datamodel.vm_swappiness) diff --git a/nodescraper/plugins/inband/sysctl/sysctl_analyzer.py b/nodescraper/plugins/inband/sysctl/sysctl_analyzer.py new file mode 100644 index 0000000..a39f899 --- /dev/null +++ b/nodescraper/plugins/inband/sysctl/sysctl_analyzer.py @@ -0,0 +1,56 @@ +############################################################################### +# +# MIT License +# +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +############################################################################### + +from typing import Optional + +from nodescraper.interfaces import DataAnalyzer +from nodescraper.models import TaskResult + +from .analyzer_args import SysctlAnalyzerArgs +from .sysctldata import SysctlDataModel + + +class SysctlAnalyzer(DataAnalyzer[SysctlDataModel, SysctlAnalyzerArgs]): + """Check sysctl matches expected sysctl details""" + + DATA_MODEL = SysctlDataModel + + def analyze_data( + self, data: SysctlDataModel, args: Optional[SysctlAnalyzerArgs] = None + ) -> TaskResult: + """Analyze the Sysctl data against expected Sysctl versions. + + Args: + data (SysctlDataModel): The Sysctl data to analyze. + args (Optional[SysctlAnalyzerArgs], optional): Expected Sysctl data. Defaults to None. + + Returns: + TaskResult: The result of the analysis, indicating whether the Sysctl data matches + the expected versions or not. + """ + + if not args: + args = SysctlAnalyzerArgs() diff --git a/nodescraper/plugins/inband/sysctl/sysctl_collector.py b/nodescraper/plugins/inband/sysctl/sysctl_collector.py new file mode 100644 index 0000000..ed2a38d --- /dev/null +++ b/nodescraper/plugins/inband/sysctl/sysctl_collector.py @@ -0,0 +1,100 @@ +############################################################################### +# +# MIT License +# +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +############################################################################### + +from nodescraper.base import InBandDataCollector +from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily +from nodescraper.models import TaskResult + +from .sysctldata import SysctlDataModel + + +class SysctlCollector(InBandDataCollector[SysctlDataModel, None]): + """Collect sysctl kernel VM settings.""" + + DATA_MODEL = SysctlDataModel + + def collect_data( + self, + args=None, + ) -> tuple[TaskResult, SysctlDataModel | None]: + """Collect sysctl VM tuning values from the system.""" + values = {} + + if self.system_info.os_family == OSFamily.WINDOWS: + self._log_event( + category=EventCategory.OS, + description="Windows is not supported for sysctl collection.", + priority=EventPriority.WARNING, + console_log=True, + ) + return self.result, None + + for field_name in SysctlDataModel.model_fields: + sysctl_key = field_name.replace("_", ".", 1) + res = self._run_sut_cmd(f"sysctl -n {sysctl_key}") + + if res.exit_code == 0: + try: + values[field_name] = int(res.stdout.strip()) + except ValueError: + self._log_event( + category=EventCategory.OS, + description=f"Invalid integer value for {sysctl_key}", + data={"stdout": res.stdout}, + priority=EventPriority.ERROR, + console_log=True, + ) + else: + self._log_event( + category=EventCategory.OS, + description=f"Error checking Linux system setting : {sysctl_key}", + data={"system_setting": sysctl_key, "exit_code": res.exit_code}, + priority=EventPriority.WARNING, + console_log=True, + ) + + if values: + sysctl_data = SysctlDataModel(**values) + self._log_event( + category="SYSCTL_READ", + description="Sysctl settings read", + data=sysctl_data.model_dump(), + priority=EventPriority.INFO, + ) + self.result.message = "SYSCTL data collected" + self.result.status = ExecutionStatus.OK + else: + sysctl_data = None + self._log_event( + category=EventCategory.OS, + description="Sysctl settings not read", + priority=EventPriority.CRITICAL, + console_log=True, + ) + self.result.message = "Sysctl settings not read" + self.result.status = ExecutionStatus.ERROR + + return self.result, sysctl_data diff --git a/nodescraper/plugins/inband/sysctl/sysctl_plugin.py b/nodescraper/plugins/inband/sysctl/sysctl_plugin.py new file mode 100644 index 0000000..2c36a10 --- /dev/null +++ b/nodescraper/plugins/inband/sysctl/sysctl_plugin.py @@ -0,0 +1,43 @@ +############################################################################### +# +# MIT License +# +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +############################################################################### +from nodescraper.base import InBandDataPlugin + +from .analyzer_args import SysctlAnalyzerArgs +from .sysctl_analyzer import SysctlAnalyzer +from .sysctl_collector import SysctlCollector +from .sysctldata import SysctlDataModel + + +class SysctlPlugin(InBandDataPlugin[SysctlDataModel, None, SysctlAnalyzerArgs]): + """Plugin for collection and analysis of BIOS data""" + + DATA_MODEL = SysctlDataModel + + COLLECTOR = SysctlCollector + + ANALYZER = SysctlAnalyzer + + ANALYZER_ARGS = SysctlAnalyzerArgs diff --git a/nodescraper/plugins/inband/sysctl/sysctldata.py b/nodescraper/plugins/inband/sysctl/sysctldata.py new file mode 100644 index 0000000..258b3b5 --- /dev/null +++ b/nodescraper/plugins/inband/sysctl/sysctldata.py @@ -0,0 +1,42 @@ +############################################################################### +# +# MIT License +# +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +############################################################################### +from typing import Optional + +from nodescraper.models import DataModel + + +class SysctlDataModel(DataModel): + vm_swappiness: Optional[int] = None + vm_numa_balancing: Optional[int] = None + vm_oom_kill_allocating_task: Optional[int] = None + vm_compaction_proactiveness: Optional[int] = None + vm_compact_unevictable_allowed: Optional[int] = None + vm_extfrag_threshold: Optional[int] = None + vm_zone_reclaim_mode: Optional[int] = None + vm_dirty_background_ratio: Optional[int] = None + vm_dirty_ratio: Optional[int] = None + vm_dirty_writeback_centisecs: Optional[int] = None + kernel_numa_balancing: Optional[int] = None From bf4917bbcd55153b3882224371c5d379e893ba54 Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Tue, 5 Aug 2025 11:44:00 -0500 Subject: [PATCH 2/5] analyzer added --- .../plugins/inband/sysctl/analyzer_args.py | 14 +++++- .../plugins/inband/sysctl/sysctl_analyzer.py | 47 +++++++++++++++---- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/nodescraper/plugins/inband/sysctl/analyzer_args.py b/nodescraper/plugins/inband/sysctl/analyzer_args.py index f69ce3a..7ecae81 100644 --- a/nodescraper/plugins/inband/sysctl/analyzer_args.py +++ b/nodescraper/plugins/inband/sysctl/analyzer_args.py @@ -52,4 +52,16 @@ def build_from_model(cls, datamodel: SysctlDataModel) -> "SysctlAnalyzerArgs": Returns: SysctlAnalyzerArgs: instance of analyzer args class """ - return cls(exp_vm_swappiness=datamodel.vm_swappiness) + return cls( + exp_vm_swappiness=datamodel.vm_swappiness, + exp_vm_numa_balancing=datamodel.vm_numa_balancing, + exp_vm_oom_kill_allocating_task=datamodel.vm_oom_kill_allocating_task, + exp_vm_compaction_proactiveness=datamodel.vm_compaction_proactiveness, + exp_vm_compact_unevictable_allowed=datamodel.vm_compact_unevictable_allowed, + exp_vm_extfrag_threshold=datamodel.vm_extfrag_threshold, + exp_vm_zone_reclaim_mode=datamodel.vm_zone_reclaim_mode, + exp_vm_dirty_background_ratio=datamodel.vm_dirty_background_ratio, + exp_vm_dirty_ratio=datamodel.vm_dirty_ratio, + exp_vm_dirty_writeback_centisecs=datamodel.vm_dirty_writeback_centisecs, + exp_kernel_numa_balancing=datamodel.kernel_numa_balancing, + ) diff --git a/nodescraper/plugins/inband/sysctl/sysctl_analyzer.py b/nodescraper/plugins/inband/sysctl/sysctl_analyzer.py index a39f899..3a614e9 100644 --- a/nodescraper/plugins/inband/sysctl/sysctl_analyzer.py +++ b/nodescraper/plugins/inband/sysctl/sysctl_analyzer.py @@ -26,6 +26,7 @@ from typing import Optional +from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus from nodescraper.interfaces import DataAnalyzer from nodescraper.models import TaskResult @@ -41,16 +42,42 @@ class SysctlAnalyzer(DataAnalyzer[SysctlDataModel, SysctlAnalyzerArgs]): def analyze_data( self, data: SysctlDataModel, args: Optional[SysctlAnalyzerArgs] = None ) -> TaskResult: - """Analyze the Sysctl data against expected Sysctl versions. - - Args: - data (SysctlDataModel): The Sysctl data to analyze. - args (Optional[SysctlAnalyzerArgs], optional): Expected Sysctl data. Defaults to None. - - Returns: - TaskResult: The result of the analysis, indicating whether the Sysctl data matches - the expected versions or not. - """ + """Analyze the Sysctl data against expected Sysctl values.""" + self.result = self.result + self.result.status = ExecutionStatus.OK + self.result.message = "All expected sysctl parameters match." + mismatches = {} if not args: args = SysctlAnalyzerArgs() + + for exp_field_name, expected_value in args.model_dump(exclude_unset=True).items(): + + data_field_name = exp_field_name.removeprefix("exp_") + actual_value = getattr(data, data_field_name, None) + + if actual_value is None: + mismatches[data_field_name] = {"expected": expected_value, "actual": "missing"} + elif actual_value != expected_value: + mismatches[data_field_name] = {"expected": expected_value, "actual": actual_value} + + if mismatches: + self.result.status = ExecutionStatus.ERROR + self.result.message = f"{len(mismatches)} sysctl parameter(s) mismatched." + self.result.message = "Sysctl parameters mismatch detected." + self._log_event( + category=EventCategory.OS, + description="Sysctl mismatch detected", + data=mismatches, + priority=EventPriority.ERROR, + console_log=True, + ) + else: + self._log_event( + category=EventCategory.OS, + description="All expected sysctl parameters matched", + priority=EventPriority.INFO, + console_log=True, + ) + + return self.result From 9a5625dd3c76571d2555cab0708bb1aeb7e6d558 Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Tue, 5 Aug 2025 14:56:09 -0500 Subject: [PATCH 3/5] added utests --- .../plugins/inband/sysctl/sysctl_collector.py | 1 - test/unit/plugin/test_sysctl_analyzer.py | 41 ++++++++++++++ test/unit/plugin/test_sysctl_collector.py | 53 +++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 test/unit/plugin/test_sysctl_analyzer.py create mode 100644 test/unit/plugin/test_sysctl_collector.py diff --git a/nodescraper/plugins/inband/sysctl/sysctl_collector.py b/nodescraper/plugins/inband/sysctl/sysctl_collector.py index ed2a38d..8b310a2 100644 --- a/nodescraper/plugins/inband/sysctl/sysctl_collector.py +++ b/nodescraper/plugins/inband/sysctl/sysctl_collector.py @@ -92,7 +92,6 @@ def collect_data( category=EventCategory.OS, description="Sysctl settings not read", priority=EventPriority.CRITICAL, - console_log=True, ) self.result.message = "Sysctl settings not read" self.result.status = ExecutionStatus.ERROR diff --git a/test/unit/plugin/test_sysctl_analyzer.py b/test/unit/plugin/test_sysctl_analyzer.py new file mode 100644 index 0000000..51fbc4d --- /dev/null +++ b/test/unit/plugin/test_sysctl_analyzer.py @@ -0,0 +1,41 @@ +import pytest + +from nodescraper.enums import ExecutionStatus +from nodescraper.plugins.inband.sysctl.analyzer_args import SysctlAnalyzerArgs +from nodescraper.plugins.inband.sysctl.sysctl_analyzer import SysctlAnalyzer +from nodescraper.plugins.inband.sysctl.sysctldata import SysctlDataModel + + +@pytest.fixture +def analyzer(system_info): + return SysctlAnalyzer(system_info=system_info) + + +@pytest.fixture +def correct_data(): + return SysctlDataModel( + vm_swappiness=1, + vm_numa_balancing=2, + vm_oom_kill_allocating_task=3, + vm_compaction_proactiveness=4, + vm_compact_unevictable_allowed=5, + vm_extfrag_threshold=6, + vm_zone_reclaim_mode=7, + vm_dirty_background_ratio=8, + vm_dirty_ratio=9, + vm_dirty_writeback_centisecs=10, + kernel_numa_balancing=11, + ) + + +def test_analyzer_all_match(analyzer, correct_data): + args = SysctlAnalyzerArgs.build_from_model(correct_data) + result = analyzer.analyze_data(correct_data, args) + assert result.status == ExecutionStatus.OK + + +def test_analyzer_mismatch(analyzer, correct_data): + args = SysctlAnalyzerArgs(exp_vm_swappiness=3, exp_vm_numa_balancing=4) + result = analyzer.analyze_data(correct_data, args) + assert result.status == ExecutionStatus.ERROR + assert "Sysctl parameters mismatch detected" in result.message diff --git a/test/unit/plugin/test_sysctl_collector.py b/test/unit/plugin/test_sysctl_collector.py new file mode 100644 index 0000000..b9e752a --- /dev/null +++ b/test/unit/plugin/test_sysctl_collector.py @@ -0,0 +1,53 @@ +from types import SimpleNamespace + +import pytest + +from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily +from nodescraper.plugins.inband.sysctl.sysctl_collector import SysctlCollector +from nodescraper.plugins.inband.sysctl.sysctldata import SysctlDataModel + + +@pytest.fixture +def linux_sysctl_collector(system_info, conn_mock): + system_info.os_family = OSFamily.LINUX + return SysctlCollector(system_info, conn_mock) + + +def make_artifact(cmd, exit_code, stdout): + return SimpleNamespace(command=cmd, exit_code=exit_code, stdout=stdout, stderr="") + + +def test_collect_data_all_fields_success(linux_sysctl_collector): + sysctl_fields = SysctlDataModel.model_fields.keys() + responses = [ + make_artifact(f"sysctl -n {f.replace('_', '.', 1)}", 0, "111") for f in sysctl_fields + ] + + linux_sysctl_collector._run_sut_cmd = lambda cmd, seq=responses: seq.pop(0) + + result, data = linux_sysctl_collector.collect_data() + + assert result.status == ExecutionStatus.OK + assert isinstance(data, SysctlDataModel) + for field in SysctlDataModel.model_fields: + assert getattr(data, field) == 111 + + event = result.events[-1] + assert event.category == "SYSCTL_READ" + assert event.priority == EventPriority.INFO.value + assert result.message == "SYSCTL data collected" + + +def test_collect_data_all_fail(linux_sysctl_collector): + def always_fail(cmd): + return make_artifact(cmd, 1, "") + + linux_sysctl_collector._run_sut_cmd = always_fail + result, data = linux_sysctl_collector.collect_data() + + assert result.status == ExecutionStatus.ERROR + assert data is None + + evt = result.events[0] + assert evt.category == EventCategory.OS.value + assert "Sysctl settings not read" in result.message From faec065f1e0af9952f2c8182a2fe8af615ac0795 Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Mon, 18 Aug 2025 09:36:46 -0500 Subject: [PATCH 4/5] addressed reviews --- nodescraper/plugins/inband/sysctl/sysctl_analyzer.py | 5 ++--- nodescraper/plugins/inband/sysctl/sysctl_collector.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/nodescraper/plugins/inband/sysctl/sysctl_analyzer.py b/nodescraper/plugins/inband/sysctl/sysctl_analyzer.py index 3a614e9..301543b 100644 --- a/nodescraper/plugins/inband/sysctl/sysctl_analyzer.py +++ b/nodescraper/plugins/inband/sysctl/sysctl_analyzer.py @@ -43,9 +43,6 @@ def analyze_data( self, data: SysctlDataModel, args: Optional[SysctlAnalyzerArgs] = None ) -> TaskResult: """Analyze the Sysctl data against expected Sysctl values.""" - self.result = self.result - self.result.status = ExecutionStatus.OK - self.result.message = "All expected sysctl parameters match." mismatches = {} if not args: @@ -79,5 +76,7 @@ def analyze_data( priority=EventPriority.INFO, console_log=True, ) + self.result.status = ExecutionStatus.OK + self.result.message = "All expected sysctl parameters match." return self.result diff --git a/nodescraper/plugins/inband/sysctl/sysctl_collector.py b/nodescraper/plugins/inband/sysctl/sysctl_collector.py index 8b310a2..0546dad 100644 --- a/nodescraper/plugins/inband/sysctl/sysctl_collector.py +++ b/nodescraper/plugins/inband/sysctl/sysctl_collector.py @@ -79,7 +79,7 @@ def collect_data( if values: sysctl_data = SysctlDataModel(**values) self._log_event( - category="SYSCTL_READ", + category="OS", description="Sysctl settings read", data=sysctl_data.model_dump(), priority=EventPriority.INFO, From f577e215eab4b949167d7997c6fa9c9dd261a7e5 Mon Sep 17 00:00:00 2001 From: Alex Bara Date: Mon, 18 Aug 2025 10:36:07 -0500 Subject: [PATCH 5/5] fixed utest --- test/unit/plugin/test_sysctl_collector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/plugin/test_sysctl_collector.py b/test/unit/plugin/test_sysctl_collector.py index b9e752a..553a6d4 100644 --- a/test/unit/plugin/test_sysctl_collector.py +++ b/test/unit/plugin/test_sysctl_collector.py @@ -33,7 +33,7 @@ def test_collect_data_all_fields_success(linux_sysctl_collector): assert getattr(data, field) == 111 event = result.events[-1] - assert event.category == "SYSCTL_READ" + assert event.category == "OS" assert event.priority == EventPriority.INFO.value assert result.message == "SYSCTL data collected"