parent
b2a1e94508
commit
b1412514f1
|
|
@ -1,5 +1,6 @@
|
|||
import itertools
|
||||
import logging
|
||||
import threading
|
||||
from collections import Counter
|
||||
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
|
|
@ -76,6 +77,11 @@ class Cable(PrimaryModel):
|
|||
"""
|
||||
A physical connection between two endpoints.
|
||||
"""
|
||||
# Per-thread tracking of Cable PKs currently in delete(); referenced by
|
||||
# dcim.signals.nullify_connected_endpoints to skip per-CableTermination
|
||||
# cable path retracing during cascade (retrace_cable_paths handles it once).
|
||||
_deletion_tracking = threading.local()
|
||||
|
||||
type = models.CharField(
|
||||
verbose_name=_('type'),
|
||||
max_length=50,
|
||||
|
|
@ -343,6 +349,26 @@ class Cable(PrimaryModel):
|
|||
except UnsupportedCablePath as e:
|
||||
raise AbortRequest(e)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
# Track this Cable as being deleted so the post_delete signal handler
|
||||
# for cascaded CableTerminations can skip redundant path retracing;
|
||||
# retrace_cable_paths() will retrace each affected path once after the
|
||||
# Cable itself is deleted. Cache the PK locally because super().delete()
|
||||
# clears self.pk before the finally block runs. The tracking set lives
|
||||
# on a threading.local() to isolate concurrent deletions across threads.
|
||||
if not hasattr(Cable._deletion_tracking, 'pks'):
|
||||
Cable._deletion_tracking.pks = set()
|
||||
pk = self.pk
|
||||
Cable._deletion_tracking.pks.add(pk)
|
||||
try:
|
||||
return super().delete(*args, **kwargs)
|
||||
finally:
|
||||
Cable._deletion_tracking.pks.discard(pk)
|
||||
|
||||
@classmethod
|
||||
def _is_being_deleted(cls, pk):
|
||||
return pk in getattr(cls._deletion_tracking, 'pks', ())
|
||||
|
||||
def clone(self):
|
||||
"""
|
||||
Return attributes suitable for cloning this cable.
|
||||
|
|
|
|||
|
|
@ -185,6 +185,12 @@ def nullify_connected_endpoints(instance, **kwargs):
|
|||
model = instance.termination_type.model_class()
|
||||
model.objects.filter(pk=instance.termination_id).update(cable=None, cable_end='')
|
||||
|
||||
# If the parent Cable is being deleted in this same operation, skip the
|
||||
# per-termination retrace; retrace_cable_paths() will retrace each affected
|
||||
# path once after the Cable is deleted.
|
||||
if Cable._is_being_deleted(instance.cable_id):
|
||||
return
|
||||
|
||||
for cablepath in CablePath.objects.filter(_nodes__contains=instance.cable):
|
||||
# Remove the deleted CableTermination if it's one of the path's originating nodes
|
||||
if instance.termination in cablepath.origins:
|
||||
|
|
|
|||
|
|
@ -55,6 +55,18 @@ class LegacyCablePathTestCase(CablePathTestCase):
|
|||
# Check that all CablePaths have been deleted
|
||||
self.assertEqual(CablePath.objects.count(), 0)
|
||||
|
||||
# Check that connected interfaces are fully cleaned up
|
||||
interface1.refresh_from_db()
|
||||
interface2.refresh_from_db()
|
||||
|
||||
self.assertIsNone(interface1.cable_id)
|
||||
self.assertEqual(interface1.cable_end, '')
|
||||
self.assertPathIsNotSet(interface1)
|
||||
|
||||
self.assertIsNone(interface2.cable_id)
|
||||
self.assertEqual(interface2.cable_end, '')
|
||||
self.assertPathIsNotSet(interface2)
|
||||
|
||||
def test_102_consoleport_to_consoleserverport(self):
|
||||
"""
|
||||
[CP1] --C1-- [CSP1]
|
||||
|
|
|
|||
Loading…
Reference in New Issue