X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=migration.c;h=ba81df7b668947f751c3140207d61a1a5b82886b;hb=9fb26641ab497eda9138f9af75cbeb02ed59b5ae;hp=78d0f6116e6ad20c25d520fa5fed4d9cbf8e7adb;hpb=0edda1c42aa49dd3207f3c76bf25fd53ac16345d;p=qemu.git diff --git a/migration.c b/migration.c index 78d0f6116..ba81df7b6 100644 --- a/migration.c +++ b/migration.c @@ -9,6 +9,8 @@ * This work is licensed under the terms of the GNU GPL, version 2. See * the COPYING file in the top-level directory. * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. */ #include "qemu-common.h" @@ -19,7 +21,7 @@ #include "block.h" #include "qemu_socket.h" #include "block-migration.h" -#include "qemu-objects.h" +#include "qmp-commands.h" //#define DEBUG_MIGRATION @@ -31,21 +33,40 @@ do { } while (0) #endif -/* Migration speed throttling */ -static int64_t max_throttle = (32 << 20); +enum { + MIG_STATE_ERROR, + MIG_STATE_SETUP, + MIG_STATE_CANCELLED, + MIG_STATE_ACTIVE, + MIG_STATE_COMPLETED, +}; -static MigrationState *current_migration; +#define MAX_THROTTLE (32 << 20) /* Migration speed throttling */ static NotifierList migration_state_notifiers = NOTIFIER_LIST_INITIALIZER(migration_state_notifiers); -int qemu_start_incoming_migration(const char *uri) +/* When we add fault tolerance, we could have several + migrations at once. For now we don't need to add + dynamic creation of migration */ + +static MigrationState *migrate_get_current(void) +{ + static MigrationState current_migration = { + .state = MIG_STATE_SETUP, + .bandwidth_limit = MAX_THROTTLE, + }; + + return ¤t_migration; +} + +int qemu_start_incoming_migration(const char *uri, Error **errp) { const char *p; int ret; if (strstart(uri, "tcp:", &p)) - ret = tcp_start_incoming_migration(p); + ret = tcp_start_incoming_migration(p, errp); #if !defined(WIN32) else if (strstart(uri, "exec:", &p)) ret = exec_start_incoming_migration(p); @@ -70,6 +91,10 @@ void process_incoming_migration(QEMUFile *f) qemu_announce_self(); DPRINTF("successfully loaded vm state\n"); + bdrv_clear_incoming_migration_all(); + /* Make sure all file formats flush their mutable metadata */ + bdrv_invalidate_cache_all(); + if (autostart) { vm_start(); } else { @@ -77,87 +102,6 @@ void process_incoming_migration(QEMUFile *f) } } -int do_migrate(Monitor *mon, const QDict *qdict, QObject **ret_data) -{ - MigrationState *s = NULL; - const char *p; - int detach = qdict_get_try_bool(qdict, "detach", 0); - int blk = qdict_get_try_bool(qdict, "blk", 0); - int inc = qdict_get_try_bool(qdict, "inc", 0); - const char *uri = qdict_get_str(qdict, "uri"); - - if (current_migration && - current_migration->get_status(current_migration) == MIG_STATE_ACTIVE) { - monitor_printf(mon, "migration already in progress\n"); - return -1; - } - - if (qemu_savevm_state_blocked(mon)) { - return -1; - } - - if (strstart(uri, "tcp:", &p)) { - s = tcp_start_outgoing_migration(mon, p, max_throttle, detach, - blk, inc); -#if !defined(WIN32) - } else if (strstart(uri, "exec:", &p)) { - s = exec_start_outgoing_migration(mon, p, max_throttle, detach, - blk, inc); - } else if (strstart(uri, "unix:", &p)) { - s = unix_start_outgoing_migration(mon, p, max_throttle, detach, - blk, inc); - } else if (strstart(uri, "fd:", &p)) { - s = fd_start_outgoing_migration(mon, p, max_throttle, detach, - blk, inc); -#endif - } else { - monitor_printf(mon, "unknown migration protocol: %s\n", uri); - return -1; - } - - if (s == NULL) { - monitor_printf(mon, "migration failed\n"); - return -1; - } - - if (current_migration) { - current_migration->release(current_migration); - } - - current_migration = s; - notifier_list_notify(&migration_state_notifiers, NULL); - return 0; -} - -int do_migrate_cancel(Monitor *mon, const QDict *qdict, QObject **ret_data) -{ - MigrationState *s = current_migration; - - if (s && s->get_status(s) == MIG_STATE_ACTIVE) { - s->cancel(s); - } - return 0; -} - -int do_migrate_set_speed(Monitor *mon, const QDict *qdict, QObject **ret_data) -{ - int64_t d; - MigrationState *s; - - d = qdict_get_int(qdict, "value"); - if (d < 0) { - d = 0; - } - max_throttle = d; - - s = current_migration; - if (s && s->file) { - qemu_file_set_rate_limit(s->file, max_throttle); - } - - return 0; -} - /* amount of nanoseconds we are willing to wait for migration to be down. * the choice of nanoseconds is because it is the maximum resolution that * get_clock() can achieve. It is an internal measure. All user-visible @@ -169,157 +113,154 @@ uint64_t migrate_max_downtime(void) return max_downtime; } -int do_migrate_set_downtime(Monitor *mon, const QDict *qdict, - QObject **ret_data) +MigrationCapabilityStatusList *qmp_query_migrate_capabilities(Error **errp) { - double d; - - d = qdict_get_double(qdict, "value") * 1e9; - d = MAX(0, MIN(UINT64_MAX, d)); - max_downtime = (uint64_t)d; + MigrationCapabilityStatusList *head = NULL; + MigrationCapabilityStatusList *caps; + MigrationState *s = migrate_get_current(); + int i; + + for (i = 0; i < MIGRATION_CAPABILITY_MAX; i++) { + if (head == NULL) { + head = g_malloc0(sizeof(*caps)); + caps = head; + } else { + caps->next = g_malloc0(sizeof(*caps)); + caps = caps->next; + } + caps->value = + g_malloc(sizeof(*caps->value)); + caps->value->capability = i; + caps->value->state = s->enabled_capabilities[i]; + } - return 0; + return head; } -static void migrate_print_status(Monitor *mon, const char *name, - const QDict *status_dict) +MigrationInfo *qmp_query_migrate(Error **errp) { - QDict *qdict; - - qdict = qobject_to_qdict(qdict_get(status_dict, name)); + MigrationInfo *info = g_malloc0(sizeof(*info)); + MigrationState *s = migrate_get_current(); + + switch (s->state) { + case MIG_STATE_SETUP: + /* no migration has happened ever */ + break; + case MIG_STATE_ACTIVE: + info->has_status = true; + info->status = g_strdup("active"); + + info->has_ram = true; + info->ram = g_malloc0(sizeof(*info->ram)); + info->ram->transferred = ram_bytes_transferred(); + info->ram->remaining = ram_bytes_remaining(); + info->ram->total = ram_bytes_total(); + info->ram->total_time = qemu_get_clock_ms(rt_clock) + - s->total_time; + + if (blk_mig_active()) { + info->has_disk = true; + info->disk = g_malloc0(sizeof(*info->disk)); + info->disk->transferred = blk_mig_bytes_transferred(); + info->disk->remaining = blk_mig_bytes_remaining(); + info->disk->total = blk_mig_bytes_total(); + } + break; + case MIG_STATE_COMPLETED: + info->has_status = true; + info->status = g_strdup("completed"); + + info->has_ram = true; + info->ram = g_malloc0(sizeof(*info->ram)); + info->ram->transferred = ram_bytes_transferred(); + info->ram->remaining = 0; + info->ram->total = ram_bytes_total(); + info->ram->total_time = s->total_time; + break; + case MIG_STATE_ERROR: + info->has_status = true; + info->status = g_strdup("failed"); + break; + case MIG_STATE_CANCELLED: + info->has_status = true; + info->status = g_strdup("cancelled"); + break; + } - monitor_printf(mon, "transferred %s: %" PRIu64 " kbytes\n", name, - qdict_get_int(qdict, "transferred") >> 10); - monitor_printf(mon, "remaining %s: %" PRIu64 " kbytes\n", name, - qdict_get_int(qdict, "remaining") >> 10); - monitor_printf(mon, "total %s: %" PRIu64 " kbytes\n", name, - qdict_get_int(qdict, "total") >> 10); + return info; } -void do_info_migrate_print(Monitor *mon, const QObject *data) +void qmp_migrate_set_capabilities(MigrationCapabilityStatusList *params, + Error **errp) { - QDict *qdict; + MigrationState *s = migrate_get_current(); + MigrationCapabilityStatusList *cap; - qdict = qobject_to_qdict(data); - - monitor_printf(mon, "Migration status: %s\n", - qdict_get_str(qdict, "status")); - - if (qdict_haskey(qdict, "ram")) { - migrate_print_status(mon, "ram", qdict); + if (s->state == MIG_STATE_ACTIVE) { + error_set(errp, QERR_MIGRATION_ACTIVE); + return; } - if (qdict_haskey(qdict, "disk")) { - migrate_print_status(mon, "disk", qdict); + for (cap = params; cap; cap = cap->next) { + s->enabled_capabilities[cap->value->capability] = cap->value->state; } } -static void migrate_put_status(QDict *qdict, const char *name, - uint64_t trans, uint64_t rem, uint64_t total) -{ - QObject *obj; - - obj = qobject_from_jsonf("{ 'transferred': %" PRId64 ", " - "'remaining': %" PRId64 ", " - "'total': %" PRId64 " }", trans, rem, total); - qdict_put_obj(qdict, name, obj); -} +/* shared migration helpers */ -void do_info_migrate(Monitor *mon, QObject **ret_data) +static int migrate_fd_cleanup(MigrationState *s) { - QDict *qdict; - - if (current_migration) { - MigrationState *s = current_migration; - - switch (s->get_status(current_migration)) { - case MIG_STATE_ACTIVE: - qdict = qdict_new(); - qdict_put(qdict, "status", qstring_from_str("active")); - - migrate_put_status(qdict, "ram", ram_bytes_transferred(), - ram_bytes_remaining(), ram_bytes_total()); + int ret = 0; - if (blk_mig_active()) { - migrate_put_status(qdict, "disk", blk_mig_bytes_transferred(), - blk_mig_bytes_remaining(), - blk_mig_bytes_total()); - } + qemu_set_fd_handler2(s->fd, NULL, NULL, NULL, NULL); - *ret_data = QOBJECT(qdict); - break; - case MIG_STATE_COMPLETED: - *ret_data = qobject_from_jsonf("{ 'status': 'completed' }"); - break; - case MIG_STATE_ERROR: - *ret_data = qobject_from_jsonf("{ 'status': 'failed' }"); - break; - case MIG_STATE_CANCELLED: - *ret_data = qobject_from_jsonf("{ 'status': 'cancelled' }"); - break; - } + if (s->file) { + DPRINTF("closing file\n"); + ret = qemu_fclose(s->file); + s->file = NULL; } -} -/* shared migration helpers */ - -static void migrate_fd_monitor_suspend(MigrationState *s, Monitor *mon) -{ - s->mon = mon; - if (monitor_suspend(mon) == 0) { - DPRINTF("suspending monitor\n"); - } else { - monitor_printf(mon, "terminal does not allow synchronous " - "migration, continuing detached\n"); + if (s->fd != -1) { + close(s->fd); + s->fd = -1; } + + return ret; } void migrate_fd_error(MigrationState *s) { DPRINTF("setting error state\n"); s->state = MIG_STATE_ERROR; - notifier_list_notify(&migration_state_notifiers, NULL); + notifier_list_notify(&migration_state_notifiers, s); migrate_fd_cleanup(s); } -int migrate_fd_cleanup(MigrationState *s) +static void migrate_fd_completed(MigrationState *s) { - int ret = 0; - - qemu_set_fd_handler2(s->fd, NULL, NULL, NULL, NULL); - - if (s->file) { - DPRINTF("closing file\n"); - if (qemu_fclose(s->file) != 0) { - ret = -1; - } - s->file = NULL; + DPRINTF("setting completed state\n"); + if (migrate_fd_cleanup(s) < 0) { + s->state = MIG_STATE_ERROR; } else { - if (s->mon) { - monitor_resume(s->mon); - } + s->state = MIG_STATE_COMPLETED; + runstate_set(RUN_STATE_POSTMIGRATE); } - - if (s->fd != -1) { - close(s->fd); - s->fd = -1; - } - - return ret; + notifier_list_notify(&migration_state_notifiers, s); } -void migrate_fd_put_notify(void *opaque) +static void migrate_fd_put_notify(void *opaque) { MigrationState *s = opaque; qemu_set_fd_handler2(s->fd, NULL, NULL, NULL, NULL); qemu_file_put_notify(s->file); - if (qemu_file_get_error(s->file)) { + if (s->file && qemu_file_get_error(s->file)) { migrate_fd_error(s); } } -ssize_t migrate_fd_put_buffer(void *opaque, const void *data, size_t size) +static ssize_t migrate_fd_put_buffer(void *opaque, const void *data, + size_t size) { MigrationState *s = opaque; ssize_t ret; @@ -342,29 +283,7 @@ ssize_t migrate_fd_put_buffer(void *opaque, const void *data, size_t size) return ret; } -void migrate_fd_connect(MigrationState *s) -{ - int ret; - - s->file = qemu_fopen_ops_buffered(s, - s->bandwidth_limit, - migrate_fd_put_buffer, - migrate_fd_put_ready, - migrate_fd_wait_for_unfreeze, - migrate_fd_close); - - DPRINTF("beginning savevm\n"); - ret = qemu_savevm_state_begin(s->mon, s->file, s->blk, s->shared); - if (ret < 0) { - DPRINTF("failed, %d\n", ret); - migrate_fd_error(s); - return; - } - - migrate_fd_put_ready(s); -} - -void migrate_fd_put_ready(void *opaque) +static void migrate_fd_put_ready(void *opaque) { MigrationState *s = opaque; int ret; @@ -375,40 +294,30 @@ void migrate_fd_put_ready(void *opaque) } DPRINTF("iterate\n"); - ret = qemu_savevm_state_iterate(s->mon, s->file); + ret = qemu_savevm_state_iterate(s->file); if (ret < 0) { migrate_fd_error(s); } else if (ret == 1) { int old_vm_running = runstate_is_running(); DPRINTF("done iterating\n"); - vm_stop(RUN_STATE_FINISH_MIGRATE); + qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER); + vm_stop_force_state(RUN_STATE_FINISH_MIGRATE); - if ((qemu_savevm_state_complete(s->mon, s->file)) < 0) { - if (old_vm_running) { - vm_start(); - } - s->state = MIG_STATE_ERROR; + if (qemu_savevm_state_complete(s->file) < 0) { + migrate_fd_error(s); + } else { + migrate_fd_completed(s); } - if (migrate_fd_cleanup(s) < 0) { + s->total_time = qemu_get_clock_ms(rt_clock) - s->total_time; + if (s->state != MIG_STATE_COMPLETED) { if (old_vm_running) { vm_start(); } - s->state = MIG_STATE_ERROR; } - if (s->state == MIG_STATE_ACTIVE) { - s->state = MIG_STATE_COMPLETED; - runstate_set(RUN_STATE_POSTMIGRATE); - } - notifier_list_notify(&migration_state_notifiers, NULL); } } -static int migrate_fd_get_status(MigrationState *s) -{ - return s->state; -} - static void migrate_fd_cancel(MigrationState *s) { if (s->state != MIG_STATE_ACTIVE) @@ -417,26 +326,13 @@ static void migrate_fd_cancel(MigrationState *s) DPRINTF("cancelling migration\n"); s->state = MIG_STATE_CANCELLED; - notifier_list_notify(&migration_state_notifiers, NULL); - qemu_savevm_state_cancel(s->mon, s->file); + notifier_list_notify(&migration_state_notifiers, s); + qemu_savevm_state_cancel(s->file); migrate_fd_cleanup(s); } -static void migrate_fd_release(MigrationState *s) -{ - - DPRINTF("releasing state\n"); - - if (s->state == MIG_STATE_ACTIVE) { - s->state = MIG_STATE_CANCELLED; - notifier_list_notify(&migration_state_notifiers, NULL); - migrate_fd_cleanup(s); - } - g_free(s); -} - -void migrate_fd_wait_for_unfreeze(void *opaque) +static void migrate_fd_wait_for_unfreeze(void *opaque) { MigrationState *s = opaque; int ret; @@ -459,13 +355,10 @@ void migrate_fd_wait_for_unfreeze(void *opaque) } } -int migrate_fd_close(void *opaque) +static int migrate_fd_close(void *opaque) { MigrationState *s = opaque; - if (s->mon) { - monitor_resume(s->mon); - } qemu_set_fd_handler2(s->fd, NULL, NULL, NULL, NULL); return s->close(s); } @@ -477,35 +370,157 @@ void add_migration_state_change_notifier(Notifier *notify) void remove_migration_state_change_notifier(Notifier *notify) { - notifier_list_remove(&migration_state_notifiers, notify); + notifier_remove(notify); } -int get_migration_state(void) +bool migration_is_active(MigrationState *s) { - if (current_migration) { - return migrate_fd_get_status(current_migration); - } else { - return MIG_STATE_ERROR; - } + return s->state == MIG_STATE_ACTIVE; } -MigrationState *migrate_new(Monitor *mon, int64_t bandwidth_limit, - int detach, int blk, int inc) +bool migration_has_finished(MigrationState *s) { - MigrationState *s = g_malloc0(sizeof(*s)); - - s->cancel = migrate_fd_cancel; - s->get_status = migrate_fd_get_status; - s->release = migrate_fd_release; - s->blk = blk; - s->shared = inc; - s->mon = NULL; - s->bandwidth_limit = bandwidth_limit; + return s->state == MIG_STATE_COMPLETED; +} + +bool migration_has_failed(MigrationState *s) +{ + return (s->state == MIG_STATE_CANCELLED || + s->state == MIG_STATE_ERROR); +} + +void migrate_fd_connect(MigrationState *s) +{ + int ret; + s->state = MIG_STATE_ACTIVE; + s->file = qemu_fopen_ops_buffered(s, + s->bandwidth_limit, + migrate_fd_put_buffer, + migrate_fd_put_ready, + migrate_fd_wait_for_unfreeze, + migrate_fd_close); - if (!detach) { - migrate_fd_monitor_suspend(s, mon); + DPRINTF("beginning savevm\n"); + ret = qemu_savevm_state_begin(s->file, &s->params); + if (ret < 0) { + DPRINTF("failed, %d\n", ret); + migrate_fd_error(s); + return; } + migrate_fd_put_ready(s); +} + +static MigrationState *migrate_init(const MigrationParams *params) +{ + MigrationState *s = migrate_get_current(); + int64_t bandwidth_limit = s->bandwidth_limit; + bool enabled_capabilities[MIGRATION_CAPABILITY_MAX]; + + memcpy(enabled_capabilities, s->enabled_capabilities, + sizeof(enabled_capabilities)); + + memset(s, 0, sizeof(*s)); + s->bandwidth_limit = bandwidth_limit; + s->params = *params; + memcpy(s->enabled_capabilities, enabled_capabilities, + sizeof(enabled_capabilities)); + + s->bandwidth_limit = bandwidth_limit; + s->state = MIG_STATE_SETUP; + s->total_time = qemu_get_clock_ms(rt_clock); return s; } + +static GSList *migration_blockers; + +void migrate_add_blocker(Error *reason) +{ + migration_blockers = g_slist_prepend(migration_blockers, reason); +} + +void migrate_del_blocker(Error *reason) +{ + migration_blockers = g_slist_remove(migration_blockers, reason); +} + +void qmp_migrate(const char *uri, bool has_blk, bool blk, + bool has_inc, bool inc, bool has_detach, bool detach, + Error **errp) +{ + MigrationState *s = migrate_get_current(); + MigrationParams params; + const char *p; + int ret; + + params.blk = blk; + params.shared = inc; + + if (s->state == MIG_STATE_ACTIVE) { + error_set(errp, QERR_MIGRATION_ACTIVE); + return; + } + + if (qemu_savevm_state_blocked(errp)) { + return; + } + + if (migration_blockers) { + *errp = error_copy(migration_blockers->data); + return; + } + + s = migrate_init(¶ms); + + if (strstart(uri, "tcp:", &p)) { + ret = tcp_start_outgoing_migration(s, p, errp); +#if !defined(WIN32) + } else if (strstart(uri, "exec:", &p)) { + ret = exec_start_outgoing_migration(s, p); + } else if (strstart(uri, "unix:", &p)) { + ret = unix_start_outgoing_migration(s, p); + } else if (strstart(uri, "fd:", &p)) { + ret = fd_start_outgoing_migration(s, p); +#endif + } else { + error_set(errp, QERR_INVALID_PARAMETER_VALUE, "uri", "a valid migration protocol"); + return; + } + + if (ret < 0) { + if (!error_is_set(errp)) { + DPRINTF("migration failed: %s\n", strerror(-ret)); + /* FIXME: we should return meaningful errors */ + error_set(errp, QERR_UNDEFINED_ERROR); + } + return; + } + + notifier_list_notify(&migration_state_notifiers, s); +} + +void qmp_migrate_cancel(Error **errp) +{ + migrate_fd_cancel(migrate_get_current()); +} + +void qmp_migrate_set_speed(int64_t value, Error **errp) +{ + MigrationState *s; + + if (value < 0) { + value = 0; + } + + s = migrate_get_current(); + s->bandwidth_limit = value; + qemu_file_set_rate_limit(s->file, s->bandwidth_limit); +} + +void qmp_migrate_set_downtime(double value, Error **errp) +{ + value *= 1e9; + value = MAX(0, MIN(UINT64_MAX, value)); + max_downtime = (uint64_t)value; +}