فهرست منبع

Fixes #22720: Fix Virtual Chassis deletion with cross-chassis LAGs (#22740)

Martin Hauser 6 روز پیش
والد
کامیت
98c94f5fc7
2فایلهای تغییر یافته به همراه61 افزوده شده و 4 حذف شده
  1. 7 4
      netbox/dcim/models/devices.py
  2. 54 0
      netbox/dcim/tests/test_models.py

+ 7 - 4
netbox/dcim/models/devices.py

@@ -1229,10 +1229,13 @@ class VirtualChassis(PrimaryModel):
             lag__device=F('device')
         )
         if interfaces:
-            raise ProtectedError(_(
-                "Unable to delete virtual chassis {self}. There are member interfaces which form a cross-chassis LAG "
-                "interfaces."
-            ).format(self=self, interfaces=InterfaceSpeedChoices))
+            raise ProtectedError(
+                _(
+                    "Unable to delete virtual chassis {virtual_chassis}. One or more member interfaces form a "
+                    "cross-chassis LAG."
+                ).format(virtual_chassis=self),
+                set(interfaces),
+            )
 
         # Clear vc_position and vc_priority on member devices BEFORE calling super().delete()
         # This must be done here because on_delete=SET_NULL executes before pre_delete signal

+ 54 - 0
netbox/dcim/tests/test_models.py

@@ -1,6 +1,7 @@
 from decimal import Decimal
 
 from django.core.exceptions import ValidationError
+from django.db.models import ProtectedError
 from django.db.models.signals import post_save
 from django.test import TestCase, tag
 
@@ -2429,6 +2430,59 @@ class VirtualChassisTestCase(TestCase):
         self.assertIsNone(device2.vc_position)
         self.assertIsNone(device2.vc_priority)
 
+    @tag('regression')  # Ref: #22720
+    def test_virtualchassis_deletion_blocked_by_cross_chassis_lag(self):
+        """
+        Deleting a VirtualChassis whose members form a cross-chassis LAG must
+        raise ProtectedError exposing the blocking interfaces, leaving the VC
+        and its member assignments unchanged.
+        """
+        device1 = Device.objects.get(name='TestDevice1')
+        device2 = Device.objects.get(name='TestDevice2')
+
+        vc = VirtualChassis.objects.create(name='Test VC', master=device1)
+
+        device1.virtual_chassis = vc
+        device1.vc_position = 1
+        device1.vc_priority = 10
+        device1.save()
+
+        device2.virtual_chassis = vc
+        device2.vc_position = 2
+        device2.vc_priority = 20
+        device2.save()
+
+        lag = Interface.objects.create(device=device1, name='lag0', type=InterfaceTypeChoices.TYPE_LAG)
+        member_interface = Interface(
+            device=device2,
+            name='eth0',
+            type=InterfaceTypeChoices.TYPE_1GE_FIXED,
+            lag=lag,
+        )
+        # A cross-chassis LAG member is valid while both devices share the VC
+        member_interface.full_clean()
+        member_interface.save()
+
+        with self.assertRaises(ProtectedError) as cm:
+            vc.delete()
+
+        self.assertEqual(
+            cm.exception.args[0],
+            'Unable to delete virtual chassis Test VC. One or more member interfaces form a cross-chassis LAG.'
+        )
+        self.assertEqual(set(cm.exception.protected_objects), {member_interface})
+
+        # The failed deletion must not clear the VC or its member assignments
+        self.assertTrue(VirtualChassis.objects.filter(pk=vc.pk).exists())
+        device1.refresh_from_db()
+        device2.refresh_from_db()
+        self.assertEqual(device1.virtual_chassis, vc)
+        self.assertEqual(device1.vc_position, 1)
+        self.assertEqual(device1.vc_priority, 10)
+        self.assertEqual(device2.virtual_chassis, vc)
+        self.assertEqual(device2.vc_position, 2)
+        self.assertEqual(device2.vc_priority, 20)
+
     def test_virtualchassis_duplicate_vc_position(self):
         """
         Test that two devices cannot be assigned to the same vc_position