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

Merge pull request #8420 from netbox-community/7853-speed_duplex

Fixes #7853 - Add speed and duplex
Jeremy Stretch 4 лет назад
Родитель
Сommit
3d6c2c5fef

+ 2 - 1
netbox/dcim/api/serializers.py

@@ -721,6 +721,7 @@ class InterfaceSerializer(PrimaryModelSerializer, LinkTerminationSerializer, Con
     bridge = NestedInterfaceSerializer(required=False, allow_null=True)
     lag = NestedInterfaceSerializer(required=False, allow_null=True)
     mode = ChoiceField(choices=InterfaceModeChoices, allow_blank=True, required=False)
+    duplex = ChoiceField(choices=InterfaceDuplexChoices, allow_blank=True, required=False)
     rf_role = ChoiceField(choices=WirelessRoleChoices, required=False, allow_null=True)
     rf_channel = ChoiceField(choices=WirelessChannelChoices, required=False)
     untagged_vlan = NestedVLANSerializer(required=False, allow_null=True)
@@ -746,7 +747,7 @@ class InterfaceSerializer(PrimaryModelSerializer, LinkTerminationSerializer, Con
         model = Interface
         fields = [
             'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag',
-            'mtu', 'mac_address', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel',
+            'mtu', 'mac_address', 'speed', 'duplex', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel',
             'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'mark_connected',
             'cable', 'wireless_link', 'link_peer', 'link_peer_type', 'wireless_lans', 'vrf', 'connected_endpoint',
             'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created',

+ 13 - 0
netbox/dcim/choices.py

@@ -951,6 +951,19 @@ class InterfaceTypeChoices(ChoiceSet):
     )
 
 
+class InterfaceDuplexChoices(ChoiceSet):
+
+    DUPLEX_HALF = 'half'
+    DUPLEX_FULL = 'full'
+    DUPLEX_AUTO = 'auto'
+
+    CHOICES = (
+        (DUPLEX_HALF, 'Half'),
+        (DUPLEX_FULL, 'Full'),
+        (DUPLEX_AUTO, 'Auto'),
+    )
+
+
 class InterfaceModeChoices(ChoiceSet):
 
     MODE_ACCESS = 'access'

+ 4 - 0
netbox/dcim/filtersets.py

@@ -1196,6 +1196,10 @@ class InterfaceFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableT
         queryset=Interface.objects.all(),
         label='LAG interface (ID)',
     )
+    speed = MultiValueNumberFilter()
+    duplex = django_filters.MultipleChoiceFilter(
+        choices=InterfaceDuplexChoices
+    )
     mac_address = MultiValueMACAddressFilter()
     wwn = MultiValueWWNFilter()
     tag = TagFilter()

+ 2 - 2
netbox/dcim/forms/bulk_create.py

@@ -72,12 +72,12 @@ class PowerOutletBulkCreateForm(
 
 
 class InterfaceBulkCreateForm(
-    form_from_model(Interface, ['type', 'enabled', 'mtu', 'mgmt_only', 'mark_connected']),
+    form_from_model(Interface, ['type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'mark_connected']),
     DeviceBulkAddComponentForm
 ):
     model = Interface
     field_order = (
-        'name_pattern', 'label_pattern', 'type', 'enabled', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'tags',
+        'name_pattern', 'label_pattern', 'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'tags',
     )
 
 

+ 8 - 3
netbox/dcim/forms/bulk_edit.py

@@ -11,7 +11,7 @@ from ipam.models import ASN, VLAN, VRF
 from tenancy.models import Tenant
 from utilities.forms import (
     add_blank_choice, BulkEditForm, BulkEditNullBooleanSelect, ColorField, CommentField, DynamicModelChoiceField,
-    DynamicModelMultipleChoiceField, form_from_model, SmallTextarea, StaticSelect,
+    DynamicModelMultipleChoiceField, form_from_model, SmallTextarea, StaticSelect, SelectSpeedWidget,
 )
 
 __all__ = (
@@ -1028,7 +1028,7 @@ class PowerOutletBulkEditForm(
 
 class InterfaceBulkEditForm(
     form_from_model(Interface, [
-        'label', 'type', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected',
+        'label', 'type', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected',
         'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
     ]),
     AddRemoveTagsForm,
@@ -1064,6 +1064,11 @@ class InterfaceBulkEditForm(
         },
         label='LAG'
     )
+    speed = forms.IntegerField(
+        required=False,
+        widget=SelectSpeedWidget(attrs={'readonly': None}),
+        label='Speed'
+    )
     mgmt_only = forms.NullBooleanField(
         required=False,
         widget=BulkEditNullBooleanSelect,
@@ -1089,7 +1094,7 @@ class InterfaceBulkEditForm(
 
     class Meta:
         nullable_fields = [
-            'label', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu', 'description', 'mode', 'rf_channel',
+            'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'description', 'mode', 'rf_channel',
             'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'vrf',
         ]
 

+ 6 - 1
netbox/dcim/forms/bulk_import.py

@@ -618,6 +618,11 @@ class InterfaceCSVForm(CustomFieldModelCSVForm):
         choices=InterfaceTypeChoices,
         help_text='Physical medium'
     )
+    duplex = CSVChoiceField(
+        choices=InterfaceDuplexChoices,
+        required=False,
+        help_text='Duplex'
+    )
     mode = CSVChoiceField(
         choices=InterfaceModeChoices,
         required=False,
@@ -638,7 +643,7 @@ class InterfaceCSVForm(CustomFieldModelCSVForm):
     class Meta:
         model = Interface
         fields = (
-            'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'enabled', 'mark_connected', 'mac_address',
+            'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'speed', 'duplex', 'enabled', 'mark_connected', 'mac_address',
             'wwn', 'mtu', 'mgmt_only', 'description', 'mode', 'vrf', 'rf_role', 'rf_channel', 'rf_channel_frequency',
             'rf_channel_width', 'tx_power',
         )

+ 13 - 2
netbox/dcim/forms/filtersets.py

@@ -10,7 +10,7 @@ from ipam.models import ASN, VRF
 from tenancy.forms import TenancyFilterForm
 from utilities.forms import (
     APISelectMultiple, add_blank_choice, ColorField, DynamicModelMultipleChoiceField, FilterForm, StaticSelect,
-    StaticSelectMultiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
+    StaticSelectMultiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, SelectSpeedWidget,
 )
 from wireless.choices import *
 
@@ -927,7 +927,7 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
     model = Interface
     field_groups = [
         ['q', 'tag'],
-        ['name', 'label', 'kind', 'type', 'enabled', 'mgmt_only'],
+        ['name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only'],
         ['vrf_id', 'mac_address', 'wwn'],
         ['rf_role', 'rf_channel', 'rf_channel_width', 'tx_power'],
         ['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
@@ -942,6 +942,17 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
         required=False,
         widget=StaticSelectMultiple()
     )
+    speed = forms.IntegerField(
+        required=False,
+        label='Select Speed',
+        widget=SelectSpeedWidget(attrs={'readonly': None})
+    )
+    duplex = forms.MultipleChoiceField(
+        choices=InterfaceDuplexChoices,
+        required=False,
+        label='Select Duplex',
+        widget=StaticSelectMultiple()
+    )
     enabled = forms.NullBooleanField(
         required=False,
         widget=StaticSelect(

+ 5 - 3
netbox/dcim/forms/models.py

@@ -14,7 +14,7 @@ from tenancy.forms import TenancyForm
 from utilities.forms import (
     APISelect, add_blank_choice, BootstrapMixin, ClearableFileInput, CommentField, ContentTypeChoiceField,
     DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SelectWithPK, SmallTextarea,
-    SlugField, StaticSelect,
+    SlugField, StaticSelect, SelectSpeedWidget,
 )
 from virtualization.models import Cluster, ClusterGroup
 from wireless.models import WirelessLAN, WirelessLANGroup
@@ -1274,12 +1274,12 @@ class InterfaceForm(InterfaceCommonForm, CustomFieldModelForm):
     class Meta:
         model = Interface
         fields = [
-            'device', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu',
+            'device', 'name', 'label', 'type', 'speed', 'duplex', 'enabled', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu',
             'mgmt_only', 'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency',
             'rf_channel_width', 'tx_power', 'wireless_lans', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
         ]
         fieldsets = (
-            ('Interface', ('device', 'name', 'type', 'label', 'description', 'tags')),
+            ('Interface', ('device', 'name', 'type', 'speed', 'duplex', 'label', 'description', 'tags')),
             ('Addressing', ('vrf', 'mac_address', 'wwn')),
             ('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
             ('Related Interfaces', ('parent', 'bridge', 'lag')),
@@ -1292,6 +1292,8 @@ class InterfaceForm(InterfaceCommonForm, CustomFieldModelForm):
         widgets = {
             'device': forms.HiddenInput(),
             'type': StaticSelect(),
+            'speed': SelectSpeedWidget(),
+            'duplex': StaticSelect(),
             'mode': StaticSelect(),
             'rf_role': StaticSelect(),
             'rf_channel': StaticSelect(),

+ 23 - 0
netbox/dcim/migrations/0150_interface_speed_duplex.py

@@ -0,0 +1,23 @@
+# Generated by Django 3.2.10 on 2022-01-08 18:23
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dcim', '0149_interface_vrf'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='interface',
+            name='duplex',
+            field=models.CharField(blank=True, max_length=50, null=True),
+        ),
+        migrations.AddField(
+            model_name='interface',
+            name='speed',
+            field=models.PositiveIntegerField(blank=True, null=True),
+        ),
+    ]

+ 12 - 0
netbox/dcim/models/device_components.py

@@ -545,6 +545,18 @@ class Interface(ModularComponentModel, BaseInterface, LinkTermination, PathEndpo
         verbose_name='Management only',
         help_text='This interface is used only for out-of-band management'
     )
+    speed = models.PositiveIntegerField(
+        verbose_name='Speed',
+        blank=True,
+        null=True
+    )
+    duplex = models.CharField(
+        verbose_name='Duplex',
+        max_length=50,
+        blank=True,
+        null=True,
+        choices=InterfaceDuplexChoices
+    )
     wwn = WWNField(
         null=True,
         blank=True,

+ 4 - 4
netbox/dcim/tables/devices.py

@@ -524,10 +524,10 @@ class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpoi
         model = Interface
         fields = (
             'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'enabled', 'type', 'mgmt_only', 'mtu',
-            'mode', 'mac_address', 'wwn', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width',
-            'tx_power', 'description', 'mark_connected', 'cable', 'cable_color', 'wireless_link', 'wireless_lans',
-            'link_peer', 'connection', 'tags', 'vrf', 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans',
-            'created', 'last_updated',
+            'speed', 'duplex', 'mode', 'mac_address', 'wwn', 'rf_role', 'rf_channel', 'rf_channel_frequency',
+            'rf_channel_width', 'tx_power', 'description', 'mark_connected', 'cable', 'cable_color', 'wireless_link',
+            'wireless_lans', 'link_peer', 'connection', 'tags', 'vrf', 'ip_addresses', 'fhrp_groups', 'untagged_vlan',
+            'tagged_vlans', 'created', 'last_updated',
         )
         default_columns = ('pk', 'name', 'device', 'label', 'enabled', 'type', 'description')
 

+ 4 - 0
netbox/dcim/tests/test_api.py

@@ -1442,6 +1442,8 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
                 'tagged_vlans': [vlans[0].pk, vlans[1].pk],
                 'untagged_vlan': vlans[2].pk,
                 'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk],
+                'speed': 1000000,
+                'duplex': 'full'
             },
             {
                 'device': device.pk,
@@ -1454,6 +1456,8 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
                 'tagged_vlans': [vlans[0].pk, vlans[1].pk],
                 'untagged_vlan': vlans[2].pk,
                 'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk],
+                'speed': 100000,
+                'duplex': 'half'
             },
             {
                 'device': device.pk,

+ 12 - 4
netbox/dcim/tests/test_filtersets.py

@@ -2383,10 +2383,10 @@ class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
         Device.objects.filter(pk=devices[1].pk).update(virtual_chassis=virtual_chassis, vc_position=2, vc_priority=2)
 
         interfaces = (
-            Interface(device=devices[0], name='Interface 1', label='A', type=InterfaceTypeChoices.TYPE_1GE_SFP, enabled=True, mgmt_only=True, mtu=100, mode=InterfaceModeChoices.MODE_ACCESS, mac_address='00-00-00-00-00-01', description='First', vrf=vrfs[0]),
-            Interface(device=devices[1], name='Interface 2', label='B', type=InterfaceTypeChoices.TYPE_1GE_GBIC, enabled=True, mgmt_only=True, mtu=200, mode=InterfaceModeChoices.MODE_TAGGED, mac_address='00-00-00-00-00-02', description='Second', vrf=vrfs[1]),
-            Interface(device=devices[2], name='Interface 3', label='C', type=InterfaceTypeChoices.TYPE_1GE_FIXED, enabled=False, mgmt_only=False, mtu=300, mode=InterfaceModeChoices.MODE_TAGGED_ALL, mac_address='00-00-00-00-00-03', description='Third', vrf=vrfs[2]),
-            Interface(device=devices[3], name='Interface 4', label='D', type=InterfaceTypeChoices.TYPE_OTHER, enabled=True, mgmt_only=True, tx_power=40),
+            Interface(device=devices[0], name='Interface 1', label='A', type=InterfaceTypeChoices.TYPE_1GE_SFP, enabled=True, mgmt_only=True, mtu=100, mode=InterfaceModeChoices.MODE_ACCESS, mac_address='00-00-00-00-00-01', description='First', vrf=vrfs[0], speed=1000000, duplex='half'),
+            Interface(device=devices[1], name='Interface 2', label='B', type=InterfaceTypeChoices.TYPE_1GE_GBIC, enabled=True, mgmt_only=True, mtu=200, mode=InterfaceModeChoices.MODE_TAGGED, mac_address='00-00-00-00-00-02', description='Second', vrf=vrfs[1], speed=1000000, duplex='full'),
+            Interface(device=devices[2], name='Interface 3', label='C', type=InterfaceTypeChoices.TYPE_1GE_FIXED, enabled=False, mgmt_only=False, mtu=300, mode=InterfaceModeChoices.MODE_TAGGED_ALL, mac_address='00-00-00-00-00-03', description='Third', vrf=vrfs[2], speed=100000, duplex='half'),
+            Interface(device=devices[3], name='Interface 4', label='D', type=InterfaceTypeChoices.TYPE_OTHER, enabled=True, mgmt_only=True, tx_power=40, speed=100000, duplex='full'),
             Interface(device=devices[3], name='Interface 5', label='E', type=InterfaceTypeChoices.TYPE_OTHER, enabled=True, mgmt_only=True, tx_power=40),
             Interface(device=devices[3], name='Interface 6', label='F', type=InterfaceTypeChoices.TYPE_OTHER, enabled=False, mgmt_only=False, tx_power=40),
             Interface(device=devices[3], name='Interface 7', type=InterfaceTypeChoices.TYPE_80211AC, rf_role=WirelessRoleChoices.ROLE_AP, rf_channel=WirelessChannelChoices.CHANNEL_24G_1, rf_channel_frequency=2412, rf_channel_width=22),
@@ -2423,6 +2423,14 @@ class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
         params = {'mtu': [100, 200]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
+    def test_speed(self):
+        params = {'speed': [1000000, 100000]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+    def test_duplex(self):
+        params = {'duplex': ['half', 'full']}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
     def test_mgmt_only(self):
         params = {'mgmt_only': 'true'}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)

+ 6 - 0
netbox/dcim/tests/test_views.py

@@ -2124,6 +2124,8 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
             'mac_address': EUI('01:02:03:04:05:06'),
             'wwn': EUI('01:02:03:04:05:06:07:08', version=64),
             'mtu': 65000,
+            'speed': 1000000,
+            'duplex': 'full',
             'mgmt_only': True,
             'description': 'A front port',
             'mode': InterfaceModeChoices.MODE_TAGGED,
@@ -2145,6 +2147,8 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
             'mac_address': EUI('01:02:03:04:05:06'),
             'wwn': EUI('01:02:03:04:05:06:07:08', version=64),
             'mtu': 2000,
+            'speed': 100000,
+            'duplex': 'half',
             'mgmt_only': True,
             'description': 'A front port',
             'mode': InterfaceModeChoices.MODE_TAGGED,
@@ -2162,6 +2166,8 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
             'mac_address': EUI('01:02:03:04:05:06'),
             'wwn': EUI('01:02:03:04:05:06:07:08', version=64),
             'mtu': 2000,
+            'speed': 1000000,
+            'duplex': 'full',
             'mgmt_only': True,
             'description': 'New description',
             'mode': InterfaceModeChoices.MODE_TAGGED,

+ 8 - 0
netbox/templates/dcim/interface.html

@@ -46,6 +46,14 @@
                             <th scope="row">Type</th>
                             <td>{{ object.get_type_display }}</td>
                         </tr>
+                        <tr>
+                            <th scope="row">Speed</th>
+                            <td>{{ object.speed|humanize_speed|placeholder }}</td>
+                        </tr>
+                        <tr>
+                            <th scope="row">Duplex</th>
+                            <td>{{ object.get_duplex_display }}</td>
+                        </tr>
                         <tr>
                             <th scope="row">Enabled</th>
                             <td>{% checkmark object.enabled %}</td>

+ 2 - 0
netbox/templates/dcim/interface_edit.html

@@ -16,6 +16,8 @@
         {% endif %}
         {% render_field form.name %}
         {% render_field form.type %}
+        {% render_field form.speed %}
+        {% render_field form.duplex %}
         {% render_field form.label %}
         {% render_field form.description %}
         {% render_field form.tags %}