Explorar o código

Drop support for split paths

Jeremy Stretch %!s(int64=5) %!d(string=hai) anos
pai
achega
610420c020

+ 8 - 13
netbox/dcim/api/serializers.py

@@ -33,11 +33,9 @@ class ConnectedEndpointSerializer(ValidatedModelSerializer):
     connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, read_only=True)
 
     def get_connected_endpoint_type(self, obj):
-        if hasattr(obj, 'connected_endpoint') and obj.connected_endpoint is not None:
-            return '{}.{}'.format(
-                obj.connected_endpoint._meta.app_label,
-                obj.connected_endpoint._meta.model_name
-            )
+        if obj.path is not None:
+            destination = obj.path.destination
+            return f'{destination._meta.app_label}.{destination._meta.model_name}'
         return None
 
     @swagger_serializer_method(serializer_or_field=serializers.DictField)
@@ -45,14 +43,11 @@ class ConnectedEndpointSerializer(ValidatedModelSerializer):
         """
         Return the appropriate serializer for the type of connected object.
         """
-        if getattr(obj, 'connected_endpoint', None) is None:
-            return None
-
-        serializer = get_serializer_for_model(obj.connected_endpoint, prefix='Nested')
-        context = {'request': self.context['request']}
-        data = serializer(obj.connected_endpoint, context=context).data
-
-        return data
+        if obj.path is not None:
+            serializer = get_serializer_for_model(obj.path.destination, prefix='Nested')
+            context = {'request': self.context['request']}
+            return serializer(obj.path.destination, context=context).data
+        return None
 
 
 #

+ 2 - 2
netbox/dcim/management/commands/retrace_paths.py

@@ -5,7 +5,7 @@ from django.db import connection
 from django.db.models import Q
 
 from dcim.models import CablePath
