Am Di., 13. Okt. 2026 um 22:16 Uhr schrieb Peter Xu <[email protected]>:
>
> On Fri, Oct 09, 2026 at 07:52:29PM +0200, Alexander Mikhalitsyn wrote:
> > Am Mi., 7. Okt. 2026 um 15:27 Uhr schrieb Peter Xu <[email protected]>:
> > >
> > > On Mon, Sep 21, 2026 at 08:03:45PM +0100, Alex Bennée wrote:
> > > > This is a hacky patch for comment but I've included the diagnostic
> > > > hacks to the migration test as well as my workaround to get itworking.
> > > >
> > > > This test was failing on my KVM system. On investigation I found the
> > > > nVME activity meant the migration never completed. I ended up
> > > > inserting some sleeps into the workload script until I was able to
> > > > get it to run.
> > >
> > > Indeed, I can reproduce it too:
> >
> > Hi Peter,
>
> Hi, Alexander,
Hi Peter,
>
> Thanks for loooking into this, and sorry for a late update. Before saying
> this is a right fix, I have some questions inline.
No problem at all! ;-)
>
> >
> > thank you checking on your system and sharing a stacktrace.
> > Unfortunately, I wasn't able to reproduce this failure on my system,
> > but I think I have a good theory what's going on there. I think it is
> > because we use `snapshot=on` for a disk device and at the same time
> > keep important stuff like `/tmp/test_nvme_mig_workload.pid` on a root
> > filesystem. Easy fix is to use tmpfs for /tmp, so all data is actually
> > migrated.
> >
> > Please, can you try the following patch and let me know if this fixes
> > the issue or not:
> >
> > --- a/tests/functional/x86_64/test_nvme_migration.py
> > +++ b/tests/functional/x86_64/test_nvme_migration.py
> > @@ -75,6 +75,11 @@ def configure_machine(self, vm):
> > def launch_source_vm(self, vm):
> > vm.launch()
> >
> > + vm.cmd('migrate-set-parameters', **{
> > + 'downtime-limit': 2000000,
>
> One thing to mention is downtime-limit is in milliseconds, this is 2000sec,
> so I would expect whatever guest runs, this converges in the 1st second..
yeah, probably I've got a bit too far with this parameter :-)
>
> > + 'max-bandwidth': 10 * 1024**3,
>
> Yes we want some sane bandwidth maybe, indeed, default is normally too
> small.
My experiments show, that with these parameters test passes like in 20
seconds, while
without it takes ~35 seconds.
Actually, this change is not a fix. It's a tweak I should make
initially when adding a test.
>
> > + })
> > +
> > self.wait_for_console_pattern('Entering emergency mode.', vm)
> > prompt = '# '
> > self.wait_for_console_pattern(prompt, vm)
> > @@ -94,6 +99,8 @@ def launch_source_vm(self, vm):
> > prompt, vm=vm)
> > exec_command_and_wait_for_pattern(self, 'mount -t sysfs sysfs
> > /sys',
> > prompt, vm=vm)
> > + exec_command_and_wait_for_pattern(self, 'mount -t tmpfs tmpfs
> > /tmp',
> > + prompt, vm=vm)
>
> Why this is needed? I was expecting /tmp normally is already mounted with
> tmpfs, no?
Unfortunately, not. Cause first of all, we boot in a rescue mode
*from* initrd (not from the disk!) and then we:
exec_command_and_wait_for_pattern(self, 'mount /dev/nvme0n1p1
/sysroot', // << manually mount root disk with the system
prompt, vm=vm)
exec_command_and_wait_for_pattern(self, 'chroot /sysroot',
// << chroot, so there is no mounts other than
"/" in "/" subtree
prompt, vm=vm)
exec_command_and_wait_for_pattern(self, 'mount -t proc proc /proc',
prompt, vm=vm)
exec_command_and_wait_for_pattern(self, 'mount -t sysfs sysfs /sys',
prompt, vm=vm)
exec_command_and_wait_for_pattern(self, 'mount -t tmpfs tmpfs /tmp',
prompt, vm=vm)
>
> One thing that might be relevant is the use of snapshot=on on the drives,
> which means, IIUC, there's a top layer qcow2 overlay on the image file
> specified.
yes, this is what I've mentioned earlier. My theory is the following:
- we run some workload inside the VM to produce some I/O. We put PID of it
in `/tmp/test_nvme_mig_workload.pid`
- later we expect to see PID in that file to kill it. And here we fail
(in accordance with your stacktrace)
My explanation is that it's because `/tmp` leaves on NVMe device,
which is backed by the drive with
`snapshot=on` which means that after migration all the changes
*written* to the disk disappear.
In this test we don't really care about anything written to the disk,
cause our test is R/O and it's job is
to make NVMe device I/O path busy with requests.
>
> Now thinking about it, if snapshot=on implies an anon invisible overlay, it
> should not support migration at all? It means, this QEMU instance contains
> some anonymous qcow2 overlay that can never (am I right?) be migrated,
> since it's neither part of device state, nor guest memory (unlike /tmp
> mount in guest).
yes, I think so.
>
> So I feel like there's still something is off, e.g. I wonder if we should:
>
> - add a migration blocker for snapshot=on whenever used.. because I can't
> see how it can work at all...
yeah, initially when I was writing this test I was heavily inspired by
`test_virtio_balloon.py` (`snapshot=on` is used there too),
so this was basically an accidental choice. I made sure that test
works just fine, creates I/O stress and forget. But now
we see that there is a bit of a problem.
>
> - for this test, should we remove snapshot=on but instead create an
> explicit overlay that will be used on both src/dst qemu?
This should solve the issue too, at the same time if test is R/O I'm
not sure if we really need this.
But I'm happy to pick any way you suggest ;-)
Kind regards,
Alex
>
> Thanks,
>
> >
> > # Run workload before migration to check if it continues
> > # to run properly after migration.
> >
> > Kind regards,
> > Alex
> >
> > >
> > > $ ./run ../tests/functional/x86_64/test_nvme_migration.py
> > > X8664NVMeMigrationTest.test_migration_with_unix
> > > TAP version 13
> > > Traceback (most recent call last):
> > > File
> > > "/home/zhexu/git/qemu/tests/functional/x86_64/test_nvme_migration.py",
> > > line 162, in test_migration_with_unix
> > > self.migration_with_unix()
> > > ~~~~~~~~~~~~~~~~~~~~~~~~^^
> > > File "/home/zhexu/git/qemu/tests/functional/migration.py", line 98, in
> > > migration_with_unix
> > > self.migrate(dst_uri)
> > > ~~~~~~~~~~~~^^^^^^^^^
> > > File "/home/zhexu/git/qemu/tests/functional/migration.py", line 78, in
> > > migrate
> > > self.migrate_vms(dst_uri, src_uri, dst_vm, src_vm)
> > > ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > > File "/home/zhexu/git/qemu/tests/functional/migration.py", line 63, in
> > > migrate_vms
> > > self.assert_dest_vm(dst_vm)
> > > ~~~~~~~~~~~~~~~~~~~^^^^^^^^
> > > File
> > > "/home/zhexu/git/qemu/tests/functional/x86_64/test_nvme_migration.py",
> > > line 128, in assert_dest_vm
> > > self.exec_command_and_check(
> > > ~~~~~~~~~~~~~~~~~~~~~~~~~~~^
> > > "kill -0 $(cat /tmp/test_nvme_mig_workload.pid)", vm)
> > > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > > File
> > > "/home/zhexu/git/qemu/tests/functional/x86_64/test_nvme_migration.py",
> > > line 48, in exec_command_and_check
> > > wait_for_console_pattern(self, 'OK', failure_message="FAIL", vm=vm)
> > > ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > > File "/home/zhexu/git/qemu/tests/functional/qemu_test/cmd.py", line
> > > 191, in wait_for_console_pattern
> > > return _console_interaction(test, success_message, failure_message,
> > > None, vm=vm)
> > > File "/home/zhexu/git/qemu/tests/functional/qemu_test/cmd.py", line
> > > 134, in _console_interaction
> > > done, line = _console_read_line_until_match(test, vm,
> > > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
> > > success_message_b,
> > > ^^^^^^^^^^^^^^^^^^
> > > failure_message_b)
> > > ^^^^^^^^^^^^^^^^^^
> > > File "/home/zhexu/git/qemu/tests/functional/qemu_test/cmd.py", line 70,
> > > in _console_read_line_until_match
> > > test.fail(
> > > ~~~~~~~~~^
> > > f"'{failure}' found in console, expected '{success}'")
> > > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > > AssertionError: 'b'FAIL'' found in console, expected 'b'OK''
> > >
> > > not ok 1
> > > test_nvme_migration.X8664NVMeMigrationTest.test_migration_with_unix
> > > 1..1
> > >
> > > Copy the author too (Alexander).
> > >
> > > >
> > > > *NOTE*: obviously this can't be merged as is but I think we could
> > > > certainly do with more tracking in the test to see when things aren't
> > > > working with possibly the sleep as another patch.
> > > >
> > > > Signed-off-by: Alex Bennée <[email protected]>
> > > > ---
> > > > tests/functional/migration.py | 37 +++++++++++++++----
> > > > .../functional/x86_64/test_nvme_migration.py | 1 +
> > > > 2 files changed, 31 insertions(+), 7 deletions(-)
> > > >
> > > > diff --git a/tests/functional/migration.py
> > > > b/tests/functional/migration.py
> > > > index 8d2428efc24..e51a82c22f4 100644
> > > > --- a/tests/functional/migration.py
> > > > +++ b/tests/functional/migration.py
> > > > @@ -22,18 +22,41 @@ class MigrationTest(QemuSystemTest):
> > > > timeout = 10
> > > >
> > > > @staticmethod
> > > > - def migration_finished(vm):
> > > > - return vm.cmd('query-migrate')['status'] in ('completed',
> > > > 'failed')
> > > > + def migration_finished(query):
> > > > + return query['status'] in ('completed', 'failed')
> > > >
> > > > - def assert_migration(self, src_vm, dst_vm):
> > > > + def wait_for_vm(self, src_or_dst, vm):
> > > > + "Track the state of one VM in the migration"
> > > > + current_state = last_state = vm.cmd('query-migrate')
> > > >
> > > > - end = time.monotonic() + self.timeout
> > > > - while time.monotonic() < end and not
> > > > self.migration_finished(src_vm):
> > > > - time.sleep(0.1)
> > > > + self.log.info(f"waiting for {src_or_dst} to complete")
> > > >
> > > > end = time.monotonic() + self.timeout
> > > > - while time.monotonic() < end and not
> > > > self.migration_finished(dst_vm):
> > > > + while time.monotonic() < end:
> > > > + if self.migration_finished(current_state):
> > > > + break
> > > > + else:
> > > > + last_remain = last_state['remaining']
> > > > + current_remain = current_state['remaining']
> > > > +
> > > > + if current_remain > last_remain:
> > > > + self.log.warning(f"slow migration {current_remain}
> > > > > {last_remain}")
> > > > +
> > > > time.sleep(0.1)
> > > > + last_state = current_state
> > > > + current_state = vm.cmd('query-migrate')
> > > > +
> > > > +
> > > > + def assert_migration(self, src_vm, dst_vm):
> > > > +
> > > > + self.wait_for_vm("src", src_vm)
> > > > +
> > > > + # if we hit this we never completed...
> > > > + self.assertEqual(src_vm.cmd('query-migrate')['status'],
> > > > 'completed')
> > > > +
> > > > + self.wait_for_vm("dst", dst_vm)
> > > > +
> > > > + self.log.info("checking everything is OK")
> > > >
> > > > self.assertEqual(src_vm.cmd('query-migrate')['status'],
> > > > 'completed')
> > > > self.assertEqual(dst_vm.cmd('query-migrate')['status'],
> > > > 'completed')
> > > > diff --git a/tests/functional/x86_64/test_nvme_migration.py
> > > > b/tests/functional/x86_64/test_nvme_migration.py
> > > > index 890f0aab6d6..58596f63cd3 100755
> > > > --- a/tests/functional/x86_64/test_nvme_migration.py
> > > > +++ b/tests/functional/x86_64/test_nvme_migration.py
> > > > @@ -109,6 +109,7 @@ def launch_source_vm(self, vm):
> > > > rm -f /tmp/test_nvme_mig_workload.iter_finished; \
> > > > echo 3 > /proc/sys/vm/drop_caches; \
> > > > find /usr/bin -type f -exec cksum {} \\;; \
> > > > + sleep 0.3; \
> > > > touch /tmp/test_nvme_mig_workload.iter_finished; \
> > > > done) > /dev/null 2> /tmp/test_nvme_mig_workload.errors &",
> > > > prompt, vm=vm)
> > > > --
> > > > 2.47.3
> > > >
> > >
> > > --
> > > Peter Xu
> > >
> >
>
> --
> Peter Xu
>