Closes #14311: Move L2VPN models from ipam to vpn (#14358)

* Move L2VPN and L2VPNTermination models from ipam to vpn

* Move L2VPN resources from ipam to vpn

* Extend migration to update content types

* Misc cleanup
This commit is contained in:
Jeremy Stretch
2023-11-28 13:45:00 -05:00
committed by GitHub
parent eeaa76226e
commit b62a1210f9
66 changed files with 1616 additions and 1441 deletions

View File

@@ -14,6 +14,8 @@ __all__ = (
'IPSecPolicyBulkEditForm',
'IPSecProfileBulkEditForm',
'IPSecProposalBulkEditForm',
'L2VPNBulkEditForm',
'L2VPNTerminationBulkEditForm',
'TunnelBulkEditForm',
'TunnelTerminationBulkEditForm',
)
@@ -241,3 +243,32 @@ class IPSecProfileBulkEditForm(NetBoxModelBulkEditForm):
nullable_fields = (
'description', 'comments',
)
class L2VPNBulkEditForm(NetBoxModelBulkEditForm):
type = forms.ChoiceField(
label=_('Type'),
choices=add_blank_choice(L2VPNTypeChoices),
required=False
)
tenant = DynamicModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False
)
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
comments = CommentField()
model = L2VPN
fieldsets = (
(None, ('type', 'tenant', 'description')),
)
nullable_fields = ('tenant', 'description', 'comments')
class L2VPNTerminationBulkEditForm(NetBoxModelBulkEditForm):
model = L2VPN

View File

@@ -1,7 +1,8 @@
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from dcim.models import Device, Interface
from ipam.models import IPAddress
from ipam.models import IPAddress, VLAN
from netbox.forms import NetBoxModelImportForm
from tenancy.models import Tenant
from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, CSVModelMultipleChoiceField
@@ -15,6 +16,8 @@ __all__ = (
'IPSecPolicyImportForm',
'IPSecProfileImportForm',
'IPSecProposalImportForm',
'L2VPNImportForm',
'L2VPNTerminationImportForm',
'TunnelImportForm',
'TunnelTerminationImportForm',
)
@@ -228,3 +231,92 @@ class IPSecProfileImportForm(NetBoxModelImportForm):
fields = (
'name', 'mode', 'ike_policy', 'ipsec_policy', 'description', 'comments', 'tags',
)
class L2VPNImportForm(NetBoxModelImportForm):
tenant = CSVModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False,
to_field_name='name',
)
type = CSVChoiceField(
label=_('Type'),
choices=L2VPNTypeChoices,
help_text=_('L2VPN type')
)
class Meta:
model = L2VPN
fields = ('identifier', 'name', 'slug', 'tenant', 'type', 'description',
'comments', 'tags')
class L2VPNTerminationImportForm(NetBoxModelImportForm):
l2vpn = CSVModelChoiceField(
queryset=L2VPN.objects.all(),
required=True,
to_field_name='name',
label=_('L2VPN'),
)
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
required=False,
to_field_name='name',
help_text=_('Parent device (for interface)')
)
virtual_machine = CSVModelChoiceField(
label=_('Virtual machine'),
queryset=VirtualMachine.objects.all(),
required=False,
to_field_name='name',
help_text=_('Parent virtual machine (for interface)')
)
interface = CSVModelChoiceField(
label=_('Interface'),
queryset=Interface.objects.none(), # Can also refer to VMInterface
required=False,
to_field_name='name',
help_text=_('Assigned interface (device or VM)')
)
vlan = CSVModelChoiceField(
label=_('VLAN'),
queryset=VLAN.objects.all(),
required=False,
to_field_name='name',
help_text=_('Assigned VLAN')
)
class Meta:
model = L2VPNTermination
fields = ('l2vpn', 'device', 'virtual_machine', 'interface', 'vlan', 'tags')
def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
if data:
# Limit interface queryset by device or VM
if data.get('device'):
self.fields['interface'].queryset = Interface.objects.filter(
**{f"device__{self.fields['device'].to_field_name}": data['device']}
)
elif data.get('virtual_machine'):
self.fields['interface'].queryset = VMInterface.objects.filter(
**{f"virtual_machine__{self.fields['virtual_machine'].to_field_name}": data['virtual_machine']}
)
def clean(self):
super().clean()
if self.cleaned_data.get('device') and self.cleaned_data.get('virtual_machine'):
raise ValidationError(_('Cannot import device and VM interface terminations simultaneously.'))
if not self.instance and not (self.cleaned_data.get('interface') or self.cleaned_data.get('vlan')):
raise ValidationError(_('Each termination must specify either an interface or a VLAN.'))
if self.cleaned_data.get('interface') and self.cleaned_data.get('vlan'):
raise ValidationError(_('Cannot assign both an interface and a VLAN.'))
# if this is an update we might not have interface or vlan in the form data
if self.cleaned_data.get('interface') or self.cleaned_data.get('vlan'):
self.instance.assigned_object = self.cleaned_data.get('interface') or self.cleaned_data.get('vlan')

