diff --git a/changelog.d/3249.added.md b/changelog.d/3249.added.md new file mode 100644 index 0000000000..7ce8ecc417 --- /dev/null +++ b/changelog.d/3249.added.md @@ -0,0 +1 @@ +Added support for setting access port VLANs for Cisco Small Business switches in PortAdmin (by reverting to non-Cisco-specific handler routines for this subgroup of products) diff --git a/python/nav/portadmin/handlers.py b/python/nav/portadmin/handlers.py index f7f7fd2042..13d0c7f7b3 100644 --- a/python/nav/portadmin/handlers.py +++ b/python/nav/portadmin/handlers.py @@ -45,9 +45,16 @@ class ManagementHandler: a class. """ + VENDOR = None + def __init__(self, netbox: manage.Netbox, **kwargs): self.netbox = netbox + @classmethod + def can_handle(cls, netbox: manage.Netbox) -> bool: + """Returns True if this handler can handle the given netbox""" + return netbox.type and netbox.type.get_enterprise_id() == cls.VENDOR + def set_interface_description(self, interface: manage.Interface, description: str): """Configures a single interface's description, AKA the ifalias value""" raise NotImplementedError diff --git a/python/nav/portadmin/management.py b/python/nav/portadmin/management.py index 6018ac0742..d207492535 100644 --- a/python/nav/portadmin/management.py +++ b/python/nav/portadmin/management.py @@ -24,7 +24,8 @@ from nav.portadmin.snmp.hp import HP from nav.portadmin.napalm.juniper import Juniper -VENDOR_MAP = {cls.VENDOR: cls for cls in (Cisco, Dell, H3C, HP, Juniper)} +SUPPORTED_HANDLERS = (Cisco, Dell, H3C, HP, Juniper) +FALLBACK_HANDLER = SNMPHandler class ManagementFactory(object): @@ -37,9 +38,9 @@ def get_instance(cls, netbox: manage.Netbox, **kwargs) -> ManagementHandler: if not netbox.type: raise NoNetboxTypeError() - vendor_id = netbox.type.get_enterprise_id() - handler = VENDOR_MAP.get(vendor_id, SNMPHandler) - return handler(netbox, **kwargs) + matched_handlers = (h for h in SUPPORTED_HANDLERS if h.can_handle(netbox)) + chosen_handler = next(matched_handlers, FALLBACK_HANDLER) + return chosen_handler(netbox, **kwargs) def __init__(self): pass diff --git a/python/nav/portadmin/snmp/base.py b/python/nav/portadmin/snmp/base.py index 70c65475fb..27dcee2979 100644 --- a/python/nav/portadmin/snmp/base.py +++ b/python/nav/portadmin/snmp/base.py @@ -88,8 +88,6 @@ class InvalidManagementProfileError(ManagementError): class SNMPHandler(ManagementHandler): """Implements PortAdmin management functions for SNMP-enabled switches""" - VENDOR = None - QBRIDGENODES = get_mib('Q-BRIDGE-MIB')['nodes'] SYSOBJECTID = '.1.3.6.1.2.1.1.2.0' diff --git a/python/nav/portadmin/snmp/cisco.py b/python/nav/portadmin/snmp/cisco.py index 7d0b330e2a..e77d1356b2 100644 --- a/python/nav/portadmin/snmp/cisco.py +++ b/python/nav/portadmin/snmp/cisco.py @@ -38,6 +38,10 @@ class Cisco(SNMPHandler): """A specialized class for handling ports in CISCO switches.""" + # Cisco sysObjectIDs under this tree are not normal Cisco products and should + # probably not be handled by this handler + OTHER_ENTERPRISES = OID('.1.3.6.1.4.1.9.6') + VENDOR = VENDOR_ID_CISCOSYSTEMS VTPNODES = get_mib('CISCO-VTP-MIB')['nodes'] @@ -85,6 +89,15 @@ def __init__(self, netbox, **kwargs): self.voice_vlan_oid = '1.3.6.1.4.1.9.9.68.1.5.1.1.1' self.cdp_oid = '1.3.6.1.4.1.9.9.23.1.1.1.1.2' + @classmethod + def can_handle(cls, netbox: manage.Netbox) -> bool: + """Returns True if this handler can handle this netbox""" + if netbox.type and cls.OTHER_ENTERPRISES.is_a_prefix_of( + netbox.type.sysobjectid + ): + return False + return super().can_handle(netbox) + @translate_protocol_errors def get_interface_native_vlan(self, interface): return self._query_netbox(self.vlan_oid, interface.ifindex) diff --git a/tests/unittests/portadmin/conftest.py b/tests/unittests/portadmin/conftest.py index 66102a03eb..4060e43d86 100644 --- a/tests/unittests/portadmin/conftest.py +++ b/tests/unittests/portadmin/conftest.py @@ -23,6 +23,7 @@ def netbox_hp(profile): netbox_type = Mock() netbox_type.vendor = vendor + netbox_type.sysobjectid = '1.3.6.1.4.1.11.2.3.7.11.45' netbox_type.get_enterprise_id.return_value = VENDOR_ID_HEWLETT_PACKARD netbox = Mock() @@ -40,6 +41,7 @@ def netbox_cisco(profile): netbox_type = Mock() netbox_type.vendor = vendor + netbox_type.sysobjectid = '1.3.6.1.4.1.9.1.278' netbox_type.get_enterprise_id.return_value = VENDOR_ID_CISCOSYSTEMS netbox = Mock() @@ -50,6 +52,12 @@ def netbox_cisco(profile): return netbox +@pytest.fixture +def netbox_cisco_smb(netbox_cisco): + netbox_cisco.type.sysobjectid = '1.3.6.1.4.1.9.6.1.1004.10.1' + return netbox_cisco + + @pytest.fixture def handler_hp(netbox_hp): return ManagementFactory.get_instance(netbox_hp) diff --git a/tests/unittests/portadmin/portadmin_test.py b/tests/unittests/portadmin/portadmin_test.py index d32a03ba9b..4c739cf303 100644 --- a/tests/unittests/portadmin/portadmin_test.py +++ b/tests/unittests/portadmin/portadmin_test.py @@ -2,6 +2,7 @@ from nav.oids import OID from nav.portadmin.management import ManagementFactory +from nav.portadmin.snmp.base import SNMPHandler from nav.portadmin.snmp.hp import HP from nav.portadmin.snmp.cisco import Cisco @@ -17,6 +18,13 @@ def test_get_cisco(self, netbox_cisco): assert handler is not None, "Could not get handler-object" assert isinstance(handler, Cisco), "Wrong handler-type" + def test_when_device_is_cisco_smb_switch_it_should_return_generic_snmp_handler( + self, netbox_cisco_smb + ): + handler = ManagementFactory.get_instance(netbox_cisco_smb) + assert handler is not None, "Could not get handler-object" + assert type(handler) is SNMPHandler, "Wrong handler-type" + class TestPortadminResponseHP: def test_get_vlan_hp(self, handler_hp):