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 }