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@xxxxxxxxxxxxx> + * + * + * 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@xxxxxxxxxxxxx> + */ + +#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@xxxxxxxxxxxxx> + */ + +#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@xxxxxxxxxxxxx> + */ + +#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@xxxxxxxxxxxxx> + */ + +#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@xxxxxxxxxxxxx> + */ + +#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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list