[libvirt] [RFC PATCH 00/10] introduce push backups

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 API and remote/qemu implementation of backup creation and correspondent backup xml description 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. Another option is to make backup synchronus operation. AFAIU on this way we have to make backup asynchronus job and thus make all modifying commands unavailable during backup. This makes backup rather obtrusive operation which is not convinient. Backup xml desription follows closely snapshot one and is described in more details in definition patch [1]. Nikolay Shirokovskiy (10): api: add API to create backup add driver based implementation of backup API remote: add backup API qemu: monitor: add backup command misc: add backup block job type conf: add backup definition [1] qemu: add qemuDomainBackupCreateXML implementation qemu: check backup destination before start qemu: prepare backup destination virsh: add create backup command daemon/remote.c | 8 + 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 | 294 ++++++++++++++++++++++++++++++++ src/conf/backup_conf.h | 69 ++++++++ src/conf/domain_conf.c | 2 +- src/datatypes.c | 60 +++++++ src/datatypes.h | 29 ++++ src/driver-hypervisor.h | 6 + src/libvirt-domain-backup.c | 203 ++++++++++++++++++++++ src/libvirt_private.syms | 9 + src/libvirt_public.syms | 10 ++ src/qemu/qemu_conf.h | 1 + src/qemu/qemu_domain.c | 14 ++ src/qemu/qemu_domain.h | 2 + src/qemu/qemu_driver.c | 249 +++++++++++++++++++++++++++ src/qemu/qemu_monitor.c | 13 ++ src/qemu/qemu_monitor.h | 5 + src/qemu/qemu_monitor_json.c | 36 ++++ src/qemu/qemu_monitor_json.h | 6 + src/remote/remote_driver.c | 7 + src/remote/remote_protocol.x | 24 ++- src/rpc/gendispatch.pl | 29 +++- src/util/virerror.c | 6 + tools/Makefile.am | 1 + tools/virsh-backup.c | 101 +++++++++++ tools/virsh-backup.h | 29 ++++ tools/virsh-domain.c | 3 +- tools/virsh.c | 2 + tools/virsh.h | 1 + 37 files changed, 1290 insertions(+), 11 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 -- 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..e2761cb --- /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) 2016 Virtuozzo + * + * 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

