]> git.proxmox.com Git - pve-qemu-kvm.git/blame - debian/patches/pve/0015-backup-add-pve-monitor-commands.patch
bump version to 2.7.1-501
[pve-qemu-kvm.git] / debian / patches / pve / 0015-backup-add-pve-monitor-commands.patch
CommitLineData
9c3bec39 1From 798846b48b31d8231a3af5858285845d932d1d6b Mon Sep 17 00:00:00 2001
ca0fe5f5
WB
2From: Wolfgang Bumiller <w.bumiller@proxmox.com>
3Date: Wed, 9 Dec 2015 15:20:56 +0100
9c3bec39 4Subject: [PATCH 15/47] backup: add pve monitor commands
ca0fe5f5
WB
5
6---
68a30562
WB
7 blockdev.c | 439 ++++++++++++++++++++++++++++++++++++++++++++++
8 blockjob.c | 3 +-
9 hmp-commands-info.hx | 13 ++
10 hmp-commands.hx | 29 +++
11 hmp.c | 61 +++++++
12 hmp.h | 3 +
13 include/block/block_int.h | 2 +-
14 qapi-schema.json | 89 ++++++++++
15 qmp-commands.hx | 18 ++
16 9 files changed, 655 insertions(+), 2 deletions(-)
ca0fe5f5
WB
17
18diff --git a/blockdev.c b/blockdev.c
68a30562 19index 5e3707d..5417bb0 100644
ca0fe5f5
WB
20--- a/blockdev.c
21+++ b/blockdev.c
b07d35a5 22@@ -52,6 +52,7 @@
ca0fe5f5 23 #include "sysemu/arch_init.h"
b07d35a5
TL
24 #include "qemu/cutils.h"
25 #include "qemu/help_option.h"
ca0fe5f5
WB
26+#include "vma.h"
27
b07d35a5
TL
28 static QTAILQ_HEAD(, BlockDriverState) monitor_bdrv_states =
29 QTAILQ_HEAD_INITIALIZER(monitor_bdrv_states);
68a30562 30@@ -2976,6 +2977,444 @@ static void block_job_cb(void *opaque, int ret)
ca0fe5f5
WB
31 }
32 }
33
34+/* PVE backup related function */
35+
36+static struct PVEBackupState {
37+ Error *error;
38+ bool cancel;
39+ uuid_t uuid;
40+ char uuid_str[37];
41+ int64_t speed;
42+ time_t start_time;
43+ time_t end_time;
44+ char *backup_file;
45+ VmaWriter *vmaw;
46+ GList *di_list;
47+ size_t total;
48+ size_t transferred;
49+ size_t zero_bytes;
50+} backup_state;
51+
52+typedef struct PVEBackupDevInfo {
53+ BlockDriverState *bs;
54+ size_t size;
55+ uint8_t dev_id;
56+ //bool started;
57+ bool completed;
58+} PVEBackupDevInfo;
59+
60+static void pvebackup_run_next_job(void);
61+
68a30562 62+static int pvebackup_dump_cb(void *opaque, BlockBackend *target,
ca0fe5f5
WB
63+ int64_t sector_num, int n_sectors,
64+ unsigned char *buf)
65+{
66+ PVEBackupDevInfo *di = opaque;
67+
68+ if (sector_num & 0x7f) {
69+ if (!backup_state.error) {
70+ error_setg(&backup_state.error,
71+ "got unaligned write inside backup dump "
72+ "callback (sector %ld)", sector_num);
73+ }
74+ return -1; // not aligned to cluster size
75+ }
76+
77+ int64_t cluster_num = sector_num >> 7;
78+ int size = n_sectors * BDRV_SECTOR_SIZE;
79+
80+ int ret = -1;
81+
82+ if (backup_state.vmaw) {
83+ size_t zero_bytes = 0;
84+ ret = vma_writer_write(backup_state.vmaw, di->dev_id, cluster_num,
85+ buf, &zero_bytes);
86+ backup_state.zero_bytes += zero_bytes;
87+ } else {
88+ ret = size;
89+ if (!buf) {
90+ backup_state.zero_bytes += size;
91+ }
92+ }
93+
94+ backup_state.transferred += size;
95+
96+ return ret;
97+}
98+
99+static void pvebackup_cleanup(void)
100+{
101+ backup_state.end_time = time(NULL);
102+
103+ if (backup_state.vmaw) {
104+ Error *local_err = NULL;
105+ vma_writer_close(backup_state.vmaw, &local_err);
106+ error_propagate(&backup_state.error, local_err);
107+ backup_state.vmaw = NULL;
108+ }
109+
110+ if (backup_state.di_list) {
111+ GList *l = backup_state.di_list;
112+ while (l) {
113+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
114+ l = g_list_next(l);
115+ g_free(di);
116+ }
117+ g_list_free(backup_state.di_list);
118+ backup_state.di_list = NULL;
119+ }
120+}
121+
122+static void pvebackup_complete_cb(void *opaque, int ret)
123+{
124+ PVEBackupDevInfo *di = opaque;
125+
126+ assert(backup_state.vmaw);
127+
128+ di->completed = true;
129+
130+ if (ret < 0 && !backup_state.error) {
131+ error_setg(&backup_state.error, "job failed with err %d - %s",
132+ ret, strerror(-ret));
133+ }
134+
135+ BlockDriverState *bs = di->bs;
136+
137+ di->bs = NULL;
138+
139+ vma_writer_close_stream(backup_state.vmaw, di->dev_id);
140+
141+ block_job_cb(bs, ret);
142+
143+ if (!backup_state.cancel) {
144+ pvebackup_run_next_job();
145+ }
146+}
147+
148+static void pvebackup_cancel(void *opaque)
149+{
150+ backup_state.cancel = true;
151+
152+ if (!backup_state.error) {
153+ error_setg(&backup_state.error, "backup cancelled");
154+ }
155+
156+ /* drain all i/o (awake jobs waiting for aio) */
157+ bdrv_drain_all();
158+
159+ GList *l = backup_state.di_list;
160+ while (l) {
161+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
162+ l = g_list_next(l);
163+ if (!di->completed && di->bs) {
164+ BlockJob *job = di->bs->job;
165+ if (job) {
166+ if (!di->completed) {
167+ block_job_cancel_sync(job);
168+ }
169+ }
170+ }
171+ }
172+
173+ pvebackup_cleanup();
174+}
175+
176+void qmp_backup_cancel(Error **errp)
177+{
68a30562
WB
178+ Coroutine *co = qemu_coroutine_create(pvebackup_cancel, NULL);
179+ qemu_coroutine_enter(co);
ca0fe5f5
WB
180+
181+ while (backup_state.vmaw) {
182+ /* vma writer use main aio context */
183+ aio_poll(qemu_get_aio_context(), true);
184+ }
185+}
186+
68a30562 187+bool block_job_should_pause(BlockJob *job);
ca0fe5f5
WB
188+static void pvebackup_run_next_job(void)
189+{
190+ GList *l = backup_state.di_list;
191+ while (l) {
192+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
193+ l = g_list_next(l);
194+ if (!di->completed && di->bs && di->bs->job) {
195+ BlockJob *job = di->bs->job;
68a30562 196+ if (block_job_should_pause(job)) {
ca0fe5f5
WB
197+ bool cancel = backup_state.error || backup_state.cancel;
198+ if (cancel) {
199+ block_job_cancel(job);
200+ } else {
201+ block_job_resume(job);
202+ }
203+ }
204+ return;
205+ }
206+ }
207+
208+ pvebackup_cleanup();
209+}
210+
211+UuidInfo *qmp_backup(const char *backup_file, bool has_format,
212+ BackupFormat format,
213+ bool has_config_file, const char *config_file,
214+ bool has_devlist, const char *devlist,
215+ bool has_speed, int64_t speed, Error **errp)
216+{
217+ BlockBackend *blk;
218+ BlockDriverState *bs = NULL;
219+ Error *local_err = NULL;
220+ uuid_t uuid;
221+ VmaWriter *vmaw = NULL;
222+ gchar **devs = NULL;
223+ GList *di_list = NULL;
224+ GList *l;
225+ UuidInfo *uuid_info;
226+
227+ if (backup_state.di_list) {
228+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
229+ "previous backup not finished");
230+ return NULL;
231+ }
232+
233+ /* Todo: try to auto-detect format based on file name */
234+ format = has_format ? format : BACKUP_FORMAT_VMA;
235+
236+ if (format != BACKUP_FORMAT_VMA) {
237+ error_set(errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
238+ return NULL;
239+ }
240+
241+ if (has_devlist) {
242+ devs = g_strsplit_set(devlist, ",;:", -1);
243+
244+ gchar **d = devs;
245+ while (d && *d) {
246+ blk = blk_by_name(*d);
247+ if (blk) {
248+ bs = blk_bs(blk);
249+ if (bdrv_is_read_only(bs)) {
250+ error_setg(errp, "Node '%s' is read only", *d);
251+ goto err;
252+ }
253+ if (!bdrv_is_inserted(bs)) {
254+ error_setg(errp, QERR_DEVICE_HAS_NO_MEDIUM, *d);
255+ goto err;
256+ }
257+ PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
258+ di->bs = bs;
259+ di_list = g_list_append(di_list, di);
260+ } else {
261+ error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
262+ "Device '%s' not found", *d);
263+ goto err;
264+ }
265+ d++;
266+ }
267+
268+ } else {
68a30562 269+ BdrvNextIterator it;
ca0fe5f5
WB
270+
271+ bs = NULL;
68a30562 272+ for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
ca0fe5f5
WB
273+ if (!bdrv_is_inserted(bs) || bdrv_is_read_only(bs)) {
274+ continue;
275+ }
276+
277+ PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
278+ di->bs = bs;
279+ di_list = g_list_append(di_list, di);
280+ }
281+ }
282+
283+ if (!di_list) {
284+ error_set(errp, ERROR_CLASS_GENERIC_ERROR, "empty device list");
285+ goto err;
286+ }
287+
288+ size_t total = 0;
289+
290+ l = di_list;
291+ while (l) {
292+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
293+ l = g_list_next(l);
294+ if (bdrv_op_is_blocked(di->bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) {
295+ goto err;
296+ }
297+
298+ ssize_t size = bdrv_getlength(di->bs);
299+ if (size < 0) {
300+ error_setg_errno(errp, -di->size, "bdrv_getlength failed");
301+ goto err;
302+ }
303+ di->size = size;
304+ total += size;
305+ }
306+
307+ uuid_generate(uuid);
308+
309+ vmaw = vma_writer_create(backup_file, uuid, &local_err);
310+ if (!vmaw) {
311+ if (local_err) {
312+ error_propagate(errp, local_err);
313+ }
314+ goto err;
315+ }
316+
317+ /* register all devices for vma writer */
318+ l = di_list;
319+ while (l) {
320+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
321+ l = g_list_next(l);
322+
323+ const char *devname = bdrv_get_device_name(di->bs);
324+ di->dev_id = vma_writer_register_stream(vmaw, devname, di->size);
325+ if (di->dev_id <= 0) {
326+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
327+ "register_stream failed");
328+ goto err;
329+ }
330+ }
331+
332+ /* add configuration file to archive */
333+ if (has_config_file) {
334+ char *cdata = NULL;
335+ gsize clen = 0;
336+ GError *err = NULL;
337+ if (!g_file_get_contents(config_file, &cdata, &clen, &err)) {
338+ error_setg(errp, "unable to read file '%s'", config_file);
339+ goto err;
340+ }
341+
342+ const char *basename = g_path_get_basename(config_file);
343+ if (vma_writer_add_config(vmaw, basename, cdata, clen) != 0) {
344+ error_setg(errp, "unable to add config data to vma archive");
345+ g_free(cdata);
346+ goto err;
347+ }
348+ g_free(cdata);
349+ }
350+
351+ /* initialize global backup_state now */
352+
353+ backup_state.cancel = false;
354+
355+ if (backup_state.error) {
356+ error_free(backup_state.error);
357+ backup_state.error = NULL;
358+ }
359+
360+ backup_state.speed = (has_speed && speed > 0) ? speed : 0;
361+
362+ backup_state.start_time = time(NULL);
363+ backup_state.end_time = 0;
364+
365+ if (backup_state.backup_file) {
366+ g_free(backup_state.backup_file);
367+ }
368+ backup_state.backup_file = g_strdup(backup_file);
369+
370+ backup_state.vmaw = vmaw;
371+
372+ uuid_copy(backup_state.uuid, uuid);
373+ uuid_unparse_lower(uuid, backup_state.uuid_str);
374+
375+ backup_state.di_list = di_list;
376+
377+ backup_state.total = total;
378+ backup_state.transferred = 0;
379+ backup_state.zero_bytes = 0;
380+
381+ /* start all jobs (paused state) */
382+ l = di_list;
383+ while (l) {
384+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
385+ l = g_list_next(l);
386+
68a30562 387+ backup_start(NULL, di->bs, NULL, speed, MIRROR_SYNC_MODE_FULL, NULL,
ca0fe5f5
WB
388+ BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
389+ pvebackup_dump_cb, pvebackup_complete_cb, di,
68a30562 390+ 1, NULL, &local_err);
ca0fe5f5
WB
391+ if (local_err != NULL) {
392+ error_setg(&backup_state.error, "backup_job_create failed");
393+ pvebackup_cancel(NULL);
394+ }
395+ }
396+
397+ if (!backup_state.error) {
398+ pvebackup_run_next_job(); // run one job
399+ }
400+
401+ uuid_info = g_malloc0(sizeof(*uuid_info));
402+ uuid_info->UUID = g_strdup(backup_state.uuid_str);
403+ return uuid_info;
404+
405+err:
406+
407+ l = di_list;
408+ while (l) {
409+ g_free(l->data);
410+ l = g_list_next(l);
411+ }
412+ g_list_free(di_list);
413+
414+ if (devs) {
415+ g_strfreev(devs);
416+ }
417+
418+ if (vmaw) {
419+ Error *err = NULL;
420+ vma_writer_close(vmaw, &err);
421+ unlink(backup_file);
422+ }
423+
424+ return NULL;
425+}
426+
427+BackupStatus *qmp_query_backup(Error **errp)
428+{
429+ BackupStatus *info = g_malloc0(sizeof(*info));
430+
431+ if (!backup_state.start_time) {
432+ /* not started, return {} */
433+ return info;
434+ }
435+
436+ info->has_status = true;
437+ info->has_start_time = true;
438+ info->start_time = backup_state.start_time;
439+
440+ if (backup_state.backup_file) {
441+ info->has_backup_file = true;
442+ info->backup_file = g_strdup(backup_state.backup_file);
443+ }
444+
445+ info->has_uuid = true;
446+ info->uuid = g_strdup(backup_state.uuid_str);
447+
448+ if (backup_state.end_time) {
449+ if (backup_state.error) {
450+ info->status = g_strdup("error");
451+ info->has_errmsg = true;
452+ info->errmsg = g_strdup(error_get_pretty(backup_state.error));
453+ } else {
454+ info->status = g_strdup("done");
455+ }
456+ info->has_end_time = true;
457+ info->end_time = backup_state.end_time;
458+ } else {
459+ info->status = g_strdup("active");
460+ }
461+
462+ info->has_total = true;
463+ info->total = backup_state.total;
464+ info->has_zero_bytes = true;
465+ info->zero_bytes = backup_state.zero_bytes;
466+ info->has_transferred = true;
467+ info->transferred = backup_state.transferred;
468+
469+ return info;
470+}
471+
68a30562 472 void qmp_block_stream(bool has_job_id, const char *job_id, const char *device,
ca0fe5f5
WB
473 bool has_base, const char *base,
474 bool has_backing_file, const char *backing_file,
68a30562
WB
475diff --git a/blockjob.c b/blockjob.c
476index a5ba3be..a550458 100644
477--- a/blockjob.c
478+++ b/blockjob.c
479@@ -331,7 +331,8 @@ void block_job_pause(BlockJob *job)
480 job->pause_count++;
481 }
482
483-static bool block_job_should_pause(BlockJob *job)
484+bool block_job_should_pause(BlockJob *job);
485+bool block_job_should_pause(BlockJob *job)
486 {
487 return job->pause_count > 0;
488 }
ca0fe5f5 489diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
68a30562 490index 74446c6..7616fe2 100644
ca0fe5f5
WB
491--- a/hmp-commands-info.hx
492+++ b/hmp-commands-info.hx
493@@ -502,6 +502,19 @@ STEXI
494 Show CPU statistics.
495 ETEXI
496
497+ {
498+ .name = "backup",
499+ .args_type = "",
500+ .params = "",
501+ .help = "show backup status",
502+ .mhandler.cmd = hmp_info_backup,
503+ },
504+
505+STEXI
506+@item info backup
507+show backup status
508+ETEXI
509+
510 #if defined(CONFIG_SLIRP)
511 {
512 .name = "usernet",
513diff --git a/hmp-commands.hx b/hmp-commands.hx
68a30562 514index 848efee..8f2f3e0 100644
ca0fe5f5
WB
515--- a/hmp-commands.hx
516+++ b/hmp-commands.hx
517@@ -87,6 +87,35 @@ STEXI
518 Copy data from a backing file into a block device.
519 ETEXI
520
521+ {
522+ .name = "backup",
523+ .args_type = "backupfile:s,speed:o?,devlist:s?",
524+ .params = "backupfile [speed [devlist]]",
525+ .help = "create a VM Backup.",
526+ .mhandler.cmd = hmp_backup,
527+ },
528+
529+STEXI
530+@item backup
531+@findex backup
532+Create a VM backup.
533+ETEXI
534+
535+ {
536+ .name = "backup_cancel",
537+ .args_type = "",
538+ .params = "",
539+ .help = "cancel the current VM backup",
540+ .mhandler.cmd = hmp_backup_cancel,
541+ },
542+
543+STEXI
544+@item backup_cancel
545+@findex backup_cancel
546+Cancel the current VM backup.
547+
548+ETEXI
549+
550 {
551 .name = "block_job_set_speed",
552 .args_type = "device:B,speed:o",
553diff --git a/hmp.c b/hmp.c
9c3bec39 554index 3b0dd81..95da164 100644
ca0fe5f5
WB
555--- a/hmp.c
556+++ b/hmp.c
68a30562 557@@ -149,6 +149,44 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict)
ca0fe5f5
WB
558 qapi_free_MouseInfoList(mice_list);
559 }
560
561+void hmp_info_backup(Monitor *mon, const QDict *qdict)
562+{
563+ BackupStatus *info;
564+
565+ info = qmp_query_backup(NULL);
566+ if (info->has_status) {
567+ if (info->has_errmsg) {
568+ monitor_printf(mon, "Backup status: %s - %s\n",
569+ info->status, info->errmsg);
570+ } else {
571+ monitor_printf(mon, "Backup status: %s\n", info->status);
572+ }
573+ }
574+
575+ if (info->has_backup_file) {
576+ monitor_printf(mon, "Start time: %s", ctime(&info->start_time));
577+ if (info->end_time) {
578+ monitor_printf(mon, "End time: %s", ctime(&info->end_time));
579+ }
580+
581+ int per = (info->has_total && info->total &&
582+ info->has_transferred && info->transferred) ?
583+ (info->transferred * 100)/info->total : 0;
584+ int zero_per = (info->has_total && info->total &&
585+ info->has_zero_bytes && info->zero_bytes) ?
586+ (info->zero_bytes * 100)/info->total : 0;
587+ monitor_printf(mon, "Backup file: %s\n", info->backup_file);
588+ monitor_printf(mon, "Backup uuid: %s\n", info->uuid);
589+ monitor_printf(mon, "Total size: %zd\n", info->total);
590+ monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
591+ info->transferred, per);
592+ monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
593+ info->zero_bytes, zero_per);
594+ }
595+
596+ qapi_free_BackupStatus(info);
597+}
598+
599 void hmp_info_migrate(Monitor *mon, const QDict *qdict)
600 {
601 MigrationInfo *info;
9c3bec39 602@@ -1493,6 +1531,29 @@ void hmp_block_stream(Monitor *mon, const QDict *qdict)
ca0fe5f5
WB
603 hmp_handle_error(mon, &error);
604 }
605
606+void hmp_backup_cancel(Monitor *mon, const QDict *qdict)
607+{
608+ Error *error = NULL;
609+
610+ qmp_backup_cancel(&error);
611+
612+ hmp_handle_error(mon, &error);
613+}
614+
615+void hmp_backup(Monitor *mon, const QDict *qdict)
616+{
617+ Error *error = NULL;
618+
619+ const char *backup_file = qdict_get_str(qdict, "backupfile");
620+ const char *devlist = qdict_get_try_str(qdict, "devlist");
621+ int64_t speed = qdict_get_try_int(qdict, "speed", 0);
622+
623+ qmp_backup(backup_file, true, BACKUP_FORMAT_VMA, false, NULL, !!devlist,
624+ devlist, qdict_haskey(qdict, "speed"), speed, &error);
625+
626+ hmp_handle_error(mon, &error);
627+}
628+
629 void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict)
630 {
631 Error *error = NULL;
632diff --git a/hmp.h b/hmp.h
68a30562 633index 0876ec0..9a4c1f6 100644
ca0fe5f5
WB
634--- a/hmp.h
635+++ b/hmp.h
636@@ -30,6 +30,7 @@ void hmp_info_migrate(Monitor *mon, const QDict *qdict);
637 void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict);
638 void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict);
639 void hmp_info_migrate_cache_size(Monitor *mon, const QDict *qdict);
640+void hmp_info_backup(Monitor *mon, const QDict *qdict);
641 void hmp_info_cpus(Monitor *mon, const QDict *qdict);
642 void hmp_info_block(Monitor *mon, const QDict *qdict);
643 void hmp_info_blockstats(Monitor *mon, const QDict *qdict);
644@@ -76,6 +77,8 @@ void hmp_eject(Monitor *mon, const QDict *qdict);
645 void hmp_change(Monitor *mon, const QDict *qdict);
646 void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict);
647 void hmp_block_stream(Monitor *mon, const QDict *qdict);
648+void hmp_backup(Monitor *mon, const QDict *qdict);
649+void hmp_backup_cancel(Monitor *mon, const QDict *qdict);
650 void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict);
651 void hmp_block_job_cancel(Monitor *mon, const QDict *qdict);
652 void hmp_block_job_pause(Monitor *mon, const QDict *qdict);
68a30562
WB
653diff --git a/include/block/block_int.h b/include/block/block_int.h
654index db4650e..0f79b51 100644
655--- a/include/block/block_int.h
656+++ b/include/block/block_int.h
657@@ -59,7 +59,7 @@
658
659 #define BLOCK_PROBE_BUF_SIZE 512
660
661-typedef int BackupDumpFunc(void *opaque, BlockDriverState *bs,
662+typedef int BackupDumpFunc(void *opaque, BlockBackend *be,
663 int64_t sector_num, int n_sectors, unsigned char *buf);
664
665 enum BdrvTrackedRequestType {
ca0fe5f5 666diff --git a/qapi-schema.json b/qapi-schema.json
68a30562 667index 518c2ea..89d9ea6 100644
ca0fe5f5
WB
668--- a/qapi-schema.json
669+++ b/qapi-schema.json
6fb04df7 670@@ -356,6 +356,95 @@
ca0fe5f5
WB
671 ##
672 { 'command': 'query-events', 'returns': ['EventInfo'] }
673
674+# @BackupStatus:
675+#
676+# Detailed backup status.
677+#
678+# @status: #optional string describing the current backup status.
679+# This can be 'active', 'done', 'error'. If this field is not
680+# returned, no backup process has been initiated
681+#
682+# @errmsg: #optional error message (only returned if status is 'error')
683+#
684+# @total: #optional total amount of bytes involved in the backup process
685+#
686+# @transferred: #optional amount of bytes already backed up.
687+#
688+# @zero-bytes: #optional amount of 'zero' bytes detected.
689+#
690+# @start-time: #optional time (epoch) when backup job started.
691+#
692+# @end-time: #optional time (epoch) when backup job finished.
693+#
694+# @backupfile: #optional backup file name
695+#
696+# @uuid: #optional uuid for this backup job
697+#
698+##
699+{ 'struct': 'BackupStatus',
700+ 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',
701+ '*transferred': 'int', '*zero-bytes': 'int',
702+ '*start-time': 'int', '*end-time': 'int',
703+ '*backup-file': 'str', '*uuid': 'str' } }
704+
705+##
706+# @BackupFormat
707+#
708+# An enumeration of supported backup formats.
709+#
710+# @vma: Proxmox vma backup format
711+##
712+{ 'enum': 'BackupFormat',
713+ 'data': [ 'vma' ] }
714+
715+##
716+# @backup:
717+#
718+# Starts a VM backup.
719+#
720+# @backup-file: the backup file name
721+#
722+# @format: format of the backup file
723+#
724+# @config-filename: #optional name of a configuration file to include into
725+# the backup archive.
726+#
727+# @speed: #optional the maximum speed, in bytes per second
728+#
729+# @devlist: #optional list of block device names (separated by ',', ';'
730+# or ':'). By default the backup includes all writable block devices.
731+#
732+# Returns: the uuid of the backup job
733+#
734+##
735+{ 'command': 'backup', 'data': { 'backup-file': 'str',
736+ '*format': 'BackupFormat',
737+ '*config-file': 'str',
738+ '*devlist': 'str', '*speed': 'int' },
739+ 'returns': 'UuidInfo' }
740+
741+##
742+# @query-backup
743+#
744+# Returns information about current/last backup task.
745+#
746+# Returns: @BackupStatus
747+#
748+##
749+{ 'command': 'query-backup', 'returns': 'BackupStatus' }
750+
751+##
752+# @backup-cancel
753+#
754+# Cancel the current executing backup process.
755+#
756+# Returns: nothing on success
757+#
758+# Notes: This command succeeds even if there is no backup process running.
759+#
760+##
761+{ 'command': 'backup-cancel' }
762+
763 ##
764 # @MigrationStats
765 #
766diff --git a/qmp-commands.hx b/qmp-commands.hx
68a30562 767index 6de28d4..a8e8522 100644
ca0fe5f5
WB
768--- a/qmp-commands.hx
769+++ b/qmp-commands.hx
68a30562 770@@ -1314,6 +1314,24 @@ Example:
ca0fe5f5
WB
771 EQMP
772
773 {
774+ .name = "backup",
775+ .args_type = "backup-file:s,format:s?,config-file:F?,speed:o?,devlist:s?",
68a30562 776+ .mhandler.cmd_new = qmp_marshal_backup,
ca0fe5f5
WB
777+ },
778+
779+ {
780+ .name = "backup-cancel",
781+ .args_type = "",
68a30562 782+ .mhandler.cmd_new = qmp_marshal_backup_cancel,
ca0fe5f5
WB
783+ },
784+
785+ {
786+ .name = "query-backup",
787+ .args_type = "",
68a30562 788+ .mhandler.cmd_new = qmp_marshal_query_backup,
ca0fe5f5
WB
789+ },
790+
791+ {
792 .name = "block-job-set-speed",
793 .args_type = "device:B,speed:o",
794 .mhandler.cmd_new = qmp_marshal_block_job_set_speed,
795--
7962.1.4
797