mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-30 20:36:26 -06:00
Misc cleanup; add tests for Q-in-Q fields
This commit is contained in:
parent
f15c26eac6
commit
cf72b7cb63
@ -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.
|
||||||
|
@ -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')]]
|
||||||
|
@ -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.")
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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)'),
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
|
@ -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(),
|
||||||
|
@ -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')
|
||||||
|
@ -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,
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
@ -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>
|
||||||
|
@ -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')]]
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user