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

Fixes #7853 - Add speed and duplex
This commit is contained in:
Jeremy Stretch 2022-01-21 13:07:23 -05:00 committed by GitHub
commit 3d6c2c5fef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 124 additions and 20 deletions

View File

@ -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',

View File

@ -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'

View File

@ -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()

View File

@ -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',
)

View File

@ -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',
]

View File

@ -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',
)

View File

@ -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(

View File

@ -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(),

View File

@ -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),
),
]

View File

@ -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,

View File

@ -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')

View File

@ -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,

View File

@ -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)

View File

@ -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,

View File

@ -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>

View File

@ -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 %}