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 }