Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion netbox/dcim/models/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -1154,7 +1154,6 @@ def clean(self):
})

def delete(self, *args, **kwargs):

# Check for LAG interfaces split across member chassis
interfaces = Interface.objects.filter(
device__in=self.members.all(),
Expand All @@ -1168,6 +1167,13 @@ def delete(self, *args, **kwargs):
"interfaces."
).format(self=self, interfaces=InterfaceSpeedChoices))

# 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
for device in self.members.all():
device.vc_position = None
device.vc_priority = None
device.save()

return super().delete(*args, **kwargs)


Expand Down
14 changes: 1 addition & 13 deletions netbox/dcim/signals.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging

from django.db.models.signals import post_save, post_delete, pre_delete
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver

from dcim.choices import CableEndChoices, LinkStatusChoices
Expand Down Expand Up @@ -85,18 +85,6 @@ def assign_virtualchassis_master(instance, created, **kwargs):
master.save()


@receiver(pre_delete, sender=VirtualChassis)
def clear_virtualchassis_members(instance, **kwargs):
"""
When a VirtualChassis is deleted, nullify the vc_position and vc_priority fields of its prior members.
"""
devices = Device.objects.filter(virtual_chassis=instance.pk)
for device in devices:
device.vc_position = None
device.vc_priority = None
device.save()


#
# Cables
#
Expand Down
89 changes: 89 additions & 0 deletions netbox/dcim/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1031,3 +1031,92 @@ def test_vdc_duplicate_identifier(self):
vdc2 = VirtualDeviceContext(device=device, name="VDC 2", identifier=1, status='active')
with self.assertRaises(ValidationError):
vdc2.full_clean()


class VirtualChassisTestCase(TestCase):

@classmethod
def setUpTestData(cls):
site = Site.objects.create(name='Test Site 1', slug='test-site-1')
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
devicetype = DeviceType.objects.create(
manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
)
role = DeviceRole.objects.create(
name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
)
Device.objects.create(
device_type=devicetype, role=role, name='TestDevice1', site=site
)
Device.objects.create(
device_type=devicetype, role=role, name='TestDevice2', site=site
)

def test_virtualchassis_deletion_clears_vc_position(self):
"""
Test that when a VirtualChassis is deleted, member devices have their
vc_position and vc_priority fields set to None.
"""
devices = Device.objects.all()
device1 = devices[0]
device2 = devices[1]

# Create a VirtualChassis with two member devices
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()

# Verify devices are members of the VC with positions set
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)

# Delete the VirtualChassis
vc.delete()

# Verify devices have vc_position and vc_priority set to None
device1.refresh_from_db()
device2.refresh_from_db()
self.assertIsNone(device1.virtual_chassis)
self.assertIsNone(device1.vc_position)
self.assertIsNone(device1.vc_priority)
self.assertIsNone(device2.virtual_chassis)
self.assertIsNone(device2.vc_position)
self.assertIsNone(device2.vc_priority)

def test_virtualchassis_duplicate_vc_position(self):
"""
Test that two devices cannot be assigned to the same vc_position
within the same VirtualChassis.
"""
devices = Device.objects.all()
device1 = devices[0]
device2 = devices[1]

# Create a VirtualChassis
vc = VirtualChassis.objects.create(name='Test VC')

# Assign first device to vc_position 1
device1.virtual_chassis = vc
device1.vc_position = 1
device1.full_clean()
device1.save()

# Try to assign second device to the same vc_position
device2.virtual_chassis = vc
device2.vc_position = 1
with self.assertRaises(ValidationError):
device2.full_clean()