[libvirt] [PATCH v2 RFC 00/12] introduce push backups

* Diff from v1 [3] - add man/html docs - add backup xml schema Push backup is a backup when hypervisor itself copy backup data to destination in contrast to pull backup when hypervisor exports backup data thru some interface and mgmt itself make a copy. This patch series basically adds creating backup to API/remote/qemu/virsh and initial backup XML definition. Just like other blockjobs backup creation is asynchronous. That is creation is merely a backup start and client should track backup error/completion thru blockjob events. As backup is done transactionally all individual disk backup jobs will be aborted by qemu itself in case of error, client need not to do it manually. Client can cancel the backup by aborting blockjob on any disk being backed up. Backup xml desription is similar to snapshot one with some exceptions and is described in more details in definition patch [p1] or in html docs. I guess good client will track progress for every disk in backup to report progress and detect hangs so it don't need extra backup complete/error event that aggregate the overall backup result. However it looks like aborting backup can be implemented in libvirt as code will be common for all clients. We need to abort some of not yet completed per disk backups and retry if job to be aborted is completed meanwhile. Of coures this series is far from being complete. Incremental backups and backup persistent metadata is to be implemented. Let's just start work in this direction. Links: In [1] we came to agreement to create distinct API to support backup operations and there is a discussion of pull backup series in [2]. The latter is blocked as some necessary operations are still experimental in qemu. [3] is the first version of series. [1] https://www.redhat.com/archives/libvir-list/2016-March/msg00937.html [2] https://www.redhat.com/archives/libvir-list/2016-September/msg00192.html [3] https://www.redhat.com/archives/libvir-list/2017-May/msg00130.html Nikolay Shirokovskiy (12): api: backup: add api to create backup api: backup: add driver based implementation remote: backup: add create backup implementation backup: qemu: monitor: add drive-backup command backup: misc: add backup block job type conf: backup: add backup xml definition [p1] qemu: backup: add qemuDomainBackupCreateXML implementation qemu: backup: check backup destination before start qemu: backup: prepare backup destination virsh: backup: add backup-create command schema: backup: add schema and its tests docs: add backup html docs daemon/remote.c | 8 + docs/Makefile.am | 3 + docs/apibuild.py | 2 + docs/docs.html.in | 4 +- docs/format.html.in | 1 + docs/formatbackup.html.in | 58 +++++ docs/index.html.in | 3 +- docs/schemas/domainbackup.rng | 79 +++++++ examples/object-events/event-test.c | 3 + include/libvirt/libvirt-domain-backup.h | 59 +++++ include/libvirt/libvirt-domain.h | 3 + include/libvirt/libvirt.h | 1 + include/libvirt/virterror.h | 2 + po/POTFILES.in | 2 + src/Makefile.am | 3 + src/access/viraccessperm.c | 3 +- src/access/viraccessperm.h | 6 + src/conf/backup_conf.c | 299 +++++++++++++++++++++++++ src/conf/backup_conf.h | 70 ++++++ src/conf/domain_conf.c | 2 +- src/datatypes.c | 60 +++++ src/datatypes.h | 29 +++ src/driver-hypervisor.h | 5 + src/libvirt-domain-backup.c | 209 +++++++++++++++++ src/libvirt_private.syms | 9 + src/libvirt_public.syms | 10 + src/qemu/qemu_conf.h | 1 + src/qemu/qemu_driver.c | 239 +++++++++++++++++++- src/qemu/qemu_monitor.c | 14 ++ src/qemu/qemu_monitor.h | 5 + src/qemu/qemu_monitor_json.c | 36 +++ src/qemu/qemu_monitor_json.h | 5 + src/remote/remote_driver.c | 7 + src/remote/remote_protocol.x | 23 +- src/rpc/gendispatch.pl | 29 ++- src/util/virerror.c | 6 + tests/domainbackupxml/block_target.xml | 5 + tests/domainbackupxml/explicit_description.xml | 6 + tests/domainbackupxml/explicit_file_type.xml | 5 + tests/domainbackupxml/explicit_format.xml | 5 + tests/domainbackupxml/explicit_name.xml | 6 + tests/domainbackupxml/multi_disk.xml | 8 + tests/domainbackupxml/ref_by_path.xml | 5 + tests/virschematest.c | 1 + tools/Makefile.am | 1 + tools/virsh-backup.c | 100 +++++++++ tools/virsh-backup.h | 29 +++ tools/virsh-domain.c | 3 +- tools/virsh-util.c | 11 + tools/virsh-util.h | 3 + tools/virsh.c | 2 + tools/virsh.h | 1 + tools/virsh.pod | 20 ++ tools/virt-xml-validate.in | 5 +- 54 files changed, 1499 insertions(+), 15 deletions(-) create mode 100644 docs/formatbackup.html.in create mode 100644 docs/schemas/domainbackup.rng 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 tests/domainbackupxml/block_target.xml create mode 100644 tests/domainbackupxml/explicit_description.xml create mode 100644 tests/domainbackupxml/explicit_file_type.xml create mode 100644 tests/domainbackupxml/explicit_format.xml create mode 100644 tests/domainbackupxml/explicit_name.xml create mode 100644 tests/domainbackupxml/multi_disk.xml create mode 100644 tests/domainbackupxml/ref_by_path.xml create mode 100644 tools/virsh-backup.c create mode 100644 tools/virsh-backup.h -- 1.8.3.1

--- include/libvirt/libvirt-domain-backup.h | 59 +++++++++++++++++++++++++++++++++ include/libvirt/libvirt.h | 1 + 2 files changed, 60 insertions(+) create mode 100644 include/libvirt/libvirt-domain-backup.h diff --git a/include/libvirt/libvirt-domain-backup.h b/include/libvirt/libvirt-domain-backup.h new file mode 100644 index 0000000..b3bd925 --- /dev/null +++ b/include/libvirt/libvirt-domain-backup.h @@ -0,0 +1,59 @@ +/* + * libvirt-domain-backup.h + * Summary: APIs for management of domain backups + * Description: Provides APIs for the management of domain backups + * Author: Nikolay Shirokovskiy <nshirokovskiy@virtuozzo.com> + * + * Copyright (C) 2017 Parallels International GmbH + * + * 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 + +/** + * virDomainBackup: + * + * a virDomainBackup is a private structure representing a backup of + * a domain. + */ +typedef struct _virDomainBackup virDomainBackup; + +/** + * virDomainBackupPtr: + * + * a virDomainBackupPtr is pointer to a virDomainBackup private structure, + * and is the type used to reference a domain backup in the API. + */ +typedef virDomainBackup *virDomainBackupPtr; + +const char *virDomainBackupGetName(virDomainBackupPtr backup); +virDomainPtr virDomainBackupGetDomain(virDomainBackupPtr backup); +virConnectPtr virDomainBackupGetConnect(virDomainBackupPtr backup); + +/* Take a backup of the current VM state */ +virDomainBackupPtr virDomainBackupCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags); + +int virDomainBackupRef(virDomainBackupPtr backup); +int virDomainBackupFree(virDomainBackupPtr backup); + +#endif /* __VIR_LIBVIRT_DOMAIN_BACKUP_H__ */ 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> -- 1.8.3.1

