This should make it easier to run things in parallel to migration, because the method switches the VM object's __dict__ instead of the VM object itself. Signed-off-by: Michael Goldish <mgoldish@xxxxxxxxxx> --- client/tests/kvm/kvm_vm.py | 142 ++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 142 insertions(+), 0 deletions(-) diff --git a/client/tests/kvm/kvm_vm.py b/client/tests/kvm/kvm_vm.py index d236359..d6ca782 100755 --- a/client/tests/kvm/kvm_vm.py +++ b/client/tests/kvm/kvm_vm.py @@ -1348,6 +1348,148 @@ class VM: return self.serial_login(internal_timeout) + def migrate(self, timeout=3600, protocol="tcp", cancel_delay=None, + offline=False, stable_check=False, clean=True, + save_path="/tmp", dest_host="localhost", remote_port=None): + """ + Migrate the VM. + + If the migration is local, the VM object's state is switched with that + of the destination VM. Otherwise, the state is switched with that of + a dead VM (returned by self.clone()). + + @param timeout: Time to wait for migration to complete. + @param protocol: Migration protocol ('tcp', 'unix' or 'exec'). + @param cancel_delay: If provided, specifies a time duration after which + migration will be canceled. Used for testing migrate_cancel. + @param offline: If True, pause the source VM before migration. + @param stable_check: If True, compare the VM's state after migration to + its state before migration and raise an exception if they + differ. + @param clean: If True, delete the saved state files (relevant only if + stable_check is also True). + @save_path: The path for state files. + @param dest_host: Destination host (defaults to 'localhost'). + @param remote_port: Port to use for remote migration. + """ + def mig_finished(): + o = self.monitor.info("migrate") + if isinstance(o, str): + return "status: active" not in o + else: + return o.get("status") != "active" + + def mig_succeeded(): + o = self.monitor.info("migrate") + if isinstance(o, str): + return "status: completed" in o + else: + return o.get("status") == "completed" + + def mig_failed(): + o = self.monitor.info("migrate") + if isinstance(o, str): + return "status: failed" in o + else: + return o.get("status") == "failed" + + def mig_cancelled(): + o = self.monitor.info("migrate") + if isinstance(o, str): + return ("Migration status: cancelled" in o or + "Migration status: canceled" in o) + else: + return (o.get("status") == "cancelled" or + o.get("status") == "canceled") + + def wait_for_migration(): + if not kvm_utils.wait_for(mig_finished, timeout, 2, 2, + "Waiting for migration to finish..."): + raise VMMigrateTimeoutError("Timeout expired while waiting " + "for migration to finish") + + local = dest_host == "localhost" + + clone = self.clone() + if local: + if stable_check: + # Pause the dest vm after creation + extra_params = clone.params.get("extra_params", "") + " -S" + clone.params["extra_params"] = extra_params + clone.create(migration_mode=protocol, mac_source=self) + + try: + if protocol == "tcp": + if local: + uri = "tcp:localhost:%d" % clone.migration_port + else: + uri = "tcp:%s:%d" % (dest_host, remote_port) + elif protocol == "unix": + uri = "unix:%s" % clone.migration_file + elif protocol == "exec": + uri = '"exec:nc localhost %s"' % clone.migration_port + + if offline: + self.monitor.cmd("stop") + + self.monitor.migrate(uri) + + if cancel_delay: + time.sleep(cancel_delay) + self.monitor.cmd("migrate_cancel") + if not kvm_utils.wait_for(mig_cancelled, 60, 2, 2, + "Waiting for migration " + "cancellation"): + raise VMMigrateCancelError("Cannot cancel migration") + return + + wait_for_migration() + + # Report migration status + if mig_succeeded(): + logging.info("Migration completed successfully") + elif mig_failed(): + raise VMMigrateFailedError("Migration failed") + else: + raise VMMigrateFailedError("Migration ended with unknown " + "status") + + # Switch self <-> clone + temp = self.clone(copy_state=True) + self.__dict__ = clone.__dict__ + clone = temp + + # From now on, clone is the source VM that will soon be destroyed + # and self is the destination VM that will remain alive. If this + # is remote migration, self is a dead VM object. + + if local and stable_check: + try: + save1 = os.path.join(save_path, "src-" + clone.instance) + save2 = os.path.join(save_path, "dst-" + self.instance) + clone.save_to_file(save1) + self.save_to_file(save2) + # Fail if we see deltas + md5_save1 = utils.hash_file(save1) + md5_save2 = utils.hash_file(save2) + if md5_save1 != md5_save2: + raise VMMigrateStateMismatchError(md5_save1, + md5_save2) + finally: + if clean: + if os.path.isfile(save1): + os.remove(save1) + if os.path.isfile(save2): + os.remove(save2) + + finally: + # If we're doing remote migration and it's completed successfully, + # self points to a dead VM object + if self.is_alive(): + self.monitor.cmd("cont") + clone.destroy(gracefully=False) + + def send_key(self, keystr): """ Send a key event to the VM. -- 1.7.3.4 -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html