qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/3] tests/qtest: add some tests for virtio-net failover
@ 2021-11-10 13:56 Laurent Vivier
  2021-11-10 13:56 ` [PATCH 1/3] qdict: make available dump_qobject(), dump_qdict(), dump_qlist() Laurent Vivier
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Laurent Vivier @ 2021-11-10 13:56 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Laurent Vivier, Thomas Huth, qemu-block,
	Michael S. Tsirkin, Markus Armbruster, Hanna Reitz,
	Paolo Bonzini

This series adds a qtest entry to test virtio-net failover feature.

We check following error cases:

- check missing id on device with failover_pair_id triggers an error
- check a primary device plugged on a bus that doesn't support hotplug
  triggers an error

We check the status of the machine before and after hotplugging cards and
feature negotiation:

- check we don't see the primary device at boot if failover is on
- check we see the primary device at boot if failover is off
- check we don't see the primary device if failover is on
  but failover_pair_id is not the one with on (I think this should be changed)
- check the primary device is plugged after the feature negotiation
- check the result if the primary device is plugged before standby device and
  vice-versa
- check the if the primary device is coldplugged and the standy device
  hotplugged and vice-versa
- check the migration triggers the unplug
  -> this one needs to be improved as we can't actualy unplug the
     card as the qtest framework doesn't allow to really do
     the OS level unplug. So we receive the UNPLUG_PRIMARY
     event but nothing more.

There are two preliminary patches in the series:

- PATCH 1 makes available functions that helped me to debug
  the qmp command result. I think it's a good point to have them
  available widely

- PATCH 2 introduces a function to enable PCI bridge.
  Failover needs to be plugged on a pcie-root-port and while
  the root port is not configured the cards behind it are not
  available

Laurent Vivier (3):
  qdict: make available dump_qobject(), dump_qdict(), dump_qlist()
  qtest/libqos: add a function to initialize secondary PCI buses
  tests/qtest: add some tests for virtio-net failover

 block/qapi.c                      |  82 +----
 include/hw/pci/pci_bridge.h       |   8 +
 include/qapi/qmp/qdict.h          |   2 +
 include/qapi/qmp/qlist.h          |   1 +
 include/qapi/qmp/qobject.h        |   1 +
 qobject/qdict.c                   |  25 ++
 qobject/qlist.c                   |  17 +
 qobject/qobject.c                 |  35 ++
 tests/qtest/libqos/pci.c          | 118 +++++++
 tests/qtest/libqos/pci.h          |   1 +
 tests/qtest/meson.build           |   3 +
 tests/qtest/virtio-net-failover.c | 567 ++++++++++++++++++++++++++++++
 12 files changed, 779 insertions(+), 81 deletions(-)
 create mode 100644 tests/qtest/virtio-net-failover.c

-- 
2.31.1




^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/3] qdict: make available dump_qobject(), dump_qdict(), dump_qlist()
  2021-11-10 13:56 [PATCH 0/3] tests/qtest: add some tests for virtio-net failover Laurent Vivier
@ 2021-11-10 13:56 ` Laurent Vivier
  2021-11-10 16:17   ` Markus Armbruster
  2021-11-10 13:56 ` [PATCH 2/3] qtest/libqos: add a function to initialize secondary PCI buses Laurent Vivier
  2021-11-10 13:56 ` [PATCH 3/3] tests/qtest: add some tests for virtio-net failover Laurent Vivier
  2 siblings, 1 reply; 6+ messages in thread
From: Laurent Vivier @ 2021-11-10 13:56 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Laurent Vivier, Thomas Huth, qemu-block,
	Michael S. Tsirkin, Markus Armbruster, Hanna Reitz,
	Paolo Bonzini

move them from block/qapi.c to qobject/qdict.c, qobject/qlist.c,
qobject/qobject.c

This is useful to debug qobjects

Signed-off-by: Laurent Vivier <lvivier@redhat.com>
---
 block/qapi.c               | 82 +-------------------------------------
 include/qapi/qmp/qdict.h   |  2 +
 include/qapi/qmp/qlist.h   |  1 +
 include/qapi/qmp/qobject.h |  1 +
 qobject/qdict.c            | 25 ++++++++++++
 qobject/qlist.c            | 17 ++++++++
 qobject/qobject.c          | 35 ++++++++++++++++
 7 files changed, 82 insertions(+), 81 deletions(-)

diff --git a/block/qapi.c b/block/qapi.c
index cf557e3aea7c..26bbc45a67f5 100644
--- a/block/qapi.c
+++ b/block/qapi.c
@@ -697,86 +697,6 @@ void bdrv_snapshot_dump(QEMUSnapshotInfo *sn)
     g_free(sizing);
 }
 
-static void dump_qdict(int indentation, QDict *dict);
-static void dump_qlist(int indentation, QList *list);
-
-static void dump_qobject(int comp_indent, QObject *obj)
-{
-    switch (qobject_type(obj)) {
-        case QTYPE_QNUM: {
-            QNum *value = qobject_to(QNum, obj);
-            char *tmp = qnum_to_string(value);
-            qemu_printf("%s", tmp);
-            g_free(tmp);
-            break;
-        }
-        case QTYPE_QSTRING: {
-            QString *value = qobject_to(QString, obj);
-            qemu_printf("%s", qstring_get_str(value));
-            break;
-        }
-        case QTYPE_QDICT: {
-            QDict *value = qobject_to(QDict, obj);
-            dump_qdict(comp_indent, value);
-            break;
-        }
-        case QTYPE_QLIST: {
-            QList *value = qobject_to(QList, obj);
-            dump_qlist(comp_indent, value);
-            break;
-        }
-        case QTYPE_QBOOL: {
-            QBool *value = qobject_to(QBool, obj);
-            qemu_printf("%s", qbool_get_bool(value) ? "true" : "false");
-            break;
-        }
-        default:
-            abort();
-    }
-}
-
-static void dump_qlist(int indentation, QList *list)
-{
-    const QListEntry *entry;
-    int i = 0;
-
-    for (entry = qlist_first(list); entry; entry = qlist_next(entry), i++) {
-        QType type = qobject_type(entry->value);
-        bool composite = (type == QTYPE_QDICT || type == QTYPE_QLIST);
-        qemu_printf("%*s[%i]:%c", indentation * 4, "", i,
-                    composite ? '\n' : ' ');
-        dump_qobject(indentation + 1, entry->value);
-        if (!composite) {
-            qemu_printf("\n");
-        }
-    }
-}
-
-static void dump_qdict(int indentation, QDict *dict)
-{
-    const QDictEntry *entry;
-
-    for (entry = qdict_first(dict); entry; entry = qdict_next(dict, entry)) {
-        QType type = qobject_type(entry->value);
-        bool composite = (type == QTYPE_QDICT || type == QTYPE_QLIST);
-        char *key = g_malloc(strlen(entry->key) + 1);
-        int i;
-
-        /* replace dashes with spaces in key (variable) names */
-        for (i = 0; entry->key[i]; i++) {
-            key[i] = entry->key[i] == '-' ? ' ' : entry->key[i];
-        }
-        key[i] = 0;
-        qemu_printf("%*s%s:%c", indentation * 4, "", key,
-                    composite ? '\n' : ' ');
-        dump_qobject(indentation + 1, entry->value);
-        if (!composite) {
-            qemu_printf("\n");
-        }
-        g_free(key);
-    }
-}
-
 void bdrv_image_info_specific_dump(ImageInfoSpecific *info_spec)
 {
     QObject *obj, *data;
@@ -785,7 +705,7 @@ void bdrv_image_info_specific_dump(ImageInfoSpecific *info_spec)
     visit_type_ImageInfoSpecific(v, NULL, &info_spec, &error_abort);
     visit_complete(v, &obj);
     data = qdict_get(qobject_to(QDict, obj), "data");
-    dump_qobject(1, data);
+    dump_qobject(1, data, qemu_printf);
     qobject_unref(obj);
     visit_free(v);
 }
diff --git a/include/qapi/qmp/qdict.h b/include/qapi/qmp/qdict.h
index d5b5430e21a9..e529a9a9a29c 100644
--- a/include/qapi/qmp/qdict.h
+++ b/include/qapi/qmp/qdict.h
@@ -67,4 +67,6 @@ QDict *qdict_clone_shallow(const QDict *src);
 QObject *qdict_crumple(const QDict *src, Error **errp);
 void qdict_flatten(QDict *qdict);
 
+void dump_qdict(int indentation, QDict *dict, int (*qemu_printf)(const char *fmt, ...));
+
 #endif /* QDICT_H */
diff --git a/include/qapi/qmp/qlist.h b/include/qapi/qmp/qlist.h
index 06e98ad5f498..4af55d82d3e4 100644
--- a/include/qapi/qmp/qlist.h
+++ b/include/qapi/qmp/qlist.h
@@ -62,4 +62,5 @@ static inline const QListEntry *qlist_next(const QListEntry *entry)
     return QTAILQ_NEXT(entry, next);
 }
 
+void dump_qlist(int indentation, QList *list, int (*qemu_printf)(const char *fmt, ...));
 #endif /* QLIST_H */
diff --git a/include/qapi/qmp/qobject.h b/include/qapi/qmp/qobject.h
index 9003b71fd32d..bf893b146217 100644
--- a/include/qapi/qmp/qobject.h
+++ b/include/qapi/qmp/qobject.h
@@ -135,4 +135,5 @@ static inline QObject *qobject_check_type(const QObject *obj, QType type)
     }
 }
 
+void dump_qobject(int comp_indent, QObject *obj, int (*qemu_printf)(const char *fmt, ...));
 #endif /* QOBJECT_H */
diff --git a/qobject/qdict.c b/qobject/qdict.c
index 0216ca7ac168..c09d98710c6d 100644
--- a/qobject/qdict.c
+++ b/qobject/qdict.c
@@ -442,3 +442,28 @@ void qdict_destroy_obj(QObject *obj)
 
     g_free(qdict);
 }
+
+void dump_qdict(int indentation, QDict *dict, int (*qemu_printf)(const char *fmt, ...))
+{
+    const QDictEntry *entry;
+
+    for (entry = qdict_first(dict); entry; entry = qdict_next(dict, entry)) {
+        QType type = qobject_type(entry->value);
+        bool composite = (type == QTYPE_QDICT || type == QTYPE_QLIST);
+        char *key = g_malloc(strlen(entry->key) + 1);
+        int i;
+
+        /* replace dashes with spaces in key (variable) names */
+        for (i = 0; entry->key[i]; i++) {
+            key[i] = entry->key[i] == '-' ? ' ' : entry->key[i];
+        }
+        key[i] = 0;
+        qemu_printf("%*s%s:%c", indentation * 4, "", key,
+                    composite ? '\n' : ' ');
+        dump_qobject(indentation + 1, entry->value, qemu_printf);
+        if (!composite) {
+            qemu_printf("\n");
+        }
+        g_free(key);
+    }
+}
diff --git a/qobject/qlist.c b/qobject/qlist.c
index 60562a1f5269..d32bd7f08925 100644
--- a/qobject/qlist.c
+++ b/qobject/qlist.c
@@ -182,3 +182,20 @@ void qlist_destroy_obj(QObject *obj)
 
     g_free(qlist);
 }
+
+void dump_qlist(int indentation, QList *list, int (*qemu_printf)(const char *fmt, ...))
+{
+    const QListEntry *entry;
+    int i = 0;
+
+    for (entry = qlist_first(list); entry; entry = qlist_next(entry), i++) {
+        QType type = qobject_type(entry->value);
+        bool composite = (type == QTYPE_QDICT || type == QTYPE_QLIST);
+        qemu_printf("%*s[%i]:%c", indentation * 4, "", i,
+                    composite ? '\n' : ' ');
+        dump_qobject(indentation + 1, entry->value, qemu_printf);
+        if (!composite) {
+            qemu_printf("\n");
+        }
+    }
+}
diff --git a/qobject/qobject.c b/qobject/qobject.c
index d7077b8f2a9d..01a3524db42f 100644
--- a/qobject/qobject.c
+++ b/qobject/qobject.c
@@ -70,3 +70,38 @@ bool qobject_is_equal(const QObject *x, const QObject *y)
 
     return qis_equal[x->base.type](x, y);
 }
+
+void dump_qobject(int comp_indent, QObject *obj, int (*qemu_printf)(const char *fmt, ...))
+{
+    switch (qobject_type(obj)) {
+        case QTYPE_QNUM: {
+            QNum *value = qobject_to(QNum, obj);
+            char *tmp = qnum_to_string(value);
+            qemu_printf("%s", tmp);
+            g_free(tmp);
+            break;
+        }
+        case QTYPE_QSTRING: {
+            QString *value = qobject_to(QString, obj);
+            qemu_printf("%s", qstring_get_str(value));
+            break;
+        }
+        case QTYPE_QDICT: {
+            QDict *value = qobject_to(QDict, obj);
+            dump_qdict(comp_indent, value, qemu_printf);
+            break;
+        }
+        case QTYPE_QLIST: {
+            QList *value = qobject_to(QList, obj);
+            dump_qlist(comp_indent, value, qemu_printf);
+            break;
+        }
+        case QTYPE_QBOOL: {
+            QBool *value = qobject_to(QBool, obj);
+            qemu_printf("%s", qbool_get_bool(value) ? "true" : "false");
+            break;
+        }
+        default:
+            abort();
+    }
+}
-- 
2.31.1



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 2/3] qtest/libqos: add a function to initialize secondary PCI buses
  2021-11-10 13:56 [PATCH 0/3] tests/qtest: add some tests for virtio-net failover Laurent Vivier
  2021-11-10 13:56 ` [PATCH 1/3] qdict: make available dump_qobject(), dump_qdict(), dump_qlist() Laurent Vivier
