Add bridge field to Interface, VMInterface models

This commit is contained in:
jeremystretch
2021-10-21 16:30:18 -04:00
parent a3e7cab935
commit e1e2c76ae1
25 changed files with 260 additions and 119 deletions

View File

@@ -107,6 +107,7 @@ class VMInterfaceSerializer(PrimaryModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:vminterface-detail')
virtual_machine = NestedVirtualMachineSerializer()
parent = NestedVMInterfaceSerializer(required=False, allow_null=True)
bridge = NestedVMInterfaceSerializer(required=False, allow_null=True)
mode = ChoiceField(choices=InterfaceModeChoices, allow_blank=True, required=False)
untagged_vlan = NestedVLANSerializer(required=False, allow_null=True)
tagged_vlans = SerializedPKRelatedField(
@@ -120,8 +121,8 @@ class VMInterfaceSerializer(PrimaryModelSerializer):
class Meta:
model = VMInterface
fields = [
'id', 'url', 'display', 'virtual_machine', 'name', 'enabled', 'parent', 'mtu', 'mac_address', 'description',
'mode', 'untagged_vlan', 'tagged_vlans', 'tags', 'custom_fields', 'created', 'last_updated',
'id', 'url', 'display', 'virtual_machine', 'name', 'enabled', 'parent', 'bridge', 'mtu', 'mac_address',
'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'tags', 'custom_fields', 'created', 'last_updated',
'count_ipaddresses',
]

View File

@@ -264,6 +264,11 @@ class VMInterfaceFilterSet(PrimaryModelFilterSet):
queryset=VMInterface.objects.all(),
label='Parent interface (ID)',
)
bridge_id = django_filters.ModelMultipleChoiceFilter(
field_name='bridge',
queryset=VMInterface.objects.all(),
label='Bridged interface (ID)',
)
mac_address = MultiValueMACAddressFilter(
label='MAC address',
)

View File

@@ -165,6 +165,10 @@ class VMInterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldMode
queryset=VMInterface.objects.all(),
required=False
)
bridge = DynamicModelChoiceField(
queryset=VMInterface.objects.all(),
required=False
)
enabled = forms.NullBooleanField(
required=False,
widget=BulkEditNullBooleanSelect()
@@ -195,7 +199,7 @@ class VMInterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldMode
class Meta:
nullable_fields = [
'parent', 'mtu', 'description',
'parent', 'bridge', 'mtu', 'description',
]
def __init__(self, *args, **kwargs):
@@ -203,8 +207,9 @@ class VMInterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldMode
if 'virtual_machine' in self.initial:
vm_id = self.initial.get('virtual_machine')
# Restrict parent interface assignment by VM
# 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)
@@ -231,6 +236,11 @@ class VMInterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldMode
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(

View File

@@ -104,6 +104,18 @@ class VMInterfaceCSVForm(CustomFieldModelCSVForm):
queryset=VirtualMachine.objects.all(),
to_field_name='name'
)
parent = CSVModelChoiceField(
queryset=VMInterface.objects.all(),
required=False,
to_field_name='name',
help_text='Parent interface'
)
bridge = CSVModelChoiceField(
queryset=VMInterface.objects.all(),
required=False,
to_field_name='name',
help_text='Bridged interface'
)
mode = CSVChoiceField(
choices=InterfaceModeChoices,
required=False,
@@ -113,7 +125,7 @@ class VMInterfaceCSVForm(CustomFieldModelCSVForm):
class Meta:
model = VMInterface
fields = (
'virtual_machine', 'name', 'enabled', 'mac_address', 'mtu', 'description', 'mode',
'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mac_address', 'mtu', 'description', 'mode',
)
def clean_enabled(self):

View File

@@ -277,6 +277,11 @@ class VMInterfaceForm(BootstrapMixin, InterfaceCommonForm, CustomFieldModelForm)
required=False,
label='Parent interface'
)
bridge = DynamicModelChoiceField(
queryset=VMInterface.objects.all(),
required=False,
label='Bridged interface'
)
vlan_group = DynamicModelChoiceField(
queryset=VLANGroup.objects.all(),
required=False,
@@ -306,8 +311,8 @@ class VMInterfaceForm(BootstrapMixin, InterfaceCommonForm, CustomFieldModelForm)
class Meta:
model = VMInterface
fields = [
'virtual_machine', 'name', 'enabled', 'parent', 'mac_address', 'mtu', 'description', 'mode', 'tags',
'untagged_vlan', 'tagged_vlans',
'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mac_address', 'mtu', 'description', 'mode',
'tags', 'untagged_vlan', 'tagged_vlans',
]
widgets = {
'virtual_machine': forms.HiddenInput(),
@@ -326,6 +331,7 @@ class VMInterfaceForm(BootstrapMixin, InterfaceCommonForm, CustomFieldModelForm)
# Restrict parent 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)

View File

@@ -35,6 +35,13 @@ class VMInterfaceCreateForm(BootstrapMixin, CustomFieldsMixin, InterfaceCommonFo
'virtual_machine_id': '$virtual_machine',
}
)
bridge = DynamicModelChoiceField(
queryset=VMInterface.objects.all(),
required=False,
query_params={
'virtual_machine_id': '$virtual_machine',
}
)
mac_address = forms.CharField(
required=False,
label='MAC Address'
@@ -61,7 +68,7 @@ class VMInterfaceCreateForm(BootstrapMixin, CustomFieldsMixin, InterfaceCommonFo
required=False
)
field_order = (
'virtual_machine', 'name_pattern', 'enabled', 'parent', 'mtu', 'mac_address', 'description', 'mode',
'virtual_machine', 'name_pattern', 'enabled', 'parent', 'bridge', 'mtu', 'mac_address', 'description', 'mode',
'untagged_vlan', 'tagged_vlans', 'tags'
)

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.2.8 on 2021-10-21 20:26
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('virtualization', '0025_extend_tag_support'),
]
operations = [
migrations.AddField(
model_name='vminterface',
name='bridge',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bridge_interfaces', to='virtualization.vminterface'),
),
]

View File

@@ -378,14 +378,6 @@ class VMInterface(PrimaryModel, BaseInterface):
max_length=200,
blank=True
)
parent = models.ForeignKey(
to='self',
on_delete=models.SET_NULL,
related_name='child_interfaces',
null=True,
blank=True,
verbose_name='Parent interface'
)
untagged_vlan = models.ForeignKey(
to='ipam.VLAN',
on_delete=models.SET_NULL,
@@ -423,6 +415,12 @@ class VMInterface(PrimaryModel, BaseInterface):
def clean(self):
super().clean()
# Parent validation
# An interface cannot be its own parent
if self.pk and self.parent_id == self.pk:
raise ValidationError({'parent': "An interface cannot be its own parent."})
# An interface's parent must belong to the same virtual machine
if self.parent and self.parent.virtual_machine != self.virtual_machine:
raise ValidationError({
@@ -430,15 +428,26 @@ class VMInterface(PrimaryModel, BaseInterface):
f"({self.parent.virtual_machine})."
})
# An interface cannot be its own parent
if self.pk and self.parent_id == self.pk:
raise ValidationError({'parent': "An interface cannot be its own parent."})
# Bridge validation
# An interface cannot be bridged to itself
if self.pk and self.bridge_id == self.pk:
raise ValidationError({'bridge': "An interface cannot be bridged to itself."})
# A bridged interface belong to the same virtual machine
if self.bridge and self.bridge.virtual_machine != self.virtual_machine:
raise ValidationError({
'bridge': f"The selected bridge interface ({self.bridge}) belongs to a different virtual machine "
f"({self.bridge.virtual_machine})."
})
# VLAN validation
# Validate untagged VLAN
if self.untagged_vlan and self.untagged_vlan.site not in [self.virtual_machine.site, None]:
raise ValidationError({
'untagged_vlan': f"The untagged VLAN ({self.untagged_vlan}) must belong to the same site as the "
f"interface's parent virtual machine, or it must be global"
f"interface's parent virtual machine, or it must be global."
})
def to_objectchange(self, action):

View File

@@ -166,9 +166,6 @@ class VMInterfaceTable(BaseInterfaceTable):
name = tables.Column(
linkify=True
)
parent = tables.Column(
linkify=True
)
tags = TagColumn(
url_name='virtualization:vminterface_list'
)
@@ -176,13 +173,19 @@ class VMInterfaceTable(BaseInterfaceTable):
class Meta(BaseTable.Meta):
model = VMInterface
fields = (
'pk', 'name', 'virtual_machine', 'enabled', 'parent', 'mac_address', 'mtu', 'mode', 'description', 'tags',
'pk', 'name', 'virtual_machine', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'tags',
'ip_addresses', 'untagged_vlan', 'tagged_vlans',
)
default_columns = ('pk', 'name', 'virtual_machine', 'enabled', 'parent', 'description')
default_columns = ('pk', 'name', 'virtual_machine', 'enabled', 'description')
class VirtualMachineVMInterfaceTable(VMInterfaceTable):
parent = tables.Column(
linkify=True
)
bridge = tables.Column(
linkify=True
)
actions = ButtonsColumn(
model=VMInterface,
buttons=('edit', 'delete'),
@@ -192,8 +195,8 @@ class VirtualMachineVMInterfaceTable(VMInterfaceTable):
class Meta(BaseTable.Meta):
model = VMInterface
fields = (
'pk', 'name', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'tags', 'ip_addresses',
'untagged_vlan', 'tagged_vlans', 'actions',
'pk', 'name', 'enabled', 'parent', 'bridge', 'mac_address', 'mtu', 'mode', 'description', 'tags',
'ip_addresses', 'untagged_vlan', 'tagged_vlans', 'actions',
)
default_columns = (
'pk', 'name', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'ip_addresses', 'actions',