[libvirt] [PATCH 0/6]: Domain Snapshot API

Hello, Here is the next set of patches for the snapshot API. This set is much more complete, and implements the virDomainRevertToSnapshot() semantic instead of virDomainCreateFromSnapshot() that we discussed earlier. I've also incorporated all of the comments from the review of the first round of patches. Aside from implementation bugs, I consider this patchset complete. The only thing missing from the patches is real documentation along the lines of the API docs that we had discussed on-list. Next week I'll followup with some additions to the documentation that explain the semantics of these calls. Again review and comments are welcome. Chris Lalancette

Signed-off-by: Chris Lalancette <clalance@redhat.com> --- daemon/remote.c | 311 ++++++++++++++++++++++++ daemon/remote_dispatch_args.h | 9 + daemon/remote_dispatch_prototypes.h | 72 ++++++ daemon/remote_dispatch_ret.h | 7 + daemon/remote_dispatch_table.h | 45 ++++ include/libvirt/libvirt.h.in | 62 +++++ include/libvirt/virterror.h | 5 +- python/generator.py | 3 + python/typewrappers.c | 15 ++ python/typewrappers.h | 10 + src/datatypes.c | 122 ++++++++++ src/datatypes.h | 25 ++ src/driver.h | 47 ++++ src/esx/esx_driver.c | 9 + src/libvirt.c | 456 +++++++++++++++++++++++++++++++++++ src/libvirt_private.syms | 1 + src/libvirt_public.syms | 10 + src/lxc/lxc_driver.c | 9 + src/opennebula/one_driver.c | 9 + src/openvz/openvz_driver.c | 9 + src/phyp/phyp_driver.c | 9 + src/qemu/qemu_driver.c | 9 + src/remote/remote_driver.c | 309 ++++++++++++++++++++++++ src/remote/remote_protocol.c | 181 ++++++++++++++ src/remote/remote_protocol.h | 145 +++++++++++ src/remote/remote_protocol.x | 97 ++++++++- src/test/test_driver.c | 9 + src/uml/uml_driver.c | 9 + src/util/virterror.c | 15 ++ src/vbox/vbox_tmpl.c | 9 + src/xen/xen_driver.c | 9 + src/xenapi/xenapi_driver.c | 9 + 32 files changed, 2044 insertions(+), 2 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index 67162d5..fd8ac32 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -67,6 +67,7 @@ static virStoragePoolPtr get_nonnull_storage_pool (virConnectPtr conn, remote_no static virStorageVolPtr get_nonnull_storage_vol (virConnectPtr conn, remote_nonnull_storage_vol vol); static virSecretPtr get_nonnull_secret (virConnectPtr conn, remote_nonnull_secret secret); static virNWFilterPtr get_nonnull_nwfilter (virConnectPtr conn, remote_nonnull_nwfilter nwfilter); +static virDomainSnapshotPtr get_nonnull_domain_snapshot (virConnectPtr conn, remote_nonnull_domain_snapshot snapshot); 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); @@ -75,6 +76,7 @@ static void make_nonnull_storage_vol (remote_nonnull_storage_vol *vol_dst, virSt static void make_nonnull_node_device (remote_nonnull_node_device *dev_dst, virNodeDevicePtr dev_src); 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); #include "remote_dispatch_prototypes.h" @@ -5766,6 +5768,298 @@ remoteDispatchDomainMigrateSetMaxDowntime(struct qemud_server *server ATTRIBUTE_ return 0; } +static int +remoteDispatchDomainSnapshotCreateXml (struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client ATTRIBUTE_UNUSED, + virConnectPtr conn, + remote_message_header *hdr ATTRIBUTE_UNUSED, + remote_error *rerr, + remote_domain_snapshot_create_xml_args *args, + remote_domain_snapshot_create_xml_ret *ret) +{ + virDomainSnapshotPtr snapshot; + virDomainPtr domain; + + domain = get_nonnull_domain(conn, args->domain); + if (domain == NULL) { + remoteDispatchConnError(rerr, conn); + return -1; + } + + snapshot = virDomainSnapshotCreateXML(domain, args->xml_desc, args->flags); + if (snapshot == NULL) { + virDomainFree(domain); + remoteDispatchConnError(rerr, conn); + return -1; + } + + make_nonnull_domain_snapshot(&ret->snap, snapshot); + + virDomainSnapshotFree(snapshot); + virDomainFree(domain); + + return 0; +} + +static int +remoteDispatchDomainSnapshotDumpXml (struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client ATTRIBUTE_UNUSED, + virConnectPtr conn, + remote_message_header *hdr ATTRIBUTE_UNUSED, + remote_error *rerr, + remote_domain_snapshot_dump_xml_args *args, + remote_domain_snapshot_dump_xml_ret *ret) +{ + virDomainSnapshotPtr snapshot; + + snapshot = get_nonnull_domain_snapshot(conn, args->snap); + if (snapshot == NULL) { + remoteDispatchConnError(rerr, conn); + return -1; + } + + /* remoteDispatchClientRequest will free this. */ + ret->xml = virDomainSnapshotGetXMLDesc(snapshot, args->flags); + if (!ret->xml) { + virDomainSnapshotFree(snapshot); + remoteDispatchConnError(rerr, conn); + return -1; + } + + virDomainSnapshotFree(snapshot); + + return 0; +} + +static int +remoteDispatchDomainSnapshotNum (struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client ATTRIBUTE_UNUSED, + virConnectPtr conn, + remote_message_header *hdr ATTRIBUTE_UNUSED, + remote_error *rerr, + remote_domain_snapshot_num_args *args, + remote_domain_snapshot_num_ret *ret) +{ + virDomainPtr domain; + + domain = get_nonnull_domain(conn, args->domain); + if (domain == NULL) { + remoteDispatchConnError(rerr, conn); + return -1; + } + + ret->num = virDomainSnapshotNum(domain, args->flags); + if (ret->num == -1) { + virDomainFree(domain); + remoteDispatchConnError(rerr, conn); + return -1; + } + + virDomainFree(domain); + + return 0; +} + +static int +remoteDispatchDomainSnapshotListNames (struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client ATTRIBUTE_UNUSED, + virConnectPtr conn, + remote_message_header *hdr ATTRIBUTE_UNUSED, + remote_error *rerr, + remote_domain_snapshot_list_names_args *args, + remote_domain_snapshot_list_names_ret *ret) +{ + virDomainPtr domain; + + if (args->nameslen > REMOTE_DOMAIN_SNAPSHOT_LIST_NAMES_MAX) { + remoteDispatchFormatError (rerr, "%s", + _("nameslen > REMOTE_DOMAIN_SNAPSHOT_LIST_NAMES_MAX")); + return -1; + } + + domain = get_nonnull_domain(conn, args->domain); + if (domain == NULL) { + remoteDispatchConnError(rerr, conn); + return -1; + } + + /* Allocate return buffer. */ + if (VIR_ALLOC_N(ret->names.names_val, args->nameslen) < 0) { + virDomainFree(domain); + remoteDispatchOOMError(rerr); + return -1; + } + + ret->names.names_len = virDomainSnapshotListNames(domain, + ret->names.names_val, + args->nameslen, + args->flags); + if (ret->names.names_len == -1) { + virDomainFree(domain); + VIR_FREE(ret->names.names_val); + remoteDispatchConnError(rerr, conn); + return -1; + } + + virDomainFree(domain); + + return 0; +} + +static int +remoteDispatchDomainSnapshotLookupByName (struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client ATTRIBUTE_UNUSED, + virConnectPtr conn, + remote_message_header *hdr ATTRIBUTE_UNUSED, + remote_error *rerr, + remote_domain_snapshot_lookup_by_name_args *args, + remote_domain_snapshot_lookup_by_name_ret *ret) +{ + virDomainSnapshotPtr snapshot; + virDomainPtr domain; + + domain = get_nonnull_domain(conn, args->domain); + if (domain == NULL) { + remoteDispatchConnError(rerr, conn); + return -1; + } + + snapshot = virDomainSnapshotLookupByName(domain, args->name, args->flags); + if (snapshot == NULL) { + virDomainFree(domain); + remoteDispatchConnError(rerr, conn); + return -1; + } + + make_nonnull_domain_snapshot (&ret->snap, snapshot); + + virDomainSnapshotFree(snapshot); + virDomainFree(domain); + + return 0; +} + +static int +remoteDispatchDomainHasCurrentSnapshot(struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client ATTRIBUTE_UNUSED, + virConnectPtr conn, + remote_message_header *hdr ATTRIBUTE_UNUSED, + remote_error *rerr, + remote_domain_has_current_snapshot_args *args, + remote_domain_has_current_snapshot_ret *ret) +{ + virDomainPtr domain; + int result; + + domain = get_nonnull_domain(conn, args->domain); + if (domain == NULL) { + remoteDispatchConnError(rerr, conn); + return -1; + } + + result = virDomainHasCurrentSnapshot(domain, args->flags); + if (result < 0) { + virDomainFree(domain); + remoteDispatchConnError(rerr, conn); + return -1; + } + + ret->result = result; + + virDomainFree(domain); + + return 0; +} + +static int +remoteDispatchDomainSnapshotCurrent(struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client ATTRIBUTE_UNUSED, + virConnectPtr conn, + remote_message_header *hdr ATTRIBUTE_UNUSED, + remote_error *rerr, + remote_domain_snapshot_current_args *args, + remote_domain_snapshot_current_ret *ret) +{ + virDomainSnapshotPtr snapshot; + virDomainPtr domain; + + domain = get_nonnull_domain(conn, args->domain); + if (domain == NULL) { + remoteDispatchConnError(rerr, conn); + return -1; + } + + snapshot = virDomainSnapshotCurrent(domain, args->flags); + if (snapshot == NULL) { + virDomainFree(domain); + remoteDispatchConnError(rerr, conn); + return -1; + } + + make_nonnull_domain_snapshot(&ret->snap, snapshot); + + virDomainSnapshotFree(snapshot); + virDomainFree(domain); + + return 0; +} + +static int +remoteDispatchDomainRevertToSnapshot (struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client ATTRIBUTE_UNUSED, + virConnectPtr conn, + remote_message_header *hdr ATTRIBUTE_UNUSED, + remote_error *rerr, + remote_domain_revert_to_snapshot_args *args, + void *ret ATTRIBUTE_UNUSED) +{ + virDomainSnapshotPtr snapshot; + + snapshot = get_nonnull_domain_snapshot(conn, args->snap); + if (snapshot == NULL) { + remoteDispatchConnError(rerr, conn); + return -1; + } + + if (virDomainRevertToSnapshot(snapshot, args->flags) == -1) { + virDomainSnapshotFree(snapshot); + remoteDispatchConnError(rerr, conn); + return -1; + } + + virDomainSnapshotFree(snapshot); + + return 0; +} + +static int +remoteDispatchDomainSnapshotDelete (struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client ATTRIBUTE_UNUSED, + virConnectPtr conn, + remote_message_header *hdr ATTRIBUTE_UNUSED, + remote_error *rerr, + remote_domain_snapshot_delete_args *args, + void *ret ATTRIBUTE_UNUSED) +{ + virDomainSnapshotPtr snapshot; + + snapshot = get_nonnull_domain_snapshot(conn, args->snap); + if (snapshot == NULL) { + remoteDispatchConnError(rerr, conn); + return -1; + } + + if (virDomainSnapshotDelete(snapshot, args->flags) == -1) { + virDomainSnapshotFree(snapshot); + remoteDispatchConnError(rerr, conn); + return -1; + } + + virDomainSnapshotFree(snapshot); + + return 0; +} + static int remoteDispatchDomainEventsRegisterAny (struct qemud_server *server ATTRIBUTE_UNUSED, @@ -6076,6 +6370,16 @@ get_nonnull_nwfilter (virConnectPtr conn, remote_nonnull_nwfilter nwfilter) return virGetNWFilter (conn, nwfilter.name, BAD_CAST nwfilter.uuid); } +static virDomainSnapshotPtr +get_nonnull_domain_snapshot (virConnectPtr conn, remote_nonnull_domain_snapshot snapshot) +{ + virDomainPtr domain; + domain = get_nonnull_domain(conn, snapshot.domain); + if (domain == NULL) + return NULL; + return virGetDomainSnapshot(domain, snapshot.name); +} + /* Make remote_nonnull_domain and remote_nonnull_network. */ static void make_nonnull_domain (remote_nonnull_domain *dom_dst, virDomainPtr dom_src) @@ -6135,3 +6439,10 @@ make_nonnull_nwfilter (remote_nonnull_nwfilter *nwfilter_dst, virNWFilterPtr nwf nwfilter_dst->name = strdup (nwfilter_src->name); memcpy (nwfilter_dst->uuid, nwfilter_src->uuid, VIR_UUID_BUFLEN); } + +static void +make_nonnull_domain_snapshot (remote_nonnull_domain_snapshot *snapshot_dst, virDomainSnapshotPtr snapshot_src) +{ + snapshot_dst->name = strdup(snapshot_src->name); + make_nonnull_domain(&snapshot_dst->domain, snapshot_src->domain); +} diff --git a/daemon/remote_dispatch_args.h b/daemon/remote_dispatch_args.h index b188e6e..cc49c31 100644 --- a/daemon/remote_dispatch_args.h +++ b/daemon/remote_dispatch_args.h @@ -151,3 +151,12 @@ remote_list_nwfilters_args val_remote_list_nwfilters_args; remote_nwfilter_define_xml_args val_remote_nwfilter_define_xml_args; remote_nwfilter_undefine_args val_remote_nwfilter_undefine_args; + remote_domain_snapshot_create_xml_args val_remote_domain_snapshot_create_xml_args; + remote_domain_snapshot_dump_xml_args val_remote_domain_snapshot_dump_xml_args; + remote_domain_snapshot_num_args val_remote_domain_snapshot_num_args; + remote_domain_snapshot_list_names_args val_remote_domain_snapshot_list_names_args; + remote_domain_snapshot_lookup_by_name_args val_remote_domain_snapshot_lookup_by_name_args; + remote_domain_has_current_snapshot_args val_remote_domain_has_current_snapshot_args; + remote_domain_snapshot_current_args val_remote_domain_snapshot_current_args; + remote_domain_revert_to_snapshot_args val_remote_domain_revert_to_snapshot_args; + remote_domain_snapshot_delete_args val_remote_domain_snapshot_delete_args; diff --git a/daemon/remote_dispatch_prototypes.h b/daemon/remote_dispatch_prototypes.h index e155c69..54ad5a7 100644 --- a/daemon/remote_dispatch_prototypes.h +++ b/daemon/remote_dispatch_prototypes.h @@ -282,6 +282,14 @@ static int remoteDispatchDomainGetVcpus( remote_error *err, remote_domain_get_vcpus_args *args, remote_domain_get_vcpus_ret *ret); +static int remoteDispatchDomainHasCurrentSnapshot( + struct qemud_server *server, + struct qemud_client *client, + virConnectPtr conn, + remote_message_header *hdr, + remote_error *err, + remote_domain_has_current_snapshot_args *args, + remote_domain_has_current_snapshot_ret *ret); static int remoteDispatchDomainInterfaceStats( struct qemud_server *server, struct qemud_client *client, @@ -434,6 +442,14 @@ static int remoteDispatchDomainResume( remote_error *err, remote_domain_resume_args *args, void *ret); +static int remoteDispatchDomainRevertToSnapshot( + struct qemud_server *server, + struct qemud_client *client, + virConnectPtr conn, + remote_message_header *hdr, + remote_error *err, + remote_domain_revert_to_snapshot_args *args, + void *ret); static int remoteDispatchDomainSave( struct qemud_server *server, struct qemud_client *client, @@ -490,6 +506,62 @@ static int remoteDispatchDomainShutdown( remote_error *err, remote_domain_shutdown_args *args, void *ret); +static int remoteDispatchDomainSnapshotCreateXml( + struct qemud_server *server, + struct qemud_client *client, + virConnectPtr conn, + remote_message_header *hdr, + remote_error *err, + remote_domain_snapshot_create_xml_args *args, + remote_domain_snapshot_create_xml_ret *ret); +static int remoteDispatchDomainSnapshotCurrent( + struct qemud_server *server, + struct qemud_client *client, + virConnectPtr conn, + remote_message_header *hdr, + remote_error *err, + remote_domain_snapshot_current_args *args, + remote_domain_snapshot_current_ret *ret); +static int remoteDispatchDomainSnapshotDelete( + struct qemud_server *server, + struct qemud_client *client, + virConnectPtr conn, + remote_message_header *hdr, + remote_error *err, + remote_domain_snapshot_delete_args *args, + void *ret); +static int remoteDispatchDomainSnapshotDumpXml( + struct qemud_server *server, + struct qemud_client *client, + virConnectPtr conn, + remote_message_header *hdr, + remote_error *err, + remote_domain_snapshot_dump_xml_args *args, + remote_domain_snapshot_dump_xml_ret *ret); +static int remoteDispatchDomainSnapshotListNames( + struct qemud_server *server, + struct qemud_client *client, + virConnectPtr conn, + remote_message_header *hdr, + remote_error *err, + remote_domain_snapshot_list_names_args *args, + remote_domain_snapshot_list_names_ret *ret); +static int remoteDispatchDomainSnapshotLookupByName( + struct qemud_server *server, + struct qemud_client *client, + virConnectPtr conn, + remote_message_header *hdr, + remote_error *err, + remote_domain_snapshot_lookup_by_name_args *args, + remote_domain_snapshot_lookup_by_name_ret *ret); +static int remoteDispatchDomainSnapshotNum( + struct qemud_server *server, + struct qemud_client *client, + virConnectPtr conn, + remote_message_header *hdr, + remote_error *err, + remote_domain_snapshot_num_args *args, + remote_domain_snapshot_num_ret *ret); static int remoteDispatchDomainSuspend( struct qemud_server *server, struct qemud_client *client, diff --git a/daemon/remote_dispatch_ret.h b/daemon/remote_dispatch_ret.h index b134f25..358bc58 100644 --- a/daemon/remote_dispatch_ret.h +++ b/daemon/remote_dispatch_ret.h @@ -125,3 +125,10 @@ remote_num_of_nwfilters_ret val_remote_num_of_nwfilters_ret; remote_list_nwfilters_ret val_remote_list_nwfilters_ret; remote_nwfilter_define_xml_ret val_remote_nwfilter_define_xml_ret; + remote_domain_snapshot_create_xml_ret val_remote_domain_snapshot_create_xml_ret; + remote_domain_snapshot_dump_xml_ret val_remote_domain_snapshot_dump_xml_ret; + remote_domain_snapshot_num_ret val_remote_domain_snapshot_num_ret; + remote_domain_snapshot_list_names_ret val_remote_domain_snapshot_list_names_ret; + remote_domain_snapshot_lookup_by_name_ret val_remote_domain_snapshot_lookup_by_name_ret; + remote_domain_has_current_snapshot_ret val_remote_domain_has_current_snapshot_ret; + remote_domain_snapshot_current_ret val_remote_domain_snapshot_current_ret; diff --git a/daemon/remote_dispatch_table.h b/daemon/remote_dispatch_table.h index 24756d7..96a30f9 100644 --- a/daemon/remote_dispatch_table.h +++ b/daemon/remote_dispatch_table.h @@ -912,3 +912,48 @@ .args_filter = (xdrproc_t) xdr_remote_nwfilter_undefine_args, .ret_filter = (xdrproc_t) xdr_void, }, +{ /* DomainSnapshotCreateXml => 182 */ + .fn = (dispatch_fn) remoteDispatchDomainSnapshotCreateXml, + .args_filter = (xdrproc_t) xdr_remote_domain_snapshot_create_xml_args, + .ret_filter = (xdrproc_t) xdr_remote_domain_snapshot_create_xml_ret, +}, +{ /* DomainSnapshotDumpXml => 183 */ + .fn = (dispatch_fn) remoteDispatchDomainSnapshotDumpXml, + .args_filter = (xdrproc_t) xdr_remote_domain_snapshot_dump_xml_args, + .ret_filter = (xdrproc_t) xdr_remote_domain_snapshot_dump_xml_ret, +}, +{ /* DomainSnapshotNum => 184 */ + .fn = (dispatch_fn) remoteDispatchDomainSnapshotNum, + .args_filter = (xdrproc_t) xdr_remote_domain_snapshot_num_args, + .ret_filter = (xdrproc_t) xdr_remote_domain_snapshot_num_ret, +}, +{ /* DomainSnapshotListNames => 185 */ + .fn = (dispatch_fn) remoteDispatchDomainSnapshotListNames, + .args_filter = (xdrproc_t) xdr_remote_domain_snapshot_list_names_args, + .ret_filter = (xdrproc_t) xdr_remote_domain_snapshot_list_names_ret, +}, +{ /* DomainSnapshotLookupByName => 186 */ + .fn = (dispatch_fn) remoteDispatchDomainSnapshotLookupByName, + .args_filter = (xdrproc_t) xdr_remote_domain_snapshot_lookup_by_name_args, + .ret_filter = (xdrproc_t) xdr_remote_domain_snapshot_lookup_by_name_ret, +}, +{ /* DomainHasCurrentSnapshot => 187 */ + .fn = (dispatch_fn) remoteDispatchDomainHasCurrentSnapshot, + .args_filter = (xdrproc_t) xdr_remote_domain_has_current_snapshot_args, + .ret_filter = (xdrproc_t) xdr_remote_domain_has_current_snapshot_ret, +}, +{ /* DomainSnapshotCurrent => 188 */ + .fn = (dispatch_fn) remoteDispatchDomainSnapshotCurrent, + .args_filter = (xdrproc_t) xdr_remote_domain_snapshot_current_args, + .ret_filter = (xdrproc_t) xdr_remote_domain_snapshot_current_ret, +}, +{ /* DomainRevertToSnapshot => 189 */ + .fn = (dispatch_fn) remoteDispatchDomainRevertToSnapshot, + .args_filter = (xdrproc_t) xdr_remote_domain_revert_to_snapshot_args, + .ret_filter = (xdrproc_t) xdr_void, +}, +{ /* DomainSnapshotDelete => 190 */ + .fn = (dispatch_fn) remoteDispatchDomainSnapshotDelete, + .args_filter = (xdrproc_t) xdr_remote_domain_snapshot_delete_args, + .ret_filter = (xdrproc_t) xdr_void, +}, diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 7cb483e..a9f434b 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -1374,6 +1374,7 @@ typedef enum { VIR_DOMAIN_EVENT_STARTED_BOOTED = 0, /* Normal startup from boot */ VIR_DOMAIN_EVENT_STARTED_MIGRATED = 1, /* Incoming migration from another host */ VIR_DOMAIN_EVENT_STARTED_RESTORED = 2, /* Restored from a state file */ + VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT = 3, /* Restored from snapshot */ } virDomainEventStartedDetailType; /** @@ -1410,6 +1411,7 @@ typedef enum { VIR_DOMAIN_EVENT_STOPPED_MIGRATED = 3, /* Migrated off to another host */ VIR_DOMAIN_EVENT_STOPPED_SAVED = 4, /* Saved to a state file */ VIR_DOMAIN_EVENT_STOPPED_FAILED = 5, /* Host emulator/mgmt failed */ + VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT = 6, /* offline snapshot loaded */ } virDomainEventStoppedDetailType; @@ -1861,6 +1863,66 @@ int virDomainGetJobInfo(virDomainPtr dom, virDomainJobInfoPtr info); int virDomainAbortJob(virDomainPtr dom); +/** + * virDomainSnapshot: + * + * a virDomainSnapshot is a private structure representing a snapshot of + * a domain. + */ +typedef struct _virDomainSnapshot virDomainSnapshot; + +/** + * virDomainSnapshotPtr: + * + * a virDomainSnapshotPtr is pointer to a virDomainSnapshot private structure, + * and is the type used to reference a domain snapshot in the API. + */ +typedef virDomainSnapshot *virDomainSnapshotPtr; + +/* Take a snapshot of the current VM state */ +virDomainSnapshotPtr virDomainSnapshotCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags); + +/* Dump the XML of a snapshot */ +char *virDomainSnapshotGetXMLDesc(virDomainSnapshotPtr snapshot, + unsigned int flags); + +/* Return the number of snapshots for this domain */ +int virDomainSnapshotNum(virDomainPtr domain, unsigned int flags); + +/* Get the names of all snapshots for this domain */ +int virDomainSnapshotListNames(virDomainPtr domain, char **names, int nameslen, + unsigned int flags); + +/* Get a handle to a named snapshot */ +virDomainSnapshotPtr virDomainSnapshotLookupByName(virDomainPtr domain, + const char *name, + unsigned int flags); + +/* Check whether a domain has a snapshot which is currently used */ +int virDomainHasCurrentSnapshot(virDomainPtr domain, unsigned flags); + +/* Get a handle to the current snapshot */ +virDomainSnapshotPtr virDomainSnapshotCurrent(virDomainPtr domain, + unsigned int flags); + +/* Revert the domain to a point-in-time snapshot. The + * state of the guest after this call will be the state + * of the guest when the snapshot in question was taken + */ +int virDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, + unsigned int flags); + +/* Deactivate a snapshot */ +typedef enum { + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN = (1 << 0), +} virDomainSnapshotDeleteFlags; + +int virDomainSnapshotDelete(virDomainSnapshotPtr snapshot, + unsigned int flags); + +int virDomainSnapshotFree(virDomainSnapshotPtr snapshot); /* A generic callback definition. Specific events usually have a customization * with extra parameters */ diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h index f69d07e..3bbb293 100644 --- a/include/libvirt/virterror.h +++ b/include/libvirt/virterror.h @@ -71,7 +71,8 @@ typedef enum { VIR_FROM_CPU, /* Error from CPU driver */ VIR_FROM_XENAPI, /* Error from XenAPI */ VIR_FROM_NWFILTER, /* Error from network filter driver */ - VIR_FROM_HOOK /* Error from Synchronous hooks */ + VIR_FROM_HOOK, /* Error from Synchronous hooks */ + VIR_FROM_DOMAIN_SNAPSHOT, /* Error from domain snapshot */ } virErrorDomain; @@ -183,6 +184,8 @@ typedef enum { VIR_ERR_MIGRATE_PERSIST_FAILED, /* a migration worked, but making the VM persist on the dest host failed */ VIR_ERR_HOOK_SCRIPT_FAILED, /* a synchronous hook script failed */ + VIR_ERR_INVALID_DOMAIN_SNAPSHOT, /* invalid domain snapshot */ + VIR_ERR_NO_DOMAIN_SNAPSHOT, /* domain snapshot not found */ } virErrorNumber; /** diff --git a/python/generator.py b/python/generator.py index a24e122..cb9f3d9 100755 --- a/python/generator.py +++ b/python/generator.py @@ -241,6 +241,8 @@ py_types = { 'const virStreamPtr': ('O', "virStream", "virStreamPtr", "virStreamPtr"), 'virStream *': ('O', "virStream", "virStreamPtr", "virStreamPtr"), 'const virStream *': ('O', "virStream", "virStreamPtr", "virStreamPtr"), + + 'virDomainSnapshotPtr': ('O', "virDomainSnapshot", "virDomainSnapshotPtr", "virDomainSnapshotPtr"), } py_return_types = { @@ -317,6 +319,7 @@ skip_impl = ( 'virNodeListDevices', 'virNodeDeviceListCaps', 'virConnectBaselineCPU', + 'virDomainSnapshotListNames', ) diff --git a/python/typewrappers.c b/python/typewrappers.c index b33822c..d0f703c 100644 --- a/python/typewrappers.c +++ b/python/typewrappers.c @@ -228,6 +228,21 @@ libvirt_virStreamPtrWrap(virStreamPtr node) } PyObject * +libvirt_virDomainSnapshotPtrWrap(virDomainSnapshotPtr node) +{ + PyObject *ret; + + if (node == NULL) { + Py_INCREF(Py_None); + return (Py_None); + } + ret = + PyCObject_FromVoidPtrAndDesc((void *) node, (char *) "virDomainSnapshotPtr", + NULL); + return (ret); +} + +PyObject * libvirt_virEventHandleCallbackWrap(virEventHandleCallback node) { PyObject *ret; diff --git a/python/typewrappers.h b/python/typewrappers.h index 8e1998e..c0e69d8 100644 --- a/python/typewrappers.h +++ b/python/typewrappers.h @@ -101,6 +101,15 @@ typedef struct { } PyvirStream_Object; +#define PyvirDomainSnapshot_Get(v) (((v) == Py_None) ? NULL : \ + (((PyvirDomainSnapshot_Object *)(v))->obj)) + +typedef struct { + PyObject_HEAD + virDomainSnapshotPtr obj; +} PyvirDomainSnapshot_Object; + + #define PyvirEventHandleCallback_Get(v) (((v) == Py_None) ? NULL : \ (((PyvirEventHandleCallback_Object *)(v))->obj)) @@ -155,6 +164,7 @@ PyObject * libvirt_virVoidPtrWrap(void* node); PyObject * libvirt_virNodeDevicePtrWrap(virNodeDevicePtr node); PyObject * libvirt_virSecretPtrWrap(virSecretPtr node); PyObject * libvirt_virStreamPtrWrap(virStreamPtr node); +PyObject * libvirt_virDomainSnapshotPtrWrap(virDomainSnapshotPtr node); /* Provide simple macro statement wrappers (adapted from GLib, in turn from Perl): diff --git a/src/datatypes.c b/src/datatypes.c index a361da6..c514f41 100644 --- a/src/datatypes.c +++ b/src/datatypes.c @@ -129,6 +129,20 @@ virSecretFreeName(void *secret_, const char *name ATTRIBUTE_UNUSED) } /** + * virDomainSnapshotFreeName: + * @snapshot: a domain snapshotobject + * + * Destroy the domain snapshot object, this is just used by the domain hash callback. + * + * Returns 0 in case of success and -1 in case of failure. + */ +static int +virDomainSnapshotFreeName(virDomainSnapshotPtr snapshot, const char *name ATTRIBUTE_UNUSED) +{ + return (virUnrefDomainSnapshot(snapshot)); +} + +/** * virGetConnect: * * Allocates a new hypervisor connection structure @@ -337,6 +351,7 @@ virGetDomain(virConnectPtr conn, const char *name, const unsigned char *uuid) { ret->id = -1; if (uuid != NULL) memcpy(&(ret->uuid[0]), uuid, VIR_UUID_BUFLEN); + ret->snapshots = virHashCreate(20); if (virHashAddEntry(conn->domains, name, ret) < 0) { virMutexUnlock(&conn->lock); @@ -389,6 +404,8 @@ virReleaseDomain(virDomainPtr domain) { domain->magic = -1; domain->id = -1; VIR_FREE(domain->name); + if (domain->snapshots != NULL) + virHashFree(domain->snapshots, (virHashDeallocator) virDomainSnapshotFreeName); VIR_FREE(domain); if (conn) { @@ -1504,3 +1521,108 @@ virUnrefNWFilter(virNWFilterPtr pool) { virMutexUnlock(&pool->conn->lock); return (refs); } + + +virDomainSnapshotPtr +virGetDomainSnapshot(virDomainPtr domain, const char *name) +{ + virDomainSnapshotPtr ret = NULL; + + if ((!VIR_IS_DOMAIN(domain)) || (name == NULL)) { + virLibConnError(NULL, VIR_ERR_INVALID_ARG, __FUNCTION__); + return(NULL); + } + virMutexLock(&domain->conn->lock); + + ret = (virDomainSnapshotPtr) virHashLookup(domain->snapshots, name); + if (ret == NULL) { + if (VIR_ALLOC(ret) < 0) { + virMutexUnlock(&domain->conn->lock); + virReportOOMError(); + goto error; + } + ret->name = strdup(name); + if (ret->name == NULL) { + virMutexUnlock(&domain->conn->lock); + virReportOOMError(); + goto error; + } + ret->magic = VIR_SNAPSHOT_MAGIC; + ret->domain = domain; + + if (virHashAddEntry(domain->snapshots, name, ret) < 0) { + virMutexUnlock(&domain->conn->lock); + virLibConnError(domain->conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("failed to add snapshot to domain hash table")); + goto error; + } + domain->refs++; + DEBUG("New hash entry %p", ret); + } else { + DEBUG("Existing hash entry %p: refs now %d", ret, ret->refs+1); + } + ret->refs++; + virMutexUnlock(&domain->conn->lock); + return(ret); + + error: + if (ret != NULL) { + VIR_FREE(ret->name); + VIR_FREE(ret); + } + return(NULL); +} + + +static void +virReleaseDomainSnapshot(virDomainSnapshotPtr snapshot) +{ + virDomainPtr domain = snapshot->domain; + DEBUG("release snapshot %p %s", snapshot, snapshot->name); + + if (virHashRemoveEntry(domain->snapshots, snapshot->name, NULL) < 0) { + virMutexUnlock(&domain->conn->lock); + virLibConnError(domain->conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("snapshot missing from domain hash table")); + domain = NULL; + } + + snapshot->magic = -1; + VIR_FREE(snapshot->name); + VIR_FREE(snapshot); + + if (domain) { + DEBUG("unref domain %p %d", domain, domain->refs); + domain->refs--; + if (domain->refs == 0) { + virReleaseDomain(domain); + /* Already unlocked mutex */ + return; + } + virMutexUnlock(&domain->conn->lock); + } +} + +int +virUnrefDomainSnapshot(virDomainSnapshotPtr snapshot) +{ + int refs; + + if (!VIR_IS_DOMAIN_SNAPSHOT(snapshot)) { + virLibConnError(NULL, VIR_ERR_INVALID_ARG, __FUNCTION__); + return(-1); + } + + virMutexLock(&snapshot->domain->conn->lock); + DEBUG("unref snapshot %p %s %d", snapshot, snapshot->name, snapshot->refs); + snapshot->refs--; + refs = snapshot->refs; + if (refs == 0) { + virReleaseDomainSnapshot(snapshot); + /* Already unlocked mutex */ + return (0); + } + + virMutexUnlock(&snapshot->domain->conn->lock); + return (refs); +} diff --git a/src/datatypes.h b/src/datatypes.h index 4663c9c..bbeb7cf 100644 --- a/src/datatypes.h +++ b/src/datatypes.h @@ -130,6 +130,15 @@ # define VIR_IS_NWFILTER(obj) ((obj) && (obj)->magic==VIR_NWFILTER_MAGIC) # define VIR_IS_CONNECTED_NWFILTER(obj) (VIR_IS_NWFILTER(obj) && VIR_IS_CONNECT((obj)->conn)) +/** + * VIR_SNAPSHOT_MAGIC: + * + * magic value used to protect the API when pointers to snapshot structures + * are passed down by the users. + */ +# define VIR_SNAPSHOT_MAGIC 0x6666DEAD +# define VIR_IS_SNAPSHOT(obj) ((obj) && (obj)->magic==VIR_SNAPSHOT_MAGIC) +# define VIR_IS_DOMAIN_SNAPSHOT(obj) (VIR_IS_SNAPSHOT(obj) && VIR_IS_DOMAIN((obj)->domain)) /** * _virConnect: @@ -202,6 +211,7 @@ struct _virDomain { char *name; /* the domain external name */ int id; /* the domain ID */ unsigned char uuid[VIR_UUID_BUFLEN]; /* the domain unique identifier */ + virHashTablePtr snapshots; /* hash table for known snapshots */ }; /** @@ -304,6 +314,17 @@ struct _virStream { void *privateData; }; +/** + * _virDomainSnapshot + * + * Internal structure associated with a domain snapshot + */ +struct _virDomainSnapshot { + unsigned int magic; + int refs; + char *name; + virDomainPtr domain; +}; /************************************************************************ * * @@ -368,4 +389,8 @@ virNWFilterPtr virGetNWFilter(virConnectPtr conn, const unsigned char *uuid); int virUnrefNWFilter(virNWFilterPtr pool); +virDomainSnapshotPtr virGetDomainSnapshot(virDomainPtr domain, + const char *name); +int virUnrefDomainSnapshot(virDomainSnapshotPtr pool); + #endif diff --git a/src/driver.h b/src/driver.h index 8f86463..ef54b5e 100644 --- a/src/driver.h +++ b/src/driver.h @@ -402,6 +402,44 @@ typedef int (*virDrvDomainEventDeregisterAny)(virConnectPtr conn, int callbackID); +typedef virDomainSnapshotPtr + (*virDrvDomainSnapshotCreateXML)(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags); + +typedef char * + (*virDrvDomainSnapshotDumpXML)(virDomainSnapshotPtr snapshot, + unsigned int flags); + +typedef int + (*virDrvDomainSnapshotNum)(virDomainPtr domain, unsigned int flags); + +typedef int + (*virDrvDomainSnapshotListNames)(virDomainPtr domain, char **names, + int nameslen, + unsigned int flags); + +typedef virDomainSnapshotPtr + (*virDrvDomainSnapshotLookupByName)(virDomainPtr domain, + const char *name, + unsigned int flags); + +typedef int + (*virDrvDomainHasCurrentSnapshot)(virDomainPtr domain, unsigned int flags); + +typedef virDomainSnapshotPtr + (*virDrvDomainSnapshotCurrent)(virDomainPtr domain, + unsigned int flags); + +typedef int + (*virDrvDomainRevertToSnapshot)(virDomainSnapshotPtr snapshot, + unsigned int flags); + +typedef int + (*virDrvDomainSnapshotDelete)(virDomainSnapshotPtr snapshot, + unsigned int flags); + + /** * _virDriver: * @@ -499,6 +537,15 @@ struct _virDriver { virDrvDomainMigrateSetMaxDowntime domainMigrateSetMaxDowntime; virDrvDomainEventRegisterAny domainEventRegisterAny; virDrvDomainEventDeregisterAny domainEventDeregisterAny; + virDrvDomainSnapshotCreateXML domainSnapshotCreateXML; + virDrvDomainSnapshotDumpXML domainSnapshotDumpXML; + virDrvDomainSnapshotNum domainSnapshotNum; + virDrvDomainSnapshotListNames domainSnapshotListNames; + virDrvDomainSnapshotLookupByName domainSnapshotLookupByName; + virDrvDomainHasCurrentSnapshot domainHasCurrentSnapshot; + virDrvDomainSnapshotCurrent domainSnapshotCurrent; + virDrvDomainRevertToSnapshot domainRevertToSnapshot; + virDrvDomainSnapshotDelete domainSnapshotDelete; }; typedef int diff --git a/src/esx/esx_driver.c b/src/esx/esx_driver.c index 7a92c4d..c7c248e 100644 --- a/src/esx/esx_driver.c +++ b/src/esx/esx_driver.c @@ -3389,6 +3389,15 @@ static virDriver esxDriver = { NULL, /* domainMigrateSetMaxDowntime */ NULL, /* domainEventRegisterAny */ NULL, /* domainEventDeregisterAny */ + NULL, /* domainSnapshotCreateXML */ + NULL, /* domainSnapshotDumpXML */ + NULL, /* domainSnapshotNum */ + NULL, /* domainSnapshotListNames */ + NULL, /* domainSnapshotLookupByName */ + NULL, /* domainHasCurrentSnapshot */ + NULL, /* domainSnapshotCurrent */ + NULL, /* domainRevertToSnapshot */ + NULL, /* domainSnapshotDelete */ }; diff --git a/src/libvirt.c b/src/libvirt.c index 5247fe7..25e358c 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -683,6 +683,31 @@ virLibNWFilterError(virNWFilterPtr pool, virErrorNumber error, } /** + * virLibDomainSnapshotError: + * @snapshot: the snapshot if available + * @error: the error number + * @info: extra information string + * + * Handle an error at the domain snapshot level + */ +static void +virLibDomainSnapshotError(virDomainSnapshotPtr snapshot, virErrorNumber error, const char *info) +{ + virConnectPtr conn = NULL; + const char *errmsg; + + if (error == VIR_ERR_OK) + return; + + errmsg = virErrorMsg(error, info); + if (error != VIR_ERR_INVALID_DOMAIN_SNAPSHOT) + conn = snapshot->domain->conn; + + virRaiseError(conn, NULL, NULL, VIR_FROM_DOMAIN_SNAPSHOT, error, VIR_ERR_ERROR, + errmsg, info, NULL, 0, 0, errmsg, info); +} + +/** * virRegisterNetworkDriver: * @driver: pointer to a network driver block * @@ -12136,3 +12161,434 @@ error: virDispatchError(conn); return -1; } + +/** + * virDomainSnapshotCreateXML: + * @domain: a domain object + * @xmlDesc: string containing an XML description of the domain + * @flags: unused flag parameters; callers should pass 0 + * + * Creates a new snapshot of a domain based on the snapshot xml + * contained in xmlDesc. + * + * Returns an (opaque) virDomainSnapshotPtr on success, NULL on failure. + */ +virDomainSnapshotPtr +virDomainSnapshotCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags) +{ + virConnectPtr conn; + + DEBUG("domain=%p, xmlDesc=%s, flags=%u", domain, xmlDesc, flags); + + virResetLastError(); + + if (!VIR_IS_CONNECTED_DOMAIN(domain)) { + virLibDomainError(NULL, VIR_ERR_INVALID_DOMAIN, __FUNCTION__); + virDispatchError(NULL); + return NULL; + } + + conn = domain->conn; + if (conn->flags & VIR_CONNECT_RO) { + virLibConnError(conn, VIR_ERR_OPERATION_DENIED, __FUNCTION__); + goto error; + } + + if (conn->driver->domainSnapshotCreateXML) { + virDomainSnapshotPtr ret; + ret = conn->driver->domainSnapshotCreateXML(domain, xmlDesc, flags); + if (!ret) + goto error; + return ret; + } + + virLibConnError (conn, VIR_ERR_NO_SUPPORT, __FUNCTION__); +error: + virDispatchError(conn); + return NULL; +} + +/** + * virDomainSnapshotGetXMLDesc: + * @snapshot: a domain snapshot object + * @flags: unused flag parameters; callers should pass 0 + * + * Provide an XML description of the domain snapshot. + * + * Returns a 0 terminated UTF-8 encoded XML instance, or NULL in case of error. + * the caller must free() the returned value. + */ +char * +virDomainSnapshotGetXMLDesc(virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + virConnectPtr conn; + DEBUG("snapshot=%p, flags=%d", snapshot, flags); + + virResetLastError(); + + if (!VIR_IS_DOMAIN_SNAPSHOT(snapshot)) { + virLibDomainSnapshotError(NULL, VIR_ERR_INVALID_DOMAIN_SNAPSHOT, + __FUNCTION__); + virDispatchError(NULL); + return (NULL); + } + + conn = snapshot->domain->conn; + + if ((conn->flags & VIR_CONNECT_RO) && (flags & VIR_DOMAIN_XML_SECURE)) { + virLibConnError(conn, VIR_ERR_OPERATION_DENIED, + _("virDomainSnapshotGetXMLDesc with secure flag")); + goto error; + } + + flags &= VIR_DOMAIN_XML_FLAGS_MASK; + + if (conn->driver->domainSnapshotDumpXML) { + char *ret; + ret = conn->driver->domainSnapshotDumpXML(snapshot, flags); + if (!ret) + goto error; + return ret; + } + + virLibConnError (conn, VIR_ERR_NO_SUPPORT, __FUNCTION__); +error: + virDispatchError(conn); + return NULL; +} + +/** + * virDomainSnapshotNum: + * @domain: a domain object + * @flags: unused flag parameters; callers should pass 0 + * + * Provides the number of domain snapshots for this domain.. + * + * Returns the number of domain snapshost found or -1 in case of error. + */ +int +virDomainSnapshotNum(virDomainPtr domain, unsigned int flags) +{ + virConnectPtr conn; + DEBUG("domain=%p", domain); + + virResetLastError(); + + if (!VIR_IS_CONNECTED_DOMAIN(domain)) { + virLibDomainError(NULL, VIR_ERR_INVALID_DOMAIN, __FUNCTION__); + virDispatchError(NULL); + return -1; + } + + conn = domain->conn; + if (conn->driver->domainSnapshotNum) { + int ret = conn->driver->domainSnapshotNum(domain, flags); + if (ret < 0) + goto error; + return ret; + } + + virLibConnError (conn, VIR_ERR_NO_SUPPORT, __FUNCTION__); +error: + virDispatchError(conn); + return -1; +} + +/** + * virDomainSnapshotListNames: + * @domain: a domain object + * @names: array to collect the list of names of snapshots + * @nameslen: size of @names + * @flags: unused flag parameters; callers should pass 0 + * + * Collect the list of domain snapshots for the given domain, and store + * their names in @names. Caller is responsible for freeing each member + * of the array. + * + * Returns the number of domain snapshots found or -1 in case of error. + */ +int +virDomainSnapshotListNames(virDomainPtr domain, char **names, int nameslen, + unsigned int flags) +{ + virConnectPtr conn; + + DEBUG("domain=%p, names=%p, nameslen=%d, flags=%u", + domain, names, nameslen, flags); + + virResetLastError(); + + if (!VIR_IS_CONNECTED_DOMAIN(domain)) { + virLibDomainError(NULL, VIR_ERR_INVALID_DOMAIN, __FUNCTION__); + virDispatchError(NULL); + return -1; + } + + conn = domain->conn; + + if ((names == NULL) || (nameslen < 0)) { + virLibConnError(conn, VIR_ERR_INVALID_ARG, __FUNCTION__); + goto error; + } + + if (conn->driver->domainSnapshotListNames) { + int ret = conn->driver->domainSnapshotListNames(domain, names, + nameslen, flags); + if (ret < 0) + goto error; + return ret; + } + + virLibConnError (conn, VIR_ERR_NO_SUPPORT, __FUNCTION__); +error: + virDispatchError(conn); + return -1; +} + +/** + * virDomainSnapshotLookupByName: + * @domain: a domain object + * @name: name for the domain snapshot + * @flags: unused flag parameters; callers should pass 0 + * + * Try to lookup a domain snapshot based on its name. + * + * Returns a domain snapshot object or NULL in case of failure. If the + * domain snapshot cannot be found, then the VIR_ERR_NO_DOMAIN_SNAPSHOT + * error is raised. + */ +virDomainSnapshotPtr +virDomainSnapshotLookupByName(virDomainPtr domain, + const char *name, + unsigned int flags) +{ + virConnectPtr conn; + DEBUG("domain=%p, name=%s, flags=%u", domain, name, flags); + + virResetLastError(); + + if (!VIR_IS_CONNECTED_DOMAIN(domain)) { + virLibDomainError(NULL, VIR_ERR_INVALID_DOMAIN, __FUNCTION__); + virDispatchError(NULL); + return (NULL); + } + + conn = domain->conn; + + if (name == NULL) { + virLibConnError(conn, VIR_ERR_INVALID_ARG, __FUNCTION__); + goto error; + } + + if (conn->driver->domainSnapshotLookupByName) { + virDomainSnapshotPtr dom; + dom = conn->driver->domainSnapshotLookupByName(domain, name, flags); + if (!dom) + goto error; + return dom; + } + + virLibConnError (conn, VIR_ERR_NO_SUPPORT, __FUNCTION__); +error: + virDispatchError(conn); + return NULL; +} + +/** + * virDomainHasCurrentSnapshot: + * @domain: pointer to the domain object + * @flags: unused flag parameters; callers should pass 0 + * + * Determine if the domain has a current snapshot. + * + * Returns 1 if such snapshot exists, 0 if it doesn't, -1 on error. + */ +int +virDomainHasCurrentSnapshot(virDomainPtr domain, unsigned int flags) +{ + virConnectPtr conn; + DEBUG("domain=%p, flags=%u", domain, flags); + + virResetLastError(); + + if (!VIR_IS_CONNECTED_DOMAIN(domain)) { + virLibDomainError(NULL, VIR_ERR_INVALID_DOMAIN, __FUNCTION__); + virDispatchError(NULL); + return -1; + } + + conn = domain->conn; + + if (conn->driver->domainHasCurrentSnapshot) { + int ret = conn->driver->domainHasCurrentSnapshot(domain, flags); + if (ret < 0) + goto error; + return ret; + } + + virLibConnError (conn, VIR_ERR_NO_SUPPORT, __FUNCTION__); +error: + virDispatchError(conn); + return -1; +} + +/** + * virDomainSnapshotCurrent: + * @domain: a domain object + * @flags: unused flag parameters; callers should pass 0 + * + * Get the current snapshot for a domain, if any. + * + * Returns a domain snapshot object or NULL in case of failure. If the + * current domain snapshot cannot be found, then the VIR_ERR_NO_DOMAIN_SNAPSHOT + * error is raised. + */ +virDomainSnapshotPtr +virDomainSnapshotCurrent(virDomainPtr domain, + unsigned int flags) +{ + virConnectPtr conn; + DEBUG("domain=%p, flags=%u", domain, flags); + + virResetLastError(); + + if (!VIR_IS_CONNECTED_DOMAIN(domain)) { + virLibDomainError(NULL, VIR_ERR_INVALID_DOMAIN, __FUNCTION__); + virDispatchError(NULL); + return (NULL); + } + + conn = domain->conn; + + if (conn->driver->domainSnapshotCurrent) { + virDomainSnapshotPtr snap; + snap = conn->driver->domainSnapshotCurrent(domain, flags); + if (!snap) + goto error; + return snap; + } + + virLibConnError (conn, VIR_ERR_NO_SUPPORT, __FUNCTION__); +error: + virDispatchError(conn); + return NULL; +} + +/** + * virDomainRevertToSnapshot + * @snapshot: a domain snapshot object + * @flags: unused flag parameters; callers should pass 0 + * + * Revert the domain to a given snapshot. + * + * Returns 0 if the creation is successful, -1 on error. + */ +int +virDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + virConnectPtr conn; + + DEBUG("snapshot=%p, flags=%u", snapshot, flags); + + virResetLastError(); + + if (!VIR_IS_DOMAIN_SNAPSHOT(snapshot)) { + virLibDomainSnapshotError(NULL, VIR_ERR_INVALID_DOMAIN_SNAPSHOT, + __FUNCTION__); + virDispatchError(NULL); + return -1; + } + + conn = snapshot->domain->conn; + + if (conn->driver->domainRevertToSnapshot) { + int ret = conn->driver->domainRevertToSnapshot(snapshot, flags); + if (ret < 0) + goto error; + return ret; + } + + virLibConnError (conn, VIR_ERR_NO_SUPPORT, __FUNCTION__); +error: + virDispatchError(conn); + return -1; +} + +/** + * virDomainSnapshotDelete + * @snapshot: a domain snapshot object + * @flags: flag parameters + * + * Delete the snapshot. + * + * If @flags is 0, then just this snapshot is deleted, and changes from + * this snapshot are automatically pushed to children snapshots. If + * flags is VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN, then this snapshot + * and any children snapshots are deleted. + * + * Returns 0 if the snapshot was successfully deleted, -1 on error. + */ +int +virDomainSnapshotDelete(virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + virConnectPtr conn; + + DEBUG("snapshot=%p, flags=%u", snapshot, flags); + + virResetLastError(); + + if (!VIR_IS_DOMAIN_SNAPSHOT(snapshot)) { + virLibDomainSnapshotError(NULL, VIR_ERR_INVALID_DOMAIN_SNAPSHOT, + __FUNCTION__); + virDispatchError(NULL); + return -1; + } + + conn = snapshot->domain->conn; + + if (conn->driver->domainSnapshotDelete) { + int ret = conn->driver->domainSnapshotDelete(snapshot, flags); + if (ret < 0) + goto error; + return ret; + } + + virLibConnError (conn, VIR_ERR_NO_SUPPORT, __FUNCTION__); +error: + virDispatchError(conn); + return -1; +} + +/** + * virDomainSnapshotFree: + * @snapshot: a domain snapshot object + * + * Free the domain snapshot object. The snapshot 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 +virDomainSnapshotFree(virDomainSnapshotPtr snapshot) +{ + DEBUG("snapshot=%p", snapshot); + + virResetLastError(); + + if (!VIR_IS_DOMAIN_SNAPSHOT(snapshot)) { + virLibDomainSnapshotError(NULL, VIR_ERR_INVALID_DOMAIN_SNAPSHOT, + __FUNCTION__); + virDispatchError(NULL); + return -1; + } + if (virUnrefDomainSnapshot(snapshot) < 0) { + virDispatchError(NULL); + return -1; + } + return 0; +} diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 878eda3..86aacd7 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -109,6 +109,7 @@ virGetStream; virUnrefStream; virGetNWFilter; virUnrefNWFilter; +virGetDomainSnapshot; # domain_conf.h diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 2f812a1..370abc4 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -378,6 +378,16 @@ LIBVIRT_0.7.8 { virNWFilterRef; virNWFilterDefineXML; virNWFilterUndefine; + virDomainSnapshotCreateXML; + virDomainSnapshotGetXMLDesc; + virDomainSnapshotNum; + virDomainSnapshotListNames; + virDomainSnapshotLookupByName; + virDomainHasCurrentSnapshot; + virDomainSnapshotCurrent; + virDomainRevertToSnapshot; + virDomainSnapshotDelete; + virDomainSnapshotFree; } LIBVIRT_0.7.7; diff --git a/src/lxc/lxc_driver.c b/src/lxc/lxc_driver.c index 9caefa1..c718d82 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -2538,6 +2538,15 @@ static virDriver lxcDriver = { NULL, /* domainMigrateSetMaxDowntime */ lxcDomainEventRegisterAny, /* domainEventRegisterAny */ lxcDomainEventDeregisterAny, /* domainEventDeregisterAny */ + NULL, /* domainSnapshotCreateXML */ + NULL, /* domainSnapshotDumpXML */ + NULL, /* domainSnapshotNum */ + NULL, /* domainSnapshotListNames */ + NULL, /* domainSnapshotLookupByName */ + NULL, /* domainHasCurrentSnapshot */ + NULL, /* domainSnapshotCurrent */ + NULL, /* domainRevertToSnapshot */ + NULL, /* domainSnapshotDelete */ }; static virStateDriver lxcStateDriver = { diff --git a/src/opennebula/one_driver.c b/src/opennebula/one_driver.c index e901a03..1767b8b 100644 --- a/src/opennebula/one_driver.c +++ b/src/opennebula/one_driver.c @@ -792,6 +792,15 @@ static virDriver oneDriver = { NULL, /* domainMigrateSetMaxDowntime */ NULL, /* domainEventRegisterAny */ NULL, /* domainEventDeregisterAny */ + NULL, /* domainSnapshotCreateXML */ + NULL, /* domainSnapshotDumpXML */ + NULL, /* domainSnapshotNum */ + NULL, /* domainSnapshotListNames */ + NULL, /* domainSnapshotLookupByName */ + NULL, /* domainHasCurrentSnapshot */ + NULL, /* domainSnapshotCurrent */ + NULL, /* domainRevertToSnapshot */ + NULL, /* domainSnapshotDelete */ }; static virStateDriver oneStateDriver = { diff --git a/src/openvz/openvz_driver.c b/src/openvz/openvz_driver.c index 6176577..a7e9ac2 100644 --- a/src/openvz/openvz_driver.c +++ b/src/openvz/openvz_driver.c @@ -1544,6 +1544,15 @@ static virDriver openvzDriver = { NULL, /* domainMigrateSetMaxDowntime */ NULL, /* domainEventRegisterAny */ NULL, /* domainEventDeregisterAny */ + NULL, /* domainSnapshotCreateXML */ + NULL, /* domainSnapshotDumpXML */ + NULL, /* domainSnapshotNum */ + NULL, /* domainSnapshotListNames */ + NULL, /* domainSnapshotLookupByName */ + NULL, /* domainHasCurrentSnapshot */ + NULL, /* domainSnapshotCurrent */ + NULL, /* domainRevertToSnapshot */ + NULL, /* domainSnapshotDelete */ }; int openvzRegister(void) { diff --git a/src/phyp/phyp_driver.c b/src/phyp/phyp_driver.c index 4f7efdb..4b1a35f 100644 --- a/src/phyp/phyp_driver.c +++ b/src/phyp/phyp_driver.c @@ -1651,6 +1651,15 @@ virDriver phypDriver = { NULL, /* domainMigrateSetMaxDowntime */ NULL, /* domainEventRegisterAny */ NULL, /* domainEventDeregisterAny */ + NULL, /* domainSnapshotCreateXML */ + NULL, /* domainSnapshotDumpXML */ + NULL, /* domainSnapshotNum */ + NULL, /* domainSnapshotListNames */ + NULL, /* domainSnapshotLookupByName */ + NULL, /* domainHasCurrentSnapshot */ + NULL, /* domainSnapshotCurrent */ + NULL, /* domainRevertToSnapshot */ + NULL, /* domainSnapshotDelete */ }; int diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 70d2781..02ed95f 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -10312,6 +10312,15 @@ static virDriver qemuDriver = { qemuDomainMigrateSetMaxDowntime, /* domainMigrateSetMaxDowntime */ qemuDomainEventRegisterAny, /* domainEventRegisterAny */ qemuDomainEventDeregisterAny, /* domainEventDeregisterAny */ + NULL, /* domainSnapshotCreateXML */ + NULL, /* domainSnapshotDumpXML */ + NULL, /* domainSnapshotNum */ + NULL, /* domainSnapshotListNames */ + NULL, /* domainSnapshotLookupByName */ + NULL, /* domainHasCurrentSnapshot */ + NULL, /* domainSnapshotCurrent */ + NULL, /* domainRevertToSnapshot */ + NULL, /* domainSnapshotDelete */ }; diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index 3b8be21..b9f6371 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -254,6 +254,7 @@ static virStoragePoolPtr get_nonnull_storage_pool (virConnectPtr conn, remote_no static virStorageVolPtr get_nonnull_storage_vol (virConnectPtr conn, remote_nonnull_storage_vol vol); 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 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); @@ -261,6 +262,7 @@ static void make_nonnull_storage_pool (remote_nonnull_storage_pool *pool_dst, vi static void make_nonnull_storage_vol (remote_nonnull_storage_vol *vol_dst, virStorageVolPtr vol_src); static void make_nonnull_secret (remote_nonnull_secret *secret_dst, virSecretPtr secret_src); static void make_nonnull_nwfilter (remote_nonnull_nwfilter *nwfilter_dst, virNWFilterPtr nwfilter_src); +static void make_nonnull_domain_snapshot (remote_nonnull_domain_snapshot *snapshot_dst, virDomainSnapshotPtr snapshot_src); void remoteDomainEventFired(int watch, int fd, int event, void *data); void remoteDomainEventQueueFlush(int timer, void *opaque); /*----------------------------------------------------------------------*/ @@ -8301,6 +8303,291 @@ done: return rv; } +static virDomainSnapshotPtr +remoteDomainSnapshotCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags) +{ + virDomainSnapshotPtr snapshot = NULL; + remote_domain_snapshot_create_xml_args args; + remote_domain_snapshot_create_xml_ret ret; + struct private_data *priv = domain->conn->privateData; + + remoteDriverLock(priv); + + make_nonnull_domain (&args.domain, domain); + args.xml_desc = (char *) xmlDesc; + args.flags = flags; + + memset (&ret, 0, sizeof ret); + if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_SNAPSHOT_CREATE_XML, + (xdrproc_t) xdr_remote_domain_snapshot_create_xml_args, (char *) &args, + (xdrproc_t) xdr_remote_domain_snapshot_create_xml_ret, (char *) &ret) == -1) + goto done; + + snapshot = get_nonnull_domain_snapshot(domain, ret.snap); + xdr_free ((xdrproc_t) &xdr_remote_domain_snapshot_create_xml_ret, (char *) &ret); + +done: + remoteDriverUnlock(priv); + return snapshot; +} + + +static char * +remoteDomainSnapshotDumpXML(virDomainSnapshotPtr snapshot, unsigned int flags) +{ + char *rv = NULL; + remote_domain_snapshot_dump_xml_args args; + remote_domain_snapshot_dump_xml_ret ret; + struct private_data *priv = snapshot->domain->conn->privateData; + + remoteDriverLock(priv); + + make_nonnull_domain_snapshot(&args.snap, snapshot); + args.flags = flags; + + memset (&ret, 0, sizeof ret); + if (call (snapshot->domain->conn, priv, 0, REMOTE_PROC_DOMAIN_SNAPSHOT_DUMP_XML, + (xdrproc_t) xdr_remote_domain_snapshot_dump_xml_args, (char *) &args, + (xdrproc_t) xdr_remote_domain_snapshot_dump_xml_ret, (char *) &ret) == -1) + goto done; + + /* Caller frees. */ + rv = ret.xml; + +done: + remoteDriverUnlock(priv); + return rv; +} + + +static int +remoteDomainSnapshotNum (virDomainPtr domain, unsigned int flags) +{ + int rv = -1; + remote_domain_snapshot_num_args args; + remote_domain_snapshot_num_ret ret; + struct private_data *priv = domain->conn->privateData; + + remoteDriverLock(priv); + + make_nonnull_domain (&args.domain, domain); + args.flags = flags; + + memset (&ret, 0, sizeof ret); + if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_SNAPSHOT_NUM, + (xdrproc_t) xdr_remote_domain_snapshot_num_args, (char *) &args, + (xdrproc_t) xdr_remote_domain_snapshot_num_ret, (char *) &ret) == -1) + goto done; + + rv = ret.num; + +done: + remoteDriverUnlock(priv); + return rv; +} + + +static int +remoteDomainSnapshotListNames (virDomainPtr domain, char **const names, + int nameslen, unsigned int flags) +{ + int rv = -1; + int i; + remote_domain_snapshot_list_names_args args; + remote_domain_snapshot_list_names_ret ret; + struct private_data *priv = domain->conn->privateData; + + remoteDriverLock(priv); + + if (nameslen > REMOTE_DOMAIN_SNAPSHOT_LIST_NAMES_MAX) { + errorf (domain->conn, VIR_ERR_RPC, + _("too many remote domain snapshot names: %d > %d"), + nameslen, REMOTE_DOMAIN_SNAPSHOT_LIST_NAMES_MAX); + goto done; + } + + make_nonnull_domain(&args.domain, domain); + args.nameslen = nameslen; + args.flags = flags; + + memset (&ret, 0, sizeof ret); + if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_SNAPSHOT_LIST_NAMES, + (xdrproc_t) xdr_remote_domain_snapshot_list_names_args, (char *) &args, + (xdrproc_t) xdr_remote_domain_snapshot_list_names_ret, (char *) &ret) == -1) + goto done; + + if (ret.names.names_len > nameslen) { + errorf (domain->conn, VIR_ERR_RPC, + _("too many remote domain snapshots: %d > %d"), + ret.names.names_len, nameslen); + goto cleanup; + } + + /* This call is caller-frees (although that isn't clear from + * the documentation). However xdr_free will free up both the + * names and the list of pointers, so we have to strdup the + * names here. + */ + for (i = 0; i < ret.names.names_len; ++i) { + names[i] = strdup (ret.names.names_val[i]); + + if (names[i] == NULL) { + for (--i; i >= 0; --i) + VIR_FREE(names[i]); + + virReportOOMError(); + goto cleanup; + } + } + + rv = ret.names.names_len; + +cleanup: + xdr_free ((xdrproc_t) xdr_remote_domain_snapshot_list_names_ret, (char *) &ret); + +done: + remoteDriverUnlock(priv); + return rv; +} + + +static virDomainSnapshotPtr +remoteDomainSnapshotLookupByName (virDomainPtr domain, const char *name, + unsigned int flags) +{ + virDomainSnapshotPtr snapshot = NULL; + remote_domain_snapshot_lookup_by_name_args args; + remote_domain_snapshot_lookup_by_name_ret ret; + struct private_data *priv = domain->conn->privateData; + + remoteDriverLock(priv); + + make_nonnull_domain(&args.domain, domain); + args.name = (char *) name; + args.flags = flags; + + memset (&ret, 0, sizeof ret); + if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_SNAPSHOT_LOOKUP_BY_NAME, + (xdrproc_t) xdr_remote_domain_snapshot_lookup_by_name_args, (char *) &args, + (xdrproc_t) xdr_remote_domain_snapshot_lookup_by_name_ret, (char *) &ret) == -1) + goto done; + + snapshot = get_nonnull_domain_snapshot (domain, ret.snap); + xdr_free ((xdrproc_t) &xdr_remote_domain_snapshot_lookup_by_name_ret, (char *) &ret); + +done: + remoteDriverUnlock(priv); + return snapshot; +} + + +static int +remoteDomainHasCurrentSnapshot(virDomainPtr domain, unsigned int flags) +{ + int rv = -1; + remote_domain_has_current_snapshot_args args; + remote_domain_has_current_snapshot_ret ret; + struct private_data *priv = domain->conn->privateData; + + remoteDriverLock(priv); + + make_nonnull_domain(&args.domain, domain); + args.flags = flags; + + if (call(domain->conn, priv, 0, REMOTE_PROC_DOMAIN_HAS_CURRENT_SNAPSHOT, + (xdrproc_t) xdr_remote_domain_has_current_snapshot_args, (char *) &args, + (xdrproc_t) xdr_remote_domain_has_current_snapshot_ret, (char *) &ret) == -1) + goto done; + + rv = ret.result; + +done: + remoteDriverUnlock(priv); + return rv; +} + + +static virDomainSnapshotPtr +remoteDomainSnapshotCurrent(virDomainPtr domain, + unsigned int flags) +{ + virDomainSnapshotPtr snapshot = NULL; + remote_domain_snapshot_current_args args; + remote_domain_snapshot_current_ret ret; + struct private_data *priv = domain->conn->privateData; + + remoteDriverLock(priv); + + make_nonnull_domain(&args.domain, domain); + args.flags = flags; + + memset(&ret, 0, sizeof ret); + if (call(domain->conn, priv, 0, REMOTE_PROC_DOMAIN_SNAPSHOT_CURRENT, + (xdrproc_t) xdr_remote_domain_snapshot_current_args, (char *) &args, + (xdrproc_t) xdr_remote_domain_snapshot_current_ret, (char *) &ret) == -1) + goto done; + + snapshot = get_nonnull_domain_snapshot(domain, ret.snap); + xdr_free((xdrproc_t) &xdr_remote_domain_snapshot_current_ret, (char *) &ret); + +done: + remoteDriverUnlock(priv); + return snapshot; +} + + +static int +remoteDomainRevertToSnapshot (virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + int rv = -1; + remote_domain_revert_to_snapshot_args args; + struct private_data *priv = snapshot->domain->conn->privateData; + + remoteDriverLock(priv); + + make_nonnull_domain_snapshot(&args.snap, snapshot); + args.flags = flags; + + if (call (snapshot->domain->conn, priv, 0, REMOTE_PROC_DOMAIN_REVERT_TO_SNAPSHOT, + (xdrproc_t) xdr_remote_domain_revert_to_snapshot_args, (char *) &args, + (xdrproc_t) xdr_void, (char *) NULL) == -1) + goto done; + + rv = 0; + +done: + remoteDriverUnlock(priv); + return rv; +} + + +static int +remoteDomainSnapshotDelete (virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + int rv = -1; + remote_domain_snapshot_delete_args args; + struct private_data *priv = snapshot->domain->conn->privateData; + + remoteDriverLock(priv); + + make_nonnull_domain_snapshot(&args.snap, snapshot); + args.flags = flags; + + if (call (snapshot->domain->conn, priv, 0, REMOTE_PROC_DOMAIN_SNAPSHOT_DELETE, + (xdrproc_t) xdr_remote_domain_snapshot_delete_args, (char *) &args, + (xdrproc_t) xdr_void, (char *) NULL) == -1) + goto done; + + rv = 0; + +done: + remoteDriverUnlock(priv); + return rv; +} static int remoteDomainEventRegisterAny(virConnectPtr conn, virDomainPtr dom, @@ -9671,6 +9958,12 @@ get_nonnull_nwfilter (virConnectPtr conn, remote_nonnull_nwfilter nwfilter) return virGetNWFilter (conn, nwfilter.name, BAD_CAST nwfilter.uuid); } +static virDomainSnapshotPtr +get_nonnull_domain_snapshot (virDomainPtr domain, remote_nonnull_domain_snapshot snapshot) +{ + return virGetDomainSnapshot(domain, snapshot.name); +} + /* Make remote_nonnull_domain and remote_nonnull_network. */ static void @@ -9726,6 +10019,13 @@ make_nonnull_nwfilter (remote_nonnull_nwfilter *nwfilter_dst, virNWFilterPtr nwf memcpy (nwfilter_dst->uuid, nwfilter_src->uuid, VIR_UUID_BUFLEN); } +static void +make_nonnull_domain_snapshot (remote_nonnull_domain_snapshot *snapshot_dst, virDomainSnapshotPtr snapshot_src) +{ + snapshot_dst->name = snapshot_src->name; + make_nonnull_domain(&snapshot_dst->domain, snapshot_src->domain); +} + /*----------------------------------------------------------------------*/ unsigned long remoteVersion(void) @@ -9818,6 +10118,15 @@ static virDriver remote_driver = { remoteDomainMigrateSetMaxDowntime, /* domainMigrateSetMaxDowntime */ remoteDomainEventRegisterAny, /* domainEventRegisterAny */ remoteDomainEventDeregisterAny, /* domainEventDeregisterAny */ + remoteDomainSnapshotCreateXML, /* domainSnapshotCreateXML */ + remoteDomainSnapshotDumpXML, /* domainSnapshotDumpXML */ + remoteDomainSnapshotNum, /* domainSnapshotNum */ + remoteDomainSnapshotListNames, /* domainSnapshotListNames */ + remoteDomainSnapshotLookupByName, /* domainSnapshotLookupByName */ + remoteDomainHasCurrentSnapshot, /* domainHasCurrentSnapshot */ + remoteDomainSnapshotCurrent, /* domainSnapshotCurrent */ + remoteDomainRevertToSnapshot, /* domainRevertToSnapshot */ + remoteDomainSnapshotDelete, /* domainSnapshotDelete */ }; static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.c b/src/remote/remote_protocol.c index f252d85..7afe13d 100644 --- a/src/remote/remote_protocol.c +++ b/src/remote/remote_protocol.c @@ -128,6 +128,17 @@ xdr_remote_nonnull_secret (XDR *xdrs, remote_nonnull_secret *objp) } bool_t +xdr_remote_nonnull_domain_snapshot (XDR *xdrs, remote_nonnull_domain_snapshot *objp) +{ + + if (!xdr_remote_nonnull_string (xdrs, &objp->name)) + return FALSE; + if (!xdr_remote_nonnull_domain (xdrs, &objp->domain)) + return FALSE; + return TRUE; +} + +bool_t xdr_remote_domain (XDR *xdrs, remote_domain *objp) { @@ -3289,6 +3300,176 @@ xdr_remote_domain_event_graphics_msg (XDR *xdrs, remote_domain_event_graphics_ms } bool_t +xdr_remote_domain_snapshot_create_xml_args (XDR *xdrs, remote_domain_snapshot_create_xml_args *objp) +{ + + if (!xdr_remote_nonnull_domain (xdrs, &objp->domain)) + return FALSE; + if (!xdr_remote_nonnull_string (xdrs, &objp->xml_desc)) + return FALSE; + if (!xdr_int (xdrs, &objp->flags)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_domain_snapshot_create_xml_ret (XDR *xdrs, remote_domain_snapshot_create_xml_ret *objp) +{ + + if (!xdr_remote_nonnull_domain_snapshot (xdrs, &objp->snap)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_domain_snapshot_dump_xml_args (XDR *xdrs, remote_domain_snapshot_dump_xml_args *objp) +{ + + if (!xdr_remote_nonnull_domain_snapshot (xdrs, &objp->snap)) + return FALSE; + if (!xdr_int (xdrs, &objp->flags)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_domain_snapshot_dump_xml_ret (XDR *xdrs, remote_domain_snapshot_dump_xml_ret *objp) +{ + + if (!xdr_remote_nonnull_string (xdrs, &objp->xml)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_domain_snapshot_num_args (XDR *xdrs, remote_domain_snapshot_num_args *objp) +{ + + if (!xdr_remote_nonnull_domain (xdrs, &objp->domain)) + return FALSE; + if (!xdr_int (xdrs, &objp->flags)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_domain_snapshot_num_ret (XDR *xdrs, remote_domain_snapshot_num_ret *objp) +{ + + if (!xdr_int (xdrs, &objp->num)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_domain_snapshot_list_names_args (XDR *xdrs, remote_domain_snapshot_list_names_args *objp) +{ + + if (!xdr_remote_nonnull_domain (xdrs, &objp->domain)) + return FALSE; + if (!xdr_int (xdrs, &objp->nameslen)) + return FALSE; + if (!xdr_int (xdrs, &objp->flags)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_domain_snapshot_list_names_ret (XDR *xdrs, remote_domain_snapshot_list_names_ret *objp) +{ + char **objp_cpp0 = (char **) (void *) &objp->names.names_val; + + if (!xdr_array (xdrs, objp_cpp0, (u_int *) &objp->names.names_len, REMOTE_DOMAIN_SNAPSHOT_LIST_NAMES_MAX, + sizeof (remote_nonnull_string), (xdrproc_t) xdr_remote_nonnull_string)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_domain_snapshot_lookup_by_name_args (XDR *xdrs, remote_domain_snapshot_lookup_by_name_args *objp) +{ + + if (!xdr_remote_nonnull_domain (xdrs, &objp->domain)) + return FALSE; + if (!xdr_remote_nonnull_string (xdrs, &objp->name)) + return FALSE; + if (!xdr_int (xdrs, &objp->flags)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_domain_snapshot_lookup_by_name_ret (XDR *xdrs, remote_domain_snapshot_lookup_by_name_ret *objp) +{ + + if (!xdr_remote_nonnull_domain_snapshot (xdrs, &objp->snap)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_domain_has_current_snapshot_args (XDR *xdrs, remote_domain_has_current_snapshot_args *objp) +{ + + if (!xdr_remote_nonnull_domain (xdrs, &objp->domain)) + return FALSE; + if (!xdr_int (xdrs, &objp->flags)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_domain_has_current_snapshot_ret (XDR *xdrs, remote_domain_has_current_snapshot_ret *objp) +{ + + if (!xdr_int (xdrs, &objp->result)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_domain_snapshot_current_args (XDR *xdrs, remote_domain_snapshot_current_args *objp) +{ + + if (!xdr_remote_nonnull_domain (xdrs, &objp->domain)) + return FALSE; + if (!xdr_int (xdrs, &objp->flags)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_domain_snapshot_current_ret (XDR *xdrs, remote_domain_snapshot_current_ret *objp) +{ + + if (!xdr_remote_nonnull_domain_snapshot (xdrs, &objp->snap)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_domain_revert_to_snapshot_args (XDR *xdrs, remote_domain_revert_to_snapshot_args *objp) +{ + + if (!xdr_remote_nonnull_domain_snapshot (xdrs, &objp->snap)) + return FALSE; + if (!xdr_int (xdrs, &objp->flags)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_domain_snapshot_delete_args (XDR *xdrs, remote_domain_snapshot_delete_args *objp) +{ + + if (!xdr_remote_nonnull_domain_snapshot (xdrs, &objp->snap)) + return FALSE; + if (!xdr_int (xdrs, &objp->flags)) + return FALSE; + return TRUE; +} + +bool_t xdr_remote_procedure (XDR *xdrs, remote_procedure *objp) { diff --git a/src/remote/remote_protocol.h b/src/remote/remote_protocol.h index d898502..fb65211 100644 --- a/src/remote/remote_protocol.h +++ b/src/remote/remote_protocol.h @@ -42,6 +42,7 @@ typedef remote_nonnull_string *remote_string; #define REMOTE_AUTH_SASL_DATA_MAX 65536 #define REMOTE_AUTH_TYPE_LIST_MAX 20 #define REMOTE_DOMAIN_MEMORY_STATS_MAX 1024 +#define REMOTE_DOMAIN_SNAPSHOT_LIST_NAMES_MAX 1024 #define REMOTE_DOMAIN_BLOCK_PEEK_BUFFER_MAX 65536 #define REMOTE_DOMAIN_MEMORY_PEEK_BUFFER_MAX 65536 #define REMOTE_SECURITY_MODEL_MAX VIR_SECURITY_MODEL_BUFLEN @@ -103,6 +104,12 @@ struct remote_nonnull_secret { }; typedef struct remote_nonnull_secret remote_nonnull_secret; +struct remote_nonnull_domain_snapshot { + remote_nonnull_string name; + remote_nonnull_domain domain; +}; +typedef struct remote_nonnull_domain_snapshot remote_nonnull_domain_snapshot; + typedef remote_nonnull_domain *remote_domain; typedef remote_nonnull_network *remote_network; @@ -1860,6 +1867,101 @@ struct remote_domain_event_graphics_msg { } subject; }; typedef struct remote_domain_event_graphics_msg remote_domain_event_graphics_msg; + +struct remote_domain_snapshot_create_xml_args { + remote_nonnull_domain domain; + remote_nonnull_string xml_desc; + int flags; +}; +typedef struct remote_domain_snapshot_create_xml_args remote_domain_snapshot_create_xml_args; + +struct remote_domain_snapshot_create_xml_ret { + remote_nonnull_domain_snapshot snap; +}; +typedef struct remote_domain_snapshot_create_xml_ret remote_domain_snapshot_create_xml_ret; + +struct remote_domain_snapshot_dump_xml_args { + remote_nonnull_domain_snapshot snap; + int flags; +}; +typedef struct remote_domain_snapshot_dump_xml_args remote_domain_snapshot_dump_xml_args; + +struct remote_domain_snapshot_dump_xml_ret { + remote_nonnull_string xml; +}; +typedef struct remote_domain_snapshot_dump_xml_ret remote_domain_snapshot_dump_xml_ret; + +struct remote_domain_snapshot_num_args { + remote_nonnull_domain domain; + int flags; +}; +typedef struct remote_domain_snapshot_num_args remote_domain_snapshot_num_args; + +struct remote_domain_snapshot_num_ret { + int num; +}; +typedef struct remote_domain_snapshot_num_ret remote_domain_snapshot_num_ret; + +struct remote_domain_snapshot_list_names_args { + remote_nonnull_domain domain; + int nameslen; + int flags; +}; +typedef struct remote_domain_snapshot_list_names_args remote_domain_snapshot_list_names_args; + +struct remote_domain_snapshot_list_names_ret { + struct { + u_int names_len; + remote_nonnull_string *names_val; + } names; +}; +typedef struct remote_domain_snapshot_list_names_ret remote_domain_snapshot_list_names_ret; + +struct remote_domain_snapshot_lookup_by_name_args { + remote_nonnull_domain domain; + remote_nonnull_string name; + int flags; +}; +typedef struct remote_domain_snapshot_lookup_by_name_args remote_domain_snapshot_lookup_by_name_args; + +struct remote_domain_snapshot_lookup_by_name_ret { + remote_nonnull_domain_snapshot snap; +}; +typedef struct remote_domain_snapshot_lookup_by_name_ret remote_domain_snapshot_lookup_by_name_ret; + +struct remote_domain_has_current_snapshot_args { + remote_nonnull_domain domain; + int flags; +}; +typedef struct remote_domain_has_current_snapshot_args remote_domain_has_current_snapshot_args; + +struct remote_domain_has_current_snapshot_ret { + int result; +}; +typedef struct remote_domain_has_current_snapshot_ret remote_domain_has_current_snapshot_ret; + +struct remote_domain_snapshot_current_args { + remote_nonnull_domain domain; + int flags; +}; +typedef struct remote_domain_snapshot_current_args remote_domain_snapshot_current_args; + +struct remote_domain_snapshot_current_ret { + remote_nonnull_domain_snapshot snap; +}; +typedef struct remote_domain_snapshot_current_ret remote_domain_snapshot_current_ret; + +struct remote_domain_revert_to_snapshot_args { + remote_nonnull_domain_snapshot snap; + int flags; +}; +typedef struct remote_domain_revert_to_snapshot_args remote_domain_revert_to_snapshot_args; + +struct remote_domain_snapshot_delete_args { + remote_nonnull_domain_snapshot snap; + int flags; +}; +typedef struct remote_domain_snapshot_delete_args remote_domain_snapshot_delete_args; #define REMOTE_PROGRAM 0x20008086 #define REMOTE_PROTOCOL_VERSION 1 @@ -2045,6 +2147,15 @@ enum remote_procedure { REMOTE_PROC_LIST_NWFILTERS = 179, REMOTE_PROC_NWFILTER_DEFINE_XML = 180, REMOTE_PROC_NWFILTER_UNDEFINE = 181, + REMOTE_PROC_DOMAIN_SNAPSHOT_CREATE_XML = 182, + REMOTE_PROC_DOMAIN_SNAPSHOT_DUMP_XML = 183, + REMOTE_PROC_DOMAIN_SNAPSHOT_NUM = 184, + REMOTE_PROC_DOMAIN_SNAPSHOT_LIST_NAMES = 185, + REMOTE_PROC_DOMAIN_SNAPSHOT_LOOKUP_BY_NAME = 186, + REMOTE_PROC_DOMAIN_HAS_CURRENT_SNAPSHOT = 187, + REMOTE_PROC_DOMAIN_SNAPSHOT_CURRENT = 188, + REMOTE_PROC_DOMAIN_REVERT_TO_SNAPSHOT = 189, + REMOTE_PROC_DOMAIN_SNAPSHOT_DELETE = 190, }; typedef enum remote_procedure remote_procedure; @@ -2088,6 +2199,7 @@ extern bool_t xdr_remote_nonnull_storage_pool (XDR *, remote_nonnull_storage_po extern bool_t xdr_remote_nonnull_storage_vol (XDR *, remote_nonnull_storage_vol*); extern bool_t xdr_remote_nonnull_node_device (XDR *, remote_nonnull_node_device*); extern bool_t xdr_remote_nonnull_secret (XDR *, remote_nonnull_secret*); +extern bool_t xdr_remote_nonnull_domain_snapshot (XDR *, remote_nonnull_domain_snapshot*); extern bool_t xdr_remote_domain (XDR *, remote_domain*); extern bool_t xdr_remote_network (XDR *, remote_network*); extern bool_t xdr_remote_nwfilter (XDR *, remote_nwfilter*); @@ -2380,6 +2492,22 @@ extern bool_t xdr_remote_domain_event_io_error_msg (XDR *, remote_domain_event_ extern bool_t xdr_remote_domain_event_graphics_address (XDR *, remote_domain_event_graphics_address*); extern bool_t xdr_remote_domain_event_graphics_identity (XDR *, remote_domain_event_graphics_identity*); extern bool_t xdr_remote_domain_event_graphics_msg (XDR *, remote_domain_event_graphics_msg*); +extern bool_t xdr_remote_domain_snapshot_create_xml_args (XDR *, remote_domain_snapshot_create_xml_args*); +extern bool_t xdr_remote_domain_snapshot_create_xml_ret (XDR *, remote_domain_snapshot_create_xml_ret*); +extern bool_t xdr_remote_domain_snapshot_dump_xml_args (XDR *, remote_domain_snapshot_dump_xml_args*); +extern bool_t xdr_remote_domain_snapshot_dump_xml_ret (XDR *, remote_domain_snapshot_dump_xml_ret*); +extern bool_t xdr_remote_domain_snapshot_num_args (XDR *, remote_domain_snapshot_num_args*); +extern bool_t xdr_remote_domain_snapshot_num_ret (XDR *, remote_domain_snapshot_num_ret*); +extern bool_t xdr_remote_domain_snapshot_list_names_args (XDR *, remote_domain_snapshot_list_names_args*); +extern bool_t xdr_remote_domain_snapshot_list_names_ret (XDR *, remote_domain_snapshot_list_names_ret*); +extern bool_t xdr_remote_domain_snapshot_lookup_by_name_args (XDR *, remote_domain_snapshot_lookup_by_name_args*); +extern bool_t xdr_remote_domain_snapshot_lookup_by_name_ret (XDR *, remote_domain_snapshot_lookup_by_name_ret*); +extern bool_t xdr_remote_domain_has_current_snapshot_args (XDR *, remote_domain_has_current_snapshot_args*); +extern bool_t xdr_remote_domain_has_current_snapshot_ret (XDR *, remote_domain_has_current_snapshot_ret*); +extern bool_t xdr_remote_domain_snapshot_current_args (XDR *, remote_domain_snapshot_current_args*); +extern bool_t xdr_remote_domain_snapshot_current_ret (XDR *, remote_domain_snapshot_current_ret*); +extern bool_t xdr_remote_domain_revert_to_snapshot_args (XDR *, remote_domain_revert_to_snapshot_args*); +extern bool_t xdr_remote_domain_snapshot_delete_args (XDR *, remote_domain_snapshot_delete_args*); extern bool_t xdr_remote_procedure (XDR *, remote_procedure*); extern bool_t xdr_remote_message_type (XDR *, remote_message_type*); extern bool_t xdr_remote_message_status (XDR *, remote_message_status*); @@ -2397,6 +2525,7 @@ extern bool_t xdr_remote_nonnull_storage_pool (); extern bool_t xdr_remote_nonnull_storage_vol (); extern bool_t xdr_remote_nonnull_node_device (); extern bool_t xdr_remote_nonnull_secret (); +extern bool_t xdr_remote_nonnull_domain_snapshot (); extern bool_t xdr_remote_domain (); extern bool_t xdr_remote_network (); extern bool_t xdr_remote_nwfilter (); @@ -2689,6 +2818,22 @@ extern bool_t xdr_remote_domain_event_io_error_msg (); extern bool_t xdr_remote_domain_event_graphics_address (); extern bool_t xdr_remote_domain_event_graphics_identity (); extern bool_t xdr_remote_domain_event_graphics_msg (); +extern bool_t xdr_remote_domain_snapshot_create_xml_args (); +extern bool_t xdr_remote_domain_snapshot_create_xml_ret (); +extern bool_t xdr_remote_domain_snapshot_dump_xml_args (); +extern bool_t xdr_remote_domain_snapshot_dump_xml_ret (); +extern bool_t xdr_remote_domain_snapshot_num_args (); +extern bool_t xdr_remote_domain_snapshot_num_ret (); +extern bool_t xdr_remote_domain_snapshot_list_names_args (); +extern bool_t xdr_remote_domain_snapshot_list_names_ret (); +extern bool_t xdr_remote_domain_snapshot_lookup_by_name_args (); +extern bool_t xdr_remote_domain_snapshot_lookup_by_name_ret (); +extern bool_t xdr_remote_domain_has_current_snapshot_args (); +extern bool_t xdr_remote_domain_has_current_snapshot_ret (); +extern bool_t xdr_remote_domain_snapshot_current_args (); +extern bool_t xdr_remote_domain_snapshot_current_ret (); +extern bool_t xdr_remote_domain_revert_to_snapshot_args (); +extern bool_t xdr_remote_domain_snapshot_delete_args (); extern bool_t xdr_remote_procedure (); extern bool_t xdr_remote_message_type (); extern bool_t xdr_remote_message_status (); diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index bf87c33..b36ae1b 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -121,6 +121,9 @@ const REMOTE_AUTH_TYPE_LIST_MAX = 20; /* Upper limit on list of memory stats */ const REMOTE_DOMAIN_MEMORY_STATS_MAX = 1024; +/* Upper limit on lists of domain snapshots. */ +const REMOTE_DOMAIN_SNAPSHOT_LIST_NAMES_MAX = 1024; + /* Maximum length of a block peek buffer message. * Note applications need to be aware of this limit and issue multiple * requests for large amounts of data. @@ -216,6 +219,12 @@ struct remote_nonnull_secret { remote_nonnull_string usageID; }; +/* A snapshot which may not be NULL. */ +struct remote_nonnull_domain_snapshot { + remote_nonnull_string name; + remote_nonnull_domain domain; +}; + /* A domain or network which may be NULL. */ typedef remote_nonnull_domain *remote_domain; typedef remote_nonnull_network *remote_network; @@ -1647,6 +1656,83 @@ struct remote_domain_event_graphics_msg { remote_domain_event_graphics_identity subject<REMOTE_DOMAIN_EVENT_GRAPHICS_IDENTITY_MAX>; }; +struct remote_domain_snapshot_create_xml_args { + remote_nonnull_domain domain; + remote_nonnull_string xml_desc; + int flags; +}; + +struct remote_domain_snapshot_create_xml_ret { + remote_nonnull_domain_snapshot snap; +}; + +struct remote_domain_snapshot_dump_xml_args { + remote_nonnull_domain_snapshot snap; + int flags; +}; + +struct remote_domain_snapshot_dump_xml_ret { + remote_nonnull_string xml; +}; + +struct remote_domain_snapshot_num_args { + remote_nonnull_domain domain; + int flags; +}; + +struct remote_domain_snapshot_num_ret { + int num; +}; + +struct remote_domain_snapshot_list_names_args { + remote_nonnull_domain domain; + int nameslen; + int flags; +}; + +struct remote_domain_snapshot_list_names_ret { + remote_nonnull_string names<REMOTE_DOMAIN_SNAPSHOT_LIST_NAMES_MAX>; +}; + +struct remote_domain_snapshot_lookup_by_name_args { + remote_nonnull_domain domain; + remote_nonnull_string name; + int flags; +}; + +struct remote_domain_snapshot_lookup_by_name_ret { + remote_nonnull_domain_snapshot snap; +}; + +struct remote_domain_has_current_snapshot_args { + remote_nonnull_domain domain; + int flags; +}; + +struct remote_domain_has_current_snapshot_ret { + int result; +}; + +struct remote_domain_snapshot_current_args { + remote_nonnull_domain domain; + int flags; +}; + +struct remote_domain_snapshot_current_ret { + remote_nonnull_domain_snapshot snap; +}; + +struct remote_domain_revert_to_snapshot_args { + remote_nonnull_domain_snapshot snap; + int flags; +}; + +struct remote_domain_snapshot_delete_args { + remote_nonnull_domain_snapshot snap; + int flags; +}; + + /*----- Protocol. -----*/ /* Define the program number, protocol version and procedure numbers here. */ @@ -1852,7 +1938,16 @@ enum remote_procedure { REMOTE_PROC_LIST_NWFILTERS = 179, REMOTE_PROC_NWFILTER_DEFINE_XML = 180, - REMOTE_PROC_NWFILTER_UNDEFINE = 181 + REMOTE_PROC_NWFILTER_UNDEFINE = 181, + REMOTE_PROC_DOMAIN_SNAPSHOT_CREATE_XML = 182, + REMOTE_PROC_DOMAIN_SNAPSHOT_DUMP_XML = 183, + REMOTE_PROC_DOMAIN_SNAPSHOT_NUM = 184, + REMOTE_PROC_DOMAIN_SNAPSHOT_LIST_NAMES = 185, + REMOTE_PROC_DOMAIN_SNAPSHOT_LOOKUP_BY_NAME = 186, + REMOTE_PROC_DOMAIN_HAS_CURRENT_SNAPSHOT = 187, + REMOTE_PROC_DOMAIN_SNAPSHOT_CURRENT = 188, + REMOTE_PROC_DOMAIN_REVERT_TO_SNAPSHOT = 189, + REMOTE_PROC_DOMAIN_SNAPSHOT_DELETE = 190 /* * Notice how the entries are grouped in sets of 10 ? diff --git a/src/test/test_driver.c b/src/test/test_driver.c index 646c7db..e322010 100644 --- a/src/test/test_driver.c +++ b/src/test/test_driver.c @@ -5306,6 +5306,15 @@ static virDriver testDriver = { NULL, /* domainMigrateSetMaxDowntime */ testDomainEventRegisterAny, /* domainEventRegisterAny */ testDomainEventDeregisterAny, /* domainEventDeregisterAny */ + NULL, /* domainSnapshotCreateXML */ + NULL, /* domainSnapshotDumpXML */ + NULL, /* domainSnapshotNum */ + NULL, /* domainSnapshotListNames */ + NULL, /* domainSnapshotLookupByName */ + NULL, /* domainHasCurrentSnapshot */ + NULL, /* domainSnapshotCurrent */ + NULL, /* domainRevertToSnapshot */ + NULL, /* domainSnapshotDelete */ }; static virNetworkDriver testNetworkDriver = { diff --git a/src/uml/uml_driver.c b/src/uml/uml_driver.c index 08fbf93..47ee5ef 100644 --- a/src/uml/uml_driver.c +++ b/src/uml/uml_driver.c @@ -1937,6 +1937,15 @@ static virDriver umlDriver = { NULL, /* domainMigrateSetMaxDowntime */ NULL, /* domainEventRegisterAny */ NULL, /* domainEventDeregisterAny */ + NULL, /* domainSnapshotCreateXML */ + NULL, /* domainSnapshotDumpXML */ + NULL, /* domainSnapshotNum */ + NULL, /* domainSnapshotListNames */ + NULL, /* domainSnapshotLookupByName */ + NULL, /* domainHasCurrentSnapshot */ + NULL, /* domainSnapshotCurrent */ + NULL, /* domainRevertToSnapshot */ + NULL, /* domainSnapshotDelete */ }; diff --git a/src/util/virterror.c b/src/util/virterror.c index 2537110..d29f95b 100644 --- a/src/util/virterror.c +++ b/src/util/virterror.c @@ -184,6 +184,9 @@ static const char *virErrorDomainName(virErrorDomain domain) { case VIR_FROM_HOOK: dom = "Sync Hook "; break; + case VIR_FROM_DOMAIN_SNAPSHOT: + dom = "Domain Snapshot "; + break; } return(dom); } @@ -1153,6 +1156,18 @@ virErrorMsg(virErrorNumber error, const char *info) else errmsg = _("Hook script execution failed: %s"); break; + case VIR_ERR_INVALID_DOMAIN_SNAPSHOT: + if (info == NULL) + errmsg = _("Invalid snapshot"); + else + errmsg = _("Invalid snapshot: %s"); + break; + case VIR_ERR_NO_DOMAIN_SNAPSHOT: + if (info == NULL) + errmsg = _("Domain snapshot not found"); + else + errmsg = _("Domain snapshot not found: %s"); + break; } return (errmsg); } diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index 59ad1b8..dac386d 100644 --- a/src/vbox/vbox_tmpl.c +++ b/src/vbox/vbox_tmpl.c @@ -7184,6 +7184,15 @@ virDriver NAME(Driver) = { vboxDomainEventRegisterAny, /* domainEventRegisterAny */ vboxDomainEventDeregisterAny, /* domainEventDeregisterAny */ #endif + NULL, /* domainSnapshotCreateXML */ + NULL, /* domainSnapshotDumpXML */ + NULL, /* domainSnapshotNum */ + NULL, /* domainSnapshotListNames */ + NULL, /* domainSnapshotLookupByName */ + NULL, /* domainHasCurrentSnapshot */ + NULL, /* domainSnapshotCurrent */ + NULL, /* domainRevertToSnapshot */ + NULL, /* domainSnapshotDelete */ }; virNetworkDriver NAME(NetworkDriver) = { diff --git a/src/xen/xen_driver.c b/src/xen/xen_driver.c index ebdc600..04489a5 100644 --- a/src/xen/xen_driver.c +++ b/src/xen/xen_driver.c @@ -1980,6 +1980,15 @@ static virDriver xenUnifiedDriver = { NULL, /* domainMigrateSetMaxDowntime */ xenUnifiedDomainEventRegisterAny, /* domainEventRegisterAny */ xenUnifiedDomainEventDeregisterAny, /* domainEventDeregisterAny */ + NULL, /* domainSnapshotCreateXML */ + NULL, /* domainSnapshotDumpXML */ + NULL, /* domainSnapshotNum */ + NULL, /* domainSnapshotListNames */ + NULL, /* domainSnapshotLookupByName */ + NULL, /* domainHasCurrentSnapshot */ + NULL, /* domainSnapshotCurrent */ + NULL, /* domainRevertToSnapshot */ + NULL, /* domainSnapshotDelete */ }; /** diff --git a/src/xenapi/xenapi_driver.c b/src/xenapi/xenapi_driver.c index dcfdc1e..19ff9da 100644 --- a/src/xenapi/xenapi_driver.c +++ b/src/xenapi/xenapi_driver.c @@ -1783,6 +1783,15 @@ static virDriver xenapiDriver = { NULL, /* domainMigrateSetMaxDowntime */ NULL, /* domainEventRegisterAny */ NULL, /* domainEventDeregisterAny */ + NULL, /* domainSnapshotCreateXML */ + NULL, /* domainSnapshotDumpXML */ + NULL, /* domainSnapshotNum */ + NULL, /* domainSnapshotListNames */ + NULL, /* domainSnapshotLookupByName */ + NULL, /* domainHasCurrentSnapshot */ + NULL, /* domainSnapshotCurrent */ + NULL, /* domainRevertToSnapshot */ + NULL, /* domainSnapshotDelete */ }; /** -- 1.6.6.1

