Misc cleanup; add tests for Q-in-Q fields

This commit is contained in:
Jeremy Stretch 2024-10-28 12:26:19 -04:00
parent f15c26eac6
commit cf72b7cb63
17 changed files with 127 additions and 23 deletions

View File

@ -33,4 +33,4 @@ For VLANs which comprise a Q-in-Q/IEEE 802.1ad topology, this field indicates wh
### Q-in-Q Service VLAN ### Q-in-Q Service VLAN
The designated parent service VLAN for a Q-in-Q customer VLAN. The designated parent service VLAN for a Q-in-Q customer VLAN. This may be set only for Q-in-Q custom VLANs.

View File

@ -385,6 +385,7 @@ class InterfaceType(IPAddressesMixin, ModularComponentType, CabledObjectMixin, P
wireless_link: Annotated["WirelessLinkType", strawberry.lazy('wireless.graphql.types')] | None wireless_link: Annotated["WirelessLinkType", strawberry.lazy('wireless.graphql.types')] | None
untagged_vlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None untagged_vlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None
vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
qinq_svlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None
vdcs: List[Annotated["VirtualDeviceContextType", strawberry.lazy('dcim.graphql.types')]] vdcs: List[Annotated["VirtualDeviceContextType", strawberry.lazy('dcim.graphql.types')]]
tagged_vlans: List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]] tagged_vlans: List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]

View File

@ -576,7 +576,7 @@ class BaseInterface(models.Model):
def clean(self): def clean(self):
super().clean() super().clean()
# Virtual Interfaces cannot have a Cable attached # SVLAN can be defined only for Q-in-Q interfaces
if self.qinq_svlan and self.mode != InterfaceModeChoices.MODE_Q_IN_Q: if self.qinq_svlan and self.mode != InterfaceModeChoices.MODE_Q_IN_Q:
raise ValidationError({ raise ValidationError({
'qinq_svlan': _("Only Q-in-Q interfaces may specify a service VLAN.") 'qinq_svlan': _("Only Q-in-Q interfaces may specify a service VLAN.")

View File

@ -7,6 +7,7 @@ from dcim.choices import *
from dcim.constants import * from dcim.constants import *
from dcim.models import * from dcim.models import *
from extras.models import ConfigTemplate from extras.models import ConfigTemplate
from ipam.choices import VLANQinQRoleChoices
from ipam.models import ASN, RIR, VLAN, VRF from ipam.models import ASN, RIR, VLAN, VRF
from netbox.api.serializers import GenericObjectSerializer from netbox.api.serializers import GenericObjectSerializer
from tenancy.models import Tenant from tenancy.models import Tenant
@ -1618,6 +1619,7 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
VLAN(name='VLAN 1', vid=1), VLAN(name='VLAN 1', vid=1),
VLAN(name='VLAN 2', vid=2), VLAN(name='VLAN 2', vid=2),
VLAN(name='VLAN 3', vid=3), VLAN(name='VLAN 3', vid=3),
VLAN(name='SVLAN 1', vid=1001, qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
) )
VLAN.objects.bulk_create(vlans) VLAN.objects.bulk_create(vlans)
@ -1676,18 +1678,22 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
'vdcs': [vdcs[1].pk], 'vdcs': [vdcs[1].pk],
'name': 'Interface 7', 'name': 'Interface 7',
'type': InterfaceTypeChoices.TYPE_80211A, 'type': InterfaceTypeChoices.TYPE_80211A,
'mode': InterfaceModeChoices.MODE_Q_IN_Q,
'tx_power': 10, 'tx_power': 10,
'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk], 'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk],
'rf_channel': WirelessChannelChoices.CHANNEL_5G_32, 'rf_channel': WirelessChannelChoices.CHANNEL_5G_32,
'qinq_svlan': vlans[3].pk,
}, },
{ {
'device': device.pk, 'device': device.pk,
'vdcs': [vdcs[1].pk], 'vdcs': [vdcs[1].pk],
'name': 'Interface 8', 'name': 'Interface 8',
'type': InterfaceTypeChoices.TYPE_80211A, 'type': InterfaceTypeChoices.TYPE_80211A,
'mode': InterfaceModeChoices.MODE_Q_IN_Q,
'tx_power': 10, 'tx_power': 10,
'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk], 'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk],
'rf_channel': "", 'rf_channel': "",
'qinq_svlan': vlans[3].pk,
}, },
] ]

