Просмотр исходного кода

Fixes #21542: Increase supported interface speed values above 2.1 Tbps (#21834)

Martin Hauser 4 дней назад
Родитель
Сommit
f242f17ce5

+ 2 - 1
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,

+ 8 - 2
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(

+ 2 - 2
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(

+ 8 - 2
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 = (

+ 1 - 0
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

+ 15 - 0
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),
+        ),
+    ]

+ 1 - 1
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)')

+ 2 - 2
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,

+ 2 - 2
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):

+ 4 - 4
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',

+ 8 - 0
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)

+ 23 - 0
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.