From 57f0d3984d648560a2d4db601f75269ad51551ff Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 19:19:54 +0200 Subject: [PATCH 01/27] fix(e2e): mount usb block device in vm test Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index 8e07f0f494..5d7a51ce8c 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -257,11 +257,35 @@ func (t *VMUSBTest) assignNodeUSB() { } func (t *VMUSBTest) mountUSB() { - mountCmd := fmt.Sprintf(` - sudo mkdir -p /mnt/usb || true && \ - sudo mount %s /mnt/usb 2>/dev/null || sudo mount -o rw %s /mnt/usb || true && \ - ls -la /mnt/usb || true - `, t.DevicePath, t.DevicePath) + mountCmd := ` + set -e + for i in $(seq 1 60); do + usb_device=$(lsblk -dpno PATH,TRAN,TYPE 2>/dev/null | awk "\$2 == \"usb\" && \$3 == \"disk\" { print \$1; exit }") + if [ -n "$usb_device" ]; then + break + fi + sleep 1 + done + if [ -z "$usb_device" ]; then + echo "USB block device not found" >&2 + exit 1 + fi + + mount_device="$usb_device" + for partition in "${usb_device}"[0-9]* "${usb_device}"p[0-9]*; do + if [ -b "$partition" ]; then + mount_device="$partition" + break + fi + done + + sudo mkdir -p /mnt/usb + if sudo mountpoint -q /mnt/usb; then + sudo umount /mnt/usb + fi + sudo mount -o rw "$mount_device" /mnt/usb + ls -la /mnt/usb + ` _, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, mountCmd) Expect(err).NotTo(HaveOccurred()) From b6754aabaee7ec0ec815ba814d5bba6b78b18050 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 19:28:53 +0200 Subject: [PATCH 02/27] fix(e2e): check usb detached before vm creation Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index 5d7a51ce8c..db9a39c3bc 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -62,13 +62,10 @@ var _ = Describe("VirtualMachineUSB", func() { } t.GenerateEnvironmentResources() - err := f.CreateWithDeferredDeletion(context.Background(), t.VD, t.VM) + err := f.CreateWithDeferredDeletion(context.Background(), t.VD) Expect(err).NotTo(HaveOccurred()) t.assignNodeUSB() - - util.UntilObjectPhase(string(v1alpha2.MachineRunning), framework.LongTimeout, t.VM) - util.UntilSSHReady(f, t.VM, framework.MiddleTimeout) }) By("Verifying NodeUSBDevice is not attached before VM attachment", func() { @@ -80,6 +77,14 @@ var _ = Describe("VirtualMachineUSB", func() { }).WithTimeout(framework.MaxTimeout).WithPolling(time.Second).Should(Succeed()) }) + By("Creating VM with USB device", func() { + err := f.CreateWithDeferredDeletion(context.Background(), t.VM) + Expect(err).NotTo(HaveOccurred()) + + util.UntilObjectPhase(string(v1alpha2.MachineRunning), framework.LongTimeout, t.VM) + util.UntilSSHReady(f, t.VM, framework.MiddleTimeout) + }) + By("Waiting for USB device to be attached and ready", func() { Eventually(func() error { vm, err := t.Framework.VirtClient().VirtualMachines(t.VM.Namespace).Get(t.ctx, t.VM.Name, metav1.GetOptions{}) From 0f4d58e92c7eb985f3c14c3563223dd332a07ab1 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 19:35:47 +0200 Subject: [PATCH 03/27] fix(e2e): wait for usb block device Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index db9a39c3bc..a180c7aae0 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -82,6 +82,7 @@ var _ = Describe("VirtualMachineUSB", func() { Expect(err).NotTo(HaveOccurred()) util.UntilObjectPhase(string(v1alpha2.MachineRunning), framework.LongTimeout, t.VM) + util.UntilVMAgentReady(crclient.ObjectKeyFromObject(t.VM), framework.LongTimeout) util.UntilSSHReady(f, t.VM, framework.MiddleTimeout) }) @@ -264,8 +265,26 @@ func (t *VMUSBTest) assignNodeUSB() { func (t *VMUSBTest) mountUSB() { mountCmd := ` set -e - for i in $(seq 1 60); do - usb_device=$(lsblk -dpno PATH,TRAN,TYPE 2>/dev/null | awk "\$2 == \"usb\" && \$3 == \"disk\" { print \$1; exit }") + for i in $(seq 1 120); do + usb_device="" + for block in /sys/block/*; do + if [ ! -e "$block" ]; then + continue + fi + device_path=$(readlink -f "$block/device" 2>/dev/null || true) + case "$device_path" in + *usb*) + usb_device="/dev/$(basename "$block")" + break + ;; + esac + done + if [ -z "$usb_device" ]; then + usb_device=$(lsblk -dpno PATH,TRAN,TYPE 2>/dev/null | awk "\$2 == \"usb\" && \$3 == \"disk\" { print \$1; exit }") + fi + if [ -z "$usb_device" ]; then + usb_device=$(lsblk -dpno PATH,TYPE,RM 2>/dev/null | awk "\$2 == \"disk\" && \$3 == \"1\" { print \$1; exit }") + fi if [ -n "$usb_device" ]; then break fi @@ -273,6 +292,7 @@ func (t *VMUSBTest) mountUSB() { done if [ -z "$usb_device" ]; then echo "USB block device not found" >&2 + lsblk -a -o PATH,TRAN,TYPE,RM,MODEL >&2 || true exit 1 fi @@ -292,7 +312,7 @@ func (t *VMUSBTest) mountUSB() { ls -la /mnt/usb ` - _, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, mountCmd) + _, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, mountCmd, framework.WithSSHTimeout(framework.LongTimeout)) Expect(err).NotTo(HaveOccurred()) } From 1b255b23bdef86b91afe7e2ffff9aca9b346772b Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 19:45:40 +0200 Subject: [PATCH 04/27] fix(e2e): flush usb data before migration Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index a180c7aae0..d9363a7936 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -116,7 +116,7 @@ var _ = Describe("VirtualMachineUSB", func() { }) By("Writing data to USB device", func() { - result, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, fmt.Sprintf("echo \"%s\" | sudo tee %s", t.testContent, t.testFile)) + result, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, fmt.Sprintf("echo \"%s\" | sudo tee %s && sudo sync && sudo umount /mnt/usb", t.testContent, t.testFile)) Expect(err).NotTo(HaveOccurred()) Expect(result).To(ContainSubstring(t.testContent)) From d75717a3d5b4f700cc5b4df820aa3c9b44cf7301 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 19:59:10 +0200 Subject: [PATCH 05/27] fix(e2e): detect usb disk without transport Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index d9363a7936..35e6a37594 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -265,6 +265,12 @@ func (t *VMUSBTest) assignNodeUSB() { func (t *VMUSBTest) mountUSB() { mountCmd := ` set -e + root_source=$(findmnt -n -o SOURCE /) + root_disk="$(lsblk -no PKNAME "$root_source" 2>/dev/null || true)" + if [ -z "$root_disk" ]; then + root_disk="$(basename "$root_source")" + fi + for i in $(seq 1 120); do usb_device="" for block in /sys/block/*; do @@ -285,6 +291,14 @@ func (t *VMUSBTest) mountUSB() { if [ -z "$usb_device" ]; then usb_device=$(lsblk -dpno PATH,TYPE,RM 2>/dev/null | awk "\$2 == \"disk\" && \$3 == \"1\" { print \$1; exit }") fi + if [ -z "$usb_device" ]; then + for disk in $(lsblk -dpno PATH,TYPE 2>/dev/null | awk "\$2 == \"disk\" { print \$1 }"); do + if [ "$(basename "$disk")" != "$root_disk" ]; then + usb_device="$disk" + break + fi + done + fi if [ -n "$usb_device" ]; then break fi From 2b13d48692ede7e13042b4d60359913f967be837 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 20:00:21 +0200 Subject: [PATCH 06/27] fix(e2e): avoid fallback to non-usb disk Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index 35e6a37594..d9363a7936 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -265,12 +265,6 @@ func (t *VMUSBTest) assignNodeUSB() { func (t *VMUSBTest) mountUSB() { mountCmd := ` set -e - root_source=$(findmnt -n -o SOURCE /) - root_disk="$(lsblk -no PKNAME "$root_source" 2>/dev/null || true)" - if [ -z "$root_disk" ]; then - root_disk="$(basename "$root_source")" - fi - for i in $(seq 1 120); do usb_device="" for block in /sys/block/*; do @@ -291,14 +285,6 @@ func (t *VMUSBTest) mountUSB() { if [ -z "$usb_device" ]; then usb_device=$(lsblk -dpno PATH,TYPE,RM 2>/dev/null | awk "\$2 == \"disk\" && \$3 == \"1\" { print \$1; exit }") fi - if [ -z "$usb_device" ]; then - for disk in $(lsblk -dpno PATH,TYPE 2>/dev/null | awk "\$2 == \"disk\" { print \$1 }"); do - if [ "$(basename "$disk")" != "$root_disk" ]; then - usb_device="$disk" - break - fi - done - fi if [ -n "$usb_device" ]; then break fi From 939bd362105692e03c73607520786d81f421c1ed Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 20:19:34 +0200 Subject: [PATCH 07/27] fix(e2e): rescan usb storage before mount Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index d9363a7936..473c151c7b 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -265,7 +265,14 @@ func (t *VMUSBTest) assignNodeUSB() { func (t *VMUSBTest) mountUSB() { mountCmd := ` set -e + sudo modprobe usb-storage 2>/dev/null || true for i in $(seq 1 120); do + for host in /sys/class/scsi_host/host*; do + if [ -w "$host/scan" ]; then + echo "- - -" | sudo tee "$host/scan" >/dev/null || true + fi + done + usb_device="" for block in /sys/block/*; do if [ ! -e "$block" ]; then @@ -292,7 +299,9 @@ func (t *VMUSBTest) mountUSB() { done if [ -z "$usb_device" ]; then echo "USB block device not found" >&2 + lsusb >&2 || true lsblk -a -o PATH,TRAN,TYPE,RM,MODEL >&2 || true + sudo dmesg | tail -n 100 >&2 || true exit 1 fi From ceb64c8697195bae7dedd1b9c8abc06e268c7188 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 20:28:47 +0200 Subject: [PATCH 08/27] fix(e2e): increase usb block device wait timeout Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index 473c151c7b..8589b3b92c 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -266,7 +266,7 @@ func (t *VMUSBTest) mountUSB() { mountCmd := ` set -e sudo modprobe usb-storage 2>/dev/null || true - for i in $(seq 1 120); do + for i in $(seq 1 300); do for host in /sys/class/scsi_host/host*; do if [ -w "$host/scan" ]; then echo "- - -" | sudo tee "$host/scan" >/dev/null || true @@ -321,7 +321,7 @@ func (t *VMUSBTest) mountUSB() { ls -la /mnt/usb ` - _, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, mountCmd, framework.WithSSHTimeout(framework.LongTimeout)) + _, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, mountCmd, framework.WithSSHTimeout(framework.MaxTimeout)) Expect(err).NotTo(HaveOccurred()) } From 099ed8d59882ee6aff4e04bca660e91a8b0f0875 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 20:40:40 +0200 Subject: [PATCH 09/27] refactor(e2e): simplify usb mount detection Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 50 ++++++++++++++++------------------------------ 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index 8589b3b92c..8f351c9f6d 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -263,9 +263,14 @@ func (t *VMUSBTest) assignNodeUSB() { } func (t *VMUSBTest) mountUSB() { - mountCmd := ` + serial := t.NodeUSBDevice.Status.Attributes.Serial + Expect(serial).NotTo(BeEmpty(), "USB device serial must be set") + + mountCmd := fmt.Sprintf(` set -e + usb_serial=%q sudo modprobe usb-storage 2>/dev/null || true + for i in $(seq 1 300); do for host in /sys/class/scsi_host/host*; do if [ -w "$host/scan" ]; then @@ -273,53 +278,32 @@ func (t *VMUSBTest) mountUSB() { fi done - usb_device="" - for block in /sys/block/*; do - if [ ! -e "$block" ]; then - continue - fi - device_path=$(readlink -f "$block/device" 2>/dev/null || true) - case "$device_path" in - *usb*) - usb_device="/dev/$(basename "$block")" - break - ;; - esac - done - if [ -z "$usb_device" ]; then - usb_device=$(lsblk -dpno PATH,TRAN,TYPE 2>/dev/null | awk "\$2 == \"usb\" && \$3 == \"disk\" { print \$1; exit }") - fi - if [ -z "$usb_device" ]; then - usb_device=$(lsblk -dpno PATH,TYPE,RM 2>/dev/null | awk "\$2 == \"disk\" && \$3 == \"1\" { print \$1; exit }") - fi - if [ -n "$usb_device" ]; then + usb_link=$(find /dev/disk/by-id -maxdepth 1 -name "usb-*${usb_serial}*" ! -name "*-part*" 2>/dev/null | head -n 1) + if [ -n "$usb_link" ]; then break fi sleep 1 done - if [ -z "$usb_device" ]; then - echo "USB block device not found" >&2 + + if [ -z "$usb_link" ]; then + echo "USB block device by serial ${usb_serial} not found" >&2 lsusb >&2 || true - lsblk -a -o PATH,TRAN,TYPE,RM,MODEL >&2 || true + find /dev/disk -maxdepth 2 -type l -print >&2 || true + lsblk -a -o PATH,TRAN,TYPE,RM,MODEL,SERIAL >&2 || true sudo dmesg | tail -n 100 >&2 || true exit 1 fi - mount_device="$usb_device" - for partition in "${usb_device}"[0-9]* "${usb_device}"p[0-9]*; do - if [ -b "$partition" ]; then - mount_device="$partition" - break - fi - done - + mount_device=$(readlink -f "$usb_link") sudo mkdir -p /mnt/usb + if sudo mountpoint -q /mnt/usb; then sudo umount /mnt/usb fi + sudo mount -o rw "$mount_device" /mnt/usb ls -la /mnt/usb - ` + `, serial) _, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, mountCmd, framework.WithSSHTimeout(framework.MaxTimeout)) Expect(err).NotTo(HaveOccurred()) From 5a377a0c3eea06a1952ed768977fb8ed6ba5c72c Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 20:46:27 +0200 Subject: [PATCH 10/27] fix(e2e): retry usb device mount Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 53 +++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index 8f351c9f6d..d7dbca3c44 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -263,47 +263,34 @@ func (t *VMUSBTest) assignNodeUSB() { } func (t *VMUSBTest) mountUSB() { - serial := t.NodeUSBDevice.Status.Attributes.Serial - Expect(serial).NotTo(BeEmpty(), "USB device serial must be set") - mountCmd := fmt.Sprintf(` set -e - usb_serial=%q - sudo modprobe usb-storage 2>/dev/null || true + last_error=/tmp/usb-mount.err for i in $(seq 1 300); do - for host in /sys/class/scsi_host/host*; do - if [ -w "$host/scan" ]; then - echo "- - -" | sudo tee "$host/scan" >/dev/null || true - fi - done - - usb_link=$(find /dev/disk/by-id -maxdepth 1 -name "usb-*${usb_serial}*" ! -name "*-part*" 2>/dev/null | head -n 1) - if [ -n "$usb_link" ]; then - break + sudo mkdir -p /mnt/usb + + if sudo mountpoint -q /mnt/usb; then + ls -la /mnt/usb + exit 0 fi + + : > "$last_error" + if sudo mount %s /mnt/usb 2>"$last_error" || sudo mount -o rw %s /mnt/usb 2>>"$last_error"; then + ls -la /mnt/usb + exit 0 + fi + sleep 1 done - if [ -z "$usb_link" ]; then - echo "USB block device by serial ${usb_serial} not found" >&2 - lsusb >&2 || true - find /dev/disk -maxdepth 2 -type l -print >&2 || true - lsblk -a -o PATH,TRAN,TYPE,RM,MODEL,SERIAL >&2 || true - sudo dmesg | tail -n 100 >&2 || true - exit 1 - fi - - mount_device=$(readlink -f "$usb_link") - sudo mkdir -p /mnt/usb - - if sudo mountpoint -q /mnt/usb; then - sudo umount /mnt/usb - fi - - sudo mount -o rw "$mount_device" /mnt/usb - ls -la /mnt/usb - `, serial) + echo "Failed to mount USB device %s" >&2 + cat "$last_error" >&2 || true + mount >&2 || true + lsusb >&2 || true + sudo dmesg | tail -n 100 >&2 || true + exit 1 + `, t.DevicePath, t.DevicePath, t.DevicePath) _, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, mountCmd, framework.WithSSHTimeout(framework.MaxTimeout)) Expect(err).NotTo(HaveOccurred()) From 12530060711084badb1ae0ae8257eecda3e07da5 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 20:48:42 +0200 Subject: [PATCH 11/27] fix(e2e): refresh usb device path after migration Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 1 + 1 file changed, 1 insertion(+) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index d7dbca3c44..d94d975f9d 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -137,6 +137,7 @@ var _ = Describe("VirtualMachineUSB", func() { for _, dev := range vm.Status.USBDevices { if dev.Name == t.NodeUSBDevice.Name && dev.Attached && dev.Ready { + t.DevicePath = fmt.Sprintf("/dev/bus/usb/%d/%d", dev.Address.Bus, dev.Address.Port) return nil } } From 50840ba9ab004f802054ca8359b701dfb7825903 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 20:49:42 +0200 Subject: [PATCH 12/27] refactor(e2e): retry usb mount with eventually Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 55 +++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index d94d975f9d..c173d99339 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -265,36 +265,31 @@ func (t *VMUSBTest) assignNodeUSB() { func (t *VMUSBTest) mountUSB() { mountCmd := fmt.Sprintf(` - set -e - last_error=/tmp/usb-mount.err - - for i in $(seq 1 300); do - sudo mkdir -p /mnt/usb - - if sudo mountpoint -q /mnt/usb; then - ls -la /mnt/usb - exit 0 - fi - - : > "$last_error" - if sudo mount %s /mnt/usb 2>"$last_error" || sudo mount -o rw %s /mnt/usb 2>>"$last_error"; then - ls -la /mnt/usb - exit 0 - fi - - sleep 1 - done - - echo "Failed to mount USB device %s" >&2 - cat "$last_error" >&2 || true - mount >&2 || true - lsusb >&2 || true - sudo dmesg | tail -n 100 >&2 || true - exit 1 - `, t.DevicePath, t.DevicePath, t.DevicePath) - - _, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, mountCmd, framework.WithSSHTimeout(framework.MaxTimeout)) - Expect(err).NotTo(HaveOccurred()) + sudo mkdir -p /mnt/usb && \ + (sudo mountpoint -q /mnt/usb || sudo mount %s /mnt/usb 2>/tmp/usb-mount.err || sudo mount -o rw %s /mnt/usb 2>>/tmp/usb-mount.err) && \ + ls -la /mnt/usb + `, t.DevicePath, t.DevicePath) + + Eventually(func() error { + _, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, mountCmd) + return err + }).WithTimeout(framework.MaxTimeout).WithPolling(time.Second).Should(Succeed(), t.usbDiagnostics()) +} + +func (t *VMUSBTest) usbDiagnostics() string { + diagnosticsCmd := ` + echo "mount error:" && cat /tmp/usb-mount.err 2>/dev/null || true + echo "mount:" && mount || true + echo "lsusb:" && lsusb || true + echo "dmesg:" && sudo dmesg | tail -n 100 || true + ` + + result, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, diagnosticsCmd, framework.WithSSHTimeout(framework.MiddleTimeout)) + if err != nil { + return fmt.Sprintf("failed to collect USB diagnostics: %v", err) + } + + return result } func nodeUSBAttachedCondition(nodeUSBDevice *v1alpha2.NodeUSBDevice) *metav1.Condition { From 0de88d4d70c14ae030f0c9b7c323d1067fd82f7c Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 21:01:29 +0200 Subject: [PATCH 13/27] fix(e2e): find usb block device by serial Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 51 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index c173d99339..467c9559ba 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -264,11 +264,57 @@ func (t *VMUSBTest) assignNodeUSB() { } func (t *VMUSBTest) mountUSB() { + serial := t.NodeUSBDevice.Status.Attributes.Serial + Expect(serial).NotTo(BeEmpty(), "USB device serial must be set") + mountCmd := fmt.Sprintf(` + set -e + usb_serial=%q + sudo modprobe usb-storage 2>/dev/null || true + + for host in /sys/class/scsi_host/host*; do + if [ -w "$host/scan" ]; then + echo "- - -" | sudo tee "$host/scan" >/dev/null || true + fi + done + + usb_sys_device="" + for serial_file in /sys/bus/usb/devices/*/serial; do + if [ -f "$serial_file" ] && [ "$(cat "$serial_file")" = "$usb_serial" ]; then + usb_sys_device="$(dirname "$serial_file")" + break + fi + done + if [ -z "$usb_sys_device" ]; then + echo "USB device with serial $usb_serial not found" >/tmp/usb-mount.err + exit 1 + fi + + block_name="" + for block_path in $(find "$usb_sys_device" -path "*/block/*" -type d 2>/dev/null); do + name="$(basename "$block_path")" + if [ -b "/dev/$name" ]; then + block_name="$name" + break + fi + done + if [ -z "$block_name" ]; then + echo "USB block device for serial $usb_serial not found" >/tmp/usb-mount.err + exit 1 + fi + + mount_device="/dev/$block_name" + for partition in /sys/block/$block_name/${block_name}[0-9]* /sys/block/$block_name/${block_name}p[0-9]*; do + if [ -e "$partition" ] && [ -b "/dev/$(basename "$partition")" ]; then + mount_device="/dev/$(basename "$partition")" + break + fi + done + sudo mkdir -p /mnt/usb && \ - (sudo mountpoint -q /mnt/usb || sudo mount %s /mnt/usb 2>/tmp/usb-mount.err || sudo mount -o rw %s /mnt/usb 2>>/tmp/usb-mount.err) && \ + (sudo mountpoint -q /mnt/usb || sudo mount "$mount_device" /mnt/usb 2>/tmp/usb-mount.err || sudo mount -o rw "$mount_device" /mnt/usb 2>>/tmp/usb-mount.err) && \ ls -la /mnt/usb - `, t.DevicePath, t.DevicePath) + `, serial) Eventually(func() error { _, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, mountCmd) @@ -280,6 +326,7 @@ func (t *VMUSBTest) usbDiagnostics() string { diagnosticsCmd := ` echo "mount error:" && cat /tmp/usb-mount.err 2>/dev/null || true echo "mount:" && mount || true + echo "usb serials:" && for serial_file in /sys/bus/usb/devices/*/serial; do [ -f "$serial_file" ] && echo "$serial_file=$(cat "$serial_file")"; done || true echo "lsusb:" && lsusb || true echo "dmesg:" && sudo dmesg | tail -n 100 || true ` From 875ed7ce0cd6de378e270b17fc0c3863d0477382 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 21:05:09 +0200 Subject: [PATCH 14/27] refactor(e2e): remove unused usb device path Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index 467c9559ba..e3b6cc4220 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -93,7 +93,6 @@ var _ = Describe("VirtualMachineUSB", func() { for _, dev := range vm.Status.USBDevices { if dev.Name == t.NodeUSBDevice.Name && dev.Attached && dev.Ready { - t.DevicePath = fmt.Sprintf("/dev/bus/usb/%d/%d", dev.Address.Bus, dev.Address.Port) return nil } } @@ -137,7 +136,6 @@ var _ = Describe("VirtualMachineUSB", func() { for _, dev := range vm.Status.USBDevices { if dev.Name == t.NodeUSBDevice.Name && dev.Attached && dev.Ready { - t.DevicePath = fmt.Sprintf("/dev/bus/usb/%d/%d", dev.Address.Bus, dev.Address.Port) return nil } } @@ -174,7 +172,6 @@ type VMUSBTest struct { VM *v1alpha2.VirtualMachine VD *v1alpha2.VirtualDisk NodeUSBDevice *v1alpha2.NodeUSBDevice - DevicePath string testFile string testContent string From 0f82fe31d04e7a1f240334a046630eee7c0520eb Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 21:09:41 +0200 Subject: [PATCH 15/27] refactor(e2e): simplify usb mount command Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index e3b6cc4220..c6daa0a2cc 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -265,42 +265,24 @@ func (t *VMUSBTest) mountUSB() { Expect(serial).NotTo(BeEmpty(), "USB device serial must be set") mountCmd := fmt.Sprintf(` - set -e usb_serial=%q - sudo modprobe usb-storage 2>/dev/null || true - - for host in /sys/class/scsi_host/host*; do - if [ -w "$host/scan" ]; then - echo "- - -" | sudo tee "$host/scan" >/dev/null || true - fi - done - - usb_sys_device="" for serial_file in /sys/bus/usb/devices/*/serial; do if [ -f "$serial_file" ] && [ "$(cat "$serial_file")" = "$usb_serial" ]; then usb_sys_device="$(dirname "$serial_file")" break fi done - if [ -z "$usb_sys_device" ]; then - echo "USB device with serial $usb_serial not found" >/tmp/usb-mount.err - exit 1 - fi + [ -n "$usb_sys_device" ] || { echo "USB device with serial $usb_serial not found" >/tmp/usb-mount.err; exit 1; } - block_name="" for block_path in $(find "$usb_sys_device" -path "*/block/*" -type d 2>/dev/null); do - name="$(basename "$block_path")" - if [ -b "/dev/$name" ]; then - block_name="$name" + block_name="$(basename "$block_path")" + if [ -b "/dev/$block_name" ]; then + mount_device="/dev/$block_name" break fi done - if [ -z "$block_name" ]; then - echo "USB block device for serial $usb_serial not found" >/tmp/usb-mount.err - exit 1 - fi + [ -n "$mount_device" ] || { echo "USB block device for serial $usb_serial not found" >/tmp/usb-mount.err; exit 1; } - mount_device="/dev/$block_name" for partition in /sys/block/$block_name/${block_name}[0-9]* /sys/block/$block_name/${block_name}p[0-9]*; do if [ -e "$partition" ] && [ -b "/dev/$(basename "$partition")" ]; then mount_device="/dev/$(basename "$partition")" @@ -314,7 +296,7 @@ func (t *VMUSBTest) mountUSB() { `, serial) Eventually(func() error { - _, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, mountCmd) + _, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, mountCmd, framework.WithSSHTimeout(framework.MiddleTimeout)) return err }).WithTimeout(framework.MaxTimeout).WithPolling(time.Second).Should(Succeed(), t.usbDiagnostics()) } From 2e7b97f66747f55459a00fc8c55eeece36ccaccb Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 21:16:56 +0200 Subject: [PATCH 16/27] fix(e2e): improve usb mount diagnostics Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index c6daa0a2cc..53f394346f 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -266,6 +266,7 @@ func (t *VMUSBTest) mountUSB() { mountCmd := fmt.Sprintf(` usb_serial=%q + : > /tmp/usb-mount.err for serial_file in /sys/bus/usb/devices/*/serial; do if [ -f "$serial_file" ] && [ "$(cat "$serial_file")" = "$usb_serial" ]; then usb_sys_device="$(dirname "$serial_file")" @@ -281,7 +282,11 @@ func (t *VMUSBTest) mountUSB() { break fi done - [ -n "$mount_device" ] || { echo "USB block device for serial $usb_serial not found" >/tmp/usb-mount.err; exit 1; } + [ -n "$mount_device" ] || { + echo "USB block device for serial $usb_serial not found" >>/tmp/usb-mount.err + find "$usb_sys_device" -maxdepth 6 -print >>/tmp/usb-mount.err 2>&1 || true + exit 1 + } for partition in /sys/block/$block_name/${block_name}[0-9]* /sys/block/$block_name/${block_name}p[0-9]*; do if [ -e "$partition" ] && [ -b "/dev/$(basename "$partition")" ]; then @@ -291,7 +296,7 @@ func (t *VMUSBTest) mountUSB() { done sudo mkdir -p /mnt/usb && \ - (sudo mountpoint -q /mnt/usb || sudo mount "$mount_device" /mnt/usb 2>/tmp/usb-mount.err || sudo mount -o rw "$mount_device" /mnt/usb 2>>/tmp/usb-mount.err) && \ + (sudo mountpoint -q /mnt/usb || sudo mount "$mount_device" /mnt/usb 2>>/tmp/usb-mount.err || sudo mount -o rw "$mount_device" /mnt/usb 2>>/tmp/usb-mount.err) && \ ls -la /mnt/usb `, serial) @@ -306,6 +311,8 @@ func (t *VMUSBTest) usbDiagnostics() string { echo "mount error:" && cat /tmp/usb-mount.err 2>/dev/null || true echo "mount:" && mount || true echo "usb serials:" && for serial_file in /sys/bus/usb/devices/*/serial; do [ -f "$serial_file" ] && echo "$serial_file=$(cat "$serial_file")"; done || true + echo "usb sysfs:" && find /sys/bus/usb/devices -maxdepth 3 -print || true + echo "lsblk:" && lsblk -a -o NAME,PATH,TYPE,TRAN,RM,SERIAL,MODEL || true echo "lsusb:" && lsusb || true echo "dmesg:" && sudo dmesg | tail -n 100 || true ` From 923c5a90e02b5cec1eb777d300db76d4a2167a6d Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 21:17:41 +0200 Subject: [PATCH 17/27] test(e2e): reduce usb mount retry timeout Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index 53f394346f..eb25237c37 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -303,7 +303,7 @@ func (t *VMUSBTest) mountUSB() { Eventually(func() error { _, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, mountCmd, framework.WithSSHTimeout(framework.MiddleTimeout)) return err - }).WithTimeout(framework.MaxTimeout).WithPolling(time.Second).Should(Succeed(), t.usbDiagnostics()) + }).WithTimeout(framework.MiddleTimeout).WithPolling(time.Second).Should(Succeed(), t.usbDiagnostics()) } func (t *VMUSBTest) usbDiagnostics() string { From a72d1c6ec2ab2c85b812e3da4a4139c5d9907d9a Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 21:21:12 +0200 Subject: [PATCH 18/27] fix(e2e): resolve usb block device via sysfs parent Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index eb25237c37..bf31064a10 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -275,16 +275,27 @@ func (t *VMUSBTest) mountUSB() { done [ -n "$usb_sys_device" ] || { echo "USB device with serial $usb_serial not found" >/tmp/usb-mount.err; exit 1; } - for block_path in $(find "$usb_sys_device" -path "*/block/*" -type d 2>/dev/null); do - block_name="$(basename "$block_path")" - if [ -b "/dev/$block_name" ]; then - mount_device="/dev/$block_name" - break - fi - done + for block_path in /sys/block/*; do + [ -e "$block_path" ] || continue + device_realpath="$(readlink -f "$block_path/device" 2>/dev/null || true)" + case "$device_realpath" in + "$usb_sys_device"/*) + block_name="$(basename "$block_path")" + if [ -b "/dev/$block_name" ]; then + mount_device="/dev/$block_name" + break + fi + ;; + esac + done [ -n "$mount_device" ] || { echo "USB block device for serial $usb_serial not found" >>/tmp/usb-mount.err - find "$usb_sys_device" -maxdepth 6 -print >>/tmp/usb-mount.err 2>&1 || true + echo "usb_sys_device=$usb_sys_device" >>/tmp/usb-mount.err + find "$usb_sys_device" -maxdepth 8 -print >>/tmp/usb-mount.err 2>&1 || true + for block_path in /sys/block/*; do + [ -e "$block_path" ] || continue + echo "$(basename "$block_path") -> $(readlink -f "$block_path/device" 2>/dev/null || true)" >>/tmp/usb-mount.err + done exit 1 } From 915ccb00b32b408244b30302425dfed852d7083a Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 21:26:08 +0200 Subject: [PATCH 19/27] fix(e2e): match usb block device by serial Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index bf31064a10..232f9e12c5 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -277,24 +277,21 @@ func (t *VMUSBTest) mountUSB() { for block_path in /sys/block/*; do [ -e "$block_path" ] || continue - device_realpath="$(readlink -f "$block_path/device" 2>/dev/null || true)" - case "$device_realpath" in - "$usb_sys_device"/*) - block_name="$(basename "$block_path")" - if [ -b "/dev/$block_name" ]; then - mount_device="/dev/$block_name" - break - fi - ;; - esac - done + block_name="$(basename "$block_path")" + if [ -b "/dev/$block_name" ] && udevadm info --query=property --name "/dev/$block_name" 2>/dev/null | grep -q "ID_SERIAL_SHORT=$usb_serial"; then + mount_device="/dev/$block_name" + break + fi + done [ -n "$mount_device" ] || { echo "USB block device for serial $usb_serial not found" >>/tmp/usb-mount.err echo "usb_sys_device=$usb_sys_device" >>/tmp/usb-mount.err find "$usb_sys_device" -maxdepth 8 -print >>/tmp/usb-mount.err 2>&1 || true for block_path in /sys/block/*; do [ -e "$block_path" ] || continue + block_name="$(basename "$block_path")" echo "$(basename "$block_path") -> $(readlink -f "$block_path/device" 2>/dev/null || true)" >>/tmp/usb-mount.err + udevadm info --query=property --name "/dev/$block_name" >>/tmp/usb-mount.err 2>&1 || true done exit 1 } @@ -324,6 +321,7 @@ func (t *VMUSBTest) usbDiagnostics() string { echo "usb serials:" && for serial_file in /sys/bus/usb/devices/*/serial; do [ -f "$serial_file" ] && echo "$serial_file=$(cat "$serial_file")"; done || true echo "usb sysfs:" && find /sys/bus/usb/devices -maxdepth 3 -print || true echo "lsblk:" && lsblk -a -o NAME,PATH,TYPE,TRAN,RM,SERIAL,MODEL || true + echo "udevadm:" && for dev in /dev/sd*; do [ -b "$dev" ] && echo "== $dev ==" && udevadm info --query=property --name "$dev"; done || true echo "lsusb:" && lsusb || true echo "dmesg:" && sudo dmesg | tail -n 100 || true ` From b7ef4edf21d61dc1a552cbbcdfed0dcede60f2b5 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 21:30:58 +0200 Subject: [PATCH 20/27] fix(e2e): match usb disk by transport and removable Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index 232f9e12c5..cc2b85bd87 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -278,11 +278,11 @@ func (t *VMUSBTest) mountUSB() { for block_path in /sys/block/*; do [ -e "$block_path" ] || continue block_name="$(basename "$block_path")" - if [ -b "/dev/$block_name" ] && udevadm info --query=property --name "/dev/$block_name" 2>/dev/null | grep -q "ID_SERIAL_SHORT=$usb_serial"; then + if [ -b "/dev/$block_name" ] && lsblk -dno TRAN,RM "/dev/$block_name" 2>/dev/null | grep -Eq '^usb[[:space:]]+1$'; then mount_device="/dev/$block_name" break fi - done + done [ -n "$mount_device" ] || { echo "USB block device for serial $usb_serial not found" >>/tmp/usb-mount.err echo "usb_sys_device=$usb_sys_device" >>/tmp/usb-mount.err @@ -291,7 +291,7 @@ func (t *VMUSBTest) mountUSB() { [ -e "$block_path" ] || continue block_name="$(basename "$block_path")" echo "$(basename "$block_path") -> $(readlink -f "$block_path/device" 2>/dev/null || true)" >>/tmp/usb-mount.err - udevadm info --query=property --name "/dev/$block_name" >>/tmp/usb-mount.err 2>&1 || true + lsblk -dno NAME,PATH,TRAN,RM,SERIAL,MODEL "/dev/$block_name" >>/tmp/usb-mount.err 2>&1 || true done exit 1 } @@ -321,7 +321,7 @@ func (t *VMUSBTest) usbDiagnostics() string { echo "usb serials:" && for serial_file in /sys/bus/usb/devices/*/serial; do [ -f "$serial_file" ] && echo "$serial_file=$(cat "$serial_file")"; done || true echo "usb sysfs:" && find /sys/bus/usb/devices -maxdepth 3 -print || true echo "lsblk:" && lsblk -a -o NAME,PATH,TYPE,TRAN,RM,SERIAL,MODEL || true - echo "udevadm:" && for dev in /dev/sd*; do [ -b "$dev" ] && echo "== $dev ==" && udevadm info --query=property --name "$dev"; done || true + echo "disks:" && for dev in /dev/sd*; do [ -b "$dev" ] && echo "== $dev ==" && lsblk -dno NAME,PATH,TRAN,RM,SERIAL,MODEL "$dev"; done || true echo "lsusb:" && lsusb || true echo "dmesg:" && sudo dmesg | tail -n 100 || true ` From 0732456c472bbfd2c6acae0f63e58bc87b34d57c Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 21:36:07 +0200 Subject: [PATCH 21/27] refactor(e2e): simplify usb disk lookup Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index cc2b85bd87..0f125e3ee3 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -269,40 +269,25 @@ func (t *VMUSBTest) mountUSB() { : > /tmp/usb-mount.err for serial_file in /sys/bus/usb/devices/*/serial; do if [ -f "$serial_file" ] && [ "$(cat "$serial_file")" = "$usb_serial" ]; then - usb_sys_device="$(dirname "$serial_file")" + usb_present=1 break fi done - [ -n "$usb_sys_device" ] || { echo "USB device with serial $usb_serial not found" >/tmp/usb-mount.err; exit 1; } + [ -n "$usb_present" ] || { echo "USB device with serial $usb_serial not found" >/tmp/usb-mount.err; exit 1; } - for block_path in /sys/block/*; do - [ -e "$block_path" ] || continue - block_name="$(basename "$block_path")" - if [ -b "/dev/$block_name" ] && lsblk -dno TRAN,RM "/dev/$block_name" 2>/dev/null | grep -Eq '^usb[[:space:]]+1$'; then - mount_device="/dev/$block_name" + for dev in /dev/sd*; do + [ -b "$dev" ] || continue + if lsblk -dno TRAN,RM "$dev" 2>/dev/null | grep -Eq '^usb[[:space:]]+1$'; then + mount_device="$dev" break fi - done + done [ -n "$mount_device" ] || { - echo "USB block device for serial $usb_serial not found" >>/tmp/usb-mount.err - echo "usb_sys_device=$usb_sys_device" >>/tmp/usb-mount.err - find "$usb_sys_device" -maxdepth 8 -print >>/tmp/usb-mount.err 2>&1 || true - for block_path in /sys/block/*; do - [ -e "$block_path" ] || continue - block_name="$(basename "$block_path")" - echo "$(basename "$block_path") -> $(readlink -f "$block_path/device" 2>/dev/null || true)" >>/tmp/usb-mount.err - lsblk -dno NAME,PATH,TRAN,RM,SERIAL,MODEL "/dev/$block_name" >>/tmp/usb-mount.err 2>&1 || true - done + echo "USB block device not found for serial $usb_serial" >>/tmp/usb-mount.err + lsblk -a -o NAME,PATH,TYPE,TRAN,RM,SERIAL,MODEL >>/tmp/usb-mount.err 2>&1 || true exit 1 } - for partition in /sys/block/$block_name/${block_name}[0-9]* /sys/block/$block_name/${block_name}p[0-9]*; do - if [ -e "$partition" ] && [ -b "/dev/$(basename "$partition")" ]; then - mount_device="/dev/$(basename "$partition")" - break - fi - done - sudo mkdir -p /mnt/usb && \ (sudo mountpoint -q /mnt/usb || sudo mount "$mount_device" /mnt/usb 2>>/tmp/usb-mount.err || sudo mount -o rw "$mount_device" /mnt/usb 2>>/tmp/usb-mount.err) && \ ls -la /mnt/usb From a9969ba52d764ec2e04bfff34fbf6ff585f79d63 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 21:48:23 +0200 Subject: [PATCH 22/27] fix(e2e): remount usb after migration Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index 0f125e3ee3..652dfdb608 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -288,8 +288,11 @@ func (t *VMUSBTest) mountUSB() { exit 1 } - sudo mkdir -p /mnt/usb && \ - (sudo mountpoint -q /mnt/usb || sudo mount "$mount_device" /mnt/usb 2>>/tmp/usb-mount.err || sudo mount -o rw "$mount_device" /mnt/usb 2>>/tmp/usb-mount.err) && \ + sudo mkdir -p /mnt/usb + if sudo mountpoint -q /mnt/usb; then + sudo umount /mnt/usb || true + fi + sudo mount "$mount_device" /mnt/usb 2>>/tmp/usb-mount.err || sudo mount -o rw "$mount_device" /mnt/usb 2>>/tmp/usb-mount.err ls -la /mnt/usb `, serial) From dcd2836c828557440e937e9b0fefa71dcf788fde Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 21:54:12 +0200 Subject: [PATCH 23/27] fix(e2e): try vfat mount for usb disk Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index 652dfdb608..b4d83da54f 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -292,7 +292,9 @@ func (t *VMUSBTest) mountUSB() { if sudo mountpoint -q /mnt/usb; then sudo umount /mnt/usb || true fi - sudo mount "$mount_device" /mnt/usb 2>>/tmp/usb-mount.err || sudo mount -o rw "$mount_device" /mnt/usb 2>>/tmp/usb-mount.err + sudo mount -t auto "$mount_device" /mnt/usb 2>>/tmp/usb-mount.err || \ + sudo mount -t vfat -o rw "$mount_device" /mnt/usb 2>>/tmp/usb-mount.err || \ + sudo mount -o rw "$mount_device" /mnt/usb 2>>/tmp/usb-mount.err ls -la /mnt/usb `, serial) @@ -311,6 +313,7 @@ func (t *VMUSBTest) usbDiagnostics() string { echo "lsblk:" && lsblk -a -o NAME,PATH,TYPE,TRAN,RM,SERIAL,MODEL || true echo "disks:" && for dev in /dev/sd*; do [ -b "$dev" ] && echo "== $dev ==" && lsblk -dno NAME,PATH,TRAN,RM,SERIAL,MODEL "$dev"; done || true echo "lsusb:" && lsusb || true + echo "fstype:" && blkid /dev/sd* || true echo "dmesg:" && sudo dmesg | tail -n 100 || true ` From 5a2c284abe2845f970fe033dda99ef2118e4008d Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 22:03:35 +0200 Subject: [PATCH 24/27] refactor(e2e): extract usb vm test helpers Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 92 ++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 49 deletions(-) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index b4d83da54f..6c511ae1f1 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -69,12 +69,7 @@ var _ = Describe("VirtualMachineUSB", func() { }) By("Verifying NodeUSBDevice is not attached before VM attachment", func() { - Eventually(func(g Gomega) { - nodeUSBDevice, err := t.Framework.VirtClient().NodeUSBDevices().Get(t.ctx, t.NodeUSBDevice.Name, metav1.GetOptions{}) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(nodeUSBAttachedCondition(nodeUSBDevice)).NotTo(BeNil()) - g.Expect(nodeUSBAttachedCondition(nodeUSBDevice).Status).To(Equal(metav1.ConditionFalse)) - }).WithTimeout(framework.MaxTimeout).WithPolling(time.Second).Should(Succeed()) + t.waitForNodeUSBAttached(metav1.ConditionFalse) }) By("Creating VM with USB device", func() { @@ -87,27 +82,11 @@ var _ = Describe("VirtualMachineUSB", func() { }) By("Waiting for USB device to be attached and ready", func() { - Eventually(func() error { - vm, err := t.Framework.VirtClient().VirtualMachines(t.VM.Namespace).Get(t.ctx, t.VM.Name, metav1.GetOptions{}) - Expect(err).NotTo(HaveOccurred()) - - for _, dev := range vm.Status.USBDevices { - if dev.Name == t.NodeUSBDevice.Name && dev.Attached && dev.Ready { - return nil - } - } - - return fmt.Errorf("USB device %s not attached or not ready", t.NodeUSBDevice.Name) - }).WithTimeout(framework.MaxTimeout).WithPolling(time.Second).Should(Succeed()) + t.waitForVMUSBReady("USB device %s not attached or not ready") }) By("Verifying NodeUSBDevice is attached", func() { - Eventually(func(g Gomega) { - nodeUSBDevice, err := t.Framework.VirtClient().NodeUSBDevices().Get(t.ctx, t.NodeUSBDevice.Name, metav1.GetOptions{}) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(nodeUSBAttachedCondition(nodeUSBDevice)).NotTo(BeNil()) - g.Expect(nodeUSBAttachedCondition(nodeUSBDevice).Status).To(Equal(metav1.ConditionTrue)) - }).WithTimeout(framework.MaxTimeout).WithPolling(time.Second).Should(Succeed()) + t.waitForNodeUSBAttached(metav1.ConditionTrue) }) By("Mounting USB device", func() { @@ -115,10 +94,7 @@ var _ = Describe("VirtualMachineUSB", func() { }) By("Writing data to USB device", func() { - result, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, fmt.Sprintf("echo \"%s\" | sudo tee %s && sudo sync && sudo umount /mnt/usb", t.testContent, t.testFile)) - - Expect(err).NotTo(HaveOccurred()) - Expect(result).To(ContainSubstring(t.testContent)) + t.writeUSBTestData() }) By("Migrating VM", func() { @@ -130,27 +106,11 @@ var _ = Describe("VirtualMachineUSB", func() { }) By("Waiting for USB device to be ready after migration", func() { - Eventually(func() error { - vm, err := t.Framework.VirtClient().VirtualMachines(t.VM.Namespace).Get(t.ctx, t.VM.Name, metav1.GetOptions{}) - Expect(err).NotTo(HaveOccurred()) - - for _, dev := range vm.Status.USBDevices { - if dev.Name == t.NodeUSBDevice.Name && dev.Attached && dev.Ready { - return nil - } - } - - return fmt.Errorf("USB device %s not ready after migration", t.NodeUSBDevice.Name) - }).WithTimeout(framework.MaxTimeout).WithPolling(time.Second).Should(Succeed()) + t.waitForVMUSBReady("USB device %s not ready after migration") }) By("Verifying NodeUSBDevice is attached after migration", func() { - Eventually(func(g Gomega) { - nodeUSBDevice, err := t.Framework.VirtClient().NodeUSBDevices().Get(t.ctx, t.NodeUSBDevice.Name, metav1.GetOptions{}) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(nodeUSBAttachedCondition(nodeUSBDevice)).NotTo(BeNil()) - g.Expect(nodeUSBAttachedCondition(nodeUSBDevice).Status).To(Equal(metav1.ConditionTrue)) - }).WithTimeout(framework.MaxTimeout).WithPolling(time.Second).Should(Succeed()) + t.waitForNodeUSBAttached(metav1.ConditionTrue) }) By("Remounting USB device after migration", func() { @@ -158,9 +118,7 @@ var _ = Describe("VirtualMachineUSB", func() { }) By("Verifying data persists after migration", func() { - result, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, fmt.Sprintf("cat %s", t.testFile)) - Expect(err).NotTo(HaveOccurred()) - Expect(result).To(ContainSubstring(t.testContent)) + t.verifyUSBTestData() }) }) }) @@ -260,6 +218,42 @@ func (t *VMUSBTest) assignNodeUSB() { }).WithTimeout(framework.MaxTimeout).WithPolling(time.Second).Should(Succeed()) } +func (t *VMUSBTest) waitForNodeUSBAttached(status metav1.ConditionStatus) { + Eventually(func(g Gomega) { + nodeUSBDevice, err := t.Framework.VirtClient().NodeUSBDevices().Get(t.ctx, t.NodeUSBDevice.Name, metav1.GetOptions{}) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(nodeUSBAttachedCondition(nodeUSBDevice)).NotTo(BeNil()) + g.Expect(nodeUSBAttachedCondition(nodeUSBDevice).Status).To(Equal(status)) + }).WithTimeout(framework.MaxTimeout).WithPolling(time.Second).Should(Succeed()) +} + +func (t *VMUSBTest) waitForVMUSBReady(message string) { + Eventually(func() error { + vm, err := t.Framework.VirtClient().VirtualMachines(t.VM.Namespace).Get(t.ctx, t.VM.Name, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + + for _, dev := range vm.Status.USBDevices { + if dev.Name == t.NodeUSBDevice.Name && dev.Attached && dev.Ready { + return nil + } + } + + return fmt.Errorf(message, t.NodeUSBDevice.Name) + }).WithTimeout(framework.MaxTimeout).WithPolling(time.Second).Should(Succeed()) +} + +func (t *VMUSBTest) writeUSBTestData() { + result, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, fmt.Sprintf("echo \"%s\" | sudo tee %s && sudo sync && sudo umount /mnt/usb", t.testContent, t.testFile)) + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(ContainSubstring(t.testContent)) +} + +func (t *VMUSBTest) verifyUSBTestData() { + result, err := t.Framework.SSHCommand(t.VM.Name, t.VM.Namespace, fmt.Sprintf("cat %s", t.testFile)) + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(ContainSubstring(t.testContent)) +} + func (t *VMUSBTest) mountUSB() { serial := t.NodeUSBDevice.Status.Attributes.Serial Expect(serial).NotTo(BeEmpty(), "USB device serial must be set") From 66e86fa49f7d805e1b48ffe4d1d782da452568c3 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 28 Apr 2026 22:18:01 +0200 Subject: [PATCH 25/27] fix(e2e): rescan scsi hosts before usb mount Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index 6c511ae1f1..46c2a0c6f9 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -269,6 +269,10 @@ func (t *VMUSBTest) mountUSB() { done [ -n "$usb_present" ] || { echo "USB device with serial $usb_serial not found" >/tmp/usb-mount.err; exit 1; } + for host in /sys/class/scsi_host/host*; do + echo "- - -" | sudo tee "$host/scan" >/dev/null || true + done + for dev in /dev/sd*; do [ -b "$dev" ] || continue if lsblk -dno TRAN,RM "$dev" 2>/dev/null | grep -Eq '^usb[[:space:]]+1$'; then From 71a76177be18bdec274954992d5aa77e0c36786a Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Wed, 29 Apr 2026 11:00:09 +0200 Subject: [PATCH 26/27] test(e2e): increase usb mount retry timeout Signed-off-by: Daniil Antoshin --- test/e2e/vm/usb.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index 46c2a0c6f9..bdb5e4cc22 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -269,10 +269,16 @@ func (t *VMUSBTest) mountUSB() { done [ -n "$usb_present" ] || { echo "USB device with serial $usb_serial not found" >/tmp/usb-mount.err; exit 1; } + sudo modprobe usb-storage + sudo modprobe uas || true + for host in /sys/class/scsi_host/host*; do echo "- - -" | sudo tee "$host/scan" >/dev/null || true done + sudo udevadm trigger + sudo udevadm settle + for dev in /dev/sd*; do [ -b "$dev" ] || continue if lsblk -dno TRAN,RM "$dev" 2>/dev/null | grep -Eq '^usb[[:space:]]+1$'; then From 700b2bd10d6c3ac36927ac4fb11d0ee68f991104 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Wed, 29 Apr 2026 14:07:45 +0200 Subject: [PATCH 27/27] test(test): add guest command readiness helper Signed-off-by: Daniil Antoshin --- test/e2e/internal/util/vm.go | 27 +++++++++++++++++++++++++++ test/e2e/vm/usb.go | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/test/e2e/internal/util/vm.go b/test/e2e/internal/util/vm.go index a6f588598e..c1a11dba64 100644 --- a/test/e2e/internal/util/vm.go +++ b/test/e2e/internal/util/vm.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "regexp" + "strings" "time" . "github.com/onsi/ginkgo/v2" @@ -184,6 +185,32 @@ func UntilSSHReady(f *framework.Framework, vm *v1alpha2.VirtualMachine, timeout }).WithTimeout(timeout).WithPolling(time.Second).Should(Succeed()) } +func UntilGuestCommandsReady(f *framework.Framework, vm *v1alpha2.VirtualMachine, commands []string, timeout time.Duration) { + GinkgoHelper() + + cmd := fmt.Sprintf(` + missing="" + for command in %s; do + command -v "$command" >/dev/null 2>&1 || missing="$missing $command" + done + [ -z "$missing" ] || { echo "missing commands:$missing"; exit 1; } + `, shellArgs(commands)) + + Eventually(func() error { + _, err := f.SSHCommand(vm.Name, vm.Namespace, cmd, framework.WithSSHTimeout(5*time.Second)) + return err + }).WithTimeout(timeout).WithPolling(time.Second).Should(Succeed()) +} + +func shellArgs(args []string) string { + quoted := make([]string, 0, len(args)) + for _, arg := range args { + quoted = append(quoted, fmt.Sprintf("%q", arg)) + } + + return strings.Join(quoted, " ") +} + func UntilVMMigrationSucceeded(key client.ObjectKey, timeout time.Duration) { GinkgoHelper() diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index bdb5e4cc22..c3b69214e0 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -77,8 +77,8 @@ var _ = Describe("VirtualMachineUSB", func() { Expect(err).NotTo(HaveOccurred()) util.UntilObjectPhase(string(v1alpha2.MachineRunning), framework.LongTimeout, t.VM) - util.UntilVMAgentReady(crclient.ObjectKeyFromObject(t.VM), framework.LongTimeout) util.UntilSSHReady(f, t.VM, framework.MiddleTimeout) + util.UntilGuestCommandsReady(f, t.VM, []string{"sudo", "tee", "udevadm"}, framework.LongTimeout) }) By("Waiting for USB device to be attached and ready", func() {