From b89b05e3d6fb1f44dc41d6fd964f0f717eb9c031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Teod=C3=B3sio?= Date: Mon, 20 Jan 2025 15:40:45 +0100 Subject: [PATCH] Use permanent MAC address as serial number on inventory network interfaces Extend the Ethtool class to also gather the permanent address of the network interfaces. Link the Inventory Items for network interface cards with the Interface Component. Update the Inventory Items for network interface cards. --- netbox_agent/ethtool.py | 9 ++++++ netbox_agent/inventory.py | 68 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/netbox_agent/ethtool.py b/netbox_agent/ethtool.py index 001993ad..6d714391 100644 --- a/netbox_agent/ethtool.py +++ b/netbox_agent/ethtool.py @@ -70,9 +70,18 @@ def _parse_ethtool_module_output(self): return {"form_factor": r.groups()[0]} return {} + def _parse_ethtool_permanent_address_output(self): + status, output = subprocess.getstatusoutput("ethtool -P {}".format(self.interface)) + if status == 0: + prefix = "Permanent address: " + if output.startswith(prefix): + return {"mac": output.removeprefix(prefix).strip()} + return {} + def parse(self): if which("ethtool") is None: return None output = self._parse_ethtool_output() output.update(self._parse_ethtool_module_output()) + output.update(self._parse_ethtool_permanent_address_output()) return output diff --git a/netbox_agent/inventory.py b/netbox_agent/inventory.py index 57b0de53..eecdcf97 100644 --- a/netbox_agent/inventory.py +++ b/netbox_agent/inventory.py @@ -1,5 +1,6 @@ from netbox_agent.config import config from netbox_agent.config import netbox_instance as nb +from netbox_agent.ethtool import Ethtool from netbox_agent.lshw import LSHW from netbox_agent.misc import get_vendor, is_tool from netbox_agent.raid.hp import HPRaid @@ -157,7 +158,12 @@ def do_netbox_motherboard(self): description="{}".format(motherboard.get("description")), ) - def create_netbox_interface(self, iface): + def create_netbox_interface(self, iface, component_type=None, component_id=None): + component_args = {} + if component_type and component_id: + component_args["component_type"] = component_type + component_args["component_id"] = component_id + manufacturer = self.find_or_create_manufacturer(iface["vendor"]) _ = nb.dcim.inventory_items.create( device=self.device_id, @@ -166,7 +172,8 @@ def create_netbox_interface(self, iface): tags=[{"name": INVENTORY_TAG["interface"]["name"]}], name="{}".format(iface["product"]), serial="{}".format(iface["serial"]), - description="{} {}".format(iface["description"], iface["name"]), + description="{}".format(iface["description"]), + **component_args, ) def do_netbox_interfaces(self): @@ -175,6 +182,13 @@ def do_netbox_interfaces(self): ) interfaces = self.lshw.interfaces + for iface in interfaces: + ethtool = Ethtool(iface["name"]).parse() + + # Override interface serial number from lshw with permanent MAC address from ethtool, if available. + if ethtool and "mac" in ethtool and ethtool.get("mac") != "00:00:00:00:00:00": + iface["serial"] = ethtool["mac"] + # delete interfaces that are in netbox but not locally # use the serial_number has the comparison element for nb_interface in nb_interfaces: @@ -186,10 +200,56 @@ def do_netbox_interfaces(self): ) nb_interface.delete() - # create interfaces that are not in netbox for iface in interfaces: + # Override interface description from lshw. + iface["description"] = "{} {}".format(iface["description"], iface["name"]) + + # Try to find the network interface component id. + component_type = None + component_id = None + nb_interface_component = nb.dcim.interfaces.get(name=iface["name"], device_id=self.device_id) + if hasattr(nb_interface_component, "id"): + component_type = "dcim.interface" + component_id = nb_interface_component.id + if iface.get("serial") not in [x.serial for x in nb_interfaces]: - self.create_netbox_interface(iface) + # create interfaces that are not in netbox + self.create_netbox_interface(iface, component_type, component_id) + else: + # update interfaces that are in netbox if necessary + update = False + nb_interface = [x for x in nb_interfaces if x.serial == iface.get("serial")][0] + + if nb_interface.description != iface["description"]: + logging.info( + "Updating interface {} description from {} to {}".format( + nb_interface.serial, + nb_interface.description, + iface["description"], + ) + ) + nb_interface.description = iface["description"] + update = True + + if ( + nb_interface.component_type != component_type + or nb_interface.component_id != component_id + ): + logging.info( + "Updating interface {} component_type/component_id from {}/{} to {}/{}".format( + nb_interface.serial, + nb_interface.component_type, + nb_interface.component_id, + component_type, + component_id, + ) + ) + nb_interface.component_type = component_type + nb_interface.component_id = component_id + update = True + + if update: + nb_interface.save() def create_netbox_cpus(self): for cpu in self.lshw.get_hw_linux("cpu"):