From 413c5948e793f78812577a0c280d14fb385fdca0 Mon Sep 17 00:00:00 2001 From: jaspals3123 Date: Fri, 30 Jan 2026 15:19:36 -0500 Subject: [PATCH] url arg and test enhancements --- .../plugins/inband/network/collector_args.py | 33 ++++++++ .../inband/network/network_collector.py | 52 +++++++++++- .../plugins/inband/network/network_plugin.py | 5 +- .../plugins/inband/network/networkdata.py | 1 + test/unit/plugin/test_network_collector.py | 82 +++++++++++++++++++ 5 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 nodescraper/plugins/inband/network/collector_args.py diff --git a/nodescraper/plugins/inband/network/collector_args.py b/nodescraper/plugins/inband/network/collector_args.py new file mode 100644 index 00000000..1e1bcedf --- /dev/null +++ b/nodescraper/plugins/inband/network/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 NetworkCollectorArgs(CollectorArgs): + url: Optional[str] = None diff --git a/nodescraper/plugins/inband/network/network_collector.py b/nodescraper/plugins/inband/network/network_collector.py index 1dac9ac4..a123b98a 100644 --- a/nodescraper/plugins/inband/network/network_collector.py +++ b/nodescraper/plugins/inband/network/network_collector.py @@ -27,9 +27,10 @@ from typing import Dict, List, Optional, Tuple from nodescraper.base import InBandDataCollector -from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus +from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily from nodescraper.models import TaskResult +from .collector_args import NetworkCollectorArgs from .networkdata import ( BroadcomNicDevice, BroadcomNicQos, @@ -55,7 +56,7 @@ ) -class NetworkCollector(InBandDataCollector[NetworkDataModel, None]): +class NetworkCollector(InBandDataCollector[NetworkDataModel, NetworkCollectorArgs]): """Collect network configuration details using ip command""" DATA_MODEL = NetworkDataModel @@ -1669,12 +1670,51 @@ def _collect_pensando_nic_info( uncollected_commands, ) + def _check_network_connectivity(self, url: str) -> bool: + """Check network connectivity by pinging a URL. + + Args: + url: URL or hostname to ping + + Returns: + bool: True if network is accessible, False otherwise + """ + cmd = "ping" + + # Determine ping options based on OS + ping_option = "-c 1" if self.system_info.os_family == OSFamily.LINUX else "-n 1" + + # Run ping command + result = self._run_sut_cmd(f"{cmd} {url} {ping_option}") + + network_accessible = result.exit_code == 0 + + if network_accessible: + self._log_event( + category=EventCategory.NETWORK, + description="System networking is up", + data={"url": url, "accessible": network_accessible}, + priority=EventPriority.INFO, + ) + else: + self._log_event( + category=EventCategory.NETWORK, + description=f"{cmd} to {url} failed!", + data={"url": url, "accessible": network_accessible}, + priority=EventPriority.ERROR, + ) + + return network_accessible + def collect_data( self, - args=None, + args: Optional[NetworkCollectorArgs] = None, ) -> Tuple[TaskResult, Optional[NetworkDataModel]]: """Collect network configuration from the system. + Args: + args: Optional NetworkCollectorArgs with URL for network connectivity check + Returns: Tuple[TaskResult, Optional[NetworkDataModel]]: tuple containing the task result and an instance of NetworkDataModel or None if collection failed. @@ -1695,6 +1735,11 @@ def collect_data( pensando_rdma_statistics: List[PensandoNicRdmaStatistics] = [] pensando_version_host_software: Optional[PensandoNicVersionHostSoftware] = None pensando_version_firmware: List[PensandoNicVersionFirmware] = [] + network_accessible: Optional[bool] = None + + # Check network connectivity if URL is provided + if args and args.url: + network_accessible = self._check_network_connectivity(args.url) # Collect interface/address information res_addr = self._run_sut_cmd(self.CMD_ADDR) @@ -1823,6 +1868,7 @@ def collect_data( pensando_nic_rdma_statistics=pensando_rdma_statistics, pensando_nic_version_host_software=pensando_version_host_software, pensando_nic_version_firmware=pensando_version_firmware, + accessible=network_accessible, ) self.result.status = ExecutionStatus.OK return self.result, network_data diff --git a/nodescraper/plugins/inband/network/network_plugin.py b/nodescraper/plugins/inband/network/network_plugin.py index 2735e705..0ba55e79 100644 --- a/nodescraper/plugins/inband/network/network_plugin.py +++ b/nodescraper/plugins/inband/network/network_plugin.py @@ -25,13 +25,16 @@ ############################################################################### from nodescraper.base import InBandDataPlugin +from .collector_args import NetworkCollectorArgs from .network_collector import NetworkCollector from .networkdata import NetworkDataModel -class NetworkPlugin(InBandDataPlugin[NetworkDataModel, None, None]): +class NetworkPlugin(InBandDataPlugin[NetworkDataModel, NetworkCollectorArgs, None]): """Plugin for collection of network configuration data""" DATA_MODEL = NetworkDataModel COLLECTOR = NetworkCollector + + COLLECTOR_ARGS = NetworkCollectorArgs diff --git a/nodescraper/plugins/inband/network/networkdata.py b/nodescraper/plugins/inband/network/networkdata.py index 34d1f63e..e6817514 100644 --- a/nodescraper/plugins/inband/network/networkdata.py +++ b/nodescraper/plugins/inband/network/networkdata.py @@ -317,3 +317,4 @@ class NetworkDataModel(DataModel): pensando_nic_rdma_statistics: List[PensandoNicRdmaStatistics] = Field(default_factory=list) pensando_nic_version_host_software: Optional[PensandoNicVersionHostSoftware] = None pensando_nic_version_firmware: List[PensandoNicVersionFirmware] = Field(default_factory=list) + accessible: Optional[bool] = None # Network accessibility check via ping diff --git a/test/unit/plugin/test_network_collector.py b/test/unit/plugin/test_network_collector.py index 222c1fc0..2de1374d 100644 --- a/test/unit/plugin/test_network_collector.py +++ b/test/unit/plugin/test_network_collector.py @@ -1859,3 +1859,85 @@ def test_network_data_model_with_pensando_nic_version_firmware(): assert len(data.pensando_nic_version_firmware) == 1 assert data.pensando_nic_version_firmware[0].nic_id == "42424650-4c32-3533-3330-323934000000" assert data.pensando_nic_version_firmware[0].cpld == "3.16 (primary)" + + +def test_network_accessibility_linux_success(collector, conn_mock): + """Test network accessibility check on Linux with successful ping""" + collector.system_info.os_family = OSFamily.LINUX + + # Mock successful ping command + def run_sut_cmd_side_effect(cmd, **kwargs): + if "ping" in cmd: + return MagicMock( + exit_code=0, + stdout=( + "PING sample.mock.com (11.22.33.44) 56(84) bytes of data.\n" + "64 bytes from mock-server 55.66.77.88): icmp_seq=1 ttl=63 time=0.408 ms\n" + "--- sample.mock.com ping statistics ---\n" + "1 packets transmitted, 1 received, 0% packet loss, time 0ms\n" + "rtt min/avg/max/mdev = 0.408/0.408/0.408/0.000 ms\n" + ), + command=cmd, + ) + return MagicMock(exit_code=1, stdout="", command=cmd) + + collector._run_sut_cmd = MagicMock(side_effect=run_sut_cmd_side_effect) + + # Test if collector has accessibility check method + if hasattr(collector, "check_network_accessibility"): + result, accessible = collector.check_network_accessibility() + assert result.status == ExecutionStatus.OK + assert accessible is True + + +def test_network_accessibility_windows_success(collector, conn_mock): + """Test network accessibility check on Windows with successful ping""" + collector.system_info.os_family = OSFamily.WINDOWS + + # Mock successful ping command + def run_sut_cmd_side_effect(cmd, **kwargs): + if "ping" in cmd: + return MagicMock( + exit_code=0, + stdout=( + "Pinging sample.mock.com [11.22.33.44] with 32 bytes of data:\n" + "Reply from 10.228.151.8: bytes=32 time=224ms TTL=55\n" + "Ping statistics for 11.22.33.44:\n" + "Packets: Sent = 1, Received = 1, Lost = 0 (0% loss),\n" + "Approximate round trip times in milli-seconds:\n" + "Minimum = 224ms, Maximum = 224ms, Average = 224ms\n" + ), + command=cmd, + ) + return MagicMock(exit_code=1, stdout="", command=cmd) + + collector._run_sut_cmd = MagicMock(side_effect=run_sut_cmd_side_effect) + + # Test if collector has accessibility check method + if hasattr(collector, "check_network_accessibility"): + result, accessible = collector.check_network_accessibility() + assert result.status == ExecutionStatus.OK + assert accessible is True + + +def test_network_accessibility_failure(collector, conn_mock): + """Test network accessibility check with failed ping""" + collector.system_info.os_family = OSFamily.LINUX + + # Mock failed ping command + def run_sut_cmd_side_effect(cmd, **kwargs): + if "ping" in cmd: + return MagicMock( + exit_code=1, + stdout="ping: www.sample.mock.com: Name or service not known", + command=cmd, + ) + return MagicMock(exit_code=1, stdout="", command=cmd) + + collector._run_sut_cmd = MagicMock(side_effect=run_sut_cmd_side_effect) + + # Test if collector has accessibility check method + if hasattr(collector, "check_network_accessibility"): + result, accessible = collector.check_network_accessibility() + assert result.status == ExecutionStatus.ERRORS_DETECTED + assert accessible is False