diff --git a/api/v1alpha1/applyconfiguration/api/v1alpha1/networkinterface.go b/api/v1alpha1/applyconfiguration/api/v1alpha1/networkinterface.go
index 55b96db10..0a466e596 100644
--- a/api/v1alpha1/applyconfiguration/api/v1alpha1/networkinterface.go
+++ b/api/v1alpha1/applyconfiguration/api/v1alpha1/networkinterface.go
@@ -25,6 +25,32 @@ type NetworkInterfaceApplyConfiguration struct {
MACAddress *string `json:"macAddress,omitempty"`
// CarrierStatus is the operational carrier status of the network interface.
CarrierStatus *string `json:"carrierStatus,omitempty"`
+ // PCIAddress is the PCI bus address of the underlying NIC (e.g. "0000:01:00.0").
+ PCIAddress *string `json:"pciAddress,omitempty"`
+ // Speed is the link speed reported by the NIC driver.
+ Speed *string `json:"speed,omitempty"`
+ // LinkModes is the list of link modes supported by the NIC.
+ LinkModes []string `json:"linkModes,omitempty"`
+ // SupportedPorts is the list of port types supported by the NIC (e.g. TP, FIBRE).
+ SupportedPorts []string `json:"supportedPorts,omitempty"`
+ // FirmwareVersion is the firmware version reported by the NIC driver.
+ FirmwareVersion *string `json:"firmwareVersion,omitempty"`
+ // NUMANode is the NUMA node ID as reported by the NIC driver.
+ NUMANode *int32 `json:"numaNode,omitempty"`
+ // Vendor is the PCI vendor ID (e.g. "0x8086") as reported by the NIC driver.
+ Vendor *string `json:"vendor,omitempty"`
+ // SubsystemVendor is the PCI subsystem vendor ID as reported by the NIC driver.
+ SubsystemVendor *string `json:"subsystemVendor,omitempty"`
+ // Device is the PCI device ID (e.g. "0x1533") as reported by the NIC driver.
+ Device *string `json:"device,omitempty"`
+ // MaxRx is the maximum number of RX channels supported by the NIC.
+ MaxRx *int32 `json:"maxRx,omitempty"`
+ // MaxTx is the maximum number of TX channels supported by the NIC.
+ MaxTx *int32 `json:"maxTx,omitempty"`
+ // MaxOther is the maximum number of other channels supported by the NIC.
+ MaxOther *int32 `json:"maxOther,omitempty"`
+ // MaxCombined is the maximum number of combined channels supported by the NIC.
+ MaxCombined *int32 `json:"maxCombined,omitempty"`
// Neighbors contains the LLDP neighbors discovered on this interface.
Neighbors []LLDPNeighborApplyConfiguration `json:"neighbors,omitempty"`
}
@@ -77,6 +103,114 @@ func (b *NetworkInterfaceApplyConfiguration) WithCarrierStatus(value string) *Ne
return b
}
+// WithPCIAddress sets the PCIAddress field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the PCIAddress field is set to the value of the last call.
+func (b *NetworkInterfaceApplyConfiguration) WithPCIAddress(value string) *NetworkInterfaceApplyConfiguration {
+ b.PCIAddress = &value
+ return b
+}
+
+// WithSpeed sets the Speed field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Speed field is set to the value of the last call.
+func (b *NetworkInterfaceApplyConfiguration) WithSpeed(value string) *NetworkInterfaceApplyConfiguration {
+ b.Speed = &value
+ return b
+}
+
+// WithLinkModes adds the given value to the LinkModes field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the LinkModes field.
+func (b *NetworkInterfaceApplyConfiguration) WithLinkModes(values ...string) *NetworkInterfaceApplyConfiguration {
+ for i := range values {
+ b.LinkModes = append(b.LinkModes, values[i])
+ }
+ return b
+}
+
+// WithSupportedPorts adds the given value to the SupportedPorts field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the SupportedPorts field.
+func (b *NetworkInterfaceApplyConfiguration) WithSupportedPorts(values ...string) *NetworkInterfaceApplyConfiguration {
+ for i := range values {
+ b.SupportedPorts = append(b.SupportedPorts, values[i])
+ }
+ return b
+}
+
+// WithFirmwareVersion sets the FirmwareVersion field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the FirmwareVersion field is set to the value of the last call.
+func (b *NetworkInterfaceApplyConfiguration) WithFirmwareVersion(value string) *NetworkInterfaceApplyConfiguration {
+ b.FirmwareVersion = &value
+ return b
+}
+
+// WithNUMANode sets the NUMANode field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the NUMANode field is set to the value of the last call.
+func (b *NetworkInterfaceApplyConfiguration) WithNUMANode(value int32) *NetworkInterfaceApplyConfiguration {
+ b.NUMANode = &value
+ return b
+}
+
+// WithVendor sets the Vendor field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Vendor field is set to the value of the last call.
+func (b *NetworkInterfaceApplyConfiguration) WithVendor(value string) *NetworkInterfaceApplyConfiguration {
+ b.Vendor = &value
+ return b
+}
+
+// WithSubsystemVendor sets the SubsystemVendor field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the SubsystemVendor field is set to the value of the last call.
+func (b *NetworkInterfaceApplyConfiguration) WithSubsystemVendor(value string) *NetworkInterfaceApplyConfiguration {
+ b.SubsystemVendor = &value
+ return b
+}
+
+// WithDevice sets the Device field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Device field is set to the value of the last call.
+func (b *NetworkInterfaceApplyConfiguration) WithDevice(value string) *NetworkInterfaceApplyConfiguration {
+ b.Device = &value
+ return b
+}
+
+// WithMaxRx sets the MaxRx field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the MaxRx field is set to the value of the last call.
+func (b *NetworkInterfaceApplyConfiguration) WithMaxRx(value int32) *NetworkInterfaceApplyConfiguration {
+ b.MaxRx = &value
+ return b
+}
+
+// WithMaxTx sets the MaxTx field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the MaxTx field is set to the value of the last call.
+func (b *NetworkInterfaceApplyConfiguration) WithMaxTx(value int32) *NetworkInterfaceApplyConfiguration {
+ b.MaxTx = &value
+ return b
+}
+
+// WithMaxOther sets the MaxOther field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the MaxOther field is set to the value of the last call.
+func (b *NetworkInterfaceApplyConfiguration) WithMaxOther(value int32) *NetworkInterfaceApplyConfiguration {
+ b.MaxOther = &value
+ return b
+}
+
+// WithMaxCombined sets the MaxCombined field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the MaxCombined field is set to the value of the last call.
+func (b *NetworkInterfaceApplyConfiguration) WithMaxCombined(value int32) *NetworkInterfaceApplyConfiguration {
+ b.MaxCombined = &value
+ return b
+}
+
// WithNeighbors adds the given value to the Neighbors field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the Neighbors field.
diff --git a/api/v1alpha1/applyconfiguration/internal/internal.go b/api/v1alpha1/applyconfiguration/internal/internal.go
index 862b00c58..dbc484553 100644
--- a/api/v1alpha1/applyconfiguration/internal/internal.go
+++ b/api/v1alpha1/applyconfiguration/internal/internal.go
@@ -930,6 +930,12 @@ var schemaYAML = typed.YAMLObject(`types:
- name: carrierStatus
type:
scalar: string
+ - name: device
+ type:
+ scalar: string
+ - name: firmwareVersion
+ type:
+ scalar: string
- name: ip
type:
scalar: string
@@ -939,9 +945,27 @@ var schemaYAML = typed.YAMLObject(`types:
elementType:
namedType: com.github.ironcore-dev.metal-operator.api.v1alpha1.IP
elementRelationship: atomic
+ - name: linkModes
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: atomic
- name: macAddress
type:
scalar: string
+ - name: maxCombined
+ type:
+ scalar: numeric
+ - name: maxOther
+ type:
+ scalar: numeric
+ - name: maxRx
+ type:
+ scalar: numeric
+ - name: maxTx
+ type:
+ scalar: numeric
- name: name
type:
scalar: string
@@ -951,6 +975,27 @@ var schemaYAML = typed.YAMLObject(`types:
elementType:
namedType: com.github.ironcore-dev.metal-operator.api.v1alpha1.LLDPNeighbor
elementRelationship: atomic
+ - name: numaNode
+ type:
+ scalar: numeric
+ - name: pciAddress
+ type:
+ scalar: string
+ - name: speed
+ type:
+ scalar: string
+ - name: subsystemVendor
+ type:
+ scalar: string
+ - name: supportedPorts
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: atomic
+ - name: vendor
+ type:
+ scalar: string
- name: com.github.ironcore-dev.metal-operator.api.v1alpha1.ObjectFieldRefSelector
map:
fields:
diff --git a/api/v1alpha1/server_types.go b/api/v1alpha1/server_types.go
index 373b25254..6df5fd095 100644
--- a/api/v1alpha1/server_types.go
+++ b/api/v1alpha1/server_types.go
@@ -329,6 +329,58 @@ type NetworkInterface struct {
// +optional
CarrierStatus string `json:"carrierStatus,omitempty"`
+ // PCIAddress is the PCI bus address of the underlying NIC (e.g. "0000:01:00.0").
+ // +optional
+ PCIAddress string `json:"pciAddress,omitempty"`
+
+ // Speed is the link speed reported by the NIC driver.
+ // +optional
+ Speed string `json:"speed,omitempty"`
+
+ // LinkModes is the list of link modes supported by the NIC.
+ // +optional
+ LinkModes []string `json:"linkModes,omitempty"`
+
+ // SupportedPorts is the list of port types supported by the NIC (e.g. TP, FIBRE).
+ // +optional
+ SupportedPorts []string `json:"supportedPorts,omitempty"`
+
+ // FirmwareVersion is the firmware version reported by the NIC driver.
+ // +optional
+ FirmwareVersion string `json:"firmwareVersion,omitempty"`
+
+ // NUMANode is the NUMA node ID as reported by the NIC driver.
+ // +optional
+ NUMANode int32 `json:"numaNode,omitempty"`
+
+ // Vendor is the PCI vendor ID (e.g. "0x8086") as reported by the NIC driver.
+ // +optional
+ Vendor string `json:"vendor,omitempty"`
+
+ // SubsystemVendor is the PCI subsystem vendor ID as reported by the NIC driver.
+ // +optional
+ SubsystemVendor string `json:"subsystemVendor,omitempty"`
+
+ // Device is the PCI device ID (e.g. "0x1533") as reported by the NIC driver.
+ // +optional
+ Device string `json:"device,omitempty"`
+
+ // MaxRx is the maximum number of RX channels supported by the NIC.
+ // +optional
+ MaxRx int32 `json:"maxRx,omitempty"`
+
+ // MaxTx is the maximum number of TX channels supported by the NIC.
+ // +optional
+ MaxTx int32 `json:"maxTx,omitempty"`
+
+ // MaxOther is the maximum number of other channels supported by the NIC.
+ // +optional
+ MaxOther int32 `json:"maxOther,omitempty"`
+
+ // MaxCombined is the maximum number of combined channels supported by the NIC.
+ // +optional
+ MaxCombined int32 `json:"maxCombined,omitempty"`
+
// Neighbors contains the LLDP neighbors discovered on this interface.
// +optional
Neighbors []LLDPNeighbor `json:"neighbors,omitempty"`
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 70b712785..c6557936e 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -1556,6 +1556,16 @@ func (in *NetworkInterface) DeepCopyInto(out *NetworkInterface) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
+ if in.LinkModes != nil {
+ in, out := &in.LinkModes, &out.LinkModes
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+ if in.SupportedPorts != nil {
+ in, out := &in.SupportedPorts, &out.SupportedPorts
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
if in.Neighbors != nil {
in, out := &in.Neighbors, &out.Neighbors
*out = make([]LLDPNeighbor, len(*in))
diff --git a/config/crd/bases/metal.ironcore.dev_servers.yaml b/config/crd/bases/metal.ironcore.dev_servers.yaml
index 4c05ab7ca..bdafd1ded 100644
--- a/config/crd/bases/metal.ironcore.dev_servers.yaml
+++ b/config/crd/bases/metal.ironcore.dev_servers.yaml
@@ -377,6 +377,14 @@ spec:
description: CarrierStatus is the operational carrier status
of the network interface.
type: string
+ device:
+ description: Device is the PCI device ID (e.g. "0x1533") as
+ reported by the NIC driver.
+ type: string
+ firmwareVersion:
+ description: FirmwareVersion is the firmware version reported
+ by the NIC driver.
+ type: string
ip:
description: |-
IP is the IP address assigned to the network interface.
@@ -389,9 +397,35 @@ spec:
format: ip
type: string
type: array
+ linkModes:
+ description: LinkModes is the list of link modes supported by
+ the NIC.
+ items:
+ type: string
+ type: array
macAddress:
description: MACAddress is the MAC address of the network interface.
type: string
+ maxCombined:
+ description: MaxCombined is the maximum number of combined channels
+ supported by the NIC.
+ format: int32
+ type: integer
+ maxOther:
+ description: MaxOther is the maximum number of other channels
+ supported by the NIC.
+ format: int32
+ type: integer
+ maxRx:
+ description: MaxRx is the maximum number of RX channels supported
+ by the NIC.
+ format: int32
+ type: integer
+ maxTx:
+ description: MaxTx is the maximum number of TX channels supported
+ by the NIC.
+ format: int32
+ type: integer
name:
description: Name is the name of the network interface.
type: string
@@ -423,6 +457,32 @@ spec:
type: string
type: object
type: array
+ numaNode:
+ description: NUMANode is the NUMA node ID as reported by the
+ NIC driver.
+ format: int32
+ type: integer
+ pciAddress:
+ description: PCIAddress is the PCI bus address of the underlying
+ NIC (e.g. "0000:01:00.0").
+ type: string
+ speed:
+ description: Speed is the link speed reported by the NIC driver.
+ type: string
+ subsystemVendor:
+ description: SubsystemVendor is the PCI subsystem vendor ID
+ as reported by the NIC driver.
+ type: string
+ supportedPorts:
+ description: SupportedPorts is the list of port types supported
+ by the NIC (e.g. TP, FIBRE).
+ items:
+ type: string
+ type: array
+ vendor:
+ description: Vendor is the PCI vendor ID (e.g. "0x8086") as
+ reported by the NIC driver.
+ type: string
required:
- macAddress
- name
diff --git a/dist/chart/templates/crd/metal.ironcore.dev_servers.yaml b/dist/chart/templates/crd/metal.ironcore.dev_servers.yaml
index ef6665cef..1818b6823 100755
--- a/dist/chart/templates/crd/metal.ironcore.dev_servers.yaml
+++ b/dist/chart/templates/crd/metal.ironcore.dev_servers.yaml
@@ -383,6 +383,14 @@ spec:
description: CarrierStatus is the operational carrier status
of the network interface.
type: string
+ device:
+ description: Device is the PCI device ID (e.g. "0x1533") as
+ reported by the NIC driver.
+ type: string
+ firmwareVersion:
+ description: FirmwareVersion is the firmware version reported
+ by the NIC driver.
+ type: string
ip:
description: |-
IP is the IP address assigned to the network interface.
@@ -395,9 +403,35 @@ spec:
format: ip
type: string
type: array
+ linkModes:
+ description: LinkModes is the list of link modes supported by
+ the NIC.
+ items:
+ type: string
+ type: array
macAddress:
description: MACAddress is the MAC address of the network interface.
type: string
+ maxCombined:
+ description: MaxCombined is the maximum number of combined channels
+ supported by the NIC.
+ format: int32
+ type: integer
+ maxOther:
+ description: MaxOther is the maximum number of other channels
+ supported by the NIC.
+ format: int32
+ type: integer
+ maxRx:
+ description: MaxRx is the maximum number of RX channels supported
+ by the NIC.
+ format: int32
+ type: integer
+ maxTx:
+ description: MaxTx is the maximum number of TX channels supported
+ by the NIC.
+ format: int32
+ type: integer
name:
description: Name is the name of the network interface.
type: string
@@ -429,6 +463,32 @@ spec:
type: string
type: object
type: array
+ numaNode:
+ description: NUMANode is the NUMA node ID as reported by the
+ NIC driver.
+ format: int32
+ type: integer
+ pciAddress:
+ description: PCIAddress is the PCI bus address of the underlying
+ NIC (e.g. "0000:01:00.0").
+ type: string
+ speed:
+ description: Speed is the link speed reported by the NIC driver.
+ type: string
+ subsystemVendor:
+ description: SubsystemVendor is the PCI subsystem vendor ID
+ as reported by the NIC driver.
+ type: string
+ supportedPorts:
+ description: SupportedPorts is the list of port types supported
+ by the NIC (e.g. TP, FIBRE).
+ items:
+ type: string
+ type: array
+ vendor:
+ description: Vendor is the PCI vendor ID (e.g. "0x8086") as
+ reported by the NIC driver.
+ type: string
required:
- macAddress
- name
diff --git a/docs/api-reference/api.md b/docs/api-reference/api.md
index 2f16dcf8b..356304c7b 100644
--- a/docs/api-reference/api.md
+++ b/docs/api-reference/api.md
@@ -1213,6 +1213,19 @@ _Appears in:_
| `ips` _[IP](#ip) array_ | IPs is a list of IP addresses (both IPv4 and IPv6) assigned to the network interface. | | Format: ip
Type: string
|
| `macAddress` _string_ | MACAddress is the MAC address of the network interface. | | |
| `carrierStatus` _string_ | CarrierStatus is the operational carrier status of the network interface. | | |
+| `pciAddress` _string_ | PCIAddress is the PCI bus address of the underlying NIC (e.g. "0000:01:00.0"). | | |
+| `speed` _string_ | Speed is the link speed reported by the NIC driver. | | |
+| `linkModes` _string array_ | LinkModes is the list of link modes supported by the NIC. | | |
+| `supportedPorts` _string array_ | SupportedPorts is the list of port types supported by the NIC (e.g. TP, FIBRE). | | |
+| `firmwareVersion` _string_ | FirmwareVersion is the firmware version reported by the NIC driver. | | |
+| `numaNode` _integer_ | NUMANode is the NUMA node ID as reported by the NIC driver. | | |
+| `vendor` _string_ | Vendor is the PCI vendor ID (e.g. "0x8086") as reported by the NIC driver. | | |
+| `subsystemVendor` _string_ | SubsystemVendor is the PCI subsystem vendor ID as reported by the NIC driver. | | |
+| `device` _string_ | Device is the PCI device ID (e.g. "0x1533") as reported by the NIC driver. | | |
+| `maxRx` _integer_ | MaxRx is the maximum number of RX channels supported by the NIC. | | |
+| `maxTx` _integer_ | MaxTx is the maximum number of TX channels supported by the NIC. | | |
+| `maxOther` _integer_ | MaxOther is the maximum number of other channels supported by the NIC. | | |
+| `maxCombined` _integer_ | MaxCombined is the maximum number of combined channels supported by the NIC. | | |
| `neighbors` _[LLDPNeighbor](#lldpneighbor) array_ | Neighbors contains the LLDP neighbors discovered on this interface. | | |
diff --git a/internal/api/registry/nic.go b/internal/api/registry/nic.go
index 6d8eebee5..26629cad2 100644
--- a/internal/api/registry/nic.go
+++ b/internal/api/registry/nic.go
@@ -11,4 +11,12 @@ type NIC struct {
LinkModes []string `json:"linkModes"`
SupportedPorts []string `json:"supportedPorts"`
FirmwareVersion string `json:"firmwareVersion"`
+ NUMANode int `json:"numaNode"`
+ Vendor string `json:"vendor"`
+ SubsystemVendor string `json:"subsystemVendor"`
+ Device string `json:"device"`
+ MaxRx uint32 `json:"maxRx"`
+ MaxTx uint32 `json:"maxTx"`
+ MaxOther uint32 `json:"maxOther"`
+ MaxCombined uint32 `json:"maxCombined"`
}
diff --git a/internal/controller/server_controller.go b/internal/controller/server_controller.go
index 94b174d79..66fc11097 100644
--- a/internal/controller/server_controller.go
+++ b/internal/controller/server_controller.go
@@ -979,9 +979,23 @@ func (r *ServerReconciler) extractServerDetailsFromRegistry(ctx context.Context,
}
serverBase := server.DeepCopy()
- // update network interfaces
- nics := make([]metalv1alpha1.NetworkInterface, 0, len(serverDetails.NetworkInterfaces))
- for _, s := range serverDetails.NetworkInterfaces {
+ server.Status.NetworkInterfaces = buildNetworkInterfaces(ctx, serverDetails.NetworkInterfaces, serverDetails.NICs, serverDetails.LLDP)
+ if err := r.Status().Patch(ctx, server, client.MergeFrom(serverBase)); err != nil {
+ return false, fmt.Errorf("failed to patch server status: %w", err)
+ }
+
+ return true, nil
+}
+
+func buildNetworkInterfaces(
+ ctx context.Context,
+ registryInterfaces []registry.NetworkInterface,
+ registryNICs []registry.NIC,
+ registryLLDP []registry.LLDPInterface,
+) []metalv1alpha1.NetworkInterface {
+ log := ctrl.LoggerFrom(ctx)
+ nics := make([]metalv1alpha1.NetworkInterface, 0, len(registryInterfaces))
+ for _, s := range registryInterfaces {
nic := metalv1alpha1.NetworkInterface{
Name: s.Name,
MACAddress: s.MACAddress,
@@ -997,14 +1011,12 @@ func (r *ServerReconciler) extractServerDetailsFromRegistry(ctx context.Context,
}
for _, ipAddr := range ipAddrs {
if ipAddr != "" {
- // Parse and validate the IP address
ip, err := metalv1alpha1.ParseIP(ipAddr)
if err != nil {
log.Error(err, "Invalid IP address, skipping", "interface", s.Name, "ip", ipAddr)
continue
}
- // Add all valid IP addresses (both IPv4 and IPv6) to the slice
allIPs = append(allIPs, ip)
}
}
@@ -1013,8 +1025,42 @@ func (r *ServerReconciler) extractServerDetailsFromRegistry(ctx context.Context,
nics = append(nics, nic)
}
+ // Merge NIC hardware details reported by metalprobe into corresponding network interfaces.
+ nicsByName := make(map[string]registry.NIC, len(registryNICs))
+ nicsByMAC := make(map[string]registry.NIC, len(registryNICs))
+ for _, n := range registryNICs {
+ if n.Name != "" {
+ nicsByName[n.Name] = n
+ }
+ if n.MAC != "" {
+ nicsByMAC[strings.ToLower(n.MAC)] = n
+ }
+ }
+ for i := range nics {
+ hw, ok := nicsByName[nics[i].Name]
+ if !ok {
+ hw, ok = nicsByMAC[strings.ToLower(nics[i].MACAddress)]
+ }
+ if !ok {
+ continue
+ }
+ nics[i].PCIAddress = hw.PCIAddress
+ nics[i].Speed = hw.Speed
+ nics[i].LinkModes = hw.LinkModes
+ nics[i].SupportedPorts = hw.SupportedPorts
+ nics[i].FirmwareVersion = hw.FirmwareVersion
+ nics[i].NUMANode = int32(hw.NUMANode)
+ nics[i].Vendor = hw.Vendor
+ nics[i].SubsystemVendor = hw.SubsystemVendor
+ nics[i].Device = hw.Device
+ nics[i].MaxRx = int32(hw.MaxRx)
+ nics[i].MaxTx = int32(hw.MaxTx)
+ nics[i].MaxOther = int32(hw.MaxOther)
+ nics[i].MaxCombined = int32(hw.MaxCombined)
+ }
+
// Merge LLDP neighbors into corresponding network interfaces
- for _, lldpIface := range serverDetails.LLDP {
+ for _, lldpIface := range registryLLDP {
// Find the matching network interface by name
for i := range nics {
if nics[i].Name == lldpIface.Name {
@@ -1035,12 +1081,7 @@ func (r *ServerReconciler) extractServerDetailsFromRegistry(ctx context.Context,
}
}
- server.Status.NetworkInterfaces = nics
- if err := r.Status().Patch(ctx, server, client.MergeFrom(serverBase)); err != nil {
- return false, fmt.Errorf("failed to patch server status: %w", err)
- }
-
- return true, nil
+ return nics
}
func (r *ServerReconciler) patchServerState(ctx context.Context, server *metalv1alpha1.Server, state metalv1alpha1.ServerState) (bool, error) {
diff --git a/internal/controller/server_controller_test.go b/internal/controller/server_controller_test.go
index 47b7cfa66..e5111bd4f 100644
--- a/internal/controller/server_controller_test.go
+++ b/internal/controller/server_controller_test.go
@@ -335,19 +335,87 @@ var _ = Describe("Server Controller", func() {
)),
))
- By("Starting the probe agent")
- probeAgent := probe.NewAgent(GinkgoLogr, server.Spec.SystemUUID, registryURL, 100*time.Millisecond, 1*time.Second, 50*time.Millisecond, 250*time.Millisecond)
- go func() {
- defer GinkgoRecover()
- Expect(probeAgent.Start(ctx)).To(Succeed(), "failed to start probe agent")
- }()
+ By("Publishing server details to the registry")
+ registerPayload := registry.RegistrationPayload{
+ SystemUUID: server.Spec.SystemUUID,
+ Data: registry.Server{
+ Timestamp: &metav1.Time{Time: time.Now()},
+ NetworkInterfaces: []registry.NetworkInterface{{
+ Name: "eth0",
+ MACAddress: "aa:bb:cc:dd:ee:ff",
+ CarrierStatus: "up",
+ IPAddresses: []string{"10.0.0.1"},
+ }},
+ NICs: []registry.NIC{{
+ Name: "eth0",
+ MAC: "aa:bb:cc:dd:ee:ff",
+ PCIAddress: "0000:01:00.0",
+ Speed: "10000",
+ LinkModes: []string{"10000baseT/Full"},
+ SupportedPorts: []string{"TP"},
+ FirmwareVersion: "1.2.3",
+ NUMANode: 0,
+ Vendor: "0x8086",
+ SubsystemVendor: "0x8086",
+ Device: "0x1533",
+ MaxRx: 8,
+ MaxTx: 8,
+ MaxOther: 0,
+ MaxCombined: 8,
+ }},
+ LLDP: []registry.LLDPInterface{{
+ Name: "eth0",
+ Neighbors: []registry.Neighbor{{
+ ChassisID: "11:22:33:44:55:66",
+ PortID: "swp1",
+ PortDescription: "uplink",
+ SystemName: "leaf-1",
+ SystemDescription: "leaf switch",
+ }},
+ }},
+ },
+ }
+ registerBody, err := json.Marshal(registerPayload)
+ Expect(err).NotTo(HaveOccurred())
+ registerResp, err := http.Post(registryURL+"/register", "application/json", bytes.NewReader(registerBody))
+ Expect(err).NotTo(HaveOccurred())
+ Expect(registerResp.Body.Close()).To(Succeed())
+ Expect(registerResp.StatusCode).To(Equal(http.StatusCreated))
+
+ expectedIP, err := metalv1alpha1.ParseIP("10.0.0.1")
+ Expect(err).NotTo(HaveOccurred())
By("Ensuring that the server is set to available and powered off")
Eventually(Object(server)).Should(SatisfyAll(
HaveField("Spec.BootConfigurationRef", BeNil()),
HaveField("Status.State", metalv1alpha1.ServerStateAvailable),
HaveField("Status.PowerState", metalv1alpha1.ServerOffPowerState),
- HaveField("Status.NetworkInterfaces", Not(BeEmpty())),
+ HaveField("Status.NetworkInterfaces", ConsistOf(metalv1alpha1.NetworkInterface{
+ Name: "eth0",
+ MACAddress: "aa:bb:cc:dd:ee:ff",
+ CarrierStatus: "up",
+ IPs: []metalv1alpha1.IP{expectedIP},
+ PCIAddress: "0000:01:00.0",
+ Speed: "10000",
+ LinkModes: []string{"10000baseT/Full"},
+ SupportedPorts: []string{"TP"},
+ FirmwareVersion: "1.2.3",
+ NUMANode: 0,
+ Vendor: "0x8086",
+ SubsystemVendor: "0x8086",
+ Device: "0x1533",
+ MaxRx: 8,
+ MaxTx: 8,
+ MaxOther: 0,
+ MaxCombined: 8,
+ Neighbors: []metalv1alpha1.LLDPNeighbor{{
+ MACAddress: "11:22:33:44:55:66",
+ PortID: "swp1",
+ PortDescription: "uplink",
+ SystemName: "leaf-1",
+ SystemDescription: "leaf switch",
+ }},
+ })),
))
By("Ensuring that the boot configuration has been removed")
diff --git a/internal/probe/nics_darwin.go b/internal/probe/nics_darwin.go
index 14b2f9eda..cfc17a54c 100644
--- a/internal/probe/nics_darwin.go
+++ b/internal/probe/nics_darwin.go
@@ -17,6 +17,14 @@ func collectNICInfoData() ([]registry.NIC, error) {
LinkModes: []string{"1000baseT/Full"},
SupportedPorts: []string{"TP"},
FirmwareVersion: "1.0.0",
+ NUMANode: 0,
+ Vendor: "0x8086",
+ SubsystemVendor: "0x8086",
+ Device: "0x1533",
+ MaxRx: 8,
+ MaxTx: 8,
+ MaxOther: 0,
+ MaxCombined: 8,
},
}, nil
}
diff --git a/internal/probe/nics_linux.go b/internal/probe/nics_linux.go
index a21f0afdf..1ddc69698 100644
--- a/internal/probe/nics_linux.go
+++ b/internal/probe/nics_linux.go
@@ -35,7 +35,7 @@ func collectNICInfoData() ([]registry.NIC, error) {
if err != nil {
return []registry.NIC{}, fmt.Errorf("failed to get driver info: %w", err)
}
- nics = append(nics, registry.NIC{
+ nicData := registry.NIC{
Name: nic.Name,
MAC: nic.MACAddress,
PCIAddress: pci,
@@ -43,7 +43,32 @@ func collectNICInfoData() ([]registry.NIC, error) {
LinkModes: nic.SupportedLinkModes,
SupportedPorts: nic.SupportedPorts,
FirmwareVersion: drvInfo.FwVersion,
- })
+ }
+
+ // Read additional NIC properties from sysfs. The device symlink may be
+ // absent for virtual interfaces (lo, bonds, bridges, etc.) — skip gracefully.
+ sysfsBase := fmt.Sprintf("/sys/class/net/%s/device", nic.Name)
+ if numaNode, err := ToInt(sysfsBase + "/numa_node"); err == nil {
+ nicData.NUMANode = numaNode
+ }
+ if vendor, err := ToString(sysfsBase + "/vendor"); err == nil {
+ nicData.Vendor = vendor
+ }
+ if subsysVendor, err := ToString(sysfsBase + "/subsystem_vendor"); err == nil {
+ nicData.SubsystemVendor = subsysVendor
+ }
+ if device, err := ToString(sysfsBase + "/device"); err == nil {
+ nicData.Device = device
+ }
+
+ if channels, err := ethHandle.GetChannels(nic.Name); err == nil {
+ nicData.MaxRx = channels.MaxRx
+ nicData.MaxTx = channels.MaxTx
+ nicData.MaxOther = channels.MaxOther
+ nicData.MaxCombined = channels.MaxCombined
+ }
+
+ nics = append(nics, nicData)
}
return nics, nil
}