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

This commit is contained in:
Martin Hauser 2026-07-22 17:43:26 +02:00 committed by GitHub
parent e79e33e9dd
commit 98c94f5fc7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 61 additions and 4 deletions

View File

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

View File

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