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

Reply via email to