Просмотр исходного кода

Adapt tracing view to account for split ends (WIP)

Jeremy Stretch 5 лет назад
Родитель
Сommit
29707cd496

+ 1 - 1
netbox/dcim/api/views.py

@@ -48,7 +48,7 @@ class CableTraceMixin(object):
         # Initialize the path array
         # Initialize the path array
         path = []
         path = []
 
 
-        for near_end, cable, far_end in obj.trace():
+        for near_end, cable, far_end in obj.trace()[0]:
 
 
             # Serialize each object
             # Serialize each object
             serializer_a = get_serializer_for_model(near_end, prefix='Nested')
             serializer_a = get_serializer_for_model(near_end, prefix='Nested')

+ 22 - 14
netbox/dcim/models/device_components.py

@@ -92,7 +92,13 @@ class CableTermination(models.Model):
 
 
     def trace(self):
     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 A, cable, termination B),
                 (termination C, cable, termination D),
                 (termination C, cable, termination D),
@@ -157,12 +163,12 @@ class CableTermination(models.Model):
             if not endpoint.cable:
             if not endpoint.cable:
                 path.append((endpoint, None, None))
                 path.append((endpoint, None, None))
                 logger.debug("No cable connected")
                 logger.debug("No cable connected")
-                return path
+                return path, None
 
 
             # Check for loops
             # Check for loops
             if endpoint.cable in [segment[1] for segment in path]:
             if endpoint.cable in [segment[1] for segment in path]:
                 logger.debug("Loop detected!")
                 logger.debug("Loop detected!")
-                return path
+                return path, None
 
 
             # Record the current segment in the path
             # Record the current segment in the path
             far_end = endpoint.get_cable_peer()
             far_end = endpoint.get_cable_peer()
@@ -172,9 +178,13 @@ class CableTermination(models.Model):
             ))
             ))
 
 
             # Get the peer port of the far end termination
             # 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:
             if endpoint is None:
-                return path
+                return path, None
 
 
     def get_cable_peer(self):
     def get_cable_peer(self):
         if self.cable is None:
         if self.cable is None:
@@ -191,15 +201,13 @@ class CableTermination(models.Model):
         endpoints = []
         endpoints = []
 
 
         # Get the far end of the last path segment
         # 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
         return endpoints
 
 

+ 1 - 1
netbox/dcim/signals.py

@@ -52,7 +52,7 @@ def update_connected_endpoints(instance, **kwargs):
     # Update any endpoints for this Cable.
     # Update any endpoints for this Cable.
     endpoints = instance.termination_a.get_path_endpoints() + instance.termination_b.get_path_endpoints()
     endpoints = instance.termination_a.get_path_endpoints() + instance.termination_b.get_path_endpoints()
     for endpoint in endpoints:
     for endpoint in endpoints:
-        path = endpoint.trace()
+        path, split_ends = endpoint.trace()
         # Determine overall path status (connected or planned)
         # Determine overall path status (connected or planned)
         path_status = True
         path_status = True
         for segment in path:
         for segment in path:

+ 7 - 3
netbox/dcim/views.py

@@ -32,6 +32,7 @@ from virtualization.models import VirtualMachine
 from . import filters, forms, tables
 from . import filters, forms, tables
 from .choices import DeviceFaceChoices
 from .choices import DeviceFaceChoices
 from .constants import NONCONNECTABLE_IFACE_TYPES
 from .constants import NONCONNECTABLE_IFACE_TYPES
+from .exceptions import CableTraceSplit
 from .models import (
 from .models import (
     Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
     Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
     DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
     DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
@@ -2033,12 +2034,15 @@ class CableTraceView(PermissionRequiredMixin, View):
     def get(self, request, model, pk):
     def get(self, request, model, pk):
 
 
         obj = get_object_or_404(model, pk=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', {
         return render(request, 'dcim/cable_trace.html', {
             'obj': obj,
             'obj': obj,
-            'trace': trace,
+            'trace': path,
+            'split_ends': split_ends,
             'total_length': total_length,
             'total_length': total_length,
         })
         })
 
 

+ 15 - 0
netbox/templates/dcim/cable_trace.html

@@ -50,4 +50,19 @@
         </div>
         </div>
         {% if not forloop.last %}<hr />{% endif %}
         {% if not forloop.last %}<hr />{% endif %}
     {% endfor %}
     {% endfor %}
+    <div class="row">
+        <div class="col-md-11 col-md-offset-1">
+            {% if split_ends %}
+                <h3 class="text-danger text-center"><i class="fa fa-warning"></i> Trace Split</h3>
+                <p>Select a termination to continue:</p>
+                <ul>
+                    {% for termination in split_ends %}
+                        <li><a href="{% url 'dcim:frontport_trace' pk=termination.pk %}">{{ termination.parent }} / {{ termination }}</a></li>
+                    {% endfor %}
+                </ul>
+            {% else %}
+                <h3 class="text-success text-center">Trace completed!</h3>
+            {% endif %}
+        </div>
+    </div>
 {% endblock %}
 {% endblock %}