diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 2e08283ff..9c8fe12de 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -48,7 +48,7 @@ class CableTraceMixin(object): # Initialize the path array path = [] - for near_end, cable, far_end in obj.trace(): + for near_end, cable, far_end in obj.trace()[0]: # Serialize each object serializer_a = get_serializer_for_model(near_end, prefix='Nested') diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 58af8bc91..f3cf0e3c8 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -92,7 +92,13 @@ class CableTermination(models.Model): def trace(self): """ - Return a list representing a complete cable path, with each individual segment represented as a three-tuple: + Return two items: the traceable portion of a cable path, and the termination points where it splits (if any). + This occurs when the trace is initiated from a midpoint along a path which traverses a RearPort. In cases where + the originating endpoint is unknown, it is not possible to know which corresponding FrontPort to follow. + + The path is a list representing a complete cable path, with each individual segment represented as a + three-tuple: + [ (termination A, cable, termination B), (termination C, cable, termination D), @@ -157,12 +163,12 @@ class CableTermination(models.Model): if not endpoint.cable: path.append((endpoint, None, None)) logger.debug("No cable connected") - return path + return path, None # Check for loops if endpoint.cable in [segment[1] for segment in path]: logger.debug("Loop detected!") - return path + return path, None # Record the current segment in the path far_end = endpoint.get_cable_peer() @@ -172,9 +178,13 @@ class CableTermination(models.Model): )) # Get the peer port of the far end termination - endpoint = get_peer_port(far_end) + try: + endpoint = get_peer_port(far_end) + except CableTraceSplit as e: + return path, e.termination.frontports.all() + if endpoint is None: - return path + return path, None def get_cable_peer(self): if self.cable is None: @@ -191,15 +201,13 @@ class CableTermination(models.Model): endpoints = [] # Get the far end of the last path segment - try: - endpoint = self.trace()[-1][2] - if endpoint is not None: - endpoints.append(endpoint) - - # We've hit a RearPort mapped to multiple FrontPorts. Recurse to trace each of them individually. - except CableTraceSplit as e: - for frontport in e.termination.frontports.all(): - endpoints.extend(frontport.get_path_endpoints()) + path, split_ends = self.trace() + endpoint = path[-1][2] + if split_ends is not None: + for termination in split_ends: + endpoints.extend(termination.get_path_endpoints()) + elif endpoint is not None: + endpoints.append(endpoint) return endpoints diff --git a/netbox/dcim/signals.py b/netbox/dcim/signals.py index 2b922ebb5..c94ecf61e 100644 --- a/netbox/dcim/signals.py +++ b/netbox/dcim/signals.py @@ -52,7 +52,7 @@ def update_connected_endpoints(instance, **kwargs): # Update any endpoints for this Cable. endpoints = instance.termination_a.get_path_endpoints() + instance.termination_b.get_path_endpoints() for endpoint in endpoints: - path = endpoint.trace() + path, split_ends = endpoint.trace() # Determine overall path status (connected or planned) path_status = True for segment in path: diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 725be6990..c10a821dc 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -32,6 +32,7 @@ from virtualization.models import VirtualMachine from . import filters, forms, tables from .choices import DeviceFaceChoices from .constants import NONCONNECTABLE_IFACE_TYPES +from .exceptions import CableTraceSplit from .models import ( Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, @@ -2033,12 +2034,15 @@ class CableTraceView(PermissionRequiredMixin, View): def get(self, request, model, pk): obj = get_object_or_404(model, pk=pk) - trace = obj.trace() - total_length = sum([entry[1]._abs_length for entry in trace if entry[1] and entry[1]._abs_length]) + path, split_ends = obj.trace() + total_length = sum( + [entry[1]._abs_length for entry in path if entry[1] and entry[1]._abs_length] + ) return render(request, 'dcim/cable_trace.html', { 'obj': obj, - 'trace': trace, + 'trace': path, + 'split_ends': split_ends, 'total_length': total_length, }) diff --git a/netbox/templates/dcim/cable_trace.html b/netbox/templates/dcim/cable_trace.html index 87f286b1f..fc637f9ef 100644 --- a/netbox/templates/dcim/cable_trace.html +++ b/netbox/templates/dcim/cable_trace.html @@ -50,4 +50,19 @@ {% if not forloop.last %}
Select a termination to continue:
+