View File

@ -4,7 +4,8 @@ from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
from dcim.choices import * from dcim.choices import *
from dcim.filtersets import * from dcim.filtersets import *
from dcim.models import * from dcim.models import *
from ipam.models import ASN, IPAddress, RIR, VRF from ipam.choices import VLANQinQRoleChoices
from ipam.models import ASN, IPAddress, RIR, VLAN, VRF
from netbox.choices import ColorChoices, WeightUnitChoices from netbox.choices import ColorChoices, WeightUnitChoices
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
from users.models import User from users.models import User
@ -3520,7 +3521,7 @@ class PowerOutletTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedF
class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests): class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = Interface.objects.all() queryset = Interface.objects.all()
filterset = InterfaceFilterSet filterset = InterfaceFilterSet
ignore_fields = ('tagged_vlans', 'untagged_vlan', 'vdcs') ignore_fields = ('tagged_vlans', 'untagged_vlan', 'qinq_svlan', 'vdcs')
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
@ -3669,6 +3670,13 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
) )
VirtualDeviceContext.objects.bulk_create(vdcs) VirtualDeviceContext.objects.bulk_create(vdcs)
vlans = (
VLAN(name='SVLAN 1', vid=1001, qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
VLAN(name='SVLAN 2', vid=1002, qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
VLAN(name='SVLAN 3', vid=1003, qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
)
VLAN.objects.bulk_create(vlans)
interfaces = ( interfaces = (
Interface( Interface(
device=devices[0], device=devices[0],
@ -3742,7 +3750,9 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
speed=100000, speed=100000,
duplex='full', duplex='full',
poe_mode=InterfacePoEModeChoices.MODE_PD, poe_mode=InterfacePoEModeChoices.MODE_PD,
poe_type=InterfacePoETypeChoices.TYPE_2_8023AT poe_type=InterfacePoETypeChoices.TYPE_2_8023AT,
mode=InterfaceModeChoices.MODE_Q_IN_Q,
qinq_svlan=vlans[0]
), ),
Interface( Interface(
device=devices[4], device=devices[4],
@ -3751,7 +3761,9 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
type=InterfaceTypeChoices.TYPE_OTHER, type=InterfaceTypeChoices.TYPE_OTHER,
enabled=True, enabled=True,
mgmt_only=True, mgmt_only=True,
tx_power=40 tx_power=40,
mode=InterfaceModeChoices.MODE_Q_IN_Q,
qinq_svlan=vlans[1]
), ),
Interface( Interface(
device=devices[4], device=devices[4],
@ -3760,7 +3772,9 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
type=InterfaceTypeChoices.TYPE_OTHER, type=InterfaceTypeChoices.TYPE_OTHER,
enabled=False, enabled=False,
mgmt_only=False, mgmt_only=False,
tx_power=40 tx_power=40,
mode=InterfaceModeChoices.MODE_Q_IN_Q,
qinq_svlan=vlans[2]
), ),
Interface( Interface(
device=devices[4], device=devices[4],
@ -4016,6 +4030,13 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
params = {'vdc_identifier': vdc.values_list('identifier', flat=True)} params = {'vdc_identifier': vdc.values_list('identifier', flat=True)}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
def test_vlan(self):
vlan = VLAN.objects.filter(qinq_role=VLANQinQRoleChoices.ROLE_SERVICE).first()
params = {'vlan_id': vlan.pk}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
params = {'vlan': vlan.vid}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests): class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = FrontPort.objects.all() queryset = FrontPort.objects.all()

View File

@ -64,7 +64,7 @@ class VLANSerializer(NetBoxModelSerializer):
status = ChoiceField(choices=VLANStatusChoices, required=False) status = ChoiceField(choices=VLANStatusChoices, required=False)
role = RoleSerializer(nested=True, required=False, allow_null=True) role = RoleSerializer(nested=True, required=False, allow_null=True)
qinq_role = ChoiceField(choices=VLANQinQRoleChoices, required=False) qinq_role = ChoiceField(choices=VLANQinQRoleChoices, required=False)
qinq_svlan = NestedVLANSerializer(required=False, allow_null=True) qinq_svlan = NestedVLANSerializer(required=False, allow_null=True, default=None)
l2vpn_termination = L2VPNTerminationSerializer(nested=True, read_only=True, allow_null=True) l2vpn_termination = L2VPNTerminationSerializer(nested=True, read_only=True, allow_null=True)
# Related object counts # Related object counts

View File

@ -1040,14 +1040,13 @@ class VLANFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
method='get_for_virtualmachine' method='get_for_virtualmachine'
) )
qinq_role = django_filters.MultipleChoiceFilter( qinq_role = django_filters.MultipleChoiceFilter(
choices=VLANQinQRoleChoices, choices=VLANQinQRoleChoices
null_value=None
) )
qinq_svlan_id = django_filters.ModelMultipleChoiceFilter( qinq_svlan_id = django_filters.ModelMultipleChoiceFilter(
queryset=VLAN.objects.all(), queryset=VLAN.objects.all(),
label=_('Q-in-Q SVLAN (ID)'), label=_('Q-in-Q SVLAN (ID)'),
) )
qinq_svlan_vid = django_filters.NumberFilter( qinq_svlan_vid = MultiValueNumberFilter(
field_name='qinq_svlan__vid', field_name='qinq_svlan__vid',
label=_('Q-in-Q SVLAN number (1-4094)'), label=_('Q-in-Q SVLAN number (1-4094)'),
) )

View File

@ -526,10 +526,18 @@ class VLANBulkEditForm(NetBoxModelBulkEditForm):
required=False required=False
) )
qinq_role = forms.ChoiceField( qinq_role = forms.ChoiceField(
label=_('Status'), label=_('Q-in-Q role'),
choices=add_blank_choice(VLANQinQRoleChoices), choices=add_blank_choice(VLANQinQRoleChoices),
required=False required=False
) )
qinq_svlan = DynamicModelChoiceField(
label=_('Q-in-Q SVLAN'),
queryset=VLAN.objects.all(),
required=False,
query_params={
'qinq_role': VLANQinQRoleChoices.ROLE_SERVICE,
}
)
comments = CommentField() comments = CommentField()
model = VLAN model = VLAN

View File

@ -440,11 +440,6 @@ class VLANImportForm(NetBoxModelImportForm):
to_field_name='name', to_field_name='name',
help_text=_('Assigned VLAN group') help_text=_('Assigned VLAN group')
) )
qinq_role = CSVChoiceField(
label=_('Q-in-Q role'),
choices=VLANStatusChoices,
help_text=_('Operational status')
)
tenant = CSVModelChoiceField( tenant = CSVModelChoiceField(
label=_('Tenant'), label=_('Tenant'),
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
@ -464,6 +459,12 @@ class VLANImportForm(NetBoxModelImportForm):
to_field_name='name', to_field_name='name',
help_text=_('Functional role') help_text=_('Functional role')
) )
qinq_role = CSVChoiceField(
label=_('Q-in-Q role'),
choices=VLANStatusChoices,
required=False,
help_text=_('Operational status')
)
qinq_svlan = CSVModelChoiceField( qinq_svlan = CSVModelChoiceField(
label=_('Q-in-Q SVLAN'), label=_('Q-in-Q SVLAN'),
queryset=VLAN.objects.all(), queryset=VLAN.objects.all(),

View File

@ -467,7 +467,7 @@ class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
FieldSet('q', 'filter_id', 'tag'), FieldSet('q', 'filter_id', 'tag'),
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')), FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
FieldSet('group_id', 'status', 'role_id', 'vid', 'l2vpn_id', name=_('Attributes')), FieldSet('group_id', 'status', 'role_id', 'vid', 'l2vpn_id', name=_('Attributes')),
FieldSet('qinq_role', 'qinq_svlan', name=_('Q-in-Q/802.1ad')), FieldSet('qinq_role', 'qinq_svlan_id', name=_('Q-in-Q/802.1ad')),
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')), FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
) )
selector_fields = ('filter_id', 'q', 'site_id') selector_fields = ('filter_id', 'q', 'site_id')

