[PATCH 5/5] test_tap_migration: add cpr test cases

Vladimir Sementsov-Ogievskiy <[email protected]>
Newsgroups gmane.comp.emulators.qemu
Message-ID <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.