From 031d7f9c6f9f1e53c8cf0c3ac5b1cf35e769c8b8 Mon Sep 17 00:00:00 2001 From: Eoghan Russell Date: Tue, 31 Jan 2023 15:02:18 +0000 Subject: [PATCH] Updates - Update README - Update to go1.18 - Update to k8s v1.24.3 - Reworked vfstats collector - Implemented endpoint unit tests - Add netlink support detection - Add image building to Makefile - Remove deprecated references - Add Mellanox driver to drivers DB - Refactor code to enable testing - Support for NFD SR-IOV feature label - Changes to ensure more uniform Makefile - Implemented initial unit tests - Implemented vfstats package unit tests Co-Authored-By: Eoghan1232 Co-Authored-By: eoghanlawless Co-Authored-By: Ipawlikx Co-Authored-By: nhennigan --- .github/workflows/codeql.yml | 41 ++ Makefile | 26 +- cmd/sriov-network-metrics-exporter_test.go | 2 +- collectors/collectors.go | 19 +- collectors/collectors_test.go | 101 +++++ collectors/pod_cpu_link.go | 210 +++++----- collectors/pod_cpu_link_test.go | 303 ++++++++++++++ collectors/pod_dev_link_test.go | 39 ++ collectors/sriovdev_readers_test.go | 115 +++++ collectors/sriovdev_test.go | 462 +++++++++++++++++++++ go.mod | 22 +- go.sum | 68 +-- pkg/drvinfo/drvinfo.go | 8 +- pkg/utils/test/target | 1 + pkg/utils/utils.go | 1 + pkg/utils/utils_test.go | 98 +++++ pkg/vfstats/netlink_test.go | 41 ++ 17 files changed, 1383 insertions(+), 174 deletions(-) create mode 100644 .github/workflows/codeql.yml create mode 100644 collectors/collectors_test.go create mode 100644 collectors/pod_cpu_link_test.go create mode 100644 collectors/pod_dev_link_test.go create mode 100644 collectors/sriovdev_readers_test.go create mode 100644 collectors/sriovdev_test.go create mode 100644 pkg/utils/test/target create mode 100644 pkg/utils/utils_test.go create mode 100644 pkg/vfstats/netlink_test.go diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..3e8e308 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,41 @@ +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: "1 7 * * 5" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ go ] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + queries: +security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{ matrix.language }}" diff --git a/Makefile b/Makefile index b0cf342..45d08eb 100644 --- a/Makefile +++ b/Makefile @@ -11,17 +11,13 @@ ifdef HTTPS_PROXY DOCKERARGS += --build-arg https_proxy=$(HTTPS_PROXY) endif -all: build +all: build docker-build test clean: rm -rf bin - go clean --modcache - + go clean -modcache -testcache + build: - go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.45.2 - go mod tidy - go fmt ./... - golangci-lint run GO111MODULE=on go build -ldflags "-s -w" -buildmode=pie -o bin/sriov-exporter cmd/sriov-network-metrics-exporter.go docker-build: @@ -30,3 +26,19 @@ docker-build: docker-push: docker push $(IMAGE_NAME) + +test: + go test ./... -coverprofile cover.out + +test-coverage: + ginkgo -v -r -cover -coverprofile=cover.out --output-dir=. + go tool cover -html=cover.out + +go-lint: + go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.49 + go mod tidy + go fmt ./... + golangci-lint run --color always -v ./... + +go-lint-report: + golangci-lint run --color always -v ./... &> golangci-lint.txt diff --git a/cmd/sriov-network-metrics-exporter_test.go b/cmd/sriov-network-metrics-exporter_test.go index 1bf1b7f..efd97fd 100644 --- a/cmd/sriov-network-metrics-exporter_test.go +++ b/cmd/sriov-network-metrics-exporter_test.go @@ -13,7 +13,7 @@ import ( "golang.org/x/time/rate" ) -func TestLogging(t *testing.T) { +func TestMain(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "main test suite") } diff --git a/collectors/collectors.go b/collectors/collectors.go index affff12..762b9f7 100644 --- a/collectors/collectors.go +++ b/collectors/collectors.go @@ -15,7 +15,8 @@ var ( collectorNamespace = "sriov" enabled = true disabled = false - enabledCollectors = make(map[string]func() prometheus.Collector) + collectorState = make(map[string]*bool) + collectorFunctions = make(map[string]func() prometheus.Collector) ) // SriovCollector registers the collectors used for specific data and exposes a Collect method to gather the data @@ -23,10 +24,10 @@ type SriovCollector []prometheus.Collector // Register defines a flag for a collector and adds it to the registry of enabled collectors if the flag is set to true - either through the default option or the flag passed on start // Run by each individual collector in its init function. -func register(name string, isDefault bool, collector func() prometheus.Collector) { - if enabled := flag.Bool("collector."+name, isDefault, fmt.Sprintf("Enables the %v collector", name)); *enabled { - enabledCollectors[name] = collector - } +func register(name string, enabled bool, collector func() prometheus.Collector) { + collectorState[name] = &enabled + collectorFunctions[name] = collector + flag.BoolVar(collectorState[name], "collector."+name, enabled, fmt.Sprintf("Enables the %v collector", name)) } // Collect metrics from all enabled collectors in unordered sequence. @@ -46,9 +47,11 @@ func (s SriovCollector) Describe(ch chan<- *prometheus.Desc) { // Enabled adds collectors enabled by default or command line flag to an SriovCollector object func Enabled() SriovCollector { collectors := make([]prometheus.Collector, 0) - for collectorName, collectorFunc := range enabledCollectors { - log.Printf("The %v collector is enabled", collectorName) - collectors = append(collectors, collectorFunc()) + for collector, enabled := range collectorState { + if enabled != nil && *enabled { + log.Printf("The %v collector is enabled", collector) + collectors = append(collectors, collectorFunctions[collector]()) + } } return collectors } diff --git a/collectors/collectors_test.go b/collectors/collectors_test.go new file mode 100644 index 0000000..8ed59b6 --- /dev/null +++ b/collectors/collectors_test.go @@ -0,0 +1,101 @@ +package collectors + +import ( + "fmt" + "io/fs" + "log" + "path/filepath" + "testing" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + "github.com/prometheus/client_golang/prometheus" + + "sriov-network-metrics-exporter/pkg/utils" +) + +var buffer gbytes.Buffer + +func TestCollectors(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "collectors test suite") +} + +var _ = BeforeSuite(func() { + utils.EvalSymlinks = evalSymlinks + + logFatal = func(msg string, args ...any) { + log.Printf(msg, args...) + } + + log.SetFlags(0) +}) + +var _ = BeforeEach(func() { + buffer = *gbytes.NewBuffer() + log.SetOutput(&buffer) +}) + +type metric struct { + labels map[string]string + counter float64 +} + +type testCollector struct { + name string +} + +func createTestCollector() prometheus.Collector { + return testCollector{ + name: "collector.test", + } +} + +func (c testCollector) Collect(ch chan<- prometheus.Metric) {} +func (c testCollector) Describe(chan<- *prometheus.Desc) {} + +var _ = DescribeTable("test registering collector", // register + func(name string, enabled bool, collector func() prometheus.Collector) { + register(name, enabled, collector) + + Expect(collectorState).To(HaveKey(name)) + Expect(collectorState[name]).To(Equal(&enabled)) + + Expect(collectorFunctions).To(HaveKey(name)) + // Expect(allCollectors[name]).To(Equal(collector)) // TODO: verify expected collector is returned + + }, + Entry("the correct collector is enabled when default is true", + "test_true", + true, + createTestCollector), + Entry("the correct collector is not enabled when default is false", + "test_false", + false, + createTestCollector), +) + +// TODO: create Enabled unit test + +func assertLogs(logs []string) { + for _, log := range logs { + Eventually(&buffer).WithTimeout(time.Duration(2 * time.Second)).Should(gbytes.Say(log)) + } +} + +// Replaces filepath.EvalSymlinks with an emulated evaluation to work with the in-memory fs. +var evalSymlinks = func(path string) (string, error) { + path = filepath.Join(filepath.Base(filepath.Dir(path)), filepath.Base(path)) + + if stat, err := fs.Stat(devfs, path); err == nil && stat.Mode() == fs.ModeSymlink { + if target, err := fs.ReadFile(devfs, path); err == nil { + return string(target), nil + } else { + return "", fmt.Errorf("error") + } + } else { + return "", fmt.Errorf("error") + } +} diff --git a/collectors/pod_cpu_link.go b/collectors/pod_cpu_link.go index 5922f35..038f198 100644 --- a/collectors/pod_cpu_link.go +++ b/collectors/pod_cpu_link.go @@ -73,14 +73,13 @@ func (c kubepodCPUCollector) Collect(ch chan<- prometheus.Metric) { ) } - podCPULinks, err := guaranteedPodCPUs() - + links, err := getGuaranteedPodCPUs() if err != nil { log.Printf("pod cpu links not available: %v", err) return } - for _, link := range podCPULinks { + for _, link := range links { desc := prometheus.NewDesc( prometheus.BuildFQName(collectorNamespace, "", c.name), "pod_cpu", @@ -103,25 +102,13 @@ func (c kubepodCPUCollector) Collect(ch chan<- prometheus.Metric) { func (c kubepodCPUCollector) Describe(ch chan<- *prometheus.Desc) { } -// readDefaultSet extracts the information about the "default" set of cpus available to kubernetes -func readDefaultSet(data []byte) string { - checkpointFile := cpuManagerCheckpoint{} - - if err := json.Unmarshal(data, &checkpointFile); err != nil { - log.Printf("cpu checkpoint file could not be unmarshalled, error: %v", err) - return "" - } - - return checkpointFile.DefaultCPUSet -} - // createKubepodCPUCollector creates a static picture of the cpu topology of the system and returns a collector // It also creates a static list of cpus in the kubernetes parent cgroup. func createKubepodCPUCollector() prometheus.Collector { cpuInfo, err := getCPUInfo() if err != nil { //Exporter will fail here if file can not be read. - log.Fatal("Fatal Error: cpu info for node can not be collected", err) + logFatal("Fatal Error: cpu info for node can not be collected, %v", err.Error()) } return kubepodCPUCollector{ @@ -130,88 +117,137 @@ func createKubepodCPUCollector() prometheus.Collector { } } -// getKubernetesCPUList returns the information about the CPUs being used by Kubernetes overall -func getKubernetesCPUList() (string, error) { - kubeCPUString, err := parseCPUFile("cpuset.cpus") +// getCPUInfo looks in the sys directory for information on CPU IDs and NUMA topology. This method runs once on initialization of the pod. +func getCPUInfo() (map[string]string, error) { + cpuInfo := make(map[string]string, 0) + files, err := fs.ReadDir(cpuinfofs, ".") if err != nil { - return "", err + return cpuInfo, fmt.Errorf("failed to read directory '%s'\n%v", *sysDevSysNodePath, err) } - return kubeCPUString, nil + + fileRE := regexp.MustCompile(`node\d+`) + cpuFileRE := regexp.MustCompile(`cpu\d+`) + for _, f := range files { + if f.IsDir() { + if fileRE.MatchString(f.Name()) { + numaNode := f.Name()[4:] + cpuFiles, err := fs.ReadDir(cpuinfofs, f.Name()) + if err != nil { + return cpuInfo, fmt.Errorf("failed to read directory '%s'\n%v", filepath.Join(*sysDevSysNodePath, numaNode), err) + } + + for _, cpu := range cpuFiles { + if cpuFileRE.MatchString(cpu.Name()) { + cpuID := cpu.Name()[3:] + cpuInfo[cpuID] = numaNode + } + } + } + } + } + return cpuInfo, nil } -// guaranteedPodCPUs creates a podCPULink for each CPU that is guaranteed +// getGuaranteedPodCPUs creates a podCPULink for each CPU that is guaranteed // This information is exposed under the cpuset in the cgroup file system with Kubernetes1.18/Docker/ // This accounting will create an entry for each guaranteed pod, even if that pod isn't managed by CPU manager // i.e. it will still create an entry if the pod is looking for millis of CPU // Todo: validate regex matching and evaluate performance of this approach // Todo: validate assumptions about directory structure against other runtimes and kubelet config. Plausibly problematic with CgroupsPerQos and other possible future cgroup changes -func guaranteedPodCPUs() ([]podCPULink, error) { - // This generate method should be updated to create the non-exclusive cores mask used by guaranteed pods with fractional core usage as well as maximum Kubernetes usage. - kubeCPUString, err := getKubernetesCPUList() - if err != nil { - // Exporter killed here as CPU collector can not work without this information. - logFatal("cannot get information on Kubernetes CPU usage, %v", err.Error()) - } +func getGuaranteedPodCPUs() ([]podCPULink, error) { + links := make([]podCPULink, 0) - cpuRawBytes, err := fs.ReadFile(cpucheckpointfs, filepath.Base(*cpuCheckPointFile)) - if err != nil { - log.Printf("unable to read cpu checkpoint file '%s', error: %v", *cpuCheckPointFile, err) - } + kubeCPUString, kubeDefaultSet := getKubeDefaults() - defaultSet := readDefaultSet(cpuRawBytes) - links := make([]podCPULink, 0) - files, err := fs.ReadDir(kubecgroupfs, ".") + podDirectoryFilenames, err := getPodDirectories() if err != nil { - return links, fmt.Errorf("could not open path kubePod cgroups: %v", err) + return links, err } - fileRE := regexp.MustCompile("pod[[:xdigit:]]{8}[_-][[:xdigit:]]{4}[_-][[:xdigit:]]{4}[_-][[:xdigit:]]{4}[_-][[:xdigit:]]{12}") - cpuSetFileRE := regexp.MustCompile("[[:xdigit:]]{20,}") + for _, directory := range podDirectoryFilenames { + containerIDs, err := getContainerIDs(directory) + if err != nil { + return links, err + } - for _, f := range files { - // Searching for files matching uuid pattern of 8-4-4-4-12 - if match := fileRE.FindString(f.Name()); match != "" { - podUID := match[3:] - containerFiles, err := fs.ReadDir(kubecgroupfs, f.Name()) + for _, container := range containerIDs { + cpuSet, err := readCPUSet(filepath.Join(directory, container, "cpuset.cpus")) if err != nil { - return links, fmt.Errorf("could not read cpu files directory: %v", err) + return links, err + } + if cpuSet == kubeCPUString || cpuSet == kubeDefaultSet { + continue } - for _, cpusetFile := range containerFiles { - if matches := cpuSetFileRE.MatchString(cpusetFile.Name()); matches { - cpuSetDesc, err := parseCPUFile(filepath.Join(f.Name(), cpusetFile.Name(), "cpuset.cpus")) - if err != nil { - return links, err - } - - cpuList, err := parseCPURange(cpuSetDesc) - if err != nil { - return links, err - } - - if cpuSetDesc == kubeCPUString || cpuSetDesc == defaultSet { - continue - } + cpuRange, err := parseCPURange(cpuSet) + if err != nil { + return links, err + } - for _, c := range cpuList { - links = append(links, podCPULink{podID: podUID, containerID: cpusetFile.Name(), cpu: c}) - } - } + for _, link := range cpuRange { + links = append(links, podCPULink{directory[12 : len(directory)-6], container, link}) } } } return links, nil } -// parseCPUFile can read cpuFiles in the Kernel cpuset format -func parseCPUFile(path string) (string, error) { - cpuRaw, err := fs.ReadFile(kubecgroupfs, path) +func getPodDirectories() ([]string, error) { + podDirectoryFilenames := make([]string, 0) + + files, err := fs.ReadDir(kubecgroupfs, ".") // all files in the directory if err != nil { - return "", fmt.Errorf("could not open cgroup cpuset files, error: %v", err) + return podDirectoryFilenames, fmt.Errorf("could not open path kubePod cgroups: %v", err) + } + + podDirectoryRegex := regexp.MustCompile("pod[[:xdigit:]]{8}[_-][[:xdigit:]]{4}[_-][[:xdigit:]]{4}[_-][[:xdigit:]]{4}[_-][[:xdigit:]]{12}") + for _, podDirectory := range files { + podDirectoryFilename := podDirectory.Name() + if match := podDirectoryRegex.MatchString(podDirectoryFilename); match { + podDirectoryFilenames = append(podDirectoryFilenames, podDirectoryFilename) + } + } + return podDirectoryFilenames, nil +} + +func getContainerIDs(podDirectoryFilename string) ([]string, error) { + containerDirectoryFilenames := make([]string, 0) + + files, err := fs.ReadDir(kubecgroupfs, podDirectoryFilename) + if err != nil { + return containerDirectoryFilenames, fmt.Errorf("could not read cpu files directory: %v", err) + } + + containerIDRegex := regexp.MustCompile("[[:xdigit:]]{20,}") // change regexback + for _, containerDirectory := range files { + containerID := containerDirectory.Name() + if match := containerIDRegex.MatchString(containerID); match { + containerDirectoryFilenames = append(containerDirectoryFilenames, containerID) + } + } + + return containerDirectoryFilenames, nil +} + +// readDefaultSet extracts the information about the "default" set of cpus available to kubernetes +func readDefaultSet(data []byte) string { + checkpointFile := cpuManagerCheckpoint{} + + if err := json.Unmarshal(data, &checkpointFile); err != nil { + log.Printf("cpu checkpoint file could not be unmarshalled, error: %v", err) + return "" } - cpuString := strings.TrimSpace(string(cpuRaw)) - return cpuString, err + return checkpointFile.DefaultCPUSet +} + +// readCPUSet can read cpuFiles in the Kernel cpuset format +func readCPUSet(cpuSetFilepath string) (string, error) { + cpuSetBytes, err := fs.ReadFile(kubecgroupfs, cpuSetFilepath) + if err != nil { + return "", fmt.Errorf("could not open cgroup cpuset files, error: %v", err) + } + return strings.TrimSpace(string(cpuSetBytes)), err } // parseCPURanges can read cpuFiles in the Kernel cpuset format @@ -242,35 +278,19 @@ func parseCPURange(cpuString string) ([]string, error) { return cpuList, nil } -// getCPUInfo looks in the sys directory for information on CPU IDs and NUMA topology. This method runs once on initialization of the pod. -func getCPUInfo() (map[string]string, error) { - cpuInfo := make(map[string]string, 0) - files, err := fs.ReadDir(cpuinfofs, ".") +func getKubeDefaults() (string, string) { + kubeCPUString, err := readCPUSet("cpuset.cpus") if err != nil { - return cpuInfo, fmt.Errorf("failed to read directory '%s'\n%v", *sysDevSysNodePath, err) + // Exporter killed here as CPU collector can not work without this information. + logFatal("Fatal Error: cannot get information on Kubernetes CPU usage, %v", err.Error()) } - fileRE := regexp.MustCompile(`node\d+`) - cpuFileRE := regexp.MustCompile(`cpu\d+`) - for _, f := range files { - if f.IsDir() { - if fileRE.MatchString(f.Name()) { - numaNode := f.Name()[4:] - cpuFiles, err := fs.ReadDir(cpuinfofs, f.Name()) - if err != nil { - return cpuInfo, fmt.Errorf("failed to read directory '%s'\n%v", filepath.Join(*sysDevSysNodePath, numaNode), err) - } - - for _, cpu := range cpuFiles { - if cpuFileRE.MatchString(cpu.Name()) { - cpuID := cpu.Name()[3:] - cpuInfo[cpuID] = numaNode - } - } - } - } + cpuRawBytes, err := fs.ReadFile(cpucheckpointfs, filepath.Base(*cpuCheckPointFile)) + if err != nil { + log.Printf("unable to read cpu checkpoint file '%s', error: %v", *cpuCheckPointFile, err) } - return cpuInfo, nil + + return kubeCPUString, readDefaultSet(cpuRawBytes) } func resolveKubePodCPUFilepaths() error { diff --git a/collectors/pod_cpu_link_test.go b/collectors/pod_cpu_link_test.go new file mode 100644 index 0000000..9475590 --- /dev/null +++ b/collectors/pod_cpu_link_test.go @@ -0,0 +1,303 @@ +package collectors + +import ( + "errors" + "fmt" + "io/fs" + "strconv" + "testing/fstest" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" +) + +var _ = DescribeTable("test pod cpu link collection", // Collect + func(fsys fs.FS, expected []metric, logs ...string) { + cpuinfofs = fsys + kubecgroupfs = fsys + cpucheckpointfs = fsys + + ch := make(chan prometheus.Metric, 1) + go createKubepodCPUCollector().Collect(ch) + + for i := 0; i < len(expected); i++ { + m := dto.Metric{} + err := (<-ch).Write(&m) + Expect(err).ToNot(HaveOccurred()) + + labels := make(map[string]string, 4) + for _, label := range m.Label { + labels[*label.Name] = *label.Value + } + + metric := metric{labels: labels, counter: *m.Counter.Value} + + Expect(metric).To(BeElementOf(expected)) + } + + assertLogs(logs) + }, + Entry("test numa node and cpuset collection", + fstest.MapFS{ + "node0/cpu0": {Mode: fs.ModeDir}, + "node0/cpu2": {Mode: fs.ModeDir}, + "node1/cpu1": {Mode: fs.ModeDir}, + "node1/cpu3": {Mode: fs.ModeDir}, + "cpuset.cpus": {Data: []byte("4-7")}, + "cpu_manager_state": {Data: []byte("{\"policyName\":\"none\",\"defaultCpuSet\":\"4-7\",\"checksum\":1353318690}")}, + "kubepods-pod6b5b533a_6307_48d1_911f_07bf5d4e1c82.slice/0123456789abcdefaaaa/cpuset.cpus": {Data: []byte("0-3")}}, + []metric{ + {map[string]string{"cpu": "cpu0", "numa_node": "0"}, 1}, + {map[string]string{"cpu": "cpu2", "numa_node": "0"}, 1}, + {map[string]string{"cpu": "cpu1", "numa_node": "1"}, 1}, + {map[string]string{"cpu": "cpu3", "numa_node": "1"}, 1}, + {map[string]string{"cpu_id": "0", "numa_node": "0", "uid": "6b5b533a_6307_48d1_911f_07bf5d4e1c82", "container_id": "0123456789abcdefaaaa"}, 1}, + {map[string]string{"cpu_id": "2", "numa_node": "0", "uid": "6b5b533a_6307_48d1_911f_07bf5d4e1c82", "container_id": "0123456789abcdefaaaa"}, 1}, + {map[string]string{"cpu_id": "1", "numa_node": "1", "uid": "6b5b533a_6307_48d1_911f_07bf5d4e1c82", "container_id": "0123456789abcdefaaaa"}, 1}, + {map[string]string{"cpu_id": "3", "numa_node": "1", "uid": "6b5b533a_6307_48d1_911f_07bf5d4e1c82", "container_id": "0123456789abcdefaaaa"}, 1}}), + Entry("test unavailable kube cgroup directory", + fstest.MapFS{ + "node0/cpu0": {Mode: fs.ModeDir}, + "node0/cpu2": {Mode: fs.ModeDir}, + "node1/cpu1": {Mode: fs.ModeDir}, + "node1/cpu3": {Mode: fs.ModeDir}, + "cpuset.cpus": {Data: []byte("4-7")}, + "cpu_manager_state": {Data: []byte("{\"policyName\":\"none\",\"defaultCpuSet\":\"4-7\",\"checksum\":1353318690}")}, + "kubepods-pod6b5b533a_6307_48d1_911f_07bf5d4e1c83.slice": {Mode: fs.ModeExclusive}}, + []metric{ + {map[string]string{"cpu": "cpu0", "numa_node": "0"}, 1}, + {map[string]string{"cpu": "cpu2", "numa_node": "0"}, 1}, + {map[string]string{"cpu": "cpu1", "numa_node": "1"}, 1}, + {map[string]string{"cpu": "cpu3", "numa_node": "1"}, 1}}, + "pod cpu links not available: could not read cpu files directory: readdir kubepods-pod6b5b533a_6307_48d1_911f_07bf5d4e1c83.slice: not implemented"), +) + +var _ = DescribeTable("test reading default cpu set", // readDefaultSet + func(data []byte, expected string, logs ...string) { + Expect(readDefaultSet(data)).To(Equal(expected)) + + assertLogs(logs) + }, + Entry("read empty", + []byte("{\"policyName\":\"none\",\"defaultCpuSet\":\"\",\"checksum\":1353318690}"), + ""), + Entry("read successful", + []byte("{\"policyName\":\"none\",\"defaultCpuSet\":\"1,2,3,4\",\"checksum\":1353318690}"), + "1,2,3,4"), + Entry("read failed with malformed data", + []byte("\"policyName\":\"none\",\"checksum\":1353318690"), + "", + "cpu checkpoint file could not be unmarshalled, error: invalid character ':' after top-level value"), +) + +var _ = DescribeTable("test creating kubepodCPU collector", // createKubepodCPUCollector + func(fsys fs.FS, expectedCollector kubepodCPUCollector, logs ...string) { + cpuinfofs = fsys + + collector := createKubepodCPUCollector() + Expect(collector).To(Equal(expectedCollector)) + + assertLogs(logs) + }, + Entry("successful creation", + fstest.MapFS{ + "node0/cpu0": {Mode: fs.ModeDir}, + "node0/cpu2": {Mode: fs.ModeDir}, + "node1/cpu1": {Mode: fs.ModeDir}, + "node1/cpu3": {Mode: fs.ModeDir}}, + kubepodCPUCollector{cpuInfo: map[string]string{"0": "0", "2": "0", "1": "1", "3": "1"}, name: kubepodcpu}), + Entry("directory doesn't exist", + fstest.MapFS{".": {Mode: fs.ModeExclusive}}, // to emulate the directory doesn't exist + kubepodCPUCollector{cpuInfo: map[string]string{}, name: kubepodcpu}, + "Fatal Error: cpu info for node can not be collected, failed to read directory '/sys/devices/system/node/'\nreaddir .: not implemented"), +) + +var _ = DescribeTable("test getting kubernetes cpu list", // getKubeDefaults + func(fsys fs.FS, expectedKubeCPUString, expectedDefaultSet string) { + kubecgroupfs = fsys + cpucheckpointfs = fsys + + kubeCPUString, kubeDefaultSet := getKubeDefaults() + Expect(kubeCPUString).To(Equal(expectedKubeCPUString)) + Expect(kubeDefaultSet).To(Equal(expectedDefaultSet)) + }, + Entry("read empty", + fstest.MapFS{ + "cpuset.cpus": {Data: []byte("")}, + "cpu_manager_state": {Data: []byte("")}}, + "", + ""), + Entry("read successful", + fstest.MapFS{ + "cpuset.cpus": {Data: []byte("0-87")}, + "cpu_manager_state": {Data: []byte("{\"policyName\":\"static\",\"defaultCpuSet\":\"0-63\",\"checksum\":1058907510}")}}, + "0-87", + "0-63"), + Entry("read successful with malformed data", + fstest.MapFS{ + "cpuset.cpus": {Data: []byte(" 0-87 ")}, + "cpu_manager_state": {Data: []byte("{\"policyName\":\"static\",\"defaultCpuSet\":\"0-63\",\"checksum\":1058 907 51 0}")}}, + "0-87", + ""), + Entry("read failed, file doesn't exist", + fstest.MapFS{}, + "", + ""), +) + +var _ = DescribeTable("test getting guaranteed pod cpus", // guaranteedPodCPUs + func(fsys fs.FS, expected []podCPULink, expectedErr error, logs ...string) { + kubecgroupfs = fsys + cpucheckpointfs = fsys + + data, err := getGuaranteedPodCPUs() + Expect(data).To(Equal(expected)) + + if expectedErr != nil { + Expect(err).To(MatchError(expectedErr)) + } + + assertLogs(logs) + }, + Entry("container cpuset available", + fstest.MapFS{ + "cpuset.cpus": {Data: []byte("0-3")}, + "cpu_manager_state": {Data: []byte("{\"policyName\":\"none\",\"defaultCpuSet\":\"4-7\",\"checksum\":1353318690}")}, + "kubepods-pod6b5b533a_6307_48d1_911f_07bf5d4e1c82.slice/0123456789abcdefaaaa/cpuset.cpus": {Data: []byte("8-11")}}, + []podCPULink{ + {"6b5b533a_6307_48d1_911f_07bf5d4e1c82", "0123456789abcdefaaaa", "8"}, + {"6b5b533a_6307_48d1_911f_07bf5d4e1c82", "0123456789abcdefaaaa", "9"}, + {"6b5b533a_6307_48d1_911f_07bf5d4e1c82", "0123456789abcdefaaaa", "10"}, + {"6b5b533a_6307_48d1_911f_07bf5d4e1c82", "0123456789abcdefaaaa", "11"}}, + nil), + Entry("cgroup directory doesn't exist", + fstest.MapFS{".": {Mode: fs.ModeExclusive}}, + []podCPULink{}, + fmt.Errorf("could not open path kubePod cgroups: readdir .: not implemented"), + "cannot get information on Kubernetes CPU usage, could not open cgroup cpuset files, error: open cpuset.cpus: file does not exist", + "unable to read cpu checkpoint file '/var/lib/kubelet/cpu_manager_state', error: open cpu_manager_state: file does not exist", + "cpu checkpoint file could not be unmarshalled, error: unexpected end of JSON input"), + Entry("unable to read pod cgroup directory", + fstest.MapFS{ + "cpuset.cpus": {Data: []byte("0-3")}, + "cpu_manager_state": {Data: []byte("{\"policyName\":\"none\",\"defaultCpuSet\":\"4-7\",\"checksum\":1353318690}")}, + "kubepods-pod6b5b533a_6307_48d1_911f_07bf5d4e1c82.slice": {Mode: fs.ModeExclusive}}, + []podCPULink{}, + fmt.Errorf("could not read cpu files directory: readdir kubepods-pod6b5b533a_6307_48d1_911f_07bf5d4e1c82.slice: not implemented")), + Entry("unable to read container cpuset file", + fstest.MapFS{ + "cpuset.cpus": {Data: []byte("0-3")}, + "cpu_manager_state": {Data: []byte("{\"policyName\":\"none\",\"defaultCpuSet\":\"4-7\",\"checksum\":1353318690}")}, + "kubepods-pod6b5b533a_6307_48d1_911f_07bf5d4e1c82.slice/0123456789abcdefaaaa/cpuset.cpus": {Mode: fs.ModeDir}}, + []podCPULink{}, + fmt.Errorf("could not open cgroup cpuset files, error: read kubepods-pod6b5b533a_6307_48d1_911f_07bf5d4e1c82.slice/0123456789abcdefaaaa/cpuset.cpus: invalid argument")), + Entry("container cpuset range covered by defaults", + fstest.MapFS{ + "cpuset.cpus": {Data: []byte("0-3")}, + "cpu_manager_state": {Data: []byte("{\"policyName\":\"none\",\"defaultCpuSet\":\"4-7\",\"checksum\":1353318690}")}, + "kubepods-pod6b5b533a_6307_48d1_911f_07bf5d4e1c82.slice/0123456789abcdefaaaa/cpuset.cpus": {Data: []byte("0-3")}}, + []podCPULink{}, + nil), +) + +var _ = DescribeTable("test parsing cpu file", // parseCPUFile + func(path string, fsys fs.FS, expectedString string, expectedErr error) { + kubecgroupfs = fsys + + data, err := readCPUSet(path) + Expect(data).To(Equal(expectedString)) + + if expectedErr != nil { + Expect(err).To(Equal(expectedErr)) + } + }, + Entry("read empty", + "cpuset.cpus", + fstest.MapFS{ + "cpuset.cpus": {Data: []byte("")}}, + "", + nil), + Entry("read successful", + "cpuset.cpus", + fstest.MapFS{ + "cpuset.cpus": {Data: []byte("0-87")}}, + "0-87", + nil), + Entry("read successful with malformed data", + "cpuset.cpus", + fstest.MapFS{ + "cpuset.cpus": {Data: []byte(" 0-87 ")}}, + "0-87", + nil), + Entry("read failed, file doesn't exist", + "cpuset.cpus", + fstest.MapFS{}, + "", + fmt.Errorf("could not open cgroup cpuset files, error: open cpuset.cpus: file does not exist")), +) + +var _ = DescribeTable("test parsing cpu range", // parseCPURange + func(cpuString string, expected []string, expectedErr error) { + data, err := parseCPURange(cpuString) + Expect(data).To(Equal(expected)) + + if expectedErr != nil { + Expect(err).To(MatchError(expectedErr)) + } + }, + Entry("valid range '0-3,7-9'", + "0-3,7-9", + []string{"0", "1", "2", "3", "7", "8", "9"}, + nil), + Entry("valid range '0-3'", + "0-3", + []string{"0", "1", "2", "3"}, + nil), + Entry("valid range '7'", + "7", + []string{"7"}, + nil), + Entry("invalid range '-1'", + "-1", + []string{}, + strconv.ErrSyntax), + Entry("invalid range '0-'", + "0-", + []string{}, + strconv.ErrSyntax), +) + +var _ = DescribeTable("test getting cpu info", // getCPUInfo + func(fsys fs.FS, expectedData map[string]string, expectedErr error) { + cpuinfofs = fsys + + data, err := getCPUInfo() + + for k, v := range expectedData { + Expect(data).To(HaveKey(k)) + Expect(data[k]).To(Equal(v)) + } + + Expect(data).To(Equal(expectedData)) + + if expectedErr != nil { + Expect(err).To(MatchError(expectedErr)) + } + }, + Entry("valid info", + fstest.MapFS{ + "node0/cpu0": {Mode: fs.ModeDir}, + "node0/cpu2": {Mode: fs.ModeDir}, + "node1/cpu1": {Mode: fs.ModeDir}, + "node1/cpu3": {Mode: fs.ModeDir}}, + map[string]string{"0": "0", "2": "0", "1": "1", "3": "1"}, + nil), + Entry("directory doesn't exist", + fstest.MapFS{".": {Mode: fs.ModeExclusive}}, // to emulate the directory doesn't exist + map[string]string{}, + errors.New("failed to read directory '/sys/devices/system/node/'\nreaddir .: not implemented")), +) + +// TODO: create integration tests for GetV1Client and PodResources, they require the kubelet API diff --git a/collectors/pod_dev_link_test.go b/collectors/pod_dev_link_test.go new file mode 100644 index 0000000..0e49c29 --- /dev/null +++ b/collectors/pod_dev_link_test.go @@ -0,0 +1,39 @@ +package collectors + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +// TODO: create Collector and dialer unit tests + +var _ = Describe("test creating podDevLink collector", func() { // createPodDevLinkCollector + It("returns the correct collector", func() { + collector := createPodDevLinkCollector() + Expect(collector).To(Equal(podDevLinkCollector{name: podDevLinkName})) + }) +}) + +var _ = DescribeTable("test pci address regexp: "+pciAddressPattern.String(), // isPci + func(pciAddr string, expected bool) { + Expect(isPci(pciAddr)).To(Equal(expected)) + }, + Entry("valid, 0000:00:00.0", "0000:00:00.0", true), + Entry("valid, ffff:00:00.0", "ffff:00:00.0", true), + Entry("valid, 0000:ff:00.0", "0000:ff:00.0", true), + Entry("valid, 0000:00:ff.0", "0000:00:ff.0", true), + Entry("valid, 0000:00:00.0", "0000:00:00.0", true), + Entry("invalid, 0000.00:00.0", "0000.00:00.0", false), + Entry("invalid, 0000:00.00.0", "0000:00.00.0", false), + Entry("invalid, 0000:00:00:0", "0000:00:00:0", false), + Entry("invalid, gggg:00:00.0", "gggg:00:00.0", false), + Entry("invalid, 0000:gg:00.0", "0000:gg:00.0", false), + Entry("invalid, 0000:00:gg.0", "0000:00:gg.0", false), + Entry("invalid, 0000:00:00.a", "0000:00:00.a", false), + Entry("invalid, 00000:00:00.0", "00000:00:00.0", false), + Entry("invalid, 0000:000:00.0", "0000:000:00.0", false), + Entry("invalid, 0000:00:000.0", "0000:00:000.0", false), + Entry("invalid, 0000:00:00.00", "0000:00:00.00", false), +) + +// TODO: create integration tests for GetV1Client and PodResources, they require the kubelet API diff --git a/collectors/sriovdev_readers_test.go b/collectors/sriovdev_readers_test.go new file mode 100644 index 0000000..df01da2 --- /dev/null +++ b/collectors/sriovdev_readers_test.go @@ -0,0 +1,115 @@ +package collectors + +import ( + "io/fs" + "sriov-network-metrics-exporter/pkg/drvinfo" + "sriov-network-metrics-exporter/pkg/vfstats" + "testing/fstest" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = DescribeTable("test getting stats reader for pf", // getStatsReader + func(pf string, priority []string, fsys fs.FS, driver *drvinfo.DriverInfo, expected sriovStatReader, logs ...string) { + netfs = fsys + + // TODO: replace with ethtool mock + drvinfo.GetDriverInfo = func(name string) (*drvinfo.DriverInfo, error) { + return driver, nil + } + + // TODO: replace with fstest.MapFS entry + supportedDrivers = drvinfo.SupportedDrivers{Drivers: drvinfo.DriversList{Drivers: []drvinfo.DriverInfo{*driver}}, DbFilePath: ""} + + statsReader := getStatsReader(pf, priority) + + if expected != nil { + Expect(statsReader).To(Equal(expected)) + } else { + Expect(statsReader).To(BeNil()) + } + + assertLogs(logs) + }, + Entry("with sysfs support", + "ens785f0", + []string{"sysfs", "netlink"}, + fstest.MapFS{"ens785f0/device/sriov": {Mode: fs.ModeDir}}, + &drvinfo.DriverInfo{Name: "ice", Version: "1.9.11"}, + sysfsReader{"/sys/class/net/%s/device/sriov/%s/stats"}, + "ens785f0 - using sysfs collector"), + Entry("without sysfs support", + "ens785f0", + []string{"sysfs", "netlink"}, + fstest.MapFS{}, + &drvinfo.DriverInfo{Name: "ice", Version: "1.9.11"}, + netlinkReader{vfstats.VfStats("ens785f0")}, + "ens785f0 does not support sysfs collector", + "ens785f0 - using netlink collector"), + Entry("without any collector support", + "ens785f0", + []string{"unsupported_collector"}, + fstest.MapFS{}, + &drvinfo.DriverInfo{Name: "ice", Version: "1.9.11"}, + nil, + "ens785f0 - 'unsupported_collector' collector not supported"), +) + +var _ = DescribeTable("test getting reading stats through sriov sysfs interface", // sysfsReader.ReadStats + func(pf string, vfId string, fsys fs.FS, expected sriovStats, logs ...string) { + netfs = fsys + + statsReader := new(sysfsReader) + stats := statsReader.ReadStats(pf, vfId) + Expect(stats).To(Equal(expected)) + + assertLogs(logs) + }, + Entry("with stats files", + "ens785f0", + "0", + fstest.MapFS{ + "ens785f0/device/sriov/0/stats/rx_packets": {Data: []byte("6")}, + "ens785f0/device/sriov/0/stats/rx_bytes": {Data: []byte("24")}, + "ens785f0/device/sriov/0/stats/tx_packets": {Data: []byte("12")}, + "ens785f0/device/sriov/0/stats/tx_bytes": {Data: []byte("48")}}, + map[string]int64{ + "rx_packets": 6, + "rx_bytes": 24, + "tx_packets": 12, + "tx_bytes": 48}, + "getting stats for ens785f0 vf0"), + Entry("without stats files", + "ens785f0", + "0", + fstest.MapFS{}, + map[string]int64{}, + "error reading stats for ens785f0 vf0", + "open ens785f0/device/sriov/0/stats: file does not exist"), + Entry("with stat file as a symlink", + "ens785f0", + "0", + fstest.MapFS{ + "ens785f0/device/sriov/0/stats/rx_packets": {Mode: fs.ModeSymlink}}, + map[string]int64{}, + "getting stats for ens785f0 vf0", + "could not stat file 'ens785f0/device/sriov/0/stats/rx_packets'"), + Entry("with stat file as a directory", + "ens785f0", + "0", + fstest.MapFS{ + "ens785f0/device/sriov/0/stats/rx_packets": {Mode: fs.ModeDir}}, + map[string]int64{}, + "getting stats for ens785f0 vf0", + "error reading file, read ens785f0/device/sriov/0/stats/rx_packets: invalid argument"), + Entry("with invalid stat file", + "ens785f0", + "0", + fstest.MapFS{ + "ens785f0/device/sriov/0/stats/rx_packets": {Data: []byte("NaN")}}, + map[string]int64{}, + "getting stats for ens785f0 vf0", + "rx_packets - error parsing integer from value 'NaN'", + "strconv.ParseInt: parsing \"NaN\": invalid syntax"), +) diff --git a/collectors/sriovdev_test.go b/collectors/sriovdev_test.go new file mode 100644 index 0000000..d2ae693 --- /dev/null +++ b/collectors/sriovdev_test.go @@ -0,0 +1,462 @@ +package collectors + +import ( + "fmt" + "io/fs" + "net" + "sriov-network-metrics-exporter/pkg/drvinfo" + "sriov-network-metrics-exporter/pkg/vfstats" + "testing/fstest" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "github.com/vishvananda/netlink" +) + +var _ = AfterEach(func() { + vfstats.GetLink = netlink.LinkByName +}) + +var _ = DescribeTable("test vf stats collection", // Collect + func(priority []string, fsys fs.FS, driver *drvinfo.DriverInfo, link netlink.Device, expected []metric, logs ...string) { + devfs = fsys + netfs = fsys + collectorPriority = priority + + // TODO: replace with ethtool mock + drvinfo.GetDriverInfo = func(name string) (*drvinfo.DriverInfo, error) { + return driver, nil + } + + // TODO: replace with fstest.MapFS entry + supportedDrivers = drvinfo.SupportedDrivers{Drivers: drvinfo.DriversList{Drivers: []drvinfo.DriverInfo{*driver}}, DbFilePath: ""} + + vfstats.GetLink = func(name string) (netlink.Link, error) { + return &link, nil + } + + ch := make(chan prometheus.Metric, 1) + go createSriovDevCollector().Collect(ch) + + for i := 0; i < len(expected); i++ { + m := dto.Metric{} + err := (<-ch).Write(&m) + Expect(err).ToNot(HaveOccurred()) + + labels := make(map[string]string, 4) + for _, label := range m.Label { + labels[*label.Name] = *label.Value + } + + metric := metric{labels: labels, counter: *m.Counter.Value} + + Expect(metric).To(BeElementOf(expected)) + } + + assertLogs(logs) + }, + Entry("with only sysfs", + []string{"sysfs"}, + fstest.MapFS{ + "0000:1d:00.0/sriov_totalvfs": {Data: []byte("64")}, + "0000:1d:00.0/net/t_ens785f0": {Mode: fs.ModeDir}, + "0000:1d:00.0/numa_node": {Data: []byte("0")}, + "0000:1d:00.0/class": {Data: []byte("0x020000")}, + "0000:1d:00.0/virtfn0": {Data: []byte("/sys/devices/0000:1d:01.0"), Mode: fs.ModeSymlink}, + "0000:1d:00.0/virtfn1": {Data: []byte("/sys/devices/0000:1d:01.1"), Mode: fs.ModeSymlink}, + "t_ens785f0/device/sriov/0/stats/rx_packets": {Data: []byte("4")}, + "t_ens785f0/device/sriov/0/stats/tx_packets": {Data: []byte("8")}, + "t_ens785f0/device/sriov/1/stats/rx_packets": {Data: []byte("16")}, + "t_ens785f0/device/sriov/1/stats/tx_packets": {Data: []byte("32")}}, + &drvinfo.DriverInfo{Name: "ice", Version: "1.9.11"}, + nil, + []metric{ + {map[string]string{"numa_node": "0", "pciAddr": "0000:1d:01.0", "pf": "t_ens785f0", "vf": "0"}, 4}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:1d:01.0", "pf": "t_ens785f0", "vf": "0"}, 8}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:1d:01.1", "pf": "t_ens785f0", "vf": "1"}, 16}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:1d:01.1", "pf": "t_ens785f0", "vf": "1"}, 32}}, + "collecting sr-iov device metrics", + "collector priority: \\[sysfs\\]", + "t_ens785f0 - using sysfs collector", + "getting stats for t_ens785f0 vf\\d", + "getting stats for t_ens785f0 vf\\d"), + Entry("with only netlink", + []string{"netlink"}, + fstest.MapFS{ + "0000:2e:00.0/sriov_totalvfs": {Data: []byte("64")}, + "0000:2e:00.0/net/t_ens801f0": {Mode: fs.ModeDir}, + "0000:2e:00.0/numa_node": {Data: []byte("0")}, + "0000:2e:00.0/class": {Data: []byte("0x020000")}, + "0000:2e:00.0/virtfn0": {Data: []byte("/sys/devices/0000:2e:01.0"), Mode: fs.ModeSymlink}, + "0000:2e:00.0/virtfn1": {Data: []byte("/sys/devices/0000:2e:01.1"), Mode: fs.ModeSymlink}}, + &drvinfo.DriverInfo{Name: "ice", Version: "1.9.11"}, + netlink.Device{LinkAttrs: netlink.LinkAttrs{Vfs: []netlink.VfInfo{ + {0, net.HardwareAddr{}, 0, 0, 0, true, 0, 0, 0, 11, 12, 13, 14, 15, 16, 17, 18, 0, 0}, //nolint:govet + {1, net.HardwareAddr{}, 0, 0, 0, true, 0, 0, 0, 21, 22, 23, 24, 25, 26, 27, 28, 0, 0}}}}, //nolint:govet + []metric{ + {map[string]string{"numa_node": "0", "pciAddr": "0000:2e:01.0", "pf": "t_ens801f0", "vf": "0"}, 11}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:2e:01.0", "pf": "t_ens801f0", "vf": "0"}, 12}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:2e:01.0", "pf": "t_ens801f0", "vf": "0"}, 13}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:2e:01.0", "pf": "t_ens801f0", "vf": "0"}, 14}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:2e:01.0", "pf": "t_ens801f0", "vf": "0"}, 15}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:2e:01.0", "pf": "t_ens801f0", "vf": "0"}, 16}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:2e:01.0", "pf": "t_ens801f0", "vf": "0"}, 17}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:2e:01.0", "pf": "t_ens801f0", "vf": "0"}, 18}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:2e:01.1", "pf": "t_ens801f0", "vf": "1"}, 21}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:2e:01.1", "pf": "t_ens801f0", "vf": "1"}, 22}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:2e:01.1", "pf": "t_ens801f0", "vf": "1"}, 23}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:2e:01.1", "pf": "t_ens801f0", "vf": "1"}, 24}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:2e:01.1", "pf": "t_ens801f0", "vf": "1"}, 25}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:2e:01.1", "pf": "t_ens801f0", "vf": "1"}, 26}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:2e:01.1", "pf": "t_ens801f0", "vf": "1"}, 27}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:2e:01.1", "pf": "t_ens801f0", "vf": "1"}, 28}}, + "collecting sr-iov device metrics", + "collector priority: \\[netlink\\]", + "t_ens801f0 - using netlink collector"), + Entry("with both sysfs and netlink", + []string{"sysfs", "netlink"}, + fstest.MapFS{ + "0000:3f:00.0/sriov_totalvfs": {Data: []byte("64")}, + "0000:3f:00.0/net/t_ens785f0": {Mode: fs.ModeDir}, + "0000:3f:00.0/numa_node": {Data: []byte("0")}, + "0000:3f:00.0/class": {Data: []byte("0x020000")}, + "0000:3f:00.0/virtfn0": {Data: []byte("/sys/devices/0000:3f:01.0"), Mode: fs.ModeSymlink}, + "t_ens785f0/device/sriov/0/stats/rx_packets": {Data: []byte("4")}, + "t_ens785f0/device/sriov/0/stats/tx_packets": {Data: []byte("8")}, + "0000:4g:00.0/sriov_totalvfs": {Data: []byte("128")}, + "0000:4g:00.0/net/t_ens801f0": {Mode: fs.ModeDir}, + "0000:4g:00.0/numa_node": {Data: []byte("0")}, + "0000:4g:00.0/class": {Data: []byte("0x020000")}, + "0000:4g:00.0/virtfn0": {Data: []byte("/sys/devices/0000:4g:01.0"), Mode: fs.ModeSymlink}}, + &drvinfo.DriverInfo{Name: "ice", Version: "1.9.11"}, + netlink.Device{LinkAttrs: netlink.LinkAttrs{Vfs: []netlink.VfInfo{ + {0, net.HardwareAddr{}, 0, 0, 0, true, 0, 0, 0, 31, 32, 33, 34, 35, 36, 37, 38, 0, 0}}}}, //nolint:govet + []metric{ + {map[string]string{"numa_node": "0", "pciAddr": "0000:3f:01.0", "pf": "t_ens785f0", "vf": "0"}, 4}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:3f:01.0", "pf": "t_ens785f0", "vf": "0"}, 8}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:4g:01.0", "pf": "t_ens801f0", "vf": "0"}, 31}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:4g:01.0", "pf": "t_ens801f0", "vf": "0"}, 32}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:4g:01.0", "pf": "t_ens801f0", "vf": "0"}, 33}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:4g:01.0", "pf": "t_ens801f0", "vf": "0"}, 34}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:4g:01.0", "pf": "t_ens801f0", "vf": "0"}, 35}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:4g:01.0", "pf": "t_ens801f0", "vf": "0"}, 36}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:4g:01.0", "pf": "t_ens801f0", "vf": "0"}, 37}, + {map[string]string{"numa_node": "0", "pciAddr": "0000:4g:01.0", "pf": "t_ens801f0", "vf": "0"}, 38}}, + "collecting sr-iov device metrics", + "collector priority: \\[sysfs netlink\\]", + "t_ens785f0 - using sysfs collector", + "getting stats for t_ens785f0 vf\\d"), + + // These logs are expected, but were causing instability in this test case, removed for now + // "t_ens801f0 does not support sysfs collector, directory 't_ens801f0/device/sriov' does not exist", + // "t_ens801f0 - using netlink collector", +) + +var _ = DescribeTable("test creating sriovDev collector", // createSriovDevCollector + func(fsys fs.FS, expected sriovDevCollector, logs ...string) { + devfs = fsys + + collector := createSriovDevCollector() + Expect(collector).To(Equal(expected)) + + assertLogs(logs) + }, + Entry("only sriov net devices", + fstest.MapFS{ + "0000:1a:00.0/sriov_totalvfs": {Data: []byte("64")}, + "0000:1a:00.0/numa_node": {Data: []byte("1")}, + "0000:1a:00.0/class": {Data: []byte("0x020000")}, + "0000:1a:00.1/sriov_totalvfs": {Data: []byte("64")}, + "0000:1a:00.1/numa_node": {Data: []byte("1")}, + "0000:1a:00.1/class": {Data: []byte("0x020000")}, + "0000:2b:00.0/sriov_totalvfs": {Data: []byte("128")}, + "0000:2b:00.0/numa_node": {Data: []byte("2")}, + "0000:2b:00.0/class": {Data: []byte("0x020000")}, + "0000:2b:00.1/sriov_totalvfs": {Data: []byte("128")}, + "0000:2b:00.1/numa_node": {Data: []byte("2")}, + "0000:2b:00.1/class": {Data: []byte("0x020000")}}, + sriovDevCollector{ + "vfstats", + map[string]string{"0000:1a:00.0": "1", "0000:1a:00.1": "1", "0000:2b:00.0": "2", "0000:2b:00.1": "2"}}), + Entry("mixed devices", + fstest.MapFS{ + "0000:3c:00.0/sriov_totalvfs": {Data: []byte("63")}, + "0000:3c:00.0/numa_node": {Data: []byte("1")}, + "0000:3c:00.0/class": {Data: []byte("0x020000")}, + "0000:3c:00.1/sriov_totalvfs": {Data: []byte("63")}, + "0000:3c:00.1/numa_node": {Data: []byte("1")}, + "0000:3c:00.1/class": {Data: []byte("0x020000")}, + "0000:4d:00.0/sriov_totalvfs": {Data: []byte("64")}, + "0000:4d:00.0/numa_node": {Data: []byte("-1")}, + "0000:4d:00.0/class": {Data: []byte("0x020000")}, + "0000:4d:00.1/sriov_totalvfs": {Data: []byte("64")}, + "0000:4d:00.1/numa_node": {Data: []byte("-1")}, + "0000:4d:00.1/class": {Data: []byte("0x020000")}}, + sriovDevCollector{ + "vfstats", + map[string]string{"0000:3c:00.0": "1", "0000:3c:00.1": "1", "0000:4d:00.0": "", "0000:4d:00.1": ""}}, + "no numa node information for device '0000:4d:00.0'", + "no numa node information for device '0000:4d:00.1'"), + Entry("no sriov net devices", + fstest.MapFS{ + "0000:5e:00.0/": {Mode: fs.ModeDir}, + "0000:5e:00.1/": {Mode: fs.ModeDir}, + "0000:5e:00.2/": {Mode: fs.ModeDir}, + "0000:5e:00.3/": {Mode: fs.ModeDir}}, + sriovDevCollector{ + "vfstats", + map[string]string{}}, + "no sriov net devices found"), +) + +var _ = DescribeTable("test getting sriov devices from filesystem", // getSriovDevAddrs + func(fsys fs.FS, driver *drvinfo.DriverInfo, expected []string, logs ...string) { + devfs = fsys + + // TODO: replace with ethtool mock + drvinfo.GetDriverInfo = func(name string) (*drvinfo.DriverInfo, error) { + return driver, nil + } + + // TODO: replace with fstest.MapFS entry + supportedDrivers = drvinfo.SupportedDrivers{Drivers: drvinfo.DriversList{Drivers: []drvinfo.DriverInfo{*driver}}, DbFilePath: ""} + + devs := getSriovDevAddrs() + Expect(devs).To(Equal(expected)) + + assertLogs(logs) + }, + Entry("only sriov net devices", + fstest.MapFS{ + "0000:6f:00.0/sriov_totalvfs": {Data: []byte("64")}, "0000:6f:00.0/class": {Data: []byte("0x020000")}, + "0000:6f:00.1/sriov_totalvfs": {Data: []byte("64")}, "0000:6f:00.1/class": {Data: []byte("0x020000")}, + "0000:7g:00.0/sriov_totalvfs": {Data: []byte("128")}, "0000:7g:00.0/class": {Data: []byte("0x020000")}, + "0000:7g:00.1/sriov_totalvfs": {Data: []byte("128")}, "0000:7g:00.1/class": {Data: []byte("0x020000")}}, + &drvinfo.DriverInfo{Name: "ice", Version: "1.9.11"}, + []string{"0000:6f:00.0", "0000:6f:00.1", "0000:7g:00.0", "0000:7g:00.1"}), + Entry("mixed devices", + fstest.MapFS{ + "0000:8h:00.0/": {Mode: fs.ModeDir}, + "0000:8h:00.1/": {Mode: fs.ModeDir}, + "0000:9i:00.0/sriov_totalvfs": {Data: []byte("63")}, "0000:9i:00.0/class": {Data: []byte("0x020000")}, + "0000:9i:00.1/sriov_totalvfs": {Data: []byte("63")}, "0000:9i:00.1/class": {Data: []byte("0x020000")}}, + &drvinfo.DriverInfo{Name: "ice", Version: "1.9.11"}, + []string{"0000:9i:00.0", "0000:9i:00.1"}), + Entry("no sriov net devices", + fstest.MapFS{ + "0000:1b:00.0/": {Mode: fs.ModeDir}, + "0000:1b:00.1/": {Mode: fs.ModeDir}, + "0000:1b:00.2/": {Mode: fs.ModeDir}, + "0000:1b:00.3/": {Mode: fs.ModeDir}}, + &drvinfo.DriverInfo{Name: "ice", Version: "1.9.11"}, + []string{}, + "no sriov net devices found"), +) + +var _ = DescribeTable("test getting sriov dev details", // getSriovDev + func(dev string, priority []string, fsys fs.FS, driver *drvinfo.DriverInfo, expected sriovDev, logs ...string) { + devfs = fsys + netfs = fsys + + // TODO: replace with ethtool mock + drvinfo.GetDriverInfo = func(name string) (*drvinfo.DriverInfo, error) { + return driver, nil + } + + // TODO: replace with fstest.MapFS entry + supportedDrivers = drvinfo.SupportedDrivers{Drivers: drvinfo.DriversList{Drivers: []drvinfo.DriverInfo{*driver}}, DbFilePath: ""} + + sriovDev := getSriovDev(dev, priority) + Expect(sriovDev).To(Equal(expected)) + + assertLogs(logs) + }, + Entry("with sysfs support", + "0000:4f:00.0", + []string{"sysfs", "netlink"}, + fstest.MapFS{ + "0000:4f:00.0/net/ens785f0": {Mode: fs.ModeDir}, + "0000:4f:00.0/virtfn0": {Data: []byte("/sys/devices/0000:4f:01.0"), Mode: fs.ModeSymlink}, + "0000:4f:00.0/virtfn1": {Data: []byte("/sys/devices/0000:4f:01.1"), Mode: fs.ModeSymlink}, + "ens785f0/device/sriov": {Mode: fs.ModeDir}, // Added to enable sysfs reader + "0000:5g:00.0/net/ens801f0": {Mode: fs.ModeDir}, + "0000:5g:00.0/virtfn0": {Data: []byte("/sys/devices/0000:5g:01.0"), Mode: fs.ModeSymlink}}, + &drvinfo.DriverInfo{Name: "ice", Version: "1.9.11"}, + sriovDev{ + "ens785f0", + sysfsReader{"/sys/class/net/%s/device/sriov/%s/stats"}, + map[string]string{"0": "0000:4f:01.0", "1": "0000:4f:01.1"}}, + "ens785f0 - using sysfs collector"), + Entry("without sysfs support", + "0000:6h:00.0", + []string{"sysfs", "netlink"}, + fstest.MapFS{ + "0000:6h:00.0/net/ens785f0": {Mode: fs.ModeDir}, + "0000:6h:00.0/virtfn0": {Data: []byte("/sys/devices/0000:6h:01.0"), Mode: fs.ModeSymlink}, + "0000:6h:00.0/virtfn1": {Data: []byte("/sys/devices/0000:6h:01.1"), Mode: fs.ModeSymlink}, + "0000:7i:00.0/net/ens801f0": {Mode: fs.ModeDir}, + "0000:7i:00.0/virtfn0": {Data: []byte("/sys/devices/0000:7i:01.0"), Mode: fs.ModeSymlink}}, + &drvinfo.DriverInfo{Name: "ice", Version: "1.9.11"}, + sriovDev{ + "ens785f0", + netlinkReader{vfstats.VfStats("ens785f0")}, + map[string]string{"0": "0000:6h:01.0", "1": "0000:6h:01.1"}}, + "ens785f0 does not support sysfs collector", + "ens785f0 - using netlink collector"), + Entry("without any collector support", + "0000:8j:00.0", + []string{"unsupported_collector"}, + fstest.MapFS{ + "0000:8j:00.0/net/ens785f0": {Mode: fs.ModeDir}, + "0000:8j:00.0/virtfn0": {Data: []byte("/sys/devices/0000:8j:01.0"), Mode: fs.ModeSymlink}, + "0000:8j:00.0/virtfn1": {Data: []byte("/sys/devices/0000:8j:01.1"), Mode: fs.ModeSymlink}}, + &drvinfo.DriverInfo{Name: "ice", Version: "1.9.11"}, + sriovDev{ + "ens785f0", + nil, + map[string]string{"0": "0000:8j:01.0", "1": "0000:8j:01.1"}}, + "ens785f0 - 'unsupported_collector' collector not supported"), + Entry("without any virtual functions", + "0000:9k:00.0", + []string{"sysfs"}, + fstest.MapFS{ + "0000:9k:00.0/net/ens785f0": {Mode: fs.ModeDir}}, + &drvinfo.DriverInfo{Name: "ice", Version: "1.9.11"}, + sriovDev{ + "ens785f0", + nil, + map[string]string{}}, + "error getting vf address", + "no virtual functions found for pf '0000:9k:00.0'", + "ens785f0 does not support sysfs collector"), +) + +var _ = DescribeTable("test getting numa node information for devices from filesystem", // getNumaNodes // TODO: ensure map order + func(devices []string, fsys fs.FS, expected map[string]string, logs ...string) { + devfs = fsys + + numaNodes := getNumaNodes(devices) + Expect(numaNodes).To(Equal(expected)) + + assertLogs(logs) + }, + Entry("only sriov net devices", + []string{"0000:2c:00.0", "0000:2c:00.1", "0000:3d:00.0", "0000:3d:00.1"}, + fstest.MapFS{ + "0000:2c:00.0/numa_node": {Data: []byte("0")}, + "0000:2c:00.1/numa_node": {Data: []byte("0")}, + "0000:3d:00.0/numa_node": {Data: []byte("1")}, + "0000:3d:00.1/numa_node": {Data: []byte("1")}}, + map[string]string{"0000:2c:00.0": "0", "0000:2c:00.1": "0", "0000:3d:00.0": "1", "0000:3d:00.1": "1"}), + Entry("mixed devices", + []string{"0000:4e:00.0", "0000:4e:00.1", "0000:5f:00.0", "0000:5f:00.1"}, + fstest.MapFS{ + "0000:4e:00.0/": {Mode: fs.ModeDir}, + "0000:4e:00.1/": {Mode: fs.ModeDir}, + "0000:5f:00.0/numa_node": {Data: []byte("-1")}, + "0000:5f:00.1/numa_node": {Data: []byte("-1")}}, + map[string]string{"0000:4e:00.0": "", "0000:4e:00.1": "", "0000:5f:00.0": "", "0000:5f:00.1": ""}, + "could not read numa_node file for device '0000:4e:00.0'", + "open 0000:4e:00.0/numa_node: file does not exist", + "could not read numa_node file for device '0000:4e:00.1'", + "open 0000:4e:00.1/numa_node: file does not exist", + "no numa node information for device '0000:5f:00.0'", + "no numa node information for device '0000:5f:00.1'"), + Entry("no sriov net devices", + []string{"0000:6g:00.0", "0000:6g:00.1", "0000:6g:00.2", "0000:6g:00.3"}, + fstest.MapFS{ + "0000:6g:00.0/": {Mode: fs.ModeDir}, + "0000:6g:00.1/": {Mode: fs.ModeDir}, + "0000:6g:00.2/": {Mode: fs.ModeDir}, + "0000:6g:00.3/": {Mode: fs.ModeDir}}, + map[string]string{"0000:6g:00.0": "", "0000:6g:00.1": "", "0000:6g:00.2": "", "0000:6g:00.3": ""}, + "could not read numa_node file for device '0000:6g:00.0'", + "open 0000:6g:00.0/numa_node: file does not exist", + "could not read numa_node file for device '0000:6g:00.1'", + "open 0000:6g:00.1/numa_node: file does not exist", + "could not read numa_node file for device '0000:6g:00.2'", + "open 0000:6g:00.2/numa_node: file does not exist", + "could not read numa_node file for device '0000:6g:00.3'", + "open 0000:6g:00.3/numa_node: file does not exist"), +) + +var _ = DescribeTable("test getting vf information for devices from filesystem", // vfList + func(dev string, fsys fs.FS, expected vfsPCIAddr, err error, logs ...string) { + devfs = fsys + + vfs, e := vfList(dev) + Expect(vfs).To(Equal(expected)) + + if err != nil { + Expect(e).Should(MatchError(err)) + } + + assertLogs(logs) + }, + Entry("only retrieve vf information for specified sriov net device", + "0000:7h:00.0", + fstest.MapFS{ + "0000:7h:00.0/virtfn0": {Data: []byte("/sys/devices/0000:7h:01.0"), Mode: fs.ModeSymlink}, + "0000:7h:00.0/virtfn1": {Data: []byte("/sys/devices/0000:7h:01.1"), Mode: fs.ModeSymlink}, + "0000:8i:00.0/virtfn0": {Data: []byte("/sys/devices/0000:8i:01.0"), Mode: fs.ModeSymlink}}, + map[string]string{"0": "0000:7h:01.0", "1": "0000:7h:01.1"}, + nil), + Entry("vf file is not a symlink for specified sriov net device", + "0000:9j:00.0", + fstest.MapFS{ + "0000:9j:00.0/virtfn0": {Data: []byte("/sys/devices/0000:9j:01.0"), Mode: fs.ModeDir}}, + map[string]string{}, + fmt.Errorf("no virtual functions found for pf '0000:9j:00.0'"), + "error evaluating symlink '0000:9j:00.0/virtfn0'"), + Entry("vf file does not exist for specified sriov net device", + "0000:1c:00.0", + fstest.MapFS{}, + map[string]string{}, + fmt.Errorf("no virtual functions found for pf '0000:1c:00.0'")), +) + +var _ = DescribeTable("test getting vf data from filesystem", // vfData + func(vfDir string, fsys fs.FS, expectedVfId string, expectedVfPciAddr string, logs ...string) { + devfs = fsys + + vfId, vfPci := vfData(vfDir) + Expect(vfId).To(Equal(expectedVfId)) + Expect(vfPci).To(Equal(expectedVfPciAddr)) + + assertLogs(logs) + }, + Entry("valid symlink", + "0000:7h:00.0/virtfn0", + fstest.MapFS{"0000:7h:00.0/virtfn0": {Data: []byte("/sys/devices/0000:7h:01.0"), Mode: fs.ModeSymlink}}, + "0", + "0000:7h:01.0"), + Entry("invalid symlink", + "0000:8i:00.0/virtfn0", + fstest.MapFS{"0000:8i:00.0/virtfn0": {Mode: fs.ModeDir}}, + "", + "", + "error evaluating symlink '0000:8i:00.0/virtfn0'"), +) + +var _ = DescribeTable("test getting pf name from pci address on filesystem", // getPFName + func(dev string, fsys fs.FS, expected string, logs ...string) { + devfs = fsys + + pfName := getPFName(dev) + Expect(pfName).To(Equal(expected)) + + assertLogs(logs) + }, + Entry("pf exists", + "0000:2d:00.0", + fstest.MapFS{"0000:2d:00.0/net/ens785f0": {Mode: fs.ModeDir}}, + "ens785f0"), + Entry("pf does not exist", + "0000:3e:00.0", + fstest.MapFS{}, + "", + "0000:3e:00.0 - could not get pf interface name in path '0000:3e:00.0/net'", + "open 0000:3e:00.0/net: file does not exist"), +) diff --git a/go.mod b/go.mod index 2ac2d3c..20b0587 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,16 @@ go 1.18 require ( github.com/hashicorp/go-version v1.6.0 - github.com/onsi/ginkgo/v2 v2.4.0 - github.com/onsi/gomega v1.22.1 + github.com/onsi/ginkgo/v2 v2.5.1 + github.com/onsi/gomega v1.24.0 github.com/prometheus/client_golang v1.12.1 + github.com/prometheus/client_model v0.2.0 github.com/safchain/ethtool v0.2.0 github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852 golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 - google.golang.org/grpc v1.47.0 + google.golang.org/grpc v1.52.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/kubelet v0.25.0 + k8s.io/kubelet v0.25.5 ) require ( @@ -21,15 +22,14 @@ require ( github.com/go-logr/logr v1.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.8 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect - github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect - golang.org/x/net v0.1.0 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/text v0.4.0 // indirect - google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect - google.golang.org/protobuf v1.28.0 // indirect + golang.org/x/net v0.4.0 // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/text v0.5.0 // indirect + google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect + google.golang.org/protobuf v1.28.1 // indirect ) diff --git a/go.sum b/go.sum index d408573..cf8f2b3 100644 --- a/go.sum +++ b/go.sum @@ -38,7 +38,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -52,20 +51,12 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -120,9 +111,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -134,10 +124,8 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -172,10 +160,10 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= -github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= -github.com/onsi/gomega v1.22.1 h1:pY8O4lBfsHKZHM/6nrxkhVPUznOlIu3quZcKP/M20KI= -github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= +github.com/onsi/ginkgo/v2 v2.5.1 h1:auzK7OI497k6x4OvWq+TKAcpcSAlod0doAH72oIN0Jw= +github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= +github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= +github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -202,7 +190,6 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/safchain/ethtool v0.2.0 h1:dILxMBqDnQfX192cCAPjZr9v2IgVXeElHPy435Z/IdE= github.com/safchain/ethtool v0.2.0/go.mod h1:WkKB1DnNtvsMlDmQ50sgwowDJV/hGbJSOvJoEXs1AJQ= @@ -214,8 +201,6 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852 h1:cPXZWzzG0NllBLdjWoD1nDfaqu98YMv+OneaKc8sPOA= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns= @@ -229,7 +214,6 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -295,10 +279,9 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -350,26 +333,22 @@ golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -466,15 +445,14 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 h1:a2S6M0+660BgMNl++4JPlcAO/CjkqYItDEZwkoDQK7c= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -487,11 +465,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk= +google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -504,9 +479,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -515,12 +489,10 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -530,8 +502,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/kubelet v0.25.0 h1:eTS5B1u1o63ndExAHKLJytzz/GBy86ROcxYtu0VK3RA= -k8s.io/kubelet v0.25.0/go.mod h1:J6aQxrZdSsGPrskYrhZdEn6PCnGha+GNvF0g9aWfQnw= +k8s.io/kubelet v0.25.5 h1:2c2qDGQ49cuX4oLF8nEEGHzHovAOxPZOO7iEV868lbc= +k8s.io/kubelet v0.25.5/go.mod h1:8QDPQLg9k5NxkekMV9ANQYLHaSekKdkdBupY6YPT/pk= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/pkg/drvinfo/drvinfo.go b/pkg/drvinfo/drvinfo.go index 824547f..5ba756b 100644 --- a/pkg/drvinfo/drvinfo.go +++ b/pkg/drvinfo/drvinfo.go @@ -33,10 +33,10 @@ type DriversList struct { type SupportedDrivers struct { Drivers DriversList - dbFilePath string + DbFilePath string } -func NewSupportedDrivers(fp string) SupportedDrivers { +var NewSupportedDrivers = func(fp string) SupportedDrivers { retv := SupportedDrivers{} supportedDrivers, err := readSupportedDrivers(fp) if err != nil { @@ -44,11 +44,11 @@ func NewSupportedDrivers(fp string) SupportedDrivers { return retv } retv.Drivers = *supportedDrivers - retv.dbFilePath = fp + retv.DbFilePath = fp return retv } -func GetDriverInfo(name string) (*DriverInfo, error) { +var GetDriverInfo = func(name string) (*DriverInfo, error) { ethHandle, err := newEthtool() if err != nil { return nil, err diff --git a/pkg/utils/test/target b/pkg/utils/test/target new file mode 100644 index 0000000..3b18e51 --- /dev/null +++ b/pkg/utils/test/target @@ -0,0 +1 @@ +hello world diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 1ed1162..8e510b3 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -45,6 +45,7 @@ func ResolvePath(path *string) error { evaluatedPath, err := EvalSymlinks(cleanPath) if err != nil { + *path = cleanPath return fmt.Errorf("unable to evaluate symbolic links on path '%s'\n%v", *path, err) } diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go new file mode 100644 index 0000000..4b20bad --- /dev/null +++ b/pkg/utils/utils_test.go @@ -0,0 +1,98 @@ +package utils + +import ( + "fmt" + "io/fs" + "log" + "os" + "path/filepath" + "testing" + "testing/fstest" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +const ( + linkPath = "test/link" + targetPath = "test/target" +) + +func TestUtils(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "utils test suite") +} + +var _ = BeforeSuite(func() { + log.SetFlags(0) +}) + +var _ = AfterEach(func() { + os.Remove(linkPath) +}) + +var _ = DescribeTable("test path resolution", // ResolvePath + func(input string, output string, isSymlink bool, expectedErr error) { + if isSymlink { + targetPath, err := filepath.Abs(output) + Expect(err).ToNot(HaveOccurred()) + + err = os.Symlink(targetPath, input) + Expect(err).ToNot(HaveOccurred()) + } + + err := ResolvePath(&input) + Expect(input).To(Equal(output)) + + if expectedErr != nil { + Expect(err).To(Equal(expectedErr)) + } + }, + Entry("path resolved without change", "/var/lib/kubelet/cpu_manager_state", "/var/lib/kubelet/cpu_manager_state", false, nil), + Entry("path resolved with change", "/var/lib/../lib/kubelet/cpu_manager_state", "/var/lib/kubelet/cpu_manager_state", false, nil), + Entry("empty path", "", "", false, fmt.Errorf("unable to resolve an empty path")), + Entry("symbolic link", linkPath, getAbsPath(targetPath), true, nil), +) + +var _ = DescribeTable("test flag resolution", // ResolveFlag + func(flag string, path string, expectedResult string, expectedErr error) { + err := ResolveFlag(flag, &path) + Expect(path).To(Equal(expectedResult)) + + if expectedErr != nil { + Expect(err).To(Equal(expectedErr)) + } + }, + Entry("flag resolved", "test_flag1", "/var/lib/kubelet/cpu_manager_state", "/var/lib/kubelet/cpu_manager_state", nil), + Entry("empty path", "test_flag2", "", "", fmt.Errorf("test_flag2 - unable to resolve an empty path")), +) + +var _ = DescribeTable("test IsSymLink", // IsSymLink + func(fsys fs.FS, path string, expected bool) { + Expect(IsSymLink(fsys, path)).To(Equal(expected)) + }, + Entry("with symlink", fstest.MapFS{"test_file": {Mode: fs.ModeSymlink}}, "test_file", true), + Entry("without symlink", fstest.MapFS{"test_file": {Mode: fs.ModeDir}}, "test_file", false), +) + +var _ = DescribeTable("test StringListFlag type", // StringListFlag + func(input string, expectedSlice StringListFlag, expectedString string) { + var list StringListFlag + err := list.Set(input) + + Expect(err).ToNot(HaveOccurred()) + Expect(list).To(Equal(expectedSlice)) + Expect(list.String()).To(Equal(expectedString)) + }, + Entry("just one value", "sysfs", StringListFlag{"sysfs"}, "sysfs"), + Entry("two values", "sysfs,netlink", StringListFlag{"sysfs", "netlink"}, "sysfs,netlink"), + Entry("odd formatting", " sysfs , netlink ", StringListFlag{"sysfs", "netlink"}, "sysfs,netlink"), +) + +func getAbsPath(fp string) string { + absPath, err := filepath.Abs(fp) + if err != nil { + log.Printf("Failed to get absolute path, %v", err.Error()) + } + return absPath +} diff --git a/pkg/vfstats/netlink_test.go b/pkg/vfstats/netlink_test.go new file mode 100644 index 0000000..5344e9c --- /dev/null +++ b/pkg/vfstats/netlink_test.go @@ -0,0 +1,41 @@ +package vfstats + +import ( + "fmt" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/vishvananda/netlink" +) + +func TestNetlink(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "netlink test suite") +} + +var _ = DescribeTable("test vf stats collection", // VfStats + func(devName string, link netlink.Device, err error, expectedPerPF PerPF, logs ...string) { + GetLink = func(name string) (netlink.Link, error) { + return &link, err + } + + Expect(VfStats(devName)).To(Equal(expectedPerPF)) + }, + Entry("Without error", + "ens801f0", + netlink.Device{LinkAttrs: netlink.LinkAttrs{Vfs: []netlink.VfInfo{ + {ID: 0, Mac: nil, Vlan: 0, Qos: 0, TxRate: 0, Spoofchk: true, LinkState: 0, MaxTxRate: 0, MinTxRate: 0, RxPackets: 11, TxPackets: 12, RxBytes: 13, TxBytes: 14, Multicast: 15, Broadcast: 16, RxDropped: 17, TxDropped: 18, RssQuery: 0, Trust: 0}, + {ID: 1, Mac: nil, Vlan: 0, Qos: 0, TxRate: 0, Spoofchk: true, LinkState: 0, MaxTxRate: 0, MinTxRate: 0, RxPackets: 21, TxPackets: 22, RxBytes: 23, TxBytes: 24, Multicast: 25, Broadcast: 26, RxDropped: 27, TxDropped: 28, RssQuery: 0, Trust: 0}}}}, + nil, + PerPF{"ens801f0", map[int]netlink.VfInfo{ + 0: {ID: 0, Mac: nil, Vlan: 0, Qos: 0, TxRate: 0, Spoofchk: true, LinkState: 0, MaxTxRate: 0, MinTxRate: 0, RxPackets: 11, TxPackets: 12, RxBytes: 13, TxBytes: 14, Multicast: 15, Broadcast: 16, RxDropped: 17, TxDropped: 18, RssQuery: 0, Trust: 0}, + 1: {ID: 1, Mac: nil, Vlan: 0, Qos: 0, TxRate: 0, Spoofchk: true, LinkState: 0, MaxTxRate: 0, MinTxRate: 0, RxPackets: 21, TxPackets: 22, RxBytes: 23, TxBytes: 24, Multicast: 25, Broadcast: 26, RxDropped: 27, TxDropped: 28, RssQuery: 0, Trust: 0}}}, + ), + Entry("With error", + "ens801f0", + nil, + fmt.Errorf("Link not found"), + PerPF{"ens801f0", map[int]netlink.VfInfo{}}, + ), +)