View File

@ -234,7 +234,7 @@ class ServiceTemplateType(NetBoxObjectType):
@strawberry_django.type( @strawberry_django.type(
models.VLAN, models.VLAN,
fields='__all__', exclude=('qinq_svlan',),
filters=VLANFilter filters=VLANFilter
) )
class VLANType(NetBoxObjectType): class VLANType(NetBoxObjectType):
@ -250,6 +250,10 @@ class VLANType(NetBoxObjectType):
interfaces_as_tagged: List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]] interfaces_as_tagged: List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
vminterfaces_as_tagged: List[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]] vminterfaces_as_tagged: List[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]
@strawberry_django.field
def qinq_svlan(self) -> Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None:
return self.qinq_svlan
@strawberry_django.type( @strawberry_django.type(
models.VLANGroup, models.VLANGroup,

View File

@ -980,6 +980,7 @@ class VLANTest(APIViewTestCases.APIViewTestCase):
VLAN(name='VLAN 1', vid=1, group=vlan_groups[0]), VLAN(name='VLAN 1', vid=1, group=vlan_groups[0]),
VLAN(name='VLAN 2', vid=2, group=vlan_groups[0]), VLAN(name='VLAN 2', vid=2, group=vlan_groups[0]),
VLAN(name='VLAN 3', vid=3, group=vlan_groups[0]), VLAN(name='VLAN 3', vid=3, group=vlan_groups[0]),
VLAN(name='SVLAN 1', vid=1001, qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
) )
VLAN.objects.bulk_create(vlans) VLAN.objects.bulk_create(vlans)
@ -999,6 +1000,12 @@ class VLANTest(APIViewTestCases.APIViewTestCase):
'name': 'VLAN 6', 'name': 'VLAN 6',
'group': vlan_groups[1].pk, 'group': vlan_groups[1].pk,
}, },
{
'vid': 2001,
'name': 'CVLAN 1',
'qinq_role': VLANQinQRoleChoices.ROLE_CUSTOMER,
'qinq_svlan': vlans[3].pk,
},
] ]
def test_delete_vlan_with_prefix(self): def test_delete_vlan_with_prefix(self):