--- I wonder should I better name virDomainBackupCreateXML virDomainBackupStartPush as operation is asynchronous. include/libvirt/virterror.h | 2 + src/Makefile.am | 2 + src/datatypes.c | 24 ++++++ src/datatypes.h | 27 ++++++ src/driver-hypervisor.h | 6 ++ src/libvirt-domain-backup.c | 203 ++++++++++++++++++++++++++++++++++++++++++++ src/libvirt_private.syms | 1 + src/libvirt_public.syms | 10 +++ src/util/virerror.c | 6 ++ 9 files changed, 281 insertions(+) create mode 100644 src/libvirt-domain-backup.c diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h index efe83aa..bcd54bc 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 @@ -317,6 +318,7 @@ typedef enum { VIR_ERR_NO_CLIENT = 96, /* Client was not found */ VIR_ERR_AGENT_UNSYNCED = 97, /* guest agent replies with wrong id to guest-sync command */ + VIR_ERR_INVALID_DOMAIN_BACKUP = 98, /* invalid domain backup */ } virErrorNumber; /** diff --git a/src/Makefile.am b/src/Makefile.am index 8ee5567..eb68728 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 \ @@ -2390,6 +2391,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 ff0c46f..b090b12 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); @@ -889,6 +892,27 @@ virDomainSnapshotDispose(void *obj) } +/** + * 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 2b6adb4..59edda8 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; @@ -278,6 +279,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 @@ -661,6 +677,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 diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h index 51af732..5ea1dfd 100644 --- a/src/driver-hypervisor.h +++ b/src/driver-hypervisor.h @@ -1251,6 +1251,11 @@ typedef int int state, unsigned int flags); +typedef virDomainBackupPtr +(*virDrvDomainBackupCreateXML)(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags); + typedef struct _virHypervisorDriver virHypervisorDriver; typedef virHypervisorDriver *virHypervisorDriverPtr; @@ -1489,6 +1494,7 @@ struct _virHypervisorDriver { virDrvDomainMigrateStartPostCopy domainMigrateStartPostCopy; virDrvDomainGetGuestVcpus domainGetGuestVcpus; virDrvDomainSetGuestVcpus domainSetGuestVcpus; + virDrvDomainBackupCreateXML domainBackupCreateXML; }; diff --git a/src/libvirt-domain-backup.c b/src/libvirt-domain-backup.c new file mode 100644 index 0000000..f07f9cd --- /dev/null +++ b/src/libvirt-domain-backup.c @@ -0,0 +1,203 @@ +/* + * libvirt-domain-backup.c: entry points for virDomainBackupPtr APIs + + * Copyright (C) 2016 Virtuozzo + * + * 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 a new backup of a domain based on the xml + * description in xmlDesc. + * + * Starts backup blockjob on exactly that disks that specified + * in description. Backup is finished when all the mentioned blockjobs + * are finished. + * + * 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 74dd527..c85602c 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1004,6 +1004,7 @@ virConnectCloseCallbackDataClass; virConnectCloseCallbackDataGetCallback; virConnectCloseCallbackDataRegister; virConnectCloseCallbackDataUnregister; +virDomainBackupClass; virDomainClass; virDomainSnapshotClass; virGetConnect; diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index e01604c..b87d04f 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -746,4 +746,14 @@ LIBVIRT_2.2.0 { virConnectNodeDeviceEventDeregisterAny; } LIBVIRT_2.0.0; +LIBVIRT_2.5.0 { + global: + virDomainBackupRef; + virDomainBackupFree; + virDomainBackupGetName; + virDomainBackupGetDomain; + virDomainBackupGetConnect; + virDomainBackupCreateXML; +} LIBVIRT_2.2.0; + # .... define new API here using predicted next version number .... diff --git a/src/util/virerror.c b/src/util/virerror.c index 2958308..448b4c5 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", ) @@ -1399,6 +1400,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; } return errmsg; -- 1.8.3.1

--- daemon/remote.c | 8 ++++++++ src/access/viraccessperm.c | 3 ++- src/access/viraccessperm.h | 6 ++++++ src/datatypes.c | 36 ++++++++++++++++++++++++++++++++++++ src/datatypes.h | 2 ++ src/remote/remote_driver.c | 7 +++++++ src/remote/remote_protocol.x | 24 +++++++++++++++++++++++- src/rpc/gendispatch.pl | 29 ++++++++++++++++++++++------- 8 files changed, 106 insertions(+), 9 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index e414f92..90cbb01 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, @@ -6733,6 +6734,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/datatypes.c b/src/datatypes.c index b090b12..86dfba3 100644 --- a/src/datatypes.c +++ b/src/datatypes.c @@ -893,6 +893,42 @@ 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 * diff --git a/src/datatypes.h b/src/datatypes.h index 59edda8..577a2a0 100644 --- a/src/datatypes.h +++ b/src/datatypes.h @@ -726,6 +726,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/remote/remote_driver.c b/src/remote/remote_driver.c index a3cd7cd..357ffc0 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -147,6 +147,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); @@ -7865,6 +7866,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 @@ -8164,6 +8170,7 @@ static virHypervisorDriver hypervisor_driver = { .domainMigrateStartPostCopy = remoteDomainMigrateStartPostCopy, /* 1.3.3 */ .domainGetGuestVcpus = remoteDomainGetGuestVcpus, /* 2.0.0 */ .domainSetGuestVcpus = remoteDomainSetGuestVcpus, /* 2.0.0 */ + .domainBackupCreateXML = remoteDomainBackupCreateXML, /* 2.5.0 */ }; static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index e8382dc..5f58abe 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; @@ -2679,6 +2685,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; @@ -5934,5 +5950,11 @@ 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_CREATE_XML = 378 }; 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

