From f242f17ce59fb5bc1fb6ca59eb462eed9ed61a22 Mon Sep 17 00:00:00 2001 From: Martin Hauser Date: Fri, 3 Apr 2026 23:55:11 +0200 Subject: [PATCH] Fixes #21542: Increase supported interface speed values above 2.1 Tbps (#21834) --- netbox/dcim/filtersets.py | 3 ++- netbox/dcim/forms/bulk_edit.py | 10 ++++++-- netbox/dcim/forms/filtersets.py | 4 ++-- netbox/dcim/graphql/filters.py | 10 ++++++-- netbox/dcim/graphql/types.py | 1 + .../0227_alter_interface_speed_bigint.py | 15 ++++++++++++ netbox/dcim/models/device_components.py | 2 +- netbox/dcim/tests/test_api.py | 4 ++-- netbox/dcim/tests/test_filtersets.py | 4 ++-- netbox/dcim/tests/test_views.py | 8 +++---- netbox/utilities/filters.py | 8 +++++++ netbox/utilities/forms/fields/fields.py | 23 +++++++++++++++++++ 12 files changed, 76 insertions(+), 16 deletions(-) create mode 100644 netbox/dcim/migrations/0227_alter_interface_speed_bigint.py diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 927ef9f45..339537b4c 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -26,6 +26,7 @@ from tenancy.models import * from users.filterset_mixins import OwnerFilterMixin from users.models import User from utilities.filters import ( + MultiValueBigNumberFilter, MultiValueCharFilter, MultiValueContentTypeFilter, MultiValueMACAddressFilter, @@ -2175,7 +2176,7 @@ class InterfaceFilterSet( distinct=False, label=_('LAG interface (ID)'), ) - speed = MultiValueNumberFilter() + speed = MultiValueBigNumberFilter(min_value=0) duplex = django_filters.MultipleChoiceFilter( choices=InterfaceDuplexChoices, distinct=False, diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index ad41e5a74..a3cc217c6 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -20,7 +20,13 @@ from netbox.forms.mixins import ChangelogMessageMixin, OwnerMixin from tenancy.models import Tenant from users.models import User from utilities.forms import BulkEditForm, add_blank_choice, form_from_model -from utilities.forms.fields import ColorField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField +from utilities.forms.fields import ( + ColorField, + DynamicModelChoiceField, + DynamicModelMultipleChoiceField, + JSONField, + PositiveBigIntegerField, +) from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups from utilities.forms.widgets import BulkEditNullBooleanSelect, NumberWithOptions from virtualization.models import Cluster @@ -1420,7 +1426,7 @@ class InterfaceBulkEditForm( 'device_id': '$device', } ) - speed = forms.IntegerField( + speed = PositiveBigIntegerField( label=_('Speed'), required=False, widget=NumberWithOptions( diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index b375f2bba..ebd380206 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -19,7 +19,7 @@ from tenancy.forms import ContactModelFilterForm, TenancyFilterForm from tenancy.models import Tenant from users.models import User from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice -from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField +from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, PositiveBigIntegerField, TagFilterField from utilities.forms.rendering import FieldSet from utilities.forms.widgets import NumberWithOptions from virtualization.models import Cluster, ClusterGroup, VirtualMachine @@ -1603,7 +1603,7 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): choices=InterfaceTypeChoices, required=False ) - speed = forms.IntegerField( + speed = PositiveBigIntegerField( label=_('Speed'), required=False, widget=NumberWithOptions( diff --git a/netbox/dcim/graphql/filters.py b/netbox/dcim/graphql/filters.py index 99bd7f8b4..c5aa04236 100644 --- a/netbox/dcim/graphql/filters.py +++ b/netbox/dcim/graphql/filters.py @@ -47,7 +47,13 @@ if TYPE_CHECKING: VRFFilter, ) from netbox.graphql.enums import ColorEnum - from netbox.graphql.filter_lookups import FloatLookup, IntegerArrayLookup, IntegerLookup, TreeNodeFilter + from netbox.graphql.filter_lookups import ( + BigIntegerLookup, + FloatLookup, + IntegerArrayLookup, + IntegerLookup, + TreeNodeFilter, + ) from users.graphql.filters import UserFilter from virtualization.graphql.filters import ClusterFilter from vpn.graphql.filters import L2VPNFilter, TunnelTerminationFilter @@ -519,7 +525,7 @@ class InterfaceFilter( strawberry_django.filter_field() ) mgmt_only: FilterLookup[bool] | None = strawberry_django.filter_field() - speed: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( + speed: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( strawberry_django.filter_field() ) duplex: BaseFilterLookup[Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( diff --git a/netbox/dcim/graphql/types.py b/netbox/dcim/graphql/types.py index cf16bc39f..2a6c3750a 100644 --- a/netbox/dcim/graphql/types.py +++ b/netbox/dcim/graphql/types.py @@ -433,6 +433,7 @@ class MACAddressType(PrimaryObjectType): ) class InterfaceType(IPAddressesMixin, ModularComponentType, CabledObjectMixin, PathEndpointMixin): _name: str + speed: BigInt | None wwn: str | None parent: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None bridge: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None diff --git a/netbox/dcim/migrations/0227_alter_interface_speed_bigint.py b/netbox/dcim/migrations/0227_alter_interface_speed_bigint.py new file mode 100644 index 000000000..c9c657a6b --- /dev/null +++ b/netbox/dcim/migrations/0227_alter_interface_speed_bigint.py @@ -0,0 +1,15 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('dcim', '0226_modulebay_rebuild_tree'), + ] + + operations = [ + migrations.AlterField( + model_name='interface', + name='speed', + field=models.PositiveBigIntegerField(blank=True, null=True), + ), + ] diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 75a26e25e..dbb59dc7f 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -806,7 +806,7 @@ class Interface( verbose_name=_('management only'), help_text=_('This interface is used only for out-of-band management') ) - speed = models.PositiveIntegerField( + speed = models.PositiveBigIntegerField( blank=True, null=True, verbose_name=_('speed (Kbps)') diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 70c212849..61928e562 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -1930,9 +1930,9 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase { 'device': device.pk, 'name': 'Interface 4', - 'type': '1000base-t', + 'type': 'other', 'mode': InterfaceModeChoices.MODE_TAGGED, - 'speed': 1000000, + 'speed': 16_000_000_000, 'duplex': 'full', 'vrf': vrfs[0].pk, 'poe_mode': InterfacePoEModeChoices.MODE_PD, diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 23ca1ba62..fb8340a6d 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -4655,7 +4655,7 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil enabled=True, mgmt_only=True, tx_power=40, - speed=100000, + speed=16_000_000_000, duplex='full', poe_mode=InterfacePoEModeChoices.MODE_PD, poe_type=InterfacePoETypeChoices.TYPE_2_8023AT, @@ -4757,7 +4757,7 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_speed(self): - params = {'speed': [1000000, 100000]} + params = {'speed': [16_000_000_000, 1_000_000, 100_000]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_duplex(self): diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index 1aac72875..197af26b3 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -2961,13 +2961,13 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase): cls.form_data = { 'device': device.pk, 'name': 'Interface X', - 'type': InterfaceTypeChoices.TYPE_1GE_GBIC, + 'type': InterfaceTypeChoices.TYPE_OTHER, 'enabled': False, 'bridge': interfaces[4].pk, 'lag': interfaces[3].pk, 'wwn': EUI('01:02:03:04:05:06:07:08', version=64), 'mtu': 65000, - 'speed': 1000000, + 'speed': 16_000_000_000, 'duplex': 'full', 'mgmt_only': True, 'description': 'A front port', @@ -2985,13 +2985,13 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase): cls.bulk_create_data = { 'device': device.pk, 'name': 'Interface [4-6]', - 'type': InterfaceTypeChoices.TYPE_1GE_GBIC, + 'type': InterfaceTypeChoices.TYPE_OTHER, 'enabled': False, 'bridge': interfaces[4].pk, 'lag': interfaces[3].pk, 'wwn': EUI('01:02:03:04:05:06:07:08', version=64), 'mtu': 2000, - 'speed': 100000, + 'speed': 16_000_000_000, 'duplex': 'half', 'mgmt_only': True, 'description': 'A front port', diff --git a/netbox/utilities/filters.py b/netbox/utilities/filters.py index 1b7f91a12..a445d16b7 100644 --- a/netbox/utilities/filters.py +++ b/netbox/utilities/filters.py @@ -7,9 +7,12 @@ from django_filters.constants import EMPTY_VALUES from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import extend_schema_field +from .forms.fields import BigIntegerField + __all__ = ( 'ContentTypeFilter', 'MultiValueArrayFilter', + 'MultiValueBigNumberFilter', 'MultiValueCharFilter', 'MultiValueContentTypeFilter', 'MultiValueDateFilter', @@ -77,6 +80,11 @@ class MultiValueNumberFilter(django_filters.MultipleChoiceFilter): field_class = multivalue_field_factory(forms.IntegerField) +@extend_schema_field(OpenApiTypes.INT64) +class MultiValueBigNumberFilter(MultiValueNumberFilter): + field_class = multivalue_field_factory(BigIntegerField) + + @extend_schema_field(OpenApiTypes.DECIMAL) class MultiValueDecimalFilter(django_filters.MultipleChoiceFilter): field_class = multivalue_field_factory(forms.DecimalField) diff --git a/netbox/utilities/forms/fields/fields.py b/netbox/utilities/forms/fields/fields.py index bd8600c4c..66cd152b5 100644 --- a/netbox/utilities/forms/fields/fields.py +++ b/netbox/utilities/forms/fields/fields.py @@ -2,6 +2,7 @@ import json from django import forms from django.conf import settings +from django.db.models import BigIntegerField as BigIntegerModelField from django.db.models import Count from django.forms.fields import InvalidJSONInput from django.forms.fields import JSONField as _JSONField @@ -13,17 +14,39 @@ from utilities.forms import widgets from utilities.validators import EnhancedURLValidator __all__ = ( + 'BigIntegerField', 'ColorField', 'CommentField', 'JSONField', 'LaxURLField', 'MACAddressField', + 'PositiveBigIntegerField', 'QueryField', 'SlugField', 'TagFilterField', ) +class BigIntegerField(forms.IntegerField): + """ + An IntegerField constrained to the range of a signed 64-bit integer. + """ + def __init__(self, *args, **kwargs): + kwargs.setdefault('min_value', -BigIntegerModelField.MAX_BIGINT - 1) + kwargs.setdefault('max_value', BigIntegerModelField.MAX_BIGINT) + super().__init__(*args, **kwargs) + + +class PositiveBigIntegerField(BigIntegerField): + """ + An IntegerField constrained to the range supported by Django's + PositiveBigIntegerField model field. + """ + def __init__(self, *args, **kwargs): + kwargs.setdefault('min_value', 0) + super().__init__(*args, **kwargs) + + class QueryField(forms.CharField): """ A CharField subclass used for global search/query fields in filter forms.