@ 2021-11-10 13:56 ` Laurent Vivier
  2021-11-10 13:56 ` [PATCH 3/3] tests/qtest: add some tests for virtio-net failover Laurent Vivier
  2 siblings, 0 replies; 6+ messages in thread
From: Laurent Vivier @ 2021-11-10 13:56 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Laurent Vivier, Thomas Huth, qemu-block,
	Michael S. Tsirkin, Markus Armbruster, Hanna Reitz,
	Paolo Bonzini

Scan the PCI devices to find bridge and set PCI_SECONDARY_BUS and
PCI_SUBORDINATE_BUS (algorithm from seabios)

Signed-off-by: Laurent Vivier <lvivier@redhat.com>
---
 include/hw/pci/pci_bridge.h |   8 +++
 tests/qtest/libqos/pci.c    | 118 ++++++++++++++++++++++++++++++++++++
 tests/qtest/libqos/pci.h    |   1 +
 3 files changed, 127 insertions(+)

diff --git a/include/hw/pci/pci_bridge.h b/include/hw/pci/pci_bridge.h
index a94d350034bf..30691a6e5728 100644
--- a/include/hw/pci/pci_bridge.h
+++ b/include/hw/pci/pci_bridge.h
@@ -138,6 +138,7 @@ typedef struct PCIBridgeQemuCap {
     uint64_t mem_pref_64; /* Prefetchable memory to reserve (64-bit MMIO) */
 } PCIBridgeQemuCap;
 
