github.com/k8snetworkplumbingwg/sriov-network-operator@v1.2.1-0.20240408194816-2d2e5a45d453/pkg/platforms/openstack/openstack.go (about)

     1  package openstack
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/hashicorp/go-retryablehttp"
    12  	"github.com/jaypipes/ghw"
    13  	"github.com/jaypipes/ghw/pkg/net"
    14  	"sigs.k8s.io/controller-runtime/pkg/log"
    15  
    16  	dputils "github.com/k8snetworkplumbingwg/sriov-network-device-plugin/pkg/utils"
    17  
    18  	sriovnetworkv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1"
    19  	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts"
    20  	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host"
    21  )
    22  
    23  const (
    24  	ospHostMetaDataDir     = "/host/var/config/openstack/2018-08-27"
    25  	ospMetaDataDir         = "/var/config/openstack/2018-08-27"
    26  	ospMetaDataBaseURL     = "http://169.254.169.254/openstack/2018-08-27"
    27  	ospNetworkDataJSON     = "network_data.json"
    28  	ospMetaDataJSON        = "meta_data.json"
    29  	ospHostNetworkDataFile = ospHostMetaDataDir + "/" + ospNetworkDataJSON
    30  	ospHostMetaDataFile    = ospHostMetaDataDir + "/" + ospMetaDataJSON
    31  	ospNetworkDataURL      = ospMetaDataBaseURL + "/" + ospNetworkDataJSON
    32  	ospMetaDataURL         = ospMetaDataBaseURL + "/" + ospMetaDataJSON
    33  )
    34  
    35  var (
    36  	ospNetworkDataFile = ospMetaDataDir + "/" + ospNetworkDataJSON
    37  	ospMetaDataFile    = ospMetaDataDir + "/" + ospMetaDataJSON
    38  )
    39  
    40  //go:generate ../../../bin/mockgen -destination mock/mock_openstack.go -source openstack.go
    41  type OpenstackInterface interface {
    42  	CreateOpenstackDevicesInfo() error
    43  	CreateOpenstackDevicesInfoFromNodeStatus(*sriovnetworkv1.SriovNetworkNodeState)
    44  	DiscoverSriovDevicesVirtual() ([]sriovnetworkv1.InterfaceExt, error)
    45  }
    46  
    47  type openstackContext struct {
    48  	hostManager          host.HostManagerInterface
    49  	openStackDevicesInfo OSPDevicesInfo
    50  }
    51  
    52  // OSPMetaDataDevice -- Device structure within meta_data.json
    53  type OSPMetaDataDevice struct {
    54  	Vlan      int      `json:"vlan,omitempty"`
    55  	VfTrusted bool     `json:"vf_trusted,omitempty"`
    56  	Type      string   `json:"type,omitempty"`
    57  	Mac       string   `json:"mac,omitempty"`
    58  	Bus       string   `json:"bus,omitempty"`
    59  	Address   string   `json:"address,omitempty"`
    60  	Tags      []string `json:"tags,omitempty"`
    61  }
    62  
    63  // OSPMetaData -- Openstack meta_data.json format
    64  type OSPMetaData struct {
    65  	UUID             string              `json:"uuid,omitempty"`
    66  	AdminPass        string              `json:"admin_pass,omitempty"`
    67  	Name             string              `json:"name,omitempty"`
    68  	LaunchIndex      int                 `json:"launch_index,omitempty"`
    69  	AvailabilityZone string              `json:"availability_zone,omitempty"`
    70  	ProjectID        string              `json:"project_id,omitempty"`
    71  	Devices          []OSPMetaDataDevice `json:"devices,omitempty"`
    72  }
    73  
    74  // OSPNetworkLink OSP Link metadata
    75  type OSPNetworkLink struct {
    76  	ID          string `json:"id"`
    77  	VifID       string `json:"vif_id,omitempty"`
    78  	Type        string `json:"type"`
    79  	Mtu         int    `json:"mtu,omitempty"`
    80  	EthernetMac string `json:"ethernet_mac_address"`
    81  }
    82  
    83  // OSPNetwork OSP Network metadata
    84  type OSPNetwork struct {
    85  	ID        string `json:"id"`
    86  	Type      string `json:"type"`
    87  	Link      string `json:"link"`
    88  	NetworkID string `json:"network_id"`
    89  }
    90  
    91  // OSPNetworkData OSP Network metadata
    92  type OSPNetworkData struct {
    93  	Links    []OSPNetworkLink `json:"links,omitempty"`
    94  	Networks []OSPNetwork     `json:"networks,omitempty"`
    95  	// Omit Services
    96  }
    97  
    98  type OSPDevicesInfo map[string]*OSPDeviceInfo
    99  
   100  type OSPDeviceInfo struct {
   101  	MacAddress string
   102  	NetworkID  string
   103  }
   104  
   105  func New(hostManager host.HostManagerInterface) OpenstackInterface {
   106  	return &openstackContext{
   107  		hostManager: hostManager,
   108  	}
   109  }
   110  
   111  // GetOpenstackData gets the metadata and network_data
   112  func getOpenstackData(useHostPath bool) (metaData *OSPMetaData, networkData *OSPNetworkData, err error) {
   113  	metaData, networkData, err = getOpenstackDataFromConfigDrive(useHostPath)
   114  	if err != nil {
   115  		metaData, networkData, err = getOpenstackDataFromMetadataService()
   116  		if err != nil {
   117  			return metaData, networkData, fmt.Errorf("GetOpenStackData(): error getting OpenStack data: %w", err)
   118  		}
   119  	}
   120  
   121  	// We can't rely on the PCI address from the metadata so we will lookup the real PCI address
   122  	// for the NIC that matches the MAC address.
   123  	//
   124  	// Libvirt/QEMU cannot guarantee that the address specified in the XML will match the address seen by the guest.
   125  	// This is a well known limitation: https://libvirt.org/pci-addresses.html
   126  	// When using the q35 machine type, it highlights this issue due to the change from using PCI to PCI-E bus for virtual devices.
   127  	//
   128  	// With that said, the PCI value in Nova Metadata is a best effort hint due to the limitations mentioned above. Therefore
   129  	// we will lookup the real PCI address for the NIC that matches the MAC address.
   130  	netInfo, err := ghw.Network()
   131  	if err != nil {
   132  		return metaData, networkData, fmt.Errorf("GetOpenStackData(): error getting network info: %w", err)
   133  	}
   134  	for i, device := range metaData.Devices {
   135  		realPCIAddr, err := getPCIAddressFromMACAddress(device.Mac, netInfo.NICs)
   136  		if err != nil {
   137  			// If we can't find the PCI address, we will just print a warning, return the data as is with no error.
   138  			// In the future, we'll want to drain the node if sno-initial-node-state.json doesn't exist when daemon is restarted and when we have SR-IOV
   139  			// allocated devices already.
   140  			log.Log.Error(err, "Warning GetOpenstackData(): error getting PCI address for device",
   141  				"device-mac", device.Mac)
   142  			return metaData, networkData, nil
   143  		}
   144  		if realPCIAddr != device.Address {
   145  			log.Log.V(2).Info("GetOpenstackData(): PCI address for device does not match Nova metadata value, it'll be overwritten",
   146  				"device-mac", device.Mac,
   147  				"current-address", device.Address,
   148  				"overwrite-address", realPCIAddr)
   149  			metaData.Devices[i].Address = realPCIAddr
   150  		}
   151  	}
   152  
   153  	return metaData, networkData, err
   154  }
   155  
   156  // getOpenstackDataFromConfigDrive reads the meta_data and network_data files
   157  func getOpenstackDataFromConfigDrive(useHostPath bool) (metaData *OSPMetaData, networkData *OSPNetworkData, err error) {
   158  	metaData = &OSPMetaData{}
   159  	networkData = &OSPNetworkData{}
   160  	log.Log.Info("reading OpenStack meta_data from config-drive")
   161  	var metadataf *os.File
   162  	ospMetaDataFilePath := ospMetaDataFile
   163  	if useHostPath {
   164  		ospMetaDataFilePath = ospHostMetaDataFile
   165  	}
   166  	metadataf, err = os.Open(ospMetaDataFilePath)
   167  	if err != nil {
   168  		return metaData, networkData, fmt.Errorf("error opening file %s: %w", ospHostMetaDataFile, err)
   169  	}
   170  	defer func() {
   171  		if e := metadataf.Close(); err == nil && e != nil {
   172  			err = fmt.Errorf("error closing file %s: %w", ospHostMetaDataFile, e)
   173  		}
   174  	}()
   175  	if err = json.NewDecoder(metadataf).Decode(&metaData); err != nil {
   176  		return metaData, networkData, fmt.Errorf("error unmarshalling metadata from file %s: %w", ospHostMetaDataFile, err)
   177  	}
   178  
   179  	log.Log.Info("reading OpenStack network_data from config-drive")
   180  	var networkDataf *os.File
   181  	ospNetworkDataFilePath := ospNetworkDataFile
   182  	if useHostPath {
   183  		ospNetworkDataFilePath = ospHostNetworkDataFile
   184  	}
   185  	networkDataf, err = os.Open(ospNetworkDataFilePath)
   186  	if err != nil {
   187  		return metaData, networkData, fmt.Errorf("error opening file %s: %w", ospHostNetworkDataFile, err)
   188  	}
   189  	defer func() {
   190  		if e := networkDataf.Close(); err == nil && e != nil {
   191  			err = fmt.Errorf("error closing file %s: %w", ospHostNetworkDataFile, e)
   192  		}
   193  	}()
   194  	if err = json.NewDecoder(networkDataf).Decode(&networkData); err != nil {
   195  		return metaData, networkData, fmt.Errorf("error unmarshalling metadata from file %s: %w", ospHostNetworkDataFile, err)
   196  	}
   197  	return metaData, networkData, err
   198  }
   199  
   200  func getBodyFromURL(url string) ([]byte, error) {
   201  	log.Log.V(2).Info("Getting body from", "url", url)
   202  	resp, err := retryablehttp.Get(url)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  	rawBytes, err := io.ReadAll(resp.Body)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	defer resp.Body.Close()
   211  	return rawBytes, nil
   212  }
   213  
   214  // getOpenstackDataFromMetadataService fetchs the metadata and network_data from the metadata service
   215  func getOpenstackDataFromMetadataService() (metaData *OSPMetaData, networkData *OSPNetworkData, err error) {
   216  	metaData = &OSPMetaData{}
   217  	networkData = &OSPNetworkData{}
   218  	log.Log.Info("getting OpenStack meta_data from metadata server")
   219  	metaDataRawBytes, err := getBodyFromURL(ospMetaDataURL)
   220  	if err != nil {
   221  		return metaData, networkData, fmt.Errorf("error getting OpenStack meta_data from %s: %v", ospMetaDataURL, err)
   222  	}
   223  	err = json.Unmarshal(metaDataRawBytes, metaData)
   224  	if err != nil {
   225  		return metaData, networkData, fmt.Errorf("error unmarshalling raw bytes %v from %s", err, ospMetaDataURL)
   226  	}
   227  
   228  	log.Log.Info("getting OpenStack network_data from metadata server")
   229  	networkDataRawBytes, err := getBodyFromURL(ospNetworkDataURL)
   230  	if err != nil {
   231  		return metaData, networkData, fmt.Errorf("error getting OpenStack network_data from %s: %v", ospNetworkDataURL, err)
   232  	}
   233  	err = json.Unmarshal(networkDataRawBytes, networkData)
   234  	if err != nil {
   235  		return metaData, networkData, fmt.Errorf("error unmarshalling raw bytes %v from %s", err, ospNetworkDataURL)
   236  	}
   237  	return metaData, networkData, nil
   238  }
   239  
   240  // getPCIAddressFromMACAddress returns the PCI address of a device given its MAC address
   241  func getPCIAddressFromMACAddress(macAddress string, nics []*net.NIC) (string, error) {
   242  	var pciAddress string
   243  	for _, nic := range nics {
   244  		if strings.EqualFold(nic.MacAddress, macAddress) {
   245  			if pciAddress == "" {
   246  				pciAddress = *nic.PCIAddress
   247  			} else {
   248  				return "", fmt.Errorf("more than one device found with MAC address %s is unsupported", macAddress)
   249  			}
   250  		}
   251  	}
   252  
   253  	if pciAddress != "" {
   254  		return pciAddress, nil
   255  	}
   256  
   257  	return "", fmt.Errorf("no device found with MAC address %s", macAddress)
   258  }
   259  
   260  // CreateOpenstackDevicesInfo create the openstack device info map
   261  func (o *openstackContext) CreateOpenstackDevicesInfo() error {
   262  	log.Log.Info("CreateOpenstackDevicesInfo()")
   263  	devicesInfo := make(OSPDevicesInfo)
   264  
   265  	metaData, networkData, err := getOpenstackData(true)
   266  	if err != nil {
   267  		log.Log.Error(err, "failed to read OpenStack data")
   268  		return err
   269  	}
   270  
   271  	if metaData == nil || networkData == nil {
   272  		o.openStackDevicesInfo = make(OSPDevicesInfo)
   273  		return nil
   274  	}
   275  
   276  	// use this for hw pass throw interfaces
   277  	for _, device := range metaData.Devices {
   278  		for _, link := range networkData.Links {
   279  			if device.Mac == link.EthernetMac {
   280  				for _, network := range networkData.Networks {
   281  					if network.Link == link.ID {
   282  						networkID := sriovnetworkv1.OpenstackNetworkID.String() + ":" + network.NetworkID
   283  						devicesInfo[device.Address] = &OSPDeviceInfo{MacAddress: device.Mac, NetworkID: networkID}
   284  					}
   285  				}
   286  			}
   287  		}
   288  	}
   289  
   290  	// for vhostuser interface type we check the interfaces on the node
   291  	pci, err := ghw.PCI()
   292  	if err != nil {
   293  		return fmt.Errorf("CreateOpenstackDevicesInfo(): error getting PCI info: %v", err)
   294  	}
   295  
   296  	devices := pci.ListDevices()
   297  	if len(devices) == 0 {
   298  		return fmt.Errorf("CreateOpenstackDevicesInfo(): could not retrieve PCI devices")
   299  	}
   300  
   301  	for _, device := range devices {
   302  		if _, exist := devicesInfo[device.Address]; exist {
   303  			//we already discover the device via openstack metadata
   304  			continue
   305  		}
   306  
   307  		devClass, err := strconv.ParseInt(device.Class.ID, 16, 64)
   308  		if err != nil {
   309  			log.Log.Error(err, "CreateOpenstackDevicesInfo(): unable to parse device class for device, skipping",
   310  				"device", device)
   311  			continue
   312  		}
   313  		if devClass != consts.NetClass {
   314  			// Not network device
   315  			continue
   316  		}
   317  
   318  		macAddress := ""
   319  		if name := o.hostManager.TryToGetVirtualInterfaceName(device.Address); name != "" {
   320  			if mac := o.hostManager.GetNetDevMac(name); mac != "" {
   321  				macAddress = mac
   322  			}
   323  		}
   324  		if macAddress == "" {
   325  			// we didn't manage to find a mac address for the nic skipping
   326  			continue
   327  		}
   328  
   329  		for _, link := range networkData.Links {
   330  			if macAddress == link.EthernetMac {
   331  				for _, network := range networkData.Networks {
   332  					if network.Link == link.ID {
   333  						networkID := sriovnetworkv1.OpenstackNetworkID.String() + ":" + network.NetworkID
   334  						devicesInfo[device.Address] = &OSPDeviceInfo{MacAddress: macAddress, NetworkID: networkID}
   335  					}
   336  				}
   337  			}
   338  		}
   339  	}
   340  
   341  	o.openStackDevicesInfo = devicesInfo
   342  	return nil
   343  }
   344  
   345  // DiscoverSriovDevicesVirtual discovers VFs on a virtual platform
   346  func (o *openstackContext) DiscoverSriovDevicesVirtual() ([]sriovnetworkv1.InterfaceExt, error) {
   347  	log.Log.V(2).Info("DiscoverSriovDevicesVirtual()")
   348  	pfList := []sriovnetworkv1.InterfaceExt{}
   349  
   350  	pci, err := ghw.PCI()
   351  	if err != nil {
   352  		return nil, fmt.Errorf("DiscoverSriovDevicesVirtual(): error getting PCI info: %v", err)
   353  	}
   354  
   355  	devices := pci.ListDevices()
   356  	if len(devices) == 0 {
   357  		return nil, fmt.Errorf("DiscoverSriovDevicesVirtual(): could not retrieve PCI devices")
   358  	}
   359  
   360  	for _, device := range devices {
   361  		devClass, err := strconv.ParseInt(device.Class.ID, 16, 64)
   362  		if err != nil {
   363  			log.Log.Error(err, "DiscoverSriovDevicesVirtual(): unable to parse device class for device, skipping",
   364  				"device", device)
   365  			continue
   366  		}
   367  		if devClass != consts.NetClass {
   368  			// Not network device
   369  			continue
   370  		}
   371  
   372  		deviceInfo, exist := o.openStackDevicesInfo[device.Address]
   373  		if !exist {
   374  			log.Log.Error(nil, "DiscoverSriovDevicesVirtual(): unable to find device in devicesInfo list, skipping",
   375  				"device", device.Address)
   376  			continue
   377  		}
   378  		netFilter := deviceInfo.NetworkID
   379  		metaMac := deviceInfo.MacAddress
   380  
   381  		driver, err := dputils.GetDriverName(device.Address)
   382  		if err != nil {
   383  			log.Log.Error(err, "DiscoverSriovDevicesVirtual(): unable to parse device driver for device, skipping",
   384  				"device", device)
   385  			continue
   386  		}
   387  		iface := sriovnetworkv1.InterfaceExt{
   388  			PciAddress: device.Address,
   389  			Driver:     driver,
   390  			Vendor:     device.Vendor.ID,
   391  			DeviceID:   device.Product.ID,
   392  			NetFilter:  netFilter,
   393  		}
   394  		if mtu := o.hostManager.GetNetdevMTU(device.Address); mtu > 0 {
   395  			iface.Mtu = mtu
   396  		}
   397  		if name := o.hostManager.TryToGetVirtualInterfaceName(device.Address); name != "" {
   398  			iface.Name = name
   399  			if iface.Mac = o.hostManager.GetNetDevMac(name); iface.Mac == "" {
   400  				iface.Mac = metaMac
   401  			}
   402  			iface.LinkSpeed = o.hostManager.GetNetDevLinkSpeed(name)
   403  			iface.LinkType = o.hostManager.GetLinkType(name)
   404  		}
   405  
   406  		iface.TotalVfs = 1
   407  		iface.NumVfs = 1
   408  
   409  		vf := sriovnetworkv1.VirtualFunction{
   410  			PciAddress: device.Address,
   411  			Driver:     driver,
   412  			VfID:       0,
   413  			Vendor:     iface.Vendor,
   414  			DeviceID:   iface.DeviceID,
   415  			Mtu:        iface.Mtu,
   416  			Mac:        iface.Mac,
   417  		}
   418  		iface.VFs = append(iface.VFs, vf)
   419  
   420  		pfList = append(pfList, iface)
   421  	}
   422  	return pfList, nil
   423  }
   424  
   425  func (o *openstackContext) CreateOpenstackDevicesInfoFromNodeStatus(networkState *sriovnetworkv1.SriovNetworkNodeState) {
   426  	devicesInfo := make(OSPDevicesInfo)
   427  	for _, iface := range networkState.Status.Interfaces {
   428  		devicesInfo[iface.PciAddress] = &OSPDeviceInfo{MacAddress: iface.Mac, NetworkID: iface.NetFilter}
   429  	}
   430  
   431  	o.openStackDevicesInfo = devicesInfo
   432  }