[libvirt] [PATCH 3/3] introduce pull backup

Nikolay Shirokovskiy nshirokovskiy at virtuozzo.com
Wed Sep 7 09:24:43 UTC 2016


Add API to start/stop exporting disks for a backup and add qemu
implementation.

The latter is not complete yet. At least backup disks are not
cleaned up and libvirt restart is not handled.
---
 examples/object-events/event-test.c     |   3 +
 include/libvirt/libvirt-domain-backup.h |  45 +++
 include/libvirt/libvirt-domain.h        |   3 +
 include/libvirt/libvirt.h               |   1 +
 include/libvirt/virterror.h             |   1 +
 po/POTFILES.in                          |   2 +
 src/Makefile.am                         |   3 +
 src/access/viraccessperm.c              |   3 +-
 src/access/viraccessperm.h              |   6 +
 src/conf/backup_conf.c                  | 295 ++++++++++++++
 src/conf/backup_conf.h                  |  85 ++++
 src/conf/domain_conf.c                  |   2 +-
 src/driver-hypervisor.h                 |  11 +
 src/libvirt-domain-backup.c             |  86 ++++
 src/libvirt_private.syms                |   6 +
 src/libvirt_public.syms                 |   2 +
 src/qemu/qemu_blockjob.c                |   2 +
 src/qemu/qemu_conf.h                    |   1 +
 src/qemu/qemu_domain.h                  |   4 +
 src/qemu/qemu_driver.c                  | 684 ++++++++++++++++++++++++++++++++
 src/qemu/qemu_monitor.c                 |  32 ++
 src/qemu/qemu_monitor.h                 |  12 +
 src/qemu/qemu_monitor_json.c            | 105 +++++
 src/qemu/qemu_monitor_json.h            |  16 +
 src/remote/remote_driver.c              |   2 +
 src/remote/remote_protocol.x            |  33 +-
 src/util/virerror.c                     |   1 +
 tools/Makefile.am                       |   1 +
 tools/virsh-backup.c                    | 150 +++++++
 tools/virsh-backup.h                    |  28 ++
 tools/virsh-domain.c                    |   3 +-
 tools/virsh.c                           |   2 +
 tools/virsh.h                           |   1 +
 33 files changed, 1627 insertions(+), 4 deletions(-)
 create mode 100644 include/libvirt/libvirt-domain-backup.h
 create mode 100644 src/conf/backup_conf.c
 create mode 100644 src/conf/backup_conf.h
 create mode 100644 src/libvirt-domain-backup.c
 create mode 100644 tools/virsh-backup.c
 create mode 100644 tools/virsh-backup.h

diff --git a/examples/object-events/event-test.c b/examples/object-events/event-test.c
index 730cb8b..08490bb 100644
--- a/examples/object-events/event-test.c
+++ b/examples/object-events/event-test.c
@@ -829,6 +829,9 @@ blockJobTypeToStr(int type)
 
     case VIR_DOMAIN_BLOCK_JOB_TYPE_ACTIVE_COMMIT:
         return "active layer block commit";
+
+    case VIR_DOMAIN_BLOCK_JOB_TYPE_BACKUP:
+        return "block backup";
     }
 
     return "unknown";
