mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 17:38:37 -06:00
#8157 - Final work on L2VPN model
This commit is contained in:
parent
03f1584d3a
commit
3be9f6c4f3
@ -10,6 +10,7 @@ from dcim.constants import *
|
|||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from ipam.api.nested_serializers import (
|
from ipam.api.nested_serializers import (
|
||||||
NestedASNSerializer, NestedIPAddressSerializer, NestedVLANSerializer, NestedVRFSerializer,
|
NestedASNSerializer, NestedIPAddressSerializer, NestedVLANSerializer, NestedVRFSerializer,
|
||||||
|
NestedL2VPNTerminationSerializer,
|
||||||
)
|
)
|
||||||
from ipam.models import ASN, VLAN
|
from ipam.models import ASN, VLAN
|
||||||
from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField
|
from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField
|
||||||
@ -823,6 +824,7 @@ class InterfaceSerializer(NetBoxModelSerializer, LinkTerminationSerializer, Conn
|
|||||||
many=True
|
many=True
|
||||||
)
|
)
|
||||||
vrf = NestedVRFSerializer(required=False, allow_null=True)
|
vrf = NestedVRFSerializer(required=False, allow_null=True)
|
||||||
|
l2vpn_termination = NestedL2VPNTerminationSerializer(read_only=True)
|
||||||
cable = NestedCableSerializer(read_only=True)
|
cable = NestedCableSerializer(read_only=True)
|
||||||
wireless_link = NestedWirelessLinkSerializer(read_only=True)
|
wireless_link = NestedWirelessLinkSerializer(read_only=True)
|
||||||
wireless_lans = SerializedPKRelatedField(
|
wireless_lans = SerializedPKRelatedField(
|
||||||
@ -841,7 +843,7 @@ class InterfaceSerializer(NetBoxModelSerializer, LinkTerminationSerializer, Conn
|
|||||||
'mtu', 'mac_address', 'speed', 'duplex', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel',
|
'mtu', 'mac_address', 'speed', 'duplex', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel',
|
||||||
'poe_mode', 'poe_type', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan',
|
'poe_mode', 'poe_type', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan',
|
||||||
'tagged_vlans', 'mark_connected', 'cable', 'wireless_link', 'link_peer', 'link_peer_type', 'wireless_lans',
|
'tagged_vlans', 'mark_connected', 'cable', 'wireless_link', 'link_peer', 'link_peer_type', 'wireless_lans',
|
||||||
'vrf', 'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags',
|
'vrf', 'l2vpn_termination', 'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags',
|
||||||
'custom_fields', 'created', 'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied',
|
'custom_fields', 'created', 'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -649,10 +649,11 @@ class Interface(ModularComponentModel, BaseInterface, LinkTermination, PathEndpo
|
|||||||
object_id_field='interface_id',
|
object_id_field='interface_id',
|
||||||
related_query_name='+'
|
related_query_name='+'
|
||||||
)
|
)
|
||||||
l2vpn = GenericRelation(
|
l2vpn_terminations = GenericRelation(
|
||||||
to='ipam.L2VPNTermination',
|
to='ipam.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',
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = ['device', 'parent', 'bridge', 'lag', 'type', 'mgmt_only', 'poe_mode', 'poe_type']
|
clone_fields = ['device', 'parent', 'bridge', 'lag', 'type', 'mgmt_only', 'poe_mode', 'poe_type']
|
||||||
@ -828,6 +829,10 @@ class Interface(ModularComponentModel, BaseInterface, LinkTermination, PathEndpo
|
|||||||
def link(self):
|
def link(self):
|
||||||
return self.cable or self.wireless_link
|
return self.cable or self.wireless_link
|
||||||
|
|
||||||
|
@property
|
||||||
|
def l2vpn_termination(self):
|
||||||
|
return self.l2vpn_terminations.first()
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Pass-through ports
|
# Pass-through ports
|
||||||
|
@ -11,6 +11,8 @@ __all__ = [
|
|||||||
'NestedFHRPGroupAssignmentSerializer',
|
'NestedFHRPGroupAssignmentSerializer',
|
||||||
'NestedIPAddressSerializer',
|
'NestedIPAddressSerializer',
|
||||||
'NestedIPRangeSerializer',
|
'NestedIPRangeSerializer',
|
||||||
|
'NestedL2VPNSerializer',
|
||||||
|
'NestedL2VPNTerminationSerializer',
|
||||||
'NestedPrefixSerializer',
|
'NestedPrefixSerializer',
|
||||||
'NestedRIRSerializer',
|
'NestedRIRSerializer',
|
||||||
'NestedRoleSerializer',
|
'NestedRoleSerializer',
|
||||||
@ -203,17 +205,17 @@ class NestedL2VPNSerializer(WritableNestedSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = L2VPN
|
model = L2VPN
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'name', 'type'
|
'id', 'url', 'display', 'identifier', 'name', 'slug', 'type'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class NestedL2VPNTerminationSerializer(WritableNestedSerializer):
|
class NestedL2VPNTerminationSerializer(WritableNestedSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:l2vpn_termination-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:l2vpntermination-detail')
|
||||||
l2vpn = NestedL2VPNSerializer()
|
l2vpn = NestedL2VPNSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = L2VPNTermination
|
model = L2VPNTermination
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'l2vpn', 'assigned_object'
|
'id', 'url', 'display', 'l2vpn'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -207,13 +207,14 @@ class VLANSerializer(NetBoxModelSerializer):
|
|||||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||||
status = ChoiceField(choices=VLANStatusChoices, required=False)
|
status = ChoiceField(choices=VLANStatusChoices, required=False)
|
||||||
role = NestedRoleSerializer(required=False, allow_null=True)
|
role = NestedRoleSerializer(required=False, allow_null=True)
|
||||||
|
l2vpn_termination = NestedL2VPNTerminationSerializer(read_only=True)
|
||||||
prefix_count = serializers.IntegerField(read_only=True)
|
prefix_count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VLAN
|
model = VLAN
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'tags',
|
'id', 'url', 'display', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description',
|
||||||
'custom_fields', 'created', 'last_updated', 'prefix_count',
|
'l2vpn_termination', 'tags', 'custom_fields', 'created', 'last_updated', 'prefix_count',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -165,7 +165,7 @@ class L2VPNViewSet(NetBoxModelViewSet):
|
|||||||
|
|
||||||
|
|
||||||
class L2VPNTerminationViewSet(NetBoxModelViewSet):
|
class L2VPNTerminationViewSet(NetBoxModelViewSet):
|
||||||
queryset = L2VPNTermination.objects
|
queryset = L2VPNTermination.objects.prefetch_related('assigned_object')
|
||||||
serializer_class = serializers.L2VPNTerminationSerializer
|
serializer_class = serializers.L2VPNTerminationSerializer
|
||||||
filterset_class = filtersets.L2VPNTerminationFilterSet
|
filterset_class = filtersets.L2VPNTerminationFilterSet
|
||||||
|
|
||||||
|
@ -957,7 +957,7 @@ class L2VPNFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = L2VPN
|
model = L2VPN
|
||||||
fields = ['identifier', 'name', 'type', 'description']
|
fields = ['id', 'identifier', 'name', 'type', 'description']
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -977,13 +977,60 @@ class L2VPNTerminationFilterSet(NetBoxModelFilterSet):
|
|||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
label='L2VPN (name)',
|
label='L2VPN (name)',
|
||||||
)
|
)
|
||||||
|
device = MultiValueCharFilter(
|
||||||
|
method='filter_device',
|
||||||
|
field_name='name',
|
||||||
|
label='Device (name)',
|
||||||
|
)
|
||||||
|
device_id = MultiValueNumberFilter(
|
||||||
|
method='filter_device',
|
||||||
|
field_name='pk',
|
||||||
|
label='Device (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)',
|
||||||
|
)
|
||||||
|
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)',
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = L2VPNTermination
|
model = L2VPNTermination
|
||||||
fields = ['l2vpn']
|
fields = ['id', ]
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
return queryset
|
return queryset
|
||||||
qs_filter = Q(l2vpn__name__icontains=value)
|
qs_filter = Q(l2vpn__name__icontains=value)
|
||||||
return queryset.filter(qs_filter)
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
|
def filter_device(self, queryset, name, value):
|
||||||
|
devices = Device.objects.filter(**{'{}__in'.format(name): value})
|
||||||
|
if not devices.exists():
|
||||||
|
return queryset.none()
|
||||||
|
interface_ids = []
|
||||||
|
for device in devices:
|
||||||
|
interface_ids.extend(device.vc_interfaces().values_list('id', flat=True))
|
||||||
|
return queryset.filter(
|
||||||
|
interface__in=interface_ids
|
||||||
|
)
|
||||||
|
@ -19,6 +19,7 @@ __all__ = (
|
|||||||
'IPAddressBulkEditForm',
|
'IPAddressBulkEditForm',
|
||||||
'IPRangeBulkEditForm',
|
'IPRangeBulkEditForm',
|
||||||
'L2VPNBulkEditForm',
|
'L2VPNBulkEditForm',
|
||||||
|
'L2VPNTerminationBulkEditForm',
|
||||||
'PrefixBulkEditForm',
|
'PrefixBulkEditForm',
|
||||||
'RIRBulkEditForm',
|
'RIRBulkEditForm',
|
||||||
'RoleBulkEditForm',
|
'RoleBulkEditForm',
|
||||||
@ -458,3 +459,7 @@ class L2VPNBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
(None, ('tenant', 'description')),
|
(None, ('tenant', 'description')),
|
||||||
)
|
)
|
||||||
nullable_fields = ('tenant', 'description',)
|
nullable_fields = ('tenant', 'description',)
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNTerminationBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
|
model = L2VPN
|
||||||
|
@ -17,6 +17,12 @@ class IPAMQuery(graphene.ObjectType):
|
|||||||
ip_range = ObjectField(IPRangeType)
|
ip_range = ObjectField(IPRangeType)
|
||||||
ip_range_list = ObjectListField(IPRangeType)
|
ip_range_list = ObjectListField(IPRangeType)
|
||||||
|
|
||||||
|
l2vpn = ObjectField(L2VPNType)
|
||||||
|
l2vpn_list = ObjectListField(L2VPNType)
|
||||||
|
|
||||||
|
l2vpn_termination = ObjectField(L2VPNTerminationType)
|
||||||
|
l2vpn_termination_list = ObjectListField(L2VPNTerminationType)
|
||||||
|
|
||||||
prefix = ObjectField(PrefixType)
|
prefix = ObjectField(PrefixType)
|
||||||
prefix_list = ObjectListField(PrefixType)
|
prefix_list = ObjectListField(PrefixType)
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ __all__ = (
|
|||||||
'FHRPGroupAssignmentType',
|
'FHRPGroupAssignmentType',
|
||||||
'IPAddressType',
|
'IPAddressType',
|
||||||
'IPRangeType',
|
'IPRangeType',
|
||||||
|
'L2VPNType',
|
||||||
|
'L2VPNTerminationType',
|
||||||
'PrefixType',
|
'PrefixType',
|
||||||
'RIRType',
|
'RIRType',
|
||||||
'RoleType',
|
'RoleType',
|
||||||
@ -151,3 +153,17 @@ class VRFType(NetBoxObjectType):
|
|||||||
model = models.VRF
|
model = models.VRF
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
filterset_class = filtersets.VRFFilterSet
|
filterset_class = filtersets.VRFFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNType(NetBoxObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = models.L2VPN
|
||||||
|
fields = '__all__'
|
||||||
|
filtersets_class = filtersets.L2VPNFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNTerminationType(NetBoxObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = models.L2VPNTermination
|
||||||
|
fields = '__all__'
|
||||||
|
filtersets_class = filtersets.L2VPNTerminationFilterSet
|
||||||
|
@ -174,10 +174,11 @@ class VLAN(NetBoxModel):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
l2vpn = GenericRelation(
|
l2vpn_terminations = GenericRelation(
|
||||||
to='ipam.L2VPNTermination',
|
to='ipam.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'
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = VLANQuerySet.as_manager()
|
objects = VLANQuerySet.as_manager()
|
||||||
@ -234,3 +235,7 @@ class VLAN(NetBoxModel):
|
|||||||
Q(untagged_vlan_id=self.pk) |
|
Q(untagged_vlan_id=self.pk) |
|
||||||
Q(tagged_vlans=self.pk)
|
Q(tagged_vlans=self.pk)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def l2vpn_termination(self):
|
||||||
|
return self.l2vpn_terminations.first()
|
||||||
|
@ -947,28 +947,28 @@ class L2VPNTest(APIViewTestCases.APIViewTestCase):
|
|||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
l2vpns = (
|
l2vpns = (
|
||||||
L2VPN(name='L2VPN 1', type='vxlan', identifier=650001),
|
L2VPN(name='L2VPN 1', slug='l2vpn-1', type='vxlan', identifier=650001),
|
||||||
L2VPN(name='L2VPN 2', type='vpws', identifier=650002),
|
L2VPN(name='L2VPN 2', slug='l2vpn-2', type='vpws', identifier=650002),
|
||||||
L2VPN(name='L2VPN 3', type='vpls'), # No RD
|
L2VPN(name='L2VPN 3', slug='l2vpn-3', type='vpls'), # No RD
|
||||||
)
|
)
|
||||||
L2VPN.objects.bulk_create(l2vpns)
|
L2VPN.objects.bulk_create(l2vpns)
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTerminationTest(APIViewTestCases.APIViewTestCase):
|
class L2VPNTerminationTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = L2VPNTermination
|
model = L2VPNTermination
|
||||||
brief_fields = ['display', 'id', 'l2vpn', 'assigned_object', 'assigned_object_id', 'assigned_object_type', 'url']
|
brief_fields = ['display', 'id', 'l2vpn', 'url']
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
vlans = (
|
vlans = (
|
||||||
VLAN(name='VLAN 1', vid=650001),
|
VLAN(name='VLAN 1', vid=651),
|
||||||
VLAN(name='VLAN 2', vid=650002),
|
VLAN(name='VLAN 2', vid=652),
|
||||||
VLAN(name='VLAN 3', vid=650003),
|
VLAN(name='VLAN 3', vid=653),
|
||||||
VLAN(name='VLAN 4', vid=650004),
|
VLAN(name='VLAN 4', vid=654),
|
||||||
VLAN(name='VLAN 5', vid=650005),
|
VLAN(name='VLAN 5', vid=655),
|
||||||
VLAN(name='VLAN 6', vid=650006),
|
VLAN(name='VLAN 6', vid=656),
|
||||||
VLAN(name='VLAN 7', vid=650007)
|
VLAN(name='VLAN 7', vid=657)
|
||||||
)
|
)
|
||||||
|
|
||||||
VLAN.objects.bulk_create(vlans)
|
VLAN.objects.bulk_create(vlans)
|
||||||
@ -986,24 +986,26 @@ class L2VPNTerminationTest(APIViewTestCases.APIViewTestCase):
|
|||||||
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[2])
|
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[2])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
L2VPNTermination.objects.bulk_create(l2vpnterminations)
|
||||||
|
|
||||||
cls.create_data = [
|
cls.create_data = [
|
||||||
{
|
{
|
||||||
'l2vpn': l2vpns[0],
|
'l2vpn': l2vpns[0].pk,
|
||||||
'assigned_object_type': 'ipam.vlan',
|
'assigned_object_type': 'ipam.vlan',
|
||||||
'assigned_object_id': vlans[3],
|
'assigned_object_id': vlans[3].pk,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'l2vpn': l2vpns[0],
|
'l2vpn': l2vpns[0].pk,
|
||||||
'assigned_object_type': 'ipam.vlan',
|
'assigned_object_type': 'ipam.vlan',
|
||||||
'assigned_object_id': vlans[4],
|
'assigned_object_id': vlans[4].pk,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'l2vpn': l2vpns[0],
|
'l2vpn': l2vpns[0].pk,
|
||||||
'assigned_object_type': 'ipam.vlan',
|
'assigned_object_type': 'ipam.vlan',
|
||||||
'assigned_object_id': vlans[5],
|
'assigned_object_id': vlans[5].pk,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
cls.bulk_update_data = {
|
cls.bulk_update_data = {
|
||||||
'l2vpn': l2vpns[2]
|
'l2vpn': l2vpns[2].pk
|
||||||
}
|
}
|
||||||
|
@ -1465,8 +1465,7 @@ class ServiceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTest(TestCase, ChangeLoggedFilterSetTests):
|
class L2VPNTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
# TODO: L2VPN Tests
|
|
||||||
queryset = L2VPN.objects.all()
|
queryset = L2VPN.objects.all()
|
||||||
filterset = L2VPNFilterSet
|
filterset = L2VPNFilterSet
|
||||||
|
|
||||||
@ -1480,20 +1479,8 @@ class L2VPNTest(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
)
|
)
|
||||||
L2VPN.objects.bulk_create(l2vpns)
|
L2VPN.objects.bulk_create(l2vpns)
|
||||||
|
|
||||||
def test_created(self):
|
|
||||||
from datetime import date, date
|
|
||||||
pk_list = self.queryset.values_list('pk', flat=True)[:2]
|
|
||||||
print(pk_list)
|
|
||||||
self.queryset.filter(pk__in=pk_list).update(created=datetime(2021, 1, 1, 0, 0, 0, tzinfo=timezone.utc))
|
|
||||||
params = {'created': '2021-01-01T00:00:00'}
|
|
||||||
fs = self.filterset({}, self.queryset).qs.all()
|
|
||||||
for res in fs:
|
|
||||||
print(f'{res.name}:{res.created}')
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
|
class L2VPNTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
class L2VPNTerminationTest(TestCase, ChangeLoggedFilterSetTests):
|
|
||||||
# TODO: L2VPN Termination Tests
|
|
||||||
queryset = L2VPNTermination.objects.all()
|
queryset = L2VPNTermination.objects.all()
|
||||||
filterset = L2VPNTerminationFilterSet
|
filterset = L2VPNTerminationFilterSet
|
||||||
|
|
||||||
@ -1511,22 +1498,24 @@ class L2VPNTerminationTest(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
device_role=device_role,
|
device_role=device_role,
|
||||||
status='active'
|
status='active'
|
||||||
)
|
)
|
||||||
interfaces = Interface.objects.bulk_create(
|
|
||||||
Interface(name='GigabitEthernet1/0/1', device=device, type='1000baset'),
|
interfaces = (
|
||||||
Interface(name='GigabitEthernet1/0/2', device=device, type='1000baset'),
|
Interface(name='Interface 1', device=device, type='1000baset'),
|
||||||
Interface(name='GigabitEthernet1/0/3', device=device, type='1000baset'),
|
Interface(name='Interface 2', device=device, type='1000baset'),
|
||||||
Interface(name='GigabitEthernet1/0/4', device=device, type='1000baset'),
|
Interface(name='Interface 3', device=device, type='1000baset'),
|
||||||
Interface(name='GigabitEthernet1/0/5', device=device, type='1000baset'),
|
Interface(name='Interface 4', device=device, type='1000baset'),
|
||||||
|
Interface(name='Interface 5', device=device, type='1000baset'),
|
||||||
|
Interface(name='Interface 6', device=device, type='1000baset')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Interface.objects.bulk_create(interfaces)
|
||||||
|
|
||||||
vlans = (
|
vlans = (
|
||||||
VLAN(name='VLAN 1', vid=650001),
|
VLAN(name='VLAN 1', vid=651),
|
||||||
VLAN(name='VLAN 2', vid=650002),
|
VLAN(name='VLAN 2', vid=652),
|
||||||
VLAN(name='VLAN 3', vid=650003),
|
VLAN(name='VLAN 3', vid=653),
|
||||||
VLAN(name='VLAN 4', vid=650004),
|
VLAN(name='VLAN 4', vid=654),
|
||||||
VLAN(name='VLAN 5', vid=650005),
|
VLAN(name='VLAN 5', vid=655)
|
||||||
VLAN(name='VLAN 6', vid=650006),
|
|
||||||
VLAN(name='VLAN 7', vid=650007)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
VLAN.objects.bulk_create(vlans)
|
VLAN.objects.bulk_create(vlans)
|
||||||
@ -1534,26 +1523,33 @@ class L2VPNTerminationTest(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
l2vpns = (
|
l2vpns = (
|
||||||
L2VPN(name='L2VPN 1', type='vxlan', identifier=650001),
|
L2VPN(name='L2VPN 1', type='vxlan', identifier=650001),
|
||||||
L2VPN(name='L2VPN 2', type='vpws', identifier=650002),
|
L2VPN(name='L2VPN 2', type='vpws', identifier=650002),
|
||||||
L2VPN(name='L2VPN 3', type='vpls'), # No RD
|
L2VPN(name='L2VPN 3', type='vpls'), # No RD,
|
||||||
)
|
)
|
||||||
L2VPN.objects.bulk_create(l2vpns)
|
L2VPN.objects.bulk_create(l2vpns)
|
||||||
|
|
||||||
l2vpnterminations = (
|
l2vpnterminations = (
|
||||||
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[0]),
|
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[0]),
|
||||||
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[1]),
|
L2VPNTermination(l2vpn=l2vpns[1], assigned_object=vlans[1]),
|
||||||
L2VPNTermination(l2vpn=l2vpns[0], assigned_object=vlans[2])
|
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.objects.bulk_create(l2vpnterminations)
|
||||||
|
|
||||||
def test_l2vpns(self):
|
def test_l2vpns(self):
|
||||||
l2vpns = L2VPN.objects.all()[:2]
|
l2vpns = L2VPN.objects.all()[:2]
|
||||||
params = {'l2vpn_id': [l2vpns[0].pk, l2vpns[1].pk]}
|
params = {'l2vpn_id': [l2vpns[0].pk, l2vpns[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
params = {'l2vpn': ['L2VPN 1', 'L2VPN 2']}
|
params = {'l2vpn': ['L2VPN 1', 'L2VPN 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
def test_interfaces(self):
|
def test_interfaces(self):
|
||||||
interfaces = Interface.objects.all()[:2]
|
interfaces = Interface.objects.all()[:2]
|
||||||
params = {'interface_id': [interfaces[0].pk, interfaces[1].pk]}
|
params = {'interface_id': [interfaces[0].pk, interfaces[1].pk]}
|
||||||
|
qs = self.filterset(params, self.queryset).qs
|
||||||
|
results = qs.all()
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'interface': ['Interface 1', 'Interface 2']}
|
params = {'interface': ['Interface 1', 'Interface 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
@ -2,8 +2,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 dcim.models import Interface, Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||||
from ipam.choices import IPAddressRoleChoices, PrefixStatusChoices
|
from ipam.choices import IPAddressRoleChoices, PrefixStatusChoices
|
||||||
from ipam.models import Aggregate, IPAddress, IPRange, Prefix, RIR, VLAN, VLANGroup, VRF
|
from ipam.models import Aggregate, IPAddress, IPRange, Prefix, RIR, VLAN, VLANGroup, VRF, L2VPN, L2VPNTermination
|
||||||
|
|
||||||
|
|
||||||
class TestAggregate(TestCase):
|
class TestAggregate(TestCase):
|
||||||
@ -540,11 +541,75 @@ class TestVLANGroup(TestCase):
|
|||||||
self.assertEqual(vlangroup.get_next_available_vid(), 105)
|
self.assertEqual(vlangroup.get_next_available_vid(), 105)
|
||||||
|
|
||||||
|
|
||||||
class TestL2VPN(TestCase):
|
|
||||||
# TODO: L2VPN Tests
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class TestL2VPNTermination(TestCase):
|
class TestL2VPNTermination(TestCase):
|
||||||
# TODO: L2VPN Termination Tests
|
|
||||||
pass
|
@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)
|
||||||
|
device_role = DeviceRole.objects.create(name='Switch')
|
||||||
|
device = Device.objects.create(
|
||||||
|
name='Device 1',
|
||||||
|
site=site,
|
||||||
|
device_type=device_type,
|
||||||
|
device_role=device_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', type='vxlan', identifier=650001),
|
||||||
|
L2VPN(name='L2VPN 2', type='vpws', identifier=650002),
|
||||||
|
L2VPN(name='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,14 +1,18 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from netaddr import IPNetwork
|
from netaddr import IPNetwork
|
||||||
|
|
||||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site, Interface
|
||||||
|
from extras.choices import ObjectChangeActionChoices
|
||||||
|
from extras.models import ObjectChange
|
||||||
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_tags
|
from users.models import ObjectPermission
|
||||||
|
from utilities.testing import ViewTestCases, create_tags, post_data
|
||||||
|
|
||||||
|
|
||||||
class ASNTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
class ASNTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||||
@ -749,10 +753,130 @@ class ServiceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
|
|
||||||
|
|
||||||
class L2VPNTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
class L2VPNTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||||
# TODO: L2VPN Tests
|
model = L2VPN
|
||||||
pass
|
csv_data = (
|
||||||
|
'name,slug,type,identifier',
|
||||||
|
'L2VPN 5,l2vpn-5,vxlan,456',
|
||||||
|
'L2VPN 6,l2vpn-6,vxlan,444',
|
||||||
|
)
|
||||||
|
bulk_edit_data = {
|
||||||
|
'description': 'New Description',
|
||||||
|
}
|
||||||
|
|
||||||
|
@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='vxlan', identifier='650001'),
|
||||||
|
L2VPN(name='L2VPN 2', slug='l2vpn-2', type='vxlan', identifier='650002'),
|
||||||
|
L2VPN(name='L2VPN 3', slug='l2vpn-3', type='vxlan', identifier='650003')
|
||||||
|
)
|
||||||
|
|
||||||
|
L2VPN.objects.bulk_create(l2vpns)
|
||||||
|
|
||||||
|
cls.form_data = {
|
||||||
|
'name': 'L2VPN 8',
|
||||||
|
'slug': 'l2vpn-8',
|
||||||
|
'type': 'vxlan',
|
||||||
|
'identifier': 123,
|
||||||
|
'description': 'Description',
|
||||||
|
'import_targets': [rts[0].pk],
|
||||||
|
'export_targets': [rts[1].pk]
|
||||||
|
}
|
||||||
|
|
||||||
|
print(cls.form_data)
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTerminationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
class L2VPNTerminationTestCase(
|
||||||
# TODO: L2VPN Termination Tests
|
ViewTestCases.GetObjectViewTestCase,
|
||||||
pass
|
ViewTestCases.GetObjectChangelogViewTestCase,
|
||||||
|
ViewTestCases.CreateObjectViewTestCase,
|
||||||
|
ViewTestCases.EditObjectViewTestCase,
|
||||||
|
ViewTestCases.DeleteObjectViewTestCase,
|
||||||
|
ViewTestCases.ListObjectsViewTestCase,
|
||||||
|
ViewTestCases.BulkImportObjectsViewTestCase,
|
||||||
|
ViewTestCases.BulkDeleteObjectsViewTestCase,
|
||||||
|
):
|
||||||
|
|
||||||
|
model = L2VPNTermination
|
||||||
|
|
||||||
|
@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)
|
||||||
|
device_role = DeviceRole.objects.create(name='Switch')
|
||||||
|
device = Device.objects.create(
|
||||||
|
name='Device 1',
|
||||||
|
site=site,
|
||||||
|
device_type=device_type,
|
||||||
|
device_role=device_role,
|
||||||
|
status='active'
|
||||||
|
)
|
||||||
|
|
||||||
|
interface = Interface.objects.create(name='Interface 1', device=device, type='1000baset')
|
||||||
|
l2vpn = L2VPN.objects.create(name='L2VPN 1', type='vxlan', identifier=650001)
|
||||||
|
l2vpn_vlans = L2VPN.objects.create(name='L2VPN 2', type='vxlan', identifier=650002)
|
||||||
|
|
||||||
|
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=l2vpn, assigned_object=vlans[0]),
|
||||||
|
L2VPNTermination(l2vpn=l2vpn, assigned_object=vlans[1]),
|
||||||
|
L2VPNTermination(l2vpn=l2vpn, assigned_object=vlans[2])
|
||||||
|
)
|
||||||
|
L2VPNTermination.objects.bulk_create(terminations)
|
||||||
|
|
||||||
|
cls.form_data = {
|
||||||
|
'l2vpn': l2vpn.pk,
|
||||||
|
'device': device.pk,
|
||||||
|
'interface': interface.pk,
|
||||||
|
}
|
||||||
|
|
||||||
|
cls.csv_data = (
|
||||||
|
"l2vpn,vlan",
|
||||||
|
"L2VPN 2,Vlan 4",
|
||||||
|
"L2VPN 2,Vlan 5",
|
||||||
|
"L2VPN 2,Vlan 6",
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.bulk_edit_data = {}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Custom assertions
|
||||||
|
#
|
||||||
|
|
||||||
|
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)
|
||||||
|
@ -201,6 +201,7 @@ urlpatterns = [
|
|||||||
path('l2vpn-termination/', views.L2VPNTerminationListView.as_view(), name='l2vpntermination_list'),
|
path('l2vpn-termination/', views.L2VPNTerminationListView.as_view(), name='l2vpntermination_list'),
|
||||||
path('l2vpn-termination/add/', views.L2VPNTerminationEditView.as_view(), name='l2vpntermination_add'),
|
path('l2vpn-termination/add/', views.L2VPNTerminationEditView.as_view(), name='l2vpntermination_add'),
|
||||||
path('l2vpn-termination/import/', views.L2VPNTerminationBulkImportView.as_view(), name='l2vpntermination_import'),
|
path('l2vpn-termination/import/', views.L2VPNTerminationBulkImportView.as_view(), name='l2vpntermination_import'),
|
||||||
|
path('l2vpn-termination/edit/', views.L2VPNTerminationBulkEditView.as_view(), name='l2vpntermination_bulk_edit'),
|
||||||
path('l2vpn-termination/delete/', views.L2VPNTerminationBulkDeleteView.as_view(), name='l2vpntermination_bulk_delete'),
|
path('l2vpn-termination/delete/', views.L2VPNTerminationBulkDeleteView.as_view(), name='l2vpntermination_bulk_delete'),
|
||||||
path('l2vpn-termination/<int:pk>/', views.L2VPNTerminationView.as_view(), name='l2vpntermination'),
|
path('l2vpn-termination/<int:pk>/', views.L2VPNTerminationView.as_view(), name='l2vpntermination'),
|
||||||
path('l2vpn-termination/<int:pk>/edit/', views.L2VPNTerminationEditView.as_view(), name='l2vpntermination_edit'),
|
path('l2vpn-termination/<int:pk>/edit/', views.L2VPNTerminationEditView.as_view(), name='l2vpntermination_edit'),
|
||||||
|
@ -1141,6 +1141,13 @@ class ServiceBulkEditView(generic.BulkEditView):
|
|||||||
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
|
||||||
|
form = forms.ServiceBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceBulkDeleteView(generic.BulkDeleteView):
|
||||||
|
queryset = Service.objects.prefetch_related('device', 'virtual_machine')
|
||||||
|
filterset = filtersets.ServiceFilterSet
|
||||||
|
table = tables.ServiceTable
|
||||||
|
|
||||||
|
|
||||||
# L2VPN
|
# L2VPN
|
||||||
@ -1232,14 +1239,14 @@ class L2VPNTerminationBulkImportView(generic.BulkImportView):
|
|||||||
table = tables.L2VPNTerminationTable
|
table = tables.L2VPNTerminationTable
|
||||||
|
|
||||||
|
|
||||||
|
class L2VPNTerminationBulkEditView(generic.BulkEditView):
|
||||||
|
queryset = L2VPNTermination.objects.all()
|
||||||
|
filterset = filtersets.L2VPNTerminationFilterSet
|
||||||
|
table = tables.L2VPNTerminationTable
|
||||||
|
form = forms.L2VPNTerminationBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTerminationBulkDeleteView(generic.BulkDeleteView):
|
class L2VPNTerminationBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = L2VPNTermination.objects.all()
|
queryset = L2VPNTermination.objects.all()
|
||||||
filterset = filtersets.L2VPNTerminationFilterSet
|
filterset = filtersets.L2VPNTerminationFilterSet
|
||||||
table = tables.L2VPNTerminationTable
|
table = tables.L2VPNTerminationTable
|
||||||
form = forms.ServiceBulkEditForm
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceBulkDeleteView(generic.BulkDeleteView):
|
|
||||||
queryset = Service.objects.prefetch_related('device', 'virtual_machine')
|
|
||||||
filterset = filtersets.ServiceFilterSet
|
|
||||||
table = tables.ServiceTable
|
|
||||||
|
@ -104,6 +104,10 @@
|
|||||||
<th scope="row">LAG</th>
|
<th scope="row">LAG</th>
|
||||||
<td>{{ object.lag|linkify|placeholder }}</td>
|
<td>{{ object.lag|linkify|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">L2VPN</th>
|
||||||
|
<td>{{ object.l2vpn_termination.l2vpn|linkify|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -64,6 +64,10 @@
|
|||||||
<th scope="row">Description</th>
|
<th scope="row">Description</th>
|
||||||
<td>{{ object.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">L2VPN</th>
|
||||||
|
<td>{{ object.l2vpn_termination.l2vpn|linkify|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user