On Fri, Apr 02, 2010 at 09:45:56PM -0400, Chris Lalancette wrote:
Signed-off-by: Chris Lalancette <clalance@redhat.com> [...] diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 7cb483e..a9f434b 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -1374,6 +1374,7 @@ typedef enum { VIR_DOMAIN_EVENT_STARTED_BOOTED = 0, /* Normal startup from boot */ VIR_DOMAIN_EVENT_STARTED_MIGRATED = 1, /* Incoming migration from another host */ VIR_DOMAIN_EVENT_STARTED_RESTORED = 2, /* Restored from a state file */ + VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT = 3, /* Restored from snapshot */ } virDomainEventStartedDetailType;
/** @@ -1410,6 +1411,7 @@ typedef enum { VIR_DOMAIN_EVENT_STOPPED_MIGRATED = 3, /* Migrated off to another host */ VIR_DOMAIN_EVENT_STOPPED_SAVED = 4, /* Saved to a state file */ VIR_DOMAIN_EVENT_STOPPED_FAILED = 5, /* Host emulator/mgmt failed */ + VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT = 6, /* offline snapshot loaded */ } virDomainEventStoppedDetailType;
@@ -1861,6 +1863,66 @@ int virDomainGetJobInfo(virDomainPtr dom, virDomainJobInfoPtr info); int virDomainAbortJob(virDomainPtr dom);
+/** + * virDomainSnapshot: + * + * a virDomainSnapshot is a private structure representing a snapshot of + * a domain. + */ +typedef struct _virDomainSnapshot virDomainSnapshot; + +/** + * virDomainSnapshotPtr: + * + * a virDomainSnapshotPtr is pointer to a virDomainSnapshot private structure, + * and is the type used to reference a domain snapshot in the API. + */ +typedef virDomainSnapshot *virDomainSnapshotPtr; + +/* Take a snapshot of the current VM state */ +virDomainSnapshotPtr virDomainSnapshotCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags); + +/* Dump the XML of a snapshot */ +char *virDomainSnapshotGetXMLDesc(virDomainSnapshotPtr snapshot, + unsigned int flags); + +/* Return the number of snapshots for this domain */ +int virDomainSnapshotNum(virDomainPtr domain, unsigned int flags); + +/* Get the names of all snapshots for this domain */ +int virDomainSnapshotListNames(virDomainPtr domain, char **names, int nameslen, + unsigned int flags); + +/* Get a handle to a named snapshot */ +virDomainSnapshotPtr virDomainSnapshotLookupByName(virDomainPtr domain, + const char *name, + unsigned int flags); + +/* Check whether a domain has a snapshot which is currently used */ +int virDomainHasCurrentSnapshot(virDomainPtr domain, unsigned flags); + +/* Get a handle to the current snapshot */ +virDomainSnapshotPtr virDomainSnapshotCurrent(virDomainPtr domain, + unsigned int flags); + +/* Revert the domain to a point-in-time snapshot. The + * state of the guest after this call will be the state + * of the guest when the snapshot in question was taken + */ +int virDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, + unsigned int flags); + +/* Deactivate a snapshot */ +typedef enum { + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN = (1 << 0), +} virDomainSnapshotDeleteFlags; + +int virDomainSnapshotDelete(virDomainSnapshotPtr snapshot, + unsigned int flags); + +int virDomainSnapshotFree(virDomainSnapshotPtr snapshot);
/* A generic callback definition. Specific events usually have a customization * with extra parameters */ diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h index f69d07e..3bbb293 100644 --- a/include/libvirt/virterror.h +++ b/include/libvirt/virterror.h @@ -71,7 +71,8 @@ typedef enum { VIR_FROM_CPU, /* Error from CPU driver */ VIR_FROM_XENAPI, /* Error from XenAPI */ VIR_FROM_NWFILTER, /* Error from network filter driver */ - VIR_FROM_HOOK /* Error from Synchronous hooks */ + VIR_FROM_HOOK, /* Error from Synchronous hooks */ + VIR_FROM_DOMAIN_SNAPSHOT, /* Error from domain snapshot */ } virErrorDomain;
@@ -183,6 +184,8 @@ typedef enum { VIR_ERR_MIGRATE_PERSIST_FAILED, /* a migration worked, but making the VM persist on the dest host failed */ VIR_ERR_HOOK_SCRIPT_FAILED, /* a synchronous hook script failed */ + VIR_ERR_INVALID_DOMAIN_SNAPSHOT, /* invalid domain snapshot */ + VIR_ERR_NO_DOMAIN_SNAPSHOT, /* domain snapshot not found */ } virErrorNumber;
API looks fine !
diff --git a/python/generator.py b/python/generator.py index a24e122..cb9f3d9 100755 --- a/python/generator.py +++ b/python/generator.py @@ -241,6 +241,8 @@ py_types = { 'const virStreamPtr': ('O', "virStream", "virStreamPtr", "virStreamPtr"), 'virStream *': ('O', "virStream", "virStreamPtr", "virStreamPtr"), 'const virStream *': ('O', "virStream", "virStreamPtr", "virStreamPtr"), + + 'virDomainSnapshotPtr': ('O', "virDomainSnapshot", "virDomainSnapshotPtr", "virDomainSnapshotPtr"), }
py_return_types = { @@ -317,6 +319,7 @@ skip_impl = ( 'virNodeListDevices', 'virNodeDeviceListCaps', 'virConnectBaselineCPU', + 'virDomainSnapshotListNames', )
with python bindings, great ! [...]
diff --git a/src/libvirt.c b/src/libvirt.c index 5247fe7..25e358c 100644
libvirt.c looks fine ACK Daniel -- Daniel Veillard | libxml Gnome XML XSLT toolkit http://xmlsoft.org/ daniel@veillard.com | Rpmfind RPM search engine http://rpmfind.net/ http://veillard.com/ | virtualization library http://libvirt.org/

2010/4/3 Chris Lalancette <clalance@redhat.com>:
Signed-off-by: Chris Lalancette <clalance@redhat.com> --- daemon/remote.c | 311 ++++++++++++++++++++++++ daemon/remote_dispatch_args.h | 9 + daemon/remote_dispatch_prototypes.h | 72 ++++++ daemon/remote_dispatch_ret.h | 7 + daemon/remote_dispatch_table.h | 45 ++++ include/libvirt/libvirt.h.in | 62 +++++ include/libvirt/virterror.h | 5 +- python/generator.py | 3 + python/typewrappers.c | 15 ++ python/typewrappers.h | 10 + src/datatypes.c | 122 ++++++++++ src/datatypes.h | 25 ++ src/driver.h | 47 ++++ src/esx/esx_driver.c | 9 + src/libvirt.c | 456 +++++++++++++++++++++++++++++++++++ src/libvirt_private.syms | 1 + src/libvirt_public.syms | 10 + src/lxc/lxc_driver.c | 9 + src/opennebula/one_driver.c | 9 + src/openvz/openvz_driver.c | 9 + src/phyp/phyp_driver.c | 9 + src/qemu/qemu_driver.c | 9 + src/remote/remote_driver.c | 309 ++++++++++++++++++++++++ src/remote/remote_protocol.c | 181 ++++++++++++++ src/remote/remote_protocol.h | 145 +++++++++++ src/remote/remote_protocol.x | 97 ++++++++- src/test/test_driver.c | 9 + src/uml/uml_driver.c | 9 + src/util/virterror.c | 15 ++ src/vbox/vbox_tmpl.c | 9 + src/xen/xen_driver.c | 9 + src/xenapi/xenapi_driver.c | 9 + 32 files changed, 2044 insertions(+), 2 deletions(-)
+/* Get a handle to the current snapshot */ +virDomainSnapshotPtr virDomainSnapshotCurrent(virDomainPtr domain, + unsigned int flags); + +/* Revert the domain to a point-in-time snapshot. The + * state of the guest after this call will be the state + * of the guest when the snapshot in question was taken + */ +int virDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, + unsigned int flags); + +/* Deactivate a snapshot */
s/Deactivate/Delete/
+typedef enum { + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN = (1 << 0), +} virDomainSnapshotDeleteFlags; + +int virDomainSnapshotDelete(virDomainSnapshotPtr snapshot, + unsigned int flags); + +int virDomainSnapshotFree(virDomainSnapshotPtr snapshot);
diff --git a/src/libvirt.c b/src/libvirt.c index 5247fe7..25e358c 100644 --- a/src/libvirt.c +++ b/src/libvirt.c
+ +/** + * virDomainSnapshotGetXMLDesc: + * @snapshot: a domain snapshot object + * @flags: unused flag parameters; callers should pass 0 + * + * Provide an XML description of the domain snapshot. + * + * Returns a 0 terminated UTF-8 encoded XML instance, or NULL in case of error. + * the caller must free() the returned value. + */ +char * +virDomainSnapshotGetXMLDesc(virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + virConnectPtr conn; + DEBUG("snapshot=%p, flags=%d", snapshot, flags); + + virResetLastError(); + + if (!VIR_IS_DOMAIN_SNAPSHOT(snapshot)) { + virLibDomainSnapshotError(NULL, VIR_ERR_INVALID_DOMAIN_SNAPSHOT, + __FUNCTION__); + virDispatchError(NULL); + return (NULL); + } + + conn = snapshot->domain->conn; + + if ((conn->flags & VIR_CONNECT_RO) && (flags & VIR_DOMAIN_XML_SECURE)) { + virLibConnError(conn, VIR_ERR_OPERATION_DENIED, + _("virDomainSnapshotGetXMLDesc with secure flag")); + goto error; + } + + flags &= VIR_DOMAIN_XML_FLAGS_MASK;
Copy&paste leftover?
+ if (conn->driver->domainSnapshotDumpXML) { + char *ret; + ret = conn->driver->domainSnapshotDumpXML(snapshot, flags); + if (!ret) + goto error; + return ret; + } + + virLibConnError (conn, VIR_ERR_NO_SUPPORT, __FUNCTION__); +error: + virDispatchError(conn); + return NULL; +} +
+ +/** + * virDomainSnapshotDelete + * @snapshot: a domain snapshot object + * @flags: flag parameters + * + * Delete the snapshot. + * + * If @flags is 0, then just this snapshot is deleted, and changes from + * this snapshot are automatically pushed to children snapshots. If + * flags is VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN, then this snapshot + * and any children snapshots are deleted.
s/pushed to/merged into/
+ * Returns 0 if the snapshot was successfully deleted, -1 on error. + */
ACK. Matthias

