diff mbox series

[v6,6/6] tests/libqtest: add a migration test with two couples of failover devices

Message ID 20211206222040.3872253-7-lvivier@redhat.com (mailing list archive)
State New, archived
Headers show
Series tests/qtest: add some tests for virtio-net failover | expand

Commit Message

Laurent Vivier Dec. 6, 2021, 10:20 p.m. UTC
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
---
 tests/qtest/virtio-net-failover.c | 279 ++++++++++++++++++++++++++++++
 1 file changed, 279 insertions(+)

Comments

Thomas Huth Dec. 7, 2021, 8:39 a.m. UTC | #1
On 06/12/2021 23.20, Laurent Vivier wrote:
> Signed-off-by: Laurent Vivier <lvivier@redhat.com>
> ---
>   tests/qtest/virtio-net-failover.c | 279 ++++++++++++++++++++++++++++++
>   1 file changed, 279 insertions(+)
> 
> diff --git a/tests/qtest/virtio-net-failover.c b/tests/qtest/virtio-net-failover.c
> index 57abb99e7f6e..ace9001894af 100644
> --- a/tests/qtest/virtio-net-failover.c
> +++ b/tests/qtest/virtio-net-failover.c
> @@ -11,6 +11,7 @@
>   
>   #define ACPI_PCIHP_ADDR_ICH9    0x0cc0
>   #define PCI_EJ_BASE             0x0008
> +#define PCI_SEL_BASE            0x0010
>   
>   #define BASE_MACHINE "-M q35 -nodefaults " \
>       "-device pcie-root-port,id=root0,addr=0x1,bus=pcie.0,chassis=1 " \
> @@ -18,6 +19,8 @@
>   
>   #define MAC_PRIMARY0 "52:54:00:11:11:11"
>   #define MAC_STANDBY0 "52:54:00:22:22:22"
> +#define MAC_PRIMARY1 "52:54:00:33:33:33"
> +#define MAC_STANDBY1 "52:54:00:44:44:44"
>   
>   static QGuestAllocator guest_malloc;
>   static QPCIBus *pcibus;
> @@ -965,6 +968,278 @@ static void test_migrate_abort_timeout(gconstpointer opaque)
>       machine_stop(qts);
>   }
>   
> +static void test_multi_out(gconstpointer opaque)
> +{
> +    QTestState *qts;
> +    QDict *resp, *args, *data, *ret;
> +    g_autofree gchar *uri = g_strdup_printf("exec: cat > %s", (gchar *)opaque);
> +    const gchar *status, *expected;
> +
> +    qts = machine_start(BASE_MACHINE
> +                "-device pcie-root-port,id=root2,addr=0x3,bus=pcie.0,chassis=3 "
> +                "-device pcie-root-port,id=root3,addr=0x4,bus=pcie.0,chassis=4 "
> +                "-netdev user,id=hs0 "
> +                "-netdev user,id=hs1 "
> +                "-netdev user,id=hs2 "
> +                "-netdev user,id=hs3 ",
> +                4);
> +
> +    check_one_card(qts, false, "standby0", MAC_STANDBY0);
> +    check_one_card(qts, false, "primary0", MAC_PRIMARY0);
> +    check_one_card(qts, false, "standby1", MAC_STANDBY1);
> +    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
> +
> +    qtest_qmp_device_add(qts, "virtio-net", "standby0",
> +                         "{'bus': 'root0',"
> +                         "'failover': 'on',"
> +                         "'netdev': 'hs0',"
> +                         "'mac': '"MAC_STANDBY0"'}");
> +
> +    check_one_card(qts, true, "standby0", MAC_STANDBY0);
> +    check_one_card(qts, false, "primary0", MAC_PRIMARY0);
> +    check_one_card(qts, false, "standby1", MAC_STANDBY1);
> +    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
> +
> +    qtest_qmp_device_add(qts, "virtio-net", "primary0",
> +                         "{'bus': 'root1',"
> +                         "'failover_pair_id': 'standby0',"
> +                         "'netdev': 'hs1',"
> +                         "'rombar': 0,"
> +                         "'romfile': '',"
> +                         "'mac': '"MAC_PRIMARY0"'}");
> +
> +    check_one_card(qts, true, "standby0", MAC_STANDBY0);
> +    check_one_card(qts, false, "primary0", MAC_PRIMARY0);
> +    check_one_card(qts, false, "standby1", MAC_STANDBY1);
> +    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
> +
> +    start_virtio_net(qts, 1, 0, "standby0");
> +
> +    check_one_card(qts, true, "standby0", MAC_STANDBY0);
> +    check_one_card(qts, true, "primary0", MAC_PRIMARY0);
> +    check_one_card(qts, false, "standby1", MAC_STANDBY1);
> +    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
> +
> +    qtest_qmp_device_add(qts, "virtio-net", "standby1",
> +                         "{'bus': 'root2',"
> +                         "'failover': 'on',"
> +                         "'netdev': 'hs2',"
> +                         "'mac': '"MAC_STANDBY1"'}");
> +
> +    check_one_card(qts, true, "standby0", MAC_STANDBY0);
> +    check_one_card(qts, true, "primary0", MAC_PRIMARY0);
> +    check_one_card(qts, true, "standby1", MAC_STANDBY1);
> +    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
> +
> +    qtest_qmp_device_add(qts, "virtio-net", "primary1",
> +                         "{'bus': 'root3',"
> +                         "'failover_pair_id': 'standby1',"
> +                         "'netdev': 'hs3',"
> +                         "'rombar': 0,"
> +                         "'romfile': '',"
> +                         "'mac': '"MAC_PRIMARY1"'}");
> +
> +    check_one_card(qts, true, "standby0", MAC_STANDBY0);
> +    check_one_card(qts, true, "primary0", MAC_PRIMARY0);
> +    check_one_card(qts, true, "standby1", MAC_STANDBY1);
> +    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
> +
> +    start_virtio_net(qts, 3, 0, "standby1");
> +
> +    check_one_card(qts, true, "standby0", MAC_STANDBY0);
> +    check_one_card(qts, true, "primary0", MAC_PRIMARY0);
> +    check_one_card(qts, true, "standby1", MAC_STANDBY1);
> +    check_one_card(qts, true, "primary1", MAC_PRIMARY1);
> +
> +    args = qdict_from_jsonf_nofail("{}");
> +    g_assert_nonnull(args);
> +    qdict_put_str(args, "uri", uri);
> +
> +    resp = qtest_qmp(qts, "{ 'execute': 'migrate', 'arguments': %p}", args);
> +    g_assert(qdict_haskey(resp, "return"));
> +    qobject_unref(resp);
> +
> +    /* the event is sent whan QEMU asks the OS to unplug the card */
> +    resp = qtest_qmp_eventwait_ref(qts, "UNPLUG_PRIMARY");
> +    g_assert(qdict_haskey(resp, "data"));
> +
> +    data = qdict_get_qdict(resp, "data");
> +    g_assert(qdict_haskey(data, "device-id"));
> +    if (strcmp(qdict_get_str(data, "device-id"), "primary0") == 0) {
> +        expected = "primary1";
> +    } else if (strcmp(qdict_get_str(data, "device-id"), "primary1") == 0) {
> +        expected = "primary0";
> +    } else {
> +        g_assert_not_reached();
> +    }
> +    qobject_unref(resp);
> +
> +    resp = qtest_qmp_eventwait_ref(qts, "UNPLUG_PRIMARY");
> +    g_assert(qdict_haskey(resp, "data"));
> +
> +    data = qdict_get_qdict(resp, "data");
> +    g_assert(qdict_haskey(data, "device-id"));
> +    g_assert_cmpstr(qdict_get_str(data, "device-id"), ==, expected);
> +    qobject_unref(resp);
> +
> +    /* wait the end of the migration setup phase */
> +    while (true) {
> +        ret = migrate_status(qts);
> +
> +        status = qdict_get_str(ret, "status");
> +        if (strcmp(status, "wait-unplug") == 0) {
> +            break;
> +        }
> +
> +        /* The migration must not start if the card is not ejected */
> +        g_assert_cmpstr(status, !=, "active");
> +        g_assert_cmpstr(status, !=, "completed");
> +        g_assert_cmpstr(status, !=, "failed");
> +        g_assert_cmpstr(status, !=, "cancelling");
> +        g_assert_cmpstr(status, !=, "cancelled");
> +
> +        qobject_unref(ret);
> +    }
> +    qobject_unref(ret);
> +
> +    /* OS unplugs primary1, but we must wait the second */
> +    qtest_outl(qts, ACPI_PCIHP_ADDR_ICH9 + PCI_EJ_BASE, 1);
> +
> +    ret = migrate_status(qts);
> +    status = qdict_get_str(ret, "status");
> +    g_assert_cmpstr(status, ==, "wait-unplug");
> +    qobject_unref(ret);
> +
> +    if (g_test_slow()) {
> +        /* check we stay in wait-unplug while the card is not ejected */
> +        int i;
> +
> +        for (i = 0; i < 10; i++) {

We're using gnu11 mode these days, so you could also write
  "for (int i = 0; ...)".
Maybe also consider to limit the test to 5 seconds.

> +            sleep(1);
> +            ret = migrate_status(qts);
> +            status = qdict_get_str(ret, "status");
> +            g_assert_cmpstr(status, ==, "wait-unplug");
> +            qobject_unref(ret);
> +        }
> +    }
> +
> +    /* OS unplugs primary0, QEMU can move from wait-unplug state */
> +    qtest_outl(qts, ACPI_PCIHP_ADDR_ICH9 + PCI_SEL_BASE, 2);
> +    qtest_outl(qts, ACPI_PCIHP_ADDR_ICH9 + PCI_EJ_BASE, 1);
> +
> +    while (true) {
> +        ret = migrate_status(qts);
> +
> +        status = qdict_get_str(ret, "status");
> +        if (strcmp(status, "completed") == 0) {
> +            break;
> +        }
> +        g_assert_cmpstr(status, !=, "failed");
> +        g_assert_cmpstr(status, !=, "cancelling");
> +        g_assert_cmpstr(status, !=, "cancelled");
> +    }
> +
> +    qtest_qmp_eventwait(qts, "STOP");
> +
> +    machine_stop(qts);
> +}
> +
> +static void test_multi_in(gconstpointer opaque)
> +{
> +    QTestState *qts;
> +    QDict *resp, *args, *ret;
> +    g_autofree gchar *uri = g_strdup_printf("exec: cat %s", (gchar *)opaque);
> +
> +    qts = machine_start(BASE_MACHINE
> +                "-device pcie-root-port,id=root2,addr=0x3,bus=pcie.0,chassis=3 "
> +                "-device pcie-root-port,id=root3,addr=0x4,bus=pcie.0,chassis=4 "
> +                "-netdev user,id=hs0 "
> +                "-netdev user,id=hs1 "
> +                "-netdev user,id=hs2 "
> +                "-netdev user,id=hs3 "
> +                "-incoming defer ",
> +                4);
> +
> +    check_one_card(qts, false, "standby0", MAC_STANDBY0);
> +    check_one_card(qts, false, "primary0", MAC_PRIMARY0);
> +    check_one_card(qts, false, "standby1", MAC_STANDBY1);
> +    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
> +
> +    qtest_qmp_device_add(qts, "virtio-net", "standby0",
> +                         "{'bus': 'root0',"
> +                         "'failover': 'on',"
> +                         "'netdev': 'hs0',"
> +                         "'mac': '"MAC_STANDBY0"'}");
> +
> +    check_one_card(qts, true, "standby0", MAC_STANDBY0);
> +    check_one_card(qts, false, "primary0", MAC_PRIMARY0);
> +    check_one_card(qts, false, "standby1", MAC_STANDBY1);
> +    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
> +
> +    qtest_qmp_device_add(qts, "virtio-net", "primary0",
> +                         "{'bus': 'root1',"
> +                         "'failover_pair_id': 'standby0',"
> +                         "'netdev': 'hs1',"
> +                         "'rombar': 0,"
> +                         "'romfile': '',"
> +                         "'mac': '"MAC_PRIMARY0"'}");
> +
> +    check_one_card(qts, true, "standby0", MAC_STANDBY0);
> +    check_one_card(qts, false, "primary0", MAC_PRIMARY0);
> +    check_one_card(qts, false, "standby1", MAC_STANDBY1);
> +    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
> +
> +    qtest_qmp_device_add(qts, "virtio-net", "standby1",
> +                         "{'bus': 'root2',"
> +                         "'failover': 'on',"
> +                         "'netdev': 'hs2',"
> +                         "'mac': '"MAC_STANDBY1"'}");
> +
> +    check_one_card(qts, true, "standby0", MAC_STANDBY0);
> +    check_one_card(qts, false, "primary0", MAC_PRIMARY0);
> +    check_one_card(qts, true, "standby1", MAC_STANDBY1);
> +    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
> +
> +    qtest_qmp_device_add(qts, "virtio-net", "primary1",
> +                         "{'bus': 'root3',"
> +                         "'failover_pair_id': 'standby1',"
> +                         "'netdev': 'hs3',"
> +                         "'rombar': 0,"
> +                         "'romfile': '',"
> +                         "'mac': '"MAC_PRIMARY1"'}");
> +
> +    check_one_card(qts, true, "standby0", MAC_STANDBY0);
> +    check_one_card(qts, false, "primary0", MAC_PRIMARY0);
> +    check_one_card(qts, true, "standby1", MAC_STANDBY1);
> +    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
> +
> +    args = qdict_from_jsonf_nofail("{}");
> +    g_assert_nonnull(args);
> +    qdict_put_str(args, "uri", uri);
> +
> +    resp = qtest_qmp(qts, "{ 'execute': 'migrate-incoming', 'arguments': %p}",
> +                     args);
> +    g_assert(qdict_haskey(resp, "return"));
> +    qobject_unref(resp);
> +
> +    qtest_qmp_eventwait(qts, "MIGRATION");
> +    qtest_qmp_eventwait(qts, "FAILOVER_NEGOTIATED");
> +    qtest_qmp_eventwait(qts, "FAILOVER_NEGOTIATED");
> +
> +    check_one_card(qts, true, "standby0", MAC_STANDBY0);
> +    check_one_card(qts, true, "primary0", MAC_PRIMARY0);
> +    check_one_card(qts, true, "standby1", MAC_STANDBY1);
> +    check_one_card(qts, true, "primary1", MAC_PRIMARY1);
> +
> +    qtest_qmp_eventwait(qts, "RESUME");
> +
> +    ret = migrate_status(qts);
> +    g_assert_cmpstr(qdict_get_str(ret, "status"), ==, "completed");
> +    qobject_unref(ret);
> +
> +    machine_stop(qts);
> +}
> +
>   int main(int argc, char **argv)
>   {
>       gchar *tmpfile = g_strdup_printf("/tmp/failover_test_migrate-%u-%u",
> @@ -1005,6 +1280,10 @@ int main(int argc, char **argv)
>           qtest_add_data_func("failover-virtio-net/migrate/abort/timeout",
>                               tmpfile, test_migrate_abort_timeout);
>       }
> +    qtest_add_data_func("failover-virtio-net/multi/out",
> +                        tmpfile, test_multi_out);
> +    qtest_add_data_func("failover-virtio-net/multi/in",
> +                   tmpfile, test_multi_in);
>   
>       ret = g_test_run();
>   
> 

Acked-by: Thomas Huth <thuth@redhat.com>
diff mbox series

Patch

diff --git a/tests/qtest/virtio-net-failover.c b/tests/qtest/virtio-net-failover.c
index 57abb99e7f6e..ace9001894af 100644
--- a/tests/qtest/virtio-net-failover.c
+++ b/tests/qtest/virtio-net-failover.c
@@ -11,6 +11,7 @@ 
 
 #define ACPI_PCIHP_ADDR_ICH9    0x0cc0
 #define PCI_EJ_BASE             0x0008
+#define PCI_SEL_BASE            0x0010
 
 #define BASE_MACHINE "-M q35 -nodefaults " \
     "-device pcie-root-port,id=root0,addr=0x1,bus=pcie.0,chassis=1 " \
@@ -18,6 +19,8 @@ 
 
 #define MAC_PRIMARY0 "52:54:00:11:11:11"
 #define MAC_STANDBY0 "52:54:00:22:22:22"
+#define MAC_PRIMARY1 "52:54:00:33:33:33"
+#define MAC_STANDBY1 "52:54:00:44:44:44"
 
 static QGuestAllocator guest_malloc;
 static QPCIBus *pcibus;
@@ -965,6 +968,278 @@  static void test_migrate_abort_timeout(gconstpointer opaque)
     machine_stop(qts);
 }
 
