Additional infrastructure added to kvm monitor for setting the allowable downtime and returning the raw status text. Both simplify the save/load vm class methods and are generally useful.
Updated kvm_vm create() method to put back in the migration_exec_cmd parameter (it was already documented). It defaults to prior behavior of listening to a random TCP port (though I'm not sure how this would ever be useful). This change is required to utilize the migration interface to restore a VM from a file on disk. A few updates to the base VM class make the uuid attribute required, though in all cases it's already established in vm.__init__ anyway. Added method which utilize a VM's name and uuid (optionally) along with an (optional) supplied string to generate a save-filename or full path. Also to BaseVM, added a restore_from_file() method, added some optional parameters to it and save_to_file(). Documented expected behavior of both. Added two virsh_* functions to libvirt_vm (though these should really go into the libvirt_monitor eventually). Both are wrappers around the save and restore virsh commands with some additional state checks surrounding them. Both throw the VMStatusError exception on state mismatch. Added overrides to libvirt_vm for save_to_file and restore_from_file based on BaseVM documentation of those methods and utilizing the common filename generator. Added/updated the kvm_vm restore_from_file() and save_to_file() respectively. Both follow the BaseVM documentation for those methods and utilizing the common filename generator. Signed-off-by: Chris Evich <[email protected]> --- client/virt/kvm_monitor.py | 17 ++++++++++- client/virt/kvm_vm.py | 54 ++++++++++++++++++++++++++++-------- client/virt/libvirt_vm.py | 66 ++++++++++++++++++++++++++++++++++++------- client/virt/virt_vm.py | 64 +++++++++++++++++++++++++++++++++++------- 4 files changed, 165 insertions(+), 36 deletions(-) diff --git a/client/virt/kvm_monitor.py b/client/virt/kvm_monitor.py index 81aabcb..aac3584 100644 --- a/client/virt/kvm_monitor.py +++ b/client/virt/kvm_monitor.py @@ -303,6 +303,10 @@ class HumanMonitor(Monitor): self.cmd("info status", debug=False) + def get_status(self): + return self.cmd("info status", debug=False) + + def verify_status(self, status): """ Verify VM status @@ -310,8 +314,7 @@ class HumanMonitor(Monitor): @param status: Optional VM status, 'running' or 'paused' @return: return True if VM status is same as we expected """ - o = self.cmd("info status", debug=False) - return (status in o) + return (status in self.get_status()) # Command wrappers @@ -422,6 +425,16 @@ class HumanMonitor(Monitor): return self.cmd("migrate_set_speed %s" % value) + def migrate_set_downtime(self, value): + """ + Set maximum tolerated downtime (in seconds) for migration. + + @param: value: maximum downtime (in seconds) + @return: The command's output + """ + return self.cmd("migrate_set_downtime %s" % value) + + def sendkey(self, keystr, hold_time=1): """ Send key combination to VM. diff --git a/client/virt/kvm_vm.py b/client/virt/kvm_vm.py index 6747c2b..e5ad4cb 100644 --- a/client/virt/kvm_vm.py +++ b/client/virt/kvm_vm.py @@ -681,7 +681,8 @@ class VM(virt_vm.BaseVM): @error.context_aware def create(self, name=None, params=None, root_dir=None, - timeout=CREATE_TIMEOUT, migration_mode=None, mac_source=None): + timeout=CREATE_TIMEOUT, migration_mode=None, + migration_exec_cmd=None, mac_source=None): """ Start the VM by running a qemu command. All parameters are optional. If name, params or root_dir are not @@ -694,6 +695,7 @@ class VM(virt_vm.BaseVM): using this protocol (either 'tcp', 'unix' or 'exec') @param migration_exec_cmd: Command to embed in '-incoming "exec: ..."' (e.g. 'gzip -c -d filename') if migration_mode is 'exec' + default to listening on a random TCP port @param mac_source: A VM object from which to copy MAC addresses. If not specified, new addresses will be generated. @@ -864,9 +866,13 @@ class VM(virt_vm.BaseVM): self.migration_file = "/tmp/migration-unix-%s" % self.instance qemu_command += " -incoming unix:%s" % self.migration_file elif migration_mode == "exec": - self.migration_port = virt_utils.find_free_port(5200, 6000) - qemu_command += (' -incoming "exec:nc -l %s"' % - self.migration_port) + if migration_exec_cmd == None: + self.migration_port = virt_utils.find_free_port(5200, 6000) + qemu_command += (' -incoming "exec:nc -l %s"' % + self.migration_port) + else: + qemu_command += (' -incoming "exec:%s"' % + migration_exec_cmd) logging.info("Running qemu command:\n%s", qemu_command) self.process = aexpect.run_bg(qemu_command, None, @@ -1585,19 +1591,43 @@ class VM(virt_vm.BaseVM): logging.warn(e) - def save_to_file(self, path): + def save_to_file(self, basepath=None, prefix="kvm-vm": """ - Save the state of virtual machine to a file through migrate to - exec + Override BaseVM save_to_file method """ - # Make sure we only get one iteration - self.monitor.cmd("migrate_set_speed 1000g") - self.monitor.cmd("migrate_set_downtime 100000000") - self.monitor.migrate('"exec:cat>%s"' % path) - # Restore the speed and downtime of migration + state = self.monitor.get_status() + if state not in ('running', 'paused'): + raise VMStatusError("Cannot save a VM that is %s" % state) + # Set high speed 1TB/S + self.monitor.migrate_set_speed(2<<39) + self.monitor.migrate_set_downtime(MIGRATE_TIMEOUT) + fullpath = self.get_state_file_path(basepath=basepath, prefix=prefix) + # Can only check status if background migration + self.monitor.migrate("exec:cat>%s" % fullpath, wait=False): + result = virt_utils.wait_for( + lambda : "status: completed" in self.monitor.info("migrate"), + MIGRATE_TIMEOUT, 2, 2, + "Waiting for save to %s to complete" % fullpath) + # Restore the speed and downtime to default values self.monitor.cmd("migrate_set_speed %d" % (32<<20)) self.monitor.cmd("migrate_set_downtime 0.03") + # Base class defines VM must be off after a save + self.monitor.cmd("system_reset") + state = self.monitor.get_status() + if state not in ('paused') + raise VMStatusError("VM not shut off after save") + def restore_from_file(self, basepath=None, prefix="kvm-vm"): + """ + Override BaseVM restore_from_file method + """ + if is_alive(): + raise VMStatusError("Can not restore VM that is alive") + fullpath = self.get_state_file_path(basepath=basepath, prefix=prefix) + # Rely on create() in incoming migration mode to 'do the right thing' + self.create(name=self.name, params=self.params, root_dir=self.root_dir + timeout=MIGRATE_TIMEOUT, migration_mode="exec", + migration_exec_cmd="cat "+fullpath, mac_source=self) def needs_restart(self, name, params, basedir): """ diff --git a/client/virt/libvirt_vm.py b/client/virt/libvirt_vm.py index 908f12a..e3d5e83 100644 --- a/client/virt/libvirt_vm.py +++ b/client/virt/libvirt_vm.py @@ -156,6 +156,48 @@ def virsh_resume(name, uri = ""): logging.error("Resume VM %s failed", name) return False +def virsh_save(name, path, uri = ""): + """ + Store state of VM into named file. + + @param: name: VM Name to operate on + @param: uri: URI of libvirt hypervisor to use + @param: path: absolute path to state file + """ + state = virsh_domstate(name, uri) + if state not in ('running', 'idle', 'paused'): + raise VMStatusError, "Cannot save a VM that is %s" % state + logging.debug("Saving VM %s to %s" %(name, path)) + virsh_cmd("save %s %s" % (name,path), uri) + # libvirt always stops VM after saving + state = virsh_domstate(name, uri) + if state not in ('shut off',): + raise VMStatusError, "VM not shut off after save" + +def virsh_restore(name, path, uri = ""): + """ + Load state of VM from named file and remove file. + + @param: name: VM Name to operate on + @param: uri: URI of libvirt hypervisor to use + @param: path: absolute path to state file. + """ + # Blindly assume named VM cooresponds with state in path + # rely on higher-layers to take exception if missmatch + state = virsh_domstate(name, uri) + if state not in ('shut off',): + raise VMStatusError, "Can not restore VM that is %s" % state + logging.debug("Restoring VM from %s" % path) + virsh_cmd("restore %s" % path, uri) + state = virsh_domstate(name, uri) + # libvirt always leaves just loaded VMs in paused state + if state not in ('paused',): + raise VMStatusError, "VM not paused after restore" + # Load was successful, remove state file + try: + os.unlink(path) + except OSError: + pass # ignore missing files def virsh_start(name, uri = ""): """ @@ -1274,15 +1316,6 @@ class VM(virt_vm.BaseVM): logging.error("VM %s failed to shut down", self.name) return False - - def send_key(self): - pass - - - def migrate(self): - pass - - def pause(self): pass @@ -1291,5 +1324,16 @@ class VM(virt_vm.BaseVM): pass - def save_to_file(self): - pass + def save_to_file(self, basepath=None, prefix="libvirt-vm"): + """ + Override BaseVM save_to_file method + """ + fullpath = self.get_state_file_path(basepath=basepath, prefix=prefix) + virsh_save(self.name, fullpath, uri=self.connect_uri) + + def restore_from_file(self, basepath=None, prefix="libvirt-vm"): + """ + Override BaseVM restore_from_file method + """ + fullpath = self.get_state_file_path(basepath=basepath, prefix=prefix) + virsh_restore(self.name, fullpath, uri=self.connect_uri) diff --git a/client/virt/virt_vm.py b/client/virt/virt_vm.py index e2443dd..c95278b 100644 --- a/client/virt/virt_vm.py +++ b/client/virt/virt_vm.py @@ -1,4 +1,4 @@ -import os, logging, time, glob, re, shutil +import os, logging, time, glob, re, shutil, uuid, tempfile from autotest_lib.client.common_lib import error from autotest_lib.client.bin import utils import virt_utils @@ -806,15 +806,11 @@ class BaseVM(object): def get_uuid(self): """ - Catch UUID of the VM. + Return the UUID for the VM. - @return: None,if not specified in config file + @return: A UUID string """ - if self.params.get("uuid") == "random": - return self.uuid - else: - return self.params.get("uuid", None) - + return self.uuid # subclass's __init__() must always set one def send_string(self, str): """ @@ -988,14 +984,60 @@ class BaseVM(object): """ raise NotImplementedError + def get_state_file_name(self, name=None, uuid=None, prefix=""): + """ + Retrieve reversable hash filename unique to a created VM + + Successive calls for the same underlying virtual machine should always + return the same filename, whether or not the instance, hypervisor, or + host has changed. Names should never clash. + + @param: name: If None, use vm.name, otherwise override w/ specified. + @param: uuid: If None, use vm.uuid, otherwise override w/ specified. + @param: prefix: Arbitrary string common to usage. + @return: filename unique to VM instance. + """ + if not name: + name = self.name + if not uuid: + uuid = self.uuid + uniqueid = str( uuid.UUID(self.uuid) ) # validate uuid formating + # Hash character rarely used in VM names and uuid's + return "%s#%s#%s" % (prefix, name, uniqueid) - def save_to_file(self, path): + def get_state_file_path(self, basepath=None, name=None, uuid=None, + prefix=""): """ - Save the state of virtual machine to a file through migrate to - exec + Returns absolute path to reversable hash filename unique to created VM + + @param: basepath: directory where VM state recorded into a file + @param: name: If None, use vm.name, otherwise override w/ specified. + @param: uuid: If None, use vm.uuid, otherwise override w/ specified. + @param: prefix: Arbitrary string common to usage. + @return: filename unique to VM instance. + """ + if not basepath: + basepath = os.path.join(tempfile.gettempdir()) + filename = self.get_state_file_name(name, uuid, prefix) + fullpath = os.path.join(basepath, filename) + return fullpath + + def save_to_file(self, basepath=None, prefix=None): + """ + State of VM recorded to vm.get_state_file_*() filename, then shutdown. + + @param: basepath: directory where VM state recorded into a file + @return: If successful, absolute path to filename VM saved to. """ raise NotImplementedError + def restore_from_file(self, basepath=None, prefix=None): + """ + Shut down VM resumed from vm.get_state_file_*() filename, file Removed + + @param: path: Directory containing file saved using vm.save_to_file() + """ + raise NotImplementedError def needs_restart(self, name, params, basedir): """ -- 1.7.1 _______________________________________________ Autotest mailing list [email protected] http://test.kernel.org/cgi-bin/mailman/listinfo/autotest