Signed-off-by: Chris Lalancette <clalance@redhat.com> --- src/conf/domain_conf.c | 365 ++++++++++++++++++++++++++++++++++++++++++++++ src/conf/domain_conf.h | 55 +++++++ src/libvirt_private.syms | 10 ++ 3 files changed, 430 insertions(+), 0 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index d6ba4f6..41c83fd 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -28,6 +28,7 @@ #include <unistd.h> #include <fcntl.h> #include <dirent.h> +#include <sys/time.h> #include "virterror_internal.h" #include "datatypes.h" @@ -43,6 +44,7 @@ #include "network.h" #include "macvtap.h" #include "nwfilter_conf.h" +#include "ignore-value.h" #define VIR_FROM_THIS VIR_FROM_DOMAIN @@ -744,6 +746,8 @@ static void virDomainObjFree(virDomainObjPtr dom) virMutexDestroy(&dom->lock); + virDomainSnapshotObjListDeinit(&dom->snapshots); + VIR_FREE(dom); } @@ -796,6 +800,8 @@ static virDomainObjPtr virDomainObjNew(virCapsPtr caps) domain->state = VIR_DOMAIN_SHUTOFF; domain->refs = 1; + virDomainSnapshotObjListInit(&domain->snapshots); + VIR_DEBUG("obj=%p", domain); return domain; } @@ -6558,4 +6564,363 @@ cleanup: return -1; } +/* Snapshot Def functions */ +void virDomainSnapshotDefFree(virDomainSnapshotDefPtr def) +{ + if (!def) + return; + + VIR_FREE(def->name); + VIR_FREE(def->description); + VIR_FREE(def->parent); + VIR_FREE(def); +} + +virDomainSnapshotDefPtr virDomainSnapshotDefParseString(const char *xmlStr, + int newSnapshot) +{ + xmlXPathContextPtr ctxt = NULL; + xmlDocPtr xml = NULL; + xmlNodePtr root; + virDomainSnapshotDefPtr def = NULL; + virDomainSnapshotDefPtr ret = NULL; + char *creation = NULL, *state = NULL; + struct timeval tv; + + xml = virXMLParse(NULL, xmlStr, "domainsnapshot.xml"); + if (!xml) { + virDomainReportError(VIR_ERR_XML_ERROR, + "%s",_("failed to parse snapshot xml document")); + return NULL; + } + + if ((root = xmlDocGetRootElement(xml)) == NULL) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("missing root element")); + goto cleanup; + } + + if (!xmlStrEqual(root->name, BAD_CAST "domainsnapshot")) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("incorrect root element")); + goto cleanup; + } + + ctxt = xmlXPathNewContext(xml); + if (ctxt == NULL) { + virReportOOMError(); + goto cleanup; + } + + if (VIR_ALLOC(def) < 0) { + virReportOOMError(); + goto cleanup; + } + + ctxt->node = root; + + gettimeofday(&tv, NULL); + + def->name = virXPathString("string(./name)", ctxt); + if (def->name == NULL) + ignore_value(virAsprintf(&def->name, "%ld", tv.tv_sec)); + + if (def->name == NULL) { + virReportOOMError(); + goto cleanup; + } + + def->description = virXPathString("string(./description)", ctxt); + + if (!newSnapshot) { + if (virXPathLong("string(./creationTime)", ctxt, + &def->creationTime) < 0) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing creationTime from existing snapshot")); + goto cleanup; + } + + def->parent = virXPathString("string(./parent/name)", ctxt); + + state = virXPathString("string(./state)", ctxt); + if (state == NULL) { + /* there was no state in an existing snapshot; this + * should never happen + */ + virDomainReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing state from existing snapshot")); + goto cleanup; + } + def->state = virDomainStateTypeFromString(state); + if (def->state < 0) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, + _("Invalid state '%s' in domain snapshot XML"), + state); + goto cleanup; + } + + if (virXPathLong("string(./active)", ctxt, &def->active) < 0) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not find 'active' element")); + goto cleanup; + } + } + else + def->creationTime = tv.tv_sec; + + ret = def; + +cleanup: + VIR_FREE(creation); + VIR_FREE(state); + xmlXPathFreeContext(ctxt); + if (ret == NULL) + virDomainSnapshotDefFree(def); + xmlFreeDoc(xml); + + return ret; +} + +char *virDomainSnapshotDefFormat(char *domain_uuid, + virDomainSnapshotDefPtr def, + int internal) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + + virBufferAddLit(&buf, "<domainsnapshot>\n"); + virBufferVSprintf(&buf, " <name>%s</name>\n", def->name); + if (def->description) + virBufferVSprintf(&buf, " <description>%s</description>\n", + def->description); + virBufferVSprintf(&buf, " <state>%s</state>\n", + virDomainStateTypeToString(def->state)); + if (def->parent) { + virBufferAddLit(&buf, " <parent>\n"); + virBufferVSprintf(&buf, " <name>%s</name>\n", def->parent); + virBufferAddLit(&buf, " </parent>\n"); + } + virBufferVSprintf(&buf, " <creationTime>%ld</creationTime>\n", + def->creationTime); + virBufferAddLit(&buf, " <domain>\n"); + virBufferVSprintf(&buf, " <uuid>%s</uuid>\n", domain_uuid); + virBufferAddLit(&buf, " </domain>\n"); + if (internal) + virBufferVSprintf(&buf, " <active>%ld</active>\n", def->active); + virBufferAddLit(&buf, "</domainsnapshot>\n"); + + if (virBufferError(&buf)) { + virBufferFreeAndReset(&buf); + virReportOOMError(); + return NULL; + } + + return virBufferContentAndReset(&buf); +} + +/* Snapshot Obj functions */ +static virDomainSnapshotObjPtr virDomainSnapshotObjNew(void) +{ + virDomainSnapshotObjPtr snapshot; + + if (VIR_ALLOC(snapshot) < 0) { + virReportOOMError(); + return NULL; + } + + snapshot->refs = 1; + + VIR_DEBUG("obj=%p", snapshot); + + return snapshot; +} + +static void virDomainSnapshotObjFree(virDomainSnapshotObjPtr snapshot) +{ + if (!snapshot) + return; + + VIR_DEBUG("obj=%p", snapshot); + + virDomainSnapshotDefFree(snapshot->def); +} + +int virDomainSnapshotObjUnref(virDomainSnapshotObjPtr snapshot) +{ + snapshot->refs--; + VIR_DEBUG("obj=%p refs=%d", snapshot, snapshot->refs); + if (snapshot->refs == 0) { + virDomainSnapshotObjFree(snapshot); + return 0; + } + return snapshot->refs; +} + +virDomainSnapshotObjPtr virDomainSnapshotAssignDef(virDomainSnapshotObjListPtr snapshots, + const virDomainSnapshotDefPtr def) +{ + virDomainSnapshotObjPtr snap; + + if (virHashLookup(snapshots->objs, def->name) != NULL) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, + _("unexpected domain snapshot %s already exists"), + def->name); + return NULL; + } + + if (!(snap = virDomainSnapshotObjNew())) + return NULL; + snap->def = def; + + if (virHashAddEntry(snapshots->objs, snap->def->name, snap) < 0) { + VIR_FREE(snap); + virReportOOMError(); + return NULL; + } + + return snap; +} + +/* Snapshot Obj List functions */ +int virDomainSnapshotObjListInit(virDomainSnapshotObjListPtr snapshots) +{ + snapshots->objs = virHashCreate(50); + if (!snapshots->objs) { + virReportOOMError(); + return -1; + } + return 0; +} + +static void virDomainSnapshotObjListDeallocator(void *payload, + const char *name ATTRIBUTE_UNUSED) +{ + virDomainSnapshotObjPtr obj = payload; + + virDomainSnapshotObjUnref(obj); +} + +void virDomainSnapshotObjListDeinit(virDomainSnapshotObjListPtr snapshots) +{ + if (snapshots->objs) + virHashFree(snapshots->objs, virDomainSnapshotObjListDeallocator); +} + +struct virDomainSnapshotNameData { + int oom; + int numnames; + int maxnames; + char **const names; +}; + +static void virDomainSnapshotObjListCopyNames(void *payload, + const char *name ATTRIBUTE_UNUSED, + void *opaque) +{ + virDomainSnapshotObjPtr obj = payload; + struct virDomainSnapshotNameData *data = opaque; + + if (data->oom) + return; + + if (data->numnames < data->maxnames) { + if (!(data->names[data->numnames] = strdup(obj->def->name))) + data->oom = 1; + else + data->numnames++; + } +} + +int virDomainSnapshotObjListGetNames(virDomainSnapshotObjListPtr snapshots, + char **const names, int maxnames) +{ + struct virDomainSnapshotNameData data = { 0, 0, maxnames, names }; + int i; + + virHashForEach(snapshots->objs, virDomainSnapshotObjListCopyNames, &data); + if (data.oom) { + virReportOOMError(); + goto cleanup; + } + + return data.numnames; + +cleanup: + for (i = 0; i < data.numnames; i++) + VIR_FREE(data.names[i]); + return -1; +} + +static void virDomainSnapshotObjListCount(void *payload ATTRIBUTE_UNUSED, + const char *name ATTRIBUTE_UNUSED, + void *data) +{ + int *count = data; + + (*count)++; +} + +int virDomainSnapshotObjListNum(virDomainSnapshotObjListPtr snapshots) +{ + int count = 0; + + virHashForEach(snapshots->objs, virDomainSnapshotObjListCount, &count); + + return count; +} + +static int virDomainSnapshotObjListSearchName(const void *payload, + const char *name ATTRIBUTE_UNUSED, + const void *data) +{ + virDomainSnapshotObjPtr obj = (virDomainSnapshotObjPtr)payload; + int want = 0; + + if (STREQ(obj->def->name, (const char *)data)) + want = 1; + + return want; +} + +virDomainSnapshotObjPtr virDomainSnapshotFindByName(const virDomainSnapshotObjListPtr snapshots, + const char *name) +{ + return virHashSearch(snapshots->objs, virDomainSnapshotObjListSearchName, name); +} + +void virDomainSnapshotObjListRemove(virDomainSnapshotObjListPtr snapshots, + virDomainSnapshotObjPtr snapshot) +{ + virHashRemoveEntry(snapshots->objs, snapshot->def->name, + virDomainSnapshotObjListDeallocator); +} + +struct snapshot_has_children { + char *name; + int number; +}; + +static void virDomainSnapshotCountChildren(void *payload, + const char *name ATTRIBUTE_UNUSED, + void *data) +{ + virDomainSnapshotObjPtr obj = payload; + struct snapshot_has_children *curr = data; + + if (obj->def->parent && STREQ(obj->def->parent, curr->name)) + curr->number++; +} + +int virDomainSnapshotHasChildren(virDomainSnapshotObjPtr snap, + virDomainSnapshotObjListPtr snapshots) +{ + struct snapshot_has_children children; + + children.name = snap->def->name; + children.number = 0; + virHashForEach(snapshots->objs, virDomainSnapshotCountChildren, &children); + + return children.number; +} + + #endif /* ! PROXY */ diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index b789289..5c64a47 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -741,6 +741,58 @@ struct _virDomainClockDef { # define VIR_DOMAIN_CPUMASK_LEN 1024 + +/* Snapshot state */ +typedef struct _virDomainSnapshotDef virDomainSnapshotDef; +typedef virDomainSnapshotDef *virDomainSnapshotDefPtr; +struct _virDomainSnapshotDef { + char *name; + char *description; + char *parent; + time_t creationTime; + int state; + + long active; +}; + +typedef struct _virDomainSnapshotObj virDomainSnapshotObj; +typedef virDomainSnapshotObj *virDomainSnapshotObjPtr; +struct _virDomainSnapshotObj { + int refs; + + virDomainSnapshotDefPtr def; +}; + +typedef struct _virDomainSnapshotObjList virDomainSnapshotObjList; +typedef virDomainSnapshotObjList *virDomainSnapshotObjListPtr; +struct _virDomainSnapshotObjList { + /* name string -> virDomainSnapshotObj mapping + * for O(1), lockless lookup-by-name */ + virHashTable *objs; +}; + +virDomainSnapshotDefPtr virDomainSnapshotDefParseString(const char *xmlStr, + int newSnapshot); +void virDomainSnapshotDefFree(virDomainSnapshotDefPtr def); +char *virDomainSnapshotDefFormat(char *domain_uuid, + virDomainSnapshotDefPtr def, + int internal); +virDomainSnapshotObjPtr virDomainSnapshotAssignDef(virDomainSnapshotObjListPtr snapshots, + const virDomainSnapshotDefPtr def); + +int virDomainSnapshotObjListInit(virDomainSnapshotObjListPtr objs); +void virDomainSnapshotObjListDeinit(virDomainSnapshotObjListPtr objs); +int virDomainSnapshotObjListGetNames(virDomainSnapshotObjListPtr snapshots, + char **const names, int maxnames); +int virDomainSnapshotObjListNum(virDomainSnapshotObjListPtr snapshots); +virDomainSnapshotObjPtr virDomainSnapshotFindByName(const virDomainSnapshotObjListPtr snapshots, + const char *name); +void virDomainSnapshotObjListRemove(virDomainSnapshotObjListPtr snapshots, + virDomainSnapshotObjPtr snapshot); +int virDomainSnapshotObjUnref(virDomainSnapshotObjPtr snapshot); +int virDomainSnapshotHasChildren(virDomainSnapshotObjPtr snap, + virDomainSnapshotObjListPtr snapshots); + /* Guest VM main configuration */ typedef struct _virDomainDef virDomainDef; typedef virDomainDef *virDomainDefPtr; @@ -828,6 +880,9 @@ struct _virDomainObj { virDomainDefPtr def; /* The current definition */ virDomainDefPtr newDef; /* New definition to activate at shutdown */ + virDomainSnapshotObjList snapshots; + virDomainSnapshotObjPtr current_snapshot; + void *privateData; void (*privateDataFreeFunc)(void *); }; diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 86aacd7..1682b25 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -206,6 +206,16 @@ virDomainTimerTickpolicyTypeToString; virDomainTimerTickpolicyTypeFromString; virDomainTimerModeTypeToString; virDomainTimerModeTypeFromString; +virDomainSnapshotObjListGetNames; +virDomainSnapshotObjListNum; +virDomainSnapshotFindByName; +virDomainSnapshotObjListAdd; +virDomainSnapshotObjListRemove; +virDomainSnapshotHasChildren; +virDomainSnapshotObjUnref; +virDomainSnapshotDefParseString; +virDomainSnapshotDefFormat; +virDomainSnapshotAssignDef; # domain_event.h -- 1.6.6.1