View File

@@ -1,10 +1,18 @@
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext as _
from dcim.models import Device, Region, Site
from ipam.models import RouteTarget, VLAN
from netbox.forms import NetBoxModelFilterSetForm
from tenancy.forms import TenancyFilterForm
from utilities.forms.fields import DynamicModelMultipleChoiceField, TagFilterField
from utilities.forms.fields import (
ContentTypeMultipleChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, TagFilterField,
)
from utilities.forms.utils import add_blank_choice
from virtualization.models import VirtualMachine
from vpn.choices import *
from vpn.constants import L2VPN_ASSIGNMENT_MODELS
from vpn.models import *
__all__ = (
@@ -13,6 +21,8 @@ __all__ = (
'IPSecPolicyFilterForm',
'IPSecProfileFilterForm',
'IPSecProposalFilterForm',
'L2VPNFilterForm',
'L2VPNTerminationFilterForm',
'TunnelFilterForm',
'TunnelTerminationFilterForm',
)
@@ -180,3 +190,90 @@ class IPSecProfileFilterForm(NetBoxModelFilterSetForm):
label=_('IPSec policy')
)
tag = TagFilterField(model)
class L2VPNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
model = L2VPN
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
(_('Attributes'), ('type', 'import_target_id', 'export_target_id')),
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
)
type = forms.ChoiceField(
label=_('Type'),
choices=add_blank_choice(L2VPNTypeChoices),
required=False
)
import_target_id = DynamicModelMultipleChoiceField(
queryset=RouteTarget.objects.all(),
required=False,
label=_('Import targets')
)
export_target_id = DynamicModelMultipleChoiceField(
queryset=RouteTarget.objects.all(),
required=False,
label=_('Export targets')
)
tag = TagFilterField(model)
class L2VPNTerminationFilterForm(NetBoxModelFilterSetForm):
model = L2VPNTermination
fieldsets = (
(None, ('filter_id', 'l2vpn_id',)),
(_('Assigned Object'), (
'assigned_object_type_id', 'region_id', 'site_id', 'device_id', 'virtual_machine_id', 'vlan_id',
)),
)
l2vpn_id = DynamicModelChoiceField(
queryset=L2VPN.objects.all(),
required=False,
label=_('L2VPN')
)
assigned_object_type_id = ContentTypeMultipleChoiceField(
queryset=ContentType.objects.filter(L2VPN_ASSIGNMENT_MODELS),
required=False,
label=_('Assigned Object Type'),
limit_choices_to=L2VPN_ASSIGNMENT_MODELS
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
required=False,
null_option='None',
query_params={
'region_id': '$region_id'
},
label=_('Site')
)
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
required=False,
null_option='None',
query_params={
'site_id': '$site_id'
},
label=_('Device')
)
vlan_id = DynamicModelMultipleChoiceField(
queryset=VLAN.objects.all(),
required=False,
null_option='None',
query_params={
'site_id': '$site_id'
},
label=_('VLAN')
)
virtual_machine_id = DynamicModelMultipleChoiceField(
queryset=VirtualMachine.objects.all(),
required=False,
null_option='None',
query_params={
'site_id': '$site_id'
},
label=_('Virtual Machine')
)

