瀏覽代碼

Merge remote-tracking branch 'remotes/amit-migration/tags/migration-for-2.7-4' into staging

Migration:

- Fixes for TLS series
- Postcopy: Add stats, fix, test case

# gpg: Signature made Thu 16 Jun 2016 05:40:09 BST
# gpg:                using RSA key 0xEB0B4DFC657EF670
# gpg: Good signature from "Amit Shah <amit@amitshah.net>"
# gpg:                 aka "Amit Shah <amit@kernel.org>"
# gpg:                 aka "Amit Shah <amitshah@gmx.net>"
# Primary key fingerprint: 48CA 3722 5FE7 F4A8 B337  2735 1E9A 3B5F 8540 83B6
#      Subkey fingerprint: CC63 D332 AB8F 4617 4529  6534 EB0B 4DFC 657E F670

* remotes/amit-migration/tags/migration-for-2.7-4:
  migration: rename functions to starting migrations
  migration: fix typos in qapi-schema from latest migration additions
  Postcopy: Check for support when setting the capability
  tests: fix libqtest socket timeouts
  test: Postcopy
  Postcopy: Add stats on page requests
  Migration: Split out ram part of qmp_query_migrate
  Postcopy: Avoid 0 length discards

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Peter Maydell 9 年之前
父節點
當前提交
a66370b08d
共有 13 個文件被更改,包括 556 次插入83 次删除
  1. 4 0
      hmp.c
  2. 15 13
      include/migration/migration.h
  3. 2 2
      migration/exec.c
  4. 2 2
      migration/fd.c
  5. 47 44
      migration/migration.c
  6. 4 1
      migration/ram.c
  7. 2 2
      migration/rdma.c
  8. 3 3
      migration/socket.c
  9. 9 9
      migration/tls.c
  10. 10 6
      qapi-schema.json
  11. 2 0
      tests/Makefile.include
  12. 1 1
      tests/libqtest.c
  13. 455 0
      tests/postcopy-test.c

+ 4 - 0
hmp.c

@@ -217,6 +217,10 @@ void hmp_info_migrate(Monitor *mon, const QDict *qdict)
             monitor_printf(mon, "dirty pages rate: %" PRIu64 " pages\n",
             monitor_printf(mon, "dirty pages rate: %" PRIu64 " pages\n",
                            info->ram->dirty_pages_rate);
                            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) {
     if (info->has_disk) {

+ 15 - 13
include/migration/migration.h

@@ -160,6 +160,8 @@ struct MigrationState
     int64_t xbzrle_cache_size;
     int64_t xbzrle_cache_size;
     int64_t setup_time;
     int64_t setup_time;
     int64_t dirty_sync_count;
     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 */
     /* Flag set once the migration has been asked to enter postcopy */
     bool start_postcopy;
     bool start_postcopy;
@@ -181,25 +183,25 @@ struct MigrationState
 
 
 void migrate_set_state(int *state, int old_state, int new_state);
 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 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);
 uint64_t migrate_max_downtime(void);
 
 

+ 2 - 2
migration/exec.c

@@ -38,7 +38,7 @@ void exec_start_outgoing_migration(MigrationState *s, const char *command, Error
         return;
         return;
     }
     }
 
 
-    migration_set_outgoing_channel(s, ioc, NULL);
+    migration_channel_connect(s, ioc, NULL);
     object_unref(OBJECT(ioc));
     object_unref(OBJECT(ioc));
 }
 }
 
 
@@ -46,7 +46,7 @@ static gboolean exec_accept_incoming_migration(QIOChannel *ioc,
                                                GIOCondition condition,
                                                GIOCondition condition,
                                                gpointer opaque)
                                                gpointer opaque)
 {
 {
-    migration_set_incoming_channel(migrate_get_current(), ioc);
+    migration_channel_process_incoming(migrate_get_current(), ioc);
     object_unref(OBJECT(ioc));
     object_unref(OBJECT(ioc));
     return FALSE; /* unregister */
     return FALSE; /* unregister */
 }
 }

+ 2 - 2
migration/fd.c

@@ -38,7 +38,7 @@ void fd_start_outgoing_migration(MigrationState *s, const char *fdname, Error **
         return;
         return;
     }
     }
 
 
-    migration_set_outgoing_channel(s, ioc, NULL);
+    migration_channel_connect(s, ioc, NULL);
     object_unref(OBJECT(ioc));
     object_unref(OBJECT(ioc));
 }
 }
 
 