--- This patch series do not use speed, bitmap and reuse arguments but we definitely want to use them later. Speed and reuse have the same meaning as in case of snapshots for example. Bitmap argument is used for incremental backups. Thus as supporting these flags is trivial delegation and arguments appear in many places let's add them all right now to make code history cleaner. src/qemu/qemu_monitor.c | 13 +++++++++++++ src/qemu/qemu_monitor.h | 5 +++++ src/qemu/qemu_monitor_json.c | 34 ++++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 6 ++++++ 4 files changed, 58 insertions(+) diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index a5e14b2..f4f89d6 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -4033,3 +4033,16 @@ qemuMonitorGetRTCTime(qemuMonitorPtr mon, return qemuMonitorJSONGetRTCTime(mon, tm); } + + +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 c3133c4..e6a33a8 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -1008,4 +1008,9 @@ int qemuMonitorMigrateStartPostCopy(qemuMonitorPtr mon); int qemuMonitorGetRTCTime(qemuMonitorPtr mon, struct tm *tm); +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 6c13832..475c7eb 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -7278,3 +7278,37 @@ qemuMonitorJSONGetHotpluggableCPUs(qemuMonitorPtr mon, 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 77b2e02..784350d 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -500,4 +500,10 @@ int qemuMonitorJSONGetHotpluggableCPUs(qemuMonitorPtr mon, struct qemuMonitorQueryHotpluggableCpusEntry **entries, size_t *nentries) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); + +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

