diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html
index f14ba1c86..873f18158 100644
--- a/netbox/templates/virtualization/virtualmachine.html
+++ b/netbox/templates/virtualization/virtualmachine.html
@@ -145,12 +145,6 @@
{% if object.disk %}
{{ object.disk }} {% trans "GB" context "Abbreviation for gigabyte" %}
-
-
-
- {% elif object.disk_size %}
- {{ object.disk_size }} {% trans "GB" context "Abbreviation for gigabyte" %}
- ({{ object.virtualdisks.count }} {% trans "disks" %})
{% else %}
{{ ''|placeholder }}
{% endif %}
diff --git a/netbox/virtualization/apps.py b/netbox/virtualization/apps.py
index 8db943ea1..f0af9a163 100644
--- a/netbox/virtualization/apps.py
+++ b/netbox/virtualization/apps.py
@@ -5,7 +5,7 @@ class VirtualizationConfig(AppConfig):
name = 'virtualization'
def ready(self):
- from . import search
+ from . import search, signals
from .models import VirtualMachine
from utilities.counters import connect_counters
diff --git a/netbox/virtualization/forms/model_forms.py b/netbox/virtualization/forms/model_forms.py
index 6e108fedd..80794281b 100644
--- a/netbox/virtualization/forms/model_forms.py
+++ b/netbox/virtualization/forms/model_forms.py
@@ -240,6 +240,11 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
if self.instance.pk:
+ # Disable the disk field if one or more VirtualDisks have been created
+ if self.instance.virtualdisks.exists():
+ self.fields['disk'].widget.attrs['disabled'] = True
+ self.fields['disk'].help_text = _("Disk size is managed via the attachment of virtual disks.")
+
# Compile list of choices for primary IPv4 and IPv6 addresses
for family in [4, 6]:
ip_choices = [(None, '---------')]
diff --git a/netbox/virtualization/models/virtualmachines.py b/netbox/virtualization/models/virtualmachines.py
index 970167bf3..634c59c9e 100644
--- a/netbox/virtualization/models/virtualmachines.py
+++ b/netbox/virtualization/models/virtualmachines.py
@@ -2,7 +2,7 @@ from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
from django.db import models
-from django.db.models import Q
+from django.db.models import Q, Sum
from django.db.models.functions import Lower
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
@@ -120,12 +120,10 @@ class VirtualMachine(ContactsMixin, RenderConfigMixin, ConfigContextModel, Prima
null=True,
verbose_name=_('memory (MB)')
)
- # TODO: Remove in v4.0
disk = models.PositiveIntegerField(
blank=True,
null=True,
- verbose_name=_('disk (GB)'),
- help_text=_('deprecated - use Virtual Disks')
+ verbose_name=_('disk (GB)')
)
# Counter fields
@@ -199,6 +197,17 @@ class VirtualMachine(ContactsMixin, RenderConfigMixin, ConfigContextModel, Prima
).format(device=self.device, cluster=self.cluster)
})
+ # Validate aggregate disk size
+ if self.pk:
+ total_disk = self.virtualdisks.aggregate(Sum('size', default=0))['size__sum']
+ if total_disk and self.disk != total_disk:
+ raise ValidationError({
+ 'disk': _(
+ "The specified disk size ({size}) must match the aggregate size of assigned virtual disks "
+ "({total_size})."
+ ).format(size=self.disk, total_size=total_disk)
+ })
+
# Validate primary IP addresses
interfaces = self.interfaces.all() if self.pk else None
for family in (4, 6):
diff --git a/netbox/virtualization/signals.py b/netbox/virtualization/signals.py
new file mode 100644
index 000000000..2ee015211
--- /dev/null
+++ b/netbox/virtualization/signals.py
@@ -0,0 +1,16 @@
+from django.db.models import Sum
+from django.db.models.signals import post_delete, post_save
+from django.dispatch import receiver
+
+from .models import VirtualDisk, VirtualMachine
+
+
+@receiver((post_delete, post_save), sender=VirtualDisk)
+def update_circuit(instance, **kwargs):
+ """
+ When a VirtualDisk has been modified, update the aggregate disk_size value of its VM.
+ """
+ vm = instance.virtual_machine
+ VirtualMachine.objects.filter(pk=vm.pk).update(
+ disk=vm.virtualdisks.aggregate(Sum('size'))['size__sum']
+ )
diff --git a/netbox/virtualization/tests/test_models.py b/netbox/virtualization/tests/test_models.py
index 782b9f07f..c94ff930e 100644
--- a/netbox/virtualization/tests/test_models.py
+++ b/netbox/virtualization/tests/test_models.py
@@ -90,3 +90,28 @@ class VirtualMachineTestCase(TestCase):
# Uniqueness validation for name should ignore case
with self.assertRaises(ValidationError):
vm2.full_clean()
+
+ def test_disk_size(self):
+ vm = VirtualMachine(
+ cluster=Cluster.objects.first(),
+ name='Virtual Machine 1'
+ )
+ vm.save()
+ vm.refresh_from_db()
+ self.assertEqual(vm.disk, None)
+
+ # Create two VirtualDisks
+ VirtualDisk.objects.create(virtual_machine=vm, name='Virtual Disk 1', size=10)
+ VirtualDisk.objects.create(virtual_machine=vm, name='Virtual Disk 2', size=10)
+ vm.refresh_from_db()
+ self.assertEqual(vm.disk, 20)
+
+ # Delete one VirtualDisk
+ VirtualDisk.objects.first().delete()
+ vm.refresh_from_db()
+ self.assertEqual(vm.disk, 10)
+
+ # Attempt to manually overwrite the aggregate disk size
+ vm.disk = 30
+ with self.assertRaises(ValidationError):
+ vm.full_clean()
|