View File

@ -1630,6 +1630,7 @@ class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
Site(name='Site 4', slug='site-4', region=regions[0], group=site_groups[0]), Site(name='Site 4', slug='site-4', region=regions[0], group=site_groups[0]),
Site(name='Site 5', slug='site-5', region=regions[1], group=site_groups[1]), Site(name='Site 5', slug='site-5', region=regions[1], group=site_groups[1]),
Site(name='Site 6', slug='site-6', region=regions[2], group=site_groups[2]), Site(name='Site 6', slug='site-6', region=regions[2], group=site_groups[2]),
Site(name='Site 7', slug='site-7'),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -1784,9 +1785,21 @@ class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
# Create one globally available VLAN # Create one globally available VLAN
VLAN(vid=1000, name='Global VLAN'), VLAN(vid=1000, name='Global VLAN'),
# Create some Q-in-Q service VLANs
VLAN(vid=2001, name='SVLAN 1', site=sites[6], qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
VLAN(vid=2002, name='SVLAN 2', site=sites[6], qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
VLAN(vid=2003, name='SVLAN 3', site=sites[6], qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
) )
VLAN.objects.bulk_create(vlans) VLAN.objects.bulk_create(vlans)
# Create Q-in-Q customer VLANs
VLAN.objects.bulk_create([
VLAN(vid=3001, name='CVLAN 1', site=sites[6], qinq_svlan=vlans[29], qinq_role=VLANQinQRoleChoices.ROLE_CUSTOMER),
VLAN(vid=3002, name='CVLAN 2', site=sites[6], qinq_svlan=vlans[30], qinq_role=VLANQinQRoleChoices.ROLE_CUSTOMER),
VLAN(vid=3003, name='CVLAN 3', site=sites[6], qinq_svlan=vlans[31], qinq_role=VLANQinQRoleChoices.ROLE_CUSTOMER),
])
# Assign VLANs to device interfaces # Assign VLANs to device interfaces
interfaces[0].untagged_vlan = vlans[0] interfaces[0].untagged_vlan = vlans[0]
interfaces[0].tagged_vlans.add(vlans[1]) interfaces[0].tagged_vlans.add(vlans[1])
@ -1897,6 +1910,17 @@ class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'vminterface_id': vminterface_id} params = {'vminterface_id': vminterface_id}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
def test_qinq_role(self):
params = {'qinq_role': [VLANQinQRoleChoices.ROLE_SERVICE, VLANQinQRoleChoices.ROLE_CUSTOMER]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
def test_qinq_svlan(self):
vlans = VLAN.objects.filter(qinq_role=VLANQinQRoleChoices.ROLE_SERVICE)[:2]
params = {'qinq_svlan_id': [vlans[0].pk, vlans[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'qinq_svlan_vid': [vlans[0].vid, vlans[1].vid]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class ServiceTemplateTestCase(TestCase, ChangeLoggedFilterSetTests): class ServiceTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = ServiceTemplate.objects.all() queryset = ServiceTemplate.objects.all()

View File

@ -64,7 +64,13 @@
</tr> </tr>
<tr> <tr>
<th scope="row">{% trans "Q-in-Q Role" %}</th> <th scope="row">{% trans "Q-in-Q Role" %}</th>
<td>{% badge object.get_qinq_role_display bg_color=object.get_qinq_role_color %}</td> <td>
{% if object.qinq_role %}
{% badge object.get_qinq_role_display bg_color=object.get_qinq_role_color %}
{% else %}
{{ ''|placeholder }}
{% endif %}
</td>
</tr> </tr>
{% if object.qinq_role == 'c-vlan' %} {% if object.qinq_role == 'c-vlan' %}
<tr> <tr>

View File

@ -100,6 +100,7 @@ class VMInterfaceType(IPAddressesMixin, ComponentType):
bridge: Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')] | None bridge: Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')] | None
untagged_vlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None untagged_vlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None
vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
qinq_svlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None
tagged_vlans: List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]] tagged_vlans: List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]
bridge_interfaces: List[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]] bridge_interfaces: List[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]

