Fixes #22187: Fix cable trace when entering profiled cable via single-position pass-through ports (#22316)
This commit is contained in:
parent
ae8bc6e6a2
commit
877ba8bf9e
|
|
@ -934,7 +934,17 @@ class CablePath(models.Model):
|
|||
# Profile-based tracing
|
||||
if links[0].profile:
|
||||
cable_profile = links[0].profile_class()
|
||||
positions = position_stack.pop() if position_stack else [None]
|
||||
if position_stack:
|
||||
positions = position_stack.pop()
|
||||
else:
|
||||
# When the position stack is empty (e.g. the trace reached this
|
||||
# profiled cable after crossing single-position pass-through ports
|
||||
# which don't push onto the stack), derive positions from each
|
||||
# termination's own cable_positions — which were set by this
|
||||
# profiled cable when it was saved.
|
||||
positions = [
|
||||
pos for term in terminations for pos in (term.cable_positions or [])
|
||||
]
|
||||
remote_terminations = []
|
||||
new_positions = []
|
||||
|
||||
|
|
@ -951,9 +961,8 @@ class CablePath(models.Model):
|
|||
remaining[cp] -= 1
|
||||
|
||||
# Fallback for when positions don't match cable_positions
|
||||
# (e.g., empty position stack yielding [None])
|
||||
if not term_position_pairs:
|
||||
term_position_pairs = [(terminations[0], pos) for pos in positions]
|
||||
term_position_pairs = [(terminations[0], pos) for pos in positions or [None]]
|
||||
|
||||
peer_results = cable_profile.get_peer_terminations(term_position_pairs)
|
||||
seen = set()
|
||||
|
|
|
|||
|
|
@ -1857,6 +1857,103 @@ class CablePathTestCase(CablePathTestCase):
|
|||
)
|
||||
self.assertEqual(CablePath.objects.count(), 4)
|
||||
|
||||
def test_225_breakout_1c2p_2c1p_to_single_position_passthroughs(self):
|
||||
"""
|
||||
Regression test for #22187: a 1C2P:2C1P breakout cable terminating on two
|
||||
single-position FrontPorts (each mapping 1:1 to a single-position RearPort)
|
||||
which are then connected to PathEndpoints via unprofiled cables.
|
||||
|
||||
[IF1] --C1 (1C2P:2C1P)-- [FP1] [RP1] --C2 (unprofiled)-- [IF2]
|
||||
[FP2] [RP2] --C3 (unprofiled)-- [IF3]
|
||||
"""
|
||||
interfaces = [
|
||||
Interface.objects.create(device=self.device, name='Interface 1'),
|
||||
Interface.objects.create(device=self.device, name='Interface 2'),
|
||||
Interface.objects.create(device=self.device, name='Interface 3'),
|
||||
]
|
||||
rear_ports = [
|
||||
RearPort.objects.create(device=self.device, name='Rear Port 1'),
|
||||
RearPort.objects.create(device=self.device, name='Rear Port 2'),
|
||||
]
|
||||
front_ports = [
|
||||
FrontPort.objects.create(device=self.device, name='Front Port 1'),
|
||||
FrontPort.objects.create(device=self.device, name='Front Port 2'),
|
||||
]
|
||||
PortMapping.objects.bulk_create([
|
||||
PortMapping(
|
||||
device=self.device,
|
||||
front_port=front_ports[0],
|
||||
front_port_position=1,
|
||||
rear_port=rear_ports[0],
|
||||
rear_port_position=1,
|
||||
),
|
||||
PortMapping(
|
||||
device=self.device,
|
||||
front_port=front_ports[1],
|
||||
front_port_position=1,
|
||||
rear_port=rear_ports[1],
|
||||
rear_port_position=1,
|
||||
),
|
||||
])
|
||||
|
||||
# Create cables
|
||||
cable1 = Cable(
|
||||
profile=CableProfileChoices.BREAKOUT_1C2P_2C1P,
|
||||
a_terminations=[interfaces[0]],
|
||||
b_terminations=[front_ports[0], front_ports[1]],
|
||||
)
|
||||
cable1.clean()
|
||||
cable1.save()
|
||||
cable2 = Cable(
|
||||
a_terminations=[rear_ports[0]],
|
||||
b_terminations=[interfaces[1]],
|
||||
)
|
||||
cable2.clean()
|
||||
cable2.save()
|
||||
cable3 = Cable(
|
||||
a_terminations=[rear_ports[1]],
|
||||
b_terminations=[interfaces[2]],
|
||||
)
|
||||
cable3.clean()
|
||||
cable3.save()
|
||||
|
||||
# The breakout splits IF1's two positions into separate downstream cables,
|
||||
# so the forward path has FP1/FP2, RP1/RP2, and the two unprofiled cables
|
||||
# at the corresponding hops.
|
||||
path1 = self.assertPathExists(
|
||||
(
|
||||
interfaces[0],
|
||||
cable1,
|
||||
[front_ports[0], front_ports[1]],
|
||||
[rear_ports[0], rear_ports[1]],
|
||||
[cable2, cable3],
|
||||
[interfaces[1], interfaces[2]],
|
||||
),
|
||||
is_complete=True,
|
||||
is_active=True,
|
||||
)
|
||||
path2 = self.assertPathExists(
|
||||
(interfaces[1], cable2, rear_ports[0], front_ports[0], cable1, interfaces[0]),
|
||||
is_complete=True,
|
||||
is_active=True,
|
||||
)
|
||||
path3 = self.assertPathExists(
|
||||
(interfaces[2], cable3, rear_ports[1], front_ports[1], cable1, interfaces[0]),
|
||||
is_complete=True,
|
||||
is_active=True,
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 3)
|
||||
for interface in interfaces:
|
||||
interface.refresh_from_db()
|
||||
self.assertPathIsSet(interfaces[0], path1)
|
||||
self.assertPathIsSet(interfaces[1], path2)
|
||||
self.assertPathIsSet(interfaces[2], path3)
|
||||
|
||||
# Test SVG generation from both directions
|
||||
CableTraceSVG(interfaces[0]).render()
|
||||
CableTraceSVG(interfaces[1]).render()
|
||||
CableTraceSVG(interfaces[2]).render()
|
||||
|
||||
def test_304_add_port_mapping_between_connected_ports(self):
|
||||
"""
|
||||
[IF1] --C1-- [FP1] [RP1] --C2-- [IF2]
|
||||
|
|
|
|||
Loading…
Reference in New Issue