--- include/libvirt/virterror.h | 2 + src/Makefile.am | 2 + src/datatypes.c | 60 +++++++++++++ src/datatypes.h | 29 ++++++ src/driver-hypervisor.h | 5 ++ src/libvirt-domain-backup.c | 209 ++++++++++++++++++++++++++++++++++++++++++++ src/libvirt_private.syms | 2 + src/libvirt_public.syms | 10 +++ src/util/virerror.c | 6 ++ 9 files changed, 325 insertions(+) create mode 100644 src/libvirt-domain-backup.c diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h index 2efee8f..c9ae10e 100644 --- a/include/libvirt/virterror.h +++ b/include/libvirt/virterror.h @@ -132,6 +132,7 @@ typedef enum { VIR_FROM_PERF = 65, /* Error from perf */ VIR_FROM_LIBSSH = 66, /* Error from libssh connection transport */ + VIR_FROM_DOMAIN_BACKUP = 67,/* Error from domain backup */ # ifdef VIR_ENUM_SENTINELS VIR_ERR_DOMAIN_LAST @@ -319,6 +320,7 @@ typedef enum { VIR_ERR_AGENT_UNSYNCED = 97, /* guest agent replies with wrong id to guest-sync command */ VIR_ERR_LIBSSH = 98, /* error in libssh transport driver */ + VIR_ERR_INVALID_DOMAIN_BACKUP = 99, /* invalid domain backup */ } virErrorNumber; /** diff --git a/src/Makefile.am b/src/Makefile.am index f95f30e..c4ffc2f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -287,6 +287,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 \ @@ -2682,6 +2683,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/datatypes.c b/src/datatypes.c index 59ba956..365687c 100644 --- a/src/datatypes.c +++ b/src/datatypes.c @@ -37,6 +37,7 @@ virClassPtr virConnectClass; virClassPtr virConnectCloseCallbackDataClass; virClassPtr virDomainClass; virClassPtr virDomainSnapshotClass; +virClassPtr virDomainBackupClass; virClassPtr virInterfaceClass; virClassPtr virNetworkClass; virClassPtr virNodeDeviceClass; @@ -50,6 +51,7 @@ static void virConnectDispose(void *obj); static void virConnectCloseCallbackDataDispose(void *obj); static void virDomainDispose(void *obj); static void virDomainSnapshotDispose(void *obj); +static void virDomainBackupDispose(void *obj); static void virInterfaceDispose(void *obj); static void virNetworkDispose(void *obj); static void virNodeDeviceDispose(void *obj); @@ -88,6 +90,7 @@ virDataTypesOnceInit(void) DECLARE_CLASS_LOCKABLE(virConnectCloseCallbackData); DECLARE_CLASS(virDomain); DECLARE_CLASS(virDomainSnapshot); + DECLARE_CLASS(virDomainBackup); DECLARE_CLASS(virInterface); DECLARE_CLASS(virNetwork); DECLARE_CLASS(virNodeDevice); @@ -891,6 +894,63 @@ virDomainSnapshotDispose(void *obj) } +/** + * virGetDomainBackup: + * @domain: the domain to backup + * @name: pointer to the domain backup name + * + * Allocates a new domain backup object. When the object is no longer needed, + * virObjectUnref() must be called in order to not leak data. + * + * Returns a pointer to the domain backup object, or NULL on error. + */ +virDomainBackupPtr +virGetDomainBackup(virDomainPtr domain, const char *name) +{ + virDomainBackupPtr ret = NULL; + + if (virDataTypesInitialize() < 0) + return NULL; + + virCheckDomainGoto(domain, error); + virCheckNonNullArgGoto(name, error); + + if (!(ret = virObjectNew(virDomainBackupClass))) + goto error; + if (VIR_STRDUP(ret->name, name) < 0) + goto error; + + ret->domain = virObjectRef(domain); + + return ret; + + error: + virObjectUnref(ret); + return NULL; +} + + +/** + * virDomainBackupDispose: + * @obj: the domain backup to release + * + * Unconditionally release all memory associated with a backup. + * The backup object must not be used once this method returns. + * + * It will also unreference the associated connection object, + * which may also be released if its ref count hits zero. + */ +static void +virDomainBackupDispose(void *obj) +{ + virDomainBackupPtr backup = obj; + VIR_DEBUG("release backup %p %s", backup, backup->name); + + VIR_FREE(backup->name); + virObjectUnref(backup->domain); +} + + virAdmConnectPtr virAdmConnectNew(void) { diff --git a/src/datatypes.h b/src/datatypes.h index 288e057..54006ef 100644 --- a/src/datatypes.h +++ b/src/datatypes.h @@ -32,6 +32,7 @@ extern virClassPtr virConnectClass; extern virClassPtr virDomainClass; extern virClassPtr virDomainSnapshotClass; +extern virClassPtr virDomainBackupClass; extern virClassPtr virInterfaceClass; extern virClassPtr virNetworkClass; extern virClassPtr virNodeDeviceClass; @@ -292,6 +293,21 @@ extern virClassPtr virAdmClientClass; } \ } while (0) +# define virCheckDomainBackupReturn(obj, retval) \ + do { \ + virDomainBackupPtr _back = (obj); \ + if (!virObjectIsClass(_back, virDomainBackupClass) || \ + !virObjectIsClass(_back->domain, virDomainClass) || \ + !virObjectIsClass(_back->domain->conn, virConnectClass)) { \ + virReportErrorHelper(VIR_FROM_DOMAIN_BACKUP, \ + VIR_ERR_INVALID_DOMAIN_BACKUP, \ + __FILE__, __FUNCTION__, __LINE__, \ + __FUNCTION__); \ + virDispatchError(NULL); \ + return retval; \ + } \ + } while (0) + /* Helper macros to implement VIR_DOMAIN_DEBUG using just C99. This * assumes you pass fewer than 15 arguments to VIR_DOMAIN_DEBUG, but @@ -675,6 +691,17 @@ struct _virNWFilter { unsigned char uuid[VIR_UUID_BUFLEN]; /* the network filter unique identifier */ }; +/** + * _virDomainBackup + * + * Internal structure associated with a domain backup + */ +struct _virDomainBackup { + virObject object; + char *name; + virDomainPtr domain; +}; + /* * Helper APIs for allocating new object instances @@ -714,6 +741,8 @@ virNWFilterPtr virGetNWFilter(virConnectPtr conn, const unsigned char *uuid); virDomainSnapshotPtr virGetDomainSnapshot(virDomainPtr domain, const char *name); +virDomainBackupPtr virGetDomainBackup(virDomainPtr domain, + const char *name); virAdmConnectPtr virAdmConnectNew(void); diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h index 3053d7a..9c1756f 100644 --- a/src/driver-hypervisor.h +++ b/src/driver-hypervisor.h @@ -1263,6 +1263,10 @@ typedef int unsigned long long threshold, unsigned int flags); +typedef virDomainBackupPtr +(*virDrvDomainBackupCreateXML)(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags); typedef struct _virHypervisorDriver virHypervisorDriver; typedef virHypervisorDriver *virHypervisorDriverPtr; @@ -1504,6 +1508,7 @@ struct _virHypervisorDriver { virDrvDomainSetGuestVcpus domainSetGuestVcpus; virDrvDomainSetVcpu domainSetVcpu; virDrvDomainSetBlockThreshold domainSetBlockThreshold; + virDrvDomainBackupCreateXML domainBackupCreateXML; }; diff --git a/src/libvirt-domain-backup.c b/src/libvirt-domain-backup.c new file mode 100644 index 0000000..f019d2c --- /dev/null +++ b/src/libvirt-domain-backup.c @@ -0,0 +1,209 @@ +/* + * libvirt-domain-backup.c: entry points for virDomainBackupPtr APIs + + * Copyright (C) 2017 Parallels International GmbH + * + * 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/>. + */ + +#include <config.h> + +#include "datatypes.h" +#include "virlog.h" + +VIR_LOG_INIT("libvirt.domain-backup"); + +#define VIR_FROM_THIS VIR_FROM_DOMAIN_BACKUP + +/** + * virDomainBackupGetName: + * @backup: a backup object + * + * Get the public name for that backup + * + * Returns a pointer to the name or NULL, the string need not be deallocated + * as its lifetime will be the same as the backup object. + */ +const char * +virDomainBackupGetName(virDomainBackupPtr backup) +{ + VIR_DEBUG("backup=%p", backup); + + virResetLastError(); + + virCheckDomainBackupReturn(backup, NULL); + + return backup->name; +} + + +/** + * virDomainBackupGetDomain: + * @backup: a backup object + * + * Provides the domain pointer associated with a backup. The + * reference counter on the domain is not increased by this + * call. + * + * WARNING: When writing libvirt bindings in other languages, do not use this + * function. Instead, store the domain and the backup object together. + * + * Returns the domain or NULL. + */ +virDomainPtr +virDomainBackupGetDomain(virDomainBackupPtr backup) +{ + VIR_DEBUG("backup=%p", backup); + + virResetLastError(); + + virCheckDomainBackupReturn(backup, NULL); + + return backup->domain; +} + + +/** + * virDomainBackupGetConnect: + * @backup: a backup object + * + * Provides the connection pointer associated with a backup. The + * reference counter on the connection is not increased by this + * call. + * + * WARNING: When writing libvirt bindings in other languages, do not use this + * function. Instead, store the connection and the backup object together. + * + * Returns the connection or NULL. + */ +virConnectPtr +virDomainBackupGetConnect(virDomainBackupPtr backup) +{ + VIR_DEBUG("backup=%p", backup); + + virResetLastError(); + + virCheckDomainBackupReturn(backup, NULL); + + return backup->domain->conn; +} + + +/** + * virDomainBackupCreateXML: + * @domain: a domain object + * @xmlDesc: domain backup XML description + * @flags: reserved, must be 0 + * + * Starts creating of the domain disks backup based on the xml description in + * @xmlDesc. Backup is a copy of the specified domain disks at the moment of + * operation start. + * + * Backup creates a blockjob for every specified disk hence the backup + * status can be tracked thru blockjob event API and the backup progress + * is given by per blockjob virDomainBlockJobInfo. Backup can be cancelled by + * cancelling any of its still active blockjobs via virDomainBlockJobAbort. + * + * Known issues. In case libvirt connection is lost and restored back and all + * backup blockjobs are already gone then currenly it is not possible to know + * whether backup is completed or failed. + * + * Returns an (opaque) virDomainBackupPtr on success, NULL on failure. + */ +virDomainBackupPtr +virDomainBackupCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(domain, "xmlDesc=%s, flags=%x", xmlDesc, flags); + + virResetLastError(); + + virCheckDomainReturn(domain, NULL); + conn = domain->conn; + + virCheckNonNullArgGoto(xmlDesc, error); + virCheckReadOnlyGoto(conn->flags, error); + + if (conn->driver->domainBackupCreateXML) { + virDomainBackupPtr ret; + ret = conn->driver->domainBackupCreateXML(domain, xmlDesc, flags); + if (!ret) + goto error; + return ret; + } + + virReportUnsupportedError(); + error: + virDispatchError(conn); + return NULL; +} + + +/** + * virDomainBackupRef: + * @backup: the backup to hold a reference on + * + * Increment the reference count on the backup. For each + * additional call to this method, there shall be a corresponding + * call to virDomainBackupFree to release the reference count, once + * the caller no longer needs the reference to this object. + * + * This method is typically useful for applications where multiple + * threads are using a connection, and it is required that the + * connection and domain remain open until all threads have finished + * using the backup. ie, each new thread using a backup would + * increment the reference count. + * + * Returns 0 in case of success and -1 in case of failure. + */ +int +virDomainBackupRef(virDomainBackupPtr backup) +{ + VIR_DEBUG("backup=%p, refs=%d", backup, + backup ? backup->object.u.s.refs : 0); + + virResetLastError(); + + virCheckDomainBackupReturn(backup, -1); + + virObjectRef(backup); + return 0; +} + + +/** + * virDomainBackupFree: + * @backup: a domain backup object + * + * Free the domain backup object. The backup itself is not modified. + * The data structure is freed and should not be used thereafter. + * + * Returns 0 in case of success and -1 in case of failure. + */ +int +virDomainBackupFree(virDomainBackupPtr backup) +{ + VIR_DEBUG("backup=%p", backup); + + virResetLastError(); + + virCheckDomainBackupReturn(backup, -1); + + virObjectUnref(backup); + return 0; +} diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index afb9100..2b40d34 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1068,10 +1068,12 @@ virConnectCloseCallbackDataClass; virConnectCloseCallbackDataGetCallback; virConnectCloseCallbackDataRegister; virConnectCloseCallbackDataUnregister; +virDomainBackupClass; virDomainClass; virDomainSnapshotClass; virGetConnect; virGetDomain; +virGetDomainBackup; virGetDomainSnapshot; virGetInterface; virGetNetwork; diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 428cf2e..e3ee083 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -759,4 +759,14 @@ LIBVIRT_3.1.0 { virDomainSetVcpu; } LIBVIRT_3.0.0; +LIBVIRT_3.4.0 { + global: + virDomainBackupRef; + virDomainBackupFree; + virDomainBackupGetName; + virDomainBackupGetDomain; + virDomainBackupGetConnect; + virDomainBackupCreateXML; +} LIBVIRT_3.1.0; + # .... define new API here using predicted next version number .... diff --git a/src/util/virerror.c b/src/util/virerror.c index ef17fb5..f666e6e 100644 --- a/src/util/virerror.c +++ b/src/util/virerror.c @@ -139,6 +139,7 @@ VIR_ENUM_IMPL(virErrorDomain, VIR_ERR_DOMAIN_LAST, "Perf", /* 65 */ "Libssh transport layer", + "Domain Backup", ) @@ -1400,6 +1401,11 @@ virErrorMsg(virErrorNumber error, const char *info) errmsg = _("guest agent replied with wrong id to guest-sync command"); else errmsg = _("guest agent replied with wrong id to guest-sync command: %s"); + case VIR_ERR_INVALID_DOMAIN_BACKUP: + if (info == NULL) + errmsg = _("Invalid backup"); + else + errmsg = _("Invalid backup: %s"); break; case VIR_ERR_LIBSSH: if (info == NULL) -- 1.8.3.1

--- daemon/remote.c | 8 ++++++++ src/access/viraccessperm.c | 3 ++- src/access/viraccessperm.h | 6 ++++++ src/remote/remote_driver.c | 7 +++++++ src/remote/remote_protocol.x | 23 ++++++++++++++++++++++- src/rpc/gendispatch.pl | 29 ++++++++++++++++++++++------- 6 files changed, 67 insertions(+), 9 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index 0dbb250..6c601ff 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -101,6 +101,7 @@ static void make_nonnull_node_device(remote_nonnull_node_device *dev_dst, virNod static void make_nonnull_secret(remote_nonnull_secret *secret_dst, virSecretPtr secret_src); static void make_nonnull_nwfilter(remote_nonnull_nwfilter *net_dst, virNWFilterPtr nwfilter_src); static void make_nonnull_domain_snapshot(remote_nonnull_domain_snapshot *snapshot_dst, virDomainSnapshotPtr snapshot_src); +static void make_nonnull_domain_backup(remote_nonnull_domain_backup *backup_dst, virDomainBackupPtr backup_src); static int remoteSerializeDomainDiskErrors(virDomainDiskErrorPtr errors, @@ -7039,6 +7040,13 @@ make_nonnull_domain_snapshot(remote_nonnull_domain_snapshot *snapshot_dst, virDo make_nonnull_domain(&snapshot_dst->dom, snapshot_src->domain); } +static void +make_nonnull_domain_backup(remote_nonnull_domain_backup *backup_dst, virDomainBackupPtr backup_src) +{ + ignore_value(VIR_STRDUP_QUIET(backup_dst->name, backup_src->name)); + make_nonnull_domain(&backup_dst->dom, backup_src->domain); +} + static int remoteSerializeDomainDiskErrors(virDomainDiskErrorPtr errors, int nerrors, 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/remote/remote_driver.c b/src/remote/remote_driver.c index 77250ea..86b03ec 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -148,6 +148,7 @@ static virStorageVolPtr get_nonnull_storage_vol(virConnectPtr conn, remote_nonnu static virNodeDevicePtr get_nonnull_node_device(virConnectPtr conn, remote_nonnull_node_device dev); static virSecretPtr get_nonnull_secret(virConnectPtr conn, remote_nonnull_secret secret); static virDomainSnapshotPtr get_nonnull_domain_snapshot(virDomainPtr domain, remote_nonnull_domain_snapshot snapshot); +static virDomainBackupPtr get_nonnull_domain_backup(virDomainPtr dom, remote_nonnull_domain_backup backup); static void make_nonnull_domain(remote_nonnull_domain *dom_dst, virDomainPtr dom_src); static void make_nonnull_network(remote_nonnull_network *net_dst, virNetworkPtr net_src); static void make_nonnull_interface(remote_nonnull_interface *interface_dst, virInterfacePtr interface_src); @@ -8133,6 +8134,11 @@ get_nonnull_domain_snapshot(virDomainPtr domain, remote_nonnull_domain_snapshot return virGetDomainSnapshot(domain, snapshot.name); } +static virDomainBackupPtr +get_nonnull_domain_backup(virDomainPtr dom, remote_nonnull_domain_backup backup) +{ + return virGetDomainBackup(dom, backup.name); +} /* Make remote_nonnull_domain and remote_nonnull_network. */ static void @@ -8434,6 +8440,7 @@ static virHypervisorDriver hypervisor_driver = { .domainSetGuestVcpus = remoteDomainSetGuestVcpus, /* 2.0.0 */ .domainSetVcpu = remoteDomainSetVcpu, /* 3.1.0 */ .domainSetBlockThreshold = remoteDomainSetBlockThreshold, /* 3.2.0 */ + .domainBackupCreateXML = remoteDomainBackupCreateXML, /* 3.4.0 */ }; static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 87b2bd3..92bcfce 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -312,6 +312,12 @@ struct remote_nonnull_domain_snapshot { remote_nonnull_domain dom; }; +/* A backup which may not be NULL. */ +struct remote_nonnull_domain_backup { + remote_nonnull_string name; + remote_nonnull_domain dom; +}; + /* A domain or network which may be NULL. */ typedef remote_nonnull_domain *remote_domain; typedef remote_nonnull_network *remote_network; @@ -2691,6 +2697,16 @@ struct remote_domain_snapshot_delete_args { unsigned int flags; }; +struct remote_domain_backup_create_xml_args { + remote_nonnull_domain dom; + remote_nonnull_string xml_desc; + unsigned int flags; +}; + +struct remote_domain_backup_create_xml_ret { + remote_nonnull_domain_backup back; +}; + struct remote_domain_open_console_args { remote_nonnull_domain dom; remote_string dev_name; @@ -6062,7 +6078,12 @@ enum remote_procedure { * @generate: both * @acl: domain:write */ - REMOTE_PROC_DOMAIN_SET_BLOCK_THRESHOLD = 386 + REMOTE_PROC_DOMAIN_SET_BLOCK_THRESHOLD = 386, + /** + * @generate: both + * @acl: domain:backup + */ + REMOTE_PROC_DOMAIN_BACKUP_CREATE_XML = 387 }; diff --git a/src/rpc/gendispatch.pl b/src/rpc/gendispatch.pl index 173189c..cd0ca83 100755 --- a/src/rpc/gendispatch.pl +++ b/src/rpc/gendispatch.pl @@ -523,6 +523,19 @@ elsif ($mode eq "server") { push(@free_list, " virObjectUnref(snapshot);\n" . " virObjectUnref(dom);"); + } elsif ($args_member =~ m/^remote_nonnull_domain_backup (\S+);$/) { + push(@vars_list, "virDomainPtr dom = NULL"); + push(@vars_list, "virDomainBackupPtr backup = NULL"); + push(@getters_list, + " if (!(dom = get_nonnull_domain(priv->conn, args->${1}.dom)))\n" . + " goto cleanup;\n" . + "\n" . + " if (!(backup = get_nonnull_domain_backup(dom, args->${1})))\n" . + " goto cleanup;\n"); + push(@args_list, "backup"); + push(@free_list, + " virObjectUnref(backup);\n" . + " virObjectUnref(dom);"); } elsif ($args_member =~ m/^(?:(?:admin|remote)_string|remote_uuid) (\S+)<\S+>;/) { push_privconn(\@args_list); push(@args_list, "args->$1.$1_val"); @@ -665,7 +678,7 @@ elsif ($mode eq "server") { if (!$modern_ret_as_list) { push(@ret_list, "ret->$3 = tmp.$3;"); } - } elsif ($ret_member =~ m/(?:admin|remote)_nonnull_(secret|nwfilter|node_device|interface|network|storage_vol|storage_pool|domain_snapshot|domain|server|client) (\S+)<(\S+)>;/) { + } elsif ($ret_member =~ m/(?:admin|remote)_nonnull_(secret|nwfilter|node_device|interface|network|storage_vol|storage_pool|domain_snapshot|domain_backup|domain|server|client) (\S+)<(\S+)>;/) { $modern_ret_struct_name = $1; $single_ret_list_error_msg_type = $1; $single_ret_list_name = $2; @@ -723,7 +736,7 @@ elsif ($mode eq "server") { $single_ret_var = $1; $single_ret_by_ref = 0; $single_ret_check = " == NULL"; - } elsif ($ret_member =~ m/^remote_nonnull_(domain|network|storage_pool|storage_vol|interface|node_device|secret|nwfilter|domain_snapshot) (\S+);/) { + } elsif ($ret_member =~ m/^remote_nonnull_(domain|network|storage_pool|storage_vol|interface|node_device|secret|nwfilter|domain_snapshot|domain_backup) (\S+);/) { my $type_name = name_to_TypeName($1); if ($call->{ProcName} eq "DomainCreateWithFlags") { @@ -1268,7 +1281,7 @@ elsif ($mode eq "client") { $priv_src = "dev->conn"; push(@args_list, "virNodeDevicePtr dev"); push(@setters_list, "args.name = dev->name;"); - } elsif ($args_member =~ m/^remote_nonnull_(domain|network|storage_pool|storage_vol|interface|secret|nwfilter|domain_snapshot) (\S+);/) { + } elsif ($args_member =~ m/^remote_nonnull_(domain|network|storage_pool|storage_vol|interface|secret|nwfilter|domain_snapshot|domain_backup) (\S+);/) { my $name = $1; my $arg_name = $2; my $type_name = name_to_TypeName($name); @@ -1276,6 +1289,8 @@ elsif ($mode eq "client") { if ($is_first_arg) { if ($name eq "domain_snapshot") { $priv_src = "$arg_name->domain->conn"; + } elsif ($name eq "domain_backup") { + $priv_src = "$arg_name->domain->conn"; } else { $priv_src = "$arg_name->conn"; } @@ -1461,7 +1476,7 @@ elsif ($mode eq "client") { } push(@ret_list, "memcpy(result->$3, ret.$3, sizeof(result->$3));"); - } elsif ($ret_member =~ m/(?:admin|remote)_nonnull_(secret|nwfilter|node_device|interface|network|storage_vol|storage_pool|domain_snapshot|domain|server|client) (\S+)<(\S+)>;/) { + } elsif ($ret_member =~ m/(?:admin|remote)_nonnull_(secret|nwfilter|node_device|interface|network|storage_vol|storage_pool|domain_snapshot|domain_backup|domain|server|client) (\S+)<(\S+)>;/) { my $proc_name = name_to_TypeName($1); if ($structprefix eq "admin") { @@ -1513,7 +1528,7 @@ elsif ($mode eq "client") { push(@ret_list, "VIR_FREE(ret.$1);"); $single_ret_var = "char *rv = NULL"; $single_ret_type = "char *"; - } elsif ($ret_member =~ m/^remote_nonnull_(domain|network|storage_pool|storage_vol|node_device|interface|secret|nwfilter|domain_snapshot) (\S+);/) { + } elsif ($ret_member =~ m/^remote_nonnull_(domain|network|storage_pool|storage_vol|node_device|interface|secret|nwfilter|domain_snapshot|domain_backup) (\S+);/) { my $name = $1; my $arg_name = $2; my $type_name = name_to_TypeName($name); @@ -1527,7 +1542,7 @@ elsif ($mode eq "client") { $single_ret_var = "int rv = -1"; $single_ret_type = "int"; } else { - if ($name eq "domain_snapshot") { + if ($name =~ m/domain_snapshot|domain_backup/) { my $dom = "$priv_src"; $dom =~ s/->conn//; push(@ret_list, "rv = get_nonnull_$name($dom, ret.$arg_name);"); @@ -1863,7 +1878,7 @@ elsif ($mode eq "client") { print " }\n"; print "\n"; } elsif ($modern_ret_as_list) { - if ($modern_ret_struct_name =~ m/domain_snapshot|client/) { + if ($modern_ret_struct_name =~ m/domain_snapshot|domain_backup|client/) { $priv_src =~ s/->conn//; } print " if (result) {\n"; -- 1.8.3.1

--- src/qemu/qemu_monitor.c | 14 ++++++++++++++ src/qemu/qemu_monitor.h | 5 +++++ src/qemu/qemu_monitor_json.c | 34 ++++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 5 +++++ 4 files changed, 58 insertions(+) diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 1d40d52..934a2c8 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -4316,3 +4316,17 @@ qemuMonitorEventPanicInfoFree(qemuMonitorEventPanicInfoPtr info) VIR_FREE(info); } + + +int +qemuMonitorDriveBackup(virJSONValuePtr actions, + const char *device, const char *target, + const char *bitmap, const char *format, + unsigned long long speed, bool reuse) +{ + VIR_DEBUG("actions=%p, device=%s, target=%s, bitmap=%s format=%s speed==%llu", + actions, device, target, bitmap, format, speed); + + return qemuMonitorJSONDriveBackup(actions, device, target, bitmap, format, + speed, reuse); +} diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index 12f98be..f0196b8 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -1125,4 +1125,9 @@ int qemuMonitorSetBlockThreshold(qemuMonitorPtr mon, virJSONValuePtr qemuMonitorQueryNamedBlockNodes(qemuMonitorPtr mon); +int qemuMonitorDriveBackup(virJSONValuePtr actions, + const char *device, const char *target, + const char *bitmap, const char *format, + unsigned long long speed, bool reuse); + #endif /* QEMU_MONITOR_H */ diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 0837290..b3446ee 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -7647,5 +7647,39 @@ qemuMonitorJSONQueryNamedBlockNodes(qemuMonitorPtr mon) virJSONValueFree(cmd); virJSONValueFree(reply); + return ret; +} + + +int +qemuMonitorJSONDriveBackup(virJSONValuePtr actions, + const char *device, const char *target, + const char *bitmap, const char *format, + unsigned long long speed, bool reuse) +{ + int ret = -1; + virJSONValuePtr cmd; + + cmd = qemuMonitorJSONMakeCommandRaw(true, + "drive-backup", + "s:device", device, + "s:target", target, + "S:bitmap", bitmap, + "s:sync", bitmap ? "incremental" : "full", + "S:format", format, + "Y:speed", speed, + "S:mode", reuse ? "existing" : NULL, + 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 d090d57..24c4fba 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -522,4 +522,9 @@ int qemuMonitorJSONSetBlockThreshold(qemuMonitorPtr mon, virJSONValuePtr qemuMonitorJSONQueryNamedBlockNodes(qemuMonitorPtr mon) ATTRIBUTE_NONNULL(1); +int qemuMonitorJSONDriveBackup(virJSONValuePtr actions, + const char *device, const char *target, + const char *bitmap, const char *format, + unsigned long long speed, bool reuse) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); #endif /* QEMU_MONITOR_JSON_H */ -- 1.8.3.1

--- examples/object-events/event-test.c | 3 +++ include/libvirt/libvirt-domain.h | 3 +++ src/conf/domain_conf.c | 2 +- src/qemu/qemu_monitor_json.c | 2 ++ tools/virsh-domain.c | 3 ++- 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/examples/object-events/event-test.c b/examples/object-events/event-test.c index 12690ca..592df09 100644 --- a/examples/object-events/event-test.c +++ b/examples/object-events/event-test.c @@ -877,6 +877,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.h b/include/libvirt/libvirt-domain.h index c9e96a6..1501405 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -2325,6 +2325,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/src/conf/domain_conf.c b/src/conf/domain_conf.c index 0ff216e..5179527 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -863,7 +863,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, diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index b3446ee..5ca0b19 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -885,6 +885,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: diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 0d19d0e..d18e1f9 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -2454,7 +2454,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) -- 1.8.3.1

Backup xml description is like this: <domainbackup> <name>backup name</name> <disk type="file" name="sda"> <target file="/path/to/backup/file.qcow2" format="qcow2"/> </disk> </domainbackup> - <name> element is optional. - disk @type attribute is optional, default to 'file'. Valid values are 'file', 'block', 'dir', 'network', 'volume' just as usual for specifing domain disk sources. It specifies backup <target> type. - <target> element has same attributes and its values as <source> element in domain disk description. - target @format attribute is optional. If it is not specified then hypervisor will choose format by its default rule (for qemu it same format as disk to be backed up). Elements that are not explicitly mentioned as optional are mandatory. Current description does not support implicitly specified disks to be backed up like snapshot xml description for example. --- po/POTFILES.in | 1 + src/Makefile.am | 1 + src/conf/backup_conf.c | 267 +++++++++++++++++++++++++++++++++++++++++++++++ src/conf/backup_conf.h | 65 ++++++++++++ src/libvirt_private.syms | 6 ++ src/qemu/qemu_conf.h | 1 + 6 files changed, 341 insertions(+) create mode 100644 src/conf/backup_conf.c create mode 100644 src/conf/backup_conf.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 5077857..cb9831e 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -20,6 +20,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 diff --git a/src/Makefile.am b/src/Makefile.am index c4ffc2f..06eb722 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -403,6 +403,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 diff --git a/src/conf/backup_conf.c b/src/conf/backup_conf.c new file mode 100644 index 0000000..a5bd995 --- /dev/null +++ b/src/conf/backup_conf.c @@ -0,0 +1,267 @@ +/* + * backup_conf.c: domain backup XML processing + * + * Copyright (C) 2017 Parallels International GmbH + * + * 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/>. + * + */ + +#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"); + + +/* Backup Def functions */ +static void +virDomainBackupDiskDefClear(virDomainBackupDiskDefPtr disk) +{ + VIR_FREE(disk->name); + virStorageSourceFree(disk->target); + disk->target = NULL; +} + + +static int +virDomainBackupDiskDefParseXML(xmlNodePtr node, + xmlXPathContextPtr ctxt, + virDomainBackupDiskDefPtr def) +{ + int ret = -1; + char *type = NULL; + char *format = NULL; + xmlNodePtr cur; + xmlNodePtr saved = ctxt->node; + virStorageSourcePtr target; + + ctxt->node = node; + + if (!(def->name = virXMLPropString(node, "name"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing name from disk backup element")); + goto cleanup; + } + + if (!(cur = virXPathNode("./target", ctxt))) { + virReportError(VIR_ERR_XML_ERROR, + _("missing target for disk '%s'"), def->name); + goto cleanup; + } + + if (VIR_ALLOC(def->target) < 0) + goto cleanup; + target = def->target; + + if ((type = virXMLPropString(node, "type"))) { + if ((target->type = virStorageTypeFromString(type)) <= 0) { + virReportError(VIR_ERR_XML_ERROR, + _("unknown disk '%s' backup type '%s'"), + def->name, type); + goto cleanup; + } + } else { + target->type = VIR_STORAGE_TYPE_FILE; + } + + if (virDomainDiskSourceParse(cur, ctxt, target) < 0) + goto cleanup; + + if ((format = virXPathString("string(./target/@format)", ctxt)) && + (target->format = virStorageFileFormatTypeFromString(format)) <= 0) { + virReportError(VIR_ERR_XML_ERROR, + _("unknown disk '%s' backup format '%s'"), + def->name, format); + goto cleanup; + } + + if (virStorageSourceIsLocalStorage(def->target)) { + if (!target->path) { + virReportError(VIR_ERR_XML_ERROR, + _("disk '%s' backup path is not specified"), + def->name); + goto cleanup; + } + if (target->path[0] != '/') { + virReportError(VIR_ERR_XML_ERROR, + _("disk '%s' backup path '%s' must be absolute"), + def->name, target->path); + goto cleanup; + } + } + + ret = 0; + cleanup: + ctxt->node = saved; + + VIR_FREE(format); + VIR_FREE(type); + if (ret < 0) + virDomainBackupDiskDefClear(def); + + return ret; +} + + +static virDomainBackupDefPtr +virDomainBackupDefParse(xmlXPathContextPtr ctxt, + virCapsPtr caps ATTRIBUTE_UNUSED, + virDomainXMLOptionPtr xmlopt ATTRIBUTE_UNUSED, + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virDomainBackupDefPtr def; + virDomainBackupDefPtr ret = NULL; + xmlNodePtr *nodes = NULL; + size_t i; + int n; + struct timeval tv; + + if (VIR_ALLOC(def) < 0) + return NULL; + + gettimeofday(&tv, NULL); + + if (!(def->name = virXPathString("string(./name)", ctxt)) && + virAsprintf(&def->name, "%lld", (long long)tv.tv_sec) < 0) + goto cleanup; + + def->description = virXPathString("string(./description)", ctxt); + def->creationTime = tv.tv_sec; + + if ((n = virXPathNodeSet("./disk", ctxt, &nodes)) < 0) + goto cleanup; + + if (n == 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("no disk is specified to be backed up")); + goto cleanup; + } + + if (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, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + xmlXPathContextPtr ctxt = NULL; + virDomainBackupDefPtr def = 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, caps, xmlopt, flags); + + 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; +} + + +void +virDomainBackupDefFree(virDomainBackupDefPtr def) +{ + size_t i; + + if (!def) + return; + + VIR_FREE(def->name); + VIR_FREE(def->description); + + for (i = 0; i < def->ndisks; i++) + virDomainBackupDiskDefClear(&def->disks[i]); + VIR_FREE(def->disks); + + VIR_FREE(def); +} diff --git a/src/conf/backup_conf.h b/src/conf/backup_conf.h new file mode 100644 index 0000000..6f689e3 --- /dev/null +++ b/src/conf/backup_conf.h @@ -0,0 +1,65 @@ +/* + * backup_conf.h: domain backup XML processing + * + * Copyright (C) 2017 Parallels International GmbH + * + * 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 __BACKUP_CONF_H +# define __BACKUP_CONF_H + +# include "internal.h" +# include "domain_conf.h" + +/* Items related to backup state */ + +/* Stores disk-backup information */ +typedef struct _virDomainBackupDiskDef virDomainBackupDiskDef; +typedef virDomainBackupDiskDef *virDomainBackupDiskDefPtr; +struct _virDomainBackupDiskDef { + char *name; /* name matching the <target dev='...' of the domain */ + virStorageSourcePtr target; +}; + +/* Stores the complete backup metadata */ +typedef struct _virDomainBackupDef virDomainBackupDef; +typedef virDomainBackupDef *virDomainBackupDefPtr; +struct _virDomainBackupDef { + /* Public XML. */ + char *name; + char *description; + long long creationTime; /* in seconds */ + + size_t ndisks; /* should not exceed dom->ndisks */ + virDomainBackupDiskDef *disks; +}; + +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/libvirt_private.syms b/src/libvirt_private.syms index 2b40d34..5ae4323 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/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 1407eef..0626729 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" -- 1.8.3.1

Supported options - backup to file or block device - specify format of backup --- src/conf/backup_conf.c | 32 ++++++++++++++ src/conf/backup_conf.h | 5 +++ src/libvirt_private.syms | 1 + src/qemu/qemu_driver.c | 112 ++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 149 insertions(+), 1 deletion(-) diff --git a/src/conf/backup_conf.c b/src/conf/backup_conf.c index a5bd995..49f1370 100644 --- a/src/conf/backup_conf.c +++ b/src/conf/backup_conf.c @@ -265,3 +265,35 @@ virDomainBackupDefFree(virDomainBackupDefPtr def) VIR_FREE(def); } + + +int +virDomainBackupDefResolveDisks(virDomainBackupDefPtr def, + virDomainDefPtr vmdef) +{ + int ret = -1; + size_t i, j; + + for (i = 0; i < def->ndisks; i++) { + virDomainBackupDiskDefPtr disk = &def->disks[i]; + + for (j = 0; j < i; j++) { + if (STREQ(def->disks[j].name, disk->name)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("disk '%s' specified twice"), disk->name); + goto cleanup; + } + } + + if (!(disk->vmdisk = virDomainDiskByName(vmdef, disk->name, false))) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("no disk named '%s'"), disk->name); + goto cleanup; + } + } + + ret = 0; + + cleanup: + return ret; +} diff --git a/src/conf/backup_conf.h b/src/conf/backup_conf.h index 6f689e3..ae7411e 100644 --- a/src/conf/backup_conf.h +++ b/src/conf/backup_conf.h @@ -33,6 +33,7 @@ typedef virDomainBackupDiskDef *virDomainBackupDiskDefPtr; struct _virDomainBackupDiskDef { char *name; /* name matching the <target dev='...' of the domain */ virStorageSourcePtr target; + virDomainDiskDefPtr vmdisk; }; /* Stores the complete backup metadata */ @@ -62,4 +63,8 @@ virDomainBackupDefParseNode(xmlDocPtr xml, void virDomainBackupDefFree(virDomainBackupDefPtr def); +int +virDomainBackupDefResolveDisks(virDomainBackupDefPtr def, + virDomainDefPtr vmdef); + #endif /* __BACKUP_CONF_H */ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 5ae4323..884b1ed 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -46,6 +46,7 @@ virAccessPermStorageVolTypeToString; virDomainBackupDefFree; virDomainBackupDefParseNode; virDomainBackupDefParseString; +virDomainBackupDefResolveDisks; # conf/capabilities.h diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 1c4873e..b20e359 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -20468,6 +20468,115 @@ qemuDomainSetBlockThreshold(virDomainPtr dom, } +static virDomainBackupPtr +qemuDomainBackupCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags) +{ + virQEMUDriverPtr driver = domain->conn->privateData; + qemuDomainObjPrivatePtr priv; + virDomainBackupDefPtr def = NULL; + virDomainBackupPtr backup = NULL; + virDomainBackupPtr ret = NULL; + virJSONValuePtr actions = NULL; + virDomainObjPtr vm = NULL; + char *path = NULL, *device = NULL; + bool job = false; + int rc; + size_t i; + + virCheckFlags(0, NULL); + + if (!(vm = qemuDomObjFromDomain(domain))) + goto cleanup; + + if (virDomainBackupCreateXMLEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + if (!(def = virDomainBackupDefParseString(xmlDesc, NULL, NULL, 0))) + goto cleanup; + + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0) + goto cleanup; + job = true; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + "%s", _("backing up inactive domains is not supported")); + goto cleanup; + } + + if (virDomainBackupDefResolveDisks(def, vm->def) < 0) + goto cleanup; + + if (!(actions = virJSONValueNewArray())) + goto cleanup; + + for (i = 0; i < def->ndisks; i++) { + virStorageSourcePtr target = def->disks[i].target; + virDomainDiskDefPtr disk = def->disks[i].vmdisk; + const char *format_str = NULL; + + if (target->type != VIR_STORAGE_TYPE_FILE && + target->type != VIR_STORAGE_TYPE_BLOCK) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("disk '%s' backup type '%s' is not supported"), + def->disks[i].name, + virStorageTypeToString(target->type)); + goto cleanup; + } + + if (qemuDomainDiskBlockJobIsActive(disk)) + goto cleanup; + + if (qemuGetDriveSourceString(target, NULL, &path) < 0) + goto cleanup; + + if (!(device = qemuAliasFromDisk(disk))) + goto cleanup; + + if (target->format) + format_str = virStorageFileFormatTypeToString(target->format); + + if (qemuMonitorDriveBackup(actions, device, path, NULL, format_str, 0, + false) < 0) + goto cleanup; + + VIR_FREE(path); + VIR_FREE(device); + } + + priv = vm->privateData; + if (!(backup = virGetDomainBackup(domain, def->name))) + goto cleanup; + + qemuDomainObjEnterMonitor(driver, vm); + rc = qemuMonitorTransaction(priv->mon, actions); + if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0) + goto cleanup; + + ret = backup; + backup = NULL; + + for (i = 0; i < def->ndisks; i++) + QEMU_DOMAIN_DISK_PRIVATE(def->disks->vmdisk)->blockjob = true; + + cleanup: + if (job) + qemuDomainObjEndJob(driver, vm); + + VIR_FREE(path); + VIR_FREE(device); + + virDomainBackupDefFree(def); + virJSONValueFree(actions); + virDomainObjEndAPI(&vm); + virObjectUnref(backup); + + return ret; +} + + static virHypervisorDriver qemuHypervisorDriver = { .name = QEMU_DRIVER_NAME, .connectOpen = qemuConnectOpen, /* 0.2.0 */ @@ -20682,7 +20791,8 @@ static virHypervisorDriver qemuHypervisorDriver = { .domainGetGuestVcpus = qemuDomainGetGuestVcpus, /* 2.0.0 */ .domainSetGuestVcpus = qemuDomainSetGuestVcpus, /* 2.0.0 */ .domainSetVcpu = qemuDomainSetVcpu, /* 3.1.0 */ - .domainSetBlockThreshold = qemuDomainSetBlockThreshold /* 3.2.0 */ + .domainSetBlockThreshold = qemuDomainSetBlockThreshold, /* 3.2.0 */ + .domainBackupCreateXML = qemuDomainBackupCreateXML, /* 3.4.0 */ }; -- 1.8.3.1

If backup target is file then check it is not present or regular empty file otherwise. If backup target is block device then check that it is present and block device actually. --- src/qemu/qemu_driver.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index b20e359..f4456da 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -20468,6 +20468,76 @@ qemuDomainSetBlockThreshold(virDomainPtr dom, } +static int +qemuDomainBackupCheckTarget(virDomainBackupDiskDefPtr disk) +{ + int ret = -1; + struct stat st; + virStorageSourcePtr target = disk->target; + + if (virStorageFileInit(target) < 0) + return -1; + + if (virStorageFileStat(target, &st) < 0) { + if (errno != ENOENT) { + virReportSystemError(errno, + _("unable to stat target path '%s' for disk '%s'"), + target->path, disk->name); + goto cleanup; + } + switch (target->type) { + case VIR_STORAGE_TYPE_FILE: + break; + + case VIR_STORAGE_TYPE_BLOCK: + virReportError(VIR_ERR_INVALID_ARG, + _("missing target block device '%s' for disk '%s'"), + target->path, disk->name); + goto cleanup; + + default: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unexpected backup target type '%s' for disk '%s'"), + virStorageTypeToString(target->type), + disk->name); + goto cleanup; + } + } else { + switch (target->type) { + case VIR_STORAGE_TYPE_FILE: + if (!S_ISREG(st.st_mode) || st.st_size > 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("invalid existing target file '%s' for disk '%s'"), + target->path, disk->name); + goto cleanup; + } + break; + + case VIR_STORAGE_TYPE_BLOCK: + if (!S_ISBLK(st.st_mode)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("target file '%s' for disk '%s' is not a block device"), + target->path, disk->name); + goto cleanup; + } + break; + + default: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unexpected backup target type '%s' for disk '%s'"), + virStorageTypeToString(target->type), disk->name); + goto cleanup; + } + } + + ret = 0; + + cleanup: + virStorageFileDeinit(target); + return ret; +} + + static virDomainBackupPtr qemuDomainBackupCreateXML(virDomainPtr domain, const char *xmlDesc, @@ -20529,6 +20599,9 @@ qemuDomainBackupCreateXML(virDomainPtr domain, if (qemuDomainDiskBlockJobIsActive(disk)) goto cleanup; + if (qemuDomainBackupCheckTarget(&def->disks[i]) < 0) + goto cleanup; + if (qemuGetDriveSourceString(target, NULL, &path) < 0) goto cleanup; -- 1.8.3.1

Prepare here is usual preparation for a disk to be used by qemu made by qemuDomainDiskChainElementPrepare. That is set security labels, add to lock manager and whitelist in cgroups. All three are related to backup target too. Adding to a lock manager is less obvious but can be useful if mirations with a running backup will be possible. Also create file in case of backup to file if it is not exist in order to set securitly labels etc. --- src/qemu/qemu_driver.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index f4456da..7df2e04 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -20468,8 +20468,17 @@ qemuDomainSetBlockThreshold(virDomainPtr dom, } +typedef struct _qemuDomainBackupDiskTrackInfo qemuDomainBackupDiskTrackInfo; +typedef qemuDomainBackupDiskTrackInfo *qemuDomainBackupDiskTrackInfoPtr; +struct _qemuDomainBackupDiskTrackInfo { + bool created; + bool prepared; +}; + + static int -qemuDomainBackupCheckTarget(virDomainBackupDiskDefPtr disk) +qemuDomainBackupCheckTarget(virDomainBackupDiskDefPtr disk, + bool *created) { int ret = -1; struct stat st; @@ -20487,6 +20496,12 @@ qemuDomainBackupCheckTarget(virDomainBackupDiskDefPtr disk) } switch (target->type) { case VIR_STORAGE_TYPE_FILE: + if (virStorageFileCreate(target) < 0) { + virReportSystemError(errno, _("failed to create image file '%s'"), + target->path); + goto cleanup; + } + *created = true; break; case VIR_STORAGE_TYPE_BLOCK: @@ -20538,6 +20553,22 @@ qemuDomainBackupCheckTarget(virDomainBackupDiskDefPtr disk) } +static void +qemuDomainBackupDiskUnlink(virDomainBackupDiskDefPtr disk) +{ + virStorageSourcePtr target = disk->target; + + if (virStorageFileInit(target) < 0) + return; + + if (virStorageFileUnlink(target) < 0) + VIR_WARN("unable to unlink target path '%s' for disk '%s', errno: %d", + target->path, disk->name, errno); + + virStorageFileDeinit(target); +} + + static virDomainBackupPtr qemuDomainBackupCreateXML(virDomainPtr domain, const char *xmlDesc, @@ -20550,6 +20581,7 @@ qemuDomainBackupCreateXML(virDomainPtr domain, virDomainBackupPtr ret = NULL; virJSONValuePtr actions = NULL; virDomainObjPtr vm = NULL; + qemuDomainBackupDiskTrackInfoPtr track_disks = NULL; char *path = NULL, *device = NULL; bool job = false; int rc; @@ -20582,6 +20614,9 @@ qemuDomainBackupCreateXML(virDomainPtr domain, if (!(actions = virJSONValueNewArray())) goto cleanup; + if (VIR_ALLOC_N(track_disks, def->ndisks) < 0) + goto cleanup; + for (i = 0; i < def->ndisks; i++) { virStorageSourcePtr target = def->disks[i].target; virDomainDiskDefPtr disk = def->disks[i].vmdisk; @@ -20599,9 +20634,15 @@ qemuDomainBackupCreateXML(virDomainPtr domain, if (qemuDomainDiskBlockJobIsActive(disk)) goto cleanup; - if (qemuDomainBackupCheckTarget(&def->disks[i]) < 0) + if (qemuDomainBackupCheckTarget(&def->disks[i], + &track_disks[i].created) < 0) goto cleanup; + if (qemuDomainDiskChainElementPrepare(driver, vm, def->disks[i].target, + false) < 0) + goto cleanup; + track_disks[i].prepared = true; + if (qemuGetDriveSourceString(target, NULL, &path) < 0) goto cleanup; @@ -20635,11 +20676,24 @@ qemuDomainBackupCreateXML(virDomainPtr domain, QEMU_DOMAIN_DISK_PRIVATE(def->disks->vmdisk)->blockjob = true; cleanup: + if (!ret && track_disks) { + for (i = 0; i < def->ndisks; i++) { + virDomainBackupDiskDefPtr backup_disk = &def->disks[i]; + + if (track_disks[i].prepared) + qemuDomainDiskChainElementRevoke(driver, vm, + backup_disk->target); + if (track_disks[i].created) + qemuDomainBackupDiskUnlink(backup_disk); + } + } + if (job) qemuDomainObjEndJob(driver, vm); VIR_FREE(path); VIR_FREE(device); + VIR_FREE(track_disks); virDomainBackupDefFree(def); virJSONValueFree(actions); -- 1.8.3.1

--- po/POTFILES.in | 1 + tools/Makefile.am | 1 + tools/virsh-backup.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh-backup.h | 29 +++++++++++++++ tools/virsh-util.c | 11 ++++++ tools/virsh-util.h | 3 ++ tools/virsh.c | 2 ++ tools/virsh.h | 1 + tools/virsh.pod | 20 +++++++++++ 9 files changed, 168 insertions(+) create mode 100644 tools/virsh-backup.c create mode 100644 tools/virsh-backup.h diff --git a/po/POTFILES.in b/po/POTFILES.in index cb9831e..2f6d594 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -300,6 +300,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/tools/Makefile.am b/tools/Makefile.am index 56691c2..7e0283f 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -228,6 +228,7 @@ virsh_SOURCES = \ virsh-snapshot.c virsh-snapshot.h \ virsh-util.c virsh-util.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..0f72444 --- /dev/null +++ b/tools/virsh-backup.c @@ -0,0 +1,100 @@ +/* + * virsh-backup.c: Commands to manage domain backup + * + * Copyright (C) 2017 Parallels International GmbH + * + * 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/>. + * + */ + +#include <config.h> +#include "virsh-backup.h" + +#include "internal.h" +#include "virfile.h" +#include "viralloc.h" +#include "virsh-domain.h" +#include "virsh-util.h" + +#define VIRSH_COMMON_OPT_DOMAIN_FULL \ + VIRSH_COMMON_OPT_DOMAIN(N_("domain name, id or uuid")) \ + +/* + * "backup-create" command + */ +static const vshCmdInfo info_backup_create[] = { + {.name = "help", + .data = N_("Create a backup from XML") + }, + {.name = "desc", + .data = N_("Create a disks backup from XML description") + }, + {.name = NULL} +}; + +static const vshCmdOptDef opts_backup_create[] = { + VIRSH_COMMON_OPT_DOMAIN_FULL, + {.name = "xmlfile", + .type = VSH_OT_STRING, + .help = N_("domain backup XML") + }, + {.name = NULL} +}; + +static bool +cmdBackupCreate(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + bool ret = false; + const char *from = NULL; + char *buffer = NULL; + unsigned int flags = 0; + virDomainBackupPtr backup = NULL; + + 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 (!(backup = virDomainBackupCreateXML(dom, buffer, flags))) + goto cleanup; + + vshPrint(ctl, _("Domain backup started from '%s'"), from); + + ret = true; + + cleanup: + VIR_FREE(buffer); + virshDomainFree(dom); + virshDomainBackupFree(backup); + + return ret; +} + +const vshCmdDef backupCmds[] = { + {.name = "backup-create", + .handler = cmdBackupCreate, + .opts = opts_backup_create, + .info = info_backup_create, + .flags = 0 + }, + {.name = NULL} +}; diff --git a/tools/virsh-backup.h b/tools/virsh-backup.h new file mode 100644 index 0000000..da7ec22 --- /dev/null +++ b/tools/virsh-backup.h @@ -0,0 +1,29 @@ +/* + * virsh-backup.h: Commands to manage domain backup + * + * Copyright (C) 2017 Parallels International GmbH + * + * 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 VIRSH_BACKUP_H +# define VIRSH_BACKUP_H + +# include "virsh.h" + +extern const vshCmdDef backupCmds[]; + +#endif /* VIRSH_BACKUP_H */ diff --git a/tools/virsh-util.c b/tools/virsh-util.c index 4b86e29..66eb61a 100644 --- a/tools/virsh-util.c +++ b/tools/virsh-util.c @@ -175,6 +175,17 @@ virshDomainSnapshotFree(virDomainSnapshotPtr snap) } +void +virshDomainBackupFree(virDomainBackupPtr backup) +{ + if (!backup) + return; + + vshSaveLibvirtHelperError(); + virDomainBackupFree(backup); /* sc_prohibit_obj_free_apis_in_virsh */ +} + + int virshDomainGetXMLFromDom(vshControl *ctl, virDomainPtr dom, diff --git a/tools/virsh-util.h b/tools/virsh-util.h index 64cef23..ebbac5d 100644 --- a/tools/virsh-util.h +++ b/tools/virsh-util.h @@ -46,6 +46,9 @@ virshDomainFree(virDomainPtr dom); void virshDomainSnapshotFree(virDomainSnapshotPtr snap); +void +virshDomainBackupFree(virDomainBackupPtr backup); + int virshDomainState(vshControl *ctl, virDomainPtr dom, diff --git a/tools/virsh.c b/tools/virsh.c index 90f8125..f68edc3 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 @@ -844,6 +845,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 9e42ef9..d7a192b 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" diff --git a/tools/virsh.pod b/tools/virsh.pod index cd1f25f..cdd90e2 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -4434,6 +4434,26 @@ the data contents from that point in time. =back +=head1 BACKUP COMMANDS + +Commands in this section manipulate domain backups. Backup is a state of domain +some disks at particualar moment in time (namely at the time of the backup +creation). While the same goal can be archived with the help of domain +snapshots backup operation is less intrusive - one don't need to merge top +image after backup is done. + +=over 4 + +=item B<backup-create> I<domain> I<xmlfile> + +Start a backup creation for the domain disks specified in I<xmlfile>. Backup +status can be tracked thru blockjob events. The backup progress can be +inspected by checking all of its blockjobs progress by calling B<blockjob> +command with the flag I<--info>. Backup can be cancelled by cancelling any of +its still active blockjobs calling B<blockjob> with the I<--abort> flag. + +=back + =head1 NWFILTER COMMANDS The following commands manipulate network filters. Network filters allow -- 1.8.3.1

--- docs/schemas/domainbackup.rng | 79 ++++++++++++++++++++++++++ tests/domainbackupxml/block_target.xml | 5 ++ tests/domainbackupxml/explicit_description.xml | 6 ++ tests/domainbackupxml/explicit_file_type.xml | 5 ++ tests/domainbackupxml/explicit_format.xml | 5 ++ tests/domainbackupxml/explicit_name.xml | 6 ++ tests/domainbackupxml/multi_disk.xml | 8 +++ tests/domainbackupxml/ref_by_path.xml | 5 ++ tests/virschematest.c | 1 + tools/virt-xml-validate.in | 5 +- 10 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 docs/schemas/domainbackup.rng create mode 100644 tests/domainbackupxml/block_target.xml create mode 100644 tests/domainbackupxml/explicit_description.xml create mode 100644 tests/domainbackupxml/explicit_file_type.xml create mode 100644 tests/domainbackupxml/explicit_format.xml create mode 100644 tests/domainbackupxml/explicit_name.xml create mode 100644 tests/domainbackupxml/multi_disk.xml create mode 100644 tests/domainbackupxml/ref_by_path.xml diff --git a/docs/schemas/domainbackup.rng b/docs/schemas/domainbackup.rng new file mode 100644 index 0000000..4a93dfb --- /dev/null +++ b/docs/schemas/domainbackup.rng @@ -0,0 +1,79 @@ +<?xml version="1.0"?> +<!-- A Relax NG schema for the libvirt domain backup properties XML format --> +<grammar xmlns="http://relaxng.org/ns/structure/1.0"> + <start> + <ref name='domainBackup'/> + </start> + + <include href='domaincommon.rng'/> + + <define name='domainBackup'> + <element name='domainbackup'> + <interleave> + <optional> + <element name='name'> + <text/> + </element> + </optional> + <optional> + <element name='description'> + <text/> + </element> + </optional> + <oneOrMore> + <ref name='diskBackup'/> + </oneOrMore> + </interleave> + </element> + </define> + + <define name='diskBackup'> + <element name='disk'> + <attribute name='name'> + <choice> + <ref name='diskTarget'/> + <ref name='absFilePath'/> + </choice> + </attribute> + <choice> + <ref name="backupTargetFile"/> + <ref name="backupTargetBlock"/> + </choice> + </element> + </define> + + <define name="backupTargetCommon"> + <optional> + <attribute name='format'> + <ref name='storageFormatBacking'/> + </attribute> + </optional> + </define> + + <define name="backupTargetFile"> + <optional> + <attribute name="type"> + <value>file</value> + </attribute> + </optional> + <element name="target"> + <attribute name="file"> + <ref name="absFilePath"/> + </attribute> + <ref name="backupTargetCommon"/> + </element> + </define> + + <define name="backupTargetBlock"> + <attribute name="type"> + <value>block</value> + </attribute> + <element name="target"> + <attribute name="dev"> + <ref name="absFilePath"/> + </attribute> + <ref name="backupTargetCommon"/> + </element> + </define> + +</grammar> diff --git a/tests/domainbackupxml/block_target.xml b/tests/domainbackupxml/block_target.xml new file mode 100644 index 0000000..a7618d1 --- /dev/null +++ b/tests/domainbackupxml/block_target.xml @@ -0,0 +1,5 @@ +<domainbackup> + <disk name="sda" type="block"> + <target dev="/path/to/backup/dev"/> + </disk> +</domainbackup> diff --git a/tests/domainbackupxml/explicit_description.xml b/tests/domainbackupxml/explicit_description.xml new file mode 100644 index 0000000..ea378a4 --- /dev/null +++ b/tests/domainbackupxml/explicit_description.xml @@ -0,0 +1,6 @@ +<domainbackup> + <description>description</description> + <disk name="sda"> + <target file="/path/to/file"/> + </disk> +</domainbackup> diff --git a/tests/domainbackupxml/explicit_file_type.xml b/tests/domainbackupxml/explicit_file_type.xml new file mode 100644 index 0000000..cfc424f --- /dev/null +++ b/tests/domainbackupxml/explicit_file_type.xml @@ -0,0 +1,5 @@ +<domainbackup> + <disk name="sda" type="file"> + <target file="/path/to/backup/file"/> + </disk> +</domainbackup> diff --git a/tests/domainbackupxml/explicit_format.xml b/tests/domainbackupxml/explicit_format.xml new file mode 100644 index 0000000..3151f84 --- /dev/null +++ b/tests/domainbackupxml/explicit_format.xml @@ -0,0 +1,5 @@ +<domainbackup> + <disk name="sda" type="file"> + <target file="/path/to/backup/file" format="qcow2"/> + </disk> +</domainbackup> diff --git a/tests/domainbackupxml/explicit_name.xml b/tests/domainbackupxml/explicit_name.xml new file mode 100644 index 0000000..61f7731 --- /dev/null +++ b/tests/domainbackupxml/explicit_name.xml @@ -0,0 +1,6 @@ +<domainbackup> + <name>name</name> + <disk name="sda"> + <target file="/path/to/file"/> + </disk> +</domainbackup> diff --git a/tests/domainbackupxml/multi_disk.xml b/tests/domainbackupxml/multi_disk.xml new file mode 100644 index 0000000..2d1797f --- /dev/null +++ b/tests/domainbackupxml/multi_disk.xml @@ -0,0 +1,8 @@ +<domainbackup> + <disk name="sda"> + <target file="/path/to/backup/sda"/> + </disk> + <disk name="sdb"> + <target file="/path/to/backup/sdb"/> + </disk> +</domainbackup> diff --git a/tests/domainbackupxml/ref_by_path.xml b/tests/domainbackupxml/ref_by_path.xml new file mode 100644 index 0000000..496fc49 --- /dev/null +++ b/tests/domainbackupxml/ref_by_path.xml @@ -0,0 +1,5 @@ +<domainbackup> + <disk name="/path/to/disk/file"> + <target file="/path/to/backup/file"/> + </disk> +</domainbackup> diff --git a/tests/virschematest.c b/tests/virschematest.c index ffed217..c1624e6 100644 --- a/tests/virschematest.c +++ b/tests/virschematest.c @@ -226,6 +226,7 @@ mymain(void) DO_TEST_DIR("domaincaps.rng", "domaincapsschemadata"); DO_TEST_DIR("domainsnapshot.rng", "domainsnapshotxml2xmlin", "domainsnapshotxml2xmlout"); + DO_TEST_DIR("domainbackup.rng", "domainbackupxml"); DO_TEST_DIR("interface.rng", "interfaceschemadata"); DO_TEST_DIR("network.rng", "../src/network", "networkxml2xmlin", "networkxml2xmlout", "networkxml2confdata"); diff --git a/tools/virt-xml-validate.in b/tools/virt-xml-validate.in index 81fde4d..b890b22 100644 --- a/tools/virt-xml-validate.in +++ b/tools/virt-xml-validate.in @@ -56,9 +56,12 @@ fi if [ -z "$TYPE" ]; then ROOT=`xmllint --stream --debug "$XMLFILE" 2>/dev/null | grep "^0 1 " | awk '{ print $3 }'` case "$ROOT" in - *domainsnapshot*) # Must come first, since *domain* is a substring + *domainsnapshot*) # Must be above domain, since *domain* is a substring TYPE="domainsnapshot" ;; + *domainbackup*) # Must be above domain, since *domain* is a substring + TYPE="domainbackup" + ;; *domain*) TYPE="domain" ;; -- 1.8.3.1

--- docs/Makefile.am | 3 +++ docs/apibuild.py | 2 ++ docs/docs.html.in | 4 +++- docs/format.html.in | 1 + docs/formatbackup.html.in | 58 +++++++++++++++++++++++++++++++++++++++++++++++ docs/index.html.in | 3 ++- 6 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 docs/formatbackup.html.in diff --git a/docs/Makefile.am b/docs/Makefile.am index 7a10a50..98c1191 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -35,6 +35,7 @@ apihtml_generated = \ html/libvirt-libvirt-secret.html \ html/libvirt-libvirt-storage.html \ html/libvirt-libvirt-stream.html \ + html/libvirt-libvirt-domain-backup.html \ html/libvirt-virterror.html apipng = \ @@ -341,6 +342,7 @@ $(APIBUILD_STAMP): $(srcdir)/apibuild.py \ $(top_srcdir)/include/libvirt/libvirt-lxc.h \ $(top_srcdir)/include/libvirt/libvirt-qemu.h \ $(top_srcdir)/include/libvirt/libvirt-admin.h \ + $(top_srcdir)/include/libvirt/libvirt-domain-backup.h \ $(top_srcdir)/include/libvirt/virterror.h \ $(top_srcdir)/src/libvirt.c \ $(top_srcdir)/src/libvirt-domain-snapshot.c \ @@ -356,6 +358,7 @@ $(APIBUILD_STAMP): $(srcdir)/apibuild.py \ $(top_srcdir)/src/libvirt-lxc.c \ $(top_srcdir)/src/libvirt-qemu.c \ $(top_srcdir)/src/libvirt-admin.c \ + $(top_srcdir)/src/libvirt-domain-backup.c \ $(top_srcdir)/src/util/virerror.c \ $(top_srcdir)/src/util/virevent.c \ $(top_srcdir)/src/util/virtypedparam.c diff --git a/docs/apibuild.py b/docs/apibuild.py index 47f340c..9d8fd75 100755 --- a/docs/apibuild.py +++ b/docs/apibuild.py @@ -24,6 +24,7 @@ included_files = { "libvirt-common.h": "header with general libvirt API definitions", "libvirt-domain.h": "header with general libvirt API definitions", "libvirt-domain-snapshot.h": "header with general libvirt API definitions", + "libvirt-domain-backup.h": "header with general libvirt API definitions", "libvirt-event.h": "header with general libvirt API definitions", "libvirt-host.h": "header with general libvirt API definitions", "libvirt-interface.h": "header with general libvirt API definitions", @@ -37,6 +38,7 @@ included_files = { "libvirt.c": "Main interfaces for the libvirt library", "libvirt-domain.c": "Domain interfaces for the libvirt library", "libvirt-domain-snapshot.c": "Domain snapshot interfaces for the libvirt library", + "libvirt-domain-backup.c": "Domain backup interfaces for the libvirt library", "libvirt-host.c": "Host interfaces for the libvirt library", "libvirt-interface.c": "Interface interfaces for the libvirt library", "libvirt-network.c": "Network interfaces for the libvirt library", diff --git a/docs/docs.html.in b/docs/docs.html.in index 60489a0..76eb97e 100644 --- a/docs/docs.html.in +++ b/docs/docs.html.in @@ -77,7 +77,8 @@ <a href="formatdomaincaps.html">domain capabilities</a>, <a href="formatnode.html">node devices</a>, <a href="formatsecret.html">secrets</a>, - <a href="formatsnapshot.html">snapshots</a></dd> + <a href="formatsnapshot.html">snapshots</a>, + <a href="formatbackup.html">backups</a></dd> <dt><a href="uri.html">URI format</a></dt> <dd>The URI formats used for connecting to libvirt</dd> @@ -95,6 +96,7 @@ <a href="html/libvirt-libvirt-common.html">common</a>, <a href="html/libvirt-libvirt-domain.html">domain</a>, <a href="html/libvirt-libvirt-domain-snapshot.html">domain snapshot</a>, + <a href="html/libvirt-libvirt-domain-backup.html">domain backup</a>, <a href="html/libvirt-virterror.html">error</a>, <a href="html/libvirt-libvirt-event.html">event</a>, <a href="html/libvirt-libvirt-host.html">host</a>, diff --git a/docs/format.html.in b/docs/format.html.in index 41211a9..39fca5a 100644 --- a/docs/format.html.in +++ b/docs/format.html.in @@ -24,6 +24,7 @@ <li><a href="formatnode.html" shape="rect">Node devices</a></li> <li><a href="formatsecret.html" shape="rect">Secrets</a></li> <li><a href="formatsnapshot.html" shape="rect">Snapshots</a></li> + <li><a href="formatbackup.html" shape="rect">Backups</a></li> </ul> <h2>Command line validation</h2> diff --git a/docs/formatbackup.html.in b/docs/formatbackup.html.in new file mode 100644 index 0000000..07c7397 --- /dev/null +++ b/docs/formatbackup.html.in @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> + <body> + <h1>Backup XML format</h1> + + <pre><![CDATA[ +<domainbackup> + <disk name="sda" type="file"> + <target file="/path/to/backup/file.qcow2" format="qcow2"/> + </disk> + <disk name="sdb" type="block"> + <target file="/path/to/backup/dev"/> + </disk> +</domainbackup>]]></pre> + + <p>Backup xml specifies disks to be backed up. It is supported only by QEMU.</p> + + <dl> + <dt><code>disk</code></dt> + <dd>Specifies single disk backup. + <dl> + <dt><code>name</code></dt> + <dd>Mandatory attribute. It identifies the disk either by + <code><target dev='name'/></code> or unambiguously by + <code><source file='name'/></code> among domain + <a href="formatdomain.html#elementsDisks">disk devices</a>.</dd> + + <dt><code>type</code></dt> + <dd>Optional attribute. Possible values are <code>file</code> and + <code>block</code>. Default value is <code>file</code>. This element + specifies backup target backend type which is described further + in <code>target</code> child element. + </dd> + </dl> + </dd> + + <dt><code>target</code></dt> + <dd>Specifies disk backup target backend. + <dl> + <dt><code>file</code></dt> + <dd>This attribute is taken into account if parent element's <code>type</code> + attribute has value <code>file</code> and specifies absolute path to the backup + file.</dd> + + <dt><code>dev</code></dt> + <dd>This attribute is taken into account if parent element's <code>type</code> + has value <code>block</code> and specifies absolute path to the + backup device.</dd> + + <dt><code>format</code></dt> + <dd>Optional attribute that specifies the backup format. If ommitted + the backup format will be the same as the disk format.</dd> + </dl> + </dd> + </dl> + </body> +</html> diff --git a/docs/index.html.in b/docs/index.html.in index 31bd6e0..fbae864 100644 --- a/docs/index.html.in +++ b/docs/index.html.in @@ -68,7 +68,8 @@ <a href="formatdomaincaps.html" shape="rect">domain capabilities</a>, <a href="formatnode.html" shape="rect">node devices</a>, <a href="formatsecret.html" shape="rect">secrets</a>, - <a href="formatsnapshot.html" shape="rect">snapshots</a></dd> + <a href="formatsnapshot.html" shape="rect">snapshots</a>, + <a href="formatbackup.html" shape="rect">backups</a></dd> <dt><a href="http://wiki.libvirt.org">Wiki</a></dt> <dd>Read further community contributed content</dd> </dl> -- 1.8.3.1
participants (1)
-
Nikolay Shirokovskiy