View File

@ -4,6 +4,7 @@ from rest_framework import status
from dcim.choices import InterfaceModeChoices from dcim.choices import InterfaceModeChoices
from dcim.models import Site from dcim.models import Site
from extras.models import ConfigTemplate from extras.models import ConfigTemplate
from ipam.choices import VLANQinQRoleChoices
from ipam.models import VLAN, VRF from ipam.models import VLAN, VRF
from utilities.testing import APITestCase, APIViewTestCases, create_test_device, create_test_virtualmachine from utilities.testing import APITestCase, APIViewTestCases, create_test_device, create_test_virtualmachine
from virtualization.choices import * from virtualization.choices import *
@ -270,6 +271,7 @@ class VMInterfaceTest(APIViewTestCases.APIViewTestCase):
VLAN(name='VLAN 1', vid=1), VLAN(name='VLAN 1', vid=1),
VLAN(name='VLAN 2', vid=2), VLAN(name='VLAN 2', vid=2),
VLAN(name='VLAN 3', vid=3), VLAN(name='VLAN 3', vid=3),
VLAN(name='SVLAN 1', vid=1001, qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
) )
VLAN.objects.bulk_create(vlans) VLAN.objects.bulk_create(vlans)
@ -307,6 +309,12 @@ class VMInterfaceTest(APIViewTestCases.APIViewTestCase):
'untagged_vlan': vlans[2].pk, 'untagged_vlan': vlans[2].pk,
'vrf': vrfs[2].pk, 'vrf': vrfs[2].pk,
}, },
{
'virtual_machine': virtualmachine.pk,
'name': 'Interface 7',
'mode': InterfaceModeChoices.MODE_Q_IN_Q,
'qinq_svlan': vlans[3].pk,
},
] ]
def test_bulk_delete_child_interfaces(self): def test_bulk_delete_child_interfaces(self):

