|
@@ -66,6 +66,7 @@
|
|
#include "sysemu/qtest.h"
|
|
#include "sysemu/qtest.h"
|
|
#include "options.h"
|
|
#include "options.h"
|
|
#include "sysemu/dirtylimit.h"
|
|
#include "sysemu/dirtylimit.h"
|
|
|
|
+#include "qemu/sockets.h"
|
|
|
|
|
|
static NotifierList migration_state_notifiers =
|
|
static NotifierList migration_state_notifiers =
|
|
NOTIFIER_LIST_INITIALIZER(migration_state_notifiers);
|
|
NOTIFIER_LIST_INITIALIZER(migration_state_notifiers);
|
|
@@ -92,31 +93,55 @@ enum mig_rp_message_type {
|
|
static MigrationState *current_migration;
|
|
static MigrationState *current_migration;
|
|
static MigrationIncomingState *current_incoming;
|
|
static MigrationIncomingState *current_incoming;
|
|
|
|
|
|
-static GSList *migration_blockers;
|
|
|
|
|
|
+static GSList *migration_blockers[MIG_MODE__MAX];
|
|
|
|
|
|
static bool migration_object_check(MigrationState *ms, Error **errp);
|
|
static bool migration_object_check(MigrationState *ms, Error **errp);
|
|
static int migration_maybe_pause(MigrationState *s,
|
|
static int migration_maybe_pause(MigrationState *s,
|
|
int *current_active_state,
|
|
int *current_active_state,
|
|
int new_state);
|
|
int new_state);
|
|
static void migrate_fd_cancel(MigrationState *s);
|
|
static void migrate_fd_cancel(MigrationState *s);
|
|
-static int close_return_path_on_source(MigrationState *s);
|
|
|
|
|
|
+static bool close_return_path_on_source(MigrationState *s);
|
|
|
|
+
|
|
|
|
+static void migration_downtime_start(MigrationState *s)
|
|
|
|
+{
|
|
|
|
+ trace_vmstate_downtime_checkpoint("src-downtime-start");
|
|
|
|
+ s->downtime_start = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void migration_downtime_end(MigrationState *s)
|
|
|
|
+{
|
|
|
|
+ int64_t now = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * If downtime already set, should mean that postcopy already set it,
|
|
|
|
+ * then that should be the real downtime already.
|
|
|
|
+ */
|
|
|
|
+ if (!s->downtime) {
|
|
|
|
+ s->downtime = now - s->downtime_start;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ trace_vmstate_downtime_checkpoint("src-downtime-end");
|
|
|
|
+}
|
|
|
|
|
|
static bool migration_needs_multiple_sockets(void)
|
|
static bool migration_needs_multiple_sockets(void)
|
|
{
|
|
{
|
|
return migrate_multifd() || migrate_postcopy_preempt();
|
|
return migrate_multifd() || migrate_postcopy_preempt();
|
|
}
|
|
}
|
|
|
|
|
|
-static bool uri_supports_multi_channels(const char *uri)
|
|
|
|
|
|
+static bool transport_supports_multi_channels(SocketAddress *saddr)
|
|
{
|
|
{
|
|
- return strstart(uri, "tcp:", NULL) || strstart(uri, "unix:", NULL) ||
|
|
|
|
- strstart(uri, "vsock:", NULL);
|
|
|
|
|
|
+ return saddr->type == SOCKET_ADDRESS_TYPE_INET ||
|
|
|
|
+ saddr->type == SOCKET_ADDRESS_TYPE_UNIX ||
|
|
|
|
+ saddr->type == SOCKET_ADDRESS_TYPE_VSOCK;
|
|
}
|
|
}
|
|
|
|
|
|
static bool
|
|
static bool
|
|
-migration_channels_and_uri_compatible(const char *uri, Error **errp)
|
|
|
|
|
|
+migration_channels_and_transport_compatible(MigrationAddress *addr,
|
|
|
|
+ Error **errp)
|
|
{
|
|
{
|
|
if (migration_needs_multiple_sockets() &&
|
|
if (migration_needs_multiple_sockets() &&
|
|
- !uri_supports_multi_channels(uri)) {
|
|
|
|
|
|
+ (addr->transport == MIGRATION_ADDRESS_TYPE_SOCKET) &&
|
|
|
|
+ !transport_supports_multi_channels(&addr->u.socket)) {
|
|
error_setg(errp, "Migration requires multi-channel URIs (e.g. tcp)");
|
|
error_setg(errp, "Migration requires multi-channel URIs (e.g. tcp)");
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
@@ -131,6 +156,15 @@ static gint page_request_addr_cmp(gconstpointer ap, gconstpointer bp)
|
|
return (a > b) - (a < b);
|
|
return (a > b) - (a < b);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+int migration_stop_vm(RunState state)
|
|
|
|
+{
|
|
|
|
+ int ret = vm_stop_force_state(state);
|
|
|
|
+
|
|
|
|
+ trace_vmstate_downtime_checkpoint("src-vm-stopped");
|
|
|
|
+
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
void migration_object_init(void)
|
|
void migration_object_init(void)
|
|
{
|
|
{
|
|
/* This can only be called once. */
|
|
/* This can only be called once. */
|
|
@@ -423,25 +457,114 @@ void migrate_add_address(SocketAddress *address)
|
|
QAPI_CLONE(SocketAddress, address));
|
|
QAPI_CLONE(SocketAddress, address));
|
|
}
|
|
}
|
|
|
|
|
|
-static void qemu_start_incoming_migration(const char *uri, Error **errp)
|
|
|
|
|
|
+bool migrate_uri_parse(const char *uri, MigrationChannel **channel,
|
|
|
|
+ Error **errp)
|
|
{
|
|
{
|
|
- const char *p = NULL;
|
|
|
|
|
|
+ g_autoptr(MigrationChannel) val = g_new0(MigrationChannel, 1);
|
|
|
|
+ g_autoptr(MigrationAddress) addr = g_new0(MigrationAddress, 1);
|
|
|
|
+ SocketAddress *saddr = NULL;
|
|
|
|
+ InetSocketAddress *isock = &addr->u.rdma;
|
|
|
|
+ strList **tail = &addr->u.exec.args;
|
|
|
|
+
|
|
|
|
+ if (strstart(uri, "exec:", NULL)) {
|
|
|
|
+ addr->transport = MIGRATION_ADDRESS_TYPE_EXEC;
|
|
|
|
+#ifdef WIN32
|
|
|
|
+ QAPI_LIST_APPEND(tail, g_strdup(exec_get_cmd_path()));
|
|
|
|
+ QAPI_LIST_APPEND(tail, g_strdup("/c"));
|
|
|
|
+#else
|
|
|
|
+ QAPI_LIST_APPEND(tail, g_strdup("/bin/sh"));
|
|
|
|
+ QAPI_LIST_APPEND(tail, g_strdup("-c"));
|
|
|
|
+#endif
|
|
|
|
+ QAPI_LIST_APPEND(tail, g_strdup(uri + strlen("exec:")));
|
|
|
|
+ } else if (strstart(uri, "rdma:", NULL)) {
|
|
|
|
+ if (inet_parse(isock, uri + strlen("rdma:"), errp)) {
|
|
|
|
+ qapi_free_InetSocketAddress(isock);
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ addr->transport = MIGRATION_ADDRESS_TYPE_RDMA;
|
|
|
|
+ } else if (strstart(uri, "tcp:", NULL) ||
|
|
|
|
+ strstart(uri, "unix:", NULL) ||
|
|
|
|
+ strstart(uri, "vsock:", NULL) ||
|
|
|
|
+ strstart(uri, "fd:", NULL)) {
|
|
|
|
+ addr->transport = MIGRATION_ADDRESS_TYPE_SOCKET;
|
|
|
|
+ saddr = socket_parse(uri, errp);
|
|
|
|
+ if (!saddr) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ addr->u.socket.type = saddr->type;
|
|
|
|
+ addr->u.socket.u = saddr->u;
|
|
|
|
+ } else if (strstart(uri, "file:", NULL)) {
|
|
|
|
+ addr->transport = MIGRATION_ADDRESS_TYPE_FILE;
|
|
|
|
+ addr->u.file.filename = g_strdup(uri + strlen("file:"));
|
|
|
|
+ if (file_parse_offset(addr->u.file.filename, &addr->u.file.offset,
|
|
|
|
+ errp)) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ error_setg(errp, "unknown migration protocol: %s", uri);
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ val->channel_type = MIGRATION_CHANNEL_TYPE_MAIN;
|
|
|
|
+ val->addr = g_steal_pointer(&addr);
|
|
|
|
+ *channel = g_steal_pointer(&val);
|
|
|
|
+ return true;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void qemu_start_incoming_migration(const char *uri, bool has_channels,
|
|
|
|
+ MigrationChannelList *channels,
|
|
|
|
+ Error **errp)
|
|
|
|
+{
|
|
|
|
+ MigrationChannel *channel = NULL;
|
|
|
|
+ MigrationAddress *addr = NULL;
|
|
MigrationIncomingState *mis = migration_incoming_get_current();
|
|
MigrationIncomingState *mis = migration_incoming_get_current();
|
|
|
|
|
|
- /* URI is not suitable for migration? */
|
|
|
|
- if (!migration_channels_and_uri_compatible(uri, errp)) {
|
|
|
|
|
|
+ /*
|
|
|
|
+ * Having preliminary checks for uri and channel
|
|
|
|
+ */
|
|
|
|
+ if (uri && has_channels) {
|
|
|
|
+ error_setg(errp, "'uri' and 'channels' arguments are mutually "
|
|
|
|
+ "exclusive; exactly one of the two should be present in "
|
|
|
|
+ "'migrate-incoming' qmp command ");
|
|
|
|
+ return;
|
|
|
|
+ } else if (channels) {
|
|
|
|
+ /* To verify that Migrate channel list has only item */
|
|
|
|
+ if (channels->next) {
|
|
|
|
+ error_setg(errp, "Channel list has more than one entries");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ channel = channels->value;
|
|
|
|
+ } else if (uri) {
|
|
|
|
+ /* caller uses the old URI syntax */
|
|
|
|
+ if (!migrate_uri_parse(uri, &channel, errp)) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ error_setg(errp, "neither 'uri' or 'channels' argument are "
|
|
|
|
+ "specified in 'migrate-incoming' qmp command ");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ addr = channel->addr;
|
|
|
|
+
|
|
|
|
+ /* transport mechanism not suitable for migration? */
|
|
|
|
+ if (!migration_channels_and_transport_compatible(addr, errp)) {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
migrate_set_state(&mis->state, MIGRATION_STATUS_NONE,
|
|
migrate_set_state(&mis->state, MIGRATION_STATUS_NONE,
|
|
MIGRATION_STATUS_SETUP);
|
|
MIGRATION_STATUS_SETUP);
|
|
|
|
|
|
- if (strstart(uri, "tcp:", &p) ||
|
|
|
|
- strstart(uri, "unix:", NULL) ||
|
|
|
|
- strstart(uri, "vsock:", NULL)) {
|
|
|
|
- socket_start_incoming_migration(p ? p : uri, errp);
|
|
|
|
|
|
+ if (addr->transport == MIGRATION_ADDRESS_TYPE_SOCKET) {
|
|
|
|
+ SocketAddress *saddr = &addr->u.socket;
|
|
|
|
+ if (saddr->type == SOCKET_ADDRESS_TYPE_INET ||
|
|
|
|
+ saddr->type == SOCKET_ADDRESS_TYPE_UNIX ||
|
|
|
|
+ saddr->type == SOCKET_ADDRESS_TYPE_VSOCK) {
|
|
|
|
+ socket_start_incoming_migration(saddr, errp);
|
|
|
|
+ } else if (saddr->type == SOCKET_ADDRESS_TYPE_FD) {
|
|
|
|
+ fd_start_incoming_migration(saddr->u.fd.str, errp);
|
|
|
|
+ }
|
|
#ifdef CONFIG_RDMA
|
|
#ifdef CONFIG_RDMA
|
|
- } else if (strstart(uri, "rdma:", &p)) {
|
|
|
|
|
|
+ } else if (addr->transport == MIGRATION_ADDRESS_TYPE_RDMA) {
|
|
if (migrate_compress()) {
|
|
if (migrate_compress()) {
|
|
error_setg(errp, "RDMA and compression can't be used together");
|
|
error_setg(errp, "RDMA and compression can't be used together");
|
|
return;
|
|
return;
|
|
@@ -454,14 +577,12 @@ static void qemu_start_incoming_migration(const char *uri, Error **errp)
|
|
error_setg(errp, "RDMA and multifd can't be used together");
|
|
error_setg(errp, "RDMA and multifd can't be used together");
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
- rdma_start_incoming_migration(p, errp);
|
|
|
|
|
|
+ rdma_start_incoming_migration(&addr->u.rdma, errp);
|
|
#endif
|
|
#endif
|
|
- } else if (strstart(uri, "exec:", &p)) {
|
|
|
|
- exec_start_incoming_migration(p, errp);
|
|
|
|
- } else if (strstart(uri, "fd:", &p)) {
|
|
|
|
- fd_start_incoming_migration(p, errp);
|
|
|
|
- } else if (strstart(uri, "file:", &p)) {
|
|
|
|
- file_start_incoming_migration(p, errp);
|
|
|
|
|
|
+ } else if (addr->transport == MIGRATION_ADDRESS_TYPE_EXEC) {
|
|
|
|
+ exec_start_incoming_migration(addr->u.exec.args, errp);
|
|
|
|
+ } else if (addr->transport == MIGRATION_ADDRESS_TYPE_FILE) {
|
|
|
|
+ file_start_incoming_migration(&addr->u.file, errp);
|
|
} else {
|
|
} else {
|
|
error_setg(errp, "unknown migration protocol: %s", uri);
|
|
error_setg(errp, "unknown migration protocol: %s", uri);
|
|
}
|
|
}
|
|
@@ -472,6 +593,8 @@ static void process_incoming_migration_bh(void *opaque)
|
|
Error *local_err = NULL;
|
|
Error *local_err = NULL;
|
|
MigrationIncomingState *mis = opaque;
|
|
MigrationIncomingState *mis = opaque;
|
|
|
|
|
|
|
|
+ trace_vmstate_downtime_checkpoint("dst-precopy-bh-enter");
|
|
|
|
+
|
|
/* If capability late_block_activate is set:
|
|
/* If capability late_block_activate is set:
|
|
* Only fire up the block code now if we're going to restart the
|
|
* Only fire up the block code now if we're going to restart the
|
|
* VM, else 'cont' will do it.
|
|
* VM, else 'cont' will do it.
|
|
@@ -497,6 +620,8 @@ static void process_incoming_migration_bh(void *opaque)
|
|
*/
|
|
*/
|
|
qemu_announce_self(&mis->announce_timer, migrate_announce_params());
|
|
qemu_announce_self(&mis->announce_timer, migrate_announce_params());
|
|
|
|
|
|
|
|
+ trace_vmstate_downtime_checkpoint("dst-precopy-bh-announced");
|
|
|
|
+
|
|
multifd_load_shutdown();
|
|
multifd_load_shutdown();
|
|
|
|
|
|
dirty_bitmap_mig_before_vm_start();
|
|
dirty_bitmap_mig_before_vm_start();
|
|
@@ -514,6 +639,7 @@ static void process_incoming_migration_bh(void *opaque)
|
|
} else {
|
|
} else {
|
|
runstate_set(global_state_get_runstate());
|
|
runstate_set(global_state_get_runstate());
|
|
}
|
|
}
|
|
|
|
+ trace_vmstate_downtime_checkpoint("dst-precopy-bh-vm-started");
|
|
/*
|
|
/*
|
|
* This must happen after any state changes since as soon as an external
|
|
* This must happen after any state changes since as soon as an external
|
|
* observer sees this event they might start to prod at the VM assuming
|
|
* observer sees this event they might start to prod at the VM assuming
|
|
@@ -548,6 +674,8 @@ process_incoming_migration_co(void *opaque)
|
|
ret = qemu_loadvm_state(mis->from_src_file);
|
|
ret = qemu_loadvm_state(mis->from_src_file);
|
|
mis->loadvm_co = NULL;
|
|
mis->loadvm_co = NULL;
|
|
|
|
|
|
|
|
+ trace_vmstate_downtime_checkpoint("dst-precopy-loadvm-completed");
|
|
|
|
+
|
|
ps = postcopy_state_get();
|
|
ps = postcopy_state_get();
|
|
trace_process_incoming_migration_co_end(ret, ps);
|
|
trace_process_incoming_migration_co_end(ret, ps);
|
|
if (ps != POSTCOPY_INCOMING_NONE) {
|
|
if (ps != POSTCOPY_INCOMING_NONE) {
|
|
@@ -1006,7 +1134,7 @@ static void fill_source_migration_info(MigrationInfo *info)
|
|
{
|
|
{
|
|
MigrationState *s = migrate_get_current();
|
|
MigrationState *s = migrate_get_current();
|
|
int state = qatomic_read(&s->state);
|
|
int state = qatomic_read(&s->state);
|
|
- GSList *cur_blocker = migration_blockers;
|
|
|
|
|
|
+ GSList *cur_blocker = migration_blockers[migrate_mode()];
|
|
|
|
|
|
info->blocked_reasons = NULL;
|
|
info->blocked_reasons = NULL;
|
|
|
|
|
|
@@ -1356,6 +1484,17 @@ bool migration_in_postcopy(void)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+bool migration_postcopy_is_alive(int state)
|
|
|
|
+{
|
|
|
|
+ switch (state) {
|
|
|
|
+ case MIGRATION_STATUS_POSTCOPY_ACTIVE:
|
|
|
|
+ case MIGRATION_STATUS_POSTCOPY_RECOVER:
|
|
|
|
+ return true;
|
|
|
|
+ default:
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
bool migration_in_postcopy_after_devices(MigrationState *s)
|
|
bool migration_in_postcopy_after_devices(MigrationState *s)
|
|
{
|
|
{
|
|
return migration_in_postcopy() && s->postcopy_after_devices;
|
|
return migration_in_postcopy() && s->postcopy_after_devices;
|
|
@@ -1438,7 +1577,6 @@ int migrate_init(MigrationState *s, Error **errp)
|
|
s->to_dst_file = NULL;
|
|
s->to_dst_file = NULL;
|
|
s->state = MIGRATION_STATUS_NONE;
|
|
s->state = MIGRATION_STATUS_NONE;
|
|
s->rp_state.from_dst_file = NULL;
|
|
s->rp_state.from_dst_file = NULL;
|
|
- s->rp_state.error = false;
|
|
|
|
s->mbps = 0.0;
|
|
s->mbps = 0.0;
|
|
s->pages_per_second = 0.0;
|
|
s->pages_per_second = 0.0;
|
|
s->downtime = 0;
|
|
s->downtime = 0;
|
|
@@ -1470,44 +1608,112 @@ int migrate_init(MigrationState *s, Error **errp)
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
-int migrate_add_blocker_internal(Error **reasonp, Error **errp)
|
|
|
|
|
|
+static bool is_busy(Error **reasonp, Error **errp)
|
|
{
|
|
{
|
|
|
|
+ ERRP_GUARD();
|
|
|
|
+
|
|
/* Snapshots are similar to migrations, so check RUN_STATE_SAVE_VM too. */
|
|
/* Snapshots are similar to migrations, so check RUN_STATE_SAVE_VM too. */
|
|
if (runstate_check(RUN_STATE_SAVE_VM) || !migration_is_idle()) {
|
|
if (runstate_check(RUN_STATE_SAVE_VM) || !migration_is_idle()) {
|
|
error_propagate_prepend(errp, *reasonp,
|
|
error_propagate_prepend(errp, *reasonp,
|
|
"disallowing migration blocker "
|
|
"disallowing migration blocker "
|
|
"(migration/snapshot in progress) for: ");
|
|
"(migration/snapshot in progress) for: ");
|
|
*reasonp = NULL;
|
|
*reasonp = NULL;
|
|
- return -EBUSY;
|
|
|
|
|
|
+ return true;
|
|
}
|
|
}
|
|
-
|
|
|
|
- migration_blockers = g_slist_prepend(migration_blockers, *reasonp);
|
|
|
|
- return 0;
|
|
|
|
|
|
+ return false;
|
|
}
|
|
}
|
|
|
|
|
|
-int migrate_add_blocker(Error **reasonp, Error **errp)
|
|
|
|
|
|
+static bool is_only_migratable(Error **reasonp, Error **errp, int modes)
|
|
{
|
|
{
|
|
- if (only_migratable) {
|
|
|
|
|
|
+ ERRP_GUARD();
|
|
|
|
+
|
|
|
|
+ if (only_migratable && (modes & BIT(MIG_MODE_NORMAL))) {
|
|
error_propagate_prepend(errp, *reasonp,
|
|
error_propagate_prepend(errp, *reasonp,
|
|
"disallowing migration blocker "
|
|
"disallowing migration blocker "
|
|
"(--only-migratable) for: ");
|
|
"(--only-migratable) for: ");
|
|
*reasonp = NULL;
|
|
*reasonp = NULL;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int get_modes(MigMode mode, va_list ap)
|
|
|
|
+{
|
|
|
|
+ int modes = 0;
|
|
|
|
+
|
|
|
|
+ while (mode != -1 && mode != MIG_MODE_ALL) {
|
|
|
|
+ assert(mode >= MIG_MODE_NORMAL && mode < MIG_MODE__MAX);
|
|
|
|
+ modes |= BIT(mode);
|
|
|
|
+ mode = va_arg(ap, MigMode);
|
|
|
|
+ }
|
|
|
|
+ if (mode == MIG_MODE_ALL) {
|
|
|
|
+ modes = BIT(MIG_MODE__MAX) - 1;
|
|
|
|
+ }
|
|
|
|
+ return modes;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int add_blockers(Error **reasonp, Error **errp, int modes)
|
|
|
|
+{
|
|
|
|
+ for (MigMode mode = 0; mode < MIG_MODE__MAX; mode++) {
|
|
|
|
+ if (modes & BIT(mode)) {
|
|
|
|
+ migration_blockers[mode] = g_slist_prepend(migration_blockers[mode],
|
|
|
|
+ *reasonp);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int migrate_add_blocker(Error **reasonp, Error **errp)
|
|
|
|
+{
|
|
|
|
+ return migrate_add_blocker_modes(reasonp, errp, MIG_MODE_ALL);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int migrate_add_blocker_normal(Error **reasonp, Error **errp)
|
|
|
|
+{
|
|
|
|
+ return migrate_add_blocker_modes(reasonp, errp, MIG_MODE_NORMAL, -1);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int migrate_add_blocker_modes(Error **reasonp, Error **errp, MigMode mode, ...)
|
|
|
|
+{
|
|
|
|
+ int modes;
|
|
|
|
+ va_list ap;
|
|
|
|
+
|
|
|
|
+ va_start(ap, mode);
|
|
|
|
+ modes = get_modes(mode, ap);
|
|
|
|
+ va_end(ap);
|
|
|
|
+
|
|
|
|
+ if (is_only_migratable(reasonp, errp, modes)) {
|
|
return -EACCES;
|
|
return -EACCES;
|
|
|
|
+ } else if (is_busy(reasonp, errp)) {
|
|
|
|
+ return -EBUSY;
|
|
}
|
|
}
|
|
|
|
+ return add_blockers(reasonp, errp, modes);
|
|
|
|
+}
|
|
|
|
|
|
- return migrate_add_blocker_internal(reasonp, errp);
|
|
|
|
|
|
+int migrate_add_blocker_internal(Error **reasonp, Error **errp)
|
|
|
|
+{
|
|
|
|
+ int modes = BIT(MIG_MODE__MAX) - 1;
|
|
|
|
+
|
|
|
|
+ if (is_busy(reasonp, errp)) {
|
|
|
|
+ return -EBUSY;
|
|
|
|
+ }
|
|
|
|
+ return add_blockers(reasonp, errp, modes);
|
|
}
|
|
}
|
|
|
|
|
|
void migrate_del_blocker(Error **reasonp)
|
|
void migrate_del_blocker(Error **reasonp)
|
|
{
|
|
{
|
|
if (*reasonp) {
|
|
if (*reasonp) {
|
|
- migration_blockers = g_slist_remove(migration_blockers, *reasonp);
|
|
|
|
|
|
+ for (MigMode mode = 0; mode < MIG_MODE__MAX; mode++) {
|
|
|
|
+ migration_blockers[mode] = g_slist_remove(migration_blockers[mode],
|
|
|
|
+ *reasonp);
|
|
|
|
+ }
|
|
error_free(*reasonp);
|
|
error_free(*reasonp);
|
|
*reasonp = NULL;
|
|
*reasonp = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-void qmp_migrate_incoming(const char *uri, Error **errp)
|
|
|
|
|
|
+void qmp_migrate_incoming(const char *uri, bool has_channels,
|
|
|
|
+ MigrationChannelList *channels, Error **errp)
|
|
{
|
|
{
|
|
Error *local_err = NULL;
|
|
Error *local_err = NULL;
|
|
static bool once = true;
|
|
static bool once = true;
|
|
@@ -1525,7 +1731,7 @@ void qmp_migrate_incoming(const char *uri, Error **errp)
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
- qemu_start_incoming_migration(uri, &local_err);
|
|
|
|
|
|
+ qemu_start_incoming_migration(uri, has_channels, channels, &local_err);
|
|
|
|
|
|
if (local_err) {
|
|
if (local_err) {
|
|
yank_unregister_instance(MIGRATION_YANK_INSTANCE);
|
|
yank_unregister_instance(MIGRATION_YANK_INSTANCE);
|
|
@@ -1561,7 +1767,7 @@ void qmp_migrate_recover(const char *uri, Error **errp)
|
|
* only re-setup the migration stream and poke existing migration
|
|
* only re-setup the migration stream and poke existing migration
|
|
* to continue using that newly established channel.
|
|
* to continue using that newly established channel.
|
|
*/
|
|
*/
|
|
- qemu_start_incoming_migration(uri, errp);
|
|
|
|
|
|
+ qemu_start_incoming_migration(uri, false, NULL, errp);
|
|
}
|
|
}
|
|
|
|
|
|
void qmp_migrate_pause(Error **errp)
|
|
void qmp_migrate_pause(Error **errp)
|
|
@@ -1570,8 +1776,15 @@ void qmp_migrate_pause(Error **errp)
|
|
MigrationIncomingState *mis = migration_incoming_get_current();
|
|
MigrationIncomingState *mis = migration_incoming_get_current();
|
|
int ret = 0;
|
|
int ret = 0;
|
|
|
|
|
|
- if (ms->state == MIGRATION_STATUS_POSTCOPY_ACTIVE) {
|
|
|
|
|
|
+ if (migration_postcopy_is_alive(ms->state)) {
|
|
/* Source side, during postcopy */
|
|
/* Source side, during postcopy */
|
|
|
|
+ Error *error = NULL;
|
|
|
|
+
|
|
|
|
+ /* Tell the core migration that we're pausing */
|
|
|
|
+ error_setg(&error, "Postcopy migration is paused by the user");
|
|
|
|
+ migrate_set_error(ms, error);
|
|
|
|
+ error_free(error);
|
|
|
|
+
|
|
qemu_mutex_lock(&ms->qemu_file_lock);
|
|
qemu_mutex_lock(&ms->qemu_file_lock);
|
|
if (ms->to_dst_file) {
|
|
if (ms->to_dst_file) {
|
|
ret = qemu_file_shutdown(ms->to_dst_file);
|
|
ret = qemu_file_shutdown(ms->to_dst_file);
|
|
@@ -1580,10 +1793,17 @@ void qmp_migrate_pause(Error **errp)
|
|
if (ret) {
|
|
if (ret) {
|
|
error_setg(errp, "Failed to pause source migration");
|
|
error_setg(errp, "Failed to pause source migration");
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Kick the migration thread out of any waiting windows (on behalf
|
|
|
|
+ * of the rp thread).
|
|
|
|
+ */
|
|
|
|
+ migration_rp_kick(ms);
|
|
|
|
+
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
- if (mis->state == MIGRATION_STATUS_POSTCOPY_ACTIVE) {
|
|
|
|
|
|
+ if (migration_postcopy_is_alive(mis->state)) {
|
|
ret = qemu_file_shutdown(mis->from_src_file);
|
|
ret = qemu_file_shutdown(mis->from_src_file);
|
|
if (ret) {
|
|
if (ret) {
|
|
error_setg(errp, "Failed to pause destination migration");
|
|
error_setg(errp, "Failed to pause destination migration");
|
|
@@ -1592,17 +1812,19 @@ void qmp_migrate_pause(Error **errp)
|
|
}
|
|
}
|
|
|
|
|
|
error_setg(errp, "migrate-pause is currently only supported "
|
|
error_setg(errp, "migrate-pause is currently only supported "
|
|
- "during postcopy-active state");
|
|
|
|
|
|
+ "during postcopy-active or postcopy-recover state");
|
|
}
|
|
}
|
|
|
|
|
|
bool migration_is_blocked(Error **errp)
|
|
bool migration_is_blocked(Error **errp)
|
|
{
|
|
{
|
|
|
|
+ GSList *blockers = migration_blockers[migrate_mode()];
|
|
|
|
+
|
|
if (qemu_savevm_state_blocked(errp)) {
|
|
if (qemu_savevm_state_blocked(errp)) {
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
- if (migration_blockers) {
|
|
|
|
- error_propagate(errp, error_copy(migration_blockers->data));
|
|
|
|
|
|
+ if (blockers) {
|
|
|
|
+ error_propagate(errp, error_copy(blockers->data));
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1702,17 +1924,46 @@ static bool migrate_prepare(MigrationState *s, bool blk, bool blk_inc,
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
-void qmp_migrate(const char *uri, bool has_blk, bool blk,
|
|
|
|
|
|
+void qmp_migrate(const char *uri, bool has_channels,
|
|
|
|
+ MigrationChannelList *channels, bool has_blk, bool blk,
|
|
bool has_inc, bool inc, bool has_detach, bool detach,
|
|
bool has_inc, bool inc, bool has_detach, bool detach,
|
|
bool has_resume, bool resume, Error **errp)
|
|
bool has_resume, bool resume, Error **errp)
|
|
{
|
|
{
|
|
bool resume_requested;
|
|
bool resume_requested;
|
|
Error *local_err = NULL;
|
|
Error *local_err = NULL;
|
|
MigrationState *s = migrate_get_current();
|
|
MigrationState *s = migrate_get_current();
|
|
- const char *p = NULL;
|
|
|
|
|
|
+ MigrationChannel *channel = NULL;
|
|
|
|
+ MigrationAddress *addr = NULL;
|
|
|
|
|
|
- /* URI is not suitable for migration? */
|
|
|
|
- if (!migration_channels_and_uri_compatible(uri, errp)) {
|
|
|
|
|
|
+ /*
|
|
|
|
+ * Having preliminary checks for uri and channel
|
|
|
|
+ */
|
|
|
|
+ if (uri && has_channels) {
|
|
|
|
+ error_setg(errp, "'uri' and 'channels' arguments are mutually "
|
|
|
|
+ "exclusive; exactly one of the two should be present in "
|
|
|
|
+ "'migrate' qmp command ");
|
|
|
|
+ return;
|
|
|
|
+ } else if (channels) {
|
|
|
|
+ /* To verify that Migrate channel list has only item */
|
|
|
|
+ if (channels->next) {
|
|
|
|
+ error_setg(errp, "Channel list has more than one entries");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ channel = channels->value;
|
|
|
|
+ } else if (uri) {
|
|
|
|
+ /* caller uses the old URI syntax */
|
|
|
|
+ if (!migrate_uri_parse(uri, &channel, errp)) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ error_setg(errp, "neither 'uri' or 'channels' argument are "
|
|
|
|
+ "specified in 'migrate' qmp command ");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ addr = channel->addr;
|
|
|
|
+
|
|
|
|
+ /* transport mechanism not suitable for migration? */
|
|
|
|
+ if (!migration_channels_and_transport_compatible(addr, errp)) {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1729,20 +1980,23 @@ void qmp_migrate(const char *uri, bool has_blk, bool blk,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- if (strstart(uri, "tcp:", &p) ||
|
|
|
|
- strstart(uri, "unix:", NULL) ||
|
|
|
|
- strstart(uri, "vsock:", NULL)) {
|
|
|
|
- socket_start_outgoing_migration(s, p ? p : uri, &local_err);
|
|
|
|
|
|
+ if (addr->transport == MIGRATION_ADDRESS_TYPE_SOCKET) {
|
|
|
|
+ SocketAddress *saddr = &addr->u.socket;
|
|
|
|
+ if (saddr->type == SOCKET_ADDRESS_TYPE_INET ||
|
|
|
|
+ saddr->type == SOCKET_ADDRESS_TYPE_UNIX ||
|
|
|
|
+ saddr->type == SOCKET_ADDRESS_TYPE_VSOCK) {
|
|
|
|
+ socket_start_outgoing_migration(s, saddr, &local_err);
|
|
|
|
+ } else if (saddr->type == SOCKET_ADDRESS_TYPE_FD) {
|
|
|
|
+ fd_start_outgoing_migration(s, saddr->u.fd.str, &local_err);
|
|
|
|
+ }
|
|
#ifdef CONFIG_RDMA
|
|
#ifdef CONFIG_RDMA
|
|
- } else if (strstart(uri, "rdma:", &p)) {
|
|
|
|
- rdma_start_outgoing_migration(s, p, &local_err);
|
|
|
|
|
|
+ } else if (addr->transport == MIGRATION_ADDRESS_TYPE_RDMA) {
|
|
|
|
+ rdma_start_outgoing_migration(s, &addr->u.rdma, &local_err);
|
|
#endif
|
|
#endif
|
|
- } else if (strstart(uri, "exec:", &p)) {
|
|
|
|
- exec_start_outgoing_migration(s, p, &local_err);
|
|
|
|
- } else if (strstart(uri, "fd:", &p)) {
|
|
|
|
- fd_start_outgoing_migration(s, p, &local_err);
|
|
|
|
- } else if (strstart(uri, "file:", &p)) {
|
|
|
|
- file_start_outgoing_migration(s, p, &local_err);
|
|
|
|
|
|
+ } else if (addr->transport == MIGRATION_ADDRESS_TYPE_EXEC) {
|
|
|
|
+ exec_start_outgoing_migration(s, addr->u.exec.args, &local_err);
|
|
|
|
+ } else if (addr->transport == MIGRATION_ADDRESS_TYPE_FILE) {
|
|
|
|
+ file_start_outgoing_migration(s, &addr->u.file, &local_err);
|
|
} else {
|
|
} else {
|
|
error_setg(&local_err, QERR_INVALID_PARAMETER_VALUE, "uri",
|
|
error_setg(&local_err, QERR_INVALID_PARAMETER_VALUE, "uri",
|
|
"a valid migration protocol");
|
|
"a valid migration protocol");
|
|
@@ -1777,19 +2031,21 @@ void qmp_migrate_continue(MigrationStatus state, Error **errp)
|
|
qemu_sem_post(&s->pause_sem);
|
|
qemu_sem_post(&s->pause_sem);
|
|
}
|
|
}
|
|
|
|
|
|
-/* migration thread support */
|
|
|
|
-/*
|
|
|
|
- * Something bad happened to the RP stream, mark an error
|
|
|
|
- * The caller shall print or trace something to indicate why
|
|
|
|
- */
|
|
|
|
-static void mark_source_rp_bad(MigrationState *s)
|
|
|
|
|
|
+int migration_rp_wait(MigrationState *s)
|
|
{
|
|
{
|
|
- s->rp_state.error = true;
|
|
|
|
-}
|
|
|
|
|
|
+ /* If migration has failure already, ignore the wait */
|
|
|
|
+ if (migrate_has_error(s)) {
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
|
|
-void migration_rp_wait(MigrationState *s)
|
|
|
|
-{
|
|
|
|
qemu_sem_wait(&s->rp_state.rp_sem);
|
|
qemu_sem_wait(&s->rp_state.rp_sem);
|
|
|
|
+
|
|
|
|
+ /* After wait, double check that there's no failure */
|
|
|
|
+ if (migrate_has_error(s)) {
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
}
|
|
}
|
|
|
|
|
|
void migration_rp_kick(MigrationState *s)
|
|
void migration_rp_kick(MigrationState *s)
|
|
@@ -1817,8 +2073,9 @@ static struct rp_cmd_args {
|
|
* We're allowed to send more than requested (e.g. to round to our page size)
|
|
* We're allowed to send more than requested (e.g. to round to our page size)
|
|
* and we don't need to send pages that have already been sent.
|
|
* and we don't need to send pages that have already been sent.
|
|
*/
|
|
*/
|
|
-static void migrate_handle_rp_req_pages(MigrationState *ms, const char* rbname,
|
|
|
|
- ram_addr_t start, size_t len)
|
|
|
|
|
|
+static void
|
|
|
|
+migrate_handle_rp_req_pages(MigrationState *ms, const char* rbname,
|
|
|
|
+ ram_addr_t start, size_t len, Error **errp)
|
|
{
|
|
{
|
|
long our_host_ps = qemu_real_host_page_size();
|
|
long our_host_ps = qemu_real_host_page_size();
|
|
|
|
|
|
@@ -1830,38 +2087,37 @@ static void migrate_handle_rp_req_pages(MigrationState *ms, const char* rbname,
|
|
*/
|
|
*/
|
|
if (!QEMU_IS_ALIGNED(start, our_host_ps) ||
|
|
if (!QEMU_IS_ALIGNED(start, our_host_ps) ||
|
|
!QEMU_IS_ALIGNED(len, our_host_ps)) {
|
|
!QEMU_IS_ALIGNED(len, our_host_ps)) {
|
|
- error_report("%s: Misaligned page request, start: " RAM_ADDR_FMT
|
|
|
|
- " len: %zd", __func__, start, len);
|
|
|
|
- mark_source_rp_bad(ms);
|
|
|
|
|
|
+ error_setg(errp, "MIG_RP_MSG_REQ_PAGES: Misaligned page request, start:"
|
|
|
|
+ RAM_ADDR_FMT " len: %zd", start, len);
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
- if (ram_save_queue_pages(rbname, start, len)) {
|
|
|
|
- mark_source_rp_bad(ms);
|
|
|
|
- }
|
|
|
|
|
|
+ ram_save_queue_pages(rbname, start, len, errp);
|
|
}
|
|
}
|
|
|
|
|
|
-static int migrate_handle_rp_recv_bitmap(MigrationState *s, char *block_name)
|
|
|
|
|
|
+static bool migrate_handle_rp_recv_bitmap(MigrationState *s, char *block_name,
|
|
|
|
+ Error **errp)
|
|
{
|
|
{
|
|
RAMBlock *block = qemu_ram_block_by_name(block_name);
|
|
RAMBlock *block = qemu_ram_block_by_name(block_name);
|
|
|
|
|
|
if (!block) {
|
|
if (!block) {
|
|
- error_report("%s: invalid block name '%s'", __func__, block_name);
|
|
|
|
- return -EINVAL;
|
|
|
|
|
|
+ error_setg(errp, "MIG_RP_MSG_RECV_BITMAP has invalid block name '%s'",
|
|
|
|
+ block_name);
|
|
|
|
+ return false;
|
|
}
|
|
}
|
|
|
|
|
|
/* Fetch the received bitmap and refresh the dirty bitmap */
|
|
/* Fetch the received bitmap and refresh the dirty bitmap */
|
|
- return ram_dirty_bitmap_reload(s, block);
|
|
|
|
|
|
+ return ram_dirty_bitmap_reload(s, block, errp);
|
|
}
|
|
}
|
|
|
|
|
|
-static int migrate_handle_rp_resume_ack(MigrationState *s, uint32_t value)
|
|
|
|
|
|
+static bool migrate_handle_rp_resume_ack(MigrationState *s,
|
|
|
|
+ uint32_t value, Error **errp)
|
|
{
|
|
{
|
|
trace_source_return_path_thread_resume_ack(value);
|
|
trace_source_return_path_thread_resume_ack(value);
|
|
|
|
|
|
if (value != MIGRATION_RESUME_ACK_VALUE) {
|
|
if (value != MIGRATION_RESUME_ACK_VALUE) {
|
|
- error_report("%s: illegal resume_ack value %"PRIu32,
|
|
|
|
- __func__, value);
|
|
|
|
- return -1;
|
|
|
|
|
|
+ error_setg(errp, "illegal resume_ack value %"PRIu32, value);
|
|
|
|
+ return false;
|
|
}
|
|
}
|
|
|
|
|
|
/* Now both sides are active. */
|
|
/* Now both sides are active. */
|
|
@@ -1871,7 +2127,7 @@ static int migrate_handle_rp_resume_ack(MigrationState *s, uint32_t value)
|
|
/* Notify send thread that time to continue send pages */
|
|
/* Notify send thread that time to continue send pages */
|
|
migration_rp_kick(s);
|
|
migration_rp_kick(s);
|
|
|
|
|
|
- return 0;
|
|
|
|
|
|
+ return true;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -1919,48 +2175,46 @@ static void *source_return_path_thread(void *opaque)
|
|
uint32_t tmp32, sibling_error;
|
|
uint32_t tmp32, sibling_error;
|
|
ram_addr_t start = 0; /* =0 to silence warning */
|
|
ram_addr_t start = 0; /* =0 to silence warning */
|
|
size_t len = 0, expected_len;
|
|
size_t len = 0, expected_len;
|
|
|
|
+ Error *err = NULL;
|
|
int res;
|
|
int res;
|
|
|
|
|
|
trace_source_return_path_thread_entry();
|
|
trace_source_return_path_thread_entry();
|
|
rcu_register_thread();
|
|
rcu_register_thread();
|
|
|
|
|
|
- while (!ms->rp_state.error && !qemu_file_get_error(rp) &&
|
|
|
|
- migration_is_setup_or_active(ms->state)) {
|
|
|
|
|
|
+ while (migration_is_setup_or_active(ms->state)) {
|
|
trace_source_return_path_thread_loop_top();
|
|
trace_source_return_path_thread_loop_top();
|
|
|
|
+
|
|
header_type = qemu_get_be16(rp);
|
|
header_type = qemu_get_be16(rp);
|
|
header_len = qemu_get_be16(rp);
|
|
header_len = qemu_get_be16(rp);
|
|
|
|
|
|
if (qemu_file_get_error(rp)) {
|
|
if (qemu_file_get_error(rp)) {
|
|
- mark_source_rp_bad(ms);
|
|
|
|
|
|
+ qemu_file_get_error_obj(rp, &err);
|
|
goto out;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
|
|
if (header_type >= MIG_RP_MSG_MAX ||
|
|
if (header_type >= MIG_RP_MSG_MAX ||
|
|
header_type == MIG_RP_MSG_INVALID) {
|
|
header_type == MIG_RP_MSG_INVALID) {
|
|
- error_report("RP: Received invalid message 0x%04x length 0x%04x",
|
|
|
|
- header_type, header_len);
|
|
|
|
- mark_source_rp_bad(ms);
|
|
|
|
|
|
+ error_setg(&err, "Received invalid message 0x%04x length 0x%04x",
|
|
|
|
+ header_type, header_len);
|
|
goto out;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
|
|
if ((rp_cmd_args[header_type].len != -1 &&
|
|
if ((rp_cmd_args[header_type].len != -1 &&
|
|
header_len != rp_cmd_args[header_type].len) ||
|
|
header_len != rp_cmd_args[header_type].len) ||
|
|
header_len > sizeof(buf)) {
|
|
header_len > sizeof(buf)) {
|
|
- error_report("RP: Received '%s' message (0x%04x) with"
|
|
|
|
- "incorrect length %d expecting %zu",
|
|
|
|
- rp_cmd_args[header_type].name, header_type, header_len,
|
|
|
|
- (size_t)rp_cmd_args[header_type].len);
|
|
|
|
- mark_source_rp_bad(ms);
|
|
|
|
|
|
+ error_setg(&err, "Received '%s' message (0x%04x) with"
|
|
|
|
+ "incorrect length %d expecting %zu",
|
|
|
|
+ rp_cmd_args[header_type].name, header_type, header_len,
|
|
|
|
+ (size_t)rp_cmd_args[header_type].len);
|
|
goto out;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
|
|
/* We know we've got a valid header by this point */
|
|
/* We know we've got a valid header by this point */
|
|
res = qemu_get_buffer(rp, buf, header_len);
|
|
res = qemu_get_buffer(rp, buf, header_len);
|
|
if (res != header_len) {
|
|
if (res != header_len) {
|
|
- error_report("RP: Failed reading data for message 0x%04x"
|
|
|
|
- " read %d expected %d",
|
|
|
|
- header_type, res, header_len);
|
|
|
|
- mark_source_rp_bad(ms);
|
|
|
|
|
|
+ error_setg(&err, "Failed reading data for message 0x%04x"
|
|
|
|
+ " read %d expected %d",
|
|
|
|
+ header_type, res, header_len);
|
|
goto out;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1970,8 +2224,7 @@ static void *source_return_path_thread(void *opaque)
|
|
sibling_error = ldl_be_p(buf);
|
|
sibling_error = ldl_be_p(buf);
|
|
trace_source_return_path_thread_shut(sibling_error);
|
|
trace_source_return_path_thread_shut(sibling_error);
|
|
if (sibling_error) {
|
|
if (sibling_error) {
|
|
- error_report("RP: Sibling indicated error %d", sibling_error);
|
|
|
|
- mark_source_rp_bad(ms);
|
|
|
|
|
|
+ error_setg(&err, "Sibling indicated error %d", sibling_error);
|
|
}
|
|
}
|
|
/*
|
|
/*
|
|
* We'll let the main thread deal with closing the RP
|
|
* We'll let the main thread deal with closing the RP
|
|
@@ -1989,7 +2242,10 @@ static void *source_return_path_thread(void *opaque)
|
|
case MIG_RP_MSG_REQ_PAGES:
|
|
case MIG_RP_MSG_REQ_PAGES:
|
|
start = ldq_be_p(buf);
|
|
start = ldq_be_p(buf);
|
|
len = ldl_be_p(buf + 8);
|
|
len = ldl_be_p(buf + 8);
|
|
- migrate_handle_rp_req_pages(ms, NULL, start, len);
|
|
|
|
|
|
+ migrate_handle_rp_req_pages(ms, NULL, start, len, &err);
|
|
|
|
+ if (err) {
|
|
|
|
+ goto out;
|
|
|
|
+ }
|
|
break;
|
|
break;
|
|
|
|
|
|
case MIG_RP_MSG_REQ_PAGES_ID:
|
|
case MIG_RP_MSG_REQ_PAGES_ID:
|
|
@@ -2004,32 +2260,32 @@ static void *source_return_path_thread(void *opaque)
|
|
expected_len += tmp32;
|
|
expected_len += tmp32;
|
|
}
|
|
}
|
|
if (header_len != expected_len) {
|
|
if (header_len != expected_len) {
|
|
- error_report("RP: Req_Page_id with length %d expecting %zd",
|
|
|
|
- header_len, expected_len);
|
|
|
|
- mark_source_rp_bad(ms);
|
|
|
|
|
|
+ error_setg(&err, "Req_Page_id with length %d expecting %zd",
|
|
|
|
+ header_len, expected_len);
|
|
|
|
+ goto out;
|
|
|
|
+ }
|
|
|
|
+ migrate_handle_rp_req_pages(ms, (char *)&buf[13], start, len,
|
|
|
|
+ &err);
|
|
|
|
+ if (err) {
|
|
goto out;
|
|
goto out;
|
|
}
|
|
}
|
|
- migrate_handle_rp_req_pages(ms, (char *)&buf[13], start, len);
|
|
|
|
break;
|
|
break;
|
|
|
|
|
|
case MIG_RP_MSG_RECV_BITMAP:
|
|
case MIG_RP_MSG_RECV_BITMAP:
|
|
if (header_len < 1) {
|
|
if (header_len < 1) {
|
|
- error_report("%s: missing block name", __func__);
|
|
|
|
- mark_source_rp_bad(ms);
|
|
|
|
|
|
+ error_setg(&err, "MIG_RP_MSG_RECV_BITMAP missing block name");
|
|
goto out;
|
|
goto out;
|
|
}
|
|
}
|
|
/* Format: len (1B) + idstr (<255B). This ends the idstr. */
|
|
/* Format: len (1B) + idstr (<255B). This ends the idstr. */
|
|
buf[buf[0] + 1] = '\0';
|
|
buf[buf[0] + 1] = '\0';
|
|
- if (migrate_handle_rp_recv_bitmap(ms, (char *)(buf + 1))) {
|
|
|
|
- mark_source_rp_bad(ms);
|
|
|
|
|
|
+ if (!migrate_handle_rp_recv_bitmap(ms, (char *)(buf + 1), &err)) {
|
|
goto out;
|
|
goto out;
|
|
}
|
|
}
|
|
break;
|
|
break;
|
|
|
|
|
|
case MIG_RP_MSG_RESUME_ACK:
|
|
case MIG_RP_MSG_RESUME_ACK:
|
|
tmp32 = ldl_be_p(buf);
|
|
tmp32 = ldl_be_p(buf);
|
|
- if (migrate_handle_rp_resume_ack(ms, tmp32)) {
|
|
|
|
- mark_source_rp_bad(ms);
|
|
|
|
|
|
+ if (!migrate_handle_rp_resume_ack(ms, tmp32, &err)) {
|
|
goto out;
|
|
goto out;
|
|
}
|
|
}
|
|
break;
|
|
break;
|
|
@@ -2045,13 +2301,29 @@ static void *source_return_path_thread(void *opaque)
|
|
}
|
|
}
|
|
|
|
|
|
out:
|
|
out:
|
|
- if (qemu_file_get_error(rp)) {
|
|
|
|
|
|
+ if (err) {
|
|
|
|
+ migrate_set_error(ms, err);
|
|
|
|
+ error_free(err);
|
|
trace_source_return_path_thread_bad_end();
|
|
trace_source_return_path_thread_bad_end();
|
|
- mark_source_rp_bad(ms);
|
|
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (ms->state == MIGRATION_STATUS_POSTCOPY_RECOVER) {
|
|
|
|
+ /*
|
|
|
|
+ * this will be extremely unlikely: that we got yet another network
|
|
|
|
+ * issue during recovering of the 1st network failure.. during this
|
|
|
|
+ * period the main migration thread can be waiting on rp_sem for
|
|
|
|
+ * this thread to sync with the other side.
|
|
|
|
+ *
|
|
|
|
+ * When this happens, explicitly kick the migration thread out of
|
|
|
|
+ * RECOVER stage and back to PAUSED, so the admin can try
|
|
|
|
+ * everything again.
|
|
|
|
+ */
|
|
|
|
+ migration_rp_kick(ms);
|
|
}
|
|
}
|
|
|
|
|
|
trace_source_return_path_thread_end();
|
|
trace_source_return_path_thread_end();
|
|
rcu_unregister_thread();
|
|
rcu_unregister_thread();
|
|
|
|
+
|
|
return NULL;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -2073,12 +2345,11 @@ static int open_return_path_on_source(MigrationState *ms)
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
-static int close_return_path_on_source(MigrationState *ms)
|
|
|
|
|
|
+/* Return true if error detected, or false otherwise */
|
|
|
|
+static bool close_return_path_on_source(MigrationState *ms)
|
|
{
|
|
{
|
|
- int ret;
|
|
|
|
-
|
|
|
|
if (!ms->rp_state.rp_thread_created) {
|
|
if (!ms->rp_state.rp_thread_created) {
|
|
- return 0;
|
|
|
|
|
|
+ return false;
|
|
}
|
|
}
|
|
|
|
|
|
trace_migration_return_path_end_before();
|
|
trace_migration_return_path_end_before();
|
|
@@ -2096,18 +2367,13 @@ static int close_return_path_on_source(MigrationState *ms)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- trace_await_return_path_close_on_source_joining();
|
|
|
|
qemu_thread_join(&ms->rp_state.rp_thread);
|
|
qemu_thread_join(&ms->rp_state.rp_thread);
|
|
ms->rp_state.rp_thread_created = false;
|
|
ms->rp_state.rp_thread_created = false;
|
|
- trace_await_return_path_close_on_source_close();
|
|
|
|
-
|
|
|
|
- ret = ms->rp_state.error;
|
|
|
|
- ms->rp_state.error = false;
|
|
|
|
-
|
|
|
|
migration_release_dst_files(ms);
|
|
migration_release_dst_files(ms);
|
|
|
|
+ trace_migration_return_path_end_after();
|
|
|
|
|
|
- trace_migration_return_path_end_after(ret);
|
|
|
|
- return ret;
|
|
|
|
|
|
+ /* Return path will persist the error in MigrationState when quit */
|
|
|
|
+ return migrate_has_error(ms);
|
|
}
|
|
}
|
|
|
|
|
|
static inline void
|
|
static inline void
|
|
@@ -2126,7 +2392,6 @@ static int postcopy_start(MigrationState *ms, Error **errp)
|
|
int ret;
|
|
int ret;
|
|
QIOChannelBuffer *bioc;
|
|
QIOChannelBuffer *bioc;
|
|
QEMUFile *fb;
|
|
QEMUFile *fb;
|
|
- int64_t time_at_stop = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
|
|
|
|
uint64_t bandwidth = migrate_max_postcopy_bandwidth();
|
|
uint64_t bandwidth = migrate_max_postcopy_bandwidth();
|
|
bool restart_block = false;
|
|
bool restart_block = false;
|
|
int cur_state = MIGRATION_STATUS_ACTIVE;
|
|
int cur_state = MIGRATION_STATUS_ACTIVE;
|
|
@@ -2148,9 +2413,11 @@ static int postcopy_start(MigrationState *ms, Error **errp)
|
|
qemu_mutex_lock_iothread();
|
|
qemu_mutex_lock_iothread();
|
|
trace_postcopy_start_set_run();
|
|
trace_postcopy_start_set_run();
|
|
|
|
|
|
|
|
+ migration_downtime_start(ms);
|
|
|
|
+
|
|
qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL);
|
|
qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL);
|
|
global_state_store();
|
|
global_state_store();
|
|
- ret = vm_stop_force_state(RUN_STATE_FINISH_MIGRATE);
|
|
|
|
|
|
+ ret = migration_stop_vm(RUN_STATE_FINISH_MIGRATE);
|
|
if (ret < 0) {
|
|
if (ret < 0) {
|
|
goto fail;
|
|
goto fail;
|
|
}
|
|
}
|
|
@@ -2250,7 +2517,7 @@ static int postcopy_start(MigrationState *ms, Error **errp)
|
|
ms->postcopy_after_devices = true;
|
|
ms->postcopy_after_devices = true;
|
|
migration_call_notifiers(ms);
|
|
migration_call_notifiers(ms);
|
|
|
|
|
|
- ms->downtime = qemu_clock_get_ms(QEMU_CLOCK_REALTIME) - time_at_stop;
|
|
|
|
|
|
+ migration_downtime_end(ms);
|
|
|
|
|
|
qemu_mutex_unlock_iothread();
|
|
qemu_mutex_unlock_iothread();
|
|
|
|
|
|
@@ -2346,13 +2613,13 @@ static int migration_completion_precopy(MigrationState *s,
|
|
int ret;
|
|
int ret;
|
|
|
|
|
|
qemu_mutex_lock_iothread();
|
|
qemu_mutex_lock_iothread();
|
|
- s->downtime_start = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
|
|
|
|
|
|
+ migration_downtime_start(s);
|
|
qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL);
|
|
qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL);
|
|
|
|
|
|
s->vm_old_state = runstate_get();
|
|
s->vm_old_state = runstate_get();
|
|
global_state_store();
|
|
global_state_store();
|
|
|
|
|
|
- ret = vm_stop_force_state(RUN_STATE_FINISH_MIGRATE);
|
|
|
|
|
|
+ ret = migration_stop_vm(RUN_STATE_FINISH_MIGRATE);
|
|
trace_migration_completion_vm_stop(ret);
|
|
trace_migration_completion_vm_stop(ret);
|
|
if (ret < 0) {
|
|
if (ret < 0) {
|
|
goto out_unlock;
|
|
goto out_unlock;
|
|
@@ -2519,7 +2786,9 @@ static int postcopy_resume_handshake(MigrationState *s)
|
|
qemu_savevm_send_postcopy_resume(s->to_dst_file);
|
|
qemu_savevm_send_postcopy_resume(s->to_dst_file);
|
|
|
|
|
|
while (s->state == MIGRATION_STATUS_POSTCOPY_RECOVER) {
|
|
while (s->state == MIGRATION_STATUS_POSTCOPY_RECOVER) {
|
|
- migration_rp_wait(s);
|
|
|
|
|
|
+ if (migration_rp_wait(s)) {
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
if (s->state == MIGRATION_STATUS_POSTCOPY_ACTIVE) {
|
|
if (s->state == MIGRATION_STATUS_POSTCOPY_ACTIVE) {
|
|
@@ -2703,15 +2972,8 @@ static void migration_calculate_complete(MigrationState *s)
|
|
int64_t end_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
|
|
int64_t end_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
|
|
int64_t transfer_time;
|
|
int64_t transfer_time;
|
|
|
|
|
|
|
|
+ migration_downtime_end(s);
|
|
s->total_time = end_time - s->start_time;
|
|
s->total_time = end_time - s->start_time;
|
|
- if (!s->downtime) {
|
|
|
|
- /*
|
|
|
|
- * It's still not set, so we are precopy migration. For
|
|
|
|
- * postcopy, downtime is calculated during postcopy_start().
|
|
|
|
- */
|
|
|
|
- s->downtime = end_time - s->downtime_start;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
transfer_time = s->total_time - s->setup_time;
|
|
transfer_time = s->total_time - s->setup_time;
|
|
if (transfer_time) {
|
|
if (transfer_time) {
|
|
s->mbps = ((double) bytes * 8.0) / transfer_time / 1000;
|
|
s->mbps = ((double) bytes * 8.0) / transfer_time / 1000;
|
|
@@ -3130,7 +3392,7 @@ static void bg_migration_vm_start_bh(void *opaque)
|
|
s->vm_start_bh = NULL;
|
|
s->vm_start_bh = NULL;
|
|
|
|
|
|
vm_start();
|
|
vm_start();
|
|
- s->downtime = qemu_clock_get_ms(QEMU_CLOCK_REALTIME) - s->downtime_start;
|
|
|
|
|
|
+ migration_downtime_end(s);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -3197,7 +3459,7 @@ static void *bg_migration_thread(void *opaque)
|
|
s->setup_time = qemu_clock_get_ms(QEMU_CLOCK_HOST) - setup_start;
|
|
s->setup_time = qemu_clock_get_ms(QEMU_CLOCK_HOST) - setup_start;
|
|
|
|
|
|
trace_migration_thread_setup_complete();
|
|
trace_migration_thread_setup_complete();
|
|
- s->downtime_start = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
|
|
|
|
|
|
+ migration_downtime_start(s);
|
|
|
|
|
|
qemu_mutex_lock_iothread();
|
|
qemu_mutex_lock_iothread();
|
|
|
|
|
|
@@ -3210,7 +3472,7 @@ static void *bg_migration_thread(void *opaque)
|
|
|
|
|
|
global_state_store();
|
|
global_state_store();
|
|
/* Forcibly stop VM before saving state of vCPUs and devices */
|
|
/* Forcibly stop VM before saving state of vCPUs and devices */
|
|
- if (vm_stop_force_state(RUN_STATE_PAUSED)) {
|
|
|
|
|
|
+ if (migration_stop_vm(RUN_STATE_PAUSED)) {
|
|
goto fail;
|
|
goto fail;
|
|
}
|
|
}
|
|
/*
|
|
/*
|