+#define REDHAT_PCI_CAP_TYPE_OFFSET      3
 #define REDHAT_PCI_CAP_RESOURCE_RESERVE 1
 
 /*
@@ -152,6 +153,13 @@ typedef struct PCIResReserve {
     uint64_t mem_pref_64;
 } PCIResReserve;
 
+#define REDHAT_PCI_CAP_RES_RESERVE_BUS_RES     4
+#define REDHAT_PCI_CAP_RES_RESERVE_IO          8
+#define REDHAT_PCI_CAP_RES_RESERVE_MEM         16
+#define REDHAT_PCI_CAP_RES_RESERVE_PREF_MEM_32 20
+#define REDHAT_PCI_CAP_RES_RESERVE_PREF_MEM_64 24
+#define REDHAT_PCI_CAP_RES_RESERVE_CAP_SIZE    32
+
 int pci_bridge_qemu_reserve_cap_init(PCIDevice *dev, int cap_offset,
                                PCIResReserve res_reserve, Error **errp);
 
diff --git a/tests/qtest/libqos/pci.c b/tests/qtest/libqos/pci.c
index e1e96189c821..3f0b18f4750b 100644
--- a/tests/qtest/libqos/pci.c
+++ b/tests/qtest/libqos/pci.c
@@ -13,6 +13,8 @@
 #include "qemu/osdep.h"
 #include "pci.h"
 
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_bridge.h"
 #include "hw/pci/pci_regs.h"
 #include "qemu/host-utils.h"
 #include "qgraph.h"
@@ -99,6 +101,122 @@ void qpci_device_init(QPCIDevice *dev, QPCIBus *bus, QPCIAddress *addr)
     g_assert(!addr->device_id || device_id == addr->device_id);
 }
 
+static uint8_t qpci_find_resource_reserve_capability(QPCIDevice *dev)
+{
+    uint16_t device_id;
+    uint8_t cap = 0;
+
+    if (qpci_config_readw(dev, PCI_VENDOR_ID) != PCI_VENDOR_ID_REDHAT) {
+        return 0;
+    }
+
+    device_id = qpci_config_readw(dev, PCI_DEVICE_ID);
+
+    if (device_id != PCI_DEVICE_ID_REDHAT_PCIE_RP &&
+        device_id != PCI_DEVICE_ID_REDHAT_BRIDGE) {
+        return 0;
+    }
+
+    do {
+        cap = qpci_find_capability(dev, PCI_CAP_ID_VNDR, cap);
+    } while (cap &&
+             qpci_config_readb(dev, cap + REDHAT_PCI_CAP_TYPE_OFFSET) !=
+             REDHAT_PCI_CAP_RESOURCE_RESERVE);
+    if (cap) {
+        uint8_t cap_len = qpci_config_readb(dev, cap + PCI_CAP_FLAGS);
+        if (cap_len < REDHAT_PCI_CAP_RES_RESERVE_CAP_SIZE) {
+            return 0;
+        }
+    }
+    return cap;
+}
+
+static void qpci_secondary_buses_rec(QPCIBus *qbus, int bus, int *pci_bus)
+{
+    QPCIDevice *dev;
+    uint16_t class;
+    uint8_t pribus, secbus, subbus;
+    int i;
+
+    for (i = 0; i < 32; i++) {
+        dev = qpci_device_find(qbus, QPCI_DEVFN(bus + i, 0));
+        if (dev == NULL) {
+            continue;
+        }
+        class = qpci_config_readw(dev, PCI_CLASS_DEVICE);
+        if (class == PCI_CLASS_BRIDGE_PCI) {
+            qpci_config_writeb(dev, PCI_SECONDARY_BUS, 255);
+            qpci_config_writeb(dev, PCI_SUBORDINATE_BUS, 0);
+        }
+        g_free(dev);
+    }
+
+    for (i = 0; i < 32; i++) {
+        dev = qpci_device_find(qbus, QPCI_DEVFN(bus + i, 0));
+        if (dev == NULL) {
+            continue;
+        }
+        class = qpci_config_readw(dev, PCI_CLASS_DEVICE);
+        if (class != PCI_CLASS_BRIDGE_PCI) {
+            continue;
+        }
+
+        pribus = qpci_config_readb(dev, PCI_PRIMARY_BUS);
+        if (pribus != bus) {
+            qpci_config_writeb(dev, PCI_PRIMARY_BUS, bus);
+        }
+
+        secbus = qpci_config_readb(dev, PCI_SECONDARY_BUS);
+        (*pci_bus)++;
+        if (*pci_bus != secbus) {
+            secbus = *pci_bus;
+            qpci_config_writeb(dev, PCI_SECONDARY_BUS, secbus);
+        }
+
+        subbus = qpci_config_readb(dev, PCI_SUBORDINATE_BUS);
+        qpci_config_writeb(dev, PCI_SUBORDINATE_BUS, 255);
+
+        qpci_secondary_buses_rec(qbus, secbus << 5, pci_bus);
+
+        if (subbus != *pci_bus) {
+            uint8_t res_bus = *pci_bus;
+            uint8_t cap = qpci_find_resource_reserve_capability(dev);
+
+            if (cap) {
+                uint32_t tmp_res_bus;
+
+                tmp_res_bus = qpci_config_readl(dev, cap +
+                                            REDHAT_PCI_CAP_RES_RESERVE_BUS_RES);
+                if (tmp_res_bus != (uint32_t)-1) {
+                    res_bus = tmp_res_bus & 0xFF;
+                    if ((uint8_t)(res_bus + secbus) < secbus ||
+                        (uint8_t)(res_bus + secbus) < res_bus) {
+                        res_bus = 0;
+                    }
+                    if (secbus + res_bus > *pci_bus) {
+                        res_bus = secbus + res_bus;
+                    }
+                }
+            }
+            subbus = res_bus;
+            *pci_bus = res_bus;
+        }
+
+        qpci_config_writeb(dev, PCI_SUBORDINATE_BUS, subbus);
+        g_free(dev);
+    }
+}
+
+int qpci_secondary_buses_init(QPCIBus *bus)
+{
+    int last_bus = 0;
+
+    qpci_secondary_buses_rec(bus, 0, &last_bus);
+
+    return last_bus;
+}
+
+
 void qpci_device_enable(QPCIDevice *dev)
 {
     uint16_t cmd;
diff --git a/tests/qtest/libqos/pci.h b/tests/qtest/libqos/pci.h
index ee64fdecbda8..becb800f9e6a 100644
--- a/tests/qtest/libqos/pci.h
+++ b/tests/qtest/libqos/pci.h
@@ -81,6 +81,7 @@ void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id,
                          void *data);
 QPCIDevice *qpci_device_find(QPCIBus *bus, int devfn);
 void qpci_device_init(QPCIDevice *dev, QPCIBus *bus, QPCIAddress *addr);
+int qpci_secondary_buses_init(QPCIBus *bus);
 
 bool qpci_has_buggy_msi(QPCIDevice *dev);
 bool qpci_check_buggy_msi(QPCIDevice *dev);
-- 
2.31.1



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 3/3] tests/qtest: add some tests for virtio-net failover
  2021-11-10 13:56 [PATCH 0/3] tests/qtest: add some tests for virtio-net failover Laurent Vivier
  2021-11-10 13:56 ` [PATCH 1/3] qdict: make available dump_qobject(), dump_qdict(), dump_qlist() Laurent Vivier
  2021-11-10 13:56 ` [PATCH 2/3] qtest/libqos: add a function to initialize secondary PCI buses Laurent Vivier
@ 2021-11-10 13:56 ` Laurent Vivier
  2 siblings, 0 replies; 6+ messages in thread
From: Laurent Vivier @ 2021-11-10 13:56 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Laurent Vivier, Thomas Huth, qemu-block,
	Michael S. Tsirkin, Markus Armbruster, Hanna Reitz,
	Paolo Bonzini

Add test cases to test several error cases that must be
generated by invalid failover configuration.

Add a combination of coldplug and hotplug test cases to be
sure the primary is correctly managed according the
presence or not of the STANDBY feature.

Signed-off-by: Laurent Vivier <lvivier@redhat.com>
---
 tests/qtest/meson.build           |   3 +
 tests/qtest/virtio-net-failover.c | 567 ++++++++++++++++++++++++++++++
 2 files changed, 570 insertions(+)
 create mode 100644 tests/qtest/virtio-net-failover.c

diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index c9d8458062ff..6d66bf522156 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -22,6 +22,9 @@ qtests_generic = \
   (config_all_devices.has_key('CONFIG_VIRTIO_SCSI') ? ['fuzz-virtio-scsi-test'] : []) + \
   (config_all_devices.has_key('CONFIG_SB16') ? ['fuzz-sb16-test'] : []) + \
   (config_all_devices.has_key('CONFIG_SDHCI_PCI') ? ['fuzz-sdcard-test'] : []) + \
+  (config_all_devices.has_key('CONFIG_VIRTIO_NET') and \
+   config_all_devices.has_key('CONFIG_Q35') and \
+   config_all_devices.has_key('CONFIG_VIRTIO_PCI') ? ['virtio-net-failover'] : []) + \
   [
   'cdrom-test',
   'device-introspect-test',
diff --git a/tests/qtest/virtio-net-failover.c b/tests/qtest/virtio-net-failover.c
new file mode 100644
index 000000000000..c7b03b887179
--- /dev/null
+++ b/tests/qtest/virtio-net-failover.c
@@ -0,0 +1,567 @@
+#include "qemu/osdep.h"
+#include "libqos/libqtest.h"
+#include "libqos/pci.h"
+#include "libqos/pci-pc.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qlist.h"
+#include "qapi/qmp/qjson.h"
+#include "libqos/malloc-pc.h"
+#include "libqos/virtio-pci.h"
+#include "hw/pci/pci.h"
+
+#define BASE_MACHINE "-M q35 -nodefaults " \
+    "-device pcie-root-port,id=root0,addr=0x1,bus=pcie.0,chassis=1 " \
+    "-device pcie-root-port,id=root1,addr=0x2,bus=pcie.0,chassis=2 "
+
+#define MAC_PRIMARY "52:54:00:11:11:11"
+#define MAC_STANDBY "52:54:00:22:22:22"
+
+static QGuestAllocator guest_malloc;
+static QPCIBus *pcibus;
+
+static QTestState *machine_start(const char *args)
+{
+    QTestState *qts;
+    QPCIDevice *dev;
+
+    qts = qtest_init(args);
+
+    pc_alloc_init(&guest_malloc, qts, 0);
+    pcibus = qpci_new_pc(qts, &guest_malloc);
+    g_assert(qpci_secondary_buses_init(pcibus) == 2);
+
+    dev = qpci_device_find(pcibus, QPCI_DEVFN(1, 0)); /* root0 */
+    g_assert_nonnull(dev);
+
+    qpci_device_enable(dev);
+    qpci_iomap(dev, 4, NULL);
+
+    g_free(dev);
+
+    dev = qpci_device_find(pcibus, QPCI_DEVFN(2, 0)); /* root1 */
+    g_assert_nonnull(dev);
+
+    qpci_device_enable(dev);
+    qpci_iomap(dev, 4, NULL);
+
+    g_free(dev);
+
+    return qts;
+}
+
+static void machine_stop(QTestState *qts)
+{
+    qpci_free_pc(pcibus);
+    alloc_destroy(&guest_malloc);
+    qtest_quit(qts);
+}
+
+static void test_error_id(void)
+{
+    QTestState *qts;
+    QDict *resp;
+    QDict *err;
+
+    qts = machine_start(BASE_MACHINE
+                        "-device virtio-net,bus=root0,id=standby0,failover=on");
+
+    resp = qtest_qmp(qts, "{'execute': 'device_add',"
+                          "'arguments': {"
+                          "'driver': 'virtio-net',"
+                          "'bus': 'root1',"
+                          "'failover_pair_id': 'standby0'"
+                          "} }");
+    g_assert(qdict_haskey(resp, "error"));
+
+    err = qdict_get_qdict(resp, "error");
+    g_assert(qdict_haskey(err, "desc"));
+
+    g_assert_cmpstr(qdict_get_str(err, "desc"), ==,
+                    "Device with failover_pair_id needs to have id");
+
+    qobject_unref(resp);
+
+    machine_stop(qts);
+}
+
+static void test_error_pcie(void)
+{
+    QTestState *qts;
+    QDict *resp;
+    QDict *err;
+
+    qts = machine_start(BASE_MACHINE
+                        "-device virtio-net,bus=root0,id=standby0,failover=on");
+
+    resp = qtest_qmp(qts, "{'execute': 'device_add',"
+                          "'arguments': {"
+                          "'driver': 'virtio-net',"
+                          "'id': 'primary0',"
+                          "'bus': 'pcie.0',"
+                          "'failover_pair_id': 'standby0'"
+                          "} }");
+    g_assert(qdict_haskey(resp, "error"));
+
+    err = qdict_get_qdict(resp, "error");
+    g_assert(qdict_haskey(err, "desc"));
+
+    g_assert_cmpstr(qdict_get_str(err, "desc"), ==,
+                    "Bus 'pcie.0' does not support hotplugging");
+
+    qobject_unref(resp);
+
+    machine_stop(qts);
+}
+
+static QDict *find_device(QDict *bus, const char *name)
+{
+    const QObject *obj;
+    QList *devices;
+    QList *list;
+
+    devices = qdict_get_qlist(bus, "devices");
+    if (devices == NULL) {
+        return NULL;
+    }
+
+    list = qlist_copy(devices);
+    while ((obj = qlist_pop(list))) {
+        QDict *device;
+
+        device = qobject_to(QDict, obj);
+
+        if (qdict_haskey(device, "pci_bridge")) {
+            QDict *bridge;
+            QDict *bridge_device;
+
+            bridge = qdict_get_qdict(device, "pci_bridge");
+
+            if (qdict_haskey(bridge, "devices")) {
+                bridge_device = find_device(bridge, name);
+                if (bridge_device) {
+                    qobject_unref(list);
+                    return bridge_device;
+                }
+            }
+        }
+
+        if (!qdict_haskey(device, "qdev_id")) {
+            continue;
+        }
+
+        if (strcmp(qdict_get_str(device, "qdev_id"), name) == 0) {
+            qobject_ref(device);
+            qobject_unref(list);
+            return device;
+        }
+    }
+    qobject_unref(list);
+
+    return NULL;
+}
+
+static QDict *get_bus(QTestState *qts, int num)
+{
+    QObject *obj;
+    QDict *resp;
+    QList *ret;
+
+    resp = qtest_qmp(qts, "{ 'execute': 'query-pci' }");
+    g_assert(qdict_haskey(resp, "return"));
+
+    ret = qdict_get_qlist(resp, "return");
+    g_assert_nonnull(ret);
+
+    while ((obj = qlist_pop(ret))) {
+        QDict *bus;
+
+        bus = qobject_to(QDict, obj);
+        if (!qdict_haskey(bus, "bus")) {
+            continue;
+        }
+        if (qdict_get_int(bus, "bus") == num) {
+            qobject_ref(bus);
+            qobject_unref(resp);
+            return bus;
+        }
+    }
+    qobject_unref(resp);
+
+    return NULL;
+}
+
+static char *get_mac(QTestState *qts, const char *name)
+{
+    QDict *resp;
+    char *mac;
+
+    resp = qtest_qmp(qts, "{ 'execute': 'qom-get', "
+                     "'arguments': { "
+                     "'path': %s, "
+                     "'property': 'mac' } }", name);
+
+    g_assert(qdict_haskey(resp, "return"));
+
+    mac = g_strdup( qdict_get_str(resp, "return"));
+
+    qobject_unref(resp);
+
+    return mac;
+}
+
+static void check_cards(QTestState *qts, bool standby, bool primary)
+{
+    QDict *device;
+    QDict *bus;
+    char *mac;
+
+    bus = get_bus(qts, 0);
+    device = find_device(bus, "standby0");
+    if (standby) {
+        g_assert_nonnull(device);
+        qobject_unref(device);
+
+        mac = get_mac(qts, "/machine/peripheral/standby0");
+        g_assert_cmpstr(mac, ==, MAC_STANDBY);
+        g_free(mac);
+    } else {
+       g_assert_null(device);
+    }
+
+    device = find_device(bus, "primary0");
+    if (primary) {
+        g_assert_nonnull(device);
+        qobject_unref(device);
+
+        mac = get_mac(qts, "/machine/peripheral/primary0");
+        g_assert_cmpstr(mac, ==, MAC_PRIMARY);
+        g_free(mac);
+    } else {
+       g_assert_null(device);
+    }
+    qobject_unref(bus);
+}
+
+static void test_on(void)
+{
+    QTestState *qts;
+
+    qts = machine_start(BASE_MACHINE
+                        "-netdev user,id=hs0 "
+                        "-device virtio-net,bus=root0,id=standby0,"
+                        "failover=on,netdev=hs0,mac="MAC_STANDBY" "
+                        "-device virtio-net,bus=root1,id=primary0,"
+                        "failover_pair_id=standby0,netdev=hs1,mac="MAC_PRIMARY);
+
+    check_cards(qts, true, false); /* standby, no primary */
+
+    machine_stop(qts);
+}
+
+static void test_on_mismatch(void)
+{
+    QTestState *qts;
+
+    qts = machine_start(BASE_MACHINE
+                     "-netdev user,id=hs0 "
+                     "-device virtio-net,bus=root0,id=standby0,"
+                     "failover=on,netdev=hs0,mac="MAC_STANDBY" "
+                     "-netdev user,id=hs1 "
+                     "-device virtio-net,bus=root1,id=primary0,"
+                     "failover_pair_id=standby1,netdev=hs1,mac="MAC_PRIMARY);
+
+    check_cards(qts, true, true); /* standby, primary (but no failover) */
+
+    machine_stop(qts);
+}
+
+static void test_off(void)
+{
+    QTestState *qts;
+
+    qts = machine_start(BASE_MACHINE
+                     "-netdev user,id=hs0 "
+                     "-device virtio-net,bus=root0,id=standby0,"
+                     "failover=off,netdev=hs0,mac="MAC_STANDBY" "
+                     "-netdev user,id=hs1 "
+                     "-device virtio-net,bus=root1,id=primary0,"
+                     "failover_pair_id=standby0,netdev=hs1,mac="MAC_PRIMARY);
+
+    check_cards(qts, true, true); /* standby, primary (but no failover) */
+
+    machine_stop(qts);
+}
+
+static void start_virtio_net(QTestState *qts)
+{
+    QVirtioPCIDevice *dev;
+    uint64_t features;
+    QPCIAddress addr;
+    QDict *resp;
+    QDict *data;
+
+    addr.devfn = QPCI_DEVFN(1 << 5, 0);
+    dev = virtio_pci_new(pcibus, &addr);
+    g_assert_nonnull(dev);
+    qvirtio_pci_device_enable(dev);
+    qvirtio_start_device(&dev->vdev);
+    features = qvirtio_get_features(&dev->vdev);
+    features = features & ~(QVIRTIO_F_BAD_FEATURE |
+                            (1ull << VIRTIO_RING_F_INDIRECT_DESC) |
+                            (1ull << VIRTIO_RING_F_EVENT_IDX));
+    qvirtio_set_features(&dev->vdev, features);
+    qvirtio_set_driver_ok(&dev->vdev);
+
+    resp = qtest_qmp_eventwait_ref(qts, "FAILOVER_NEGOTIATED");
+    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"), ==, "standby0");
+
+    qobject_unref(resp);
+}
+
+static void test_enabled(void)
+{
+    QTestState *qts;
+
+    qts = machine_start(BASE_MACHINE
+                     "-netdev user,id=hs0 "
+                     "-device virtio-net,bus=root0,id=standby0,"
+                     "failover=on,netdev=hs0,mac="MAC_STANDBY" "
+                     "-netdev user,id=hs1 "
+                     "-device virtio-net,bus=root1,id=primary0,"
+                     "failover_pair_id=standby0,netdev=hs1,mac="MAC_PRIMARY" "
+                     );
+
+    check_cards(qts, true, false); /* standby, no primary */
+
+    start_virtio_net(qts);
+
+    check_cards(qts, true, true); /* standby, primary with failover */
+
+    machine_stop(qts);
+}
+
+static void test_hotplug_1(void)
+{
+    QTestState *qts;
+
+    qts = machine_start(BASE_MACHINE
+                     "-netdev user,id=hs0 "
+                     "-device virtio-net,bus=root0,id=standby0,"
+                     "failover=on,netdev=hs0,mac="MAC_STANDBY" "
+                     "-netdev user,id=hs1 "
+                     );
+
+    check_cards(qts, true, false); /* no standby, no primary */
+
+    start_virtio_net(qts);
+
+    check_cards(qts, true, false);
+
+    qtest_qmp_device_add(qts, "virtio-net", "primary0",
+                         "{'bus': 'root1',"
+                         "'failover_pair_id': 'standby0',"
+                         "'netdev': 'hs1',"
+                         "'mac': '"MAC_PRIMARY"'}");
+
+    check_cards(qts, true, true);
+
+    machine_stop(qts);
+}
+
+static void test_hotplug_1_reverse(void)
+{
+    QTestState *qts;
+
+    qts = machine_start(BASE_MACHINE
+                     "-netdev user,id=hs0 "
+                     "-netdev user,id=hs1 "
+                     "-device virtio-net,bus=root1,id=primary0,"
+                     "failover_pair_id=standby0,netdev=hs1,mac="MAC_PRIMARY" "
+                     );
+
+    check_cards(qts, false, true);
+
+    qtest_qmp_device_add(qts, "virtio-net", "standby0",
+                         "{'bus': 'root0',"
+                         "'failover': 'on',"
+                         "'netdev': 'hs0',"
+                         "'mac': '"MAC_STANDBY"'}");
+
+    check_cards(qts, true, true); /* XXX: sounds like a bug */
+
+    start_virtio_net(qts);
+
+    check_cards(qts, true, true);
+
+    machine_stop(qts);
+}
+
+static void test_hotplug_2(void)
+{
+    QTestState *qts;
+
+    qts = machine_start(BASE_MACHINE
+                     "-netdev user,id=hs0 "
+                     "-netdev user,id=hs1 "
+                     );
+
+    check_cards(qts, false, false); /* no standby, no primary */
+
+    qtest_qmp_device_add(qts, "virtio-net", "standby0",
+                         "{'bus': 'root0',"
+                         "'failover': 'on',"
+                         "'netdev': 'hs0',"
+                         "'mac': '"MAC_STANDBY"'}");
+
+    check_cards(qts, true, false);
+
+    start_virtio_net(qts);
+
+    check_cards(qts, true, false);
+
+    qtest_qmp_device_add(qts, "virtio-net", "primary0",
+                         "{'bus': 'root1',"
+                         "'failover_pair_id': 'standby0',"
+                         "'netdev': 'hs1',"
+                         "'mac': '"MAC_PRIMARY"'}");
+
+    check_cards(qts, true, true);
+
+    machine_stop(qts);
+}
+
+static void test_hotplug_2_reverse(void)
+{
+    QTestState *qts;
+
+    qts = machine_start(BASE_MACHINE
+                     "-netdev user,id=hs0 "
+                     "-netdev user,id=hs1 "
+                     );
+
+    check_cards(qts, false, false); /* no standby, no primary */
+
+    qtest_qmp_device_add(qts, "virtio-net", "primary0",
+                         "{'bus': 'root1',"
+                         "'failover_pair_id': 'standby0',"
+                         "'netdev': 'hs1',"
+                         "'mac': '"MAC_PRIMARY"'}");
+
+    check_cards(qts, false, true);
+
+    qtest_qmp_device_add(qts, "virtio-net", "standby0",
+                         "{'bus': 'root0',"
+                         "'failover': 'on',"
+                         "'netdev': 'hs0',"
+                         "'mac': '"MAC_STANDBY"'}");
+
+    check_cards(qts, true, true); /* XXX: sounds like a bug */
+
+    start_virtio_net(qts);
+
+    check_cards(qts, true, true);
+
+    machine_stop(qts);
+}
+
+static void test_migrate(void)
+{
+    QTestState *qts;
+    QDict *resp, *args, *data;
+    g_autofree gchar *tmpfile = g_strdup_printf(
+                                            "/tmp/failover_test_migrate-%u-%u",
+                                            getpid(), g_test_rand_int());
+    g_autofree gchar *uri = g_strdup_printf("exec: cat > %s", tmpfile);
+
+    qts = machine_start(BASE_MACHINE
+                     "-netdev user,id=hs0 "
+                     "-netdev user,id=hs1 "
+                     );
+
+    check_cards(qts, false, false); /* no standby, no primary */
+
+    qtest_qmp_device_add(qts, "virtio-net", "standby0",
+                         "{'bus': 'root0',"
+                         "'failover': 'on',"
+                         "'netdev': 'hs0',"
+                         "'mac': '"MAC_STANDBY"'}");
+
+    check_cards(qts, true, false);
+
+    start_virtio_net(qts);
+
+    check_cards(qts, true, false);
+
+    qtest_qmp_device_add(qts, "virtio-net", "primary0",
+                         "{'bus': 'root1',"
+                         "'failover_pair_id': 'standby0',"
+                         "'netdev': 'hs1',"
+                         "'mac': '"MAC_PRIMARY"'}");
+
+    check_cards(qts, true, true);
+
+    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);
+
+    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"), ==, "primary0");
+
+    qobject_unref(resp);
+
+    /*
+     * normaly OS manages the hotunplug request and we should wait for:
+     *   qtest_qmp_eventwait(qts, "DEVICE_DELETED");
+     * and then:
+     *   check_cards(qts, true, false);
+     */
+
+    unlink(tmpfile);
+
+    machine_stop(qts);
+}
+
+int main(int argc, char **argv)
+{
+    const char *arch;
+
+    g_test_init(&argc, &argv, NULL);
+
+    arch = qtest_get_arch();
+    if (strcmp(arch, "i386") && strcmp(arch, "x86_64")) {
+        g_test_message("Skipping test for non-x86");
+        return g_test_run();
+    }
+
+    qtest_add_func("failover-virtio-net/params/error/id", test_error_id);
+    qtest_add_func("failover-virtio-net/params/error/pcie", test_error_pcie);
+    qtest_add_func("failover-virtio-net/params/error/on", test_on);
+    qtest_add_func("failover-virtio-net/params/error/on_mismatch",
+                   test_on_mismatch);
+    qtest_add_func("failover-virtio-net/params/error/off", test_off);
+    qtest_add_func("failover-virtio-net/params/error/enabled", test_enabled);
+    qtest_add_func("failover-virtio-net/params/error/hotplug_1",
+                   test_hotplug_1);
+    qtest_add_func("failover-virtio-net/params/error/hotplug_1_reverse",
+                   test_hotplug_1_reverse);
+    qtest_add_func("failover-virtio-net/params/error/hotplug_2",
+                   test_hotplug_2);
+    qtest_add_func("failover-virtio-net/params/error/hotplug_2_reverse",
+                   test_hotplug_2_reverse);
+    qtest_add_func("failover-virtio-net/params/error/migrate",
+                   test_migrate);
+
+    return g_test_run();
+}
-- 
2.31.1



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/3] qdict: make available dump_qobject(), dump_qdict(), dump_qlist()
  2021-11-10 13:56 ` [PATCH 1/3] qdict: make available dump_qobject(), dump_qdict(), dump_qlist() Laurent Vivier
@ 2021-11-10 16:17   ` Markus Armbruster
  2021-11-10 17:11     ` Laurent Vivier
  0 siblings, 1 reply; 6+ messages in thread
From: Markus Armbruster @ 2021-11-10 16:17 UTC (permalink / raw)
  To: Laurent Vivier
  Cc: Kevin Wolf, Thomas Huth, qemu-block, Michael S. Tsirkin,
	qemu-devel, Hanna Reitz, Paolo Bonzini

Laurent Vivier <lvivier@redhat.com> writes:

> move them from block/qapi.c to qobject/qdict.c, qobject/qlist.c,
> qobject/qobject.c
>
> This is useful to debug qobjects
>
> Signed-off-by: Laurent Vivier <lvivier@redhat.com>

I think qobject_to_json_pretty() is better suited to debugging, because
it preserves differences like the one between the string "1" and the
number 1.



^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/3] qdict: make available dump_qobject(), dump_qdict(), dump_qlist()
  2021-11-10 16:17   ` Markus Armbruster