diff --git a/include/libvirt/libvirt-domain-backup.h b/include/libvirt/libvirt-domain-backup.h
new file mode 100644
index 0000000..cd24995
--- /dev/null
+++ b/include/libvirt/libvirt-domain-backup.h
@@ -0,0 +1,45 @@
+/*
+ * libvirt-domain-backup.h
+ * Summary: APIs for management of domain backups
+ * Description: Provides APIs for the management of domain backups
+ * Author: Nikolay Shirokovskiy <nshirokovskiy at virtuozzo.com>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __VIR_LIBVIRT_DOMAIN_BACKUP_H__
+# define __VIR_LIBVIRT_DOMAIN_BACKUP_H__
+
+# ifndef __VIR_LIBVIRT_H_INCLUDES__
+#  error "Don't include this file directly, only use libvirt/libvirt.h"
+# endif
+
+typedef enum {
+    VIR_DOMAIN_BACKUP_START_QUIESCE        = (1 << 0), /* use guest agent to
+                                                          quiesce all mounted
+                                                          file systems within
+                                                          the domain */
+} virDomainBackupStartFlags;
+
+int virDomainBackupStart(virDomainPtr domain,
+                         const char *xmlDesc,
+                         unsigned int flags);
+
+int virDomainBackupStop(virDomainPtr domaine,
+                        unsigned int flags);
+
+
+#endif /* __VIR_LIBVIRT_DOMAIN_BACKUP_H__ */
diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h
index 6e0e7fb..f2cb759 100644
--- a/include/libvirt/libvirt-domain.h
+++ b/include/libvirt/libvirt-domain.h
@@ -2048,6 +2048,9 @@ typedef enum {
     /* Active Block Commit (virDomainBlockCommit with flags), job
      * exists as long as sync is active */
 
+    VIR_DOMAIN_BLOCK_JOB_TYPE_BACKUP = 5,
+    /* Block Backup */
+
 # ifdef VIR_ENUM_SENTINELS
     VIR_DOMAIN_BLOCK_JOB_TYPE_LAST
 # endif
diff --git a/include/libvirt/libvirt.h b/include/libvirt/libvirt.h
index 36f6d60..be0d570 100644
--- a/include/libvirt/libvirt.h
+++ b/include/libvirt/libvirt.h
@@ -37,6 +37,7 @@ extern "C" {
 # include <libvirt/libvirt-host.h>
 # include <libvirt/libvirt-domain.h>
 # include <libvirt/libvirt-domain-snapshot.h>
+# include <libvirt/libvirt-domain-backup.h>
 # include <libvirt/libvirt-event.h>
 # include <libvirt/libvirt-interface.h>
 # include <libvirt/libvirt-network.h>
diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h
index 2ec560e..c1f8c6c 100644
--- a/include/libvirt/virterror.h
+++ b/include/libvirt/virterror.h
@@ -131,6 +131,7 @@ typedef enum {
     VIR_FROM_XENXL = 64,        /* Error from Xen xl config code */
 
     VIR_FROM_PERF = 65,         /* Error from perf */
+    VIR_FROM_DOMAIN_BACKUP = 66,/* Error from domain backup */
 
 # ifdef VIR_ENUM_SENTINELS
     VIR_ERR_DOMAIN_LAST
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 25dbc84..4cdeb2f 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -18,6 +18,7 @@ src/bhyve/bhyve_driver.c
 src/bhyve/bhyve_monitor.c
 src/bhyve/bhyve_parse_command.c
 src/bhyve/bhyve_process.c
+src/conf/backup_conf.c
 src/conf/capabilities.c
 src/conf/cpu_conf.c
 src/conf/device_conf.c
@@ -281,6 +282,7 @@ src/xenconfig/xen_xl.c
 src/xenconfig/xen_xm.c
 tests/virpolkittest.c
 tools/libvirt-guests.sh.in
+tools/virsh-backup.c
 tools/virsh-console.c
 tools/virsh-domain-monitor.c
 tools/virsh-domain.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 8ee5567..c04e72c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -219,6 +219,7 @@ DRIVER_SOURCES =							\
 		libvirt.c libvirt_internal.h				\
 		libvirt-domain.c 					\
 		libvirt-domain-snapshot.c 				\
+		libvirt-domain-backup.c 				\
 		libvirt-host.c 						\
 		libvirt-interface.c	 				\
 		libvirt-network.c	 				\
@@ -334,6 +335,7 @@ DOMAIN_CONF_SOURCES =						\
 		conf/domain_audit.c conf/domain_audit.h		\
 		conf/domain_nwfilter.c conf/domain_nwfilter.h	\
 		conf/snapshot_conf.c conf/snapshot_conf.h	\
+		conf/backup_conf.c conf/backup_conf.h	\
 		conf/numa_conf.c conf/numa_conf.h	\
 		conf/virdomainobjlist.c conf/virdomainobjlist.h
 
@@ -2390,6 +2392,7 @@ libvirt_setuid_rpc_client_la_SOURCES = 		\
 		libvirt.c			\
 		libvirt-domain.c		\
 		libvirt-domain-snapshot.c	\
+		libvirt-domain-backup.c	\
 		libvirt-host.c			\
 		libvirt-interface.c		\
 		libvirt-network.c		\
diff --git a/src/access/viraccessperm.c b/src/access/viraccessperm.c
index 0f58290..16216c0 100644
--- a/src/access/viraccessperm.c
+++ b/src/access/viraccessperm.c
@@ -43,7 +43,8 @@ VIR_ENUM_IMPL(virAccessPermDomain,
               "fs_trim", "fs_freeze",
               "block_read", "block_write", "mem_read",
               "open_graphics", "open_device", "screenshot",
-              "open_namespace", "set_time", "set_password");
+              "open_namespace", "set_time", "set_password",
+              "backup");
 
 VIR_ENUM_IMPL(virAccessPermInterface,
               VIR_ACCESS_PERM_INTERFACE_LAST,
diff --git a/src/access/viraccessperm.h b/src/access/viraccessperm.h
index 1817da7..06d5184 100644
--- a/src/access/viraccessperm.h
+++ b/src/access/viraccessperm.h
@@ -306,6 +306,12 @@ typedef enum {
      */
     VIR_ACCESS_PERM_DOMAIN_SET_PASSWORD,
 
+    /**
+     * @desc: Backup domain
+     * @message: Backing domain up requires authorization
+     */
+    VIR_ACCESS_PERM_DOMAIN_BACKUP,  /* Backup domain */
+
     VIR_ACCESS_PERM_DOMAIN_LAST,
 } virAccessPermDomain;
 
diff --git a/src/conf/backup_conf.c b/src/conf/backup_conf.c
new file mode 100644
index 0000000..ed00922
--- /dev/null
+++ b/src/conf/backup_conf.c
@@ -0,0 +1,295 @@
+/*
+ * backup_conf.c: domain backup XML processing
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Nikolay Shirokovskiy <nshirokovskiy at virtuozzo.com>
+ */
+
+#include <config.h>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "internal.h"
+#include "virbitmap.h"
+#include "virbuffer.h"
+#include "count-one-bits.h"
+#include "datatypes.h"
+#include "domain_conf.h"
+#include "virlog.h"
+#include "viralloc.h"
+#include "netdev_bandwidth_conf.h"
+#include "netdev_vport_profile_conf.h"
+#include "nwfilter_conf.h"
+#include "secret_conf.h"
+#include "backup_conf.h"
+#include "virstoragefile.h"
+#include "viruuid.h"
+#include "virfile.h"
+#include "virerror.h"
+#include "virxml.h"
+#include "virstring.h"
+
+#define VIR_FROM_THIS VIR_FROM_DOMAIN_BACKUP
+
+VIR_LOG_INIT("conf.backup_conf");
+
+VIR_ENUM_IMPL(virDomainBackupAddress, VIR_DOMAIN_BACKUP_ADDRESS_LAST,
+              "ip",
+              "unix")
+
+static int
+virDomainBackupDiskDefParseXML(xmlNodePtr node,
+                               xmlXPathContextPtr ctxt,
+                               virDomainBackupDiskDefPtr def)
+{
+    int ret = -1;
+    char *present = NULL;
+    char *type = NULL;
+    xmlNodePtr cur;
+    xmlNodePtr saved = ctxt->node;
+
+    ctxt->node = node;
+
+    if ((type = virXMLPropString(node, "type")) &&
+        virStorageTypeFromString(type) != VIR_STORAGE_TYPE_FILE) {
+        virReportError(VIR_ERR_XML_ERROR, "%s",
+                       _("'file' is the only supported backup source type"));
+        goto cleanup;
+    }
+
+    if ((cur = virXPathNode("./source", ctxt))) {
+        if (VIR_ALLOC(def->src) < 0)
+            goto cleanup;
+
+        def->src->type = VIR_STORAGE_TYPE_FILE;
+        def->src->format = VIR_STORAGE_FILE_QCOW2;
+
+        if (virDomainDiskSourceParse(cur, ctxt, def->src) < 0)
+            goto cleanup;
+    }
+
+    def->name = virXMLPropString(node, "name");
+    if (!def->name) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("missing name from disk backup element"));
+        goto cleanup;
+    }
+
+    present = virXMLPropString(node, "present");
+    if (present && (def->present = virTristateBoolTypeFromString(present)) <= 0) {
+        virReportError(VIR_ERR_XML_ERROR,
+                       _("Invalid disk '%s' present attribute value"),
+                       def->name);
+        goto cleanup;
+    }
+
+    ret = 0;
+
+ cleanup:
+    VIR_FREE(present);
+    VIR_FREE(type);
+
+    ctxt->node = saved;
+
+    return ret;
+}
+
+static int
+virDomainBackupAddressDefParseXML(xmlNodePtr node,
+                                  virDomainBackupAddressDefPtr def)
+{
+    char *type = virXMLPropString(node, "type");
+    char *port = NULL;
+    int ret = -1;
+
+    if (!type) {
+        virReportError(VIR_ERR_XML_ERROR, "%s",
+                       _("backup address type must be specified"));
+        goto cleanup;
+    }
+
+    if ((def->type = virDomainBackupAddressTypeFromString(type)) < 0) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("unknown backup address type '%s'"), type);
+        goto cleanup;
+    }
+
+    switch (def->type) {
+    case VIR_DOMAIN_BACKUP_ADDRESS_IP:
+        def->data.ip.host = virXMLPropString(node, "host");
+        port = virXMLPropString(node, "port");
+        if (!def->data.ip.host || !port) {
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                           _("both host and port must be specified "
+                             "for ip address type"));
+            goto cleanup;
+        }
+        if (virStrToLong_i(port, NULL, 10, &def->data.ip.port) < 0) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("cannot parse port %s"), port);
+            goto cleanup;
+        }
+        break;
+    case VIR_DOMAIN_BACKUP_ADDRESS_UNIX:
+        def->data.socket.path = virXMLPropString(node, "path");
+        if (!def->data.socket.path) {
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                           _("path must be specified for unix address type"));
+            goto cleanup;
+        }
+        break;
+    }
+
+    ret = 0;
+
+ cleanup:
+    VIR_FREE(type);
+    VIR_FREE(port);
+
+    return ret;
+}
+
+static virDomainBackupDefPtr
+virDomainBackupDefParse(xmlXPathContextPtr ctxt)
+{
+    virDomainBackupDefPtr def = NULL;
+    virDomainBackupDefPtr ret = NULL;
+    xmlNodePtr *nodes = NULL, node;
+    size_t i;
+    int n;
+
+    if (VIR_ALLOC(def) < 0)
+        goto cleanup;
+
+    if (!(node = virXPathNode("./address[1]", ctxt))) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("export address must be specifed"));
+        goto cleanup;
+    }
+
+    if (virDomainBackupAddressDefParseXML(node, &def->address) < 0)
+        goto cleanup;
+
+    if ((n = virXPathNodeSet("./disks/*", ctxt, &nodes)) < 0)
+        goto cleanup;
+
+    if (n && VIR_ALLOC_N(def->disks, n) < 0)
+        goto cleanup;
+    def->ndisks = n;
+    for (i = 0; i < def->ndisks; i++) {
+        if (virDomainBackupDiskDefParseXML(nodes[i], ctxt, &def->disks[i]) < 0)
+            goto cleanup;
+    }
+
+    ret = def;
+    def = NULL;
+
+ cleanup:
+    VIR_FREE(nodes);
+    virDomainBackupDefFree(def);
+
+    return ret;
+}
+
+virDomainBackupDefPtr
+virDomainBackupDefParseNode(xmlDocPtr xml,
+                            xmlNodePtr root,
+                            virCapsPtr caps ATTRIBUTE_UNUSED,
+                            virDomainXMLOptionPtr xmlopt ATTRIBUTE_UNUSED,
+                            unsigned int flags)
+{
+    xmlXPathContextPtr ctxt = NULL;
+    virDomainBackupDefPtr def = NULL;
+
+    virCheckFlags(0, NULL);
+
+    if (!xmlStrEqual(root->name, BAD_CAST "domainbackup")) {
+        virReportError(VIR_ERR_XML_ERROR, "%s", _("domainbackup"));
+        goto cleanup;
+    }
+
+    ctxt = xmlXPathNewContext(xml);
+    if (ctxt == NULL) {
+        virReportOOMError();
+        goto cleanup;
+    }
+
+    ctxt->node = root;
+    def = virDomainBackupDefParse(ctxt);
+
+ cleanup:
+    xmlXPathFreeContext(ctxt);
+
+    return def;
+}
+
+
+virDomainBackupDefPtr
+virDomainBackupDefParseString(const char *xmlStr,
+                              virCapsPtr caps,
+                              virDomainXMLOptionPtr xmlopt,
+                              unsigned int flags)
+{
+    virDomainBackupDefPtr ret = NULL;
+    xmlDocPtr xml;
+    int keepBlanksDefault = xmlKeepBlanksDefault(0);
+
+    if ((xml = virXMLParse(NULL, xmlStr, _("(domain_backup)")))) {
+        xmlKeepBlanksDefault(keepBlanksDefault);
+        ret = virDomainBackupDefParseNode(xml, xmlDocGetRootElement(xml),
+                                          caps, xmlopt, flags);
+        xmlFreeDoc(xml);
+    }
+    xmlKeepBlanksDefault(keepBlanksDefault);
+
+    return ret;
+}
+
+static
+void virDomainBackupAddressDefFree(virDomainBackupAddressDefPtr def)
+{
+    switch (def->type) {
+    case VIR_DOMAIN_BACKUP_ADDRESS_IP:
+        VIR_FREE(def->data.ip.host);
+        break;
+    case VIR_DOMAIN_BACKUP_ADDRESS_UNIX:
+        VIR_FREE(def->data.socket.path);
+        break;
+    }
+}
+
+void virDomainBackupDefFree(virDomainBackupDefPtr def)
+{
+    size_t i;
+
+    if (!def)
+        return;
+
+    for (i = 0; i < def->ndisks; i++) {
+        virDomainBackupDiskDefPtr disk = &def->disks[i];
+
+        virStorageSourceFree(disk->src);
+        VIR_FREE(disk->name);
+    }
+    VIR_FREE(def->disks);
+
+    virDomainBackupAddressDefFree(&def->address);
+
+    VIR_FREE(def);
+}
diff --git a/src/conf/backup_conf.h b/src/conf/backup_conf.h
new file mode 100644
index 0000000..1b09647
--- /dev/null
+++ b/src/conf/backup_conf.h
@@ -0,0 +1,85 @@
+/*
+ * backup_conf.h: domain backup XML processing
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Nikolay Shirokovskiy <nshirokovskiy at virtuozzo.com>
+ */
+
+#ifndef __BACKUP_CONF_H
+# define __BACKUP_CONF_H
+
+# include "internal.h"
+# include "domain_conf.h"
+
+typedef struct _virDomainBackupDiskDef virDomainBackupDiskDef;
+typedef virDomainBackupDiskDef *virDomainBackupDiskDefPtr;
+struct _virDomainBackupDiskDef {
+    char *name;     /* name matching the <target dev='...' of the domain */
+    int present;    /* enum virTristateBool */
+    int idx;        /* index within dom->disks that matches name */
+    virStorageSourcePtr src;
+};
+
+typedef enum {
+    VIR_DOMAIN_BACKUP_ADDRESS_IP,
+    VIR_DOMAIN_BACKUP_ADDRESS_UNIX,
+
+    VIR_DOMAIN_BACKUP_ADDRESS_LAST,
+} virDomainBackupAddressType;
+
+VIR_ENUM_DECL(virDomainBackupAddress)
+
+typedef struct _virDomainBackupAddressDef virDomainBackupAddressDef;
+typedef virDomainBackupAddressDef *virDomainBackupAddressDefPtr;
+struct _virDomainBackupAddressDef {
+    union {
+        struct {
+            char *host;
+            int port;
+        } ip;
+        struct {
+            char *path;
+        } socket;
+    } data;
+    int type; /* virDomainBackupAddress */
+};
+
+/* Stores the complete backup metadata */
+typedef struct _virDomainBackupDef virDomainBackupDef;
+typedef virDomainBackupDef *virDomainBackupDefPtr;
+struct _virDomainBackupDef {
+    virDomainBackupAddressDef address;
+
+    size_t ndisks;
+    virDomainBackupDiskDef *disks;
+
+    virDomainDefPtr dom;
+};
+
+virDomainBackupDefPtr virDomainBackupDefParseString(const char *xmlStr,
+                                                    virCapsPtr caps,
+                                                    virDomainXMLOptionPtr xmlopt,
+                                                    unsigned int flags);
+
+virDomainBackupDefPtr virDomainBackupDefParseNode(xmlDocPtr xml,
+                                                  xmlNodePtr root,
+                                                  virCapsPtr caps,
+                                                  virDomainXMLOptionPtr xmlopt,
+                                                  unsigned int flags);
+
+void virDomainBackupDefFree(virDomainBackupDefPtr def);
+
+#endif /* __BACKUP_CONF_H */
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index c8c4f61..f0247e2 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -838,7 +838,7 @@ VIR_ENUM_IMPL(virDomainLoader,
  * <mirror> XML (remaining types are not two-phase). */
 VIR_ENUM_DECL(virDomainBlockJob)
 VIR_ENUM_IMPL(virDomainBlockJob, VIR_DOMAIN_BLOCK_JOB_TYPE_LAST,
-              "", "", "copy", "", "active-commit")
+              "", "", "copy", "", "active-commit", "")
 
 VIR_ENUM_IMPL(virDomainMemoryModel, VIR_DOMAIN_MEMORY_MODEL_LAST,
               "", "dimm")
diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h
index 5cd1fdf..63ada62 100644
--- a/src/driver-hypervisor.h
+++ b/src/driver-hypervisor.h
@@ -1251,6 +1251,15 @@ typedef int
                              int state,
                              unsigned int flags);
 
+typedef int
+(*virDrvDomainBackupStart)(virDomainPtr domain,
+                           const char *xmlDesc,
+                           unsigned int flags);
+
+typedef int
+(*virDrvDomainBackupStop)(virDomainPtr domain,
+                          unsigned int flags);
+
 typedef struct _virHypervisorDriver virHypervisorDriver;
 typedef virHypervisorDriver *virHypervisorDriverPtr;
 
@@ -1489,6 +1498,8 @@ struct _virHypervisorDriver {
     virDrvDomainMigrateStartPostCopy domainMigrateStartPostCopy;
     virDrvDomainGetGuestVcpus domainGetGuestVcpus;
     virDrvDomainSetGuestVcpus domainSetGuestVcpus;
+    virDrvDomainBackupStart domainBackupStart;
+    virDrvDomainBackupStop domainBackupStop;
 };
 
 
diff --git a/src/libvirt-domain-backup.c b/src/libvirt-domain-backup.c
new file mode 100644
index 0000000..e4b8a7b
--- /dev/null
+++ b/src/libvirt-domain-backup.c
@@ -0,0 +1,86 @@
+/*
+ * libvirt-domain-backup.c: entry points for virDomainBackupPtr APIs
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Nikolay Shirokovskiy <nshirokovskiy at virtuozzo.com>
+ */
+
+#include <config.h>
+
+#include "datatypes.h"
+#include "virlog.h"
+
+VIR_LOG_INIT("libvirt.domain-backup");
+
+#define VIR_FROM_THIS VIR_FROM_DOMAIN_BACKUP
+
+int
+virDomainBackupStart(virDomainPtr domain,
+                     const char *xmlDesc,
+                     unsigned int flags)
+{
+    virConnectPtr conn;
+
+    VIR_DOMAIN_DEBUG(domain, "xmlDesc=%s, flags=%x", xmlDesc, flags);
+
+    virResetLastError();
+
+    virCheckDomainReturn(domain, -1);
+    conn = domain->conn;
+
+    virCheckNonNullArgGoto(xmlDesc, error);
+    virCheckReadOnlyGoto(conn->flags, error);
+
+    if (conn->driver->domainBackupStart) {
+        if (conn->driver->domainBackupStart(domain, xmlDesc, flags) < 0)
+            goto error;
+
+        return 0;
+    }
+
+    virReportUnsupportedError();
+ error:
+    virDispatchError(conn);
+    return -1;
+}
+
+int
+virDomainBackupStop(virDomainPtr domain,
+                    unsigned int flags)
+{
+    virConnectPtr conn;
+
+    VIR_DOMAIN_DEBUG(domain, "flags=%x", flags);
+
+    virResetLastError();
+
+    virCheckDomainReturn(domain, -1);
+    conn = domain->conn;
+
+    virCheckReadOnlyGoto(conn->flags, error);
+
+    if (conn->driver->domainBackupStop) {
+        if (conn->driver->domainBackupStop(domain, flags) < 0)
+            goto error;
+
+        return 0;
+    }
+
+    virReportUnsupportedError();
+ error:
+    virDispatchError(conn);
+    return -1;
+}
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index d62c74c..e0ae661 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -42,6 +42,12 @@ virAccessPermStorageVolTypeFromString;
 virAccessPermStorageVolTypeToString;
 
 