On Fri, Apr 02, 2010 at 09:45:57PM -0400, Chris Lalancette wrote:
Signed-off-by: Chris Lalancette <clalance@redhat.com> [...] +char *virDomainSnapshotDefFormat(char *domain_uuid, + virDomainSnapshotDefPtr def, + int internal) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + + virBufferAddLit(&buf, "<domainsnapshot>\n"); + virBufferVSprintf(&buf, " <name>%s</name>\n", def->name); + if (def->description) + virBufferVSprintf(&buf, " <description>%s</description>\n", + def->description); + virBufferVSprintf(&buf, " <state>%s</state>\n", + virDomainStateTypeToString(def->state)); + if (def->parent) { + virBufferAddLit(&buf, " <parent>\n"); + virBufferVSprintf(&buf, " <name>%s</name>\n", def->parent); + virBufferAddLit(&buf, " </parent>\n"); + } + virBufferVSprintf(&buf, " <creationTime>%ld</creationTime>\n", + def->creationTime); + virBufferAddLit(&buf, " <domain>\n"); + virBufferVSprintf(&buf, " <uuid>%s</uuid>\n", domain_uuid); + virBufferAddLit(&buf, " </domain>\n"); + if (internal) + virBufferVSprintf(&buf, " <active>%ld</active>\n", def->active); + virBufferAddLit(&buf, "</domainsnapshot>\n"); + + if (virBufferError(&buf)) { + virBufferFreeAndReset(&buf); + virReportOOMError(); + return NULL; + } + + return virBufferContentAndReset(&buf); +}
My only suggestion would be if we can add the name of the host where the snapshot was taken, but that's a "nice to have" and can probably be added later, ACK Daniel -- Daniel Veillard | libxml Gnome XML XSLT toolkit http://xmlsoft.org/ daniel@veillard.com | Rpmfind RPM search engine http://rpmfind.net/ http://veillard.com/ | virtualization library http://libvirt.org/

