diff --git a/nodescraper/plugins/inband/journal/collector_args.py b/nodescraper/plugins/inband/journal/collector_args.py new file mode 100644 index 00000000..583c94ab --- /dev/null +++ b/nodescraper/plugins/inband/journal/collector_args.py @@ -0,0 +1,33 @@ +############################################################################### +# +# 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 CollectorArgs + + +class JournalCollectorArgs(CollectorArgs): + boot: Optional[int] = None diff --git a/nodescraper/plugins/inband/journal/journal_collector.py b/nodescraper/plugins/inband/journal/journal_collector.py index ecf48e0a..6b41dcc1 100644 --- a/nodescraper/plugins/inband/journal/journal_collector.py +++ b/nodescraper/plugins/inband/journal/journal_collector.py @@ -25,27 +25,50 @@ ############################################################################### from typing import Optional +from pydantic import ValidationError + from nodescraper.base import InBandDataCollector from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily from nodescraper.models import TaskResult +from nodescraper.utils import get_exception_details +from .collector_args import JournalCollectorArgs from .journaldata import JournalData -class JournalCollector(InBandDataCollector[JournalData, None]): +class JournalCollector(InBandDataCollector[JournalData, JournalCollectorArgs]): """Read journal log via journalctl.""" SUPPORTED_OS_FAMILY = {OSFamily.LINUX} DATA_MODEL = JournalData CMD = "journalctl --no-pager --system --output=short-iso" - def _read_with_journalctl(self): + def _read_with_journalctl(self, args: Optional[JournalCollectorArgs] = None): """Read journal logs using journalctl Returns: str|None: system journal read """ - res = self._run_sut_cmd(self.CMD, sudo=True, log_artifact=False, strip=False) + + cmd = "journalctl --no-pager --system --output=short-iso" + try: + # safe check for args.boot + if args is not None and getattr(args, "boot", None): + cmd = f"journalctl --no-pager -b {args.boot} --system --output=short-iso" + + res = self._run_sut_cmd(cmd, sudo=True, log_artifact=False, strip=False) + + except ValidationError as val_err: + self._log_event( + category=EventCategory.OS, + description="Exception while running journalctl", + data=get_exception_details(val_err), + priority=EventPriority.ERROR, + console_log=True, + ) + self.result.message = "Could not read journalctl data" + self.result.status = ExecutionStatus.ERROR + return None if res.exit_code != 0: self._log_event( @@ -61,16 +84,22 @@ def _read_with_journalctl(self): return res.stdout - def collect_data(self, args=None) -> tuple[TaskResult, Optional[JournalData]]: + def collect_data( + self, + args: Optional[JournalCollectorArgs] = None, + ) -> tuple[TaskResult, Optional[JournalData]]: """Collect journal logs Args: args (_type_, optional): Collection args. Defaults to None. Returns: - tuple[TaskResult, Optional[JournalData, None]]: Tuple of results and data model or none. + tuple[TaskResult, Optional[JournalData]]: Tuple of results and data model or none. """ - journal_log = self._read_with_journalctl() + if args is None: + args = JournalCollectorArgs() + + journal_log = self._read_with_journalctl(args) if journal_log: data = JournalData(journal_log=journal_log) self.result.message = self.result.message or "Journal data collected" diff --git a/nodescraper/plugins/inband/journal/journal_plugin.py b/nodescraper/plugins/inband/journal/journal_plugin.py index 72ccca5a..a3044fbe 100644 --- a/nodescraper/plugins/inband/journal/journal_plugin.py +++ b/nodescraper/plugins/inband/journal/journal_plugin.py @@ -25,13 +25,16 @@ ############################################################################### from nodescraper.base import InBandDataPlugin +from .collector_args import JournalCollectorArgs from .journal_collector import JournalCollector from .journaldata import JournalData -class JournalPlugin(InBandDataPlugin[JournalData, None, None]): +class JournalPlugin(InBandDataPlugin[JournalData, JournalCollectorArgs, None]): """Plugin for collection of journal data""" DATA_MODEL = JournalData COLLECTOR = JournalCollector + + COLLECTOR_ARGS = JournalCollectorArgs