github.com/k8snetworkplumbingwg/sriov-network-operator@v1.2.1-0.20240408194816-2d2e5a45d453/pkg/vendors/mellanox/mellanox.go (about) 1 package mlxutils 2 3 import ( 4 "fmt" 5 "regexp" 6 "strconv" 7 "strings" 8 9 "sigs.k8s.io/controller-runtime/pkg/log" 10 11 sriovnetworkv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1" 12 "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts" 13 "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/utils" 14 ) 15 16 // BlueField mode representation 17 type BlueFieldMode int 18 19 const ( 20 BluefieldDpu BlueFieldMode = iota 21 BluefieldConnectXMode 22 23 internalCPUPageSupplier = "INTERNAL_CPU_PAGE_SUPPLIER" 24 internalCPUEswitchManager = "INTERNAL_CPU_ESWITCH_MANAGER" 25 internalCPUIbVporto = "INTERNAL_CPU_IB_VPORT0" 26 internalCPUOffloadEngine = "INTERNAL_CPU_OFFLOAD_ENGINE" 27 internalCPUModel = "INTERNAL_CPU_MODEL" 28 29 ecpf = "ECPF" 30 extHostPf = "EXT_HOST_PF" 31 embeddedCPU = "EMBEDDED_CPU" 32 33 disabled = "DISABLED" 34 enabled = "ENABLED" 35 36 VendorMellanox = "15b3" 37 DeviceBF2 = "a2d6" 38 DeviceBF3 = "a2dc" 39 40 PreconfiguredLinkType = "Preconfigured" 41 UknownLinkType = "Uknown" 42 TotalVfs = "NUM_OF_VFS" 43 EnableSriov = "SRIOV_EN" 44 LinkTypeP1 = "LINK_TYPE_P1" 45 LinkTypeP2 = "LINK_TYPE_P2" 46 MellanoxVendorID = "15b3" 47 ) 48 49 type MlxNic struct { 50 EnableSriov bool 51 TotalVfs int 52 LinkTypeP1 string 53 LinkTypeP2 string 54 } 55 56 //go:generate ../../../bin/mockgen -destination mock/mock_mellanox.go -source mellanox.go 57 type MellanoxInterface interface { 58 MstConfigReadData(string) (string, string, error) 59 GetMellanoxBlueFieldMode(string) (BlueFieldMode, error) 60 GetMlxNicFwData(pciAddress string) (current, next *MlxNic, err error) 61 62 MlxConfigFW(attributesToChange map[string]MlxNic) error 63 } 64 65 type mellanoxHelper struct { 66 utils utils.CmdInterface 67 } 68 69 func New(utilsHelper utils.CmdInterface) MellanoxInterface { 70 return &mellanoxHelper{ 71 utils: utilsHelper, 72 } 73 } 74 75 func (m *mellanoxHelper) MstConfigReadData(pciAddress string) (string, string, error) { 76 log.Log.Info("MstConfigReadData()", "device", pciAddress) 77 args := []string{"-e", "-d", pciAddress, "q"} 78 stdout, stderr, err := m.utils.RunCommand("mstconfig", args...) 79 return stdout, stderr, err 80 } 81 82 func (m *mellanoxHelper) GetMellanoxBlueFieldMode(PciAddress string) (BlueFieldMode, error) { 83 log.Log.V(2).Info("MellanoxBlueFieldMode(): checking mode for device", "device", PciAddress) 84 stdout, stderr, err := m.MstConfigReadData(PciAddress) 85 if err != nil { 86 log.Log.Error(err, "MellanoxBlueFieldMode(): failed to get mlx nic fw data", "stderr", stderr) 87 return -1, fmt.Errorf("failed to get mlx nic fw data %w", err) 88 } 89 90 attrs := []string{internalCPUPageSupplier, 91 internalCPUEswitchManager, 92 internalCPUIbVporto, 93 internalCPUOffloadEngine, 94 internalCPUModel} 95 mstCurrentData, _ := ParseMstconfigOutput(stdout, attrs) 96 97 internalCPUPageSupplierstatus, exist := mstCurrentData[internalCPUPageSupplier] 98 if !exist { 99 return 0, fmt.Errorf("failed to find %s in the mstconfig output command", internalCPUPageSupplier) 100 } 101 102 internalCPUEswitchManagerStatus, exist := mstCurrentData[internalCPUEswitchManager] 103 if !exist { 104 return 0, fmt.Errorf("failed to find %s in the mstconfig output command", internalCPUEswitchManager) 105 } 106 107 internalCPUIbVportoStatus, exist := mstCurrentData[internalCPUIbVporto] 108 if !exist { 109 return 0, fmt.Errorf("failed to find %s in the mstconfig output command", internalCPUIbVporto) 110 } 111 112 internalCPUOffloadEngineStatus, exist := mstCurrentData[internalCPUOffloadEngine] 113 if !exist { 114 return 0, fmt.Errorf("failed to find %s in the mstconfig output command", internalCPUOffloadEngine) 115 } 116 117 internalCPUModelStatus, exist := mstCurrentData[internalCPUModel] 118 if !exist { 119 return 0, fmt.Errorf("failed to find %s in the mstconfig output command", internalCPUModel) 120 } 121 122 // check for DPU 123 if strings.Contains(internalCPUPageSupplierstatus, ecpf) && 124 strings.Contains(internalCPUEswitchManagerStatus, ecpf) && 125 strings.Contains(internalCPUIbVportoStatus, ecpf) && 126 strings.Contains(internalCPUOffloadEngineStatus, enabled) && 127 strings.Contains(internalCPUModelStatus, embeddedCPU) { 128 log.Log.V(2).Info("MellanoxBlueFieldMode(): device in DPU mode", "device", PciAddress) 129 return BluefieldDpu, nil 130 } else if strings.Contains(internalCPUPageSupplierstatus, extHostPf) && 131 strings.Contains(internalCPUEswitchManagerStatus, extHostPf) && 132 strings.Contains(internalCPUIbVportoStatus, extHostPf) && 133 strings.Contains(internalCPUOffloadEngineStatus, disabled) && 134 strings.Contains(internalCPUModelStatus, embeddedCPU) { 135 log.Log.V(2).Info("MellanoxBlueFieldMode(): device in ConnectX mode", "device", PciAddress) 136 return BluefieldConnectXMode, nil 137 } 138 139 log.Log.Error(err, "MellanoxBlueFieldMode(): unknown device status", 140 "device", PciAddress, "mstconfig-output", stdout) 141 return -1, fmt.Errorf("MellanoxBlueFieldMode(): unknown device status for %s", PciAddress) 142 } 143 144 func (m *mellanoxHelper) MlxConfigFW(attributesToChange map[string]MlxNic) error { 145 log.Log.Info("mellanox-plugin configFW()") 146 for pciAddr, fwArgs := range attributesToChange { 147 cmdArgs := []string{"-d", pciAddr, "-y", "set"} 148 if fwArgs.EnableSriov { 149 cmdArgs = append(cmdArgs, fmt.Sprintf("%s=True", EnableSriov)) 150 } else if fwArgs.TotalVfs == 0 { 151 cmdArgs = append(cmdArgs, fmt.Sprintf("%s=False", EnableSriov)) 152 } 153 if fwArgs.TotalVfs > -1 { 154 cmdArgs = append(cmdArgs, fmt.Sprintf("%s=%d", TotalVfs, fwArgs.TotalVfs)) 155 } 156 if len(fwArgs.LinkTypeP1) > 0 { 157 cmdArgs = append(cmdArgs, fmt.Sprintf("%s=%s", LinkTypeP1, fwArgs.LinkTypeP1)) 158 } 159 if len(fwArgs.LinkTypeP2) > 0 { 160 cmdArgs = append(cmdArgs, fmt.Sprintf("%s=%s", LinkTypeP2, fwArgs.LinkTypeP2)) 161 } 162 163 log.Log.V(2).Info("mellanox-plugin: configFW()", "cmd-args", cmdArgs) 164 if len(cmdArgs) <= 4 { 165 continue 166 } 167 _, strerr, err := m.utils.RunCommand("mstconfig", cmdArgs...) 168 if err != nil { 169 log.Log.Error(err, "mellanox-plugin configFW(): failed", "stderr", strerr) 170 return err 171 } 172 } 173 return nil 174 } 175 176 func (m *mellanoxHelper) GetMlxNicFwData(pciAddress string) (current, next *MlxNic, err error) { 177 log.Log.Info("mellanox-plugin getMlnxNicFwData()", "device", pciAddress) 178 attrs := []string{TotalVfs, EnableSriov, LinkTypeP1, LinkTypeP2} 179 180 out, stderr, err := m.MstConfigReadData(pciAddress) 181 if err != nil { 182 log.Log.Error(err, "mellanox-plugin getMlnxNicFwData(): failed", "stderr", stderr) 183 return 184 } 185 mstCurrentData, mstNextData := ParseMstconfigOutput(out, attrs) 186 current, err = mlnxNicFromMap(mstCurrentData) 187 if err != nil { 188 log.Log.Error(err, "mellanox-plugin mlnxNicFromMap() for current mstconfig data failed") 189 return 190 } 191 next, err = mlnxNicFromMap(mstNextData) 192 if err != nil { 193 log.Log.Error(err, "mellanox-plugin mlnxNicFromMap() for next mstconfig data failed") 194 } 195 return 196 } 197 198 func ParseMstconfigOutput(mstOutput string, attributes []string) (fwCurrent, fwNext map[string]string) { 199 log.Log.Info("ParseMstconfigOutput()", "attributes", attributes) 200 fwCurrent = map[string]string{} 201 fwNext = map[string]string{} 202 formatRegex := regexp.MustCompile(`(?P<Attribute>\w+)\s+(?P<Default>\S+)\s+(?P<Current>\S+)\s+(?P<Next>\S+)`) 203 mstOutputLines := strings.Split(mstOutput, "\n") 204 for _, attr := range attributes { 205 for _, line := range mstOutputLines { 206 if strings.Contains(line, attr) { 207 regexResult := formatRegex.FindStringSubmatch(line) 208 fwCurrent[attr] = regexResult[3] 209 fwNext[attr] = regexResult[4] 210 break 211 } 212 } 213 } 214 return 215 } 216 217 func HasMellanoxInterfacesInSpec(ifaceStatuses sriovnetworkv1.InterfaceExts, ifaceSpecs sriovnetworkv1.Interfaces) bool { 218 for _, ifaceStatus := range ifaceStatuses { 219 if ifaceStatus.Vendor == VendorMellanox { 220 for _, iface := range ifaceSpecs { 221 if iface.PciAddress == ifaceStatus.PciAddress { 222 log.Log.V(2).Info("hasMellanoxInterfacesInSpec(): Mellanox device specified in SriovNetworkNodeState spec", 223 "name", ifaceStatus.Name, 224 "address", ifaceStatus.PciAddress) 225 return true 226 } 227 } 228 } 229 } 230 return false 231 } 232 233 func GetPciAddressPrefix(pciAddress string) string { 234 return pciAddress[:len(pciAddress)-1] 235 } 236 237 func IsDualPort(pciAddress string, mellanoxNicsStatus map[string]map[string]sriovnetworkv1.InterfaceExt) bool { 238 log.Log.Info("mellanox-plugin IsDualPort()", "address", pciAddress) 239 pciAddressPrefix := GetPciAddressPrefix(pciAddress) 240 return len(mellanoxNicsStatus[pciAddressPrefix]) > 1 241 } 242 243 // handleTotalVfs return required total VFs or max (required VFs for dual port NIC) and needReboot if totalVfs will change 244 func HandleTotalVfs(fwCurrent, fwNext, attrs *MlxNic, ifaceSpec sriovnetworkv1.Interface, isDualPort bool, mellanoxNicsSpec map[string]sriovnetworkv1.Interface) ( 245 totalVfs int, needReboot, changeWithoutReboot bool) { 246 totalVfs = ifaceSpec.NumVfs 247 // Check if the other port is changing theGetMlnxNicFwData number of VF 248 if isDualPort { 249 otherIfaceSpec := getOtherPortSpec(ifaceSpec.PciAddress, mellanoxNicsSpec) 250 if otherIfaceSpec != nil { 251 if otherIfaceSpec.NumVfs > totalVfs { 252 totalVfs = otherIfaceSpec.NumVfs 253 } 254 } 255 } 256 257 // if the PF is externally managed we just need to check the totalVfs requested in the policy is not higher than 258 // the configured amount 259 if ifaceSpec.ExternallyManaged { 260 if totalVfs > fwCurrent.TotalVfs { 261 log.Log.Error(nil, "The nic is externallyManaged and TotalVfs configured on the system is lower then requested VFs, failing configuration", 262 "current", fwCurrent.TotalVfs, "requested", totalVfs) 263 attrs.TotalVfs = totalVfs 264 needReboot = true 265 changeWithoutReboot = false 266 } 267 return 268 } 269 270 if fwCurrent.TotalVfs != totalVfs { 271 log.Log.V(2).Info("Changing TotalVfs, needs reboot", "current", fwCurrent.TotalVfs, "requested", totalVfs) 272 attrs.TotalVfs = totalVfs 273 needReboot = true 274 } 275 276 // Remove policy then re-apply it 277 if !needReboot && fwNext.TotalVfs != totalVfs { 278 log.Log.V(2).Info("Changing TotalVfs to same as Next Boot value, doesn't require rebooting", 279 "current", fwCurrent.TotalVfs, "next", fwNext.TotalVfs, "requested", totalVfs) 280 attrs.TotalVfs = totalVfs 281 changeWithoutReboot = true 282 } 283 284 return 285 } 286 287 // handleEnableSriov based on totalVfs it decide to disable (totalVfs=0) or enable (totalVfs changed from 0) sriov 288 // and need reboot if enableSriov will change 289 func HandleEnableSriov(totalVfs int, fwCurrent, fwNext, attrs *MlxNic) (needReboot, changeWithoutReboot bool) { 290 if totalVfs == 0 && fwCurrent.EnableSriov { 291 log.Log.V(2).Info("disabling Sriov, needs reboot") 292 attrs.EnableSriov = false 293 return true, false 294 } else if totalVfs > 0 && !fwCurrent.EnableSriov { 295 log.Log.V(2).Info("enabling Sriov, needs reboot") 296 attrs.EnableSriov = true 297 return true, false 298 } else if totalVfs > 0 && !fwNext.EnableSriov { 299 attrs.EnableSriov = true 300 return false, true 301 } 302 303 return false, false 304 } 305 306 // handleLinkType based on existing linkType and requested link 307 func HandleLinkType(pciPrefix string, fwData, attr *MlxNic, 308 mellanoxNicsSpec map[string]sriovnetworkv1.Interface, 309 mellanoxNicsStatus map[string]map[string]sriovnetworkv1.InterfaceExt) (bool, error) { 310 needReboot := false 311 312 pciAddress := pciPrefix + "0" 313 if firstPortSpec, ok := mellanoxNicsSpec[pciAddress]; ok { 314 ifaceStatus := getIfaceStatus(pciAddress, mellanoxNicsStatus) 315 needChange, err := isLinkTypeRequireChange(firstPortSpec, ifaceStatus, fwData.LinkTypeP1) 316 if err != nil { 317 return false, err 318 } 319 320 if needChange { 321 log.Log.V(2).Info("Changing LinkTypeP1, needs reboot", 322 "from", fwData.LinkTypeP1, "to", firstPortSpec.LinkType) 323 attr.LinkTypeP1 = firstPortSpec.LinkType 324 needReboot = true 325 } 326 } 327 328 pciAddress = pciPrefix + "1" 329 if secondPortSpec, ok := mellanoxNicsSpec[pciAddress]; ok { 330 ifaceStatus := getIfaceStatus(pciAddress, mellanoxNicsStatus) 331 needChange, err := isLinkTypeRequireChange(secondPortSpec, ifaceStatus, fwData.LinkTypeP2) 332 if err != nil { 333 return false, err 334 } 335 336 if needChange { 337 log.Log.V(2).Info("Changing LinkTypeP2, needs reboot", 338 "from", fwData.LinkTypeP2, "to", secondPortSpec.LinkType) 339 attr.LinkTypeP2 = secondPortSpec.LinkType 340 needReboot = true 341 } 342 } 343 344 return needReboot, nil 345 } 346 347 func mlnxNicFromMap(mstData map[string]string) (*MlxNic, error) { 348 log.Log.Info("mellanox-plugin mlnxNicFromMap()", "data", mstData) 349 fwData := &MlxNic{} 350 if strings.Contains(mstData[EnableSriov], "True") { 351 fwData.EnableSriov = true 352 } 353 i, err := strconv.Atoi(mstData[TotalVfs]) 354 if err != nil { 355 return nil, err 356 } 357 358 fwData.TotalVfs = i 359 fwData.LinkTypeP1 = getLinkType(mstData[LinkTypeP1]) 360 if linkTypeP2, ok := mstData[LinkTypeP2]; ok { 361 fwData.LinkTypeP2 = getLinkType(linkTypeP2) 362 } 363 364 return fwData, nil 365 } 366 367 func getLinkType(linkType string) string { 368 log.Log.Info("mellanox-plugin getLinkType()", "link-type", linkType) 369 if strings.Contains(linkType, consts.LinkTypeETH) { 370 return consts.LinkTypeETH 371 } else if strings.Contains(linkType, consts.LinkTypeIB) { 372 return consts.LinkTypeIB 373 } else if len(linkType) > 0 { 374 log.Log.Error(nil, "mellanox-plugin getLinkType(): link type is not one of [ETH, IB], treating as unknown", 375 "link-type", linkType) 376 return UknownLinkType 377 } else { 378 log.Log.Info("mellanox-plugin getLinkType(): LINK_TYPE_P* attribute was not found, treating as preconfigured link type") 379 return PreconfiguredLinkType 380 } 381 } 382 383 func isLinkTypeRequireChange(iface sriovnetworkv1.Interface, ifaceStatus sriovnetworkv1.InterfaceExt, fwLinkType string) (bool, error) { 384 log.Log.Info("mellanox-plugin isLinkTypeRequireChange()", "device", iface.PciAddress) 385 if iface.LinkType != "" && !strings.EqualFold(ifaceStatus.LinkType, iface.LinkType) { 386 if !strings.EqualFold(iface.LinkType, consts.LinkTypeETH) && !strings.EqualFold(iface.LinkType, consts.LinkTypeIB) { 387 return false, fmt.Errorf("mellanox-plugin OnNodeStateChange(): Not supported link type: %s,"+ 388 " supported link types: [eth, ETH, ib, and IB]", iface.LinkType) 389 } 390 if fwLinkType == UknownLinkType { 391 return false, fmt.Errorf("mellanox-plugin OnNodeStateChange(): Unknown link type: %s", fwLinkType) 392 } 393 if fwLinkType == PreconfiguredLinkType { 394 return false, fmt.Errorf("mellanox-plugin OnNodeStateChange(): Network card %s does not support link type change", iface.PciAddress) 395 } 396 397 return true, nil 398 } 399 400 return false, nil 401 } 402 403 func getOtherPortSpec(pciAddress string, mellanoxNicsSpec map[string]sriovnetworkv1.Interface) *sriovnetworkv1.Interface { 404 log.Log.Info("mellanox-plugin getOtherPortSpec()", "pciAddress", pciAddress) 405 pciAddrPrefix := GetPciAddressPrefix(pciAddress) 406 pciAddrSuffix := pciAddress[len(pciAddrPrefix):] 407 408 if pciAddrSuffix == "0" { 409 iface := mellanoxNicsSpec[pciAddrPrefix+"1"] 410 return &iface 411 } 412 413 iface := mellanoxNicsSpec[pciAddrPrefix+"0"] 414 return &iface 415 } 416 417 func getIfaceStatus(pciAddress string, mellanoxNicsStatus map[string]map[string]sriovnetworkv1.InterfaceExt) sriovnetworkv1.InterfaceExt { 418 return mellanoxNicsStatus[GetPciAddressPrefix(pciAddress)][pciAddress] 419 }