diff --git a/pkg/utils/utils_virtual.go b/pkg/utils/utils_virtual.go index 193e67d539..a6c0618dd9 100644 --- a/pkg/utils/utils_virtual.go +++ b/pkg/utils/utils_virtual.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "strconv" + "strings" "github.com/golang/glog" "github.com/hashicorp/go-retryablehttp" @@ -118,6 +119,27 @@ func GetOpenstackData(useHostPath bool) (metaData *OSPMetaData, networkData *OSP if err != nil { metaData, networkData, err = getOpenstackDataFromMetadataService() } + + // We can't rely on the PCI address from the metadata so we will lookup the real PCI address + // for the NIC that matches the MAC address. + // + // Libvirt/QEMU cannot guarantee that the address specified in the XML will match the address seen by the guest. + // This is a well known limitation: https://libvirt.org/pci-addresses.html + // When using the q35 machine type, it highlights this issue due to the change from using PCI to PCI-E bus for virtual devices. + // + // With that said, the PCI value in Nova Metadata is a best effort hint due to the limitations mentioned above. Therefore + // we will lookup the real PCI address for the NIC that matches the MAC address. + for _, device := range metaData.Devices { + realPCIAddr, err := getPCIAddressFromMACAddress(device.Mac) + if err != nil { + return metaData, networkData, fmt.Errorf("GetOpenstackData(): error getting PCI address for device %s: %w", device.Mac, err) + } + if realPCIAddr != device.Address { + glog.V(2).Infof("GetOpenstackData(): PCI address for device %s does not match Nova metadata value %s, it'll be overwritten with %s", device.Mac, device.Address, realPCIAddr) + } + device.Address = realPCIAddr + } + return metaData, networkData, err } @@ -205,6 +227,33 @@ func getOpenstackDataFromMetadataService() (metaData *OSPMetaData, networkData * return metaData, networkData, nil } +// getPCIAddressFromMACAddress returns the PCI address of a device given its MAC address +func getPCIAddressFromMACAddress(macAddress string) (string, error) { + nics, err := ghw.Network() + if err != nil { + return "", fmt.Errorf("error getting network info: %w", err) + } + + var pciAddress string + for _, nic := range nics.NICs { + if strings.EqualFold(nic.MacAddress, macAddress) { + if pciAddress == "" { + pciAddress = *nic.PCIAddress + } else { + // Check if there are more than one device with the same MAC address + // and return an error if that's the case, we don't support that scenario for now. + return "", fmt.Errorf("more than one device found with MAC address %s", macAddress) + } + } + } + + if pciAddress != "" { + return pciAddress, nil + } + + return "", fmt.Errorf("no device found with MAC address %s", macAddress) +} + // CreateOpenstackDevicesInfo create the openstack device info map func CreateOpenstackDevicesInfo(metaData *OSPMetaData, networkData *OSPNetworkData) (OSPDevicesInfo, error) { glog.Infof("CreateOpenstackDevicesInfo()")