@ 2021-11-10 17:11     ` Laurent Vivier
  0 siblings, 0 replies; 6+ messages in thread
From: Laurent Vivier @ 2021-11-10 17:11 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, Thomas Huth, qemu-block, Michael S. Tsirkin,
	qemu-devel, Hanna Reitz, Paolo Bonzini

On 10/11/2021 17:17, Markus Armbruster wrote:
> Laurent Vivier <lvivier@redhat.com> writes:
> 
>> move them from block/qapi.c to qobject/qdict.c, qobject/qlist.c,
>> qobject/qobject.c
>>
>> This is useful to debug qobjects
>>
>> Signed-off-by: Laurent Vivier <lvivier@redhat.com>
> 
> I think qobject_to_json_pretty() is better suited to debugging, because
> it preserves differences like the one between the string "1" and the
> number 1.
> 

Yes, you're right. I didn't think about this solution.

So we can drop this patch.

Thanks,
Laurent



^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2021-11-10 17:15 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-11-10 13:56 [PATCH 0/3] tests/qtest: add some tests for virtio-net failover Laurent Vivier
2021-11-10 13:56 ` [PATCH 1/3] qdict: make available dump_qobject(), dump_qdict(), dump_qlist() Laurent Vivier
2021-11-10 16:17   ` Markus Armbruster
2021-11-10 17:11     ` Laurent Vivier
2021-11-10 13:56 ` [PATCH 2/3] qtest/libqos: add a function to initialize secondary PCI buses Laurent Vivier
2021-11-10 13:56 ` [PATCH 3/3] tests/qtest: add some tests for virtio-net failover Laurent Vivier

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).