Signed-off-by: Vladimir Sementsov-Ogievskiy <[email protected]>
---
 tests/functional/x86_64/test_tap_migration.py | 251 ++++++++++++++++--
 1 file changed, 230 insertions(+), 21 deletions(-)

diff --git a/tests/functional/x86_64/test_tap_migration.py 
b/tests/functional/x86_64/test_tap_migration.py
index 0506b01926e..103421a029a 100755
--- a/tests/functional/x86_64/test_tap_migration.py
+++ b/tests/functional/x86_64/test_tap_migration.py
@@ -24,6 +24,7 @@
     exec_command_and_wait_for_pattern,
 )
 from qemu_test.decorators import skipWithoutSudo
+from qemu.qmp.legacy import QEMUMonitorProtocol
 
 
 GUEST_IP = "192.168.100.2"
@@ -289,7 +290,8 @@ def setup_shared_memory(self):
             self.fail(f"Failed to create shared memory file: {e}")
 
     def prepare_vm(
-        self, shm_path, vhost, incoming=False, vm=None, local=True
+        self, shm_path, vhost, incoming=False, vm=None, local=True,
+        cpr=False, cpr_sock=""
     ):
         if not vm:
             vm = self.vm
@@ -304,7 +306,10 @@ def prepare_vm(
             "-object",
             
f"memory-backend-file,id=ram0,size=1G,mem-path={shm_path},share=on",
         )
-        vm.add_args("-machine", "memory-backend=ram0")
+        machine_opt = "memory-backend=ram0"
+        if cpr:
+            machine_opt += ",aux-ram-share=on"
+        vm.add_args("-machine", machine_opt)
 
         vm.add_args(
             "-drive",
@@ -315,6 +320,13 @@ def prepare_vm(
 
         if incoming:
             vm.add_args("-incoming", "defer")
+            if cpr:
+                vm.add_args(
+                    "-incoming",
+                    '{"channel-type": "cpr", '
+                    '"addr": {"transport": "socket", '
+                    f'"type": "unix", "path": "{cpr_sock}"}}}}',
+                )
 
     def add_virtio_net(
         self, vm, vhost: bool, tap_name: str, local: bool, incoming: bool
@@ -348,7 +360,7 @@ def add_virtio_net(
             disable_legacy="off",
         )
 
-    def set_migration_capabilities(self, vm, local):
+    def set_migration_capabilities(self, vm, local, mode):
         vm.cmd(
             "migrate-set-capabilities",
             {
@@ -358,7 +370,10 @@ def set_migration_capabilities(self, vm, local):
                 ]
             },
         )
-        vm.cmd("migrate-set-parameters", {"local": local})
+        vm.cmd(
+            "migrate-set-parameters",
+            {"local": local, "mode": mode},
+        )
 
     def setup_guest_network(self) -> None:
         exec_command_and_wait_for_pattern(self, "ip addr", "# ")
@@ -370,20 +385,51 @@ def setup_guest_network(self) -> None:
         )
         self.wait_for_console_pattern("# ")
 
-    def migrate(self, vm, mig_sock):
-        vm.cmd("migrate", uri=f"unix:{mig_sock}")
-    def do_test_tap_fd_migration(self, vhost, local=True):
+    def migrate(self, vm, mig_sock, cpr=False, cpr_sock=""):
+        if cpr:
+            vm.cmd(
+                "migrate",
+                {
+                    "channels": [
+                        {
+                            "channel-type": "main",
+                            "addr": {
+                                "transport": "socket",
+                                "type": "unix",
+                                "path": mig_sock,
+                            },
+                        },
+                        {
+                            "channel-type": "cpr",
+                            "addr": {
+                                "transport": "socket",
+                                "type": "unix",
+                                "path": cpr_sock,
+                            },
+                        },
+                    ]
+                },
+            )
+        else:
+            vm.cmd("migrate", uri=f"unix:{mig_sock}")
+
+    def do_test_tap_fd_migration(self, vhost, local=True,
+                                 cpr=False):
+        mig_mode = "cpr-transfer" if cpr else "normal"
+
         socket_dir = self.socket_dir()
         mig_sock = os.path.join(socket_dir.name, "mig.sock")
+        cpr_sock = os.path.join(socket_dir.name, "cpr.sock")
 
         # Setup second TAP if needed
         if not local:
             del_tap(TAP_ID2)
             init_tap(TAP_ID2, with_ip=False)
 
-        self.prepare_vm(self.shm_path, vhost, local=local)
+        self.prepare_vm(self.shm_path, vhost, local=local, cpr=cpr,
+                        cpr_sock=cpr_sock)
         self.vm.launch()
-        self.set_migration_capabilities(self.vm, local=local)
+        self.set_migration_capabilities(self.vm, local=local, mode=mig_mode)
         self.add_virtio_net(self.vm, vhost, TAP_ID, local, incoming=False)
 
         self.vm.cmd("cont")
@@ -406,21 +452,53 @@ def do_test_tap_fd_migration(self, vhost, local=True):
             incoming=True,
             vm=target_vm,
             local=local,
+            cpr=cpr,
+            cpr_sock=cpr_sock,
         )
 
