|
@@ -3,6 +3,7 @@ from django.test import TestCase
|
|
|
|
|
|
|
|
from dcim.models import Site
|
|
from dcim.models import Site
|
|
|
from tenancy.models import Tenant
|
|
from tenancy.models import Tenant
|
|
|
|
|
+from utilities.testing import create_test_device
|
|
|
from virtualization.models import *
|
|
from virtualization.models import *
|
|
|
|
|
|
|
|
|
|
|
|
@@ -10,29 +11,77 @@ class VirtualMachineTestCase(TestCase):
|
|
|
|
|
|
|
|
@classmethod
|
|
@classmethod
|
|
|
def setUpTestData(cls):
|
|
def setUpTestData(cls):
|
|
|
- cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
|
|
|
|
|
- Cluster.objects.create(name='Cluster 1', type=cluster_type)
|
|
|
|
|
|
|
+ # Create the cluster type
|
|
|
|
|
+ cls.cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
|
|
|
|
|
+
|
|
|
|
|
+ # Create sites
|
|
|
|
|
+ cls.sites = (
|
|
|
|
|
+ Site.objects.create(name='Site 1', slug='site-1'),
|
|
|
|
|
+ Site.objects.create(name='Site 2', slug='site-2'),
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # Create clusters with various site scopes
|
|
|
|
|
+ cls.cluster_with_site = Cluster.objects.create(
|
|
|
|
|
+ name='Cluster with Site',
|
|
|
|
|
+ type=cls.cluster_type,
|
|
|
|
|
+ scope=cls.sites[0],
|
|
|
|
|
+ )
|
|
|
|
|
+ cls.cluster_with_site2 = Cluster.objects.create(
|
|
|
|
|
+ name='Cluster with Site 2',
|
|
|
|
|
+ type=cls.cluster_type,
|
|
|
|
|
+ scope=cls.sites[1],
|
|
|
|
|
+ )
|
|
|
|
|
+ cls.cluster_no_site = Cluster.objects.create(
|
|
|
|
|
+ name='Cluster No Site',
|
|
|
|
|
+ type=cls.cluster_type,
|
|
|
|
|
+ scope=None,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # Create devices
|
|
|
|
|
+ cls.device_in_cluster = create_test_device(
|
|
|
|
|
+ 'Device in Cluster',
|
|
|
|
|
+ site=cls.sites[0],
|
|
|
|
|
+ cluster=cls.cluster_with_site,
|
|
|
|
|
+ )
|
|
|
|
|
+ cls.device_in_cluster2 = create_test_device(
|
|
|
|
|
+ 'Device in Cluster 2',
|
|
|
|
|
+ site=cls.sites[0],
|
|
|
|
|
+ cluster=cls.cluster_with_site,
|
|
|
|
|
+ )
|
|
|
|
|
+ cls.standalone_device = create_test_device(
|
|
|
|
|
+ 'Standalone Device',
|
|
|
|
|
+ site=cls.sites[1],
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # Create tenants
|
|
|
|
|
+ cls.tenants = (
|
|
|
|
|
+ Tenant.objects.create(name='Tenant 1', slug='tenant-1'),
|
|
|
|
|
+ Tenant.objects.create(name='Tenant 2', slug='tenant-2'),
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
def test_vm_duplicate_name_per_cluster(self):
|
|
def test_vm_duplicate_name_per_cluster(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Test that creating two Virtual Machines with the same name in
|
|
|
|
|
+ the same cluster fails validation.
|
|
|
|
|
+ """
|
|
|
vm1 = VirtualMachine(
|
|
vm1 = VirtualMachine(
|
|
|
- cluster=Cluster.objects.first(),
|
|
|
|
|
- name='Test VM 1'
|
|
|
|
|
|
|
+ cluster=self.cluster_with_site,
|
|
|
|
|
+ name='Test VM 1',
|
|
|
)
|
|
)
|
|
|
vm1.save()
|
|
vm1.save()
|
|
|
|
|
|
|
|
vm2 = VirtualMachine(
|
|
vm2 = VirtualMachine(
|
|
|
cluster=vm1.cluster,
|
|
cluster=vm1.cluster,
|
|
|
- name=vm1.name
|
|
|
|
|
|
|
+ name=vm1.name,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# Two VMs assigned to the same Cluster and no Tenant should fail validation
|
|
# Two VMs assigned to the same Cluster and no Tenant should fail validation
|
|
|
with self.assertRaises(ValidationError):
|
|
with self.assertRaises(ValidationError):
|
|
|
vm2.full_clean()
|
|
vm2.full_clean()
|
|
|
|
|
|
|
|
- tenant = Tenant.objects.create(name='Test Tenant 1', slug='test-tenant-1')
|
|
|
|
|
- vm1.tenant = tenant
|
|
|
|
|
|
|
+ vm1.tenant = self.tenants[0]
|
|
|
vm1.save()
|
|
vm1.save()
|
|
|
- vm2.tenant = tenant
|
|
|
|
|
|
|
+ vm2.tenant = self.tenants[0]
|
|
|
|
|
|
|
|
# Two VMs assigned to the same Cluster and the same Tenant should fail validation
|
|
# Two VMs assigned to the same Cluster and the same Tenant should fail validation
|
|
|
with self.assertRaises(ValidationError):
|
|
with self.assertRaises(ValidationError):
|
|
@@ -45,50 +94,38 @@ class VirtualMachineTestCase(TestCase):
|
|
|
vm2.save()
|
|
vm2.save()
|
|
|
|
|
|
|
|
def test_vm_mismatched_site_cluster(self):
|
|
def test_vm_mismatched_site_cluster(self):
|
|
|
- cluster_type = ClusterType.objects.first()
|
|
|
|
|
-
|
|
|
|
|
- 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, scope=sites[0]),
|
|
|
|
|
- Cluster(name='Cluster 2', type=cluster_type, scope=sites[1]),
|
|
|
|
|
- Cluster(name='Cluster 3', type=cluster_type, scope=None),
|
|
|
|
|
- )
|
|
|
|
|
- for cluster in clusters:
|
|
|
|
|
- cluster.save()
|
|
|
|
|
-
|
|
|
|
|
|
|
+ """
|
|
|
|
|
+ Test that creating a Virtual Machine with a mismatched site and
|
|
|
|
|
+ cluster fails validation.
|
|
|
|
|
+ """
|
|
|
# VM with site only should pass
|
|
# VM with site only should pass
|
|
|
- VirtualMachine(name='vm1', site=sites[0]).full_clean()
|
|
|
|
|
|
|
+ VirtualMachine(name='vm1', site=self.sites[0]).full_clean()
|
|
|
|
|
|
|
|
# VM with site, cluster non-site should pass
|
|
# VM with site, cluster non-site should pass
|
|
|
- VirtualMachine(name='vm1', site=sites[0], cluster=clusters[2]).full_clean()
|
|
|
|
|
|
|
+ VirtualMachine(name='vm2', site=self.sites[0], cluster=self.cluster_no_site).full_clean()
|
|
|
|
|
|
|
|
# VM with non-site cluster only should pass
|
|
# VM with non-site cluster only should pass
|
|
|
- VirtualMachine(name='vm1', cluster=clusters[2]).full_clean()
|
|
|
|
|
|
|
+ VirtualMachine(name='vm3', cluster=self.cluster_no_site).full_clean()
|
|
|
|
|
|
|
|
# VM with mismatched site & cluster should fail
|
|
# VM with mismatched site & cluster should fail
|
|
|
with self.assertRaises(ValidationError):
|
|
with self.assertRaises(ValidationError):
|
|
|
- VirtualMachine(name='vm1', site=sites[0], cluster=clusters[1]).full_clean()
|
|
|
|
|
|
|
+ VirtualMachine(name='vm4', site=self.sites[0], cluster=self.cluster_with_site2).full_clean()
|
|
|
|
|
|
|
|
- # VM with cluster site but no direct site should have its site set automatically
|
|
|
|
|
- vm = VirtualMachine(name='vm1', site=None, cluster=clusters[0])
|
|
|
|
|
|
|
+ # VM with a cluster site but no direct site should have its site set automatically
|
|
|
|
|
+ vm = VirtualMachine(name='vm5', site=None, cluster=self.cluster_with_site)
|
|
|
vm.save()
|
|
vm.save()
|
|
|
- self.assertEqual(vm.site, sites[0])
|
|
|
|
|
|
|
+ self.assertEqual(vm.site, self.sites[0])
|
|
|
|
|
|
|
|
def test_vm_name_case_sensitivity(self):
|
|
def test_vm_name_case_sensitivity(self):
|
|
|
vm1 = VirtualMachine(
|
|
vm1 = VirtualMachine(
|
|
|
- cluster=Cluster.objects.first(),
|
|
|
|
|
- name='virtual machine 1'
|
|
|
|
|
|
|
+ cluster=self.cluster_with_site,
|
|
|
|
|
+ name='virtual machine 1',
|
|
|
)
|
|
)
|
|
|
vm1.save()
|
|
vm1.save()
|
|
|
|
|
|
|
|
vm2 = VirtualMachine(
|
|
vm2 = VirtualMachine(
|
|
|
cluster=vm1.cluster,
|
|
cluster=vm1.cluster,
|
|
|
- name='VIRTUAL MACHINE 1'
|
|
|
|
|
|
|
+ name='VIRTUAL MACHINE 1',
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# Uniqueness validation for name should ignore case
|
|
# Uniqueness validation for name should ignore case
|
|
@@ -97,8 +134,8 @@ class VirtualMachineTestCase(TestCase):
|
|
|
|
|
|
|
|
def test_disk_size(self):
|
|
def test_disk_size(self):
|
|
|
vm = VirtualMachine(
|
|
vm = VirtualMachine(
|
|
|
- cluster=Cluster.objects.first(),
|
|
|
|
|
- name='Virtual Machine 1'
|
|
|
|
|
|
|
+ cluster=self.cluster_with_site,
|
|
|
|
|
+ name='VM Disk Test',
|
|
|
)
|
|
)
|
|
|
vm.save()
|
|
vm.save()
|
|
|
vm.refresh_from_db()
|
|
vm.refresh_from_db()
|
|
@@ -111,7 +148,7 @@ class VirtualMachineTestCase(TestCase):
|
|
|
self.assertEqual(vm.disk, 20)
|
|
self.assertEqual(vm.disk, 20)
|
|
|
|
|
|
|
|
# Delete one VirtualDisk
|
|
# Delete one VirtualDisk
|
|
|
- VirtualDisk.objects.first().delete()
|
|
|
|
|
|
|
+ VirtualDisk.objects.filter(virtual_machine=vm).first().delete()
|
|
|
vm.refresh_from_db()
|
|
vm.refresh_from_db()
|
|
|
self.assertEqual(vm.disk, 10)
|
|
self.assertEqual(vm.disk, 10)
|
|
|
|
|
|
|
@@ -119,3 +156,228 @@ class VirtualMachineTestCase(TestCase):
|
|
|
vm.disk = 30
|
|
vm.disk = 30
|
|
|
with self.assertRaises(ValidationError):
|
|
with self.assertRaises(ValidationError):
|
|
|
vm.full_clean()
|
|
vm.full_clean()
|
|
|
|
|
+
|
|
|
|
|
+ #
|
|
|
|
|
+ # Device assignment tests
|
|
|
|
|
+ #
|
|
|
|
|
+
|
|
|
|
|
+ def test_vm_assignment_valid_combinations(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Test valid assignment combinations for VirtualMachine.
|
|
|
|
|
+ """
|
|
|
|
|
+ # Valid: Site only
|
|
|
|
|
+ VirtualMachine(name='vm-site-only', site=self.sites[0]).full_clean()
|
|
|
|
|
+
|
|
|
|
|
+ # Valid: Cluster only (cluster has a site scope)
|
|
|
|
|
+ VirtualMachine(name='vm-cluster-only', cluster=self.cluster_with_site).full_clean()
|
|
|
|
|
+
|
|
|
|
|
+ # Valid: Cluster only (cluster has no site scope)
|
|
|
|
|
+ VirtualMachine(name='vm-cluster-no-site', cluster=self.cluster_no_site).full_clean()
|
|
|
|
|
+
|
|
|
|
|
+ # Valid: Device only (standalone device, no cluster)
|
|
|
|
|
+ VirtualMachine(name='vm-device-standalone', device=self.standalone_device).full_clean()
|
|
|
|
|
+
|
|
|
|
|
+ # Valid: Site + Cluster (matching)
|
|
|
|
|
+ VirtualMachine(name='vm-site-cluster', site=self.sites[0], cluster=self.cluster_with_site).full_clean()
|
|
|
|
|
+
|
|
|
|
|
+ # Valid: Site + Cluster (cluster has no site scope)
|
|
|
|
|
+ VirtualMachine(name='vm-site-cluster-no-scope', site=self.sites[0], cluster=self.cluster_no_site).full_clean()
|
|
|
|
|
+
|
|
|
|
|
+ # Valid: Cluster + Device (device belongs to the cluster)
|
|
|
|
|
+ VirtualMachine(
|
|
|
|
|
+ name='vm-cluster-device', cluster=self.cluster_with_site, device=self.device_in_cluster
|
|
|
|
|
+ ).full_clean()
|
|
|
|
|
+
|
|
|
|
|
+ # Valid: Site + Cluster + Device (all matching)
|
|
|
|
|
+ VirtualMachine(
|
|
|
|
|
+ name='vm-all-three',
|
|
|
|
|
+ site=self.sites[0],
|
|
|
|
|
+ cluster=self.cluster_with_site,
|
|
|
|
|
+ device=self.device_in_cluster,
|
|
|
|
|
+ ).full_clean()
|
|
|
|
|
+
|
|
|
|
|
+ def test_vm_assignment_invalid_no_assignment(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Test that a VirtualMachine without any assignment fails validation.
|
|
|
|
|
+ """
|
|
|
|
|
+ vm = VirtualMachine(name='vm-no-assignment')
|
|
|
|
|
+ with self.assertRaises(ValidationError) as context:
|
|
|
|
|
+ vm.full_clean()
|
|
|
|
|
+ self.assertIn('__all__', context.exception.message_dict)
|
|
|
|
|
+
|
|
|
|
|
+ def test_vm_assignment_invalid_site_cluster_mismatch(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Test that a VirtualMachine with a mismatched site and cluster fails validation.
|
|
|
|
|
+ """
|
|
|
|
|
+ # VM with Site 2 but Cluster scoped to Site 1 should fail
|
|
|
|
|
+ vm = VirtualMachine(name='vm-mismatch', site=self.sites[1], cluster=self.cluster_with_site)
|
|
|
|
|
+ with self.assertRaises(ValidationError) as context:
|
|
|
|
|
+ vm.full_clean()
|
|
|
|
|
+ self.assertIn('cluster', context.exception.message_dict)
|
|
|
|
|
+
|
|
|
|
|
+ def test_vm_assignment_invalid_device_in_cluster_without_cluster(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Test that assigning a VM to a device that belongs to a cluster
|
|
|
|
|
+ without specifying the cluster fails validation.
|
|
|
|
|
+ """
|
|
|
|
|
+ # VM assigned to a device without specifying the cluster should fail
|
|
|
|
|
+ vm = VirtualMachine(name='vm-device-no-cluster', device=self.device_in_cluster)
|
|
|
|
|
+ with self.assertRaises(ValidationError) as context:
|
|
|
|
|
+ vm.full_clean()
|
|
|
|
|
+ self.assertIn('cluster', context.exception.message_dict)
|
|
|
|
|
+
|
|
|
|
|
+ def test_vm_assignment_invalid_device_cluster_mismatch(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Test that a VirtualMachine with a device and cluster that don't match fails validation.
|
|
|
|
|
+ """
|
|
|
|
|
+ # VM with a device in cluster_with_site but assigned to cluster_with_site2 should fail
|
|
|
|
|
+ vm = VirtualMachine(
|
|
|
|
|
+ name='vm-device-wrong-cluster',
|
|
|
|
|
+ device=self.device_in_cluster,
|
|
|
|
|
+ cluster=self.cluster_with_site2,
|
|
|
|
|
+ )
|
|
|
|
|
+ with self.assertRaises(ValidationError) as context:
|
|
|
|
|
+ vm.full_clean()
|
|
|
|
|
+ self.assertIn('device', context.exception.message_dict)
|
|
|
|
|
+
|
|
|
|
|
+ def test_vm_standalone_device_assignment(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Test that a VirtualMachine can be assigned directly to a standalone device
|
|
|
|
|
+ (device not in any cluster).
|
|
|
|
|
+ """
|
|
|
|
|
+ # VM assigned to a standalone device only should pass
|
|
|
|
|
+ vm = VirtualMachine(name='vm-standalone', device=self.standalone_device)
|
|
|
|
|
+ vm.full_clean()
|
|
|
|
|
+ vm.save()
|
|
|
|
|
+
|
|
|
|
|
+ # Verify the site was automatically set from the device
|
|
|
|
|
+ self.assertEqual(vm.site, self.sites[1])
|
|
|
|
|
+ self.assertIsNone(vm.cluster)
|
|
|
|
|
+
|
|
|
|
|
+ def test_vm_standalone_device_with_site(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Test that a VirtualMachine can be assigned to a standalone device
|
|
|
|
|
+ with an explicit matching site.
|
|
|
|
|
+ """
|
|
|
|
|
+ # VM assigned to a standalone device with an explicit site should pass
|
|
|
|
|
+ vm = VirtualMachine(name='vm-standalone-site', site=self.sites[1], device=self.standalone_device)
|
|
|
|
|
+ vm.full_clean()
|
|
|
|
|
+ vm.save()
|
|
|
|
|
+
|
|
|
|
|
+ self.assertEqual(vm.site, self.sites[1])
|
|
|
|
|
+ self.assertEqual(vm.device, self.standalone_device)
|
|
|
|
|
+ self.assertIsNone(vm.cluster)
|
|
|
|
|
+
|
|
|
|
|
+ def test_vm_duplicate_name_per_device(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Test that VirtualMachine names must be unique per standalone device (when no cluster).
|
|
|
|
|
+ """
|
|
|
|
|
+ vm1 = VirtualMachine(name='vm-dup', device=self.standalone_device)
|
|
|
|
|
+ vm1.full_clean()
|
|
|
|
|
+ vm1.save()
|
|
|
|
|
+
|
|
|
|
|
+ vm2 = VirtualMachine(name='vm-dup', device=self.standalone_device)
|
|
|
|
|
+
|
|
|
|
|
+ # Duplicate name on the same standalone device should fail
|
|
|
|
|
+ with self.assertRaises(ValidationError):
|
|
|
|
|
+ vm2.full_clean()
|
|
|
|
|
+
|
|
|
|
|
+ def test_vm_site_auto_assignment_from_device(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Test that a VirtualMachine's site is automatically set from its assigned
|
|
|
|
|
+ standalone device when no site is explicitly provided.
|
|
|
|
|
+ """
|
|
|
|
|
+ # VM with a device only (no explicit site)
|
|
|
|
|
+ vm = VirtualMachine(name='vm-auto-site', device=self.standalone_device)
|
|
|
|
|
+ vm.full_clean()
|
|
|
|
|
+ vm.save()
|
|
|
|
|
+
|
|
|
|
|
+ # Site should be automatically inherited from the device
|
|
|
|
|
+ self.assertEqual(vm.site, self.sites[1])
|
|
|
|
|
+
|
|
|
|
|
+ def test_vm_duplicate_name_per_device_with_tenant(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Test that VirtualMachine names can be duplicated across different tenants
|
|
|
|
|
+ on the same standalone device.
|
|
|
|
|
+ """
|
|
|
|
|
+ # Create VM with tenant1
|
|
|
|
|
+ vm1 = VirtualMachine(name='vm-tenant-test', device=self.standalone_device, tenant=self.tenants[0])
|
|
|
|
|
+ vm1.full_clean()
|
|
|
|
|
+ vm1.save()
|
|
|
|
|
+
|
|
|
|
|
+ # The same name with tenant2 on the same device should pass
|
|
|
|
|
+ vm2 = VirtualMachine(name='vm-tenant-test', device=self.standalone_device, tenant=self.tenants[1])
|
|
|
|
|
+ vm2.full_clean()
|
|
|
|
|
+ vm2.save()
|
|
|
|
|
+
|
|
|
|
|
+ # The same name with the same tenant should fail
|
|
|
|
|
+ vm3 = VirtualMachine(name='vm-tenant-test', device=self.standalone_device, tenant=self.tenants[0])
|
|
|
|
|
+ with self.assertRaises(ValidationError):
|
|
|
|
|
+ vm3.full_clean()
|
|
|
|
|
+
|
|
|
|
|
+ def test_vm_device_name_case_sensitivity(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Test that VirtualMachine name uniqueness per device is case-insensitive.
|
|
|
|
|
+ """
|
|
|
|
|
+ vm1 = VirtualMachine(name='test vm', device=self.standalone_device)
|
|
|
|
|
+ vm1.full_clean()
|
|
|
|
|
+ vm1.save()
|
|
|
|
|
+
|
|
|
|
|
+ # The same name with a different case should fail
|
|
|
|
|
+ vm2 = VirtualMachine(name='TEST VM', device=self.standalone_device)
|
|
|
|
|
+ with self.assertRaises(ValidationError):
|
|
|
|
|
+ vm2.full_clean()
|
|
|
|
|
+
|
|
|
|
|
+ def test_vm_cluster_device_with_site(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Test that a VirtualMachine can be pinned to a device within a cluster
|
|
|
|
|
+ with an explicit matching site.
|
|
|
|
|
+ """
|
|
|
|
|
+ # VM with site + cluster + device (all matching)
|
|
|
|
|
+ vm = VirtualMachine(
|
|
|
|
|
+ name='vm-cluster-device-site',
|
|
|
|
|
+ site=self.sites[0],
|
|
|
|
|
+ cluster=self.cluster_with_site,
|
|
|
|
|
+ device=self.device_in_cluster,
|
|
|
|
|
+ )
|
|
|
|
|
+ vm.full_clean()
|
|
|
|
|
+ vm.save()
|
|
|
|
|
+
|
|
|
|
|
+ self.assertEqual(vm.site, self.sites[0])
|
|
|
|
|
+ self.assertEqual(vm.cluster, self.cluster_with_site)
|
|
|
|
|
+ self.assertEqual(vm.device, self.device_in_cluster)
|
|
|
|
|
+
|
|
|
|
|
+ def test_vm_move_between_devices_in_cluster(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Test that a VirtualMachine can be moved between devices within the same cluster.
|
|
|
|
|
+ """
|
|
|
|
|
+ # Create a VM pinned to device_in_cluster
|
|
|
|
|
+ vm = VirtualMachine(name='vm-movable', cluster=self.cluster_with_site, device=self.device_in_cluster)
|
|
|
|
|
+ vm.full_clean()
|
|
|
|
|
+ vm.save()
|
|
|
|
|
+
|
|
|
|
|
+ # Move VM to device_in_cluster2
|
|
|
|
|
+ vm.device = self.device_in_cluster2
|
|
|
|
|
+ vm.full_clean()
|
|
|
|
|
+ vm.save()
|
|
|
|
|
+
|
|
|
|
|
+ self.assertEqual(vm.device, self.device_in_cluster2)
|
|
|
|
|
+ self.assertEqual(vm.cluster, self.cluster_with_site)
|
|
|
|
|
+
|
|
|
|
|
+ def test_vm_unpin_from_device(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Test that a VirtualMachine can be unpinned from a device while remaining
|
|
|
|
|
+ in the cluster.
|
|
|
|
|
+ """
|
|
|
|
|
+ # Create a VM pinned to a device
|
|
|
|
|
+ vm = VirtualMachine(name='vm-unpinnable', cluster=self.cluster_with_site, device=self.device_in_cluster)
|
|
|
|
|
+ vm.full_clean()
|
|
|
|
|
+ vm.save()
|
|
|
|
|
+
|
|
|
|
|
+ # Unpin VM from the device (keep in cluster)
|
|
|
|
|
+ vm.device = None
|
|
|
|
|
+ vm.full_clean()
|
|
|
|
|
+ vm.save()
|
|
|
|
|
+
|
|
|
|
|
+ self.assertIsNone(vm.device)
|
|
|
|
|
+ self.assertEqual(vm.cluster, self.cluster_with_site)
|