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  }