فهرست منبع

Drop support for split paths

Jeremy Stretch 5 سال پیش
والد
کامیت
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)
     connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, read_only=True)
 
 
     def get_connected_endpoint_type(self, obj):
     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
         return None
 
 
     @swagger_serializer_method(serializer_or_field=serializers.DictField)
     @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.
         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 django.db.models import Q
 
 
 from dcim.models import CablePath
 from dcim.models import CablePath
-from dcim.signals import create_cablepaths
+from dcim.signals import create_cablepath
 
 
 ENDPOINT_MODELS = (
 ENDPOINT_MODELS = (
     'circuits.CircuitTermination',
     'circuits.CircuitTermination',
@@ -60,7 +60,7 @@ class Command(BaseCommand):
             print(f'Retracing {origins.count()} cabled {model._meta.verbose_name_plural}...')
             print(f'Retracing {origins.count()} cabled {model._meta.verbose_name_plural}...')
             i = 0
             i = 0
             for i, obj in enumerate(origins, start=1):
             for i, obj in enumerate(origins, start=1):
-                create_cablepaths(obj)
+                create_cablepath(obj)
                 if not i % 1000:
                 if not i % 1000:
                     self.stdout.write(f'  {i}')
                     self.stdout.write(f'  {i}')
             self.stdout.write(self.style.SUCCESS(f'  Retraced {i} {model._meta.verbose_name_plural}'))
             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
 import logging
 
 
 from django.contrib.contenttypes.fields import GenericRelation
 from django.contrib.contenttypes.fields import GenericRelation
-from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ObjectDoesNotExist, ValidationError
 from django.core.exceptions import ObjectDoesNotExist, ValidationError
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.db import models
 from django.db import models
@@ -256,7 +255,7 @@ class PathEndpoint(models.Model):
     """
     """
     Any object which may serve as either endpoint of a CablePath.
     Any object which may serve as either endpoint of a CablePath.
     """
     """
-    paths = GenericRelation(
+    _paths = GenericRelation(
         to='dcim.CablePath',
         to='dcim.CablePath',
         content_type_field='origin_type',
         content_type_field='origin_type',
         object_id_field='origin_id',
         object_id_field='origin_id',
@@ -266,6 +265,15 @@ class PathEndpoint(models.Model):
     class Meta:
     class Meta:
         abstract = True
         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
 # Console ports

+ 7 - 6
netbox/dcim/signals.py

@@ -6,14 +6,15 @@ from django.db import transaction
 from django.dispatch import receiver
 from django.dispatch import receiver
 
 
 from .models import Cable, CablePath, Device, PathEndpoint, VirtualChassis
 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.
     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 = CablePath(origin=node, path=path, destination=destination)
         cp.save()
         cp.save()
 
 
@@ -28,7 +29,7 @@ def rebuild_paths(obj):
     with transaction.atomic():
     with transaction.atomic():
         for cp in cable_paths:
         for cp in cable_paths:
             cp.delete()
             cp.delete()
-            create_cablepaths(cp.origin)
+            create_cablepath(cp.origin)
 
 
 
 
 @receiver(post_save, sender=VirtualChassis)
 @receiver(post_save, sender=VirtualChassis)
@@ -76,7 +77,7 @@ def update_connected_endpoints(instance, created, **kwargs):
     if created:
     if created:
         for termination in (instance.termination_a, instance.termination_b):
         for termination in (instance.termination_a, instance.termination_b):
             if isinstance(termination, PathEndpoint):
             if isinstance(termination, PathEndpoint):
-                create_cablepaths(termination)
+                create_cablepath(termination)
             else:
             else:
                 rebuild_paths(termination)
                 rebuild_paths(termination)
     else:
     else:
@@ -116,4 +117,4 @@ def nullify_connected_endpoints(instance, **kwargs):
             origin_type=ContentType.objects.get_for_model(origin),
             origin_type=ContentType.objects.get_for_model(origin),
             origin_id=origin.pk
             origin_id=origin.pk
         ).delete()
         ).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
         # Check that all CablePaths have been deleted
         self.assertEqual(CablePath.objects.count(), 0)
         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()
         cable1.save()
-        cable2 = Cable(termination_a=self.interfaces[1], termination_b=self.front_ports[1])
-        cable2.save()
         self.assertPathExists(
         self.assertPathExists(
             origin=self.interfaces[0],
             origin=self.interfaces[0],
             destination=None,
             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(
         self.assertPathExists(
             origin=self.interfaces[0],
             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(
         self.assertPathExists(
             origin=self.interfaces[1],
             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],
             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(
         self.assertPathExists(
             origin=self.interfaces[0],
             origin=self.interfaces[0],
             destination=None,
             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):
     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 django.contrib.contenttypes.models import ContentType
 
 
+from .exceptions import CableTraceSplit
 from .models import FrontPort, RearPort
 from .models import FrontPort, RearPort
 
 
 
 
@@ -17,13 +18,13 @@ def path_node_to_object(repr):
     return model_class.objects.get(pk=int(object_id))
     return model_class.objects.get(pk=int(object_id))
 
 
 
 
-def trace_paths(node):
+def trace_path(node):
     destination = None
     destination = None
     path = []
     path = []
     position_stack = []
     position_stack = []
 
 
     if node.cable is None:
     if node.cable is None:
-        return []
+        return [], None
 
 
     while node.cable is not 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)
                 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, 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
         # Anything else marks the end of the path
         else:
         else:
             destination = peer_termination
             destination = peer_termination
             break
             break
 
 
-    return [(path, destination)]
+    return path, destination

+ 5 - 5
netbox/dcim/views.py

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

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

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

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

@@ -38,7 +38,7 @@
     </td>
     </td>
 
 
     {# Connection #}
     {# Connection #}
-    {% include 'dcim/inc/endpoint_connection.html' with paths=csp.paths.all %}
+    {% include 'dcim/inc/endpoint_connection.html' with path=csp.path %}
 
 
     {# Actions #}
     {# Actions #}
     <td class="text-right noprint">
     <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.parent.get_absolute_url }}">{{ endpoint.parent }}</a></td>
         <td><a href="{{ endpoint.get_absolute_url }}">{{ endpoint }}</a></td>
         <td><a href="{{ endpoint.get_absolute_url }}">{{ endpoint }}</a></td>
     {% endwith %}
     {% endwith %}

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

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

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

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

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

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