[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH v3 09/17] migration/snapshot: Introduce qemu-snapshot tool
From: |
nikita . lapshin |
Subject: |
[PATCH v3 09/17] migration/snapshot: Introduce qemu-snapshot tool |
Date: |
Thu, 16 Jun 2022 13:28:03 +0300 |
From: Nikita Lapshin <nikita.lapshin@openvz.org>
Execution environment, command-line argument parsing, usage/version info etc.
Signed-off-by: Nikita Lapshin <nikita.lapshin@openvz.org>
---
include/qemu-snapshot.h | 65 ++++++
migration/qemu-snapshot.c | 57 +++++
qemu-snapshot.c | 433 ++++++++++++++++++++++++++++++++++++++
3 files changed, 555 insertions(+)
create mode 100644 include/qemu-snapshot.h
create mode 100644 migration/qemu-snapshot.c
create mode 100644 qemu-snapshot.c
diff --git a/include/qemu-snapshot.h b/include/qemu-snapshot.h
new file mode 100644
index 0000000000..8e548e7630
--- /dev/null
+++ b/include/qemu-snapshot.h
@@ -0,0 +1,65 @@
+/*
+ * QEMU External Snapshot Utility
+ *
+ * Copyright Virtuozzo GmbH, 2021
+ *
+ * Authors:
+ * Andrey Gruzdev <andrey.gruzdev@virtuozzo.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * later. See the COPYING file in the top-level directory.
+ */
+
+#ifndef QEMU_SNAPSHOT_H
+#define QEMU_SNAPSHOT_H
+
+/* Invalid offset */
+#define INVALID_OFFSET -1
+/* Maximum byte count for qemu_get_buffer_in_place() */
+#define INPLACE_READ_MAX (32768 - 4096)
+
+/* Backing cluster size */
+#define BDRV_CLUSTER_SIZE (1024 * 1024)
+
+/* Minimum supported target page size */
+#define PAGE_SIZE_MIN 4096
+/*
+ * Maximum supported target page size. The limit is caused by using
+ * QEMUFile and qemu_get_buffer_in_place() on migration channel.
+ * IO_BUF_SIZE is currently 32KB.
+ */
+#define PAGE_SIZE_MAX 16384
+/* RAM slice size for snapshot saving */
+#define SLICE_SIZE PAGE_SIZE_MAX
+/* RAM slice size for snapshot revert */
+#define SLICE_SIZE_REVERT (16 * PAGE_SIZE_MAX)
+
+typedef struct StateInfo {
+ int64_t page_size;
+ int64_t page_mask;
+ int page_bits;
+ int64_t slice_size;
+ int64_t slice_mask;
+ int slice_bits;
+} StateInfo;
+
+typedef struct StateSaveCtx {
+ BlockBackend *blk; /* Block backend */
+
+ StateInfo state_parameters; /* Migration state info*/
+} StateSaveCtx;
+
+typedef struct StateLoadCtx {
+ BlockBackend *blk; /* Block backend */
+
+ StateInfo state_parameters; /* Migration state info*/
+} StateLoadCtx;
+
+void ram_init_state(void);
+void ram_destroy_state(void);
+StateSaveCtx *get_save_context(void);
+StateLoadCtx *get_load_context(void);
+int coroutine_fn save_state_main(StateSaveCtx *s);
+int coroutine_fn load_state_main(StateLoadCtx *s);
+
+#endif /* QEMU_SNAPSHOT_H */
diff --git a/migration/qemu-snapshot.c b/migration/qemu-snapshot.c
new file mode 100644
index 0000000000..f7695e75c7
--- /dev/null
+++ b/migration/qemu-snapshot.c
@@ -0,0 +1,57 @@
+/*
+ * QEMU External Snapshot Utility
+ *
+ * Copyright Virtuozzo GmbH, 2021
+ *
+ * Authors:
+ * Andrey Gruzdev <andrey.gruzdev@virtuozzo.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * later. See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "sysemu/block-backend.h"
+#include "qemu/coroutine.h"
+#include "qemu/cutils.h"
+#include "qemu/bitmap.h"
+#include "qemu/error-report.h"
+#include "io/channel-buffer.h"
+#include "migration/qemu-file-channel.h"
+#include "migration/qemu-file.h"
+#include "migration/savevm.h"
+#include "migration/ram.h"
+#include "qemu-snapshot.h"
+
+/* RAM transfer context */
+typedef struct RAMCtx {
+ int64_t normal_pages; /* Total number of normal pages */
+} RAMCtx;
+
+static RAMCtx ram_ctx;
+
+int coroutine_fn save_state_main(StateSaveCtx *s)
+{
+ /* TODO: implement */
+ return 0;
+}
+
+int coroutine_fn load_state_main(StateLoadCtx *s)
+{
+ /* TODO: implement */
+ return 0;
+}
+
+/* Initialize snapshot RAM state */
+void ram_init_state(void)
+{
+ RAMCtx *ram = &ram_ctx;
+
+ memset(ram, 0, sizeof(ram_ctx));
+}
+
+/* Destroy snapshot RAM state */
+void ram_destroy_state(void)
+{
+ /* TODO: implement */
+}
diff --git a/qemu-snapshot.c b/qemu-snapshot.c
new file mode 100644
index 0000000000..683f1b265a
--- /dev/null
+++ b/qemu-snapshot.c
@@ -0,0 +1,433 @@
+/*
+ * QEMU External Snapshot Utility
+ *
+ * Copyright Virtuozzo GmbH, 2021
+ *
+ * Authors:
+ * Andrey Gruzdev <andrey.gruzdev@virtuozzo.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * later. See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include <getopt.h>
+
+#include "qemu/memalign.h"
+#include "qemu-common.h"
+#include "qemu-version.h"
+#include "qapi/error.h"
+#include "qapi/qmp/qdict.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/block-backend.h"
+#include "qemu/cutils.h"
+#include "qemu/coroutine.h"
+#include "qemu/error-report.h"
+#include "qemu/config-file.h"
+#include "qemu/log.h"
+#include "qemu/option_int.h"
+#include "qemu/main-loop.h"
+#include "trace/control.h"
+#include "io/channel-util.h"
+#include "io/channel-buffer.h"
+#include "migration/qemu-file-channel.h"
+#include "migration/qemu-file.h"
+#include "migration/savevm.h"
+#include "migration/misc.h"
+#include "qemu-snapshot.h"
+
+int64_t page_size;
+int64_t page_mask;
+int page_bits;
+int64_t slice_size;
+int64_t slice_mask;
+int slice_bits;
+
+static QemuOptsList snap_blk_optslist = {
+ .name = "blockdev",
+ .implied_opt_name = "file.filename",
+ .head = QTAILQ_HEAD_INITIALIZER(snap_blk_optslist.head),
+ .desc = {
+ { /*End of the list */ }
+ },
+};
+
+static struct {
+ bool revert; /* Operation is snapshot revert */
+
+ int fd; /* Migration channel fd */
+ int rp_fd; /* Return path fd (for postcopy) */
+
+ const char *blk_optstr; /* Command-line options for vmstate blockdev */
+ QDict *blk_options; /* Blockdev options */
+ int blk_flags; /* Blockdev flags */
+
+ bool postcopy; /* Use postcopy */
+ bool save_vmstate; /* If true tool will get onle vmstate part */
+} params;
+
+static StateSaveCtx state_save_ctx;
+static StateLoadCtx state_load_ctx;
+
+static enum {
+ RUNNING = 0,
+ TERMINATED
+} state;
+
+StateSaveCtx *get_save_context(void)
+{
+ return &state_save_ctx;
+}
+
+StateLoadCtx *get_load_context(void)
+{
+ return &state_load_ctx;
+}
+
+static void init_save_context(void)
+{
+ memset(&state_save_ctx, 0, sizeof(state_save_ctx));
+}
+
+static void destroy_save_context(void)
+{
+ /* TODO: implement */
+}
+
+static void init_load_context(void)
+{
+ memset(&state_load_ctx, 0, sizeof(state_load_ctx));
+}
+
+static void destroy_load_context(void)
+{
+ /* TODO: implement */
+}
+
+static BlockBackend *image_open_opts(const char *optstr, QDict *options,
+ int flags)
+{
+ BlockBackend *blk;
+ Error *local_err = NULL;
+
+ /* Open image and create block backend */
+ blk = blk_new_open(NULL, NULL, options, flags, &local_err);
+ if (!blk) {
+ error_reportf_err(local_err, "Failed to open image '%s': ", optstr);
+ return NULL;
+ }
+
+ blk_set_enable_write_cache(blk, true);
+
+ return blk;
+}
+
+/* Use BH to enter coroutine from the main loop */
+static void enter_co_bh(void *opaque)
+{
+ Coroutine *co = (Coroutine *) opaque;
+ qemu_coroutine_enter(co);
+}
+
+static void coroutine_fn snapshot_save_co(void *opaque)
+{
+ StateSaveCtx *s = get_save_context();
+ int res = -1;
+ init_save_context();
+
+ /* Block backend */
+ s->blk = image_open_opts(params.blk_optstr, params.blk_options,
+ params.blk_flags);
+ if (!s->blk) {
+ goto fail;
+ }
+
+ res = save_state_main(s);
+ if (res) {
+ error_report("Failed to save snapshot: %s", strerror(-res));
+
+fail:
+ destroy_save_context();
+ state = TERMINATED;
+}
+
+static void coroutine_fn snapshot_load_co(void *opaque)
+{
+ StateLoadCtx *s = get_load_context();
+ int res = -1;
+
+ init_load_context();
+
+ /* Block backend */
+ s->blk = image_open_opts(params.blk_optstr, params.blk_options,
+ params.blk_flags);
+ if (!s->blk) {
+ goto fail;
+ }
+
+ res = load_state_main(s);
+ if (res) {
+ error_report("Failed to load snapshot: %s", strerror(-res));
+ }
+
+fail:
+ destroy_load_context();
+ state = TERMINATED;
+}
+
+static void usage(const char *name)
+{
+ printf(
+ "Usage: %s [options] <image-blockspec>\n"
+ "QEMU External Snapshot Utility\n"
+ "\n"
+ "'image-blockspec' is a block device specification for vmstate image\n"
+ "\n"
+ " -h, --help display this help and exit\n"
+ " -V, --version output version information and exit\n"
+ "\n"
+ "Options:\n"
+ " -T, --trace [[enable=]<pattern>][,events=<file>][,file=<file>]\n"
+ " specify tracing options\n"
+ " -r, --revert revert to snapshot\n"
+ " --uri=fd:<fd> specify migration fd\n"
+ " --page-size=<size> specify target page size\n"
+ " --postcopy load ram in postcopy mode\n"
+ "\n"
+ QEMU_HELP_BOTTOM "\n", name);
+}
+
+static void version(const char *name)
+{
+ printf(
+ "%s " QEMU_FULL_VERSION "\n"
+ "Written by Andrey Gruzdev.\n"
+ "\n"
+ QEMU_COPYRIGHT "\n"
+ "This is free software; see the source for copying conditions. There
is NO\n"
+ "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE.\n",
+ name);
+}
+
+enum {
+ OPTION_PAGE_SIZE = 256,
+ OPTION_POSTCOPY,
+ OPTION_URI,
+};
+
+static void process_options(int argc, char *argv[], StateInfo *si)
+{
+ static const char *s_opt = "rhVT:";
+ static const struct option l_opt[] = {
+ { "page-size", required_argument, NULL, OPTION_PAGE_SIZE },
+ { "postcopy", no_argument, NULL, OPTION_POSTCOPY },
+ { "uri", required_argument, NULL, OPTION_URI },
+ { "revert", no_argument, NULL, 'r' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "trace", required_argument, NULL, 'T' },
+ { "save-vmstate", no_argument, NULL, 'S' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ bool has_page_size = false;
+ bool has_uri = false;
+
+ long target_page_size = qemu_real_host_page_size;
+ int uri_fd = -1;
+ bool revert = false;
+ bool postcopy = false;
+ const char *blk_optstr;
+ QemuOpts *blk_opts;
+ QDict *blk_options;
+ int c;
+
+ while ((c = getopt_long(argc, argv, s_opt, l_opt, NULL)) != -1) {
+ switch (c) {
+ case '?':
+ exit(EXIT_FAILURE);
+
+ case 'h':
+ usage(argv[0]);
+ exit(EXIT_SUCCESS);
+
+ case 'V':
+ version(argv[0]);
+ exit(EXIT_SUCCESS);
+
+ case 'T':
+ trace_opt_parse(optarg);
+ break;
+
+ case 'r':
+ if (revert) {
+ error_report("-r and --revert can only be specified once");
+ exit(EXIT_FAILURE);
+ }
+ revert = true;
+
+ break;
+
+ case 'S':
+ params.save_vmstate = true;
+
+ break;
+
+ case OPTION_POSTCOPY:
+ {
+ if (postcopy) {
+ error_report("--postcopy can only be specified once");
+ exit(EXIT_FAILURE);
+ }
+ postcopy = true;
+
+ break;
+ }
+
+ case OPTION_PAGE_SIZE:
+ {
+ const char *r;
+
+ if (has_page_size) {
+ error_report("--page-size can only be specified once");
+ exit(EXIT_FAILURE);
+ }
+ has_page_size = true;
+
+ qemu_strtol(optarg, &r, 0, &target_page_size);
+ if (*r != '\0' ||
+ (target_page_size & (target_page_size - 1)) != 0 ||
+ target_page_size < PAGE_SIZE_MIN ||
+ target_page_size > PAGE_SIZE_MAX) {
+ error_report("Invalid argument to --page-size");
+ exit(EXIT_FAILURE);
+ }
+
+ break;
+ }
+
+ case OPTION_URI:
+ {
+ const char *p;
+
+ if (has_uri) {
+ error_report("--uri can only be specified once");
+ exit(EXIT_FAILURE);
+ }
+ has_uri = true;
+
+ /* Only "--uri=fd:<fd>" is currently supported */
+ if (strstart(optarg, "fd:", &p)) {
+ const char *r;
+ long fd;
+
+ qemu_strtol(p, &r, 10, &fd);
+ if (*r != '\0' || fd <= STDERR_FILENO) {
+ error_report("Invalid FD value");
+ exit(EXIT_FAILURE);
+ }
+
+ uri_fd = qemu_dup_flags(fd, O_CLOEXEC);
+ if (uri_fd < 0) {
+ error_report("Could not dup FD %ld", fd);
+ exit(EXIT_FAILURE);
+ }
+
+ /* Close original fd */
+ close(fd);
+ } else {
+ error_report("Invalid argument to --uri");
+ exit(EXIT_FAILURE);
+ }
+
+ break;
+ }
+
+ default:
+ g_assert_not_reached();
+ }
+ }
+
+ if ((argc - optind) != 1) {
+ error_report("Invalid number of arguments");
+ exit(EXIT_FAILURE);
+ }
+
+ blk_optstr = argv[optind];
+
+ blk_opts = qemu_opts_parse_noisily(&snap_blk_optslist, blk_optstr, true);
+ if (!blk_opts) {
+ exit(EXIT_FAILURE);
+ }
+ blk_options = qemu_opts_to_qdict(blk_opts, NULL);
+ qemu_opts_reset(&snap_blk_optslist);
+
+ /* Enforced block layer options */
+ qdict_put_str(blk_options, "driver", "qcow2");
+ qdict_put_null(blk_options, "backing");
+ qdict_put_str(blk_options, "overlap-check", "none");
+ qdict_put_str(blk_options, "auto-read-only", "off");
+ qdict_put_str(blk_options, "detect-zeroes", "off");
+ qdict_put_str(blk_options, "lazy-refcounts", "on");
+ qdict_put_str(blk_options, "file.auto-read-only", "off");
+ qdict_put_str(blk_options, "file.detect-zeroes", "off");
+
+ params.revert = revert;
+
+ if (uri_fd != -1) {
+ params.fd = params.rp_fd = uri_fd;
+ } else {
+ params.fd = revert ? STDOUT_FILENO : STDIN_FILENO;
+ params.rp_fd = revert ? STDIN_FILENO : -1;
+ }
+ params.blk_optstr = blk_optstr;
+ params.blk_options = blk_options;
+ params.blk_flags = revert ? 0 : BDRV_O_RDWR;
+ params.postcopy = postcopy;
+
+ si->page_size = target_page_size;
+ si->page_mask = ~(target_page_size - 1);
+ si->page_bits = ctz64(target_page_size);
+ si->slice_size = SLICE_SIZE;
+ si->slice_mask = ~(si->slice_size - 1);
+ si->slice_bits = ctz64(si->slice_size);
+}
+
+int main(int argc, char **argv)
+{
+ Coroutine *co;
+ StateInfo state_info;
+
+ os_setup_early_signal_handling();
+ os_setup_signal_handling();
+ error_init(argv[0]);
+ qemu_init_exec_dir(argv[0]);
+ module_call_init(MODULE_INIT_QOM);
+ qemu_init_main_loop(&error_fatal);
+ qemu_init_subsystems();
+
+ migration_object_init();
+ qemu_add_opts(&qemu_trace_opts);
+ process_options(argc, argv, &state_info);
+
+ if (!trace_init_backends()) {
+ exit(EXIT_FAILURE);
+ }
+ trace_init_file();
+ qemu_set_log(LOG_TRACE);
+
+ ram_init_state();
+
+ if (params.revert) {
+ co = qemu_coroutine_create(snapshot_load_co, &state_info);
+ } else {
+ co = qemu_coroutine_create(snapshot_save_co, &state_info);
+ }
+ aio_bh_schedule_oneshot(qemu_get_aio_context(), enter_co_bh, co);
+
+ do {
+ main_loop_wait(false);
+ } while (state != TERMINATED);
+
+ exit(EXIT_SUCCESS);
+}
--
2.31.1
- [PATCH v3 05/17] migration: Add block part of migration stream, (continued)
- [PATCH v3 05/17] migration: Add block part of migration stream, nikita . lapshin, 2022/06/16
- [PATCH v3 06/17] migration: Add RAM part of migration stream, nikita . lapshin, 2022/06/16
- [PATCH v3 07/17] migration: analyze-migration script changed, nikita . lapshin, 2022/06/16
- [PATCH v3 08/17] migration: Test for RAM and vmstate parts, nikita . lapshin, 2022/06/16
- [PATCH v3 11/17] migration/qemu-file: Fix qemu_ftell() for non-writable file, nikita . lapshin, 2022/06/16
- [PATCH v3 13/17] migration/snapshot: Block layer support in qemu-snapshot, nikita . lapshin, 2022/06/16
- [PATCH v3 14/17] migration/snpashot: Implement API for RAMBlock, nikita . lapshin, 2022/06/16
- [PATCH v3 04/17] migration: Add dirty-bitmaps part of migration stream, nikita . lapshin, 2022/06/16
- [PATCH v3 09/17] migration/snapshot: Introduce qemu-snapshot tool,
nikita . lapshin <=
- [PATCH v3 10/17] migration/snapshot: Build changes for qemu-snapshot-tool, nikita . lapshin, 2022/06/16
- [PATCH v3 12/17] migration/snapshot: Move RAM_SAVE_FLAG_xxx defines to migration/ram.h, nikita . lapshin, 2022/06/16
- [PATCH v3 16/17] migration/snapshot: Precopy load implemented, nikita . lapshin, 2022/06/16
- [PATCH v3 15/17] migration/snapshot: Save part implement, nikita . lapshin, 2022/06/16
- [PATCH v3 17/17] migration/snapshot: Postcopy load implemented, nikita . lapshin, 2022/06/16