From bbde409bb1e239e2c80362e36ca8c87fc0d10a09 Mon Sep 17 00:00:00 2001 From: Victor Toso Date: Thu, 29 Jun 2023 09:34:44 +0200 Subject: [PATCH] hostdev: usb: Add functional testing The test takes inspiration on PCI's tests/vmi_hostdev_test.go Since the introduction of emulated USB devices [0], this can be tested locally with: > export KUBEVIRT_PROVIDER=k8s-1.26-centos9 > export KUBEVIRTCI_TAG=latest > export KUBEVIRT_PROVIDER_EXTRA_ARGS="--usb 20M --usb 40M" > make cluster-up [0] https://github.com/kubevirt/kubevirtci/pull/996 Signed-off-by: Luboslav Pivarc Signed-off-by: Victor Toso --- pkg/virt-handler/device-manager/usb_device.go | 2 +- tests/BUILD.bazel | 1 + tests/decorators/decorators.go | 1 + tests/tests_suite_test.go | 1 + tests/usb/BUILD.bazel | 24 ++++ tests/usb/usb.go | 117 ++++++++++++++++++ 6 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 tests/usb/BUILD.bazel create mode 100644 tests/usb/usb.go diff --git a/pkg/virt-handler/device-manager/usb_device.go b/pkg/virt-handler/device-manager/usb_device.go index 4d55fde3b042..3a687185c6d3 100644 --- a/pkg/virt-handler/device-manager/usb_device.go +++ b/pkg/virt-handler/device-manager/usb_device.go @@ -482,7 +482,7 @@ func parseSysUeventFile(path string) *USBDevice { } u.BCD = int(val) case "DEVNAME": - u.DevicePath = "/dev/" + values[1] + u.DevicePath = filepath.Join("/dev", values[1]) default: log.Log.V(5).Infof("Skipping unhandled line: %s", line) } diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 7e2a5265be55..bba826723922 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -230,6 +230,7 @@ go_test( "//tests/scale:go_default_library", "//tests/storage:go_default_library", "//tests/testsuite:go_default_library", + "//tests/usb:go_default_library", "//tests/util:go_default_library", "//tests/virtctl:go_default_library", "//tests/virtiofs:go_default_library", diff --git a/tests/decorators/decorators.go b/tests/decorators/decorators.go index 0b36cd772705..2c5b17083370 100644 --- a/tests/decorators/decorators.go +++ b/tests/decorators/decorators.go @@ -44,4 +44,5 @@ var ( RequiresTwoSchedulableNodes = []interface{}{Label("requires-two-schedulable-nodes")} VMLiveUpdateFeaturesGate = []interface{}{Label("VMLiveUpdateFeaturesGate")} RequiresRWXFilesystemStorage = []interface{}{Label("rwxfs")} + USB = []interface{}{Label("USB")} ) diff --git a/tests/tests_suite_test.go b/tests/tests_suite_test.go index 7c43646a82e9..7370fc74a5b9 100644 --- a/tests/tests_suite_test.go +++ b/tests/tests_suite_test.go @@ -53,6 +53,7 @@ import ( _ "kubevirt.io/kubevirt/tests/realtime" _ "kubevirt.io/kubevirt/tests/scale" _ "kubevirt.io/kubevirt/tests/storage" + _ "kubevirt.io/kubevirt/tests/usb" _ "kubevirt.io/kubevirt/tests/virtctl" _ "kubevirt.io/kubevirt/tests/virtiofs" ) diff --git a/tests/usb/BUILD.bazel b/tests/usb/BUILD.bazel new file mode 100644 index 000000000000..d15fc9000cac --- /dev/null +++ b/tests/usb/BUILD.bazel @@ -0,0 +1,24 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["usb.go"], + importpath = "kubevirt.io/kubevirt/tests/usb", + visibility = ["//visibility:public"], + deps = [ + "//pkg/virt-config:go_default_library", + "//staging/src/kubevirt.io/api/core/v1:go_default_library", + "//staging/src/kubevirt.io/client-go/kubecli:go_default_library", + "//tests:go_default_library", + "//tests/console:go_default_library", + "//tests/decorators:go_default_library", + "//tests/framework/kubevirt:go_default_library", + "//tests/libvmi:go_default_library", + "//tests/libwait:go_default_library", + "//tests/util:go_default_library", + "//vendor/github.com/google/goexpect:go_default_library", + "//vendor/github.com/onsi/ginkgo/v2:go_default_library", + "//vendor/github.com/onsi/gomega:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + ], +) diff --git a/tests/usb/usb.go b/tests/usb/usb.go new file mode 100644 index 000000000000..a1fdaafc216b --- /dev/null +++ b/tests/usb/usb.go @@ -0,0 +1,117 @@ +package usb + +import ( + "context" + "fmt" + "strings" + + expect "github.com/google/goexpect" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + v1 "kubevirt.io/api/core/v1" + "kubevirt.io/client-go/kubecli" + + virtconfig "kubevirt.io/kubevirt/pkg/virt-config" + "kubevirt.io/kubevirt/tests" + "kubevirt.io/kubevirt/tests/console" + "kubevirt.io/kubevirt/tests/decorators" + "kubevirt.io/kubevirt/tests/framework/kubevirt" + "kubevirt.io/kubevirt/tests/libvmi" + "kubevirt.io/kubevirt/tests/libwait" + "kubevirt.io/kubevirt/tests/util" +) + +const ( + failedDeleteVMI = "Failed to delete VMI" + cmdNumberUSBs = "dmesg | grep -c idVendor=46f4" +) + +var _ = Describe("[Serial][sig-compute][USB] host USB Passthrough", Serial, decorators.SigCompute, decorators.USB, func() { + var ( + virtClient kubecli.KubevirtClient + config v1.KubeVirtConfiguration + ) + + BeforeEach(func() { + virtClient = kubevirt.Client() + kv := util.GetCurrentKv(virtClient) + config = kv.Spec.Configuration + + nodeName := tests.NodeNameWithHandler() + Expect(nodeName).ToNot(BeEmpty()) + + // Emulated USB devices only on c9s providers. Remove this when sig-compute 1.26 is the + // oldest sig-compute with test with. + // See: https://github.com/kubevirt/project-infra/pull/2922 + stdout, err := tests.ExecuteCommandInVirtHandlerPod(nodeName, []string{"dmesg"}) + Expect(err).ToNot(HaveOccurred()) + if strings.Count(stdout, "idVendor=46f4") == 0 { + Skip("No emulated USB devices present for functional test.") + } + }) + + AfterEach(func() { + kv := util.GetCurrentKv(virtClient) + // Reinitialized the DeveloperConfiguration to avoid to influence the next test + config = kv.Spec.Configuration + config.DeveloperConfiguration = &v1.DeveloperConfiguration{} + config.PermittedHostDevices = &v1.PermittedHostDevices{} + tests.UpdateKubeVirtConfigValueAndWait(config) + }) + + Context("with usb storage", func() { + DescribeTable("with emulated USB devices", func(deviceNames []string) { + const resourceName = "kubevirt.io/usb-storage" + + By("Adding the emulated USB device to the permitted host devices") + config.DeveloperConfiguration = &v1.DeveloperConfiguration{ + FeatureGates: []string{virtconfig.HostDevicesGate}, + } + config.PermittedHostDevices = &v1.PermittedHostDevices{ + USB: []v1.USBHostDevice{ + { + ResourceName: resourceName, + Selectors: []v1.USBSelector{ + { + Vendor: "46f4", + Product: "0001", + }}, + }}, + } + tests.UpdateKubeVirtConfigValueAndWait(config) + + By("Creating a Fedora VMI with the usb host device") + hostDevs := []v1.HostDevice{} + for i, name := range deviceNames { + hostDevs = append(hostDevs, v1.HostDevice{ + Name: fmt.Sprintf("usb-%d-%s", i, name), + DeviceName: resourceName, + }) + } + + randomVMI := libvmi.NewCirros() + randomVMI.Spec.Domain.Devices.HostDevices = hostDevs + vmi, err := virtClient.VirtualMachineInstance(util.NamespaceTestDefault).Create(context.Background(), randomVMI) + Expect(err).ToNot(HaveOccurred()) + libwait.WaitForSuccessfulVMIStart(vmi) + Expect(console.LoginToCirros(vmi)).To(Succeed()) + + By("Making sure the usb is present inside the VMI") + Expect(console.SafeExpectBatch(vmi, []expect.Batcher{ + &expect.BSnd{S: fmt.Sprintf("%s\n", cmdNumberUSBs)}, + &expect.BExp{R: console.RetValue(fmt.Sprintf("%d", len(deviceNames)))}, + }, 15)).To(Succeed(), "Device not found") + + // Make sure to delete the VMI before ending the test otherwise a device could still be taken + err = virtClient.VirtualMachineInstance(util.NamespaceTestDefault).Delete(context.Background(), vmi.ObjectMeta.Name, &metav1.DeleteOptions{}) + Expect(err).ToNot(HaveOccurred(), failedDeleteVMI) + libwait.WaitForVirtualMachineToDisappearWithTimeout(vmi, 180) + }, + Entry("Should successfully passthrough 1 emulated USB device", []string{"slow-storage"}), + Entry("Should successfully passthrough 2 emulated USB devices", []string{"fast-storage", "low-storage"}), + ) + }) +})