1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00
Arthur Hanson 549b0ea107 Closes #8356: Add virtual disk to Virtual Machines (#14087)
* 8356 add virtual disk model

* 8356 add supplemental forms

* 8356 add menu

* 8356 cleanup views

* 8356 virtual machine tab

* 8356 migrations

* 8356 vm disk tables

* 8356 cleanup

* 8356 graphql

* 8356 graphql

* 8356 add components button

* 8356 bulk add on virtualmachine

* 8356 bulk add fixes

* 8356 api tests

* 8356 news tests add rename

* 8356 VirtualDiskCreateForm

* 8356 fix test

* 8356 add todo to remove disk from vm

* 8356 review changes

* 8356 fix test

* 8356 deprecate disk field

* 8356 review changes

* 8356 fix test

* 8356 fix test

* Simplify view actions

* 8356 review changes

* 8356 split trans tag

* 8356 add total virtual disk size to api

* 8356 add virtual disk list to virtual machine detail view

* 8356 move virtual disk size to property

* 8356 revert property

* Tweak display of deprecated disk field

* 8356 render single disk field

* 8356 update serializer

* 8356 model property

* 8356 fix test

* 8356 review changes

* Revert disk space annotation

* Use existing disk field to store aggregate virtual disk size

* Introduce abstract ComponentModel for VM components

* Add search index for VirtualDisk

* Misc cleanup

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
2023-11-17 15:02:56 -05:00

352 lines
10 KiB
Python

