diff --git a/module/sources/vmware/config.py b/module/sources/vmware/config.py index 771eef4..8da9c4c 100644 --- a/module/sources/vmware/config.py +++ b/module/sources/vmware/config.py @@ -553,6 +553,12 @@ def __init__(self): description="""define if the name of the device interface discovered overwrites the interface name in NetBox. The interface will only be matched by identical MAC address""", default_value=True), + ConfigOption("overwrite_host_interface_type", + bool, + description="""define if the type of a host interface derived from its link speed + overwrites a physical interface type in NetBox. Set to False if a source which knows the + hardware (i.e. check_redfish) sets the type of the host interfaces""", + default_value=True), ConfigOption("overwrite_vm_interface_name", bool, description="""define if the name of the VM interface discovered overwrites the diff --git a/module/sources/vmware/connection.py b/module/sources/vmware/connection.py index 6c5d255..85e41c2 100644 --- a/module/sources/vmware/connection.py +++ b/module/sources/vmware/connection.py @@ -1894,6 +1894,16 @@ def add_device_vm_to_inventory(self, object_type, object_data, pnic_data=None, v if object_type == NBVM and self.settings.overwrite_vm_interface_name is False: del int_data["name"] + # vCenter derives the pNIC type from the current link speed only. Keep a physical type + # set by a source which knows the hardware (i.e. check_redfish) if configured. + if object_type == NBDevice and self.settings.overwrite_host_interface_type is False: + current_type = grab(nic_object_dict.get(int_name), "data.type") + # NetBox returns choice fields as {"value": ..., "label": ...} + if isinstance(current_type, dict): + current_type = current_type.get("value") + if current_type not in [None, "virtual", "bridge", "lag", "other"]: + int_data.pop("type", None) + # add/update interface with retrieved data nic_object, ip_address_objects = self.add_update_interface(nic_object_dict.get(int_name), device_vm_object, int_data, nic_ips.get(int_name, list()), diff --git a/settings-example.ini b/settings-example.ini index d8640c2..c63a034 100644 --- a/settings-example.ini +++ b/settings-example.ini @@ -518,6 +518,11 @@ password = super-secret ; NetBox. The interface will only be matched by identical MAC address ;overwrite_device_interface_name = True +; define if the type of a host interface derived from its link speed overwrites a physical +; interface type in NetBox. Set to False if a source which knows the hardware (i.e. +; check_redfish) sets the type of the host interfaces +;overwrite_host_interface_type = True + ; define if the name of the VM interface discovered overwrites the interface name in ; NetBox. The interface will only be matched by identical MAC address ;overwrite_vm_interface_name = True diff --git a/tests/test_vmware_host_nic_type.py b/tests/test_vmware_host_nic_type.py new file mode 100644 index 0000000..7a8e06b --- /dev/null +++ b/tests/test_vmware_host_nic_type.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020 - 2026 netbox-sync team. All rights reserved. +# +# netbox-sync.py +# +# This work is licensed under the terms of the MIT license. +# For a copy, see file LICENSE.txt included in this +# repository or visit: . + +""" +vCenter only knows the current link speed of a host pNIC, so the interface type it derives +is a guess. With 'overwrite_host_interface_type = False' a physical type already in NetBox +(i.e. an SFP+ port set by the check_redfish source, running at 1G or without link) is kept. +Otherwise both sources overwrite each other's type. By default vCenter still sets the type. +""" +import pytest + +from module.netbox.object_classes import NBDevice, NBInterface +from module.sources import instantiate_sources + +KEEP_TYPE = "\noverwrite_host_interface_type = False\n" + + +def first_sync(vcsim, inventory, load_config, settings): + load_config(settings) + first_run = instantiate_sources()[0] + inventory.resolve_relations() + first_run.apply() + + interfaces = [i for i in inventory.get_all_items(NBInterface) + if i.data.get("type") not in [None, "virtual", "other"]] + if not interfaces: + pytest.skip(f"vcsim dump '{vcsim.name}' has no physical host interfaces") + + # the second run finds the objects of the first one in NetBox + for nb_id, this_object in enumerate(inventory.get_all_items(NBInterface) + inventory.get_all_items(NBDevice), + start=1): + this_object.nb_id = nb_id + this_object.is_new = False + this_object.updated_items = list() + this_object.unset_items = list() + + return {interface: interface.data.get("type") for interface in interfaces} + + +def second_sync(inventory, load_config, settings, nic_type): + for interface in inventory.get_all_items(NBInterface): + if interface.data.get("type") not in [None, "virtual"]: + # the shape NetBox hands back for a choice field + interface.data["type"] = {"value": nic_type, "label": nic_type} + + inventory.source_list = [] + load_config(settings) + second_run = instantiate_sources()[0] + inventory.resolve_relations() + second_run.apply() + + +def type_value(interface): + nic_type = interface.data.get("type") + return nic_type.get("value") if isinstance(nic_type, dict) else nic_type + + +def test_physical_type_is_kept(vcsim, inventory, load_config, vmware_settings): + discovered = first_sync(vcsim, inventory, load_config, vmware_settings + KEEP_TYPE) + + second_sync(inventory, load_config, vmware_settings + KEEP_TYPE, "10gbase-x-sfpp") + + for interface in discovered: + assert type_value(interface) == "10gbase-x-sfpp", \ + f"type of '{interface.get_display_name()}' was overwritten from the link speed" + + +def test_type_other_is_still_updated(vcsim, inventory, load_config, vmware_settings): + discovered = first_sync(vcsim, inventory, load_config, vmware_settings + KEEP_TYPE) + + second_sync(inventory, load_config, vmware_settings + KEEP_TYPE, "other") + + for interface, discovered_type in discovered.items(): + assert type_value(interface) == discovered_type, \ + f"type 'other' of '{interface.get_display_name()}' was not updated" + + +def test_type_is_overwritten_by_default(vcsim, inventory, load_config, vmware_settings): + """vCenter-only setups still get a wrong type corrected""" + discovered = first_sync(vcsim, inventory, load_config, vmware_settings) + + second_sync(inventory, load_config, vmware_settings, "400gbase-x-qsfpdd") + + for interface, discovered_type in discovered.items(): + if discovered_type == "400gbase-x-qsfpdd": + continue + assert type_value(interface) == discovered_type, \ + f"type of '{interface.get_display_name()}' was not set from the link speed"