View File

@@ -1,11 +1,12 @@
from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from dcim.models import Device, Interface
from ipam.models import IPAddress
from ipam.models import IPAddress, RouteTarget, VLAN
from netbox.forms import NetBoxModelForm
from tenancy.forms import TenancyForm
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField
from utilities.forms.utils import add_blank_choice
from utilities.forms.widgets import HTMXSelect
from virtualization.models import VirtualMachine, VMInterface
@@ -18,6 +19,8 @@ __all__ = (
'IPSecPolicyForm',
'IPSecProfileForm',
'IPSecProposalForm',
'L2VPNForm',
'L2VPNTerminationForm',
'TunnelCreateForm',
'TunnelForm',
'TunnelTerminationForm',
@@ -355,3 +358,96 @@ class IPSecProfileForm(NetBoxModelForm):
fields = [
'name', 'description', 'mode', 'ike_policy', 'ipsec_policy', 'description', 'comments', 'tags',
]
#
# L2VPN
#
class L2VPNForm(TenancyForm, NetBoxModelForm):
slug = SlugField()
import_targets = DynamicModelMultipleChoiceField(
label=_('Import targets'),
queryset=RouteTarget.objects.all(),
required=False
)
export_targets = DynamicModelMultipleChoiceField(
label=_('Export targets'),
queryset=RouteTarget.objects.all(),
required=False
)
comments = CommentField()
fieldsets = (
(_('L2VPN'), ('name', 'slug', 'type', 'identifier', 'description', 'tags')),
(_('Route Targets'), ('import_targets', 'export_targets')),
(_('Tenancy'), ('tenant_group', 'tenant')),
)
class Meta:
model = L2VPN
fields = (
'name', 'slug', 'type', 'identifier', 'import_targets', 'export_targets', 'tenant', 'description',
'comments', 'tags'
)
class L2VPNTerminationForm(NetBoxModelForm):
l2vpn = DynamicModelChoiceField(
queryset=L2VPN.objects.all(),
required=True,
query_params={},
label=_('L2VPN'),
fetch_trigger='open'
)
vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(),
required=False,
selector=True,
label=_('VLAN')
)
interface = DynamicModelChoiceField(
label=_('Interface'),
queryset=Interface.objects.all(),
required=False,
selector=True
)
vminterface = DynamicModelChoiceField(
queryset=VMInterface.objects.all(),
required=False,
selector=True,
label=_('Interface')
)
class Meta:
model = L2VPNTermination
fields = ('l2vpn', )
def __init__(self, *args, **kwargs):
instance = kwargs.get('instance')
initial = kwargs.get('initial', {}).copy()
if instance:
if type(instance.assigned_object) is Interface:
initial['interface'] = instance.assigned_object
elif type(instance.assigned_object) is VLAN:
initial['vlan'] = instance.assigned_object
elif type(instance.assigned_object) is VMInterface:
initial['vminterface'] = instance.assigned_object
kwargs['initial'] = initial
super().__init__(*args, **kwargs)
def clean(self):
super().clean()
interface = self.cleaned_data.get('interface')
vminterface = self.cleaned_data.get('vminterface')
vlan = self.cleaned_data.get('vlan')
if not (interface or vminterface or vlan):
raise ValidationError(_('A termination must specify an interface or VLAN.'))
if len([x for x in (interface, vminterface, vlan) if x]) > 1:
raise ValidationError(_('A termination can only have one terminating object (an interface or VLAN).'))
self.instance.assigned_object = interface or vminterface or vlan