--- I wonder should we add 'push' part to job type name having in mind that there are pull backups too and these two have different completion type. Looks like we have for the same reason 'commit' and 'active commit'. Or just don't bother as we can add a proper synonym lately. 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 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.h b/include/libvirt/libvirt-domain.h index 5f50660..c668d2a 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -2051,6 +2051,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 a233c0c..19b0b7d 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -839,7 +839,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/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 475c7eb..65fda4e 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: diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 184f64d..aaa17e0 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -2504,7 +2504,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> <disks> <disk type="file" name="sda"> <target file="/path/to/backup/file.qcow2"/> <driver type="qcow2"/> </disk> </disks> </domainbackup> 1. <name> element is optional. 2. 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. 3. <target> element has same attributes and its values as <source> element in domain disk description. 4. <driver> element is optional. It specifies backup target format. If it is omitted that target format is same as domain disks format. 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. --- A few more notes. 1. introducing virDomainBackupDefParseNode is future proof. We definitely want to support backups in test driver and its infrastructure requires such a function. 2. domain, snapshot xmls use <source> element to specify disk images etc. However this naming is rather unexpected in case of backups. Let's use <target>. <source> choice is biased towards domain disk representation. po/POTFILES.in | 1 + src/Makefile.am | 1 + src/conf/backup_conf.c | 259 +++++++++++++++++++++++++++++++++++++++++++++++ src/conf/backup_conf.h | 65 ++++++++++++ src/libvirt_private.syms | 6 ++ src/qemu/qemu_conf.h | 1 + 6 files changed, 333 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 1469240..2124eb0 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 diff --git a/src/Makefile.am b/src/Makefile.am index eb68728..c04e72c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -335,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 diff --git a/src/conf/backup_conf.c b/src/conf/backup_conf.c new file mode 100644 index 0000000..f8111e9 --- /dev/null +++ b/src/conf/backup_conf.c @@ -0,0 +1,259 @@ +/* + * backup_conf.c: domain backup XML processing + * + * Copyright (C) 2016 Virtuozzo + * + * 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(./driver/@type)", 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("./disks/*", 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..223fc19 --- /dev/null +++ b/src/conf/backup_conf.h @@ -0,0 +1,65 @@ +/* + * backup_conf.h: domain backup XML processing + * + * Copyright (C) 2016 Virtuozzo + * + * 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 c85602c..beeee9b 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 12b2661..b322ee6 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

Patch adds backup of active domains to files and block devices. Option to specify backup file format is available. --- Using blockjob flag is not complete consistent - on libvirt crash/restart the flag will be lost. However this is not the problem of this particular patch or place. src/conf/backup_conf.c | 35 +++++++++++++ src/conf/backup_conf.h | 4 ++ src/libvirt_private.syms | 2 + src/qemu/qemu_domain.c | 14 +++++ src/qemu/qemu_domain.h | 2 + src/qemu/qemu_driver.c | 129 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 186 insertions(+) diff --git a/src/conf/backup_conf.c b/src/conf/backup_conf.c index f8111e9..b1203b5 100644 --- a/src/conf/backup_conf.c +++ b/src/conf/backup_conf.c @@ -257,3 +257,38 @@ virDomainBackupDefFree(virDomainBackupDefPtr def) VIR_FREE(def); } + +int +virDomainBackupDefCheckDisks(virDomainBackupDefPtr def, + virDomainDefPtr vmdef) +{ + int ret = -1; + virBitmapPtr map = NULL; + size_t i; + + if (!(map = virBitmapNew(vmdef->ndisks))) + return -1; + + for (i = 0; i < def->ndisks; i++) { + virDomainBackupDiskDefPtr disk = &def->disks[i]; + int idx = virDomainDiskIndexByName(vmdef, 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)); + } + ret = 0; + + cleanup: + virBitmapFree(map); + return ret; +} diff --git a/src/conf/backup_conf.h b/src/conf/backup_conf.h index 223fc19..d99926b 100644 --- a/src/conf/backup_conf.h +++ b/src/conf/backup_conf.h @@ -62,4 +62,8 @@ virDomainBackupDefParseNode(xmlDocPtr xml, void virDomainBackupDefFree(virDomainBackupDefPtr def); +int +virDomainBackupDefCheckDisks(virDomainBackupDefPtr def, + virDomainDefPtr vmdef); + #endif /* __BACKUP_CONF_H */ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index beeee9b..046941f 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -43,6 +43,7 @@ virAccessPermStorageVolTypeToString; # conf/backup_conf.h +virDomainBackupDefCheckDisks; virDomainBackupDefFree; virDomainBackupDefParseNode; virDomainBackupDefParseString; @@ -1015,6 +1016,7 @@ virDomainClass; virDomainSnapshotClass; virGetConnect; virGetDomain; +virGetDomainBackup; virGetDomainSnapshot; virGetInterface; virGetNetwork; diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 8cba755..55dba34 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -6428,6 +6428,20 @@ qemuDomainDiskByName(virDomainDefPtr def, } +virDomainDiskDefPtr +qemuDomainDiskByNameChecked(virDomainDefPtr def, + const char *name) +{ + int idx = virDomainDiskIndexByName(def, name, false); + if (idx < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, _("no disk named '%s'"), name); + return NULL; + } + + return def->disks[idx]; +} + + /** * qemuDomainDefValidateDiskLunSource: * @src: disk source struct diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 2ee1829..1b5a21b 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -700,6 +700,8 @@ int qemuDomainSetPrivatePaths(virQEMUDriverPtr driver, void qemuDomainClearPrivatePaths(virDomainObjPtr vm); virDomainDiskDefPtr qemuDomainDiskByName(virDomainDefPtr def, const char *name); +virDomainDiskDefPtr qemuDomainDiskByNameChecked(virDomainDefPtr def, + const char *name); char *qemuDomainGetMasterKeyFilePath(const char *libDir); diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 38c8414..35f5b65 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -20261,6 +20261,134 @@ qemuDomainSetGuestVcpus(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 (virDomainBackupDefCheckDisks(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; + const char *format_str = NULL; + + if (!(disk = qemuDomainDiskByNameChecked(vm->def, def->disks[i].name))) + goto cleanup; + + 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; + } else if (target->type == VIR_STORAGE_TYPE_BLOCK) { + int format = target->format ? target->format : disk->src->format; + + /* qemu itself does not make this check */ + if (format != VIR_STORAGE_FILE_RAW) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("disk '%s' backup to block device is only" + " possible in raw format"), + def->disks[i].name); + 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++) { + virDomainDiskDefPtr disk; + /* NULL is internal error, that is a bug, at least we get it in log */ + if (!(disk = qemuDomainDiskByNameChecked(vm->def, def->disks[i].name))) + continue; + + QEMU_DOMAIN_DISK_PRIVATE(disk)->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 */ @@ -20474,6 +20602,7 @@ static virHypervisorDriver qemuHypervisorDriver = { .domainMigrateStartPostCopy = qemuDomainMigrateStartPostCopy, /* 1.3.3 */ .domainGetGuestVcpus = qemuDomainGetGuestVcpus, /* 2.0.0 */ .domainSetGuestVcpus = qemuDomainSetGuestVcpus, /* 2.0.0 */ + .domainBackupCreateXML = qemuDomainBackupCreateXML, /* 2.5.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 it is present. --- I decided to use switches instead of more compact if branches as in other places so that the code will noticeably break when this function will be called for storage types other then file or block. src/qemu/qemu_driver.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 17055b2..a606058 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -20261,6 +20261,72 @@ qemuDomainSetGuestVcpus(virDomainPtr dom, } +static int +qemuDomainBackupPrepareDisk(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, @@ -20336,6 +20402,9 @@ qemuDomainBackupCreateXML(virDomainPtr domain, if (qemuDomainDiskBlockJobIsActive(disk)) goto cleanup; + if (qemuDomainBackupPrepareDisk(&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. --- src/qemu/qemu_driver.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index a606058..c7f1b4f 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -20261,8 +20261,17 @@ qemuDomainSetGuestVcpus(virDomainPtr dom, } +typedef struct _qemuDomainBackupDiskTrackInfo qemuDomainBackupDiskTrackInfo; +typedef qemuDomainBackupDiskTrackInfo *qemuDomainBackupDiskTrackInfoPtr; +struct _qemuDomainBackupDiskTrackInfo { + bool created; + bool prepared; +}; + + static int -qemuDomainBackupPrepareDisk(virDomainBackupDiskDefPtr disk) +qemuDomainBackupPrepareDisk(virDomainBackupDiskDefPtr disk, + qemuDomainBackupDiskTrackInfoPtr track) { int ret = -1; struct stat st; @@ -20280,6 +20289,12 @@ qemuDomainBackupPrepareDisk(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; + } + track->created = true; break; case VIR_STORAGE_TYPE_BLOCK: virReportError(VIR_ERR_INVALID_ARG, @@ -20327,6 +20342,22 @@ qemuDomainBackupPrepareDisk(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, @@ -20339,6 +20370,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; @@ -20371,6 +20403,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; @@ -20402,9 +20437,14 @@ qemuDomainBackupCreateXML(virDomainPtr domain, if (qemuDomainDiskBlockJobIsActive(disk)) goto cleanup; - if (qemuDomainBackupPrepareDisk(&def->disks[i]) < 0) + if (qemuDomainBackupPrepareDisk(&def->disks[i], &track_disks[i]) < 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; @@ -20442,11 +20482,23 @@ qemuDomainBackupCreateXML(virDomainPtr domain, } cleanup: + if (!ret && def && 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 | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh-backup.h | 29 +++++++++++++++ tools/virsh.c | 2 + tools/virsh.h | 1 + 6 files changed, 135 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 2124eb0..5695498 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -283,6 +283,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 319abb2..7670509 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..1dde326 --- /dev/null +++ b/tools/virsh-backup.c @@ -0,0 +1,101 @@ +/* + * virsh-backup.c: Commands to manage domain backup + * + * Copyright (C) 2016 Virtuozzo + * + * 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 "virsh-domain.h" +#include "viralloc.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); + if (backup) + virDomainBackupFree(backup); + if (dom) + virDomainFree(dom); + + 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..a155626 --- /dev/null +++ b/tools/virsh-backup.h @@ -0,0 +1,29 @@ +/* + * virsh-backup.h: Commands to manage domain backup + * + * Copyright (C) 2016 Virtuozzo + * + * 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.c b/tools/virsh.c index 1068447..2df1103 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 @@ -876,6 +877,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