+# conf/backup_conf.h
+virDomainBackupDefFree;
+virDomainBackupDefParseNode;
+virDomainBackupDefParseString;
+
+
 # conf/capabilities.h
 virCapabilitiesAddGuest;
 virCapabilitiesAddGuestDomain;
diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms
index e01604c..8ea164e 100644
--- a/src/libvirt_public.syms
+++ b/src/libvirt_public.syms
@@ -744,6 +744,8 @@ LIBVIRT_2.2.0 {
     global:
         virConnectNodeDeviceEventRegisterAny;
         virConnectNodeDeviceEventDeregisterAny;
+        virDomainBackupStart;
+        virDomainBackupStop;
 } LIBVIRT_2.0.0;
 
 # .... define new API here using predicted next version number ....
diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c
index 83a5a3f..66e4ea7 100644
--- a/src/qemu/qemu_blockjob.c
+++ b/src/qemu/qemu_blockjob.c
@@ -171,6 +171,8 @@ qemuBlockJobEventProcess(virQEMUDriverPtr driver,
         break;
 
     case VIR_DOMAIN_BLOCK_JOB_FAILED:
+        if (diskPriv->backuping)
+            diskPriv->backupFailed = true;
     case VIR_DOMAIN_BLOCK_JOB_CANCELED:
         virStorageSourceFree(disk->mirror);
         disk->mirror = NULL;
diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h
index 510cd9a..43f85bf 100644
--- a/src/qemu/qemu_conf.h
+++ b/src/qemu/qemu_conf.h
@@ -32,6 +32,7 @@
 # include "network_conf.h"
 # include "domain_conf.h"
 # include "snapshot_conf.h"
+# include "backup_conf.h"
 # include "domain_event.h"
 # include "virthread.h"
 # include "security/security_manager.h"
diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h
index ea57111..527ecb0 100644
--- a/src/qemu/qemu_domain.h
+++ b/src/qemu/qemu_domain.h
@@ -284,6 +284,10 @@ struct _qemuDomainDiskPrivate {
      * single disk */
     bool blockjob;
 
+    bool backuping;
+    bool backupdev;
+    bool backupFailed;
+
     /* for some synchronous block jobs, we need to notify the owner */
     int blockJobType;   /* type of the block job from the event */
     int blockJobStatus; /* status of the finished block job */
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 807e06d..9b0cb12 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -20118,6 +20118,688 @@ qemuDomainSetGuestVcpus(virDomainPtr dom,
     return ret;
 }
 
+/**
+ * FIXME nearly copy-paste of virDomainSnapshotDefAssignExternalNames
+ *
+ */
+static int
+virDomainBackupDefGeneratePaths(virDomainBackupDefPtr def)
+{
+    char *tmppath;
+    char *tmp;
+    size_t i;
+    size_t j;
+
+    for (i = 0; i < def->ndisks; i++) {
+        virDomainBackupDiskDefPtr disk = &def->disks[i];
+
+        if (disk->present != VIR_TRISTATE_BOOL_YES || disk->src)
+            continue;
+
+        if (VIR_ALLOC(disk->src) < 0)
+            return -1;
+
+        disk->src->type = VIR_STORAGE_TYPE_FILE;
+        disk->src->format = VIR_STORAGE_FILE_QCOW2;
+
+        if (VIR_STRDUP(tmppath, virDomainDiskGetSource(def->dom->disks[i])) < 0)
+            return -1;
+
+        /* drop suffix of the file name */
+        if ((tmp = strrchr(tmppath, '.')) && !strchr(tmp, '/'))
+            *tmp = '\0';
+
+        if (virAsprintf(&disk->src->path, "%s.backup", tmppath) < 0) {
+            VIR_FREE(tmppath);
+            return -1;
+        }
+
+        VIR_FREE(tmppath);
+
+        /* verify that we didn't generate a duplicate name */
+        for (j = 0; j < i; j++) {
+            if (STREQ_NULLABLE(disk->src->path, def->disks[j].src->path)) {
+                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                               _("cannot generate backup name for "
+                                 "disk '%s': collision with disk '%s'"),
+                               disk->name, def->disks[j].name);
+                return -1;
+            }
+        }
+    }
+
+    return 0;
+}
+
+static int
+virDomainBackupCompareDiskIndex(const void *a, const void *b)
+{
+    const virDomainBackupDiskDef *diska = a;
+    const virDomainBackupDiskDef *diskb = b;
+
+    /* Integer overflow shouldn't be a problem here.  */
+    return diska->idx - diskb->idx;
+}
+
+static int
+virDomainBackupAlignDisks(virDomainBackupDefPtr def)
+{
+    int ret = -1;
+    virBitmapPtr map = NULL;
+    size_t i;
+    int ndisks;
+
+    if (!def->dom) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("missing domain in backup"));
+        goto cleanup;
+    }
+
+    if (!(map = virBitmapNew(def->dom->ndisks)))
+        goto cleanup;
+
+    /* Double check requested disks.  */
+    for (i = 0; i < def->ndisks; i++) {
+        virDomainBackupDiskDefPtr disk = &def->disks[i];
+        int idx = virDomainDiskIndexByName(def->dom, disk->name, false);
+
+        if (idx < 0) {
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                           _("no disk named '%s'"), disk->name);
+            goto cleanup;
+        }
+
+        if (virBitmapIsBitSet(map, idx)) {
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                           _("disk '%s' specified twice"), disk->name);
+            goto cleanup;
+        }
+        ignore_value(virBitmapSetBit(map, idx));
+        disk->idx = idx;
+
+        if (STRNEQ(disk->name, def->dom->disks[idx]->dst)) {
+            VIR_FREE(disk->name);
+            if (VIR_STRDUP(disk->name, def->dom->disks[idx]->dst) < 0)
+                goto cleanup;
+        }
+
+        if (!disk->present)
+            disk->present = VIR_TRISTATE_BOOL_YES;
+
+        if (virStorageSourceIsEmpty(def->dom->disks[i]->src) &&
+            disk->present == VIR_TRISTATE_BOOL_YES) {
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                           _("disk '%s' has no source, can not be exported"),
+                           disk->name);
+        }
+    }
+
+    /* Provide defaults for all remaining disks.  */
+    ndisks = def->ndisks;
+    if (VIR_EXPAND_N(def->disks, def->ndisks,
+                     def->dom->ndisks - def->ndisks) < 0)
+        goto cleanup;
+
+    for (i = 0; i < def->dom->ndisks; i++) {
+        virDomainBackupDiskDefPtr disk;
+
+        if (virBitmapIsBitSet(map, i))
+            continue;
+
+        disk = &def->disks[ndisks++];
+        if (VIR_STRDUP(disk->name, def->dom->disks[i]->dst) < 0)
+            goto cleanup;
+        disk->idx = i;
+
+        if (virStorageSourceIsEmpty(def->dom->disks[i]->src) ||
+            def->dom->disks[i]->src->readonly)
+            disk->present = VIR_TRISTATE_BOOL_NO;
+        else
+            disk->present = VIR_TRISTATE_BOOL_YES;
+    }
+
+    qsort(&def->disks[0], def->ndisks, sizeof(def->disks[0]),
+          virDomainBackupCompareDiskIndex);
+
+    if (virDomainBackupDefGeneratePaths(def) < 0)
+        goto cleanup;
+
+    ret = 0;
+
+ cleanup:
+    virBitmapFree(map);
+    return ret;
+}
+
+
+/*
+ * FIXME has much in common with qemuMigrationCancelOneDriveMirror
+ */
+static int
+qemuBackupDriveCancelSingle(virQEMUDriverPtr driver,
+                            virDomainObjPtr vm,
+                            virDomainDiskDefPtr disk)
+{
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    char *diskAlias = NULL;
+    int ret = -1;
+    int status;
+    int rv;
+
+    status = qemuBlockJobUpdate(driver, vm, disk);
+    if (status == VIR_DOMAIN_BLOCK_JOB_FAILED) {
+        virReportError(VIR_ERR_OPERATION_FAILED,
+                       _("disk %s backup failed"), disk->dst);
+        goto cleanup;
+    }
+
+    if (!(diskAlias = qemuAliasFromDisk(disk)))
+        goto cleanup;
+
+    qemuDomainObjEnterMonitor(driver, vm);
+    rv = qemuMonitorBlockJobCancel(priv->mon, diskAlias, true);
+    /* -2 means race condition, job is already failed but
+     * event is not yet delivered, thus we continue as
+     * in case of success, next block job update will deliver the event */
+    if (qemuDomainObjExitMonitor(driver, vm) < 0 || rv == -1)
+        goto cleanup;
+
+    ret = 0;
+
+ cleanup:
+    if (ret < 0) {
+        qemuBlockJobSyncEnd(driver, vm, disk);
+        QEMU_DOMAIN_DISK_PRIVATE(disk)->backuping = false;
+        QEMU_DOMAIN_DISK_PRIVATE(disk)->backupFailed = false;
+    }
+
+    VIR_FREE(diskAlias);
+
+    return ret;
+}
+
+static bool
+qemuBackupDriveCancelled(virQEMUDriverPtr driver,
+                         virDomainObjPtr vm,
+                         virErrorPtr *err)
+{
+    size_t i;
+    size_t active = 0;
+    int status;
+
+    *err = NULL;
+
+    for (i = 0; i < vm->def->ndisks; i++) {
+        virDomainDiskDefPtr disk = vm->def->disks[i];
+        qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
+
+        if (!diskPriv->backuping)
+            continue;
+
+        status = qemuBlockJobUpdate(driver, vm, disk);
+        switch (status) {
+        case VIR_DOMAIN_BLOCK_JOB_FAILED:
+            virReportError(VIR_ERR_OPERATION_FAILED,
+                           _("migration of disk %s failed"), disk->dst);
+            if (!*err)
+                *err = virSaveLastError();
+            /* fallthrough */
+        case VIR_DOMAIN_BLOCK_JOB_CANCELED:
+            qemuBlockJobSyncEnd(driver, vm, disk);
+            diskPriv->backuping = false;
+            diskPriv->backupFailed = false;
+            break;
+
+        default:
+            ++active;
+        }
+    }
+
+    return active == 0;
+}
+
+/**
+ * FIXME nearly copy paste from qemuMigrationCancelDriveMirror
+ *
+ * Cancel backup jobs for all disks
+ *
+ * Returns 0 on success, -1 otherwise.
+ */
+static int
+qemuBackupDriveCancelAll(virQEMUDriverPtr driver,
+                         virDomainObjPtr vm)
+{
+    virErrorPtr err = NULL, wait_err;
+    size_t i;
+    int ret = -1;
+
+    VIR_DEBUG("Cancelling drive mirrors for domain %s", vm->def->name);
+
+    for (i = 0; i < vm->def->ndisks; i++) {
+        virDomainDiskDefPtr disk = vm->def->disks[i];
+        qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
+
+        if (!diskPriv->backuping)
+            continue;
+
+        if (qemuBackupDriveCancelSingle(driver, vm, disk) < 0) {
+            if (!virDomainObjIsActive(vm))
+                goto cleanup;
+
+            if (!err)
+                err = virSaveLastError();
+        }
+    }
+
+    while (!qemuBackupDriveCancelled(driver, vm, &wait_err)) {
+        if (!err && wait_err)
+            err = wait_err;
+        else
+            virFreeError(wait_err);
+
+        if (virDomainObjWait(vm) < 0)
+            goto cleanup;
+    }
+
+    if (!err)
+        ret = 0;
+
+ cleanup:
+    if (err) {
+        virSetError(err);
+        virFreeError(err);
+    }
+
+    return ret;
+}
+
+static int
+qemuDomainBackupFinishExport(virQEMUDriverPtr driver,
+                             virDomainObjPtr vm)
+{
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    virErrorPtr err = NULL;
+    char *dev = NULL;
+    int ret = -1;
+    size_t i;
+
+    qemuDomainObjEnterMonitor(driver, vm);
+    ignore_value(qemuMonitorNBDServerStop(priv->mon));
+    if (qemuDomainObjExitMonitor(driver, vm) < 0)
+        return -1;
+
+    if (qemuBackupDriveCancelAll(driver, vm) < 0) {
+        if (!virDomainObjIsActive(vm))
+            goto cleanup;
+
+        err = virSaveLastError();
+    }
+
+    for (i = 0; i < vm->def->ndisks; i++) {
+        virDomainDiskDefPtr disk = vm->def->disks[i];
+        qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
+
+        if (!diskPriv->backupdev)
+            continue;
+        diskPriv->backupdev = false;
+
+        if (virAsprintf(&dev, "backup-%s", disk->info.alias) < 0)
+            continue;
+
+        qemuDomainObjEnterMonitor(driver, vm);
+        ignore_value(qemuMonitorBlockdevDel(priv->mon, dev));
+        if (qemuDomainObjExitMonitor(driver, vm) < 0)
+            goto cleanup;
+
+        VIR_FREE(dev);
+    }
+
+    if (!err)
+        ret = 0;
+ cleanup:
+    if (err) {
+        virSetError(err);
+        virFreeError(err);
+    }
+    VIR_FREE(dev);
+
+    return ret;
+}
+
+static int
+qemuDomainBackupPrepareDisk(virQEMUDriverPtr driver,
+                            virDomainObjPtr vm,
+                            virStorageSourcePtr src,
+                            struct qemuDomainDiskInfo *info)
+{
+    char *path = NULL;
+    virCommandPtr cmd = NULL;
+    int ret = -1;
+    const char *qemuImgPath;
+
+    if (!(qemuImgPath = qemuFindQemuImgBinary(driver)))
+        goto cleanup;
+
+    if (qemuGetDriveSourceString(src, NULL, &path) < 0)
+        goto cleanup;
+
+    if (qemuDomainStorageFileInit(driver, vm, src) < 0)
+        goto cleanup;
+
+    if (virStorageFileCreate(src) < 0) {
+        virReportSystemError(errno, _("failed to create image file '%s'"),
+                             path);
+        goto cleanup;
+    }
+
+    if (!(cmd = virCommandNewArgList(qemuImgPath, "create",
+                                     "-f", "qcow2",
+                                     NULL)))
+        goto cleanup;
+
+    virCommandAddArg(cmd, src->path);
+    virCommandAddArgFormat(cmd, "%lli", info->guest_size);
+
+    if (virCommandRun(cmd, NULL) < 0)
+        goto cleanup;
+
+    ret = 0;
+
+ cleanup:
+    virStorageFileDeinit(src);
+    VIR_FREE(path);
+    virCommandFree(cmd);
+
+    return ret;
+}
+
+static int
+qemuDomainBackupExportDisks(virQEMUDriverPtr driver,
+                            virDomainObjPtr vm,
+                            virDomainBackupDefPtr def)
+{
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    char *dev = NULL;
+    char *dev_backup = NULL;
+    int ret = -1, rc;
+    virJSONValuePtr actions = NULL;
+    size_t i;
+    virHashTablePtr block_info = NULL;
+
+    qemuDomainObjEnterMonitor(driver, vm);
+    rc = qemuMonitorNBDServerStart(priv->mon, def->address.data.ip.host,
+                                   def->address.data.ip.port);
+    if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0)
+        return -1;
+
+    if (!(actions = virJSONValueNewArray()))
+        goto cleanup;
+
+    qemuDomainObjEnterMonitor(driver, vm);
+    block_info = qemuMonitorGetBlockInfo(priv->mon);
+    if (qemuDomainObjExitMonitor(driver, vm) < 0 || !block_info)
+        goto cleanup;
+
+    for (i = 0; i < vm->def->ndisks; i++) {
+        virDomainDiskDefPtr disk = vm->def->disks[i];
+        struct qemuDomainDiskInfo *disk_info;
+
+        if (def->disks[i].present != VIR_TRISTATE_BOOL_YES)
+            continue;
+
+        if (!(disk_info = virHashLookup(block_info, disk->info.alias))) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("missing block info for '%s'"), disk->info.alias);
+            goto cleanup;
+        }
+
+        if (qemuDomainBackupPrepareDisk(driver, vm, def->disks[i].src,
+                                        disk_info) < 0)
+            goto cleanup;
+
+        if (virAsprintf(&dev, "drive-%s", disk->info.alias) < 0)
+            goto cleanup;
+        if (virAsprintf(&dev_backup, "backup-%s", disk->info.alias) < 0)
+            goto cleanup;
+
+        qemuDomainObjEnterMonitor(driver, vm);
+        rc = qemuMonitorBlockdevAdd(priv->mon, dev_backup,
+                                    def->disks[i].src->path);
+        if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0)
+            goto cleanup;
+
+        QEMU_DOMAIN_DISK_PRIVATE(disk)->backupdev = true;
+
+        qemuDomainObjEnterMonitor(driver, vm);
+        rc = qemuMonitorBlockdevBackup(actions, dev, dev_backup, 0);
+        if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0)
+            goto cleanup;
+
+        VIR_FREE(dev);
+        VIR_FREE(dev_backup);
+    }
+
+    qemuDomainObjEnterMonitor(driver, vm);
+    rc = qemuMonitorTransaction(priv->mon, actions);
+    if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0)
+        goto cleanup;
+
+    for (i = 0; i < vm->def->ndisks; i++) {
+        virDomainDiskDefPtr disk = vm->def->disks[i];
+
+        if (def->disks[i].present != VIR_TRISTATE_BOOL_YES)
+            continue;
+
+        QEMU_DOMAIN_DISK_PRIVATE(disk)->blockjob = true;
+        QEMU_DOMAIN_DISK_PRIVATE(disk)->backuping = true;
+        qemuBlockJobSyncBegin(disk);
+    }
+
+    for (i = 0; i < vm->def->ndisks; i++) {
+        virDomainDiskDefPtr disk = vm->def->disks[i];
+
+        if (def->disks[i].present != VIR_TRISTATE_BOOL_YES)
+            continue;
+
+        if (virAsprintf(&dev, "drive-%s", disk->info.alias) < 0)
+            goto cleanup;
+
+        qemuDomainObjEnterMonitor(driver, vm);
+        rc = qemuMonitorNBDServerAdd(priv->mon, dev, false);
+        if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0)
+            goto cleanup;
+    }
+
+    ret = 0;
+
+ cleanup:
+    virJSONValueFree(actions);
+    VIR_FREE(dev);
+    VIR_FREE(dev_backup);
+    virHashFree(block_info);
+
+    return ret;
+}
+
+static int
+qemuDomainBackupStart(virDomainPtr domain,
+                      const char *xmlDesc,
+                      unsigned int flags)
+{
+    virQEMUDriverPtr driver = domain->conn->privateData;
+    virCapsPtr caps = NULL;
+    virDomainBackupDefPtr def = NULL;
+    virDomainObjPtr vm = NULL;
+    int ret = -1;
+    bool job = false;
+    int freezed = -1;
+    size_t i;
+
+    virCheckFlags(VIR_DOMAIN_BACKUP_START_QUIESCE,
+                  -1);
+
+    if (!(vm = qemuDomObjFromDomain(domain)))
+        goto cleanup;
+
+    if (virDomainBackupStartEnsureACL(domain->conn, vm->def) < 0)
+        goto cleanup;
+
+    if (!(caps = virQEMUDriverGetCapabilities(driver, false)))
+        goto cleanup;
+
+    if (!(def = virDomainBackupDefParseString(xmlDesc, caps, driver->xmlopt, 0)))
+        goto cleanup;
+
+    /* FIXME refactor start nbd monitor function to support unix sockets */
+    if (def->address.type != VIR_DOMAIN_BACKUP_ADDRESS_IP) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       "%s", _("backup thru unix sockets is not supported"));
+        goto cleanup;
+    }
+
+    if (!virDomainObjIsActive(vm)) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("backing up inactive domains is not supported"));
+        goto cleanup;
+    }
+
+    if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0)
+        goto cleanup;
+    job = true;
+
+    def->dom = vm->def;
+
+    if (virDomainBackupAlignDisks(def) < 0)
+        goto cleanup;
+
+    for (i = 0; i < vm->def->ndisks; i++) {
+        virDomainDiskDefPtr disk = vm->def->disks[i];
+        qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
+
+        if (def->disks[i].present != VIR_TRISTATE_BOOL_YES)
+            continue;
+
+        if (diskPriv->blockjob) {
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                           _("disk '%s' already has a blockjob"), disk->dst);
+            goto cleanup;
+        }
+    }
+
+    /* FIXME remove snapshot from below function name */
+    if ((flags & VIR_DOMAIN_BACKUP_START_QUIESCE) &&
+        (freezed = qemuDomainSnapshotFSFreeze(driver, vm, NULL, 0)) < 0)
+        goto cleanup;
+
+    if (qemuDomainBackupExportDisks(driver, vm, def) < 0)
+        goto cleanup;
+
+    ret = 0;
+
+ cleanup:
+    /* FIXME remove snapshot from name below */
+    if (freezed != -1 && qemuDomainSnapshotFSThaw(driver, vm, ret == 0) < 0)
+        ret = -1;
+
+    if (ret < 0 && virDomainObjIsActive(vm)) {
+        virErrorPtr err = virSaveLastError();
+
+        ignore_value(qemuDomainBackupFinishExport(driver, vm));
+        virSetError(err);
+        virFreeError(err);
+    } else if (ret == 0) {
+        for (i = 0; i < vm->def->ndisks; i++) {
+            virDomainDiskDefPtr disk = vm->def->disks[i];
+            qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
+
+            if (!diskPriv->backuping)
+                continue;
+
+            qemuBlockJobSyncEnd(driver, vm, disk);
+        }
+    }
+
+    if (job)
+        qemuDomainObjEndJob(driver, vm);
+
+    virDomainBackupDefFree(def);
+    virObjectUnref(caps);
+    virDomainObjEndAPI(&vm);
+
+    return ret;
+}
+
+static int
+qemuDomainBackupStop(virDomainPtr domain,
+                     unsigned int flags)
+{
+    virQEMUDriverPtr driver = domain->conn->privateData;
+    virDomainObjPtr vm = NULL;
+    virErrorPtr err = NULL;
+    int ret = -1;
+    bool job = false;
+    size_t i;
+
+    virCheckFlags(0, -1);
+
+    if (!(vm = qemuDomObjFromDomain(domain)))
+        goto cleanup;
+
+    if (virDomainBackupStopEnsureACL(domain->conn, vm->def) < 0)
+        goto cleanup;
+
+    if (!virDomainObjIsActive(vm)) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       "%s", _("inactive domain can't have active backup"));
+        goto cleanup;
+    }
+
+    if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0)
+        goto cleanup;
+    job = true;
+
+    for (i = 0; i < vm->def->ndisks; i++) {
+        virDomainDiskDefPtr disk = vm->def->disks[i];
+        qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
+
+        if (!diskPriv->backuping)
+            continue;
+
+        /* backup blockjob can fail/cancelled between start and stop */
+        if (!diskPriv->blockjob) {
+            diskPriv->backuping = false;
+
+            if (diskPriv->backupFailed) {
+                virReportError(VIR_ERR_OPERATION_FAILED,
+                               _("disk %s backup failed"), disk->dst);
+                if (!err)
+                    err = virSaveLastError();
+                diskPriv->backupFailed = false;
+            }
+            continue;
+        }
+
+        qemuBlockJobSyncBegin(disk);
+    }
+
+    if (qemuDomainBackupFinishExport(driver, vm) < 0)
+        goto cleanup;
+
+    if (!err)
+        ret = 0;
+
+ cleanup:
+    if (job)
+        qemuDomainObjEndJob(driver, vm);
+
+    virDomainObjEndAPI(&vm);
+    if (err) {
+        virSetError(err);
+        virFreeError(err);
+    }
+
+    return ret;
+}
 
 static virHypervisorDriver qemuHypervisorDriver = {
     .name = QEMU_DRIVER_NAME,
@@ -20332,6 +21014,8 @@ static virHypervisorDriver qemuHypervisorDriver = {
     .domainMigrateStartPostCopy = qemuDomainMigrateStartPostCopy, /* 1.3.3 */
     .domainGetGuestVcpus = qemuDomainGetGuestVcpus, /* 2.0.0 */
     .domainSetGuestVcpus = qemuDomainSetGuestVcpus, /* 2.0.0 */
+    .domainBackupStart = qemuDomainBackupStart, /* 2.3.0 */
+    .domainBackupStop = qemuDomainBackupStop, /* 2.3.0 */
 };
 
 
diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c
index 944856d..dad61bd 100644
--- a/src/qemu/qemu_monitor.c
+++ b/src/qemu/qemu_monitor.c
@@ -4001,3 +4001,35 @@ qemuMonitorGetRTCTime(qemuMonitorPtr mon,
 
     return qemuMonitorJSONGetRTCTime(mon, tm);
 }
+
+int qemuMonitorBlockdevBackup(virJSONValuePtr actions,
+                              const char *device,
+                              const char *target,
+                              unsigned long long speed)
+{
+    VIR_DEBUG("actions=%p, device=%s, target=%s, speed=%llu",
+              actions, device, target, speed);
+
+    return qemuMonitorJSONBlockdevBackup(actions, device, target, speed);
+}
+
+int qemuMonitorBlockdevAdd(qemuMonitorPtr mon,
+                           const char *id,
+                           const char *path)
+{
+    VIR_DEBUG("mon=%p, id=%s, path=%s", mon, id, path);
+
+    QEMU_CHECK_MONITOR_JSON(mon);
+
+    return qemuMonitorJSONBlockdevAdd(mon, id, path);
+}
+
+int qemuMonitorBlockdevDel(qemuMonitorPtr mon,
+                           const char *id)
+{
+    VIR_DEBUG("mon=%p, id=%s", mon, id);
+
+    QEMU_CHECK_MONITOR_JSON(mon);
+
+    return qemuMonitorJSONBlockdevDel(mon, id);
+}
diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h
index b838725..3cfd32b 100644
--- a/src/qemu/qemu_monitor.h
+++ b/src/qemu/qemu_monitor.h
@@ -991,4 +991,16 @@ int qemuMonitorMigrateStartPostCopy(qemuMonitorPtr mon);
 int qemuMonitorGetRTCTime(qemuMonitorPtr mon,
                           struct tm *tm);
 
+int qemuMonitorBlockdevAdd(qemuMonitorPtr mon,
+                           const char *id,
+                           const char *path);
+
+int qemuMonitorBlockdevDel(qemuMonitorPtr mon,
+                           const char *id);
+
+int qemuMonitorBlockdevBackup(virJSONValuePtr actions,
+                              const char *device,
+                              const char *target,
+                              unsigned long long speed);
+
 #endif /* QEMU_MONITOR_H */
diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c
index 42b05c4..9ee025a 100644
--- a/src/qemu/qemu_monitor_json.c
+++ b/src/qemu/qemu_monitor_json.c
@@ -831,6 +831,8 @@ qemuMonitorJSONHandleBlockJobImpl(qemuMonitorPtr mon,
         type = VIR_DOMAIN_BLOCK_JOB_TYPE_COMMIT;
     else if (STREQ(type_str, "mirror"))
         type = VIR_DOMAIN_BLOCK_JOB_TYPE_COPY;
+    else if (STREQ(type_str, "backup"))
+        type = VIR_DOMAIN_BLOCK_JOB_TYPE_BACKUP;
 
     switch ((virConnectDomainEventBlockJobStatus) event) {
     case VIR_DOMAIN_BLOCK_JOB_COMPLETED:
@@ -7260,3 +7262,106 @@ qemuMonitorJSONGetHotpluggableCPUs(qemuMonitorPtr mon,
     virJSONValueFree(reply);
     return ret;
 }
+
+int qemuMonitorJSONBlockdevAdd(qemuMonitorPtr mon,
+                               const char *id,
+                               const char *path)
+{
+    int ret = -1;
+    virJSONValuePtr cmd = NULL;
+    virJSONValuePtr args = NULL;
+    virJSONValuePtr file = NULL;
+    virJSONValuePtr reply = NULL;
+    virJSONValuePtr options = NULL;
+
+    if (!(cmd = virJSONValueNewObject()) ||
+        !(args = virJSONValueNewObject()) ||
+        !(options = virJSONValueNewObject()) ||
+        !(file = virJSONValueNewObject()))
+        goto cleanup;
+
+    if (virJSONValueObjectAppendString(file, "driver", "file") < 0 ||
+        virJSONValueObjectAppendString(file, "filename", path) < 0)
+        goto cleanup;
+
+    if (virJSONValueObjectAppendString(options, "driver", "qcow2") < 0 ||
+        virJSONValueObjectAppendString(options, "id", id) < 0 ||
+        virJSONValueObjectAppend(options, "file", file) < 0)
+        goto cleanup;
+
+    if (virJSONValueObjectAppend(args, "options", options) < 0)
+        goto cleanup;
+
+    if (virJSONValueObjectAppendString(cmd, "execute", "blockdev-add") < 0 ||
+        virJSONValueObjectAppend(cmd, "arguments", args) < 0)
+        goto cleanup;
+
+    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
+        goto cleanup;
+
+    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
+        goto cleanup;
+
+    ret = 0;
+ cleanup:
+    virJSONValueFree(cmd);
+    virJSONValueFree(args);
+    virJSONValueFree(file);
+    virJSONValueFree(reply);
+    virJSONValueFree(options);
+    return ret;
+}
+
+int qemuMonitorJSONBlockdevDel(qemuMonitorPtr mon,
+                               const char *id)
+{
+    int ret = -1;
+    virJSONValuePtr cmd;
+    virJSONValuePtr reply = NULL;
+
+    cmd = qemuMonitorJSONMakeCommand("x-blockdev-del",
+                                     "s:id", id,
+                                     NULL);
+    if (!cmd)
+        return -1;
+
+    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
+        goto cleanup;
+
+    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
+        goto cleanup;
+
+    ret = 0;
+ cleanup:
+    virJSONValueFree(cmd);
+    virJSONValueFree(reply);
+    return ret;
+}
+
+int qemuMonitorJSONBlockdevBackup(virJSONValuePtr actions,
+                                  const char *device,
+                                  const char *target,
+                                  unsigned long long speed)
+{
+    int ret = -1;
+    virJSONValuePtr cmd;
+
+    cmd = qemuMonitorJSONMakeCommandRaw(true,
+                                        "blockdev-backup",
+                                        "s:device", device,
+                                        "s:target", target,
+                                        "s:sync", "none",
+                                        "Y:speed", speed,
+                                        NULL);
+    if (!cmd)
+        return -1;
+
+    if (virJSONValueArrayAppend(actions, cmd) < 0)
+        goto cleanup;
+
+    ret = 0;
+    cmd = NULL;
+ cleanup:
+    virJSONValueFree(cmd);
+    return ret;
+}
diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h
index 0eab96f..8ae6c1d 100644
--- a/src/qemu/qemu_monitor_json.h
+++ b/src/qemu/qemu_monitor_json.h
@@ -499,4 +499,20 @@ int qemuMonitorJSONGetHotpluggableCPUs(qemuMonitorPtr mon,
                                        struct qemuMonitorQueryHotpluggableCpusEntry **entries,
                                        size_t *nentries)
     ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3);
+
+int qemuMonitorJSONBlockdevAdd(qemuMonitorPtr mon,
+                               const char *id,
+                               const char *path)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3);
+
+int qemuMonitorJSONBlockdevDel(qemuMonitorPtr mon,
+                               const char *id)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+
+int qemuMonitorJSONBlockdevBackup(virJSONValuePtr actions,
+                                  const char *device,
+                                  const char *target,
+                                  unsigned long long speed)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3);
+
 #endif /* QEMU_MONITOR_JSON_H */
diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c
index 3b8b796..52e53f5 100644
--- a/src/remote/remote_driver.c
+++ b/src/remote/remote_driver.c
@@ -8164,6 +8164,8 @@ static virHypervisorDriver hypervisor_driver = {
     .domainMigrateStartPostCopy = remoteDomainMigrateStartPostCopy, /* 1.3.3 */
     .domainGetGuestVcpus = remoteDomainGetGuestVcpus, /* 2.0.0 */
     .domainSetGuestVcpus = remoteDomainSetGuestVcpus, /* 2.0.0 */
+    .domainBackupStart = remoteDomainBackupStart, /* 2.3.0 */
+    .domainBackupStop = remoteDomainBackupStop, /* 2.3.0 */
 };
 
 static virNetworkDriver network_driver = {
diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x
index 8a8e263..c62ee0e 100644
--- a/src/remote/remote_protocol.x
+++ b/src/remote/remote_protocol.x
@@ -2679,6 +2679,25 @@ struct remote_domain_snapshot_delete_args {
     unsigned int flags;
 };
 
+struct remote_domain_backup_start_args {
+    remote_nonnull_domain dom;
+    remote_nonnull_string xml_desc;
+    unsigned int flags;
+};
+
+struct remote_domain_backup_start_ret {
+    int result;
+};
+
+struct remote_domain_backup_stop_args {
+    remote_nonnull_domain dom;
+    unsigned int flags;
+};
+
+struct remote_domain_backup_stop_ret {
+    int result;
+};
+
 struct remote_domain_open_console_args {
     remote_nonnull_domain dom;
     remote_string dev_name;
@@ -5934,5 +5953,17 @@ enum remote_procedure {
      * @generate: both
      * @acl: none
      */
-    REMOTE_PROC_NODE_DEVICE_EVENT_UPDATE = 377
+    REMOTE_PROC_NODE_DEVICE_EVENT_UPDATE = 377,
+
+     /**
+     * @generate: both
+     * @acl: domain:backup
+     */
+    REMOTE_PROC_DOMAIN_BACKUP_START = 378,
+
+     /**
+     * @generate: both
+     * @acl: domain:backup
+     */
+    REMOTE_PROC_DOMAIN_BACKUP_STOP = 379
 };
diff --git a/src/util/virerror.c b/src/util/virerror.c
index 1177570..62b5c62 100644
--- a/src/util/virerror.c
+++ b/src/util/virerror.c
@@ -138,6 +138,7 @@ VIR_ENUM_IMPL(virErrorDomain, VIR_ERR_DOMAIN_LAST,
               "Xen XL Config",
 
               "Perf",
+              "Domain Backup",
     )
 
 
diff --git a/tools/Makefile.am b/tools/Makefile.am
index e7e42c3..1527c5f 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -201,6 +201,7 @@ virsh_SOURCES =							\
 		virsh-secret.c virsh-secret.h			\
 		virsh-snapshot.c virsh-snapshot.h		\
 		virsh-volume.c virsh-volume.h			\
+		virsh-backup.c virsh-backup.h			\
 		$(NULL)
 
 virsh_LDFLAGS = \
diff --git a/tools/virsh-backup.c b/tools/virsh-backup.c
new file mode 100644
index 0000000..c5ed972
--- /dev/null
+++ b/tools/virsh-backup.c
@@ -0,0 +1,150 @@
+/*
+ * virsh-backup.c: Commands to manage domain backup
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Nikolay Shirokovskiy <nshirokovskiy at virtuozzo.com>
+ */
+
+#include <config.h>
+#include "virsh-backup.h"
+
+#include "internal.h"
+#include "virfile.h"
+#include "virsh-domain.h"
+#include "viralloc.h"
+
+#define VIRSH_COMMON_OPT_DOMAIN_FULL                       \
+    VIRSH_COMMON_OPT_DOMAIN(N_("domain name, id or uuid")) \
+
+/*
+ * "backup-start" command
+ */
+static const vshCmdInfo info_backup_start[] = {
+    {.name = "help",
+     .data = N_("Start pull backup")
+    },
+    {.name = "desc",
+     .data = N_("Start pull backup")
+    },
+    {.name = NULL}
+};
+
+static const vshCmdOptDef opts_backup_start[] = {
+    VIRSH_COMMON_OPT_DOMAIN_FULL,
+    {.name = "xmlfile",
+     .type = VSH_OT_DATA,
+     .flags = VSH_OFLAG_REQ,
+     .help = N_("domain backup XML")
+    },
+    {.name = "quiesce",
+     .type = VSH_OT_BOOL,
+     .help = N_("quiesce guest's file systems")
+    },
+    {.name = NULL}
+};
+
+static bool
+cmdBackupStart(vshControl *ctl, const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    const char *from = NULL;
+    char *buffer = NULL;
+    unsigned int flags = 0;
+
+    if (vshCommandOptBool(cmd, "quiesce"))
+        flags |= VIR_DOMAIN_BACKUP_START_QUIESCE;
+
+    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
+        goto cleanup;
+
+    if (vshCommandOptStringReq(ctl, cmd, "xmlfile", &from) < 0)
+        goto cleanup;
+
+    if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0) {
+        vshSaveLibvirtError();
+        goto cleanup;
+    }
+
+    if (virDomainBackupStart(dom, buffer, flags) < 0)
+        goto cleanup;
+
+    vshPrint(ctl, _("Domain backup started"));
+    ret = true;
+
+ cleanup:
+    VIR_FREE(buffer);
+    if (dom)
+        virDomainFree(dom);
+
+    return ret;
+}
+
+/*
+ * "backup-stop" command
+ */
+static const vshCmdInfo info_backup_stop[] = {
+    {.name = "help",
+     .data = N_("Stop pull backup")
+    },
+    {.name = "desc",
+     .data = N_("Stop pull backup")
+    },
+    {.name = NULL}
+};
+
+static const vshCmdOptDef opts_backup_stop[] = {
+    VIRSH_COMMON_OPT_DOMAIN_FULL,
+    {.name = NULL}
+};
+
+static bool
+cmdBackupStop(vshControl *ctl, const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+
+    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
+        goto cleanup;
+
+    if (virDomainBackupStop(dom, 0) < 0)
+        goto cleanup;
+
+    vshPrint(ctl, _("Domain backup stopped"));
+    ret = true;
+
+ cleanup:
+    if (dom)
+        virDomainFree(dom);
+
+    return ret;
+}
+
+const vshCmdDef backupCmds[] = {
+    {.name = "backup-start",
+     .handler = cmdBackupStart,
+     .opts = opts_backup_start,
+     .info = info_backup_start,
+     .flags = 0
+    },
+    {.name = "backup-stop",
+     .handler = cmdBackupStop,
+     .opts = opts_backup_stop,
+     .info = info_backup_stop,
+     .flags = 0
+    },
+    {.name = NULL}
+};
diff --git a/tools/virsh-backup.h b/tools/virsh-backup.h
new file mode 100644
index 0000000..b765ad7
--- /dev/null
+++ b/tools/virsh-backup.h
@@ -0,0 +1,28 @@
+/*
+ * virsh-backup.h: Commands to manage domain backup
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Nikolay Shirokovskiy <nshirokovskiy at virtuozzo.com>
+ */
+
+#ifndef VIRSH_BACKUP_H
+# define VIRSH_BACKUP_H
+
+# include "virsh.h"
+
+extern const vshCmdDef backupCmds[];
+
+#endif /* VIRSH_BACKUP_H */
diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c
index a614512..16a7b4c 100644
--- a/tools/virsh-domain.c
+++ b/tools/virsh-domain.c
@@ -2528,7 +2528,8 @@ VIR_ENUM_IMPL(virshDomainBlockJob,
               N_("Block Pull"),
               N_("Block Copy"),
               N_("Block Commit"),
-              N_("Active Block Commit"))
+              N_("Active Block Commit"),
+              N_("Block Backup"))
 
 static const char *
 virshDomainBlockJobToString(int type)
