mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 20:12:00 -06:00
Handle traces which split at a RearPort
This commit is contained in:
parent
29eebf9fbe
commit
0c5efa243d
@ -3,12 +3,3 @@ class LoopDetected(Exception):
|
|||||||
A loop has been detected while tracing a cable path.
|
A loop has been detected while tracing a cable path.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CableTraceSplit(Exception):
|
|
||||||
"""
|
|
||||||
A cable trace cannot be completed because a RearPort maps to multiple FrontPorts and
|
|
||||||
we don't know which one to follow.
|
|
||||||
"""
|
|
||||||
def __init__(self, termination, *args, **kwargs):
|
|
||||||
self.termination = termination
|
|
||||||
|
@ -172,9 +172,11 @@ class PathEndpoint(models.Model):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
# Construct the complete path
|
# Construct the complete path
|
||||||
path = [self, *[path_node_to_object(obj) for obj in self._path.path], self._path.destination]
|
path = [self, *[path_node_to_object(obj) for obj in self._path.path]]
|
||||||
assert not len(path) % 3,\
|
while (len(path) + 1) % 3:
|
||||||
f"Invalid path length for CablePath #{self.pk}: {len(self._path.path)} elements in path"
|
# Pad to ensure we have complete three-tuples (e.g. for paths that end at a RearPort)
|
||||||
|
path.append(None)
|
||||||
|
path.append(self._path.destination)
|
||||||
|
|
||||||
# Return the path as a list of three-tuples (A termination, cable, B termination)
|
# Return the path as a list of three-tuples (A termination, cable, B termination)
|
||||||
return list(zip(*[iter(path)] * 3))
|
return list(zip(*[iter(path)] * 3))
|
||||||
|
@ -721,6 +721,63 @@ class CablePathTestCase(TestCase):
|
|||||||
self.assertEqual(CablePath.objects.filter(destination_id__isnull=True).count(), 4)
|
self.assertEqual(CablePath.objects.filter(destination_id__isnull=True).count(), 4)
|
||||||
self.assertEqual(CablePath.objects.filter(destination_id__isnull=False).count(), 0)
|
self.assertEqual(CablePath.objects.filter(destination_id__isnull=False).count(), 0)
|
||||||
|
|
||||||
|
def test_206_unidirectional_split_paths(self):
|
||||||
|
"""
|
||||||
|
[IF1] --C1-- [RP1] [FP1:1] --C2-- [IF2]
|
||||||
|
[FP1:2] --C3-- [IF3]
|
||||||
|
"""
|
||||||
|
self.interface1.refresh_from_db()
|
||||||
|
self.interface2.refresh_from_db()
|
||||||
|
self.interface3.refresh_from_db()
|
||||||
|
|
||||||
|
# Create cables 1
|
||||||
|
cable1 = Cable(termination_a=self.interface1, termination_b=self.rear_port1)
|
||||||
|
cable1.save()
|
||||||
|
self.assertPathExists(
|
||||||
|
origin=self.interface1,
|
||||||
|
destination=None,
|
||||||
|
path=(cable1, self.rear_port1),
|
||||||
|
is_active=False
|
||||||
|
)
|
||||||
|
self.assertEqual(CablePath.objects.count(), 1)
|
||||||
|
|
||||||
|
# Create cables 2-3
|
||||||
|
cable2 = Cable(termination_a=self.interface2, termination_b=self.front_port1_1)
|
||||||
|
cable2.save()
|
||||||
|
cable3 = Cable(termination_a=self.interface3, termination_b=self.front_port1_2)
|
||||||
|
cable3.save()
|
||||||
|
self.assertPathExists(
|
||||||
|
origin=self.interface2,
|
||||||
|
destination=self.interface1,
|
||||||
|
path=(cable2, self.front_port1_1, self.rear_port1, cable1),
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
self.assertPathExists(
|
||||||
|
origin=self.interface3,
|
||||||
|
destination=self.interface1,
|
||||||
|
path=(cable3, self.front_port1_2, self.rear_port1, cable1),
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
self.assertEqual(CablePath.objects.count(), 3)
|
||||||
|
|
||||||
|
# Delete cable 1
|
||||||
|
cable1.delete()
|
||||||
|
|
||||||
|
# Check that the partial path was deleted and the two complete paths are now partial
|
||||||
|
self.assertPathExists(
|
||||||
|
origin=self.interface2,
|
||||||
|
destination=None,
|
||||||
|
path=(cable2, self.front_port1_1, self.rear_port1),
|
||||||
|
is_active=False
|
||||||
|
)
|
||||||
|
self.assertPathExists(
|
||||||
|
origin=self.interface3,
|
||||||
|
destination=None,
|
||||||
|
path=(cable3, self.front_port1_2, self.rear_port1),
|
||||||
|
is_active=False
|
||||||
|
)
|
||||||
|
self.assertEqual(CablePath.objects.count(), 2)
|
||||||
|
|
||||||
def test_301_create_path_via_existing_cable(self):
|
def test_301_create_path_via_existing_cable(self):
|
||||||
"""
|
"""
|
||||||
[IF1] --C1-- [FP5] [RP5] --C2-- [RP6] [FP6] --C3-- [IF2]
|
[IF1] --C1-- [FP5] [RP5] --C2-- [RP6] [FP6] --C3-- [IF2]
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from .choices import CableStatusChoices
|
from .choices import CableStatusChoices
|
||||||
from .exceptions import CableTraceSplit
|
|
||||||
|
|
||||||
|
|
||||||
def compile_path_node(ct_id, object_id):
|
def compile_path_node(ct_id, object_id):
|
||||||
@ -69,8 +68,8 @@ def trace_path(node):
|
|||||||
node = FrontPort.objects.get(rear_port=peer_termination, rear_port_position=position)
|
node = FrontPort.objects.get(rear_port=peer_termination, rear_port_position=position)
|
||||||
path.append(object_to_path_node(node))
|
path.append(object_to_path_node(node))
|
||||||
else:
|
else:
|
||||||
# No position indicated: path has split (probably invalid?)
|
# No position indicated: path has split, so we stop at the RearPort
|
||||||
raise CableTraceSplit(peer_termination)
|
break
|
||||||
|
|
||||||
# Anything else marks the end of the path
|
# Anything else marks the end of the path
|
||||||
else:
|
else:
|
||||||
|
@ -19,16 +19,17 @@
|
|||||||
{% elif near_end.circuit %}
|
{% elif near_end.circuit %}
|
||||||
{% include 'dcim/trace/circuit.html' with circuit=near_end.circuit %}
|
{% include 'dcim/trace/circuit.html' with circuit=near_end.circuit %}
|
||||||
{% include 'dcim/trace/termination.html' with termination=near_end %}
|
{% include 'dcim/trace/termination.html' with termination=near_end %}
|
||||||
|
{% else %}
|
||||||
|
<h3 class="text-danger text-center">Split Paths!</h3>
|
||||||
|
{# TODO: Present the user with successive paths to choose from #}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Cable #}
|
{# Cable #}
|
||||||
<div class="row">
|
{% if cable %}
|
||||||
{% if cable %}
|
<div class="row">
|
||||||
{% include 'dcim/trace/cable.html' %}
|
{% include 'dcim/trace/cable.html' %}
|
||||||
{% else %}
|
</div>
|
||||||
<h4>No cable</h4>
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Far end #}
|
{# Far end #}
|
||||||
{% if far_end.device %}
|
{% if far_end.device %}
|
||||||
@ -45,15 +46,18 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if forloop.last and far_end %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="text-center">
|
||||||
|
<h3 class="text-success text-center">Trace completed!</h3>
|
||||||
|
{% if total_length %}
|
||||||
|
<h5>Total length: {{ total_length|floatformat:"-2" }} Meters<h5>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="row">
|
|
||||||
<div class="text-center">
|
|
||||||
<h3 class="text-success text-center">Trace completed!</h3>
|
|
||||||
{% if total_length %}
|
|
||||||
<h5>Total length: {{ total_length|floatformat:"-2" }} Meters<h5>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-7 col-sm-12">
|
<div class="col-md-7 col-sm-12">
|
||||||
|
Loading…
Reference in New Issue
Block a user