from django import forms
from django.utils.translation import gettext_lazy as _
from dcim.choices import InterfaceModeChoices
from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN
from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup
from extras.models import ConfigTemplate
from ipam.models import VLAN, VLANGroup, VRF
from netbox.forms import NetBoxModelBulkEditForm
from tenancy.models import Tenant
from utilities.forms import BulkRenameForm, add_blank_choice
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
from utilities.forms.widgets import BulkEditNullBooleanSelect
from virtualization.choices import *
from virtualization.models import *
__all__ = (
'ClusterBulkEditForm',
'ClusterGroupBulkEditForm',
'ClusterTypeBulkEditForm',
'VirtualDiskBulkEditForm',
'VirtualDiskBulkRenameForm',
'VirtualMachineBulkEditForm',
'VMInterfaceBulkEditForm',
'VMInterfaceBulkRenameForm',
)
class ClusterTypeBulkEditForm(NetBoxModelBulkEditForm):
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
model = ClusterType
fieldsets = (
(None, ('description',)),
)
nullable_fields = ('description',)
class ClusterGroupBulkEditForm(NetBoxModelBulkEditForm):
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
model = ClusterGroup
fieldsets = (
(None, ('description',)),
)
nullable_fields = ('description',)
class ClusterBulkEditForm(NetBoxModelBulkEditForm):
type = DynamicModelChoiceField(
label=_('Type'),
queryset=ClusterType.objects.all(),
required=False
)
group = DynamicModelChoiceField(
label=_('Group'),
queryset=ClusterGroup.objects.all(),
required=False
)
status = forms.ChoiceField(
label=_('Status'),
choices=add_blank_choice(ClusterStatusChoices),
required=False,
initial=''
)
tenant = DynamicModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False
)
region = DynamicModelChoiceField(
label=_('Region'),
queryset=Region.objects.all(),
required=False,
)
site_group = DynamicModelChoiceField(
label=_('Site group'),
queryset=SiteGroup.objects.all(),
required=False,
)
site = DynamicModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
required=False,
query_params={
'region_id': '$region',
'group_id': '$site_group',
}
)
description = forms.CharField(
label=_('Site'),
max_length=200,
required=False
)
comments = CommentField()
model = Cluster
fieldsets = (
(None, ('type', 'group', 'status', 'tenant', 'description')),
(_('Site'), ('region', 'site_group', 'site')),
)
nullable_fields = (
'group', 'site', 'tenant', 'description', 'comments',
)
class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
status = forms.ChoiceField(
label=_('Status'),
choices=add_blank_choice(VirtualMachineStatusChoices),
required=False,
initial='',
)
site = DynamicModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
required=False
)
cluster = DynamicModelChoiceField(
label=_('Cluster'),
queryset=Cluster.objects.all(),
required=False,
query_params={
'site_id': '$site'
}
)
device = DynamicModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
required=False,
query_params={
'cluster_id': '$cluster'
}
)
role = DynamicModelChoiceField(
label=_('Role'),
queryset=DeviceRole.objects.filter(
vm_role=True
),
required=False,
query_params={
"vm_role": "True"
}
)
tenant = DynamicModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False
)
platform = DynamicModelChoiceField(
label=_('Platform'),
queryset=Platform.objects.all(),
required=False
)
vcpus = forms.IntegerField(
required=False,
label=_('vCPUs')
)
memory = forms.IntegerField(
required=False,
label=_('Memory (MB)')
)
disk = forms.IntegerField(
required=False,
label=_('Disk (GB)')
)
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
config_template = DynamicModelChoiceField(
queryset=ConfigTemplate.objects.all(),
required=False
)
comments = CommentField()
model = VirtualMachine
fieldsets = (
(None, ('site', 'cluster', 'device', 'status', 'role', 'tenant', 'platform', 'description')),
(_('Resources'), ('vcpus', 'memory', 'disk')),
('Configuration', ('config_template',)),
)
nullable_fields = (
'site', 'cluster', 'device', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'description', 'comments',
)
class VMInterfaceBulkEditForm(NetBoxModelBulkEditForm):
virtual_machine = forms.ModelChoiceField(
label=_('Virtual machine'),
queryset=VirtualMachine.objects.all(),
required=False,
disabled=True,
widget=forms.HiddenInput()
)
parent = DynamicModelChoiceField(
label=_('Parent'),
queryset=VMInterface.objects.all(),
required=False
)
bridge = DynamicModelChoiceField(
label=_('Bridge'),
queryset=VMInterface.objects.all(),
required=False
)
enabled = forms.NullBooleanField(
label=_('Enabled'),
required=False,
widget=BulkEditNullBooleanSelect()
)
mtu = forms.IntegerField(
required=False,
min_value=INTERFACE_MTU_MIN,
max_value=INTERFACE_MTU_MAX,
label=_('MTU')
)
description = forms.CharField(
label=_('Description'),
max_length=100,
required=False
)
mode = forms.ChoiceField(
label=_('Mode'),
choices=add_blank_choice(InterfaceModeChoices),
required=False
)
vlan_group = DynamicModelChoiceField(
queryset=VLANGroup.objects.all(),
required=False,
label=_('VLAN group')
)
untagged_vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(),
required=False,
query_params={
'group_id': '$vlan_group',
},
label=_('Untagged VLAN')
)
tagged_vlans = DynamicModelMultipleChoiceField(
queryset=VLAN.objects.all(),
required=False,
query_params={
'group_id': '$vlan_group',
},
label=_('Tagged VLANs')
)
vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(),
required=False,
label=_('VRF')
)
model = VMInterface
fieldsets = (
(None, ('mtu', 'enabled', 'vrf', 'description')),
(_('Related Interfaces'), ('parent', 'bridge')),
(_('802.1Q Switching'), ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
)
nullable_fields = (
'parent', 'bridge', 'mtu', 'vrf', 'description',
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'virtual_machine' in self.initial:
vm_id = self.initial.get('virtual_machine')
# Restrict parent/bridge interface assignment by VM
self.fields['parent'].widget.add_query_param('virtual_machine_id', vm_id)
self.fields['bridge'].widget.add_query_param('virtual_machine_id', vm_id)
# Limit VLAN choices by virtual machine
self.fields['untagged_vlan'].widget.add_query_param('available_on_virtualmachine', vm_id)
self.fields['tagged_vlans'].widget.add_query_param('available_on_virtualmachine', vm_id)
else:
# See 5643
if 'pk' in self.initial:
site = None
interfaces = VMInterface.objects.filter(
pk__in=self.initial['pk']
).prefetch_related(
'virtual_machine__site'
)
# Check interface sites. First interface should set site, further interfaces will either continue the
# loop or reset back to no site and break the loop.
for interface in interfaces:
if site is None:
site = interface.virtual_machine.cluster.site
elif interface.virtual_machine.cluster.site is not site:
site = None
break
if site is not None:
self.fields['untagged_vlan'].widget.add_query_param('site_id', site.pk)
self.fields['tagged_vlans'].widget.add_query_param('site_id', site.pk)
self.fields['parent'].choices = ()
self.fields['parent'].widget.attrs['disabled'] = True
self.fields['bridge'].choices = ()
self.fields['bridge'].widget.attrs['disabled'] = True
class VMInterfaceBulkRenameForm(BulkRenameForm):
pk = forms.ModelMultipleChoiceField(
queryset=VMInterface.objects.all(),
widget=forms.MultipleHiddenInput()
)
class VirtualDiskBulkEditForm(NetBoxModelBulkEditForm):
virtual_machine = forms.ModelChoiceField(
label=_('Virtual machine'),
queryset=VirtualMachine.objects.all(),
required=False,
disabled=True,
widget=forms.HiddenInput()
)
size = forms.IntegerField(
required=False,
label=_('Size (GB)')
)
description = forms.CharField(
label=_('Description'),
max_length=100,
required=False
)
model = VirtualDisk
fieldsets = (
(None, ('size', 'description')),
)
nullable_fields = ('description',)
class VirtualDiskBulkRenameForm(BulkRenameForm):
pk = forms.ModelMultipleChoiceField(
queryset=VirtualDisk.objects.all(),
widget=forms.MultipleHiddenInput()
)