diff --git a/docs/models/dcim/interface.md b/docs/models/dcim/interface.md index 7fa52fa9f..e3237c2ee 100644 --- a/docs/models/dcim/interface.md +++ b/docs/models/dcim/interface.md @@ -11,6 +11,10 @@ Interfaces may be physical or virtual in nature, but only physical interfaces ma Physical interfaces may be arranged into a link aggregation group (LAG) and associated with a parent LAG (virtual) interface. LAG interfaces can be recursively nested to model bonding of trunk groups. Like all virtual interfaces, LAG interfaces cannot be connected physically. +### Power over Ethernet (PoE) + +Physical interfaces can be assigned a PoE mode to indicate PoE capability: power supplying equipment (PSE) or powered device (PD). Additionally, a PoE mode may be specified. This can be one of the listed IEEE 802.3 standards, or a passive setting (24 or 48 volts across two or four pairs). + ### Wireless Interfaces Wireless interfaces may additionally track the following attributes: diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index fc8c24f4c..801e45b51 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -11,6 +11,8 @@ #### Half-Height Rack Units ([#51](https://github.com/netbox-community/netbox/issues/51)) +#### PoE Interface Attributes ([#1099](https://github.com/netbox-community/netbox/issues/1099)) + ### Enhancements * [#1202](https://github.com/netbox-community/netbox/issues/1202) - Support overlapping assignment of NAT IP addresses @@ -33,6 +35,8 @@ * The `position` field has been changed from an integer to a decimal * dcim.DeviceType * The `u_height` field has been changed from an integer to a decimal +* dcim.Interface + * Added the option `poe_mode` and `poe_type` fields * dcim.Rack * The `elevation` endpoint now includes half-height rack units, and utilizes decimal values for the ID and name of each unit * extras.CustomField diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 401c9a901..f3d223d4c 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -812,6 +812,8 @@ class InterfaceSerializer(NetBoxModelSerializer, LinkTerminationSerializer, Conn duplex = ChoiceField(choices=InterfaceDuplexChoices, required=False, allow_blank=True) rf_role = ChoiceField(choices=WirelessRoleChoices, required=False, allow_blank=True) rf_channel = ChoiceField(choices=WirelessChannelChoices, required=False, allow_blank=True) + poe_mode = ChoiceField(choices=InterfacePoEModeChoices, required=False, allow_blank=True) + poe_type = ChoiceField(choices=InterfacePoETypeChoices, required=False, allow_blank=True) untagged_vlan = NestedVLANSerializer(required=False, allow_null=True) tagged_vlans = SerializedPKRelatedField( queryset=VLAN.objects.all(), @@ -836,10 +838,10 @@ class InterfaceSerializer(NetBoxModelSerializer, LinkTerminationSerializer, Conn fields = [ 'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag', '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', - 'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied', + 'poe_mode', 'poe_type', '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', 'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied', ] def validate(self, data): diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 2e96f9c67..44ec3fb88 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1003,6 +1003,51 @@ class InterfaceModeChoices(ChoiceSet): ) +class InterfacePoEModeChoices(ChoiceSet): + + MODE_PD = 'pd' + MODE_PSE = 'pse' + + CHOICES = ( + (MODE_PD, 'Powered device (PD)'), + (MODE_PSE, 'Power sourcing equipment (PSE)'), + ) + + +class InterfacePoETypeChoices(ChoiceSet): + + TYPE_1_8023AF = 'type1-ieee802.3af' + TYPE_2_8023AT = 'type2-ieee802.3at' + TYPE_3_8023BT = 'type3-ieee802.3bt' + TYPE_4_8023BT = 'type4-ieee802.3bt' + + PASSIVE_24V_2PAIR = 'passive-24v-2pair' + PASSIVE_24V_4PAIR = 'passive-24v-4pair' + PASSIVE_48V_2PAIR = 'passive-48v-2pair' + PASSIVE_48V_4PAIR = 'passive-48v-4pair' + + CHOICES = ( + ( + 'IEEE Standard', + ( + (TYPE_1_8023AF, '802.3af (Type 1)'), + (TYPE_2_8023AT, '802.3at (Type 2)'), + (TYPE_3_8023BT, '802.3bt (Type 3)'), + (TYPE_4_8023BT, '802.3bt (Type 4)'), + ) + ), + ( + 'Passive', + ( + (PASSIVE_24V_2PAIR, 'Passive 24V (2-pair)'), + (PASSIVE_24V_4PAIR, 'Passive 24V (4-pair)'), + (PASSIVE_48V_2PAIR, 'Passive 48V (2-pair)'), + (PASSIVE_48V_2PAIR, 'Passive 48V (4-pair)'), + ) + ), + ) + + # # FrontPorts/RearPorts # diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index f052a8be9..7c2d02bb3 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1238,6 +1238,12 @@ class InterfaceFilterSet( ) mac_address = MultiValueMACAddressFilter() wwn = MultiValueWWNFilter() + poe_mode = django_filters.MultipleChoiceFilter( + choices=InterfacePoEModeChoices + ) + poe_type = django_filters.MultipleChoiceFilter( + choices=InterfacePoETypeChoices + ) vlan_id = django_filters.CharFilter( method='filter_vlan_id', label='Assigned VLAN' @@ -1271,8 +1277,8 @@ class InterfaceFilterSet( class Meta: model = Interface fields = [ - 'id', 'name', 'label', 'type', 'enabled', 'mtu', 'mgmt_only', 'mode', 'rf_role', 'rf_channel', - 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'description', + 'id', 'name', 'label', 'type', 'enabled', 'mtu', 'mgmt_only', 'poe_mode', 'poe_type', 'mode', 'rf_role', + 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'description', ] def filter_device(self, queryset, name, value): diff --git a/netbox/dcim/forms/bulk_create.py b/netbox/dcim/forms/bulk_create.py index 314a7a75f..43b852928 100644 --- a/netbox/dcim/forms/bulk_create.py +++ b/netbox/dcim/forms/bulk_create.py @@ -72,12 +72,15 @@ class PowerOutletBulkCreateForm( class InterfaceBulkCreateForm( - form_from_model(Interface, ['type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'mark_connected']), + form_from_model(Interface, [ + 'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'mark_connected', 'poe_mode', 'poe_type', + ]), DeviceBulkAddComponentForm ): model = Interface field_order = ( - 'name_pattern', 'label_pattern', 'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'tags', + 'name_pattern', 'label_pattern', 'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'poe_mode', + 'poe_type', 'mark_connected', 'description', 'tags', ) diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index 231d01ddd..88f043c32 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -1063,6 +1063,18 @@ class InterfaceBulkEditForm( widget=BulkEditNullBooleanSelect, label='Management only' ) + poe_mode = forms.ChoiceField( + choices=add_blank_choice(InterfacePoEModeChoices), + required=False, + initial='', + widget=StaticSelect() + ) + poe_type = forms.ChoiceField( + choices=add_blank_choice(InterfacePoETypeChoices), + required=False, + initial='', + widget=StaticSelect() + ) mark_connected = forms.NullBooleanField( required=False, widget=BulkEditNullBooleanSelect @@ -1105,14 +1117,15 @@ class InterfaceBulkEditForm( (None, ('module', 'type', 'label', 'speed', 'duplex', 'description')), ('Addressing', ('vrf', 'mac_address', 'wwn')), ('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')), + ('PoE', ('poe_mode', 'poe_type')), ('Related Interfaces', ('parent', 'bridge', 'lag')), ('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')), ('Wireless', ('rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width')), ) nullable_fields = ( 'module', 'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'description', - 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'vlan_group', 'untagged_vlan', - 'tagged_vlans', 'vrf', + 'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', + 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vrf', ) def __init__(self, *args, **kwargs): diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index b28c16fad..292f58785 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -622,6 +622,16 @@ class InterfaceCSVForm(NetBoxModelCSVForm): choices=InterfaceDuplexChoices, required=False ) + poe_mode = CSVChoiceField( + choices=InterfacePoEModeChoices, + required=False, + help_text='PoE mode' + ) + poe_type = CSVChoiceField( + choices=InterfacePoETypeChoices, + required=False, + help_text='PoE type' + ) mode = CSVChoiceField( choices=InterfaceModeChoices, required=False, @@ -642,9 +652,9 @@ class InterfaceCSVForm(NetBoxModelCSVForm): class Meta: model = Interface fields = ( - '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', + 'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'speed', 'duplex', 'enabled', + 'mark_connected', 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'description', 'poe_mode', 'poe_type', 'mode', + 'vrf', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', ) def __init__(self, data=None, *args, **kwargs): diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 1535e5718..bdef32ec3 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -969,6 +969,7 @@ class InterfaceFilterForm(DeviceComponentFilterForm): (None, ('q', 'tag')), ('Attributes', ('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only')), ('Addressing', ('vrf_id', 'mac_address', 'wwn')), + ('PoE', ('poe_mode', 'poe_type')), ('Wireless', ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')), ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')), ) @@ -1009,6 +1010,14 @@ class InterfaceFilterForm(DeviceComponentFilterForm): required=False, label='WWN' ) + poe_mode = MultipleChoiceField( + choices=InterfacePoEModeChoices, + required=False + ) + poe_type = MultipleChoiceField( + choices=InterfacePoEModeChoices, + required=False + ) rf_role = MultipleChoiceField( choices=WirelessRoleChoices, required=False, diff --git a/netbox/dcim/forms/models.py b/netbox/dcim/forms/models.py index fe461b061..c58500198 100644 --- a/netbox/dcim/forms/models.py +++ b/netbox/dcim/forms/models.py @@ -1314,6 +1314,7 @@ class InterfaceForm(InterfaceCommonForm, NetBoxModelForm): ('Addressing', ('vrf', 'mac_address', 'wwn')), ('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')), ('Related Interfaces', ('parent', 'bridge', 'lag')), + ('PoE', ('poe_mode', 'poe_type')), ('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')), ('Wireless', ( 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans', @@ -1324,14 +1325,16 @@ class InterfaceForm(InterfaceCommonForm, NetBoxModelForm): model = Interface fields = [ 'device', 'module', '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', + 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'poe_mode', 'poe_type', 'mode', + 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'wireless_lans', + 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags', ] widgets = { 'device': forms.HiddenInput(), 'type': StaticSelect(), 'speed': SelectSpeedWidget(), + 'poe_mode': StaticSelect(), + 'poe_type': StaticSelect(), 'duplex': StaticSelect(), 'mode': StaticSelect(), 'rf_role': StaticSelect(), diff --git a/netbox/dcim/graphql/types.py b/netbox/dcim/graphql/types.py index d25a6bba6..17d6bc646 100644 --- a/netbox/dcim/graphql/types.py +++ b/netbox/dcim/graphql/types.py @@ -226,6 +226,12 @@ class InterfaceType(IPAddressesMixin, ComponentObjectType): exclude = ('_path',) filterset_class = filtersets.InterfaceFilterSet + def resolve_poe_mode(self, info): + return self.poe_mode or None + + def resolve_poe_type(self, info): + return self.poe_type or None + def resolve_mode(self, info): return self.mode or None diff --git a/netbox/dcim/migrations/0155_interface_poe_mode_type.py b/netbox/dcim/migrations/0155_interface_poe_mode_type.py new file mode 100644 index 000000000..0615d5d7e --- /dev/null +++ b/netbox/dcim/migrations/0155_interface_poe_mode_type.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0.5 on 2022-06-22 00:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0154_half_height_rack_units'), + ] + + operations = [ + migrations.AddField( + model_name='interface', + name='poe_mode', + field=models.CharField(blank=True, max_length=50), + ), + migrations.AddField( + model_name='interface', + name='poe_type', + field=models.CharField(blank=True, max_length=50), + ), + ] diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 9a0609c12..f49db08ab 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -590,6 +590,18 @@ class Interface(ModularComponentModel, BaseInterface, LinkTermination, PathEndpo validators=(MaxValueValidator(127),), verbose_name='Transmit power (dBm)' ) + poe_mode = models.CharField( + max_length=50, + choices=InterfacePoEModeChoices, + blank=True, + verbose_name='PoE mode' + ) + poe_type = models.CharField( + max_length=50, + choices=InterfacePoETypeChoices, + blank=True, + verbose_name='PoE type' + ) wireless_link = models.ForeignKey( to='wireless.WirelessLink', on_delete=models.SET_NULL, @@ -638,7 +650,7 @@ class Interface(ModularComponentModel, BaseInterface, LinkTermination, PathEndpo related_query_name='+' ) - clone_fields = ['device', 'parent', 'bridge', 'lag', 'type', 'mgmt_only'] + clone_fields = ['device', 'parent', 'bridge', 'lag', 'type', 'mgmt_only', 'poe_mode', 'poe_type'] class Meta: ordering = ('device', CollateAsChar('_name')) @@ -726,6 +738,24 @@ class Interface(ModularComponentModel, BaseInterface, LinkTermination, PathEndpo f"of virtual chassis {self.device.virtual_chassis}." }) + # PoE validation + + # Only physical interfaces may have a PoE mode/type assigned + if self.poe_mode and self.is_virtual: + raise ValidationError({ + 'poe_mode': "Virtual interfaces cannot have a PoE mode." + }) + if self.poe_type and self.is_virtual: + raise ValidationError({ + 'poe_type': "Virtual interfaces cannot have a PoE type." + }) + + # An interface with a PoE type set must also specify a mode + if self.poe_type and not self.poe_mode: + raise ValidationError({ + 'poe_type': "Must specify PoE mode when designating a PoE type." + }) + # Wireless validation # RF role & channel may only be set for wireless interfaces diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 0f015b7f3..b3dd700cb 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -520,10 +520,10 @@ class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpoi model = Interface fields = ( 'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', - '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', + 'speed', 'duplex', 'mode', 'mac_address', 'wwn', 'poe_mode', 'poe_type', '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') diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index a6631208b..a61b44f91 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -1507,6 +1507,8 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase 'speed': 1000000, 'duplex': 'full', 'vrf': vrfs[0].pk, + 'poe_mode': InterfacePoEModeChoices.MODE_PD, + 'poe_type': InterfacePoETypeChoices.TYPE_1_8023AF, 'tagged_vlans': [vlans[0].pk, vlans[1].pk], 'untagged_vlan': vlans[2].pk, }, diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 273ee6570..f7d4c4e0a 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -2540,14 +2540,109 @@ 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], module=modules[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], module=modules[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], module=modules[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), - Interface(device=devices[3], name='Interface 8', type=InterfaceTypeChoices.TYPE_80211AC, rf_role=WirelessRoleChoices.ROLE_STATION, rf_channel=WirelessChannelChoices.CHANNEL_5G_32, rf_channel_frequency=5160, rf_channel_width=20), + Interface( + device=devices[0], + module=modules[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', + poe_mode=InterfacePoEModeChoices.MODE_PSE, + poe_type=InterfacePoETypeChoices.TYPE_1_8023AF + ), + Interface( + device=devices[1], + module=modules[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', + poe_mode=InterfacePoEModeChoices.MODE_PD, + poe_type=InterfacePoETypeChoices.TYPE_1_8023AF + ), + Interface( + device=devices[2], + module=modules[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', + poe_mode=InterfacePoEModeChoices.MODE_PSE, + poe_type=InterfacePoETypeChoices.TYPE_2_8023AT + ), + 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', + poe_mode=InterfacePoEModeChoices.MODE_PD, + poe_type=InterfacePoETypeChoices.TYPE_2_8023AT + ), + 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 + ), + Interface( + device=devices[3], + name='Interface 8', + type=InterfaceTypeChoices.TYPE_80211AC, + rf_role=WirelessRoleChoices.ROLE_STATION, + rf_channel=WirelessChannelChoices.CHANNEL_5G_32, + rf_channel_frequency=5160, + rf_channel_width=20 + ), ) Interface.objects.bulk_create(interfaces) @@ -2594,6 +2689,14 @@ class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests): params = {'mgmt_only': 'false'} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + def test_poe_mode(self): + params = {'poe_mode': [InterfacePoEModeChoices.MODE_PD, InterfacePoEModeChoices.MODE_PSE]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + + def test_poe_type(self): + params = {'poe_type': [InterfacePoETypeChoices.TYPE_1_8023AF, InterfacePoETypeChoices.TYPE_2_8023AT]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + def test_mode(self): params = {'mode': InterfaceModeChoices.MODE_ACCESS} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index e17f94682..9cce21a21 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -2204,6 +2204,8 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase): 'description': 'A front port', 'mode': InterfaceModeChoices.MODE_TAGGED, 'tx_power': 10, + 'poe_mode': InterfacePoEModeChoices.MODE_PSE, + 'poe_type': InterfacePoETypeChoices.TYPE_1_8023AF, 'untagged_vlan': vlans[0].pk, 'tagged_vlans': [v.pk for v in vlans[1:4]], 'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk], @@ -2225,6 +2227,8 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase): 'duplex': 'half', 'mgmt_only': True, 'description': 'A front port', + 'poe_mode': InterfacePoEModeChoices.MODE_PSE, + 'poe_type': InterfacePoETypeChoices.TYPE_1_8023AF, 'mode': InterfaceModeChoices.MODE_TAGGED, 'untagged_vlan': vlans[0].pk, 'tagged_vlans': [v.pk for v in vlans[1:4]], @@ -2244,6 +2248,8 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase): 'duplex': 'full', 'mgmt_only': True, 'description': 'New description', + 'poe_mode': InterfacePoEModeChoices.MODE_PD, + 'poe_type': InterfacePoETypeChoices.TYPE_2_8023AT, 'mode': InterfaceModeChoices.MODE_TAGGED, 'tx_power': 10, 'untagged_vlan': vlans[0].pk, @@ -2252,10 +2258,10 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase): } cls.csv_data = ( - f"device,name,type,vrf.pk", - f"Device 1,Interface 4,1000base-t,{vrfs[0].pk}", - f"Device 1,Interface 5,1000base-t,{vrfs[0].pk}", - f"Device 1,Interface 6,1000base-t,{vrfs[0].pk}", + f"device,name,type,vrf.pk,poe_mode,poe_type", + f"Device 1,Interface 4,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af", + f"Device 1,Interface 5,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af", + f"Device 1,Interface 6,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af", ) @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) diff --git a/netbox/templates/dcim/interface.html b/netbox/templates/dcim/interface.html index c4cb8b72f..e98750518 100644 --- a/netbox/templates/dcim/interface.html +++ b/netbox/templates/dcim/interface.html @@ -69,6 +69,14 @@