diff --git a/osism/commands/report.py b/osism/commands/report.py index 141886002..2ab308f32 100644 --- a/osism/commands/report.py +++ b/osism/commands/report.py @@ -1,7 +1,8 @@ # SPDX-License-Identifier: Apache-2.0 import configparser -from datetime import datetime +from datetime import datetime, timezone +import fnmatch import json import subprocess @@ -9,8 +10,43 @@ from loguru import logger from tabulate import tabulate -from osism import settings +from osism import settings, utils from osism.commands.console import resolve_host_with_fallback +from osism.utils.hardware import parse_linux, parse_sonic +from osism.utils.hardware.collect import ( + Request, + add_collection_arguments, + collect, + dump_raw, +) +from osism.utils.hardware.mapping import ( + fetch_interfaces, + map_node_ports, + map_switch_ports, + physical_interfaces, + sonic_name_index, +) +from osism.utils.hardware.parse_lldp import LldpError, linux_neighbours +from osism.utils.hardware.render import ( + Column, + RenderError, + add_output_arguments, + flatten, + render_csv, + render_json, + render_table, + select_columns, + sort_rows, + write_output, +) +from osism.utils.hardware.selection import ( + KIND_NODE, + KIND_SWITCH, + SelectionError, + add_selection_arguments, + resolve_addresses, + select_targets, +) from osism.utils.inventory import get_hosts_from_inventory, get_inventory_path from osism.utils.ssh import ensure_known_hosts_file, KNOWN_HOSTS_PATH @@ -623,3 +659,398 @@ def take_action(self, parsed_args): logger.warning( f"Failed to query {len(failed_hosts)} host(s): {', '.join(failed_hosts)}" ) + + +def _collect_hardware(parsed_args, requests): + """Select the devices of a hardware report and query them. + + Returns: + ``(targets, collection)`` + + Raises: + SelectionError: The selection failed or matched no device + """ + if not ensure_known_hosts_file(): + logger.warning( + f"Could not initialize {KNOWN_HOSTS_PATH}, SSH may show warnings" + ) + + targets = select_targets(parsed_args, utils.nb) + resolve_addresses(targets, resolve_host_with_fallback) + + collection = collect( + targets, + requests, + parallel=parsed_args.parallel, + timeout=parsed_args.timeout, + ) + if parsed_args.dump_raw: + paths = dump_raw(collection, parsed_args.dump_raw) + logger.info(f"Wrote {len(paths)} raw document(s) to {parsed_args.dump_raw}") + + return targets, collection + + +def _fetch_netbox_interfaces(targets): + """Return the NetBox interfaces per device ID, empty without NetBox.""" + device_ids = [target.device_id for target in targets if target.device_id] + if not utils.nb or not device_ids: + return {} + try: + return fetch_interfaces(utils.nb, device_ids) + except Exception as exc: + logger.warning(f"Could not load interfaces from NetBox: {exc}") + return {} + + +def _warn_failed(failed): + if failed: + details = ", ".join(f"{item['name']} ({item['error']})" for item in failed) + logger.warning(f"Failed to query {len(failed)} device(s): {details}") + + +def _matches_interface(patterns, *names): + if not patterns: + return True + return any( + fnmatch.fnmatchcase(name, pattern) + for pattern in patterns + for name in names + if name + ) + + +TRANSCEIVER_COLUMNS = [ + Column("device_name", "Device"), + Column("device_inventory_hostname", "Inventory Hostname", default=False), + Column("device_kind", "Kind", default=False), + Column("device_address", "Address", default=False), + Column("device_site", "Site", default=False), + Column("device_rack", "Rack", default=False), + Column("device_role", "Role", default=False), + Column("device_device_type", "Device Type", default=False), + Column("device_manufacturer", "Manufacturer", default=False), + Column("device_serial", "Device Serial", default=False), + Column("device_system_vendor", "System Vendor", default=False), + Column("device_system_model", "System Model", default=False), + Column("device_hwsku", "HWSKU", default=False), + Column("device_platform", "Platform", default=False), + Column("device_asic", "ASIC", default=False), + Column("device_os_version", "OS Version", default=False), + Column("device_firmware_bios", "BIOS", default=False), + Column("device_firmware_cpld", "CPLD", default=False), + Column("device_firmware_onie", "ONIE", default=False), + Column("interface_name", "Interface"), + Column("interface_kind", "Port Kind", default=False), + Column("interface_parent", "Parent", default=False), + Column("interface_alias", "Alias", default=False), + Column("interface_netbox_name", "NetBox Interface"), + Column("interface_mapping", "Mapping"), + Column("interface_netbox_peer_device", "NetBox Peer Device", default=False), + Column("interface_netbox_peer_interface", "NetBox Peer Interface", default=False), + Column("interface_lldp_neighbor", "LLDP Neighbor", default=False), + Column("interface_lldp_neighbor_port", "LLDP Neighbor Port", default=False), + Column("interface_admin_status", "Admin"), + Column("interface_oper_status", "Oper"), + Column("interface_speed", "Speed"), + Column("interface_duplex", "Duplex", default=False), + Column("interface_autoneg", "Autoneg", default=False), + Column("interface_fec", "FEC", default=False), + Column("interface_mtu", "MTU", default=False), + Column("interface_mac", "MAC", default=False), + Column("interface_permanent_mac", "Permanent MAC", default=False), + Column("interface_port_type", "Port Type", default=False), + Column("interface_bond", "Bond", default=False), + Column("nic_pci_address", "PCI Address", default=False), + Column("nic_vendor", "NIC Vendor", default=False), + Column("nic_model", "NIC Model"), + Column("nic_subsystem_vendor", "NIC Subsystem Vendor", default=False), + Column("nic_subsystem_model", "NIC Subsystem Model", default=False), + Column("nic_revision", "NIC Revision", default=False), + Column("nic_serial", "NIC Serial", default=False), + Column("nic_driver", "Driver", default=False), + Column("nic_driver_version", "Driver Version", default=False), + Column("nic_firmware_version", "NIC Firmware"), + Column("transceiver_present", "Present", default=False), + Column("transceiver_form_factor", "Form Factor"), + Column("transceiver_connector", "Connector", default=False), + Column("transceiver_vendor", "Vendor"), + Column("transceiver_vendor_oui", "Vendor OUI", default=False), + Column("transceiver_part_number", "Part Number"), + Column("transceiver_serial", "Serial"), + Column("transceiver_revision", "Revision"), + Column("transceiver_date_code", "Date Code", default=False), + Column("transceiver_firmware_active", "Firmware"), + Column("transceiver_firmware_inactive", "Inactive Firmware", default=False), + Column("transceiver_compliance", "Compliance", default=False), + Column("transceiver_cable_length", "Cable Length", default=False), + Column("transceiver_wavelength", "Wavelength", default=False), + Column("transceiver_dom_temperature", "Temperature", default=False), + Column("transceiver_dom_voltage", "Voltage", default=False), + Column("transceiver_dom_lanes", "Lanes", default=False), +] + +# Fields of an interface entry, in the order they appear in JSON +TRANSCEIVER_INTERFACE_FIELDS = [ + "name", + "kind", + "parent", + "alias", + "netbox_name", + "mapping", + "netbox_peer_device", + "netbox_peer_interface", + "lldp_neighbor", + "lldp_neighbor_port", + "admin_status", + "oper_status", + "speed", + "duplex", + "autoneg", + "fec", + "mtu", + "mac", + "permanent_mac", + "port_type", + "bond", +] + + +def _transceiver_device(target, document, netbox_interfaces, dom): + """Build the report entry of one device from its collector document. + + Args: + target: The device + document: Raw document of the collector + netbox_interfaces: All NetBox interfaces of the device + dom: Include the diagnostic readings of the modules + """ + # Management, virtual and LAG interfaces have no port with a cage + documented = physical_interfaces(netbox_interfaces, include_mgmt=False) + neighbours = {} + + if target.kind == KIND_SWITCH: + system, ports = parse_sonic.parse_inventory(document, dom) + index = sonic_name_index( + target.device_id, + target.name, + target.hwsku or system.get("hwsku"), + netbox_interfaces, + ) + matches, unmatched = map_switch_ports(ports, documented, index) + else: + system, ports = parse_linux.parse_inventory(document, dom) + try: + neighbours = linux_neighbours(document) + except LldpError as exc: + # LLDP is a bonus in this report, a node without lldpd is fine + logger.debug(f"{target.name}: no LLDP data: {exc}") + matches, unmatched = map_node_ports(ports, documented, neighbours) + + interfaces = [] + for port in ports: + match = matches[port["name"]] + port["mapping"] = match.method + if match.interface is not None: + port["netbox_name"] = match.interface.name + endpoints = match.interface.endpoints + port["netbox_peer_device"] = ( + ", ".join(endpoint.device for endpoint in endpoints) or None + ) + port["netbox_peer_interface"] = ( + ", ".join(endpoint.interface for endpoint in endpoints) or None + ) + seen = neighbours.get(port["name"]) or [] + if seen: + port["lldp_neighbor"] = seen[0]["system_name"] or seen[0]["chassis_id"] + port["lldp_neighbor_port"] = seen[0]["port_id"] + + entry = {field: port.get(field) for field in TRANSCEIVER_INTERFACE_FIELDS} + entry["nic"] = port.get("nic") + entry["transceiver"] = port["transceiver"] + interfaces.append(entry) + + device = target.describe() + # What the device says about itself is more current than NetBox + device["serial"] = system.get("serial") or device["serial"] + device["system_vendor"] = system.get("system_vendor") + device["system_model"] = system.get("system_model") + if target.kind == KIND_SWITCH: + for field in ("hwsku", "platform", "asic", "os_version", "firmware"): + device[field] = system.get(field) + device["interfaces"] = interfaces + device["unmatched_netbox_interfaces"] = [interface.name for interface in unmatched] + return device + + +class Transceiver(Command): + """Report transceivers and NIC hardware of nodes and SONiC switches.""" + + def get_parser(self, prog_name): + parser = super(Transceiver, self).get_parser(prog_name) + add_selection_arguments(parser) + parser.add_argument( + "--include-vf", + action="store_true", + help="Also report SR-IOV virtual functions", + ) + parser.add_argument( + "--include-infiniband", + action="store_true", + help="Also report InfiniBand ports", + ) + parser.add_argument( + "--interface", + action="append", + metavar="PATTERN", + help="Only show ports whose name on the device or in NetBox matches the pattern (can be used multiple times)", + ) + parser.add_argument( + "--present", + action="store_true", + help="Only show ports with a module plugged in", + ) + parser.add_argument( + "--oper-status", + choices=["up", "down"], + help="Only show ports with this link state", + ) + add_output_arguments(parser) + parser.add_argument( + "--dom", + action="store_true", + help="Include the diagnostic readings of the modules (temperature, voltage, optical power)", + ) + add_collection_arguments(parser) + parser.add_argument( + "--strict", + action="store_true", + help="Exit with 1 when a device could not be queried", + ) + return parser + + def take_action(self, parsed_args): + try: + columns = select_columns( + TRANSCEIVER_COLUMNS, parsed_args.column, parsed_args.long + ) + except RenderError as exc: + logger.error(str(exc)) + return 1 + + node_options = [] + if parsed_args.include_vf: + node_options.append("--include-vf") + if parsed_args.include_infiniband: + node_options.append("--include-infiniband") + switch_sections = ["ports", "transceiver", "system"] + if parsed_args.dom: + switch_sections.append("dom") + requests = { + # Reading the module EEPROM needs root + KIND_NODE: Request( + sections=["ports", "lldp", "ethtool", "module", "nic", "system"], + options=node_options, + sudo=True, + ), + KIND_SWITCH: Request(sections=switch_sections), + } + + try: + targets, collection = _collect_hardware(parsed_args, requests) + except SelectionError as exc: + logger.error(str(exc)) + return 1 + + netbox_interfaces = _fetch_netbox_interfaces(targets) + failed = list(collection.failed) + devices = [] + for target in targets: + document = collection.documents.get(target.name) + if document is None: + continue + try: + devices.append( + _transceiver_device( + target, + document, + netbox_interfaces.get(target.device_id) or [], + parsed_args.dom, + ) + ) + except Exception as exc: + logger.debug(f"{target.name}: {type(exc).__name__}: {exc}") + failed.append({"name": target.name, "error": "unparsable output"}) + + for device in devices: + device["interfaces"] = [ + interface + for interface in device["interfaces"] + if _matches_interface( + parsed_args.interface, interface["name"], interface["netbox_name"] + ) + and (not parsed_args.present or interface["transceiver"]["present"]) + and ( + not parsed_args.oper_status + or interface["oper_status"] == parsed_args.oper_status + ) + ] + + if parsed_args.format == "json": + document = { + "generated_at": datetime.now(timezone.utc).strftime( + "%Y-%m-%dT%H:%M:%SZ" + ), + "devices": devices, + "failed": failed, + } + write_output(render_json(document), parsed_args.output) + else: + rows = [] + for device in devices: + attributes = { + key: value + for key, value in device.items() + if key not in ("interfaces", "unmatched_netbox_interfaces") + } + for interface in device["interfaces"]: + port = { + key: value + for key, value in interface.items() + if key not in ("nic", "transceiver") + } + rows.append( + flatten( + { + "device": attributes, + "interface": port, + "nic": interface["nic"] or {}, + "transceiver": interface["transceiver"], + } + ) + ) + rows = sort_rows(rows, "device_name", "interface_name") + + if parsed_args.format == "csv": + write_output(render_csv(rows, columns), parsed_args.output) + elif rows: + present = sum(1 for row in rows if row.get("transceiver_present")) + footer = [ + f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", + f"Devices: {len(devices)}", + f"Ports: {len(rows)}", + f"Modules present: {present}", + ] + write_output(render_table(rows, columns, footer), parsed_args.output) + + for device in devices: + unmatched = device["unmatched_netbox_interfaces"] + if unmatched: + logger.warning( + f"{device['name']}: {len(unmatched)} NetBox interface(s) " + f"without a live port: {', '.join(unmatched)}" + ) + + _warn_failed(failed) + if parsed_args.strict and failed: + return 1 diff --git a/osism/utils/hardware/__init__.py b/osism/utils/hardware/__init__.py index 5984ee113..17b2d1f23 100644 --- a/osism/utils/hardware/__init__.py +++ b/osism/utils/hardware/__init__.py @@ -3,9 +3,11 @@ """Helpers shared by the live hardware reports (``osism report ...``).""" import re -from typing import Any, List, Optional +from typing import Any, Dict, List, Optional _MAC_DIGITS = re.compile(r"^[0-9a-f]{12}$") +_ISO_DATE = re.compile(r"^(\d{4})-(\d{2})-(\d{2})(?!\d)") +_SFF_DATE = re.compile(r"^(\d{2})(\d{2})(\d{2})(?:\s*\S{1,2})?$") def normalize_mac(value: Any) -> Optional[str]: @@ -30,3 +32,53 @@ def natural_key(text: Any) -> List[Any]: for part in re.split(r"(\d+)", str(text or "")) if part != "" ] + + +def normalize_date_code(value: Any) -> Optional[str]: + """Return the manufacturing date of a module as ``YYYY-MM-DD``. + + The EEPROM of a module holds the date as ``YYMMDD``, optionally followed + by a lot code of two characters, and ``ethtool -m`` prints it like that. + SONiC decodes it: Enterprise SONiC writes ``2022-09-07T00:00:00+00:00``, + community SONiC ``2022-09-07`` followed by the lot code. A value in + another format is returned as it is. + """ + text = str(value).strip() if value is not None else "" + if not text: + return None + match = _ISO_DATE.match(text) + if match: + return "-".join(match.groups()) + match = _SFF_DATE.match(text) + if match and 1 <= int(match.group(2)) <= 12 and 1 <= int(match.group(3)) <= 31: + return "20{}-{}-{}".format(*match.groups()) + return text + + +TRANSCEIVER_FIELDS = [ + "form_factor", + "connector", + "vendor", + "vendor_oui", + "part_number", + "serial", + "revision", + "date_code", + "firmware_active", + "firmware_inactive", + "compliance", + "cable_length", + "wavelength", +] + + +def transceiver_record(present: Optional[bool]) -> Dict[str, Any]: + """Return a transceiver record with every identity field unset. + + Args: + present: True for a plugged-in module, False for a port without a + module, None when the port has no module of its own + """ + record: Dict[str, Any] = {"present": present} + record.update({name: None for name in TRANSCEIVER_FIELDS}) + return record diff --git a/osism/utils/hardware/parse_linux.py b/osism/utils/hardware/parse_linux.py index cf21db7b5..8540c7fa9 100644 --- a/osism/utils/hardware/parse_linux.py +++ b/osism/utils/hardware/parse_linux.py @@ -4,13 +4,23 @@ import json import re -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Tuple -from osism.utils.hardware import natural_key, normalize_mac +from osism.utils.hardware import ( + natural_key, + normalize_date_code, + normalize_mac, + transceiver_record, +) _PERMANENT_ADDRESS = re.compile(r"Permanent address:\s*(\S+)") # The domain has four digits, or more behind a bridge such as Intel VMD _PCI_ADDRESS = re.compile(r"^[0-9a-f]{4,8}:[0-9a-f]{2}:[0-9a-f]{2}\.[0-7]$", re.I) +_NUMBER = re.compile(r"-?\d+(?:\.\d+)?") +_DBM = re.compile(r"(-?\d+(?:\.\d+)?)\s*dBm") +_CHANNEL = re.compile(r"\(Channel\s*(\d+)\)", re.IGNORECASE) +_PCI_NAME = re.compile(r"^(.*?)\s*\[([0-9a-fA-F]{4})\]$") +_IB_RATE = re.compile(r"^\s*(\d+(?:\.\d+)?)\s*Gb/sec") def command_output(result: Any) -> Optional[str]: @@ -128,3 +138,313 @@ def parse_ports(document: Dict[str, Any]) -> List[Dict[str, Any]]: for name, raw in (document.get("ports") or {}).items() ] return sorted(ports, key=lambda port: natural_key(port["name"])) + + +def parse_key_values(text: Optional[str]) -> List[Tuple[str, str]]: + """Split ``Key: Value`` lines as printed by ethtool. + + Only the first colon separates key and value, since values contain + colons themselves (``Vendor OUI : 00:90:65``). A line without a colon + continues the value of the line before it. Keys can repeat. + """ + pairs: List[Tuple[str, str]] = [] + for line in (text or "").splitlines(): + if not line.strip(): + continue + key, separator, value = line.partition(":") + if separator: + pairs.append((key.strip(), value.strip())) + elif pairs: + last_key, last_value = pairs[-1] + pairs[-1] = (last_key, f"{last_value} {line.strip()}".strip()) + return pairs + + +def _first(pairs: List[Tuple[str, str]], *keys: str) -> Optional[str]: + wanted = [key.lower() for key in keys] + for key in wanted: + for name, value in pairs: + if name.lower() == key and value: + return value + return None + + +def _described(value: Optional[str]) -> Optional[str]: + """Return the text in parentheses of ``0x03 (SFP)``, else the value.""" + if not value: + return None + start, end = value.find("("), value.rfind(")") + if 0 <= start < end: + return value[start + 1 : end].strip() or None + return value + + +def _number(value: Optional[str]) -> Optional[float]: + match = _NUMBER.search(value or "") + return float(match.group(0)) if match else None + + +def parse_ethtool(result: Any) -> Dict[str, Any]: + """Parse the link settings printed by ``ethtool ``.""" + pairs = parse_key_values(command_output(result)) + + speed = _first(pairs, "Speed") + speed_match = re.match(r"(\d+)Mb/s", speed or "") + duplex = (_first(pairs, "Duplex") or "").lower() + autoneg = (_first(pairs, "Auto-negotiation") or "").lower() + link = (_first(pairs, "Link detected") or "").lower() + return { + "speed": int(speed_match.group(1)) if speed_match else None, + "duplex": duplex if duplex in ("full", "half") else None, + "autoneg": {"on": True, "off": False}.get(autoneg), + "port_type": _first(pairs, "Port"), + "link_detected": {"yes": True, "no": False}.get(link), + } + + +def parse_ethtool_driver(result: Any) -> Dict[str, Any]: + """Parse driver and firmware information printed by ``ethtool -i``.""" + pairs = parse_key_values(command_output(result)) + return { + "driver": _first(pairs, "driver"), + "driver_version": _first(pairs, "version"), + "firmware_version": _first(pairs, "firmware-version"), + "pci_address": _first(pairs, "bus-info"), + } + + +def parse_ethtool_fec(result: Any) -> Optional[str]: + """Return the active FEC encoding printed by ``ethtool --show-fec``.""" + pairs = parse_key_values(command_output(result)) + active = _first(pairs, "Active FEC encoding", "Active FEC encodings") + return active.lower() if active else None + + +def _parse_dom(pairs: List[Tuple[str, str]]) -> Optional[Dict[str, Any]]: + """Extract the diagnostic readings of a module, or None without any.""" + lanes: Dict[int, Dict[str, Any]] = {} + + def lane(key: str) -> Dict[str, Any]: + match = _CHANNEL.search(key) + number = int(match.group(1)) if match else 1 + return lanes.setdefault( + number, {"rx_power": None, "tx_power": None, "tx_bias": None} + ) + + for key, value in pairs: + name = key.lower() + # Threshold and alarm lines repeat the names of the readings + if "threshold" in name or "alarm" in name or "warning" in name: + continue + power = _DBM.search(value) + if "bias current" in name: + lane(key)["tx_bias"] = _number(value) + elif power and ("receiver signal" in name or "rcvr signal" in name): + lane(key)["rx_power"] = float(power.group(1)) + elif power and ("laser output power" in name or "transmit avg" in name): + lane(key)["tx_power"] = float(power.group(1)) + + temperature = _number(_first(pairs, "Module temperature")) + voltage = _number(_first(pairs, "Module voltage")) + if temperature is None and voltage is None and not lanes: + return None + return { + "temperature": temperature, + "voltage": voltage, + "lanes": [lanes[number] for number in sorted(lanes)], + } + + +def parse_module(result: Any, dom: bool = False) -> Dict[str, Any]: + """Parse the module EEPROM printed by ``ethtool -m``. + + ``ethtool -m`` fails for a port without a cage and for an empty cage. + Both are reported as ``present = False``. + + Args: + result: Collected command result + dom: Add the diagnostic readings as ``dom`` + """ + pairs = parse_key_values(command_output(result)) + identifier = _first(pairs, "Identifier") + if not identifier and not _first(pairs, "Vendor name", "Vendor PN"): + return transceiver_record(False) + + record = transceiver_record(True) + form_factor = _described(identifier) + record["form_factor"] = form_factor.split()[0] if form_factor else None + record["connector"] = _described(_first(pairs, "Connector")) + record["vendor"] = _first(pairs, "Vendor name") + record["vendor_oui"] = _first(pairs, "Vendor OUI") + record["part_number"] = _first(pairs, "Vendor PN") + record["serial"] = _first(pairs, "Vendor SN") + record["revision"] = _first(pairs, "Vendor rev") + record["date_code"] = normalize_date_code(_first(pairs, "Date code")) + record["firmware_active"] = _first(pairs, "Active firmware version") + record["firmware_inactive"] = _first(pairs, "Inactive firmware version") + + types = [value for key, value in pairs if key.lower() == "transceiver type"] + record["compliance"] = "; ".join(types) or None + + for key, value in pairs: + name = key.lower() + if not (name.startswith("length") or name == "cable assembly length"): + continue + number = _number(value) + if number: + record["cable_length"] = value + break + + wavelength = _first(pairs, "Laser wavelength") + if _number(wavelength): + record["wavelength"] = wavelength + + if dom: + record["dom"] = _parse_dom(pairs) + return record + + +def _pci_name(value: Optional[str]) -> Tuple[Optional[str], Optional[str]]: + """Split ``Mellanox Technologies [15b3]`` into name and ID.""" + if not value: + return None, None + match = _PCI_NAME.match(value.strip()) + if match: + return match.group(1).strip() or None, match.group(2).lower() + return value.strip(), None + + +def parse_lspci(result: Any) -> Dict[str, Any]: + """Parse one device of ``lspci -D -vmm -nn``.""" + pairs = parse_key_values(command_output(result)) + vendor, vendor_id = _pci_name(_first(pairs, "Vendor")) + model, model_id = _pci_name(_first(pairs, "Device")) + subsystem_vendor, _ = _pci_name(_first(pairs, "SVendor")) + subsystem_model, _ = _pci_name(_first(pairs, "SDevice")) + return { + "vendor": vendor, + "vendor_id": vendor_id, + "model": model, + "model_id": model_id, + "subsystem_vendor": subsystem_vendor, + "subsystem_model": subsystem_model, + "revision": _first(pairs, "Rev"), + } + + +def parse_devlink(result: Any) -> Dict[str, Any]: + """Parse ``devlink -j dev info`` of one PCI function. + + What devlink reports depends on the driver. Missing values stay None. + """ + empty: Dict[str, Any] = {"serial": None, "board_id": None, "firmware": None} + output = command_output(result) + if not output: + return empty + try: + info = json.loads(output).get("info") or {} + except (ValueError, AttributeError): + return empty + if not isinstance(info, dict) or not info: + return empty + + device = next(iter(info.values())) + if not isinstance(device, dict): + return empty + versions = device.get("versions") or {} + fixed = versions.get("fixed") or {} + firmware = { + kind: versions[kind] + for kind in ("running", "stored") + if isinstance(versions.get(kind), dict) and versions[kind] + } + return { + # bnxt_en reports the serial number of the board next to the one of + # the PCI device, which is derived from the MAC address. + "serial": device.get("board.serial_number") or device.get("serial_number"), + "board_id": fixed.get("board.id") or fixed.get("fw.psid"), + "firmware": firmware or None, + } + + +def parse_infiniband(raw: Any) -> Dict[str, Any]: + """Parse state and rate of an InfiniBand port from sysfs. + + The state reads ``4: ACTIVE``, the rate ``100 Gb/sec (4X EDR)``. + """ + if not isinstance(raw, dict): + return {"oper_status": None, "speed": None} + state = (raw.get("state") or "").upper() + rate = _IB_RATE.match(raw.get("rate") or "") + status = None + if state: + status = "up" if "ACTIVE" in state else "down" + return { + "oper_status": status, + "speed": int(float(rate.group(1)) * 1000) if rate else None, + } + + +def parse_system(document: Dict[str, Any]) -> Dict[str, Any]: + """Return vendor, model and serial number of the node from DMI.""" + dmi = document.get("dmi") or {} + return { + "system_vendor": dmi.get("sys_vendor") or None, + "system_model": dmi.get("product_name") or None, + "serial": dmi.get("product_serial") or None, + } + + +def parse_inventory( + document: Dict[str, Any], dom: bool = False +) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]: + """Return the hardware view of a node. + + Returns: + ``(system, interfaces)``: the node itself, and one record per port + holding the port fields plus ``nic`` and ``transceiver`` + """ + interfaces = [] + for port in parse_ports(document): + raw = (document.get("ports") or {}).get(port["name"]) or {} + settings = parse_ethtool(raw.get("ethtool")) + driver = parse_ethtool_driver(raw.get("ethtool_i")) + pci = parse_lspci(raw.get("lspci")) + devlink = parse_devlink(raw.get("devlink")) + + port["speed"] = settings["speed"] or port["speed"] + port["duplex"] = settings["duplex"] or (port["duplex"] or "").lower() or None + port["autoneg"] = settings["autoneg"] + port["port_type"] = settings["port_type"] + port["fec"] = parse_ethtool_fec(raw.get("ethtool_fec")) + + if port["kind"] == "infiniband": + link = parse_infiniband(raw.get("infiniband")) + port["oper_status"] = link["oper_status"] or port["oper_status"] + port["speed"] = link["speed"] or port["speed"] + + port["nic"] = { + # bus-info of a USB NIC is a USB path (usb-0000:00:14.0-7.2) + "pci_address": pci_address(driver["pci_address"]) + or port.get("pci_address"), + "vendor": pci["vendor"], + "model": pci["model"], + "subsystem_vendor": pci["subsystem_vendor"], + "subsystem_model": pci["subsystem_model"], + "revision": pci["revision"], + "serial": devlink["serial"], + "board_id": devlink["board_id"], + "driver": driver["driver"], + "driver_version": driver["driver_version"], + "firmware_version": driver["firmware_version"], + "firmware": devlink["firmware"], + } + # The module sits in the cage of the physical function. A virtual + # function has none of its own. + if port["kind"] == "vf": + port["transceiver"] = transceiver_record(None) + else: + port["transceiver"] = parse_module(raw.get("ethtool_m"), dom) + interfaces.append(port) + + return parse_system(document), interfaces diff --git a/osism/utils/hardware/parse_sonic.py b/osism/utils/hardware/parse_sonic.py index b0a7c89c7..101db68a6 100644 --- a/osism/utils/hardware/parse_sonic.py +++ b/osism/utils/hardware/parse_sonic.py @@ -2,13 +2,71 @@ """Parsers for the raw document of the SONiC collector (``remote_sonic``).""" +import ast import re -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Tuple -from osism.utils.hardware import natural_key, normalize_mac +from osism.utils.hardware import ( + natural_key, + normalize_date_code, + normalize_mac, + transceiver_record, +) MGMT_PORT = "eth0" _FRONT_PANEL = re.compile(r"^Ethernet\d+$") +_VERSION_LINE = re.compile(r"^\s*([A-Za-z_]+)\s*:\s*(.*?)\s*$") +_SYSEEPROM_LINE = re.compile(r"^(.+?)\s+0x[0-9A-Fa-f]{2}\s+\d+\s+(.*)$") +_OUI = re.compile(r"^[0-9A-Fa-f]{2}([:-])[0-9A-Fa-f]{2}\1[0-9A-Fa-f]{2}$") +# "SONiC.202311.1" on community SONiC, "SONiC-OS-4.5.2" on Enterprise SONiC +_VERSION_PREFIX = re.compile(r"^SONiC(?:\.|-OS-)") + +# Values SONiC writes for "no information" +_MISSING = {"", "n/a", "na", "none", "unknown", "not available"} + +# Field names in TRANSCEIVER_INFO differ between community SONiC releases +# and Enterprise SONiC. Each output field has a list of candidate keys; the +# first one present with a value wins. +TRANSCEIVER_KEYS = { + "form_factor": ["type_abbrv_name", "type", "form_factor"], + "connector": ["connector", "connector_type"], + "vendor": ["manufacturer", "vendor_name", "manufacturename"], + "vendor_oui": ["vendor_oui"], + "part_number": [ + "model", + "vendor_pn", + "modelname", + "vendor_part_number", + "part_number", + ], + "serial": [ + "serial", + "vendor_sn", + "serialnum", + "vendor_serial_number", + "serial_number", + ], + "revision": ["hardware_rev", "vendor_rev", "hardwarerev", "vendor_revision"], + "date_code": ["vendor_date", "vendor_date_code", "date_code"], + "firmware_active": [ + "active_firmware", + "active_firmware_version", + "firmware_version", + ], + "firmware_inactive": ["inactive_firmware", "inactive_firmware_version"], + "compliance": ["specification_compliance", "spec_compliance", "compliance"], + "cable_length": ["cable_length", "length_cable_assembly", "length"], + "wavelength": ["nominal_wavelength", "wavelength"], +} + +# Names of the entries in EEPROM_INFO and "show platform syseeprom" +EEPROM_NAMES = { + "system_vendor": ["Manufacturer", "Vendor Name"], + "system_model": ["Product Name"], + "part_number": ["Part Number"], + "serial": ["Serial Number"], + "onie": ["ONIE Version"], +} def table( @@ -118,3 +176,301 @@ def parse_mgmt_port(document: Dict[str, Any]) -> Optional[Dict[str, Any]]: "permanent_mac": mac, "bond": None, } + + +def _clean(value: Any) -> Optional[str]: + text = str(value).strip() if value is not None else "" + return None if text.lower() in _MISSING else text + + +def _pick(fields: Dict[str, Any], candidates: List[str]) -> Optional[str]: + for key in candidates: + value = _clean(fields.get(key)) + if value is not None: + return value + return None + + +def _float(value: Any) -> Optional[float]: + try: + return float(value) + except (TypeError, ValueError): + return None + + +def _compliance(value: Optional[str]) -> Optional[str]: + """Return the compliance codes of a module as one line. + + SONiC stores them as the text of a Python dict, one entry per class of + codes: ``{'10/40G Ethernet Compliance Code': '100GBASE-CR4'}``. Only + the codes are kept. Anything else is returned as it is. + """ + if not value or not value.startswith("{"): + return value + try: + codes = ast.literal_eval(value) + except (ValueError, SyntaxError, RecursionError, MemoryError): + return value + if not isinstance(codes, dict): + return value + found = [text for text in (_clean(code) for code in codes.values()) if text] + return "; ".join(found) or None + + +def _vendor_oui(value: Optional[str]) -> Optional[str]: + """Return an OUI as ``ethtool`` prints it: ``64-9D-99`` is ``64:9d:99``.""" + if value and _OUI.match(value): + return value.replace("-", ":").lower() + return value + + +def _quantity(value: Optional[str], unit: str) -> Optional[str]: + """Return a number with its unit, as ``ethtool`` prints it. + + SONiC writes ``2.0`` for a cable of 2 m and ``850.00000000`` for 850 nm. + Zero stands for "not applicable": an optical module has no cable length. + A value that is not a number is returned as it is. + """ + number = _float(value) + if number is None: + return value + if number == 0: + return None + return f"{number:g}{unit}" + + +def _cable_length(fields: Dict[str, Any], value: Optional[str]) -> Optional[str]: + # The unit is part of the name of the EEPROM field the length was read + # from: "Length Cable Assembly(m)", but also "Length OM3(2m)". + unit = _clean(fields.get("cable_type")) + if unit and not unit.endswith("(m)") and _float(value): + return f"{value} ({unit})" + return _quantity(value, "m") + + +def parse_transceivers(document: Dict[str, Any]) -> Dict[str, Dict[str, Any]]: + """Return the transceiver record of every port that has a module. + + A port without an entry in ``TRANSCEIVER_INFO`` has no module plugged + in and is not part of the result. Values are written the way the Linux + parser writes them, so both ends of a cable read alike. + """ + info = table(document, "STATE_DB", "TRANSCEIVER_INFO") + firmware = table(document, "STATE_DB", "TRANSCEIVER_FIRMWARE_INFO") + + transceivers = {} + for name, fields in info.items(): + # The table also holds markers such as XcvrInitDone + if not _FRONT_PANEL.match(name): + continue + merged = dict(firmware.get(name) or {}) + merged.update({k: v for k, v in fields.items() if _clean(v) is not None}) + record = transceiver_record(True) + for output, candidates in TRANSCEIVER_KEYS.items(): + record[output] = _pick(merged, candidates) + record["vendor_oui"] = _vendor_oui(record["vendor_oui"]) + record["date_code"] = normalize_date_code(record["date_code"]) + record["compliance"] = _compliance(record["compliance"]) + record["cable_length"] = _cable_length(merged, record["cable_length"]) + record["wavelength"] = _quantity(record["wavelength"], "nm") + transceivers[name] = record + return transceivers + + +def parse_dom(document: Dict[str, Any]) -> Dict[str, Dict[str, Any]]: + """Return the diagnostic readings per port from TRANSCEIVER_DOM_SENSOR. + + Lanes are numbered from 1 (``rx1power``, ``tx1bias``, ``tx1power``). A + lane is listed when the module reports at least one value for it. A + module without diagnostics, such as a passive copper cable, has an + entry with ``N/A`` for every value and is not part of the result. + """ + readings = {} + for name, fields in table(document, "STATE_DB", "TRANSCEIVER_DOM_SENSOR").items(): + lanes = [] + for number in range(1, 9): + lane = { + "rx_power": _float(_clean(fields.get(f"rx{number}power"))), + "tx_power": _float(_clean(fields.get(f"tx{number}power"))), + "tx_bias": _float(_clean(fields.get(f"tx{number}bias"))), + } + if any(value is not None for value in lane.values()): + lanes.append(lane) + temperature = _float(_clean(fields.get("temperature"))) + voltage = _float(_clean(fields.get("voltage"))) + if temperature is None and voltage is None and not lanes: + continue + readings[name] = { + "temperature": temperature, + "voltage": voltage, + "lanes": lanes, + } + return readings + + +def parse_version_file(text: Optional[str]) -> Dict[str, str]: + """Parse ``/etc/sonic/sonic_version.yml``, a flat ``key: 'value'`` file.""" + values = {} + for line in (text or "").splitlines(): + match = _VERSION_LINE.match(line) + if match: + values[match.group(1)] = match.group(2).strip("'\"") + return values + + +def parse_show_version(text: Optional[str]) -> Dict[str, Optional[str]]: + """Parse the ``Key: Value`` lines of ``show version``.""" + values = {} + for line in (text or "").splitlines(): + key, separator, value = line.partition(":") + if separator: + values[key.strip().lower()] = value.strip() + version = values.get("sonic software version") or "" + return { + "os_version": _VERSION_PREFIX.sub("", version) or None, + "asic": values.get("asic") or None, + "platform": values.get("platform") or None, + "hwsku": values.get("hwsku") or None, + "serial": values.get("serial number") or None, + } + + +def parse_syseeprom(document: Dict[str, Any]) -> Dict[str, Optional[str]]: + """Return the chassis identity from the system EEPROM. + + ``EEPROM_INFO`` in STATE_DB is preferred. Its entries are keyed by TLV + code and carry ``Name`` and ``Value``. ``show platform syseeprom`` prints + the same as a table and is the fallback. + """ + names: Dict[str, str] = {} + for fields in table(document, "STATE_DB", "EEPROM_INFO").values(): + name, value = _clean(fields.get("Name")), _clean(fields.get("Value")) + if name and value: + names[name.lower()] = value + + if not names: + output = _command(document, "show platform syseeprom") + for line in (output or "").splitlines(): + match = _SYSEEPROM_LINE.match(line) + if match and _clean(match.group(2)): + names[match.group(1).strip().lower()] = match.group(2).strip() + + return { + output: next( + (names[name.lower()] for name in candidates if name.lower() in names), + None, + ) + for output, candidates in EEPROM_NAMES.items() + } + + +def parse_firmware_status(text: Optional[str]) -> Dict[str, str]: + """Parse the table of ``show platform firmware status``. + + The table is column-aligned with a line of dashes below the header. + Cells are cut at the positions of the dash groups, because values such + as the description contain spaces. + + Returns: + Version per component name, e.g. ``{"BIOS": "3.42", "CPLD1": "7"}`` + """ + lines = (text or "").splitlines() + for index, line in enumerate(lines): + if index and line.strip() and set(line.strip()) <= {"-", " "}: + break + else: + return {} + + spans = [match.span() for match in re.finditer(r"-+", line)] + header = [lines[index - 1][start:end].strip().lower() for start, end in spans] + if "component" not in header or "version" not in header: + return {} + component, version = header.index("component"), header.index("version") + + def cut(row: str, column: int) -> str: + start = spans[column][0] + # The last column is not padded and can be longer than its dashes + end = spans[column + 1][0] if column + 1 < len(spans) else len(row) + return row[start:end].strip() + + versions = {} + for row in lines[index + 1 :]: + name, value = cut(row, component), _clean(cut(row, version)) + if name and value: + versions[name] = value + return versions + + +def _command(document: Dict[str, Any], name: str) -> Optional[str]: + result = (document.get("commands") or {}).get(name) + if not isinstance(result, dict) or result.get("rc") != 0: + return None + return result.get("out") or "" + + +def parse_system(document: Dict[str, Any]) -> Dict[str, Any]: + """Return platform, software and chassis identity of a switch.""" + metadata = device_metadata(document) + version = parse_version_file((document.get("files") or {}).get("sonic_version")) + shown = parse_show_version(_command(document, "show version")) + eeprom = parse_syseeprom(document) + + components = parse_firmware_status( + _command(document, "show platform firmware status") + ) + cpld = { + name: value + for name, value in components.items() + if name.upper().startswith("CPLD") + } + if len(cpld) == 1: + cpld_text: Optional[str] = next(iter(cpld.values())) + else: + cpld_text = ", ".join(f"{name} {value}" for name, value in cpld.items()) or None + bios = next( + (value for name, value in components.items() if name.upper() == "BIOS"), None + ) + onie = next( + (value for name, value in components.items() if name.upper() == "ONIE"), None + ) + + return { + "hwsku": _clean(metadata.get("hwsku")) or shown["hwsku"], + "platform": _clean(metadata.get("platform")) or shown["platform"], + "asic": version.get("asic_type") or shown["asic"], + "os_version": version.get("build_version") or shown["os_version"], + "firmware": {"bios": bios, "cpld": cpld_text, "onie": onie or eeprom["onie"]}, + "system_vendor": eeprom["system_vendor"], + "system_model": eeprom["system_model"], + "part_number": eeprom["part_number"], + "serial": eeprom["serial"] or shown["serial"], + "base_mac": normalize_mac(metadata.get("mac")), + } + + +def parse_inventory( + document: Dict[str, Any], dom: bool = False +) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]: + """Return the hardware view of a switch. + + Only front-panel ports are part of it; the management port has no cage. + + Returns: + ``(system, interfaces)``: the switch itself, and one record per port + holding the port fields plus ``transceiver`` + """ + transceivers = parse_transceivers(document) + readings = parse_dom(document) if dom else {} + + interfaces = [] + for port in parse_ports(document): + port["autoneg"] = None + port["port_type"] = None + port["nic"] = None + record = transceivers.get(port["name"]) or transceiver_record(False) + if dom: + record["dom"] = readings.get(port["name"]) if record["present"] else None + port["transceiver"] = record + interfaces.append(port) + + return parse_system(document), interfaces diff --git a/osism/utils/hardware/remote_linux.py b/osism/utils/hardware/remote_linux.py index 1fc915795..794a38459 100644 --- a/osism/utils/hardware/remote_linux.py +++ b/osism/utils/hardware/remote_linux.py @@ -21,6 +21,7 @@ VERSION = 1 SYS_NET = "/sys/class/net" +SYS_DMI = "/sys/class/dmi/id" ARPHRD_ETHER = "1" ARPHRD_INFINIBAND = "32" @@ -129,9 +130,38 @@ def collect_port(name, kind, sys_net=SYS_NET, runner=run): ) except OSError: port["physfn"] = [] + if kind == "infiniband": + port["infiniband"] = collect_infiniband(base, port["sysfs"].get("dev_port")) return port +def collect_infiniband(base, dev_port): + """Collect link state and rate of an InfiniBand port. + + The netdev of an IPoIB port only knows its IB device. State and rate + are kept per port of that device, numbered from 1, while ``dev_port`` + counts from 0. + """ + directory = os.path.join(base, "device", "infiniband") + try: + devices = sorted(os.listdir(directory)) + except OSError: + return {} + if not devices: + return {} + try: + number = int(dev_port) + 1 + except (TypeError, ValueError): + number = 1 + port = os.path.join(directory, devices[0], "ports", str(number)) + return { + "device": devices[0], + "port": number, + "state": read(os.path.join(port, "state")), + "rate": read(os.path.join(port, "rate")), + } + + def section_ports(document, ports, sys_net, runner): document["ip_link"] = runner(["ip", "-j", "-d", "link", "show"]) for name, kind in ports.items(): @@ -142,9 +172,54 @@ def section_lldp(document, ports, sys_net, runner): document["lldpctl"] = runner(["lldpctl", "-f", "json"]) +def section_ethtool(document, ports, sys_net, runner): + for name in ports: + document["ports"][name].update( + { + "ethtool": runner(["ethtool", name]), + "ethtool_i": runner(["ethtool", "-i", name]), + "ethtool_fec": runner(["ethtool", "--show-fec", name]), + } + ) + + +def section_module(document, ports, sys_net, runner): + # Fails for a port without a cage and for an empty cage. That is a + # result, not an error, and is kept with its exit code. + for name in ports: + document["ports"][name]["ethtool_m"] = runner(["ethtool", "-m", name]) + + +def section_nic(document, ports, sys_net, runner): + # Several ports can share one PCI function, and devlink reports per + # function, so each address is queried once. + cache = {} + for name in ports: + address = os.path.basename( + os.path.realpath(os.path.join(sys_net, name, "device")) + ) + if address not in cache: + cache[address] = { + "lspci": runner(["lspci", "-D", "-vmm", "-nn", "-s", address]), + "devlink": runner(["devlink", "-j", "dev", "info", "pci/" + address]), + } + document["ports"][name].update(cache[address]) + + +def section_system(document, ports, sys_net, runner): + document["dmi"] = dict( + (attribute, read(os.path.join(SYS_DMI, attribute))) + for attribute in ("sys_vendor", "product_name", "product_serial") + ) + + SECTIONS = { "ports": section_ports, "lldp": section_lldp, + "ethtool": section_ethtool, + "module": section_module, + "nic": section_nic, + "system": section_system, } diff --git a/osism/utils/hardware/remote_sonic.py b/osism/utils/hardware/remote_sonic.py index 680d519d9..baeb7c423 100644 --- a/osism/utils/hardware/remote_sonic.py +++ b/osism/utils/hardware/remote_sonic.py @@ -22,6 +22,7 @@ VERSION = 1 SYS_NET = "/sys/class/net" MGMT_PORT = "eth0" +VERSION_FILE = "/etc/sonic/sonic_version.yml" # Tables dumped per section: (database, table, key pattern, mandatory) TABLES = { @@ -30,6 +31,23 @@ ("CONFIG_DB", "DEVICE_METADATA", "DEVICE_METADATA|localhost", False), ("APPL_DB", "PORT_TABLE", "PORT_TABLE:*", False), ], + "transceiver": [ + ("STATE_DB", "TRANSCEIVER_INFO", "TRANSCEIVER_INFO|*", False), + # Newer releases keep the CMIS firmware versions in a table of + # their own instead of in TRANSCEIVER_INFO. + ( + "STATE_DB", + "TRANSCEIVER_FIRMWARE_INFO", + "TRANSCEIVER_FIRMWARE_INFO|*", + False, + ), + ], + "dom": [ + ("STATE_DB", "TRANSCEIVER_DOM_SENSOR", "TRANSCEIVER_DOM_SENSOR|*", False), + ], + "system": [ + ("STATE_DB", "EEPROM_INFO", "EEPROM_INFO|*", False), + ], } @@ -101,8 +119,24 @@ def extra_ports(document, sys_net, runner): } +def extra_system(document, sys_net, runner): + document["files"] = {"sonic_version": read(VERSION_FILE)} + commands = {} + # The show commands are fallbacks for what the files and tables above + # did not deliver, and the only source for the firmware versions. + if not document["files"]["sonic_version"]: + commands["show version"] = runner(["show", "version"]) + if not (document["db"].get("STATE_DB") or {}).get("EEPROM_INFO"): + commands["show platform syseeprom"] = runner(["show", "platform", "syseeprom"]) + commands["show platform firmware status"] = runner( + ["show", "platform", "firmware", "status"] + ) + document["commands"] = commands + + EXTRAS = { "ports": extra_ports, + "system": extra_system, } diff --git a/setup.cfg b/setup.cfg index bafd77e5e..55e5f3f42 100644 --- a/setup.cfg +++ b/setup.cfg @@ -130,6 +130,7 @@ osism.commands: report status = osism.commands.report:Status report lldp = osism.commands.report:Lldp report memory = osism.commands.report:Memory + report transceiver = osism.commands.report:Transceiver reconciler = osism.commands.reconciler:Run reconciler sync = osism.commands.reconciler:Sync reset facts = osism.commands.reset:Facts diff --git a/tests/unit/commands/test_report.py b/tests/unit/commands/test_report.py index 517cd9a16..343aca865 100644 --- a/tests/unit/commands/test_report.py +++ b/tests/unit/commands/test_report.py @@ -12,16 +12,28 @@ ``subprocess.run`` results: parsed rows must land in the printed table with the documented defaults, while hosts whose output cannot be read or parsed must end up in the failure summary instead of the table. + +The hardware reports (``report transceiver``) select devices through NetBox +and collect over SSH in parallel. Their tests replace selection, collection +and the NetBox interface fetch, and feed the collector documents that are +also used as parser fixtures. """ +import csv +import io import json import subprocess from contextlib import contextmanager +from pathlib import Path +from types import SimpleNamespace from unittest.mock import MagicMock, patch import pytest from osism.commands import report +from osism.utils.hardware.collect import Collection +from osism.utils.hardware.mapping import Endpoint, NetboxInterface +from osism.utils.hardware.selection import Target COMMANDS = [report.Memory, report.Lldp, report.Bgp, report.Status] @@ -420,3 +432,583 @@ def test_status_marks_host_failed_on_unparsable_facts(loguru_logs): messages = [record["message"] for record in loguru_logs] assert any("Could not parse bootstrap info from host-a." in m for m in messages) assert any("Failed to query 1 host(s): host-a" in m for m in messages) + + +# --------------------------------------------------------------------------- +# Hardware reports: shared helpers +# --------------------------------------------------------------------------- + +HARDWARE_FIXTURES = Path(__file__).parents[1] / "utils" / "hardware" / "fixtures" + + +def _document(name): + return json.loads((HARDWARE_FIXTURES / name).read_text()) + + +def _target(name, kind, device_id=None, **device): + return Target( + name=name, + hostname=name, + kind=kind, + address="192.0.2.1", + user="admin" if kind == "switch" else "dragon", + device_id=device_id, + device=dict(device, id=device_id, name=name) if device_id else {}, + ) + + +def _netbox_interface(interface_id, device, name, peer=None, **kwargs): + endpoints = [Endpoint(device=peer[0], interface=peer[1])] if peer else [] + return NetboxInterface( + id=interface_id, + device_id=kwargs.pop("device_id", 1), + device=device, + name=name, + has_cable=bool(peer), + endpoints=endpoints, + **kwargs, + ) + + +@contextmanager +def _hardware_env(targets, documents, failed=(), interfaces=None, netbox=True): + """Replace selection, collection and NetBox for a hardware report. + + Yields the mocks, so that a test can look at what was requested. + """ + collection = Collection(documents=dict(documents), failed=list(failed)) + with patch( + "osism.commands.report.ensure_known_hosts_file", return_value=True + ), patch( + "osism.commands.report.select_targets", return_value=list(targets) + ) as select, patch( + "osism.commands.report.resolve_addresses" + ) as resolve, patch( + "osism.commands.report.collect", return_value=collection + ) as collect, patch( + "osism.commands.report.fetch_interfaces", return_value=interfaces or {} + ) as fetch, patch( + "osism.commands.report.sonic_name_index", return_value={} + ), patch( + "osism.utils.nb", new=MagicMock() if netbox else None, create=True + ): + yield SimpleNamespace( + select=select, resolve=resolve, collect=collect, fetch=fetch + ) + + +def _run(cls, argv, *args, **kwargs): + """Run a hardware report and return ``(exit code, mocks)``.""" + cmd = cls(MagicMock(), MagicMock()) + parsed_args = cmd.get_parser("test").parse_args(argv) + with _hardware_env(*args, **kwargs) as mocks: + return cmd.take_action(parsed_args), mocks + + +def _logged(records, level, substring): + return any(r["level"] == level and substring in r["message"] for r in records) + + +# --------------------------------------------------------------------------- +# report transceiver +# --------------------------------------------------------------------------- + +NODE = _target("compute-042", "node", device_id=10, serial="NETBOX-SERIAL") +SWITCH = _target( + "leaf-1a", + "switch", + device_id=1, + site={"slug": "fra1"}, + rack={"name": "R12"}, + role={"slug": "leaf"}, +) +DOCUMENTS = { + "compute-042": _document("linux-mellanox.json"), + "leaf-1a": _document("sonic-synthetic.json"), +} +INTERFACES = { + 10: [ + _netbox_interface( + 101, + "compute-042", + "nic1-port1", + peer=("leaf-1a", "Eth1/12"), + device_id=10, + macs=["b8:ce:f6:aa:00:10"], + ), + _netbox_interface(102, "compute-042", "enp59s0f1np1", device_id=10), + _netbox_interface(103, "compute-042", "bmc", device_id=10, mgmt_only=True), + _netbox_interface(104, "compute-042", "bond0", device_id=10, type="lag"), + _netbox_interface(105, "compute-042", "ens9f0", device_id=10), + ], + 1: [ + _netbox_interface(1, "leaf-1a", "Eth1/1"), + _netbox_interface(12, "leaf-1a", "Eth1/12", peer=("compute-042", "nic1-port1")), + _netbox_interface(49, "leaf-1a", "Ethernet48"), + _netbox_interface(99, "leaf-1a", "eth0", mgmt_only=True), + ], +} + + +def _transceiver_json(argv=(), **kwargs): + kwargs.setdefault("interfaces", INTERFACES) + stdout = io.StringIO() + with patch("sys.stdout", stdout): + result, mocks = _run( + report.Transceiver, + ["--format", "json", *argv], + kwargs.pop("targets", [NODE, SWITCH]), + kwargs.pop("documents", DOCUMENTS), + **kwargs, + ) + return result, json.loads(stdout.getvalue()), mocks + + +def _by_name(document, device): + (entry,) = [d for d in document["devices"] if d["name"] == device] + return entry, {i["name"]: i for i in entry["interfaces"]} + + +def test_transceiver_reports_every_port_of_nodes_and_switches(capsys): + result, _ = _run( + report.Transceiver, [], [NODE, SWITCH], DOCUMENTS, interfaces=INTERFACES + ) + + assert not result + out = capsys.readouterr().out + for port in ("eno1", "eno2", "ens1f0np0", "ens1f1np1", "Ethernet0", "Ethernet52"): + assert f"| {port} " in out + assert "MCP2M00-A003E30L" in out + assert "MT27800 Family [ConnectX-5]" in out + assert "Devices: 2" in out + assert "Ports: 11" in out + assert "Modules present: 6" in out + + +def test_transceiver_collects_with_one_session_per_device(): + _, mocks = _run(report.Transceiver, [], [NODE, SWITCH], DOCUMENTS) + + targets, requests = mocks.collect.call_args.args + assert [target.name for target in targets] == ["compute-042", "leaf-1a"] + assert requests["node"].sections == [ + "ports", + "lldp", + "ethtool", + "module", + "nic", + "system", + ] + assert requests["node"].sudo is True + assert requests["node"].options == [] + assert requests["switch"].sections == ["ports", "transceiver", "system"] + assert requests["switch"].sudo is False + assert mocks.collect.call_args.kwargs == {"parallel": 10, "timeout": 60} + mocks.resolve.assert_called_once() + + +def test_transceiver_options_reach_the_collectors(): + _, mocks = _run( + report.Transceiver, + [ + "--include-vf", + "--include-infiniband", + "--dom", + "--parallel", + "3", + "--timeout", + "5", + ], + [NODE, SWITCH], + DOCUMENTS, + ) + + requests = mocks.collect.call_args.args[1] + assert requests["node"].options == ["--include-vf", "--include-infiniband"] + assert requests["switch"].sections == ["ports", "transceiver", "system", "dom"] + assert mocks.collect.call_args.kwargs == {"parallel": 3, "timeout": 5.0} + + +def test_transceiver_node_rows_carry_port_module_and_nic(): + _, document, _ = _transceiver_json() + + device, ports = _by_name(document, "compute-042") + assert device["kind"] == "node" + assert device["system_vendor"] == "Supermicro" + assert device["system_model"] == "SYS-120C-TN10R" + # The serial read from the node wins over the one in NetBox + assert device["serial"] == "S123456X0A01234" + assert "hwsku" not in device + + port = ports["ens1f0np0"] + assert (port["admin_status"], port["oper_status"], port["speed"]) == ( + "up", + "up", + 25000, + ) + assert port["kind"] == "physical" + assert port["transceiver"]["vendor"] == "Mellanox" + assert port["transceiver"]["part_number"] == "MCP2M00-A003E30L" + assert port["transceiver"]["serial"] == "MT2114VS01234" + assert port["transceiver"]["revision"] == "A3" + assert port["nic"]["vendor"] == "Mellanox Technologies" + assert port["nic"]["model"] == "MT27800 Family [ConnectX-5]" + assert port["nic"]["driver"] == "mlx5_core" + assert port["nic"]["firmware_version"] == "16.35.2000 (MT_0000000080)" + assert port["lldp_neighbor"] == "leaf-1a" + assert port["lldp_neighbor_port"] == "Eth1/12" + + +def test_transceiver_switch_rows_carry_platform_and_software(): + _, document, _ = _transceiver_json() + + device, ports = _by_name(document, "leaf-1a") + assert device["kind"] == "switch" + assert device["site"] == "fra1" + assert device["rack"] == "R12" + assert device["platform"] == "x86_64-accton_as7326_56x-r0" + assert device["hwsku"] == "Accton-AS7326-56X" + assert device["asic"] == "broadcom" + assert device["os_version"] == "4.5.0-Enterprise_Base" + assert device["firmware"]["bios"] == "v41.01.00" + assert ports["Ethernet48"]["transceiver"]["part_number"] == "0W8H6T" + assert ports["Ethernet48"]["nic"] is None + assert "eth0" not in ports + + +def test_transceiver_ports_without_module_are_rows_not_failures(): + result, document, _ = _transceiver_json() + + _, node = _by_name(document, "compute-042") + _, switch = _by_name(document, "leaf-1a") + assert node["eno1"]["transceiver"]["present"] is False + assert switch["Ethernet52"]["transceiver"]["present"] is False + assert document["failed"] == [] + assert not result + + +def test_transceiver_shows_netbox_name_and_mapping_method(): + _, document, _ = _transceiver_json() + + node_device, node = _by_name(document, "compute-042") + switch_device, switch = _by_name(document, "leaf-1a") + + assert (node["ens1f0np0"]["netbox_name"], node["ens1f0np0"]["mapping"]) == ( + "nic1-port1", + "mac", + ) + assert node["ens1f0np0"]["netbox_peer_device"] == "leaf-1a" + assert node["ens1f0np0"]["netbox_peer_interface"] == "Eth1/12" + assert (node["ens1f1np1"]["netbox_name"], node["ens1f1np1"]["mapping"]) == ( + "enp59s0f1np1", + "name", + ) + assert (node["eno1"]["netbox_name"], node["eno1"]["mapping"]) == (None, "none") + + assert (switch["Ethernet0"]["netbox_name"], switch["Ethernet0"]["mapping"]) == ( + "Eth1/1", + "alias", + ) + assert (switch["Ethernet48"]["netbox_name"], switch["Ethernet48"]["mapping"]) == ( + "Ethernet48", + "name", + ) + assert switch["Ethernet11"]["netbox_peer_device"] == "compute-042" + + # Documented interfaces without a live port. Management and LAG + # interfaces have no cage and are not expected to show up. + assert node_device["unmatched_netbox_interfaces"] == ["ens9f0"] + assert switch_device["unmatched_netbox_interfaces"] == [] + + +def test_transceiver_uses_the_port_config_translation(): + eth1_1 = INTERFACES[1][0] + cmd = report.Transceiver(MagicMock(), MagicMock()) + parsed_args = cmd.get_parser("test").parse_args(["--format", "json"]) + stdout = io.StringIO() + + with _hardware_env([SWITCH], DOCUMENTS, interfaces=INTERFACES), patch( + "osism.commands.report.sonic_name_index", return_value={"Ethernet10": eth1_1} + ) as index, patch("sys.stdout", stdout): + cmd.take_action(parsed_args) + + _, ports = _by_name(json.loads(stdout.getvalue()), "leaf-1a") + assert (ports["Ethernet10"]["netbox_name"], ports["Ethernet10"]["mapping"]) == ( + "Eth1/1", + "port-config", + ) + assert ports["Ethernet0"]["mapping"] == "none" + # HWSKU read from the switch, since the NetBox device has none here + assert index.call_args.args[:3] == (1, "leaf-1a", "Accton-AS7326-56X") + + +def test_transceiver_warns_about_unmatched_netbox_interfaces(capsys, loguru_logs): + _run(report.Transceiver, [], [NODE, SWITCH], DOCUMENTS, interfaces=INTERFACES) + + assert _logged( + loguru_logs, + "WARNING", + "compute-042: 1 NetBox interface(s) without a live port: ens9f0", + ) + assert not _logged(loguru_logs, "WARNING", "leaf-1a: ") + + +def test_transceiver_failed_device_does_not_abort_the_report(capsys, loguru_logs): + failed = [{"name": "compute-043", "error": "timeout"}] + targets = [NODE, _target("compute-043", "node", device_id=11), SWITCH] + + result, _ = _run(report.Transceiver, [], targets, DOCUMENTS, failed=failed) + + assert not result + out = capsys.readouterr().out + assert "ens1f0np0" in out and "Ethernet0" in out + assert "Devices: 2" in out + assert _logged( + loguru_logs, "WARNING", "Failed to query 1 device(s): compute-043 (timeout)" + ) + + +def test_transceiver_strict_turns_a_failed_device_into_exit_code_1(): + failed = [{"name": "compute-043", "error": "timeout"}] + + result, _ = _run(report.Transceiver, ["--strict"], [NODE], DOCUMENTS, failed=failed) + assert result == 1 + + result, _ = _run(report.Transceiver, ["--strict"], [NODE], DOCUMENTS) + assert not result + + +def test_transceiver_lists_failed_devices_in_json(): + failed = [{"name": "compute-043", "error": "Permission denied (publickey)."}] + + _, document, _ = _transceiver_json(failed=failed) + + assert document["failed"] == failed + assert [device["name"] for device in document["devices"]] == [ + "compute-042", + "leaf-1a", + ] + assert document["generated_at"].endswith("Z") + + +def test_transceiver_unparsable_document_is_a_failed_device(loguru_logs): + documents = dict(DOCUMENTS, **{"leaf-1a": {"db": "garbage"}}) + + result, document, _ = _transceiver_json(documents=documents) + + assert not result + assert document["failed"] == [{"name": "leaf-1a", "error": "unparsable output"}] + assert [device["name"] for device in document["devices"]] == ["compute-042"] + + +def test_transceiver_json_on_stdout_is_one_document(capsys, loguru_logs): + failed = [{"name": "compute-043", "error": "timeout"}] + + _run( + report.Transceiver, + ["--format", "json"], + [NODE, SWITCH], + DOCUMENTS, + failed=failed, + interfaces=INTERFACES, + ) + + # Warnings go to the log, stdout parses as a whole + document = json.loads(capsys.readouterr().out) + assert sorted(document) == ["devices", "failed", "generated_at"] + assert _logged(loguru_logs, "WARNING", "Failed to query 1 device(s)") + + +def test_transceiver_csv_has_a_header_and_no_footer(capsys): + _run( + report.Transceiver, + ["--format", "csv"], + [NODE, SWITCH], + DOCUMENTS, + interfaces=INTERFACES, + ) + + rows = list(csv.reader(io.StringIO(capsys.readouterr().out))) + assert rows[0][:5] == [ + "device_name", + "interface_name", + "interface_netbox_name", + "interface_mapping", + "interface_admin_status", + ] + assert len(rows) == 12 + assert all(len(row) == len(rows[0]) for row in rows) + by_port = {(row[0], row[1]): row for row in rows[1:]} + assert by_port[("leaf-1a", "Ethernet0")][2:4] == ["Eth1/1", "alias"] + # Natural order: Ethernet2 before Ethernet10 + names = [row[1] for row in rows[1:] if row[0] == "leaf-1a"] + assert names.index("Ethernet2") < names.index("Ethernet10") + + +def test_transceiver_column_and_long_select_the_columns(capsys): + _run( + report.Transceiver, + ["--format", "csv", "--column", "Serial", "--column", "device_name"], + [NODE], + DOCUMENTS, + ) + rows = list(csv.reader(io.StringIO(capsys.readouterr().out))) + assert rows[0] == ["transceiver_serial", "device_name"] + assert ["MT2114VS01234", "compute-042"] in rows + + _run(report.Transceiver, ["--format", "csv", "--long"], [NODE], DOCUMENTS) + header = list(csv.reader(io.StringIO(capsys.readouterr().out)))[0] + assert len(header) == len(report.TRANSCEIVER_COLUMNS) + assert "nic_driver" in header and "transceiver_vendor_oui" in header + + +def test_transceiver_rejects_unknown_columns_before_connecting(loguru_logs): + result, mocks = _run(report.Transceiver, ["--column", "bogus"], [NODE], DOCUMENTS) + + assert result == 1 + mocks.select.assert_not_called() + mocks.collect.assert_not_called() + assert _logged(loguru_logs, "ERROR", "No valid columns specified") + + +def test_transceiver_output_writes_a_file(tmp_path, capsys): + path = tmp_path / "optics.csv" + + _run( + report.Transceiver, + ["--format", "csv", "--output", str(path)], + [NODE], + DOCUMENTS, + ) + + assert capsys.readouterr().out == "" + assert path.read_text().splitlines()[0].startswith("device_name,interface_name") + + +def test_transceiver_dom_adds_the_readings(): + _, document, _ = _transceiver_json(["--dom"], targets=[NODE]) + + _, ports = _by_name(document, "compute-042") + assert ports["ens1f1np1"]["transceiver"]["dom"]["temperature"] == 33.45 + assert ports["ens1f0np0"]["transceiver"]["dom"] is None + + _, document, _ = _transceiver_json(targets=[NODE]) + _, ports = _by_name(document, "compute-042") + assert "dom" not in ports["ens1f1np1"]["transceiver"] + + +def test_transceiver_marks_virtual_functions_and_infiniband_ports(): + target = _target("gpu-001", "node") + documents = {"gpu-001": _document("linux-mellanox-vf-ib.json")} + + _, document, _ = _transceiver_json( + ["--include-vf", "--include-infiniband"], + targets=[target], + documents=documents, + interfaces={}, + ) + + _, ports = _by_name(document, "gpu-001") + assert {name: port["kind"] for name, port in ports.items()} == { + "ens3f0np0": "physical", + "ens3f0v0": "vf", + "ibp129s0": "infiniband", + } + assert ports["ens3f0v0"]["parent"] == "ens3f0np0" + + +@pytest.mark.parametrize( + "argv,expected", + [ + (["--present"], ["ens1f0np0", "ens1f1np1"]), + (["--oper-status", "down"], ["eno2"]), + (["--interface", "eno*"], ["eno1", "eno2"]), + (["--interface", "nic1-*", "--interface", "eno2"], ["eno2", "ens1f0np0"]), + ( + ["--interface", "ens*", "--present", "--oper-status", "up"], + ["ens1f0np0", "ens1f1np1"], + ), + ], +) +def test_transceiver_row_filters(argv, expected): + _, document, _ = _transceiver_json(argv, targets=[NODE]) + + _, ports = _by_name(document, "compute-042") + assert sorted(ports) == expected + + +def test_transceiver_without_netbox_reports_nodes_without_mapping(): + target = _target("compute-042", "node") + + _, document, mocks = _transceiver_json( + targets=[target], interfaces={}, netbox=False + ) + + device, ports = _by_name(document, "compute-042") + assert ports["ens1f0np0"]["mapping"] == "none" + assert ports["ens1f0np0"]["netbox_name"] is None + assert ports["ens1f0np0"]["transceiver"]["vendor"] == "Mellanox" + assert device["unmatched_netbox_interfaces"] == [] + assert device["site"] is None + mocks.fetch.assert_not_called() + + +def test_transceiver_without_netbox_rejects_netbox_filters(loguru_logs): + cmd = report.Transceiver(MagicMock(), MagicMock()) + parsed_args = cmd.get_parser("test").parse_args(["--site", "fra1"]) + + with patch( + "osism.commands.report.ensure_known_hosts_file", return_value=True + ), patch("osism.utils.nb", new=None, create=True), patch( + "osism.commands.report.collect" + ) as collect: + result = cmd.take_action(parsed_args) + + assert result == 1 + collect.assert_not_called() + assert _logged(loguru_logs, "ERROR", "NetBox is not configured") + + +def test_transceiver_netbox_failure_after_collection_keeps_the_report(loguru_logs): + cmd = report.Transceiver(MagicMock(), MagicMock()) + parsed_args = cmd.get_parser("test").parse_args(["--format", "json"]) + stdout = io.StringIO() + + with _hardware_env([NODE], DOCUMENTS), patch( + "osism.commands.report.fetch_interfaces", side_effect=RuntimeError("503") + ), patch("sys.stdout", stdout): + result = cmd.take_action(parsed_args) + + assert not result + _, ports = _by_name(json.loads(stdout.getvalue()), "compute-042") + assert ports["ens1f0np0"]["mapping"] == "none" + assert _logged(loguru_logs, "WARNING", "Could not load interfaces from NetBox: 503") + + +def test_transceiver_dump_raw_writes_the_collected_documents(tmp_path, loguru_logs): + directory = tmp_path / "capture" + + _run( + report.Transceiver, + ["--dump-raw", str(directory)], + [NODE, SWITCH], + DOCUMENTS, + ) + + assert sorted(path.name for path in directory.iterdir()) == [ + "compute-042.json", + "leaf-1a.json", + ] + assert json.loads((directory / "leaf-1a.json").read_text()) == DOCUMENTS["leaf-1a"] + assert _logged(loguru_logs, "INFO", "Wrote 2 raw document(s)") + + +def test_transceiver_is_registered_as_a_command(): + import configparser + + config = configparser.ConfigParser() + config.read(Path(__file__).parents[3] / "setup.cfg") + + assert ( + "report transceiver = osism.commands.report:Transceiver" + in config["options.entry_points"]["osism.commands"] + ) diff --git a/tests/unit/utils/hardware/fixtures/README.md b/tests/unit/utils/hardware/fixtures/README.md index 5c376fe7c..8c7eb6fa0 100644 --- a/tests/unit/utils/hardware/fixtures/README.md +++ b/tests/unit/utils/hardware/fixtures/README.md @@ -9,9 +9,15 @@ the `parse_*` modules. | File | Origin | Device | | --- | --- | --- | | `linux-connectx4.json` | captured | Node with an Intel I350 (`igb`) and a Mellanox ConnectX-4 Lx (`mlx5_core`) on 25G passive copper cables, Ubuntu 24.04 | -| `sonic-enterprise.json` | captured | Accton AS7726-32X with Enterprise SONiC 4.5.2, QSFP28 cages broken out into 4x25G and 4x10G | -| `linux-mellanox.json` | synthetic | Node with a bond, an optical module and an empty cage | +| `linux-bcm57414.json` | captured | Node with a Broadcom BCM57414 (`bnxt_en`) on 25G passive copper cables, an Intel X710 10GBASE-T (`i40e`) and the USB host interface of its BMC | +| `linux-82599.json` | captured | Node with an Intel 82599ES (`ixgbe`) on 10G, cabled with 25G passive copper cables, and the USB host interface of its BMC | +| `sonic-enterprise.json` | captured | Accton AS7726-32X with Enterprise SONiC 4.5.2: QSFP28 cages broken out into 4x25G and 4x10G with passive copper cables, 100G SR4 optics with diagnostics, empty cages | +| `linux-mellanox.json` | synthetic | Node with a bond, an optical module with diagnostics and an empty cage | | `sonic-synthetic.json` | synthetic | Switch `leaf-1a`, the cable peer of `linux-mellanox.json` in the tests of the report commands | +| `linux-intel.json` | synthetic | Node with a multi-lane optical module and a CMIS module | +| `linux-broadcom.json` | synthetic | Node without `lspci`, `devlink` and `lldpctl` | +| `linux-mellanox-vf-ib.json` | synthetic | Node with SR-IOV virtual functions and an InfiniBand port | +| `sonic-community.json` | synthetic | Switch with the field names of community SONiC, a CMIS module, and without version file, EEPROM table and firmware status | ### Captured files diff --git a/tests/unit/utils/hardware/fixtures/linux-82599.json b/tests/unit/utils/hardware/fixtures/linux-82599.json new file mode 100644 index 000000000..5c4c16609 --- /dev/null +++ b/tests/unit/utils/hardware/fixtures/linux-82599.json @@ -0,0 +1,180 @@ +{ + "collector": "linux", + "dmi": { + "product_name": "SYS-530MT-H12TRF", + "product_serial": "SN00000012", + "sys_vendor": "Supermicro" + }, + "hostname": "node-5", + "ip_link": { + "err": "", + "out": "[{\"ifindex\":1,\"ifname\":\"lo\",\"flags\":[\"LOOPBACK\",\"UP\",\"LOWER_UP\"],\"mtu\":65536,\"qdisc\":\"noqueue\",\"operstate\":\"UNKNOWN\",\"linkmode\":\"DEFAULT\",\"group\":\"default\",\"txqlen\":1000,\"link_type\":\"loopback\",\"address\":\"02:00:00:00:00:18\",\"broadcast\":\"02:00:00:00:00:18\",\"promiscuity\":0,\"allmulti\":0,\"min_mtu\":0,\"max_mtu\":0,\"inet6_addr_gen_mode\":\"eui64\",\"num_tx_queues\":1,\"num_rx_queues\":1,\"gso_max_size\":65536,\"gso_max_segs\":65535,\"tso_max_size\":524280,\"tso_max_segs\":65535,\"gro_max_size\":65536},{\"ifindex\":2,\"ifname\":\"data1\",\"flags\":[\"BROADCAST\",\"MULTICAST\",\"UP\",\"LOWER_UP\"],\"mtu\":9100,\"qdisc\":\"mq\",\"operstate\":\"UP\",\"linkmode\":\"DEFAULT\",\"group\":\"default\",\"txqlen\":1000,\"link_type\":\"ether\",\"address\":\"02:00:00:00:00:07\",\"broadcast\":\"02:00:00:00:00:1a\",\"promiscuity\":0,\"allmulti\":0,\"min_mtu\":68,\"max_mtu\":9710,\"inet6_addr_gen_mode\":\"eui64\",\"num_tx_queues\":64,\"num_rx_queues\":64,\"gso_max_size\":65536,\"gso_max_segs\":65535,\"tso_max_size\":65536,\"tso_max_segs\":65535,\"gro_max_size\":65536,\"phys_switch_id\":\"020000ffff000007\",\"parentbus\":\"pci\",\"parentdev\":\"0000:01:00.0\",\"vfinfo_list\":[]},{\"ifindex\":3,\"ifname\":\"enx02000000001f\",\"flags\":[\"BROADCAST\",\"MULTICAST\"],\"mtu\":1500,\"qdisc\":\"noop\",\"operstate\":\"DOWN\",\"linkmode\":\"DEFAULT\",\"group\":\"default\",\"txqlen\":1000,\"link_type\":\"ether\",\"address\":\"02:00:00:00:00:1f\",\"broadcast\":\"02:00:00:00:00:1a\",\"promiscuity\":0,\"allmulti\":0,\"min_mtu\":0,\"max_mtu\":1500,\"inet6_addr_gen_mode\":\"eui64\",\"num_tx_queues\":1,\"num_rx_queues\":1,\"gso_max_size\":65536,\"gso_max_segs\":65535,\"tso_max_size\":65536,\"tso_max_segs\":65535,\"gro_max_size\":65536,\"parentbus\":\"usb\",\"parentdev\":\"1-13.2:2.0\"},{\"ifindex\":4,\"ifname\":\"data2\",\"flags\":[\"BROADCAST\",\"MULTICAST\",\"UP\",\"LOWER_UP\"],\"mtu\":9100,\"qdisc\":\"mq\",\"operstate\":\"UP\",\"linkmode\":\"DEFAULT\",\"group\":\"default\",\"txqlen\":1000,\"link_type\":\"ether\",\"address\":\"02:00:00:00:00:08\",\"broadcast\":\"02:00:00:00:00:1a\",\"promiscuity\":0,\"allmulti\":0,\"min_mtu\":68,\"max_mtu\":9710,\"inet6_addr_gen_mode\":\"eui64\",\"num_tx_queues\":64,\"num_rx_queues\":64,\"gso_max_size\":65536,\"gso_max_segs\":65535,\"tso_max_size\":65536,\"tso_max_segs\":65535,\"gro_max_size\":65536,\"phys_switch_id\":\"020000ffff000008\",\"parentbus\":\"pci\",\"parentdev\":\"0000:01:00.1\",\"vfinfo_list\":[]}]\n", + "rc": 0 + }, + "lldpctl": { + "err": "", + "out": "{\n \"lldp\": {\n \"interface\": [\n {\n \"data1\": {\n \"via\": \"LLDP\",\n \"rid\": \"3\",\n \"age\": \"4 days, 19:56:33\",\n \"chassis\": {\n \"leaf-2\": {\n \"id\": {\n \"type\": \"mac\",\n \"value\": \"02:00:00:00:00:1e\"\n },\n \"descr\": \"SONiC Software Version: SONiC.4.4.1-Enterprise_Base - HwSku: Accton-AS7726-32X - Distribution: Debian 11.11 - Kernel: 5.10.0-21-amd64\",\n \"mgmt-ip\": \"192.0.2.3\",\n \"capability\": [\n {\n \"type\": \"Bridge\",\n \"enabled\": false\n },\n {\n \"type\": \"Router\",\n \"enabled\": true\n },\n {\n \"type\": \"Wlan\",\n \"enabled\": false\n },\n {\n \"type\": \"Station\",\n \"enabled\": false\n }\n ]\n }\n },\n \"port\": {\n \"id\": {\n \"type\": \"local\",\n \"value\": \"Eth1/6/3\"\n },\n \"descr\": \"Ethernet22\",\n \"ttl\": \"120\",\n \"mfs\": \"9100\",\n \"auto-negotiation\": {\n \"supported\": false,\n \"enabled\": false,\n \"current\": \"10GigBaseCX4 - X copper over 8 pair 100-Ohm balanced cable\"\n }\n },\n \"lldp-med\": {\n \"device-type\": \"Network Connectivity Device\",\n \"capability\": [\n {\n \"type\": \"Capabilities\",\n \"available\": true\n },\n {\n \"type\": \"Policy\",\n \"available\": true\n },\n {\n \"type\": \"Location\",\n \"available\": true\n },\n {\n \"type\": \"MDI/PSE\",\n \"available\": true\n },\n {\n \"type\": \"MDI/PD\",\n \"available\": true\n },\n {\n \"type\": \"Inventory\",\n \"available\": true\n }\n ],\n \"inventory\": {\n \"hardware\": \"R01H\",\n \"software\": \"5.10.0-21-amd64\",\n \"firmware\": \"v40.01.01.01\",\n \"serial\": \"SN00000002\",\n \"manufacturer\": \"Accton\",\n \"model\": \"AS7726-32X\",\n \"asset\": \"None\"\n }\n },\n \"unknown-tlvs\": {\n \"unknown-tlv\": {\n \"oui\": \"00,80,C2\",\n \"subtype\": \"7\",\n \"len\": \"5\",\n \"value\": \"01,00,00,00,00\"\n }\n }\n }\n },\n {\n \"data2\": {\n \"via\": \"LLDP\",\n \"rid\": \"4\",\n \"age\": \"4 days, 19:56:33\",\n \"chassis\": {\n \"leaf-1\": {\n \"id\": {\n \"type\": \"mac\",\n \"value\": \"02:00:00:00:00:1d\"\n },\n \"descr\": \"SONiC Software Version: SONiC.4.5.2-Enterprise_Base - HwSku: Accton-AS7726-32X - Distribution: Debian 11.11 - Kernel: 5.10.0-39-amd64\",\n \"mgmt-ip\": \"192.0.2.2\",\n \"capability\": [\n {\n \"type\": \"Bridge\",\n \"enabled\": false\n },\n {\n \"type\": \"Router\",\n \"enabled\": true\n },\n {\n \"type\": \"Wlan\",\n \"enabled\": false\n },\n {\n \"type\": \"Station\",\n \"enabled\": false\n }\n ]\n }\n },\n \"port\": {\n \"id\": {\n \"type\": \"local\",\n \"value\": \"Eth1/6/3\"\n },\n \"descr\": \"Ethernet22\",\n \"ttl\": \"120\",\n \"mfs\": \"9100\",\n \"auto-negotiation\": {\n \"supported\": false,\n \"enabled\": false,\n \"current\": \"10GigBaseCX4 - X copper over 8 pair 100-Ohm balanced cable\"\n }\n },\n \"lldp-med\": {\n \"device-type\": \"Network Connectivity Device\",\n \"capability\": [\n {\n \"type\": \"Capabilities\",\n \"available\": true\n },\n {\n \"type\": \"Policy\",\n \"available\": true\n },\n {\n \"type\": \"Location\",\n \"available\": true\n },\n {\n \"type\": \"MDI/PSE\",\n \"available\": true\n },\n {\n \"type\": \"MDI/PD\",\n \"available\": true\n },\n {\n \"type\": \"Inventory\",\n \"available\": true\n }\n ],\n \"inventory\": {\n \"hardware\": \"R01H\",\n \"software\": \"5.10.0-39-amd64\",\n \"firmware\": \"v40.01.01.01\",\n \"serial\": \"SN00000001\",\n \"manufacturer\": \"Accton\",\n \"model\": \"AS7726-32X\",\n \"asset\": \"None\"\n }\n },\n \"unknown-tlvs\": {\n \"unknown-tlv\": {\n \"oui\": \"00,80,C2\",\n \"subtype\": \"7\",\n \"len\": \"5\",\n \"value\": \"01,00,00,00,00\"\n }\n }\n }\n }\n ]\n }\n}\n", + "rc": 0 + }, + "ports": { + "data1": { + "devlink": { + "err": "", + "out": "{\"info\":{\"pci/0000:01:00.0\":{\"driver\":\"ixgbe\",\"serial_number\":\"02-00-00-ff-ff-00-00-07\",\"versions\":{\"fixed\":{\"board.id\":\"0101FF-0FF\"},\"running\":{\"fw.undi\":\"1.2527.0\",\"fw.bundle_id\":\"0x800006da\"}}}}}\n", + "rc": 0 + }, + "ethtool": { + "err": "", + "out": "Settings for data1:\n\tSupported ports: [ FIBRE ]\n\tSupported link modes: 10000baseT/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: No\n\tSupported FEC modes: Not reported\n\tAdvertised link modes: 10000baseT/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: No\n\tAdvertised FEC modes: Not reported\n\tSpeed: 10000Mb/s\n\tDuplex: Full\n\tAuto-negotiation: off\n\tPort: Direct Attach Copper\n\tPHYAD: 0\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n Current message level: 0x00000007 (7)\n drv probe link\n\tLink detected: yes\n", + "rc": 0 + }, + "ethtool_P": { + "err": "", + "out": "Permanent address: 02:00:00:00:00:07\n", + "rc": 0 + }, + "ethtool_fec": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 1 + }, + "ethtool_i": { + "err": "", + "out": "driver: ixgbe\nversion: 7.0.0-31-generic\nfirmware-version: 0x800006da, 1.2527.0\nexpansion-rom-version: \nbus-info: 0000:01:00.0\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: yes\nsupports-register-dump: yes\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "", + "out": "\tIdentifier : 0x03 (SFP)\n\tExtended identifier : 0x04 (GBIC/SFP defined by 2-wire interface ID)\n\tConnector : 0x21 (Copper pigtail)\n\tTransceiver codes : 0x00 0x00 0x00 0x00 0x00 0x04 0x00 0x00 0x0c\n\tTransceiver type : Passive Cable\n\tTransceiver type : Extended: 25G Base-CR CA-S\n\tEncoding : 0x00 (unspecified)\n\tBR, Nominal : 25750MBd\n\tRate identifier : 0x00 (unspecified)\n\tLength (SMF,km) : 0km\n\tLength (SMF) : 0m\n\tLength (50um) : 0m\n\tLength (62.5um) : 0m\n\tLength (Copper) : 2m\n\tLength (OM3) : 0m\n\tPassive Cu cmplnce. : 0x00 (unspecified) [SFF-8472 rev10.4 only]\n\tVendor name : FS\n\tVendor OUI : 64:9d:99\n\tVendor PN : Q-4S28PC02\n\tVendor rev : 01\n\tOption values : 0x08 0x00\n\tOption : Retimer or CDR implemented\n\tBR margin, max : 0%\n\tBR margin, min : 0%\n\tVendor SN : SN00000013-3\n\tDate code : 240704\n", + "rc": 0 + }, + "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:01:00.0\nClass:\tEthernet controller [0200]\nVendor:\tIntel Corporation [8086]\nDevice:\t82599ES 10-Gigabit SFI/SFP+ Network Connection [10fb]\nSVendor:\tSuper Micro Computer Inc [15d9]\nSDevice:\t82599ES 10-Gigabit SFI/SFP+ Network Connection [1c0f]\nRev:\t01\nProgIf:\t00\nIOMMUGroup:\t14\n\n", + "rc": 0 + }, + "pci": "0000:01:00.0", + "sysfs": { + "address": "02:00:00:00:00:07", + "carrier": "1", + "dev_port": "0", + "duplex": "full", + "mtu": "9100", + "operstate": "up", + "phys_port_name": null, + "speed": "10000", + "type": "1" + } + }, + "data2": { + "devlink": { + "err": "", + "out": "{\"info\":{\"pci/0000:01:00.1\":{\"driver\":\"ixgbe\",\"serial_number\":\"02-00-00-ff-ff-00-00-07\",\"versions\":{\"fixed\":{\"board.id\":\"0101FF-0FF\"},\"running\":{\"fw.undi\":\"1.2527.0\",\"fw.bundle_id\":\"0x800006da\"}}}}}\n", + "rc": 0 + }, + "ethtool": { + "err": "", + "out": "Settings for data2:\n\tSupported ports: [ FIBRE ]\n\tSupported link modes: 10000baseT/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: No\n\tSupported FEC modes: Not reported\n\tAdvertised link modes: 10000baseT/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: No\n\tAdvertised FEC modes: Not reported\n\tSpeed: 10000Mb/s\n\tDuplex: Full\n\tAuto-negotiation: off\n\tPort: Direct Attach Copper\n\tPHYAD: 0\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n Current message level: 0x00000007 (7)\n drv probe link\n\tLink detected: yes\n", + "rc": 0 + }, + "ethtool_P": { + "err": "", + "out": "Permanent address: 02:00:00:00:00:08\n", + "rc": 0 + }, + "ethtool_fec": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 1 + }, + "ethtool_i": { + "err": "", + "out": "driver: ixgbe\nversion: 7.0.0-31-generic\nfirmware-version: 0x800006da, 1.2527.0\nexpansion-rom-version: \nbus-info: 0000:01:00.1\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: yes\nsupports-register-dump: yes\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "", + "out": "\tIdentifier : 0x03 (SFP)\n\tExtended identifier : 0x04 (GBIC/SFP defined by 2-wire interface ID)\n\tConnector : 0x21 (Copper pigtail)\n\tTransceiver codes : 0x00 0x00 0x00 0x00 0x00 0x04 0x00 0x00 0x0c\n\tTransceiver type : Passive Cable\n\tTransceiver type : Extended: 25G Base-CR CA-S\n\tEncoding : 0x00 (unspecified)\n\tBR, Nominal : 25750MBd\n\tRate identifier : 0x00 (unspecified)\n\tLength (SMF,km) : 0km\n\tLength (SMF) : 0m\n\tLength (50um) : 0m\n\tLength (62.5um) : 0m\n\tLength (Copper) : 2m\n\tLength (OM3) : 0m\n\tPassive Cu cmplnce. : 0x00 (unspecified) [SFF-8472 rev10.4 only]\n\tVendor name : FS\n\tVendor OUI : 64:9d:99\n\tVendor PN : QSFP-100G-4SPC02\n\tVendor rev : 01\n\tOption values : 0x08 0x00\n\tOption : Retimer or CDR implemented\n\tBR margin, max : 0%\n\tBR margin, min : 0%\n\tVendor SN : SN00000015-3\n\tDate code : 240418\n", + "rc": 0 + }, + "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:01:00.1\nClass:\tEthernet controller [0200]\nVendor:\tIntel Corporation [8086]\nDevice:\t82599ES 10-Gigabit SFI/SFP+ Network Connection [10fb]\nSVendor:\tSuper Micro Computer Inc [15d9]\nSDevice:\t82599ES 10-Gigabit SFI/SFP+ Network Connection [1c0f]\nRev:\t01\nProgIf:\t00\nIOMMUGroup:\t15\n\n", + "rc": 0 + }, + "pci": "0000:01:00.1", + "sysfs": { + "address": "02:00:00:00:00:08", + "carrier": "1", + "dev_port": "0", + "duplex": "full", + "mtu": "9100", + "operstate": "up", + "phys_port_name": null, + "speed": "10000", + "type": "1" + } + }, + "enx02000000001f": { + "devlink": { + "err": "kernel answers: No such device", + "out": "{\"info\":{}}\n", + "rc": 1 + }, + "ethtool": { + "err": "", + "out": "Settings for enx02000000001f:\n\tSupported ports: [ ]\n\tSupported link modes: Not reported\n\tSupported pause frame use: No\n\tSupports auto-negotiation: No\n\tSupported FEC modes: Not reported\n\tAdvertised link modes: Not reported\n\tAdvertised pause frame use: No\n\tAdvertised auto-negotiation: No\n\tAdvertised FEC modes: Not reported\n\tSpeed: Unknown!\n\tDuplex: Unknown! (255)\n\tAuto-negotiation: off\n\tPort: Twisted Pair\n\tPHYAD: 0\n\tTransceiver: internal\n\tMDI-X: Unknown\n Current message level: 0x00000007 (7)\n drv probe link\n\tLink detected: no\n", + "rc": 0 + }, + "ethtool_P": { + "err": "", + "out": "Permanent address: 02:00:00:00:00:1f\n", + "rc": 0 + }, + "ethtool_fec": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 1 + }, + "ethtool_i": { + "err": "", + "out": "driver: rndis_host\nversion: 7.0.0-31-generic\nfirmware-version: RNDIS device\nexpansion-rom-version: \nbus-info: usb-0000:00:14.0-13.2\nsupports-statistics: no\nsupports-test: no\nsupports-eeprom-access: no\nsupports-register-dump: no\nsupports-priv-flags: no\n", + "rc": 0 + }, + "ethtool_m": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 1 + }, + "kind": "physical", + "lspci": { + "err": "lspci: -s: Invalid bus number", + "out": "", + "rc": 1 + }, + "pci": "1-13.2:2.0", + "sysfs": { + "address": "02:00:00:00:00:1f", + "carrier": null, + "dev_port": "0", + "duplex": null, + "mtu": "1500", + "operstate": "down", + "phys_port_name": null, + "speed": null, + "type": "1" + } + } + }, + "sections": [ + "ports", + "lldp", + "ethtool", + "module", + "nic", + "system" + ], + "version": 1 +} diff --git a/tests/unit/utils/hardware/fixtures/linux-bcm57414.json b/tests/unit/utils/hardware/fixtures/linux-bcm57414.json new file mode 100644 index 000000000..3a5aa258c --- /dev/null +++ b/tests/unit/utils/hardware/fixtures/linux-bcm57414.json @@ -0,0 +1,280 @@ +{ + "collector": "linux", + "dmi": { + "product_name": "SYS-620TP-HTTR", + "product_serial": "SN00000006", + "sys_vendor": "Supermicro" + }, + "hostname": "deploy-1", + "ip_link": { + "err": "", + "out": "[{\"ifindex\":1,\"ifname\":\"lo\",\"flags\":[\"LOOPBACK\",\"UP\",\"LOWER_UP\"],\"mtu\":65536,\"qdisc\":\"noqueue\",\"operstate\":\"UNKNOWN\",\"linkmode\":\"DEFAULT\",\"group\":\"default\",\"txqlen\":1000,\"link_type\":\"loopback\",\"address\":\"02:00:00:00:00:18\",\"broadcast\":\"02:00:00:00:00:18\",\"promiscuity\":0,\"allmulti\":0,\"min_mtu\":0,\"max_mtu\":0,\"inet6_addr_gen_mode\":\"eui64\",\"num_tx_queues\":1,\"num_rx_queues\":1,\"gso_max_size\":65536,\"gso_max_segs\":65535,\"tso_max_size\":524280,\"tso_max_segs\":65535,\"gro_max_size\":65536},{\"ifindex\":2,\"ifname\":\"data1\",\"flags\":[\"BROADCAST\",\"MULTICAST\",\"UP\",\"LOWER_UP\"],\"mtu\":9100,\"qdisc\":\"mq\",\"operstate\":\"UP\",\"linkmode\":\"DEFAULT\",\"group\":\"default\",\"txqlen\":1000,\"link_type\":\"ether\",\"address\":\"02:00:00:00:00:03\",\"broadcast\":\"02:00:00:00:00:1a\",\"promiscuity\":0,\"allmulti\":0,\"min_mtu\":60,\"max_mtu\":9600,\"inet6_addr_gen_mode\":\"eui64\",\"num_tx_queues\":1184,\"num_rx_queues\":148,\"gso_max_size\":65536,\"gso_max_segs\":65535,\"tso_max_size\":524280,\"tso_max_segs\":65535,\"gro_max_size\":65536,\"phys_port_name\":\"p0\",\"phys_switch_id\":\"020000ffff000003\",\"parentbus\":\"pci\",\"parentdev\":\"0000:98:00.0\",\"vfinfo_list\":[],\"altnames\":[\"enp152s0f0np0\"]},{\"ifindex\":3,\"ifname\":\"data2\",\"flags\":[\"BROADCAST\",\"MULTICAST\",\"UP\",\"LOWER_UP\"],\"mtu\":9100,\"qdisc\":\"mq\",\"operstate\":\"UP\",\"linkmode\":\"DEFAULT\",\"group\":\"default\",\"txqlen\":1000,\"link_type\":\"ether\",\"address\":\"02:00:00:00:00:04\",\"broadcast\":\"02:00:00:00:00:1a\",\"promiscuity\":0,\"allmulti\":0,\"min_mtu\":60,\"max_mtu\":9600,\"inet6_addr_gen_mode\":\"eui64\",\"num_tx_queues\":1184,\"num_rx_queues\":148,\"gso_max_size\":65536,\"gso_max_segs\":65535,\"tso_max_size\":524280,\"tso_max_segs\":65535,\"gro_max_size\":65536,\"phys_port_name\":\"p1\",\"phys_switch_id\":\"020000ffff000004\",\"parentbus\":\"pci\",\"parentdev\":\"0000:98:00.1\",\"vfinfo_list\":[],\"altnames\":[\"enp152s0f1np1\"]},{\"ifindex\":4,\"ifname\":\"mgmt1\",\"flags\":[\"BROADCAST\",\"MULTICAST\",\"UP\",\"LOWER_UP\"],\"mtu\":9100,\"qdisc\":\"mq\",\"operstate\":\"UP\",\"linkmode\":\"DEFAULT\",\"group\":\"default\",\"txqlen\":1000,\"link_type\":\"ether\",\"address\":\"02:00:00:00:00:05\",\"broadcast\":\"02:00:00:00:00:1a\",\"promiscuity\":0,\"allmulti\":0,\"min_mtu\":68,\"max_mtu\":9702,\"inet6_addr_gen_mode\":\"eui64\",\"num_tx_queues\":80,\"num_rx_queues\":80,\"gso_max_size\":65536,\"gso_max_segs\":65535,\"tso_max_size\":65536,\"tso_max_segs\":65535,\"gro_max_size\":65536,\"phys_port_name\":\"p0\",\"phys_port_id\":\"020000000005\",\"phys_switch_id\":\"020000ffff000005\",\"parentbus\":\"pci\",\"parentdev\":\"0000:65:00.0\",\"vfinfo_list\":[],\"altnames\":[\"enp101s0f0np0\"]},{\"ifindex\":5,\"ifname\":\"enx02000000001f\",\"flags\":[\"BROADCAST\",\"MULTICAST\"],\"mtu\":1500,\"qdisc\":\"noop\",\"operstate\":\"DOWN\",\"linkmode\":\"DEFAULT\",\"group\":\"default\",\"txqlen\":1000,\"link_type\":\"ether\",\"address\":\"02:00:00:00:00:1f\",\"broadcast\":\"02:00:00:00:00:1a\",\"promiscuity\":0,\"allmulti\":0,\"min_mtu\":0,\"max_mtu\":1500,\"inet6_addr_gen_mode\":\"eui64\",\"num_tx_queues\":1,\"num_rx_queues\":1,\"gso_max_size\":65536,\"gso_max_segs\":65535,\"tso_max_size\":65536,\"tso_max_segs\":65535,\"gro_max_size\":65536,\"parentbus\":\"usb\",\"parentdev\":\"1-7.2:2.0\"},{\"ifindex\":6,\"ifname\":\"eno2np1\",\"flags\":[\"BROADCAST\",\"MULTICAST\"],\"mtu\":1500,\"qdisc\":\"noop\",\"operstate\":\"DOWN\",\"linkmode\":\"DEFAULT\",\"group\":\"default\",\"txqlen\":1000,\"link_type\":\"ether\",\"address\":\"02:00:00:00:00:06\",\"broadcast\":\"02:00:00:00:00:1a\",\"promiscuity\":0,\"allmulti\":0,\"min_mtu\":68,\"max_mtu\":9702,\"inet6_addr_gen_mode\":\"eui64\",\"num_tx_queues\":80,\"num_rx_queues\":80,\"gso_max_size\":65536,\"gso_max_segs\":65535,\"tso_max_size\":65536,\"tso_max_segs\":65535,\"gro_max_size\":65536,\"phys_port_name\":\"p1\",\"phys_port_id\":\"020000000006\",\"phys_switch_id\":\"020000ffff000006\",\"parentbus\":\"pci\",\"parentdev\":\"0000:65:00.1\",\"vfinfo_list\":[],\"altnames\":[\"enp101s0f1np1\"]}]\n", + "rc": 0 + }, + "lldpctl": { + "err": "", + "out": "{\n \"lldp\": {\n \"interface\": [\n {\n \"data1\": {\n \"via\": \"LLDP\",\n \"rid\": \"9\",\n \"age\": \"39 days, 22:54:05\",\n \"chassis\": {\n \"leaf-1\": {\n \"id\": {\n \"type\": \"mac\",\n \"value\": \"02:00:00:00:00:1d\"\n },\n \"descr\": \"SONiC Software Version: SONiC.4.5.2-Enterprise_Base - HwSku: Accton-AS7726-32X - Distribution: Debian 11.11 - Kernel: 5.10.0-39-amd64\",\n \"mgmt-ip\": \"192.0.2.2\",\n \"capability\": [\n {\n \"type\": \"Bridge\",\n \"enabled\": false\n },\n {\n \"type\": \"Router\",\n \"enabled\": true\n },\n {\n \"type\": \"Wlan\",\n \"enabled\": false\n },\n {\n \"type\": \"Station\",\n \"enabled\": false\n }\n ]\n }\n },\n \"port\": {\n \"id\": {\n \"type\": \"local\",\n \"value\": \"Eth1/2/3\"\n },\n \"descr\": \"Ethernet6\",\n \"ttl\": \"120\",\n \"mfs\": \"9100\"\n },\n \"lldp-med\": {\n \"device-type\": \"Network Connectivity Device\",\n \"capability\": [\n {\n \"type\": \"Capabilities\",\n \"available\": true\n },\n {\n \"type\": \"Policy\",\n \"available\": true\n },\n {\n \"type\": \"Location\",\n \"available\": true\n },\n {\n \"type\": \"MDI/PSE\",\n \"available\": true\n },\n {\n \"type\": \"MDI/PD\",\n \"available\": true\n },\n {\n \"type\": \"Inventory\",\n \"available\": true\n }\n ],\n \"inventory\": {\n \"hardware\": \"R01H\",\n \"software\": \"5.10.0-39-amd64\",\n \"firmware\": \"v40.01.01.01\",\n \"serial\": \"SN00000001\",\n \"manufacturer\": \"Accton\",\n \"model\": \"AS7726-32X\",\n \"asset\": \"None\"\n }\n },\n \"unknown-tlvs\": {\n \"unknown-tlv\": {\n \"oui\": \"00,80,C2\",\n \"subtype\": \"7\",\n \"len\": \"5\",\n \"value\": \"01,00,00,00,00\"\n }\n }\n }\n },\n {\n \"data2\": {\n \"via\": \"LLDP\",\n \"rid\": \"8\",\n \"age\": \"39 days, 23:22:25\",\n \"chassis\": {\n \"leaf-2\": {\n \"id\": {\n \"type\": \"mac\",\n \"value\": \"02:00:00:00:00:1e\"\n },\n \"descr\": \"SONiC Software Version: SONiC.4.4.1-Enterprise_Base - HwSku: Accton-AS7726-32X - Distribution: Debian 11.11 - Kernel: 5.10.0-21-amd64\",\n \"mgmt-ip\": \"192.0.2.3\",\n \"capability\": [\n {\n \"type\": \"Bridge\",\n \"enabled\": false\n },\n {\n \"type\": \"Router\",\n \"enabled\": true\n },\n {\n \"type\": \"Wlan\",\n \"enabled\": false\n },\n {\n \"type\": \"Station\",\n \"enabled\": false\n }\n ]\n }\n },\n \"port\": {\n \"id\": {\n \"type\": \"local\",\n \"value\": \"Eth1/2/3\"\n },\n \"descr\": \"Ethernet6\",\n \"ttl\": \"120\",\n \"mfs\": \"9100\"\n },\n \"lldp-med\": {\n \"device-type\": \"Network Connectivity Device\",\n \"capability\": [\n {\n \"type\": \"Capabilities\",\n \"available\": true\n },\n {\n \"type\": \"Policy\",\n \"available\": true\n },\n {\n \"type\": \"Location\",\n \"available\": true\n },\n {\n \"type\": \"MDI/PSE\",\n \"available\": true\n },\n {\n \"type\": \"MDI/PD\",\n \"available\": true\n },\n {\n \"type\": \"Inventory\",\n \"available\": true\n }\n ],\n \"inventory\": {\n \"hardware\": \"R01H\",\n \"software\": \"5.10.0-21-amd64\",\n \"firmware\": \"v40.01.01.01\",\n \"serial\": \"SN00000002\",\n \"manufacturer\": \"Accton\",\n \"model\": \"AS7726-32X\",\n \"asset\": \"None\"\n }\n },\n \"unknown-tlvs\": {\n \"unknown-tlv\": {\n \"oui\": \"00,80,C2\",\n \"subtype\": \"7\",\n \"len\": \"5\",\n \"value\": \"01,00,00,00,00\"\n }\n }\n }\n },\n {\n \"mgmt1\": {\n \"via\": \"LLDP\",\n \"rid\": \"2\",\n \"age\": \"61 days, 15:45:39\",\n \"chassis\": {\n \"oob-1\": {\n \"id\": {\n \"type\": \"mac\",\n \"value\": \"02:00:00:00:00:1c\"\n },\n \"descr\": \"MikroTik RouterOS 6.49.1 (stable) CRS354-48G-4S+2Q+\",\n \"mgmt-ip\": \"192.0.2.1\",\n \"mgmt-iface\": \"31\",\n \"capability\": [\n {\n \"type\": \"Bridge\",\n \"enabled\": true\n },\n {\n \"type\": \"Router\",\n \"enabled\": true\n }\n ]\n }\n },\n \"port\": {\n \"id\": {\n \"type\": \"ifname\",\n \"value\": \"bridge/ether18\"\n },\n \"ttl\": \"120\"\n }\n }\n }\n ]\n }\n}\n", + "rc": 0 + }, + "ports": { + "data1": { + "devlink": { + "err": "", + "out": "{\"info\":{\"pci/0000:98:00.0\":{\"driver\":\"bnxt_en\",\"serial_number\":\"02-00-00-FF-FE-00-00-03\",\"board.serial_number\":\"SN00000009\",\"versions\":{\"fixed\":{\"board.id\":\"BCM957414A4142CC\",\"asic.id\":\"16D7\",\"asic.rev\":\"B1\"},\"running\":{\"fw.psid\":\"216.0.0\",\"fw\":\"216.0.333.11\",\"fw.mgmt\":\"214.4.91.1\",\"fw.mgmt.api\":\"1.10.1\",\"fw.ncsi\":\"214.4.42.0\",\"fw.roce\":\"214.0.194.0\"},\"stored\":{\"fw.psid\":\"216.0.0\",\"fw\":\"216.0.333.11\"}}}}}\n", + "rc": 0 + }, + "ethtool": { + "err": "", + "out": "Settings for data1:\n\tSupported ports: [ FIBRE ]\n\tSupported link modes: 25000baseCR/Full\n\t 1000baseX/Full\n\tSupported pause frame use: Symmetric Receive-only\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: RS\t BASER\n\tAdvertised link modes: 25000baseCR/Full\n\t 1000baseX/Full\n\tAdvertised pause frame use: No\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: Not reported\n\tSpeed: 25000Mb/s\n\tLanes: 1\n\tDuplex: Full\n\tAuto-negotiation: on\n\tPort: Direct Attach Copper\n\tPHYAD: 1\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n Current message level: 0x00002081 (8321)\n drv tx_err hw\n\tLink detected: yes\n", + "rc": 0 + }, + "ethtool_P": { + "err": "", + "out": "Permanent address: 02:00:00:00:00:03\n", + "rc": 0 + }, + "ethtool_fec": { + "err": "", + "out": "FEC parameters for data1:\nSupported/Configured FEC encodings: None\nActive FEC encoding: Off\n", + "rc": 0 + }, + "ethtool_i": { + "err": "", + "out": "driver: bnxt_en\nversion: 7.0.0-31-generic\nfirmware-version: 214.4.91.1/pkg 216.0.333.11\nexpansion-rom-version: \nbus-info: 0000:98:00.0\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: yes\nsupports-register-dump: yes\nsupports-priv-flags: no\n", + "rc": 0 + }, + "ethtool_m": { + "err": "", + "out": "\tIdentifier : 0x03 (SFP)\n\tExtended identifier : 0x04 (GBIC/SFP defined by 2-wire interface ID)\n\tConnector : 0x21 (Copper pigtail)\n\tTransceiver codes : 0x00 0x00 0x00 0x00 0x00 0x04 0x00 0x00 0x0c\n\tTransceiver type : Passive Cable\n\tTransceiver type : Extended: 25G Base-CR CA-S\n\tEncoding : 0x00 (unspecified)\n\tBR, Nominal : 25750MBd\n\tRate identifier : 0x00 (unspecified)\n\tLength (SMF,km) : 0km\n\tLength (SMF) : 0m\n\tLength (50um) : 0m\n\tLength (62.5um) : 0m\n\tLength (Copper) : 3m\n\tLength (OM3) : 0m\n\tPassive Cu cmplnce. : 0x00 (unspecified) [SFF-8472 rev10.4 only]\n\tVendor name : FS\n\tVendor OUI : 64:9d:99\n\tVendor PN : Q-4S28PC03\n\tVendor rev : 01\n\tOption values : 0x08 0x00\n\tOption : Retimer or CDR implemented\n\tBR margin, max : 0%\n\tBR margin, min : 0%\n\tVendor SN : SN00000007-3\n\tDate code : 220310\n", + "rc": 0 + }, + "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:98:00.0\nClass:\tEthernet controller [0200]\nVendor:\tBroadcom Inc. and subsidiaries [14e4]\nDevice:\tBCM57414 NetXtreme-E 10Gb/25Gb RDMA Ethernet Controller [16d7]\nSVendor:\tBroadcom Inc. and subsidiaries [14e4]\nSDevice:\tBCM957414A4142CC 10Gb/25Gb Ethernet PCIe [1402]\nPhySlot:\t1\nRev:\t01\nProgIf:\t00\nNUMANode:\t1\nIOMMUGroup:\t8\n\n", + "rc": 0 + }, + "pci": "0000:98:00.0", + "sysfs": { + "address": "02:00:00:00:00:03", + "carrier": "1", + "dev_port": "0", + "duplex": "full", + "mtu": "9100", + "operstate": "up", + "phys_port_name": "p0", + "speed": "25000", + "type": "1" + } + }, + "data2": { + "devlink": { + "err": "", + "out": "{\"info\":{\"pci/0000:98:00.1\":{\"driver\":\"bnxt_en\",\"serial_number\":\"02-00-00-FF-FE-00-00-03\",\"board.serial_number\":\"SN00000009\",\"versions\":{\"fixed\":{\"board.id\":\"BCM957414A4142CC\",\"asic.id\":\"16D7\",\"asic.rev\":\"B1\"},\"running\":{\"fw.psid\":\"216.0.0\",\"fw\":\"216.0.333.11\",\"fw.mgmt\":\"214.4.91.1\",\"fw.mgmt.api\":\"1.10.1\",\"fw.ncsi\":\"214.4.42.0\",\"fw.roce\":\"214.0.194.0\"},\"stored\":{\"fw.psid\":\"216.0.0\",\"fw\":\"216.0.333.11\"}}}}}\n", + "rc": 0 + }, + "ethtool": { + "err": "", + "out": "Settings for data2:\n\tSupported ports: [ FIBRE ]\n\tSupported link modes: 25000baseCR/Full\n\t 1000baseX/Full\n\tSupported pause frame use: Symmetric Receive-only\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: RS\t BASER\n\tAdvertised link modes: 25000baseCR/Full\n\t 1000baseX/Full\n\tAdvertised pause frame use: No\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: Not reported\n\tSpeed: 25000Mb/s\n\tLanes: 1\n\tDuplex: Full\n\tAuto-negotiation: on\n\tPort: Direct Attach Copper\n\tPHYAD: 1\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n Current message level: 0x00002081 (8321)\n drv tx_err hw\n\tLink detected: yes\n", + "rc": 0 + }, + "ethtool_P": { + "err": "", + "out": "Permanent address: 02:00:00:00:00:04\n", + "rc": 0 + }, + "ethtool_fec": { + "err": "", + "out": "FEC parameters for data2:\nSupported/Configured FEC encodings: None\nActive FEC encoding: Off\n", + "rc": 0 + }, + "ethtool_i": { + "err": "", + "out": "driver: bnxt_en\nversion: 7.0.0-31-generic\nfirmware-version: 214.4.91.1/pkg 216.0.333.11\nexpansion-rom-version: \nbus-info: 0000:98:00.1\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: yes\nsupports-register-dump: yes\nsupports-priv-flags: no\n", + "rc": 0 + }, + "ethtool_m": { + "err": "", + "out": "\tIdentifier : 0x03 (SFP)\n\tExtended identifier : 0x04 (GBIC/SFP defined by 2-wire interface ID)\n\tConnector : 0x21 (Copper pigtail)\n\tTransceiver codes : 0x00 0x00 0x00 0x00 0x00 0x04 0x00 0x00 0x0c\n\tTransceiver type : Passive Cable\n\tTransceiver type : Extended: 25G Base-CR CA-S\n\tEncoding : 0x00 (unspecified)\n\tBR, Nominal : 25750MBd\n\tRate identifier : 0x00 (unspecified)\n\tLength (SMF,km) : 0km\n\tLength (SMF) : 0m\n\tLength (50um) : 0m\n\tLength (62.5um) : 0m\n\tLength (Copper) : 3m\n\tLength (OM3) : 0m\n\tPassive Cu cmplnce. : 0x00 (unspecified) [SFF-8472 rev10.4 only]\n\tVendor name : FS\n\tVendor OUI : 64:9d:99\n\tVendor PN : Q-4S28PC03\n\tVendor rev : 01\n\tOption values : 0x08 0x00\n\tOption : Retimer or CDR implemented\n\tBR margin, max : 0%\n\tBR margin, min : 0%\n\tVendor SN : SN00000010-2\n\tDate code : 220310\n", + "rc": 0 + }, + "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:98:00.1\nClass:\tEthernet controller [0200]\nVendor:\tBroadcom Inc. and subsidiaries [14e4]\nDevice:\tBCM57414 NetXtreme-E 10Gb/25Gb RDMA Ethernet Controller [16d7]\nSVendor:\tBroadcom Inc. and subsidiaries [14e4]\nSDevice:\tBCM957414A4142CC 10Gb/25Gb Ethernet PCIe [1402]\nPhySlot:\t1\nRev:\t01\nProgIf:\t00\nNUMANode:\t1\nIOMMUGroup:\t9\n\n", + "rc": 0 + }, + "pci": "0000:98:00.1", + "sysfs": { + "address": "02:00:00:00:00:04", + "carrier": "1", + "dev_port": "0", + "duplex": "full", + "mtu": "9100", + "operstate": "up", + "phys_port_name": "p1", + "speed": "25000", + "type": "1" + } + }, + "eno2np1": { + "devlink": { + "err": "", + "out": "{\"info\":{\"pci/0000:65:00.1\":{\"driver\":\"i40e\",\"serial_number\":\"02-00-00-ff-ff-00-00-06\",\"versions\":{\"running\":{\"fw.mgmt\":\"8.1\",\"fw.mgmt.build\":\"63299\",\"fw.mgmt.api\":\"1.12\",\"fw.psid.api\":\"8.10\",\"fw.bundle_id\":\"0x8000a7b1\",\"fw.undi\":\"1.2926.0\"}}}}}\n", + "rc": 0 + }, + "ethtool": { + "err": "", + "out": "Settings for eno2np1:\n\tSupported ports: [ ]\n\tSupported link modes: 100baseT/Full\n\t 1000baseT/Full\n\t 10000baseT/Full\n\t 2500baseT/Full\n\t 5000baseT/Full\n\tSupported pause frame use: Symmetric Receive-only\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: Not reported\n\tAdvertised link modes: 100baseT/Full\n\t 1000baseT/Full\n\t 10000baseT/Full\n\t 2500baseT/Full\n\t 5000baseT/Full\n\tAdvertised pause frame use: No\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: Not reported\n\tSpeed: Unknown!\n\tDuplex: Unknown! (255)\n\tAuto-negotiation: off\n\tPort: Other\n\tPHYAD: 0\n\tTransceiver: internal\n\tSupports Wake-on: g\n\tWake-on: g\n Current message level: 0x00000007 (7)\n drv probe link\n\tLink detected: no\n", + "rc": 0 + }, + "ethtool_P": { + "err": "", + "out": "Permanent address: 02:00:00:00:00:06\n", + "rc": 0 + }, + "ethtool_fec": { + "err": "", + "out": "FEC parameters for eno2np1:\nSupported/Configured FEC encodings: Off\nActive FEC encoding: BaseR\n", + "rc": 0 + }, + "ethtool_i": { + "err": "", + "out": "driver: i40e\nversion: 7.0.0-31-generic\nfirmware-version: 8.10 0x8000a7b1 1.2926.0\nexpansion-rom-version: \nbus-info: 0000:65:00.1\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: yes\nsupports-register-dump: yes\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 1 + }, + "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:65:00.1\nClass:\tEthernet controller [0200]\nVendor:\tIntel Corporation [8086]\nDevice:\tEthernet Controller X710 for 10GBASE-T [15ff]\nSVendor:\tSuper Micro Computer Inc [15d9]\nSDevice:\tEthernet Controller X710 for 10GBASE-T [0000]\nPhySlot:\t0-2\nRev:\t02\nProgIf:\t00\nNUMANode:\t0\nIOMMUGroup:\t2\n\n", + "rc": 0 + }, + "pci": "0000:65:00.1", + "sysfs": { + "address": "02:00:00:00:00:06", + "carrier": null, + "dev_port": "0", + "duplex": null, + "mtu": "1500", + "operstate": "down", + "phys_port_name": "p1", + "speed": null, + "type": "1" + } + }, + "enx02000000001f": { + "devlink": { + "err": "kernel answers: No such device", + "out": "{\"info\":{}}\n", + "rc": 1 + }, + "ethtool": { + "err": "", + "out": "Settings for enx02000000001f:\n\tSupported ports: [ ]\n\tSupported link modes: Not reported\n\tSupported pause frame use: No\n\tSupports auto-negotiation: No\n\tSupported FEC modes: Not reported\n\tAdvertised link modes: Not reported\n\tAdvertised pause frame use: No\n\tAdvertised auto-negotiation: No\n\tAdvertised FEC modes: Not reported\n\tSpeed: Unknown!\n\tDuplex: Unknown! (255)\n\tAuto-negotiation: off\n\tPort: Twisted Pair\n\tPHYAD: 0\n\tTransceiver: internal\n\tMDI-X: Unknown\n Current message level: 0x00000007 (7)\n drv probe link\n\tLink detected: no\n", + "rc": 0 + }, + "ethtool_P": { + "err": "", + "out": "Permanent address: 02:00:00:00:00:1f\n", + "rc": 0 + }, + "ethtool_fec": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 1 + }, + "ethtool_i": { + "err": "", + "out": "driver: rndis_host\nversion: 7.0.0-31-generic\nfirmware-version: RNDIS device\nexpansion-rom-version: \nbus-info: usb-0000:00:14.0-7.2\nsupports-statistics: no\nsupports-test: no\nsupports-eeprom-access: no\nsupports-register-dump: no\nsupports-priv-flags: no\n", + "rc": 0 + }, + "ethtool_m": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 1 + }, + "kind": "physical", + "lspci": { + "err": "lspci: -s: Invalid bus number", + "out": "", + "rc": 1 + }, + "pci": "1-7.2:2.0", + "sysfs": { + "address": "02:00:00:00:00:1f", + "carrier": null, + "dev_port": "0", + "duplex": null, + "mtu": "1500", + "operstate": "down", + "phys_port_name": null, + "speed": null, + "type": "1" + } + }, + "mgmt1": { + "devlink": { + "err": "", + "out": "{\"info\":{\"pci/0000:65:00.0\":{\"driver\":\"i40e\",\"serial_number\":\"02-00-00-ff-ff-00-00-06\",\"versions\":{\"running\":{\"fw.mgmt\":\"8.1\",\"fw.mgmt.build\":\"63299\",\"fw.mgmt.api\":\"1.12\",\"fw.psid.api\":\"8.10\",\"fw.bundle_id\":\"0x8000a7b1\",\"fw.undi\":\"1.2926.0\"}}}}}\n", + "rc": 0 + }, + "ethtool": { + "err": "", + "out": "Settings for mgmt1:\n\tSupported ports: [ TP ]\n\tSupported link modes: 100baseT/Full\n\t 1000baseT/Full\n\t 10000baseT/Full\n\t 2500baseT/Full\n\t 5000baseT/Full\n\tSupported pause frame use: Symmetric Receive-only\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: Not reported\n\tAdvertised link modes: 100baseT/Full\n\t 1000baseT/Full\n\t 10000baseT/Full\n\t 2500baseT/Full\n\t 5000baseT/Full\n\tAdvertised pause frame use: No\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: Not reported\n\tSpeed: 1000Mb/s\n\tDuplex: Full\n\tAuto-negotiation: on\n\tPort: Twisted Pair\n\tPHYAD: 0\n\tTransceiver: internal\n\tMDI-X: Unknown\n\tSupports Wake-on: g\n\tWake-on: d\n Current message level: 0x00000007 (7)\n drv probe link\n\tLink detected: yes\n", + "rc": 0 + }, + "ethtool_P": { + "err": "", + "out": "Permanent address: 02:00:00:00:00:05\n", + "rc": 0 + }, + "ethtool_fec": { + "err": "", + "out": "FEC parameters for mgmt1:\nSupported/Configured FEC encodings: Off\nActive FEC encoding: BaseR\n", + "rc": 0 + }, + "ethtool_i": { + "err": "", + "out": "driver: i40e\nversion: 7.0.0-31-generic\nfirmware-version: 8.10 0x8000a7b1 1.2926.0\nexpansion-rom-version: \nbus-info: 0000:65:00.0\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: yes\nsupports-register-dump: yes\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 1 + }, + "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:65:00.0\nClass:\tEthernet controller [0200]\nVendor:\tIntel Corporation [8086]\nDevice:\tEthernet Controller X710 for 10GBASE-T [15ff]\nSVendor:\tSuper Micro Computer Inc [15d9]\nSDevice:\tEthernet Controller X710 for 10GBASE-T [15ff]\nPhySlot:\t0-2\nRev:\t02\nProgIf:\t00\nNUMANode:\t0\nIOMMUGroup:\t1\n\n", + "rc": 0 + }, + "pci": "0000:65:00.0", + "sysfs": { + "address": "02:00:00:00:00:05", + "carrier": "1", + "dev_port": "0", + "duplex": "full", + "mtu": "9100", + "operstate": "up", + "phys_port_name": "p0", + "speed": "1000", + "type": "1" + } + } + }, + "sections": [ + "ports", + "lldp", + "ethtool", + "module", + "nic", + "system" + ], + "version": 1 +} diff --git a/tests/unit/utils/hardware/fixtures/linux-broadcom.json b/tests/unit/utils/hardware/fixtures/linux-broadcom.json new file mode 100644 index 000000000..64305735a --- /dev/null +++ b/tests/unit/utils/hardware/fixtures/linux-broadcom.json @@ -0,0 +1,130 @@ +{ + "collector": "linux", + "dmi": { + "product_name": "ProLiant DL360 Gen10 Plus", + "product_serial": "CZ2D1A0B1C", + "sys_vendor": "HPE" + }, + "hostname": "compute-101", + "ip_link": { + "err": "", + "out": "[{\"ifindex\": 2, \"ifname\": \"ens2f0np0\", \"flags\": [\"BROADCAST\", \"MULTICAST\", \"UP\", \"LOWER_UP\"], \"mtu\": 1500, \"qdisc\": \"mq\", \"operstate\": \"UP\", \"link_type\": \"ether\", \"address\": \"bc:97:e1:00:30:00\", \"broadcast\": \"ff:ff:ff:ff:ff:ff\"}, {\"ifindex\": 3, \"ifname\": \"ens2f1np1\", \"flags\": [\"BROADCAST\", \"MULTICAST\", \"UP\", \"LOWER_UP\"], \"mtu\": 1500, \"qdisc\": \"mq\", \"operstate\": \"UP\", \"link_type\": \"ether\", \"address\": \"bc:97:e1:00:30:01\", \"broadcast\": \"ff:ff:ff:ff:ff:ff\"}]", + "rc": 0 + }, + "lldpctl": { + "err": "[Errno 2] No such file or directory: 'lldpctl'", + "out": "", + "rc": 127 + }, + "ports": { + "ens2f0np0": { + "devlink": { + "err": "[Errno 2] No such file or directory: 'devlink'", + "out": "", + "rc": 127 + }, + "ethtool": { + "err": "", + "out": "Settings for IFACE:\n\tSupported ports: [ FIBRE ]\n\tSupported link modes: 1000baseKX/Full\n\t 25000baseCR/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: None\t RS\t BASER\n\tAdvertised link modes: 25000baseCR/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: RS\n\tSpeed: 25000Mb/s\n\tDuplex: Full\n\tAuto-negotiation: on\n\tPort: Direct Attach Copper\n\tPHYAD: 0\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n\tLink detected: yes\n", + "rc": 0 + }, + "ethtool_P": { + "err": "", + "out": "Permanent address: bc:97:e1:00:30:00\n", + "rc": 0 + }, + "ethtool_fec": { + "err": "", + "out": "FEC parameters for IFACE:\nSupported/Configured FEC encodings: Auto RS BaseR\nActive FEC encoding: BaseR\n", + "rc": 0 + }, + "ethtool_i": { + "err": "", + "out": "driver: bnxt_en\nversion: 6.8.0-51-generic\nfirmware-version: 229.0.141.0/pkg 229.1.123.0\nexpansion-rom-version: \nbus-info: 0000:12:00.0\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: no\nsupports-register-dump: no\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "", + "out": "\tIdentifier : 0x03 (SFP)\n\tExtended identifier : 0x04 (GBIC/SFP defined by 2-wire interface ID)\n\tConnector : 0x21 (Copper pigtail)\n\tTransceiver codes : 0x01 0x00 0x00 0x04 0x00 0x04 0x80 0x00 0x00\n\tTransceiver type : Infiniband: 1X Copper Passive\n\tTransceiver type : Ethernet: 1000BASE-CX\n\tTransceiver type : Passive Cable\n\tTransceiver type : FC: Twin Axial Pair (TW)\n\tTransceiver type : Extended: 25G Base-CR CA-N\n\tEncoding : 0x00 (unspecified)\n\tBR, Nominal : 25500MBd\n\tRate identifier : 0x00 (unspecified)\n\tLength (SMF,km) : 0km\n\tLength (SMF) : 0m\n\tLength (50um) : 0m\n\tLength (62.5um) : 0m\n\tLength (Copper) : 3m\n\tLength (OM3) : 0m\n\tPassive Cu cmplnce. : 0x01 (SFF-8431 appendix E) [SFF-8472 rev10.4 only]\n\tVendor name : Mellanox\n\tVendor OUI : 00:02:c9\n\tVendor PN : MCP2M00-A003E30L\n\tVendor rev : A3\n\tOption values : 0x00 0x00\n\tBR margin, max : 0%\n\tBR margin, min : 0%\n\tVendor SN : MT2114VS01234\n\tDate code : 210408\n", + "rc": 0 + }, + "kind": "physical", + "lspci": { + "err": "[Errno 2] No such file or directory: 'lspci'", + "out": "", + "rc": 127 + }, + "pci": "0000:12:00.0", + "sysfs": { + "address": "bc:97:e1:00:30:00", + "carrier": "1", + "dev_port": "0", + "duplex": "full", + "mtu": "1500", + "operstate": "up", + "phys_port_name": "p0", + "speed": "25000", + "type": "1" + } + }, + "ens2f1np1": { + "devlink": { + "err": "[Errno 2] No such file or directory: 'devlink'", + "out": "", + "rc": 127 + }, + "ethtool": { + "err": "", + "out": "Settings for IFACE:\n\tSupported ports: [ FIBRE ]\n\tSupported link modes: 1000baseKX/Full\n\t 10000baseSR/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: None\t RS\t BASER\n\tAdvertised link modes: 10000baseSR/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: RS\n\tSpeed: 10000Mb/s\n\tDuplex: Full\n\tAuto-negotiation: off\n\tPort: FIBRE\n\tPHYAD: 0\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n\tLink detected: yes\n", + "rc": 0 + }, + "ethtool_P": { + "err": "", + "out": "Permanent address: bc:97:e1:00:30:01\n", + "rc": 0 + }, + "ethtool_fec": { + "err": "", + "out": "FEC parameters for IFACE:\nSupported/Configured FEC encodings: Auto RS BaseR\nActive FEC encoding: Off\n", + "rc": 0 + }, + "ethtool_i": { + "err": "", + "out": "driver: bnxt_en\nversion: 6.8.0-51-generic\nfirmware-version: 229.0.141.0/pkg 229.1.123.0\nexpansion-rom-version: \nbus-info: 0000:12:00.1\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: no\nsupports-register-dump: no\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "", + "out": "\tIdentifier : 0x03 (SFP)\n\tExtended identifier : 0x04 (GBIC/SFP defined by 2-wire interface ID)\n\tConnector : 0x07 (LC)\n\tTransceiver codes : 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02\n\tTransceiver type : Extended: 100G Base-SR4 or 25GBase-SR\n\tEncoding : 0x06 (64B/66B)\n\tBR, Nominal : 25500MBd\n\tRate identifier : 0x00 (unspecified)\n\tLength (SMF,km) : 0km\n\tLength (SMF) : 0m\n\tLength (50um) : 0m\n\tLength (62.5um) : 0m\n\tLength (Copper) : 0m\n\tLength (OM3) : 70m\n\tLaser wavelength : 850nm\n\tVendor name : FS\n\tVendor OUI : 64:9d:99\n\tVendor PN : SFP28-25GSR-85\n\tVendor rev : A\n\tOption values : 0x08 0x1a\n\tOption : RX_LOS implemented\n\tOption : TX_FAULT implemented\n\tBR margin, max : 0%\n\tBR margin, min : 0%\n\tVendor SN : F2030123456\n\tDate code : 220915\n\tOptical diagnostics support : Yes\n\tLaser bias current : 6.212 mA\n\tLaser output power : 0.5823 mW / -2.35 dBm\n\tReceiver signal average optical power : 0.4501 mW / -3.47 dBm\n\tModule temperature : 33.45 degrees C / 92.21 degrees F\n\tModule voltage : 3.2934 V\n\tAlarm/warning flags implemented : Yes\n\tLaser bias current high alarm : Off\n\tLaser bias current high alarm threshold : 15.000 mA\n\tLaser bias current low alarm threshold : 1.000 mA\n\tLaser output power high alarm threshold : 1.9953 mW / 3.00 dBm\n\tLaser output power low alarm threshold : 0.1259 mW / -9.00 dBm\n\tModule temperature high alarm threshold : 75.00 degrees C / 167.00 degrees F\n\tModule voltage high alarm threshold : 3.6300 V\n\tLaser rx power high alarm threshold : 1.9953 mW / 3.00 dBm\n\tLaser rx power low alarm threshold : 0.0398 mW / -14.00 dBm\n", + "rc": 0 + }, + "kind": "physical", + "lspci": { + "err": "[Errno 2] No such file or directory: 'lspci'", + "out": "", + "rc": 127 + }, + "pci": "0000:12:00.1", + "sysfs": { + "address": "bc:97:e1:00:30:01", + "carrier": "1", + "dev_port": "0", + "duplex": "full", + "mtu": "1500", + "operstate": "up", + "phys_port_name": "p1", + "speed": "10000", + "type": "1" + } + } + }, + "sections": [ + "ports", + "lldp", + "ethtool", + "module", + "nic", + "system" + ], + "version": 1 +} diff --git a/tests/unit/utils/hardware/fixtures/linux-connectx4.json b/tests/unit/utils/hardware/fixtures/linux-connectx4.json index 0b3cea67d..4c79e138c 100644 --- a/tests/unit/utils/hardware/fixtures/linux-connectx4.json +++ b/tests/unit/utils/hardware/fixtures/linux-connectx4.json @@ -1,5 +1,10 @@ { "collector": "linux", + "dmi": { + "product_name": "X10DRi", + "product_serial": "SN00000003", + "sys_vendor": "Supermicro" + }, "hostname": "node-1", "ip_link": { "err": "info: Using default fan map value (33)", @@ -8,17 +13,47 @@ }, "lldpctl": { "err": "", - "out": "{\n \"lldp\": {\n \"interface\": [\n {\n \"enp4s0f0\": {\n \"via\": \"LLDP\",\n \"rid\": \"3\",\n \"age\": \"5 days, 15:27:45\",\n \"chassis\": {\n \"oob-1\": {\n \"id\": {\n \"type\": \"mac\",\n \"value\": \"02:00:00:00:00:1c\"\n },\n \"descr\": \"MikroTik RouterOS 6.49.1 (stable) CRS354-48G-4S+2Q+\",\n \"mgmt-ip\": \"192.0.2.1\",\n \"mgmt-iface\": \"27\",\n \"capability\": [\n {\n \"type\": \"Bridge\",\n \"enabled\": true\n },\n {\n \"type\": \"Router\",\n \"enabled\": true\n }\n ]\n }\n },\n \"port\": {\n \"id\": {\n \"type\": \"ifname\",\n \"value\": \"bridge/ether14\"\n },\n \"ttl\": \"120\"\n }\n }\n },\n {\n \"ens4f0np0\": {\n \"via\": \"LLDP\",\n \"rid\": \"2\",\n \"age\": \"5 days, 15:28:25\",\n \"chassis\": {\n \"leaf-1\": {\n \"id\": {\n \"type\": \"mac\",\n \"value\": \"02:00:00:00:00:1d\"\n },\n \"descr\": \"SONiC Software Version: SONiC.4.5.2-Enterprise_Base - HwSku: Accton-AS7726-32X - Distribution: Debian 11.11 - Kernel: 5.10.0-39-amd64\",\n \"mgmt-ip\": \"192.0.2.2\",\n \"capability\": [\n {\n \"type\": \"Bridge\",\n \"enabled\": false\n },\n {\n \"type\": \"Router\",\n \"enabled\": true\n },\n {\n \"type\": \"Wlan\",\n \"enabled\": false\n },\n {\n \"type\": \"Station\",\n \"enabled\": false\n }\n ]\n }\n },\n \"port\": {\n \"id\": {\n \"type\": \"local\",\n \"value\": \"Eth1/1/1\"\n },\n \"descr\": \"Ethernet0\",\n \"ttl\": \"120\",\n \"mfs\": \"9100\"\n },\n \"vlan\": [\n {\n \"vlan-id\": \"101\",\n \"pvid\": false,\n \"value\": \"Vlan_101\"\n },\n {\n \"vlan-id\": \"102\",\n \"pvid\": false,\n \"value\": \"Vlan_102\"\n },\n {\n \"vlan-id\": \"103\",\n \"pvid\": false,\n \"value\": \"Vlan_103\"\n }\n ],\n \"lldp-med\": {\n \"device-type\": \"Network Connectivity Device\",\n \"capability\": [\n {\n \"type\": \"Capabilities\",\n \"available\": true\n },\n {\n \"type\": \"Policy\",\n \"available\": true\n },\n {\n \"type\": \"Location\",\n \"available\": true\n },\n {\n \"type\": \"MDI/PSE\",\n \"available\": true\n },\n {\n \"type\": \"MDI/PD\",\n \"available\": true\n },\n {\n \"type\": \"Inventory\",\n \"available\": true\n }\n ],\n \"inventory\": {\n \"hardware\": \"R01H\",\n \"software\": \"5.10.0-39-amd64\",\n \"firmware\": \"v40.01.01.01\",\n \"serial\": \"SN00000001\",\n \"manufacturer\": \"Accton\",\n \"model\": \"AS7726-32X\",\n \"asset\": \"None\"\n }\n },\n \"unknown-tlvs\": {\n \"unknown-tlv\": {\n \"oui\": \"00,80,C2\",\n \"subtype\": \"7\",\n \"len\": \"5\",\n \"value\": \"01,00,00,00,00\"\n }\n }\n }\n },\n {\n \"ens4f1np1\": {\n \"via\": \"LLDP\",\n \"rid\": \"1\",\n \"age\": \"5 days, 15:28:25\",\n \"chassis\": {\n \"leaf-2\": {\n \"id\": {\n \"type\": \"mac\",\n \"value\": \"02:00:00:00:00:1e\"\n },\n \"descr\": \"SONiC Software Version: SONiC.4.4.1-Enterprise_Base - HwSku: Accton-AS7726-32X - Distribution: Debian 11.11 - Kernel: 5.10.0-21-amd64\",\n \"mgmt-ip\": \"192.0.2.3\",\n \"capability\": [\n {\n \"type\": \"Bridge\",\n \"enabled\": false\n },\n {\n \"type\": \"Router\",\n \"enabled\": true\n },\n {\n \"type\": \"Wlan\",\n \"enabled\": false\n },\n {\n \"type\": \"Station\",\n \"enabled\": false\n }\n ]\n }\n },\n \"port\": {\n \"id\": {\n \"type\": \"local\",\n \"value\": \"Eth1/1/1\"\n },\n \"descr\": \"Ethernet0\",\n \"ttl\": \"120\",\n \"mfs\": \"9100\"\n },\n \"vlan\": [\n {\n \"vlan-id\": \"104\",\n \"pvid\": false,\n \"value\": \"Vlan_104\"\n },\n {\n \"vlan-id\": \"105\",\n \"pvid\": false,\n \"value\": \"Vlan_105\"\n }\n ],\n \"lldp-med\": {\n \"device-type\": \"Network Connectivity Device\",\n \"capability\": [\n {\n \"type\": \"Capabilities\",\n \"available\": true\n },\n {\n \"type\": \"Policy\",\n \"available\": true\n },\n {\n \"type\": \"Location\",\n \"available\": true\n },\n {\n \"type\": \"MDI/PSE\",\n \"available\": true\n },\n {\n \"type\": \"MDI/PD\",\n \"available\": true\n },\n {\n \"type\": \"Inventory\",\n \"available\": true\n }\n ],\n \"inventory\": {\n \"hardware\": \"R01H\",\n \"software\": \"5.10.0-21-amd64\",\n \"firmware\": \"v40.01.01.01\",\n \"serial\": \"SN00000002\",\n \"manufacturer\": \"Accton\",\n \"model\": \"AS7726-32X\",\n \"asset\": \"None\"\n }\n },\n \"unknown-tlvs\": {\n \"unknown-tlv\": {\n \"oui\": \"00,80,C2\",\n \"subtype\": \"7\",\n \"len\": \"5\",\n \"value\": \"01,00,00,00,00\"\n }\n }\n }\n }\n ]\n }\n}\n", + "out": "{\n \"lldp\": {\n \"interface\": [\n {\n \"enp4s0f0\": {\n \"via\": \"LLDP\",\n \"rid\": \"3\",\n \"age\": \"5 days, 15:16:28\",\n \"chassis\": {\n \"oob-1\": {\n \"id\": {\n \"type\": \"mac\",\n \"value\": \"02:00:00:00:00:1c\"\n },\n \"descr\": \"MikroTik RouterOS 6.49.1 (stable) CRS354-48G-4S+2Q+\",\n \"mgmt-ip\": \"192.0.2.1\",\n \"mgmt-iface\": \"27\",\n \"capability\": [\n {\n \"type\": \"Bridge\",\n \"enabled\": true\n },\n {\n \"type\": \"Router\",\n \"enabled\": true\n }\n ]\n }\n },\n \"port\": {\n \"id\": {\n \"type\": \"ifname\",\n \"value\": \"bridge/ether14\"\n },\n \"ttl\": \"120\"\n }\n }\n },\n {\n \"ens4f0np0\": {\n \"via\": \"LLDP\",\n \"rid\": \"2\",\n \"age\": \"5 days, 15:17:08\",\n \"chassis\": {\n \"leaf-1\": {\n \"id\": {\n \"type\": \"mac\",\n \"value\": \"02:00:00:00:00:1d\"\n },\n \"descr\": \"SONiC Software Version: SONiC.4.5.2-Enterprise_Base - HwSku: Accton-AS7726-32X - Distribution: Debian 11.11 - Kernel: 5.10.0-39-amd64\",\n \"mgmt-ip\": \"192.0.2.2\",\n \"capability\": [\n {\n \"type\": \"Bridge\",\n \"enabled\": false\n },\n {\n \"type\": \"Router\",\n \"enabled\": true\n },\n {\n \"type\": \"Wlan\",\n \"enabled\": false\n },\n {\n \"type\": \"Station\",\n \"enabled\": false\n }\n ]\n }\n },\n \"port\": {\n \"id\": {\n \"type\": \"local\",\n \"value\": \"Eth1/1/1\"\n },\n \"descr\": \"Ethernet0\",\n \"ttl\": \"120\",\n \"mfs\": \"9100\"\n },\n \"vlan\": [\n {\n \"vlan-id\": \"101\",\n \"pvid\": false,\n \"value\": \"Vlan_101\"\n },\n {\n \"vlan-id\": \"102\",\n \"pvid\": false,\n \"value\": \"Vlan_102\"\n },\n {\n \"vlan-id\": \"103\",\n \"pvid\": false,\n \"value\": \"Vlan_103\"\n }\n ],\n \"lldp-med\": {\n \"device-type\": \"Network Connectivity Device\",\n \"capability\": [\n {\n \"type\": \"Capabilities\",\n \"available\": true\n },\n {\n \"type\": \"Policy\",\n \"available\": true\n },\n {\n \"type\": \"Location\",\n \"available\": true\n },\n {\n \"type\": \"MDI/PSE\",\n \"available\": true\n },\n {\n \"type\": \"MDI/PD\",\n \"available\": true\n },\n {\n \"type\": \"Inventory\",\n \"available\": true\n }\n ],\n \"inventory\": {\n \"hardware\": \"R01H\",\n \"software\": \"5.10.0-39-amd64\",\n \"firmware\": \"v40.01.01.01\",\n \"serial\": \"SN00000001\",\n \"manufacturer\": \"Accton\",\n \"model\": \"AS7726-32X\",\n \"asset\": \"None\"\n }\n },\n \"unknown-tlvs\": {\n \"unknown-tlv\": {\n \"oui\": \"00,80,C2\",\n \"subtype\": \"7\",\n \"len\": \"5\",\n \"value\": \"01,00,00,00,00\"\n }\n }\n }\n },\n {\n \"ens4f1np1\": {\n \"via\": \"LLDP\",\n \"rid\": \"1\",\n \"age\": \"5 days, 15:17:08\",\n \"chassis\": {\n \"leaf-2\": {\n \"id\": {\n \"type\": \"mac\",\n \"value\": \"02:00:00:00:00:1e\"\n },\n \"descr\": \"SONiC Software Version: SONiC.4.4.1-Enterprise_Base - HwSku: Accton-AS7726-32X - Distribution: Debian 11.11 - Kernel: 5.10.0-21-amd64\",\n \"mgmt-ip\": \"192.0.2.3\",\n \"capability\": [\n {\n \"type\": \"Bridge\",\n \"enabled\": false\n },\n {\n \"type\": \"Router\",\n \"enabled\": true\n },\n {\n \"type\": \"Wlan\",\n \"enabled\": false\n },\n {\n \"type\": \"Station\",\n \"enabled\": false\n }\n ]\n }\n },\n \"port\": {\n \"id\": {\n \"type\": \"local\",\n \"value\": \"Eth1/1/1\"\n },\n \"descr\": \"Ethernet0\",\n \"ttl\": \"120\",\n \"mfs\": \"9100\"\n },\n \"vlan\": [\n {\n \"vlan-id\": \"104\",\n \"pvid\": false,\n \"value\": \"Vlan_104\"\n },\n {\n \"vlan-id\": \"105\",\n \"pvid\": false,\n \"value\": \"Vlan_105\"\n }\n ],\n \"lldp-med\": {\n \"device-type\": \"Network Connectivity Device\",\n \"capability\": [\n {\n \"type\": \"Capabilities\",\n \"available\": true\n },\n {\n \"type\": \"Policy\",\n \"available\": true\n },\n {\n \"type\": \"Location\",\n \"available\": true\n },\n {\n \"type\": \"MDI/PSE\",\n \"available\": true\n },\n {\n \"type\": \"MDI/PD\",\n \"available\": true\n },\n {\n \"type\": \"Inventory\",\n \"available\": true\n }\n ],\n \"inventory\": {\n \"hardware\": \"R01H\",\n \"software\": \"5.10.0-21-amd64\",\n \"firmware\": \"v40.01.01.01\",\n \"serial\": \"SN00000002\",\n \"manufacturer\": \"Accton\",\n \"model\": \"AS7726-32X\",\n \"asset\": \"None\"\n }\n },\n \"unknown-tlvs\": {\n \"unknown-tlv\": {\n \"oui\": \"00,80,C2\",\n \"subtype\": \"7\",\n \"len\": \"5\",\n \"value\": \"01,00,00,00,00\"\n }\n }\n }\n }\n ]\n }\n}\n", "rc": 0 }, "ports": { "enp4s0f0": { + "devlink": { + "err": "kernel answers: No such device", + "out": "{\"info\":{}}\n", + "rc": 1 + }, + "ethtool": { + "err": "", + "out": "Settings for enp4s0f0:\n\tSupported ports: [ TP ]\n\tSupported link modes: 10baseT/Half 10baseT/Full\n\t 100baseT/Half 100baseT/Full\n\t 1000baseT/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: Not reported\n\tAdvertised link modes: 10baseT/Half 10baseT/Full\n\t 100baseT/Half 100baseT/Full\n\t 1000baseT/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: Not reported\n\tSpeed: 1000Mb/s\n\tDuplex: Full\n\tAuto-negotiation: on\n\tPort: Twisted Pair\n\tPHYAD: 1\n\tTransceiver: internal\n\tMDI-X: off (auto)\n\tSupports Wake-on: pumbg\n\tWake-on: g\n Current message level: 0x00000007 (7)\n drv probe link\n\tLink detected: yes\n", + "rc": 0 + }, "ethtool_P": { "err": "", "out": "Permanent address: 02:00:00:00:00:19\n", "rc": 0 }, + "ethtool_fec": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 1 + }, + "ethtool_i": { + "err": "", + "out": "driver: igb\nversion: 6.8.0-136-generic\nfirmware-version: 1.63, 0x80000a05\nexpansion-rom-version: \nbus-info: 0000:04:00.0\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: yes\nsupports-register-dump: yes\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 1 + }, "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:04:00.0\nClass:\tEthernet controller [0200]\nVendor:\tIntel Corporation [8086]\nDevice:\tI350 Gigabit Network Connection [1521]\nSVendor:\tSuper Micro Computer Inc [15d9]\nSDevice:\tI350 Gigabit Network Connection [1521]\nPhySlot:\t0\nRev:\t01\nProgIf:\t00\nNUMANode:\t0\nIOMMUGroup:\t69\n\n", + "rc": 0 + }, "pci": "0000:04:00.0", "sysfs": { "address": "02:00:00:00:00:19", @@ -33,12 +68,42 @@ } }, "enp4s0f1": { + "devlink": { + "err": "kernel answers: No such device", + "out": "{\"info\":{}}\n", + "rc": 1 + }, + "ethtool": { + "err": "", + "out": "Settings for enp4s0f1:\n\tSupported ports: [ TP ]\n\tSupported link modes: 10baseT/Half 10baseT/Full\n\t 100baseT/Half 100baseT/Full\n\t 1000baseT/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: Not reported\n\tAdvertised link modes: 10baseT/Half 10baseT/Full\n\t 100baseT/Half 100baseT/Full\n\t 1000baseT/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: Not reported\n\tSpeed: Unknown!\n\tDuplex: Unknown! (255)\n\tAuto-negotiation: on\n\tPort: Twisted Pair\n\tPHYAD: 1\n\tTransceiver: internal\n\tMDI-X: off (auto)\n\tSupports Wake-on: d\n\tWake-on: d\n Current message level: 0x00000007 (7)\n drv probe link\n\tLink detected: no\n", + "rc": 0 + }, "ethtool_P": { "err": "", "out": "Permanent address: 02:00:00:00:00:1b\n", "rc": 0 }, + "ethtool_fec": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 1 + }, + "ethtool_i": { + "err": "", + "out": "driver: igb\nversion: 6.8.0-136-generic\nfirmware-version: 1.63, 0x80000a05\nexpansion-rom-version: \nbus-info: 0000:04:00.1\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: yes\nsupports-register-dump: yes\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 1 + }, "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:04:00.1\nClass:\tEthernet controller [0200]\nVendor:\tIntel Corporation [8086]\nDevice:\tI350 Gigabit Network Connection [1521]\nSVendor:\tSuper Micro Computer Inc [15d9]\nSDevice:\tI350 Gigabit Network Connection [1521]\nPhySlot:\t0\nRev:\t01\nProgIf:\t00\nNUMANode:\t0\nIOMMUGroup:\t70\n\n", + "rc": 0 + }, "pci": "0000:04:00.1", "sysfs": { "address": "02:00:00:00:00:1b", @@ -53,12 +118,42 @@ } }, "ens4f0np0": { + "devlink": { + "err": "", + "out": "{\"info\":{\"pci/0000:82:00.0\":{\"driver\":\"mlx5_core\",\"versions\":{\"fixed\":{\"fw.psid\":\"MT_2420110034\"},\"running\":{\"fw.version\":\"14.32.1010\",\"fw\":\"14.32.1010\"},\"stored\":{\"fw.version\":\"14.32.1010\",\"fw\":\"14.32.1010\"}}}}}\n", + "rc": 0 + }, + "ethtool": { + "err": "", + "out": "Settings for ens4f0np0:\n\tSupported ports: [ Backplane ]\n\tSupported link modes: 1000baseKX/Full\n\t 10000baseKR/Full\n\t 25000baseCR/Full\n\t 25000baseKR/Full\n\t 25000baseSR/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: None\t RS\t BASER\n\tAdvertised link modes: 1000baseKX/Full\n\t 10000baseKR/Full\n\t 25000baseCR/Full\n\t 25000baseKR/Full\n\t 25000baseSR/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: None\n\tSpeed: 25000Mb/s\n\tDuplex: Full\n\tAuto-negotiation: on\n\tPort: Direct Attach Copper\n\tPHYAD: 0\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n\tLink detected: yes\n", + "rc": 0 + }, "ethtool_P": { "err": "", "out": "Permanent address: 02:00:00:00:00:01\n", "rc": 0 }, + "ethtool_fec": { + "err": "", + "out": "FEC parameters for ens4f0np0:\nSupported/Configured FEC encodings: Auto\nActive FEC encoding: Off\n", + "rc": 0 + }, + "ethtool_i": { + "err": "", + "out": "driver: mlx5_core\nversion: 6.8.0-136-generic\nfirmware-version: 14.32.1010 (MT_2420110034)\nexpansion-rom-version: \nbus-info: 0000:82:00.0\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: no\nsupports-register-dump: no\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "", + "out": "\tIdentifier : 0x03 (SFP)\n\tExtended identifier : 0x04 (GBIC/SFP defined by 2-wire interface ID)\n\tConnector : 0x21 (Copper pigtail)\n\tTransceiver codes : 0x00 0x00 0x00 0x00 0x00 0x04 0x00 0x00 0x0c\n\tTransceiver type : Passive Cable\n\tTransceiver type : Extended: 25G Base-CR CA-S\n\tEncoding : 0x00 (unspecified)\n\tBR, Nominal : 25750MBd\n\tRate identifier : 0x00 (unspecified)\n\tLength (SMF,km) : 0km\n\tLength (SMF) : 0m\n\tLength (50um) : 0m\n\tLength (62.5um) : 0m\n\tLength (Copper) : 2m\n\tLength (OM3) : 0m\n\tPassive Cu cmplnce. : 0x00 (unspecified) [SFF-8472 rev10.4 only]\n\tVendor name : FS\n\tVendor OUI : 64:9d:99\n\tVendor PN : Q-4S28PC02\n\tVendor rev : 01\n\tOption values : 0x08 0x00\n\tOption : Retimer or CDR implemented\n\tBR margin, max : 0%\n\tBR margin, min : 0%\n\tVendor SN : SN00000004-1\n\tDate code : 220907\n", + "rc": 0 + }, "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:82:00.0\nClass:\tEthernet controller [0200]\nVendor:\tMellanox Technologies [15b3]\nDevice:\tMT27710 Family [ConnectX-4 Lx] [1015]\nSVendor:\tMellanox Technologies [15b3]\nSDevice:\tStand-up ConnectX-4 Lx EN, 25GbE dual-port SFP28, PCIe3.0 x8, MCX4121A-ACAT [0003]\nPhySlot:\t4\nProgIf:\t00\nNUMANode:\t1\nIOMMUGroup:\t11\n\n", + "rc": 0 + }, "pci": "0000:82:00.0", "sysfs": { "address": "02:00:00:00:00:01", @@ -73,12 +168,42 @@ } }, "ens4f1np1": { + "devlink": { + "err": "", + "out": "{\"info\":{\"pci/0000:82:00.1\":{\"driver\":\"mlx5_core\",\"versions\":{\"fixed\":{\"fw.psid\":\"MT_2420110034\"},\"running\":{\"fw.version\":\"14.32.1010\",\"fw\":\"14.32.1010\"},\"stored\":{\"fw.version\":\"14.32.1010\",\"fw\":\"14.32.1010\"}}}}}\n", + "rc": 0 + }, + "ethtool": { + "err": "", + "out": "Settings for ens4f1np1:\n\tSupported ports: [ Backplane ]\n\tSupported link modes: 1000baseKX/Full\n\t 10000baseKR/Full\n\t 25000baseCR/Full\n\t 25000baseKR/Full\n\t 25000baseSR/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: None\t RS\t BASER\n\tAdvertised link modes: 1000baseKX/Full\n\t 10000baseKR/Full\n\t 25000baseCR/Full\n\t 25000baseKR/Full\n\t 25000baseSR/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: None\n\tSpeed: 25000Mb/s\n\tDuplex: Full\n\tAuto-negotiation: on\n\tPort: Direct Attach Copper\n\tPHYAD: 0\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n\tLink detected: yes\n", + "rc": 0 + }, "ethtool_P": { "err": "", "out": "Permanent address: 02:00:00:00:00:02\n", "rc": 0 }, + "ethtool_fec": { + "err": "", + "out": "FEC parameters for ens4f1np1:\nSupported/Configured FEC encodings: Auto\nActive FEC encoding: Off\n", + "rc": 0 + }, + "ethtool_i": { + "err": "", + "out": "driver: mlx5_core\nversion: 6.8.0-136-generic\nfirmware-version: 14.32.1010 (MT_2420110034)\nexpansion-rom-version: \nbus-info: 0000:82:00.1\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: no\nsupports-register-dump: no\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "", + "out": "\tIdentifier : 0x03 (SFP)\n\tExtended identifier : 0x04 (GBIC/SFP defined by 2-wire interface ID)\n\tConnector : 0x21 (Copper pigtail)\n\tTransceiver codes : 0x00 0x00 0x00 0x00 0x00 0x04 0x00 0x00 0x0c\n\tTransceiver type : Passive Cable\n\tTransceiver type : Extended: 25G Base-CR CA-S\n\tEncoding : 0x00 (unspecified)\n\tBR, Nominal : 25750MBd\n\tRate identifier : 0x00 (unspecified)\n\tLength (SMF,km) : 0km\n\tLength (SMF) : 0m\n\tLength (50um) : 0m\n\tLength (62.5um) : 0m\n\tLength (Copper) : 2m\n\tLength (OM3) : 0m\n\tPassive Cu cmplnce. : 0x00 (unspecified) [SFF-8472 rev10.4 only]\n\tVendor name : FS\n\tVendor OUI : 64:9d:99\n\tVendor PN : Q-4S28PC02\n\tVendor rev : 01\n\tOption values : 0x08 0x00\n\tOption : Retimer or CDR implemented\n\tBR margin, max : 0%\n\tBR margin, min : 0%\n\tVendor SN : SN00000005-1\n\tDate code : 220907\n", + "rc": 0 + }, "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:82:00.1\nClass:\tEthernet controller [0200]\nVendor:\tMellanox Technologies [15b3]\nDevice:\tMT27710 Family [ConnectX-4 Lx] [1015]\nSVendor:\tMellanox Technologies [15b3]\nSDevice:\tStand-up ConnectX-4 Lx EN, 25GbE dual-port SFP28, PCIe3.0 x8, MCX4121A-ACAT [0003]\nPhySlot:\t4\nProgIf:\t00\nNUMANode:\t1\nIOMMUGroup:\t12\n\n", + "rc": 0 + }, "pci": "0000:82:00.1", "sysfs": { "address": "02:00:00:00:00:02", @@ -95,7 +220,11 @@ }, "sections": [ "ports", - "lldp" + "lldp", + "ethtool", + "module", + "nic", + "system" ], "version": 1 } diff --git a/tests/unit/utils/hardware/fixtures/linux-intel.json b/tests/unit/utils/hardware/fixtures/linux-intel.json new file mode 100644 index 000000000..f9619f898 --- /dev/null +++ b/tests/unit/utils/hardware/fixtures/linux-intel.json @@ -0,0 +1,180 @@ +{ + "collector": "linux", + "dmi": { + "product_name": "PowerEdge R750", + "product_serial": "7ABCDE3", + "sys_vendor": "Dell Inc." + }, + "hostname": "storage-001", + "ip_link": { + "err": "", + "out": "[{\"ifindex\": 2, \"ifname\": \"enp65s0f0\", \"flags\": [\"BROADCAST\", \"MULTICAST\", \"UP\", \"LOWER_UP\"], \"mtu\": 9000, \"qdisc\": \"mq\", \"operstate\": \"UP\", \"link_type\": \"ether\", \"address\": \"40:a6:b7:00:10:00\", \"broadcast\": \"ff:ff:ff:ff:ff:ff\"}, {\"ifindex\": 3, \"ifname\": \"enp65s0f1\", \"flags\": [\"NO-CARRIER\", \"BROADCAST\", \"MULTICAST\", \"UP\"], \"mtu\": 9000, \"qdisc\": \"mq\", \"operstate\": \"DOWN\", \"link_type\": \"ether\", \"address\": \"40:a6:b7:00:10:01\", \"broadcast\": \"ff:ff:ff:ff:ff:ff\"}, {\"ifindex\": 4, \"ifname\": \"enp66s0\", \"flags\": [\"BROADCAST\", \"MULTICAST\", \"UP\", \"LOWER_UP\"], \"mtu\": 9000, \"qdisc\": \"mq\", \"operstate\": \"UP\", \"link_type\": \"ether\", \"address\": \"40:a6:b7:00:20:00\", \"broadcast\": \"ff:ff:ff:ff:ff:ff\"}]", + "rc": 0 + }, + "lldpctl": { + "err": "", + "out": "{\"lldp\": {}}", + "rc": 0 + }, + "ports": { + "enp65s0f0": { + "devlink": { + "err": "", + "out": "{\"info\": {\"pci/0000:41:00.0\": {\"driver\": \"ice\", \"versions\": {\"fixed\": {\"board.id\": \"K91258-006\"}, \"running\": {\"fw.mgmt\": \"7.4.13\", \"fw.undi\": \"1.3534.0\", \"fw.psid.api\": \"4.40\", \"fw.bundle_id\": \"0x8001c7d4\"}, \"stored\": {\"fw.undi\": \"1.3534.0\", \"fw.bundle_id\": \"0x8001c7d4\"}}, \"serial_number\": \"00-01-00-ff-ff-00-00-00\"}}}", + "rc": 0 + }, + "ethtool": { + "err": "", + "out": "Settings for IFACE:\n\tSupported ports: [ FIBRE ]\n\tSupported link modes: 1000baseKX/Full\n\t 100000baseSR4/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: None\t RS\t BASER\n\tAdvertised link modes: 100000baseSR4/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: RS\n\tSpeed: 100000Mb/s\n\tDuplex: Full\n\tAuto-negotiation: off\n\tPort: FIBRE\n\tPHYAD: 0\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n\tLink detected: yes\n", + "rc": 0 + }, + "ethtool_P": { + "err": "", + "out": "Permanent address: 40:a6:b7:00:10:00\n", + "rc": 0 + }, + "ethtool_fec": { + "err": "", + "out": "FEC parameters for IFACE:\nSupported/Configured FEC encodings: Auto RS BaseR\nActive FEC encoding: RS\n", + "rc": 0 + }, + "ethtool_i": { + "err": "", + "out": "driver: ice\nversion: 6.8.0-51-generic\nfirmware-version: 4.40 0x8001c7d4 1.3534.0\nexpansion-rom-version: \nbus-info: 0000:41:00.0\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: no\nsupports-register-dump: no\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "", + "out": "\tIdentifier : 0x11 (QSFP28)\n\tExtended identifier : 0xcc\n\tExtended identifier description : 3.5W max. Power consumption\n\tConnector : 0x0c (MPO Parallel Optic)\n\tTransceiver codes : 0x80 0x00 0x00 0x00 0x00 0x00 0x00 0x00\n\tTransceiver type : 100G Ethernet: 100G Base-SR4 or 25GBase-SR\n\tEncoding : 0x05 (64B/66B)\n\tBR, Nominal : 25500Mbps\n\tRate identifier : 0x00\n\tLength (SMF,km) : 0km\n\tLength (OM3 50um) : 70m\n\tLength (OM2 50um) : 0m\n\tLength (OM1 62.5um) : 0m\n\tLength (Copper or Active cable) : 50m\n\tTransmitter technology : 0x00 (850 nm VCSEL)\n\tLaser wavelength : 850.000nm\n\tLaser wavelength tolerance : 10.000nm\n\tVendor name : Intel Corp\n\tVendor OUI : 00:1b:21\n\tVendor PN : SPTSBP2CLCKS\n\tVendor rev : 01\n\tVendor SN : INCAL1234567\n\tDate code : 230117\n\tRevision Compliance : SFF-8636 Rev 2.5/2.6/2.7\n\tModule temperature : 31.00 degrees C / 87.80 degrees F\n\tModule voltage : 3.2800 V\n\tAlarm/warning flags implemented : Yes\n\tLaser tx bias current (Channel 1) : 6.750 mA\n\tLaser tx bias current (Channel 2) : 6.812 mA\n\tLaser tx bias current (Channel 3) : 6.700 mA\n\tLaser tx bias current (Channel 4) : 6.790 mA\n\tTransmit avg optical power (Channel 1) : 0.9500 mW / -0.22 dBm\n\tTransmit avg optical power (Channel 2) : 0.9300 mW / -0.32 dBm\n\tTransmit avg optical power (Channel 3) : 0.9700 mW / -0.13 dBm\n\tTransmit avg optical power (Channel 4) : 0.9100 mW / -0.41 dBm\n\tRcvr signal avg optical power(Channel 1) : 0.8200 mW / -0.86 dBm\n\tRcvr signal avg optical power(Channel 2) : 0.8000 mW / -0.97 dBm\n\tRcvr signal avg optical power(Channel 3) : 0.7900 mW / -1.02 dBm\n\tRcvr signal avg optical power(Channel 4) : 0.8100 mW / -0.92 dBm\n\tLaser bias current high alarm threshold : 10.000 mA\n\tLaser rx power high alarm threshold : 2.1878 mW / 3.40 dBm\n", + "rc": 0 + }, + "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:41:00.0\nClass:\tEthernet controller [0200]\nVendor:\tIntel Corporation [8086]\nDevice:\tEthernet Controller E810-C for QSFP [1592]\nSVendor:\tIntel Corporation [8086]\nSDevice:\tEthernet Network Adapter E810-C-Q2 [0002]\nRev:\t02\nNUMANode:\t0\n\n", + "rc": 0 + }, + "pci": "0000:41:00.0", + "sysfs": { + "address": "40:a6:b7:00:10:00", + "carrier": "1", + "dev_port": "0", + "duplex": "full", + "mtu": "9000", + "operstate": "up", + "phys_port_name": "p0", + "speed": "100000", + "type": "1" + } + }, + "enp65s0f1": { + "devlink": { + "err": "", + "out": "{\"info\": {\"pci/0000:41:00.1\": {\"driver\": \"ice\", \"versions\": {\"fixed\": {\"board.id\": \"K91258-006\"}, \"running\": {\"fw.mgmt\": \"7.4.13\", \"fw.undi\": \"1.3534.0\", \"fw.psid.api\": \"4.40\", \"fw.bundle_id\": \"0x8001c7d4\"}, \"stored\": {\"fw.undi\": \"1.3534.0\", \"fw.bundle_id\": \"0x8001c7d4\"}}, \"serial_number\": \"00-01-00-ff-ff-00-00-00\"}}}", + "rc": 0 + }, + "ethtool": { + "err": "", + "out": "Settings for IFACE:\n\tSupported ports: [ FIBRE ]\n\tSupported link modes: 1000baseKX/Full\n\t 100000baseSR4/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: None\t RS\t BASER\n\tAdvertised link modes: 100000baseSR4/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: RS\n\tSpeed: Unknown!\n\tDuplex: Unknown! (255)\n\tAuto-negotiation: off\n\tPort: Other\n\tPHYAD: 0\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n\tLink detected: no\n", + "rc": 0 + }, + "ethtool_P": { + "err": "", + "out": "Permanent address: 40:a6:b7:00:10:01\n", + "rc": 0 + }, + "ethtool_fec": { + "err": "", + "out": "FEC parameters for IFACE:\nSupported/Configured FEC encodings: Auto RS BaseR\nActive FEC encoding: None\n", + "rc": 0 + }, + "ethtool_i": { + "err": "", + "out": "driver: ice\nversion: 6.8.0-51-generic\nfirmware-version: 4.40 0x8001c7d4 1.3534.0\nexpansion-rom-version: \nbus-info: 0000:41:00.1\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: no\nsupports-register-dump: no\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "Cannot get module EEPROM information: Input/output error", + "out": "", + "rc": 1 + }, + "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:41:00.1\nClass:\tEthernet controller [0200]\nVendor:\tIntel Corporation [8086]\nDevice:\tEthernet Controller E810-C for QSFP [1592]\nSVendor:\tIntel Corporation [8086]\nSDevice:\tEthernet Network Adapter E810-C-Q2 [0002]\nRev:\t02\nNUMANode:\t0\n\n", + "rc": 0 + }, + "pci": "0000:41:00.1", + "sysfs": { + "address": "40:a6:b7:00:10:01", + "carrier": "0", + "dev_port": "0", + "duplex": null, + "mtu": "9000", + "operstate": "down", + "phys_port_name": "p1", + "speed": "-1", + "type": "1" + } + }, + "enp66s0": { + "devlink": { + "err": "", + "out": "{\"info\": {\"pci/0000:42:00.0\": {\"driver\": \"ice\", \"versions\": {\"fixed\": {\"board.id\": \"K91258-006\"}, \"running\": {\"fw.mgmt\": \"7.4.13\", \"fw.undi\": \"1.3534.0\", \"fw.psid.api\": \"4.40\", \"fw.bundle_id\": \"0x8001c7d4\"}, \"stored\": {\"fw.undi\": \"1.3534.0\", \"fw.bundle_id\": \"0x8001c7d4\"}}, \"serial_number\": \"00-01-00-ff-ff-00-00-00\"}}}", + "rc": 0 + }, + "ethtool": { + "err": "", + "out": "Settings for IFACE:\n\tSupported ports: [ FIBRE ]\n\tSupported link modes: 1000baseKX/Full\n\t 400000baseSR8/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: None\t RS\t BASER\n\tAdvertised link modes: 400000baseSR8/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: RS\n\tSpeed: 400000Mb/s\n\tDuplex: Full\n\tAuto-negotiation: off\n\tPort: FIBRE\n\tPHYAD: 0\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n\tLink detected: yes\n", + "rc": 0 + }, + "ethtool_P": { + "err": "", + "out": "Permanent address: 40:a6:b7:00:20:00\n", + "rc": 0 + }, + "ethtool_fec": { + "err": "", + "out": "FEC parameters for IFACE:\nSupported/Configured FEC encodings: Auto RS BaseR\nActive FEC encoding: RS\n", + "rc": 0 + }, + "ethtool_i": { + "err": "", + "out": "driver: ice\nversion: 6.8.0-51-generic\nfirmware-version: 4.40 0x8001c7d4 1.3534.0\nexpansion-rom-version: \nbus-info: 0000:42:00.0\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: no\nsupports-register-dump: no\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "", + "out": "\tIdentifier : 0x18 (QSFP-DD Double Density 8X Pluggable Transceiver (INF-8628))\n\tPower class : 5\n\tMax power : 10.00W\n\tConnector : 0x28 (MPO 1x16)\n\tCable assembly length : 0.00m\n\tTx CDR bypass control : No\n\tRx CDR bypass control : No\n\tTx CDR : No\n\tRx CDR : No\n\tTransmitter technology : 0x00 (850 nm VCSEL)\n\tLaser wavelength : 850.000nm\n\tLaser wavelength tolerance : 0.000nm\n\tLength (SMF) : 0.00km\n\tLength (OM5) : 0m\n\tLength (OM4) : 100m\n\tLength (OM3 50/125um) : 70m\n\tLength (OM2 50/125um) : 0m\n\tVendor name : ACME Optics\n\tVendor OUI : 00:11:22\n\tVendor PN : QDD-400G-SR8\n\tVendor rev : 1A\n\tVendor SN : AO24070001\n\tDate code : 240702\n\tRevision compliance : Rev. 4.0\n\tModule State : 0x03 (ModuleReady)\n\tActive firmware version : 2.9\n\tInactive firmware version : 2.8\n\tModule temperature : 41.50 degrees C / 106.70 degrees F\n\tModule voltage : 3.3100 V\n", + "rc": 0 + }, + "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:42:00.0\nClass:\tEthernet controller [0200]\nVendor:\tIntel Corporation [8086]\nDevice:\tEthernet Controller E810-C for QSFP [1592]\nSVendor:\tIntel Corporation [8086]\nSDevice:\tEthernet Network Adapter E810-C-Q2 [0002]\nRev:\t02\nNUMANode:\t0\n\n", + "rc": 0 + }, + "pci": "0000:42:00.0", + "sysfs": { + "address": "40:a6:b7:00:20:00", + "carrier": "1", + "dev_port": "0", + "duplex": "full", + "mtu": "9000", + "operstate": "up", + "phys_port_name": null, + "speed": "400000", + "type": "1" + } + } + }, + "sections": [ + "ports", + "lldp", + "ethtool", + "module", + "nic", + "system" + ], + "version": 1 +} diff --git a/tests/unit/utils/hardware/fixtures/linux-mellanox-vf-ib.json b/tests/unit/utils/hardware/fixtures/linux-mellanox-vf-ib.json new file mode 100644 index 000000000..f629eedb2 --- /dev/null +++ b/tests/unit/utils/hardware/fixtures/linux-mellanox-vf-ib.json @@ -0,0 +1,189 @@ +{ + "collector": "linux", + "dmi": { + "product_name": "SYS-420GP-TNR", + "product_serial": "S987654X1B00001", + "sys_vendor": "Supermicro" + }, + "hostname": "gpu-001", + "ip_link": { + "err": "", + "out": "[{\"ifindex\": 2, \"ifname\": \"ens3f0np0\", \"flags\": [\"BROADCAST\", \"MULTICAST\", \"UP\", \"LOWER_UP\"], \"mtu\": 9000, \"qdisc\": \"mq\", \"operstate\": \"UP\", \"link_type\": \"ether\", \"address\": \"b8:ce:f6:bb:00:10\", \"broadcast\": \"ff:ff:ff:ff:ff:ff\"}, {\"ifindex\": 3, \"ifname\": \"ens3f0v0\", \"flags\": [\"BROADCAST\", \"MULTICAST\", \"UP\", \"LOWER_UP\"], \"mtu\": 9000, \"qdisc\": \"mq\", \"operstate\": \"UP\", \"link_type\": \"ether\", \"address\": \"02:00:00:00:00:01\", \"broadcast\": \"ff:ff:ff:ff:ff:ff\"}, {\"ifindex\": 4, \"ifname\": \"ibp129s0\", \"flags\": [\"BROADCAST\", \"MULTICAST\", \"UP\", \"LOWER_UP\"], \"mtu\": 2044, \"operstate\": \"UP\", \"link_type\": \"infiniband\", \"address\": \"00:00:10:49:fe:80:00:00:00:00:00:00:b8:ce:f6:03:00:cc:00:10\"}]", + "rc": 0 + }, + "lldpctl": { + "err": "", + "out": "{\"lldp\": {}}", + "rc": 0 + }, + "ports": { + "ens3f0np0": { + "devlink": { + "err": "kernel answers: Operation not supported", + "out": "", + "rc": 1 + }, + "ethtool": { + "err": "", + "out": "Settings for IFACE:\n\tSupported ports: [ FIBRE ]\n\tSupported link modes: 1000baseKX/Full\n\t 25000baseCR/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: None\t RS\t BASER\n\tAdvertised link modes: 25000baseCR/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: RS\n\tSpeed: 25000Mb/s\n\tDuplex: Full\n\tAuto-negotiation: on\n\tPort: Direct Attach Copper\n\tPHYAD: 0\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n\tLink detected: yes\n", + "rc": 0 + }, + "ethtool_P": { + "err": "", + "out": "Permanent address: b8:ce:f6:bb:00:10\n", + "rc": 0 + }, + "ethtool_fec": { + "err": "", + "out": "FEC parameters for IFACE:\nSupported/Configured FEC encodings: Auto RS BaseR\nActive FEC encoding: RS\n", + "rc": 0 + }, + "ethtool_i": { + "err": "", + "out": "driver: mlx5_core\nversion: 6.8.0-51-generic\nfirmware-version: 16.35.2000 (MT_0000000080)\nexpansion-rom-version: \nbus-info: 0000:5e:00.0\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: no\nsupports-register-dump: no\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "", + "out": "\tIdentifier : 0x03 (SFP)\n\tExtended identifier : 0x04 (GBIC/SFP defined by 2-wire interface ID)\n\tConnector : 0x21 (Copper pigtail)\n\tTransceiver codes : 0x01 0x00 0x00 0x04 0x00 0x04 0x80 0x00 0x00\n\tTransceiver type : Infiniband: 1X Copper Passive\n\tTransceiver type : Ethernet: 1000BASE-CX\n\tTransceiver type : Passive Cable\n\tTransceiver type : FC: Twin Axial Pair (TW)\n\tTransceiver type : Extended: 25G Base-CR CA-N\n\tEncoding : 0x00 (unspecified)\n\tBR, Nominal : 25500MBd\n\tRate identifier : 0x00 (unspecified)\n\tLength (SMF,km) : 0km\n\tLength (SMF) : 0m\n\tLength (50um) : 0m\n\tLength (62.5um) : 0m\n\tLength (Copper) : 3m\n\tLength (OM3) : 0m\n\tPassive Cu cmplnce. : 0x01 (SFF-8431 appendix E) [SFF-8472 rev10.4 only]\n\tVendor name : Mellanox\n\tVendor OUI : 00:02:c9\n\tVendor PN : MCP2M00-A003E30L\n\tVendor rev : A3\n\tOption values : 0x00 0x00\n\tBR margin, max : 0%\n\tBR margin, min : 0%\n\tVendor SN : MT2114VS01234\n\tDate code : 210408\n", + "rc": 0 + }, + "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:5e:00.0\nClass:\tEthernet controller [0200]\nVendor:\tMellanox Technologies [15b3]\nDevice:\tMT27800 Family [ConnectX-5] [1017]\nSVendor:\tMellanox Technologies [15b3]\nSDevice:\tConnectX-5 EN network interface card, 25GbE dual-port SFP28 [0080]\nRev:\t00\nNUMANode:\t0\n\n", + "rc": 0 + }, + "pci": "0000:5e:00.0", + "sysfs": { + "address": "b8:ce:f6:bb:00:10", + "carrier": "1", + "dev_port": "0", + "duplex": "full", + "mtu": "9000", + "operstate": "up", + "phys_port_name": "p0", + "speed": "25000", + "type": "1" + } + }, + "ens3f0v0": { + "devlink": { + "err": "kernel answers: Operation not supported", + "out": "", + "rc": 1 + }, + "ethtool": { + "err": "", + "out": "Settings for IFACE:\n\tSupported ports: [ FIBRE ]\n\tSupported link modes: 1000baseKX/Full\n\t 25000baseCR/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: None\t RS\t BASER\n\tAdvertised link modes: 25000baseCR/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: RS\n\tSpeed: 25000Mb/s\n\tDuplex: Full\n\tAuto-negotiation: off\n\tPort: Other\n\tPHYAD: 0\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n\tLink detected: yes\n", + "rc": 0 + }, + "ethtool_P": { + "err": "", + "out": "Permanent address: 00:00:00:00:00:00\n", + "rc": 0 + }, + "ethtool_fec": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 1 + }, + "ethtool_i": { + "err": "", + "out": "driver: mlx5_core\nversion: 6.8.0-51-generic\nfirmware-version: 16.35.2000 (MT_0000000080)\nexpansion-rom-version: \nbus-info: 0000:5e:00.2\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: no\nsupports-register-dump: no\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 1 + }, + "kind": "vf", + "lspci": { + "err": "", + "out": "Slot:\t0000:5e:00.2\nClass:\tEthernet controller [0200]\nVendor:\tMellanox Technologies [15b3]\nDevice:\tMT27800 Family [ConnectX-5 Virtual Function] [1018]\nSVendor:\tMellanox Technologies [15b3]\nSDevice:\tDevice [0080]\nRev:\t00\nNUMANode:\t0\n\n", + "rc": 0 + }, + "pci": "0000:5e:00.2", + "physfn": [ + "ens3f0np0" + ], + "sysfs": { + "address": "02:00:00:00:00:01", + "carrier": "1", + "dev_port": "0", + "duplex": "full", + "mtu": "9000", + "operstate": "up", + "phys_port_name": null, + "speed": "25000", + "type": "1" + } + }, + "ibp129s0": { + "devlink": { + "err": "kernel answers: Operation not supported", + "out": "", + "rc": 1 + }, + "ethtool": { + "err": "", + "out": "Settings for IFACE:\n\tSupported ports: [ FIBRE ]\n\tSupported link modes: 1000baseKX/Full\n\t 25000baseCR/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: None\t RS\t BASER\n\tAdvertised link modes: 25000baseCR/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: RS\n\tSpeed: Unknown!\n\tDuplex: Unknown! (255)\n\tAuto-negotiation: off\n\tPort: Other\n\tPHYAD: 0\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n\tLink detected: yes\n", + "rc": 0 + }, + "ethtool_P": { + "err": "Cannot read permanent address: Operation not supported", + "out": "", + "rc": 1 + }, + "ethtool_fec": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 1 + }, + "ethtool_i": { + "err": "", + "out": "driver: mlx5_core[ib_ipoib]\nversion: 6.8.0-51-generic\nfirmware-version: 20.35.2000 (MT_0000000224)\nexpansion-rom-version: \nbus-info: 0000:81:00.0\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: no\nsupports-register-dump: no\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "", + "out": "\tIdentifier : 0x11 (QSFP28)\n\tExtended identifier : 0xcc\n\tExtended identifier description : 3.5W max. Power consumption\n\tConnector : 0x0c (MPO Parallel Optic)\n\tTransceiver codes : 0x80 0x00 0x00 0x00 0x00 0x00 0x00 0x00\n\tTransceiver type : 100G Ethernet: 100G Base-SR4 or 25GBase-SR\n\tEncoding : 0x05 (64B/66B)\n\tBR, Nominal : 25500Mbps\n\tRate identifier : 0x00\n\tLength (SMF,km) : 0km\n\tLength (OM3 50um) : 70m\n\tLength (OM2 50um) : 0m\n\tLength (OM1 62.5um) : 0m\n\tLength (Copper or Active cable) : 50m\n\tTransmitter technology : 0x00 (850 nm VCSEL)\n\tLaser wavelength : 850.000nm\n\tLaser wavelength tolerance : 10.000nm\n\tVendor name : Intel Corp\n\tVendor OUI : 00:1b:21\n\tVendor PN : SPTSBP2CLCKS\n\tVendor rev : 01\n\tVendor SN : INCAL1234567\n\tDate code : 230117\n\tRevision Compliance : SFF-8636 Rev 2.5/2.6/2.7\n\tModule temperature : 31.00 degrees C / 87.80 degrees F\n\tModule voltage : 3.2800 V\n\tAlarm/warning flags implemented : Yes\n\tLaser tx bias current (Channel 1) : 6.750 mA\n\tLaser tx bias current (Channel 2) : 6.812 mA\n\tLaser tx bias current (Channel 3) : 6.700 mA\n\tLaser tx bias current (Channel 4) : 6.790 mA\n\tTransmit avg optical power (Channel 1) : 0.9500 mW / -0.22 dBm\n\tTransmit avg optical power (Channel 2) : 0.9300 mW / -0.32 dBm\n\tTransmit avg optical power (Channel 3) : 0.9700 mW / -0.13 dBm\n\tTransmit avg optical power (Channel 4) : 0.9100 mW / -0.41 dBm\n\tRcvr signal avg optical power(Channel 1) : 0.8200 mW / -0.86 dBm\n\tRcvr signal avg optical power(Channel 2) : 0.8000 mW / -0.97 dBm\n\tRcvr signal avg optical power(Channel 3) : 0.7900 mW / -1.02 dBm\n\tRcvr signal avg optical power(Channel 4) : 0.8100 mW / -0.92 dBm\n\tLaser bias current high alarm threshold : 10.000 mA\n\tLaser rx power high alarm threshold : 2.1878 mW / 3.40 dBm\n", + "rc": 0 + }, + "infiniband": { + "device": "mlx5_2", + "port": 1, + "rate": "100 Gb/sec (4X EDR)", + "state": "4: ACTIVE" + }, + "kind": "infiniband", + "lspci": { + "err": "", + "out": "Slot:\t0000:81:00.0\nClass:\tEthernet controller [0200]\nVendor:\tMellanox Technologies [15b3]\nDevice:\tMT28908 Family [ConnectX-6] [101b]\nSVendor:\tMellanox Technologies [15b3]\nSDevice:\tConnectX-6 InfiniBand/Ethernet adapter card, 100Gb/s [0007]\nRev:\t00\nNUMANode:\t0\n\n", + "rc": 0 + }, + "pci": "0000:81:00.0", + "sysfs": { + "address": "00:00:10:49:fe:80:00:00:00:00:00:00:b8:ce:f6:03:00:cc:00:10", + "carrier": "1", + "dev_port": "0", + "duplex": null, + "mtu": "2044", + "operstate": "up", + "phys_port_name": null, + "speed": "-1", + "type": "32" + } + } + }, + "sections": [ + "ports", + "lldp", + "ethtool", + "module", + "nic", + "system" + ], + "version": 1 +} diff --git a/tests/unit/utils/hardware/fixtures/linux-mellanox.json b/tests/unit/utils/hardware/fixtures/linux-mellanox.json index ef188c8b8..73890e6d4 100644 --- a/tests/unit/utils/hardware/fixtures/linux-mellanox.json +++ b/tests/unit/utils/hardware/fixtures/linux-mellanox.json @@ -1,5 +1,10 @@ { "collector": "linux", + "dmi": { + "product_name": "SYS-120C-TN10R", + "product_serial": "S123456X0A01234", + "sys_vendor": "Supermicro" + }, "hostname": "compute-042", "ip_link": { "err": "", @@ -13,12 +18,42 @@ }, "ports": { "eno1": { + "devlink": { + "err": "kernel answers: Operation not supported", + "out": "", + "rc": 1 + }, + "ethtool": { + "err": "", + "out": "Settings for IFACE:\n\tSupported ports: [ FIBRE ]\n\tSupported link modes: 1000baseKX/Full\n\t 1000baseT/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: None\t RS\t BASER\n\tAdvertised link modes: 1000baseT/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: RS\n\tSpeed: 1000Mb/s\n\tDuplex: Full\n\tAuto-negotiation: on\n\tPort: Twisted Pair\n\tPHYAD: 0\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n\tLink detected: yes\n", + "rc": 0 + }, "ethtool_P": { "err": "", "out": "Permanent address: 3c:ec:ef:10:20:30\n", "rc": 0 }, + "ethtool_fec": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 1 + }, + "ethtool_i": { + "err": "", + "out": "driver: igb\nversion: 6.8.0-51-generic\nfirmware-version: 1.63, 0x80000e3d\nexpansion-rom-version: \nbus-info: 0000:03:00.0\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: no\nsupports-register-dump: no\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 75 + }, "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:03:00.0\nClass:\tEthernet controller [0200]\nVendor:\tIntel Corporation [8086]\nDevice:\tI350 Gigabit Network Connection [1521]\nSVendor:\tSuper Micro Computer Inc [15d9]\nSDevice:\tI350 Gigabit Network Connection [1521]\nRev:\t01\nNUMANode:\t0\n\n", + "rc": 0 + }, "pci": "0000:03:00.0", "sysfs": { "address": "3c:ec:ef:10:20:30", @@ -33,12 +68,42 @@ } }, "eno2": { + "devlink": { + "err": "kernel answers: Operation not supported", + "out": "", + "rc": 1 + }, + "ethtool": { + "err": "", + "out": "Settings for IFACE:\n\tSupported ports: [ FIBRE ]\n\tSupported link modes: 1000baseKX/Full\n\t 1000baseT/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: None\t RS\t BASER\n\tAdvertised link modes: 1000baseT/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: RS\n\tSpeed: Unknown!\n\tDuplex: Unknown! (255)\n\tAuto-negotiation: on\n\tPort: Twisted Pair\n\tPHYAD: 0\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n\tLink detected: no\n", + "rc": 0 + }, "ethtool_P": { "err": "", "out": "Permanent address: 3c:ec:ef:10:20:31\n", "rc": 0 }, + "ethtool_fec": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 1 + }, + "ethtool_i": { + "err": "", + "out": "driver: igb\nversion: 6.8.0-51-generic\nfirmware-version: 1.63, 0x80000e3d\nexpansion-rom-version: \nbus-info: 0000:03:00.1\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: no\nsupports-register-dump: no\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "netlink error: Operation not supported", + "out": "", + "rc": 75 + }, "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:03:00.1\nClass:\tEthernet controller [0200]\nVendor:\tIntel Corporation [8086]\nDevice:\tI350 Gigabit Network Connection [1521]\nSVendor:\tSuper Micro Computer Inc [15d9]\nSDevice:\tI350 Gigabit Network Connection [1521]\nRev:\t01\nNUMANode:\t0\n\n", + "rc": 0 + }, "pci": "0000:03:00.1", "sysfs": { "address": "3c:ec:ef:10:20:31", @@ -53,12 +118,42 @@ } }, "ens1f0np0": { + "devlink": { + "err": "", + "out": "{\"info\": {\"pci/0000:3b:00.0\": {\"driver\": \"mlx5_core\", \"versions\": {\"fixed\": {\"fw.psid\": \"MT_0000000080\"}, \"running\": {\"fw.version\": \"16.35.2000\", \"fw\": \"16.35.2000\"}, \"stored\": {\"fw.version\": \"16.35.2000\", \"fw\": \"16.35.2000\"}}}}}", + "rc": 0 + }, + "ethtool": { + "err": "", + "out": "Settings for IFACE:\n\tSupported ports: [ FIBRE ]\n\tSupported link modes: 1000baseKX/Full\n\t 25000baseCR/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: None\t RS\t BASER\n\tAdvertised link modes: 25000baseCR/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: RS\n\tSpeed: 25000Mb/s\n\tDuplex: Full\n\tAuto-negotiation: on\n\tPort: Direct Attach Copper\n\tPHYAD: 0\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n\tLink detected: yes\n", + "rc": 0 + }, "ethtool_P": { "err": "", "out": "Permanent address: b8:ce:f6:aa:00:10\n", "rc": 0 }, + "ethtool_fec": { + "err": "", + "out": "FEC parameters for IFACE:\nSupported/Configured FEC encodings: Auto RS BaseR\nActive FEC encoding: RS\n", + "rc": 0 + }, + "ethtool_i": { + "err": "", + "out": "driver: mlx5_core\nversion: 6.8.0-51-generic\nfirmware-version: 16.35.2000 (MT_0000000080)\nexpansion-rom-version: \nbus-info: 0000:3b:00.0\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: no\nsupports-register-dump: no\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "", + "out": "\tIdentifier : 0x03 (SFP)\n\tExtended identifier : 0x04 (GBIC/SFP defined by 2-wire interface ID)\n\tConnector : 0x21 (Copper pigtail)\n\tTransceiver codes : 0x01 0x00 0x00 0x04 0x00 0x04 0x80 0x00 0x00\n\tTransceiver type : Infiniband: 1X Copper Passive\n\tTransceiver type : Ethernet: 1000BASE-CX\n\tTransceiver type : Passive Cable\n\tTransceiver type : FC: Twin Axial Pair (TW)\n\tTransceiver type : Extended: 25G Base-CR CA-N\n\tEncoding : 0x00 (unspecified)\n\tBR, Nominal : 25500MBd\n\tRate identifier : 0x00 (unspecified)\n\tLength (SMF,km) : 0km\n\tLength (SMF) : 0m\n\tLength (50um) : 0m\n\tLength (62.5um) : 0m\n\tLength (Copper) : 3m\n\tLength (OM3) : 0m\n\tPassive Cu cmplnce. : 0x01 (SFF-8431 appendix E) [SFF-8472 rev10.4 only]\n\tVendor name : Mellanox\n\tVendor OUI : 00:02:c9\n\tVendor PN : MCP2M00-A003E30L\n\tVendor rev : A3\n\tOption values : 0x00 0x00\n\tBR margin, max : 0%\n\tBR margin, min : 0%\n\tVendor SN : MT2114VS01234\n\tDate code : 210408\n", + "rc": 0 + }, "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:3b:00.0\nClass:\tEthernet controller [0200]\nVendor:\tMellanox Technologies [15b3]\nDevice:\tMT27800 Family [ConnectX-5] [1017]\nSVendor:\tMellanox Technologies [15b3]\nSDevice:\tConnectX-5 EN network interface card, 25GbE dual-port SFP28 [0080]\nRev:\t00\nNUMANode:\t0\n\n", + "rc": 0 + }, "pci": "0000:3b:00.0", "sysfs": { "address": "b8:ce:f6:aa:00:10", @@ -73,12 +168,42 @@ } }, "ens1f1np1": { + "devlink": { + "err": "", + "out": "{\"info\": {\"pci/0000:3b:00.1\": {\"driver\": \"mlx5_core\", \"versions\": {\"fixed\": {\"fw.psid\": \"MT_0000000080\"}, \"running\": {\"fw.version\": \"16.35.2000\", \"fw\": \"16.35.2000\"}, \"stored\": {\"fw.version\": \"16.35.2000\", \"fw\": \"16.35.2000\"}}}}}", + "rc": 0 + }, + "ethtool": { + "err": "", + "out": "Settings for IFACE:\n\tSupported ports: [ FIBRE ]\n\tSupported link modes: 1000baseKX/Full\n\t 25000baseCR/Full\n\tSupported pause frame use: Symmetric\n\tSupports auto-negotiation: Yes\n\tSupported FEC modes: None\t RS\t BASER\n\tAdvertised link modes: 25000baseCR/Full\n\tAdvertised pause frame use: Symmetric\n\tAdvertised auto-negotiation: Yes\n\tAdvertised FEC modes: RS\n\tSpeed: 25000Mb/s\n\tDuplex: Full\n\tAuto-negotiation: on\n\tPort: FIBRE\n\tPHYAD: 0\n\tTransceiver: internal\n\tSupports Wake-on: d\n\tWake-on: d\n\tLink detected: yes\n", + "rc": 0 + }, "ethtool_P": { "err": "", "out": "Permanent address: b8:ce:f6:aa:00:11\n", "rc": 0 }, + "ethtool_fec": { + "err": "", + "out": "FEC parameters for IFACE:\nSupported/Configured FEC encodings: Auto RS BaseR\nActive FEC encoding: RS\n", + "rc": 0 + }, + "ethtool_i": { + "err": "", + "out": "driver: mlx5_core\nversion: 6.8.0-51-generic\nfirmware-version: 16.35.2000 (MT_0000000080)\nexpansion-rom-version: \nbus-info: 0000:3b:00.1\nsupports-statistics: yes\nsupports-test: yes\nsupports-eeprom-access: no\nsupports-register-dump: no\nsupports-priv-flags: yes\n", + "rc": 0 + }, + "ethtool_m": { + "err": "", + "out": "\tIdentifier : 0x03 (SFP)\n\tExtended identifier : 0x04 (GBIC/SFP defined by 2-wire interface ID)\n\tConnector : 0x07 (LC)\n\tTransceiver codes : 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02\n\tTransceiver type : Extended: 100G Base-SR4 or 25GBase-SR\n\tEncoding : 0x06 (64B/66B)\n\tBR, Nominal : 25500MBd\n\tRate identifier : 0x00 (unspecified)\n\tLength (SMF,km) : 0km\n\tLength (SMF) : 0m\n\tLength (50um) : 0m\n\tLength (62.5um) : 0m\n\tLength (Copper) : 0m\n\tLength (OM3) : 70m\n\tLaser wavelength : 850nm\n\tVendor name : FS\n\tVendor OUI : 64:9d:99\n\tVendor PN : SFP28-25GSR-85\n\tVendor rev : A\n\tOption values : 0x08 0x1a\n\tOption : RX_LOS implemented\n\tOption : TX_FAULT implemented\n\tBR margin, max : 0%\n\tBR margin, min : 0%\n\tVendor SN : F2030123456\n\tDate code : 220915\n\tOptical diagnostics support : Yes\n\tLaser bias current : 6.212 mA\n\tLaser output power : 0.5823 mW / -2.35 dBm\n\tReceiver signal average optical power : 0.4501 mW / -3.47 dBm\n\tModule temperature : 33.45 degrees C / 92.21 degrees F\n\tModule voltage : 3.2934 V\n\tAlarm/warning flags implemented : Yes\n\tLaser bias current high alarm : Off\n\tLaser bias current high alarm threshold : 15.000 mA\n\tLaser bias current low alarm threshold : 1.000 mA\n\tLaser output power high alarm threshold : 1.9953 mW / 3.00 dBm\n\tLaser output power low alarm threshold : 0.1259 mW / -9.00 dBm\n\tModule temperature high alarm threshold : 75.00 degrees C / 167.00 degrees F\n\tModule voltage high alarm threshold : 3.6300 V\n\tLaser rx power high alarm threshold : 1.9953 mW / 3.00 dBm\n\tLaser rx power low alarm threshold : 0.0398 mW / -14.00 dBm\n", + "rc": 0 + }, "kind": "physical", + "lspci": { + "err": "", + "out": "Slot:\t0000:3b:00.1\nClass:\tEthernet controller [0200]\nVendor:\tMellanox Technologies [15b3]\nDevice:\tMT27800 Family [ConnectX-5] [1017]\nSVendor:\tMellanox Technologies [15b3]\nSDevice:\tConnectX-5 EN network interface card, 25GbE dual-port SFP28 [0080]\nRev:\t00\nNUMANode:\t0\n\n", + "rc": 0 + }, "pci": "0000:3b:00.1", "sysfs": { "address": "b8:ce:f6:aa:00:10", @@ -95,7 +220,11 @@ }, "sections": [ "ports", - "lldp" + "lldp", + "ethtool", + "module", + "nic", + "system" ], "version": 1 } diff --git a/tests/unit/utils/hardware/fixtures/sonic-community.json b/tests/unit/utils/hardware/fixtures/sonic-community.json new file mode 100644 index 000000000..f984935df --- /dev/null +++ b/tests/unit/utils/hardware/fixtures/sonic-community.json @@ -0,0 +1,250 @@ +{ + "collector": "sonic", + "commands": { + "show platform firmware status": { + "err": "Error: Platform API is not available", + "out": "", + "rc": 1 + }, + "show platform syseeprom": { + "err": "", + "out": "TlvInfo Header:\n Id String: TlvInfo\n Version: 1\n Total Length: 597\nTLV Name Code Len Value\n-------------------- ---- --- -----\nProduct Name 0x21 64 MSN3700C\nPart Number 0x22 20 MSN3700-CS2FO\nSerial Number 0x23 24 MT2012X04321\nBase MAC Address 0x24 6 50:6B:4B:00:C0:00\nManufacturer 0x2B 8 Mellanox\nONIE Version 0x29 21 2019.11-5.2.0020-9600\nCRC-32 0xFE 4 0x1A2B3C4D\n\n(checksum valid)\n", + "rc": 0 + }, + "show version": { + "err": "", + "out": "\nSONiC Software Version: SONiC.202311.1-0a1b2c3d4\nSONiC OS Version: 11\nDistribution: Debian 11.9\nKernel: 5.10.0-23-2-amd64\nBuild commit: 0a1b2c3d4\nBuild date: Tue Apr 2 10:11:12 UTC 2024\nBuilt by: build@sonic\n\nPlatform: x86_64-mlnx_msn3700c-r0\nHwSKU: Mellanox-SN3700C\nASIC: mellanox\nASIC Count: 1\nSerial Number: MT2012X04321\nModel Number: MSN3700-CS2FO\nHardware Revision: A4\nUptime: 10:11:12 up 41 days\n", + "rc": 0 + } + }, + "db": { + "APPL_DB": { + "PORT_TABLE": { + "PORT_TABLE:Ethernet0": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "admin_status": "up", + "mtu": "9100", + "oper_status": "up", + "speed": "100000" + } + }, + "PORT_TABLE:Ethernet4": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "admin_status": "up", + "mtu": "9100", + "oper_status": "up", + "speed": "100000" + } + }, + "PORT_TABLE:Ethernet8": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "admin_status": "down", + "mtu": "9100", + "oper_status": "down", + "speed": "100000" + } + } + } + }, + "CONFIG_DB": { + "DEVICE_METADATA": { + "DEVICE_METADATA|localhost": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "hostname": "spine-1", + "hwsku": "Mellanox-SN3700C", + "mac": "50:6b:4b:00:c0:00", + "platform": "x86_64-mlnx_msn3700c-r0", + "type": "SpineRouter" + } + } + }, + "PORT": { + "PORT|Ethernet0": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "admin_status": "up", + "alias": "etp1", + "fec": "rs", + "index": "1", + "lanes": "0,1,2,3", + "mtu": "9100", + "speed": "100000" + } + }, + "PORT|Ethernet4": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "admin_status": "up", + "alias": "etp2", + "index": "2", + "lanes": "4,5,6,7", + "mtu": "9100", + "speed": "100000" + } + }, + "PORT|Ethernet8": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "admin_status": "down", + "alias": "etp3", + "index": "3", + "lanes": "8,9,10,11", + "mtu": "9100", + "speed": "100000" + } + } + } + }, + "STATE_DB": { + "TRANSCEIVER_DOM_SENSOR": { + "TRANSCEIVER_DOM_SENSOR|Ethernet0": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "rx1power": "-1.02", + "rx2power": "-0.97", + "rx3power": "-1.11", + "rx4power": "-0.9", + "rx5power": "N/A", + "temperature": "34.5", + "tx1bias": "6.75", + "tx1power": "-0.22", + "tx2bias": "6.81", + "tx2power": "-0.32", + "tx3bias": "6.7", + "tx3power": "-0.13", + "tx4bias": "6.79", + "tx4power": "-0.41", + "tx5bias": "N/A", + "tx5power": "N/A", + "voltage": "3.281" + } + }, + "TRANSCEIVER_DOM_SENSOR|Ethernet4": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "rx1power": "-2.5", + "temperature": "41.0", + "tx1bias": "55.0", + "tx1power": "0.8", + "voltage": "3.31" + } + } + }, + "TRANSCEIVER_FIRMWARE_INFO": { + "TRANSCEIVER_FIRMWARE_INFO|Ethernet0": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "active_firmware": "N/A", + "inactive_firmware": "N/A" + } + }, + "TRANSCEIVER_FIRMWARE_INFO|Ethernet4": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "active_firmware": "1.3.0", + "inactive_firmware": "1.2.9" + } + } + }, + "TRANSCEIVER_INFO": { + "TRANSCEIVER_INFO|Ethernet0": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "application_advertisement": "N/A", + "cable_length": "10.0", + "cable_type": "Length Cable Assembly(m)", + "connector": "No separable connector", + "dom_capability": "N/A", + "encoding": "64B/66B", + "ext_identifier": "Power Class 3 Module (2.5W max.)", + "ext_rateselect_compliance": "Unknown", + "hardware_rev": "A2", + "is_replaceable": "True", + "manufacturer": "Mellanox", + "model": "MFA1A00-C010", + "nominal_bit_rate": "255", + "serial": "MT2030FT01234", + "specification_compliance": "{'10/40G Ethernet Compliance Code': 'Extended'}", + "type": "QSFP28 or later", + "type_abbrv_name": "QSFP28", + "vendor_date": "2020-07-24", + "vendor_oui": "00-02-c9" + } + }, + "TRANSCEIVER_INFO|Ethernet4": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "application_advertisement": "{1: {'host_electrical_interface_id': '100GAUI-2 C2M (Annex 135G)'}}", + "cable_length": "0.0", + "cable_type": "Length Cable Assembly(m)", + "connector": "LC", + "hardware_rev": "1.0", + "is_replaceable": "True", + "manufacturer": "ACME Optics", + "model": "QDD-100G-DR", + "serial": "AO24070002", + "specification_compliance": "sm_media_interface", + "type": "QSFP-DD Double Density 8X Pluggable Transceiver", + "type_abbrv_name": "QSFP-DD", + "vendor_date": "2024-07-02", + "vendor_oui": "00-11-22" + } + } + } + } + }, + "db_access": "sudo", + "errors": { + "STATE_DB EEPROM_INFO": "no output" + }, + "files": { + "sonic_version": null + }, + "hostname": "spine-1", + "mgmt": { + "eth0": { + "address": "50:6b:4b:00:c0:01", + "carrier": "1", + "mtu": "1500", + "operstate": "up", + "speed": "1000" + } + }, + "sections": [ + "ports", + "transceiver", + "dom", + "system" + ], + "version": 1 +} diff --git a/tests/unit/utils/hardware/fixtures/sonic-enterprise.json b/tests/unit/utils/hardware/fixtures/sonic-enterprise.json index e28b51ba4..373ea28e3 100644 --- a/tests/unit/utils/hardware/fixtures/sonic-enterprise.json +++ b/tests/unit/utils/hardware/fixtures/sonic-enterprise.json @@ -1,5 +1,12 @@ { "collector": "sonic", + "commands": { + "show platform firmware status": { + "err": "", + "out": "Chassis Module Component Version Description\n--------------- -------- ----------- ------------ -------------------------\n7726-32X-O-AC-B N/A CPLD1 14 CPLD 1\n CPLD2 142 CPLD 2\n CPLD3 142 CPLD 3\n BIOS v40.01.01.01 Basic Input/Output System\n PCIE D102_08 ASIC PCIe Firmware\n", + "rc": 0 + } + }, "db": { "APPL_DB": { "PORT_TABLE": { @@ -587,10 +594,1018 @@ } } } + }, + "STATE_DB": { + "EEPROM_INFO": { + "EEPROM_INFO|0x21": { + "expireat": 1794564780.760895, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "15", + "Name": "Product Name", + "Value": "7726-32X-O-AC-B" + } + }, + "EEPROM_INFO|0x22": { + "expireat": 1794564780.7609217, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "13", + "Name": "Part Number", + "Value": "FP3ZZ7632076A" + } + }, + "EEPROM_INFO|0x23": { + "expireat": 1794564780.7608845, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "14", + "Name": "Serial Number", + "Value": "SN00000001" + } + }, + "EEPROM_INFO|0x24": { + "expireat": 1794564780.7608917, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "6", + "Name": "Base MAC Address", + "Value": "02:00:00:00:00:1D" + } + }, + "EEPROM_INFO|0x25": { + "expireat": 1794564780.7608812, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "19", + "Name": "Manufacture Date", + "Value": "01/01/2023 00:00:00" + } + }, + "EEPROM_INFO|0x27": { + "expireat": 1794564780.7609186, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "4", + "Name": "Label Revision", + "Value": "R01H" + } + }, + "EEPROM_INFO|0x28": { + "expireat": 1794564780.760874, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "27", + "Name": "Platform Name", + "Value": "x86_64-accton_as7726_32x-r0" + } + }, + "EEPROM_INFO|0x29": { + "expireat": 1794564780.7609046, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "13", + "Name": "ONIE Version", + "Value": "2017.11.00.05" + } + }, + "EEPROM_INFO|0x2a": { + "expireat": 1794564780.7609158, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "2", + "Name": "MAC Addresses", + "Value": "256" + } + }, + "EEPROM_INFO|0x2b": { + "expireat": 1794564780.7609246, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "6", + "Name": "Manufacturer", + "Value": "Accton" + } + }, + "EEPROM_INFO|0x2c": { + "expireat": 1794564780.7608778, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "2", + "Name": "Manufacture Country", + "Value": "TW" + } + }, + "EEPROM_INFO|0x2d": { + "expireat": 1794564780.760901, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "8", + "Name": "Vendor Name", + "Value": "Edgecore" + } + }, + "EEPROM_INFO|0x2e": { + "expireat": 1794564780.760898, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "11", + "Name": "Diag Version", + "Value": "01.01.01.09" + } + }, + "EEPROM_INFO|0xfe": { + "expireat": 1794564780.760913, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "4", + "Name": "CRC-32", + "Value": "0x00000000" + } + }, + "EEPROM_INFO|Checksum": { + "expireat": 1794564780.7609098, + "ttl": -0.001, + "type": "hash", + "value": { + "Valid": "1" + } + }, + "EEPROM_INFO|State": { + "expireat": 1794564780.7608657, + "ttl": -0.001, + "type": "hash", + "value": { + "Initialized": "1" + } + }, + "EEPROM_INFO|TlvHeader": { + "expireat": 1794564780.7608883, + "ttl": -0.001, + "type": "hash", + "value": { + "Id String": "TlvInfo", + "Total Length": "172", + "Version": "1" + } + } + }, + "TRANSCEIVER_DOM_SENSOR": { + "TRANSCEIVER_DOM_SENSOR|Ethernet0": { + "expireat": 1794564780.46885, + "ttl": -0.001, + "type": "hash", + "value": { + "rx1power": "N/A", + "rx2power": "N/A", + "rx3power": "N/A", + "rx4power": "N/A", + "temperature": "N/A", + "tx1bias": "N/A", + "tx1power": "N/A", + "tx2bias": "N/A", + "tx2power": "N/A", + "tx3bias": "N/A", + "tx3power": "N/A", + "tx4bias": "N/A", + "tx4power": "N/A", + "voltage": "N/A" + } + }, + "TRANSCEIVER_DOM_SENSOR|Ethernet1": { + "expireat": 1794564780.4689205, + "ttl": -0.001, + "type": "hash", + "value": { + "rx1power": "N/A", + "rx2power": "N/A", + "rx3power": "N/A", + "rx4power": "N/A", + "temperature": "N/A", + "tx1bias": "N/A", + "tx1power": "N/A", + "tx2bias": "N/A", + "tx2power": "N/A", + "tx3bias": "N/A", + "tx3power": "N/A", + "tx4bias": "N/A", + "tx4power": "N/A", + "voltage": "N/A" + } + }, + "TRANSCEIVER_DOM_SENSOR|Ethernet12": { + "expireat": 1794564780.4687939, + "ttl": -0.001, + "type": "hash", + "value": { + "rx1power": "N/A", + "rx2power": "N/A", + "rx3power": "N/A", + "rx4power": "N/A", + "temperature": "N/A", + "tx1bias": "N/A", + "tx1power": "N/A", + "tx2bias": "N/A", + "tx2power": "N/A", + "tx3bias": "N/A", + "tx3power": "N/A", + "tx4bias": "N/A", + "tx4power": "N/A", + "voltage": "N/A" + } + }, + "TRANSCEIVER_DOM_SENSOR|Ethernet120": { + "expireat": 1794564780.4689112, + "ttl": -0.001, + "type": "hash", + "value": { + "rx1power": "0.5990", + "rx2power": "0.6070", + "rx3power": "0.7170", + "rx4power": "-1.3094", + "rxpowerhighalarm": "3.4001", + "rxpowerhighwarning": "2.4000", + "rxpowerlowalarm": "-11.9997", + "rxpowerlowwarning": "-8.9997", + "temperature": "39.5703", + "temphighalarm": "75.0000", + "temphighwarning": "70.0000", + "templowalarm": "-5.0000", + "templowwarning": "0.0000", + "tx1bias": "6.5880", + "tx1power": "0.1708", + "tx2bias": "6.5800", + "tx2power": "0.2633", + "tx3bias": "6.6200", + "tx3power": "0.2849", + "tx4bias": "6.6000", + "tx4power": "0.1720", + "txbiashighalarm": "11.0000", + "txbiashighwarning": "10.0000", + "txbiaslowalarm": "2.0000", + "txbiaslowwarning": "3.0000", + "txpowerhighalarm": "4.4000", + "txpowerhighwarning": "3.4001", + "txpowerlowalarm": "-10.4001", + "txpowerlowwarning": "-8.4013", + "vcchighalarm": "3.6300", + "vcchighwarning": "3.4650", + "vcclowalarm": "2.9700", + "vcclowwarning": "3.1350", + "voltage": "3.3170" + } + }, + "TRANSCEIVER_DOM_SENSOR|Ethernet124": { + "expireat": 1794564780.4689567, + "ttl": -0.001, + "type": "hash", + "value": { + "rx1power": "-0.3096", + "rx2power": "0.4700", + "rx3power": "0.3703", + "rx4power": "-3.4544", + "rxpowerhighalarm": "3.4001", + "rxpowerhighwarning": "2.4000", + "rxpowerlowalarm": "-11.9997", + "rxpowerlowwarning": "-8.9997", + "temperature": "39.2930", + "temphighalarm": "75.0000", + "temphighwarning": "70.0000", + "templowalarm": "-5.0000", + "templowwarning": "0.0000", + "tx1bias": "7.0120", + "tx1power": "0.0307", + "tx2bias": "7.0060", + "tx2power": "0.3977", + "tx3bias": "6.9980", + "tx3power": "0.2325", + "tx4bias": "6.3920", + "tx4power": "0.4992", + "txbiashighalarm": "11.0000", + "txbiashighwarning": "10.0000", + "txbiaslowalarm": "2.0000", + "txbiaslowwarning": "3.0000", + "txpowerhighalarm": "4.4000", + "txpowerhighwarning": "3.4001", + "txpowerlowalarm": "-10.4001", + "txpowerlowwarning": "-8.4013", + "vcchighalarm": "3.6300", + "vcchighwarning": "3.4650", + "vcclowalarm": "2.9700", + "vcclowwarning": "3.1350", + "voltage": "3.3000" + } + }, + "TRANSCEIVER_DOM_SENSOR|Ethernet13": { + "expireat": 1794564780.4687753, + "ttl": -0.001, + "type": "hash", + "value": { + "rx1power": "N/A", + "rx2power": "N/A", + "rx3power": "N/A", + "rx4power": "N/A", + "temperature": "N/A", + "tx1bias": "N/A", + "tx1power": "N/A", + "tx2bias": "N/A", + "tx2power": "N/A", + "tx3bias": "N/A", + "tx3power": "N/A", + "tx4bias": "N/A", + "tx4power": "N/A", + "voltage": "N/A" + } + }, + "TRANSCEIVER_DOM_SENSOR|Ethernet22": { + "expireat": 1794564780.468786, + "ttl": -0.001, + "type": "hash", + "value": { + "rx1power": "N/A", + "rx2power": "N/A", + "rx3power": "N/A", + "rx4power": "N/A", + "temperature": "N/A", + "tx1bias": "N/A", + "tx1power": "N/A", + "tx2bias": "N/A", + "tx2power": "N/A", + "tx3bias": "N/A", + "tx3power": "N/A", + "tx4bias": "N/A", + "tx4power": "N/A", + "voltage": "N/A" + } + }, + "TRANSCEIVER_DOM_SENSOR|Ethernet4": { + "expireat": 1794564780.4689877, + "ttl": -0.001, + "type": "hash", + "value": { + "rx1power": "N/A", + "rx2power": "N/A", + "rx3power": "N/A", + "rx4power": "N/A", + "temperature": "N/A", + "tx1bias": "N/A", + "tx1power": "N/A", + "tx2bias": "N/A", + "tx2power": "N/A", + "tx3bias": "N/A", + "tx3power": "N/A", + "tx4bias": "N/A", + "tx4power": "N/A", + "voltage": "N/A" + } + }, + "TRANSCEIVER_DOM_SENSOR|Ethernet6": { + "expireat": 1794564780.4688013, + "ttl": -0.001, + "type": "hash", + "value": { + "rx1power": "N/A", + "rx2power": "N/A", + "rx3power": "N/A", + "rx4power": "N/A", + "temperature": "N/A", + "tx1bias": "N/A", + "tx1power": "N/A", + "tx2bias": "N/A", + "tx2power": "N/A", + "tx3bias": "N/A", + "tx3power": "N/A", + "tx4bias": "N/A", + "tx4power": "N/A", + "voltage": "N/A" + } + }, + "TRANSCEIVER_DOM_SENSOR|Ethernet9": { + "expireat": 1794564780.4688084, + "ttl": -0.001, + "type": "hash", + "value": { + "rx1power": "N/A", + "rx2power": "N/A", + "rx3power": "N/A", + "rx4power": "N/A", + "temperature": "N/A", + "tx1bias": "N/A", + "tx1power": "N/A", + "tx2bias": "N/A", + "tx2power": "N/A", + "tx3bias": "N/A", + "tx3power": "N/A", + "tx4bias": "N/A", + "tx4power": "N/A", + "voltage": "N/A" + } + } + }, + "TRANSCEIVER_FIRMWARE_INFO": {}, + "TRANSCEIVER_INFO": { + "TRANSCEIVER_INFO|Ethernet0": { + "expireat": 1794564779.8951104, + "ttl": -0.001, + "type": "hash", + "value": { + "application_advertisement": "N/A", + "cable_breakout": "1x1", + "cable_class": "DAC", + "cable_length": "2.0", + "cable_length_detailed": "2.0", + "cable_type": "Length Cable Assembly(m)", + "connector": "No separable connector", + "connector_type": "No separable connector", + "display_name": "QSFP28 100GBASE-CR4-DAC-2.0M", + "encoding": "Unspecified", + "ext_identifier": "Power Class 1(1.5W max)", + "ext_rateselect_compliance": "QSFP+ Rate Select Version 1", + "firmware_revision": "N/A", + "form_factor": "QSFP28", + "hardware_rev": "A", + "host_electrical_lane_count": "N/A", + "host_lane_count": "4", + "is_lpo_transceiver": "No", + "is_qualified": "False", + "is_replaceable": "False", + "lane_count": "4", + "laser_capabilities": "N/A", + "link_length": "N/A", + "manufacturer": "FS", + "max_port_power": "5.0", + "media_gen_rev": "N/A", + "media_interface": "CR", + "media_lane_count": "4", + "memory_type": "Flat", + "model": "Q-4S28PC02", + "module_lane_count": "N/A", + "module_type": "2", + "nominal_bit_rate": "255", + "power_rating_max": "1.5", + "qsa_adapter": "N/A", + "serial": "SN00000004", + "special_fields": "N/A", + "specification_compliance": "{'10/40G Ethernet Compliance Code': '100GBASE-CR4'}", + "supported_ctrls": "N/A", + "type": "QSFP28", + "type_abbrv_name": "QSFP28", + "vendor_date": "2022-09-07T00:00:00+00:00", + "vendor_date_code": "2022-09-07T00:00:00+00:00", + "vendor_name": "FS", + "vendor_oui": "64-9D-99", + "vendor_part_number": "Q-4S28PC02", + "vendor_revision": "A", + "vendor_serial_number": "SN00000004", + "vendorrev": "A", + "xcvr_speed_max": "100000" + } + }, + "TRANSCEIVER_INFO|Ethernet1": { + "expireat": 1794564779.8953102, + "ttl": -0.001, + "type": "hash", + "value": { + "application_advertisement": "N/A", + "cable_breakout": "1x1", + "cable_class": "DAC", + "cable_length": "2.0", + "cable_length_detailed": "2.0", + "cable_type": "Length Cable Assembly(m)", + "connector": "No separable connector", + "connector_type": "No separable connector", + "display_name": "QSFP28 100GBASE-CR4-DAC-2.0M", + "encoding": "Unspecified", + "ext_identifier": "Power Class 1(1.5W max)", + "ext_rateselect_compliance": "QSFP+ Rate Select Version 1", + "firmware_revision": "N/A", + "form_factor": "QSFP28", + "hardware_rev": "A", + "host_electrical_lane_count": "N/A", + "host_lane_count": "4", + "is_lpo_transceiver": "No", + "is_qualified": "False", + "is_replaceable": "False", + "lane_count": "4", + "laser_capabilities": "N/A", + "link_length": "N/A", + "manufacturer": "FS", + "max_port_power": "5.0", + "media_gen_rev": "N/A", + "media_interface": "CR", + "media_lane_count": "4", + "memory_type": "Flat", + "model": "Q-4S28PC02", + "module_lane_count": "N/A", + "module_type": "2", + "nominal_bit_rate": "255", + "power_rating_max": "1.5", + "qsa_adapter": "N/A", + "serial": "SN00000004", + "special_fields": "N/A", + "specification_compliance": "{'10/40G Ethernet Compliance Code': '100GBASE-CR4'}", + "supported_ctrls": "N/A", + "type": "QSFP28", + "type_abbrv_name": "QSFP28", + "vendor_date": "2022-09-07T00:00:00+00:00", + "vendor_date_code": "2022-09-07T00:00:00+00:00", + "vendor_name": "FS", + "vendor_oui": "64-9D-99", + "vendor_part_number": "Q-4S28PC02", + "vendor_revision": "A", + "vendor_serial_number": "SN00000004", + "vendorrev": "A", + "xcvr_speed_max": "100000" + } + }, + "TRANSCEIVER_INFO|Ethernet12": { + "expireat": 1794564779.8952625, + "ttl": -0.001, + "type": "hash", + "value": { + "application_advertisement": "N/A", + "cable_breakout": "1x1", + "cable_class": "DAC", + "cable_length": "2.0", + "cable_length_detailed": "2.0", + "cable_type": "Length Cable Assembly(m)", + "connector": "No separable connector", + "connector_type": "No separable connector", + "display_name": "QSFP28 100GBASE-CR4-DAC-2.0M", + "encoding": "Unspecified", + "ext_identifier": "Power Class 1(1.5W max)", + "ext_rateselect_compliance": "QSFP+ Rate Select Version 1", + "firmware_revision": "N/A", + "form_factor": "QSFP28", + "hardware_rev": "A", + "host_electrical_lane_count": "N/A", + "host_lane_count": "4", + "is_lpo_transceiver": "No", + "is_qualified": "False", + "is_replaceable": "False", + "lane_count": "4", + "laser_capabilities": "N/A", + "link_length": "N/A", + "manufacturer": "FS", + "max_port_power": "5.0", + "media_gen_rev": "N/A", + "media_interface": "CR", + "media_lane_count": "4", + "memory_type": "Flat", + "model": "QSFP-100G-4SPC02", + "module_lane_count": "N/A", + "module_type": "2", + "nominal_bit_rate": "255", + "power_rating_max": "1.5", + "qsa_adapter": "N/A", + "serial": "SN00000016", + "special_fields": "N/A", + "specification_compliance": "{'10/40G Ethernet Compliance Code': '100GBASE-CR4'}", + "supported_ctrls": "N/A", + "type": "QSFP28", + "type_abbrv_name": "QSFP28", + "vendor_date": "2024-04-18T00:00:00+00:00", + "vendor_date_code": "2024-04-18T00:00:00+00:00", + "vendor_name": "FS", + "vendor_oui": "64-9D-99", + "vendor_part_number": "QSFP-100G-4SPC02", + "vendor_revision": "A", + "vendor_serial_number": "SN00000016", + "vendorrev": "A", + "xcvr_speed_max": "100000" + } + }, + "TRANSCEIVER_INFO|Ethernet120": { + "expireat": 1794564779.8948877, + "ttl": -0.001, + "type": "hash", + "value": { + "application_advertisement": "N/A", + "cable_breakout": "1x1", + "cable_class": "FIBER", + "cable_length": "0.0", + "cable_length_detailed": "0.0", + "cable_type": "Length OM3(2m)", + "connector": "MPOx12", + "connector_type": "MPOx12", + "display_name": "QSFP28 100GBASE-SR4", + "encoding": "NRZ", + "ext_identifier": "Power Class 4(3.5W max), CDR present in Rx Tx", + "ext_rateselect_compliance": "Unknown", + "firmware_revision": "N/A", + "form_factor": "QSFP28", + "hardware_rev": "1A", + "host_electrical_lane_count": "N/A", + "host_lane_count": "4", + "is_lpo_transceiver": "No", + "is_qualified": "False", + "is_replaceable": "False", + "lane_count": "4", + "laser_capabilities": "N/A", + "link_length": "N/A", + "manufacturer": "FS", + "max_port_power": "5.0", + "media_gen_rev": "N/A", + "media_interface": "SR", + "media_lane_count": "4", + "memory_type": "Paged", + "model": "QSFP28-SR4-100G", + "module_lane_count": "N/A", + "module_type": "2", + "nominal_bit_rate": "255", + "power_rating_max": "3.5", + "qsa_adapter": "N/A", + "serial": "SN00000017", + "special_fields": "N/A", + "specification_compliance": "{'10/40G Ethernet Compliance Code': '100GBASE-SR4', 'Gigabit Ethernet Compliant codes': '1000BASE-SX', 'SAS/SATA compliance codes': 'SAS 3.0G'}", + "supported_ctrls": "N/A", + "type": "QSFP28", + "type_abbrv_name": "QSFP28", + "vendor_date": "2022-03-10T00:00:00+00:00", + "vendor_date_code": "2022-03-10T00:00:00+00:00", + "vendor_name": "FS", + "vendor_oui": "64-9D-99", + "vendor_part_number": "QSFP28-SR4-100G", + "vendor_revision": "1A", + "vendor_serial_number": "SN00000017", + "vendorrev": "1A", + "wavelength": "850.00000000", + "xcvr_speed_max": "100000" + } + }, + "TRANSCEIVER_INFO|Ethernet124": { + "expireat": 1794564779.8953598, + "ttl": -0.001, + "type": "hash", + "value": { + "application_advertisement": "N/A", + "cable_breakout": "1x1", + "cable_class": "FIBER", + "cable_length": "0.0", + "cable_length_detailed": "0.0", + "cable_type": "Length OM3(2m)", + "connector": "MPOx12", + "connector_type": "MPOx12", + "display_name": "QSFP28 100GBASE-SR4", + "encoding": "NRZ", + "ext_identifier": "Power Class 4(3.5W max), CDR present in Rx Tx", + "ext_rateselect_compliance": "Unknown", + "firmware_revision": "N/A", + "form_factor": "QSFP28", + "hardware_rev": "1A", + "host_electrical_lane_count": "N/A", + "host_lane_count": "4", + "is_lpo_transceiver": "No", + "is_qualified": "False", + "is_replaceable": "False", + "lane_count": "4", + "laser_capabilities": "N/A", + "link_length": "N/A", + "manufacturer": "FS", + "max_port_power": "5.0", + "media_gen_rev": "N/A", + "media_interface": "SR", + "media_lane_count": "4", + "memory_type": "Paged", + "model": "QSFP28-SR4-100G", + "module_lane_count": "N/A", + "module_type": "2", + "nominal_bit_rate": "255", + "power_rating_max": "3.5", + "qsa_adapter": "N/A", + "serial": "SN00000018", + "special_fields": "N/A", + "specification_compliance": "{'10/40G Ethernet Compliance Code': '100GBASE-SR4', 'Gigabit Ethernet Compliant codes': '1000BASE-SX', 'SAS/SATA compliance codes': 'SAS 3.0G'}", + "supported_ctrls": "N/A", + "type": "QSFP28", + "type_abbrv_name": "QSFP28", + "vendor_date": "2022-03-10T00:00:00+00:00", + "vendor_date_code": "2022-03-10T00:00:00+00:00", + "vendor_name": "FS", + "vendor_oui": "64-9D-99", + "vendor_part_number": "QSFP28-SR4-100G", + "vendor_revision": "1A", + "vendor_serial_number": "SN00000018", + "vendorrev": "1A", + "wavelength": "850.00000000", + "xcvr_speed_max": "100000" + } + }, + "TRANSCEIVER_INFO|Ethernet13": { + "expireat": 1794564779.8951387, + "ttl": -0.001, + "type": "hash", + "value": { + "application_advertisement": "N/A", + "cable_breakout": "1x1", + "cable_class": "DAC", + "cable_length": "2.0", + "cable_length_detailed": "2.0", + "cable_type": "Length Cable Assembly(m)", + "connector": "No separable connector", + "connector_type": "No separable connector", + "display_name": "QSFP28 100GBASE-CR4-DAC-2.0M", + "encoding": "Unspecified", + "ext_identifier": "Power Class 1(1.5W max)", + "ext_rateselect_compliance": "QSFP+ Rate Select Version 1", + "firmware_revision": "N/A", + "form_factor": "QSFP28", + "hardware_rev": "A", + "host_electrical_lane_count": "N/A", + "host_lane_count": "4", + "is_lpo_transceiver": "No", + "is_qualified": "False", + "is_replaceable": "False", + "lane_count": "4", + "laser_capabilities": "N/A", + "link_length": "N/A", + "manufacturer": "FS", + "max_port_power": "5.0", + "media_gen_rev": "N/A", + "media_interface": "CR", + "media_lane_count": "4", + "memory_type": "Flat", + "model": "QSFP-100G-4SPC02", + "module_lane_count": "N/A", + "module_type": "2", + "nominal_bit_rate": "255", + "power_rating_max": "1.5", + "qsa_adapter": "N/A", + "serial": "SN00000016", + "special_fields": "N/A", + "specification_compliance": "{'10/40G Ethernet Compliance Code': '100GBASE-CR4'}", + "supported_ctrls": "N/A", + "type": "QSFP28", + "type_abbrv_name": "QSFP28", + "vendor_date": "2024-04-18T00:00:00+00:00", + "vendor_date_code": "2024-04-18T00:00:00+00:00", + "vendor_name": "FS", + "vendor_oui": "64-9D-99", + "vendor_part_number": "QSFP-100G-4SPC02", + "vendor_revision": "A", + "vendor_serial_number": "SN00000016", + "vendorrev": "A", + "xcvr_speed_max": "100000" + } + }, + "TRANSCEIVER_INFO|Ethernet22": { + "expireat": 1794564779.894862, + "ttl": -0.001, + "type": "hash", + "value": { + "application_advertisement": "N/A", + "cable_breakout": "1x1", + "cable_class": "DAC", + "cable_length": "2.0", + "cable_length_detailed": "2.0", + "cable_type": "Length Cable Assembly(m)", + "connector": "No separable connector", + "connector_type": "No separable connector", + "display_name": "QSFP28 100GBASE-CR4-DAC-2.0M", + "encoding": "Unspecified", + "ext_identifier": "Power Class 1(1.5W max)", + "ext_rateselect_compliance": "QSFP+ Rate Select Version 1", + "firmware_revision": "N/A", + "form_factor": "QSFP28", + "hardware_rev": "A", + "host_electrical_lane_count": "N/A", + "host_lane_count": "4", + "is_lpo_transceiver": "No", + "is_qualified": "False", + "is_replaceable": "False", + "lane_count": "4", + "laser_capabilities": "N/A", + "link_length": "N/A", + "manufacturer": "FS", + "max_port_power": "5.0", + "media_gen_rev": "N/A", + "media_interface": "CR", + "media_lane_count": "4", + "memory_type": "Flat", + "model": "QSFP-100G-4SPC02", + "module_lane_count": "N/A", + "module_type": "2", + "nominal_bit_rate": "255", + "power_rating_max": "1.5", + "qsa_adapter": "N/A", + "serial": "SN00000015", + "special_fields": "N/A", + "specification_compliance": "{'10/40G Ethernet Compliance Code': '100GBASE-CR4'}", + "supported_ctrls": "N/A", + "type": "QSFP28", + "type_abbrv_name": "QSFP28", + "vendor_date": "2024-04-18T00:00:00+00:00", + "vendor_date_code": "2024-04-18T00:00:00+00:00", + "vendor_name": "FS", + "vendor_oui": "64-9D-99", + "vendor_part_number": "QSFP-100G-4SPC02", + "vendor_revision": "A", + "vendor_serial_number": "SN00000015", + "vendorrev": "A", + "xcvr_speed_max": "100000" + } + }, + "TRANSCEIVER_INFO|Ethernet4": { + "expireat": 1794564779.8948135, + "ttl": -0.001, + "type": "hash", + "value": { + "application_advertisement": "N/A", + "cable_breakout": "1x1", + "cable_class": "DAC", + "cable_length": "3.0", + "cable_length_detailed": "3.0", + "cable_type": "Length Cable Assembly(m)", + "connector": "No separable connector", + "connector_type": "No separable connector", + "display_name": "QSFP28 100GBASE-CR4-DAC-3.0M", + "encoding": "Unspecified", + "ext_identifier": "Power Class 1(1.5W max)", + "ext_rateselect_compliance": "QSFP+ Rate Select Version 1", + "firmware_revision": "N/A", + "form_factor": "QSFP28", + "hardware_rev": "A", + "host_electrical_lane_count": "N/A", + "host_lane_count": "4", + "is_lpo_transceiver": "No", + "is_qualified": "False", + "is_replaceable": "False", + "lane_count": "4", + "laser_capabilities": "N/A", + "link_length": "N/A", + "manufacturer": "FS", + "max_port_power": "5.0", + "media_gen_rev": "N/A", + "media_interface": "CR", + "media_lane_count": "4", + "memory_type": "Flat", + "model": "Q-4S28PC03", + "module_lane_count": "N/A", + "module_type": "2", + "nominal_bit_rate": "255", + "power_rating_max": "1.5", + "qsa_adapter": "N/A", + "serial": "SN00000007", + "special_fields": "N/A", + "specification_compliance": "{'10/40G Ethernet Compliance Code': '100GBASE-CR4'}", + "supported_ctrls": "N/A", + "type": "QSFP28", + "type_abbrv_name": "QSFP28", + "vendor_date": "2022-03-10T00:00:00+00:00", + "vendor_date_code": "2022-03-10T00:00:00+00:00", + "vendor_name": "FS", + "vendor_oui": "64-9D-99", + "vendor_part_number": "Q-4S28PC03", + "vendor_revision": "A", + "vendor_serial_number": "SN00000007", + "vendorrev": "A", + "xcvr_speed_max": "100000" + } + }, + "TRANSCEIVER_INFO|Ethernet6": { + "expireat": 1794564779.8953347, + "ttl": -0.001, + "type": "hash", + "value": { + "application_advertisement": "N/A", + "cable_breakout": "1x1", + "cable_class": "DAC", + "cable_length": "3.0", + "cable_length_detailed": "3.0", + "cable_type": "Length Cable Assembly(m)", + "connector": "No separable connector", + "connector_type": "No separable connector", + "display_name": "QSFP28 100GBASE-CR4-DAC-3.0M", + "encoding": "Unspecified", + "ext_identifier": "Power Class 1(1.5W max)", + "ext_rateselect_compliance": "QSFP+ Rate Select Version 1", + "firmware_revision": "N/A", + "form_factor": "QSFP28", + "hardware_rev": "A", + "host_electrical_lane_count": "N/A", + "host_lane_count": "4", + "is_lpo_transceiver": "No", + "is_qualified": "False", + "is_replaceable": "False", + "lane_count": "4", + "laser_capabilities": "N/A", + "link_length": "N/A", + "manufacturer": "FS", + "max_port_power": "5.0", + "media_gen_rev": "N/A", + "media_interface": "CR", + "media_lane_count": "4", + "memory_type": "Flat", + "model": "Q-4S28PC03", + "module_lane_count": "N/A", + "module_type": "2", + "nominal_bit_rate": "255", + "power_rating_max": "1.5", + "qsa_adapter": "N/A", + "serial": "SN00000007", + "special_fields": "N/A", + "specification_compliance": "{'10/40G Ethernet Compliance Code': '100GBASE-CR4'}", + "supported_ctrls": "N/A", + "type": "QSFP28", + "type_abbrv_name": "QSFP28", + "vendor_date": "2022-03-10T00:00:00+00:00", + "vendor_date_code": "2022-03-10T00:00:00+00:00", + "vendor_name": "FS", + "vendor_oui": "64-9D-99", + "vendor_part_number": "Q-4S28PC03", + "vendor_revision": "A", + "vendor_serial_number": "SN00000007", + "vendorrev": "A", + "xcvr_speed_max": "100000" + } + }, + "TRANSCEIVER_INFO|Ethernet9": { + "expireat": 1794564779.8952086, + "ttl": -0.001, + "type": "hash", + "value": { + "application_advertisement": "N/A", + "cable_breakout": "1x1", + "cable_class": "DAC", + "cable_length": "2.0", + "cable_length_detailed": "2.0", + "cable_type": "Length Cable Assembly(m)", + "connector": "No separable connector", + "connector_type": "No separable connector", + "display_name": "QSFP28 100GBASE-CR4-DAC-2.0M", + "encoding": "Unspecified", + "ext_identifier": "Power Class 1(1.5W max)", + "ext_rateselect_compliance": "QSFP+ Rate Select Version 1", + "firmware_revision": "N/A", + "form_factor": "QSFP28", + "hardware_rev": "A", + "host_electrical_lane_count": "N/A", + "host_lane_count": "4", + "is_lpo_transceiver": "No", + "is_qualified": "False", + "is_replaceable": "False", + "lane_count": "4", + "laser_capabilities": "N/A", + "link_length": "N/A", + "manufacturer": "FS", + "max_port_power": "5.0", + "media_gen_rev": "N/A", + "media_interface": "CR", + "media_lane_count": "4", + "memory_type": "Flat", + "model": "Q-4S28PC02", + "module_lane_count": "N/A", + "module_type": "2", + "nominal_bit_rate": "255", + "power_rating_max": "1.5", + "qsa_adapter": "N/A", + "serial": "SN00000019", + "special_fields": "N/A", + "specification_compliance": "{'10/40G Ethernet Compliance Code': '100GBASE-CR4'}", + "supported_ctrls": "N/A", + "type": "QSFP28", + "type_abbrv_name": "QSFP28", + "vendor_date": "2024-07-04T00:00:00+00:00", + "vendor_date_code": "2024-07-04T00:00:00+00:00", + "vendor_name": "FS", + "vendor_oui": "64-9D-99", + "vendor_part_number": "Q-4S28PC02", + "vendor_revision": "A", + "vendor_serial_number": "SN00000019", + "vendorrev": "A", + "xcvr_speed_max": "100000" + } + }, + "TRANSCEIVER_INFO|XcvrInitDone": { + "expireat": 1794564779.8952112, + "ttl": -0.001, + "type": "hash", + "value": { + "status": "yes" + } + } + } } }, "db_access": "user", "errors": {}, + "files": { + "sonic_version": "build_version: '4.5.2-Enterprise_Base'\ndebian_version: '11.11'\nkernel_version: '5.10.0-39-amd64'\nasic_type: broadcom\ncommit_id: '3cd846de9f'\nbuild_date: Sat Apr 25 01:30:35 UTC 2026\nbuild_number: 0\nbuilt_by: sonicbld@bld-lvn-csg-10\nconfig_db_version: version_4_5_4" + }, "hostname": "leaf-1", "mgmt": { "eth0": { @@ -602,7 +1617,10 @@ } }, "sections": [ - "ports" + "ports", + "transceiver", + "dom", + "system" ], "version": 1 } diff --git a/tests/unit/utils/hardware/fixtures/sonic-synthetic.json b/tests/unit/utils/hardware/fixtures/sonic-synthetic.json index e202318b5..f1412de84 100644 --- a/tests/unit/utils/hardware/fixtures/sonic-synthetic.json +++ b/tests/unit/utils/hardware/fixtures/sonic-synthetic.json @@ -1,5 +1,12 @@ { "collector": "sonic", + "commands": { + "show platform firmware status": { + "err": "", + "out": "Chassis Module Component Version Description\n--------- -------- ----------- --------- ----------------------------------\n7326-56X N/A BIOS v41.01.00 BIOS - Basic Input/Output System\n ONIE 2018.05 ONIE - Open Network Install Environment\n CPLD1 0x12 CPLD - Complex Programmable Logic Device\n CPLD2 0x0a CPLD - Complex Programmable Logic Device\n", + "rc": 0 + } + }, "db": { "APPL_DB": { "PORT_TABLE": { @@ -214,10 +221,178 @@ } } } + }, + "STATE_DB": { + "EEPROM_INFO": { + "EEPROM_INFO|0x21": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "9", + "Name": "Product Name", + "Value": "7326-56X" + } + }, + "EEPROM_INFO|0x22": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "13", + "Name": "Part Number", + "Value": "FP1ZZ5654002A" + } + }, + "EEPROM_INFO|0x23": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "14", + "Name": "Serial Number", + "Value": "732656X2012345" + } + }, + "EEPROM_INFO|0x24": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "6", + "Name": "Base MAC Address", + "Value": "0C:29:EF:D1:A0:00" + } + }, + "EEPROM_INFO|0x29": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "13", + "Name": "ONIE Version", + "Value": "2018.05.00.05" + } + }, + "EEPROM_INFO|0x2b": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "6", + "Name": "Manufacturer", + "Value": "Accton" + } + }, + "EEPROM_INFO|0x2d": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "Len": "8", + "Name": "Vendor Name", + "Value": "Edgecore" + } + }, + "EEPROM_INFO|State": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "Initialized": "1" + } + }, + "EEPROM_INFO|TlvHeader": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "Id String": "TlvInfo", + "Total Length": "169", + "Version": "1" + } + } + }, + "TRANSCEIVER_INFO": { + "TRANSCEIVER_INFO|Ethernet0": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "cable_length": "3", + "connector_type": "No separable connector", + "display_name": "SFP28 25GBASE-CR-DAC-3.0M", + "form_factor": "SFP28", + "spec_compliance": "25GBASE-CR CA-N", + "vendor_date_code": "2021-03-14", + "vendor_name": "DELL EMC", + "vendor_oui": "3c-18-a0", + "vendor_pn": "0VXFJY", + "vendor_rev": "A0", + "vendor_sn": "CN0APX0098765AB" + } + }, + "TRANSCEIVER_INFO|Ethernet1": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "cable_length": "N/A", + "connector_type": "LC", + "form_factor": "SFP+", + "nominal_wavelength": "850", + "spec_compliance": "10GBASE-SR", + "vendor_date_code": "2019-03-12", + "vendor_name": "FINISAR CORP.", + "vendor_oui": "00-90-65", + "vendor_pn": "FTLX8574D3BCL", + "vendor_rev": "A", + "vendor_sn": "AQF0ABC1234" + } + }, + "TRANSCEIVER_INFO|Ethernet11": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "cable_length": "3", + "connector_type": "No separable connector", + "form_factor": "SFP28", + "spec_compliance": "25GBASE-CR CA-N", + "vendor_date_code": "2021-04-08", + "vendor_name": "Mellanox", + "vendor_oui": "00-02-c9", + "vendor_pn": "MCP2M00-A003E30L", + "vendor_rev": "A3", + "vendor_sn": "MT2114VS01234" + } + }, + "TRANSCEIVER_INFO|Ethernet48": { + "expireat": 1767225600.0, + "ttl": -0.001, + "type": "hash", + "value": { + "cable_length": "N/A", + "connector_type": "MPO 1x12", + "form_factor": "QSFP28", + "nominal_wavelength": "850", + "spec_compliance": "100GBASE-SR4", + "vendor_date_code": "2022-01-20", + "vendor_name": "DELL EMC", + "vendor_oui": "3c-18-a0", + "vendor_pn": "0W8H6T", + "vendor_rev": "A1", + "vendor_sn": "CN0W8H6T0012345" + } + } + } } }, "db_access": "user", "errors": {}, + "files": { + "sonic_version": "---\nbuild_version: '4.5.0-Enterprise_Base'\ndebian_version: '10.13'\nkernel_version: '5.10.0-21-amd64'\nasic_type: broadcom\ncommit_id: '1a2b3c4d5'\nbuild_date: Fri Mar 14 09:12:44 UTC 2025\nbuilt_by: sonicbld@sonic-lvn-csg-006\n" + }, "hostname": "leaf-1a", "mgmt": { "eth0": { @@ -229,7 +404,9 @@ } }, "sections": [ - "ports" + "ports", + "transceiver", + "system" ], "version": 1 } diff --git a/tests/unit/utils/hardware/test_init.py b/tests/unit/utils/hardware/test_init.py index e6284e075..459a4a088 100644 --- a/tests/unit/utils/hardware/test_init.py +++ b/tests/unit/utils/hardware/test_init.py @@ -4,7 +4,7 @@ import pytest -from osism.utils.hardware import natural_key, normalize_mac +from osism.utils.hardware import natural_key, normalize_date_code, normalize_mac @pytest.mark.parametrize( @@ -38,3 +38,25 @@ def test_natural_key_orders_numbers_by_value(): def test_natural_key_handles_none_and_mixed_values(): assert sorted([None, "b", "1", "a"], key=natural_key) == [None, "1", "a", "b"] + + +@pytest.mark.parametrize( + "value,expected", + [ + # ethtool -m: the EEPROM field, with and without a lot code + ("220907", "2022-09-07"), + ("220907 ", "2022-09-07"), + ("220907A1", "2022-09-07"), + # Enterprise SONiC and community SONiC + ("2022-09-07T00:00:00+00:00", "2022-09-07"), + ("2020-07-24 A1", "2020-07-24"), + ("2020-07-24", "2020-07-24"), + # Not a date: left alone + ("991332", "991332"), + ("week 12", "week 12"), + ("", None), + (None, None), + ], +) +def test_normalize_date_code(value, expected): + assert normalize_date_code(value) == expected diff --git a/tests/unit/utils/hardware/test_parse_linux.py b/tests/unit/utils/hardware/test_parse_linux.py index 506712048..5a54bb8d5 100644 --- a/tests/unit/utils/hardware/test_parse_linux.py +++ b/tests/unit/utils/hardware/test_parse_linux.py @@ -9,7 +9,16 @@ from osism.utils.hardware.parse_linux import ( command_output, oper_status, + parse_devlink, + parse_ethtool, + parse_ethtool_driver, + parse_ethtool_fec, + parse_infiniband, + parse_inventory, parse_ip_link, + parse_key_values, + parse_lspci, + parse_module, parse_permanent_address, parse_ports, pci_address, @@ -208,3 +217,492 @@ def test_parse_ports_without_ip_link_uses_sysfs(): def test_parse_ports_of_an_empty_document(): assert parse_ports({}) == [] + + +# --------------------------------------------------------------------------- +# Hardware view: link settings, driver, module, NIC +# --------------------------------------------------------------------------- + + +def _inventory(load_fixture, name, dom=False): + system, interfaces = parse_inventory(load_fixture(name), dom) + return system, {interface["name"]: interface for interface in interfaces} + + +def test_parse_key_values_splits_at_the_first_colon_and_joins_continuations(): + text = ( + "Settings for eth0:\n" + "\tSupported link modes: 1000baseT/Full\n" + "\t 10000baseT/Full\n" + "\tVendor OUI : 00:90:65\n" + "\n" + "\tTransceiver type : 10G Ethernet: 10G Base-SR\n" + ) + + assert parse_key_values(text) == [ + ("Settings for eth0", ""), + ("Supported link modes", "1000baseT/Full 10000baseT/Full"), + ("Vendor OUI", "00:90:65"), + ("Transceiver type", "10G Ethernet: 10G Base-SR"), + ] + assert parse_key_values(None) == [] + + +def test_parse_ethtool_link_settings(): + up = _ok( + "Settings for eth0:\n\tSpeed: 25000Mb/s\n\tDuplex: Full\n" + "\tAuto-negotiation: on\n\tPort: Direct Attach Copper\n\tLink detected: yes\n" + ) + down = _ok( + "Settings for eth0:\n\tSpeed: Unknown!\n\tDuplex: Unknown! (255)\n" + "\tAuto-negotiation: off\n\tPort: FIBRE\n\tLink detected: no\n" + ) + + assert parse_ethtool(up) == { + "speed": 25000, + "duplex": "full", + "autoneg": True, + "port_type": "Direct Attach Copper", + "link_detected": True, + } + assert parse_ethtool(down) == { + "speed": None, + "duplex": None, + "autoneg": False, + "port_type": "FIBRE", + "link_detected": False, + } + assert parse_ethtool(None)["speed"] is None + + +def test_parse_ethtool_driver(): + result = _ok( + "driver: mlx5_core\nversion: 6.8.0-51-generic\n" + "firmware-version: 16.35.2000 (MT_0000000080)\nexpansion-rom-version: \n" + "bus-info: 0000:3b:00.0\n" + ) + + assert parse_ethtool_driver(result) == { + "driver": "mlx5_core", + "driver_version": "6.8.0-51-generic", + "firmware_version": "16.35.2000 (MT_0000000080)", + "pci_address": "0000:3b:00.0", + } + + +def test_parse_ethtool_fec(): + text = "FEC parameters for eth0:\nConfigured FEC encodings: Auto RS\nActive FEC encoding: RS\n" + + assert parse_ethtool_fec(_ok(text)) == "rs" + assert parse_ethtool_fec({"rc": 1, "out": "", "err": "not supported"}) is None + + +def test_module_of_a_passive_copper_cable(load_fixture): + _, ports = _inventory(load_fixture, "linux-mellanox.json") + + module = ports["ens1f0np0"]["transceiver"] + + assert module["present"] is True + assert module["form_factor"] == "SFP" + assert module["connector"] == "Copper pigtail" + assert module["vendor"] == "Mellanox" + assert module["vendor_oui"] == "00:02:c9" + assert module["part_number"] == "MCP2M00-A003E30L" + assert module["serial"] == "MT2114VS01234" + assert module["revision"] == "A3" + assert module["date_code"] == "2021-04-08" + assert module["cable_length"] == "3m" + assert module["wavelength"] is None + assert "Extended: 25G Base-CR CA-N" in module["compliance"] + # SFF-8472 modules have a vendor revision but no firmware field + assert module["firmware_active"] is None + assert "dom" not in module + + +def test_module_of_an_optic_with_diagnostics(load_fixture): + _, ports = _inventory(load_fixture, "linux-mellanox.json", dom=True) + + module = ports["ens1f1np1"]["transceiver"] + + assert module["form_factor"] == "SFP" + assert module["connector"] == "LC" + assert module["part_number"] == "SFP28-25GSR-85" + assert module["cable_length"] == "70m" + assert module["wavelength"] == "850nm" + # Readings, not the alarm thresholds printed with the same names + assert module["dom"] == { + "temperature": 33.45, + "voltage": 3.2934, + "lanes": [{"rx_power": -3.47, "tx_power": -2.35, "tx_bias": 6.212}], + } + + +def test_module_with_several_lanes(load_fixture): + _, ports = _inventory(load_fixture, "linux-intel.json", dom=True) + + module = ports["enp65s0f0"]["transceiver"] + + assert module["form_factor"] == "QSFP28" + assert module["connector"] == "MPO Parallel Optic" + assert module["vendor"] == "Intel Corp" + assert module["dom"]["temperature"] == 31.0 + assert module["dom"]["lanes"] == [ + {"rx_power": -0.86, "tx_power": -0.22, "tx_bias": 6.75}, + {"rx_power": -0.97, "tx_power": -0.32, "tx_bias": 6.812}, + {"rx_power": -1.02, "tx_power": -0.13, "tx_bias": 6.7}, + {"rx_power": -0.92, "tx_power": -0.41, "tx_bias": 6.79}, + ] + + +def test_cmis_module_exposes_its_firmware(load_fixture): + _, ports = _inventory(load_fixture, "linux-intel.json") + + module = ports["enp66s0"]["transceiver"] + + assert module["form_factor"] == "QSFP-DD" + assert module["connector"] == "MPO 1x16" + assert module["firmware_active"] == "2.9" + assert module["firmware_inactive"] == "2.8" + assert module["cable_length"] == "100m" + + +@pytest.mark.parametrize( + "result", + [ + {"rc": 75, "out": "", "err": "netlink error: Operation not supported"}, + {"rc": 1, "out": "", "err": "Cannot get module EEPROM information"}, + {"rc": 0, "out": "", "err": ""}, + None, + ], +) +def test_port_without_a_module_is_not_present(result): + module = parse_module(result, dom=True) + + assert module["present"] is False + assert module["vendor"] is None + assert module["form_factor"] is None + + +def test_ports_without_a_module_are_reported_not_failed(load_fixture): + _, mellanox = _inventory(load_fixture, "linux-mellanox.json") + _, intel = _inventory(load_fixture, "linux-intel.json") + + # RJ45 port without a cage, and an empty QSFP28 cage + assert mellanox["eno1"]["transceiver"]["present"] is False + assert intel["enp65s0f1"]["transceiver"]["present"] is False + assert intel["enp65s0f1"]["oper_status"] == "down" + assert intel["enp65s0f1"]["nic"]["model"] == "Ethernet Controller E810-C for QSFP" + + +def test_parse_lspci_splits_names_and_ids(): + result = _ok( + "Slot:\t0000:3b:00.0\nClass:\tEthernet controller [0200]\n" + "Vendor:\tMellanox Technologies [15b3]\n" + "Device:\tMT27800 Family [ConnectX-5] [1017]\n" + "SVendor:\tMellanox Technologies [15b3]\n" + "SDevice:\tConnectX-5 EN network interface card [0080]\nRev:\t00\n" + ) + + assert parse_lspci(result) == { + "vendor": "Mellanox Technologies", + "vendor_id": "15b3", + "model": "MT27800 Family [ConnectX-5]", + "model_id": "1017", + "subsystem_vendor": "Mellanox Technologies", + "subsystem_model": "ConnectX-5 EN network interface card", + "revision": "00", + } + assert parse_lspci({"rc": 127, "out": "", "err": ""})["vendor"] is None + + +def test_parse_devlink(): + info = { + "info": { + "pci/0000:41:00.0": { + "driver": "ice", + "serial_number": "00-01-00-ff-ff-00-00-00", + "versions": { + "fixed": {"board.id": "K91258-006"}, + "running": {"fw.mgmt": "7.4.13"}, + "stored": {}, + }, + } + } + } + + assert parse_devlink(_ok(json.dumps(info))) == { + "serial": "00-01-00-ff-ff-00-00-00", + "board_id": "K91258-006", + "firmware": {"running": {"fw.mgmt": "7.4.13"}}, + } + # devlink prints an empty document next to its error for a USB NIC + empty = {"serial": None, "board_id": None, "firmware": None} + assert parse_devlink({"rc": 1, "out": '{"info":{}}', "err": "x"}) == empty + assert parse_devlink(_ok("not json")) == empty + assert parse_devlink(_ok('{"info": {}}')) == empty + assert parse_devlink({"rc": 1, "out": "", "err": "not supported"}) == empty + + +def test_parse_devlink_prefers_the_serial_number_of_the_board(): + info = { + "info": { + "pci/0000:98:00.0": { + "driver": "bnxt_en", + "serial_number": "02-00-00-FF-FE-00-00-03", + "board.serial_number": "SN00000009", + "versions": {"fixed": {"board.id": "BCM957414A4142CC"}}, + } + } + } + + assert parse_devlink(_ok(json.dumps(info))) == { + "serial": "SN00000009", + "board_id": "BCM957414A4142CC", + "firmware": None, + } + + +@pytest.mark.parametrize( + "raw,expected", + [ + ({"state": "4: ACTIVE", "rate": "100 Gb/sec (4X EDR)"}, ("up", 100000)), + ({"state": "1: DOWN", "rate": "2.5 Gb/sec (1X SDR)"}, ("down", 2500)), + ({"state": None, "rate": None}, (None, None)), + (None, (None, None)), + ], +) +def test_parse_infiniband(raw, expected): + link = parse_infiniband(raw) + + assert (link["oper_status"], link["speed"]) == expected + + +def test_inventory_of_a_mellanox_node(load_fixture): + system, ports = _inventory(load_fixture, "linux-mellanox.json") + + assert system == { + "system_vendor": "Supermicro", + "system_model": "SYS-120C-TN10R", + "serial": "S123456X0A01234", + } + port = ports["ens1f0np0"] + assert port["speed"] == 25000 + assert port["duplex"] == "full" + assert port["autoneg"] is True + assert port["fec"] == "rs" + assert port["port_type"] == "Direct Attach Copper" + assert port["nic"] == { + "pci_address": "0000:3b:00.0", + "vendor": "Mellanox Technologies", + "model": "MT27800 Family [ConnectX-5]", + "subsystem_vendor": "Mellanox Technologies", + "subsystem_model": "ConnectX-5 EN network interface card, 25GbE dual-port SFP28", + "revision": "00", + "serial": None, + "board_id": "MT_0000000080", + "driver": "mlx5_core", + "driver_version": "6.8.0-51-generic", + "firmware_version": "16.35.2000 (MT_0000000080)", + "firmware": { + "running": {"fw.version": "16.35.2000", "fw": "16.35.2000"}, + "stored": {"fw.version": "16.35.2000", "fw": "16.35.2000"}, + }, + } + # The onboard port has no devlink support and no FEC + assert ports["eno1"]["nic"]["driver"] == "igb" + assert ports["eno1"]["nic"]["firmware"] is None + assert ports["eno1"]["fec"] is None + assert ports["eno2"]["speed"] is None + + +def test_inventory_of_an_intel_node(load_fixture): + system, ports = _inventory(load_fixture, "linux-intel.json") + + assert system["system_model"] == "PowerEdge R750" + nic = ports["enp65s0f0"]["nic"] + assert nic["vendor"] == "Intel Corporation" + assert nic["model"] == "Ethernet Controller E810-C for QSFP" + assert nic["driver"] == "ice" + assert nic["firmware_version"] == "4.40 0x8001c7d4 1.3534.0" + assert nic["serial"] == "00-01-00-ff-ff-00-00-00" + assert nic["board_id"] == "K91258-006" + assert ports["enp65s0f0"]["speed"] == 100000 + assert ports["enp66s0"]["speed"] == 400000 + + +def test_inventory_of_a_broadcom_node_without_optional_tools(load_fixture): + """lspci and devlink are missing on this node. Their fields stay empty.""" + system, ports = _inventory(load_fixture, "linux-broadcom.json") + + assert system["system_vendor"] == "HPE" + nic = ports["ens2f0np0"]["nic"] + assert nic["driver"] == "bnxt_en" + assert nic["firmware_version"] == "229.0.141.0/pkg 229.1.123.0" + assert nic["pci_address"] == "0000:12:00.0" + assert nic["vendor"] is None and nic["model"] is None + assert nic["serial"] is None and nic["firmware"] is None + assert ports["ens2f0np0"]["fec"] == "baser" + assert ports["ens2f1np1"]["speed"] == 10000 + assert ports["ens2f1np1"]["transceiver"]["part_number"] == "SFP28-25GSR-85" + + +def test_inventory_marks_virtual_functions_and_infiniband_ports(load_fixture): + _, ports = _inventory(load_fixture, "linux-mellanox-vf-ib.json") + + assert {name: port["kind"] for name, port in ports.items()} == { + "ens3f0np0": "physical", + "ens3f0v0": "vf", + "ibp129s0": "infiniband", + } + + vf = ports["ens3f0v0"] + assert vf["parent"] == "ens3f0np0" + assert vf["mac"] == "02:00:00:00:00:01" + assert vf["nic"]["driver"] == "mlx5_core" + # The module belongs to the physical function and is not repeated + assert vf["transceiver"]["present"] is None + assert vf["transceiver"]["vendor"] is None + + ib = ports["ibp129s0"] + assert ib["oper_status"] == "up" + assert ib["speed"] == 100000 + assert ib["transceiver"]["form_factor"] == "QSFP28" + + +# --------------------------------------------------------------------------- +# Captured nodes +# --------------------------------------------------------------------------- + + +def test_inventory_of_a_captured_connectx4_node(load_fixture): + system, ports = _inventory(load_fixture, "linux-connectx4.json") + + assert system == { + "system_vendor": "Supermicro", + "system_model": "X10DRi", + "serial": "SN00000003", + } + port = ports["ens4f0np0"] + assert port["speed"] == 25000 + assert port["duplex"] == "full" + assert port["autoneg"] is True + assert port["port_type"] == "Direct Attach Copper" + assert port["fec"] == "off" + assert port["nic"] == { + "pci_address": "0000:82:00.0", + "vendor": "Mellanox Technologies", + "model": "MT27710 Family [ConnectX-4 Lx]", + "subsystem_vendor": "Mellanox Technologies", + "subsystem_model": ( + "Stand-up ConnectX-4 Lx EN, 25GbE dual-port SFP28, PCIe3.0 x8," + " MCX4121A-ACAT" + ), + "revision": None, + # mlx5_core reports no serial number for this card + "serial": None, + "board_id": "MT_2420110034", + "driver": "mlx5_core", + "driver_version": "6.8.0-136-generic", + "firmware_version": "14.32.1010 (MT_2420110034)", + "firmware": { + "running": {"fw.version": "14.32.1010", "fw": "14.32.1010"}, + "stored": {"fw.version": "14.32.1010", "fw": "14.32.1010"}, + }, + } + # One SFP28 end of a QSFP28 to 4x SFP28 breakout cable + assert port["transceiver"] == { + "present": True, + "form_factor": "SFP", + "connector": "Copper pigtail", + "vendor": "FS", + "vendor_oui": "64:9d:99", + "part_number": "Q-4S28PC02", + "serial": "SN00000004-1", + "revision": "01", + "date_code": "2022-09-07", + "firmware_active": None, + "firmware_inactive": None, + "compliance": "Passive Cable; Extended: 25G Base-CR CA-S", + "cable_length": "2m", + "wavelength": None, + } + + # An onboard RJ45 port: ethtool -m is not supported, devlink is silent + onboard = ports["enp4s0f0"] + assert onboard["port_type"] == "Twisted Pair" + assert onboard["transceiver"]["present"] is False + assert onboard["nic"]["driver"] == "igb" + assert onboard["nic"]["model"] == "I350 Gigabit Network Connection" + assert onboard["nic"]["firmware"] is None + # A port without link reports "Unknown!" as speed and duplex + assert ports["enp4s0f1"]["speed"] is None + assert ports["enp4s0f1"]["duplex"] is None + + +def test_inventory_of_a_captured_broadcom_node(load_fixture): + _, ports = _inventory(load_fixture, "linux-bcm57414.json") + + assert sorted(ports) == ["data1", "data2", "eno2np1", "enx02000000001f", "mgmt1"] + nic = ports["data1"]["nic"] + assert nic["vendor"] == "Broadcom Inc. and subsidiaries" + assert nic["model"] == "BCM57414 NetXtreme-E 10Gb/25Gb RDMA Ethernet Controller" + assert nic["driver"] == "bnxt_en" + assert nic["firmware_version"] == "214.4.91.1/pkg 216.0.333.11" + # The serial number of the board, not the one derived from the MAC + assert nic["serial"] == "SN00000009" + assert nic["board_id"] == "BCM957414A4142CC" + assert nic["firmware"]["running"]["fw.mgmt"] == "214.4.91.1" + assert ports["data1"]["transceiver"]["cable_length"] == "3m" + assert ports["data1"]["altnames"] == ["enp152s0f0np0"] + + # 10GBASE-T ports of an Intel X710 on the same node + assert ports["mgmt1"]["nic"]["driver"] == "i40e" + assert ports["mgmt1"]["speed"] == 1000 + assert ports["mgmt1"]["transceiver"]["present"] is False + assert ports["eno2np1"]["oper_status"] == "down" + assert ports["eno2np1"]["port_type"] == "Other" + + +def test_usb_nic_of_a_bmc_is_a_port_without_pci_device(load_fixture): + """The host interface of the BMC shows up as a USB network device.""" + _, ports = _inventory(load_fixture, "linux-bcm57414.json") + + port = ports["enx02000000001f"] + assert port["kind"] == "physical" + assert port["oper_status"] == "down" + assert port["pci_address"] is None + assert port["fec"] is None + assert port["transceiver"]["present"] is False + assert port["nic"] == { + "pci_address": None, + "vendor": None, + "model": None, + "subsystem_vendor": None, + "subsystem_model": None, + "revision": None, + "serial": None, + "board_id": None, + "driver": "rndis_host", + "driver_version": "7.0.0-31-generic", + "firmware_version": "RNDIS device", + "firmware": None, + } + + +def test_inventory_of_a_captured_82599_node(load_fixture): + system, ports = _inventory(load_fixture, "linux-82599.json") + + assert system["system_model"] == "SYS-530MT-H12TRF" + port = ports["data2"] + # A 10G port on a 25G capable cable; ixgbe does not report FEC + assert port["speed"] == 10000 + assert port["autoneg"] is False + assert port["fec"] is None + assert port["nic"]["driver"] == "ixgbe" + assert port["nic"]["model"] == "82599ES 10-Gigabit SFI/SFP+ Network Connection" + assert port["nic"]["firmware_version"] == "0x800006da, 1.2527.0" + assert port["nic"]["firmware"] == { + "running": {"fw.undi": "1.2527.0", "fw.bundle_id": "0x800006da"} + } + assert port["transceiver"]["part_number"] == "QSFP-100G-4SPC02" + assert port["transceiver"]["serial"] == "SN00000015-3" diff --git a/tests/unit/utils/hardware/test_parse_sonic.py b/tests/unit/utils/hardware/test_parse_sonic.py index 0bc4edd07..b0637d03b 100644 --- a/tests/unit/utils/hardware/test_parse_sonic.py +++ b/tests/unit/utils/hardware/test_parse_sonic.py @@ -2,10 +2,21 @@ """Unit tests for ``osism.utils.hardware.parse_sonic``.""" +import pytest + +from osism.utils.hardware.parse_linux import parse_inventory as parse_linux_inventory from osism.utils.hardware.parse_sonic import ( device_metadata, + parse_dom, + parse_firmware_status, + parse_inventory, parse_mgmt_port, parse_ports, + parse_show_version, + parse_syseeprom, + parse_system, + parse_transceivers, + parse_version_file, table, ) @@ -167,3 +178,394 @@ def test_parse_mgmt_port_uses_carrier_for_unknown_operstate(): def test_parse_mgmt_port_is_none_when_it_was_not_seen(): assert parse_mgmt_port({}) is None assert parse_mgmt_port({"mgmt": {}}) is None + + +# --------------------------------------------------------------------------- +# Hardware view: transceivers, diagnostics, platform +# --------------------------------------------------------------------------- + + +def _inventory(load_fixture, name, dom=False): + system, interfaces = parse_inventory(load_fixture(name), dom) + return system, {interface["name"]: interface for interface in interfaces} + + +def test_transceivers_of_an_enterprise_switch(load_fixture): + transceivers = parse_transceivers(load_fixture("sonic-enterprise.json")) + + # The table also holds the marker XcvrInitDone, which is not a port + assert sorted(transceivers) == [ + "Ethernet0", + "Ethernet1", + "Ethernet12", + "Ethernet120", + "Ethernet124", + "Ethernet13", + "Ethernet22", + "Ethernet4", + "Ethernet6", + "Ethernet9", + ] + # A passive breakout cable: all four ports of the cage report its + # QSFP28 end + assert transceivers["Ethernet0"] == { + "present": True, + "form_factor": "QSFP28", + "connector": "No separable connector", + "vendor": "FS", + "vendor_oui": "64:9d:99", + "part_number": "Q-4S28PC02", + "serial": "SN00000004", + "revision": "A", + "date_code": "2022-09-07", + "firmware_active": None, + "firmware_inactive": None, + "compliance": "100GBASE-CR4", + "cable_length": "2m", + "wavelength": None, + } + assert transceivers["Ethernet1"] == transceivers["Ethernet0"] + assert transceivers["Ethernet4"]["cable_length"] == "3m" + + optic = transceivers["Ethernet120"] + assert optic["connector"] == "MPOx12" + assert optic["part_number"] == "QSFP28-SR4-100G" + assert optic["compliance"] == "100GBASE-SR4; 1000BASE-SX; SAS 3.0G" + assert optic["wavelength"] == "850nm" + # "0.0" is the cable length of a module without a cable + assert optic["cable_length"] is None + + +def test_both_ends_of_a_breakout_cable_read_alike(load_fixture): + """node-1 is cabled to Ethernet0 of leaf-1 in the captures.""" + switch = parse_transceivers(load_fixture("sonic-enterprise.json"))["Ethernet0"] + _, ports = parse_linux_inventory(load_fixture("linux-connectx4.json")) + node = {port["name"]: port for port in ports}["ens4f0np0"]["transceiver"] + + for field in ("vendor", "vendor_oui", "part_number", "date_code", "cable_length"): + assert node[field] == switch[field], field + # The SFP28 ends carry the serial number of the cable plus their number + assert node["serial"] == switch["serial"] + "-1" + + +def _transceiver(**fields): + document = { + "db": { + "STATE_DB": { + "TRANSCEIVER_INFO": {"TRANSCEIVER_INFO|Ethernet0": {"value": fields}} + } + } + } + return parse_transceivers(document)["Ethernet0"] + + +@pytest.mark.parametrize( + "value,expected", + [ + ("{'10/40G Ethernet Compliance Code': '100GBASE-CR4'}", "100GBASE-CR4"), + ( + "{'a': '100GBASE-SR4', 'b': 'N/A', 'c': '1000BASE-SX'}", + "100GBASE-SR4; 1000BASE-SX", + ), + ("{'a': 'N/A'}", None), + ("{}", None), + ("25GBASE-CR CA-N", "25GBASE-CR CA-N"), + ("sm_media_interface", "sm_media_interface"), + ("{not a dict", "{not a dict"), + ("{'a', 'b'}", "{'a', 'b'}"), + ], +) +def test_compliance_is_reduced_to_its_codes(value, expected): + assert _transceiver(specification_compliance=value)["compliance"] == expected + + +@pytest.mark.parametrize( + "fields,expected", + [ + ({"cable_length": "2.0"}, "2m"), + ({"cable_length": "0.5", "cable_type": "Length Cable Assembly(m)"}, "0.5m"), + ({"cable_length": "0.0", "cable_type": "Length OM3(2m)"}, None), + # The unit of the EEPROM field is not the metre: keep it visible + ( + {"cable_length": "35.0", "cable_type": "Length OM3(2m)"}, + "35.0 (Length OM3(2m))", + ), + ({"cable_length": "N/A"}, None), + ({"cable_length": "short"}, "short"), + ], +) +def test_cable_length_is_written_with_its_unit(fields, expected): + assert _transceiver(vendor_name="X", **fields)["cable_length"] == expected + + +def test_oui_date_and_wavelength_are_written_like_ethtool_does(): + module = _transceiver( + vendor_oui="3C-18-A0", + vendor_date="2021-03-14T00:00:00+00:00", + wavelength="1310.00000000", + ) + + assert module["vendor_oui"] == "3c:18:a0" + assert module["date_code"] == "2021-03-14" + assert module["wavelength"] == "1310nm" + assert _transceiver(vendor_oui="unknown-oui")["vendor_oui"] == "unknown-oui" + + +def test_transceivers_with_community_field_names(load_fixture): + transceivers = parse_transceivers(load_fixture("sonic-community.json")) + + module = transceivers["Ethernet0"] + assert module["form_factor"] == "QSFP28" + assert module["vendor"] == "Mellanox" + assert module["part_number"] == "MFA1A00-C010" + assert module["serial"] == "MT2030FT01234" + assert module["revision"] == "A2" + assert module["date_code"] == "2020-07-24" + assert module["cable_length"] == "10m" + assert module["vendor_oui"] == "00:02:c9" + assert module["compliance"] == "Extended" + assert module["firmware_active"] is None + + +def test_cmis_firmware_comes_from_the_firmware_table(load_fixture): + transceivers = parse_transceivers(load_fixture("sonic-community.json")) + + assert transceivers["Ethernet4"]["form_factor"] == "QSFP-DD" + assert transceivers["Ethernet4"]["firmware_active"] == "1.3.0" + assert transceivers["Ethernet4"]["firmware_inactive"] == "1.2.9" + + +def test_firmware_in_the_info_table_wins_over_the_firmware_table(): + document = { + "db": { + "STATE_DB": { + "TRANSCEIVER_INFO": { + "TRANSCEIVER_INFO|Ethernet0": { + "value": {"manufacturer": "X", "active_firmware": "2.0"} + } + }, + "TRANSCEIVER_FIRMWARE_INFO": { + "TRANSCEIVER_FIRMWARE_INFO|Ethernet0": { + "value": {"active_firmware": "1.0", "inactive_firmware": "0.9"} + } + }, + } + } + } + + module = parse_transceivers(document)["Ethernet0"] + + assert module["firmware_active"] == "2.0" + assert module["firmware_inactive"] == "0.9" + + +def test_dom_readings(load_fixture): + readings = parse_dom(load_fixture("sonic-community.json")) + + assert readings["Ethernet0"]["temperature"] == 34.5 + assert readings["Ethernet0"]["voltage"] == 3.281 + assert len(readings["Ethernet0"]["lanes"]) == 4 + assert readings["Ethernet0"]["lanes"][0] == { + "rx_power": -1.02, + "tx_power": -0.22, + "tx_bias": 6.75, + } + assert readings["Ethernet4"]["lanes"] == [ + {"rx_power": -2.5, "tx_power": 0.8, "tx_bias": 55.0} + ] + assert parse_dom({}) == {} + + +def test_dom_readings_of_an_enterprise_switch(load_fixture): + readings = parse_dom(load_fixture("sonic-enterprise.json")) + + # A passive copper cable has an entry with "N/A" for every value + assert sorted(readings) == ["Ethernet120", "Ethernet124"] + optic = readings["Ethernet120"] + assert optic["temperature"] == 39.5703 + assert optic["voltage"] == 3.317 + assert len(optic["lanes"]) == 4 + assert optic["lanes"][0] == { + "rx_power": 0.599, + "tx_power": 0.1708, + "tx_bias": 6.588, + } + + +def test_parse_version_file(): + text = "---\nbuild_version: '4.5.0-Enterprise_Base'\nasic_type: broadcom\nbuild_date: Fri Mar 14 09:12:44 UTC 2025\n" + + values = parse_version_file(text) + + assert values["build_version"] == "4.5.0-Enterprise_Base" + assert values["asic_type"] == "broadcom" + assert values["build_date"] == "Fri Mar 14 09:12:44 UTC 2025" + assert parse_version_file(None) == {} + + +def test_parse_show_version(): + text = "SONiC Software Version: SONiC.202311.1-0a1b2c3d4\nPlatform: x86_64-mlnx_msn3700c-r0\nHwSKU: Mellanox-SN3700C\nASIC: mellanox\nSerial Number: MT2012X04321\n" + + assert parse_show_version(text) == { + "os_version": "202311.1-0a1b2c3d4", + "asic": "mellanox", + "platform": "x86_64-mlnx_msn3700c-r0", + "hwsku": "Mellanox-SN3700C", + "serial": "MT2012X04321", + } + assert parse_show_version(None)["os_version"] is None + enterprise = "SONiC Software Version: SONiC-OS-4.5.2-Enterprise_Base\n" + assert parse_show_version(enterprise)["os_version"] == "4.5.2-Enterprise_Base" + + +def test_syseeprom_from_state_db(load_fixture): + assert parse_syseeprom(load_fixture("sonic-enterprise.json")) == { + # The EEPROM names Accton as manufacturer and Edgecore as vendor + "system_vendor": "Accton", + "system_model": "7726-32X-O-AC-B", + "part_number": "FP3ZZ7632076A", + "serial": "SN00000001", + "onie": "2017.11.00.05", + } + + +def test_syseeprom_falls_back_to_the_show_command(load_fixture): + assert parse_syseeprom(load_fixture("sonic-community.json")) == { + "system_vendor": "Mellanox", + "system_model": "MSN3700C", + "part_number": "MSN3700-CS2FO", + "serial": "MT2012X04321", + "onie": "2019.11-5.2.0020-9600", + } + + +def test_parse_firmware_status_cuts_cells_at_the_column_positions(): + text = ( + "Chassis Module Component Version Description\n" + "--------- -------- ----------- --------- ----------------------------\n" + "7326-56X N/A BIOS v41.01.00 BIOS - Basic Input/Output System\n" + " CPLD1 0x12 CPLD - Complex Programmable Logic\n" + " FPGA N/A FPGA\n" + ) + + assert parse_firmware_status(text) == {"BIOS": "v41.01.00", "CPLD1": "0x12"} + + +@pytest.mark.parametrize( + "text", + [ + None, + "", + "Error: Platform API is not available", + "Name Value\n---- -----\nx y\n", + ], +) +def test_parse_firmware_status_without_a_usable_table(text): + assert parse_firmware_status(text) == {} + + +def test_system_of_an_enterprise_switch(load_fixture): + assert parse_system(load_fixture("sonic-enterprise.json")) == { + "hwsku": "Accton-AS7726-32X", + "platform": "x86_64-accton_as7726_32x-r0", + "asic": "broadcom", + "os_version": "4.5.2-Enterprise_Base", + "firmware": { + "bios": "v40.01.01.01", + "cpld": "CPLD1 14, CPLD2 142, CPLD3 142", + # Not a component of the firmware status, taken from the EEPROM + "onie": "2017.11.00.05", + }, + "system_vendor": "Accton", + "system_model": "7726-32X-O-AC-B", + "part_number": "FP3ZZ7632076A", + "serial": "SN00000001", + "base_mac": "02:00:00:00:00:20", + } + + +def test_system_names_a_single_cpld_and_the_onie_component(): + document = { + "commands": { + "show platform firmware status": { + "rc": 0, + "err": "", + "out": ( + "Chassis Module Component Version Description\n" + "--------- -------- ----------- --------- -----------\n" + "7326-56X N/A BIOS v41.01.00 BIOS\n" + " ONIE 2018.05 ONIE\n" + " CPLD1 0x12 CPLD\n" + ), + } + } + } + + assert parse_system(document)["firmware"] == { + "bios": "v41.01.00", + "cpld": "0x12", + "onie": "2018.05", + } + + +def test_system_of_a_community_switch_uses_the_fallbacks(load_fixture): + """No version file, no EEPROM table and no firmware status on this one.""" + system = parse_system(load_fixture("sonic-community.json")) + + assert system["os_version"] == "202311.1-0a1b2c3d4" + assert system["asic"] == "mellanox" + assert system["hwsku"] == "Mellanox-SN3700C" + assert system["system_model"] == "MSN3700C" + assert system["serial"] == "MT2012X04321" + assert system["firmware"] == { + "bios": None, + "cpld": None, + "onie": "2019.11-5.2.0020-9600", + } + + +def test_inventory_of_a_switch(load_fixture): + system, ports = _inventory(load_fixture, "sonic-enterprise.json") + + assert system["hwsku"] == "Accton-AS7726-32X" + assert sorted(ports, key=lambda name: int(name[8:])) == [ + "Ethernet0", + "Ethernet1", + "Ethernet4", + "Ethernet6", + "Ethernet9", + "Ethernet12", + "Ethernet13", + "Ethernet22", + "Ethernet24", + "Ethernet120", + "Ethernet124", + "Ethernet125", + ] + assert "eth0" not in ports + assert ports["Ethernet120"]["transceiver"]["form_factor"] == "QSFP28" + assert ports["Ethernet120"]["nic"] is None + # No entry in TRANSCEIVER_INFO means no module, not a failure + assert ports["Ethernet24"]["transceiver"]["present"] is False + assert ports["Ethernet125"]["transceiver"]["present"] is False + # A module stays visible in a port that is administratively down + assert ports["Ethernet4"]["admin_status"] == "down" + assert ports["Ethernet4"]["transceiver"]["present"] is True + assert "dom" not in ports["Ethernet0"]["transceiver"] + + +def test_inventory_of_a_switch_with_diagnostics(load_fixture): + _, ports = _inventory(load_fixture, "sonic-enterprise.json", dom=True) + + assert ports["Ethernet120"]["transceiver"]["dom"]["temperature"] == 39.5703 + # A passive copper cable has no diagnostics, an empty cage no module + assert ports["Ethernet0"]["transceiver"]["dom"] is None + assert ports["Ethernet24"]["transceiver"]["dom"] is None + + +def test_inventory_with_diagnostics(load_fixture): + _, ports = _inventory(load_fixture, "sonic-community.json", dom=True) + + assert ports["Ethernet0"]["transceiver"]["dom"]["temperature"] == 34.5 + assert ports["Ethernet8"]["transceiver"]["present"] is False + assert ports["Ethernet8"]["transceiver"]["dom"] is None diff --git a/tests/unit/utils/hardware/test_remote_linux.py b/tests/unit/utils/hardware/test_remote_linux.py index b84551348..93509013f 100644 --- a/tests/unit/utils/hardware/test_remote_linux.py +++ b/tests/unit/utils/hardware/test_remote_linux.py @@ -171,3 +171,103 @@ def test_run_reports_missing_commands_and_output(): ok = remote_linux.run(["python3", "-c", "print('hello')"]) assert ok == {"rc": 0, "out": "hello\n", "err": ""} + + +# --------------------------------------------------------------------------- +# Hardware sections +# --------------------------------------------------------------------------- + + +def test_hardware_sections_query_every_port(sys_net): + calls = [] + + document = remote_linux.collect( + ["ports", "ethtool", "module", "nic"], + ["physical", "vf"], + sys_net, + _runner(calls), + ) + + port = document["ports"]["ens1f0"] + assert port["ethtool"]["out"] == "output of ethtool ens1f0" + assert port["ethtool_i"]["out"] == "output of ethtool -i ens1f0" + assert port["ethtool_fec"]["out"] == "output of ethtool --show-fec ens1f0" + assert port["ethtool_m"]["out"] == "output of ethtool -m ens1f0" + assert port["lspci"]["out"].startswith("output of lspci -D -vmm -nn -s ") + assert port["devlink"]["out"].startswith("output of devlink -j dev info pci/") + assert ["ethtool", "-m", "ens1f0v0"] in calls + + +def test_module_section_keeps_the_exit_code_of_a_port_without_module(sys_net): + def runner(argv, timeout=20): + if argv[:2] == ["ethtool", "-m"]: + return {"rc": 75, "out": "", "err": "Operation not supported"} + return {"rc": 0, "out": "", "err": ""} + + document = remote_linux.collect(["ports", "module"], ["physical"], sys_net, runner) + + assert document["ports"]["ens1f0"]["ethtool_m"] == { + "rc": 75, + "out": "", + "err": "Operation not supported", + } + + +def test_nic_section_queries_a_shared_pci_function_once(tmp_path): + root = tmp_path / "net" + function = tmp_path / "devices" / "0000:3b:00.0" + function.mkdir(parents=True) + for name in ("p0", "p1"): + base = root / name + base.mkdir(parents=True) + (base / "type").write_text("1\n") + (base / "device").symlink_to(function) + calls = [] + + document = remote_linux.collect( + ["ports", "nic"], ["physical"], str(root), _runner(calls) + ) + + assert document["ports"]["p0"]["pci"] == "0000:3b:00.0" + assert document["ports"]["p1"]["lspci"] == document["ports"]["p0"]["lspci"] + assert calls.count(["lspci", "-D", "-vmm", "-nn", "-s", "0000:3b:00.0"]) == 1 + assert calls.count(["devlink", "-j", "dev", "info", "pci/0000:3b:00.0"]) == 1 + + +def test_system_section_reads_dmi(sys_net, tmp_path, monkeypatch): + dmi = tmp_path / "dmi" + dmi.mkdir() + (dmi / "sys_vendor").write_text("Supermicro\n") + (dmi / "product_name").write_text("SYS-120C\n") + monkeypatch.setattr(remote_linux, "SYS_DMI", str(dmi)) + + document = remote_linux.collect(["system"], ["physical"], sys_net, _runner()) + + assert document["dmi"] == { + "sys_vendor": "Supermicro", + "product_name": "SYS-120C", + "product_serial": None, + } + + +def test_infiniband_port_reports_state_and_rate(sys_net, tmp_path): + port = tmp_path / "net" / "ib0" / "device" / "infiniband" / "mlx5_2" / "ports" / "2" + port.mkdir(parents=True) + (port / "state").write_text("4: ACTIVE\n") + (port / "rate").write_text("100 Gb/sec (4X EDR)\n") + (tmp_path / "net" / "ib0" / "dev_port").write_text("1\n") + + document = remote_linux.collect(["ports"], ["infiniband"], sys_net, _runner()) + + assert document["ports"]["ib0"]["infiniband"] == { + "device": "mlx5_2", + "port": 2, + "state": "4: ACTIVE", + "rate": "100 Gb/sec (4X EDR)", + } + + +def test_infiniband_port_without_ib_device(sys_net): + document = remote_linux.collect(["ports"], ["infiniband"], sys_net, _runner()) + + assert document["ports"]["ib0"]["infiniband"] == {} diff --git a/tests/unit/utils/hardware/test_remote_sonic.py b/tests/unit/utils/hardware/test_remote_sonic.py index d6bde22a7..ae327bb8e 100644 --- a/tests/unit/utils/hardware/test_remote_sonic.py +++ b/tests/unit/utils/hardware/test_remote_sonic.py @@ -137,3 +137,94 @@ def test_main_rejects_unknown_sections(tmp_path, capsys): assert code == 2 assert "unknown section(s): bogus" in capsys.readouterr().err + + +# --------------------------------------------------------------------------- +# Hardware sections +# --------------------------------------------------------------------------- + +INFO_DUMP = {"TRANSCEIVER_INFO|Ethernet0": {"value": {"manufacturer": "ACME"}}} +DOM_DUMP = {"TRANSCEIVER_DOM_SENSOR|Ethernet0": {"value": {"temperature": "30"}}} +EEPROM_DUMP = {"EEPROM_INFO|0x21": {"value": {"Name": "Product Name", "Value": "X"}}} + + +def _show_runner(responses, calls): + """Answer ``sonic-db-dump`` from ``responses`` and record ``show`` calls.""" + dump = _runner(responses) + + def run(argv, timeout=30): + if argv[0] == "show": + calls.append(" ".join(argv)) + return {"rc": 0, "out": "output of " + " ".join(argv), "err": ""} + return dump(argv, timeout) + + return run + + +def test_transceiver_and_dom_sections_dump_state_db(tmp_path): + runner = _runner( + { + (False, "STATE_DB", "TRANSCEIVER_INFO|*"): INFO_DUMP, + (False, "STATE_DB", "TRANSCEIVER_DOM_SENSOR|*"): DOM_DUMP, + } + ) + + document = remote_sonic.collect(["transceiver", "dom"], str(tmp_path), runner) + + assert document["db"]["STATE_DB"]["TRANSCEIVER_INFO"] == INFO_DUMP + assert document["db"]["STATE_DB"]["TRANSCEIVER_DOM_SENSOR"] == DOM_DUMP + # The firmware table only exists on newer releases + assert document["errors"] == {"STATE_DB TRANSCEIVER_FIRMWARE_INFO": "no such table"} + + +def test_system_section_prefers_files_and_tables_over_show_commands( + tmp_path, monkeypatch +): + version = tmp_path / "sonic_version.yml" + version.write_text("build_version: '4.5.0'\n") + monkeypatch.setattr(remote_sonic, "VERSION_FILE", str(version)) + calls = [] + runner = _show_runner({(False, "STATE_DB", "EEPROM_INFO|*"): EEPROM_DUMP}, calls) + + document = remote_sonic.collect(["system"], str(tmp_path), runner) + + assert document["files"]["sonic_version"] == "build_version: '4.5.0'" + assert calls == ["show platform firmware status"] + assert sorted(document["commands"]) == ["show platform firmware status"] + + +def test_system_section_falls_back_to_show_commands(tmp_path, monkeypatch): + monkeypatch.setattr(remote_sonic, "VERSION_FILE", str(tmp_path / "missing.yml")) + calls = [] + + document = remote_sonic.collect(["system"], str(tmp_path), _show_runner({}, calls)) + + assert document["files"]["sonic_version"] is None + assert calls == [ + "show version", + "show platform syseeprom", + "show platform firmware status", + ] + assert document["commands"]["show version"]["out"] == "output of show version" + + +def test_main_accepts_the_hardware_sections(tmp_path): + stream = io.StringIO() + runner = _runner({(False, "CONFIG_DB", "PORT|*"): PORT_DUMP}) + + code = remote_sonic.main( + ["--sections", "ports,transceiver,dom,system"], + sys_net=str(tmp_path), + runner=lambda argv, timeout=30: ( + {"rc": 1, "out": "", "err": "n/a"} if argv[0] == "show" else runner(argv) + ), + stream=stream, + ) + + assert code == 0 + assert json.loads(stream.getvalue())["sections"] == [ + "ports", + "transceiver", + "dom", + "system", + ]