mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
#9816: Add TunnelGroup
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
# Tunnels
|
# Tunnels
|
||||||
|
|
||||||
NetBox can model private tunnels formed among virtual termination points across your network. Typical tunnel implementations include GRE, IP-in-IP, and IPSec. A tunnel may be terminated to two or more device or virtual machine interfaces.
|
NetBox can model private tunnels formed among virtual termination points across your network. Typical tunnel implementations include GRE, IP-in-IP, and IPSec. A tunnel may be terminated to two or more device or virtual machine interfaces. For convenient organization, tunnels may be assigned to user-defined groups.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
|
@ -14,15 +14,17 @@ A unique name assigned to the tunnel for identification.
|
|||||||
|
|
||||||
The operational status of the tunnel. By default, the following statuses are available:
|
The operational status of the tunnel. By default, the following statuses are available:
|
||||||
|
|
||||||
| Name |
|
* Planned
|
||||||
|----------------|
|
* Active
|
||||||
| Planned |
|
* Disabled
|
||||||
| Active |
|
|
||||||
| Disabled |
|
|
||||||
|
|
||||||
!!! tip "Custom tunnel statuses"
|
!!! tip "Custom tunnel statuses"
|
||||||
Additional tunnel statuses may be defined by setting `Tunnel.status` under the [`FIELD_CHOICES`](../../configuration/data-validation.md#field_choices) configuration parameter.
|
Additional tunnel statuses may be defined by setting `Tunnel.status` under the [`FIELD_CHOICES`](../../configuration/data-validation.md#field_choices) configuration parameter.
|
||||||
|
|
||||||
|
### Group
|
||||||
|
|
||||||
|
The [administrative group](./tunnelgroup.md) to which this tunnel is assigned (optional).
|
||||||
|
|
||||||
### Encapsulation
|
### Encapsulation
|
||||||
|
|
||||||
The encapsulation protocol or technique employed to effect the tunnel. NetBox supports GRE, IP-in-IP, and IPSec encapsulations.
|
The encapsulation protocol or technique employed to effect the tunnel. NetBox supports GRE, IP-in-IP, and IPSec encapsulations.
|
||||||
|
13
docs/models/vpn/tunnelgroup.md
Normal file
13
docs/models/vpn/tunnelgroup.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Tunnel Group
|
||||||
|
|
||||||
|
[Tunnels](./tunnel.md) can be arranged into administrative groups for organization. For example, you might crete a group to manage all peer-to-peer tunnels inside a mesh network. The assignment of a tunnel to a group is optional.
|
||||||
|
|
||||||
|
## Fields
|
||||||
|
|
||||||
|
### Name
|
||||||
|
|
||||||
|
A unique human-friendly name.
|
||||||
|
|
||||||
|
### Slug
|
||||||
|
|
||||||
|
A unique URL-friendly identifier. (This value can be used for filtering.)
|
@ -361,7 +361,7 @@ INTERFACE_BUTTONS = """
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% elif record.type == 'virtual' %}
|
{% elif record.type == 'virtual' %}
|
||||||
{% if perms.vpn.add_tunnel and not record.tunnel_termination %}
|
{% if perms.vpn.add_tunnel and not record.tunnel_termination %}
|
||||||
<a href="{% url 'vpn:tunnel_add' %}?termination1_type=dcim.device&termination1_parent={{ record.device.pk }}&termination1_interface={{ record.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}" title="Create a tunnel" class="btn btn-success btn-sm">
|
<a href="{% url 'vpn:tunnel_add' %}?termination1_type=dcim.device&termination1_parent={{ record.device.pk }}&termination1_termination={{ record.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}" title="Create a tunnel" class="btn btn-success btn-sm">
|
||||||
<i class="mdi mdi-tunnel-outline" aria-hidden="true"></i>
|
<i class="mdi mdi-tunnel-outline" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% elif perms.vpn.delete_tunneltermination and record.tunnel_termination %}
|
{% elif perms.vpn.delete_tunneltermination and record.tunnel_termination %}
|
||||||
|
@ -203,6 +203,7 @@ VPN_MENU = Menu(
|
|||||||
label=_('Tunnels'),
|
label=_('Tunnels'),
|
||||||
items=(
|
items=(
|
||||||
get_model_item('vpn', 'tunnel', _('Tunnels')),
|
get_model_item('vpn', 'tunnel', _('Tunnels')),
|
||||||
|
get_model_item('vpn', 'tunnelgroup', _('Tunnel Groups')),
|
||||||
get_model_item('vpn', 'tunneltermination', _('Tunnel Terminations')),
|
get_model_item('vpn', 'tunneltermination', _('Tunnel Terminations')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -26,6 +26,10 @@
|
|||||||
<th scope="row">{% trans "Status" %}</th>
|
<th scope="row">{% trans "Status" %}</th>
|
||||||
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
|
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Group" %}</th>
|
||||||
|
<td>{{ object.group|linkify|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Description" %}</th>
|
<th scope="row">{% trans "Description" %}</th>
|
||||||
<td>{{ object.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
|
53
netbox/templates/vpn/tunnelgroup.html
Normal file
53
netbox/templates/vpn/tunnelgroup.html
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
{% extends 'generic/object.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load plugins %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'vpn:tunnelgroup_list' %}">{% trans "Tunnel Groups" %}</a></li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_controls %}
|
||||||
|
{% if perms.vpn.add_tunnel %}
|
||||||
|
<a href="{% url 'vpn:tunnel_add' %}?group={{ object.pk }}" class="btn btn-sm btn-primary">
|
||||||
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add Tunnel" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock extra_controls %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<h5 class="card-header">
|
||||||
|
{% trans "Tunnel Group" %}
|
||||||
|
</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-hover attr-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Name" %}</th>
|
||||||
|
<td>{{ object.name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Description" %}</th>
|
||||||
|
<td>{{ object.description|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include 'inc/panels/tags.html' %}
|
||||||
|
{% plugin_left_page object %}
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-6">
|
||||||
|
{% include 'inc/panels/related_objects.html' %}
|
||||||
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
|
{% plugin_right_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
{% plugin_full_width_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -1,3 +1,4 @@
|
|||||||
|
from drf_spectacular.utils import extend_schema_serializer
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from netbox.api.serializers import WritableNestedSerializer
|
from netbox.api.serializers import WritableNestedSerializer
|
||||||
@ -11,11 +12,24 @@ __all__ = (
|
|||||||
'NestedIPSecProposalSerializer',
|
'NestedIPSecProposalSerializer',
|
||||||
'NestedL2VPNSerializer',
|
'NestedL2VPNSerializer',
|
||||||
'NestedL2VPNTerminationSerializer',
|
'NestedL2VPNTerminationSerializer',
|
||||||
|
'NestedTunnelGroupSerializer',
|
||||||
'NestedTunnelSerializer',
|
'NestedTunnelSerializer',
|
||||||
'NestedTunnelTerminationSerializer',
|
'NestedTunnelTerminationSerializer',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_serializer(
|
||||||
|
exclude_fields=('tunnel_count',),
|
||||||
|
)
|
||||||
|
class NestedTunnelGroupSerializer(WritableNestedSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='vpn-api:tunnelgroup-detail')
|
||||||
|
tunnel_count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.TunnelGroup
|
||||||
|
fields = ['id', 'url', 'display', 'name', 'slug', 'tunnel_count']
|
||||||
|
|
||||||
|
|
||||||
class NestedTunnelSerializer(WritableNestedSerializer):
|
class NestedTunnelSerializer(WritableNestedSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(
|
url = serializers.HyperlinkedIdentityField(
|
||||||
view_name='vpn-api:tunnel-detail'
|
view_name='vpn-api:tunnel-detail'
|
||||||
|
@ -21,11 +21,24 @@ __all__ = (
|
|||||||
'IPSecProposalSerializer',
|
'IPSecProposalSerializer',
|
||||||
'L2VPNSerializer',
|
'L2VPNSerializer',
|
||||||
'L2VPNTerminationSerializer',
|
'L2VPNTerminationSerializer',
|
||||||
|
'TunnelGroupSerializer',
|
||||||
'TunnelSerializer',
|
'TunnelSerializer',
|
||||||
'TunnelTerminationSerializer',
|
'TunnelTerminationSerializer',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TunnelGroupSerializer(NetBoxModelSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='vpn-api:tunnelgroup-detail')
|
||||||
|
tunnel_count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = TunnelGroup
|
||||||
|
fields = [
|
||||||
|
'id', 'url', 'display', 'name', 'slug', 'description', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||||
|
'tunnel_count',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class TunnelSerializer(NetBoxModelSerializer):
|
class TunnelSerializer(NetBoxModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(
|
url = serializers.HyperlinkedIdentityField(
|
||||||
view_name='vpn-api:tunnel-detail'
|
view_name='vpn-api:tunnel-detail'
|
||||||
@ -33,6 +46,7 @@ class TunnelSerializer(NetBoxModelSerializer):
|
|||||||
status = ChoiceField(
|
status = ChoiceField(
|
||||||
choices=TunnelStatusChoices
|
choices=TunnelStatusChoices
|
||||||
)
|
)
|
||||||
|
group = NestedTunnelGroupSerializer()
|
||||||
encapsulation = ChoiceField(
|
encapsulation = ChoiceField(
|
||||||
choices=TunnelEncapsulationChoices
|
choices=TunnelEncapsulationChoices
|
||||||
)
|
)
|
||||||
@ -48,7 +62,7 @@ class TunnelSerializer(NetBoxModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Tunnel
|
model = Tunnel
|
||||||
fields = (
|
fields = (
|
||||||
'id', 'url', 'display', 'name', 'status', 'encapsulation', 'ipsec_profile', 'tenant', 'tunnel_id',
|
'id', 'url', 'display', 'name', 'status', 'group', 'encapsulation', 'ipsec_profile', 'tenant', 'tunnel_id',
|
||||||
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ router.register('ike-proposals', views.IKEProposalViewSet)
|
|||||||
router.register('ipsec-policies', views.IPSecPolicyViewSet)
|
router.register('ipsec-policies', views.IPSecPolicyViewSet)
|
||||||
router.register('ipsec-proposals', views.IPSecProposalViewSet)
|
router.register('ipsec-proposals', views.IPSecProposalViewSet)
|
||||||
router.register('ipsec-profiles', views.IPSecProfileViewSet)
|
router.register('ipsec-profiles', views.IPSecProfileViewSet)
|
||||||
|
router.register('tunnel-groups', views.TunnelGroupViewSet)
|
||||||
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('l2vpns', views.L2VPNViewSet)
|
||||||
|
@ -14,6 +14,7 @@ __all__ = (
|
|||||||
'IPSecProposalViewSet',
|
'IPSecProposalViewSet',
|
||||||
'L2VPNViewSet',
|
'L2VPNViewSet',
|
||||||
'L2VPNTerminationViewSet',
|
'L2VPNTerminationViewSet',
|
||||||
|
'TunnelGroupViewSet',
|
||||||
'TunnelTerminationViewSet',
|
'TunnelTerminationViewSet',
|
||||||
'TunnelViewSet',
|
'TunnelViewSet',
|
||||||
'VPNRootView',
|
'VPNRootView',
|
||||||
@ -32,6 +33,14 @@ class VPNRootView(APIRootView):
|
|||||||
# Viewsets
|
# Viewsets
|
||||||
#
|
#
|
||||||
|
|
||||||
|
class TunnelGroupViewSet(NetBoxModelViewSet):
|
||||||
|
queryset = TunnelGroup.objects.annotate(
|
||||||
|
tunnel_count=count_related(Tunnel, 'group')
|
||||||
|
)
|
||||||
|
serializer_class = serializers.TunnelGroupSerializer
|
||||||
|
filterset_class = filtersets.TunnelGroupFilterSet
|
||||||
|
|
||||||
|
|
||||||
class TunnelViewSet(NetBoxModelViewSet):
|
class TunnelViewSet(NetBoxModelViewSet):
|
||||||
queryset = Tunnel.objects.prefetch_related('ipsec_profile', 'tenant').annotate(
|
queryset = Tunnel.objects.prefetch_related('ipsec_profile', 'tenant').annotate(
|
||||||
terminations_count=count_related(TunnelTermination, 'tunnel')
|
terminations_count=count_related(TunnelTermination, 'tunnel')
|
||||||
|
@ -4,7 +4,7 @@ from django.utils.translation import gettext as _
|
|||||||
|
|
||||||
from dcim.models import Device, Interface
|
from dcim.models import Device, Interface
|
||||||
from ipam.models import IPAddress, RouteTarget, VLAN
|
from ipam.models import IPAddress, RouteTarget, VLAN
|
||||||
from netbox.filtersets import NetBoxModelFilterSet
|
from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet
|
||||||
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 VirtualMachine, VMInterface
|
from virtualization.models import VirtualMachine, VMInterface
|
||||||
@ -20,14 +20,32 @@ __all__ = (
|
|||||||
'L2VPNFilterSet',
|
'L2VPNFilterSet',
|
||||||
'L2VPNTerminationFilterSet',
|
'L2VPNTerminationFilterSet',
|
||||||
'TunnelFilterSet',
|
'TunnelFilterSet',
|
||||||
|
'TunnelGroupFilterSet',
|
||||||
'TunnelTerminationFilterSet',
|
'TunnelTerminationFilterSet',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TunnelGroupFilterSet(OrganizationalModelFilterSet):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = TunnelGroup
|
||||||
|
fields = ['id', 'name', 'slug', 'description']
|
||||||
|
|
||||||
|
|
||||||
class TunnelFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
class TunnelFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=TunnelStatusChoices
|
choices=TunnelStatusChoices
|
||||||
)
|
)
|
||||||
|
group_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=TunnelGroup.objects.all(),
|
||||||
|
label=_('Tunnel group (ID)'),
|
||||||
|
)
|
||||||
|
group = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='group__slug',
|
||||||
|
queryset=TunnelGroup.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label=_('Tunnel group (slug)'),
|
||||||
|
)
|
||||||
encapsulation = django_filters.MultipleChoiceFilter(
|
encapsulation = django_filters.MultipleChoiceFilter(
|
||||||
choices=TunnelEncapsulationChoices
|
choices=TunnelEncapsulationChoices
|
||||||
)
|
)
|
||||||
|
@ -17,16 +17,33 @@ __all__ = (
|
|||||||
'L2VPNBulkEditForm',
|
'L2VPNBulkEditForm',
|
||||||
'L2VPNTerminationBulkEditForm',
|
'L2VPNTerminationBulkEditForm',
|
||||||
'TunnelBulkEditForm',
|
'TunnelBulkEditForm',
|
||||||
|
'TunnelGroupBulkEditForm',
|
||||||
'TunnelTerminationBulkEditForm',
|
'TunnelTerminationBulkEditForm',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TunnelGroupBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
|
description = forms.CharField(
|
||||||
|
label=_('Description'),
|
||||||
|
max_length=200,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
model = TunnelGroup
|
||||||
|
nullable_fields = ('description',)
|
||||||
|
|
||||||
|
|
||||||
class TunnelBulkEditForm(NetBoxModelBulkEditForm):
|
class TunnelBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
label=_('Status'),
|
label=_('Status'),
|
||||||
choices=add_blank_choice(TunnelStatusChoices),
|
choices=add_blank_choice(TunnelStatusChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
group = DynamicModelChoiceField(
|
||||||
|
queryset=TunnelGroup.objects.all(),
|
||||||
|
label=_('Tunnel group'),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
encapsulation = forms.ChoiceField(
|
encapsulation = forms.ChoiceField(
|
||||||
label=_('Encapsulation'),
|
label=_('Encapsulation'),
|
||||||
choices=add_blank_choice(TunnelEncapsulationChoices),
|
choices=add_blank_choice(TunnelEncapsulationChoices),
|
||||||
@ -55,12 +72,12 @@ class TunnelBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = Tunnel
|
model = Tunnel
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Tunnel'), ('status', 'encapsulation', 'tunnel_id', 'description')),
|
(_('Tunnel'), ('status', 'group', 'encapsulation', 'tunnel_id', 'description')),
|
||||||
(_('Security'), ('ipsec_profile',)),
|
(_('Security'), ('ipsec_profile',)),
|
||||||
(_('Tenancy'), ('tenant',)),
|
(_('Tenancy'), ('tenant',)),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'ipsec_profile', 'tunnel_id', 'tenant', 'description', 'comments',
|
'group', 'ipsec_profile', 'tunnel_id', 'tenant', 'description', 'comments',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ from dcim.models import Device, Interface
|
|||||||
from ipam.models import IPAddress, VLAN
|
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, SlugField
|
||||||
from virtualization.models import VirtualMachine, VMInterface
|
from virtualization.models import VirtualMachine, VMInterface
|
||||||
from vpn.choices import *
|
from vpn.choices import *
|
||||||
from vpn.models import *
|
from vpn.models import *
|
||||||
@ -19,16 +19,31 @@ __all__ = (
|
|||||||
'L2VPNImportForm',
|
'L2VPNImportForm',
|
||||||
'L2VPNTerminationImportForm',
|
'L2VPNTerminationImportForm',
|
||||||
'TunnelImportForm',
|
'TunnelImportForm',
|
||||||
|
'TunnelGroupImportForm',
|
||||||
'TunnelTerminationImportForm',
|
'TunnelTerminationImportForm',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TunnelGroupImportForm(NetBoxModelImportForm):
|
||||||
|
slug = SlugField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = TunnelGroup
|
||||||
|
fields = ('name', 'slug', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class TunnelImportForm(NetBoxModelImportForm):
|
class TunnelImportForm(NetBoxModelImportForm):
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
label=_('Status'),
|
label=_('Status'),
|
||||||
choices=TunnelStatusChoices,
|
choices=TunnelStatusChoices,
|
||||||
help_text=_('Operational status')
|
help_text=_('Operational status')
|
||||||
)
|
)
|
||||||
|
group = CSVModelChoiceField(
|
||||||
|
label=_('Tunnel group'),
|
||||||
|
queryset=TunnelGroup.objects.all(),
|
||||||
|
required=False,
|
||||||
|
to_field_name='name'
|
||||||
|
)
|
||||||
encapsulation = CSVChoiceField(
|
encapsulation = CSVChoiceField(
|
||||||
label=_('Encapsulation'),
|
label=_('Encapsulation'),
|
||||||
choices=TunnelEncapsulationChoices,
|
choices=TunnelEncapsulationChoices,
|
||||||
@ -51,8 +66,8 @@ class TunnelImportForm(NetBoxModelImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Tunnel
|
model = Tunnel
|
||||||
fields = (
|
fields = (
|
||||||
'name', 'status', 'encapsulation', 'ipsec_profile', 'tenant', 'tunnel_id', 'description', 'comments',
|
'name', 'status', 'group', 'encapsulation', 'ipsec_profile', 'tenant', 'tunnel_id', 'description',
|
||||||
'tags',
|
'comments', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,10 +24,16 @@ __all__ = (
|
|||||||
'L2VPNFilterForm',
|
'L2VPNFilterForm',
|
||||||
'L2VPNTerminationFilterForm',
|
'L2VPNTerminationFilterForm',
|
||||||
'TunnelFilterForm',
|
'TunnelFilterForm',
|
||||||
|
'TunnelGroupFilterForm',
|
||||||
'TunnelTerminationFilterForm',
|
'TunnelTerminationFilterForm',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TunnelGroupFilterForm(NetBoxModelFilterSetForm):
|
||||||
|
model = TunnelGroup
|
||||||
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
class TunnelFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
class TunnelFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = Tunnel
|
model = Tunnel
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
@ -41,6 +47,11 @@ class TunnelFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
choices=TunnelStatusChoices,
|
choices=TunnelStatusChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
group_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=TunnelGroup.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Tunnel group')
|
||||||
|
)
|
||||||
encapsulation = forms.MultipleChoiceField(
|
encapsulation = forms.MultipleChoiceField(
|
||||||
label=_('Encapsulation'),
|
label=_('Encapsulation'),
|
||||||
choices=TunnelEncapsulationChoices,
|
choices=TunnelEncapsulationChoices,
|
||||||
|
@ -23,11 +23,31 @@ __all__ = (
|
|||||||
'L2VPNTerminationForm',
|
'L2VPNTerminationForm',
|
||||||
'TunnelCreateForm',
|
'TunnelCreateForm',
|
||||||
'TunnelForm',
|
'TunnelForm',
|
||||||
|
'TunnelGroupForm',
|
||||||
'TunnelTerminationForm',
|
'TunnelTerminationForm',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TunnelGroupForm(NetBoxModelForm):
|
||||||
|
slug = SlugField()
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
(_('Tunnel Group'), ('name', 'slug', 'description', 'tags')),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = TunnelGroup
|
||||||
|
fields = [
|
||||||
|
'name', 'slug', 'description', 'tags',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class TunnelForm(TenancyForm, NetBoxModelForm):
|
class TunnelForm(TenancyForm, NetBoxModelForm):
|
||||||
|
group = DynamicModelChoiceField(
|
||||||
|
queryset=TunnelGroup.objects.all(),
|
||||||
|
label=_('Tunnel Group'),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
ipsec_profile = DynamicModelChoiceField(
|
ipsec_profile = DynamicModelChoiceField(
|
||||||
queryset=IPSecProfile.objects.all(),
|
queryset=IPSecProfile.objects.all(),
|
||||||
label=_('IPSec Profile'),
|
label=_('IPSec Profile'),
|
||||||
@ -36,7 +56,7 @@ class TunnelForm(TenancyForm, NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Tunnel'), ('name', 'status', 'encapsulation', 'description', 'tunnel_id', 'tags')),
|
(_('Tunnel'), ('name', 'status', 'group', 'encapsulation', 'description', 'tunnel_id', 'tags')),
|
||||||
(_('Security'), ('ipsec_profile',)),
|
(_('Security'), ('ipsec_profile',)),
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
(_('Tenancy'), ('tenant_group', 'tenant')),
|
||||||
)
|
)
|
||||||
@ -44,8 +64,8 @@ class TunnelForm(TenancyForm, NetBoxModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Tunnel
|
model = Tunnel
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'status', 'encapsulation', 'description', 'tunnel_id', 'ipsec_profile', 'tenant_group', 'tenant',
|
'name', 'status', 'group', 'encapsulation', 'description', 'tunnel_id', 'ipsec_profile', 'tenant_group',
|
||||||
'comments', 'tags',
|
'tenant', 'comments', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,6 +56,12 @@ class VPNQuery(graphene.ObjectType):
|
|||||||
def resolve_tunnel_list(root, info, **kwargs):
|
def resolve_tunnel_list(root, info, **kwargs):
|
||||||
return gql_query_optimizer(models.Tunnel.objects.all(), info)
|
return gql_query_optimizer(models.Tunnel.objects.all(), info)
|
||||||
|
|
||||||
|
tunnel_group = ObjectField(TunnelGroupType)
|
||||||
|
tunnel_group_list = ObjectListField(TunnelGroupType)
|
||||||
|
|
||||||
|
def resolve_tunnel_group_list(root, info, **kwargs):
|
||||||
|
return gql_query_optimizer(models.TunnelGroup.objects.all(), info)
|
||||||
|
|
||||||
tunnel_termination = ObjectField(TunnelTerminationType)
|
tunnel_termination = ObjectField(TunnelTerminationType)
|
||||||
tunnel_termination_list = ObjectListField(TunnelTerminationType)
|
tunnel_termination_list = ObjectListField(TunnelTerminationType)
|
||||||
|
|
||||||
|
@ -12,11 +12,20 @@ __all__ = (
|
|||||||
'IPSecProposalType',
|
'IPSecProposalType',
|
||||||
'L2VPNType',
|
'L2VPNType',
|
||||||
'L2VPNTerminationType',
|
'L2VPNTerminationType',
|
||||||
|
'TunnelGroupType',
|
||||||
'TunnelTerminationType',
|
'TunnelTerminationType',
|
||||||
'TunnelType',
|
'TunnelType',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TunnelGroupType(OrganizationalObjectType):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.TunnelGroup
|
||||||
|
fields = '__all__'
|
||||||
|
filterset_class = filtersets.TunnelGroupFilterSet
|
||||||
|
|
||||||
|
|
||||||
class TunnelTerminationType(CustomFieldsMixin, TagsMixin, ObjectType):
|
class TunnelTerminationType(CustomFieldsMixin, TagsMixin, ObjectType):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -16,140 +16,7 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
# IKE
|
||||||
name='IKEPolicy',
|
|
||||||
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)),
|
|
||||||
('version', models.PositiveSmallIntegerField(default=2)),
|
|
||||||
('mode', models.CharField()),
|
|
||||||
('preshared_key', models.TextField(blank=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'IKE policy',
|
|
||||||
'verbose_name_plural': 'IKE policies',
|
|
||||||
'ordering': ('name',),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='IPSecPolicy',
|
|
||||||
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)),
|
|
||||||
('pfs_group', models.PositiveSmallIntegerField(blank=True, null=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'IPSec policy',
|
|
||||||
'verbose_name_plural': 'IPSec policies',
|
|
||||||
'ordering': ('name',),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='IPSecProfile',
|
|
||||||
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)),
|
|
||||||
('mode', models.CharField()),
|
|
||||||
('ike_policy', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='ipsec_profiles', to='vpn.ikepolicy')),
|
|
||||||
('ipsec_policy', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='ipsec_profiles', to='vpn.ipsecpolicy')),
|
|
||||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'IPSec profile',
|
|
||||||
'verbose_name_plural': 'IPSec profiles',
|
|
||||||
'ordering': ('name',),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Tunnel',
|
|
||||||
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)),
|
|
||||||
('status', models.CharField(default='active', max_length=50)),
|
|
||||||
('encapsulation', models.CharField(max_length=50)),
|
|
||||||
('tunnel_id', models.PositiveBigIntegerField(blank=True, null=True)),
|
|
||||||
('ipsec_profile', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='tunnels', to='vpn.ipsecprofile')),
|
|
||||||
('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='tunnels', to='tenancy.tenant')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'tunnel',
|
|
||||||
'verbose_name_plural': 'tunnels',
|
|
||||||
'ordering': ('name',),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='TunnelTermination',
|
|
||||||
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)),
|
|
||||||
('role', models.CharField(default='peer', max_length=50)),
|
|
||||||
('termination_id', models.PositiveBigIntegerField(blank=True, null=True)),
|
|
||||||
('termination_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype')),
|
|
||||||
('outside_ip', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='tunnel_termination', to='ipam.ipaddress')),
|
|
||||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
|
||||||
('tunnel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='terminations', to='vpn.tunnel')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'tunnel termination',
|
|
||||||
'verbose_name_plural': 'tunnel terminations',
|
|
||||||
'ordering': ('tunnel', 'role', 'pk'),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='IPSecProposal',
|
|
||||||
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)),
|
|
||||||
('encryption_algorithm', models.CharField()),
|
|
||||||
('authentication_algorithm', models.CharField()),
|
|
||||||
('sa_lifetime_seconds', models.PositiveIntegerField(blank=True, null=True)),
|
|
||||||
('sa_lifetime_data', models.PositiveIntegerField(blank=True, null=True)),
|
|
||||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'IPSec proposal',
|
|
||||||
'verbose_name_plural': 'IPSec proposals',
|
|
||||||
'ordering': ('name',),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='ipsecpolicy',
|
|
||||||
name='proposals',
|
|
||||||
field=models.ManyToManyField(related_name='ipsec_policies', to='vpn.ipsecproposal'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='ipsecpolicy',
|
|
||||||
name='tags',
|
|
||||||
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='IKEProposal',
|
name='IKEProposal',
|
||||||
fields=[
|
fields=[
|
||||||
@ -173,6 +40,26 @@ class Migration(migrations.Migration):
|
|||||||
'ordering': ('name',),
|
'ordering': ('name',),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='IKEPolicy',
|
||||||
|
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)),
|
||||||
|
('version', models.PositiveSmallIntegerField(default=2)),
|
||||||
|
('mode', models.CharField()),
|
||||||
|
('preshared_key', models.TextField(blank=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'IKE policy',
|
||||||
|
'verbose_name_plural': 'IKE policies',
|
||||||
|
'ordering': ('name',),
|
||||||
|
},
|
||||||
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='ikepolicy',
|
model_name='ikepolicy',
|
||||||
name='proposals',
|
name='proposals',
|
||||||
@ -183,6 +70,155 @@ class Migration(migrations.Migration):
|
|||||||
name='tags',
|
name='tags',
|
||||||
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
|
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
# IPSec
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='IPSecProposal',
|
||||||
|
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)),
|
||||||
|
('encryption_algorithm', models.CharField()),
|
||||||
|
('authentication_algorithm', models.CharField()),
|
||||||
|
('sa_lifetime_seconds', models.PositiveIntegerField(blank=True, null=True)),
|
||||||
|
('sa_lifetime_data', models.PositiveIntegerField(blank=True, null=True)),
|
||||||
|
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'IPSec proposal',
|
||||||
|
'verbose_name_plural': 'IPSec proposals',
|
||||||
|
'ordering': ('name',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='IPSecPolicy',
|
||||||
|
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)),
|
||||||
|
('pfs_group', models.PositiveSmallIntegerField(blank=True, null=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'IPSec policy',
|
||||||
|
'verbose_name_plural': 'IPSec policies',
|
||||||
|
'ordering': ('name',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ipsecpolicy',
|
||||||
|
name='proposals',
|
||||||
|
field=models.ManyToManyField(related_name='ipsec_policies', to='vpn.ipsecproposal'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ipsecpolicy',
|
||||||
|
name='tags',
|
||||||
|
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='IPSecProfile',
|
||||||
|
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)),
|
||||||
|
('mode', models.CharField()),
|
||||||
|
('ike_policy', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='ipsec_profiles', to='vpn.ikepolicy')),
|
||||||
|
('ipsec_policy', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='ipsec_profiles', to='vpn.ipsecpolicy')),
|
||||||
|
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'IPSec profile',
|
||||||
|
'verbose_name_plural': 'IPSec profiles',
|
||||||
|
'ordering': ('name',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
# Tunnels
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TunnelGroup',
|
||||||
|
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)),
|
||||||
|
('name', models.CharField(max_length=100, unique=True)),
|
||||||
|
('slug', models.SlugField(max_length=100, unique=True)),
|
||||||
|
('description', models.CharField(blank=True, max_length=200)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'tunnel group',
|
||||||
|
'verbose_name_plural': 'tunnel groups',
|
||||||
|
'ordering': ('name',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tunnelgroup',
|
||||||
|
name='tags',
|
||||||
|
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Tunnel',
|
||||||
|
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)),
|
||||||
|
('status', models.CharField(default='active', max_length=50)),
|
||||||
|
('group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='tunnels', to='vpn.tunnelgroup')),
|
||||||
|
('encapsulation', models.CharField(max_length=50)),
|
||||||
|
('tunnel_id', models.PositiveBigIntegerField(blank=True, null=True)),
|
||||||
|
('ipsec_profile', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='tunnels', to='vpn.ipsecprofile')),
|
||||||
|
('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='tunnels', to='tenancy.tenant')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'tunnel',
|
||||||
|
'verbose_name_plural': 'tunnels',
|
||||||
|
'ordering': ('name',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='tunnel',
|
||||||
|
constraint=models.UniqueConstraint(fields=('group', 'name'), name='vpn_tunnel_group_name'),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='tunnel',
|
||||||
|
constraint=models.UniqueConstraint(condition=models.Q(('group__isnull', True)), fields=('name',), name='vpn_tunnel_name'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TunnelTermination',
|
||||||
|
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)),
|
||||||
|
('role', models.CharField(default='peer', max_length=50)),
|
||||||
|
('termination_id', models.PositiveBigIntegerField(blank=True, null=True)),
|
||||||
|
('termination_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype')),
|
||||||
|
('outside_ip', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='tunnel_termination', to='ipam.ipaddress')),
|
||||||
|
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||||
|
('tunnel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='terminations', to='vpn.tunnel')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'tunnel termination',
|
||||||
|
'verbose_name_plural': 'tunnel terminations',
|
||||||
|
'ordering': ('tunnel', 'role', 'pk'),
|
||||||
|
},
|
||||||
|
),
|
||||||
migrations.AddConstraint(
|
migrations.AddConstraint(
|
||||||
model_name='tunneltermination',
|
model_name='tunneltermination',
|
||||||
constraint=models.UniqueConstraint(fields=('termination_type', 'termination_id'), name='vpn_tunneltermination_termination', violation_error_message='An object may be terminated to only one tunnel at a time.'),
|
constraint=models.UniqueConstraint(fields=('termination_type', 'termination_id'), name='vpn_tunneltermination_termination', violation_error_message='An object may be terminated to only one tunnel at a time.'),
|
||||||
|
@ -1,19 +1,35 @@
|
|||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from netbox.models import ChangeLoggedModel, PrimaryModel
|
from netbox.models import ChangeLoggedModel, OrganizationalModel, PrimaryModel
|
||||||
from netbox.models.features import CustomFieldsMixin, CustomLinksMixin, TagsMixin
|
from netbox.models.features import CustomFieldsMixin, CustomLinksMixin, TagsMixin
|
||||||
from vpn.choices import *
|
from vpn.choices import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Tunnel',
|
'Tunnel',
|
||||||
|
'TunnelGroup',
|
||||||
'TunnelTermination',
|
'TunnelTermination',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TunnelGroup(OrganizationalModel):
|
||||||
|
"""
|
||||||
|
An administrative grouping of Tunnels. This can be used to correlate peer-to-peer tunnels which form a mesh,
|
||||||
|
for example.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
ordering = ('name',)
|
||||||
|
verbose_name = _('tunnel group')
|
||||||
|
verbose_name_plural = _('tunnel groups')
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('vpn:tunnelgroup', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
class Tunnel(PrimaryModel):
|
class Tunnel(PrimaryModel):
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
verbose_name=_('name'),
|
verbose_name=_('name'),
|
||||||
@ -26,6 +42,13 @@ class Tunnel(PrimaryModel):
|
|||||||
choices=TunnelStatusChoices,
|
choices=TunnelStatusChoices,
|
||||||
default=TunnelStatusChoices.STATUS_ACTIVE
|
default=TunnelStatusChoices.STATUS_ACTIVE
|
||||||
)
|
)
|
||||||
|
group = models.ForeignKey(
|
||||||
|
to='vpn.TunnelGroup',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='tunnels',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
encapsulation = models.CharField(
|
encapsulation = models.CharField(
|
||||||
verbose_name=_('encapsulation'),
|
verbose_name=_('encapsulation'),
|
||||||
max_length=50,
|
max_length=50,
|
||||||
@ -57,6 +80,17 @@ class Tunnel(PrimaryModel):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('name',)
|
ordering = ('name',)
|
||||||
|
constraints = (
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=('group', 'name'),
|
||||||
|
name='%(app_label)s_%(class)s_group_name'
|
||||||
|
),
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=('name',),
|
||||||
|
name='%(app_label)s_%(class)s_name',
|
||||||
|
condition=Q(group__isnull=True)
|
||||||
|
),
|
||||||
|
)
|
||||||
verbose_name = _('tunnel')
|
verbose_name = _('tunnel')
|
||||||
verbose_name_plural = _('tunnels')
|
verbose_name_plural = _('tunnels')
|
||||||
|
|
||||||
|
@ -8,10 +8,33 @@ from vpn.models import *
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'TunnelTable',
|
'TunnelTable',
|
||||||
|
'TunnelGroupTable',
|
||||||
'TunnelTerminationTable',
|
'TunnelTerminationTable',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TunnelGroupTable(NetBoxTable):
|
||||||
|
name = tables.Column(
|
||||||
|
verbose_name=_('Name'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
tunnel_count = columns.LinkedCountColumn(
|
||||||
|
viewname='vpn:tunnel_list',
|
||||||
|
url_params={'group_id': 'pk'},
|
||||||
|
verbose_name=_('Tunnels')
|
||||||
|
)
|
||||||
|
tags = columns.TagColumn(
|
||||||
|
url_name='vpn:tunnelgroup_list'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(NetBoxTable.Meta):
|
||||||
|
model = TunnelGroup
|
||||||
|
fields = (
|
||||||
|
'pk', 'id', 'name', 'tunnel_count', 'description', 'slug', 'tags', 'actions', 'created', 'last_updated',
|
||||||
|
)
|
||||||
|
default_columns = ('pk', 'name', 'tunnel_count', 'description')
|
||||||
|
|
||||||
|
|
||||||
class TunnelTable(TenancyColumnsMixin, NetBoxTable):
|
class TunnelTable(TenancyColumnsMixin, NetBoxTable):
|
||||||
name = tables.Column(
|
name = tables.Column(
|
||||||
verbose_name=_('Name'),
|
verbose_name=_('Name'),
|
||||||
|
@ -17,6 +17,38 @@ class AppTest(APITestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
|
class TunnelGroupTest(APIViewTestCases.APIViewTestCase):
|
||||||
|
model = TunnelGroup
|
||||||
|
brief_fields = ['display', 'id', 'name', 'slug', 'tunnel_count', 'url']
|
||||||
|
create_data = (
|
||||||
|
{
|
||||||
|
'name': 'Tunnel Group 4',
|
||||||
|
'slug': 'tunnel-group-4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Tunnel Group 5',
|
||||||
|
'slug': 'tunnel-group-5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Tunnel Group 6',
|
||||||
|
'slug': 'tunnel-group-6',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
tunnel_groups = (
|
||||||
|
TunnelGroup(name='Tunnel Group 1', slug='tunnel-group-1'),
|
||||||
|
TunnelGroup(name='Tunnel Group 2', slug='tunnel-group-2'),
|
||||||
|
TunnelGroup(name='Tunnel Group 3', slug='tunnel-group-3'),
|
||||||
|
)
|
||||||
|
TunnelGroup.objects.bulk_create(tunnel_groups)
|
||||||
|
|
||||||
|
|
||||||
class TunnelTest(APIViewTestCases.APIViewTestCase):
|
class TunnelTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = Tunnel
|
model = Tunnel
|
||||||
brief_fields = ['display', 'id', 'name', 'url']
|
brief_fields = ['display', 'id', 'name', 'url']
|
||||||
@ -29,20 +61,29 @@ class TunnelTest(APIViewTestCases.APIViewTestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
tunnel_groups = (
|
||||||
|
TunnelGroup(name='Tunnel Group 1', slug='tunnel-group-1'),
|
||||||
|
TunnelGroup(name='Tunnel Group 2', slug='tunnel-group-2'),
|
||||||
|
)
|
||||||
|
TunnelGroup.objects.bulk_create(tunnel_groups)
|
||||||
|
|
||||||
tunnels = (
|
tunnels = (
|
||||||
Tunnel(
|
Tunnel(
|
||||||
name='Tunnel 1',
|
name='Tunnel 1',
|
||||||
status=TunnelStatusChoices.STATUS_ACTIVE,
|
status=TunnelStatusChoices.STATUS_ACTIVE,
|
||||||
|
group=tunnel_groups[0],
|
||||||
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
|
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
|
||||||
),
|
),
|
||||||
Tunnel(
|
Tunnel(
|
||||||
name='Tunnel 2',
|
name='Tunnel 2',
|
||||||
status=TunnelStatusChoices.STATUS_ACTIVE,
|
status=TunnelStatusChoices.STATUS_ACTIVE,
|
||||||
|
group=tunnel_groups[0],
|
||||||
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
|
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
|
||||||
),
|
),
|
||||||
Tunnel(
|
Tunnel(
|
||||||
name='Tunnel 3',
|
name='Tunnel 3',
|
||||||
status=TunnelStatusChoices.STATUS_ACTIVE,
|
status=TunnelStatusChoices.STATUS_ACTIVE,
|
||||||
|
group=tunnel_groups[0],
|
||||||
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
|
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -52,16 +93,19 @@ class TunnelTest(APIViewTestCases.APIViewTestCase):
|
|||||||
{
|
{
|
||||||
'name': 'Tunnel 4',
|
'name': 'Tunnel 4',
|
||||||
'status': TunnelStatusChoices.STATUS_DISABLED,
|
'status': TunnelStatusChoices.STATUS_DISABLED,
|
||||||
|
'group': tunnel_groups[1].pk,
|
||||||
'encapsulation': TunnelEncapsulationChoices.ENCAP_GRE,
|
'encapsulation': TunnelEncapsulationChoices.ENCAP_GRE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Tunnel 5',
|
'name': 'Tunnel 5',
|
||||||
'status': TunnelStatusChoices.STATUS_DISABLED,
|
'status': TunnelStatusChoices.STATUS_DISABLED,
|
||||||
|
'group': tunnel_groups[1].pk,
|
||||||
'encapsulation': TunnelEncapsulationChoices.ENCAP_GRE,
|
'encapsulation': TunnelEncapsulationChoices.ENCAP_GRE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Tunnel 6',
|
'name': 'Tunnel 6',
|
||||||
'status': TunnelStatusChoices.STATUS_DISABLED,
|
'status': TunnelStatusChoices.STATUS_DISABLED,
|
||||||
|
'group': tunnel_groups[1].pk,
|
||||||
'encapsulation': TunnelEncapsulationChoices.ENCAP_GRE,
|
'encapsulation': TunnelEncapsulationChoices.ENCAP_GRE,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -11,6 +11,32 @@ from vpn.filtersets import *
|
|||||||
from vpn.models import *
|
from vpn.models import *
|
||||||
|
|
||||||
|
|
||||||
|
class TunnelGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
|
queryset = TunnelGroup.objects.all()
|
||||||
|
filterset = TunnelGroupFilterSet
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
TunnelGroup.objects.bulk_create((
|
||||||
|
TunnelGroup(name='Tunnel Group 1', slug='tunnel-group-1', description='foobar1'),
|
||||||
|
TunnelGroup(name='Tunnel Group 2', slug='tunnel-group-2', description='foobar2'),
|
||||||
|
TunnelGroup(name='Tunnel Group 3', slug='tunnel-group-3'),
|
||||||
|
))
|
||||||
|
|
||||||
|
def test_name(self):
|
||||||
|
params = {'name': ['Tunnel Group 1']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_slug(self):
|
||||||
|
params = {'slug': ['tunnel-group-1']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_description(self):
|
||||||
|
params = {'description': ['foobar1', 'foobar2']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class TunnelTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class TunnelTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Tunnel.objects.all()
|
queryset = Tunnel.objects.all()
|
||||||
filterset = TunnelFilterSet
|
filterset = TunnelFilterSet
|
||||||
@ -56,10 +82,18 @@ class TunnelTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
)
|
)
|
||||||
IPSecProfile.objects.bulk_create(ipsec_profiles)
|
IPSecProfile.objects.bulk_create(ipsec_profiles)
|
||||||
|
|
||||||
|
tunnel_groups = (
|
||||||
|
TunnelGroup(name='Tunnel Group 1', slug='tunnel-group-1'),
|
||||||
|
TunnelGroup(name='Tunnel Group 2', slug='tunnel-group-2'),
|
||||||
|
TunnelGroup(name='Tunnel Group 3', slug='tunnel-group-3'),
|
||||||
|
)
|
||||||
|
TunnelGroup.objects.bulk_create(tunnel_groups)
|
||||||
|
|
||||||
tunnels = (
|
tunnels = (
|
||||||
Tunnel(
|
Tunnel(
|
||||||
name='Tunnel 1',
|
name='Tunnel 1',
|
||||||
status=TunnelStatusChoices.STATUS_ACTIVE,
|
status=TunnelStatusChoices.STATUS_ACTIVE,
|
||||||
|
group=tunnel_groups[0],
|
||||||
encapsulation=TunnelEncapsulationChoices.ENCAP_GRE,
|
encapsulation=TunnelEncapsulationChoices.ENCAP_GRE,
|
||||||
ipsec_profile=ipsec_profiles[0],
|
ipsec_profile=ipsec_profiles[0],
|
||||||
tunnel_id=100
|
tunnel_id=100
|
||||||
@ -67,6 +101,7 @@ class TunnelTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
Tunnel(
|
Tunnel(
|
||||||
name='Tunnel 2',
|
name='Tunnel 2',
|
||||||
status=TunnelStatusChoices.STATUS_PLANNED,
|
status=TunnelStatusChoices.STATUS_PLANNED,
|
||||||
|
group=tunnel_groups[1],
|
||||||
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP,
|
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP,
|
||||||
ipsec_profile=ipsec_profiles[0],
|
ipsec_profile=ipsec_profiles[0],
|
||||||
tunnel_id=200
|
tunnel_id=200
|
||||||
@ -74,6 +109,7 @@ class TunnelTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
Tunnel(
|
Tunnel(
|
||||||
name='Tunnel 3',
|
name='Tunnel 3',
|
||||||
status=TunnelStatusChoices.STATUS_DISABLED,
|
status=TunnelStatusChoices.STATUS_DISABLED,
|
||||||
|
group=tunnel_groups[2],
|
||||||
encapsulation=TunnelEncapsulationChoices.ENCAP_IPSEC_TUNNEL,
|
encapsulation=TunnelEncapsulationChoices.ENCAP_IPSEC_TUNNEL,
|
||||||
ipsec_profile=None,
|
ipsec_profile=None,
|
||||||
tunnel_id=300
|
tunnel_id=300
|
||||||
@ -89,6 +125,13 @@ class TunnelTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'status': [TunnelStatusChoices.STATUS_ACTIVE, TunnelStatusChoices.STATUS_PLANNED]}
|
params = {'status': [TunnelStatusChoices.STATUS_ACTIVE, TunnelStatusChoices.STATUS_PLANNED]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_group(self):
|
||||||
|
tunnel_groups = TunnelGroup.objects.all()[:2]
|
||||||
|
params = {'group_id': [tunnel_groups[0].pk, tunnel_groups[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'group': [tunnel_groups[0].slug, tunnel_groups[1].slug]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_encapsulation(self):
|
def test_encapsulation(self):
|
||||||
params = {'encapsulation': [TunnelEncapsulationChoices.ENCAP_GRE, TunnelEncapsulationChoices.ENCAP_IP_IP]}
|
params = {'encapsulation': [TunnelEncapsulationChoices.ENCAP_GRE, TunnelEncapsulationChoices.ENCAP_IP_IP]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
@ -6,26 +6,78 @@ from vpn.choices import *
|
|||||||
from vpn.models import *
|
from vpn.models import *
|
||||||
|
|
||||||
|
|
||||||
|
class TunnelGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
||||||
|
model = TunnelGroup
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
tunnel_groups = (
|
||||||
|
TunnelGroup(name='Tunnel Group 1', slug='tunnel-group-1'),
|
||||||
|
TunnelGroup(name='Tunnel Group 2', slug='tunnel-group-2'),
|
||||||
|
TunnelGroup(name='Tunnel Group 3', slug='tunnel-group-3'),
|
||||||
|
)
|
||||||
|
TunnelGroup.objects.bulk_create(tunnel_groups)
|
||||||
|
|
||||||
|
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||||
|
|
||||||
|
cls.form_data = {
|
||||||
|
'name': 'Tunnel Group X',
|
||||||
|
'slug': 'tunnel-group-x',
|
||||||
|
'description': 'A new Tunnel Group',
|
||||||
|
'tags': [t.pk for t in tags],
|
||||||
|
}
|
||||||
|
|
||||||
|
cls.csv_data = (
|
||||||
|
"name,slug",
|
||||||
|
"Tunnel Group 4,tunnel-group-4",
|
||||||
|
"Tunnel Group 5,tunnel-group-5",
|
||||||
|
"Tunnel Group 6,tunnel-group-6",
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.csv_update_data = (
|
||||||
|
"id,name,description",
|
||||||
|
f"{tunnel_groups[0].pk},Tunnel Group 7,New description7",
|
||||||
|
f"{tunnel_groups[1].pk},Tunnel Group 8,New description8",
|
||||||
|
f"{tunnel_groups[2].pk},Tunnel Group 9,New description9",
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.bulk_edit_data = {
|
||||||
|
'description': 'Foo',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TunnelTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
class TunnelTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||||
model = Tunnel
|
model = Tunnel
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
tunnel_groups = (
|
||||||
|
TunnelGroup(name='Tunnel Group 1', slug='tunnel-group-1'),
|
||||||
|
TunnelGroup(name='Tunnel Group 2', slug='tunnel-group-2'),
|
||||||
|
TunnelGroup(name='Tunnel Group 3', slug='tunnel-group-3'),
|
||||||
|
TunnelGroup(name='Tunnel Group 4', slug='tunnel-group-4'),
|
||||||
|
)
|
||||||
|
TunnelGroup.objects.bulk_create(tunnel_groups)
|
||||||
|
|
||||||
tunnels = (
|
tunnels = (
|
||||||
Tunnel(
|
Tunnel(
|
||||||
name='Tunnel 1',
|
name='Tunnel 1',
|
||||||
status=TunnelStatusChoices.STATUS_ACTIVE,
|
status=TunnelStatusChoices.STATUS_ACTIVE,
|
||||||
|
group=tunnel_groups[0],
|
||||||
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
|
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
|
||||||
),
|
),
|
||||||
Tunnel(
|
Tunnel(
|
||||||
name='Tunnel 2',
|
name='Tunnel 2',
|
||||||
status=TunnelStatusChoices.STATUS_ACTIVE,
|
status=TunnelStatusChoices.STATUS_ACTIVE,
|
||||||
|
group=tunnel_groups[1],
|
||||||
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
|
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
|
||||||
),
|
),
|
||||||
Tunnel(
|
Tunnel(
|
||||||
name='Tunnel 3',
|
name='Tunnel 3',
|
||||||
status=TunnelStatusChoices.STATUS_ACTIVE,
|
status=TunnelStatusChoices.STATUS_ACTIVE,
|
||||||
|
group=tunnel_groups[2],
|
||||||
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
|
encapsulation=TunnelEncapsulationChoices.ENCAP_IP_IP
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -37,26 +89,28 @@ class TunnelTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
'name': 'Tunnel X',
|
'name': 'Tunnel X',
|
||||||
'description': 'New tunnel',
|
'description': 'New tunnel',
|
||||||
'status': TunnelStatusChoices.STATUS_PLANNED,
|
'status': TunnelStatusChoices.STATUS_PLANNED,
|
||||||
|
'group': tunnel_groups[3].pk,
|
||||||
'encapsulation': TunnelEncapsulationChoices.ENCAP_GRE,
|
'encapsulation': TunnelEncapsulationChoices.ENCAP_GRE,
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"name,status,encapsulation",
|
"name,status,group,encapsulation",
|
||||||
"Tunnel 4,planned,gre",
|
"Tunnel 4,planned,Tunnel Group 1,gre",
|
||||||
"Tunnel 5,planned,gre",
|
"Tunnel 5,planned,Tunnel Group 2,gre",
|
||||||
"Tunnel 6,planned,gre",
|
"Tunnel 6,planned,Tunnel Group 3,gre",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
"id,status,encapsulation",
|
"id,status,group,encapsulation",
|
||||||
f"{tunnels[0].pk},active,ip-ip",
|
f"{tunnels[0].pk},active,Tunnel Group 4,ip-ip",
|
||||||
f"{tunnels[1].pk},active,ip-ip",
|
f"{tunnels[1].pk},active,Tunnel Group 4,ip-ip",
|
||||||
f"{tunnels[2].pk},active,ip-ip",
|
f"{tunnels[2].pk},active,Tunnel Group 4,ip-ip",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
|
'group': tunnel_groups[3].pk,
|
||||||
'status': TunnelStatusChoices.STATUS_DISABLED,
|
'status': TunnelStatusChoices.STATUS_DISABLED,
|
||||||
'encapsulation': TunnelEncapsulationChoices.ENCAP_GRE,
|
'encapsulation': TunnelEncapsulationChoices.ENCAP_GRE,
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,14 @@ from . import views
|
|||||||
app_name = 'vpn'
|
app_name = 'vpn'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|
||||||
|
# Tunnel groups
|
||||||
|
path('tunnel-groups/', views.TunnelGroupListView.as_view(), name='tunnelgroup_list'),
|
||||||
|
path('tunnel-groups/add/', views.TunnelGroupEditView.as_view(), name='tunnelgroup_add'),
|
||||||
|
path('tunnel-groups/import/', views.TunnelGroupBulkImportView.as_view(), name='tunnelgroup_import'),
|
||||||
|
path('tunnel-groups/edit/', views.TunnelGroupBulkEditView.as_view(), name='tunnelgroup_bulk_edit'),
|
||||||
|
path('tunnel-groups/delete/', views.TunnelGroupBulkDeleteView.as_view(), name='tunnelgroup_bulk_delete'),
|
||||||
|
path('tunnel-groups/<int:pk>/', include(get_model_urls('vpn', 'tunnelgroup'))),
|
||||||
|
|
||||||
# Tunnels
|
# Tunnels
|
||||||
path('tunnels/', views.TunnelListView.as_view(), name='tunnel_list'),
|
path('tunnels/', views.TunnelListView.as_view(), name='tunnel_list'),
|
||||||
path('tunnels/add/', views.TunnelEditView.as_view(), name='tunnel_add'),
|
path('tunnels/add/', views.TunnelEditView.as_view(), name='tunnel_add'),
|
||||||
|
@ -7,6 +7,66 @@ from . import filtersets, forms, tables
|
|||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Tunnel groups
|
||||||
|
#
|
||||||
|
|
||||||
|
class TunnelGroupListView(generic.ObjectListView):
|
||||||
|
queryset = TunnelGroup.objects.annotate(
|
||||||
|
tunnel_count=count_related(Tunnel, 'group')
|
||||||
|
)
|
||||||
|
filterset = filtersets.TunnelGroupFilterSet
|
||||||
|
filterset_form = forms.TunnelGroupFilterForm
|
||||||
|
table = tables.TunnelGroupTable
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(TunnelGroup)
|
||||||
|
class TunnelGroupView(generic.ObjectView):
|
||||||
|
queryset = TunnelGroup.objects.all()
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
related_models = (
|
||||||
|
(Tunnel.objects.restrict(request.user, 'view').filter(group=instance), 'group_id'),
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'related_models': related_models,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(TunnelGroup, 'edit')
|
||||||
|
class TunnelGroupEditView(generic.ObjectEditView):
|
||||||
|
queryset = TunnelGroup.objects.all()
|
||||||
|
form = forms.TunnelGroupForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(TunnelGroup, 'delete')
|
||||||
|
class TunnelGroupDeleteView(generic.ObjectDeleteView):
|
||||||
|
queryset = TunnelGroup.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class TunnelGroupBulkImportView(generic.BulkImportView):
|
||||||
|
queryset = TunnelGroup.objects.all()
|
||||||
|
model_form = forms.TunnelGroupImportForm
|
||||||
|
|
||||||
|
|
||||||
|
class TunnelGroupBulkEditView(generic.BulkEditView):
|
||||||
|
queryset = TunnelGroup.objects.annotate(
|
||||||
|
tunnel_count=count_related(Tunnel, 'group')
|
||||||
|
)
|
||||||
|
filterset = filtersets.TunnelGroupFilterSet
|
||||||
|
table = tables.TunnelGroupTable
|
||||||
|
form = forms.TunnelGroupBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
class TunnelGroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
|
queryset = TunnelGroup.objects.annotate(
|
||||||
|
tunnel_count=count_related(Tunnel, 'group')
|
||||||
|
)
|
||||||
|
filterset = filtersets.TunnelGroupFilterSet
|
||||||
|
table = tables.TunnelGroupTable
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Tunnels
|
# Tunnels
|
||||||
#
|
#
|
||||||
|
Reference in New Issue
Block a user