On Mon, Nov 14, 2016 at 10:14:52AM +0300, Nikolay Shirokovskiy wrote:
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 seems interesting. I'm sorry for bringing this old thread to life even when it recieved no pings. Let me know if this is not in your interests anymore.
This patch series basically adds API and remote/qemu implementation of backup creation and correspondent backup xml description definition.
Looks very similar to snapshots from libvirt's POV. I don't really have any experience with qemu backups, so this might be a bit stupid question. But is the only difference between backup and snapshot the fact that after backup, the domain still runs on top of the original file, whereas in snapshots it runs on the new one?
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. Another option is to make backup synchronus operation. AFAIU on this way we have to make backup asynchronus job and thus make all modifying commands unavailable during backup. This makes backup rather obtrusive operation which is not convinient.
Sync will block everything, async will block everything excepts query (by default, you can change the mask of course). We should be aiming at async, otherwise you can't even do virDomainJobGetInfo().
Backup xml desription follows closely snapshot one and is described in more details in definition patch [1].
You said you didn't use <source/> as snapshots do, but you are thinking of source/target in the host, however libvirt uses the naming for host/guest-side representations respectively. So since the disk is on host side, <source/> actually makes sense (even though it's, technically a destination). How much of snapshot code could we re-use? Again, sorry for respawning old thread if it's already been obsoleted. Martin