diff --git a/tools/virsh.c b/tools/virsh.c
index cb60edc..96e2862 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -71,6 +71,7 @@
 #include "virsh-secret.h"
 #include "virsh-snapshot.h"
 #include "virsh-volume.h"
+#include "virsh-backup.h"
 
 /* Gnulib doesn't guarantee SA_SIGINFO support.  */
 #ifndef SA_SIGINFO
@@ -919,6 +920,7 @@ static const vshCmdGrp cmdGroups[] = {
     {VIRSH_CMD_GRP_NODEDEV, "nodedev", nodedevCmds},
     {VIRSH_CMD_GRP_SECRET, "secret", secretCmds},
     {VIRSH_CMD_GRP_SNAPSHOT, "snapshot", snapshotCmds},
+    {VIRSH_CMD_GRP_BACKUP, "backup", backupCmds},
     {VIRSH_CMD_GRP_STORAGE_POOL, "pool", storagePoolCmds},
     {VIRSH_CMD_GRP_STORAGE_VOL, "volume", storageVolCmds},
     {VIRSH_CMD_GRP_VIRSH, "virsh", virshCmds},
diff --git a/tools/virsh.h b/tools/virsh.h
index fd552bb..839f36f 100644
--- a/tools/virsh.h
+++ b/tools/virsh.h
@@ -57,6 +57,7 @@
 # define VIRSH_CMD_GRP_NWFILTER         "Network Filter"
 # define VIRSH_CMD_GRP_SECRET           "Secret"
 # define VIRSH_CMD_GRP_SNAPSHOT         "Snapshot"
+# define VIRSH_CMD_GRP_BACKUP           "Backup"
 # define VIRSH_CMD_GRP_HOST_AND_HV      "Host and Hypervisor"
 # define VIRSH_CMD_GRP_VIRSH            "Virsh itself"
 
-- 
1.8.3.1




More information about the libvir-list mailing list