2010/4/3 Chris Lalancette <clalance@redhat.com>:
Signed-off-by: Chris Lalancette <clalance@redhat.com> --- src/conf/domain_conf.c | 365 ++++++++++++++++++++++++++++++++++++++++++++++ src/conf/domain_conf.h | 55 +++++++ src/libvirt_private.syms | 10 ++ 3 files changed, 430 insertions(+), 0 deletions(-)
ACK. Matthias

While playing around with def/newDef with the qemu code, I noticed that newDef was *always* getting set to a value, even when I didn't redefine the domain. I think the problem is the virDomainLoadConfig is always doing virDomainAssignDef regardless of whether the domain already exists in the hashtable. In turn, virDomainAssignDef is assigning the definition (which is actually a duplicate) to newDef. Fix this so that newDef stays NULL until we actually have a new def. Signed-off-by: Chris Lalancette <clalance@redhat.com> --- src/conf/domain_conf.c | 23 +++++++++++++---------- 1 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 41c83fd..3cd43eb 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -6151,22 +6151,25 @@ static virDomainObjPtr virDomainLoadConfig(virCapsPtr caps, if ((configFile = virDomainConfigFile(configDir, name)) == NULL) goto error; - if ((autostartLink = virDomainConfigFile(autostartDir, name)) == NULL) - goto error; - - if ((autostart = virFileLinkPointsTo(autostartLink, configFile)) < 0) - goto error; - if (!(def = virDomainDefParseFile(caps, configFile, VIR_DOMAIN_XML_INACTIVE))) goto error; - if ((dom = virDomainFindByName(doms, def->name))) { - virDomainObjUnlock(dom); - dom = NULL; - newVM = 0; + /* if the domain is already in our hashtable, we don't need to do + * anything further + */ + if ((dom = virDomainFindByUUID(doms, def->uuid))) { + VIR_FREE(configFile); + virDomainDefFree(def); + return dom; } + if ((autostartLink = virDomainConfigFile(autostartDir, name)) == NULL) + goto error; + + if ((autostart = virFileLinkPointsTo(autostartLink, configFile)) < 0) + goto error; + if (!(dom = virDomainAssignDef(caps, doms, def, false))) goto error; -- 1.6.6.1

