Explorar el Código

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

Martin Hauser hace 4 días
padre
commit
f242f17ce5

+ 2 - 1
netbox/dcim/filtersets.py

@@ -26,6 +26,7 @@ from tenancy.models import *
 from users.filterset_mixins import OwnerFilterMixin
 from users.filterset_mixins import OwnerFilterMixin
 from users.models import User
 from users.models import User
 from utilities.filters import (
 from utilities.filters import (
+    MultiValueBigNumberFilter,
     MultiValueCharFilter,
     MultiValueCharFilter,
     MultiValueContentTypeFilter,
     MultiValueContentTypeFilter,
     MultiValueMACAddressFilter,
     MultiValueMACAddressFilter,
@@ -2175,7 +2176,7 @@ class InterfaceFilterSet(
         distinct=False,
         distinct=False,
         label=_('LAG interface (ID)'),
         label=_('LAG interface (ID)'),
     )
     )
-    speed = MultiValueNumberFilter()
+    speed = MultiValueBigNumberFilter(min_value=0)
     duplex = django_filters.MultipleChoiceFilter(
     duplex = django_filters.MultipleChoiceFilter(
         choices=InterfaceDuplexChoices,
         choices=InterfaceDuplexChoices,
         distinct=False,
         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 tenancy.models import Tenant
 from users.models import User
 from users.models import User
 from utilities.forms import BulkEditForm, add_blank_choice, form_from_model
 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.rendering import FieldSet, InlineFields, TabbedGroups
 from utilities.forms.widgets import BulkEditNullBooleanSelect, NumberWithOptions
 from utilities.forms.widgets import BulkEditNullBooleanSelect, NumberWithOptions
 from virtualization.models import Cluster
 from virtualization.models import Cluster
@@ -1420,7 +1426,7 @@ class InterfaceBulkEditForm(
             'device_id': '$device',
             'device_id': '$device',
         }
         }
     )
     )
-    speed = forms.IntegerField(
+    speed = PositiveBigIntegerField(
         label=_('Speed'),
         label=_('Speed'),
         required=False,
         required=False,
         widget=NumberWithOptions(
         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 tenancy.models import Tenant
 from users.models import User
 from users.models import User
 from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice
 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.rendering import FieldSet
 from utilities.forms.widgets import NumberWithOptions
 from utilities.forms.widgets import NumberWithOptions
 from virtualization.models import Cluster, ClusterGroup, VirtualMachine
 from virtualization.models import Cluster, ClusterGroup, VirtualMachine
@@ -1603,7 +1603,7 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
         choices=InterfaceTypeChoices,
         choices=InterfaceTypeChoices,
         required=False
         required=False
     )
     )
-    speed = forms.IntegerField(
+    speed = PositiveBigIntegerField(
         label=_('Speed'),
         label=_('Speed'),
         required=False,
         required=False,
         widget=NumberWithOptions(
         widget=NumberWithOptions(

+ 8 - 2
netbox/dcim/graphql/filters.py

@@ -47,7 +47,13 @@ if TYPE_CHECKING:
         VRFFilter,
         VRFFilter,
     )
     )
     from netbox.graphql.enums import ColorEnum
     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 users.graphql.filters import UserFilter
     from virtualization.graphql.filters import ClusterFilter
     from virtualization.graphql.filters import ClusterFilter
     from vpn.graphql.filters import L2VPNFilter, TunnelTerminationFilter
     from vpn.graphql.filters import L2VPNFilter, TunnelTerminationFilter
@@ -519,7 +525,7 @@ class InterfaceFilter(
         strawberry_django.filter_field()
         strawberry_django.filter_field()
     )
     )
     mgmt_only: FilterLookup[bool] | None = 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()
         strawberry_django.filter_field()
     )
     )
     duplex: BaseFilterLookup[Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (
     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):
 class InterfaceType(IPAddressesMixin, ModularComponentType, CabledObjectMixin, PathEndpointMixin):
     _name: str
     _name: str
+    speed: BigInt | None
     wwn: str | None
     wwn: str | None
     parent: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None
     parent: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None
     bridge: 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'),
         verbose_name=_('management only'),
         help_text=_('This interface is used only for out-of-band management')
         help_text=_('This interface is used only for out-of-band management')
     )
     )