On 12.01.2017 16:16, Martin Kletzander wrote:
On Mon, Nov 14, 2016 at 10:14:52AM +0300, Nikolay Shirokovskiy wrote:
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 seems interesting. I'm sorry for bringing this old thread to life even when it recieved no pings. Let me know if this is not in your interests anymore.
It is not that old ))) and I has been looking forward for a review all this time. So I hope your response brings more attention.
This patch series basically adds API and remote/qemu implementation of backup creation and correspondent backup xml description definition.
Looks very similar to snapshots from libvirt's POV. I don't really have any experience with qemu backups, so this might be a bit stupid question. But is the only difference between backup and snapshot the fact that after backup, the domain still runs on top of the original file, whereas in snapshots it runs on the new one?
The mechanics is quite different from snapshot one too. Disk only snapshot is basically a creation of new disk image file with old one as backing file and switching running qemu process to the new image, it takes very little time generally. Backup on the other hand is lenghy process. Qemu keeps running on the same image but creates kind of inverse in memory snapshot to keep disk state at the moment of backup start. This disk state is written to disk as backup.
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. Another option is to make backup synchronus operation. AFAIU on this way we have to make backup asynchronus job and thus make all modifying commands unavailable during backup. This makes backup rather obtrusive operation which is not convinient.
Sync will block everything, async will block everything excepts query (by default, you can change the mask of course). We should be aiming at async, otherwise you can't even do virDomainJobGetInfo().
Well good point, but then you can not use virDomainSetMemoryFlags (QEMU_JOB_MODIFY job) on a domain running backup for example. This can be inconvinient as backup expected to be rather transparent. On the other hand job types can be revised to allow modifications to domain running a backup.
Backup xml desription follows closely snapshot one and is described in more details in definition patch [1].
You said you didn't use <source/> as snapshots do, but you are thinking of source/target in the host, however libvirt uses the naming for host/guest-side representations respectively. So since the disk is on host side, <source/> actually makes sense (even though it's, technically
ok, i'll use <source>
a destination). How much of snapshot code could we re-use?
Well I read the snapshot code to some extent to find out similar cases like disks labeling etc. From my POV in general there is no much common for snapshot and backup code. Parts that can be reused are sparse and need extra preparation so I would not put this work in this patch series. Nikolay

By the way we came to agreement to create distinct API to support backup operations in [1] and there is a discussion of pull backup series in [2]. The latter is blocked as some necessary operations are still experimental in qemu. [1] https://www.redhat.com/archives/libvir-list/2016-March/msg00937.html [2] https://www.redhat.com/archives/libvir-list/2016-September/msg00192.html On 18.01.2017 12:18, Nikolay Shirokovskiy wrote:
On 12.01.2017 16:16, Martin Kletzander wrote:
On Mon, Nov 14, 2016 at 10:14:52AM +0300, Nikolay Shirokovskiy wrote:
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 seems interesting. I'm sorry for bringing this old thread to life even when it recieved no pings. Let me know if this is not in your interests anymore.
It is not that old ))) and I has been looking forward for a review all this time. So I hope your response brings more attention.
This patch series basically adds API and remote/qemu implementation of backup creation and correspondent backup xml description definition.
Looks very similar to snapshots from libvirt's POV. I don't really have any experience with qemu backups, so this might be a bit stupid question. But is the only difference between backup and snapshot the fact that after backup, the domain still runs on top of the original file, whereas in snapshots it runs on the new one?
The mechanics is quite different from snapshot one too. Disk only snapshot is basically a creation of new disk image file with old one as backing file and switching running qemu process to the new image, it takes very little time generally.
Backup on the other hand is lenghy process. Qemu keeps running on the same image but creates kind of inverse in memory snapshot to keep disk state at the moment of backup start. This disk state is written to disk as backup.
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. Another option is to make backup synchronus operation. AFAIU on this way we have to make backup asynchronus job and thus make all modifying commands unavailable during backup. This makes backup rather obtrusive operation which is not convinient.
Sync will block everything, async will block everything excepts query (by default, you can change the mask of course). We should be aiming at async, otherwise you can't even do virDomainJobGetInfo().
Well good point, but then you can not use virDomainSetMemoryFlags (QEMU_JOB_MODIFY job) on a domain running backup for example. This can be inconvinient as backup expected to be rather transparent. On the other hand job types can be revised to allow modifications to domain running a backup.
Backup xml desription follows closely snapshot one and is described in more details in definition patch [1].
You said you didn't use <source/> as snapshots do, but you are thinking of source/target in the host, however libvirt uses the naming for host/guest-side representations respectively. So since the disk is on host side, <source/> actually makes sense (even though it's, technically
ok, i'll use <source>
a destination). How much of snapshot code could we re-use?
Well I read the snapshot code to some extent to find out similar cases like disks labeling etc. From my POV in general there is no much common for snapshot and backup code. Parts that can be reused are sparse and need extra preparation so I would not put this work in this patch series.
Nikolay
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

