diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2016-10-31 13:06:38 +0000 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2016-10-31 13:06:38 +0000 |
commit | eab9e9629c8f3c0ce87f7bcad82176f55029a640 (patch) | |
tree | e0f7f1579137a38c611b3988d8d5e339076aace6 /migration | |
parent | 5ff06787d4921579192e1becfedb9b1954f9bc0d (diff) | |
parent | a4cc318e15955b4b9c7231c37128d4e7466d4307 (diff) |
Merge remote-tracking branch 'remotes/amit-migration/tags/migration-for-2.8' into staging
Migration bits from the COLO project
# gpg: Signature made Sun 30 Oct 2016 10:39:55 GMT
# gpg: using RSA key 0xEB0B4DFC657EF670
# gpg: Good signature from "Amit Shah <amit@amitshah.net>"
# gpg: aka "Amit Shah <amit@kernel.org>"
# gpg: aka "Amit Shah <amitshah@gmx.net>"
# Primary key fingerprint: 48CA 3722 5FE7 F4A8 B337 2735 1E9A 3B5F 8540 83B6
# Subkey fingerprint: CC63 D332 AB8F 4617 4529 6534 EB0B 4DFC 657E F670
* remotes/amit-migration/tags/migration-for-2.8:
MAINTAINERS: Add maintainer for COLO framework related files
configure: Support enable/disable COLO feature
docs: Add documentation for COLO feature
COLO: Implement failover work for secondary VM
COLO: Implement the process of failover for primary VM
COLO: Introduce state to record failover process
COLO: Add 'x-colo-lost-heartbeat' command to trigger failover
COLO: Synchronize PVM's state to SVM periodically
COLO: Add checkpoint-delay parameter for migrate-set-parameters
COLO: Load VMState into QIOChannelBuffer before restore it
COLO: Send PVM state to secondary side when do checkpoint
COLO: Add a new RunState RUN_STATE_COLO
COLO: Introduce checkpointing protocol
COLO: Establish a new communicating path for COLO
migration: Switch to COLO process after finishing loadvm
migration: Enter into COLO mode after migration if COLO is enabled
COLO: migrate COLO related info to secondary node
migration: Introduce capability 'x-colo' to migration
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'migration')
-rw-r--r-- | migration/Makefile.objs | 2 | ||||
-rw-r--r-- | migration/colo-comm.c | 72 | ||||
-rw-r--r-- | migration/colo-failover.c | 83 | ||||
-rw-r--r-- | migration/colo.c | 529 | ||||
-rw-r--r-- | migration/migration.c | 84 | ||||
-rw-r--r-- | migration/ram.c | 37 | ||||
-rw-r--r-- | migration/trace-events | 6 |
7 files changed, 798 insertions, 15 deletions
diff --git a/migration/Makefile.objs b/migration/Makefile.objs index 30ad945918..3f3e237142 100644 --- a/migration/Makefile.objs +++ b/migration/Makefile.objs @@ -1,5 +1,7 @@ common-obj-y += migration.o socket.o fd.o exec.o common-obj-y += tls.o +common-obj-y += colo-comm.o +common-obj-$(CONFIG_COLO) += colo.o colo-failover.o common-obj-y += vmstate.o common-obj-y += qemu-file.o common-obj-y += qemu-file-channel.o diff --git a/migration/colo-comm.c b/migration/colo-comm.c new file mode 100644 index 0000000000..20b60ec384 --- /dev/null +++ b/migration/colo-comm.c @@ -0,0 +1,72 @@ +/* + * COarse-grain LOck-stepping Virtual Machines for Non-stop Service (COLO) + * (a.k.a. Fault Tolerance or Continuous Replication) + * + * Copyright (c) 2016 HUAWEI TECHNOLOGIES CO., LTD. + * Copyright (c) 2016 FUJITSU LIMITED + * Copyright (c) 2016 Intel Corporation + * + * 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 <migration/colo.h> +#include "trace.h" + +typedef struct { + bool colo_requested; +} COLOInfo; + +static COLOInfo colo_info; + +COLOMode get_colo_mode(void) +{ + if (migration_in_colo_state()) { + return COLO_MODE_PRIMARY; + } else if (migration_incoming_in_colo_state()) { + return COLO_MODE_SECONDARY; + } else { + return COLO_MODE_UNKNOWN; + } +} + +static void colo_info_pre_save(void *opaque) +{ + COLOInfo *s = opaque; + + s->colo_requested = migrate_colo_enabled(); +} + +static bool colo_info_need(void *opaque) +{ + return migrate_colo_enabled(); +} + +static const VMStateDescription colo_state = { + .name = "COLOState", + .version_id = 1, + .minimum_version_id = 1, + .pre_save = colo_info_pre_save, + .needed = colo_info_need, + .fields = (VMStateField[]) { + VMSTATE_BOOL(colo_requested, COLOInfo), + VMSTATE_END_OF_LIST() + }, +}; + +void colo_info_init(void) +{ + vmstate_register(NULL, 0, &colo_state, &colo_info); +} + +bool migration_incoming_enable_colo(void) +{ + return colo_info.colo_requested; +} + +void migration_incoming_exit_colo(void) +{ + colo_info.colo_requested = false; +} diff --git a/migration/colo-failover.c b/migration/colo-failover.c new file mode 100644 index 0000000000..cc229f5ab1 --- /dev/null +++ b/migration/colo-failover.c @@ -0,0 +1,83 @@ +/* + * COarse-grain LOck-stepping Virtual Machines for Non-stop Service (COLO) + * (a.k.a. Fault Tolerance or Continuous Replication) + * + * Copyright (c) 2016 HUAWEI TECHNOLOGIES CO., LTD. + * Copyright (c) 2016 FUJITSU LIMITED + * Copyright (c) 2016 Intel Corporation + * + * 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 "migration/colo.h" +#include "migration/failover.h" +#include "qmp-commands.h" +#include "qapi/qmp/qerror.h" +#include "qemu/error-report.h" +#include "trace.h" + +static QEMUBH *failover_bh; +static FailoverStatus failover_state; + +static void colo_failover_bh(void *opaque) +{ + int old_state; + + qemu_bh_delete(failover_bh); + failover_bh = NULL; + + old_state = failover_set_state(FAILOVER_STATUS_REQUIRE, + FAILOVER_STATUS_ACTIVE); + if (old_state != FAILOVER_STATUS_REQUIRE) { + error_report("Unknown error for failover, old_state = %s", + FailoverStatus_lookup[old_state]); + return; + } + + colo_do_failover(NULL); +} + +void failover_request_active(Error **errp) +{ + if (failover_set_state(FAILOVER_STATUS_NONE, + FAILOVER_STATUS_REQUIRE) != FAILOVER_STATUS_NONE) { + error_setg(errp, "COLO failover is already actived"); + return; + } + failover_bh = qemu_bh_new(colo_failover_bh, NULL); + qemu_bh_schedule(failover_bh); +} + +void failover_init_state(void) +{ + failover_state = FAILOVER_STATUS_NONE; +} + +FailoverStatus failover_set_state(FailoverStatus old_state, + FailoverStatus new_state) +{ + FailoverStatus old; + + old = atomic_cmpxchg(&failover_state, old_state, new_state); + if (old == old_state) { + trace_colo_failover_set_state(FailoverStatus_lookup[new_state]); + } + return old; +} + +FailoverStatus failover_get_state(void) +{ + return atomic_read(&failover_state); +} + +void qmp_x_colo_lost_heartbeat(Error **errp) +{ + if (get_colo_mode() == COLO_MODE_UNKNOWN) { + error_setg(errp, QERR_FEATURE_DISABLED, "colo"); + return; + } + + failover_request_active(errp); +} diff --git a/migration/colo.c b/migration/colo.c new file mode 100644 index 0000000000..e7224b8a0a --- /dev/null +++ b/migration/colo.c @@ -0,0 +1,529 @@ +/* + * COarse-grain LOck-stepping Virtual Machines for Non-stop Service (COLO) + * (a.k.a. Fault Tolerance or Continuous Replication) + * + * Copyright (c) 2016 HUAWEI TECHNOLOGIES CO., LTD. + * Copyright (c) 2016 FUJITSU LIMITED + * Copyright (c) 2016 Intel Corporation + * + * 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 "qemu/timer.h" +#include "sysemu/sysemu.h" +#include "migration/colo.h" +#include "io/channel-buffer.h" +#include "trace.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include "migration/failover.h" + +#define COLO_BUFFER_BASE_SIZE (4 * 1024 * 1024) + +bool colo_supported(void) +{ + return true; +} + +bool migration_in_colo_state(void) +{ + MigrationState *s = migrate_get_current(); + + return (s->state == MIGRATION_STATUS_COLO); +} + +bool migration_incoming_in_colo_state(void) +{ + MigrationIncomingState *mis = migration_incoming_get_current(); + + return mis && (mis->state == MIGRATION_STATUS_COLO); +} + +static bool colo_runstate_is_stopped(void) +{ + return runstate_check(RUN_STATE_COLO) || !runstate_is_running(); +} + +static void secondary_vm_do_failover(void) +{ + int old_state; + MigrationIncomingState *mis = migration_incoming_get_current(); + + migrate_set_state(&mis->state, MIGRATION_STATUS_COLO, + MIGRATION_STATUS_COMPLETED); + + if (!autostart) { + error_report("\"-S\" qemu option will be ignored in secondary side"); + /* recover runstate to normal migration finish state */ + autostart = true; + } + + old_state = failover_set_state(FAILOVER_STATUS_ACTIVE, + FAILOVER_STATUS_COMPLETED); + if (old_state != FAILOVER_STATUS_ACTIVE) { + error_report("Incorrect state (%s) while doing failover for " + "secondary VM", FailoverStatus_lookup[old_state]); + return; + } + /* For Secondary VM, jump to incoming co */ + if (mis->migration_incoming_co) { + qemu_coroutine_enter(mis->migration_incoming_co); + } +} + +static void primary_vm_do_failover(void) +{ + MigrationState *s = migrate_get_current(); + int old_state; + + migrate_set_state(&s->state, MIGRATION_STATUS_COLO, + MIGRATION_STATUS_COMPLETED); + + old_state = failover_set_state(FAILOVER_STATUS_ACTIVE, + FAILOVER_STATUS_COMPLETED); + if (old_state != FAILOVER_STATUS_ACTIVE) { + error_report("Incorrect state (%s) while doing failover for Primary VM", + FailoverStatus_lookup[old_state]); + return; + } +} + +void colo_do_failover(MigrationState *s) +{ + /* Make sure VM stopped while failover happened. */ + if (!colo_runstate_is_stopped()) { + vm_stop_force_state(RUN_STATE_COLO); + } + + if (get_colo_mode() == COLO_MODE_PRIMARY) { + primary_vm_do_failover(); + } else { + secondary_vm_do_failover(); + } +} + +static void colo_send_message(QEMUFile *f, COLOMessage msg, + Error **errp) +{ + int ret; + + if (msg >= COLO_MESSAGE__MAX) { + error_setg(errp, "%s: Invalid message", __func__); + return; + } + qemu_put_be32(f, msg); + qemu_fflush(f); + + ret = qemu_file_get_error(f); + if (ret < 0) { + error_setg_errno(errp, -ret, "Can't send COLO message"); + } + trace_colo_send_message(COLOMessage_lookup[msg]); +} + +static void colo_send_message_value(QEMUFile *f, COLOMessage msg, + uint64_t value, Error **errp) +{ + Error *local_err = NULL; + int ret; + + colo_send_message(f, msg, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + qemu_put_be64(f, value); + qemu_fflush(f); + + ret = qemu_file_get_error(f); + if (ret < 0) { + error_setg_errno(errp, -ret, "Failed to send value for message:%s", + COLOMessage_lookup[msg]); + } +} + +static COLOMessage colo_receive_message(QEMUFile *f, Error **errp) +{ + COLOMessage msg; + int ret; + + msg = qemu_get_be32(f); + ret = qemu_file_get_error(f); + if (ret < 0) { + error_setg_errno(errp, -ret, "Can't receive COLO message"); + return msg; + } + if (msg >= COLO_MESSAGE__MAX) { + error_setg(errp, "%s: Invalid message", __func__); + return msg; + } + trace_colo_receive_message(COLOMessage_lookup[msg]); + return msg; +} + +static void colo_receive_check_message(QEMUFile *f, COLOMessage expect_msg, + Error **errp) +{ + COLOMessage msg; + Error *local_err = NULL; + + msg = colo_receive_message(f, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + if (msg != expect_msg) { + error_setg(errp, "Unexpected COLO message %d, expected %d", + msg, expect_msg); + } +} + +static uint64_t colo_receive_message_value(QEMUFile *f, uint32_t expect_msg, + Error **errp) +{ + Error *local_err = NULL; + uint64_t value; + int ret; + + colo_receive_check_message(f, expect_msg, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return 0; + } + + value = qemu_get_be64(f); + ret = qemu_file_get_error(f); + if (ret < 0) { + error_setg_errno(errp, -ret, "Failed to get value for COLO message: %s", + COLOMessage_lookup[expect_msg]); + } + return value; +} + +static int colo_do_checkpoint_transaction(MigrationState *s, + QIOChannelBuffer *bioc, + QEMUFile *fb) +{ + Error *local_err = NULL; + int ret = -1; + + colo_send_message(s->to_dst_file, COLO_MESSAGE_CHECKPOINT_REQUEST, + &local_err); + if (local_err) { + goto out; + } + + colo_receive_check_message(s->rp_state.from_dst_file, + COLO_MESSAGE_CHECKPOINT_REPLY, &local_err); + if (local_err) { + goto out; + } + /* Reset channel-buffer directly */ + qio_channel_io_seek(QIO_CHANNEL(bioc), 0, 0, NULL); + bioc->usage = 0; + + qemu_mutex_lock_iothread(); + if (failover_get_state() != FAILOVER_STATUS_NONE) { + qemu_mutex_unlock_iothread(); + goto out; + } + vm_stop_force_state(RUN_STATE_COLO); + qemu_mutex_unlock_iothread(); + trace_colo_vm_state_change("run", "stop"); + /* + * Failover request bh could be called after vm_stop_force_state(), + * So we need check failover_request_is_active() again. + */ + if (failover_get_state() != FAILOVER_STATUS_NONE) { + goto out; + } + + /* Disable block migration */ + s->params.blk = 0; + s->params.shared = 0; + qemu_savevm_state_header(fb); + qemu_savevm_state_begin(fb, &s->params); + qemu_mutex_lock_iothread(); + qemu_savevm_state_complete_precopy(fb, false); + qemu_mutex_unlock_iothread(); + + qemu_fflush(fb); + + colo_send_message(s->to_dst_file, COLO_MESSAGE_VMSTATE_SEND, &local_err); + if (local_err) { + goto out; + } + /* + * We need the size of the VMstate data in Secondary side, + * With which we can decide how much data should be read. + */ + colo_send_message_value(s->to_dst_file, COLO_MESSAGE_VMSTATE_SIZE, + bioc->usage, &local_err); + if (local_err) { + goto out; + } + + qemu_put_buffer(s->to_dst_file, bioc->data, bioc->usage); + qemu_fflush(s->to_dst_file); + ret = qemu_file_get_error(s->to_dst_file); + if (ret < 0) { + goto out; + } + + colo_receive_check_message(s->rp_state.from_dst_file, + COLO_MESSAGE_VMSTATE_RECEIVED, &local_err); + if (local_err) { + goto out; + } + + colo_receive_check_message(s->rp_state.from_dst_file, + COLO_MESSAGE_VMSTATE_LOADED, &local_err); + if (local_err) { + goto out; + } + + ret = 0; + + qemu_mutex_lock_iothread(); + vm_start(); + qemu_mutex_unlock_iothread(); + trace_colo_vm_state_change("stop", "run"); + +out: + if (local_err) { + error_report_err(local_err); + } + return ret; +} + +static void colo_process_checkpoint(MigrationState *s) +{ + QIOChannelBuffer *bioc; + QEMUFile *fb = NULL; + int64_t current_time, checkpoint_time = qemu_clock_get_ms(QEMU_CLOCK_HOST); + Error *local_err = NULL; + int ret; + + failover_init_state(); + + s->rp_state.from_dst_file = qemu_file_get_return_path(s->to_dst_file); + if (!s->rp_state.from_dst_file) { + error_report("Open QEMUFile from_dst_file failed"); + goto out; + } + + /* + * Wait for Secondary finish loading VM states and enter COLO + * restore. + */ + colo_receive_check_message(s->rp_state.from_dst_file, + COLO_MESSAGE_CHECKPOINT_READY, &local_err); + if (local_err) { + goto out; + } + bioc = qio_channel_buffer_new(COLO_BUFFER_BASE_SIZE); + fb = qemu_fopen_channel_output(QIO_CHANNEL(bioc)); + object_unref(OBJECT(bioc)); + + qemu_mutex_lock_iothread(); + vm_start(); + qemu_mutex_unlock_iothread(); + trace_colo_vm_state_change("stop", "run"); + + while (s->state == MIGRATION_STATUS_COLO) { + if (failover_get_state() != FAILOVER_STATUS_NONE) { + error_report("failover request"); + goto out; + } + + current_time = qemu_clock_get_ms(QEMU_CLOCK_HOST); + if (current_time - checkpoint_time < + s->parameters.x_checkpoint_delay) { + int64_t delay_ms; + + delay_ms = s->parameters.x_checkpoint_delay - + (current_time - checkpoint_time); + g_usleep(delay_ms * 1000); + } + ret = colo_do_checkpoint_transaction(s, bioc, fb); + if (ret < 0) { + goto out; + } + checkpoint_time = qemu_clock_get_ms(QEMU_CLOCK_HOST); + } + +out: + /* Throw the unreported error message after exited from loop */ + if (local_err) { + error_report_err(local_err); + } + + if (fb) { + qemu_fclose(fb); + } + + if (s->rp_state.from_dst_file) { + qemu_fclose(s->rp_state.from_dst_file); + } +} + +void migrate_start_colo_process(MigrationState *s) +{ + qemu_mutex_unlock_iothread(); + migrate_set_state(&s->state, MIGRATION_STATUS_ACTIVE, + MIGRATION_STATUS_COLO); + colo_process_checkpoint(s); + qemu_mutex_lock_iothread(); +} + +static void colo_wait_handle_message(QEMUFile *f, int *checkpoint_request, + Error **errp) +{ + COLOMessage msg; + Error *local_err = NULL; + + msg = colo_receive_message(f, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + + switch (msg) { + case COLO_MESSAGE_CHECKPOINT_REQUEST: + *checkpoint_request = 1; + break; + default: + *checkpoint_request = 0; + error_setg(errp, "Got unknown COLO message: %d", msg); + break; + } +} + +void *colo_process_incoming_thread(void *opaque) +{ + MigrationIncomingState *mis = opaque; + QEMUFile *fb = NULL; + QIOChannelBuffer *bioc = NULL; /* Cache incoming device state */ + uint64_t total_size; + uint64_t value; + Error *local_err = NULL; + + migrate_set_state(&mis->state, MIGRATION_STATUS_ACTIVE, + MIGRATION_STATUS_COLO); + + failover_init_state(); + + mis->to_src_file = qemu_file_get_return_path(mis->from_src_file); + if (!mis->to_src_file) { + error_report("COLO incoming thread: Open QEMUFile to_src_file failed"); + goto out; + } + /* + * Note: the communication between Primary side and Secondary side + * should be sequential, we set the fd to unblocked in migration incoming + * coroutine, and here we are in the COLO incoming thread, so it is ok to + * set the fd back to blocked. + */ + qemu_file_set_blocking(mis->from_src_file, true); + + bioc = qio_channel_buffer_new(COLO_BUFFER_BASE_SIZE); + fb = qemu_fopen_channel_input(QIO_CHANNEL(bioc)); + object_unref(OBJECT(bioc)); + + colo_send_message(mis->to_src_file, COLO_MESSAGE_CHECKPOINT_READY, + &local_err); + if (local_err) { + goto out; + } + + while (mis->state == MIGRATION_STATUS_COLO) { + int request; + + colo_wait_handle_message(mis->from_src_file, &request, &local_err); + if (local_err) { + goto out; + } + assert(request); + if (failover_get_state() != FAILOVER_STATUS_NONE) { + error_report("failover request"); + goto out; + } + + /* FIXME: This is unnecessary for periodic checkpoint mode */ + colo_send_message(mis->to_src_file, COLO_MESSAGE_CHECKPOINT_REPLY, + &local_err); + if (local_err) { + goto out; + } + + colo_receive_check_message(mis->from_src_file, + COLO_MESSAGE_VMSTATE_SEND, &local_err); + if (local_err) { + goto out; + } + + value = colo_receive_message_value(mis->from_src_file, + COLO_MESSAGE_VMSTATE_SIZE, &local_err); + if (local_err) { + goto out; + } + + /* + * Read VM device state data into channel buffer, + * It's better to re-use the memory allocated. + * Here we need to handle the channel buffer directly. + */ + if (value > bioc->capacity) { + bioc->capacity = value; + bioc->data = g_realloc(bioc->data, bioc->capacity); + } + total_size = qemu_get_buffer(mis->from_src_file, bioc->data, value); + if (total_size != value) { + error_report("Got %" PRIu64 " VMState data, less than expected" + " %" PRIu64, total_size, value); + goto out; + } + bioc->usage = total_size; + qio_channel_io_seek(QIO_CHANNEL(bioc), 0, 0, NULL); + + colo_send_message(mis->to_src_file, COLO_MESSAGE_VMSTATE_RECEIVED, + &local_err); + if (local_err) { + goto out; + } + + qemu_mutex_lock_iothread(); + qemu_system_reset(VMRESET_SILENT); + if (qemu_loadvm_state(fb) < 0) { + error_report("COLO: loadvm failed"); + qemu_mutex_unlock_iothread(); + goto out; + } + qemu_mutex_unlock_iothread(); + + colo_send_message(mis->to_src_file, COLO_MESSAGE_VMSTATE_LOADED, + &local_err); + if (local_err) { + goto out; + } + } + +out: + /* Throw the unreported error message after exited from loop */ + if (local_err) { + error_report_err(local_err); + } + + if (fb) { + qemu_fclose(fb); + } + + if (mis->to_src_file) { + qemu_fclose(mis->to_src_file); + } + migration_incoming_exit_colo(); + + return NULL; +} diff --git a/migration/migration.c b/migration/migration.c index 4084eb6f1b..e331f28382 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -36,6 +36,7 @@ #include "exec/address-spaces.h" #include "io/channel-buffer.h" #include "io/channel-tls.h" +#include "migration/colo.h" #define MAX_THROTTLE (32 << 20) /* Migration transfer speed throttling */ @@ -62,6 +63,11 @@ /* Migration XBZRLE default cache size */ #define DEFAULT_MIGRATE_CACHE_SIZE (64 * 1024 * 1024) +/* The delay time (in ms) between two COLO checkpoints + * Note: Please change this default value to 10000 when we support hybrid mode. + */ +#define DEFAULT_MIGRATE_X_CHECKPOINT_DELAY 200 + static NotifierList migration_state_notifiers = NOTIFIER_LIST_INITIALIZER(migration_state_notifiers); @@ -94,6 +100,7 @@ MigrationState *migrate_get_current(void) .cpu_throttle_increment = DEFAULT_MIGRATE_CPU_THROTTLE_INCREMENT, .max_bandwidth = MAX_THROTTLE, .downtime_limit = DEFAULT_MIGRATE_SET_DOWNTIME, + .x_checkpoint_delay = DEFAULT_MIGRATE_X_CHECKPOINT_DELAY, }, }; @@ -406,6 +413,18 @@ static void process_incoming_migration_co(void *opaque) /* Else if something went wrong then just fall out of the normal exit */ } + /* we get COLO info, and know if we are in COLO mode */ + if (!ret && migration_incoming_enable_colo()) { + mis->migration_incoming_co = qemu_coroutine_self(); + qemu_thread_create(&mis->colo_incoming_thread, "COLO incoming", + colo_process_incoming_thread, mis, QEMU_THREAD_JOINABLE); + mis->have_colo_incoming_thread = true; + qemu_coroutine_yield(); + + /* Wait checkpoint incoming thread exit before free resource */ + qemu_thread_join(&mis->colo_incoming_thread); + } + qemu_fclose(f); free_xbzrle_decoded_buf(); @@ -531,6 +550,9 @@ MigrationCapabilityStatusList *qmp_query_migrate_capabilities(Error **errp) caps = NULL; /* silence compiler warning */ for (i = 0; i < MIGRATION_CAPABILITY__MAX; i++) { + if (i == MIGRATION_CAPABILITY_X_COLO && !colo_supported()) { + continue; + } if (head == NULL) { head = g_malloc0(sizeof(*caps)); caps = head; @@ -571,6 +593,7 @@ MigrationParameters *qmp_query_migrate_parameters(Error **errp) params->max_bandwidth = s->parameters.max_bandwidth; params->has_downtime_limit = true; params->downtime_limit = s->parameters.downtime_limit; + params->x_checkpoint_delay = s->parameters.x_checkpoint_delay; return params; } @@ -691,6 +714,10 @@ MigrationInfo *qmp_query_migrate(Error **errp) get_xbzrle_cache_stats(info); break; + case MIGRATION_STATUS_COLO: + info->has_status = true; + /* TODO: display COLO specific information (checkpoint info etc.) */ + break; case MIGRATION_STATUS_COMPLETED: get_xbzrle_cache_stats(info); @@ -733,6 +760,14 @@ void qmp_migrate_set_capabilities(MigrationCapabilityStatusList *params, } for (cap = params; cap; cap = cap->next) { + if (cap->value->capability == MIGRATION_CAPABILITY_X_COLO) { + if (!colo_supported()) { + error_setg(errp, "COLO is not currently supported, please" + " configure with --enable-colo option in order to" + " support COLO feature"); + continue; + } + } s->enabled_capabilities[cap->value->capability] = cap->value->state; } @@ -817,6 +852,11 @@ void qmp_migrate_set_parameters(MigrationParameters *params, Error **errp) "an integer in the range of 0 to 2000000 milliseconds"); return; } + if (params->has_x_checkpoint_delay && (params->x_checkpoint_delay < 0)) { + error_setg(errp, QERR_INVALID_PARAMETER_VALUE, + "x_checkpoint_delay", + "is invalid, it should be positive"); + } if (params->has_compress_level) { s->parameters.compress_level = params->compress_level; @@ -851,6 +891,10 @@ void qmp_migrate_set_parameters(MigrationParameters *params, Error **errp) if (params->has_downtime_limit) { s->parameters.downtime_limit = params->downtime_limit; } + + if (params->has_x_checkpoint_delay) { + s->parameters.x_checkpoint_delay = params->x_checkpoint_delay; + } } @@ -1101,7 +1145,8 @@ void qmp_migrate(const char *uri, bool has_blk, bool blk, params.shared = has_inc && inc; if (migration_is_setup_or_active(s->state) || - s->state == MIGRATION_STATUS_CANCELLING) { + s->state == MIGRATION_STATUS_CANCELLING || + s->state == MIGRATION_STATUS_COLO) { error_setg(errp, QERR_MIGRATION_ACTIVE); return; } @@ -1649,7 +1694,11 @@ static void migration_completion(MigrationState *s, int current_active_state, if (!ret) { ret = vm_stop_force_state(RUN_STATE_FINISH_MIGRATE); - if (ret >= 0) { + /* + * Don't mark the image with BDRV_O_INACTIVE flag if + * we will go into COLO stage later. + */ + if (ret >= 0 && !migrate_colo_enabled()) { ret = bdrv_inactivate_all(); } if (ret >= 0) { @@ -1691,8 +1740,11 @@ static void migration_completion(MigrationState *s, int current_active_state, goto fail_invalidate; } - migrate_set_state(&s->state, current_active_state, - MIGRATION_STATUS_COMPLETED); + if (!migrate_colo_enabled()) { + migrate_set_state(&s->state, current_active_state, + MIGRATION_STATUS_COMPLETED); + } + return; fail_invalidate: @@ -1713,6 +1765,12 @@ fail: MIGRATION_STATUS_FAILED); } +bool migrate_colo_enabled(void) +{ + MigrationState *s = migrate_get_current(); + return s->enabled_capabilities[MIGRATION_CAPABILITY_X_COLO]; +} + /* * Master migration thread on the source VM. * It drives the migration and pumps the data down the outgoing channel. @@ -1731,6 +1789,7 @@ static void *migration_thread(void *opaque) bool entered_postcopy = false; /* The active state we expect to be in; ACTIVE or POSTCOPY_ACTIVE */ enum MigrationStatus current_active_state = MIGRATION_STATUS_ACTIVE; + bool enable_colo = migrate_colo_enabled(); rcu_register_thread(); @@ -1839,7 +1898,13 @@ static void *migration_thread(void *opaque) end_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); qemu_mutex_lock_iothread(); - qemu_savevm_state_cleanup(); + /* + * The resource has been allocated by migration will be reused in COLO + * process, so don't release them. + */ + if (!enable_colo) { + qemu_savevm_state_cleanup(); + } if (s->state == MIGRATION_STATUS_COMPLETED) { uint64_t transferred_bytes = qemu_ftell(s->to_dst_file); s->total_time = end_time - s->total_time; @@ -1852,6 +1917,15 @@ static void *migration_thread(void *opaque) } runstate_set(RUN_STATE_POSTMIGRATE); } else { + if (s->state == MIGRATION_STATUS_ACTIVE && enable_colo) { + migrate_start_colo_process(s); + qemu_savevm_state_cleanup(); + /* + * Fixme: we will run VM in COLO no matter its old running state. + * After exited COLO, we will keep running. + */ + old_vm_running = true; + } if (old_vm_running && !entered_postcopy) { vm_start(); } else { diff --git a/migration/ram.c b/migration/ram.c index d032d389c4..fb9252d722 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -43,6 +43,7 @@ #include "trace.h" #include "exec/ram_addr.h" #include "qemu/rcu_queue.h" +#include "migration/colo.h" #ifdef DEBUG_MIGRATION_RAM #define DPRINTF(fmt, ...) \ @@ -1871,16 +1872,8 @@ err: return ret; } - -/* Each of ram_save_setup, ram_save_iterate and ram_save_complete has - * long-running RCU critical section. When rcu-reclaims in the code - * start to become numerous it will be necessary to reduce the - * granularity of these critical sections. - */ - -static int ram_save_setup(QEMUFile *f, void *opaque) +static int ram_save_init_globals(void) { - RAMBlock *block; int64_t ram_bitmap_pages; /* Size of bitmap in pages, including gaps */ dirty_rate_high_cnt = 0; @@ -1947,6 +1940,29 @@ static int ram_save_setup(QEMUFile *f, void *opaque) migration_bitmap_sync(); qemu_mutex_unlock_ramlist(); qemu_mutex_unlock_iothread(); + rcu_read_unlock(); + + return 0; +} + +/* Each of ram_save_setup, ram_save_iterate and ram_save_complete has + * long-running RCU critical section. When rcu-reclaims in the code + * start to become numerous it will be necessary to reduce the + * granularity of these critical sections. + */ + +static int ram_save_setup(QEMUFile *f, void *opaque) +{ + RAMBlock *block; + + /* migration has already setup the bitmap, reuse it. */ + if (!migration_in_colo_state()) { + if (ram_save_init_globals() < 0) { + return -1; + } + } + + rcu_read_lock(); qemu_put_be64(f, ram_bytes_total() | RAM_SAVE_FLAG_MEM_SIZE); @@ -2048,7 +2064,8 @@ static int ram_save_complete(QEMUFile *f, void *opaque) while (true) { int pages; - pages = ram_find_and_save_block(f, true, &bytes_transferred); + pages = ram_find_and_save_block(f, !migration_in_colo_state(), + &bytes_transferred); /* no more blocks to sent */ if (pages == 0) { break; diff --git a/migration/trace-events b/migration/trace-events index dfee75abf4..94134f700b 100644 --- a/migration/trace-events +++ b/migration/trace-events @@ -207,3 +207,9 @@ migration_tls_outgoing_handshake_complete(void) "" migration_tls_incoming_handshake_start(void) "" migration_tls_incoming_handshake_error(const char *err) "err=%s" migration_tls_incoming_handshake_complete(void) "" + +# migration/colo.c +colo_vm_state_change(const char *old, const char *new) "Change '%s' => '%s'" +colo_send_message(const char *msg) "Send '%s' message" +colo_receive_message(const char *msg) "Receive '%s' message" +colo_failover_set_state(const char *new_state) "new state %s" |