-        target_vm.launch()
-        if not local:
-            tap_name = TAP_ID2
+        if cpr:
+            # Launch target QEMU process; it will block in cpr_state_load()
+            # waiting for the source to connect and send CPR state.  QMP is
+            # not available until after CPR state is loaded.
+            target_vm.launch(do_qmp_connect=False)
+            time.sleep(1)
+
+            # Tell source to start CPR migration; it connects to the CPR
+            # socket, sends CPR state, then target unblocks and QMP becomes
+            # available.
+            self.migrate(self.vm, mig_sock, True, cpr_sock=cpr_sock)
+
+            # Now block waiting for target QMP greeting; it will arrive once
+            # the CPR state transfer completes.
+            target_vm.launch(do_start_process=False, do_qmp_connect=True)
+
+            if not local:
+                tap_name = TAP_ID2
+            else:
+                tap_name = TAP_ID
+            self.set_migration_capabilities(target_vm, local=local,
+                                            mode="cpr-transfer")
+            self.add_virtio_net(target_vm, vhost, tap_name, local,
+                                incoming=True)
+
+            freeze_start = time.time()
+            target_vm.cmd("migrate-incoming", {"uri": f"unix:{mig_sock}"})
         else:
-            tap_name = TAP_ID
-        self.set_migration_capabilities(target_vm, local=local)
-        self.add_virtio_net(target_vm, vhost, tap_name, local, incoming=True)
-
-        target_vm.cmd("migrate-incoming", {"uri": f"unix:{mig_sock}"})
-
-        self.log.info("Starting migration")
-        freeze_start = time.time()
-        self.migrate(self.vm, mig_sock)
+            target_vm.launch()
+            if not local:
+                tap_name = TAP_ID2
+            else:
+                tap_name = TAP_ID
+            self.set_migration_capabilities(target_vm, local=local,
+                                            mode="normal")
+            self.add_virtio_net(target_vm, vhost, tap_name, local,
+                                incoming=True)
+
+            target_vm.cmd("migrate-incoming", {"uri": f"unix:{mig_sock}"})
+
+            self.log.info("Starting migration")
+            freeze_start = time.time()
+            self.migrate(self.vm, mig_sock)
 
         self.log.info("Waiting for migration completion")
         wait_migration_finish(self.vm, target_vm)
@@ -444,6 +522,119 @@ def do_test_tap_fd_migration(self, vhost, local=True):
 
         target_vm.shutdown()
 