-    speed = models.PositiveIntegerField(
+    speed = models.PositiveBigIntegerField(
         blank=True,
         blank=True,
         null=True,
         null=True,
         verbose_name=_('speed (Kbps)')
         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,
                 'device': device.pk,
                 'name': 'Interface 4',
                 'name': 'Interface 4',
-                'type': '1000base-t',
+                'type': 'other',
                 'mode': InterfaceModeChoices.MODE_TAGGED,
                 'mode': InterfaceModeChoices.MODE_TAGGED,
-                'speed': 1000000,
+                'speed': 16_000_000_000,
                 'duplex': 'full',
                 'duplex': 'full',
                 'vrf': vrfs[0].pk,
                 'vrf': vrfs[0].pk,
                 'poe_mode': InterfacePoEModeChoices.MODE_PD,
                 'poe_mode': InterfacePoEModeChoices.MODE_PD,

+ 2 - 2
netbox/dcim/tests/test_filtersets.py

@@ -4655,7 +4655,7 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
                 enabled=True,
                 enabled=True,
                 mgmt_only=True,
                 mgmt_only=True,
                 tx_power=40,
                 tx_power=40,
-                speed=100000,
+                speed=16_000_000_000,
                 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,
@@ -4757,7 +4757,7 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
     def test_speed(self):
     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)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
 
     def test_duplex(self):
     def test_duplex(self):

+ 4 - 4
netbox/dcim/tests/test_views.py

@@ -2961,13 +2961,13 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
         cls.form_data = {
         cls.form_data = {
             'device': device.pk,
             'device': device.pk,
             'name': 'Interface X',
             'name': 'Interface X',
-            'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
+            'type': InterfaceTypeChoices.TYPE_OTHER,
             'enabled': False,
             'enabled': False,
             'bridge': interfaces[4].pk,
             'bridge': interfaces[4].pk,
             'lag': interfaces[3].pk,
             'lag': interfaces[3].pk,
             'wwn': EUI('01:02:03:04:05:06:07:08', version=64),
             'wwn': EUI('01:02:03:04:05:06:07:08', version=64),
             'mtu': 65000,
             'mtu': 65000,
-            'speed': 1000000,
+            'speed': 16_000_000_000,
             'duplex': 'full',
             'duplex': 'full',
             'mgmt_only': True,
             'mgmt_only': True,
             'description': 'A front port',
             'description': 'A front port',
@@ -2985,13 +2985,13 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
         cls.bulk_create_data = {
         cls.bulk_create_data = {
             'device': device.pk,
             'device': device.pk,
             'name': 'Interface [4-6]',
             'name': 'Interface [4-6]',
-            'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
+            'type': InterfaceTypeChoices.TYPE_OTHER,
             'enabled': False,
             'enabled': False,
             'bridge': interfaces[4].pk,
             'bridge': interfaces[4].pk,
             'lag': interfaces[3].pk,
             'lag': interfaces[3].pk,
             'wwn': EUI('01:02:03:04:05:06:07:08', version=64),
             'wwn': EUI('01:02:03:04:05:06:07:08', version=64),
             'mtu': 2000,
             'mtu': 2000,
-            'speed': 100000,
+            'speed': 16_000_000_000,
             'duplex': 'half',
             'duplex': 'half',
             'mgmt_only': True,
             'mgmt_only': True,
             'description': 'A front port',
             '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.types import OpenApiTypes
 from drf_spectacular.utils import extend_schema_field
 from drf_spectacular.utils import extend_schema_field
 
 
+from .forms.fields import BigIntegerField
+
 __all__ = (
 __all__ = (
     'ContentTypeFilter',
     'ContentTypeFilter',
     'MultiValueArrayFilter',
     'MultiValueArrayFilter',
+    'MultiValueBigNumberFilter',
     'MultiValueCharFilter',
     'MultiValueCharFilter',
     'MultiValueContentTypeFilter',
     'MultiValueContentTypeFilter',
     'MultiValueDateFilter',
     'MultiValueDateFilter',
@@ -77,6 +80,11 @@ class MultiValueNumberFilter(django_filters.MultipleChoiceFilter):
     field_class = multivalue_field_factory(forms.IntegerField)
     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)
 @extend_schema_field(OpenApiTypes.DECIMAL)
 class MultiValueDecimalFilter(django_filters.MultipleChoiceFilter):
 class MultiValueDecimalFilter(django_filters.MultipleChoiceFilter):
     field_class = multivalue_field_factory(forms.DecimalField)
     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 import forms
 from django.conf import settings
 from django.conf import settings
+from django.db.models import BigIntegerField as BigIntegerModelField
 from django.db.models import Count
 from django.db.models import Count
 from django.forms.fields import InvalidJSONInput
 from django.forms.fields import InvalidJSONInput
 from django.forms.fields import JSONField as _JSONField
 from django.forms.fields import JSONField as _JSONField
@@ -13,17 +14,39 @@ from utilities.forms import widgets
 from utilities.validators import EnhancedURLValidator
 from utilities.validators import EnhancedURLValidator
 
 
 __all__ = (
 __all__ = (
+    'BigIntegerField',
     'ColorField',
     'ColorField',
     'CommentField',
     'CommentField',
     'JSONField',
     'JSONField',
     'LaxURLField',
     'LaxURLField',
     'MACAddressField',
     'MACAddressField',
+    'PositiveBigIntegerField',
     'QueryField',
     'QueryField',
     'SlugField',
     'SlugField',
     'TagFilterField',
     '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):
 class QueryField(forms.CharField):
     """
     """
     A CharField subclass used for global search/query fields in filter forms.
     A CharField subclass used for global search/query fields in filter forms.