From a0f4d481dce4ef7bb69f63e271e46cd6504f462c Mon Sep 17 00:00:00 2001 From: Sander Steffann Date: Thu, 7 May 2020 02:12:08 +0200 Subject: [PATCH] make single front/rear port work when between panels --- netbox/dcim/models/device_components.py | 30 +++-- netbox/dcim/tests/test_models.py | 162 +++++++++++++++++++++++- 2 files changed, 176 insertions(+), 16 deletions(-) diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 4005d41a4..d854fdc6e 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -115,26 +115,32 @@ class CableTermination(models.Model): # Map a front port to its corresponding rear port if isinstance(termination, FrontPort): - position_stack.append(termination.rear_port_position) # Retrieve the corresponding RearPort from database to ensure we have an up-to-date instance peer_port = RearPort.objects.get(pk=termination.rear_port.pk) + + # Don't use the stack for 1-on-1 ports, they don't have to come in pairs + if peer_port.positions > 1: + position_stack.append(termination.rear_port_position) + return peer_port # Map a rear port/position to its corresponding front port elif isinstance(termination, RearPort): + if termination.positions > 1: + # Can't map to a FrontPort without a position if there are multiple options + if not position_stack: + raise CableTraceSplit(termination) - # Can't map to a FrontPort without a position if there are multiple options - if termination.positions > 1 and not position_stack: - raise CableTraceSplit(termination) + position = position_stack.pop() - # We can assume position 1 if the RearPort has only one position - position = position_stack.pop() if position_stack else 1 - - # Validate the position - if position not in range(1, termination.positions + 1): - raise Exception("Invalid position for {} ({} positions): {})".format( - termination, termination.positions, position - )) + # Validate the position + if position not in range(1, termination.positions + 1): + raise Exception("Invalid position for {} ({} positions): {})".format( + termination, termination.positions, position + )) + else: + # Don't use the stack for 1-on-1 ports, they don't have to come in pairs + position = 1 try: peer_port = FrontPort.objects.get( diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index 6db938732..62167b601 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -512,6 +512,11 @@ class CablePathTestCase(TestCase): FrontPort(device=patch_panel, name='Front Port 4', rear_port=rearport, rear_port_position=4, type=PortTypeChoices.TYPE_8P8C), )) + # Create a 1-on-1 patch panel + patch_panel = Device.objects.create(device_type=devicetype, device_role=devicerole, name='Panel 5', site=site) + rearport = RearPort.objects.create(device=patch_panel, name='Rear Port 1', positions=1, type=PortTypeChoices.TYPE_8P8C) + FrontPort.objects.create(device=patch_panel, name='Front Port 1', rear_port=rearport, rear_port_position=1, type=PortTypeChoices.TYPE_8P8C) + def test_direct_connection(self): """ Test a direct connection between two interfaces. @@ -554,17 +559,17 @@ class CablePathTestCase(TestCase): Test a connection which passes through a single front/rear port pair. 1 2 - [Device 1] ----- [Panel 1] ----- [Device 2] + [Device 1] ----- [Panel 5] ----- [Device 2] Iface1 FP1 RP1 Iface1 """ - # Create cables + # Create cables (FP first, RP second) cable1 = Cable( termination_a=Interface.objects.get(device__name='Device 1', name='Interface 1'), - termination_b=FrontPort.objects.get(device__name='Panel 1', name='Front Port 1') + termination_b=FrontPort.objects.get(device__name='Panel 5', name='Front Port 1') ) cable1.save() cable2 = Cable( - termination_b=RearPort.objects.get(device__name='Panel 1', name='Rear Port 1'), + termination_b=RearPort.objects.get(device__name='Panel 5', name='Rear Port 1'), termination_a=Interface.objects.get(device__name='Device 2', name='Interface 1') ) cable2.save() @@ -592,6 +597,155 @@ class CablePathTestCase(TestCase): self.assertIsNone(endpoint_a.connection_status) self.assertIsNone(endpoint_b.connection_status) + # Recreate cable 1 to test creating the cables in reverse order (RP first, FP second) + cable1.save() + + # Refresh endpoints + endpoint_a.refresh_from_db() + endpoint_b.refresh_from_db() + + # Validate connections + self.assertEqual(endpoint_a.connected_endpoint, endpoint_b) + self.assertEqual(endpoint_b.connected_endpoint, endpoint_a) + self.assertTrue(endpoint_a.connection_status) + self.assertTrue(endpoint_b.connection_status) + + # Delete cable 2 + cable2.delete() + + # Refresh endpoints + endpoint_a.refresh_from_db() + endpoint_b.refresh_from_db() + + # Check that connections have been nullified + self.assertIsNone(endpoint_a.connected_endpoint) + self.assertIsNone(endpoint_b.connected_endpoint) + self.assertIsNone(endpoint_a.connection_status) + self.assertIsNone(endpoint_b.connection_status) + + def test_connection_via_nested_single_rear_port(self): + """ + Test a connection which passes through a single front/rear port pair between two multi-port MUXes. + + Test two connections via patched rear ports: + Device 1 <---> Device 2 + Device 3 <---> Device 4 + + 1 2 + [Device 1] -----------+ +----------- [Device 2] + Iface1 | | Iface1 + FP1 | 3 4 | FP1 + [Panel 1] ----- [Panel 5] ----- [Panel 2] + FP2 | RP1 RP1 FP1 RP1 | FP2 + Iface1 | | Iface1 + [Device 3] -----------+ +----------- [Device 4] + 5 6 + """ + # Create cables (Panel 5 RP first, FP second) + cable1 = Cable( + termination_a=Interface.objects.get(device__name='Device 1', name='Interface 1'), + termination_b=FrontPort.objects.get(device__name='Panel 1', name='Front Port 1') + ) + cable1.save() + cable2 = Cable( + termination_b=FrontPort.objects.get(device__name='Panel 2', name='Front Port 1'), + termination_a=Interface.objects.get(device__name='Device 2', name='Interface 1') + ) + cable2.save() + cable3 = Cable( + termination_b=RearPort.objects.get(device__name='Panel 1', name='Rear Port 1'), + termination_a=RearPort.objects.get(device__name='Panel 5', name='Rear Port 1') + ) + cable3.save() + cable4 = Cable( + termination_b=FrontPort.objects.get(device__name='Panel 5', name='Front Port 1'), + termination_a=RearPort.objects.get(device__name='Panel 2', name='Rear Port 1') + ) + cable4.save() + cable5 = Cable( + termination_b=FrontPort.objects.get(device__name='Panel 1', name='Front Port 2'), + termination_a=Interface.objects.get(device__name='Device 3', name='Interface 1') + ) + cable5.save() + cable6 = Cable( + termination_b=FrontPort.objects.get(device__name='Panel 2', name='Front Port 2'), + termination_a=Interface.objects.get(device__name='Device 4', name='Interface 1') + ) + cable6.save() + + # Retrieve endpoints + endpoint_a = Interface.objects.get(device__name='Device 1', name='Interface 1') + endpoint_b = Interface.objects.get(device__name='Device 2', name='Interface 1') + endpoint_c = Interface.objects.get(device__name='Device 3', name='Interface 1') + endpoint_d = Interface.objects.get(device__name='Device 4', name='Interface 1') + + # Validate connections + self.assertEqual(endpoint_a.connected_endpoint, endpoint_b) + self.assertEqual(endpoint_b.connected_endpoint, endpoint_a) + self.assertEqual(endpoint_c.connected_endpoint, endpoint_d) + self.assertEqual(endpoint_d.connected_endpoint, endpoint_c) + self.assertTrue(endpoint_a.connection_status) + self.assertTrue(endpoint_b.connection_status) + self.assertTrue(endpoint_c.connection_status) + self.assertTrue(endpoint_d.connection_status) + + # Delete cable 3 + cable3.delete() + + # Refresh endpoints + endpoint_a.refresh_from_db() + endpoint_b.refresh_from_db() + endpoint_c.refresh_from_db() + endpoint_d.refresh_from_db() + + # Check that connections have been nullified + self.assertIsNone(endpoint_a.connected_endpoint) + self.assertIsNone(endpoint_b.connected_endpoint) + self.assertIsNone(endpoint_c.connected_endpoint) + self.assertIsNone(endpoint_d.connected_endpoint) + self.assertIsNone(endpoint_a.connection_status) + self.assertIsNone(endpoint_b.connection_status) + self.assertIsNone(endpoint_c.connection_status) + self.assertIsNone(endpoint_d.connection_status) + + # Recreate cable 3 to test reverse order (Panel 5 FP first, RP second) + cable3.save() + + # Refresh endpoints + endpoint_a.refresh_from_db() + endpoint_b.refresh_from_db() + endpoint_c.refresh_from_db() + endpoint_d.refresh_from_db() + + # Validate connections + self.assertEqual(endpoint_a.connected_endpoint, endpoint_b) + self.assertEqual(endpoint_b.connected_endpoint, endpoint_a) + self.assertEqual(endpoint_c.connected_endpoint, endpoint_d) + self.assertEqual(endpoint_d.connected_endpoint, endpoint_c) + self.assertTrue(endpoint_a.connection_status) + self.assertTrue(endpoint_b.connection_status) + self.assertTrue(endpoint_c.connection_status) + self.assertTrue(endpoint_d.connection_status) + + # Delete cable 4 + cable4.delete() + + # Refresh endpoints + endpoint_a.refresh_from_db() + endpoint_b.refresh_from_db() + endpoint_c.refresh_from_db() + endpoint_d.refresh_from_db() + + # Check that connections have been nullified + self.assertIsNone(endpoint_a.connected_endpoint) + self.assertIsNone(endpoint_b.connected_endpoint) + self.assertIsNone(endpoint_c.connected_endpoint) + self.assertIsNone(endpoint_d.connected_endpoint) + self.assertIsNone(endpoint_a.connection_status) + self.assertIsNone(endpoint_b.connection_status) + self.assertIsNone(endpoint_c.connection_status) + self.assertIsNone(endpoint_d.connection_status) + def test_connections_via_patch(self): """ Test two connections via patched rear ports: