[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