+    def do_test_tap_cpr_exec_migration(self, vhost):
+        socket_dir = self.socket_dir()
+        mig_file = self.scratch_file("migstate")
+
+        # Source VM: use a UNIX socket path for QMP so that the exec'ed
+        # new QEMU process can reconnect to the same monitor socket.
+        qmp_sock = os.path.join(socket_dir.name, "qmp.sock")
+        self.get_vm(name="default", monitor_address=qmp_sock)
+        self.prepare_vm(self.shm_path, vhost, local=True,
+                                   cpr=True)
+        self.vm.launch()
+        self.set_migration_capabilities(self.vm, local=True, mode="cpr-exec")
+        self.add_virtio_net(self.vm, vhost, TAP_ID, local=True, incoming=False)
+
+        self.vm.cmd("cont")
+        self.wait_for_console_pattern("login:")
+        exec_command_and_wait_for_pattern(self, "root", "# ")
+
+        self.setup_guest_network()
+
+        self.one_ping_from_guest(self.vm)
+        self.one_ping_from_host()
+        self.start_outer_ping()
+
+        # Get some successful pings before migration
+        time.sleep(0.5)
+
+        # Capture the console socket fd so we can pass it to the exec'ed
+        # QEMU; it is inheritable and preserved across exec.
+        cons_fd = self.vm._cons_sock_saved_fd
+
+        # Build the command line for the new QEMU process that will replace
+        # the source after exec.  Reuse the same QMP path and console fd.
+        exec_cmd = [self.qemu_bin]
+        exec_cmd.extend(self.vm._harness_args)
+        exec_cmd.extend(["-chardev", f"socket,id=console,fd={cons_fd}",
+                         "-serial", "chardev:console"])
+        exec_cmd.extend(self.vm._base_args)
+        exec_cmd.extend(self.vm.args)
+        exec_cmd.extend(["-incoming", "defer"])
+
+        self.vm.cmd("migrate-set-parameters",
+                    {"cpr-exec-command": exec_cmd})
+
+        self.log.info("Starting cpr-exec migration")
+        freeze_start = time.time()
+        self.vm.cmd("migrate", {"uri": f"file:{mig_file}"})
+
+        self.log.info("Waiting for source migration to complete (exec)")
+        source_e = self.vm.events_wait(
+            (
+                ("MIGRATION", {"data": {"status": "completed"}}),
+                ("MIGRATION", {"data": {"status": "failed"}}),
+            )
+        )["data"]
+        assert source_e["status"] == "completed", \
+            f"Source migration failed: {source_e}"
+
+        # The source QEMU has now exec'ed the new QEMU.  The old QMP
+        # connection is gone; create a fresh QMP server on the same path
+        # and accept the new QEMU's connection.
+        self.vm._qmp_connection.close()
+        self.vm._events.clear()
+        # Remove the stale UNIX socket file left by the old server.
+        try:
+            os.unlink(qmp_sock)
+        except FileNotFoundError:
+            pass
+        self.vm._qmp_connection = QEMUMonitorProtocol(
+            qmp_sock, server=True, nickname=self.vm._name)
+        self.vm._qmp_connection.accept(timeout=120)
+
+        # The new QEMU is now reachable through the same QEMUMachine object.
+        self.set_migration_capabilities(self.vm, local=True, mode="cpr-exec")
+        self.add_virtio_net(self.vm, vhost, TAP_ID, local=True, incoming=True)
+        self.vm.cmd(
+            "migrate-incoming",
+            channels=[
+                {
+                    "channel-type": "main",
+                    "addr": {
+                        "transport": "file",
+                        "filename": mig_file,
+                        "offset": 0,
+                    },
+                }
+            ],
+            conv_keys=False,
+        )
+
+        self.log.info("Waiting for target migration to complete")
+        target_e = self.vm.events_wait(
+            (
+                ("MIGRATION", {"data": {"status": "completed"}}),
+                ("MIGRATION", {"data": {"status": "failed"}}),
+            )
+        )["data"]
+        assert target_e["status"] == "completed", \
+            f"Target migration failed: {target_e}"
+
+        self.vm.cmd("cont")
+        freeze_end = time.time()
+
+        self.log.info("Verifying PING on target VM after migration")
+        self.one_ping_from_guest(self.vm)
+        self.one_ping_from_host()
+
+        # And a bit more pings after migration
+        time.sleep(0.3)
+        self.stop_ping_and_check(freeze_start, freeze_end)
+
+        self.vm.shutdown()
+
     def test_tap_fd_migration(self):
         self.do_test_tap_fd_migration(False)
 
@@ -456,6 +647,24 @@ def test_tap_new_tap_migration(self):
     def test_tap_new_tap_migration_vhost(self):
         self.do_test_tap_fd_migration(True, local=False)
 
+    def test_tap_fd_migration_cpr(self):
+        self.do_test_tap_fd_migration(False, cpr=True)
+
+    def test_tap_fd_migration_vhost_cpr(self):
+        self.do_test_tap_fd_migration(True, cpr=True)
+
+    def test_tap_new_tap_migration_cpr(self):
+        self.do_test_tap_fd_migration(False, local=False, cpr=True)
+
+    def test_tap_new_tap_migration_vhost_cpr(self):
+        self.do_test_tap_fd_migration(True, local=False, cpr=True)
+
+    def test_tap_cpr_exec_migration(self):
+        self.do_test_tap_cpr_exec_migration(False)
+
+    def test_tap_cpr_exec_migration_vhost(self):
+        self.do_test_tap_cpr_exec_migration(True)
+
 
 if __name__ == "__main__":
     LinuxKernelTest.main()
-- 
2.43.0


Reply via email to