Hi all!
Here is a migration for TAP net backend, including its properties and
open fds.
With this new feature, management software doesn't need to initialize
new TAP and do a switch to it. Nothing should be done around
virtio-net in local migration: it just migrates and continues to use
same TAP device. So we avoid extra logic in management software, extra
allocations in kernel (for new TAP), and corresponding extra delay in
migration downtime.
v17: rework interface
02-04: new
07: - drop local-migration parameter for virtio-net and the whole
code to migrate backends (they should migrate by themselves).
- drop virtio_net_update_host_features(), and call
peer_test_vnet_hdr() and virtio_net_get_features() directly
from post-load handlers
- drop r-b
08: - add local-migratieon-supported property (instead of
local-migration that is dropped in 07)
- register own vmstate
- add check for being in incoming migration when use incoming-fds
- drop r-b, a-b
10: only move 'local_migration=local' from virtio-net device
to 'local-migration-supported": local' in netdev. keep r-bs
Based-on: <20260318113144.15697-1-vsementsov@yandex-team.ru>
"[PATCH v4 00/13] net: refactoring and fixes"
v17 is pushed to
https://gitlab.com/vsementsov/qemu.git
tag: up-tap-fd-migration-with-bk-opt-v17
To run the test, use sudo, as test needs to configure TAP device:
sudo PYTHONPATH=python:tests/functional \
QEMU_TEST_QEMU_BINARY=$PWD/build/qemu-system-x86_64 \
MESON_BUILD_ROOT=$PWD/build \
./build/pyvenv/bin/python3 tests/functional/x86_64/test_tap_migration.py
Or, to test the feature by hand, you may follow the instruction:
1. Prerequisites
----------------
QEMU=/path/to/your/build/qemu-system-x86_64
# download same image as in test
wget -O /tmp/alpine.iso "https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/x86_64/alpine-standard-3.22.1-x86_64.iso"
# prepare tap device (be careful to not break your own networks)
sudo ip tuntap add dev tap0 mode tap multi_queue
sudo ip addr add 192.168.100.1/24 dev tap0
sudo ip link set tap0 up
2. Start source VM
------------------
Open terminal A, and run:
$QEMU \
-name source \
-machine q35 \
-accel kvm \
-m 1G \
-object memory-backend-file,id=ram0,size=1G,mem-path=/dev/shm/qemu_migration_test,share=on \
-machine memory-backend=ram0 \
-drive file=/tmp/alpine.iso,media=cdrom,format=raw \
-device pcie-pci-bridge,id=pci.1,bus=pcie.0 \
-netdev tap,id=netdev.1,ifname=tap0,queues=4,vnet_hdr=on,script=no,downscript=no,local-migration-supported=on \
-device virtio-net-pci,netdev=netdev.1,id=vnet.1,bus=pci.1,mq=on,vectors=18,romfile=,disable-legacy=off \
-serial stdio \
-nographic \
-qmp unix:/tmp/qmp-source.sock,server=on,wait=off
Wait for Alpine to boot. When you see the login prompt, log in as root
(no password):
localhost login: root
Configure the guest network:
ip addr add 192.168.100.2/24 dev eth0
ip link set eth0 up
Verify connectivity from the guest:
ping -c 3 192.168.100.1
And from the host (in another terminal):
ping -c 3 192.168.100.2
3. Start target VM
------------------
Open terminal B. The target VM uses the same shared memory file and
the same ISO. The NIC is configured with incoming-fds=on — it will
not open tap0 itself; instead it will receive the TAP file descriptors
from the source over the migration channel.
$QEMU \
-name target \
-machine q35 \
-accel kvm \
-m 1G \
-object memory-backend-file,id=ram0,size=1G,mem-path=/dev/shm/qemu_migration_test,share=on \
-machine memory-backend=ram0 \
-drive file=/tmp/alpine.iso,media=cdrom,format=raw \
-device pcie-pci-bridge,id=pci.1,bus=pcie.0 \
-netdev tap,id=netdev.1,queues=4,script=no,downscript=no,local-migration-supported=on,incoming-fds=on \
-device virtio-net-pci,netdev=netdev.1,id=vnet.1,bus=pci.1,mq=on,vectors=18,romfile=,disable-legacy=off \
-serial stdio \
-nographic \
-qmp unix:/tmp/qmp-target.sock,server=on,wait=off \
-incoming defer
4. Start migration
------------------
Open terminal C and connect to the source QMP socket:
socat - UNIX-CONNECT:/tmp/qmp-source.sock
Negotiate capabilities and configure migration:
{"execute": "qmp_capabilities"}
{"execute": "migrate-set-capabilities", "arguments": {
"capabilities": [
{"capability": "events", "state": true},
{"capability": "x-ignore-shared", "state": true}
]
}}
{"execute": "migrate-set-parameters", "arguments": {"local": true}}
Open terminal D and connect to the target QMP socket:
socat - UNIX-CONNECT:/tmp/qmp-target.sock
Negotiate capabilities and configure migration:
{"execute": "qmp_capabilities"}
{"execute": "migrate-set-capabilities", "arguments": {
"capabilities": [
{"capability": "events", "state": true},
{"capability": "x-ignore-shared", "state": true}
]
}}
{"execute": "migrate-set-parameters", "arguments": {"local": true}}
Tell the target to listen for the incoming migration (terminal D):
{"execute": "migrate-incoming",
"arguments": {"uri": "unix:/tmp/migration.sock"}}
Trigger the migration from the source (terminal C):
{"execute": "migrate",
"arguments": {"uri": "unix:/tmp/migration.sock"}}
Poll migration status until it completes (terminal C):
{"execute": "query-migrate"}
# repeat until "status" == "completed"
Or just wait for the MIGRATION event that QEMU emits automatically:
# {"event": "MIGRATION", "data": {"status": "completed"}, ...}
Once the source reports "completed", resume the target VM (terminal D):
{"execute": "cont"}
The target VM is now running with the migrated state and the TAP file
descriptors that were passed from the source.
Verify that the guest is still reachable from the host:
ping -c 3 192.168.100.2
And from inside the guest (terminal B, target serial console):
ping -c 3 192.168.100.1
5. Cleanup
----------
Shut down the target VM (terminal D):
{"execute": "quit"}
Shut down the source VM (terminal C):
{"execute": "quit"}
Remove the TAP device:
sudo ip tuntap del tap0 mode tap multi_queue
Remove the shared memory file:
rm /dev/shm/qemu_migration_test
Remove leftover sockets if they still exist:
rm -f /tmp/migration.sock /tmp/qmp-source.sock /tmp/qmp-target.sock
Vladimir Sementsov-Ogievskiy (10):
net/tap: move vhost-net open() calls to tap_parse_vhost_fds()
net/tap: move vhost initialization to tap_setup_vhost()
net/tap: use container_of instead of DO_UPCAST
net/tap: QOMify tap backend
net/tap: add TYPE_VMSTATE_IF interface
qapi: add local migration parameter
virtio-net: support local migration of backend
net/tap: support local migration with virtio-net
tests/functional: add skipWithoutSudo() decorator
tests/functional: add test_tap_migration
hw/core/machine.c | 2 +
hw/net/virtio-net.c | 89 +++-
include/hw/virtio/virtio-net.h | 1 +
include/migration/misc.h | 2 +
include/migration/vmstate.h | 2 +
include/net/net.h | 9 +
include/net/tap.h | 2 +
migration/options.c | 18 +-
net/net.c | 14 +-
net/tap.c | 429 +++++++++++++---
qapi/migration.json | 12 +-
qapi/net.json | 20 +-
tests/functional/qemu_test/decorators.py | 16 +
tests/functional/x86_64/meson.build | 1 +
tests/functional/x86_64/test_tap_migration.py | 458 ++++++++++++++++++
15 files changed, 986 insertions(+), 89 deletions(-)
create mode 100755 tests/functional/x86_64/test_tap_migration.py
--
2.52.0