Merge "Add complete LLDP fields to Port.Ethernet.LLDPReceive per DMTF Redfish v1.12.0"

This commit is contained in:
Zuul
2025-11-18 23:02:41 +00:00
committed by Gerrit Code Review
5 changed files with 219 additions and 4 deletions

View File

@@ -0,0 +1,22 @@
---
features:
- |
Enhances the existing LLDPReceiveField class by adding new LLDP receive data
fields as specified in Redfish Port Schema v1.12.0. The existing class already
contained chassis_id and port_id fields, and this enhancement adds 9 additional
LLDP receive properties.
New LLDP receive fields added:
- chassis_id_subtype: IEEE 802 chassis ID subtype identification
- port_id_subtype: IEEE 802 port ID subtype with MAC address handling
- system_name: System name received from remote link partner
- system_description: System description from remote link partner
- system_capabilities: Network device capabilities mapping
- management_address_ipv4: IPv4 management address
- management_address_ipv6: IPv6 management address
- management_address_mac: MAC management address
- management_vlan_id: Management VLAN ID configuration (0-4095)
This enhancement provides LLDP receive information through
the EthernetField.lldp_receive property, enabling better network topology
discovery.

View File

@@ -137,3 +137,71 @@ class PortLinkStatus(enum.Enum):
TRAINING = 'Training'
"""This physical link on this interface is training."""
class IEEE802IdSubtype(enum.Enum):
"""IEEE 802.1AB ID Subtypes for Chassis ID and Port ID.
Based on IEEE 802.1AB-2009 and DMTF Redfish Port Schema v1.12.0
"""
CHASSIS_COMP = 'ChassisComp'
"""Chassis component, based on entPhysicalAlias in RFC4133."""
IF_ALIAS = 'IfAlias'
"""Interface alias, based on the ifAlias MIB object."""
PORT_COMP = 'PortComp'
"""Port component, based on entPhysicalAlias in RFC4133."""
MAC_ADDR = 'MacAddr'
"""MAC address, based on agent-detected unicast source (IEEE 802)."""
NETWORK_ADDR = 'NetworkAddr'
"""Network address, based on agent-detected network address."""
IF_NAME = 'IfName'
"""Interface name, based on the ifName MIB object."""
AGENT_ID = 'AgentId'
"""Agent circuit ID, based on agent-local identifier (RFC3046)."""
LOCAL_ASSIGN = 'LocalAssign'
"""Locally assigned, based on alphanumeric value."""
NOT_TRANSMITTED = 'NotTransmitted'
"""No data to be sent to/received from remote partner."""
class LLDPSystemCapabilities(enum.Enum):
"""LLDP System Capabilities.
Based on IEEE 802.1AB and DMTF Redfish Port Schema v1.12.0
"""
NONE = 'None'
"""System capabilities are transmitted, but no capabilities are set."""
BRIDGE = 'Bridge'
"""'bridge' capability."""
DOCSIS_CABLE_DEVICE = 'DOCSISCableDevice'
"""'DOCSIS cable device' capability."""
OTHER = 'Other'
"""'other' capability."""
REPEATER = 'Repeater'
"""'repeater' capability."""
ROUTER = 'Router'
"""'router' capability."""
STATION = 'Station'
"""'station' capability."""
TELEPHONE = 'Telephone'
"""'telephone' capability."""
WLAN_ACCESS_POINT = 'WLANAccessPoint'
"""'WLAN access point' capability."""

View File