@@ -46,7 +46,7 @@ static gboolean fd_accept_incoming_migration(QIOChannel *ioc,
                                              GIOCondition condition,
                                              GIOCondition condition,
                                              gpointer opaque)
                                              gpointer opaque)
 {
 {
-    migration_set_incoming_channel(migrate_get_current(), ioc);
+    migration_channel_process_incoming(migrate_get_current(), ioc);
     object_unref(OBJECT(ioc));
     object_unref(OBJECT(ioc));
     return FALSE; /* unregister */
     return FALSE; /* unregister */
 }
 }

+ 47 - 44
migration/migration.c

@@ -416,7 +416,7 @@ static void process_incoming_migration_co(void *opaque)
     qemu_bh_schedule(mis->bh);
     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);
     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(
     trace_migration_set_incoming_channel(
         ioc, object_get_typename(OBJECT(ioc)));
         ioc, object_get_typename(OBJECT(ioc)));
@@ -436,20 +436,20 @@ void migration_set_incoming_channel(MigrationState *s,
         !object_dynamic_cast(OBJECT(ioc),
         !object_dynamic_cast(OBJECT(ioc),
                              TYPE_QIO_CHANNEL_TLS)) {
                              TYPE_QIO_CHANNEL_TLS)) {
         Error *local_err = NULL;
         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) {
         if (local_err) {
             error_report_err(local_err);
             error_report_err(local_err);
         }
         }
     } else {
     } else {
         QEMUFile *f = qemu_fopen_channel_input(ioc);
         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(
     trace_migration_set_outgoing_channel(
         ioc, object_get_typename(OBJECT(ioc)), hostname);
         ioc, object_get_typename(OBJECT(ioc)), hostname);
@@ -458,7 +458,7 @@ void migration_set_outgoing_channel(MigrationState *s,
         !object_dynamic_cast(OBJECT(ioc),
         !object_dynamic_cast(OBJECT(ioc),
                              TYPE_QIO_CHANNEL_TLS)) {
                              TYPE_QIO_CHANNEL_TLS)) {
         Error *local_err = NULL;
         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) {
         if (local_err) {
             migrate_fd_error(s, local_err);
             migrate_fd_error(s, local_err);
             error_free(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 *qmp_query_migrate(Error **errp)
 {
 {
     MigrationInfo *info = g_malloc0(sizeof(*info));
     MigrationInfo *info = g_malloc0(sizeof(*info));
@@ -626,18 +646,7 @@ MigrationInfo *qmp_query_migrate(Error **errp)
         info->has_setup_time = true;
         info->has_setup_time = true;
         info->setup_time = s->setup_time;
         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()) {
         if (blk_mig_active()) {
             info->has_disk = true;
             info->has_disk = true;
@@ -665,18 +674,7 @@ MigrationInfo *qmp_query_migrate(Error **errp)
         info->has_setup_time = true;
         info->has_setup_time = true;
         info->setup_time = s->setup_time;
         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()) {
         if (blk_mig_active()) {
             info->has_disk = true;
             info->has_disk = true;
@@ -699,17 +697,7 @@ MigrationInfo *qmp_query_migrate(Error **errp)
         info->has_setup_time = true;
         info->has_setup_time = true;
         info->setup_time = s->setup_time;
         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;
         break;
     case MIGRATION_STATUS_FAILED:
     case MIGRATION_STATUS_FAILED:
         info->has_status = true;
         info->has_status = true;
@@ -732,6 +720,7 @@ void qmp_migrate_set_capabilities(MigrationCapabilityStatusList *params,
 {
 {
     MigrationState *s = migrate_get_current();
     MigrationState *s = migrate_get_current();
     MigrationCapabilityStatusList *cap;
     MigrationCapabilityStatusList *cap;
+    bool old_postcopy_cap = migrate_postcopy_ram();
 
 
     if (migration_is_setup_or_active(s->state)) {
     if (migration_is_setup_or_active(s->state)) {
         error_setg(errp, QERR_MIGRATION_ACTIVE);
         error_setg(errp, QERR_MIGRATION_ACTIVE);
@@ -754,6 +743,19 @@ void qmp_migrate_set_capabilities(MigrationCapabilityStatusList *params,
             s->enabled_capabilities[MIGRATION_CAPABILITY_POSTCOPY_RAM] =
             s->enabled_capabilities[MIGRATION_CAPABILITY_POSTCOPY_RAM] =
                 false;
                 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->dirty_sync_count = 0;
     s->start_postcopy = false;
     s->start_postcopy = false;
     s->postcopy_after_devices = false;
     s->postcopy_after_devices = false;
+    s->postcopy_requests = 0;
     s->migration_thread_running = false;
     s->migration_thread_running = false;
     s->last_req_rb = NULL;
     s->last_req_rb = NULL;
     error_free(s->error);
     error_free(s->error);

+ 4 - 1
migration/ram.c

@@ -1169,6 +1169,7 @@ int ram_save_queue_pages(MigrationState *ms, const char *rbname,
 {
 {
     RAMBlock *ramblock;
     RAMBlock *ramblock;
 
 
+    ms->postcopy_requests++;
     rcu_read_lock();
     rcu_read_lock();
     if (!rbname) {
     if (!rbname) {
         /* Reuse last RAMBlock */
         /* Reuse last RAMBlock */
@@ -1557,7 +1558,9 @@ static int postcopy_send_discard_bm_ram(MigrationState *ms,
             } else {
             } else {
                 discard_length = zero - one;
                 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;
             current = one + discard_length;
         } else {
         } else {
             current = one;
             current = one;

+ 2 - 2
migration/rdma.c

@@ -1511,7 +1511,7 @@ static int qemu_rdma_block_for_wrid(RDMAContext *rdma, int wrid_requested,
 
 
     while (1) {
     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.
          * so don't yield unless we know we're running inside of a coroutine.
          */
          */
         if (rdma->migration_started_on_destination) {
         if (rdma->migration_started_on_destination) {
@@ -3620,7 +3620,7 @@ static void rdma_accept_incoming_migration(void *opaque)
     }
     }
 
 
     rdma->migration_started_on_destination = 1;
     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)
 void rdma_start_incoming_migration(const char *host_port, Error **errp)

+ 3 - 3
migration/socket.c

@@ -83,7 +83,7 @@ static void socket_outgoing_migration(Object *src,
         migrate_fd_error(data->s, err);
         migrate_fd_error(data->s, err);
     } else {
     } else {
         trace_migration_socket_outgoing_connected(data->hostname);
         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);
     object_unref(src);
 }
 }
@@ -140,8 +140,8 @@ static gboolean socket_accept_incoming_migration(QIOChannel *ioc,
 
 
     trace_migration_socket_incoming_accepted();
     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));
     object_unref(OBJECT(sioc));
 
 
 out:
 out:

+ 9 - 9
migration/tls.c

@@ -72,14 +72,14 @@ static void migration_tls_incoming_handshake(Object *src,
         error_report("%s", error_get_pretty(err));
         error_report("%s", error_get_pretty(err));
     } else {
     } else {
         trace_migration_tls_incoming_handshake_complete();
         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));
     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;
     QCryptoTLSCreds *creds;
     QIOChannelTLS *tioc;
     QIOChannelTLS *tioc;
@@ -119,16 +119,16 @@ static void migration_tls_outgoing_handshake(Object *src,
         migrate_fd_error(s, err);
         migrate_fd_error(s, err);
     } else {
     } else {
         trace_migration_tls_outgoing_handshake_complete();
         trace_migration_tls_outgoing_handshake_complete();
-        migration_set_outgoing_channel(s, ioc, NULL);
+        migration_channel_connect(s, ioc, NULL);
     }
     }
     object_unref(OBJECT(ioc));
     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;
     QCryptoTLSCreds *creds;
     QIOChannelTLS *tioc;
     QIOChannelTLS *tioc;

+ 10 - 6
qapi-schema.json

@@ -382,13 +382,17 @@
 #
 #
 # @dirty-sync-count: number of times that dirty ram was synchronized (since 2.1)
 # @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
 # Since: 0.14.0
 ##
 ##
 { 'struct': 'MigrationStats',
 { 'struct': 'MigrationStats',
   'data': {'transferred': 'int', 'remaining': 'int', 'total': 'int' ,
   'data': {'transferred': 'int', 'remaining': 'int', 'total': 'int' ,
            'duplicate': 'int', 'skipped': 'int', 'normal': 'int',
            'duplicate': 'int', 'skipped': 'int', 'normal': 'int',
            'normal-bytes': 'int', 'dirty-pages-rate' : 'int',
            'normal-bytes': 'int', 'dirty-pages-rate' : 'int',
-           'mbps' : 'number', 'dirty-sync-count' : 'int' } }
+           'mbps' : 'number', 'dirty-sync-count' : 'int',
+           'postcopy-requests' : 'int' } }
 
 
 ##
 ##
 # @XBZRLECacheStats
 # @XBZRLECacheStats
@@ -486,7 +490,7 @@
 #
 #
 # @error-desc: #optional the human readable error description string, when
 # @error-desc: #optional the human readable error description string, when
 #              @status is 'failed'. Clients should not attempt to parse the
 #              @status is 'failed'. Clients should not attempt to parse the
-#              error strings. (Since 2.6)
+#              error strings. (Since 2.7)
 #
 #
 # Since: 0.14.0
 # Since: 0.14.0
 ##
 ##
@@ -631,7 +635,7 @@
 #                migration URI does not already include a hostname. For
 #                migration URI does not already include a hostname. For
 #                example if using fd: or exec: based migration, the
 #                example if using fd: or exec: based migration, the
 #                hostname must be provided so that the server's x509
 #                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
 # Since: 2.4
 ##
 ##
@@ -672,7 +676,7 @@
 #                migration URI does not already include a hostname. For
 #                migration URI does not already include a hostname. For
 #                example if using fd: or exec: based migration, the
 #                example if using fd: or exec: based migration, the
 #                hostname must be provided so that the server's x509
 #                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
 # Since: 2.4
 ##
 ##
@@ -708,14 +712,14 @@
 #             be for a 'client' endpoint, while for the incoming side the
 #             be for a 'client' endpoint, while for the incoming side the
 #             credentials must be for a 'server' endpoint. Setting this
 #             credentials must be for a 'server' endpoint. Setting this
 #             will enable TLS for all migrations. The default is unset,
 #             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
 # @tls-hostname: hostname of the target host for the migration. This is
 #                required when using x509 based TLS credentials and the
 #                required when using x509 based TLS credentials and the
 #                migration URI does not already include a hostname. For
 #                migration URI does not already include a hostname. For
 #                example if using fd: or exec: based migration, the
 #                example if using fd: or exec: based migration, the
 #                hostname must be provided so that the server's x509
 #                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
 # Since: 2.4
 ##
 ##

+ 2 - 0
tests/Makefile.include

@@ -234,6 +234,7 @@ endif
 check-qtest-i386-y += tests/test-netfilter$(EXESUF)
 check-qtest-i386-y += tests/test-netfilter$(EXESUF)
 check-qtest-i386-y += tests/test-filter-mirror$(EXESUF)
 check-qtest-i386-y += tests/test-filter-mirror$(EXESUF)
 check-qtest-i386-y += tests/test-filter-redirector$(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)
 check-qtest-x86_64-y = $(check-qtest-i386-y)
 gcov-files-i386-y += i386-softmmu/hw/timer/mc146818rtc.c
 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))
 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-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/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/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/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/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)
 tests/test-qemu-opts$(EXESUF): tests/test-qemu-opts.o $(test-util-obj-y)

+ 1 - 1
tests/libqtest.c

@@ -26,7 +26,7 @@
 #include "qapi/qmp/qjson.h"
 #include "qapi/qmp/qjson.h"
 
 
 #define MAX_IRQ 256
 #define MAX_IRQ 256
-#define SOCKET_TIMEOUT 5
+#define SOCKET_TIMEOUT 50
 
 
 QTestState *global_qtest;
 QTestState *global_qtest;
 
 

+ 455 - 0
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 <glib.h>
+
+#include "libqtest.h"
+#include "qemu/option.h"
+#include "qemu/range.h"
+#include "sysemu/char.h"
+#include "sysemu/sysemu.h"
+
+#include <qemu/sockets.h>
+
+const unsigned start_address = 1024 * 1024;
+const unsigned end_address = 100 * 1024 * 1024;
+bool got_stop;
+
+#if defined(__linux__)
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <sys/vfs.h>
+#endif
+
+#if defined(__linux__) && defined(__NR_userfaultfd) && defined(CONFIG_EVENTFD)
+#include <sys/eventfd.h>
+#include <sys/ioctl.h>
+#include <linux/userfaultfd.h>
+
+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;
+}