[lxc-devel] [lxd/master] lxd/device/nic/sriov: Updates networkGetVirtFuncInfo to use json output from ip tool

tomponline on Github lxc-bot at linuxcontainers.org
Thu Apr 9 12:19:29 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 474 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200409/8b48cdd2/attachment.bin>
-------------- next part --------------
From e7d98a1f36899fc43badf89f80c0fe8befab01c0 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 9 Apr 2020 13:18:11 +0100
Subject: [PATCH] lxd/device/nic/sriov: Updates networkGetVirtFuncInfo to use
 json output from ip tool

Also adds support for newer versions of the ip tool that use "address" rather than "MAC" field for VF hwaddr.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/nic_sriov.go | 172 ++++++++++++++++++++++++++++++----------
 1 file changed, 129 insertions(+), 43 deletions(-)

diff --git a/lxd/device/nic_sriov.go b/lxd/device/nic_sriov.go
index d725db3ca1..8cba0fd09e 100644
--- a/lxd/device/nic_sriov.go
+++ b/lxd/device/nic_sriov.go
@@ -3,7 +3,9 @@ package device
 import (
 	"bufio"
 	"bytes"
+	"encoding/json"
 	"fmt"
+	"io"
 	"io/ioutil"
 	"os/exec"
 	"regexp"
@@ -352,10 +354,10 @@ func (d *nicSRIOV) setupSriovParent(vfDevice string, vfID int, volatile map[stri
 	defer revert.Fail()
 
 	// Record properties of VF settings on the parent device.
-	volatile["last_state.vf.hwaddr"] = vfInfo.mac
+	volatile["last_state.vf.hwaddr"] = vfInfo.Address
 	volatile["last_state.vf.id"] = fmt.Sprintf("%d", vfID)
-	volatile["last_state.vf.vlan"] = fmt.Sprintf("%d", vfInfo.vlan)
-	volatile["last_state.vf.spoofcheck"] = fmt.Sprintf("%t", vfInfo.spoofcheck)
+	volatile["last_state.vf.vlan"] = fmt.Sprintf("%d", vfInfo.VLANs[0]["vlan"])
+	volatile["last_state.vf.spoofcheck"] = fmt.Sprintf("%t", vfInfo.SpoofCheck)
 
 	// Record the host interface we represents the VF device which we will move into instance.
 	volatile["host_name"] = vfDevice
@@ -480,65 +482,149 @@ func (d *nicSRIOV) setupSriovParent(vfDevice string, vfID int, volatile map[stri
 	return vfPCIDev, nil
 }
 
-// virtFuncInfo holds information about SR-IOV virtual functions.
-type virtFuncInfo struct {
-	mac        string
-	vlan       int
-	spoofcheck bool
+// VirtFuncInfo holds information about SR-IOV virtual functions.
+type VirtFuncInfo struct {
+	VF         int              `json:"vf"`
+	Address    string           `json:"address"`
+	MAC        string           `json:"mac"` // Deprecated
+	VLANs      []map[string]int `json:"vlan_list"`
+	SpoofCheck bool             `json:"spoofchk"`
 }
 
 // networkGetVirtFuncInfo returns info about an SR-IOV virtual function from the ip tool.
-func (d *nicSRIOV) networkGetVirtFuncInfo(devName string, vfID int) (vf virtFuncInfo, err error) {
-	cmd := exec.Command("ip", "link", "show", devName)
-	stdout, err := cmd.StdoutPipe()
-	if err != nil {
-		return
-	}
+func (d *nicSRIOV) networkGetVirtFuncInfo(devName string, vfID int) (VirtFuncInfo, error) {
+	vf := VirtFuncInfo{}
+	vfNotFoundErr := fmt.Errorf("no matching virtual function found")
 
-	err = cmd.Start()
+	ipPath, err := exec.LookPath("ip")
 	if err != nil {
-		return
+		return vf, fmt.Errorf("ip command not found")
 	}
-	defer stdout.Close()
 
-	// Try and match: "vf 1 MAC 00:00:00:00:00:00, vlan 4095, spoof checking off"
-	reVlan := regexp.MustCompile(fmt.Sprintf(`vf %d MAC ((?:[[:xdigit:]]{2}:){5}[[:xdigit:]]{2}).*, vlan (\d+), spoof checking (\w+)`, vfID))
-
-	// IP link command doesn't show the vlan property if its set to 0, so we need to detect that.
-	// Try and match: "vf 1 MAC 00:00:00:00:00:00, spoof checking off"
-	reNoVlan := regexp.MustCompile(fmt.Sprintf(`vf %d MAC ((?:[[:xdigit:]]{2}:){5}[[:xdigit:]]{2}).*, spoof checking (\w+)`, vfID))
-	scanner := bufio.NewScanner(stdout)
-	for scanner.Scan() {
-		// First try and find VF and reads its properties with VLAN activated.
-		res := reVlan.FindStringSubmatch(scanner.Text())
-		if len(res) == 4 {
-			vlan, err := strconv.Atoi(res[2])
-			if err != nil {
+	// Function to get VF info using regex matching, for older versions of ip tool. Less reliable.
+	vfFindByRegex := func(devName string, vfID int) (VirtFuncInfo, error) {
+		cmd := exec.Command(ipPath, "link", "show", devName)
+		stdout, err := cmd.StdoutPipe()
+		if err != nil {
+			return vf, err
+		}
+		defer stdout.Close()
+
+		err = cmd.Start()
+		if err != nil {
+			return vf, err
+		}
+		defer cmd.Wait()
+
+		// Try and match: "vf 1 MAC 00:00:00:00:00:00, vlan 4095, spoof checking off"
+		reVlan := regexp.MustCompile(fmt.Sprintf(`vf %d MAC ((?:[[:xdigit:]]{2}:){5}[[:xdigit:]]{2}).*, vlan (\d+), spoof checking (\w+)`, vfID))
+
+		// IP link command doesn't show the vlan property if its set to 0, so we need to detect that.
+		// Try and match: "vf 1 MAC 00:00:00:00:00:00, spoof checking off"
+		reNoVlan := regexp.MustCompile(fmt.Sprintf(`vf %d MAC ((?:[[:xdigit:]]{2}:){5}[[:xdigit:]]{2}).*, spoof checking (\w+)`, vfID))
+		scanner := bufio.NewScanner(stdout)
+		for scanner.Scan() {
+			// First try and find VF and read its properties with VLAN activated.
+			res := reVlan.FindStringSubmatch(scanner.Text())
+			if len(res) == 4 {
+				vlan, err := strconv.Atoi(res[2])
+				if err != nil {
+					return vf, err
+				}
+
+				vf.Address = res[1]
+				vf.VLANs = append(vf.VLANs, map[string]int{"vlan": vlan})
+				vf.SpoofCheck = shared.IsTrue(res[3])
+
 				return vf, err
 			}
 
-			vf.mac = res[1]
-			vf.vlan = vlan
-			vf.spoofcheck = shared.IsTrue(res[3])
-			return vf, err
+			// Next try and find VF and read its properties with VLAN missing.
+			res = reNoVlan.FindStringSubmatch(scanner.Text())
+			if len(res) == 3 {
+				vf.Address = res[1]
+				// Missing VLAN ID means 0 when resetting later.
+				vf.VLANs = append(vf.VLANs, map[string]int{"vlan": 0})
+				vf.SpoofCheck = shared.IsTrue(res[2])
+
+				return vf, err
+			}
 		}
 
-		// Next try and find VF and reads its properties with VLAN missing.
-		res = reNoVlan.FindStringSubmatch(scanner.Text())
-		if len(res) == 3 {
-			vf.mac = res[1]
-			vf.vlan = 0 // Missing VLAN ID means 0 when resetting later.
-			vf.spoofcheck = shared.IsTrue(res[2])
+		err = scanner.Err()
+		if err != nil {
 			return vf, err
 		}
+
+		return vf, vfNotFoundErr
 	}
 
-	err = scanner.Err()
+	// First try using the JSON output format as is more reliable to parse.
+	cmd := exec.Command(ipPath, "-j", "link", "show", devName)
+	stdout, err := cmd.StdoutPipe()
 	if err != nil {
-		return
+		return vf, err
+	}
+	defer stdout.Close()
+
+	err = cmd.Start()
+	if err != nil {
+		return vf, err
+	}
+	defer cmd.Wait()
+
+	// Temporary struct to decode ip output into.
+	var ifInfo []struct {
+		VFList []VirtFuncInfo `json:"vfinfo_list"`
+	}
+
+	// Decode JSON output.
+	dec := json.NewDecoder(stdout)
+	err = dec.Decode(&ifInfo)
+	if err != nil && err != io.EOF {
+		return vf, err
+	}
+
+	err = cmd.Wait()
+	if err != nil {
+		// If JSON command fails, fallback to using regex match mode for older versions of ip tool.
+		// This does not support the newer VF "link/ether" output prefix.
+		return vfFindByRegex(devName, vfID)
+	}
+
+	if len(ifInfo) == 0 {
+		return vf, vfNotFoundErr
+	}
+
+	// Search VFs returned for match.
+	found := false
+	for _, vfInfo := range ifInfo[0].VFList {
+		if vfInfo.VF == vfID {
+			vf = vfInfo // Found a match.
+			found = true
+		}
+	}
+
+	if !found {
+		return vf, vfNotFoundErr
+	}
+
+	// Always populate VLANs slice if not already populated. Missing VLAN ID means 0 when resetting later.
+	if len(vf.VLANs) == 0 {
+		vf.VLANs = append(vf.VLANs, map[string]int{"vlan": 0})
+	}
+
+	// Ensure empty VLAN entry is consistently populated.
+	if _, found = vf.VLANs[0]["vlan"]; !found {
+		vf.VLANs[0]["vlan"] = 0
+	}
+
+	// If ip tool has provided old mac field, copy into newer address field.
+	if vf.MAC != "" && vf.Address == "" {
+		vf.Address = vf.MAC
 	}
 
-	return vf, fmt.Errorf("no matching virtual function found")
+	return vf, nil
 }
 
 // networkGetVFDevicePCISlot returns the PCI slot name for a network virtual function device.


More information about the lxc-devel mailing list