-from dcim.signals import create_cablepaths
+from dcim.signals import create_cablepath
 
 ENDPOINT_MODELS = (
     'circuits.CircuitTermination',
@@ -60,7 +60,7 @@ class Command(BaseCommand):
             print(f'Retracing {origins.count()} cabled {model._meta.verbose_name_plural}...')
             i = 0
             for i, obj in enumerate(origins, start=1):
-                create_cablepaths(obj)
+                create_cablepath(obj)
                 if not i % 1000:
                     self.stdout.write(f'  {i}')
             self.stdout.write(self.style.SUCCESS(f'  Retraced {i} {model._meta.verbose_name_plural}'))

+ 10 - 2
netbox/dcim/models/device_components.py

@@ -1,7 +1,6 @@
 import logging
 
 from django.contrib.contenttypes.fields import GenericRelation
-from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ObjectDoesNotExist, ValidationError
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.db import models
@@ -256,7 +255,7 @@ class PathEndpoint(models.Model):
     """
     Any object which may serve as either endpoint of a CablePath.
     """
-    paths = GenericRelation(
+    _paths = GenericRelation(
         to='dcim.CablePath',
         content_type_field='origin_type',
         object_id_field='origin_id',
@@ -266,6 +265,15 @@ class PathEndpoint(models.Model):
     class Meta:
         abstract = True
 
+    @property
+    def path(self):
+        """
+        Return the _complete_ CablePath associated with this origin point, if any.
+        """
+        if not hasattr(self, '_path'):
+            self._path = self._paths.filter(destination_id__isnull=False).first()
+        return self._path
+
 
 #
 # Console ports

+ 7 - 6
netbox/dcim/signals.py

@@ -6,14 +6,15 @@ from django.db import transaction
 from django.dispatch import receiver
 
 from .models import Cable, CablePath, Device, PathEndpoint, VirtualChassis
-from .utils import object_to_path_node, trace_paths
+from .utils import object_to_path_node, trace_path
 
 
-def create_cablepaths(node):
+def create_cablepath(node):
     """
     Create CablePaths for all paths originating from the specified node.
     """
-    for path, destination in trace_paths(node):
+    path, destination = trace_path(node)
+    if path:
         cp = CablePath(origin=node, path=path, destination=destination)
         cp.save()
 
@@ -28,7 +29,7 @@ def rebuild_paths(obj):
     with transaction.atomic():
         for cp in cable_paths:
             cp.delete()
-            create_cablepaths(cp.origin)
+            create_cablepath(cp.origin)
 
 
 @receiver(post_save, sender=VirtualChassis)
@@ -76,7 +77,7 @@ def update_connected_endpoints(instance, created, **kwargs):
     if created:
         for termination in (instance.termination_a, instance.termination_b):
             if isinstance(termination, PathEndpoint):
-                create_cablepaths(termination)
+                create_cablepath(termination)
             else:
                 rebuild_paths(termination)
     else:
@@ -116,4 +117,4 @@ def nullify_connected_endpoints(instance, **kwargs):
             origin_type=ContentType.objects.get_for_model(origin),
             origin_id=origin.pk
         ).delete()
-        create_cablepaths(origin)
+        create_cablepath(origin)

+ 17 - 43
netbox/dcim/tests/test_cablepaths.py

@@ -123,69 +123,43 @@ class CablePathTestCase(TestCase):
         # Check that all CablePaths have been deleted
         self.assertEqual(CablePath.objects.count(), 0)
 
-    def test_02_interfaces_to_interface_via_pass_through(self):
+    def test_02_interface_to_interface_via_pass_through(self):
         """
-        [IF1] --C1-- [FP1:1] [RP1] --C3-- [IF3]
-        [IF2] --C2-- [FP1:2]
+        [IF1] --C1-- [FP5] [RP5] --C2-- [IF2]
         """
-        # Create cables 1 and 2
-        cable1 = Cable(termination_a=self.interfaces[0], termination_b=self.front_ports[0])
+        # Create cable 1
+        cable1 = Cable(termination_a=self.interfaces[0], termination_b=self.front_ports[16])
         cable1.save()
-        cable2 = Cable(termination_a=self.interfaces[1], termination_b=self.front_ports[1])
-        cable2.save()
         self.assertPathExists(
             origin=self.interfaces[0],
             destination=None,
-            path=(cable1, self.front_ports[0], self.rear_ports[0])
-        )
-        self.assertPathExists(
-            origin=self.interfaces[1],
-            destination=None,
-            path=(cable2, self.front_ports[1], self.rear_ports[0])
+            path=(cable1, self.front_ports[16], self.rear_ports[4])
         )
-        self.assertEqual(CablePath.objects.count(), 2)
+        self.assertEqual(CablePath.objects.count(), 1)
 
-        # Create cable 3
-        cable3 = Cable(termination_a=self.rear_ports[0], termination_b=self.interfaces[2])
-        cable3.save()
+        # Create cable 2
+        cable2 = Cable(termination_a=self.rear_ports[4], termination_b=self.interfaces[1])
+        cable2.save()
         self.assertPathExists(
             origin=self.interfaces[0],
-            destination=self.interfaces[2],
-            path=(cable1, self.front_ports[0], self.rear_ports[0], cable3)
+            destination=self.interfaces[1],
+            path=(cable1, self.front_ports[16], self.rear_ports[4], cable2)
         )
         self.assertPathExists(
             origin=self.interfaces[1],
-            destination=self.interfaces[2],
-            path=(cable2, self.front_ports[1], self.rear_ports[0], cable3)
-        )
-        self.assertPathExists(
-            origin=self.interfaces[2],
             destination=self.interfaces[0],
-            path=(cable3, self.rear_ports[0], self.front_ports[0], cable1)
+            path=(cable2, self.rear_ports[4], self.front_ports[16], cable1)
         )
-        self.assertPathExists(
-            origin=self.interfaces[2],
-            destination=self.interfaces[1],
-            path=(cable3, self.rear_ports[0], self.front_ports[1], cable2)
-        )
-        self.assertEqual(CablePath.objects.count(), 6)  # Four complete + two partial paths
+        self.assertEqual(CablePath.objects.count(), 2)
 
-        # Delete cable 3
-        cable3.delete()
+        # Delete cable 2
+        cable2.delete()
         self.assertPathExists(
             origin=self.interfaces[0],
             destination=None,
-            path=(cable1, self.front_ports[0], self.rear_ports[0])
-        )
-        self.assertPathExists(
-            origin=self.interfaces[1],
-            destination=None,
-            path=(cable2, self.front_ports[1], self.rear_ports[0])
+            path=(cable1, self.front_ports[16], self.rear_ports[4])
         )
-
-        # Check for two partial paths from IF1 and IF2
-        self.assertEqual(CablePath.objects.filter(destination_id__isnull=True).count(), 2)
-        self.assertEqual(CablePath.objects.filter(destination_id__isnull=False).count(), 0)
+        self.assertEqual(CablePath.objects.count(), 1)
 
     def test_03_interfaces_to_interfaces_via_pass_through(self):
         """

+ 6 - 13
netbox/dcim/utils.py

@@ -1,5 +1,6 @@
 from django.contrib.contenttypes.models import ContentType
 
+from .exceptions import CableTraceSplit
 from .models import FrontPort, RearPort
 
 
@@ -17,13 +18,13 @@ def path_node_to_object(repr):
     return model_class.objects.get(pk=int(object_id))
 
 
-def trace_paths(node):
+def trace_path(node):
     destination = None
     path = []
     position_stack = []
 
     if node.cable is None:
-        return []
+        return [], None
 
     while node.cable is not None:
 
@@ -50,20 +51,12 @@ def trace_paths(node):
                 node = FrontPort.objects.get(rear_port=peer_termination, rear_port_position=position)
                 path.append(object_to_path_node(node))
             else:
-                # No position indicated, so we have to trace _all_ peer FrontPorts
-                paths = []
-                for frontport in FrontPort.objects.filter(rear_port=peer_termination):
-                    branches = trace_paths(frontport)
-                    if branches:
-                        for branch, destination in branches:
-                            paths.append(([*path, object_to_path_node(frontport), *branch], destination))
-                    else:
-                        paths.append(([*path, object_to_path_node(frontport)], None))
-                return paths
+                # No position indicated: path has split (probably invalid?)
+                raise CableTraceSplit(peer_termination)
 
         # Anything else marks the end of the path
         else:
             destination = peer_termination
             break
 
-    return [(path, destination)]
+    return path, destination

+ 5 - 5
netbox/dcim/views.py

@@ -1018,7 +1018,7 @@ class DeviceView(ObjectView):
 
         # Console ports
         consoleports = ConsolePort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
-            Prefetch('paths', queryset=CablePath.objects.filter(destination_id__isnull=False)),
+            Prefetch('_paths', queryset=CablePath.objects.filter(destination_id__isnull=False)),
             'cable',
         )
 
@@ -1026,25 +1026,25 @@ class DeviceView(ObjectView):
         consoleserverports = ConsoleServerPort.objects.restrict(request.user, 'view').filter(
             device=device
         ).prefetch_related(
-            Prefetch('paths', queryset=CablePath.objects.filter(destination_id__isnull=False)),
+            Prefetch('_paths', queryset=CablePath.objects.filter(destination_id__isnull=False)),
             'cable',
         )
 
         # Power ports
         powerports = PowerPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
-            Prefetch('paths', queryset=CablePath.objects.filter(destination_id__isnull=False)),
+            Prefetch('_paths', queryset=CablePath.objects.filter(destination_id__isnull=False)),
             'cable',
         )
 
         # Power outlets
         poweroutlets = PowerOutlet.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
-            Prefetch('paths', queryset=CablePath.objects.filter(destination_id__isnull=False)),
+            Prefetch('_paths', queryset=CablePath.objects.filter(destination_id__isnull=False)),
             'cable', 'power_port',
         )
 
         # Interfaces
         interfaces = device.vc_interfaces.restrict(request.user, 'view').prefetch_related(
-            Prefetch('paths', queryset=CablePath.objects.filter(destination_id__isnull=False)),
+            Prefetch('_paths', queryset=CablePath.objects.filter(destination_id__isnull=False)),
             Prefetch('ip_addresses', queryset=IPAddress.objects.restrict(request.user)),
             Prefetch('member_interfaces', queryset=Interface.objects.restrict(request.user)),
             'lag', 'cable', 'tags',

+ 1 - 1
netbox/templates/dcim/inc/consoleport.html

@@ -36,7 +36,7 @@
     </td>
 
     {# Connection #}
-    {% include 'dcim/inc/endpoint_connection.html' with paths=cp.paths.all %}
+    {% include 'dcim/inc/endpoint_connection.html' with path=cp.path %}
 
     {# Actions #}
     <td class="text-right noprint">

+ 1 - 1
netbox/templates/dcim/inc/consoleserverport.html

@@ -38,7 +38,7 @@
     </td>
 
     {# Connection #}
-    {% include 'dcim/inc/endpoint_connection.html' with paths=csp.paths.all %}
+    {% include 'dcim/inc/endpoint_connection.html' with path=csp.path %}
 
     {# Actions #}
     <td class="text-right noprint">

+ 2 - 4
netbox/templates/dcim/inc/endpoint_connection.html

@@ -1,7 +1,5 @@
-{% if paths|length > 1 %}
-    <td colspan="2">Multiple connections</td>
-{% elif paths %}
-    {% with endpoint=paths.0.destination %}
+{% if path %}
+    {% with endpoint=path.destination %}
         <td><a href="{{ endpoint.parent.get_absolute_url }}">{{ endpoint.parent }}</a></td>
         <td><a href="{{ endpoint.get_absolute_url }}">{{ endpoint }}</a></td>
     {% endwith %}

+ 1 - 1
netbox/templates/dcim/inc/interface.html

@@ -76,7 +76,7 @@
     {% elif iface.is_wireless %}
         <td colspan="2" class="text-muted">Wireless interface</td>
     {% else %}
-        {% include 'dcim/inc/endpoint_connection.html' with paths=iface.paths.all %}
+        {% include 'dcim/inc/endpoint_connection.html' with path=iface.path %}
     {% endif %}
 
     {# Buttons #}

+ 1 - 1
netbox/templates/dcim/inc/poweroutlet.html

@@ -49,7 +49,7 @@
     </td>
 
     {# Connection #}
-    {% with paths=po.paths.all %}
+    {% with path=po.path %}
         {% include 'dcim/inc/endpoint_connection.html' %}
         <td>
             {% if paths|length == 1 %}

+ 1 - 1
netbox/templates/dcim/inc/powerport.html

@@ -45,7 +45,7 @@
     </td>
 
     {# Connection #}
-    {% include 'dcim/inc/endpoint_connection.html' with paths=pp.paths.all %}
+    {% include 'dcim/inc/endpoint_connection.html' with path=pp.path %}
 
     {# Actions #}
     <td class="text-right noprint">