diff --git a/hmp.c b/hmp.c index a4b1d3d220840536a07b0edc70149395ffc14464..4b8e9878b17c45f4ce1fbb26e3cc6c5b1d17b0a4 100644 --- a/hmp.c +++ b/hmp.c @@ -217,6 +217,10 @@ void hmp_info_migrate(Monitor *mon, const QDict *qdict) monitor_printf(mon, "dirty pages rate: %" PRIu64 " pages\n", info->ram->dirty_pages_rate); } + if (info->ram->postcopy_requests) { + monitor_printf(mon, "postcopy request count: %" PRIu64 "\n", + info->ram->postcopy_requests); + } } if (info->has_disk) { diff --git a/include/migration/migration.h b/include/migration/migration.h index 13b12b7e87530f3b86122c992356af8e67996e8c..3c96623d3dfbf57eb7f4a49fff13acae7ce78560 100644 --- a/include/migration/migration.h +++ b/include/migration/migration.h @@ -160,6 +160,8 @@ struct MigrationState int64_t xbzrle_cache_size; int64_t setup_time; int64_t dirty_sync_count; + /* Count of requests incoming from destination */ + int64_t postcopy_requests; /* Flag set once the migration has been asked to enter postcopy */ bool start_postcopy; @@ -181,25 +183,25 @@ struct MigrationState void migrate_set_state(int *state, int old_state, int new_state); -void process_incoming_migration(QEMUFile *f); +void migration_fd_process_incoming(QEMUFile *f); void qemu_start_incoming_migration(const char *uri, Error **errp); -void migration_set_incoming_channel(MigrationState *s, - QIOChannel *ioc); +void migration_channel_process_incoming(MigrationState *s, + QIOChannel *ioc); -void migration_tls_set_incoming_channel(MigrationState *s, - QIOChannel *ioc, - Error **errp); +void migration_tls_channel_process_incoming(MigrationState *s, + QIOChannel *ioc, + Error **errp); -void migration_set_outgoing_channel(MigrationState *s, - QIOChannel *ioc, - const char *hostname); +void migration_channel_connect(MigrationState *s, + QIOChannel *ioc, + const char *hostname); -void migration_tls_set_outgoing_channel(MigrationState *s, - QIOChannel *ioc, - const char *hostname, - Error **errp); +void migration_tls_channel_connect(MigrationState *s, + QIOChannel *ioc, + const char *hostname, + Error **errp); uint64_t migrate_max_downtime(void); diff --git a/migration/exec.c b/migration/exec.c index 1515cc331998258bcc206626455debf8772b1d95..2af63cced6d7145205a320056aaaed1685e1722c 100644 --- a/migration/exec.c +++ b/migration/exec.c @@ -38,7 +38,7 @@ void exec_start_outgoing_migration(MigrationState *s, const char *command, Error return; } - migration_set_outgoing_channel(s, ioc, NULL); + migration_channel_connect(s, ioc, NULL); object_unref(OBJECT(ioc)); } @@ -46,7 +46,7 @@ static gboolean exec_accept_incoming_migration(QIOChannel *ioc, GIOCondition condition, gpointer opaque) { - migration_set_incoming_channel(migrate_get_current(), ioc); + migration_channel_process_incoming(migrate_get_current(), ioc); object_unref(OBJECT(ioc)); return FALSE; /* unregister */ } diff --git a/migration/fd.c b/migration/fd.c index fc5c9eee02d4d30625b7d3f4caca6941a7412f1d..84a10fd68f0967978404a9d62d57e8cb0fb1ae8e 100644 --- a/migration/fd.c +++ b/migration/fd.c @@ -38,7 +38,7 @@ void fd_start_outgoing_migration(MigrationState *s, const char *fdname, Error ** return; } - migration_set_outgoing_channel(s, ioc, NULL); + migration_channel_connect(s, ioc, NULL); object_unref(OBJECT(ioc)); } @@ -46,7 +46,7 @@ static gboolean fd_accept_incoming_migration(QIOChannel *ioc, GIOCondition condition, gpointer opaque) { - migration_set_incoming_channel(migrate_get_current(), ioc); + migration_channel_process_incoming(migrate_get_current(), ioc); object_unref(OBJECT(ioc)); return FALSE; /* unregister */ } diff --git a/migration/migration.c b/migration/migration.c index 7ecbadee6f99f5f291fc5a2f9361745a7a35d780..20f88757d8380e3458e919bc7da1ca8514b625e1 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -416,7 +416,7 @@ static void process_incoming_migration_co(void *opaque) qemu_bh_schedule(mis->bh); } -void process_incoming_migration(QEMUFile *f) +void migration_fd_process_incoming(QEMUFile *f) { Coroutine *co = qemu_coroutine_create(process_incoming_migration_co); @@ -426,8 +426,8 @@ void process_incoming_migration(QEMUFile *f) } -void migration_set_incoming_channel(MigrationState *s, - QIOChannel *ioc) +void migration_channel_process_incoming(MigrationState *s, + QIOChannel *ioc) { trace_migration_set_incoming_channel( ioc, object_get_typename(OBJECT(ioc))); @@ -436,20 +436,20 @@ void migration_set_incoming_channel(MigrationState *s, !object_dynamic_cast(OBJECT(ioc), TYPE_QIO_CHANNEL_TLS)) { Error *local_err = NULL; - migration_tls_set_incoming_channel(s, ioc, &local_err); + migration_tls_channel_process_incoming(s, ioc, &local_err); if (local_err) { error_report_err(local_err); } } else { QEMUFile *f = qemu_fopen_channel_input(ioc); - process_incoming_migration(f); + migration_fd_process_incoming(f); } } -void migration_set_outgoing_channel(MigrationState *s, - QIOChannel *ioc, - const char *hostname) +void migration_channel_connect(MigrationState *s, + QIOChannel *ioc, + const char *hostname) { trace_migration_set_outgoing_channel( ioc, object_get_typename(OBJECT(ioc)), hostname); @@ -458,7 +458,7 @@ void migration_set_outgoing_channel(MigrationState *s, !object_dynamic_cast(OBJECT(ioc), TYPE_QIO_CHANNEL_TLS)) { Error *local_err = NULL; - migration_tls_set_outgoing_channel(s, ioc, hostname, &local_err); + migration_tls_channel_connect(s, ioc, hostname, &local_err); if (local_err) { migrate_fd_error(s, local_err); error_free(local_err); @@ -602,6 +602,26 @@ static void get_xbzrle_cache_stats(MigrationInfo *info) } } +static void populate_ram_info(MigrationInfo *info, MigrationState *s) +{ + info->has_ram = true; + info->ram = g_malloc0(sizeof(*info->ram)); + info->ram->transferred = ram_bytes_transferred(); + info->ram->total = ram_bytes_total(); + info->ram->duplicate = dup_mig_pages_transferred(); + info->ram->skipped = skipped_mig_pages_transferred(); + info->ram->normal = norm_mig_pages_transferred(); + info->ram->normal_bytes = norm_mig_bytes_transferred(); + info->ram->mbps = s->mbps; + info->ram->dirty_sync_count = s->dirty_sync_count; + info->ram->postcopy_requests = s->postcopy_requests; + + if (s->state != MIGRATION_STATUS_COMPLETED) { + info->ram->remaining = ram_bytes_remaining(); + info->ram->dirty_pages_rate = s->dirty_pages_rate; + } +} + MigrationInfo *qmp_query_migrate(Error **errp) { MigrationInfo *info = g_malloc0(sizeof(*info)); @@ -626,18 +646,7 @@ MigrationInfo *qmp_query_migrate(Error **errp) info->has_setup_time = true; info->setup_time = s->setup_time; - 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->duplicate = dup_mig_pages_transferred(); - info->ram->skipped = skipped_mig_pages_transferred(); - info->ram->normal = norm_mig_pages_transferred(); - info->ram->normal_bytes = norm_mig_bytes_transferred(); - info->ram->dirty_pages_rate = s->dirty_pages_rate; - info->ram->mbps = s->mbps; - info->ram->dirty_sync_count = s->dirty_sync_count; + populate_ram_info(info, s); if (blk_mig_active()) { info->has_disk = true; @@ -665,18 +674,7 @@ MigrationInfo *qmp_query_migrate(Error **errp) info->has_setup_time = true; info->setup_time = s->setup_time; - 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->duplicate = dup_mig_pages_transferred(); - info->ram->skipped = skipped_mig_pages_transferred(); - info->ram->normal = norm_mig_pages_transferred(); - info->ram->normal_bytes = norm_mig_bytes_transferred(); - info->ram->dirty_pages_rate = s->dirty_pages_rate; - info->ram->mbps = s->mbps; - info->ram->dirty_sync_count = s->dirty_sync_count; + populate_ram_info(info, s); if (blk_mig_active()) { info->has_disk = true; @@ -699,17 +697,7 @@ MigrationInfo *qmp_query_migrate(Error **errp) info->has_setup_time = true; info->setup_time = s->setup_time; - 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->duplicate = dup_mig_pages_transferred(); - info->ram->skipped = skipped_mig_pages_transferred(); - info->ram->normal = norm_mig_pages_transferred(); - info->ram->normal_bytes = norm_mig_bytes_transferred(); - info->ram->mbps = s->mbps; - info->ram->dirty_sync_count = s->dirty_sync_count; + populate_ram_info(info, s); break; case MIGRATION_STATUS_FAILED: info->has_status = true; @@ -732,6 +720,7 @@ void qmp_migrate_set_capabilities(MigrationCapabilityStatusList *params, { MigrationState *s = migrate_get_current(); MigrationCapabilityStatusList *cap; + bool old_postcopy_cap = migrate_postcopy_ram(); if (migration_is_setup_or_active(s->state)) { error_setg(errp, QERR_MIGRATION_ACTIVE); @@ -754,6 +743,19 @@ void qmp_migrate_set_capabilities(MigrationCapabilityStatusList *params, s->enabled_capabilities[MIGRATION_CAPABILITY_POSTCOPY_RAM] = false; } + /* This check is reasonably expensive, so only when it's being + * set the first time, also it's only the destination that needs + * special support. + */ + if (!old_postcopy_cap && runstate_check(RUN_STATE_INMIGRATE) && + !postcopy_ram_supported_by_host()) { + /* postcopy_ram_supported_by_host will have emitted a more + * detailed message + */ + error_report("Postcopy is not supported"); + s->enabled_capabilities[MIGRATION_CAPABILITY_POSTCOPY_RAM] = + false; + } } } @@ -1004,6 +1006,7 @@ MigrationState *migrate_init(const MigrationParams *params) s->dirty_sync_count = 0; s->start_postcopy = false; s->postcopy_after_devices = false; + s->postcopy_requests = 0; s->migration_thread_running = false; s->last_req_rb = NULL; error_free(s->error); diff --git a/migration/ram.c b/migration/ram.c index 844ea4694f5c1da151bd6fbaa1410a0d1d52beba..42fb8ac6d6b663a0d918ff0290aceae37405a48b 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -1169,6 +1169,7 @@ int ram_save_queue_pages(MigrationState *ms, const char *rbname, { RAMBlock *ramblock; + ms->postcopy_requests++; rcu_read_lock(); if (!rbname) { /* Reuse last RAMBlock */ @@ -1557,7 +1558,9 @@ static int postcopy_send_discard_bm_ram(MigrationState *ms, } else { discard_length = zero - one; } - postcopy_discard_send_range(ms, pds, one, discard_length); + if (discard_length) { + postcopy_discard_send_range(ms, pds, one, discard_length); + } current = one + discard_length; } else { current = one; diff --git a/migration/rdma.c b/migration/rdma.c index 51bafc702bdd1eb2492a02eedf0b9f06545f66b6..5110ec828d26fb6da710216f7618276290b6ab91 100644 --- a/migration/rdma.c +++ b/migration/rdma.c @@ -1511,7 +1511,7 @@ static int qemu_rdma_block_for_wrid(RDMAContext *rdma, int wrid_requested, while (1) { /* - * Coroutine doesn't start until process_incoming_migration() + * Coroutine doesn't start until migration_fd_process_incoming() * so don't yield unless we know we're running inside of a coroutine. */ if (rdma->migration_started_on_destination) { @@ -3620,7 +3620,7 @@ static void rdma_accept_incoming_migration(void *opaque) } rdma->migration_started_on_destination = 1; - process_incoming_migration(f); + migration_fd_process_incoming(f); } void rdma_start_incoming_migration(const char *host_port, Error **errp) diff --git a/migration/socket.c b/migration/socket.c index 977a8d3c1d89ded71e449baaafd22ea06533ab50..5c0a38f7b9b6b1b52462bdd95336918a16c17668 100644 --- a/migration/socket.c +++ b/migration/socket.c @@ -83,7 +83,7 @@ static void socket_outgoing_migration(Object *src, migrate_fd_error(data->s, err); } else { trace_migration_socket_outgoing_connected(data->hostname); - migration_set_outgoing_channel(data->s, sioc, data->hostname); + migration_channel_connect(data->s, sioc, data->hostname); } object_unref(src); } @@ -140,8 +140,8 @@ static gboolean socket_accept_incoming_migration(QIOChannel *ioc, trace_migration_socket_incoming_accepted(); - migration_set_incoming_channel(migrate_get_current(), - QIO_CHANNEL(sioc)); + migration_channel_process_incoming(migrate_get_current(), + QIO_CHANNEL(sioc)); object_unref(OBJECT(sioc)); out: diff --git a/migration/tls.c b/migration/tls.c index 75f959ff9c2a59cc86b5b02331768e1afc8c6260..12c053d15f383d997de49b9ac53743e4529bd838 100644 --- a/migration/tls.c +++ b/migration/tls.c @@ -72,14 +72,14 @@ static void migration_tls_incoming_handshake(Object *src, error_report("%s", error_get_pretty(err)); } else { trace_migration_tls_incoming_handshake_complete(); - migration_set_incoming_channel(migrate_get_current(), ioc); + migration_channel_process_incoming(migrate_get_current(), ioc); } object_unref(OBJECT(ioc)); } -void migration_tls_set_incoming_channel(MigrationState *s, - QIOChannel *ioc, - Error **errp) +void migration_tls_channel_process_incoming(MigrationState *s, + QIOChannel *ioc, + Error **errp) { QCryptoTLSCreds *creds; QIOChannelTLS *tioc; @@ -119,16 +119,16 @@ static void migration_tls_outgoing_handshake(Object *src, migrate_fd_error(s, err); } else { trace_migration_tls_outgoing_handshake_complete(); - migration_set_outgoing_channel(s, ioc, NULL); + migration_channel_connect(s, ioc, NULL); } object_unref(OBJECT(ioc)); } -void migration_tls_set_outgoing_channel(MigrationState *s, - QIOChannel *ioc, - const char *hostname, - Error **errp) +void migration_tls_channel_connect(MigrationState *s, + QIOChannel *ioc, + const char *hostname, + Error **errp) { QCryptoTLSCreds *creds; QIOChannelTLS *tioc; diff --git a/qapi-schema.json b/qapi-schema.json index 48c3a6f5cd8c44a6b01a047274c1247fbd6dce74..40b1db427165a2dd24f7501414c60b268eadf670 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -382,13 +382,17 @@ # # @dirty-sync-count: number of times that dirty ram was synchronized (since 2.1) # +# @postcopy-requests: The number of page requests received from the destination +# (since 2.7) +# # Since: 0.14.0 ## { 'struct': 'MigrationStats', 'data': {'transferred': 'int', 'remaining': 'int', 'total': 'int' , 'duplicate': 'int', 'skipped': 'int', 'normal': 'int', 'normal-bytes': 'int', 'dirty-pages-rate' : 'int', - 'mbps' : 'number', 'dirty-sync-count' : 'int' } } + 'mbps' : 'number', 'dirty-sync-count' : 'int', + 'postcopy-requests' : 'int' } } ## # @XBZRLECacheStats @@ -486,7 +490,7 @@ # # @error-desc: #optional the human readable error description string, when # @status is 'failed'. Clients should not attempt to parse the -# error strings. (Since 2.6) +# error strings. (Since 2.7) # # Since: 0.14.0 ## @@ -631,7 +635,7 @@ # migration URI does not already include a hostname. For # example if using fd: or exec: based migration, the # hostname must be provided so that the server's x509 -# certificate identity canbe validated. (Since 2.7) +# certificate identity can be validated. (Since 2.7) # # Since: 2.4 ## @@ -672,7 +676,7 @@ # migration URI does not already include a hostname. For # example if using fd: or exec: based migration, the # hostname must be provided so that the server's x509 -# certificate identity canbe validated. (Since 2.7) +# certificate identity can be validated. (Since 2.7) # # Since: 2.4 ## @@ -708,14 +712,14 @@ # be for a 'client' endpoint, while for the incoming side the # credentials must be for a 'server' endpoint. Setting this # will enable TLS for all migrations. The default is unset, -# resulting in unsecured migration at the QEMU level. (Since 2.6) +# resulting in unsecured migration at the QEMU level. (Since 2.7) # # @tls-hostname: hostname of the target host for the migration. This is # required when using x509 based TLS credentials and the # migration URI does not already include a hostname. For # example if using fd: or exec: based migration, the # hostname must be provided so that the server's x509 -# certificate identity canbe validated. (Since 2.6) +# certificate identity can be validated. (Since 2.7) # # Since: 2.4 ## diff --git a/tests/Makefile.include b/tests/Makefile.include index 7d63d16c3f516bbc0a9b67b16b91dcac4b73a97d..6135875c37f6b134182fde845a7faf9beb0de38e 100644 --- a/tests/Makefile.include +++ b/tests/Makefile.include @@ -234,6 +234,7 @@ endif check-qtest-i386-y += tests/test-netfilter$(EXESUF) check-qtest-i386-y += tests/test-filter-mirror$(EXESUF) check-qtest-i386-y += tests/test-filter-redirector$(EXESUF) +check-qtest-i386-y += tests/postcopy-test$(EXESUF) check-qtest-x86_64-y = $(check-qtest-i386-y) gcov-files-i386-y += i386-softmmu/hw/timer/mc146818rtc.c gcov-files-x86_64-y = $(subst i386-softmmu/,x86_64-softmmu/,$(gcov-files-i386-y)) @@ -599,6 +600,7 @@ tests/usb-hcd-uhci-test$(EXESUF): tests/usb-hcd-uhci-test.o $(libqos-usb-obj-y) tests/usb-hcd-ehci-test$(EXESUF): tests/usb-hcd-ehci-test.o $(libqos-usb-obj-y) tests/usb-hcd-xhci-test$(EXESUF): tests/usb-hcd-xhci-test.o $(libqos-usb-obj-y) tests/pc-cpu-test$(EXESUF): tests/pc-cpu-test.o +tests/postcopy-test$(EXESUF): tests/postcopy-test.o tests/vhost-user-test$(EXESUF): tests/vhost-user-test.o qemu-char.o qemu-timer.o $(qtest-obj-y) $(test-io-obj-y) tests/qemu-iotests/socket_scm_helper$(EXESUF): tests/qemu-iotests/socket_scm_helper.o tests/test-qemu-opts$(EXESUF): tests/test-qemu-opts.o $(test-util-obj-y) diff --git a/tests/libqtest.c b/tests/libqtest.c index 5c8234826595a4d3e7778fa1a428b8353ed98142..eb00f1392bad081e62f1a6ecbd1a0de65f8f8868 100644 --- a/tests/libqtest.c +++ b/tests/libqtest.c @@ -26,7 +26,7 @@ #include "qapi/qmp/qjson.h" #define MAX_IRQ 256 -#define SOCKET_TIMEOUT 5 +#define SOCKET_TIMEOUT 50 QTestState *global_qtest; diff --git a/tests/postcopy-test.c b/tests/postcopy-test.c new file mode 100644 index 0000000000000000000000000000000000000000..9ff88eea0fe220c8ab755f30d90def5c393ccd31 --- /dev/null +++ b/tests/postcopy-test.c @@ -0,0 +1,455 @@ +/* + * QTest testcase for postcopy + * + * Copyright (c) 2016 Red Hat, Inc. and/or its affiliates + * based on the vhost-user-test.c that is: + * Copyright (c) 2014 Virtual Open Systems Sarl. + * + * 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 + +#include "libqtest.h" +#include "qemu/option.h" +#include "qemu/range.h" +#include "sysemu/char.h" +#include "sysemu/sysemu.h" + +#include + +const unsigned start_address = 1024 * 1024; +const unsigned end_address = 100 * 1024 * 1024; +bool got_stop; + +#if defined(__linux__) +#include +#include +#include +#endif + +#if defined(__linux__) && defined(__NR_userfaultfd) && defined(CONFIG_EVENTFD) +#include +#include +#include + +static bool ufd_version_check(void) +{ + struct uffdio_api api_struct; + uint64_t ioctl_mask; + + int ufd = ufd = syscall(__NR_userfaultfd, O_CLOEXEC); + + if (ufd == -1) { + g_test_message("Skipping test: userfaultfd not available"); + return false; + } + + api_struct.api = UFFD_API; + api_struct.features = 0; + if (ioctl(ufd, UFFDIO_API, &api_struct)) { + g_test_message("Skipping test: UFFDIO_API failed"); + return false; + } + + ioctl_mask = (__u64)1 << _UFFDIO_REGISTER | + (__u64)1 << _UFFDIO_UNREGISTER; + if ((api_struct.ioctls & ioctl_mask) != ioctl_mask) { + g_test_message("Skipping test: Missing userfault feature"); + return false; + } + + return true; +} + +#else +static bool ufd_version_check(void) +{ + g_test_message("Skipping test: Userfault not available (builtdtime)"); + return false; +} + +#endif + +static const char *tmpfs; + +/* A simple PC boot sector that modifies memory (1-100MB) quickly + * outputing a 'B' every so often if it's still running. + */ +unsigned char bootsect[] = { + 0xfa, 0x0f, 0x01, 0x16, 0x74, 0x7c, 0x66, 0xb8, 0x01, 0x00, 0x00, 0x00, + 0x0f, 0x22, 0xc0, 0x66, 0xea, 0x20, 0x7c, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0x92, 0x0c, 0x02, + 0xe6, 0x92, 0xb8, 0x10, 0x00, 0x00, 0x00, 0x8e, 0xd8, 0x66, 0xb8, 0x41, + 0x00, 0x66, 0xba, 0xf8, 0x03, 0xee, 0xb3, 0x00, 0xb8, 0x00, 0x00, 0x10, + 0x00, 0xfe, 0x00, 0x05, 0x00, 0x10, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x40, + 0x06, 0x7c, 0xf2, 0xfe, 0xc3, 0x75, 0xe9, 0x66, 0xb8, 0x42, 0x00, 0x66, + 0xba, 0xf8, 0x03, 0xee, 0xeb, 0xde, 0x66, 0x90, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x9a, 0xcf, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x92, 0xcf, 0x00, 0x27, 0x00, 0x5c, 0x7c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa +}; + +/* + * Wait for some output in the serial output file, + * we get an 'A' followed by an endless string of 'B's + * but on the destination we won't have the A. + */ +static void wait_for_serial(const char *side) +{ + char *serialpath = g_strdup_printf("%s/%s", tmpfs, side); + FILE *serialfile = fopen(serialpath, "r"); + + do { + int readvalue = fgetc(serialfile); + + switch (readvalue) { + case 'A': + /* Fine */ + break; + + case 'B': + /* It's alive! */ + fclose(serialfile); + g_free(serialpath); + return; + + case EOF: + fseek(serialfile, 0, SEEK_SET); + usleep(1000); + break; + + default: + fprintf(stderr, "Unexpected %d on %s serial\n", readvalue, side); + g_assert_not_reached(); + } + } while (true); +} + +/* + * Events can get in the way of responses we are actually waiting for. + */ +static QDict *return_or_event(QDict *response) +{ + const char *event_string; + if (!qdict_haskey(response, "event")) { + return response; + } + + /* OK, it was an event */ + event_string = qdict_get_str(response, "event"); + if (!strcmp(event_string, "STOP")) { + got_stop = true; + } + QDECREF(response); + return return_or_event(qtest_qmp_receive(global_qtest)); +} + + +/* + * It's tricky to use qemu's migration event capability with qtest, + * events suddenly appearing confuse the qmp()/hmp() responses. + * so wait for a couple of passes to have happened before + * going postcopy. + */ + +static uint64_t get_migration_pass(void) +{ + QDict *rsp, *rsp_return, *rsp_ram; + uint64_t result; + + rsp = return_or_event(qmp("{ 'execute': 'query-migrate' }")); + rsp_return = qdict_get_qdict(rsp, "return"); + if (!qdict_haskey(rsp_return, "ram")) { + /* Still in setup */ + result = 0; + } else { + rsp_ram = qdict_get_qdict(rsp_return, "ram"); + result = qdict_get_try_int(rsp_ram, "dirty-sync-count", 0); + QDECREF(rsp); + } + return result; +} + +static void wait_for_migration_complete(void) +{ + QDict *rsp, *rsp_return; + bool completed; + + do { + const char *status; + + rsp = return_or_event(qmp("{ 'execute': 'query-migrate' }")); + rsp_return = qdict_get_qdict(rsp, "return"); + status = qdict_get_str(rsp_return, "status"); + completed = strcmp(status, "completed") == 0; + g_assert_cmpstr(status, !=, "failed"); + QDECREF(rsp); + usleep(1000 * 100); + } while (!completed); +} + +static void wait_for_migration_pass(void) +{ + uint64_t initial_pass = get_migration_pass(); + uint64_t pass; + + /* Wait for the 1st sync */ + do { + initial_pass = get_migration_pass(); + if (got_stop || initial_pass) { + break; + } + usleep(1000 * 100); + } while (true); + + do { + usleep(1000 * 100); + pass = get_migration_pass(); + } while (pass == initial_pass && !got_stop); +} + +static void check_guests_ram(void) +{ + /* Our ASM test will have been incrementing one byte from each page from + * 1MB to <100MB in order. + * This gives us a constraint that any page's byte should be equal or less + * than the previous pages byte (mod 256); and they should all be equal + * except for one transition at the point where we meet the incrementer. + * (We're running this with the guest stopped). + */ + unsigned address; + uint8_t first_byte; + uint8_t last_byte; + bool hit_edge = false; + bool bad = false; + + qtest_memread(global_qtest, start_address, &first_byte, 1); + last_byte = first_byte; + + for (address = start_address + 4096; address < end_address; address += 4096) + { + uint8_t b; + qtest_memread(global_qtest, address, &b, 1); + if (b != last_byte) { + if (((b + 1) % 256) == last_byte && !hit_edge) { + /* This is OK, the guest stopped at the point of + * incrementing the previous page but didn't get + * to us yet. + */ + hit_edge = true; + } else { + fprintf(stderr, "Memory content inconsistency at %x" + " first_byte = %x last_byte = %x current = %x" + " hit_edge = %x\n", + address, first_byte, last_byte, b, hit_edge); + bad = true; + } + } + last_byte = b; + } + g_assert_false(bad); +} + +static void cleanup(const char *filename) +{ + char *path = g_strdup_printf("%s/%s", tmpfs, filename); + + unlink(path); +} + +static void test_migrate(void) +{ + char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs); + QTestState *global = global_qtest, *from, *to; + unsigned char dest_byte_a, dest_byte_b, dest_byte_c, dest_byte_d; + gchar *cmd; + QDict *rsp; + + char *bootpath = g_strdup_printf("%s/bootsect", tmpfs); + FILE *bootfile = fopen(bootpath, "wb"); + + got_stop = false; + g_assert_cmpint(fwrite(bootsect, 512, 1, bootfile), ==, 1); + fclose(bootfile); + + cmd = g_strdup_printf("-machine accel=kvm:tcg -m 150M" + " -name pcsource,debug-threads=on" + " -serial file:%s/src_serial" + " -drive file=%s,format=raw", + tmpfs, bootpath); + from = qtest_start(cmd); + g_free(cmd); + + cmd = g_strdup_printf("-machine accel=kvm:tcg -m 150M" + " -name pcdest,debug-threads=on" + " -serial file:%s/dest_serial" + " -drive file=%s,format=raw" + " -incoming %s", + tmpfs, bootpath, uri); + to = qtest_init(cmd); + g_free(cmd); + + global_qtest = from; + rsp = qmp("{ 'execute': 'migrate-set-capabilities'," + "'arguments': { " + "'capabilities': [ {" + "'capability': 'postcopy-ram'," + "'state': true } ] } }"); + g_assert(qdict_haskey(rsp, "return")); + QDECREF(rsp); + + global_qtest = to; + rsp = qmp("{ 'execute': 'migrate-set-capabilities'," + "'arguments': { " + "'capabilities': [ {" + "'capability': 'postcopy-ram'," + "'state': true } ] } }"); + g_assert(qdict_haskey(rsp, "return")); + QDECREF(rsp); + + /* We want to pick a speed slow enough that the test completes + * quickly, but that it doesn't complete precopy even on a slow + * machine, so also set the downtime. + */ + global_qtest = from; + rsp = qmp("{ 'execute': 'migrate_set_speed'," + "'arguments': { 'value': 100000000 } }"); + g_assert(qdict_haskey(rsp, "return")); + QDECREF(rsp); + + /* 1ms downtime - it should never finish precopy */ + rsp = qmp("{ 'execute': 'migrate_set_downtime'," + "'arguments': { 'value': 0.001 } }"); + g_assert(qdict_haskey(rsp, "return")); + QDECREF(rsp); + + + /* Wait for the first serial output from the source */ + wait_for_serial("src_serial"); + + cmd = g_strdup_printf("{ 'execute': 'migrate'," + "'arguments': { 'uri': '%s' } }", + uri); + rsp = qmp(cmd); + g_free(cmd); + g_assert(qdict_haskey(rsp, "return")); + QDECREF(rsp); + + wait_for_migration_pass(); + + rsp = return_or_event(qmp("{ 'execute': 'migrate-start-postcopy' }")); + g_assert(qdict_haskey(rsp, "return")); + QDECREF(rsp); + + if (!got_stop) { + qmp_eventwait("STOP"); + } + + global_qtest = to; + qmp_eventwait("RESUME"); + + wait_for_serial("dest_serial"); + global_qtest = from; + wait_for_migration_complete(); + + qtest_quit(from); + + global_qtest = to; + + qtest_memread(to, start_address, &dest_byte_a, 1); + + /* Destination still running, wait for a byte to change */ + do { + qtest_memread(to, start_address, &dest_byte_b, 1); + usleep(10 * 1000); + } while (dest_byte_a == dest_byte_b); + + qmp("{ 'execute' : 'stop'}"); + /* With it stopped, check nothing changes */ + qtest_memread(to, start_address, &dest_byte_c, 1); + sleep(1); + qtest_memread(to, start_address, &dest_byte_d, 1); + g_assert_cmpint(dest_byte_c, ==, dest_byte_d); + + check_guests_ram(); + + qtest_quit(to); + g_free(uri); + + global_qtest = global; + + cleanup("bootsect"); + cleanup("migsocket"); + cleanup("src_serial"); + cleanup("dest_serial"); +} + +int main(int argc, char **argv) +{ + char template[] = "/tmp/postcopy-test-XXXXXX"; + int ret; + + g_test_init(&argc, &argv, NULL); + + if (!ufd_version_check()) { + return 0; + } + + tmpfs = mkdtemp(template); + if (!tmpfs) { + g_test_message("mkdtemp on path (%s): %s\n", template, strerror(errno)); + } + g_assert(tmpfs); + + module_call_init(MODULE_INIT_QOM); + + qtest_add_func("/postcopy", test_migrate); + + ret = g_test_run(); + + g_assert_cmpint(ret, ==, 0); + + ret = rmdir(tmpfs); + if (ret != 0) { + g_test_message("unable to rmdir: path (%s): %s\n", + tmpfs, strerror(errno)); + } + + return ret; +}