Skip to content

Commit 9599083

Browse files
committed
Capture and store LLDP data in HardwareData
Add support for capturing Link Layer Discovery Protocol (LLDP) data during hardware inspection and storing it in the HardwareData CRD. This provides network topology information that can be used for port identification and network configuration. Changes: - Add LLDP struct to store switch topology data (chassis ID, port ID, system name) - Add LLDP field to NIC struct in BareMetalHost API types - Implement getLLDPData() to extract LLDP data from Ironic introspection - Populate LLDP data for each network interface during hardware inspection - Update CRD manifests for BareMetalHost and HardwareData The LLDP data includes: - switch_chassis_id: Switch chassis identifier - switch_port_id: Switch port identifier - switch_system_name: Switch system name Fixes: #1731 Assisted-by: Claude Code/sonnet-4.5 Signed-off-by: Allain Legacy <[email protected]>
1 parent cb60def commit 9599083

File tree

9 files changed

+295
-57
lines changed

9 files changed

+295
-57
lines changed

apis/metal3.io/v1alpha1/baremetalhost_types.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,21 @@ type VLAN struct {
678678
Name string `json:"name,omitempty"`
679679
}
680680

681+
// LLDP represents Link Layer Discovery Protocol data for a network interface.
682+
type LLDP struct {
683+
// The switch chassis ID from LLDP
684+
// +optional
685+
SwitchID string `json:"switchID,omitempty"`
686+
687+
// The switch port ID from LLDP
688+
// +optional
689+
PortID string `json:"portID,omitempty"`
690+
691+
// The switch system name from LLDP
692+
// +optional
693+
SwitchSystemName string `json:"switchSystemName,omitempty"`
694+
}
695+
681696
// NIC describes one network interface on the host.
682697
type NIC struct {
683698
// The name of the network interface, e.g. "en0"
@@ -707,6 +722,10 @@ type NIC struct {
707722

708723
// Whether the NIC is PXE Bootable
709724
PXE bool `json:"pxe,omitempty"`
725+
726+
// LLDP data for this interface
727+
// +optional
728+
LLDP *LLDP `json:"lldp,omitempty"`
710729
}
711730

712731
// Firmware describes the firmware on the host.

apis/metal3.io/v1alpha1/zz_generated.deepcopy.go

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/base/crds/bases/metal3.io_baremetalhosts.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,19 @@ spec:
716716
if one is present. If both IPv4 and IPv6 addresses are present in a
717717
dual-stack environment, two nics will be output, one with each IP.
718718
type: string
719+
lldp:
720+
description: LLDP data for this interface
721+
properties:
722+
portID:
723+
description: The switch port ID from LLDP
724+
type: string
725+
switchID:
726+
description: The switch chassis ID from LLDP
727+
type: string
728+
switchSystemName:
729+
description: The switch system name from LLDP
730+
type: string
731+
type: object
719732
mac:
720733
description: The device MAC address
721734
pattern: '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}'

config/base/crds/bases/metal3.io_hardwaredata.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,19 @@ spec:
9797
if one is present. If both IPv4 and IPv6 addresses are present in a
9898
dual-stack environment, two nics will be output, one with each IP.
9999
type: string
100+
lldp:
101+
description: LLDP data for this interface
102+
properties:
103+
portID:
104+
description: The switch port ID from LLDP
105+
type: string
106+
switchID:
107+
description: The switch chassis ID from LLDP
108+
type: string
109+
switchSystemName:
110+
description: The switch system name from LLDP
111+
type: string
112+
type: object
100113
mac:
101114
description: The device MAC address
102115
pattern: '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}'

config/render/capm3.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,19 @@ spec:
716716
if one is present. If both IPv4 and IPv6 addresses are present in a
717717
dual-stack environment, two nics will be output, one with each IP.
718718
type: string
719+
lldp:
720+
description: LLDP data for this interface
721+
properties:
722+
portID:
723+
description: The switch port ID from LLDP
724+
type: string
725+
switchID:
726+
description: The switch chassis ID from LLDP
727+
type: string
728+
switchSystemName:
729+
description: The switch system name from LLDP
730+
type: string
731+
type: object
719732
mac:
720733
description: The device MAC address
721734
pattern: '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}'
@@ -1631,6 +1644,19 @@ spec:
16311644
if one is present. If both IPv4 and IPv6 addresses are present in a
16321645
dual-stack environment, two nics will be output, one with each IP.
16331646
type: string
1647+
lldp:
1648+
description: LLDP data for this interface
1649+
properties:
1650+
portID:
1651+
description: The switch port ID from LLDP
1652+
type: string
1653+
switchID:
1654+
description: The switch chassis ID from LLDP
1655+
type: string
1656+
switchSystemName:
1657+
description: The switch system name from LLDP
1658+
type: string
1659+
type: object
16341660
mac:
16351661
description: The device MAC address
16361662
pattern: '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}'

internal/controller/metal3.io/baremetalhost_controller_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,42 @@ func TestInspectEnabled(t *testing.T) {
579579
assert.NotNil(t, host.Status.HardwareDetails)
580580
}
581581

582+
// TestInspectLLDPData ensures that LLDP data is removed from BareMetalHost status
583+
// but retained in HardwareData resource.
584+
func TestInspectLLDPData(t *testing.T) {
585+
host := newDefaultHost(t)
586+
r := newTestReconciler(t, host)
587+
588+
// Wait for inspection to complete
589+
waitForProvisioningState(t, r, host, metal3api.StatePreparing)
590+
591+
// Verify that the BMH status has hardware details
592+
assert.NotNil(t, host.Status.HardwareDetails, "Hardware details should not be nil")
593+
assert.Len(t, host.Status.HardwareDetails.NIC, 2, "Should have 2 NICs")
594+
595+
// Verify that HardwareData resource was created with LLDP data intact
596+
hardwareData := &metal3api.HardwareData{}
597+
hardwareDataKey := client.ObjectKey{
598+
Name: host.Name,
599+
Namespace: host.Namespace,
600+
}
601+
err := r.Client.Get(t.Context(), hardwareDataKey, hardwareData)
602+
require.NoError(t, err, "HardwareData should be created")
603+
assert.NotNil(t, hardwareData.Spec.HardwareDetails, "HardwareData should have hardware details")
604+
assert.Len(t, hardwareData.Spec.HardwareDetails.NIC, 2, "HardwareData should have 2 NICs")
605+
606+
// Verify LLDP data is present in HardwareData
607+
assert.NotNil(t, hardwareData.Spec.HardwareDetails.NIC[0].LLDP, "LLDP data should be present in HardwareData for NIC 0")
608+
assert.Equal(t, "aa:bb:cc:dd:ee:ff", hardwareData.Spec.HardwareDetails.NIC[0].LLDP.SwitchID)
609+
assert.Equal(t, "Ethernet1/1", hardwareData.Spec.HardwareDetails.NIC[0].LLDP.PortID)
610+
assert.Equal(t, "switch01.example.com", hardwareData.Spec.HardwareDetails.NIC[0].LLDP.SwitchSystemName)
611+
612+
assert.NotNil(t, hardwareData.Spec.HardwareDetails.NIC[1].LLDP, "LLDP data should be present in HardwareData for NIC 1")
613+
assert.Equal(t, "ff:ee:dd:cc:bb:aa", hardwareData.Spec.HardwareDetails.NIC[1].LLDP.SwitchID)
614+
assert.Equal(t, "Ethernet1/2", hardwareData.Spec.HardwareDetails.NIC[1].LLDP.PortID)
615+
assert.Equal(t, "switch02.example.com", hardwareData.Spec.HardwareDetails.NIC[1].LLDP.SwitchSystemName)
616+
}
617+
582618
// TestAddFinalizers ensures that the finalizers for the host are
583619
// updated as part of reconciling it.
584620
func TestAddFinalizers(t *testing.T) {

pkg/provisioner/fixture/fixture.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ func (p *fixtureProvisioner) InspectHardware(_ provisioner.InspectData, _, _, _
171171
IP: "192.168.100.1",
172172
SpeedGbps: 1,
173173
PXE: true,
174+
LLDP: &metal3api.LLDP{
175+
SwitchID: "aa:bb:cc:dd:ee:ff",
176+
PortID: "Ethernet1/1",
177+
SwitchSystemName: "switch01.example.com",
178+
},
174179
},
175180
{
176181
Name: "nic-2",
@@ -179,6 +184,11 @@ func (p *fixtureProvisioner) InspectHardware(_ provisioner.InspectData, _, _, _
179184
IP: "192.168.100.2",
180185
SpeedGbps: 1,
181186
PXE: false,
187+
LLDP: &metal3api.LLDP{
188+
SwitchID: "ff:ee:dd:cc:bb:aa",
189+
PortID: "Ethernet1/2",
190+
SwitchSystemName: "switch02.example.com",
191+
},
182192
},
183193
},
184194
Storage: []metal3api.Storage{

pkg/provisioner/ironic/hardwaredetails/hardwaredetails.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,42 @@ func getVLANs(lldp map[string]interface{}) (vlans []metal3api.VLAN, vlanid metal
5353
return
5454
}
5555

56+
func getLLDPData(lldp map[string]interface{}) *metal3api.LLDP {
57+
if lldp == nil {
58+
return nil
59+
}
60+
61+
lldpData := &metal3api.LLDP{}
62+
hasData := false
63+
64+
if val, ok := lldp["switch_chassis_id"].(string); ok && val != "" {
65+
lldpData.SwitchID = val
66+
hasData = true
67+
}
68+
if val, ok := lldp["switch_port_id"].(string); ok && val != "" {
69+
lldpData.PortID = val
70+
hasData = true
71+
}
72+
if val, ok := lldp["switch_system_name"].(string); ok && val != "" {
73+
lldpData.SwitchSystemName = val
74+
hasData = true
75+
}
76+
77+
if !hasData {
78+
return nil
79+
}
80+
81+
return lldpData
82+
}
83+
5684
func getNICDetails(ifdata []inventory.InterfaceType, ironicData inventory.StandardPluginData) []metal3api.NIC {
5785
var nics []metal3api.NIC
5886
for _, intf := range ifdata {
5987
pxeEnabled := ironicData.AllInterfaces[intf.Name].PXEEnabled
6088
lldp := ironicData.ParsedLLDP[intf.Name]
6189

6290
vlans, vlanid := getVLANs(lldp)
91+
lldpData := getLLDPData(lldp)
6392
// We still store one nic even if both ips are unset
6493
// if both are set, we store two nics with each ip
6594
if intf.IPV4Address != "" || intf.IPV6Address == "" {
@@ -73,6 +102,7 @@ func getNICDetails(ifdata []inventory.InterfaceType, ironicData inventory.Standa
73102
VLANID: vlanid,
74103
SpeedGbps: intf.SpeedMbps / 1000, //nolint:mnd
75104
PXE: pxeEnabled,
105+
LLDP: lldpData,
76106
})
77107
}
78108
if intf.IPV6Address != "" {
@@ -86,6 +116,7 @@ func getNICDetails(ifdata []inventory.InterfaceType, ironicData inventory.Standa
86116
VLANID: vlanid,
87117
SpeedGbps: intf.SpeedMbps / 1000, //nolint:mnd
88118
PXE: pxeEnabled,
119+
LLDP: lldpData,
89120
})
90121
}
91122
}

0 commit comments

Comments
 (0)