View File

@ -1,7 +1,9 @@
from django.test import TestCase from django.test import TestCase
from dcim.choices import InterfaceModeChoices
from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup
from ipam.models import IPAddress, VRF from ipam.choices import VLANQinQRoleChoices
from ipam.models import IPAddress, VLAN, VRF
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device from utilities.testing import ChangeLoggedFilterSetTests, create_test_device
from virtualization.choices import * from virtualization.choices import *
@ -528,7 +530,7 @@ class VirtualMachineTestCase(TestCase, ChangeLoggedFilterSetTests):
class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests): class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = VMInterface.objects.all() queryset = VMInterface.objects.all()
filterset = VMInterfaceFilterSet filterset = VMInterfaceFilterSet
ignore_fields = ('tagged_vlans', 'untagged_vlan',) ignore_fields = ('tagged_vlans', 'untagged_vlan', 'qinq_svlan')
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
@ -554,6 +556,13 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
) )
VRF.objects.bulk_create(vrfs) VRF.objects.bulk_create(vrfs)
vlans = (
VLAN(name='SVLAN 1', vid=1001, qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
VLAN(name='SVLAN 2', vid=1002, qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
VLAN(name='SVLAN 3', vid=1003, qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
)
VLAN.objects.bulk_create(vlans)
vms = ( vms = (
VirtualMachine(name='Virtual Machine 1', cluster=clusters[0]), VirtualMachine(name='Virtual Machine 1', cluster=clusters[0]),
VirtualMachine(name='Virtual Machine 2', cluster=clusters[1]), VirtualMachine(name='Virtual Machine 2', cluster=clusters[1]),
@ -587,7 +596,9 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
mtu=300, mtu=300,
mac_address='00-00-00-00-00-03', mac_address='00-00-00-00-00-03',
vrf=vrfs[2], vrf=vrfs[2],
description='foobar3' description='foobar3',
mode=InterfaceModeChoices.MODE_Q_IN_Q,
qinq_svlan=vlans[0]
), ),
) )
VMInterface.objects.bulk_create(interfaces) VMInterface.objects.bulk_create(interfaces)
@ -658,6 +669,13 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'description': ['foobar1', 'foobar2']} params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_vlan(self):
vlan = VLAN.objects.filter(qinq_role=VLANQinQRoleChoices.ROLE_SERVICE).first()
params = {'vlan_id': vlan.pk}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
params = {'vlan': vlan.vid}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
class VirtualDiskTestCase(TestCase, ChangeLoggedFilterSetTests): class VirtualDiskTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = VirtualDisk.objects.all() queryset = VirtualDisk.objects.all()