From 3a10ea61e08ec80c5eddfb99460ce22d18eaf149 Mon Sep 17 00:00:00 2001 From: Ryan Gillespie <24619595+nopg@users.noreply.github.com> Date: Wed, 17 Apr 2024 03:22:22 +0000 Subject: [PATCH] Fixes #15717: Allow VM with Site to Cluster without Site --- netbox/dcim/tests/test_models.py | 58 +++++++++++++++++++ .../virtualization/models/virtualmachines.py | 2 +- netbox/virtualization/tests/test_models.py | 3 + 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index cab1760ed..37d6aeb8c 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -8,6 +8,7 @@ from dcim.models import * from extras.models import CustomField from tenancy.models import Tenant from utilities.data import drange +from virtualization.models import Cluster, ClusterType class LocationTestCase(TestCase): @@ -533,6 +534,63 @@ class DeviceTestCase(TestCase): device2.full_clean() device2.save() + def test_device_mismatched_site_cluster(self): + cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1') + Cluster.objects.create(name='Cluster 1', type=cluster_type) + + sites = ( + Site(name='Site 1', slug='site-1'), + Site(name='Site 2', slug='site-2'), + ) + Site.objects.bulk_create(sites) + + clusters = ( + Cluster(name='Cluster 1', type=cluster_type, site=sites[0]), + Cluster(name='Cluster 2', type=cluster_type, site=sites[1]), + Cluster(name='Cluster 3', type=cluster_type, site=None), + ) + Cluster.objects.bulk_create(clusters) + + device_type = DeviceType.objects.first() + device_role = DeviceRole.objects.first() + + # Device with site only should pass + Device(name='device1', site=sites[0], device_type=device_type, role=device_role).full_clean() + + # Device with site, cluster non-site should pass + Device(name='device1', site=sites[0], device_type=device_type, role=device_role, cluster=clusters[2]).full_clean() + + # Device with non-site cluster only should pass + Device(name='device1', site=sites[0], device_type=device_type, role=device_role, cluster=clusters[2]).full_clean() + + # Device with mismatched site & cluster should fail + with self.assertRaises(ValidationError): + Device(name='device1', site=sites[0], device_type=device_type, role=device_role, cluster=clusters[1]).full_clean() + + def test_old_device_role_field(self): + """ + Ensure that the old device role field sets the value in the new role field. + """ + + # Test getter method + device = Device( + site=Site.objects.first(), + device_type=DeviceType.objects.first(), + role=DeviceRole.objects.first(), + name='Test Device 1', + device_role=DeviceRole.objects.first() + ) + device.full_clean() + device.save() + + self.assertEqual(device.role, device.device_role) + + # Test setter method + device.device_role = DeviceRole.objects.last() + device.full_clean() + device.save() + self.assertEqual(device.role, device.device_role) + class CableTestCase(TestCase): diff --git a/netbox/virtualization/models/virtualmachines.py b/netbox/virtualization/models/virtualmachines.py index 92f1a9472..2ca1599bf 100644 --- a/netbox/virtualization/models/virtualmachines.py +++ b/netbox/virtualization/models/virtualmachines.py @@ -180,7 +180,7 @@ class VirtualMachine(ContactsMixin, ImageAttachmentsMixin, RenderConfigMixin, Co }) # Validate site for cluster & device - if self.cluster and self.site and self.cluster.site != self.site: + if self.cluster and self.cluster.site is not None and self.cluster.site != self.site: raise ValidationError({ 'cluster': _( 'The selected cluster ({cluster}) is not assigned to this site ({site}).' diff --git a/netbox/virtualization/tests/test_models.py b/netbox/virtualization/tests/test_models.py index c94ff930e..a4e8d7947 100644 --- a/netbox/virtualization/tests/test_models.py +++ b/netbox/virtualization/tests/test_models.py @@ -63,6 +63,9 @@ class VirtualMachineTestCase(TestCase): # VM with site only should pass VirtualMachine(name='vm1', site=sites[0]).full_clean() + # VM with site, cluster non-site should pass + VirtualMachine(name='vm1', site=sites[0], cluster=clusters[2]).full_clean() + # VM with non-site cluster only should pass VirtualMachine(name='vm1', cluster=clusters[2]).full_clean()