File snapshot-xend.patch of Package xen

Index: xen-3.3.1-testing/tools/python/xen/xend/image.py
===================================================================
--- xen-3.3.1-testing.orig/tools/python/xen/xend/image.py
+++ xen-3.3.1-testing/tools/python/xen/xend/image.py
@@ -424,7 +424,7 @@ class ImageHandler:
         #  have a callback but sadly we don't have Twisted in xend
         self.sentinel_thread = thread.start_new_thread(self._sentinel_watch,())
 
-    def signalDeviceModel(self, cmd, ret, par = None):
+    def signalDeviceModel(self, cmd, ret, par = None, timeout = True):
         if self.device_model is None:
             return
         # Signal the device model to for action
@@ -447,10 +447,17 @@ class ImageHandler:
         while state != ret:
             state = xstransact.Read("/local/domain/0/device-model/%i/state"
                                     % self.vm.getDomid())
+            if state == 'error':
+                msg = ("The device model returned an error: %s"
+                      % xstransact.Read("/local/domain/0/device-model/%i/error"
+                                        % self.vm.getDomid()))
+                raise VmError(msg)
+
             time.sleep(0.1)
-            count += 1
-            if count > 100:
-                raise VmError('Timed out waiting for device model action')
+            if timeout:
+                count += 1
+                if count > 100:
+                    raise VmError('Timed out waiting for device model action')
 
         #resotre orig state
         xstransact.Store("/local/domain/0/device-model/%i"
@@ -476,6 +483,10 @@ class ImageHandler:
         # but this can easily lead to very rapid restart loops against
         # which we currently have no protection
 
+    def snapshotDeviceModel(self, name):
+        # Signal the device model to perform snapshot operation
+        self.signalDeviceModel('snapshot', 'paused', name, False)
+
     def recreate(self):
         if self.device_model is None:
             return
Index: xen-3.3.1-testing/tools/python/xen/xend/server/blkif.py
===================================================================
--- xen-3.3.1-testing.orig/tools/python/xen/xend/server/blkif.py
+++ xen-3.3.1-testing/tools/python/xen/xend/server/blkif.py
@@ -88,6 +88,9 @@ class BlkifController(DevController):
         if bootable != None:
             back['bootable'] = str(bootable)
 
+        if 'snapshotname' in self.vm.info:
+            back['snapshot'] = self.vm.info['snapshotname']
+
         if security.on() == xsconstants.XS_POLICY_ACM:
             self.do_access_control(config, uname)
 
Index: xen-3.3.1-testing/tools/python/xen/xend/server/SrvDomain.py
===================================================================
--- xen-3.3.1-testing.orig/tools/python/xen/xend/server/SrvDomain.py
+++ xen-3.3.1-testing/tools/python/xen/xend/server/SrvDomain.py
@@ -95,6 +95,31 @@ class SrvDomain(SrvDir):
     def do_save(self, _, req):
         return self.xd.domain_save(self.dom.domid, req.args['file'][0])
 
+    def op_snapshot_create(self, op, req):
+        self.acceptCommand(req)
+        return req.threadRequest(self.do_snapshot_create, op, req)
+
+    def do_snapshot_create(self, _, req):
+        return self.xd.domain_snapshot_create(self.dom.domid, req.args['name'][0])
+
+    def op_snapshot_list(self, op, req):
+        self.acceptCommand(req)
+        return self.xd.domain_snapshot_list(self.dom.getName())
+
+    def op_snapshot_apply(self, op, req):
+        self.acceptCommand(req)
+        return req.threadRequest(self.do_snapshot_apply, op, req)
+
+    def do_snapshot_apply(self, _, req):
+        return self.xd.domain_snapshot_apply(self.dom.getName(), req.args['name'][0])
+
+    def op_snapshot_delete(self, op, req):
+        self.acceptCommand(req)
+        return req.threadRequest(self.do_snapshot_delete, op, req)
+
+    def do_snapshot_delete(self, _, req):
+        return self.xd.domain_snapshot_delete(self.dom.getName(), req.args['name'][0])
+
     def op_dump(self, op, req):
         self.acceptCommand(req)
         return req.threadRequest(self.do_dump, op, req)
@@ -230,7 +255,7 @@ class SrvDomain(SrvDir):
     def render_GET(self, req):
         op = req.args.get('op')
 
-        if op and op[0] in ['vcpuinfo']:
+        if op and op[0] in ['vcpuinfo', 'snapshot_list']:
             return self.perform(req)
 
         #
Index: xen-3.3.1-testing/tools/python/xen/xend/XendCheckpoint.py
===================================================================
--- xen-3.3.1-testing.orig/tools/python/xen/xend/XendCheckpoint.py
+++ xen-3.3.1-testing/tools/python/xen/xend/XendCheckpoint.py
@@ -65,7 +65,7 @@ def insert_after(list, pred, value):
     return
 
 
-def save(fd, dominfo, network, live, dst, checkpoint=False, node=-1):
+def save(fd, dominfo, network, live, dst, checkpoint=False, node=-1, name=None, diskonly=False):
     write_exact(fd, SIGNATURE, "could not write guest state file: signature")
 
     sxprep = dominfo.sxpr()
@@ -91,52 +91,61 @@ def save(fd, dominfo, network, live, dst
         image_cfg = dominfo.info.get('image', {})
         hvm = dominfo.info.is_hvm()
 
-        # xc_save takes three customization parameters: maxit, max_f, and
-        # flags the last controls whether or not save is 'live', while the
-        # first two further customize behaviour when 'live' save is
-        # enabled. Passing "0" simply uses the defaults compiled into
-        # libxenguest; see the comments and/or code in xc_linux_save() for
-        # more information.
-        cmd = [xen.util.auxbin.pathTo(XC_SAVE), str(fd),
-               str(dominfo.getDomid()), "0", "0", 
-               str(int(live) | (int(hvm) << 2)) ]
-        log.debug("[xc_save]: %s", string.join(cmd))
-
-        def saveInputHandler(line, tochild):
-            log.debug("In saveInputHandler %s", line)
-            if line == "suspend":
-                log.debug("Suspending %d ...", dominfo.getDomid())
-                dominfo.shutdown('suspend')
-                dominfo.waitForShutdown()
-            if line in ('suspend', 'suspended'):
-                dominfo.migrateDevices(network, dst, DEV_MIGRATE_STEP2,
-                                       domain_name)
-                log.info("Domain %d suspended.", dominfo.getDomid())
-                dominfo.migrateDevices(network, dst, DEV_MIGRATE_STEP3,
-                                       domain_name)
-                if hvm:
-                    dominfo.image.saveDeviceModel()
-
-            if line == "suspend":
-                tochild.write("done\n")
-                tochild.flush()
-                log.debug('Written done')
-
-        forkHelper(cmd, fd, saveInputHandler, False)
-
-        # put qemu device model state
-        if os.path.exists("/var/lib/xen/qemu-save.%d" % dominfo.getDomid()):
-            write_exact(fd, QEMU_SIGNATURE, "could not write qemu signature")
-            qemu_fd = os.open("/var/lib/xen/qemu-save.%d" % dominfo.getDomid(),
-                              os.O_RDONLY)
-            while True:
-                buf = os.read(qemu_fd, dm_batch)
-                if len(buf):
-                    write_exact(fd, buf, "could not write device model state")
-                else:
-                    break
-            os.close(qemu_fd)
-            os.remove("/var/lib/xen/qemu-save.%d" % dominfo.getDomid())
+        if not diskonly:
+            # xc_save takes three customization parameters: maxit, max_f, and
+            # flags the last controls whether or not save is 'live', while the
+            # first two further customize behaviour when 'live' save is
+            # enabled. Passing "0" simply uses the defaults compiled into
+            # libxenguest; see the comments and/or code in xc_linux_save() for
+            # more information.
+            cmd = [xen.util.auxbin.pathTo(XC_SAVE), str(fd),
+                   str(dominfo.getDomid()), "0", "0",
+                   str(int(live) | (int(hvm) << 2)) ]
+            log.debug("[xc_save]: %s", string.join(cmd))
+
+            def saveInputHandler(line, tochild):
+                log.debug("In saveInputHandler %s", line)
+                if line == "suspend":
+                    log.debug("Suspending %d ...", dominfo.getDomid())
+                    dominfo.shutdown('suspend')
+                    dominfo.waitForShutdown()
+                if line in ('suspend', 'suspended'):
+                    dominfo.migrateDevices(network, dst, DEV_MIGRATE_STEP2,
+                                           domain_name)
+                    log.info("Domain %d suspended.", dominfo.getDomid())
+                    dominfo.migrateDevices(network, dst, DEV_MIGRATE_STEP3,
+                                           domain_name)
+                    if hvm:
+                        dominfo.image.saveDeviceModel()
+                        if name:
+                            dominfo.image.resumeDeviceModel()
+
+                if line == "suspend":
+                    tochild.write("done\n")
+                    tochild.flush()
+                    log.debug('Written done')
+
+            forkHelper(cmd, fd, saveInputHandler, False)
+
+            # put qemu device model state
+            if os.path.exists("/var/lib/xen/qemu-save.%d" % dominfo.getDomid()):
+                write_exact(fd, QEMU_SIGNATURE, "could not write qemu signature")
+                qemu_fd = os.open("/var/lib/xen/qemu-save.%d" % dominfo.getDomid(),
+                                  os.O_RDONLY)
+                while True:
+                    buf = os.read(qemu_fd, dm_batch)
+                    if len(buf):
+                        write_exact(fd, buf, "could not write device model state")
+                    else:
+                        break
+                os.close(qemu_fd)
+                os.remove("/var/lib/xen/qemu-save.%d" % dominfo.getDomid())
+        else:
+            dominfo.shutdown('suspend')
+            dominfo.waitForShutdown()
+
+        if name:
+            dominfo.image.snapshotDeviceModel(name)
 
         if checkpoint:
             dominfo.resumeDomain()
@@ -193,6 +202,71 @@ def restore(xd, fd, dominfo = None, paus
         if othervm is not None and othervm.domid is not None: 
             raise VmError("Domain '%s' already exists with ID '%d'" % (domconfig["name_label"], othervm.domid))
 
+    def contains_state(fd):
+        try:
+            cur = os.lseek(fd, 0, 1)
+            end = os.lseek(fd, 0, 2)
+
+            ret = False
+            if cur < end:
+                ret = True
+
+            os.lseek(fd, cur, 0)
+            return ret
+        except OSError, (errno, strerr):
+            # lseek failed <==> socket <==> state
+            return True
+
+   #
+   # We shouldn't hold the domains_lock over a waitForDevices
+   # As this function sometime gets called holding this lock,
+   # we must release it and re-acquire it appropriately
+   #
+    def wait_devs(dominfo):
+        from xen.xend import XendDomain
+
+        lock = True;
+        try:
+            XendDomain.instance().domains_lock.release()
+        except:
+            lock = False;
+
+        try:
+            dominfo.waitForDevices() # Wait for backends to set up
+        except Exception, exn:
+            log.exception(exn)
+            if lock:
+                XendDomain.instance().domains_lock.acquire()
+            raise
+
+        if lock:
+            XendDomain.instance().domains_lock.acquire()
+
+
+    if not contains_state(fd):
+        # Disk-only snapshot.  Just start the vm from config (which should
+        # contain snapshotname.
+        if dominfo:
+            log.debug("### starting domain directly through XendDomainInfo")
+            dominfo.start()
+        else:
+            # Warning! Do we need to call into XendDomain to get domain
+            # lock?  Similar to the xd.restore_() call below?
+            # We'll try XendDomain.domain_create()
+            log.debug("### starting domain through XendDomain.create()")
+            dominfo = xd.domain_create(vmconfig)
+
+        try:
+            wait_devs(dominfo)
+        except:
+            dominfo.destroy()
+            raise
+
+        dominfo.unpause()
+
+        # Done if disk only snapshot
+        return dominfo
+
     if dominfo:
         dominfo.resume()
     else:
@@ -308,26 +382,7 @@ def restore(xd, fd, dominfo = None, paus
         
         dominfo.completeRestore(handler.store_mfn, handler.console_mfn)
 
-        #
-        # We shouldn't hold the domains_lock over a waitForDevices
-        # As this function sometime gets called holding this lock,
-        # we must release it and re-acquire it appropriately
-        #
-        from xen.xend import XendDomain
-
-        lock = True;
-        try:
-            XendDomain.instance().domains_lock.release()
-        except:
-            lock = False;
-
-        try:
-            dominfo.waitForDevices() # Wait for backends to set up
-        except Exception, exn:
-            log.exception(exn)
-
-        if lock:
-            XendDomain.instance().domains_lock.acquire()
+        wait_devs(dominfo)
 
         if not paused:
             dominfo.unpause()
Index: xen-3.3.1-testing/tools/python/xen/xend/XendConfig.py
===================================================================
--- xen-3.3.1-testing.orig/tools/python/xen/xend/XendConfig.py
+++ xen-3.3.1-testing/tools/python/xen/xend/XendConfig.py
@@ -209,6 +209,7 @@ XENAPI_CFG_TYPES = {
     'cpuid_check' : dict,
     'machine_address_size': int,
     'suppress_spurious_page_faults': bool0,
+    'snapshotname': str,
 }
 
 # List of legacy configuration keys that have no equivalent in the
Index: xen-3.3.1-testing/tools/python/xen/xend/XendDomain.py
===================================================================
--- xen-3.3.1-testing.orig/tools/python/xen/xend/XendDomain.py
+++ xen-3.3.1-testing/tools/python/xen/xend/XendDomain.py
@@ -52,6 +52,7 @@ from xen.xend.xenstore.xstransact import
 from xen.xend.xenstore.xswatch import xswatch
 from xen.util import mkdir
 from xen.xend import uuid
+from xen.xend import sxp
 
 xc = xen.lowlevel.xc.xc()
 xoptions = XendOptions.instance() 
@@ -1403,6 +1404,187 @@ class XendDomain:
             raise XendError("can't write guest state file %s: %s" %
                             (dst, ex[1]))
 
+    def domain_snapshot_create(self, domid, name, diskonly=False):
+        """Snapshot a running domain.
+
+        @param domid: Domain ID or Name
+        @type domid: int or string.
+        @param name: Snapshot name
+        @type dst: string
+        @param diskonly: Snapshot disk only - exclude machine state
+        @type dst: bool
+        @rtype: None
+        @raise XendError: Failed to snapshot domain
+        @raise XendInvalidDomain: Domain is not valid
+        """
+        try:
+            dominfo = self.domain_lookup_nr(domid)
+            if not dominfo:
+                raise XendInvalidDomain(str(domid))
+
+            snap_file = os.path.join(xoptions.get_xend_domains_path(),
+                                dominfo.get_uuid(), "snapshots", name)
+
+            if os.access(snap_file, os.F_OK):
+                raise XendError("Snapshot:%s exist for domain %s\n" % (name, str(domid)))
+
+            if dominfo.getDomid() == DOM0_ID:
+                raise XendError("Cannot snapshot privileged domain %s" % str(domid))
+            if dominfo._stateGet() != DOM_STATE_RUNNING:
+                raise VMBadState("Domain is not running",
+                                 POWER_STATE_NAMES[DOM_STATE_RUNNING],
+                                 POWER_STATE_NAMES[dominfo._stateGet()])
+
+            if not os.path.exists(self._managed_config_path(dominfo.get_uuid())):
+                raise XendError("Domain is not managed by Xend lifecycle " +
+                                "support.")
+
+            # Check if all images support snapshots
+            for dev_type, dev_info in dominfo.info.all_devices_sxpr():
+                mode = sxp.child_value(dev_info, 'mode')
+                if mode == 'r':
+                    continue;
+                if dev_type == 'vbd':
+                    raise XendError("All writable images need to use the " +
+                        "tap:qcow2 protocol for snapshot support")
+                if dev_type == 'tap':
+                    # Fetch the protocol name from tap:xyz:filename
+                    type = sxp.child_value(dev_info, 'uname')
+                    type = type.split(':')[1]
+                    if type != 'qcow2':
+                        raise XendError("All writable images need to use the " +
+                            "tap:qcow2 protocol for snapshot support")
+
+            snap_path = os.path.join(xoptions.get_xend_domains_path(),
+                                    dominfo.get_uuid(), "snapshots")
+            mkdir.parents(snap_path, stat.S_IRWXU)
+            snap_file = os.path.join(snap_path, name)
+
+
+            oflags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
+            if hasattr(os, "O_LARGEFILE"):
+                oflags |= os.O_LARGEFILE
+            fd = os.open(snap_file, oflags)
+            try:
+                XendCheckpoint.save(fd, dominfo, False, False, snap_file,
+                                    True, name=name, diskonly=diskonly)
+            except Exception, e:
+                os.close(fd)
+                os.unlink(snap_file)
+                raise e
+            os.close(fd)
+        except OSError, ex:
+            raise XendError("can't write guest state file %s: %s" %
+                            (snap_file, ex[1]))
+
+    def domain_snapshot_list(self, domid):
+        """List available snapshots for a domain.
+
+        @param domid: Domain ID or Name
+        @type domid: int or string.
+        @rtype: list of snapshot names
+        @raise XendInvalidDomain: Domain is not valid
+        """
+        try:
+            dominfo = self.domain_lookup_nr(domid)
+            if not dominfo:
+                raise XendInvalidDomain(str(domid))
+
+            snap_path = os.path.join(xoptions.get_xend_domains_path(),
+                                     dominfo.get_uuid(), "snapshots")
+
+            if not os.access(snap_path, os.R_OK):
+                return []
+
+            return os.listdir(snap_path)
+
+        except:
+            return []
+
+    def domain_snapshot_apply(self, domid, name):
+        """Start a domain from snapshot
+
+        @param domid: Domain ID or Name
+        @type domid: int or string.
+        @param name: Snapshot name
+        @type dst: string
+        @rtype: None
+        @raise XendError: Failed to apply snapshot
+        @raise XendInvalidDomain: Domain is not valid
+        """
+        try:
+            dominfo = self.domain_lookup_nr(domid)
+            if not dominfo:
+                log.debug("## no dominfo")
+                raise XendInvalidDomain(str(domid))
+
+            if dominfo.getDomid() == DOM0_ID:
+                raise XendError("Cannot apply snapshots to  privileged domain %s" % str(domid))
+            if dominfo._stateGet() != DOM_STATE_HALTED:
+                raise VMBadState("Domain is not halted",
+                                 POWER_STATE_NAMES[DOM_STATE_HALTED],
+                                 POWER_STATE_NAMES[dominfo._stateGet()])
+
+            snap_file = os.path.join(xoptions.get_xend_domains_path(),
+                                    dominfo.get_uuid(), "snapshots", name)
+            if not os.access(snap_file, os.R_OK):
+                raise XendError("Unable to access snapshot %s for domain %s" %
+                                (name, str(domid)))
+
+            oflags = os.O_RDONLY
+            if hasattr(os, "O_LARGEFILE"):
+                oflags |= os.O_LARGEFILE
+            fd = os.open(snap_file, oflags)
+            try:
+                self.domain_restore_fd(fd)
+            finally:
+                os.close(fd)
+        except OSError, ex:
+            raise XendError("Unable to read snapshot file file %s: %s" %
+                            (snap_file, ex[1]))
+
+    def domain_snapshot_delete(self, domid, name):
+        """Delete domain snapshot
+
+        @param domid: Domain ID or Name
+        @type domid: int or string.
+        @param name: Snapshot name
+        @type domid: string
+        @rtype: None
+        @raise XendInvalidDomain: Domain is not valid
+        """
+        dominfo = self.domain_lookup_nr(domid)
+        if not dominfo:
+            raise XendInvalidDomain(str(domid))
+
+        snap_file = os.path.join(xoptions.get_xend_domains_path(),
+                                 dominfo.get_uuid(), "snapshots", name)
+
+        if not os.access(snap_file, os.F_OK):
+            raise XendError("Snapshot %s does not exist for domain %s" %
+                            (name, str(domid)))
+
+        # Need to "remove" snapshot from qcow2 image file.
+        # For running domains, this is left to ioemu. For stopped domains
+        # we must invoke qemu-img for all devices ourselves
+        if dominfo._stateGet() != DOM_STATE_HALTED:
+            dominfo.image.signalDeviceModel("snapshot-delete",
+                "snapshot-deleted", name)
+        else:
+            for dev_type, dev_info in dominfo.info.all_devices_sxpr():
+                if dev_type != 'tap':
+                    continue
+
+                # Fetch the filename and strip off tap:xyz:
+                image_file = sxp.child_value(dev_info, 'uname')
+                image_file = image_file.split(':')[2]
+
+                os.system("qemu-img-xen snapshot -d %s %s" %
+                    (name, image_file));
+
+
+        os.unlink(snap_file)
+
     def domain_pincpu(self, domid, vcpu, cpumap):
         """Set which cpus vcpu can use
 
Index: xen-3.3.1-testing/tools/python/xen/xm/main.py
===================================================================
--- xen-3.3.1-testing.orig/tools/python/xen/xm/main.py
+++ xen-3.3.1-testing/tools/python/xen/xm/main.py
@@ -126,6 +126,14 @@ SUBCOMMAND_HELP = {
                      'Restore a domain from a saved state.'),
     'save'        : ('[-c|-f] <Domain> <CheckpointFile>',
                      'Save a domain state to restore later.'),
+    'snapshot-create' : ('[-d] <Domain> <SnapshotName>',
+                         'Snapshot a running domain.'),
+    'snapshot-list'   : ('<Domain>',
+                         'List available snapshots for a domain.'),
+    'snapshot-apply'  : ('<Domain> <SnapshotName>',
+                         'Apply previous snapshot to domain.'),
+    'snapshot-delete' : ('<Domain> <SnapshotName>',
+                         'Delete snapshot of domain.'),
     'shutdown'    : ('<Domain> [-waRH]', 'Shutdown a domain.'),
     'top'         : ('', 'Monitor a host and the domains in real time.'),
     'unpause'     : ('<Domain>', 'Unpause a paused domain.'),
@@ -283,6 +291,9 @@ SUBCOMMAND_OPTIONS = {
        ('-c', '--checkpoint', 'Leave domain running after creating snapshot'),
        ('-f', '--force', 'Force to overwrite exist file'),
     ),
+    'snapshot-create': (
+       ('-d', '--diskonly', 'Perform disk only snapshot of domain'),
+    ),
     'restore': (
        ('-p', '--paused', 'Do not unpause domain after restoring it'),
     ),
@@ -309,6 +320,10 @@ common_commands = [
     "restore",
     "resume",
     "save",
+    "snapshot-create",
+    "snapshot-list",
+    "snapshot-apply",
+    "snapshot-delete",
     "shell",
     "shutdown",
     "start",
@@ -340,6 +355,10 @@ domain_commands = [
     "restore",
     "resume",
     "save",
+    "snapshot-create",
+    "snapshot-list",
+    "snapshot-apply",
+    "snapshot-delete",
     "shutdown",
     "start",
     "suspend",
@@ -731,6 +750,62 @@ def xm_event_monitor(args):
 #
 #########################################################################
 
+def xm_snapshot_create(args):
+
+    arg_check(args, "snapshot-create", 2, 3)
+
+    try:
+        (options, params) = getopt.gnu_getopt(args, 'd', ['diskonly'])
+    except getopt.GetoptError, opterr:
+        err(opterr)
+        sys.exit(1)
+
+    diskonly = False
+    for (k, v) in options:
+        if k in ['-d', '--diskonly']:
+            diskonly = True
+
+    if len(params) != 2:
+        err("Wrong number of parameters")
+        usage('snapshot-create')
+
+    if serverType == SERVER_XEN_API:
+        server.xenapi.VM.snapshot_create(get_single_vm(params[0]), params[1], diskonly)
+    else:
+        server.xend.domain.snapshot_create(params[0], params[1], diskonly)
+
+def xm_snapshot_list(args):
+    arg_check(args, "snapshot-list", 1, 2)
+
+    snapshots = None
+    if serverType == SERVER_XEN_API:
+        snapshots = server.xenapi.VM.snapshot_list(get_single_vm(args[0]))
+    else:
+        snapshots = server.xend.domain.snapshot_list(args[0])
+
+    if snapshots:
+        print "Available snapshots for domain %s" % args[0]
+        for snapshot in snapshots:
+            print "  %s" % snapshot
+    else:
+        print "No snapshot available for domain %s" % args[0]
+
+def xm_snapshot_apply(args):
+    arg_check(args, "snapshot-apply", 2, 3)
+
+    if serverType == SERVER_XEN_API:
+        server.xenapi.VM.snapshot_apply(get_single_vm(args[0]), args[1])
+    else:
+        server.xend.domain.snapshot_apply(args[0], args[1])
+
+def xm_snapshot_delete(args):
+    arg_check(args, "snapshot-delete", 2, 3)
+
+    if serverType == SERVER_XEN_API:
+        server.xenapi.VM.snapshot_delete(get_single_vm(args[0]), args[1])
+    else:
+        server.xend.domain.snapshot_delete(args[0], args[1])
+
 def xm_save(args):
 
     arg_check(args, "save", 2, 4)
@@ -2765,6 +2840,10 @@ commands = {
     "restore": xm_restore,
     "resume": xm_resume,
     "save": xm_save,
+    "snapshot-create": xm_snapshot_create,
+    "snapshot-list": xm_snapshot_list,
+    "snapshot-apply": xm_snapshot_apply,
+    "snapshot-delete": xm_snapshot_delete,
     "shutdown": xm_shutdown,
     "start": xm_start,
     "sysrq": xm_sysrq,
openSUSE Build Service is sponsored by