mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 17:38:37 -06:00
* 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:
parent
8e7146cd06
commit
d2fea4edc4
@ -2,8 +2,8 @@ import decimal
|
|||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from drf_spectacular.utils import extend_schema_field
|
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from timezone_field.rest_framework import TimeZoneSerializerField
|
from timezone_field.rest_framework import TimeZoneSerializerField
|
||||||
|
|
||||||
@ -12,8 +12,7 @@ from dcim.constants import *
|
|||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from extras.api.nested_serializers import NestedConfigTemplateSerializer
|
from extras.api.nested_serializers import NestedConfigTemplateSerializer
|
||||||
from ipam.api.nested_serializers import (
|
from ipam.api.nested_serializers import (
|
||||||
NestedASNSerializer, NestedIPAddressSerializer, NestedL2VPNTerminationSerializer, NestedVLANSerializer,
|
NestedASNSerializer, NestedIPAddressSerializer, NestedVLANSerializer, NestedVRFSerializer,
|
||||||
NestedVRFSerializer,
|
|
||||||
)
|
)
|
||||||
from ipam.models import ASN, VLAN
|
from ipam.models import ASN, VLAN
|
||||||
from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField
|
from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField
|
||||||
@ -27,6 +26,7 @@ from tenancy.api.nested_serializers import NestedTenantSerializer
|
|||||||
from users.api.nested_serializers import NestedUserSerializer
|
from users.api.nested_serializers import NestedUserSerializer
|
||||||
from utilities.api import get_serializer_for_model
|
from utilities.api import get_serializer_for_model
|
||||||
from virtualization.api.nested_serializers import NestedClusterSerializer
|
from virtualization.api.nested_serializers import NestedClusterSerializer
|
||||||
|
from vpn.api.nested_serializers import NestedL2VPNTerminationSerializer
|
||||||
from wireless.api.nested_serializers import NestedWirelessLANSerializer, NestedWirelessLinkSerializer
|
from wireless.api.nested_serializers import NestedWirelessLANSerializer, NestedWirelessLinkSerializer
|
||||||
from wireless.choices import *
|
from wireless.choices import *
|
||||||
from wireless.models import WirelessLAN
|
from wireless.models import WirelessLAN
|
||||||
|
@ -5,7 +5,7 @@ from django.utils.translation import gettext as _
|
|||||||
from extras.filtersets import LocalConfigContextFilterSet
|
from extras.filtersets import LocalConfigContextFilterSet
|
||||||
from extras.models import ConfigTemplate
|
from extras.models import ConfigTemplate
|
||||||
from ipam.filtersets import PrimaryIPFilterSet
|
from ipam.filtersets import PrimaryIPFilterSet
|
||||||
from ipam.models import ASN, L2VPN, IPAddress, VRF
|
from ipam.models import ASN, IPAddress, VRF
|
||||||
from netbox.filtersets import (
|
from netbox.filtersets import (
|
||||||
BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet,
|
BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet,
|
||||||
)
|
)
|
||||||
@ -17,6 +17,7 @@ from utilities.filters import (
|
|||||||
TreeNodeMultipleChoiceFilter,
|
TreeNodeMultipleChoiceFilter,
|
||||||
)
|
)
|
||||||
from virtualization.models import Cluster
|
from virtualization.models import Cluster
|
||||||
|
from vpn.models import L2VPN
|
||||||
from wireless.choices import WirelessRoleChoices, WirelessChannelChoices
|
from wireless.choices import WirelessRoleChoices, WirelessChannelChoices
|
||||||
from .choices import *
|
from .choices import *
|
||||||
from .constants import *
|
from .constants import *
|
||||||
|
@ -7,12 +7,13 @@ from dcim.constants import *
|
|||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from extras.forms import LocalConfigContextFilterForm
|
from extras.forms import LocalConfigContextFilterForm
|
||||||
from extras.models import ConfigTemplate
|
from extras.models import ConfigTemplate
|
||||||
from ipam.models import ASN, L2VPN, VRF
|
from ipam.models import ASN, VRF
|
||||||
from netbox.forms import NetBoxModelFilterSetForm
|
from netbox.forms import NetBoxModelFilterSetForm
|
||||||
from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
|
from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
|
||||||
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice
|
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice
|
||||||
from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField
|
from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField
|
||||||
from utilities.forms.widgets import APISelectMultiple, NumberWithOptions
|
from utilities.forms.widgets import APISelectMultiple, NumberWithOptions
|
||||||
|
from vpn.models import L2VPN
|
||||||
from wireless.choices import *
|
from wireless.choices import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
@ -730,7 +730,7 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
|
|||||||
related_query_name='interface'
|
related_query_name='interface'
|
||||||
)
|
)
|
||||||
l2vpn_terminations = GenericRelation(
|
l2vpn_terminations = GenericRelation(
|
||||||
to='ipam.L2VPNTermination',
|
to='vpn.L2VPNTermination',
|
||||||
content_type_field='assigned_object_type',
|
content_type_field='assigned_object_type',
|
||||||
object_id_field='assigned_object_id',
|
object_id_field='assigned_object_id',
|
||||||
related_query_name='interface',
|
related_query_name='interface',
|
||||||
|
@ -316,8 +316,8 @@ INTERFACE_BUTTONS = """
|
|||||||
{% if perms.dcim.add_interface %}
|
{% if perms.dcim.add_interface %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:interface_add' %}?device={{ record.device_id }}&parent={{ record.pk }}&name={{ record.name }}.&type=virtual&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Child Interface</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:interface_add' %}?device={{ record.device_id }}&parent={{ record.pk }}&name={{ record.name }}.&type=virtual&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Child Interface</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.ipam.add_l2vpntermination %}
|
{% if perms.vpn.add_l2vpntermination %}
|
||||||
<li><a class="dropdown-item" href="{% url 'ipam:l2vpntermination_add' %}?device={{ object.pk }}&interface={{ record.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">L2VPN Termination</a></li>
|
<li><a class="dropdown-item" href="{% url 'vpn:l2vpntermination_add' %}?device={{ object.pk }}&interface={{ record.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">L2VPN Termination</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.ipam.add_fhrpgroupassignment %}
|
{% if perms.ipam.add_fhrpgroupassignment %}
|
||||||
<li><a class="dropdown-item" href="{% url 'ipam:fhrpgroupassignment_add' %}?interface_type={{ record|content_type_id }}&interface_id={{ record.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Assign FHRP Group</a></li>
|
<li><a class="dropdown-item" href="{% url 'ipam:fhrpgroupassignment_add' %}?interface_type={{ record|content_type_id }}&interface_id={{ record.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Assign FHRP Group</a></li>
|
||||||
|
@ -2,7 +2,6 @@ from drf_spectacular.utils import extend_schema_serializer
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from ipam import models
|
from ipam import models
|
||||||
from ipam.models.l2vpn import L2VPNTermination, L2VPN
|
|
||||||
from netbox.api.serializers import WritableNestedSerializer
|
from netbox.api.serializers import WritableNestedSerializer
|
||||||
from .field_serializers import IPAddressField
|
from .field_serializers import IPAddressField
|
||||||
|
|
||||||
@ -14,8 +13,6 @@ __all__ = [
|
|||||||
'NestedFHRPGroupAssignmentSerializer',
|
'NestedFHRPGroupAssignmentSerializer',
|
||||||
'NestedIPAddressSerializer',
|
'NestedIPAddressSerializer',
|
||||||
'NestedIPRangeSerializer',
|
'NestedIPRangeSerializer',
|
||||||
'NestedL2VPNSerializer',
|
|
||||||
'NestedL2VPNTerminationSerializer',
|
|
||||||
'NestedPrefixSerializer',
|
'NestedPrefixSerializer',
|
||||||
'NestedRIRSerializer',
|
'NestedRIRSerializer',
|
||||||
'NestedRoleSerializer',
|
'NestedRoleSerializer',
|
||||||
@ -223,28 +220,3 @@ class NestedServiceSerializer(WritableNestedSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = models.Service
|
model = models.Service
|
||||||
fields = ['id', 'url', 'display', 'name', 'protocol', 'ports']
|
fields = ['id', 'url', 'display', 'name', 'protocol', 'ports']
|
||||||
|
|
||||||
#
|
|
||||||
# L2VPN
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
class NestedL2VPNSerializer(WritableNestedSerializer):
|
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:l2vpn-detail')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = L2VPN
|
|
||||||
fields = [
|
|
||||||
'id', 'url', 'display', 'identifier', 'name', 'slug', 'type'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class NestedL2VPNTerminationSerializer(WritableNestedSerializer):
|
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:l2vpntermination-detail')
|
|
||||||
l2vpn = NestedL2VPNSerializer()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = L2VPNTermination
|
|
||||||
fields = [
|
|
||||||
'id', 'url', 'display', 'l2vpn'
|
|
||||||
]
|
|
||||||
|
@ -12,8 +12,9 @@ from netbox.constants import NESTED_SERIALIZER_PREFIX
|
|||||||
from tenancy.api.nested_serializers import NestedTenantSerializer
|
from tenancy.api.nested_serializers import NestedTenantSerializer
|
||||||
from utilities.api import get_serializer_for_model
|
from utilities.api import get_serializer_for_model
|
||||||
from virtualization.api.nested_serializers import NestedVirtualMachineSerializer
|
from virtualization.api.nested_serializers import NestedVirtualMachineSerializer
|
||||||
from .nested_serializers import *
|
from vpn.api.nested_serializers import NestedL2VPNTerminationSerializer
|
||||||
from .field_serializers import IPAddressField, IPNetworkField
|
from .field_serializers import IPAddressField, IPNetworkField
|
||||||
|
from .nested_serializers import *
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -479,54 +480,3 @@ class ServiceSerializer(NetBoxModelSerializer):
|
|||||||
'id', 'url', 'display', 'device', 'virtual_machine', 'name', 'ports', 'protocol', 'ipaddresses',
|
'id', 'url', 'display', 'device', 'virtual_machine', 'name', 'ports', 'protocol', 'ipaddresses',
|
||||||
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
|
|
||||||
#
|
|
||||||
# L2VPN
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
class L2VPNSerializer(NetBoxModelSerializer):
|
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:l2vpn-detail')
|
|
||||||
type = ChoiceField(choices=L2VPNTypeChoices, required=False)
|
|
||||||
import_targets = SerializedPKRelatedField(
|
|
||||||
queryset=RouteTarget.objects.all(),
|
|
||||||
serializer=NestedRouteTargetSerializer,
|
|
||||||
required=False,
|
|
||||||
many=True
|
|
||||||
)
|
|
||||||
export_targets = SerializedPKRelatedField(
|
|
||||||
queryset=RouteTarget.objects.all(),
|
|
||||||
serializer=NestedRouteTargetSerializer,
|
|
||||||
required=False,
|
|
||||||
many=True
|
|
||||||
)
|
|
||||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = L2VPN
|
|
||||||
fields = [
|
|
||||||
'id', 'url', 'display', 'identifier', 'name', 'slug', 'type', 'import_targets', 'export_targets',
|
|
||||||
'description', 'comments', 'tenant', 'tags', 'custom_fields', 'created', 'last_updated'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTerminationSerializer(NetBoxModelSerializer):
|
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:l2vpntermination-detail')
|
|
||||||
l2vpn = NestedL2VPNSerializer()
|
|
||||||
assigned_object_type = ContentTypeField(
|
|
||||||
queryset=ContentType.objects.all()
|
|
||||||
)
|
|
||||||
assigned_object = serializers.SerializerMethodField(read_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = L2VPNTermination
|
|
||||||
fields = [
|
|
||||||
'id', 'url', 'display', 'l2vpn', 'assigned_object_type', 'assigned_object_id',
|
|
||||||
'assigned_object', 'tags', 'custom_fields', 'created', 'last_updated'
|
|
||||||
]
|
|
||||||
|
|
||||||
@extend_schema_field(serializers.JSONField(allow_null=True))
|
|
||||||
def get_assigned_object(self, instance):
|
|
||||||
serializer = get_serializer_for_model(instance.assigned_object, prefix=NESTED_SERIALIZER_PREFIX)
|
|
||||||
context = {'request': self.context['request']}
|
|
||||||
return serializer(instance.assigned_object, context=context).data
|
|
||||||
|
@ -23,8 +23,6 @@ router.register('vlan-groups', views.VLANGroupViewSet)
|
|||||||
router.register('vlans', views.VLANViewSet)
|
router.register('vlans', views.VLANViewSet)
|
||||||
router.register('service-templates', views.ServiceTemplateViewSet)
|
router.register('service-templates', views.ServiceTemplateViewSet)
|
||||||
router.register('services', views.ServiceViewSet)
|
router.register('services', views.ServiceViewSet)
|
||||||
router.register('l2vpns', views.L2VPNViewSet)
|
|
||||||
router.register('l2vpn-terminations', views.L2VPNTerminationViewSet)
|
|
||||||
|
|
||||||
app_name = 'ipam-api'
|
app_name = 'ipam-api'
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@ from circuits.models import Provider
|
|||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from ipam import filtersets
|
from ipam import filtersets
|
||||||
from ipam.models import *
|
from ipam.models import *
|
||||||
from ipam.models import L2VPN, L2VPNTermination
|
|
||||||
from ipam.utils import get_next_available_prefix
|
from ipam.utils import get_next_available_prefix
|
||||||
from netbox.api.viewsets import NetBoxModelViewSet
|
from netbox.api.viewsets import NetBoxModelViewSet
|
||||||
from netbox.api.viewsets.mixins import ObjectValidationMixin
|
from netbox.api.viewsets.mixins import ObjectValidationMixin
|
||||||
@ -178,18 +177,6 @@ class ServiceViewSet(NetBoxModelViewSet):
|
|||||||
filterset_class = filtersets.ServiceFilterSet
|
filterset_class = filtersets.ServiceFilterSet
|
||||||
|
|
||||||
|
|
||||||
class L2VPNViewSet(NetBoxModelViewSet):
|
|
||||||
queryset = L2VPN.objects.prefetch_related('import_targets', 'export_targets', 'tenant', 'tags')
|
|
||||||
serializer_class = serializers.L2VPNSerializer
|
|
||||||
filterset_class = filtersets.L2VPNFilterSet
|
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTerminationViewSet(NetBoxModelViewSet):
|
|
||||||
queryset = L2VPNTermination.objects.prefetch_related('assigned_object')
|
|
||||||
serializer_class = serializers.L2VPNTerminationSerializer
|
|
||||||
filterset_class = filtersets.L2VPNTerminationFilterSet
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Views
|
# Views
|
||||||
#
|
#
|
||||||
|
@ -172,52 +172,3 @@ class ServiceProtocolChoices(ChoiceSet):
|
|||||||
(PROTOCOL_UDP, 'UDP'),
|
(PROTOCOL_UDP, 'UDP'),
|
||||||
(PROTOCOL_SCTP, 'SCTP'),
|
(PROTOCOL_SCTP, 'SCTP'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTypeChoices(ChoiceSet):
|
|
||||||
TYPE_VPLS = 'vpls'
|
|
||||||
TYPE_VPWS = 'vpws'
|
|
||||||
TYPE_EPL = 'epl'
|
|
||||||
TYPE_EVPL = 'evpl'
|
|
||||||
TYPE_EPLAN = 'ep-lan'
|
|
||||||
TYPE_EVPLAN = 'evp-lan'
|
|
||||||
TYPE_EPTREE = 'ep-tree'
|
|
||||||
TYPE_EVPTREE = 'evp-tree'
|
|
||||||
TYPE_VXLAN = 'vxlan'
|
|
||||||
TYPE_VXLAN_EVPN = 'vxlan-evpn'
|
|
||||||
TYPE_MPLS_EVPN = 'mpls-evpn'
|
|
||||||
TYPE_PBB_EVPN = 'pbb-evpn'
|
|
||||||
|
|
||||||
CHOICES = (
|
|
||||||
('VPLS', (
|
|
||||||
(TYPE_VPWS, 'VPWS'),
|
|
||||||
(TYPE_VPLS, 'VPLS'),
|
|
||||||
)),
|
|
||||||
('VXLAN', (
|
|
||||||
(TYPE_VXLAN, 'VXLAN'),
|
|
||||||
(TYPE_VXLAN_EVPN, 'VXLAN-EVPN'),
|
|
||||||
)),
|
|
||||||
('L2VPN E-VPN', (
|
|
||||||
(TYPE_MPLS_EVPN, 'MPLS EVPN'),
|
|
||||||
(TYPE_PBB_EVPN, 'PBB EVPN'),
|
|
||||||
)),
|
|
||||||
('E-Line', (
|
|
||||||
(TYPE_EPL, 'EPL'),
|
|
||||||
(TYPE_EVPL, 'EVPL'),
|
|
||||||
)),
|
|
||||||
('E-LAN', (
|
|
||||||
(TYPE_EPLAN, 'Ethernet Private LAN'),
|
|
||||||
(TYPE_EVPLAN, 'Ethernet Virtual Private LAN'),
|
|
||||||
)),
|
|
||||||
('E-Tree', (
|
|
||||||
(TYPE_EPTREE, 'Ethernet Private Tree'),
|
|
||||||
(TYPE_EVPTREE, 'Ethernet Virtual Private Tree'),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
|
|
||||||
P2P = (
|
|
||||||
TYPE_VPWS,
|
|
||||||
TYPE_EPL,
|
|
||||||
TYPE_EPLAN,
|
|
||||||
TYPE_EPTREE
|
|
||||||
)
|
|
||||||
|
@ -86,9 +86,3 @@ VLANGROUP_SCOPE_TYPES = (
|
|||||||
# 16-bit port number
|
# 16-bit port number
|
||||||
SERVICE_PORT_MIN = 1
|
SERVICE_PORT_MIN = 1
|
||||||
SERVICE_PORT_MAX = 65535
|
SERVICE_PORT_MAX = 65535
|
||||||
|
|
||||||
L2VPN_ASSIGNMENT_MODELS = Q(
|
|
||||||
Q(app_label='dcim', model='interface') |
|
|
||||||
Q(app_label='ipam', model='vlan') |
|
|
||||||
Q(app_label='virtualization', model='vminterface')
|
|
||||||
)
|
|
||||||
|
@ -4,8 +4,8 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from drf_spectacular.utils import extend_schema_field
|
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from netaddr.core import AddrFormatError
|
from netaddr.core import AddrFormatError
|
||||||
|
|
||||||
from dcim.models import Device, Interface, Region, Site, SiteGroup
|
from dcim.models import Device, Interface, Region, Site, SiteGroup
|
||||||
@ -15,6 +15,7 @@ from utilities.filters import (
|
|||||||
ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NumericArrayFilter, TreeNodeMultipleChoiceFilter,
|
ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NumericArrayFilter, TreeNodeMultipleChoiceFilter,
|
||||||
)
|
)
|
||||||
from virtualization.models import VirtualMachine, VMInterface
|
from virtualization.models import VirtualMachine, VMInterface
|
||||||
|
from vpn.models import L2VPN
|
||||||
from .choices import *
|
from .choices import *
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
@ -26,8 +27,6 @@ __all__ = (
|
|||||||
'FHRPGroupFilterSet',
|
'FHRPGroupFilterSet',
|
||||||
'IPAddressFilterSet',
|
'IPAddressFilterSet',
|
||||||
'IPRangeFilterSet',
|
'IPRangeFilterSet',
|
||||||
'L2VPNFilterSet',
|
|
||||||
'L2VPNTerminationFilterSet',
|
|
||||||
'PrefixFilterSet',
|
'PrefixFilterSet',
|
||||||
'PrimaryIPFilterSet',
|
'PrimaryIPFilterSet',
|
||||||
'RIRFilterSet',
|
'RIRFilterSet',
|
||||||
@ -1059,182 +1058,6 @@ class ServiceFilterSet(NetBoxModelFilterSet):
|
|||||||
return queryset.filter(qs_filter)
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# L2VPN
|
|
||||||
#
|
|
||||||
|
|
||||||
class L2VPNFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|
||||||
type = django_filters.MultipleChoiceFilter(
|
|
||||||
choices=L2VPNTypeChoices,
|
|
||||||
null_value=None
|
|
||||||
)
|
|
||||||
import_target_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='import_targets',
|
|
||||||
queryset=RouteTarget.objects.all(),
|
|
||||||
label=_('Import target'),
|
|
||||||
)
|
|
||||||
import_target = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='import_targets__name',
|
|
||||||
queryset=RouteTarget.objects.all(),
|
|
||||||
to_field_name='name',
|
|
||||||
label=_('Import target (name)'),
|
|
||||||
)
|
|
||||||
export_target_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='export_targets',
|
|
||||||
queryset=RouteTarget.objects.all(),
|
|
||||||
label=_('Export target'),
|
|
||||||
)
|
|
||||||
export_target = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='export_targets__name',
|
|
||||||
queryset=RouteTarget.objects.all(),
|
|
||||||
to_field_name='name',
|
|
||||||
label=_('Export target (name)'),
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = L2VPN
|
|
||||||
fields = ['id', 'identifier', 'name', 'slug', 'type', 'description']
|
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
|
||||||
if not value.strip():
|
|
||||||
return queryset
|
|
||||||
qs_filter = Q(name__icontains=value) | Q(description__icontains=value)
|
|
||||||
try:
|
|
||||||
qs_filter |= Q(identifier=int(value))
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
return queryset.filter(qs_filter)
|
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTerminationFilterSet(NetBoxModelFilterSet):
|
|
||||||
l2vpn_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
queryset=L2VPN.objects.all(),
|
|
||||||
label=_('L2VPN (ID)'),
|
|
||||||
)
|
|
||||||
l2vpn = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='l2vpn__slug',
|
|
||||||
queryset=L2VPN.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label=_('L2VPN (slug)'),
|
|
||||||
)
|
|
||||||
region = MultiValueCharFilter(
|
|
||||||
method='filter_region',
|
|
||||||
field_name='slug',
|
|
||||||
label=_('Region (slug)'),
|
|
||||||
)
|
|
||||||
region_id = MultiValueNumberFilter(
|
|
||||||
method='filter_region',
|
|
||||||
field_name='pk',
|
|
||||||
label=_('Region (ID)'),
|
|
||||||
)
|
|
||||||
site = MultiValueCharFilter(
|
|
||||||
method='filter_site',
|
|
||||||
field_name='slug',
|
|
||||||
label=_('Site (slug)'),
|
|
||||||
)
|
|
||||||
site_id = MultiValueNumberFilter(
|
|
||||||
method='filter_site',
|
|
||||||
field_name='pk',
|
|
||||||
label=_('Site (ID)'),
|
|
||||||
)
|
|
||||||
device = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='interface__device__name',
|
|
||||||
queryset=Device.objects.all(),
|
|
||||||
to_field_name='name',
|
|
||||||
label=_('Device (name)'),
|
|
||||||
)
|
|
||||||
device_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='interface__device',
|
|
||||||
queryset=Device.objects.all(),
|
|
||||||
label=_('Device (ID)'),
|
|
||||||
)
|
|
||||||
virtual_machine = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='vminterface__virtual_machine__name',
|
|
||||||
queryset=VirtualMachine.objects.all(),
|
|
||||||
to_field_name='name',
|
|
||||||
label=_('Virtual machine (name)'),
|
|
||||||
)
|
|
||||||
virtual_machine_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='vminterface__virtual_machine',
|
|
||||||
queryset=VirtualMachine.objects.all(),
|
|
||||||
label=_('Virtual machine (ID)'),
|
|
||||||
)
|
|
||||||
interface = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='interface__name',
|
|
||||||
queryset=Interface.objects.all(),
|
|
||||||
to_field_name='name',
|
|
||||||
label=_('Interface (name)'),
|
|
||||||
)
|
|
||||||
interface_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='interface',
|
|
||||||
queryset=Interface.objects.all(),
|
|
||||||
label=_('Interface (ID)'),
|
|
||||||
)
|
|
||||||
vminterface = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='vminterface__name',
|
|
||||||
queryset=VMInterface.objects.all(),
|
|
||||||
to_field_name='name',
|
|
||||||
label=_('VM interface (name)'),
|
|
||||||
)
|
|
||||||
vminterface_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='vminterface',
|
|
||||||
queryset=VMInterface.objects.all(),
|
|
||||||
label=_('VM Interface (ID)'),
|
|
||||||
)
|
|
||||||
vlan = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='vlan__name',
|
|
||||||
queryset=VLAN.objects.all(),
|
|
||||||
to_field_name='name',
|
|
||||||
label=_('VLAN (name)'),
|
|
||||||
)
|
|
||||||
vlan_vid = django_filters.NumberFilter(
|
|
||||||
field_name='vlan__vid',
|
|
||||||
label=_('VLAN number (1-4094)'),
|
|
||||||
)
|
|
||||||
vlan_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='vlan',
|
|
||||||
queryset=VLAN.objects.all(),
|
|
||||||
label=_('VLAN (ID)'),
|
|
||||||
)
|
|
||||||
assigned_object_type = ContentTypeFilter()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = L2VPNTermination
|
|
||||||
fields = ('id', 'assigned_object_type_id')
|
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
|
||||||
if not value.strip():
|
|
||||||
return queryset
|
|
||||||
qs_filter = Q(l2vpn__name__icontains=value)
|
|
||||||
return queryset.filter(qs_filter)
|
|
||||||
|
|
||||||
def filter_assigned_object(self, queryset, name, value):
|
|
||||||
qs = queryset.filter(
|
|
||||||
Q(**{'{}__in'.format(name): value})
|
|
||||||
)
|
|
||||||
return qs
|
|
||||||
|
|
||||||
def filter_site(self, queryset, name, value):
|
|
||||||
qs = queryset.filter(
|
|
||||||
Q(
|
|
||||||
Q(**{'vlan__site__{}__in'.format(name): value}) |
|
|
||||||
Q(**{'interface__device__site__{}__in'.format(name): value}) |
|
|
||||||
Q(**{'vminterface__virtual_machine__site__{}__in'.format(name): value})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return qs
|
|
||||||
|
|
||||||
def filter_region(self, queryset, name, value):
|
|
||||||
qs = queryset.filter(
|
|
||||||
Q(
|
|
||||||
Q(**{'vlan__site__region__{}__in'.format(name): value}) |
|
|
||||||
Q(**{'interface__device__site__region__{}__in'.format(name): value}) |
|
|
||||||
Q(**{'vminterface__virtual_machine__site__region__{}__in'.format(name): value})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return qs
|
|
||||||
|
|
||||||
|
|
||||||
class PrimaryIPFilterSet(django_filters.FilterSet):
|
class PrimaryIPFilterSet(django_filters.FilterSet):
|
||||||
"""
|
"""
|
||||||
An inheritable FilterSet for models which support primary IP assignment.
|
An inheritable FilterSet for models which support primary IP assignment.
|
||||||
|
@ -23,8 +23,6 @@ __all__ = (
|
|||||||
'FHRPGroupBulkEditForm',
|
'FHRPGroupBulkEditForm',
|
||||||
'IPAddressBulkEditForm',
|
'IPAddressBulkEditForm',
|
||||||
'IPRangeBulkEditForm',
|
'IPRangeBulkEditForm',
|
||||||
'L2VPNBulkEditForm',
|
|
||||||
'L2VPNTerminationBulkEditForm',
|
|
||||||
'PrefixBulkEditForm',
|
'PrefixBulkEditForm',
|
||||||
'RIRBulkEditForm',
|
'RIRBulkEditForm',
|
||||||
'RoleBulkEditForm',
|
'RoleBulkEditForm',
|
||||||
@ -596,32 +594,3 @@ class ServiceTemplateBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
class ServiceBulkEditForm(ServiceTemplateBulkEditForm):
|
class ServiceBulkEditForm(ServiceTemplateBulkEditForm):
|
||||||
model = Service
|
model = Service
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from dcim.models import Device, Interface, Site
|
from dcim.models import Device, Interface, Site
|
||||||
@ -21,8 +20,6 @@ __all__ = (
|
|||||||
'FHRPGroupImportForm',
|
'FHRPGroupImportForm',
|
||||||
'IPAddressImportForm',
|
'IPAddressImportForm',
|
||||||
'IPRangeImportForm',
|
'IPRangeImportForm',
|
||||||
'L2VPNImportForm',
|
|
||||||
'L2VPNTerminationImportForm',
|
|
||||||
'PrefixImportForm',
|
'PrefixImportForm',
|
||||||
'RIRImportForm',
|
'RIRImportForm',
|
||||||
'RoleImportForm',
|
'RoleImportForm',
|
||||||
@ -529,92 +526,3 @@ class ServiceImportForm(NetBoxModelImportForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return self.cleaned_data['ipaddresses']
|
return self.cleaned_data['ipaddresses']
|
||||||
|
|
||||||
|
|
||||||
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')
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from dcim.models import Location, Rack, Region, Site, SiteGroup, Device
|
from dcim.models import Location, Rack, Region, Site, SiteGroup, Device
|
||||||
@ -9,10 +8,9 @@ from ipam.models import *
|
|||||||
from netbox.forms import NetBoxModelFilterSetForm
|
from netbox.forms import NetBoxModelFilterSetForm
|
||||||
from tenancy.forms import TenancyFilterForm
|
from tenancy.forms import TenancyFilterForm
|
||||||
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice
|
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice
|
||||||
from utilities.forms.fields import (
|
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, TagFilterField
|
||||||
ContentTypeMultipleChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, TagFilterField,
|
|
||||||
)
|
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
|
from vpn.models import L2VPN
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'AggregateFilterForm',
|
'AggregateFilterForm',
|
||||||
@ -21,8 +19,6 @@ __all__ = (
|
|||||||
'FHRPGroupFilterForm',
|
'FHRPGroupFilterForm',
|
||||||
'IPAddressFilterForm',
|
'IPAddressFilterForm',
|
||||||
'IPRangeFilterForm',
|
'IPRangeFilterForm',
|
||||||
'L2VPNFilterForm',
|
|
||||||
'L2VPNTerminationFilterForm',
|
|
||||||
'PrefixFilterForm',
|
'PrefixFilterForm',
|
||||||
'RIRFilterForm',
|
'RIRFilterForm',
|
||||||
'RoleFilterForm',
|
'RoleFilterForm',
|
||||||
@ -539,90 +535,3 @@ class ServiceFilterForm(ServiceTemplateFilterForm):
|
|||||||
label=_('Virtual Machine'),
|
label=_('Virtual Machine'),
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
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')
|
|
||||||
)
|
|
||||||
|
@ -29,8 +29,6 @@ __all__ = (
|
|||||||
'IPAddressBulkAddForm',
|
'IPAddressBulkAddForm',
|
||||||
'IPAddressForm',
|
'IPAddressForm',
|
||||||
'IPRangeForm',
|
'IPRangeForm',
|
||||||
'L2VPNForm',
|
|
||||||
'L2VPNTerminationForm',
|
|
||||||
'PrefixForm',
|
'PrefixForm',
|
||||||
'RIRForm',
|
'RIRForm',
|
||||||
'RoleForm',
|
'RoleForm',
|
||||||
@ -754,97 +752,3 @@ class ServiceCreateForm(ServiceForm):
|
|||||||
self.cleaned_data['description'] = service_template.description
|
self.cleaned_data['description'] = service_template.description
|
||||||
elif not all(self.cleaned_data[f] for f in ('name', 'protocol', 'ports')):
|
elif not all(self.cleaned_data[f] for f in ('name', 'protocol', 'ports')):
|
||||||
raise forms.ValidationError("Must specify name, protocol, and port(s) if not using a service template.")
|
raise forms.ValidationError("Must specify name, protocol, and port(s) if not using a service template.")
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import graphene
|
import graphene
|
||||||
|
|
||||||
from ipam import models
|
from ipam import models
|
||||||
from utilities.graphql_optimizer import gql_query_optimizer
|
|
||||||
|
|
||||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||||
|
from utilities.graphql_optimizer import gql_query_optimizer
|
||||||
from .types import *
|
from .types import *
|
||||||
|
|
||||||
|
|
||||||
@ -38,18 +37,6 @@ class IPAMQuery(graphene.ObjectType):
|
|||||||
def resolve_ip_range_list(root, info, **kwargs):
|
def resolve_ip_range_list(root, info, **kwargs):
|
||||||
return gql_query_optimizer(models.IPRange.objects.all(), info)
|
return gql_query_optimizer(models.IPRange.objects.all(), info)
|
||||||
|
|
||||||
l2vpn = ObjectField(L2VPNType)
|
|
||||||
l2vpn_list = ObjectListField(L2VPNType)
|
|
||||||
|
|
||||||
def resolve_l2vpn_list(root, info, **kwargs):
|
|
||||||
return gql_query_optimizer(models.L2VPN.objects.all(), info)
|
|
||||||
|
|
||||||
l2vpn_termination = ObjectField(L2VPNTerminationType)
|
|
||||||
l2vpn_termination_list = ObjectListField(L2VPNTerminationType)
|
|
||||||
|
|
||||||
def resolve_l2vpn_termination_list(root, info, **kwargs):
|
|
||||||
return gql_query_optimizer(models.L2VPNTermination.objects.all(), info)
|
|
||||||
|
|
||||||
prefix = ObjectField(PrefixType)
|
prefix = ObjectField(PrefixType)
|
||||||
prefix_list = ObjectListField(PrefixType)
|
prefix_list = ObjectListField(PrefixType)
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import graphene
|
import graphene
|
||||||
|
|
||||||
from extras.graphql.mixins import ContactsMixin
|
|
||||||
from ipam import filtersets, models
|
from ipam import filtersets, models
|
||||||
from netbox.graphql.scalars import BigInt
|
from netbox.graphql.scalars import BigInt
|
||||||
from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType
|
from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType
|
||||||
@ -13,8 +12,6 @@ __all__ = (
|
|||||||
'FHRPGroupAssignmentType',
|
'FHRPGroupAssignmentType',
|
||||||
'IPAddressType',
|
'IPAddressType',
|
||||||
'IPRangeType',
|
'IPRangeType',
|
||||||
'L2VPNType',
|
|
||||||
'L2VPNTerminationType',
|
|
||||||
'PrefixType',
|
'PrefixType',
|
||||||
'RIRType',
|
'RIRType',
|
||||||
'RoleType',
|
'RoleType',
|
||||||
@ -188,19 +185,3 @@ class VRFType(NetBoxObjectType):
|
|||||||
model = models.VRF
|
model = models.VRF
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
filterset_class = filtersets.VRFFilterSet
|
filterset_class = filtersets.VRFFilterSet
|
||||||
|
|
||||||
|
|
||||||
class L2VPNType(ContactsMixin, NetBoxObjectType):
|
|
||||||
class Meta:
|
|
||||||
model = models.L2VPN
|
|
||||||
fields = '__all__'
|
|
||||||
filtersets_class = filtersets.L2VPNFilterSet
|
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTerminationType(NetBoxObjectType):
|
|
||||||
assigned_object = graphene.Field('ipam.graphql.gfk_mixins.L2VPNAssignmentType')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = models.L2VPNTermination
|
|
||||||
exclude = ('assigned_object_type', 'assigned_object_id')
|
|
||||||
filtersets_class = filtersets.L2VPNTerminationFilterSet
|
|
||||||
|
64
netbox/ipam/migrations/0068_move_l2vpn.py
Normal file
64
netbox/ipam/migrations/0068_move_l2vpn.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def update_content_types(apps, schema_editor):
|
||||||
|
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||||
|
|
||||||
|
# Delete the new ContentTypes effected by the new models in the vpn app
|
||||||
|
ContentType.objects.filter(app_label='vpn', model='l2vpn').delete()
|
||||||
|
ContentType.objects.filter(app_label='vpn', model='l2vpntermination').delete()
|
||||||
|
|
||||||
|
# Update the app labels of the original ContentTypes for ipam.L2VPN and ipam.L2VPNTermination to ensure
|
||||||
|
# that any foreign key references are preserved
|
||||||
|
ContentType.objects.filter(app_label='ipam', model='l2vpn').update(app_label='vpn')
|
||||||
|
ContentType.objects.filter(app_label='ipam', model='l2vpntermination').update(app_label='vpn')
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ipam', '0067_ipaddress_index_host'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveConstraint(
|
||||||
|
model_name='l2vpntermination',
|
||||||
|
name='ipam_l2vpntermination_assigned_object',
|
||||||
|
),
|
||||||
|
migrations.SeparateDatabaseAndState(
|
||||||
|
state_operations=[
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='l2vpntermination',
|
||||||
|
name='assigned_object_type',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='l2vpntermination',
|
||||||
|
name='l2vpn',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='l2vpntermination',
|
||||||
|
name='tags',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='L2VPN',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='L2VPNTermination',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
database_operations=[
|
||||||
|
migrations.AlterModelTable(
|
||||||
|
name='L2VPN',
|
||||||
|
table='vpn_l2vpn',
|
||||||
|
),
|
||||||
|
migrations.AlterModelTable(
|
||||||
|
name='L2VPNTermination',
|
||||||
|
table='vpn_l2vpntermination',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.RunPython(
|
||||||
|
code=update_content_types,
|
||||||
|
reverse_code=migrations.RunPython.noop
|
||||||
|
),
|
||||||
|
]
|
@ -3,27 +3,5 @@ from .asns import *
|
|||||||
from .fhrp import *
|
from .fhrp import *
|
||||||
from .vrfs import *
|
from .vrfs import *
|
||||||
from .ip import *
|
from .ip import *
|
||||||
from .l2vpn import *
|
|
||||||
from .services import *
|
from .services import *
|
||||||
from .vlans import *
|
from .vlans import *
|
||||||
|
|
||||||
__all__ = (
|
|
||||||
'ASN',
|
|
||||||
'ASNRange',
|
|
||||||
'Aggregate',
|
|
||||||
'IPAddress',
|
|
||||||
'IPRange',
|
|
||||||
'FHRPGroup',
|
|
||||||
'FHRPGroupAssignment',
|
|
||||||
'L2VPN',
|
|
||||||
'L2VPNTermination',
|
|
||||||
'Prefix',
|
|
||||||
'RIR',
|
|
||||||
'Role',
|
|
||||||
'RouteTarget',
|
|
||||||
'Service',
|
|
||||||
'ServiceTemplate',
|
|
||||||
'VLAN',
|
|
||||||
'VLANGroup',
|
|
||||||
'VRF',
|
|
||||||
)
|
|
||||||
|
@ -183,9 +183,8 @@ class VLAN(PrimaryModel):
|
|||||||
null=True,
|
null=True,
|
||||||
help_text=_("The primary function of this VLAN")
|
help_text=_("The primary function of this VLAN")
|
||||||
)
|
)
|
||||||
|
|
||||||
l2vpn_terminations = GenericRelation(
|
l2vpn_terminations = GenericRelation(
|
||||||
to='ipam.L2VPNTermination',
|
to='vpn.L2VPNTermination',
|
||||||
content_type_field='assigned_object_type',
|
content_type_field='assigned_object_type',
|
||||||
object_id_field='assigned_object_id',
|
object_id_field='assigned_object_id',
|
||||||
related_query_name='vlan'
|
related_query_name='vlan'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from . import models
|
|
||||||
from netbox.search import SearchIndex, register_search
|
from netbox.search import SearchIndex, register_search
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -69,18 +69,6 @@ class IPRangeIndex(SearchIndex):
|
|||||||
display_attrs = ('vrf', 'tenant', 'status', 'role', 'description')
|
display_attrs = ('vrf', 'tenant', 'status', 'role', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
|
||||||
class L2VPNIndex(SearchIndex):
|
|
||||||
model = models.L2VPN
|
|
||||||
fields = (
|
|
||||||
('name', 100),
|
|
||||||
('slug', 110),
|
|
||||||
('description', 500),
|
|
||||||
('comments', 5000),
|
|
||||||
)
|
|
||||||
display_attrs = ('type', 'identifier', 'tenant', 'description')
|
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
class PrefixIndex(SearchIndex):
|
class PrefixIndex(SearchIndex):
|
||||||
model = models.Prefix
|
model = models.Prefix
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from .asn import *
|
from .asn import *
|
||||||
from .fhrp import *
|
from .fhrp import *
|
||||||
from .ip import *
|
from .ip import *
|
||||||
from .l2vpn import *
|
|
||||||
from .services import *
|
from .services import *
|
||||||
from .vlans import *
|
from .vlans import *
|
||||||
from .vrfs import *
|
from .vrfs import *
|
||||||
|
@ -1100,96 +1100,3 @@ class ServiceTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'ports': [6],
|
'ports': [6],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTest(APIViewTestCases.APIViewTestCase):
|
|
||||||
model = L2VPN
|
|
||||||
brief_fields = ['display', 'id', 'identifier', 'name', 'slug', 'type', 'url']
|
|
||||||
create_data = [
|
|
||||||
{
|
|
||||||
'name': 'L2VPN 4',
|
|
||||||
'slug': 'l2vpn-4',
|
|
||||||
'type': 'vxlan',
|
|
||||||
'identifier': 33343344
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'L2VPN 5',
|
|
||||||
'slug': 'l2vpn-5',
|
|
||||||
'type': 'vxlan',
|
|
||||||
'identifier': 33343345
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'L2VPN 6',
|
|
||||||
'slug': 'l2vpn-6',
|
|
||||||
'type': 'vpws',
|
|
||||||
'identifier': 33343346
|
|
||||||
},
|
|
||||||
]
|
|
||||||
bulk_update_data = {
|
|
||||||
'description': 'New description',
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
|
|
||||||
l2vpns = (
|
|
||||||
L2VPN(name='L2VPN 1', slug='l2vpn-1', type='vxlan', identifier=650001),
|
|
||||||
L2VPN(name='L2VPN 2', slug='l2vpn-2', type='vpws', identifier=650002),
|
|
||||||
L2VPN(name='L2VPN 3', slug='l2vpn-3', type='vpls'), # No RD
|
|
||||||
)
|
|
||||||
L2VPN.objects.bulk_create(l2vpns)
|
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTerminationTest(APIViewTestCases.APIViewTestCase):
|
|
||||||
model = L2VPNTermination
|
|
||||||
brief_fields = ['display', 'id', 'l2vpn', 'url']
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
|
|
||||||
vlans = (
|
|
||||||
VLAN(name='VLAN 1', vid=651),
|
|
||||||
VLAN(name='VLAN 2', vid=652),
|
|
||||||
VLAN(name='VLAN 3', vid=653),
|
|
||||||
VLAN(name='VLAN 4', vid=654),
|
|
||||||
VLAN(name='VLAN 5', vid=655),
|
|
||||||
VLAN(name='VLAN 6', vid=656),
|
|
||||||
VLAN(name='VLAN 7', vid=657)
|
|
||||||
)
|
|
||||||
VLAN.objects.bulk_create(vlans)
|
|
||||||
|
|
||||||
l2vpns = (
|
|
||||||
L2VPN(name='L2VPN 1', slug='l2vpn-1', type='vxlan', identifier=650001),
|
|
||||||
L2VPN(name='L2VPN 2', slug='l2vpn-2', type='vpws', identifier=650002),
|
|
||||||
L2VPN(name='L2VPN 3', slug='l2vpn-3', type='vpls'), # No RD
|
|
||||||
)
|
|
||||||
L2VPN.objects.bulk_create(l2vpns)
|
|
||||||
|
|
||||||
l2vpnterminations = (
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[0]),
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[1]),
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[2])
|
|
||||||
)
|
|
||||||
L2VPNTermination.objects.bulk_create(l2vpnterminations)
|
|
||||||
|
|
||||||
cls.create_data = [
|
|
||||||
{
|
|
||||||
'l2vpn': l2vpns[0].pk,
|
|
||||||
'assigned_object_type': 'ipam.vlan',
|
|
||||||
'assigned_object_id': vlans[3].pk,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'l2vpn': l2vpns[0].pk,
|
|
||||||
'assigned_object_type': 'ipam.vlan',
|
|
||||||
'assigned_object_id': vlans[4].pk,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'l2vpn': l2vpns[0].pk,
|
|
||||||
'assigned_object_type': 'ipam.vlan',
|
|
||||||
'assigned_object_id': vlans[5].pk,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
cls.bulk_update_data = {
|
|
||||||
'l2vpn': l2vpns[2].pk
|
|
||||||
}
|
|
||||||
|
@ -7,9 +7,9 @@ from dcim.models import Device, DeviceRole, DeviceType, Interface, Location, Man
|
|||||||
from ipam.choices import *
|
from ipam.choices import *
|
||||||
from ipam.filtersets import *
|
from ipam.filtersets import *
|
||||||
from ipam.models import *
|
from ipam.models import *
|
||||||
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device, create_test_virtualmachine
|
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device, create_test_virtualmachine
|
||||||
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
||||||
from tenancy.models import Tenant, TenantGroup
|
|
||||||
|
|
||||||
|
|
||||||
class ASNRangeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class ASNRangeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
@ -1616,163 +1616,3 @@ class ServiceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'ipaddress': [str(ips[0].address), str(ips[1].address)]}
|
params = {'ipaddress': [str(ips[0].address), str(ips[1].address)]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|
||||||
queryset = L2VPN.objects.all()
|
|
||||||
filterset = L2VPNFilterSet
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
|
|
||||||
route_targets = (
|
|
||||||
RouteTarget(name='1:1'),
|
|
||||||
RouteTarget(name='1:2'),
|
|
||||||
RouteTarget(name='1:3'),
|
|
||||||
RouteTarget(name='2:1'),
|
|
||||||
RouteTarget(name='2:2'),
|
|
||||||
RouteTarget(name='2:3'),
|
|
||||||
)
|
|
||||||
RouteTarget.objects.bulk_create(route_targets)
|
|
||||||
|
|
||||||
l2vpns = (
|
|
||||||
L2VPN(name='L2VPN 1', slug='l2vpn-1', type=L2VPNTypeChoices.TYPE_VXLAN, identifier=65001),
|
|
||||||
L2VPN(name='L2VPN 2', slug='l2vpn-2', type=L2VPNTypeChoices.TYPE_VPWS, identifier=65002),
|
|
||||||
L2VPN(name='L2VPN 3', slug='l2vpn-3', type=L2VPNTypeChoices.TYPE_VPLS),
|
|
||||||
)
|
|
||||||
L2VPN.objects.bulk_create(l2vpns)
|
|
||||||
l2vpns[0].import_targets.add(route_targets[0])
|
|
||||||
l2vpns[1].import_targets.add(route_targets[1])
|
|
||||||
l2vpns[2].import_targets.add(route_targets[2])
|
|
||||||
l2vpns[0].export_targets.add(route_targets[3])
|
|
||||||
l2vpns[1].export_targets.add(route_targets[4])
|
|
||||||
l2vpns[2].export_targets.add(route_targets[5])
|
|
||||||
|
|
||||||
def test_name(self):
|
|
||||||
params = {'name': ['L2VPN 1', 'L2VPN 2']}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_slug(self):
|
|
||||||
params = {'slug': ['l2vpn-1', 'l2vpn-2']}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_identifier(self):
|
|
||||||
params = {'identifier': ['65001', '65002']}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_type(self):
|
|
||||||
params = {'type': [L2VPNTypeChoices.TYPE_VXLAN, L2VPNTypeChoices.TYPE_VPWS]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_import_targets(self):
|
|
||||||
route_targets = RouteTarget.objects.filter(name__in=['1:1', '1:2'])
|
|
||||||
params = {'import_target_id': [route_targets[0].pk, route_targets[1].pk]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
params = {'import_target': [route_targets[0].name, route_targets[1].name]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_export_targets(self):
|
|
||||||
route_targets = RouteTarget.objects.filter(name__in=['2:1', '2:2'])
|
|
||||||
params = {'export_target_id': [route_targets[0].pk, route_targets[1].pk]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
params = {'export_target': [route_targets[0].name, route_targets[1].name]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|
||||||
queryset = L2VPNTermination.objects.all()
|
|
||||||
filterset = L2VPNTerminationFilterSet
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
device = create_test_device('Device 1')
|
|
||||||
interfaces = (
|
|
||||||
Interface(name='Interface 1', device=device, type=InterfaceTypeChoices.TYPE_1GE_FIXED),
|
|
||||||
Interface(name='Interface 2', device=device, type=InterfaceTypeChoices.TYPE_1GE_FIXED),
|
|
||||||
Interface(name='Interface 3', device=device, type=InterfaceTypeChoices.TYPE_1GE_FIXED),
|
|
||||||
)
|
|
||||||
Interface.objects.bulk_create(interfaces)
|
|
||||||
|
|
||||||
vm = create_test_virtualmachine('Virtual Machine 1')
|
|
||||||
vminterfaces = (
|
|
||||||
VMInterface(name='Interface 1', virtual_machine=vm),
|
|
||||||
VMInterface(name='Interface 2', virtual_machine=vm),
|
|
||||||
VMInterface(name='Interface 3', virtual_machine=vm),
|
|
||||||
)
|
|
||||||
VMInterface.objects.bulk_create(vminterfaces)
|
|
||||||
|
|
||||||
vlans = (
|
|
||||||
VLAN(name='VLAN 1', vid=101),
|
|
||||||
VLAN(name='VLAN 2', vid=102),
|
|
||||||
VLAN(name='VLAN 3', vid=103),
|
|
||||||
)
|
|
||||||
VLAN.objects.bulk_create(vlans)
|
|
||||||
|
|
||||||
l2vpns = (
|
|
||||||
L2VPN(name='L2VPN 1', slug='l2vpn-1', type='vxlan', identifier=65001),
|
|
||||||
L2VPN(name='L2VPN 2', slug='l2vpn-2', type='vpws', identifier=65002),
|
|
||||||
L2VPN(name='L2VPN 3', slug='l2vpn-3', type='vpls'), # No RD,
|
|
||||||
)
|
|
||||||
L2VPN.objects.bulk_create(l2vpns)
|
|
||||||
|
|
||||||
l2vpnterminations = (
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[0]),
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[1], assigned_object=vlans[1]),
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[2], assigned_object=vlans[2]),
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=interfaces[0]),
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[1], assigned_object=interfaces[1]),
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[2], assigned_object=interfaces[2]),
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vminterfaces[0]),
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[1], assigned_object=vminterfaces[1]),
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[2], assigned_object=vminterfaces[2]),
|
|
||||||
)
|
|
||||||
L2VPNTermination.objects.bulk_create(l2vpnterminations)
|
|
||||||
|
|
||||||
def test_l2vpn(self):
|
|
||||||
l2vpns = L2VPN.objects.all()[:2]
|
|
||||||
params = {'l2vpn_id': [l2vpns[0].pk, l2vpns[1].pk]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
|
||||||
params = {'l2vpn': [l2vpns[0].slug, l2vpns[1].slug]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
|
||||||
|
|
||||||
def test_content_type(self):
|
|
||||||
params = {'assigned_object_type_id': ContentType.objects.get(model='vlan').pk}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
|
||||||
|
|
||||||
def test_interface(self):
|
|
||||||
interfaces = Interface.objects.all()[:2]
|
|
||||||
params = {'interface_id': [interfaces[0].pk, interfaces[1].pk]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_vminterface(self):
|
|
||||||
vminterfaces = VMInterface.objects.all()[:2]
|
|
||||||
params = {'vminterface_id': [vminterfaces[0].pk, vminterfaces[1].pk]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_vlan(self):
|
|
||||||
vlans = VLAN.objects.all()[:2]
|
|
||||||
params = {'vlan_id': [vlans[0].pk, vlans[1].pk]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
params = {'vlan': ['VLAN 1', 'VLAN 2']}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_site(self):
|
|
||||||
site = Site.objects.all().first()
|
|
||||||
params = {'site_id': [site.pk]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
|
||||||
params = {'site': ['site-1']}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
|
||||||
|
|
||||||
def test_device(self):
|
|
||||||
device = Device.objects.all().first()
|
|
||||||
params = {'device_id': [device.pk]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
|
||||||
params = {'device': ['Device 1']}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
|
||||||
|
|
||||||
def test_virtual_machine(self):
|
|
||||||
virtual_machine = VirtualMachine.objects.all().first()
|
|
||||||
params = {'virtual_machine_id': [virtual_machine.pk]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
|
||||||
params = {'virtual_machine': ['Virtual Machine 1']}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
from netaddr import IPNetwork, IPSet
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
|
from netaddr import IPNetwork, IPSet
|
||||||
|
|
||||||
from dcim.models import Interface, Device, DeviceRole, DeviceType, Manufacturer, Site
|
from ipam.choices import *
|
||||||
from ipam.choices import IPAddressRoleChoices, PrefixStatusChoices
|
from ipam.models import *
|
||||||
from ipam.models import Aggregate, IPAddress, IPRange, Prefix, RIR, VLAN, VLANGroup, VRF, L2VPN, L2VPNTermination
|
|
||||||
|
|
||||||
|
|
||||||
class TestAggregate(TestCase):
|
class TestAggregate(TestCase):
|
||||||
@ -539,76 +538,3 @@ class TestVLANGroup(TestCase):
|
|||||||
|
|
||||||
VLAN.objects.create(name='VLAN 104', vid=104, group=vlangroup)
|
VLAN.objects.create(name='VLAN 104', vid=104, group=vlangroup)
|
||||||
self.assertEqual(vlangroup.get_next_available_vid(), 105)
|
self.assertEqual(vlangroup.get_next_available_vid(), 105)
|
||||||
|
|
||||||
|
|
||||||
class TestL2VPNTermination(TestCase):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
|
|
||||||
site = Site.objects.create(name='Site 1')
|
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1')
|
|
||||||
device_type = DeviceType.objects.create(model='Device Type 1', manufacturer=manufacturer)
|
|
||||||
role = DeviceRole.objects.create(name='Switch')
|
|
||||||
device = Device.objects.create(
|
|
||||||
name='Device 1',
|
|
||||||
site=site,
|
|
||||||
device_type=device_type,
|
|
||||||
role=role,
|
|
||||||
status='active'
|
|
||||||
)
|
|
||||||
|
|
||||||
interfaces = (
|
|
||||||
Interface(name='Interface 1', device=device, type='1000baset'),
|
|
||||||
Interface(name='Interface 2', device=device, type='1000baset'),
|
|
||||||
Interface(name='Interface 3', device=device, type='1000baset'),
|
|
||||||
Interface(name='Interface 4', device=device, type='1000baset'),
|
|
||||||
Interface(name='Interface 5', device=device, type='1000baset'),
|
|
||||||
)
|
|
||||||
|
|
||||||
Interface.objects.bulk_create(interfaces)
|
|
||||||
|
|
||||||
vlans = (
|
|
||||||
VLAN(name='VLAN 1', vid=651),
|
|
||||||
VLAN(name='VLAN 2', vid=652),
|
|
||||||
VLAN(name='VLAN 3', vid=653),
|
|
||||||
VLAN(name='VLAN 4', vid=654),
|
|
||||||
VLAN(name='VLAN 5', vid=655),
|
|
||||||
VLAN(name='VLAN 6', vid=656),
|
|
||||||
VLAN(name='VLAN 7', vid=657)
|
|
||||||
)
|
|
||||||
|
|
||||||
VLAN.objects.bulk_create(vlans)
|
|
||||||
|
|
||||||
l2vpns = (
|
|
||||||
L2VPN(name='L2VPN 1', slug='l2vpn-1', type='vxlan', identifier=650001),
|
|
||||||
L2VPN(name='L2VPN 2', slug='l2vpn-2', type='vpws', identifier=650002),
|
|
||||||
L2VPN(name='L2VPN 3', slug='l2vpn-3', type='vpls'), # No RD
|
|
||||||
)
|
|
||||||
L2VPN.objects.bulk_create(l2vpns)
|
|
||||||
|
|
||||||
l2vpnterminations = (
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[0]),
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[1]),
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[2])
|
|
||||||
)
|
|
||||||
|
|
||||||
L2VPNTermination.objects.bulk_create(l2vpnterminations)
|
|
||||||
|
|
||||||
def test_duplicate_interface_terminations(self):
|
|
||||||
device = Device.objects.first()
|
|
||||||
interface = Interface.objects.filter(device=device).first()
|
|
||||||
l2vpn = L2VPN.objects.first()
|
|
||||||
|
|
||||||
L2VPNTermination.objects.create(l2vpn=l2vpn, assigned_object=interface)
|
|
||||||
duplicate = L2VPNTermination(l2vpn=l2vpn, assigned_object=interface)
|
|
||||||
|
|
||||||
self.assertRaises(ValidationError, duplicate.clean)
|
|
||||||
|
|
||||||
def test_duplicate_vlan_terminations(self):
|
|
||||||
vlan = Interface.objects.first()
|
|
||||||
l2vpn = L2VPN.objects.first()
|
|
||||||
|
|
||||||
L2VPNTermination.objects.create(l2vpn=l2vpn, assigned_object=vlan)
|
|
||||||
duplicate = L2VPNTermination(l2vpn=l2vpn, assigned_object=vlan)
|
|
||||||
self.assertRaises(ValidationError, duplicate.clean)
|
|
||||||
|
@ -9,7 +9,7 @@ from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site, Inte
|
|||||||
from ipam.choices import *
|
from ipam.choices import *
|
||||||
from ipam.models import *
|
from ipam.models import *
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.testing import ViewTestCases, create_test_device, create_tags
|
from utilities.testing import ViewTestCases, create_tags
|
||||||
|
|
||||||
|
|
||||||
class ASNRangeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
class ASNRangeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||||
@ -986,142 +986,3 @@ class ServiceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
self.assertEqual(instance.protocol, service_template.protocol)
|
self.assertEqual(instance.protocol, service_template.protocol)
|
||||||
self.assertEqual(instance.ports, service_template.ports)
|
self.assertEqual(instance.ports, service_template.ports)
|
||||||
self.assertEqual(instance.description, service_template.description)
|
self.assertEqual(instance.description, service_template.description)
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
||||||
model = L2VPN
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
rts = (
|
|
||||||
RouteTarget(name='64534:123'),
|
|
||||||
RouteTarget(name='64534:321')
|
|
||||||
)
|
|
||||||
RouteTarget.objects.bulk_create(rts)
|
|
||||||
|
|
||||||
l2vpns = (
|
|
||||||
L2VPN(name='L2VPN 1', slug='l2vpn-1', type=L2VPNTypeChoices.TYPE_VXLAN, identifier='650001'),
|
|
||||||
L2VPN(name='L2VPN 2', slug='l2vpn-2', type=L2VPNTypeChoices.TYPE_VXLAN, identifier='650002'),
|
|
||||||
L2VPN(name='L2VPN 3', slug='l2vpn-3', type=L2VPNTypeChoices.TYPE_VXLAN, identifier='650003')
|
|
||||||
)
|
|
||||||
L2VPN.objects.bulk_create(l2vpns)
|
|
||||||
|
|
||||||
cls.csv_data = (
|
|
||||||
'name,slug,type,identifier',
|
|
||||||
'L2VPN 5,l2vpn-5,vxlan,456',
|
|
||||||
'L2VPN 6,l2vpn-6,vxlan,444',
|
|
||||||
)
|
|
||||||
|
|
||||||
cls.csv_update_data = (
|
|
||||||
'id,name,description',
|
|
||||||
f'{l2vpns[0].pk},L2VPN 7,New description 7',
|
|
||||||
f'{l2vpns[1].pk},L2VPN 8,New description 8',
|
|
||||||
)
|
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
|
||||||
'description': 'New Description',
|
|
||||||
}
|
|
||||||
|
|
||||||
cls.form_data = {
|
|
||||||
'name': 'L2VPN 8',
|
|
||||||
'slug': 'l2vpn-8',
|
|
||||||
'type': L2VPNTypeChoices.TYPE_VXLAN,
|
|
||||||
'identifier': 123,
|
|
||||||
'description': 'Description',
|
|
||||||
'import_targets': [rts[0].pk],
|
|
||||||
'export_targets': [rts[1].pk]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTerminationTestCase(
|
|
||||||
ViewTestCases.GetObjectViewTestCase,
|
|
||||||
ViewTestCases.GetObjectChangelogViewTestCase,
|
|
||||||
ViewTestCases.CreateObjectViewTestCase,
|
|
||||||
ViewTestCases.EditObjectViewTestCase,
|
|
||||||
ViewTestCases.DeleteObjectViewTestCase,
|
|
||||||
ViewTestCases.ListObjectsViewTestCase,
|
|
||||||
ViewTestCases.BulkImportObjectsViewTestCase,
|
|
||||||
ViewTestCases.BulkDeleteObjectsViewTestCase,
|
|
||||||
):
|
|
||||||
|
|
||||||
model = L2VPNTermination
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
device = create_test_device('Device 1')
|
|
||||||
interface = Interface.objects.create(name='Interface 1', device=device, type='1000baset')
|
|
||||||
l2vpns = (
|
|
||||||
L2VPN(name='L2VPN 1', slug='l2vpn-1', type=L2VPNTypeChoices.TYPE_VXLAN, identifier=650001),
|
|
||||||
L2VPN(name='L2VPN 2', slug='l2vpn-2', type=L2VPNTypeChoices.TYPE_VXLAN, identifier=650002),
|
|
||||||
)
|
|
||||||
L2VPN.objects.bulk_create(l2vpns)
|
|
||||||
|
|
||||||
vlans = (
|
|
||||||
VLAN(name='Vlan 1', vid=1001),
|
|
||||||
VLAN(name='Vlan 2', vid=1002),
|
|
||||||
VLAN(name='Vlan 3', vid=1003),
|
|
||||||
VLAN(name='Vlan 4', vid=1004),
|
|
||||||
VLAN(name='Vlan 5', vid=1005),
|
|
||||||
VLAN(name='Vlan 6', vid=1006)
|
|
||||||
)
|
|
||||||
VLAN.objects.bulk_create(vlans)
|
|
||||||
|
|
||||||
terminations = (
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[0]),
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[1]),
|
|
||||||
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[2])
|
|
||||||
)
|
|
||||||
L2VPNTermination.objects.bulk_create(terminations)
|
|
||||||
|
|
||||||
cls.form_data = {
|
|
||||||
'l2vpn': l2vpns[0].pk,
|
|
||||||
'device': device.pk,
|
|
||||||
'interface': interface.pk,
|
|
||||||
}
|
|
||||||
|
|
||||||
cls.csv_data = (
|
|
||||||
"l2vpn,vlan",
|
|
||||||
"L2VPN 1,Vlan 4",
|
|
||||||
"L2VPN 1,Vlan 5",
|
|
||||||
"L2VPN 1,Vlan 6",
|
|
||||||
)
|
|
||||||
|
|
||||||
cls.csv_update_data = (
|
|
||||||
f"id,l2vpn",
|
|
||||||
f"{terminations[0].pk},{l2vpns[0].name}",
|
|
||||||
f"{terminations[1].pk},{l2vpns[0].name}",
|
|
||||||
f"{terminations[2].pk},{l2vpns[0].name}",
|
|
||||||
)
|
|
||||||
|
|
||||||
cls.bulk_edit_data = {}
|
|
||||||
|
|
||||||
# TODO: Fix L2VPNTerminationImportForm validation to support bulk updates
|
|
||||||
def test_bulk_update_objects_with_permission(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
#
|
|
||||||
# Custom assertions
|
|
||||||
#
|
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
def assertInstanceEqual(self, instance, data, exclude=None, api=False):
|
|
||||||
"""
|
|
||||||
Override parent
|
|
||||||
"""
|
|
||||||
if exclude is None:
|
|
||||||
exclude = []
|
|
||||||
|
|
||||||
fields = [k for k in data.keys() if k not in exclude]
|
|
||||||
model_dict = self.model_to_dict(instance, fields=fields, api=api)
|
|
||||||
|
|
||||||
# Omit any dictionary keys which are not instance attributes or have been excluded
|
|
||||||
relevant_data = {
|
|
||||||
k: v for k, v in data.items() if hasattr(instance, k) and k not in exclude
|
|
||||||
}
|
|
||||||
|
|
||||||
# Handle relations on the model
|
|
||||||
for k, v in model_dict.items():
|
|
||||||
if isinstance(v, object) and hasattr(v, 'first'):
|
|
||||||
model_dict[k] = v.first().pk
|
|
||||||
|
|
||||||
self.assertDictEqual(model_dict, relevant_data)
|
|
||||||
|
@ -131,20 +131,4 @@ urlpatterns = [
|
|||||||
path('services/edit/', views.ServiceBulkEditView.as_view(), name='service_bulk_edit'),
|
path('services/edit/', views.ServiceBulkEditView.as_view(), name='service_bulk_edit'),
|
||||||
path('services/delete/', views.ServiceBulkDeleteView.as_view(), name='service_bulk_delete'),
|
path('services/delete/', views.ServiceBulkDeleteView.as_view(), name='service_bulk_delete'),
|
||||||
path('services/<int:pk>/', include(get_model_urls('ipam', 'service'))),
|
path('services/<int:pk>/', include(get_model_urls('ipam', 'service'))),
|
||||||
|
|
||||||
# L2VPN
|
|
||||||
path('l2vpns/', views.L2VPNListView.as_view(), name='l2vpn_list'),
|
|
||||||
path('l2vpns/add/', views.L2VPNEditView.as_view(), name='l2vpn_add'),
|
|
||||||
path('l2vpns/import/', views.L2VPNBulkImportView.as_view(), name='l2vpn_import'),
|
|
||||||
path('l2vpns/edit/', views.L2VPNBulkEditView.as_view(), name='l2vpn_bulk_edit'),
|
|
||||||
path('l2vpns/delete/', views.L2VPNBulkDeleteView.as_view(), name='l2vpn_bulk_delete'),
|
|
||||||
path('l2vpns/<int:pk>/', include(get_model_urls('ipam', 'l2vpn'))),
|
|
||||||
|
|
||||||
# L2VPN terminations
|
|
||||||
path('l2vpn-terminations/', views.L2VPNTerminationListView.as_view(), name='l2vpntermination_list'),
|
|
||||||
path('l2vpn-terminations/add/', views.L2VPNTerminationEditView.as_view(), name='l2vpntermination_add'),
|
|
||||||
path('l2vpn-terminations/import/', views.L2VPNTerminationBulkImportView.as_view(), name='l2vpntermination_import'),
|
|
||||||
path('l2vpn-terminations/edit/', views.L2VPNTerminationBulkEditView.as_view(), name='l2vpntermination_bulk_edit'),
|
|
||||||
path('l2vpn-terminations/delete/', views.L2VPNTerminationBulkDeleteView.as_view(), name='l2vpntermination_bulk_delete'),
|
|
||||||
path('l2vpn-terminations/<int:pk>/', include(get_model_urls('ipam', 'l2vpntermination'))),
|
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import F, Prefetch
|
from django.db.models import Prefetch
|
||||||
from django.db.models.expressions import RawSQL
|
from django.db.models.expressions import RawSQL
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@ -9,7 +9,6 @@ from circuits.models import Provider
|
|||||||
from dcim.filtersets import InterfaceFilterSet
|
from dcim.filtersets import InterfaceFilterSet
|
||||||
from dcim.models import Interface, Site
|
from dcim.models import Interface, Site
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from tenancy.views import ObjectContactsView
|
|
||||||
from utilities.tables import get_table_ordering
|
from utilities.tables import get_table_ordering
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
from utilities.views import ViewTab, register_model_view
|
from utilities.views import ViewTab, register_model_view
|
||||||
@ -19,7 +18,6 @@ from . import filtersets, forms, tables
|
|||||||
from .choices import PrefixStatusChoices
|
from .choices import PrefixStatusChoices
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .models import *
|
from .models import *
|
||||||
from .tables.l2vpn import L2VPNTable, L2VPNTerminationTable
|
|
||||||
from .utils import add_requested_prefixes, add_available_ipaddresses, add_available_vlans
|
from .utils import add_requested_prefixes, add_available_ipaddresses, add_available_vlans
|
||||||
|
|
||||||
|
|
||||||
@ -1243,112 +1241,3 @@ class ServiceBulkDeleteView(generic.BulkDeleteView):
|
|||||||
queryset = Service.objects.prefetch_related('device', 'virtual_machine')
|
queryset = Service.objects.prefetch_related('device', 'virtual_machine')
|
||||||
filterset = filtersets.ServiceFilterSet
|
filterset = filtersets.ServiceFilterSet
|
||||||
table = tables.ServiceTable
|
table = tables.ServiceTable
|
||||||
|
|
||||||
|
|
||||||
# L2VPN
|
|
||||||
|
|
||||||
class L2VPNListView(generic.ObjectListView):
|
|
||||||
queryset = L2VPN.objects.all()
|
|
||||||
table = L2VPNTable
|
|
||||||
filterset = filtersets.L2VPNFilterSet
|
|
||||||
filterset_form = forms.L2VPNFilterForm
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(L2VPN)
|
|
||||||
class L2VPNView(generic.ObjectView):
|
|
||||||
queryset = L2VPN.objects.all()
|
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
|
||||||
import_targets_table = tables.RouteTargetTable(
|
|
||||||
instance.import_targets.prefetch_related('tenant'),
|
|
||||||
orderable=False
|
|
||||||
)
|
|
||||||
export_targets_table = tables.RouteTargetTable(
|
|
||||||
instance.export_targets.prefetch_related('tenant'),
|
|
||||||
orderable=False
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'import_targets_table': import_targets_table,
|
|
||||||
'export_targets_table': export_targets_table,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(L2VPN, 'edit')
|
|
||||||
class L2VPNEditView(generic.ObjectEditView):
|
|
||||||
queryset = L2VPN.objects.all()
|
|
||||||
form = forms.L2VPNForm
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(L2VPN, 'delete')
|
|
||||||
class L2VPNDeleteView(generic.ObjectDeleteView):
|
|
||||||
queryset = L2VPN.objects.all()
|
|
||||||
|
|
||||||
|
|
||||||
class L2VPNBulkImportView(generic.BulkImportView):
|
|
||||||
queryset = L2VPN.objects.all()
|
|
||||||
model_form = forms.L2VPNImportForm
|
|
||||||
|
|
||||||
|
|
||||||
class L2VPNBulkEditView(generic.BulkEditView):
|
|
||||||
queryset = L2VPN.objects.all()
|
|
||||||
filterset = filtersets.L2VPNFilterSet
|
|
||||||
table = tables.L2VPNTable
|
|
||||||
form = forms.L2VPNBulkEditForm
|
|
||||||
|
|
||||||
|
|
||||||
class L2VPNBulkDeleteView(generic.BulkDeleteView):
|
|
||||||
queryset = L2VPN.objects.all()
|
|
||||||
filterset = filtersets.L2VPNFilterSet
|
|
||||||
table = tables.L2VPNTable
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(L2VPN, 'contacts')
|
|
||||||
class L2VPNContactsView(ObjectContactsView):
|
|
||||||
queryset = L2VPN.objects.all()
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# L2VPN terminations
|
|
||||||
#
|
|
||||||
|
|
||||||
class L2VPNTerminationListView(generic.ObjectListView):
|
|
||||||
queryset = L2VPNTermination.objects.all()
|
|
||||||
table = L2VPNTerminationTable
|
|
||||||
filterset = filtersets.L2VPNTerminationFilterSet
|
|
||||||
filterset_form = forms.L2VPNTerminationFilterForm
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(L2VPNTermination)
|
|
||||||
class L2VPNTerminationView(generic.ObjectView):
|
|
||||||
queryset = L2VPNTermination.objects.all()
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(L2VPNTermination, 'edit')
|
|
||||||
class L2VPNTerminationEditView(generic.ObjectEditView):
|
|
||||||
queryset = L2VPNTermination.objects.all()
|
|
||||||
form = forms.L2VPNTerminationForm
|
|
||||||
template_name = 'ipam/l2vpntermination_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(L2VPNTermination, 'delete')
|
|
||||||
class L2VPNTerminationDeleteView(generic.ObjectDeleteView):
|
|
||||||
queryset = L2VPNTermination.objects.all()
|
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTerminationBulkImportView(generic.BulkImportView):
|
|
||||||
queryset = L2VPNTermination.objects.all()
|
|
||||||
model_form = forms.L2VPNTerminationImportForm
|
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTerminationBulkEditView(generic.BulkEditView):
|
|
||||||
queryset = L2VPNTermination.objects.all()
|
|
||||||
filterset = filtersets.L2VPNTerminationFilterSet
|
|
||||||
table = tables.L2VPNTerminationTable
|
|
||||||
form = forms.L2VPNTerminationBulkEditForm
|
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTerminationBulkDeleteView(generic.BulkDeleteView):
|
|
||||||
queryset = L2VPNTermination.objects.all()
|
|
||||||
filterset = filtersets.L2VPNTerminationFilterSet
|
|
||||||
table = tables.L2VPNTerminationTable
|
|
||||||
|
@ -209,8 +209,8 @@ VPN_MENU = Menu(
|
|||||||
MenuGroup(
|
MenuGroup(
|
||||||
label=_('L2VPNs'),
|
label=_('L2VPNs'),
|
||||||
items=(
|
items=(
|
||||||
get_model_item('ipam', 'l2vpn', _('L2VPNs')),
|
get_model_item('vpn', 'l2vpn', _('L2VPNs')),
|
||||||
get_model_item('ipam', 'l2vpntermination', _('Terminations')),
|
get_model_item('vpn', 'l2vpntermination', _('Terminations')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
MenuGroup(
|
MenuGroup(
|
||||||
|
@ -59,7 +59,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">{% trans "Importing L2VPNs" %}</h5>
|
<h5 class="card-header">{% trans "Importing L2VPNs" %}</h5>
|
||||||
<div class="card-body htmx-container table-responsive"
|
<div class="card-body htmx-container table-responsive"
|
||||||
hx-get="{% url 'ipam:l2vpn_list' %}?import_target_id={{ object.pk }}"
|
hx-get="{% url 'vpn:l2vpn_list' %}?import_target_id={{ object.pk }}"
|
||||||
hx-trigger="load"
|
hx-trigger="load"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
@ -68,7 +68,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">{% trans "Exporting L2VPNs" %}</h5>
|
<h5 class="card-header">{% trans "Exporting L2VPNs" %}</h5>
|
||||||
<div class="card-body htmx-container table-responsive"
|
<div class="card-body htmx-container table-responsive"
|
||||||
hx-get="{% url 'ipam:l2vpn_list' %}?export_target_id={{ object.pk }}"
|
hx-get="{% url 'vpn:l2vpn_list' %}?export_target_id={{ object.pk }}"
|
||||||
hx-trigger="load"
|
hx-trigger="load"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/panels/tags.html' with tags=object.tags.all url='ipam:l2vpn_list' %}
|
{% include 'inc/panels/tags.html' with tags=object.tags.all url='vpn:l2vpn_list' %}
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
@ -56,12 +56,12 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">{% trans "Terminations" %}</h5>
|
<h5 class="card-header">{% trans "Terminations" %}</h5>
|
||||||
<div class="card-body htmx-container table-responsive"
|
<div class="card-body htmx-container table-responsive"
|
||||||
hx-get="{% url 'ipam:l2vpntermination_list' %}?l2vpn_id={{ object.pk }}"
|
hx-get="{% url 'vpn:l2vpntermination_list' %}?l2vpn_id={{ object.pk }}"
|
||||||
hx-trigger="load"
|
hx-trigger="load"
|
||||||
></div>
|
></div>
|
||||||
{% if perms.ipam.add_l2vpntermination %}
|
{% if perms.vpn.add_l2vpntermination %}
|
||||||
<div class="card-footer text-end noprint">
|
<div class="card-footer text-end noprint">
|
||||||
<a href="{% url 'ipam:l2vpntermination_add' %}?l2vpn={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary btn-sm{% if not object.can_add_termination %} disabled" aria-disabled="true{% endif %}">
|
<a href="{% url 'vpn:l2vpntermination_add' %}?l2vpn={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary btn-sm{% if not object.can_add_termination %} disabled" aria-disabled="true{% endif %}">
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add a Termination" %}
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add a Termination" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
@ -25,7 +25,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
{% include 'inc/panels/tags.html' with tags=object.tags.all url='ipam:l2vpntermination_list' %}
|
{% include 'inc/panels/tags.html' with tags=object.tags.all url='vpn:l2vpntermination_list' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -6,15 +6,14 @@ from dcim.api.nested_serializers import (
|
|||||||
)
|
)
|
||||||
from dcim.choices import InterfaceModeChoices
|
from dcim.choices import InterfaceModeChoices
|
||||||
from extras.api.nested_serializers import NestedConfigTemplateSerializer
|
from extras.api.nested_serializers import NestedConfigTemplateSerializer
|
||||||
from ipam.api.nested_serializers import (
|
from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer, NestedVRFSerializer
|
||||||
NestedIPAddressSerializer, NestedL2VPNTerminationSerializer, NestedVLANSerializer, NestedVRFSerializer,
|
|
||||||
)
|
|
||||||
from ipam.models import VLAN
|
from ipam.models import VLAN
|
||||||
from netbox.api.fields import ChoiceField, SerializedPKRelatedField
|
from netbox.api.fields import ChoiceField, SerializedPKRelatedField
|
||||||
from netbox.api.serializers import NetBoxModelSerializer
|
from netbox.api.serializers import NetBoxModelSerializer
|
||||||
from tenancy.api.nested_serializers import NestedTenantSerializer
|
from tenancy.api.nested_serializers import NestedTenantSerializer
|
||||||
from virtualization.choices import *
|
from virtualization.choices import *
|
||||||
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualDisk, VirtualMachine, VMInterface
|
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualDisk, VirtualMachine, VMInterface
|
||||||
|
from vpn.api.nested_serializers import NestedL2VPNTerminationSerializer
|
||||||
from .nested_serializers import *
|
from .nested_serializers import *
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,13 +4,14 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup
|
from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup
|
||||||
from extras.forms import LocalConfigContextFilterForm
|
from extras.forms import LocalConfigContextFilterForm
|
||||||
from extras.models import ConfigTemplate
|
from extras.models import ConfigTemplate
|
||||||
from ipam.models import L2VPN, VRF
|
from ipam.models import VRF
|
||||||
from netbox.forms import NetBoxModelFilterSetForm
|
from netbox.forms import NetBoxModelFilterSetForm
|
||||||
from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
|
from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
|
||||||
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES
|
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES
|
||||||
from utilities.forms.fields import DynamicModelMultipleChoiceField, TagFilterField
|
from utilities.forms.fields import DynamicModelMultipleChoiceField, TagFilterField
|
||||||
from virtualization.choices import *
|
from virtualization.choices import *
|
||||||
from virtualization.models import *
|
from virtualization.models import *
|
||||||
|
from vpn.models import L2VPN
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ClusterFilterForm',
|
'ClusterFilterForm',
|
||||||
|
@ -358,7 +358,7 @@ class VMInterface(ComponentModel, BaseInterface, TrackingModelMixin):
|
|||||||
related_query_name='vminterface',
|
related_query_name='vminterface',
|
||||||
)
|
)
|
||||||
l2vpn_terminations = GenericRelation(
|
l2vpn_terminations = GenericRelation(
|
||||||
to='ipam.L2VPNTermination',
|
to='vpn.L2VPNTermination',
|
||||||
content_type_field='assigned_object_type',
|
content_type_field='assigned_object_type',
|
||||||
object_id_field='assigned_object_id',
|
object_id_field='assigned_object_id',
|
||||||
related_query_name='vminterface',
|
related_query_name='vminterface',
|
||||||
|
@ -24,8 +24,8 @@ VMINTERFACE_BUTTONS = """
|
|||||||
{% if perms.ipam.add_ipaddress %}
|
{% if perms.ipam.add_ipaddress %}
|
||||||
<li><a class="dropdown-item" href="{% url 'ipam:ipaddress_add' %}?vminterface={{ record.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}">IP Address</a></li>
|
<li><a class="dropdown-item" href="{% url 'ipam:ipaddress_add' %}?vminterface={{ record.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}">IP Address</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.ipam.add_l2vpntermination %}
|
{% if perms.vpn.add_l2vpntermination %}
|
||||||
<li><a class="dropdown-item" href="{% url 'ipam:l2vpntermination_add' %}?virtual_machine={{ object.pk }}&vminterface={{ record.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}">L2VPN Termination</a></li>
|
<li><a class="dropdown-item" href="{% url 'vpn:l2vpntermination_add' %}?virtual_machine={{ object.pk }}&vminterface={{ record.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}">L2VPN Termination</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.ipam.add_fhrpgroupassignment %}
|
{% if perms.ipam.add_fhrpgroupassignment %}
|
||||||
<li><a class="dropdown-item" href="{% url 'ipam:fhrpgroupassignment_add' %}?interface_type={{ record|content_type_id }}&interface_id={{ record.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}">Assign FHRP Group</a></li>
|
<li><a class="dropdown-item" href="{% url 'ipam:fhrpgroupassignment_add' %}?interface_type={{ record|content_type_id }}&interface_id={{ record.pk }}&return_url={% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}">Assign FHRP Group</a></li>
|
||||||
|
@ -9,6 +9,8 @@ __all__ = (
|
|||||||
'NestedIPSecPolicySerializer',
|
'NestedIPSecPolicySerializer',
|
||||||
'NestedIPSecProfileSerializer',
|
'NestedIPSecProfileSerializer',
|
||||||
'NestedIPSecProposalSerializer',
|
'NestedIPSecProposalSerializer',
|
||||||
|
'NestedL2VPNSerializer',
|
||||||
|
'NestedL2VPNTerminationSerializer',
|
||||||
'NestedTunnelSerializer',
|
'NestedTunnelSerializer',
|
||||||
'NestedTunnelTerminationSerializer',
|
'NestedTunnelTerminationSerializer',
|
||||||
)
|
)
|
||||||
@ -82,3 +84,28 @@ class NestedIPSecProfileSerializer(WritableNestedSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = models.IPSecProfile
|
model = models.IPSecProfile
|
||||||
fields = ('id', 'url', 'display', 'name')
|
fields = ('id', 'url', 'display', 'name')
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# L2VPN
|
||||||
|
#
|
||||||
|
|
||||||
|
class NestedL2VPNSerializer(WritableNestedSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='vpn-api:l2vpn-detail')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.L2VPN
|
||||||
|
fields = [
|
||||||
|
'id', 'url', 'display', 'identifier', 'name', 'slug', 'type'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class NestedL2VPNTerminationSerializer(WritableNestedSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='vpn-api:l2vpntermination-detail')
|
||||||
|
l2vpn = NestedL2VPNSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.L2VPNTermination
|
||||||
|
fields = [
|
||||||
|
'id', 'url', 'display', 'l2vpn'
|
||||||
|
]
|
||||||
|
@ -2,7 +2,8 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from ipam.api.nested_serializers import NestedIPAddressSerializer
|
from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedRouteTargetSerializer
|
||||||
|
from ipam.models import RouteTarget
|
||||||
from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField
|
from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField
|
||||||
from netbox.api.serializers import NetBoxModelSerializer
|
from netbox.api.serializers import NetBoxModelSerializer
|
||||||
from netbox.constants import NESTED_SERIALIZER_PREFIX
|
from netbox.constants import NESTED_SERIALIZER_PREFIX
|
||||||
@ -18,6 +19,8 @@ __all__ = (
|
|||||||
'IPSecPolicySerializer',
|
'IPSecPolicySerializer',
|
||||||
'IPSecProfileSerializer',
|
'IPSecProfileSerializer',
|
||||||
'IPSecProposalSerializer',
|
'IPSecProposalSerializer',
|
||||||
|
'L2VPNSerializer',
|
||||||
|
'L2VPNTerminationSerializer',
|
||||||
'TunnelSerializer',
|
'TunnelSerializer',
|
||||||
'TunnelTerminationSerializer',
|
'TunnelTerminationSerializer',
|
||||||
)
|
)
|
||||||
@ -191,3 +194,54 @@ class IPSecProfileSerializer(NetBoxModelSerializer):
|
|||||||
'id', 'url', 'display', 'name', 'description', 'mode', 'ike_policy', 'ipsec_policy', 'comments', 'tags',
|
'id', 'url', 'display', 'name', 'description', 'mode', 'ike_policy', 'ipsec_policy', 'comments', 'tags',
|
||||||
'custom_fields', 'created', 'last_updated',
|
'custom_fields', 'created', 'last_updated',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# L2VPN
|
||||||
|
#
|
||||||
|
|
||||||
|
class L2VPNSerializer(NetBoxModelSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='vpn-api:l2vpn-detail')
|
||||||
|
type = ChoiceField(choices=L2VPNTypeChoices, required=False)
|
||||||
|
import_targets = SerializedPKRelatedField(
|
||||||
|
queryset=RouteTarget.objects.all(),
|
||||||
|
serializer=NestedRouteTargetSerializer,
|
||||||
|
required=False,
|
||||||
|
many=True
|
||||||
|
)
|
||||||
|
export_targets = SerializedPKRelatedField(
|
||||||
|
queryset=RouteTarget.objects.all(),
|
||||||
|
serializer=NestedRouteTargetSerializer,
|
||||||
|
required=False,
|
||||||
|
many=True
|
||||||
|
)
|
||||||
|
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = L2VPN
|
||||||
|
fields = [
|
||||||
|
'id', 'url', 'display', 'identifier', 'name', 'slug', 'type', 'import_targets', 'export_targets',
|
||||||
|
'description', 'comments', 'tenant', 'tags', 'custom_fields', 'created', 'last_updated'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNTerminationSerializer(NetBoxModelSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='vpn-api:l2vpntermination-detail')
|
||||||
|
l2vpn = NestedL2VPNSerializer()
|
||||||
|
assigned_object_type = ContentTypeField(
|
||||||
|
queryset=ContentType.objects.all()
|
||||||
|
)
|
||||||
|
assigned_object = serializers.SerializerMethodField(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = L2VPNTermination
|
||||||
|
fields = [
|
||||||
|
'id', 'url', 'display', 'l2vpn', 'assigned_object_type', 'assigned_object_id',
|
||||||
|
'assigned_object', 'tags', 'custom_fields', 'created', 'last_updated'
|
||||||
|
]
|
||||||
|
|
||||||
|
@extend_schema_field(serializers.JSONField(allow_null=True))
|
||||||
|
def get_assigned_object(self, instance):
|
||||||
|
serializer = get_serializer_for_model(instance.assigned_object, prefix=NESTED_SERIALIZER_PREFIX)
|
||||||
|
context = {'request': self.context['request']}
|
||||||
|
return serializer(instance.assigned_object, context=context).data
|
||||||
|
@ -10,6 +10,8 @@ router.register('ipsec-proposals', views.IPSecProposalViewSet)
|
|||||||
router.register('ipsec-profiles', views.IPSecProfileViewSet)
|
router.register('ipsec-profiles', views.IPSecProfileViewSet)
|
||||||
router.register('tunnels', views.TunnelViewSet)
|
router.register('tunnels', views.TunnelViewSet)
|
||||||
router.register('tunnel-terminations', views.TunnelTerminationViewSet)
|
router.register('tunnel-terminations', views.TunnelTerminationViewSet)
|
||||||
|
router.register('l2vpns', views.L2VPNViewSet)
|
||||||
|
router.register('l2vpn-terminations', views.L2VPNTerminationViewSet)
|
||||||
|
|
||||||
app_name = 'vpn-api'
|
app_name = 'vpn-api'
|
||||||
urlpatterns = router.urls
|
urlpatterns = router.urls
|
||||||
|
@ -12,6 +12,8 @@ __all__ = (
|
|||||||
'IPSecPolicyViewSet',
|
'IPSecPolicyViewSet',
|
||||||
'IPSecProfileViewSet',
|
'IPSecProfileViewSet',
|
||||||
'IPSecProposalViewSet',
|
'IPSecProposalViewSet',
|
||||||
|
'L2VPNViewSet',
|
||||||
|
'L2VPNTerminationViewSet',
|
||||||
'TunnelTerminationViewSet',
|
'TunnelTerminationViewSet',
|
||||||
'TunnelViewSet',
|
'TunnelViewSet',
|
||||||
'VPNRootView',
|
'VPNRootView',
|
||||||
@ -72,3 +74,15 @@ class IPSecProfileViewSet(NetBoxModelViewSet):
|
|||||||
queryset = IPSecProfile.objects.all()
|
queryset = IPSecProfile.objects.all()
|
||||||
serializer_class = serializers.IPSecProfileSerializer
|
serializer_class = serializers.IPSecProfileSerializer
|
||||||
filterset_class = filtersets.IPSecProfileFilterSet
|
filterset_class = filtersets.IPSecProfileFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNViewSet(NetBoxModelViewSet):
|
||||||
|
queryset = L2VPN.objects.prefetch_related('import_targets', 'export_targets', 'tenant', 'tags')
|
||||||
|
serializer_class = serializers.L2VPNSerializer
|
||||||
|
filterset_class = filtersets.L2VPNFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNTerminationViewSet(NetBoxModelViewSet):
|
||||||
|
queryset = L2VPNTermination.objects.prefetch_related('assigned_object')
|
||||||
|
serializer_class = serializers.L2VPNTerminationSerializer
|
||||||
|
filterset_class = filtersets.L2VPNTerminationFilterSet
|
||||||
|
@ -199,3 +199,56 @@ class DHGroupChoices(ChoiceSet):
|
|||||||
(GROUP_33, _('Group {n}').format(n=33)),
|
(GROUP_33, _('Group {n}').format(n=33)),
|
||||||
(GROUP_34, _('Group {n}').format(n=34)),
|
(GROUP_34, _('Group {n}').format(n=34)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# L2VPN
|
||||||
|
#
|
||||||
|
|
||||||
|
class L2VPNTypeChoices(ChoiceSet):
|
||||||
|
TYPE_VPLS = 'vpls'
|
||||||
|
TYPE_VPWS = 'vpws'
|
||||||
|
TYPE_EPL = 'epl'
|
||||||
|
TYPE_EVPL = 'evpl'
|
||||||
|
TYPE_EPLAN = 'ep-lan'
|
||||||
|
TYPE_EVPLAN = 'evp-lan'
|
||||||
|
TYPE_EPTREE = 'ep-tree'
|
||||||
|
TYPE_EVPTREE = 'evp-tree'
|
||||||
|
TYPE_VXLAN = 'vxlan'
|
||||||
|
TYPE_VXLAN_EVPN = 'vxlan-evpn'
|
||||||
|
TYPE_MPLS_EVPN = 'mpls-evpn'
|
||||||
|
TYPE_PBB_EVPN = 'pbb-evpn'
|
||||||
|
|
||||||
|
CHOICES = (
|
||||||
|
('VPLS', (
|
||||||
|
(TYPE_VPWS, 'VPWS'),
|
||||||
|
(TYPE_VPLS, 'VPLS'),
|
||||||
|
)),
|
||||||
|
('VXLAN', (
|
||||||
|
(TYPE_VXLAN, 'VXLAN'),
|
||||||
|
(TYPE_VXLAN_EVPN, 'VXLAN-EVPN'),
|
||||||
|
)),
|
||||||
|
('L2VPN E-VPN', (
|
||||||
|
(TYPE_MPLS_EVPN, 'MPLS EVPN'),
|
||||||
|
(TYPE_PBB_EVPN, 'PBB EVPN'),
|
||||||
|
)),
|
||||||
|
('E-Line', (
|
||||||
|
(TYPE_EPL, 'EPL'),
|
||||||
|
(TYPE_EVPL, 'EVPL'),
|
||||||
|
)),
|
||||||
|
('E-LAN', (
|
||||||
|
(TYPE_EPLAN, _('Ethernet Private LAN')),
|
||||||
|
(TYPE_EVPLAN, _('Ethernet Virtual Private LAN')),
|
||||||
|
)),
|
||||||
|
('E-Tree', (
|
||||||
|
(TYPE_EPTREE, _('Ethernet Private Tree')),
|
||||||
|
(TYPE_EVPTREE, _('Ethernet Virtual Private Tree')),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
|
P2P = (
|
||||||
|
TYPE_VPWS,
|
||||||
|
TYPE_EPL,
|
||||||
|
TYPE_EPLAN,
|
||||||
|
TYPE_EPTREE
|
||||||
|
)
|
||||||
|
7
netbox/vpn/constants.py
Normal file
7
netbox/vpn/constants.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
L2VPN_ASSIGNMENT_MODELS = Q(
|
||||||
|
Q(app_label='dcim', model='interface') |
|
||||||
|
Q(app_label='ipam', model='vlan') |
|
||||||
|
Q(app_label='virtualization', model='vminterface')
|
||||||
|
)
|
@ -2,12 +2,12 @@ import django_filters
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from dcim.models import Interface
|
from dcim.models import Device, Interface
|
||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress, RouteTarget, VLAN
|
||||||
from netbox.filtersets import NetBoxModelFilterSet
|
from netbox.filtersets import NetBoxModelFilterSet
|
||||||
from tenancy.filtersets import TenancyFilterSet
|
from tenancy.filtersets import TenancyFilterSet
|
||||||
from utilities.filters import ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter
|
from utilities.filters import ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter
|
||||||
from virtualization.models import VMInterface
|
from virtualization.models import VirtualMachine, VMInterface
|
||||||
from .choices import *
|
from .choices import *
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
@ -17,6 +17,8 @@ __all__ = (
|
|||||||
'IPSecPolicyFilterSet',
|
'IPSecPolicyFilterSet',
|
||||||
'IPSecProfileFilterSet',
|
'IPSecProfileFilterSet',
|
||||||
'IPSecProposalFilterSet',
|
'IPSecProposalFilterSet',
|
||||||
|
'L2VPNFilterSet',
|
||||||
|
'L2VPNTerminationFilterSet',
|
||||||
'TunnelFilterSet',
|
'TunnelFilterSet',
|
||||||
'TunnelTerminationFilterSet',
|
'TunnelTerminationFilterSet',
|
||||||
)
|
)
|
||||||
@ -239,3 +241,175 @@ class IPSecProfileFilterSet(NetBoxModelFilterSet):
|
|||||||
Q(description__icontains=value) |
|
Q(description__icontains=value) |
|
||||||
Q(comments__icontains=value)
|
Q(comments__icontains=value)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
||||||
|
type = django_filters.MultipleChoiceFilter(
|
||||||
|
choices=L2VPNTypeChoices,
|
||||||
|
null_value=None
|
||||||
|
)
|
||||||
|
import_target_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='import_targets',
|
||||||
|
queryset=RouteTarget.objects.all(),
|
||||||
|
label=_('Import target'),
|
||||||
|
)
|
||||||
|
import_target = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='import_targets__name',
|
||||||
|
queryset=RouteTarget.objects.all(),
|
||||||
|
to_field_name='name',
|
||||||
|
label=_('Import target (name)'),
|
||||||
|
)
|
||||||
|
export_target_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='export_targets',
|
||||||
|
queryset=RouteTarget.objects.all(),
|
||||||
|
label=_('Export target'),
|
||||||
|
)
|
||||||
|
export_target = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='export_targets__name',
|
||||||
|
queryset=RouteTarget.objects.all(),
|
||||||
|
to_field_name='name',
|
||||||
|
label=_('Export target (name)'),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = L2VPN
|
||||||
|
fields = ['id', 'identifier', 'name', 'slug', 'type', 'description']
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
qs_filter = Q(name__icontains=value) | Q(description__icontains=value)
|
||||||
|
try:
|
||||||
|
qs_filter |= Q(identifier=int(value))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNTerminationFilterSet(NetBoxModelFilterSet):
|
||||||
|
l2vpn_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=L2VPN.objects.all(),
|
||||||
|
label=_('L2VPN (ID)'),
|
||||||
|
)
|
||||||
|
l2vpn = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='l2vpn__slug',
|
||||||
|
queryset=L2VPN.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label=_('L2VPN (slug)'),
|
||||||
|
)
|
||||||
|
region = MultiValueCharFilter(
|
||||||
|
method='filter_region',
|
||||||
|
field_name='slug',
|
||||||
|
label=_('Region (slug)'),
|
||||||
|
)
|
||||||
|
region_id = MultiValueNumberFilter(
|
||||||
|
method='filter_region',
|
||||||
|
field_name='pk',
|
||||||
|
label=_('Region (ID)'),
|
||||||
|
)
|
||||||
|
site = MultiValueCharFilter(
|
||||||
|
method='filter_site',
|
||||||
|
field_name='slug',
|
||||||
|
label=_('Site (slug)'),
|
||||||
|
)
|
||||||
|
site_id = MultiValueNumberFilter(
|
||||||
|
method='filter_site',
|
||||||
|
field_name='pk',
|
||||||
|
label=_('Site (ID)'),
|
||||||
|
)
|
||||||
|
device = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='interface__device__name',
|
||||||
|
queryset=Device.objects.all(),
|
||||||
|
to_field_name='name',
|
||||||
|
label=_('Device (name)'),
|
||||||
|
)
|
||||||
|
device_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='interface__device',
|
||||||
|
queryset=Device.objects.all(),
|
||||||
|
label=_('Device (ID)'),
|
||||||
|
)
|
||||||
|
virtual_machine = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='vminterface__virtual_machine__name',
|
||||||
|
queryset=VirtualMachine.objects.all(),
|
||||||
|
to_field_name='name',
|
||||||
|
label=_('Virtual machine (name)'),
|
||||||
|
)
|
||||||
|
virtual_machine_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='vminterface__virtual_machine',
|
||||||
|
queryset=VirtualMachine.objects.all(),
|
||||||
|
label=_('Virtual machine (ID)'),
|
||||||
|
)
|
||||||
|
interface = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='interface__name',
|
||||||
|
queryset=Interface.objects.all(),
|
||||||
|
to_field_name='name',
|
||||||
|
label=_('Interface (name)'),
|
||||||
|
)
|
||||||
|
interface_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='interface',
|
||||||
|
queryset=Interface.objects.all(),
|
||||||
|
label=_('Interface (ID)'),
|
||||||
|
)
|
||||||
|
vminterface = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='vminterface__name',
|
||||||
|
queryset=VMInterface.objects.all(),
|
||||||
|
to_field_name='name',
|
||||||
|
label=_('VM interface (name)'),
|
||||||
|
)
|
||||||
|
vminterface_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='vminterface',
|
||||||
|
queryset=VMInterface.objects.all(),
|
||||||
|
label=_('VM Interface (ID)'),
|
||||||
|
)
|
||||||
|
vlan = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='vlan__name',
|
||||||
|
queryset=VLAN.objects.all(),
|
||||||
|
to_field_name='name',
|
||||||
|
label=_('VLAN (name)'),
|
||||||
|
)
|
||||||
|
vlan_vid = django_filters.NumberFilter(
|
||||||
|
field_name='vlan__vid',
|
||||||
|
label=_('VLAN number (1-4094)'),
|
||||||
|
)
|
||||||
|
vlan_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='vlan',
|
||||||
|
queryset=VLAN.objects.all(),
|
||||||
|
label=_('VLAN (ID)'),
|
||||||
|
)
|
||||||
|
assigned_object_type = ContentTypeFilter()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = L2VPNTermination
|
||||||
|
fields = ('id', 'assigned_object_type_id')
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
qs_filter = Q(l2vpn__name__icontains=value)
|
||||||
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
|
def filter_assigned_object(self, queryset, name, value):
|
||||||
|
qs = queryset.filter(
|
||||||
|
Q(**{'{}__in'.format(name): value})
|
||||||
|
)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
def filter_site(self, queryset, name, value):
|
||||||
|
qs = queryset.filter(
|
||||||
|
Q(
|
||||||
|
Q(**{'vlan__site__{}__in'.format(name): value}) |
|
||||||
|
Q(**{'interface__device__site__{}__in'.format(name): value}) |
|
||||||
|
Q(**{'vminterface__virtual_machine__site__{}__in'.format(name): value})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
def filter_region(self, queryset, name, value):
|
||||||
|
qs = queryset.filter(
|
||||||
|
Q(
|
||||||
|
Q(**{'vlan__site__region__{}__in'.format(name): value}) |
|
||||||
|
Q(**{'interface__device__site__region__{}__in'.format(name): value}) |
|
||||||
|
Q(**{'vminterface__virtual_machine__site__region__{}__in'.format(name): value})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return qs
|
||||||
|
@ -14,6 +14,8 @@ __all__ = (
|
|||||||
'IPSecPolicyBulkEditForm',
|
'IPSecPolicyBulkEditForm',
|
||||||
'IPSecProfileBulkEditForm',
|
'IPSecProfileBulkEditForm',
|
||||||
'IPSecProposalBulkEditForm',
|
'IPSecProposalBulkEditForm',
|
||||||
|
'L2VPNBulkEditForm',
|
||||||
|
'L2VPNTerminationBulkEditForm',
|
||||||
'TunnelBulkEditForm',
|
'TunnelBulkEditForm',
|
||||||
'TunnelTerminationBulkEditForm',
|
'TunnelTerminationBulkEditForm',
|
||||||
)
|
)
|
||||||
@ -241,3 +243,32 @@ class IPSecProfileBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'description', 'comments',
|
'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
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from dcim.models import Device, Interface
|
from dcim.models import Device, Interface
|
||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress, VLAN
|
||||||
from netbox.forms import NetBoxModelImportForm
|
from netbox.forms import NetBoxModelImportForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, CSVModelMultipleChoiceField
|
from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, CSVModelMultipleChoiceField
|
||||||
@ -15,6 +16,8 @@ __all__ = (
|
|||||||
'IPSecPolicyImportForm',
|
'IPSecPolicyImportForm',
|
||||||
'IPSecProfileImportForm',
|
'IPSecProfileImportForm',
|
||||||
'IPSecProposalImportForm',
|
'IPSecProposalImportForm',
|
||||||
|
'L2VPNImportForm',
|
||||||
|
'L2VPNTerminationImportForm',
|
||||||
'TunnelImportForm',
|
'TunnelImportForm',
|
||||||
'TunnelTerminationImportForm',
|
'TunnelTerminationImportForm',
|
||||||
)
|
)
|
||||||
@ -228,3 +231,92 @@ class IPSecProfileImportForm(NetBoxModelImportForm):
|
|||||||
fields = (
|
fields = (
|
||||||
'name', 'mode', 'ike_policy', 'ipsec_policy', 'description', 'comments', 'tags',
|
'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')
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.translation import gettext as _
|
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 netbox.forms import NetBoxModelFilterSetForm
|
||||||
from tenancy.forms import TenancyFilterForm
|
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.choices import *
|
||||||
|
from vpn.constants import L2VPN_ASSIGNMENT_MODELS
|
||||||
from vpn.models import *
|
from vpn.models import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -13,6 +21,8 @@ __all__ = (
|
|||||||
'IPSecPolicyFilterForm',
|
'IPSecPolicyFilterForm',
|
||||||
'IPSecProfileFilterForm',
|
'IPSecProfileFilterForm',
|
||||||
'IPSecProposalFilterForm',
|
'IPSecProposalFilterForm',
|
||||||
|
'L2VPNFilterForm',
|
||||||
|
'L2VPNTerminationFilterForm',
|
||||||
'TunnelFilterForm',
|
'TunnelFilterForm',
|
||||||
'TunnelTerminationFilterForm',
|
'TunnelTerminationFilterForm',
|
||||||
)
|
)
|
||||||
@ -180,3 +190,90 @@ class IPSecProfileFilterForm(NetBoxModelFilterSetForm):
|
|||||||
label=_('IPSec policy')
|
label=_('IPSec policy')
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
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')
|
||||||
|
)
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from dcim.models import Device, Interface
|
from dcim.models import Device, Interface
|
||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress, RouteTarget, VLAN
|
||||||
from netbox.forms import NetBoxModelForm
|
from netbox.forms import NetBoxModelForm
|
||||||
from tenancy.forms import TenancyForm
|
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.utils import add_blank_choice
|
||||||
from utilities.forms.widgets import HTMXSelect
|
from utilities.forms.widgets import HTMXSelect
|
||||||
from virtualization.models import VirtualMachine, VMInterface
|
from virtualization.models import VirtualMachine, VMInterface
|
||||||
@ -18,6 +19,8 @@ __all__ = (
|
|||||||
'IPSecPolicyForm',
|
'IPSecPolicyForm',
|
||||||
'IPSecProfileForm',
|
'IPSecProfileForm',
|
||||||
'IPSecProposalForm',
|
'IPSecProposalForm',
|
||||||
|
'L2VPNForm',
|
||||||
|
'L2VPNTerminationForm',
|
||||||
'TunnelCreateForm',
|
'TunnelCreateForm',
|
||||||
'TunnelForm',
|
'TunnelForm',
|
||||||
'TunnelTerminationForm',
|
'TunnelTerminationForm',
|
||||||
@ -355,3 +358,96 @@ class IPSecProfileForm(NetBoxModelForm):
|
|||||||
fields = [
|
fields = [
|
||||||
'name', 'description', 'mode', 'ike_policy', 'ipsec_policy', 'description', 'comments', 'tags',
|
'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
|
||||||
|
30
netbox/vpn/graphql/gfk_mixins.py
Normal file
30
netbox/vpn/graphql/gfk_mixins.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import graphene
|
||||||
|
|
||||||
|
from dcim.graphql.types import InterfaceType
|
||||||
|
from dcim.models import Interface
|
||||||
|
from ipam.graphql.types import VLANType
|
||||||
|
from ipam.models import VLAN
|
||||||
|
from virtualization.graphql.types import VMInterfaceType
|
||||||
|
from virtualization.models import VMInterface
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'L2VPNAssignmentType',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNAssignmentType(graphene.Union):
|
||||||
|
class Meta:
|
||||||
|
types = (
|
||||||
|
InterfaceType,
|
||||||
|
VLANType,
|
||||||
|
VMInterfaceType,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resolve_type(cls, instance, info):
|
||||||
|
if type(instance) is Interface:
|
||||||
|
return InterfaceType
|
||||||
|
if type(instance) is VLAN:
|
||||||
|
return VLANType
|
||||||
|
if type(instance) is VMInterface:
|
||||||
|
return VMInterfaceType
|
@ -38,6 +38,18 @@ class VPNQuery(graphene.ObjectType):
|
|||||||
def resolve_ipsec_proposal_list(root, info, **kwargs):
|
def resolve_ipsec_proposal_list(root, info, **kwargs):
|
||||||
return gql_query_optimizer(models.IPSecProposal.objects.all(), info)
|
return gql_query_optimizer(models.IPSecProposal.objects.all(), info)
|
||||||
|
|
||||||
|
l2vpn = ObjectField(L2VPNType)
|
||||||
|
l2vpn_list = ObjectListField(L2VPNType)
|
||||||
|
|
||||||
|
def resolve_l2vpn_list(root, info, **kwargs):
|
||||||
|
return gql_query_optimizer(models.L2VPN.objects.all(), info)
|
||||||
|
|
||||||
|
l2vpn_termination = ObjectField(L2VPNTerminationType)
|
||||||
|
l2vpn_termination_list = ObjectListField(L2VPNTerminationType)
|
||||||
|
|
||||||
|
def resolve_l2vpn_termination_list(root, info, **kwargs):
|
||||||
|
return gql_query_optimizer(models.L2VPNTermination.objects.all(), info)
|
||||||
|
|
||||||
tunnel = ObjectField(TunnelType)
|
tunnel = ObjectField(TunnelType)
|
||||||
tunnel_list = ObjectListField(TunnelType)
|
tunnel_list = ObjectListField(TunnelType)
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
from extras.graphql.mixins import CustomFieldsMixin, TagsMixin
|
import graphene
|
||||||
|
|
||||||
|
from extras.graphql.mixins import ContactsMixin, CustomFieldsMixin, TagsMixin
|
||||||
from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType
|
from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType
|
||||||
from vpn import filtersets, models
|
from vpn import filtersets, models
|
||||||
|
|
||||||
@ -8,6 +10,8 @@ __all__ = (
|
|||||||
'IPSecPolicyType',
|
'IPSecPolicyType',
|
||||||
'IPSecProfileType',
|
'IPSecProfileType',
|
||||||
'IPSecProposalType',
|
'IPSecProposalType',
|
||||||
|
'L2VPNType',
|
||||||
|
'L2VPNTerminationType',
|
||||||
'TunnelTerminationType',
|
'TunnelTerminationType',
|
||||||
'TunnelType',
|
'TunnelType',
|
||||||
)
|
)
|
||||||
@ -67,3 +71,19 @@ class IPSecProfileType(OrganizationalObjectType):
|
|||||||
model = models.IPSecProfile
|
model = models.IPSecProfile
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
filterset_class = filtersets.IPSecProfileFilterSet
|
filterset_class = filtersets.IPSecProfileFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNType(ContactsMixin, NetBoxObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = models.L2VPN
|
||||||
|
fields = '__all__'
|
||||||
|
filtersets_class = filtersets.L2VPNFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNTerminationType(NetBoxObjectType):
|
||||||
|
assigned_object = graphene.Field('vpn.graphql.gfk_mixins.L2VPNAssignmentType')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.L2VPNTermination
|
||||||
|
exclude = ('assigned_object_type', 'assigned_object_id')
|
||||||
|
filtersets_class = filtersets.L2VPNTerminationFilterSet
|
||||||
|
73
netbox/vpn/migrations/0002_move_l2vpn.py
Normal file
73
netbox/vpn/migrations/0002_move_l2vpn.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import taggit.managers
|
||||||
|
import utilities.json
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('extras', '0099_cachedvalue_ordering'),
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
('tenancy', '0012_contactassignment_custom_fields'),
|
||||||
|
('ipam', '0068_move_l2vpn'),
|
||||||
|
('vpn', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.SeparateDatabaseAndState(
|
||||||
|
state_operations=[
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='L2VPN',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True, null=True)),
|
||||||
|
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||||
|
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
|
||||||
|
('description', models.CharField(blank=True, max_length=200)),
|
||||||
|
('comments', models.TextField(blank=True)),
|
||||||
|
('name', models.CharField(max_length=100, unique=True)),
|
||||||
|
('slug', models.SlugField(max_length=100, unique=True)),
|
||||||
|
('type', models.CharField(max_length=50)),
|
||||||
|
('identifier', models.BigIntegerField(blank=True, null=True)),
|
||||||
|
('export_targets', models.ManyToManyField(blank=True, related_name='exporting_l2vpns', to='ipam.routetarget')),
|
||||||
|
('import_targets', models.ManyToManyField(blank=True, related_name='importing_l2vpns', to='ipam.routetarget')),
|
||||||
|
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||||
|
('tenant', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='l2vpns', to='tenancy.tenant')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'L2VPN',
|
||||||
|
'verbose_name_plural': 'L2VPNs',
|
||||||
|
'ordering': ('name', 'identifier'),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='L2VPNTermination',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True, null=True)),
|
||||||
|
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||||
|
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
|
||||||
|
('assigned_object_id', models.PositiveBigIntegerField()),
|
||||||
|
('assigned_object_type', models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'dcim'), ('model', 'interface')), models.Q(('app_label', 'ipam'), ('model', 'vlan')), models.Q(('app_label', 'virtualization'), ('model', 'vminterface')), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype')),
|
||||||
|
('l2vpn', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='terminations', to='vpn.l2vpn')),
|
||||||
|
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'L2VPN termination',
|
||||||
|
'verbose_name_plural': 'L2VPN terminations',
|
||||||
|
'ordering': ('l2vpn',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
# Tables have been renamed from ipam
|
||||||
|
database_operations=[],
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='l2vpntermination',
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=('assigned_object_type', 'assigned_object_id'),
|
||||||
|
name='vpn_l2vpntermination_assigned_object'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -1,2 +1,3 @@
|
|||||||
from .crypto import *
|
from .crypto import *
|
||||||
|
from .l2vpn import *
|
||||||
from .tunnels import *
|
from .tunnels import *
|
||||||
|
@ -6,10 +6,10 @@ from django.utils.functional import cached_property
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from core.models import ContentType
|
from core.models import ContentType
|
||||||
from ipam.choices import L2VPNTypeChoices
|
|
||||||
from ipam.constants import L2VPN_ASSIGNMENT_MODELS
|
|
||||||
from netbox.models import NetBoxModel, PrimaryModel
|
from netbox.models import NetBoxModel, PrimaryModel
|
||||||
from netbox.models.features import ContactsMixin
|
from netbox.models.features import ContactsMixin
|
||||||
|
from vpn.choices import L2VPNTypeChoices
|
||||||
|
from vpn.constants import L2VPN_ASSIGNMENT_MODELS
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'L2VPN',
|
'L2VPN',
|
||||||
@ -69,7 +69,7 @@ class L2VPN(ContactsMixin, PrimaryModel):
|
|||||||
return f'{self.name}'
|
return f'{self.name}'
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('ipam:l2vpn', args=[self.pk])
|
return reverse('vpn:l2vpn', args=[self.pk])
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def can_add_termination(self):
|
def can_add_termination(self):
|
||||||
@ -81,7 +81,7 @@ class L2VPN(ContactsMixin, PrimaryModel):
|
|||||||
|
|
||||||
class L2VPNTermination(NetBoxModel):
|
class L2VPNTermination(NetBoxModel):
|
||||||
l2vpn = models.ForeignKey(
|
l2vpn = models.ForeignKey(
|
||||||
to='ipam.L2VPN',
|
to='vpn.L2VPN',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='terminations'
|
related_name='terminations'
|
||||||
)
|
)
|
||||||
@ -99,7 +99,7 @@ class L2VPNTermination(NetBoxModel):
|
|||||||
|
|
||||||
clone_fields = ('l2vpn',)
|
clone_fields = ('l2vpn',)
|
||||||
prerequisite_models = (
|
prerequisite_models = (
|
||||||
'ipam.L2VPN',
|
'vpn.L2VPN',
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -107,7 +107,7 @@ class L2VPNTermination(NetBoxModel):
|
|||||||
constraints = (
|
constraints = (
|
||||||
models.UniqueConstraint(
|
models.UniqueConstraint(
|
||||||
fields=('assigned_object_type', 'assigned_object_id'),
|
fields=('assigned_object_type', 'assigned_object_id'),
|
||||||
name='ipam_l2vpntermination_assigned_object'
|
name='vpn_l2vpntermination_assigned_object'
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
verbose_name = _('L2VPN termination')
|
verbose_name = _('L2VPN termination')
|
||||||
@ -119,7 +119,7 @@ class L2VPNTermination(NetBoxModel):
|
|||||||
return super().__str__()
|
return super().__str__()
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('ipam:l2vpntermination', args=[self.pk])
|
return reverse('vpn:l2vpntermination', args=[self.pk])
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
# Only check is assigned_object is set. Required otherwise we have an Integrity Error thrown.
|
# Only check is assigned_object is set. Required otherwise we have an Integrity Error thrown.
|
@ -63,3 +63,15 @@ class IPSecProfileIndex(SearchIndex):
|
|||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
display_attrs = ('description',)
|
display_attrs = ('description',)
|
||||||
|
|
||||||
|
|
||||||
|
@register_search
|
||||||
|
class L2VPNIndex(SearchIndex):
|
||||||
|
model = models.L2VPN
|
||||||
|
fields = (
|
||||||
|
('name', 100),
|
||||||
|
('slug', 110),
|
||||||
|
('description', 500),
|
||||||
|
('comments', 5000),
|
||||||
|
)
|
||||||
|
display_attrs = ('type', 'identifier', 'tenant', 'description')
|
||||||
|
3
netbox/vpn/tables/__init__.py
Normal file
3
netbox/vpn/tables/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from .crypto import *
|
||||||
|
from .l2vpn import *
|
||||||
|
from .tunnels import *
|
@ -1,8 +1,6 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_tables2.utils import Accessor
|
|
||||||
|
|
||||||
from tenancy.tables import TenancyColumnsMixin
|
|
||||||
from netbox.tables import NetBoxTable, columns
|
from netbox.tables import NetBoxTable, columns
|
||||||
from vpn.models import *
|
from vpn.models import *
|
||||||
|
|
||||||
@ -12,88 +10,9 @@ __all__ = (
|
|||||||
'IPSecPolicyTable',
|
'IPSecPolicyTable',
|
||||||
'IPSecProposalTable',
|
'IPSecProposalTable',
|
||||||
'IPSecProfileTable',
|
'IPSecProfileTable',
|
||||||
'TunnelTable',
|
|
||||||
'TunnelTerminationTable',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TunnelTable(TenancyColumnsMixin, NetBoxTable):
|
|
||||||
name = tables.Column(
|
|
||||||
verbose_name=_('Name'),
|
|
||||||
linkify=True
|
|
||||||
)
|
|
||||||
status = columns.ChoiceFieldColumn(
|
|
||||||
verbose_name=_('Status')
|
|
||||||
)
|
|
||||||
ipsec_profile = tables.Column(
|
|
||||||
verbose_name=_('IPSec profile'),
|
|
||||||
linkify=True
|
|
||||||
)
|
|
||||||
terminations_count = columns.LinkedCountColumn(
|
|
||||||
accessor=Accessor('count_terminations'),
|
|
||||||
viewname='vpn:tunneltermination_list',
|
|
||||||
url_params={'tunnel_id': 'pk'},
|
|
||||||
verbose_name=_('Terminations')
|
|
||||||
)
|
|
||||||
comments = columns.MarkdownColumn(
|
|
||||||
verbose_name=_('Comments'),
|
|
||||||
)
|
|
||||||
tags = columns.TagColumn(
|
|
||||||
url_name='vpn:tunnel_list'
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta(NetBoxTable.Meta):
|
|
||||||
model = Tunnel
|
|
||||||
fields = (
|
|
||||||
'pk', 'id', 'name', 'status', 'encapsulation', 'ipsec_profile', 'tenant', 'tenant_group', 'tunnel_id',
|
|
||||||
'termination_count', 'description', 'comments', 'tags', 'created', 'last_updated',
|
|
||||||
)
|
|
||||||
default_columns = ('pk', 'name', 'status', 'encapsulation', 'ipsec_profile', 'tenant', 'terminations_count')
|
|
||||||
|
|
||||||
|
|
||||||
class TunnelTerminationTable(TenancyColumnsMixin, NetBoxTable):
|
|
||||||
tunnel = tables.Column(
|
|
||||||
verbose_name=_('Tunnel'),
|
|
||||||
linkify=True
|
|
||||||
)
|
|
||||||
role = columns.ChoiceFieldColumn(
|
|
||||||
verbose_name=_('Role')
|
|
||||||
)
|
|
||||||
termination_parent = tables.Column(
|
|
||||||
accessor='termination__parent_object',
|
|
||||||
linkify=True,
|
|
||||||
orderable=False,
|
|
||||||
verbose_name=_('Host')
|
|
||||||
)
|
|
||||||
termination = tables.Column(
|
|
||||||
verbose_name=_('Termination'),
|
|
||||||
linkify=True
|
|
||||||
)
|
|
||||||
ip_addresses = tables.ManyToManyColumn(
|
|
||||||
accessor=tables.A('termination__ip_addresses'),
|
|
||||||
orderable=False,
|
|
||||||
linkify_item=True,
|
|
||||||
verbose_name=_('IP Addresses')
|
|
||||||
)
|
|
||||||
outside_ip = tables.Column(
|
|
||||||
verbose_name=_('Outside IP'),
|
|
||||||
linkify=True
|
|
||||||
)
|
|
||||||
tags = columns.TagColumn(
|
|
||||||
url_name='vpn:tunneltermination_list'
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta(NetBoxTable.Meta):
|
|
||||||
model = TunnelTermination
|
|
||||||
fields = (
|
|
||||||
'pk', 'id', 'tunnel', 'role', 'termination_parent', 'termination', 'ip_addresses', 'outside_ip', 'tags',
|
|
||||||
'created', 'last_updated',
|
|
||||||
)
|
|
||||||
default_columns = (
|
|
||||||
'pk', 'id', 'tunnel', 'role', 'termination_parent', 'termination', 'ip_addresses', 'outside_ip',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class IKEProposalTable(NetBoxTable):
|
class IKEProposalTable(NetBoxTable):
|
||||||
name = tables.Column(
|
name = tables.Column(
|
||||||
verbose_name=_('Name'),
|
verbose_name=_('Name'),
|
@ -1,9 +1,9 @@
|
|||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from ipam.models import L2VPN, L2VPNTermination
|
|
||||||
from netbox.tables import NetBoxTable, columns
|
from netbox.tables import NetBoxTable, columns
|
||||||
from tenancy.tables import TenancyColumnsMixin
|
from tenancy.tables import TenancyColumnsMixin
|
||||||
|
from vpn.models import L2VPN, L2VPNTermination
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'L2VPNTable',
|
'L2VPNTable',
|
||||||
@ -37,7 +37,7 @@ class L2VPNTable(TenancyColumnsMixin, NetBoxTable):
|
|||||||
verbose_name=_('Comments'),
|
verbose_name=_('Comments'),
|
||||||
)
|
)
|
||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='ipam:l2vpn_list'
|
url_name='vpn:l2vpn_list'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
87
netbox/vpn/tables/tunnels.py
Normal file
87
netbox/vpn/tables/tunnels.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import django_tables2 as tables
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django_tables2.utils import Accessor
|
||||||
|
|
||||||
|
from netbox.tables import NetBoxTable, columns
|
||||||
|
from tenancy.tables import TenancyColumnsMixin
|
||||||
|
from vpn.models import *
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'TunnelTable',
|
||||||
|
'TunnelTerminationTable',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TunnelTable(TenancyColumnsMixin, NetBoxTable):
|
||||||
|
name = tables.Column(
|
||||||
|
verbose_name=_('Name'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
status = columns.ChoiceFieldColumn(
|
||||||
|
verbose_name=_('Status')
|
||||||
|
)
|
||||||
|
ipsec_profile = tables.Column(
|
||||||
|
verbose_name=_('IPSec profile'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
terminations_count = columns.LinkedCountColumn(
|
||||||
|
accessor=Accessor('count_terminations'),
|
||||||
|
viewname='vpn:tunneltermination_list',
|
||||||
|
url_params={'tunnel_id': 'pk'},
|
||||||
|
verbose_name=_('Terminations')
|
||||||
|
)
|
||||||
|
comments = columns.MarkdownColumn(
|
||||||
|
verbose_name=_('Comments'),
|
||||||
|
)
|
||||||
|
tags = columns.TagColumn(
|
||||||
|
url_name='vpn:tunnel_list'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(NetBoxTable.Meta):
|
||||||
|
model = Tunnel
|
||||||
|
fields = (
|
||||||
|
'pk', 'id', 'name', 'status', 'encapsulation', 'ipsec_profile', 'tenant', 'tenant_group', 'tunnel_id',
|
||||||
|
'termination_count', 'description', 'comments', 'tags', 'created', 'last_updated',
|
||||||
|
)
|
||||||
|
default_columns = ('pk', 'name', 'status', 'encapsulation', 'tenant', 'terminations_count')
|
||||||
|
|
||||||
|
|
||||||
|
class TunnelTerminationTable(TenancyColumnsMixin, NetBoxTable):
|
||||||
|
tunnel = tables.Column(
|
||||||
|
verbose_name=_('Tunnel'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
role = columns.ChoiceFieldColumn(
|
||||||
|
verbose_name=_('Role')
|
||||||
|
)
|
||||||
|
interface_parent = tables.Column(
|
||||||
|
accessor='interface__parent_object',
|
||||||
|
linkify=True,
|
||||||
|
orderable=False,
|
||||||
|
verbose_name=_('Host')
|
||||||
|
)
|
||||||
|
interface = tables.Column(
|
||||||
|
verbose_name=_('Interface'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
ip_addresses = tables.ManyToManyColumn(
|
||||||
|
accessor=tables.A('interface__ip_addresses'),
|
||||||
|
orderable=False,
|
||||||
|
linkify_item=True,
|
||||||
|
verbose_name=_('IP Addresses')
|
||||||
|
)
|
||||||
|
outside_ip = tables.Column(
|
||||||
|
verbose_name=_('Outside IP'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
tags = columns.TagColumn(
|
||||||
|
url_name='vpn:tunneltermination_list'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(NetBoxTable.Meta):
|
||||||
|
model = TunnelTermination
|
||||||
|
fields = (
|
||||||
|
'pk', 'id', 'tunnel', 'role', 'interface_parent', 'interface', 'ip_addresses', 'outside_ip', 'tags',
|
||||||
|
'created', 'last_updated',
|
||||||
|
)
|
||||||
|
default_columns = ('pk', 'id', 'tunnel', 'role', 'interface_parent', 'interface', 'ip_addresses', 'outside_ip')
|
@ -2,6 +2,7 @@ from django.urls import reverse
|
|||||||
|
|
||||||
from dcim.choices import InterfaceTypeChoices
|
from dcim.choices import InterfaceTypeChoices
|
||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
|
from ipam.models import VLAN
|
||||||
from utilities.testing import APITestCase, APIViewTestCases, create_test_device
|
from utilities.testing import APITestCase, APIViewTestCases, create_test_device
|
||||||
from vpn.choices import *
|
from vpn.choices import *
|
||||||
from vpn.models import *
|
from vpn.models import *
|
||||||
@ -471,3 +472,96 @@ class IPSecProfileTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'ipsec_policy': ipsec_policies[1].pk,
|
'ipsec_policy': ipsec_policies[1].pk,
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNTest(APIViewTestCases.APIViewTestCase):
|
||||||
|
model = L2VPN
|
||||||
|
brief_fields = ['display', 'id', 'identifier', 'name', 'slug', 'type', 'url']
|
||||||
|
create_data = [
|
||||||
|
{
|
||||||
|
'name': 'L2VPN 4',
|
||||||
|
'slug': 'l2vpn-4',
|
||||||
|
'type': 'vxlan',
|
||||||
|
'identifier': 33343344
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'L2VPN 5',
|
||||||
|
'slug': 'l2vpn-5',
|
||||||
|
'type': 'vxlan',
|
||||||
|
'identifier': 33343345
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'L2VPN 6',
|
||||||
|
'slug': 'l2vpn-6',
|
||||||
|
'type': 'vpws',
|
||||||
|
'identifier': 33343346
|
||||||
|
},
|
||||||
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
l2vpns = (
|
||||||
|
L2VPN(name='L2VPN 1', slug='l2vpn-1', type='vxlan', identifier=650001),
|
||||||
|
L2VPN(name='L2VPN 2', slug='l2vpn-2', type='vpws', identifier=650002),
|
||||||
|
L2VPN(name='L2VPN 3', slug='l2vpn-3', type='vpls'), # No RD
|
||||||
|
)
|
||||||
|
L2VPN.objects.bulk_create(l2vpns)
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNTerminationTest(APIViewTestCases.APIViewTestCase):
|
||||||
|
model = L2VPNTermination
|
||||||
|
brief_fields = ['display', 'id', 'l2vpn', 'url']
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
vlans = (
|
||||||
|
VLAN(name='VLAN 1', vid=651),
|
||||||
|
VLAN(name='VLAN 2', vid=652),
|
||||||
|
VLAN(name='VLAN 3', vid=653),
|
||||||
|
VLAN(name='VLAN 4', vid=654),
|
||||||
|
VLAN(name='VLAN 5', vid=655),
|
||||||
|
VLAN(name='VLAN 6', vid=656),
|
||||||
|
VLAN(name='VLAN 7', vid=657)
|
||||||
|
)
|
||||||
|
VLAN.objects.bulk_create(vlans)
|
||||||
|
|
||||||
|
l2vpns = (
|
||||||
|
L2VPN(name='L2VPN 1', slug='l2vpn-1', type='vxlan', identifier=650001),
|
||||||
|
L2VPN(name='L2VPN 2', slug='l2vpn-2', type='vpws', identifier=650002),
|
||||||
|
L2VPN(name='L2VPN 3', slug='l2vpn-3', type='vpls'), # No RD
|
||||||
|
)
|
||||||
|
L2VPN.objects.bulk_create(l2vpns)
|
||||||
|
|
||||||
|
l2vpnterminations = (
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[0]),
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[1]),
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[2])
|
||||||
|
)
|
||||||
|
L2VPNTermination.objects.bulk_create(l2vpnterminations)
|
||||||
|
|
||||||
|
cls.create_data = [
|
||||||
|
{
|
||||||
|
'l2vpn': l2vpns[0].pk,
|
||||||
|
'assigned_object_type': 'ipam.vlan',
|
||||||
|
'assigned_object_id': vlans[3].pk,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'l2vpn': l2vpns[0].pk,
|
||||||
|
'assigned_object_type': 'ipam.vlan',
|
||||||
|
'assigned_object_id': vlans[4].pk,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'l2vpn': l2vpns[0].pk,
|
||||||
|
'assigned_object_type': 'ipam.vlan',
|
||||||
|
'assigned_object_id': vlans[5].pk,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
cls.bulk_update_data = {
|
||||||
|
'l2vpn': l2vpns[2].pk
|
||||||
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from dcim.choices import InterfaceTypeChoices
|
from dcim.choices import InterfaceTypeChoices
|
||||||
from dcim.models import Interface
|
from dcim.models import Device, Interface, Site
|
||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress, VLAN, RouteTarget
|
||||||
from virtualization.models import VMInterface
|
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device, create_test_virtualmachine
|
||||||
|
from virtualization.models import VirtualMachine, VMInterface
|
||||||
from vpn.choices import *
|
from vpn.choices import *
|
||||||
from vpn.filtersets import *
|
from vpn.filtersets import *
|
||||||
from vpn.models import *
|
from vpn.models import *
|
||||||
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device, create_test_virtualmachine
|
|
||||||
|
|
||||||
|
|
||||||
class TunnelTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class TunnelTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
@ -590,3 +591,163 @@ class IPSecProfileTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'ipsec_policy': [ipsec_policies[0].name, ipsec_policies[1].name]}
|
params = {'ipsec_policy': [ipsec_policies[0].name, ipsec_policies[1].name]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
|
queryset = L2VPN.objects.all()
|
||||||
|
filterset = L2VPNFilterSet
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
route_targets = (
|
||||||
|
RouteTarget(name='1:1'),
|
||||||
|
RouteTarget(name='1:2'),
|
||||||
|
RouteTarget(name='1:3'),
|
||||||
|
RouteTarget(name='2:1'),
|
||||||
|
RouteTarget(name='2:2'),
|
||||||
|
RouteTarget(name='2:3'),
|
||||||
|
)
|
||||||
|
RouteTarget.objects.bulk_create(route_targets)
|
||||||
|
|
||||||
|
l2vpns = (
|
||||||
|
L2VPN(name='L2VPN 1', slug='l2vpn-1', type=L2VPNTypeChoices.TYPE_VXLAN, identifier=65001),
|
||||||
|
L2VPN(name='L2VPN 2', slug='l2vpn-2', type=L2VPNTypeChoices.TYPE_VPWS, identifier=65002),
|
||||||
|
L2VPN(name='L2VPN 3', slug='l2vpn-3', type=L2VPNTypeChoices.TYPE_VPLS),
|
||||||
|
)
|
||||||
|
L2VPN.objects.bulk_create(l2vpns)
|
||||||
|
l2vpns[0].import_targets.add(route_targets[0])
|
||||||
|
l2vpns[1].import_targets.add(route_targets[1])
|
||||||
|
l2vpns[2].import_targets.add(route_targets[2])
|
||||||
|
l2vpns[0].export_targets.add(route_targets[3])
|
||||||
|
l2vpns[1].export_targets.add(route_targets[4])
|
||||||
|
l2vpns[2].export_targets.add(route_targets[5])
|
||||||
|
|
||||||
|
def test_name(self):
|
||||||
|
params = {'name': ['L2VPN 1', 'L2VPN 2']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_slug(self):
|
||||||
|
params = {'slug': ['l2vpn-1', 'l2vpn-2']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_identifier(self):
|
||||||
|
params = {'identifier': ['65001', '65002']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_type(self):
|
||||||
|
params = {'type': [L2VPNTypeChoices.TYPE_VXLAN, L2VPNTypeChoices.TYPE_VPWS]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_import_targets(self):
|
||||||
|
route_targets = RouteTarget.objects.filter(name__in=['1:1', '1:2'])
|
||||||
|
params = {'import_target_id': [route_targets[0].pk, route_targets[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'import_target': [route_targets[0].name, route_targets[1].name]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_export_targets(self):
|
||||||
|
route_targets = RouteTarget.objects.filter(name__in=['2:1', '2:2'])
|
||||||
|
params = {'export_target_id': [route_targets[0].pk, route_targets[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'export_target': [route_targets[0].name, route_targets[1].name]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
|
queryset = L2VPNTermination.objects.all()
|
||||||
|
filterset = L2VPNTerminationFilterSet
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
device = create_test_device('Device 1')
|
||||||
|
interfaces = (
|
||||||
|
Interface(name='Interface 1', device=device, type=InterfaceTypeChoices.TYPE_1GE_FIXED),
|
||||||
|
Interface(name='Interface 2', device=device, type=InterfaceTypeChoices.TYPE_1GE_FIXED),
|
||||||
|
Interface(name='Interface 3', device=device, type=InterfaceTypeChoices.TYPE_1GE_FIXED),
|
||||||
|
)
|
||||||
|
Interface.objects.bulk_create(interfaces)
|
||||||
|
|
||||||
|
vm = create_test_virtualmachine('Virtual Machine 1')
|
||||||
|
vminterfaces = (
|
||||||
|
VMInterface(name='Interface 1', virtual_machine=vm),
|
||||||
|
VMInterface(name='Interface 2', virtual_machine=vm),
|
||||||
|
VMInterface(name='Interface 3', virtual_machine=vm),
|
||||||
|
)
|
||||||
|
VMInterface.objects.bulk_create(vminterfaces)
|
||||||
|
|
||||||
|
vlans = (
|
||||||
|
VLAN(name='VLAN 1', vid=101),
|
||||||
|
VLAN(name='VLAN 2', vid=102),
|
||||||
|
VLAN(name='VLAN 3', vid=103),
|
||||||
|
)
|
||||||
|
VLAN.objects.bulk_create(vlans)
|
||||||
|
|
||||||
|
l2vpns = (
|
||||||
|
L2VPN(name='L2VPN 1', slug='l2vpn-1', type='vxlan', identifier=65001),
|
||||||
|
L2VPN(name='L2VPN 2', slug='l2vpn-2', type='vpws', identifier=65002),
|
||||||
|
L2VPN(name='L2VPN 3', slug='l2vpn-3', type='vpls'), # No RD,
|
||||||
|
)
|
||||||
|
L2VPN.objects.bulk_create(l2vpns)
|
||||||
|
|
||||||
|
l2vpnterminations = (
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[0]),
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[1], assigned_object=vlans[1]),
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[2], assigned_object=vlans[2]),
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=interfaces[0]),
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[1], assigned_object=interfaces[1]),
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[2], assigned_object=interfaces[2]),
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vminterfaces[0]),
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[1], assigned_object=vminterfaces[1]),
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[2], assigned_object=vminterfaces[2]),
|
||||||
|
)
|
||||||
|
L2VPNTermination.objects.bulk_create(l2vpnterminations)
|
||||||
|
|
||||||
|
def test_l2vpn(self):
|
||||||
|
l2vpns = L2VPN.objects.all()[:2]
|
||||||
|
params = {'l2vpn_id': [l2vpns[0].pk, l2vpns[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
|
params = {'l2vpn': [l2vpns[0].slug, l2vpns[1].slug]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
|
|
||||||
|
def test_content_type(self):
|
||||||
|
params = {'assigned_object_type_id': ContentType.objects.get(model='vlan').pk}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
|
||||||
|
def test_interface(self):
|
||||||
|
interfaces = Interface.objects.all()[:2]
|
||||||
|
params = {'interface_id': [interfaces[0].pk, interfaces[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_vminterface(self):
|
||||||
|
vminterfaces = VMInterface.objects.all()[:2]
|
||||||
|
params = {'vminterface_id': [vminterfaces[0].pk, vminterfaces[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_vlan(self):
|
||||||
|
vlans = VLAN.objects.all()[:2]
|
||||||
|
params = {'vlan_id': [vlans[0].pk, vlans[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'vlan': ['VLAN 1', 'VLAN 2']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_site(self):
|
||||||
|
site = Site.objects.all().first()
|
||||||
|
params = {'site_id': [site.pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
params = {'site': ['site-1']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
|
||||||
|
def test_device(self):
|
||||||
|
device = Device.objects.all().first()
|
||||||
|
params = {'device_id': [device.pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
params = {'device': ['Device 1']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
|
||||||
|
def test_virtual_machine(self):
|
||||||
|
virtual_machine = VirtualMachine.objects.all().first()
|
||||||
|
params = {'virtual_machine_id': [virtual_machine.pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
params = {'virtual_machine': ['Virtual Machine 1']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
79
netbox/vpn/tests/test_models.py
Normal file
79
netbox/vpn/tests/test_models.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from dcim.models import Interface, Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||||
|
from ipam.models import VLAN
|
||||||
|
from vpn.models import *
|
||||||
|
|
||||||
|
|
||||||
|
class TestL2VPNTermination(TestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
site = Site.objects.create(name='Site 1')
|
||||||
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1')
|
||||||
|
device_type = DeviceType.objects.create(model='Device Type 1', manufacturer=manufacturer)
|
||||||
|
role = DeviceRole.objects.create(name='Switch')
|
||||||
|
device = Device.objects.create(
|
||||||
|
name='Device 1',
|
||||||
|
site=site,
|
||||||
|
device_type=device_type,
|
||||||
|
role=role,
|
||||||
|
status='active'
|
||||||
|
)
|
||||||
|
|
||||||
|
interfaces = (
|
||||||
|
Interface(name='Interface 1', device=device, type='1000baset'),
|
||||||
|
Interface(name='Interface 2', device=device, type='1000baset'),
|
||||||
|
Interface(name='Interface 3', device=device, type='1000baset'),
|
||||||
|
Interface(name='Interface 4', device=device, type='1000baset'),
|
||||||
|
Interface(name='Interface 5', device=device, type='1000baset'),
|
||||||
|
)
|
||||||
|
|
||||||
|
Interface.objects.bulk_create(interfaces)
|
||||||
|
|
||||||
|
vlans = (
|
||||||
|
VLAN(name='VLAN 1', vid=651),
|
||||||
|
VLAN(name='VLAN 2', vid=652),
|
||||||
|
VLAN(name='VLAN 3', vid=653),
|
||||||
|
VLAN(name='VLAN 4', vid=654),
|
||||||
|
VLAN(name='VLAN 5', vid=655),
|
||||||
|
VLAN(name='VLAN 6', vid=656),
|
||||||
|
VLAN(name='VLAN 7', vid=657)
|
||||||
|
)
|
||||||
|
|
||||||
|
VLAN.objects.bulk_create(vlans)
|
||||||
|
|
||||||
|
l2vpns = (
|
||||||
|
L2VPN(name='L2VPN 1', slug='l2vpn-1', type='vxlan', identifier=650001),
|
||||||
|
L2VPN(name='L2VPN 2', slug='l2vpn-2', type='vpws', identifier=650002),
|
||||||
|
L2VPN(name='L2VPN 3', slug='l2vpn-3', type='vpls'), # No RD
|
||||||
|
)
|
||||||
|
L2VPN.objects.bulk_create(l2vpns)
|
||||||
|
|
||||||
|
l2vpnterminations = (
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[0]),
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[1]),
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[2])
|
||||||
|
)
|
||||||
|
|
||||||
|
L2VPNTermination.objects.bulk_create(l2vpnterminations)
|
||||||
|
|
||||||
|
def test_duplicate_interface_terminations(self):
|
||||||
|
device = Device.objects.first()
|
||||||
|
interface = Interface.objects.filter(device=device).first()
|
||||||
|
l2vpn = L2VPN.objects.first()
|
||||||
|
|
||||||
|
L2VPNTermination.objects.create(l2vpn=l2vpn, assigned_object=interface)
|
||||||
|
duplicate = L2VPNTermination(l2vpn=l2vpn, assigned_object=interface)
|
||||||
|
|
||||||
|
self.assertRaises(ValidationError, duplicate.clean)
|
||||||
|
|
||||||
|
def test_duplicate_vlan_terminations(self):
|
||||||
|
vlan = Interface.objects.first()
|
||||||
|
l2vpn = L2VPN.objects.first()
|
||||||
|
|
||||||
|
L2VPNTermination.objects.create(l2vpn=l2vpn, assigned_object=vlan)
|
||||||
|
duplicate = L2VPNTermination(l2vpn=l2vpn, assigned_object=vlan)
|
||||||
|
self.assertRaises(ValidationError, duplicate.clean)
|
@ -1,8 +1,9 @@
|
|||||||
from dcim.choices import InterfaceTypeChoices
|
from dcim.choices import InterfaceTypeChoices
|
||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
|
from ipam.models import RouteTarget, VLAN
|
||||||
|
from utilities.testing import ViewTestCases, create_tags, create_test_device
|
||||||
from vpn.choices import *
|
from vpn.choices import *
|
||||||
from vpn.models import *
|
from vpn.models import *
|
||||||
from utilities.testing import ViewTestCases, create_tags, create_test_device
|
|
||||||
|
|
||||||
|
|
||||||
class TunnelTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
class TunnelTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||||
@ -506,3 +507,142 @@ class IPSecProfileTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
'ike_policy': ike_policies[1].pk,
|
'ike_policy': ike_policies[1].pk,
|
||||||
'ipsec_policy': ipsec_policies[1].pk,
|
'ipsec_policy': ipsec_policies[1].pk,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||||
|
model = L2VPN
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
rts = (
|
||||||
|
RouteTarget(name='64534:123'),
|
||||||
|
RouteTarget(name='64534:321')
|
||||||
|
)
|
||||||
|
RouteTarget.objects.bulk_create(rts)
|
||||||
|
|
||||||
|
l2vpns = (
|
||||||
|
L2VPN(name='L2VPN 1', slug='l2vpn-1', type=L2VPNTypeChoices.TYPE_VXLAN, identifier='650001'),
|
||||||
|
L2VPN(name='L2VPN 2', slug='l2vpn-2', type=L2VPNTypeChoices.TYPE_VXLAN, identifier='650002'),
|
||||||
|
L2VPN(name='L2VPN 3', slug='l2vpn-3', type=L2VPNTypeChoices.TYPE_VXLAN, identifier='650003')
|
||||||
|
)
|
||||||
|
L2VPN.objects.bulk_create(l2vpns)
|
||||||
|
|
||||||
|
cls.csv_data = (
|
||||||
|
'name,slug,type,identifier',
|
||||||
|
'L2VPN 5,l2vpn-5,vxlan,456',
|
||||||
|
'L2VPN 6,l2vpn-6,vxlan,444',
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.csv_update_data = (
|
||||||
|
'id,name,description',
|
||||||
|
f'{l2vpns[0].pk},L2VPN 7,New description 7',
|
||||||
|
f'{l2vpns[1].pk},L2VPN 8,New description 8',
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.bulk_edit_data = {
|
||||||
|
'description': 'New Description',
|
||||||
|
}
|
||||||
|
|
||||||
|
cls.form_data = {
|
||||||
|
'name': 'L2VPN 8',
|
||||||
|
'slug': 'l2vpn-8',
|
||||||
|
'type': L2VPNTypeChoices.TYPE_VXLAN,
|
||||||
|
'identifier': 123,
|
||||||
|
'description': 'Description',
|
||||||
|
'import_targets': [rts[0].pk],
|
||||||
|
'export_targets': [rts[1].pk]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNTerminationTestCase(
|
||||||
|
ViewTestCases.GetObjectViewTestCase,
|
||||||
|
ViewTestCases.GetObjectChangelogViewTestCase,
|
||||||
|
ViewTestCases.CreateObjectViewTestCase,
|
||||||
|
ViewTestCases.EditObjectViewTestCase,
|
||||||
|
ViewTestCases.DeleteObjectViewTestCase,
|
||||||
|
ViewTestCases.ListObjectsViewTestCase,
|
||||||
|
ViewTestCases.BulkImportObjectsViewTestCase,
|
||||||
|
ViewTestCases.BulkDeleteObjectsViewTestCase,
|
||||||
|
):
|
||||||
|
|
||||||
|
model = L2VPNTermination
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
device = create_test_device('Device 1')
|
||||||
|
interface = Interface.objects.create(name='Interface 1', device=device, type='1000baset')
|
||||||
|
l2vpns = (
|
||||||
|
L2VPN(name='L2VPN 1', slug='l2vpn-1', type=L2VPNTypeChoices.TYPE_VXLAN, identifier=650001),
|
||||||
|
L2VPN(name='L2VPN 2', slug='l2vpn-2', type=L2VPNTypeChoices.TYPE_VXLAN, identifier=650002),
|
||||||
|
)
|
||||||
|
L2VPN.objects.bulk_create(l2vpns)
|
||||||
|
|
||||||
|
vlans = (
|
||||||
|
VLAN(name='Vlan 1', vid=1001),
|
||||||
|
VLAN(name='Vlan 2', vid=1002),
|
||||||
|
VLAN(name='Vlan 3', vid=1003),
|
||||||
|
VLAN(name='Vlan 4', vid=1004),
|
||||||
|
VLAN(name='Vlan 5', vid=1005),
|
||||||
|
VLAN(name='Vlan 6', vid=1006)
|
||||||
|
)
|
||||||
|
VLAN.objects.bulk_create(vlans)
|
||||||
|
|
||||||
|
terminations = (
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[0]),
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[1]),
|
||||||
|
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[2])
|
||||||
|
)
|
||||||
|
L2VPNTermination.objects.bulk_create(terminations)
|
||||||
|
|
||||||
|
cls.form_data = {
|
||||||
|
'l2vpn': l2vpns[0].pk,
|
||||||
|
'device': device.pk,
|
||||||
|
'interface': interface.pk,
|
||||||
|
}
|
||||||
|
|
||||||
|
cls.csv_data = (
|
||||||
|
"l2vpn,vlan",
|
||||||
|
"L2VPN 1,Vlan 4",
|
||||||
|
"L2VPN 1,Vlan 5",
|
||||||
|
"L2VPN 1,Vlan 6",
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.csv_update_data = (
|
||||||
|
f"id,l2vpn",
|
||||||
|
f"{terminations[0].pk},{l2vpns[0].name}",
|
||||||
|
f"{terminations[1].pk},{l2vpns[0].name}",
|
||||||
|
f"{terminations[2].pk},{l2vpns[0].name}",
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.bulk_edit_data = {}
|
||||||
|
|
||||||
|
# TODO: Fix L2VPNTerminationImportForm validation to support bulk updates
|
||||||
|
def test_bulk_update_objects_with_permission(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
#
|
||||||
|
# Custom assertions
|
||||||
|
#
|
||||||
|
|
||||||
|
# TODO: Remove this
|
||||||
|
def assertInstanceEqual(self, instance, data, exclude=None, api=False):
|
||||||
|
"""
|
||||||
|
Override parent
|
||||||
|
"""
|
||||||
|
if exclude is None:
|
||||||
|
exclude = []
|
||||||
|
|
||||||
|
fields = [k for k in data.keys() if k not in exclude]
|
||||||
|
model_dict = self.model_to_dict(instance, fields=fields, api=api)
|
||||||
|
|
||||||
|
# Omit any dictionary keys which are not instance attributes or have been excluded
|
||||||
|
relevant_data = {
|
||||||
|
k: v for k, v in data.items() if hasattr(instance, k) and k not in exclude
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle relations on the model
|
||||||
|
for k, v in model_dict.items():
|
||||||
|
if isinstance(v, object) and hasattr(v, 'first'):
|
||||||
|
model_dict[k] = v.first().pk
|
||||||
|
|
||||||
|
self.assertDictEqual(model_dict, relevant_data)
|
||||||
|
@ -62,4 +62,20 @@ urlpatterns = [
|
|||||||
path('ipsec-profiles/delete/', views.IPSecProfileBulkDeleteView.as_view(), name='ipsecprofile_bulk_delete'),
|
path('ipsec-profiles/delete/', views.IPSecProfileBulkDeleteView.as_view(), name='ipsecprofile_bulk_delete'),
|
||||||
path('ipsec-profiles/<int:pk>/', include(get_model_urls('vpn', 'ipsecprofile'))),
|
path('ipsec-profiles/<int:pk>/', include(get_model_urls('vpn', 'ipsecprofile'))),
|
||||||
|
|
||||||
|
# L2VPN
|
||||||
|
path('l2vpns/', views.L2VPNListView.as_view(), name='l2vpn_list'),
|
||||||
|
path('l2vpns/add/', views.L2VPNEditView.as_view(), name='l2vpn_add'),
|
||||||
|
path('l2vpns/import/', views.L2VPNBulkImportView.as_view(), name='l2vpn_import'),
|
||||||
|
path('l2vpns/edit/', views.L2VPNBulkEditView.as_view(), name='l2vpn_bulk_edit'),
|
||||||
|
path('l2vpns/delete/', views.L2VPNBulkDeleteView.as_view(), name='l2vpn_bulk_delete'),
|
||||||
|
path('l2vpns/<int:pk>/', include(get_model_urls('vpn', 'l2vpn'))),
|
||||||
|
|
||||||
|
# L2VPN terminations
|
||||||
|
path('l2vpn-terminations/', views.L2VPNTerminationListView.as_view(), name='l2vpntermination_list'),
|
||||||
|
path('l2vpn-terminations/add/', views.L2VPNTerminationEditView.as_view(), name='l2vpntermination_add'),
|
||||||
|
path('l2vpn-terminations/import/', views.L2VPNTerminationBulkImportView.as_view(), name='l2vpntermination_import'),
|
||||||
|
path('l2vpn-terminations/edit/', views.L2VPNTerminationBulkEditView.as_view(), name='l2vpntermination_bulk_edit'),
|
||||||
|
path('l2vpn-terminations/delete/', views.L2VPNTerminationBulkDeleteView.as_view(), name='l2vpntermination_bulk_delete'),
|
||||||
|
path('l2vpn-terminations/<int:pk>/', include(get_model_urls('vpn', 'l2vpntermination'))),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
from ipam.tables import RouteTargetTable
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
|
from tenancy.views import ObjectContactsView
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
from utilities.views import register_model_view
|
from utilities.views import register_model_view
|
||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
@ -332,3 +334,112 @@ class IPSecProfileBulkDeleteView(generic.BulkDeleteView):
|
|||||||
queryset = IPSecProfile.objects.all()
|
queryset = IPSecProfile.objects.all()
|
||||||
filterset = filtersets.IPSecProfileFilterSet
|
filterset = filtersets.IPSecProfileFilterSet
|
||||||
table = tables.IPSecProfileTable
|
table = tables.IPSecProfileTable
|
||||||
|
|
||||||
|
|
||||||
|
# L2VPN
|
||||||
|
|
||||||
|
class L2VPNListView(generic.ObjectListView):
|
||||||
|
queryset = L2VPN.objects.all()
|
||||||
|
table = tables.L2VPNTable
|
||||||
|
filterset = filtersets.L2VPNFilterSet
|
||||||
|
filterset_form = forms.L2VPNFilterForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(L2VPN)
|
||||||
|
class L2VPNView(generic.ObjectView):
|
||||||
|
queryset = L2VPN.objects.all()
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
import_targets_table = RouteTargetTable(
|
||||||
|
instance.import_targets.prefetch_related('tenant'),
|
||||||
|
orderable=False
|
||||||
|
)
|
||||||
|
export_targets_table = RouteTargetTable(
|
||||||
|
instance.export_targets.prefetch_related('tenant'),
|
||||||
|
orderable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'import_targets_table': import_targets_table,
|
||||||
|
'export_targets_table': export_targets_table,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(L2VPN, 'edit')
|
||||||
|
class L2VPNEditView(generic.ObjectEditView):
|
||||||
|
queryset = L2VPN.objects.all()
|
||||||
|
form = forms.L2VPNForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(L2VPN, 'delete')
|
||||||
|
class L2VPNDeleteView(generic.ObjectDeleteView):
|
||||||
|
queryset = L2VPN.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNBulkImportView(generic.BulkImportView):
|
||||||
|
queryset = L2VPN.objects.all()
|
||||||
|
model_form = forms.L2VPNImportForm
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNBulkEditView(generic.BulkEditView):
|
||||||
|
queryset = L2VPN.objects.all()
|
||||||
|
filterset = filtersets.L2VPNFilterSet
|
||||||
|
table = tables.L2VPNTable
|
||||||
|
form = forms.L2VPNBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNBulkDeleteView(generic.BulkDeleteView):
|
||||||
|
queryset = L2VPN.objects.all()
|
||||||
|
filterset = filtersets.L2VPNFilterSet
|
||||||
|
table = tables.L2VPNTable
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(L2VPN, 'contacts')
|
||||||
|
class L2VPNContactsView(ObjectContactsView):
|
||||||
|
queryset = L2VPN.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# L2VPN terminations
|
||||||
|
#
|
||||||
|
|
||||||
|
class L2VPNTerminationListView(generic.ObjectListView):
|
||||||
|
queryset = L2VPNTermination.objects.all()
|
||||||
|
table = tables.L2VPNTerminationTable
|
||||||
|
filterset = filtersets.L2VPNTerminationFilterSet
|
||||||
|
filterset_form = forms.L2VPNTerminationFilterForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(L2VPNTermination)
|
||||||
|
class L2VPNTerminationView(generic.ObjectView):
|
||||||
|
queryset = L2VPNTermination.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(L2VPNTermination, 'edit')
|
||||||
|
class L2VPNTerminationEditView(generic.ObjectEditView):
|
||||||
|
queryset = L2VPNTermination.objects.all()
|
||||||
|
form = forms.L2VPNTerminationForm
|
||||||
|
template_name = 'vpn/l2vpntermination_edit.html'
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(L2VPNTermination, 'delete')
|
||||||
|
class L2VPNTerminationDeleteView(generic.ObjectDeleteView):
|
||||||
|
queryset = L2VPNTermination.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNTerminationBulkImportView(generic.BulkImportView):
|
||||||
|
queryset = L2VPNTermination.objects.all()
|
||||||
|
model_form = forms.L2VPNTerminationImportForm
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNTerminationBulkEditView(generic.BulkEditView):
|
||||||
|
queryset = L2VPNTermination.objects.all()
|
||||||
|
filterset = filtersets.L2VPNTerminationFilterSet
|
||||||
|
table = tables.L2VPNTerminationTable
|
||||||
|
form = forms.L2VPNTerminationBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNTerminationBulkDeleteView(generic.BulkDeleteView):
|
||||||
|
queryset = L2VPNTermination.objects.all()
|
||||||
|
filterset = filtersets.L2VPNTerminationFilterSet
|
||||||
|
table = tables.L2VPNTerminationTable
|
||||||
|
Loading…
Reference in New Issue
Block a user