Skip to content

Commit c2d1bc3

Browse files
ON-15180: Read sysfs to check for NICs
Rather than rely on an external tool (lshw) which requires extra dependencies in container images and doesn't provide an simple way of determining the vendor of a nic, it would be better to search through sysfs directly (since that is basically all lshw is doing). This approach also allows us to easily get other information from sysfs (such as numa nodes).
1 parent e1bbd22 commit c2d1bc3

File tree

3 files changed

+44
-48
lines changed

3 files changed

+44
-48
lines changed

deviceplugin.Dockerfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ COPY LICENSE /app/LICENSE
2222
RUN CGO_ENABLED=0 make device-plugin-build worker-build
2323

2424
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9
25-
RUN microdnf install -y lshw-B.02.19.2 && microdnf clean all
2625
COPY --from=builder /app/bin/onload-device-plugin /app/bin/onload-worker /usr/bin/
2726
COPY --from=builder /app/LICENSE /licenses/LICENSE
2827
USER 1001

pkg/deviceplugin/manager.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ func NewNicManager(
9393
// Initialises the set of devices to advertise to kubernetes
9494
func (manager *NicManager) initDevices() {
9595
manager.devices = []*pluginapi.Device{}
96-
fmt.Println(manager.interfaces)
9796
for i := 0; i < manager.config.MaxPodsPerNode; i++ {
9897
name := fmt.Sprintf("sfc-%v", i)
9998
device := &pluginapi.Device{

pkg/deviceplugin/nic.go

Lines changed: 44 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,69 +3,67 @@
33
package deviceplugin
44

55
import (
6+
"errors"
67
"os"
7-
"os/exec"
8-
"regexp"
8+
"path"
99
"strings"
1010

1111
"github.com/golang/glog"
1212
)
1313

14-
// Takes the output from lshw and returns the device name for each solarflare
15-
// device.
16-
func parseOutput(output string) []string {
17-
// "lshw -short -class network" sample output:
18-
// H/W path Device Class Description
19-
// =========================================================
20-
// /0/100/1b/0 enp2s0f0 network XtremeScale SFC9250 10/25/40/50/100G Ethernet Controller
21-
// /0/100/1b/0.1 enp2s0f1 network XtremeScale SFC9250 10/25/40/50/100G Ethernet Controller
22-
// /0/100/1c.1/0 eno1 network NetXtreme BCM5720 Gigabit Ethernet PCIe
23-
// /0/100/1c.1/0.1 eno2 network NetXtreme BCM5720 Gigabit Ethernet PCIe
24-
25-
lines := strings.Split(output, "\n")
14+
const (
15+
sysClassNetPath = "/sys/class/net/"
16+
solarflareVendor = "0x1924"
17+
)
2618

27-
var interfaces []string
19+
func isSFCNic(devicePath string) bool {
20+
deviceDir, err := os.Stat(path.Join(devicePath, "device"))
21+
if errors.Is(err, os.ErrNotExist) {
22+
// Not a physical device, so won't have the "vendor" file
23+
return false
24+
} else if err != nil {
25+
glog.Errorf("Failed to stat %s (%v)", devicePath, err)
26+
return false
27+
}
28+
if !deviceDir.IsDir() {
29+
return false
30+
}
2831

29-
// Assume that we are running as root, if not then we would have to skip
30-
// an additional line at the start of the output
31-
skip_lines := 2
32-
end_lines := 1
33-
if os.Geteuid() != 0 {
34-
skip_lines = 3
35-
end_lines = 2
32+
data, err := os.ReadFile(path.Join(devicePath, "device", "vendor"))
33+
if errors.Is(err, os.ErrNotExist) {
34+
// File doesn't exist but that is fine
35+
return false
36+
} else if err != nil {
37+
glog.Errorf("Error reading %s (%v)",
38+
path.Join(devicePath, "device", "vendor"), err)
39+
return false
3640
}
3741

38-
for _, line := range lines[skip_lines : len(lines)-end_lines] {
39-
// This regex makes the assumption that all interface names only
40-
// contain either lowercase letters or numbers. If that is not true,
41-
// then this should be updated to reflect that.
42-
r := regexp.MustCompile("([a-z0-9]+) *network *.*SFC")
43-
out := r.FindStringSubmatch(line)
44-
if out != nil {
45-
// It is safe to access out[1] here since the return value of
46-
// FindStringSubmatch is an array where the first value is the
47-
// whole string and any subsequent values are the submatches.
48-
// In this case since there is a submatch that should match the
49-
// device name if FindStringSubmatch returns non-nil then there
50-
// will be at least 2 elements in the return array.
51-
interfaces = append(interfaces, out[1])
42+
vendor := strings.TrimSuffix(string(data), "\n")
43+
return vendor == solarflareVendor
44+
}
45+
46+
func readSysFiles() ([]string, error) {
47+
infos, err := os.ReadDir(sysClassNetPath)
48+
if err != nil {
49+
glog.Errorf("Error reading %s (%v)", sysClassNetPath, err)
50+
return []string{}, err
51+
}
52+
interfaces := []string{}
53+
for _, info := range infos {
54+
if isSFCNic(path.Join(sysClassNetPath, info.Name())) {
55+
interfaces = append(interfaces, info.Name())
5256
}
5357
}
54-
return interfaces
58+
return interfaces, nil
5559
}
5660

5761
// Returns a list of the Solarflare interfaces present on the node
5862
func queryNics() ([]string, error) {
59-
// Depending on what information we are looking for in the output I think it
60-
// is quite tempting to retrieve the information in a json format, then
61-
// parse this using golang's built-in features.
62-
bytes, err := exec.Command("lshw", "-short", "-class", "network").CombinedOutput()
63-
output := string(bytes)
63+
interfaces, err := readSysFiles()
6464
if err != nil {
65-
glog.Error(output)
66-
glog.Errorf("error while listing sfc devices : %v", err)
67-
return nil, err
65+
glog.Errorf("Failed to list interfaces (%v)", err)
66+
return []string{}, err
6867
}
69-
interfaces := parseOutput(output)
7068
return interfaces, nil
7169
}

0 commit comments

Comments
 (0)