+static void test_multi_out(gconstpointer opaque)
+{
+    QTestState *qts;
+    QDict *resp, *args, *data, *ret;
+    g_autofree gchar *uri = g_strdup_printf("exec: cat > %s", (gchar *)opaque);
+    const gchar *status, *expected;
+
+    qts = machine_start(BASE_MACHINE
+                "-device pcie-root-port,id=root2,addr=0x3,bus=pcie.0,chassis=3 "
+                "-device pcie-root-port,id=root3,addr=0x4,bus=pcie.0,chassis=4 "
+                "-netdev user,id=hs0 "
+                "-netdev user,id=hs1 "
+                "-netdev user,id=hs2 "
+                "-netdev user,id=hs3 ",
+                4);
+
+    check_one_card(qts, false, "standby0", MAC_STANDBY0);
+    check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+    check_one_card(qts, false, "standby1", MAC_STANDBY1);
+    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+    qtest_qmp_device_add(qts, "virtio-net", "standby0",
+                         "{'bus': 'root0',"
+                         "'failover': 'on',"
+                         "'netdev': 'hs0',"
+                         "'mac': '"MAC_STANDBY0"'}");
+
+    check_one_card(qts, true, "standby0", MAC_STANDBY0);
+    check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+    check_one_card(qts, false, "standby1", MAC_STANDBY1);
+    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+    qtest_qmp_device_add(qts, "virtio-net", "primary0",
+                         "{'bus': 'root1',"
+                         "'failover_pair_id': 'standby0',"
+                         "'netdev': 'hs1',"
+                         "'rombar': 0,"
+                         "'romfile': '',"
+                         "'mac': '"MAC_PRIMARY0"'}");
+
+    check_one_card(qts, true, "standby0", MAC_STANDBY0);
+    check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+    check_one_card(qts, false, "standby1", MAC_STANDBY1);
+    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+    start_virtio_net(qts, 1, 0, "standby0");
+
+    check_one_card(qts, true, "standby0", MAC_STANDBY0);
+    check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+    check_one_card(qts, false, "standby1", MAC_STANDBY1);
+    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+    qtest_qmp_device_add(qts, "virtio-net", "standby1",
+                         "{'bus': 'root2',"
+                         "'failover': 'on',"
+                         "'netdev': 'hs2',"
+                         "'mac': '"MAC_STANDBY1"'}");
+
+    check_one_card(qts, true, "standby0", MAC_STANDBY0);
+    check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+    check_one_card(qts, true, "standby1", MAC_STANDBY1);
+    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+    qtest_qmp_device_add(qts, "virtio-net", "primary1",
+                         "{'bus': 'root3',"
+                         "'failover_pair_id': 'standby1',"
+                         "'netdev': 'hs3',"
+                         "'rombar': 0,"
+                         "'romfile': '',"
+                         "'mac': '"MAC_PRIMARY1"'}");
+
+    check_one_card(qts, true, "standby0", MAC_STANDBY0);
+    check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+    check_one_card(qts, true, "standby1", MAC_STANDBY1);
+    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+    start_virtio_net(qts, 3, 0, "standby1");
+
+    check_one_card(qts, true, "standby0", MAC_STANDBY0);
+    check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+    check_one_card(qts, true, "standby1", MAC_STANDBY1);
+    check_one_card(qts, true, "primary1", MAC_PRIMARY1);
+
+    args = qdict_from_jsonf_nofail("{}");
+    g_assert_nonnull(args);
+    qdict_put_str(args, "uri", uri);
+
+    resp = qtest_qmp(qts, "{ 'execute': 'migrate', 'arguments': %p}", args);
+    g_assert(qdict_haskey(resp, "return"));
+    qobject_unref(resp);
+
+    /* the event is sent whan QEMU asks the OS to unplug the card */
+    resp = qtest_qmp_eventwait_ref(qts, "UNPLUG_PRIMARY");
+    g_assert(qdict_haskey(resp, "data"));
+
+    data = qdict_get_qdict(resp, "data");
+    g_assert(qdict_haskey(data, "device-id"));
+    if (strcmp(qdict_get_str(data, "device-id"), "primary0") == 0) {
+        expected = "primary1";
+    } else if (strcmp(qdict_get_str(data, "device-id"), "primary1") == 0) {
+        expected = "primary0";
+    } else {
+        g_assert_not_reached();
+    }
+    qobject_unref(resp);
+
+    resp = qtest_qmp_eventwait_ref(qts, "UNPLUG_PRIMARY");
+    g_assert(qdict_haskey(resp, "data"));
+
+    data = qdict_get_qdict(resp, "data");
+    g_assert(qdict_haskey(data, "device-id"));
+    g_assert_cmpstr(qdict_get_str(data, "device-id"), ==, expected);
+    qobject_unref(resp);
+
+    /* wait the end of the migration setup phase */
+    while (true) {
+        ret = migrate_status(qts);
+
+        status = qdict_get_str(ret, "status");
+        if (strcmp(status, "wait-unplug") == 0) {
+            break;
+        }
+
+        /* The migration must not start if the card is not ejected */
+        g_assert_cmpstr(status, !=, "active");
+        g_assert_cmpstr(status, !=, "completed");
+        g_assert_cmpstr(status, !=, "failed");
+        g_assert_cmpstr(status, !=, "cancelling");
+        g_assert_cmpstr(status, !=, "cancelled");
+
+        qobject_unref(ret);
+    }
+    qobject_unref(ret);
+
+    /* OS unplugs primary1, but we must wait the second */
+    qtest_outl(qts, ACPI_PCIHP_ADDR_ICH9 + PCI_EJ_BASE, 1);
+
+    ret = migrate_status(qts);
+    status = qdict_get_str(ret, "status");
+    g_assert_cmpstr(status, ==, "wait-unplug");
+    qobject_unref(ret);
+
+    if (g_test_slow()) {
+        /* check we stay in wait-unplug while the card is not ejected */
+        int i;
+
+        for (i = 0; i < 10; i++) {
+            sleep(1);
+            ret = migrate_status(qts);
+            status = qdict_get_str(ret, "status");
+            g_assert_cmpstr(status, ==, "wait-unplug");
+            qobject_unref(ret);
+        }
+    }
+
+    /* OS unplugs primary0, QEMU can move from wait-unplug state */
+    qtest_outl(qts, ACPI_PCIHP_ADDR_ICH9 + PCI_SEL_BASE, 2);
+    qtest_outl(qts, ACPI_PCIHP_ADDR_ICH9 + PCI_EJ_BASE, 1);
+
+    while (true) {
+        ret = migrate_status(qts);
+
+        status = qdict_get_str(ret, "status");
+        if (strcmp(status, "completed") == 0) {
+            break;
+        }
+        g_assert_cmpstr(status, !=, "failed");
+        g_assert_cmpstr(status, !=, "cancelling");
+        g_assert_cmpstr(status, !=, "cancelled");
+    }
+
+    qtest_qmp_eventwait(qts, "STOP");
+
+    machine_stop(qts);
+}
+
+static void test_multi_in(gconstpointer opaque)
+{
+    QTestState *qts;
+    QDict *resp, *args, *ret;
+    g_autofree gchar *uri = g_strdup_printf("exec: cat %s", (gchar *)opaque);
+
+    qts = machine_start(BASE_MACHINE
+                "-device pcie-root-port,id=root2,addr=0x3,bus=pcie.0,chassis=3 "
+                "-device pcie-root-port,id=root3,addr=0x4,bus=pcie.0,chassis=4 "
+                "-netdev user,id=hs0 "
+                "-netdev user,id=hs1 "
+                "-netdev user,id=hs2 "
+                "-netdev user,id=hs3 "
+                "-incoming defer ",
+                4);
+
+    check_one_card(qts, false, "standby0", MAC_STANDBY0);
+    check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+    check_one_card(qts, false, "standby1", MAC_STANDBY1);
+    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+    qtest_qmp_device_add(qts, "virtio-net", "standby0",
+                         "{'bus': 'root0',"
+                         "'failover': 'on',"
+                         "'netdev': 'hs0',"
+                         "'mac': '"MAC_STANDBY0"'}");
+
+    check_one_card(qts, true, "standby0", MAC_STANDBY0);
+    check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+    check_one_card(qts, false, "standby1", MAC_STANDBY1);
+    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+    qtest_qmp_device_add(qts, "virtio-net", "primary0",
+                         "{'bus': 'root1',"
+                         "'failover_pair_id': 'standby0',"
+                         "'netdev': 'hs1',"
+                         "'rombar': 0,"
+                         "'romfile': '',"
+                         "'mac': '"MAC_PRIMARY0"'}");
+
+    check_one_card(qts, true, "standby0", MAC_STANDBY0);
+    check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+    check_one_card(qts, false, "standby1", MAC_STANDBY1);
+    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+    qtest_qmp_device_add(qts, "virtio-net", "standby1",
+                         "{'bus': 'root2',"
+                         "'failover': 'on',"
+                         "'netdev': 'hs2',"
+                         "'mac': '"MAC_STANDBY1"'}");
+
+    check_one_card(qts, true, "standby0", MAC_STANDBY0);
+    check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+    check_one_card(qts, true, "standby1", MAC_STANDBY1);
+    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+    qtest_qmp_device_add(qts, "virtio-net", "primary1",
+                         "{'bus': 'root3',"
+                         "'failover_pair_id': 'standby1',"
+                         "'netdev': 'hs3',"
+                         "'rombar': 0,"
+                         "'romfile': '',"
+                         "'mac': '"MAC_PRIMARY1"'}");
+
+    check_one_card(qts, true, "standby0", MAC_STANDBY0);
+    check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+    check_one_card(qts, true, "standby1", MAC_STANDBY1);
+    check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+    args = qdict_from_jsonf_nofail("{}");
+    g_assert_nonnull(args);
+    qdict_put_str(args, "uri", uri);
+
+    resp = qtest_qmp(qts, "{ 'execute': 'migrate-incoming', 'arguments': %p}",
+                     args);
+    g_assert(qdict_haskey(resp, "return"));
+    qobject_unref(resp);
+
+    qtest_qmp_eventwait(qts, "MIGRATION");
+    qtest_qmp_eventwait(qts, "FAILOVER_NEGOTIATED");
+    qtest_qmp_eventwait(qts, "FAILOVER_NEGOTIATED");
+
+    check_one_card(qts, true, "standby0", MAC_STANDBY0);
+    check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+    check_one_card(qts, true, "standby1", MAC_STANDBY1);
+    check_one_card(qts, true, "primary1", MAC_PRIMARY1);
+
+    qtest_qmp_eventwait(qts, "RESUME");
+
+    ret = migrate_status(qts);
+    g_assert_cmpstr(qdict_get_str(ret, "status"), ==, "completed");
+    qobject_unref(ret);
+
+    machine_stop(qts);
+}
+
 int main(int argc, char **argv)
 {
     gchar *tmpfile = g_strdup_printf("/tmp/failover_test_migrate-%u-%u",
@@ -1005,6 +1280,10 @@  int main(int argc, char **argv)
         qtest_add_data_func("failover-virtio-net/migrate/abort/timeout",
                             tmpfile, test_migrate_abort_timeout);
     }
+    qtest_add_data_func("failover-virtio-net/multi/out",
+                        tmpfile, test_multi_out);
+    qtest_add_data_func("failover-virtio-net/multi/in",
+                   tmpfile, test_multi_in);
 
     ret = g_test_run();