]>
Commit | Line | Data |
---|---|---|
884c5e9f | 1 | From cf9cc8878d3246069da3fdf7ec865b7b983487fe Mon Sep 17 00:00:00 2001 |
5ad5891c DM |
2 | From: Dietmar Maurer <dietmar@proxmox.com> |
3 | Date: Tue, 13 Nov 2012 11:27:56 +0100 | |
884c5e9f | 4 | Subject: [PATCH v5 3/6] add backup related monitor commands |
5ad5891c | 5 | |
4244016d | 6 | We use a generic BackupDriver struct to encapsulate all archive format |
5ad5891c DM |
7 | related function. |
8 | ||
9 | Another option would be to simply dump <devid,cluster_num,cluster_data> to | |
10 | the output fh (pipe), and an external binary saves the data. That way we | |
11 | could move the whole archive format related code out of qemu. | |
12 | ||
13 | Signed-off-by: Dietmar Maurer <dietmar@proxmox.com> | |
14 | --- | |
884c5e9f | 15 | backup.h | 15 ++ |
359f03dc | 16 | blockdev.c | 423 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
ddfd618f | 17 | hmp-commands.hx | 31 ++++ |
55827521 | 18 | hmp.c | 63 ++++++++ |
5ad5891c DM |
19 | hmp.h | 3 + |
20 | monitor.c | 7 + | |
89af8a77 | 21 | qapi-schema.json | 95 ++++++++++++ |
5ad5891c | 22 | qmp-commands.hx | 27 ++++ |
884c5e9f | 23 | 8 files changed, 664 insertions(+), 0 deletions(-) |
5ad5891c DM |
24 | |
25 | diff --git a/backup.h b/backup.h | |
884c5e9f | 26 | index 9b1ea1c..22598a6 100644 |
5ad5891c DM |
27 | --- a/backup.h |
28 | +++ b/backup.h | |
884c5e9f DM |
29 | @@ -14,6 +14,9 @@ |
30 | #ifndef QEMU_BACKUP_H | |
31 | #define QEMU_BACKUP_H | |
32 | ||
33 | +#include <uuid/uuid.h> | |
34 | +#include "block/block.h" | |
35 | + | |
36 | #define BACKUP_CLUSTER_BITS 16 | |
37 | #define BACKUP_CLUSTER_SIZE (1<<BACKUP_CLUSTER_BITS) | |
38 | #define BACKUP_BLOCKS_PER_CLUSTER (BACKUP_CLUSTER_SIZE/BDRV_SECTOR_SIZE) | |
39 | @@ -27,4 +30,16 @@ int backup_job_create(BlockDriverState *bs, BackupDumpFunc *backup_dump_cb, | |
89af8a77 DM |
40 | BlockDriverCompletionFunc *backup_complete_cb, |
41 | void *opaque, int64_t speed); | |
5ad5891c DM |
42 | |
43 | +typedef struct BackupDriver { | |
44 | + const char *format; | |
884c5e9f DM |
45 | + void *(*open)(const char *filename, uuid_t uuid, Error **errp); |
46 | + int (*close)(void *opaque, Error **errp); | |
47 | + int (*register_config)(void *opaque, const char *name, gpointer data, | |
5ad5891c | 48 | + size_t data_len); |
884c5e9f DM |
49 | + int (*register_stream)(void *opaque, const char *devname, size_t size); |
50 | + int (*dump)(void *opaque, uint8_t dev_id, int64_t cluster_num, | |
5ad5891c | 51 | + unsigned char *buf, size_t *zero_bytes); |
884c5e9f | 52 | + int (*complete)(void *opaque, uint8_t dev_id, int ret); |
5ad5891c DM |
53 | +} BackupDriver; |
54 | + | |
55 | #endif /* QEMU_BACKUP_H */ | |
56 | diff --git a/blockdev.c b/blockdev.c | |
884c5e9f | 57 | index 63e6f1e..84f598d 100644 |
5ad5891c DM |
58 | --- a/blockdev.c |
59 | +++ b/blockdev.c | |
60 | @@ -20,6 +20,7 @@ | |
61 | #include "qmp-commands.h" | |
62 | #include "trace.h" | |
92bf040c | 63 | #include "sysemu/arch_init.h" |
5ad5891c DM |
64 | +#include "backup.h" |
65 | ||
66 | static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives); | |
67 | ||
92bf040c | 68 | @@ -1334,6 +1335,428 @@ void qmp_drive_mirror(const char *device, const char *target, |
5ad5891c DM |
69 | drive_get_ref(drive_get_by_blockdev(bs)); |
70 | } | |
71 | ||
72 | +/* Backup related function */ | |
73 | + | |
309874bd DM |
74 | +static void backup_run_next_job(void); |
75 | + | |
5ad5891c DM |
76 | +static struct GenericBackupState { |
77 | + Error *error; | |
359f03dc | 78 | + bool cancel; |
5ad5891c DM |
79 | + uuid_t uuid; |
80 | + char uuid_str[37]; | |
55827521 | 81 | + int64_t speed; |
5ad5891c DM |
82 | + time_t start_time; |
83 | + time_t end_time; | |
89af8a77 | 84 | + char *backup_file; |
5ad5891c DM |
85 | + const BackupDriver *driver; |
86 | + void *writer; | |
87 | + GList *bcb_list; | |
88 | + size_t total; | |
89 | + size_t transferred; | |
90 | + size_t zero_bytes; | |
91 | +} backup_state; | |
92 | + | |
93 | +typedef struct BackupCB { | |
94 | + BlockDriverState *bs; | |
5ad5891c | 95 | + uint8_t dev_id; |
ddfd618f DM |
96 | + bool started; |
97 | + bool completed; | |
5ad5891c DM |
98 | + size_t size; |
99 | + size_t transferred; | |
100 | + size_t zero_bytes; | |
101 | +} BackupCB; | |
102 | + | |
103 | +static int backup_dump_cb(void *opaque, BlockDriverState *bs, | |
104 | + int64_t cluster_num, unsigned char *buf) | |
105 | +{ | |
106 | + BackupCB *bcb = opaque; | |
107 | + | |
108 | + assert(backup_state.driver); | |
109 | + assert(backup_state.writer); | |
884c5e9f | 110 | + assert(backup_state.driver->dump); |
5ad5891c DM |
111 | + |
112 | + size_t zero_bytes = 0; | |
884c5e9f DM |
113 | + int bytes = backup_state.driver->dump(backup_state.writer, |
114 | + bcb->dev_id, cluster_num, | |
115 | + buf, &zero_bytes); | |
5ad5891c DM |
116 | + |
117 | + if (bytes > 0) { | |
118 | + bcb->transferred += bytes; | |
119 | + backup_state.transferred += bytes; | |
120 | + if (zero_bytes) { | |
121 | + bcb->zero_bytes += bytes; | |
122 | + backup_state.zero_bytes += zero_bytes; | |
123 | + } | |
124 | + } | |
125 | + | |
126 | + return bytes; | |
127 | +} | |
128 | + | |
129 | +static void backup_cleanup(void) | |
130 | +{ | |
131 | + if (backup_state.writer && backup_state.driver) { | |
132 | + backup_state.end_time = time(NULL); | |
133 | + Error *local_err = NULL; | |
884c5e9f | 134 | + backup_state.driver->close(backup_state.writer, &local_err); |
5ad5891c DM |
135 | + error_propagate(&backup_state.error, local_err); |
136 | + backup_state.writer = NULL; | |
5ad5891c | 137 | + } |
309874bd | 138 | + |
ddfd618f DM |
139 | + if (backup_state.bcb_list) { |
140 | + GList *l = backup_state.bcb_list; | |
141 | + while (l) { | |
142 | + BackupCB *bcb = l->data; | |
143 | + l = g_list_next(l); | |
144 | + drive_put_ref_bh_schedule(drive_get_by_blockdev(bcb->bs)); | |
145 | + g_free(bcb); | |
146 | + } | |
147 | + g_list_free(backup_state.bcb_list); | |
148 | + backup_state.bcb_list = NULL; | |
309874bd | 149 | + } |
5ad5891c DM |
150 | +} |
151 | + | |
152 | +static void backup_complete_cb(void *opaque, int ret) | |
153 | +{ | |
154 | + BackupCB *bcb = opaque; | |
155 | + | |
156 | + assert(backup_state.driver); | |
157 | + assert(backup_state.writer); | |
884c5e9f DM |
158 | + assert(backup_state.driver->complete); |
159 | + assert(backup_state.driver->close); | |
5ad5891c | 160 | + |
ddfd618f | 161 | + bcb->completed = true; |
5ad5891c | 162 | + |
884c5e9f | 163 | + backup_state.driver->complete(backup_state.writer, bcb->dev_id, ret); |
5ad5891c | 164 | + |
359f03dc DM |
165 | + if (!backup_state.cancel) { |
166 | + backup_run_next_job(); | |
167 | + } | |
5ad5891c DM |
168 | +} |
169 | + | |
170 | +static void backup_cancel(void) | |
171 | +{ | |
359f03dc DM |
172 | + backup_state.cancel = true; |
173 | + | |
ddfd618f DM |
174 | + if (!backup_state.error) { |
175 | + error_setg(&backup_state.error, "backup cancelled"); | |
176 | + } | |
5ad5891c | 177 | + |
2dfd543c DM |
178 | + /* drain all i/o (awake jobs waiting for aio) */ |
179 | + bdrv_drain_all(); | |
180 | + | |
2dfd543c | 181 | + int job_count = 0; |
ddfd618f | 182 | + GList *l = backup_state.bcb_list; |
5ad5891c DM |
183 | + while (l) { |
184 | + BackupCB *bcb = l->data; | |
185 | + l = g_list_next(l); | |
89af8a77 DM |
186 | + BlockJob *job = bcb->bs->job; |
187 | + if (job) { | |
2dfd543c | 188 | + job_count++; |
359f03dc | 189 | + if (!bcb->started) { |
89af8a77 DM |
190 | + bcb->started = true; |
191 | + backup_job_start(bcb->bs, true); | |
359f03dc | 192 | + } |
2dfd543c | 193 | + if (!bcb->completed) { |
c6a99f54 | 194 | + block_job_cancel_sync(job); |
ddfd618f | 195 | + } |
2dfd543c | 196 | + } |
5ad5891c DM |
197 | + } |
198 | + | |
359f03dc | 199 | + backup_cleanup(); |
5ad5891c DM |
200 | +} |
201 | + | |
202 | +void qmp_backup_cancel(Error **errp) | |
203 | +{ | |
5ad5891c DM |
204 | + backup_cancel(); |
205 | +} | |
206 | + | |
359f03dc | 207 | +static void backup_run_next_job(void) |
309874bd DM |
208 | +{ |
209 | + GList *l = backup_state.bcb_list; | |
89af8a77 | 210 | + while (l) { |
309874bd | 211 | + BackupCB *bcb = l->data; |
ddfd618f DM |
212 | + l = g_list_next(l); |
213 | + | |
214 | + if (bcb->bs && bcb->bs->job && !bcb->completed) { | |
215 | + if (!bcb->started) { | |
216 | + bcb->started = true; | |
359f03dc | 217 | + bool cancel = backup_state.error || backup_state.cancel; |
89af8a77 | 218 | + backup_job_start(bcb->bs, cancel); |
ddfd618f DM |
219 | + } |
220 | + return; | |
221 | + } | |
309874bd | 222 | + } |
ddfd618f DM |
223 | + |
224 | + backup_cleanup(); | |
309874bd DM |
225 | +} |
226 | + | |
5ad5891c DM |
227 | +static void backup_start_jobs(void) |
228 | +{ | |
ddfd618f | 229 | + /* create all jobs (one for each device), start first one */ |
5ad5891c DM |
230 | + GList *l = backup_state.bcb_list; |
231 | + while (l) { | |
232 | + BackupCB *bcb = l->data; | |
233 | + l = g_list_next(l); | |
234 | + | |
309874bd | 235 | + if (backup_job_create(bcb->bs, backup_dump_cb, backup_complete_cb, |
55827521 | 236 | + bcb, backup_state.speed) != 0) { |
ddfd618f | 237 | + error_setg(&backup_state.error, "backup_job_create failed"); |
5ad5891c | 238 | + backup_cancel(); |
ddfd618f | 239 | + return; |
309874bd DM |
240 | + } |
241 | + } | |
242 | + | |
ddfd618f | 243 | + backup_run_next_job(); |
5ad5891c DM |
244 | +} |
245 | + | |
89af8a77 DM |
246 | +char *qmp_backup(const char *backup_file, bool has_format, BackupFormat format, |
247 | + bool has_config_file, const char *config_file, | |
5ad5891c DM |
248 | + bool has_devlist, const char *devlist, |
249 | + bool has_speed, int64_t speed, Error **errp) | |
250 | +{ | |
251 | + BlockDriverState *bs; | |
252 | + Error *local_err = NULL; | |
253 | + uuid_t uuid; | |
254 | + void *writer = NULL; | |
255 | + gchar **devs = NULL; | |
256 | + GList *bcblist = NULL; | |
257 | + | |
359f03dc DM |
258 | + if (backup_state.bcb_list) { |
259 | + error_set(errp, ERROR_CLASS_GENERIC_ERROR, | |
260 | + "previous backup not finished"); | |
261 | + return NULL; | |
262 | + } | |
263 | + | |
5ad5891c | 264 | + /* Todo: try to auto-detect format based on file name */ |
3055eeb4 | 265 | + format = has_format ? format : BACKUP_FORMAT_VMA; |
5ad5891c DM |
266 | + |
267 | + /* fixme: find driver for specifued format */ | |
268 | + const BackupDriver *driver = NULL; | |
269 | + | |
270 | + if (!driver) { | |
3055eeb4 | 271 | + error_set(errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format"); |
5ad5891c DM |
272 | + return NULL; |
273 | + } | |
274 | + | |
275 | + if (has_devlist) { | |
b09e7646 | 276 | + devs = g_strsplit_set(devlist, ",;:", -1); |
5ad5891c DM |
277 | + |
278 | + gchar **d = devs; | |
279 | + while (d && *d) { | |
280 | + bs = bdrv_find(*d); | |
281 | + if (bs) { | |
282 | + if (bdrv_is_read_only(bs)) { | |
283 | + error_set(errp, QERR_DEVICE_IS_READ_ONLY, *d); | |
284 | + goto err; | |
285 | + } | |
286 | + if (!bdrv_is_inserted(bs)) { | |
287 | + error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, *d); | |
288 | + goto err; | |
289 | + } | |
290 | + BackupCB *bcb = g_new0(BackupCB, 1); | |
291 | + bcb->bs = bs; | |
292 | + bcblist = g_list_append(bcblist, bcb); | |
293 | + } else { | |
294 | + error_set(errp, QERR_DEVICE_NOT_FOUND, *d); | |
295 | + goto err; | |
296 | + } | |
297 | + d++; | |
298 | + } | |
299 | + | |
300 | + } else { | |
301 | + | |
302 | + bs = NULL; | |
303 | + while ((bs = bdrv_next(bs))) { | |
304 | + | |
305 | + if (!bdrv_is_inserted(bs) || bdrv_is_read_only(bs)) { | |
306 | + continue; | |
307 | + } | |
308 | + | |
309 | + BackupCB *bcb = g_new0(BackupCB, 1); | |
310 | + bcb->bs = bs; | |
311 | + bcblist = g_list_append(bcblist, bcb); | |
312 | + } | |
313 | + } | |
314 | + | |
315 | + if (!bcblist) { | |
316 | + error_set(errp, ERROR_CLASS_GENERIC_ERROR, "empty device list"); | |
317 | + goto err; | |
318 | + } | |
319 | + | |
320 | + GList *l = bcblist; | |
321 | + while (l) { | |
322 | + BackupCB *bcb = l->data; | |
323 | + l = g_list_next(l); | |
324 | + if (bcb->bs->job) { | |
325 | + error_set(errp, QERR_DEVICE_IN_USE, bdrv_get_device_name(bcb->bs)); | |
326 | + goto err; | |
327 | + } | |
328 | + } | |
329 | + | |
330 | + uuid_generate(uuid); | |
331 | + | |
884c5e9f | 332 | + writer = driver->open(backup_file, uuid, &local_err); |
5ad5891c DM |
333 | + if (!writer) { |
334 | + if (error_is_set(&local_err)) { | |
335 | + error_propagate(errp, local_err); | |
336 | + } | |
337 | + goto err; | |
338 | + } | |
339 | + | |
340 | + size_t total = 0; | |
341 | + | |
342 | + /* register all devices for vma writer */ | |
343 | + l = bcblist; | |
344 | + while (l) { | |
345 | + BackupCB *bcb = l->data; | |
346 | + l = g_list_next(l); | |
347 | + | |
348 | + int64_t size = bdrv_getlength(bcb->bs); | |
349 | + const char *devname = bdrv_get_device_name(bcb->bs); | |
884c5e9f | 350 | + bcb->dev_id = driver->register_stream(writer, devname, size); |
5ad5891c DM |
351 | + if (bcb->dev_id <= 0) { |
352 | + error_set(errp, ERROR_CLASS_GENERIC_ERROR, | |
353 | + "register_stream failed"); | |
354 | + goto err; | |
355 | + } | |
356 | + bcb->size = size; | |
357 | + total += size; | |
358 | + } | |
359 | + | |
360 | + /* add configuration file to archive */ | |
89af8a77 | 361 | + if (has_config_file) { |
5ad5891c DM |
362 | + char *cdata = NULL; |
363 | + gsize clen = 0; | |
364 | + GError *err = NULL; | |
89af8a77 DM |
365 | + if (!g_file_get_contents(config_file, &cdata, &clen, &err)) { |
366 | + error_setg(errp, "unable to read file '%s'", config_file); | |
5ad5891c DM |
367 | + goto err; |
368 | + } | |
369 | + | |
89af8a77 | 370 | + const char *basename = g_path_get_basename(config_file); |
884c5e9f | 371 | + if (driver->register_config(writer, basename, cdata, clen) < 0) { |
5ad5891c DM |
372 | + error_setg(errp, "register_config failed"); |
373 | + g_free(cdata); | |
374 | + goto err; | |
375 | + } | |
376 | + g_free(cdata); | |
377 | + } | |
378 | + | |
379 | + /* initialize global backup_state now */ | |
380 | + | |
359f03dc DM |
381 | + backup_state.cancel = false; |
382 | + | |
5ad5891c DM |
383 | + if (backup_state.error) { |
384 | + error_free(backup_state.error); | |
385 | + backup_state.error = NULL; | |
386 | + } | |
387 | + | |
388 | + backup_state.driver = driver; | |
389 | + | |
55827521 DM |
390 | + backup_state.speed = (has_speed && speed > 0) ? speed : 0; |
391 | + | |
5ad5891c DM |
392 | + backup_state.start_time = time(NULL); |
393 | + backup_state.end_time = 0; | |
394 | + | |
89af8a77 DM |
395 | + if (backup_state.backup_file) { |
396 | + g_free(backup_state.backup_file); | |
5ad5891c | 397 | + } |
89af8a77 | 398 | + backup_state.backup_file = g_strdup(backup_file); |
5ad5891c DM |
399 | + |
400 | + backup_state.writer = writer; | |
401 | + | |
402 | + uuid_copy(backup_state.uuid, uuid); | |
403 | + uuid_unparse_lower(uuid, backup_state.uuid_str); | |
404 | + | |
405 | + backup_state.bcb_list = bcblist; | |
406 | + | |
407 | + backup_state.total = total; | |
408 | + backup_state.transferred = 0; | |
409 | + backup_state.zero_bytes = 0; | |
410 | + | |
309874bd DM |
411 | + /* Grab a reference so hotplug does not delete the |
412 | + * BlockDriverState from underneath us. | |
413 | + */ | |
414 | + l = bcblist; | |
415 | + while (l) { | |
416 | + BackupCB *bcb = l->data; | |
417 | + l = g_list_next(l); | |
418 | + drive_get_ref(drive_get_by_blockdev(bcb->bs)); | |
419 | + } | |
420 | + | |
5ad5891c DM |
421 | + backup_start_jobs(); |
422 | + | |
423 | + return g_strdup(backup_state.uuid_str); | |
424 | + | |
425 | +err: | |
426 | + | |
427 | + l = bcblist; | |
428 | + while (l) { | |
429 | + g_free(l->data); | |
430 | + l = g_list_next(l); | |
431 | + } | |
432 | + g_list_free(bcblist); | |
433 | + | |
434 | + if (devs) { | |
435 | + g_strfreev(devs); | |
436 | + } | |
437 | + | |
438 | + if (writer) { | |
89af8a77 | 439 | + unlink(backup_file); |
5ad5891c DM |
440 | + if (driver) { |
441 | + Error *err = NULL; | |
884c5e9f | 442 | + driver->close(writer, &err); |
5ad5891c DM |
443 | + } |
444 | + } | |
445 | + | |
446 | + return NULL; | |
447 | +} | |
448 | + | |
449 | +BackupStatus *qmp_query_backup(Error **errp) | |
450 | +{ | |
451 | + BackupStatus *info = g_malloc0(sizeof(*info)); | |
452 | + | |
453 | + if (!backup_state.start_time) { | |
454 | + /* not started, return {} */ | |
455 | + return info; | |
456 | + } | |
457 | + | |
458 | + info->has_status = true; | |
459 | + info->has_start_time = true; | |
460 | + info->start_time = backup_state.start_time; | |
461 | + | |
89af8a77 DM |
462 | + if (backup_state.backup_file) { |
463 | + info->has_backup_file = true; | |
464 | + info->backup_file = g_strdup(backup_state.backup_file); | |
5ad5891c DM |
465 | + } |
466 | + | |
467 | + info->has_uuid = true; | |
468 | + info->uuid = g_strdup(backup_state.uuid_str); | |
469 | + | |
470 | + if (backup_state.end_time) { | |
471 | + if (backup_state.error) { | |
472 | + info->status = g_strdup("error"); | |
473 | + info->has_errmsg = true; | |
474 | + info->errmsg = g_strdup(error_get_pretty(backup_state.error)); | |
475 | + } else { | |
476 | + info->status = g_strdup("done"); | |
477 | + } | |
478 | + info->has_end_time = true; | |
479 | + info->end_time = backup_state.end_time; | |
480 | + } else { | |
481 | + info->status = g_strdup("active"); | |
482 | + } | |
483 | + | |
484 | + info->has_total = true; | |
485 | + info->total = backup_state.total; | |
486 | + info->has_zero_bytes = true; | |
487 | + info->zero_bytes = backup_state.zero_bytes; | |
488 | + info->has_transferred = true; | |
489 | + info->transferred = backup_state.transferred; | |
490 | + | |
491 | + return info; | |
492 | +} | |
493 | + | |
494 | static BlockJob *find_block_job(const char *device) | |
495 | { | |
496 | BlockDriverState *bs; | |
497 | diff --git a/hmp-commands.hx b/hmp-commands.hx | |
92bf040c | 498 | index 64008a9..0f178d8 100644 |
5ad5891c DM |
499 | --- a/hmp-commands.hx |
500 | +++ b/hmp-commands.hx | |
501 | @@ -83,6 +83,35 @@ STEXI | |
502 | Copy data from a backing file into a block device. | |
503 | ETEXI | |
504 | ||
505 | + { | |
506 | + .name = "backup", | |
507 | + .args_type = "backupfile:s,speed:o?,devlist:s?", | |
508 | + .params = "backupfile [speed [devlist]]", | |
509 | + .help = "create a VM Backup.", | |
510 | + .mhandler.cmd = hmp_backup, | |
511 | + }, | |
512 | + | |
513 | +STEXI | |
514 | +@item backup | |
515 | +@findex backup | |
516 | +Create a VM backup. | |
517 | +ETEXI | |
518 | + | |
519 | + { | |
520 | + .name = "backup_cancel", | |
521 | + .args_type = "", | |
522 | + .params = "", | |
523 | + .help = "cancel the current VM backup", | |
524 | + .mhandler.cmd = hmp_backup_cancel, | |
525 | + }, | |
526 | + | |
527 | +STEXI | |
528 | +@item backup_cancel | |
529 | +@findex backup_cancel | |
530 | +Cancel the current VM backup. | |
531 | + | |
532 | +ETEXI | |
533 | + | |
534 | { | |
535 | .name = "block_job_set_speed", | |
536 | .args_type = "device:B,speed:o", | |
92bf040c | 537 | @@ -1630,6 +1659,8 @@ show CPU statistics |
5ad5891c DM |
538 | show user network stack connection states |
539 | @item info migrate | |
540 | show migration status | |
541 | +@item info backup | |
542 | +show backup status | |
543 | @item info migrate_capabilities | |
544 | show current migration capabilities | |
545 | @item info migrate_cache_size | |
546 | diff --git a/hmp.c b/hmp.c | |
89af8a77 | 547 | index 2f47a8a..b2c1f23 100644 |
5ad5891c DM |
548 | --- a/hmp.c |
549 | +++ b/hmp.c | |
92bf040c | 550 | @@ -131,6 +131,38 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict) |
5ad5891c DM |
551 | qapi_free_MouseInfoList(mice_list); |
552 | } | |
553 | ||
92bf040c | 554 | +void hmp_info_backup(Monitor *mon, const QDict *qdict) |
5ad5891c DM |
555 | +{ |
556 | + BackupStatus *info; | |
557 | + | |
558 | + info = qmp_query_backup(NULL); | |
559 | + if (info->has_status) { | |
560 | + if (info->has_errmsg) { | |
561 | + monitor_printf(mon, "Backup status: %s - %s\n", | |
562 | + info->status, info->errmsg); | |
563 | + } else { | |
564 | + monitor_printf(mon, "Backup status: %s\n", info->status); | |
565 | + } | |
566 | + } | |
89af8a77 | 567 | + if (info->has_backup_file) { |
5ad5891c DM |
568 | + int per = (info->has_total && info->total && |
569 | + info->has_transferred && info->transferred) ? | |
570 | + (info->transferred * 100)/info->total : 0; | |
571 | + int zero_per = (info->has_total && info->total && | |
572 | + info->has_zero_bytes && info->zero_bytes) ? | |
573 | + (info->zero_bytes * 100)/info->total : 0; | |
89af8a77 | 574 | + monitor_printf(mon, "Backup file: %s\n", info->backup_file); |
5ad5891c DM |
575 | + monitor_printf(mon, "Backup uuid: %s\n", info->uuid); |
576 | + monitor_printf(mon, "Total size: %zd\n", info->total); | |
577 | + monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n", | |
578 | + info->transferred, per); | |
579 | + monitor_printf(mon, "Zero bytes: %zd (%d%%)\n", | |
580 | + info->zero_bytes, zero_per); | |
581 | + } | |
582 | + | |
583 | + qapi_free_BackupStatus(info); | |
584 | +} | |
585 | + | |
92bf040c | 586 | void hmp_info_migrate(Monitor *mon, const QDict *qdict) |
5ad5891c DM |
587 | { |
588 | MigrationInfo *info; | |
92bf040c | 589 | @@ -998,6 +1030,37 @@ void hmp_block_stream(Monitor *mon, const QDict *qdict) |
5ad5891c DM |
590 | hmp_handle_error(mon, &error); |
591 | } | |
592 | ||
593 | +void hmp_backup_cancel(Monitor *mon, const QDict *qdict) | |
594 | +{ | |
595 | + Error *errp = NULL; | |
596 | + | |
597 | + qmp_backup_cancel(&errp); | |
598 | + | |
599 | + if (error_is_set(&errp)) { | |
600 | + monitor_printf(mon, "%s\n", error_get_pretty(errp)); | |
601 | + error_free(errp); | |
602 | + return; | |
603 | + } | |
604 | +} | |
605 | + | |
606 | +void hmp_backup(Monitor *mon, const QDict *qdict) | |
607 | +{ | |
89af8a77 | 608 | + const char *backup_file = qdict_get_str(qdict, "backup-file"); |
5ad5891c DM |
609 | + const char *devlist = qdict_get_try_str(qdict, "devlist"); |
610 | + int64_t speed = qdict_get_try_int(qdict, "speed", 0); | |
611 | + | |
612 | + Error *errp = NULL; | |
613 | + | |
89af8a77 | 614 | + qmp_backup(backup_file, true, BACKUP_FORMAT_VMA, false, NULL, !!devlist, |
309874bd | 615 | + devlist, qdict_haskey(qdict, "speed"), speed, &errp); |
5ad5891c DM |
616 | + |
617 | + if (error_is_set(&errp)) { | |
618 | + monitor_printf(mon, "%s\n", error_get_pretty(errp)); | |
619 | + error_free(errp); | |
620 | + return; | |
621 | + } | |
622 | +} | |
623 | + | |
624 | void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict) | |
625 | { | |
626 | Error *error = NULL; | |
627 | diff --git a/hmp.h b/hmp.h | |
92bf040c | 628 | index 30b3c20..ad4cf80 100644 |
5ad5891c DM |
629 | --- a/hmp.h |
630 | +++ b/hmp.h | |
92bf040c DM |
631 | @@ -28,6 +28,7 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict); |
632 | void hmp_info_migrate(Monitor *mon, const QDict *qdict); | |
633 | void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict); | |
634 | void hmp_info_migrate_cache_size(Monitor *mon, const QDict *qdict); | |
635 | +void hmp_info_backup(Monitor *mon, const QDict *qdict); | |
636 | void hmp_info_cpus(Monitor *mon, const QDict *qdict); | |
637 | void hmp_info_block(Monitor *mon, const QDict *qdict); | |
638 | void hmp_info_blockstats(Monitor *mon, const QDict *qdict); | |
639 | @@ -65,6 +66,8 @@ void hmp_eject(Monitor *mon, const QDict *qdict); | |
5ad5891c DM |
640 | void hmp_change(Monitor *mon, const QDict *qdict); |
641 | void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict); | |
642 | void hmp_block_stream(Monitor *mon, const QDict *qdict); | |
643 | +void hmp_backup(Monitor *mon, const QDict *qdict); | |
644 | +void hmp_backup_cancel(Monitor *mon, const QDict *qdict); | |
645 | void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict); | |
646 | void hmp_block_job_cancel(Monitor *mon, const QDict *qdict); | |
647 | void hmp_block_job_pause(Monitor *mon, const QDict *qdict); | |
648 | diff --git a/monitor.c b/monitor.c | |
89af8a77 | 649 | index 6a0f257..e4a810c 100644 |
5ad5891c DM |
650 | --- a/monitor.c |
651 | +++ b/monitor.c | |
92bf040c | 652 | @@ -2666,6 +2666,13 @@ static mon_cmd_t info_cmds[] = { |
5ad5891c DM |
653 | }, |
654 | #endif | |
655 | { | |
656 | + .name = "backup", | |
657 | + .args_type = "", | |
658 | + .params = "", | |
659 | + .help = "show backup status", | |
92bf040c | 660 | + .mhandler.cmd = hmp_info_backup, |
5ad5891c DM |
661 | + }, |
662 | + { | |
663 | .name = "migrate", | |
664 | .args_type = "", | |
665 | .params = "", | |
666 | diff --git a/qapi-schema.json b/qapi-schema.json | |
89af8a77 | 667 | index 7275b5d..09ca8ef 100644 |
5ad5891c DM |
668 | --- a/qapi-schema.json |
669 | +++ b/qapi-schema.json | |
92bf040c | 670 | @@ -425,6 +425,39 @@ |
5ad5891c DM |
671 | { 'type': 'EventInfo', 'data': {'name': 'str'} } |
672 | ||
673 | ## | |
674 | +# @BackupStatus: | |
675 | +# | |
676 | +# Detailed backup status. | |
677 | +# | |
678 | +# @status: #optional string describing the current backup status. | |
4244016d | 679 | +# This can be 'active', 'done', 'error'. If this field is not |
5ad5891c DM |
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 | +# | |
89af8a77 | 698 | +# Since: 1.5.0 |
5ad5891c DM |
699 | +## |
700 | +{ 'type': 'BackupStatus', | |
701 | + 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int', | |
702 | + '*transferred': 'int', '*zero-bytes': 'int', | |
703 | + '*start-time': 'int', '*end-time': 'int', | |
89af8a77 | 704 | + '*backup-file': 'str', '*uuid': 'str' } } |
5ad5891c DM |
705 | + |
706 | +## | |
707 | # @query-events: | |
708 | # | |
709 | # Return a list of supported QMP events by this server | |
89af8a77 | 710 | @@ -1824,6 +1857,68 @@ |
5ad5891c DM |
711 | 'data': { 'path': 'str' }, |
712 | 'returns': [ 'ObjectPropertyInfo' ] } | |
713 | ||
3055eeb4 DM |
714 | + |
715 | +## | |
716 | +# @BackupFormat | |
717 | +# | |
718 | +# An enumeration of supported backup formats. | |
719 | +# | |
720 | +# @vma: Proxmox vma backup format | |
721 | +## | |
722 | +{ 'enum': 'BackupFormat', | |
723 | + 'data': [ 'vma' ] } | |
5ad5891c DM |
724 | + |
725 | +## | |
726 | +# @backup: | |
727 | +# | |
728 | +# Starts a VM backup. | |
729 | +# | |
89af8a77 | 730 | +# @backup-file: the backup file name |
5ad5891c DM |
731 | +# |
732 | +# @format: format of the backup file | |
733 | +# | |
734 | +# @config-filename: #optional name of a configuration file to include into | |
735 | +# the backup archive. | |
736 | +# | |
737 | +# @speed: #optional the maximum speed, in bytes per second | |
738 | +# | |
89af8a77 DM |
739 | +# @devlist: #optional list of block device names (separated by ',', ';' |
740 | +# or ':'). By default the backup includes all writable block devices. | |
741 | +# | |
5ad5891c DM |
742 | +# Returns: the uuid of the backup job |
743 | +# | |
89af8a77 | 744 | +# Since: 1.5.0 |
5ad5891c | 745 | +## |
89af8a77 DM |
746 | +{ 'command': 'backup', 'data': { 'backup-file': 'str', |
747 | + '*format': 'BackupFormat', | |
748 | + '*config-file': 'str', | |
5ad5891c DM |
749 | + '*devlist': 'str', '*speed': 'int' }, |
750 | + 'returns': 'str' } | |
751 | + | |
752 | +## | |
753 | +# @query-backup | |
754 | +# | |
755 | +# Returns information about current/last backup task. | |
756 | +# | |
757 | +# Returns: @BackupStatus | |
758 | +# | |
89af8a77 | 759 | +# Since: 1.5.0 |
5ad5891c DM |
760 | +## |
761 | +{ 'command': 'query-backup', 'returns': 'BackupStatus' } | |
762 | + | |
763 | +## | |
764 | +# @backup-cancel | |
765 | +# | |
766 | +# Cancel the current executing backup process. | |
767 | +# | |
768 | +# Returns: nothing on success | |
769 | +# | |
770 | +# Notes: This command succeeds even if there is no backup process running. | |
771 | +# | |
89af8a77 | 772 | +# Since: 1.5.0 |
5ad5891c DM |
773 | +## |
774 | +{ 'command': 'backup-cancel' } | |
775 | + | |
776 | ## | |
777 | # @qom-get: | |
778 | # | |
779 | diff --git a/qmp-commands.hx b/qmp-commands.hx | |
89af8a77 | 780 | index 799adea..17e381b 100644 |
5ad5891c DM |
781 | --- a/qmp-commands.hx |
782 | +++ b/qmp-commands.hx | |
92bf040c | 783 | @@ -889,6 +889,18 @@ EQMP |
5ad5891c DM |
784 | }, |
785 | ||
786 | { | |
787 | + .name = "backup", | |
89af8a77 | 788 | + .args_type = "backup-file:s,format:s?,config-file:F?,speed:o?,devlist:s?", |
5ad5891c DM |
789 | + .mhandler.cmd_new = qmp_marshal_input_backup, |
790 | + }, | |
791 | + | |
792 | + { | |
89af8a77 | 793 | + .name = "backup-cancel", |
5ad5891c DM |
794 | + .args_type = "", |
795 | + .mhandler.cmd_new = qmp_marshal_input_backup_cancel, | |
796 | + }, | |
797 | + | |
798 | + { | |
799 | .name = "block-job-set-speed", | |
800 | .args_type = "device:B,speed:o", | |
801 | .mhandler.cmd_new = qmp_marshal_input_block_job_set_speed, | |
92bf040c | 802 | @@ -2566,6 +2578,21 @@ EQMP |
5ad5891c DM |
803 | }, |
804 | ||
805 | SQMP | |
806 | + | |
807 | +query-backup | |
808 | +------------- | |
809 | + | |
810 | +Backup status. | |
811 | + | |
812 | +EQMP | |
813 | + | |
814 | + { | |
815 | + .name = "query-backup", | |
816 | + .args_type = "", | |
817 | + .mhandler.cmd_new = qmp_marshal_input_query_backup, | |
818 | + }, | |
819 | + | |
820 | +SQMP | |
821 | migrate-set-capabilities | |
822 | ------- | |
823 | ||
824 | -- | |
825 | 1.7.2.5 | |
826 |