From f4f45c8b9d315905f462fa72fe8aa29ab98f654e Mon Sep 17 00:00:00 2001 From: lou lecrivain Date: Wed, 8 Apr 2026 12:28:30 +0200 Subject: [PATCH] fix subinterface warn logic and add missing tests + doc --- cosmo/netbox_types.py | 5 +++++ cosmo/routervisitor.py | 12 ++---------- cosmo/tests/test_case_2.yaml | 22 ++++++++++++++++++++++ cosmo/tests/test_serializer.py | 6 +++++- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/cosmo/netbox_types.py b/cosmo/netbox_types.py index 29550b4..2833eb1 100644 --- a/cosmo/netbox_types.py +++ b/cosmo/netbox_types.py @@ -520,6 +520,10 @@ def isCurrentDeviceInterface(self) -> bool: return self.getParent(DeviceType).isCompositeRoot() def getUntaggedVLAN(self): + """ + Retrieves untagged VLAN on interface, if any (None else). Compatible with Netbox native VLANType + and outer_tag CF. Will raise an error if native VLANType and outer_tag CF are used at the same time. + """ cf = self.getCustomFields() if "untagged_vlan" in self.keys() and self["untagged_vlan"]: if cf.get("outer_tag"): @@ -536,6 +540,7 @@ def getUntaggedVLAN(self): return VLANType( {"vid": int(cf["outer_tag"]), "interfaces_as_untagged": [self]} ) + return None def getTaggedVLANS(self) -> list: return self.get("tagged_vlans", []) diff --git a/cosmo/routervisitor.py b/cosmo/routervisitor.py index db279dd..ae68211 100644 --- a/cosmo/routervisitor.py +++ b/cosmo/routervisitor.py @@ -259,19 +259,11 @@ def processSubInterface(self, o: InterfaceType): # easy checks first, narrow down afterward if not o.getUntaggedVLAN() and not o.getUnitNumber() == 0: # costlier checks it is then - device = o.getParent(DeviceType) parent_interface = o.getPhysicalInterfaceByFilter() if parent_interface: - all_parent_sub_interfaces = list( - filter( - lambda i: i.getName().startswith(parent_interface.getName()) - and i.isSubInterface(), - device.getInterfaces(), - ) - ) parent_interface_type = parent_interface.getAssociatedType() - # cases where no VLAN is authorized: we have only one sub interface, or it's a loopback or virtual - if len(all_parent_sub_interfaces) > 1 and parent_interface_type not in [ + # cases where no VLAN is authorized: parent is a loopback or virtual + if parent_interface_type not in [ "loopback", "virtual", ]: diff --git a/cosmo/tests/test_case_2.yaml b/cosmo/tests/test_case_2.yaml index fa28e53..a31393b 100644 --- a/cosmo/tests/test_case_2.yaml +++ b/cosmo/tests/test_case_2.yaml @@ -123,6 +123,28 @@ device_list: name: TEST-VLAN vid: 139 vrf: null + - custom_fields: + bpdufilter: false + inner_tag: null + outer_tag: null + storm_control__broadcast: null + storm_control__multicast: null + storm_control__unknown_unicast: null + description: 'Missing subinterface VLAN' + __typename: InterfaceType + enabled: true + id: '183110' + ip_addresses: [ ] + lag: null + mac_address: null + mode: ACCESS + mtu: null + name: et-0/0/0.200 + tagged_vlans: [ ] + tags: [ ] + type: VIRTUAL + untagged_vlan: null + vrf: null name: TEST0001 platform: __typename: PlatformType diff --git a/cosmo/tests/test_serializer.py b/cosmo/tests/test_serializer.py index d2798d2..5cc3889 100644 --- a/cosmo/tests/test_serializer.py +++ b/cosmo/tests/test_serializer.py @@ -305,10 +305,11 @@ def test_router_physical_interface(): def test_router_logical_interface(capsys): [sd] = get_router_sd_from_path("./test_case_2.yaml") - assert len(sd["interfaces"]["et-0/0/0"]["units"]) == 4 + assert len(sd["interfaces"]["et-0/0/0"]["units"]) == 5 assert 139 in sd["interfaces"]["et-0/0/0"]["units"] assert 150 in sd["interfaces"]["et-0/0/0"]["units"] + assert 200 in sd["interfaces"]["et-0/0/0"]["units"] assert 1 in sd["interfaces"]["et-0/0/0"]["units"] assert 2 in sd["interfaces"]["et-0/0/0"]["units"] @@ -328,6 +329,9 @@ def test_router_logical_interface(capsys): assert re.search( r"0.2] sub-interface number should be same as VLAN \(456\)", output.out ) + assert re.search( + r"0.200] sub interfaces should have an access VLAN configured!", output.out + ) @with_feature(features, "interface-auto-descriptions")