1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Drop support for split paths

This commit is contained in:
Jeremy Stretch
2020-10-01 14:16:43 -04:00
parent cd398b15d8
commit 610420c020
13 changed files with 66 additions and 97 deletions

View File

@ -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
#

View File

@ -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}'))

View File

@ -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

View File

@ -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)

View File

@ -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])
self.assertPathExists(
origin=self.interfaces[0],
destination=None,
path=(cable1, self.front_ports[16], self.rear_ports[4])
)
self.assertEqual(CablePath.objects.count(), 1)
# 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=None,
path=(cable1, self.front_ports[0], self.rear_ports[0])
destination=self.interfaces[1],
path=(cable1, self.front_ports[16], self.rear_ports[4], cable2)
)
self.assertPathExists(
origin=self.interfaces[1],
destination=None,
path=(cable2, self.front_ports[1], self.rear_ports[0])
destination=self.interfaces[0],
path=(cable2, self.rear_ports[4], self.front_ports[16], cable1)
)
self.assertEqual(CablePath.objects.count(), 2)
# Create cable 3
cable3 = Cable(termination_a=self.rear_ports[0], termination_b=self.interfaces[2])
cable3.save()
self.assertPathExists(
origin=self.interfaces[0],
destination=self.interfaces[2],
path=(cable1, self.front_ports[0], self.rear_ports[0], cable3)
)
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)
)
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
# 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])
path=(cable1, self.front_ports[16], self.rear_ports[4])
)
self.assertPathExists(
origin=self.interfaces[1],
destination=None,
path=(cable2, self.front_ports[1], self.rear_ports[0])
)
# 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):
"""

View File

@ -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

View File

@ -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',

View File

@ -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">

View File

@ -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">

View File

@ -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 %}

View File

@ -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 #}

View File

@ -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 %}

View File

@ -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">