mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Handle traces which split at a RearPort
This commit is contained in:
netbox
dcim
templates/dcim
@ -3,12 +3,3 @@ class LoopDetected(Exception):
|
||||
A loop has been detected while tracing a cable path.
|
||||
"""
|
||||
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 []
|
||||
|
||||
# Construct the complete path
|
||||
path = [self, *[path_node_to_object(obj) for obj in self._path.path], self._path.destination]
|
||||
assert not len(path) % 3,\
|
||||
f"Invalid path length for CablePath #{self.pk}: {len(self._path.path)} elements in path"
|
||||
path = [self, *[path_node_to_object(obj) for obj in self._path.path]]
|
||||
while (len(path) + 1) % 3:
|
||||
# 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 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=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):
|
||||
"""
|
||||
[IF1] --C1-- [FP5] [RP5] --C2-- [RP6] [FP6] --C3-- [IF2]
|
||||
|
@ -1,7 +1,6 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from .choices import CableStatusChoices
|
||||
from .exceptions import CableTraceSplit
|
||||
|
||||
|
||||
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)
|
||||
path.append(object_to_path_node(node))
|
||||
else:
|
||||
# No position indicated: path has split (probably invalid?)
|
||||
raise CableTraceSplit(peer_termination)
|
||||
# No position indicated: path has split, so we stop at the RearPort
|
||||
break
|
||||
|
||||
# Anything else marks the end of the path
|
||||
else:
|
||||
|
@ -19,16 +19,17 @@
|
||||
{% elif near_end.circuit %}
|
||||
{% include 'dcim/trace/circuit.html' with circuit=near_end.circuit %}
|
||||
{% 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 %}
|
||||
|
||||
{# Cable #}
|
||||
<div class="row">
|
||||
{% if cable %}
|
||||
{% if cable %}
|
||||
<div class="row">
|
||||
{% include 'dcim/trace/cable.html' %}
|
||||
{% else %}
|
||||
<h4>No cable</h4>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Far end #}
|
||||
{% if far_end.device %}
|
||||
@ -45,15 +46,18 @@
|
||||
{% 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 %}
|
||||
<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 class="col-md-7 col-sm-12">
|
||||
|
Reference in New Issue
Block a user