On Fri, Apr 02, 2010 at 09:45:58PM -0400, Chris Lalancette wrote:
While playing around with def/newDef with the qemu code, I noticed that newDef was *always* getting set to a value, even when I didn't redefine the domain. I think the problem is the virDomainLoadConfig is always doing virDomainAssignDef regardless of whether the domain already exists in the hashtable. In turn, virDomainAssignDef is assigning the definition (which is actually a duplicate) to newDef. Fix this so that newDef stays NULL until we actually have a new def.
Signed-off-by: Chris Lalancette <clalance@redhat.com> --- src/conf/domain_conf.c | 23 +++++++++++++---------- 1 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 41c83fd..3cd43eb 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -6151,22 +6151,25 @@ static virDomainObjPtr virDomainLoadConfig(virCapsPtr caps,
if ((configFile = virDomainConfigFile(configDir, name)) == NULL) goto error; - if ((autostartLink = virDomainConfigFile(autostartDir, name)) == NULL) - goto error; - - if ((autostart = virFileLinkPointsTo(autostartLink, configFile)) < 0) - goto error; - if (!(def = virDomainDefParseFile(caps, configFile, VIR_DOMAIN_XML_INACTIVE))) goto error;
- if ((dom = virDomainFindByName(doms, def->name))) { - virDomainObjUnlock(dom); - dom = NULL; - newVM = 0; + /* if the domain is already in our hashtable, we don't need to do + * anything further + */ + if ((dom = virDomainFindByUUID(doms, def->uuid))) { + VIR_FREE(configFile); + virDomainDefFree(def); + return dom; }
+ if ((autostartLink = virDomainConfigFile(autostartDir, name)) == NULL) + goto error; + + if ((autostart = virFileLinkPointsTo(autostartLink, configFile)) < 0) + goto error; + if (!(dom = virDomainAssignDef(caps, doms, def, false))) goto error;
More a bug fix than purely part of the patch set, ACK Daniel -- Daniel Veillard | libxml Gnome XML XSLT toolkit http://xmlsoft.org/ daniel@veillard.com | Rpmfind RPM search engine http://rpmfind.net/ http://veillard.com/ | virtualization library http://libvirt.org/

Signed-off-by: Chris Lalancette <clalance@redhat.com> --- src/qemu/qemu_conf.c | 9 +- src/qemu/qemu_conf.h | 4 +- src/qemu/qemu_driver.c | 830 +++++++++++++++++++++++++++++++++++++++++- src/qemu/qemu_monitor.c | 39 ++ src/qemu/qemu_monitor.h | 4 + src/qemu/qemu_monitor_json.c | 66 ++++ src/qemu/qemu_monitor_json.h | 4 + src/qemu/qemu_monitor_text.c | 141 +++++++ src/qemu/qemu_monitor_text.h | 4 + tests/qemuxml2argvtest.c | 2 +- 10 files changed, 1089 insertions(+), 14 deletions(-) diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 55397cd..0e431c6 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -3373,7 +3373,9 @@ int qemudBuildCommandLine(virConnectPtr conn, const char ***retenv, int **tapfds, int *ntapfds, - const char *migrateFrom) { + const char *migrateFrom, + virDomainSnapshotObjPtr current_snapshot) +{ int i; char memory[50]; char boot[VIR_DOMAIN_BOOT_LAST]; @@ -4578,6 +4580,11 @@ int qemudBuildCommandLine(virConnectPtr conn, ADD_ARG_LIT("virtio"); } + if (current_snapshot && current_snapshot->def->active) { + ADD_ARG_LIT("-loadvm"); + ADD_ARG_LIT(current_snapshot->def->name); + } + ADD_ARG(NULL); ADD_ENV(NULL); diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 39518ca..44b7daa 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -120,6 +120,7 @@ struct qemud_driver { * the QEMU user/group */ char *libDir; char *cacheDir; + char *snapshotDir; unsigned int vncTLS : 1; unsigned int vncTLSx509verify : 1; unsigned int vncSASL : 1; @@ -198,7 +199,8 @@ int qemudBuildCommandLine (virConnectPtr conn, const char ***retenv, int **tapfds, int *ntapfds, - const char *migrateFrom) + const char *migrateFrom, + virDomainSnapshotObjPtr current_snapshot) ATTRIBUTE_NONNULL(1); /* With vlan == -1, use netdev syntax, else old hostnet */ diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 02ed95f..8c9bb46 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -1342,6 +1342,93 @@ no_memory: return NULL; } +static void qemuDomainSnapshotLoad(void *payload, + const char *name ATTRIBUTE_UNUSED, + void *data) +{ + virDomainObjPtr vm = (virDomainObjPtr)payload; + char *baseDir = (char *)data; + char *snapDir = NULL; + DIR *dir = NULL; + struct dirent *entry; + char *xmlStr; + int ret; + char *fullpath; + virDomainSnapshotObjPtr snap = NULL; + virDomainSnapshotDefPtr def = NULL; + char ebuf[1024]; + + virDomainObjLock(vm); + if (virAsprintf(&snapDir, "%s/%s", baseDir, vm->def->name) < 0) { + VIR_ERROR("Failed to allocate memory for snapshot directory for domain %s", + vm->def->name); + goto cleanup; + } + + VIR_INFO("Scanning for snapshots for domain %s in %s", vm->def->name, + snapDir); + + if (!(dir = opendir(snapDir))) { + if (errno != ENOENT) + VIR_ERROR("Failed to open snapshot directory %s for domain %s: %s", + snapDir, vm->def->name, + virStrerror(errno, ebuf, sizeof(ebuf))); + goto cleanup; + } + + while ((entry = readdir(dir))) { + if (entry->d_name[0] == '.') + continue; + + /* NB: ignoring errors, so one malformed config doesn't + kill the whole process */ + VIR_INFO("Loading snapshot file '%s'", entry->d_name); + + if (virAsprintf(&fullpath, "%s/%s", snapDir, entry->d_name) < 0) { + VIR_ERROR0("Failed to allocate memory for path"); + continue; + } + + ret = virFileReadAll(fullpath, 1024*1024*1, &xmlStr); + VIR_FREE(fullpath); + if (ret < 0) { + /* Nothing we can do here, skip this one */ + VIR_ERROR("Failed to read snapshot file %s: %s", fullpath, + virStrerror(errno, ebuf, sizeof(ebuf))); + continue; + } + + def = virDomainSnapshotDefParseString(xmlStr, 0); + if (def == NULL) { + /* Nothing we can do here, skip this one */ + VIR_ERROR("Failed to parse snapshot XML from file '%s'", fullpath); + VIR_FREE(xmlStr); + continue; + } + + snap = virDomainSnapshotAssignDef(&vm->snapshots, def); + + VIR_FREE(xmlStr); + } + + /* FIXME: qemu keeps internal track of snapshots. We can get access + * to this info via the "info snapshots" monitor command for running + * domains, or via "qemu-img snapshot -l" for shutoff domains. It would + * be nice to update our internal state based on that, but there is a + * a problem. qemu doesn't track all of the same metadata that we do. + * In particular we wouldn't be able to fill in the <parent>, which is + * pretty important in our metadata. + */ + + virResetLastError(); + +cleanup: + if (dir) + closedir(dir); + VIR_FREE(snapDir); + virDomainObjUnlock(vm); +} + /** * qemudStartup: * @@ -1399,6 +1486,9 @@ qemudStartup(int privileged) { if (virAsprintf(&qemu_driver->cacheDir, "%s/cache/libvirt/qemu", LOCAL_STATE_DIR) == -1) goto out_of_memory; + if (virAsprintf(&qemu_driver->snapshotDir, + "%s/run/libvirt/qemu/snapshot", LOCAL_STATE_DIR) == -1) + goto out_of_memory; } else { uid_t uid = geteuid(); char *userdir = virGetUserDirectory(uid); @@ -1423,6 +1513,8 @@ qemudStartup(int privileged) { goto out_of_memory; if (virAsprintf(&qemu_driver->cacheDir, "%s/qemu/cache", base) == -1) goto out_of_memory; + if (virAsprintf(&qemu_driver->snapshotDir, "%s/qemu/snapshot", base) == -1) + goto out_of_memory; } if (virFileMakePath(qemu_driver->stateDir) != 0) { @@ -1443,6 +1535,12 @@ qemudStartup(int privileged) { qemu_driver->cacheDir, virStrerror(errno, ebuf, sizeof ebuf)); goto error; } + if (virFileMakePath(qemu_driver->snapshotDir) != 0) { + char ebuf[1024]; + VIR_ERROR(_("Failed to create save dir '%s': %s"), + qemu_driver->snapshotDir, virStrerror(errno, ebuf, sizeof ebuf)); + goto error; + } /* Configuration paths are either ~/.libvirt/qemu/... (session) or * /etc/libvirt/qemu/... (system). @@ -1493,6 +1591,12 @@ qemudStartup(int privileged) { qemu_driver->cacheDir, qemu_driver->user, qemu_driver->group); goto error; } + if (chown(qemu_driver->snapshotDir, qemu_driver->user, qemu_driver->group) < 0) { + virReportSystemError(errno, + _("unable to set ownership of '%s' to %d:%d"), + qemu_driver->snapshotDir, qemu_driver->user, qemu_driver->group); + goto error; + } } /* If hugetlbfs is present, then we need to create a sub-directory within @@ -1543,6 +1647,11 @@ qemudStartup(int privileged) { qemu_driver->autostartDir, 0, NULL, NULL) < 0) goto error; + + + virHashForEach(qemu_driver->domains.objs, qemuDomainSnapshotLoad, + qemu_driver->snapshotDir); + qemuDriverUnlock(qemu_driver); qemudAutostartConfigs(qemu_driver); @@ -1645,6 +1754,7 @@ qemudShutdown(void) { VIR_FREE(qemu_driver->stateDir); VIR_FREE(qemu_driver->libDir); VIR_FREE(qemu_driver->cacheDir); + VIR_FREE(qemu_driver->snapshotDir); VIR_FREE(qemu_driver->vncTLSx509certdir); VIR_FREE(qemu_driver->vncListen); VIR_FREE(qemu_driver->vncPassword); @@ -3008,6 +3118,11 @@ qemuPrepareMonitorChr(struct qemud_driver *driver, return 0; } +static int qemuDomainSnapshotSetActive(virDomainObjPtr vm, + char *snapshotDir); +static int qemuDomainSnapshotSetInactive(virDomainObjPtr vm, + char *snapshotDir); + static int qemudStartVMDaemon(virConnectPtr conn, struct qemud_driver *driver, virDomainObjPtr vm, @@ -3175,7 +3290,11 @@ static int qemudStartVMDaemon(virConnectPtr conn, vm->def->id = driver->nextvmid++; if (qemudBuildCommandLine(conn, driver, vm->def, priv->monConfig, priv->monJSON, qemuCmdFlags, &argv, &progenv, - &tapfds, &ntapfds, migrateFrom) < 0) + &tapfds, &ntapfds, migrateFrom, + vm->current_snapshot) < 0) + goto cleanup; + + if (qemuDomainSnapshotSetInactive(vm, driver->snapshotDir) < 0) goto cleanup; /* now that we know it is about to start call the hook if present */ @@ -5911,7 +6030,7 @@ static char *qemuDomainXMLToNative(virConnectPtr conn, &monConfig, 0, qemuCmdFlags, &retargv, &retenv, NULL, NULL, /* Don't want it to create TAP devices */ - NULL) < 0) { + NULL, NULL) < 0) { goto cleanup; } @@ -10226,6 +10345,695 @@ cleanup: return ret; } +static char *qemuFindQemuImgBinary(void) +{ + char *ret; + + ret = virFindFileInPath("kvm-img"); + if (ret == NULL) + ret = virFindFileInPath("qemu-img"); + if (ret == NULL) + qemuReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("unable to find kvm-img or qemu-img")); + + return ret; +} + +static int qemuDomainSnapshotWriteSnapshotMetadata(virDomainObjPtr vm, + char *snapshotDir) +{ + int fd = -1; + char *newxml = NULL; + int ret = -1; + char *snapDir = NULL; + char *snapFile = NULL; + int err; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + virUUIDFormat(vm->def->uuid, uuidstr); + newxml = virDomainSnapshotDefFormat(uuidstr, vm->current_snapshot->def, 1); + if (newxml == NULL) { + virReportOOMError(); + return -1; + } + + if (virAsprintf(&snapDir, "%s/%s", snapshotDir, vm->def->name) < 0) { + virReportOOMError(); + goto cleanup; + } + err = virFileMakePath(snapDir); + if (err < 0) { + virReportSystemError(err, _("cannot create snapshot directory '%s'"), + snapDir); + goto cleanup; + } + + if (virAsprintf(&snapFile, "%s/%s.xml", snapDir, + vm->current_snapshot->def->name) < 0) { + virReportOOMError(); + goto cleanup; + } + fd = open(snapFile, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR); + if (fd < 0) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("failed to create snapshot file '%s'"), snapFile); + goto cleanup; + } + if (safewrite(fd, newxml, strlen(newxml)) != strlen(newxml)) { + virReportSystemError(errno, _("Failed to write snapshot data to %s"), + snapFile); + goto cleanup; + } + + ret = 0; + +cleanup: + VIR_FREE(snapFile); + VIR_FREE(snapDir); + VIR_FREE(newxml); + if (fd != -1) + close(fd); + return ret; +} + +static int qemuDomainSnapshotSetActive(virDomainObjPtr vm, + char *snapshotDir) +{ + if (vm->current_snapshot) { + vm->current_snapshot->def->active = 1; + + return qemuDomainSnapshotWriteSnapshotMetadata(vm, snapshotDir); + } + + return 0; +} + +static int qemuDomainSnapshotSetInactive(virDomainObjPtr vm, + char *snapshotDir) +{ + if (vm->current_snapshot) { + vm->current_snapshot->def->active = 0; + + return qemuDomainSnapshotWriteSnapshotMetadata(vm, snapshotDir); + } + + return 0; +} + + +static int qemuDomainSnapshotIsAllowed(virDomainObjPtr vm) +{ + int i; + + /* FIXME: we need to figure out what else here might succeed; in + * particular, if it's a raw device but on LVM, we could probably make + * that succeed as well + */ + for (i = 0; i < vm->def->ndisks; i++) { + if (vm->def->disks[i]->device == VIR_DOMAIN_DISK_DEVICE_DISK && + (!vm->def->disks[i]->driverType || + STRNEQ(vm->def->disks[i]->driverType, "qcow2"))) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("Disk device '%s' does not support snapshotting"), + vm->def->disks[i]->info.alias); + return 0; + } + } + + return 1; +} + +static virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = domain->conn->privateData; + virDomainObjPtr vm = NULL; + virDomainSnapshotObjPtr snap = NULL; + virDomainSnapshotPtr snapshot = NULL; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virDomainSnapshotDefPtr def; + qemuDomainObjPrivatePtr priv; + const char *qemuimgarg[] = { NULL, "snapshot", "-c", NULL, NULL, NULL }; + int i; + + qemuDriverLock(driver); + virUUIDFormat(domain->uuid, uuidstr); + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (!vm) { + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + /* in a perfect world, we would allow qemu to tell us this. The problem + * is that qemu only does this check device-by-device; so if you had a + * domain that booted from a large qcow2 device, but had a secondary raw + * device attached, you wouldn't find out that you can't snapshot your + * guest until *after* it had spent the time to snapshot the boot device. + * This is probably a bug in qemu, but we'll work around it here for now. + */ + if (!qemuDomainSnapshotIsAllowed(vm)) + goto cleanup; + + if (!(def = virDomainSnapshotDefParseString(xmlDesc, 1))) + goto cleanup; + + if (!(snap = virDomainSnapshotAssignDef(&vm->snapshots, def))) + goto cleanup; + + /* actually do the snapshot */ + if (!virDomainObjIsActive(vm)) { + qemuimgarg[0] = qemuFindQemuImgBinary(); + if (qemuimgarg[0] == NULL) + /* qemuFindQemuImgBinary set the error */ + goto cleanup; + + qemuimgarg[3] = snap->def->name; + + for (i = 0; i < vm->def->ndisks; i++) { + /* FIXME: we also need to handle LVM here */ + /* FIXME: if we fail halfway through this loop, we are in an + * inconsistent state. I'm not quite sure what to do about that + */ + if (vm->def->disks[i]->device == VIR_DOMAIN_DISK_DEVICE_DISK) { + if (!vm->def->disks[i]->driverType || + STRNEQ(vm->def->disks[i]->driverType, "qcow2")) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("Disk device '%s' does not support snapshotting"), + vm->def->disks[i]->info.alias); + goto cleanup; + } + + qemuimgarg[4] = vm->def->disks[i]->src; + + if (virRun(qemuimgarg, NULL) < 0) { + virReportSystemError(errno, + _("Failed to run '%s' to create snapshot '%s' from disk '%s'"), + qemuimgarg[0], snap->def->name, + vm->def->disks[i]->src); + goto cleanup; + } + } + } + } + else { + priv = vm->privateData; + qemuDomainObjEnterMonitorWithDriver(driver, vm); + if (qemuMonitorCreateSnapshot(priv->mon, def->name) < 0) { + qemuDomainObjExitMonitorWithDriver(driver, vm); + goto cleanup; + } + qemuDomainObjExitMonitorWithDriver(driver, vm); + + } + + snap->def->state = vm->state; + + /* FIXME: if we fail after this point, there's not a whole lot we can + * do; we've successfully taken the snapshot, and we are now running + * on it, so we have to go forward the best we can + */ + + if (vm->current_snapshot) { + def->parent = strdup(vm->current_snapshot->def->name); + if (def->parent == NULL) { + virReportOOMError(); + goto cleanup; + } + } + + /* Now we set the new current_snapshot for the domain */ + vm->current_snapshot = snap; + + if (qemuDomainSnapshotWriteSnapshotMetadata(vm, driver->snapshotDir) < 0) + /* qemuDomainSnapshotWriteSnapshotMetadata set the error */ + goto cleanup; + + snapshot = virGetDomainSnapshot(domain, snap->def->name); + +cleanup: + VIR_FREE(qemuimgarg[0]); + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return snapshot; +} + +static int qemuDomainSnapshotListNames(virDomainPtr domain, char **names, + int nameslen, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = domain->conn->privateData; + virDomainObjPtr vm = NULL; + int n = -1; + + qemuDriverLock(driver); + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(domain->uuid, uuidstr); + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + n = virDomainSnapshotObjListGetNames(&vm->snapshots, names, nameslen); + +cleanup: + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return n; +} + +static int qemuDomainSnapshotNum(virDomainPtr domain, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = domain->conn->privateData; + virDomainObjPtr vm = NULL; + int n = -1; + + qemuDriverLock(driver); + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(domain->uuid, uuidstr); + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + n = virDomainSnapshotObjListNum(&vm->snapshots); + +cleanup: + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return n; +} + +static virDomainSnapshotPtr qemuDomainSnapshotLookupByName(virDomainPtr domain, + const char *name, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = domain->conn->privateData; + virDomainObjPtr vm; + virDomainSnapshotObjPtr snap = NULL; + virDomainSnapshotPtr snapshot = NULL; + + qemuDriverLock(driver); + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(domain->uuid, uuidstr); + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + snap = virDomainSnapshotFindByName(&vm->snapshots, name); + if (!snap) { + qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, + _("no snapshot with matching name '%s'"), name); + goto cleanup; + } + + snapshot = virGetDomainSnapshot(domain, snap->def->name); + +cleanup: + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return snapshot; +} + +static int qemuDomainHasCurrentSnapshot(virDomainPtr domain, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = domain->conn->privateData; + virDomainObjPtr vm; + int ret = -1; + + qemuDriverLock(driver); + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(domain->uuid, uuidstr); + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + ret = (vm->current_snapshot != NULL); + +cleanup: + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return ret; +} + +static virDomainSnapshotPtr qemuDomainSnapshotCurrent(virDomainPtr domain, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = domain->conn->privateData; + virDomainObjPtr vm; + virDomainSnapshotPtr snapshot = NULL; + + qemuDriverLock(driver); + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(domain->uuid, uuidstr); + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + if (!vm->current_snapshot) { + qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, "%s", + _("the domain does not have a current snapshot")); + goto cleanup; + } + + snapshot = virGetDomainSnapshot(domain, vm->current_snapshot->def->name); + +cleanup: + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return snapshot; +} + +static char *qemuDomainSnapshotDumpXML(virDomainSnapshotPtr snapshot, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = snapshot->domain->conn->privateData; + virDomainObjPtr vm = NULL; + char *xml = NULL; + virDomainSnapshotObjPtr snap = NULL; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + qemuDriverLock(driver); + virUUIDFormat(snapshot->domain->uuid, uuidstr); + vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid); + if (!vm) { + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name); + if (!snap) { + qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, + _("no domain snapshot with matching name '%s'"), + snapshot->name); + goto cleanup; + } + + xml = virDomainSnapshotDefFormat(uuidstr, snap->def, 0); + +cleanup: + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return xml; +} + +static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = snapshot->domain->conn->privateData; + virDomainObjPtr vm = NULL; + int ret = -1; + virDomainSnapshotObjPtr snap = NULL; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virDomainEventPtr event = NULL; + qemuDomainObjPrivatePtr priv; + int rc; + + qemuDriverLock(driver); + virUUIDFormat(snapshot->domain->uuid, uuidstr); + vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid); + if (!vm) { + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name); + if (!snap) { + qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, + _("no domain snapshot with matching name '%s'"), + snapshot->name); + goto cleanup; + } + + vm->current_snapshot = snap; + + if (qemuDomainObjBeginJobWithDriver(driver, vm) < 0) + goto cleanup; + + if (snap->def->state == VIR_DOMAIN_RUNNING + || snap->def->state == VIR_DOMAIN_PAUSED) { + + if (virDomainObjIsActive(vm)) { + priv = vm->privateData; + qemuDomainObjEnterMonitorWithDriver(driver, vm); + rc = qemuMonitorLoadSnapshot(priv->mon, snap->def->name); + qemuDomainObjExitMonitorWithDriver(driver, vm); + if (rc < 0) + goto cleanup; + } + else { + if (qemuDomainSnapshotSetActive(vm, driver->snapshotDir) < 0) + goto cleanup; + + rc = qemudStartVMDaemon(snapshot->domain->conn, driver, vm, NULL, + -1); + if (qemuDomainSnapshotSetInactive(vm, driver->snapshotDir) < 0) + goto cleanup; + if (rc < 0) + goto cleanup; + } + + if (snap->def->state == VIR_DOMAIN_PAUSED) { + /* qemu unconditionally starts the domain running again after + * loadvm, so let's pause it to keep consistency + */ + priv = vm->privateData; + qemuDomainObjEnterMonitorWithDriver(driver, vm); + rc = qemuMonitorStopCPUs(priv->mon); + qemuDomainObjExitMonitorWithDriver(driver, vm); + if (rc < 0) + goto cleanup; + } + + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT); + } + else { + /* qemu is a little funny with running guests and the restoration + * of snapshots. If the snapshot was taken online, + * then after a "loadvm" monitor command, the VM is set running + * again. If the snapshot was taken offline, then after a "loadvm" + * monitor command the VM is left paused. Unpausing it leads to + * the memory state *before* the loadvm with the disk *after* the + * loadvm, which obviously is bound to corrupt something. + * Therefore we destroy the domain and set it to "off" in this case. + */ + + if (virDomainObjIsActive(vm)) { + qemudShutdownVMDaemon(driver, vm); + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STOPPED, + VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT); + } + + if (qemuDomainSnapshotSetActive(vm, driver->snapshotDir) < 0) + goto cleanup; + } + + vm->state = snap->def->state; + + ret = 0; + +cleanup: + if (vm && qemuDomainObjEndJob(vm) == 0) + vm = NULL; + + if (event) + qemuDomainEventQueue(driver, event); + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + + return ret; +} + +static int qemuDomainSnapshotDiscard(struct qemud_driver *driver, + virDomainObjPtr vm, + virDomainSnapshotObjPtr snap) +{ + const char *qemuimgarg[] = { NULL, "snapshot", "-d", NULL, NULL, NULL }; + char *snapFile = NULL; + int ret = -1; + int i; + qemuDomainObjPrivatePtr priv; + virDomainSnapshotObjPtr parentsnap; + + if (!virDomainObjIsActive(vm)) { + qemuimgarg[0] = qemuFindQemuImgBinary(); + if (qemuimgarg[0] == NULL) + /* qemuFindQemuImgBinary set the error */ + goto cleanup; + + qemuimgarg[3] = snap->def->name; + + for (i = 0; i < vm->def->ndisks; i++) { + /* FIXME: we also need to handle LVM here */ + if (vm->def->disks[i]->device == VIR_DOMAIN_DISK_DEVICE_DISK) { + if (!vm->def->disks[i]->driverType || + STRNEQ(vm->def->disks[i]->driverType, "qcow2")) { + /* we continue on even in the face of error, since other + * disks in this VM may have this snapshot in place + */ + continue; + } + + qemuimgarg[4] = vm->def->disks[i]->src; + + if (virRun(qemuimgarg, NULL) < 0) { + /* we continue on even in the face of error, since other + * disks in this VM may have this snapshot in place + */ + continue; + } + } + } + } + else { + priv = vm->privateData; + qemuDomainObjEnterMonitorWithDriver(driver, vm); + /* we continue on even in the face of error */ + qemuMonitorDeleteSnapshot(priv->mon, snap->def->name); + qemuDomainObjExitMonitorWithDriver(driver, vm); + } + + if (snap == vm->current_snapshot) { + if (snap->def->parent) { + parentsnap = virDomainSnapshotFindByName(&vm->snapshots, + snap->def->parent); + if (!parentsnap) { + qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, + _("no domain snapshot parent with matching name '%s'"), + snap->def->parent); + goto cleanup; + } + + /* Now we set the new current_snapshot for the domain */ + vm->current_snapshot = parentsnap; + } + else + vm->current_snapshot = NULL; + } + + if (virAsprintf(&snapFile, "%s/%s/%s.xml", driver->snapshotDir, + vm->def->name, snap->def->name) < 0) { + virReportOOMError(); + goto cleanup; + } + unlink(snapFile); + + virDomainSnapshotObjListRemove(&vm->snapshots, snap); + + ret = 0; + +cleanup: + VIR_FREE(snapFile); + VIR_FREE(qemuimgarg[0]); + + return ret; +} + +struct snap_remove { + struct qemud_driver *driver; + virDomainObjPtr vm; + char *parent; + int err; +}; + +static void qemuDomainSnapshotDiscardChildren(void *payload, + const char *name ATTRIBUTE_UNUSED, + void *data) +{ + virDomainSnapshotObjPtr snap = payload; + struct snap_remove *curr = data; + struct snap_remove this; + + if (snap->def->parent && STREQ(snap->def->parent, curr->parent)) { + this.driver = curr->driver; + this.vm = curr->vm; + this.parent = snap->def->name; + this.err = 0; + virHashForEach(curr->vm->snapshots.objs, + qemuDomainSnapshotDiscardChildren, &this); + + if (this.err) + curr->err = this.err; + else + this.err = qemuDomainSnapshotDiscard(curr->driver, curr->vm, snap); + } +} + +static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + struct qemud_driver *driver = snapshot->domain->conn->privateData; + virDomainObjPtr vm = NULL; + int ret = -1; + virDomainSnapshotObjPtr snap = NULL; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + struct snap_remove rem; + + qemuDriverLock(driver); + virUUIDFormat(snapshot->domain->uuid, uuidstr); + vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid); + if (!vm) { + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name); + if (!snap) { + qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, + _("no domain snapshot with matching name '%s'"), + snapshot->name); + goto cleanup; + } + + if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN) { + rem.driver = driver; + rem.vm = vm; + rem.parent = snap->def->name; + rem.err = 0; + virHashForEach(vm->snapshots.objs, qemuDomainSnapshotDiscardChildren, + &rem); + if (rem.err < 0) + goto cleanup; + } + + ret = qemuDomainSnapshotDiscard(driver, vm, snap); + +cleanup: + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return ret; +} static virDriver qemuDriver = { VIR_DRV_QEMU, @@ -10312,15 +11120,15 @@ static virDriver qemuDriver = { qemuDomainMigrateSetMaxDowntime, /* domainMigrateSetMaxDowntime */ qemuDomainEventRegisterAny, /* domainEventRegisterAny */ qemuDomainEventDeregisterAny, /* domainEventDeregisterAny */ - NULL, /* domainSnapshotCreateXML */ - NULL, /* domainSnapshotDumpXML */ - NULL, /* domainSnapshotNum */ - NULL, /* domainSnapshotListNames */ - NULL, /* domainSnapshotLookupByName */ - NULL, /* domainHasCurrentSnapshot */ - NULL, /* domainSnapshotCurrent */ - NULL, /* domainRevertToSnapshot */ - NULL, /* domainSnapshotDelete */ + qemuDomainSnapshotCreateXML, /* domainSnapshotCreateXML */ + qemuDomainSnapshotDumpXML, /* domainSnapshotDumpXML */ + qemuDomainSnapshotNum, /* domainSnapshotNum */ + qemuDomainSnapshotListNames, /* domainSnapshotListNames */ + qemuDomainSnapshotLookupByName, /* domainSnapshotLookupByName */ + qemuDomainHasCurrentSnapshot, /* domainHasCurrentSnapshot */ + qemuDomainSnapshotCurrent, /* domainSnapshotCurrent */ + qemuDomainRevertToSnapshot, /* domainRevertToSnapshot */ + qemuDomainSnapshotDelete, /* domainSnapshotDelete */ }; diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 64779ac..01e3a46 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -1491,3 +1491,42 @@ int qemuMonitorSetDrivePassphrase(qemuMonitorPtr mon, ret = qemuMonitorTextSetDrivePassphrase(mon, alias, passphrase); return ret; } + +int qemuMonitorCreateSnapshot(qemuMonitorPtr mon, const char *name) +{ + int ret; + + DEBUG("mon=%p, name=%s",mon,name); + + if (mon->json) + ret = qemuMonitorJSONCreateSnapshot(mon, name); + else + ret = qemuMonitorTextCreateSnapshot(mon, name); + return ret; +} + +int qemuMonitorLoadSnapshot(qemuMonitorPtr mon, const char *name) +{ + int ret; + + DEBUG("mon=%p, name=%s",mon,name); + + if (mon->json) + ret = qemuMonitorJSONLoadSnapshot(mon, name); + else + ret = qemuMonitorTextLoadSnapshot(mon, name); + return ret; +} + +int qemuMonitorDeleteSnapshot(qemuMonitorPtr mon, const char *name) +{ + int ret; + + DEBUG("mon=%p, name=%s",mon,name); + + if (mon->json) + ret = qemuMonitorJSONDeleteSnapshot(mon, name); + else + ret = qemuMonitorTextDeleteSnapshot(mon, name); + return ret; +} diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index 07773bd..21b8989 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -344,4 +344,8 @@ int qemuMonitorSetDrivePassphrase(qemuMonitorPtr mon, const char *alias, const char *passphrase); +int qemuMonitorCreateSnapshot(qemuMonitorPtr mon, const char *name); +int qemuMonitorLoadSnapshot(qemuMonitorPtr mon, const char *name); +int qemuMonitorDeleteSnapshot(qemuMonitorPtr mon, const char *name); + #endif /* QEMU_MONITOR_H */ diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index eac3aca..1b0ecdf 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -2156,3 +2156,69 @@ int qemuMonitorJSONSetDrivePassphrase(qemuMonitorPtr mon, virJSONValueFree(reply); return ret; } + +int qemuMonitorJSONCreateSnapshot(qemuMonitorPtr mon, const char *name) +{ + int ret; + virJSONValuePtr cmd; + virJSONValuePtr reply = NULL; + + cmd = qemuMonitorJSONMakeCommand("savevm", + "s:name", name, + NULL); + if (!cmd) + return -1; + + ret = qemuMonitorJSONCommand(mon, cmd, &reply); + + if (ret == 0) + ret = qemuMonitorJSONCheckError(cmd, reply); + + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + +int qemuMonitorJSONLoadSnapshot(qemuMonitorPtr mon, const char *name) +{ + int ret; + virJSONValuePtr cmd; + virJSONValuePtr reply = NULL; + + cmd = qemuMonitorJSONMakeCommand("loadvm", + "s:name", name, + NULL); + if (!cmd) + return -1; + + ret = qemuMonitorJSONCommand(mon, cmd, &reply); + + if (ret == 0) + ret = qemuMonitorJSONCheckError(cmd, reply); + + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + +int qemuMonitorJSONDeleteSnapshot(qemuMonitorPtr mon, const char *name) +{ + int ret; + virJSONValuePtr cmd; + virJSONValuePtr reply = NULL; + + cmd = qemuMonitorJSONMakeCommand("delvm", + "s:name", name, + NULL); + if (!cmd) + return -1; + + ret = qemuMonitorJSONCommand(mon, cmd, &reply); + + if (ret == 0) + ret = qemuMonitorJSONCheckError(cmd, reply); + + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index fc05153..e7baf84 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -175,4 +175,8 @@ int qemuMonitorJSONSetDrivePassphrase(qemuMonitorPtr mon, const char *alias, const char *passphrase); +int qemuMonitorJSONCreateSnapshot(qemuMonitorPtr mon, const char *name); +int qemuMonitorJSONLoadSnapshot(qemuMonitorPtr mon, const char *name); +int qemuMonitorJSONDeleteSnapshot(qemuMonitorPtr mon, const char *name); + #endif /* QEMU_MONITOR_JSON_H */ diff --git a/src/qemu/qemu_monitor_text.c b/src/qemu/qemu_monitor_text.c index a199de7..e057bbe 100644 --- a/src/qemu/qemu_monitor_text.c +++ b/src/qemu/qemu_monitor_text.c @@ -2290,3 +2290,144 @@ cleanup: VIR_FREE(safe_str); return ret; } + +int qemuMonitorTextCreateSnapshot(qemuMonitorPtr mon, const char *name) +{ + char *cmd; + char *reply = NULL; + int ret = -1; + + if (virAsprintf(&cmd, "savevm \"%s\"", name) < 0) { + virReportOOMError(); + return -1; + } + + if (qemuMonitorCommand(mon, cmd, &reply)) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("failed to take snapshot using command '%s'"), cmd); + goto cleanup; + } + + if (strstr(reply, "Error while creating snapshot") != NULL) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("Failed to take snapshot: %s"), reply); + goto cleanup; + } + else if (strstr(reply, "No block device can accept snapshots") != NULL) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("this domain does not have a device to take snapshots")); + goto cleanup; + } + else if (strstr(reply, "Could not open VM state file") != NULL) { + qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", reply); + goto cleanup; + } + else if (strstr(reply, "Error") != NULL + && strstr(reply, "while writing VM") != NULL) { + qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", reply); + goto cleanup; + } + + ret = 0; + +cleanup: + VIR_FREE(cmd); + VIR_FREE(reply); + return ret; +} + +int qemuMonitorTextLoadSnapshot(qemuMonitorPtr mon, const char *name) +{ + char *cmd; + char *reply = NULL; + int ret = -1; + + if (virAsprintf(&cmd, "loadvm \"%s\"", name) < 0) { + virReportOOMError(); + return -1; + } + + if (qemuMonitorCommand(mon, cmd, &reply)) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("failed to restore snapshot using command '%s'"), + cmd); + goto cleanup; + } + + if (strstr(reply, "No block device supports snapshots") != NULL) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("this domain does not have a device to load snapshots")); + goto cleanup; + } + else if (strstr(reply, "Could not find snapshot") != NULL) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("the snapshot '%s' does not exist, and was not loaded"), + name); + goto cleanup; + } + else if (strstr(reply, "Snapshots not supported on device") != NULL) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", reply); + goto cleanup; + } + else if (strstr(reply, "Could not open VM state file") != NULL) { + qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", reply); + goto cleanup; + } + else if (strstr(reply, "Error") != NULL + && strstr(reply, "while loading VM state") != NULL) { + qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", reply); + goto cleanup; + } + else if (strstr(reply, "Error") != NULL + && strstr(reply, "while activating snapshot on") != NULL) { + qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", reply); + goto cleanup; + } + + ret = 0; + +cleanup: + VIR_FREE(cmd); + VIR_FREE(reply); + return ret; +} + +int qemuMonitorTextDeleteSnapshot(qemuMonitorPtr mon, const char *name) +{ + char *cmd; + char *reply = NULL; + int ret = -1; + + if (virAsprintf(&cmd, "delvm \"%s\"", name) < 0) { + virReportOOMError(); + return -1; + } + if (qemuMonitorCommand(mon, cmd, &reply)) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("failed to delete snapshot using command '%s'"), + cmd); + goto cleanup; + } + + if (strstr(reply, "No block device supports snapshots") != NULL) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("this domain does not have a device to delete snapshots")); + goto cleanup; + } + else if (strstr(reply, "Snapshots not supported on device") != NULL) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", reply); + goto cleanup; + } + else if (strstr(reply, "Error") != NULL + && strstr(reply, "while deleting snapshot") != NULL) { + qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", reply); + goto cleanup; + } + + ret = 0; + +cleanup: + VIR_FREE(cmd); + VIR_FREE(reply); + return ret; +} diff --git a/src/qemu/qemu_monitor_text.h b/src/qemu/qemu_monitor_text.h index 4e1939c..fb7d08b 100644 --- a/src/qemu/qemu_monitor_text.h +++ b/src/qemu/qemu_monitor_text.h @@ -177,4 +177,8 @@ int qemuMonitorTextSetDrivePassphrase(qemuMonitorPtr mon, const char *alias, const char *passphrase); +int qemuMonitorTextCreateSnapshot(qemuMonitorPtr mon, const char *name); +int qemuMonitorTextLoadSnapshot(qemuMonitorPtr mon, const char *name); +int qemuMonitorTextDeleteSnapshot(qemuMonitorPtr mon, const char *name); + #endif /* QEMU_MONITOR_TEXT_H */ diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index c98de19..9e4d5bf 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -83,7 +83,7 @@ static int testCompareXMLToArgvFiles(const char *xml, if (qemudBuildCommandLine(conn, &driver, vmdef, &monitor_chr, 0, flags, &argv, &qenv, - NULL, NULL, migrateFrom) < 0) + NULL, NULL, migrateFrom, NULL) < 0) goto fail; len = 1; /* for trailing newline */ -- 1.6.6.1

On Fri, Apr 02, 2010 at 09:45:59PM -0400, Chris Lalancette wrote:
Signed-off-by: Chris Lalancette <clalance@redhat.com> --- src/qemu/qemu_conf.c | 9 +- src/qemu/qemu_conf.h | 4 +- src/qemu/qemu_driver.c | 830 +++++++++++++++++++++++++++++++++++++++++- src/qemu/qemu_monitor.c | 39 ++ src/qemu/qemu_monitor.h | 4 + src/qemu/qemu_monitor_json.c | 66 ++++ src/qemu/qemu_monitor_json.h | 4 + src/qemu/qemu_monitor_text.c | 141 +++++++ src/qemu/qemu_monitor_text.h | 4 + tests/qemuxml2argvtest.c | 2 +- 10 files changed, 1089 insertions(+), 14 deletions(-)
looks fine ACK, Daniel -- Daniel Veillard | libxml Gnome XML XSLT toolkit http://xmlsoft.org/ daniel@veillard.com | Rpmfind RPM search engine http://rpmfind.net/ http://veillard.com/ | virtualization library http://libvirt.org/

2010/4/3 Chris Lalancette <clalance@redhat.com>:
Signed-off-by: Chris Lalancette <clalance@redhat.com> --- src/qemu/qemu_conf.c | 9 +- src/qemu/qemu_conf.h | 4 +- src/qemu/qemu_driver.c | 830 +++++++++++++++++++++++++++++++++++++++++- src/qemu/qemu_monitor.c | 39 ++ src/qemu/qemu_monitor.h | 4 + src/qemu/qemu_monitor_json.c | 66 ++++ src/qemu/qemu_monitor_json.h | 4 + src/qemu/qemu_monitor_text.c | 141 +++++++ src/qemu/qemu_monitor_text.h | 4 + tests/qemuxml2argvtest.c | 2 +- 10 files changed, 1089 insertions(+), 14 deletions(-)
ACK. Matthias

From: Jiri Denemark <jdenemar@redhat.com> --- src/vbox/vbox_tmpl.c | 988 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 979 insertions(+), 9 deletions(-) diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index dac386d..ede2798 100644 --- a/src/vbox/vbox_tmpl.c +++ b/src/vbox/vbox_tmpl.c @@ -5007,6 +5007,976 @@ static int vboxDomainDetachDeviceFlags(virDomainPtr dom, const char *xml, return vboxDomainDetachDevice(dom, xml); } +static int +vboxDomainSnapshotGetAll(virDomainPtr dom, + IMachine *machine, + ISnapshot ***snapshots) +{ + ISnapshot **list = NULL; + PRUint32 count; + nsresult rc; + unsigned int next; + unsigned int top; + + rc = machine->vtbl->GetSnapshotCount(machine, &count); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not get snapshot count for domain %s"), + dom->name); + goto error; + } + + if (count == 0) + goto out; + + if (VIR_ALLOC_N(list, count) < 0) { + virReportOOMError(); + goto error; + } + + rc = machine->vtbl->GetSnapshot(machine, NULL, list); + if (NS_FAILED(rc) || !list[0]) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not get root snapshot for domain %s"), + dom->name); + goto error; + } + + /* BFS walk through snapshot tree */ + top = 1; + for (next = 0; next < count; next++) { + PRUint32 childrenCount = 0; + ISnapshot **children = NULL; + unsigned int i; + + if (!list[next]) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("unexpected number of snapshots < %u"), count); + goto error; + } + + rc = list[next]->vtbl->GetChildren(list[next], &childrenCount, + &children); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("could not get children snapshots")); + goto error; + } + for (i = 0; i < childrenCount; i++) { + if (!children[i]) + continue; + if (top == count) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("unexpected number of snapshots > %u"), count); + goto error; + } + list[top++] = children[i]; + } + } + +out: + *snapshots = list; + return count; + +error: + if (list) { + for (next = 0; next < count; next++) + VBOX_RELEASE(list[next]); + } + VIR_FREE(list); + + return -1; +} + +static ISnapshot * +vboxDomainSnapshotGet(vboxGlobalData *data, + virDomainPtr dom, + IMachine *machine, + const char *name) +{ + ISnapshot **snapshots = NULL; + ISnapshot *snapshot = NULL; + nsresult rc; + int count = 0; + int i; + + if ((count = vboxDomainSnapshotGetAll(dom, machine, &snapshots)) < 0) + goto cleanup; + + for (i = 0; i < count; i++) { + PRUnichar *nameUtf16; + char *nameUtf8; + + rc = snapshots[i]->vtbl->GetName(snapshots[i], &nameUtf16); + if (NS_FAILED(rc) || !nameUtf16) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("could not get snapshot name")); + goto cleanup; + } + VBOX_UTF16_TO_UTF8(nameUtf16, &nameUtf8); + VBOX_UTF16_FREE(nameUtf16); + if (STREQ(name, nameUtf8)) + snapshot = snapshots[i]; + VBOX_UTF8_FREE(nameUtf8); + + if (snapshot) + break; + } + + if (!snapshot) { + vboxError(dom->conn, VIR_ERR_OPERATION_INVALID, + _("domain %s has no snapshots with name %s"), + dom->name, name); + goto cleanup; + } + +cleanup: + if (count > 0) { + for (i = 0; i < count; i++) { + if (snapshots[i] != snapshot) + VBOX_RELEASE(snapshots[i]); + } + } + VIR_FREE(snapshots); + return snapshot; +} + +static virDomainSnapshotPtr +vboxDomainSnapshotCreateXML(virDomainPtr dom, + const char *xmlDesc, + unsigned int flags ATTRIBUTE_UNUSED) +{ + VBOX_OBJECT_CHECK(dom->conn, virDomainSnapshotPtr, NULL); + virDomainSnapshotDefPtr def = NULL; + vboxIID *domiid = NULL; + IMachine *machine = NULL; + IConsole *console = NULL; + IProgress *progress = NULL; + ISnapshot *snapshot = NULL; + PRUnichar *name = NULL; + PRUnichar *description = NULL; + PRUint32 state; + nsresult rc; +#if VBOX_API_VERSION == 2002 + nsresult result; +#else + PRInt32 result; +#endif + + if (!(def = virDomainSnapshotDefParseString(xmlDesc, 1))) + goto cleanup; + +#if VBOX_API_VERSION == 2002 + if (VIR_ALLOC(domiid) < 0) { + virReportOOMError(); + goto cleanup; + } +#endif + + vboxIIDFromUUID(dom->uuid, domiid); + rc = data->vboxObj->vtbl->GetMachine(data->vboxObj, domiid, &machine); + if (NS_FAILED(rc) || !machine) { + vboxError(dom->conn, VIR_ERR_INVALID_DOMAIN, "%s", + _("no domain with matching UUID")); + goto cleanup; + } + + rc = machine->vtbl->GetState(machine, &state); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("could not get domain state")); + goto cleanup; + } + + if ((state >= MachineState_FirstOnline) + && (state <= MachineState_LastOnline)) { + rc = data->vboxObj->vtbl->OpenExistingSession(data->vboxObj, + data->vboxSession, + domiid); + } else { + rc = data->vboxObj->vtbl->OpenSession(data->vboxObj, + data->vboxSession, + domiid); + } + if (NS_SUCCEEDED(rc)) + rc = data->vboxSession->vtbl->GetConsole(data->vboxSession, &console); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not open VirtualBox session with domain %s"), + dom->name); + goto cleanup; + } + + VBOX_UTF8_TO_UTF16(def->name, &name); + if (!name) { + virReportOOMError(); + goto cleanup; + } + + if (def->description) { + VBOX_UTF8_TO_UTF16(def->description, &description); + if (!description) { + virReportOOMError(); + goto cleanup; + } + } + + rc = console->vtbl->TakeSnapshot(console, name, description, &progress); + if (NS_FAILED(rc) || !progress) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not take snapshot of domain %s"), dom->name); + goto cleanup; + } + + progress->vtbl->WaitForCompletion(progress, -1); + progress->vtbl->GetResultCode(progress, &result); + if (NS_FAILED(result)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not take snapshot of domain %s"), dom->name); + goto cleanup; + } + + rc = machine->vtbl->GetCurrentSnapshot(machine, &snapshot); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not get current snapshot of domain %s"), + dom->name); + goto cleanup; + } + + ret = virGetDomainSnapshot(dom, def->name); + +cleanup: + VBOX_RELEASE(progress); + VBOX_UTF16_FREE(description); + VBOX_UTF16_FREE(name); + VBOX_RELEASE(console); + data->vboxSession->vtbl->Close(data->vboxSession); + VBOX_RELEASE(machine); + vboxIIDFree(domiid); + virDomainSnapshotDefFree(def); + return ret; +} + +static char * +vboxDomainSnapshotDumpXML(virDomainSnapshotPtr snapshot, + unsigned int flags ATTRIBUTE_UNUSED) +{ + virDomainPtr dom = snapshot->domain; + VBOX_OBJECT_CHECK(dom->conn, char *, NULL); + vboxIID *domiid = NULL; + IMachine *machine = NULL; + ISnapshot *snap = NULL; + ISnapshot *parent = NULL; + nsresult rc; + virDomainSnapshotDefPtr def = NULL; + PRUnichar *str16; + char *str8; + PRInt64 timestamp; + PRBool online = PR_FALSE; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + +#if VBOX_API_VERSION == 2002 + if (VIR_ALLOC(domiid) < 0) { + virReportOOMError(); + goto cleanup; + } +#endif + + vboxIIDFromUUID(dom->uuid, domiid); + rc = data->vboxObj->vtbl->GetMachine(data->vboxObj, domiid, &machine); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INVALID_DOMAIN, "%s", + _("no domain with matching UUID")); + goto cleanup; + } + + if (!(snap = vboxDomainSnapshotGet(data, dom, machine, snapshot->name))) + goto cleanup; + + if (VIR_ALLOC(def) < 0 + || !(def->name = strdup(snapshot->name))) + goto no_memory; + + rc = snap->vtbl->GetDescription(snap, &str16); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not get description of snapshot %s"), + snapshot->name); + goto cleanup; + } + if (str16) { + VBOX_UTF16_TO_UTF8(str16, &str8); + VBOX_UTF16_FREE(str16); + def->description = strdup(str8); + VBOX_UTF8_FREE(str8); + if (!def->description) + goto no_memory; + } + + rc = snap->vtbl->GetTimeStamp(snap, ×tamp); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not get creation time of snapshot %s"), + snapshot->name); + goto cleanup; + } + /* timestamp is in milliseconds while creationTime in seconds */ + def->creationTime = timestamp / 1000; + + rc = snap->vtbl->GetParent(snap, &parent); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not get parent of snapshot %s"), + snapshot->name); + goto cleanup; + } + if (parent) { + rc = parent->vtbl->GetName(parent, &str16); + if (NS_FAILED(rc) || !str16) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not get name of parent of snapshot %s"), + snapshot->name); + goto cleanup; + } + VBOX_UTF16_TO_UTF8(str16, &str8); + VBOX_UTF16_FREE(str16); + def->parent = strdup(str8); + VBOX_UTF8_FREE(str8); + if (!def->parent) + goto no_memory; + } + + rc = snap->vtbl->GetOnline(snap, &online); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not get online state of snapshot %s"), + snapshot->name); + goto cleanup; + } + if (online) + def->state = VIR_DOMAIN_RUNNING; + else + def->state = VIR_DOMAIN_SHUTOFF; + + virUUIDFormat(dom->uuid, uuidstr); + ret = virDomainSnapshotDefFormat(uuidstr, def, 0); + +cleanup: + virDomainSnapshotDefFree(def); + VBOX_RELEASE(parent); + VBOX_RELEASE(snap); + VBOX_RELEASE(machine); + vboxIIDFree(domiid); + return ret; + +no_memory: + virReportOOMError(); + goto cleanup; +} + +static int +vboxDomainSnapshotNum(virDomainPtr dom, + unsigned int flags ATTRIBUTE_UNUSED) +{ + VBOX_OBJECT_CHECK(dom->conn, int, -1); + vboxIID *iid = NULL; + IMachine *machine = NULL; + nsresult rc; + PRUint32 snapshotCount; + +#if VBOX_API_VERSION == 2002 + if (VIR_ALLOC(iid) < 0) { + virReportOOMError(); + goto cleanup; + } +#endif + + vboxIIDFromUUID(dom->uuid, iid); + rc = data->vboxObj->vtbl->GetMachine(data->vboxObj, iid, &machine); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INVALID_DOMAIN, "%s", + _("no domain with matching UUID")); + goto cleanup; + } + + rc = machine->vtbl->GetSnapshotCount(machine, &snapshotCount); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not get snapshot count for domain %s"), + dom->name); + goto cleanup; + } + + ret = snapshotCount; + +cleanup: + VBOX_RELEASE(machine); + vboxIIDFree(iid); + return ret; +} + +static int +vboxDomainSnapshotListNames(virDomainPtr dom, + char **names, + int nameslen, + unsigned int flags ATTRIBUTE_UNUSED) +{ + VBOX_OBJECT_CHECK(dom->conn, int, -1); + vboxIID *iid = NULL; + IMachine *machine = NULL; + nsresult rc; + ISnapshot **snapshots = NULL; + int count = 0; + int i; + +#if VBOX_API_VERSION == 2002 + if (VIR_ALLOC(iid) < 0) { + virReportOOMError(); + goto cleanup; + } +#endif + + vboxIIDFromUUID(dom->uuid, iid); + rc = data->vboxObj->vtbl->GetMachine(data->vboxObj, iid, &machine); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INVALID_DOMAIN, "%s", + _("no domain with matching UUID")); + goto cleanup; + } + + if ((count = vboxDomainSnapshotGetAll(dom, machine, &snapshots)) < 0) + goto cleanup; + + for (i = 0; i < nameslen; i++) { + PRUnichar *nameUtf16; + char *name; + + if (i >= count) + break; + + rc = snapshots[i]->vtbl->GetName(snapshots[i], &nameUtf16); + if (NS_FAILED(rc) || !nameUtf16) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("could not get snapshot name")); + goto cleanup; + } + VBOX_UTF16_TO_UTF8(nameUtf16, &name); + VBOX_UTF16_FREE(nameUtf16); + names[i] = strdup(name); + VBOX_UTF8_FREE(name); + if (!names[i]) { + virReportOOMError(); + goto cleanup; + } + } + + if (count <= nameslen) + ret = count; + else + ret = nameslen; + +cleanup: + if (count > 0) { + for (i = 0; i < count; i++) + VBOX_RELEASE(snapshots[i]); + } + VIR_FREE(snapshots); + VBOX_RELEASE(machine); + vboxIIDFree(iid); + return ret; +} + +static virDomainSnapshotPtr +vboxDomainSnapshotLookupByName(virDomainPtr dom, + const char *name, + unsigned int flags ATTRIBUTE_UNUSED) +{ + VBOX_OBJECT_CHECK(dom->conn, virDomainSnapshotPtr, NULL); + vboxIID *iid = NULL; + IMachine *machine = NULL; + ISnapshot *snapshot = NULL; + nsresult rc; + +#if VBOX_API_VERSION == 2002 + if (VIR_ALLOC(iid) < 0) { + virReportOOMError(); + goto cleanup; + } +#endif + + vboxIIDFromUUID(dom->uuid, iid); + rc = data->vboxObj->vtbl->GetMachine(data->vboxObj, iid, &machine); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INVALID_DOMAIN, "%s", + _("no domain with matching UUID")); + goto cleanup; + } + + if (!(snapshot = vboxDomainSnapshotGet(data, dom, machine, name))) + goto cleanup; + + ret = virGetDomainSnapshot(dom, name); + +cleanup: + VBOX_RELEASE(snapshot); + VBOX_RELEASE(machine); + vboxIIDFree(iid); + return ret; +} + +static int +vboxDomainHasCurrentSnapshot(virDomainPtr dom, + unsigned int flags ATTRIBUTE_UNUSED) +{ + VBOX_OBJECT_CHECK(dom->conn, int, -1); + vboxIID *iid = NULL; + IMachine *machine = NULL; + ISnapshot *snapshot = NULL; + nsresult rc; + +#if VBOX_API_VERSION == 2002 + if (VIR_ALLOC(iid) < 0) { + virReportOOMError(); + goto cleanup; + } +#endif + + vboxIIDFromUUID(dom->uuid, iid); + rc = data->vboxObj->vtbl->GetMachine(data->vboxObj, iid, &machine); + if (NS_FAILED(rc) || !machine) { + vboxError(dom->conn, VIR_ERR_INVALID_DOMAIN, "%s", + _("no domain with matching UUID")); + goto cleanup; + } + + rc = machine->vtbl->GetCurrentSnapshot(machine, &snapshot); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("could not get current snapshot")); + goto cleanup; + } + + if (snapshot) + ret = 1; + else + ret = 0; + +cleanup: + VBOX_RELEASE(machine); + vboxIIDFree(iid); + return ret; +} + +static virDomainSnapshotPtr +vboxDomainSnapshotCurrent(virDomainPtr dom, + unsigned int flags ATTRIBUTE_UNUSED) +{ + VBOX_OBJECT_CHECK(dom->conn, virDomainSnapshotPtr, NULL); + vboxIID *iid = NULL; + IMachine *machine = NULL; + ISnapshot *snapshot = NULL; + PRUnichar *nameUtf16 = NULL; + char *name = NULL; + nsresult rc; + +#if VBOX_API_VERSION == 2002 + if (VIR_ALLOC(iid) < 0) { + virReportOOMError(); + goto cleanup; + } +#endif + + vboxIIDFromUUID(dom->uuid, iid); + rc = data->vboxObj->vtbl->GetMachine(data->vboxObj, iid, &machine); + if (NS_FAILED(rc) || !machine) { + vboxError(dom->conn, VIR_ERR_INVALID_DOMAIN, "%s", + _("no domain with matching UUID")); + goto cleanup; + } + + rc = machine->vtbl->GetCurrentSnapshot(machine, &snapshot); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("could not get current snapshot")); + goto cleanup; + } + + if (!snapshot) { + vboxError(dom->conn, VIR_ERR_OPERATION_INVALID, "%s", + _("domain has no snapshots")); + goto cleanup; + } + + rc = snapshot->vtbl->GetName(snapshot, &nameUtf16); + if (NS_FAILED(rc) || !nameUtf16) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("could not get current snapshot name")); + goto cleanup; + } + + VBOX_UTF16_TO_UTF8(nameUtf16, &name); + if (!name) { + virReportOOMError(); + goto cleanup; + } + + ret = virGetDomainSnapshot(dom, name); + +cleanup: + VBOX_UTF8_FREE(name); + VBOX_UTF16_FREE(nameUtf16); + VBOX_RELEASE(snapshot); + VBOX_RELEASE(machine); + vboxIIDFree(iid); + return ret; +} + +#if VBOX_API_VERSION < 3001 +static int +vboxDomainSnapshotRestore(virDomainPtr dom, + IMachine *machine, + ISnapshot *snapshot) +{ + VBOX_OBJECT_CHECK(dom->conn, int, -1); + vboxIID *iid = NULL; + nsresult rc; + + rc = snapshot->vtbl->GetId(snapshot, &iid); + if (NS_FAILED(rc) || !iid) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("could not get snapshot UUID")); + goto cleanup; + } + + rc = machine->vtbl->SetCurrentSnapshot(machine, iid); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not restore snapshot for domain %s"), dom->name); + goto cleanup; + } + + ret = 0; + +cleanup: + vboxIIDUnalloc(iid); + return ret; +} +#else +static int +vboxDomainSnapshotRestore(virDomainPtr dom, + IMachine *machine, + ISnapshot *snapshot) +{ + VBOX_OBJECT_CHECK(dom->conn, int, -1); + IConsole *console = NULL; + IProgress *progress = NULL; + PRUint32 state; + nsresult rc; + PRInt32 result; + vboxIID *domiid; + + rc = machine->vtbl->GetId(machine, &domiid); + if (NS_FAILED(rc) || !domiid) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("could not get domain UUID")); + goto cleanup; + } + + rc = machine->vtbl->GetState(machine, &state); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("could not get domain state")); + goto cleanup; + } + + if (state >= MachineState_FirstOnline + && state <= MachineState_LastOnline) { + vboxError(dom->conn, VIR_ERR_OPERATION_INVALID, + _("domain %s is already running"), dom->name); + goto cleanup; + } + + rc = data->vboxObj->vtbl->OpenSession(data->vboxObj, data->vboxSession, + domiid); + if (NS_SUCCEEDED(rc)) + rc = data->vboxSession->vtbl->GetConsole(data->vboxSession, &console); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not open VirtualBox session with domain %s"), + dom->name); + goto cleanup; + } + + rc = console->vtbl->RestoreSnapshot(console, snapshot, &progress); + if (NS_FAILED(rc) || !progress) { + if (rc == VBOX_E_INVALID_VM_STATE) { + vboxError(dom->conn, VIR_ERR_OPERATION_INVALID, "%s", + _("cannot restore domain snapshot for running domain")); + } else { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not restore snapshot for domain %s"), + dom->name); + } + goto cleanup; + } + + progress->vtbl->WaitForCompletion(progress, -1); + progress->vtbl->GetResultCode(progress, &result); + if (NS_FAILED(result)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not restore snapshot for domain %s"), dom->name); + goto cleanup; + } + + ret = 0; + +cleanup: + VBOX_RELEASE(progress); + VBOX_RELEASE(console); + data->vboxSession->vtbl->Close(data->vboxSession); + return ret; +} +#endif + +static int +vboxDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, + unsigned int flags ATTRIBUTE_UNUSED) +{ + virDomainPtr dom = snapshot->domain; + VBOX_OBJECT_CHECK(dom->conn, int, -1); + vboxIID *domiid = NULL; + IMachine *machine = NULL; + ISnapshot *newSnapshot = NULL; + ISnapshot *prevSnapshot = NULL; + PRBool online = PR_FALSE; + PRUint32 state; + nsresult rc; + +#if VBOX_API_VERSION == 2002 + if (VIR_ALLOC(domiid) < 0) { + virReportOOMError(); + goto cleanup; + } +#endif + + vboxIIDFromUUID(dom->uuid, domiid); + rc = data->vboxObj->vtbl->GetMachine(data->vboxObj, domiid, &machine); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INVALID_DOMAIN, "%s", + _("no domain with matching UUID")); + goto cleanup; + } + + newSnapshot = vboxDomainSnapshotGet(data, dom, machine, snapshot->name); + if (!newSnapshot) + goto cleanup; + + rc = newSnapshot->vtbl->GetOnline(newSnapshot, &online); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not get online state of snapshot %s"), + snapshot->name); + goto cleanup; + } + + rc = machine->vtbl->GetCurrentSnapshot(machine, &prevSnapshot); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not get current snapshot of domain %s"), + dom->name); + goto cleanup; + } + + rc = machine->vtbl->GetState(machine, &state); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("could not get domain state")); + goto cleanup; + } + + if (state >= MachineState_FirstOnline + && state <= MachineState_LastOnline) { + vboxError(dom->conn, VIR_ERR_OPERATION_INVALID, "%s", + _("cannot revert snapshot of running domain")); + goto cleanup; + } + + if (vboxDomainSnapshotRestore(dom, machine, newSnapshot)) + goto cleanup; + + if (online) { + ret = vboxDomainCreate(dom); + if (!ret) + vboxDomainSnapshotRestore(dom, machine, prevSnapshot); + } else + ret = 0; + +cleanup: + VBOX_RELEASE(prevSnapshot); + VBOX_RELEASE(newSnapshot); + vboxIIDUnalloc(domiid); + return ret; +} + +static int +vboxDomainSnapshotDeleteSingle(vboxGlobalData *data, + IConsole *console, + ISnapshot *snapshot) +{ + IProgress *progress = NULL; + vboxIID *iid = NULL; + int ret = -1; + nsresult rc; +#if VBOX_API_VERSION == 2002 + nsresult result; +#else + PRInt32 result; +#endif + + rc = snapshot->vtbl->GetId(snapshot, &iid); + if (NS_FAILED(rc) || !iid) { + vboxError(NULL, VIR_ERR_INTERNAL_ERROR, "%s", + _("could not get snapshot UUID")); + goto cleanup; + } + +#if VBOX_API_VERSION < 3001 + rc = console->vtbl->DiscardSnapshot(console, iid, &progress); +#else + rc = console->vtbl->DeleteSnapshot(console, iid, &progress); +#endif + if (NS_FAILED(rc) || !progress) { + if (rc == VBOX_E_INVALID_VM_STATE) { + vboxError(NULL, VIR_ERR_OPERATION_INVALID, "%s", + _("cannot delete domain snapshot for running domain")); + } else { + vboxError(NULL, VIR_ERR_INTERNAL_ERROR, "%s", + _("could not delete snapshot")); + } + goto cleanup; + } + + progress->vtbl->WaitForCompletion(progress, -1); + progress->vtbl->GetResultCode(progress, &result); + if (NS_FAILED(result)) { + vboxError(NULL, VIR_ERR_INTERNAL_ERROR, "%s", + _("could not delete snapshot")); + goto cleanup; + } + + ret = 0; + +cleanup: + VBOX_RELEASE(progress); + vboxIIDUnalloc(iid); + return ret; +} + +static int +vboxDomainSnapshotDeleteTree(vboxGlobalData *data, + IConsole *console, + ISnapshot *snapshot) +{ + PRUint32 childrenCount = 0; + ISnapshot **children = NULL; + int ret = -1; + nsresult rc; + unsigned int i; + + rc = snapshot->vtbl->GetChildren(snapshot, &childrenCount, &children); + if (NS_FAILED(rc)) { + vboxError(NULL, VIR_ERR_INTERNAL_ERROR, "%s", + _("could not get children snapshots")); + goto cleanup; + } + + if (childrenCount > 0) { + for (i = 0; i < childrenCount; i++) { + if (vboxDomainSnapshotDeleteTree(data, console, children[i])) + goto cleanup; + } + } + + ret = vboxDomainSnapshotDeleteSingle(data, console, snapshot); + +cleanup: + for (i = 0; i < childrenCount; i++) + VBOX_RELEASE(children[i]); + return ret; +} + +static int +vboxDomainSnapshotDelete(virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + virDomainPtr dom = snapshot->domain; + VBOX_OBJECT_CHECK(dom->conn, int, -1); + vboxIID *domiid = NULL; + IMachine *machine = NULL; + ISnapshot *snap = NULL; + IConsole *console = NULL; + PRUint32 state; + nsresult rc; + +#if VBOX_API_VERSION == 2002 + if (VIR_ALLOC(domiid) < 0) { + virReportOOMError(); + goto cleanup; + } +#endif + + vboxIIDFromUUID(dom->uuid, domiid); + + rc = data->vboxObj->vtbl->GetMachine(data->vboxObj, domiid, &machine); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INVALID_DOMAIN, "%s", + _("no domain with matching UUID")); + goto cleanup; + } + + snap = vboxDomainSnapshotGet(data, dom, machine, snapshot->name); + if (!snap) + goto cleanup; + + rc = machine->vtbl->GetState(machine, &state); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("could not get domain state")); + goto cleanup; + } + + if (state >= MachineState_FirstOnline + && state <= MachineState_LastOnline) { + vboxError(dom->conn, VIR_ERR_OPERATION_INVALID, "%s", + _("cannot delete snapshots of running domain")); + goto cleanup; + } + + rc = data->vboxObj->vtbl->OpenSession(data->vboxObj, data->vboxSession, + domiid); + if (NS_SUCCEEDED(rc)) + rc = data->vboxSession->vtbl->GetConsole(data->vboxSession, &console); + if (NS_FAILED(rc)) { + vboxError(dom->conn, VIR_ERR_INTERNAL_ERROR, + _("could not open VirtualBox session with domain %s"), + dom->name); + goto cleanup; + } + + if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN) + ret = vboxDomainSnapshotDeleteTree(data, console, snap); + else + ret = vboxDomainSnapshotDeleteSingle(data, console, snap); + +cleanup: + VBOX_RELEASE(console); + VBOX_RELEASE(snap); + vboxIIDUnalloc(domiid); + data->vboxSession->vtbl->Close(data->vboxSession); + return ret; +} + #if VBOX_API_VERSION == 2002 /* No Callback support for VirtualBox 2.2.* series */ #else /* !(VBOX_API_VERSION == 2002) */ @@ -7184,15 +8154,15 @@ virDriver NAME(Driver) = { vboxDomainEventRegisterAny, /* domainEventRegisterAny */ vboxDomainEventDeregisterAny, /* domainEventDeregisterAny */ #endif - NULL, /* domainSnapshotCreateXML */ - NULL, /* domainSnapshotDumpXML */ - NULL, /* domainSnapshotNum */ - NULL, /* domainSnapshotListNames */ - NULL, /* domainSnapshotLookupByName */ - NULL, /* domainHasCurrentSnapshot */ - NULL, /* domainSnapshotCurrent */ - NULL, /* domainRevertToSnapshot */ - NULL, /* domainSnapshotDelete */ + vboxDomainSnapshotCreateXML, /* domainSnapshotCreateXML */ + vboxDomainSnapshotDumpXML, /* domainSnapshotDumpXML */ + vboxDomainSnapshotNum, /* domainSnapshotNum */ + vboxDomainSnapshotListNames, /* domainSnapshotListNames */ + vboxDomainSnapshotLookupByName, /* domainSnapshotLookupByName */ + vboxDomainHasCurrentSnapshot, /* domainHasCurrentSnapshot */ + vboxDomainSnapshotCurrent, /* domainSnapshotCurrent */ + vboxDomainRevertToSnapshot, /* domainRevertToSnapshot */ + vboxDomainSnapshotDelete, /* domainSnapshotDelete */ }; virNetworkDriver NAME(NetworkDriver) = { -- 1.6.6.1

On Fri, Apr 02, 2010 at 09:46:00PM -0400, Chris Lalancette wrote:
From: Jiri Denemark <jdenemar@redhat.com>
--- src/vbox/vbox_tmpl.c | 988 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 979 insertions(+), 9 deletions(-)
diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index dac386d..ede2798 100644 --- a/src/vbox/vbox_tmpl.c +++ b/src/vbox/vbox_tmpl.c @@ -5007,6 +5007,976 @@ static int vboxDomainDetachDeviceFlags(virDomainPtr dom, const char *xml, return vboxDomainDetachDevice(dom, xml); } [...] + +#if VBOX_API_VERSION == 2002 + if (VIR_ALLOC(iid) < 0) { + virReportOOMError(); + goto cleanup; + } +#endif
Hum, did that specific allocation really need to occur only on one of the released versions of VBox ? and not before (or we don't support anything older than 2002) Except that and the constant UTF16 / UTF8 conversions which makes code harder to follow, that looks normal, ACK, Daniel -- Daniel Veillard | libxml Gnome XML XSLT toolkit http://xmlsoft.org/ daniel@veillard.com | Rpmfind RPM search engine http://rpmfind.net/ http://veillard.com/ | virtualization library http://libvirt.org/

diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index dac386d..ede2798 100644 --- a/src/vbox/vbox_tmpl.c +++ b/src/vbox/vbox_tmpl.c @@ -5007,6 +5007,976 @@ static int vboxDomainDetachDeviceFlags(virDomainPtr dom, const char *xml, return vboxDomainDetachDevice(dom, xml); } [...] + +#if VBOX_API_VERSION == 2002 + if (VIR_ALLOC(iid) < 0) { + virReportOOMError(); + goto cleanup; + } +#endif
Hum, did that specific allocation really need to occur only on one of the released versions of VBox ? and not before (or we don't support anything older than 2002)
We don't support anything older than 2.2 (2002). The only supported VirtualBox API versions are 2.2, 3.0 and 3.1.
Except that and the constant UTF16 / UTF8 conversions which makes code harder to follow, that looks normal,
Heh, yeah, that is a mess. Actually, the whole VirtualBox driver would deserve to be cleaned up/refactored... Jirka

2010/4/3 Chris Lalancette <clalance@redhat.com>:
From: Jiri Denemark <jdenemar@redhat.com>
--- src/vbox/vbox_tmpl.c | 988 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 979 insertions(+), 9 deletions(-)
ACK. Matthias

Signed-off-by: Chris Lalancette <clalance@redhat.com> --- tools/virsh.c | 451 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 451 insertions(+), 0 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index 5c56fa6..39a2ffe 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -8137,6 +8137,449 @@ cmdQuit(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED) } /* + * "snapshot-create" command + */ +static const vshCmdInfo info_snapshot_create[] = { + {"help", N_("Create a snapshot")}, + {"desc", N_("Snapshot create")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_snapshot_create[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"xmlfile", VSH_OT_DATA, 0, N_("domain snapshot XML")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdSnapshotCreate(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + int ret = FALSE; + char *from; + char *buffer = NULL; + virDomainSnapshotPtr snapshot = NULL; + xmlDocPtr xml = NULL; + xmlXPathContextPtr ctxt = NULL; + char *doc = NULL; + char *name = NULL; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + goto cleanup; + + dom = vshCommandOptDomain(ctl, cmd, NULL); + if (dom == NULL) + goto cleanup; + + from = vshCommandOptString(cmd, "xmlfile", NULL); + if (from == NULL) + buffer = strdup("<domainsnapshot/>"); + else { + if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0) { + /* we have to report the error here because during cleanup + * we'll run through virDomainFree(), which loses the + * last error + */ + virshReportError(ctl); + goto cleanup; + } + } + if (buffer == NULL) { + vshError(ctl, "%s", _("Out of memory")); + goto cleanup; + } + + snapshot = virDomainSnapshotCreateXML(dom, buffer, 0); + if (snapshot == NULL) + goto cleanup; + + doc = virDomainSnapshotGetXMLDesc(snapshot, 0); + if (!doc) + goto cleanup; + + xml = xmlReadDoc((const xmlChar *) doc, "domainsnapshot.xml", NULL, + XML_PARSE_NOENT | XML_PARSE_NONET | + XML_PARSE_NOWARNING); + if (!xml) + goto cleanup; + ctxt = xmlXPathNewContext(xml); + if (!ctxt) + goto cleanup; + + name = virXPathString("string(/domainsnapshot/name)", ctxt); + if (!name) { + vshError(ctl, "%s", + _("Could not find 'name' element in domain snapshot XML")); + goto cleanup; + } + + vshPrint(ctl, _("Domain snapshot %s created"), name); + if (from) + vshPrint(ctl, _(" from '%s'"), from); + vshPrint(ctl, "\n"); + + ret = TRUE; + +cleanup: + VIR_FREE(name); + xmlXPathFreeContext(ctxt); + if (xml) + xmlFreeDoc(xml); + if (snapshot) + virDomainSnapshotFree(snapshot); + VIR_FREE(doc); + VIR_FREE(buffer); + if (dom) + virDomainFree(dom); + + return ret; +} + +/* + * "snapshot-current" command + */ +static const vshCmdInfo info_snapshot_current[] = { + {"help", N_("Get the current snapshot")}, + {"desc", N_("Get the current snapshot")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_snapshot_current[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdSnapshotCurrent(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + int ret = FALSE; + int current; + virDomainSnapshotPtr snapshot = NULL; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + goto cleanup; + + dom = vshCommandOptDomain(ctl, cmd, NULL); + if (dom == NULL) + goto cleanup; + + current = virDomainHasCurrentSnapshot(dom, 0); + if (current < 0) + goto cleanup; + else if (current) { + char *xml; + + if (!(snapshot = virDomainSnapshotCurrent(dom, 0))) + goto cleanup; + + xml = virDomainSnapshotGetXMLDesc(snapshot, 0); + if (!xml) + goto cleanup; + + vshPrint(ctl, "%s", xml); + VIR_FREE(xml); + } + + ret = TRUE; + +cleanup: + if (snapshot) + virDomainSnapshotFree(snapshot); + if (dom) + virDomainFree(dom); + + return ret; +} + +/* + * "snapshot-list" command + */ +static const vshCmdInfo info_snapshot_list[] = { + {"help", N_("List snapshots for a domain")}, + {"desc", N_("Snapshot List")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_snapshot_list[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdSnapshotList(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + int ret = FALSE; + int numsnaps; + char **names = NULL; + int actual; + int i; + xmlDocPtr xml = NULL; + xmlXPathContextPtr ctxt = NULL; + char *doc = NULL; + virDomainSnapshotPtr snapshot = NULL; + char *state = NULL; + long creation; + char timestr[100]; + struct tm time_info; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + goto cleanup; + + dom = vshCommandOptDomain(ctl, cmd, NULL); + if (dom == NULL) + goto cleanup; + + numsnaps = virDomainSnapshotNum(dom, 0); + + if (numsnaps < 0) + goto cleanup; + + vshPrint(ctl, " %-20s %-20s %s\n", _("Name"), _("Creation Time"), _("State")); + vshPrint(ctl, "---------------------------------------------------\n"); + + if (numsnaps) { + if (VIR_ALLOC_N(names, numsnaps) < 0) + goto cleanup; + + actual = virDomainSnapshotListNames(dom, names, numsnaps, 0); + if (actual < 0) + goto cleanup; + + qsort(&names[0], actual, sizeof(char*), namesorter); + + for (i = 0; i < actual; i++) { + /* free up memory from previous iterations of the loop */ + VIR_FREE(state); + if (snapshot) + virDomainSnapshotFree(snapshot); + xmlXPathFreeContext(ctxt); + if (xml) + xmlFreeDoc(xml); + VIR_FREE(doc); + + snapshot = virDomainSnapshotLookupByName(dom, names[i], 0); + if (snapshot == NULL) + continue; + + doc = virDomainSnapshotGetXMLDesc(snapshot, 0); + if (!doc) + continue; + + xml = xmlReadDoc((const xmlChar *) doc, "domainsnapshot.xml", NULL, + XML_PARSE_NOENT | XML_PARSE_NONET | + XML_PARSE_NOWARNING); + if (!xml) + continue; + ctxt = xmlXPathNewContext(xml); + if (!ctxt) + continue; + + state = virXPathString("string(/domainsnapshot/state)", ctxt); + if (state == NULL) + continue; + if (virXPathLong("string(/domainsnapshot/creationTime)", ctxt, + &creation) < 0) + continue; + gmtime_r(&creation, &time_info); + strftime(timestr, sizeof(timestr), "%F %T", &time_info); + + vshPrint(ctl, " %-20s %-20s %s\n", names[i], timestr, state); + } + } + + ret = TRUE; + +cleanup: + /* this frees up memory from the last iteration of the loop */ + VIR_FREE(state); + if (snapshot) + virDomainSnapshotFree(snapshot); + xmlXPathFreeContext(ctxt); + if (xml) + xmlFreeDoc(xml); + VIR_FREE(doc); + VIR_FREE(names); + if (dom) + virDomainFree(dom); + + return ret; +} + +/* + * "snapshot-dumpxml" command + */ +static const vshCmdInfo info_snapshot_dumpxml[] = { + {"help", N_("Dump XML for a domain snapshot")}, + {"desc", N_("Snapshot Dump XML")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_snapshot_dumpxml[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"snapshotname", VSH_OT_DATA, VSH_OFLAG_REQ, N_("snapshot name")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdSnapshotDumpXML(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + int ret = FALSE; + char *name; + virDomainSnapshotPtr snapshot = NULL; + char *xml = NULL; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + goto cleanup; + + dom = vshCommandOptDomain(ctl, cmd, NULL); + if (dom == NULL) + goto cleanup; + + name = vshCommandOptString(cmd, "snapshotname", NULL); + if (name == NULL) { + vshError(ctl, "%s", _("cmdSnapshotDumpXML: Missing snapshotname")); + goto cleanup; + } + + snapshot = virDomainSnapshotLookupByName(dom, name, 0); + if (snapshot == NULL) + goto cleanup; + + xml = virDomainSnapshotGetXMLDesc(snapshot, 0); + if (!xml) + goto cleanup; + + printf("%s", xml); + + ret = TRUE; + +cleanup: + VIR_FREE(xml); + if (snapshot) + virDomainSnapshotFree(snapshot); + if (dom) + virDomainFree(dom); + + return ret; +} + +/* + * "revert-to-snapshot" command + */ +static const vshCmdInfo info_revert_to_snapshot[] = { + {"help", N_("Revert a domain to a snapshot")}, + {"desc", N_("Revert domain to snapshot")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_revert_to_snapshot[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"snapshotname", VSH_OT_DATA, VSH_OFLAG_REQ, N_("snapshot name")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdDomainRevertToSnapshot(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + int ret = FALSE; + char *name; + virDomainSnapshotPtr snapshot = NULL; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + goto cleanup; + + dom = vshCommandOptDomain(ctl, cmd, NULL); + if (dom == NULL) + goto cleanup; + + name = vshCommandOptString(cmd, "snapshotname", NULL); + if (name == NULL) { + vshError(ctl, "%s", _("cmdDomainRevertToSnapshot: Missing snapshotname")); + goto cleanup; + } + + snapshot = virDomainSnapshotLookupByName(dom, name, 0); + if (snapshot == NULL) + goto cleanup; + + if (virDomainRevertToSnapshot(snapshot, 0) < 0) + goto cleanup; + + ret = TRUE; + +cleanup: + if (snapshot) + virDomainSnapshotFree(snapshot); + if (dom) + virDomainFree(dom); + + return ret; +} + +/* + * "snapshot-delete" command + */ +static const vshCmdInfo info_snapshot_delete[] = { + {"help", N_("Delete a domain snapshot")}, + {"desc", N_("Snapshot Delete")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_snapshot_delete[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"snapshotname", VSH_OT_DATA, VSH_OFLAG_REQ, N_("snapshot name")}, + {"children", VSH_OT_BOOL, 0, N_("delete snapshot and all children")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdSnapshotDelete(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + int ret = FALSE; + char *name; + virDomainSnapshotPtr snapshot = NULL; + unsigned int flags = 0; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + goto cleanup; + + dom = vshCommandOptDomain(ctl, cmd, NULL); + if (dom == NULL) + goto cleanup; + + name = vshCommandOptString(cmd, "snapshotname", NULL); + if (name == NULL) { + vshError(ctl, "%s", _("cmdDomainRevertToSnapshot: Missing snapshotname")); + goto cleanup; + } + + if (vshCommandOptBool(cmd, "children")) + flags |= VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN; + + snapshot = virDomainSnapshotLookupByName(dom, name, 0); + if (snapshot == NULL) + goto cleanup; + + if (virDomainSnapshotDelete(snapshot, flags) < 0) + goto cleanup; + + ret = TRUE; + +cleanup: + if (snapshot) + virDomainSnapshotFree(snapshot); + if (dom) + virDomainFree(dom); + + return ret; +} + +/* * Commands */ static const vshCmdDef commands[] = { @@ -8288,6 +8731,14 @@ static const vshCmdDef commands[] = { {"vcpupin", cmdVcpupin, opts_vcpupin, info_vcpupin}, {"version", cmdVersion, NULL, info_version}, {"vncdisplay", cmdVNCDisplay, opts_vncdisplay, info_vncdisplay}, + + {"snapshot-create", cmdSnapshotCreate, opts_snapshot_create, info_snapshot_create}, + {"snapshot-current", cmdSnapshotCurrent, opts_snapshot_current, info_snapshot_current}, + {"snapshot-delete", cmdSnapshotDelete, opts_snapshot_delete, info_snapshot_delete}, + {"snapshot-dumpxml", cmdSnapshotDumpXML, opts_snapshot_dumpxml, info_snapshot_dumpxml}, + {"snapshot-list", cmdSnapshotList, opts_snapshot_list, info_snapshot_list}, + {"revert-to-snapshot", cmdDomainRevertToSnapshot, opts_revert_to_snapshot, info_revert_to_snapshot}, + {NULL, NULL, NULL, NULL} }; -- 1.6.6.1

On Fri, Apr 02, 2010 at 09:46:01PM -0400, Chris Lalancette wrote:
Signed-off-by: Chris Lalancette <clalance@redhat.com> --- tools/virsh.c | 451 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 451 insertions(+), 0 deletions(-)
adding the host info where a snapshot was taken would be nice. We need to add the new command to virsh.pod ACK, Daniel -- Daniel Veillard | libxml Gnome XML XSLT toolkit http://xmlsoft.org/ daniel@veillard.com | Rpmfind RPM search engine http://rpmfind.net/ http://veillard.com/ | virtualization library http://libvirt.org/

2010/4/3 Chris Lalancette <clalance@redhat.com>:
Signed-off-by: Chris Lalancette <clalance@redhat.com> --- tools/virsh.c | 451 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 451 insertions(+), 0 deletions(-)
+ +/* + * "snapshot-list" command + */ +static const vshCmdInfo info_snapshot_list[] = { + {"help", N_("List snapshots for a domain")}, + {"desc", N_("Snapshot List")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_snapshot_list[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdSnapshotList(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + int ret = FALSE; + int numsnaps; + char **names = NULL; + int actual; + int i; + xmlDocPtr xml = NULL; + xmlXPathContextPtr ctxt = NULL; + char *doc = NULL; + virDomainSnapshotPtr snapshot = NULL; + char *state = NULL; + long creation; + char timestr[100]; + struct tm time_info; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + goto cleanup; + + dom = vshCommandOptDomain(ctl, cmd, NULL); + if (dom == NULL) + goto cleanup; + + numsnaps = virDomainSnapshotNum(dom, 0); + + if (numsnaps < 0) + goto cleanup; + + vshPrint(ctl, " %-20s %-20s %s\n", _("Name"), _("Creation Time"), _("State")); + vshPrint(ctl, "---------------------------------------------------\n"); + + if (numsnaps) { + if (VIR_ALLOC_N(names, numsnaps) < 0) + goto cleanup; + + actual = virDomainSnapshotListNames(dom, names, numsnaps, 0); + if (actual < 0) + goto cleanup; + + qsort(&names[0], actual, sizeof(char*), namesorter); + + for (i = 0; i < actual; i++) { + /* free up memory from previous iterations of the loop */ + VIR_FREE(state); + if (snapshot) + virDomainSnapshotFree(snapshot); + xmlXPathFreeContext(ctxt); + if (xml) + xmlFreeDoc(xml); + VIR_FREE(doc); + + snapshot = virDomainSnapshotLookupByName(dom, names[i], 0); + if (snapshot == NULL) + continue; + + doc = virDomainSnapshotGetXMLDesc(snapshot, 0); + if (!doc) + continue; + + xml = xmlReadDoc((const xmlChar *) doc, "domainsnapshot.xml", NULL, + XML_PARSE_NOENT | XML_PARSE_NONET | + XML_PARSE_NOWARNING); + if (!xml) + continue; + ctxt = xmlXPathNewContext(xml); + if (!ctxt) + continue; + + state = virXPathString("string(/domainsnapshot/state)", ctxt); + if (state == NULL) + continue; + if (virXPathLong("string(/domainsnapshot/creationTime)", ctxt, + &creation) < 0) + continue; + gmtime_r(&creation, &time_info); + strftime(timestr, sizeof(timestr), "%F %T", &time_info);
If we use gmtime_r here then we should indicate that the printed time is in UTC, maybe using strftime(timestr, sizeof(timestr), "%F %T UTC", &time_info); or changing the header line from "Creation Time" to "Creation Time (UTC)", or really use localtime_r instead of gmtime_r and maybe indicate the timezone offset using %z as recommended in RFC 2822: strftime(timestr, sizeof(timestr), "%F %T %z", &time_info);
+ vshPrint(ctl, " %-20s %-20s %s\n", names[i], timestr, state); + } + } + + ret = TRUE; + +cleanup: + /* this frees up memory from the last iteration of the loop */ + VIR_FREE(state); + if (snapshot) + virDomainSnapshotFree(snapshot); + xmlXPathFreeContext(ctxt); + if (xml) + xmlFreeDoc(xml); + VIR_FREE(doc); + VIR_FREE(names); + if (dom) + virDomainFree(dom); + + return ret; +} +
+ +/* + * "snapshot-delete" command + */ +static const vshCmdInfo info_snapshot_delete[] = { + {"help", N_("Delete a domain snapshot")}, + {"desc", N_("Snapshot Delete")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_snapshot_delete[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"snapshotname", VSH_OT_DATA, VSH_OFLAG_REQ, N_("snapshot name")}, + {"children", VSH_OT_BOOL, 0, N_("delete snapshot and all children")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdSnapshotDelete(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + int ret = FALSE; + char *name; + virDomainSnapshotPtr snapshot = NULL; + unsigned int flags = 0; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + goto cleanup; + + dom = vshCommandOptDomain(ctl, cmd, NULL); + if (dom == NULL) + goto cleanup; + + name = vshCommandOptString(cmd, "snapshotname", NULL); + if (name == NULL) { + vshError(ctl, "%s", _("cmdDomainRevertToSnapshot: Missing snapshotname"));
Copy&paste error: cmdDomainRevertToSnapshot Actually the error message should not contain the function name. The same goes for the other new functions. ACK. Matthias

On Fri, 2010-04-02 at 21:46 -0400, Chris Lalancette wrote: <snip>
@@ -8288,6 +8731,14 @@ static const vshCmdDef commands[] = { {"vcpupin", cmdVcpupin, opts_vcpupin, info_vcpupin}, {"version", cmdVersion, NULL, info_version}, {"vncdisplay", cmdVNCDisplay, opts_vncdisplay, info_vncdisplay}, + + {"snapshot-create", cmdSnapshotCreate, opts_snapshot_create, info_snapshot_create}, + {"snapshot-current", cmdSnapshotCurrent, opts_snapshot_current, info_snapshot_current}, + {"snapshot-delete", cmdSnapshotDelete, opts_snapshot_delete, info_snapshot_delete}, + {"snapshot-dumpxml", cmdSnapshotDumpXML, opts_snapshot_dumpxml, info_snapshot_dumpxml}, + {"snapshot-list", cmdSnapshotList, opts_snapshot_list, info_snapshot_list}, + {"revert-to-snapshot", cmdDomainRevertToSnapshot, opts_revert_to_snapshot, info_revert_to_snapshot}, + {NULL, NULL, NULL, NULL} };
All the new virsh commands for snapshot management follow the convention of snapshot- prefix (like similar pool-, vol-, net- command prefixes) except for revert-to-snapshot. From a virsh usability perspective, would it make sense to rename this command snapshot-revert? I appreciate that does not capture what the command does as well as revert-to-snapshot but I think this may be a reasonable trade-off for usability and users finding the command easily. Thanks, Paul

On 04/06/2010 03:00 PM, Paul Jenner wrote:
On Fri, 2010-04-02 at 21:46 -0400, Chris Lalancette wrote: <snip>
@@ -8288,6 +8731,14 @@ static const vshCmdDef commands[] = { {"vcpupin", cmdVcpupin, opts_vcpupin, info_vcpupin}, {"version", cmdVersion, NULL, info_version}, {"vncdisplay", cmdVNCDisplay, opts_vncdisplay, info_vncdisplay}, + + {"snapshot-create", cmdSnapshotCreate, opts_snapshot_create, info_snapshot_create}, + {"snapshot-current", cmdSnapshotCurrent, opts_snapshot_current, info_snapshot_current}, + {"snapshot-delete", cmdSnapshotDelete, opts_snapshot_delete, info_snapshot_delete}, + {"snapshot-dumpxml", cmdSnapshotDumpXML, opts_snapshot_dumpxml, info_snapshot_dumpxml}, + {"snapshot-list", cmdSnapshotList, opts_snapshot_list, info_snapshot_list}, + {"revert-to-snapshot", cmdDomainRevertToSnapshot, opts_revert_to_snapshot, info_revert_to_snapshot}, + {NULL, NULL, NULL, NULL} };
All the new virsh commands for snapshot management follow the convention of snapshot- prefix (like similar pool-, vol-, net- command prefixes) except for revert-to-snapshot. From a virsh usability perspective, would it make sense to rename this command snapshot-revert? I appreciate that does not capture what the command does as well as revert-to-snapshot but I think this may be a reasonable trade-off for usability and users finding the command easily.
Yeah, I know exactly where you are coming from. I struggled with this for the whole API, since I really liked that fact that everything was prefixed by virDomainSnaphot... The one advantage to "revert-to-snapshot" is that it's a little clearer that this is a command that operates on a domain instead of on a snapshot, but it's pretty minor. I'm happy to go either way; I guess if we want, we can actually have both :). -- Chris Lalancette

On Tue, Apr 06, 2010 at 08:00:01PM +0100, Paul Jenner wrote:
On Fri, 2010-04-02 at 21:46 -0400, Chris Lalancette wrote: <snip>
@@ -8288,6 +8731,14 @@ static const vshCmdDef commands[] = { {"vcpupin", cmdVcpupin, opts_vcpupin, info_vcpupin}, {"version", cmdVersion, NULL, info_version}, {"vncdisplay", cmdVNCDisplay, opts_vncdisplay, info_vncdisplay}, + + {"snapshot-create", cmdSnapshotCreate, opts_snapshot_create, info_snapshot_create}, + {"snapshot-current", cmdSnapshotCurrent, opts_snapshot_current, info_snapshot_current}, + {"snapshot-delete", cmdSnapshotDelete, opts_snapshot_delete, info_snapshot_delete}, + {"snapshot-dumpxml", cmdSnapshotDumpXML, opts_snapshot_dumpxml, info_snapshot_dumpxml}, + {"snapshot-list", cmdSnapshotList, opts_snapshot_list, info_snapshot_list}, + {"revert-to-snapshot", cmdDomainRevertToSnapshot, opts_revert_to_snapshot, info_revert_to_snapshot}, + {NULL, NULL, NULL, NULL} };
All the new virsh commands for snapshot management follow the convention of snapshot- prefix (like similar pool-, vol-, net- command prefixes) except for revert-to-snapshot. From a virsh usability perspective, would it make sense to rename this command snapshot-revert? I appreciate that does not capture what the command does as well as revert-to-snapshot but I think this may be a reasonable trade-off for usability and users finding the command easily.
I agree, this should be called snapshot-revert Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://deltacloud.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

On Wed, 2010-04-07 at 10:43 +0100, Daniel P. Berrange wrote:
On Tue, Apr 06, 2010 at 08:00:01PM +0100, Paul Jenner wrote:
On Fri, 2010-04-02 at 21:46 -0400, Chris Lalancette wrote: <snip>
@@ -8288,6 +8731,14 @@ static const vshCmdDef commands[] = { {"vcpupin", cmdVcpupin, opts_vcpupin, info_vcpupin}, {"version", cmdVersion, NULL, info_version}, {"vncdisplay", cmdVNCDisplay, opts_vncdisplay, info_vncdisplay}, + + {"snapshot-create", cmdSnapshotCreate, opts_snapshot_create, info_snapshot_create}, + {"snapshot-current", cmdSnapshotCurrent, opts_snapshot_current, info_snapshot_current}, + {"snapshot-delete", cmdSnapshotDelete, opts_snapshot_delete, info_snapshot_delete}, + {"snapshot-dumpxml", cmdSnapshotDumpXML, opts_snapshot_dumpxml, info_snapshot_dumpxml}, + {"snapshot-list", cmdSnapshotList, opts_snapshot_list, info_snapshot_list}, + {"revert-to-snapshot", cmdDomainRevertToSnapshot, opts_revert_to_snapshot, info_revert_to_snapshot}, + {NULL, NULL, NULL, NULL} };
All the new virsh commands for snapshot management follow the convention of snapshot- prefix (like similar pool-, vol-, net- command prefixes) except for revert-to-snapshot. From a virsh usability perspective, would it make sense to rename this command snapshot-revert? I appreciate that does not capture what the command does as well as revert-to-snapshot but I think this may be a reasonable trade-off for usability and users finding the command easily.
I agree, this should be called snapshot-revert
If there is consensus, would someone be willing to submit a patch to change this before 0.8.0? Thanks, Paul

On 04/09/2010 03:54 PM, Paul Jenner wrote:
I agree, this should be called snapshot-revert
If there is consensus, would someone be willing to submit a patch to change this before 0.8.0?
I just pushed this change into libvirt (so it should make 0.8.0). -- Chris Lalancette

On 04/02/2010 09:45 PM, Chris Lalancette wrote:
Hello, Here is the next set of patches for the snapshot API. This set is much more complete, and implements the virDomainRevertToSnapshot() semantic instead of virDomainCreateFromSnapshot() that we discussed earlier. I've also incorporated all of the comments from the review of the first round of patches. Aside from implementation bugs, I consider this patchset complete. The only thing missing from the patches is real documentation along the lines of the API docs that we had discussed on-list. Next week I'll followup with some additions to the documentation that explain the semantics of these calls. Again review and comments are welcome.
I fixed up the minor comments from both DV and Mattias, and I pushed this series. Thanks again to Jirka for the vbox code, Mattias for detailed API discussions, and everyone else who contributed. -- Chris Lalancette
participants (6)
-
Chris Lalancette
-
Daniel P. Berrange
-
Daniel Veillard
-
Jiri Denemark
-
Matthias Bolte
-
Paul Jenner