@@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
# This is referred from Redfish standard schema.
# https://redfish.dmtf.org/schemas/v1/Port.v1_8_0.json
# https://redfish.dmtf.org/schemas/v1/Port.v1_12_0.json
from sushy.resources import base
from sushy.resources import common
@@ -18,12 +18,56 @@ from sushy.resources.system.network import constants
class LLDPReceiveField(base.CompositeField):
"""LLDP data being received on this link.
Based on DMTF Redfish Port Schema v1.12.0
https://redfish.dmtf.org/schemas/v1/Port.v1_12_0.json#/definitions/LLDPReceive
"""
chassis_id = base.Field("ChassisId")
"""chassis ID received from the remote partner across this link."""
chassis_id_subtype = base.MappedField("ChassisIdSubtype",
constants.IEEE802IdSubtype)
"""The type of identifier used for the chassis ID"""
port_id = base.Field("PortId")
"""A colon delimited string of hexadecimal octets identifying a port."""
port_id_subtype = base.MappedField("PortIdSubtype",
constants.IEEE802IdSubtype)
"""The port ID subtype received from the remote partner"""
# TLV Type 3 - Time To Live not in current schema
# TLV Type 4 - Port Description ,not in schema
# TLV Type 5 - System Name
system_name = base.Field("SystemName")
"""The system name received from the remote partner across this link."""
# TLV Type 6 - System Description
system_description = base.Field("SystemDescription")
"""The system description received from the remote partner."""
# TLV Type 7 - System Capabilities
system_capabilities = base.MappedListField(
"SystemCapabilities", constants.LLDPSystemCapabilities)
"""The system capabilities received from the remote partner."""
# TLV Type 8 - Management Addresses
management_address_ipv4 = base.Field("ManagementAddressIPv4")
"""The IPv4 management address received from the remote partner."""
management_address_ipv6 = base.Field("ManagementAddressIPv6")
"""The IPv6 management address received from the remote partner."""
management_address_mac = base.Field("ManagementAddressMAC")
"""The management MAC address received from the remote partner."""
management_vlan_id = base.Field("ManagementVlanId", adapter=int)
"""The management VLAN ID received from the remote partner (0-4095)."""
class EthernetField(base.CompositeField):
associated_mac_addresses = base.Field(

View File

@@ -13,10 +13,17 @@
"FlowControlConfiguration": "None",
"FlowControlStatus": "None",
"LLDPReceive": {
"ChassisId": "Not Available",
"ChassisIdSubtype": null,
"ChassisId": "c4:7e:e0:e4:55:3f",
"ChassisIdSubtype": "MacAddr",
"PortId": "0A:1B:2C:3D:4E:5F:6A:7B:8C:9D:0E:1F:2A",
"PortIdSubtype": null
"PortIdSubtype": "IfName",
"SystemName": "switch-00.example.com",
"SystemDescription": "Test Software, Version 00.00.00",
"SystemCapabilities": ["Bridge", "Router"],
"ManagementAddressIPv4": "192.168.1.1",
"ManagementAddressIPv6": "fe80::1",
"ManagementAddressMAC": "c4:7e:e0:e4:55:40",
"ManagementVlanId": 100
},
"WakeOnLANEnabled": false
},

View File

@@ -53,6 +53,80 @@ class PortTestCase(base.TestCase):
self.port.ethernet.flow_control_status)
self.assertEqual(net_cons.PortLinkStatus.LINKUP, self.port.link_status)
def test_lldp_receive_all_fields(self):
"""Test all enhanced LLDP fields are parsed correctly"""
self.port._parse_attributes(self.json_doc)
lldp = self.port.ethernet.lldp_receive
# Test TLV Type 1 - Chassis ID with subtype
self.assertEqual('c4:7e:e0:e4:55:3f', lldp.chassis_id)
self.assertEqual(net_cons.IEEE802IdSubtype.MAC_ADDR,
lldp.chassis_id_subtype)
# Test TLV Type 2 - Port ID with subtype
self.assertEqual('0A:1B:2C:3D:4E:5F:6A:7B:8C:9D:0E:1F:2A',
lldp.port_id)
self.assertEqual(net_cons.IEEE802IdSubtype.IF_NAME,
lldp.port_id_subtype)
# Test TLV Type 5 - System Name
self.assertEqual('switch-00.example.com', lldp.system_name)
# Test TLV Type 6 - System Description
self.assertEqual('Test Software, Version 00.00.00',
lldp.system_description)
# Test TLV Type 7 - System Capabilities
self.assertIsNotNone(lldp.system_capabilities)
self.assertEqual(2, len(lldp.system_capabilities))
self.assertIn(net_cons.LLDPSystemCapabilities.BRIDGE,
lldp.system_capabilities)
self.assertIn(net_cons.LLDPSystemCapabilities.ROUTER,
lldp.system_capabilities)
# Test TLV Type 8 - Management Addresses
self.assertEqual('192.168.1.1', lldp.management_address_ipv4)
self.assertEqual('fe80::1', lldp.management_address_ipv6)
self.assertEqual('c4:7e:e0:e4:55:40', lldp.management_address_mac)
self.assertEqual(100, lldp.management_vlan_id)
def test_lldp_receive_minimal_data(self):
"""Test LLDP with minimal data (only mandatory fields)"""
minimal_doc = self.json_doc.copy()
minimal_doc['Ethernet']['LLDPReceive'] = {
"ChassisId": "aa:bb:cc:dd:ee:ff",
"PortId": "port-1"
}
self.port._parse_attributes(minimal_doc)
lldp = self.port.ethernet.lldp_receive
# Mandatory fields present
self.assertEqual('aa:bb:cc:dd:ee:ff', lldp.chassis_id)
self.assertEqual('port-1', lldp.port_id)
# Optional fields None
self.assertIsNone(lldp.chassis_id_subtype)
self.assertIsNone(lldp.port_id_subtype)
self.assertIsNone(lldp.system_name)
self.assertIsNone(lldp.system_description)
self.assertIsNone(lldp.system_capabilities)
self.assertIsNone(lldp.management_address_ipv4)
def test_lldp_receive_empty(self):
"""Test empty LLDPReceive (Dell scenario)"""
empty_doc = self.json_doc.copy()
empty_doc['Ethernet']['LLDPReceive'] = {}
self.port._parse_attributes(empty_doc)
lldp = self.port.ethernet.lldp_receive
# All fields should be None
self.assertIsNone(lldp.chassis_id)
self.assertIsNone(lldp.port_id)
self.assertIsNone(lldp.system_name)
class PortCollectionTestCase(base.TestCase):