Closes #22375: Fix VLAN filter_interface_id performance: use UNION instead of OR across M2M joins (#22387)

This commit is contained in:
Alex Houlton 2026-06-04 20:50:39 +01:00 committed by GitHub
parent 553b97464a
commit b905e99e63
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 46 additions and 9 deletions

View File

@ -1142,19 +1142,17 @@ class VLANFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
def filter_interface_id(self, queryset, name, value):
if value is None:
return queryset.none()
return queryset.filter(
Q(interfaces_as_tagged=value) |
Q(interfaces_as_untagged=value)
).distinct()
tagged = queryset.filter(interfaces_as_tagged=value)
untagged = queryset.filter(interfaces_as_untagged=value)
return queryset.filter(pk__in=tagged.union(untagged).values('pk'))
@extend_schema_field(OpenApiTypes.INT)
def filter_vminterface_id(self, queryset, name, value):
if value is None:
return queryset.none()
return queryset.filter(
Q(vminterfaces_as_tagged=value) |
Q(vminterfaces_as_untagged=value)
).distinct()
tagged = queryset.filter(vminterfaces_as_tagged=value)
untagged = queryset.filter(vminterfaces_as_untagged=value)
return queryset.filter(pk__in=tagged.union(untagged).values('pk'))
@register_filterset

View File

@ -4,7 +4,7 @@ from django.test import TestCase
from netaddr import IPNetwork
from circuits.models import Provider
from dcim.choices import InterfaceTypeChoices
from dcim.choices import InterfaceModeChoices, InterfaceTypeChoices
from dcim.models import Device, DeviceRole, DeviceType, Interface, Location, Manufacturer, Rack, Region, Site, SiteGroup
from ipam.choices import *
from ipam.filtersets import *
@ -2206,11 +2206,50 @@ class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'interface_id': interface_id}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
# An interface untagged on one VLAN and tagged on a different VLAN should return both (UNION across paths)
vlans = self.queryset.all()[:2]
interface = Interface.objects.create(
device=Device.objects.first(),
name='Interface X',
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
mode=InterfaceModeChoices.MODE_TAGGED,
untagged_vlan=vlans[0],
)
interface.tagged_vlans.add(vlans[1])
params = {'interface_id': interface.pk}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
# A VLAN that is both untagged and tagged on the same interface should be returned only once (deduplication)
interface.tagged_vlans.add(vlans[0])
params = {'interface_id': interface.pk}
qs = self.filterset(params, self.queryset).qs
self.assertEqual(qs.count(), 2)
self.assertEqual(len(qs), len(set(qs.values_list('pk', flat=True))))
def test_vminterface(self):
vminterface_id = VMInterface.objects.first().pk
params = {'vminterface_id': vminterface_id}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
# A VM interface untagged on one VLAN and tagged on a different VLAN should return both (UNION across paths)
vlans = self.queryset.all()[:2]
vminterface = VMInterface.objects.create(
virtual_machine=VirtualMachine.objects.first(),
name='VM Interface X',
mode=InterfaceModeChoices.MODE_TAGGED,
untagged_vlan=vlans[0],
)
vminterface.tagged_vlans.add(vlans[1])
params = {'vminterface_id': vminterface.pk}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
# A VLAN that is both untagged and tagged on the same interface should be returned only once (deduplication)
vminterface.tagged_vlans.add(vlans[0])
params = {'vminterface_id': vminterface.pk}
qs = self.filterset(params, self.queryset).qs
self.assertEqual(qs.count(), 2)
self.assertEqual(len(qs), len(set(qs.values_list('pk', flat=True))))
def test_qinq_role(self):
params = {'qinq_role': [VLANQinQRoleChoices.ROLE_SERVICE, VLANQinQRoleChoices.ROLE_CUSTOMER]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)