On Wed, Jan 18, 2017 at 01:21:48PM +0300, Nikolay Shirokovskiy wrote:
By the way we came to agreement to create distinct API to support backup operations in [1] and there is a discussion of pull backup series in [2]. The latter is blocked as some necessary operations are still experimental in qemu.
[1] https://www.redhat.com/archives/libvir-list/2016-March/msg00937.html [2] https://www.redhat.com/archives/libvir-list/2016-September/msg00192.html
I went through the conversations and I feel quite better about understanding the code, would you mind sending a new version (no matter whether it's an RFC or not) to the list so it's applicable (and hopefully will draw more attention)?

08-Feb-17 17:52, Martin Kletzander пишет:
On Wed, Jan 18, 2017 at 01:21:48PM +0300, Nikolay Shirokovskiy wrote:
By the way we came to agreement to create distinct API to support backup operations in [1] and there is a discussion of pull backup series in [2]. The latter is blocked as some necessary operations are still experimental in qemu.
[1] https://www.redhat.com/archives/libvir-list/2016-March/msg00937.html [2] https://www.redhat.com/archives/libvir-list/2016-September/msg00192.html
I went through the conversations and I feel quite better about understanding the code, would you mind sending a new version (no matter whether it's an RFC or not) to the list so it's applicable (and hopefully will draw more attention)?
Just to have more context. The continuation of the thread [1] went to https://www.redhat.com/archives/libvir-list/2016-April/msg00401.html

On Wed, Feb 08, 2017 at 06:11:30PM +0300, Maxim Nestratov wrote:
08-Feb-17 17:52, Martin Kletzander пишет:
On Wed, Jan 18, 2017 at 01:21:48PM +0300, Nikolay Shirokovskiy wrote:
By the way we came to agreement to create distinct API to support backup operations in [1] and there is a discussion of pull backup series in [2]. The latter is blocked as some necessary operations are still experimental in qemu.
[1] https://www.redhat.com/archives/libvir-list/2016-March/msg00937.html [2] https://www.redhat.com/archives/libvir-list/2016-September/msg00192.html
I went through the conversations and I feel quite better about understanding the code, would you mind sending a new version (no matter whether it's an RFC or not) to the list so it's applicable (and hopefully will draw more attention)?
Just to have more context. The continuation of the thread [1] went to https://www.redhat.com/archives/libvir-list/2016-April/msg00401.html
Of course, with the great mailman =) Thanks for pointing that out ;)

On 08.02.2017 17:52, Martin Kletzander wrote:
On Wed, Jan 18, 2017 at 01:21:48PM +0300, Nikolay Shirokovskiy wrote:
By the way we came to agreement to create distinct API to support backup operations in [1] and there is a discussion of pull backup series in [2]. The latter is blocked as some necessary operations are still experimental in qemu.
[1] https://www.redhat.com/archives/libvir-list/2016-March/msg00937.html [2] https://www.redhat.com/archives/libvir-list/2016-September/msg00192.html
I went through the conversations and I feel quite better about understanding the code, would you mind sending a new version (no matter whether it's an RFC or not) to the list so it's applicable (and hopefully will draw more attention)?
Sorry, I was rather busy latery, I will resend as I find time. Nikolay
participants (3)
-
Martin Kletzander
-
Maxim Nestratov
-
Nikolay Shirokovskiy