diff --git a/test/conformance/tests/test_sriov_operator.go b/test/conformance/tests/test_sriov_operator.go index b665c99f0..3c3cc4e84 100644 --- a/test/conformance/tests/test_sriov_operator.go +++ b/test/conformance/tests/test_sriov_operator.go @@ -1822,6 +1822,7 @@ func findMainSriovDevice(executorPod *corev1.Pod, sriovDevices []*sriovv1.Interf stdout, _, err = pod.ExecCommand(clients, executorPod, "ip", "link", "show", device.Name) Expect(err).ToNot(HaveOccurred()) Expect(len(stdout)).Should(Not(Equal(0)), "Unable to query link state") + if strings.Contains(stdout, "state DOWN") { continue // The interface is not active } @@ -1830,6 +1831,7 @@ func findMainSriovDevice(executorPod *corev1.Pod, sriovDevices []*sriovv1.Interf return device } } + return nil } @@ -2124,7 +2126,7 @@ func createVanillaNetworkPolicy(node string, sriovInfos *cluster.EnabledNodes, n }))) } -func runCommandOnConfigDaemon(nodeName string, command ...string) (string, string, error) { +func getConfigDaemonPod(nodeName string) *corev1.Pod { pods := &corev1.PodList{} label, err := labels.Parse("app=sriov-network-config-daemon") Expect(err).ToNot(HaveOccurred()) @@ -2133,8 +2135,11 @@ func runCommandOnConfigDaemon(nodeName string, command ...string) (string, strin err = clients.List(context.Background(), pods, &runtimeclient.ListOptions{Namespace: operatorNamespace, LabelSelector: label, FieldSelector: field}) Expect(err).ToNot(HaveOccurred()) Expect(len(pods.Items)).To(Equal(1)) + return &pods.Items[0] +} - output, errOutput, err := pod.ExecCommand(clients, &pods.Items[0], command...) +func runCommandOnConfigDaemon(nodeName string, command ...string) (string, string, error) { + output, errOutput, err := pod.ExecCommand(clients, getConfigDaemonPod(nodeName), command...) return output, errOutput, err } diff --git a/test/conformance/tests/test_switchdev.go b/test/conformance/tests/test_switchdev.go new file mode 100644 index 000000000..62e340b1b --- /dev/null +++ b/test/conformance/tests/test_switchdev.go @@ -0,0 +1,107 @@ +package tests + +import ( + "context" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + sriovv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1" + "github.com/k8snetworkplumbingwg/sriov-network-operator/test/util/cluster" + "github.com/k8snetworkplumbingwg/sriov-network-operator/test/util/discovery" + "github.com/k8snetworkplumbingwg/sriov-network-operator/test/util/namespaces" + "github.com/k8snetworkplumbingwg/sriov-network-operator/test/util/network" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("[sriov] Switchdev", Ordered, func() { + + BeforeAll(func() { + if cluster.VirtualCluster() { + Skip("IGB driver does not support switchdev driver model") + } + + err := namespaces.Create(namespaces.Test, clients) + Expect(err).ToNot(HaveOccurred()) + + err = namespaces.Clean(operatorNamespace, namespaces.Test, clients, discovery.Enabled()) + Expect(err).ToNot(HaveOccurred()) + + WaitForSRIOVStable() + }) + + It("create switchdev policies on supported devices", func() { + sriovInfos, err := cluster.DiscoverSriov(clients, operatorNamespace) + Expect(err).ToNot(HaveOccurred()) + Expect(len(sriovInfos.Nodes)).ToNot(BeZero()) + + testNode, interfaces, err := sriovInfos.FindSriovDevicesAndNode() + Expect(err).ToNot(HaveOccurred()) + + // Avoid testing against primary NIC + interfaces, err = findUnusedSriovDevices(testNode, interfaces) + Expect(err).ToNot(HaveOccurred()) + + // Avoid testing the same NIC model more than once + interfaces = filterDuplicateNICModels(interfaces) + + By(fmt.Sprintf("Testing on node %s, %d devices found", testNode, len(interfaces))) + + for _, intf := range interfaces { + if !doesInterfaceSupportSwitchdev(intf) { + continue + } + + By("Testing device " + nameVendorDeviceID(intf) + " on node " + testNode) + resourceName := "swtichdev" + intf.Name + _, err = network.CreateSriovPolicy(clients, "test-switchdev-policy-", operatorNamespace, intf.Name, testNode, 8, resourceName, "netdevice", func(snnp *sriovv1.SriovNetworkNodePolicy) { + snnp.Spec.EswitchMode = "switchdev" + }) + Expect(err).ToNot(HaveOccurred()) + + WaitForSRIOVStable() + + Eventually(func() int64 { + testedNode, err := clients.CoreV1Interface.Nodes().Get(context.Background(), testNode, metav1.GetOptions{}) + Expect(err).ToNot(HaveOccurred()) + resNum := testedNode.Status.Allocatable[corev1.ResourceName("openshift.io/"+resourceName)] + capacity, _ := resNum.AsInt64() + return capacity + }, 10*time.Minute, time.Second).Should(Equal(int64(8))) + } + }) +}) + +func doesInterfaceSupportSwitchdev(intf *sriovv1.InterfaceExt) bool { + if intf.Driver == "mlx5_core" { + return true + } + + if intf.Driver == "ice" { + return true + } + + return false +} + +func filterDuplicateNICModels(devices []*sriovv1.InterfaceExt) []*sriovv1.InterfaceExt { + foundVendorAndModels := map[string]bool{} + ret := []*sriovv1.InterfaceExt{} + + for _, device := range devices { + vendorAndDevice := device.Vendor + "_" + device.DeviceID + if _, value := foundVendorAndModels[vendorAndDevice]; !value { + ret = append(ret, device) + foundVendorAndModels[vendorAndDevice] = true + } + } + return ret +} + +func nameVendorDeviceID(intf *sriovv1.InterfaceExt) string { + return fmt.Sprintf("%s (%s:%s)", intf.Name, intf.Vendor, intf.DeviceID) +} diff --git a/test/util/cluster/cluster.go b/test/util/cluster/cluster.go index b79e61ad2..2d34c327a 100644 --- a/test/util/cluster/cluster.go +++ b/test/util/cluster/cluster.go @@ -153,6 +153,33 @@ func (n *EnabledNodes) FindSriovDevices(node string) ([]*sriovv1.InterfaceExt, e return filteredDevices, nil } +// FindSriovDevicesAndNode retrieves the node with the most number of SRIOV devices after filtering by `SRIOV_NODE_AND_DEVICE_NAME_FILTER` environment variable. +func (n *EnabledNodes) FindSriovDevicesAndNode() (string, []*sriovv1.InterfaceExt, error) { + errs := []error{} + + retNode := "" + retDevices := []*sriovv1.InterfaceExt{} + + for _, node := range n.Nodes { + devices, err := n.FindSriovDevices(node) + if err != nil { + errs = append(errs, err) + continue + } + + if len(devices) > len(retDevices) { + retNode = node + retDevices = devices + } + } + + if len(retDevices) == 0 { + return "", nil, fmt.Errorf("can't find any SR-IOV devices in cluster's nodes: %w", errors.Join(errs...)) + } + + return retNode, retDevices, nil +} + // FindSriovDevicesIgnoreFilters retrieves all valid sriov devices for the given node. func (n *EnabledNodes) FindSriovDevicesIgnoreFilters(node string) ([]*sriovv1.InterfaceExt, error) { devices := []*sriovv1.InterfaceExt{}