Previously fdb/show required exactly one bridge argument.  This change
lets it accept several bridge names in a single call, and show all
bridges when no argument is given.  This reduces the number of unixctl
round-trips for clients that poll FDB entries across many bridges
(e.g. EVPN agents managing multiple VNIs), while still allowing a
client to query only the subset of bridges it cares about.

When more than one bridge is shown, the entries are grouped under a
"Bridge" column, sized to the widest bridge name shown, that prints
each bridge name only on its first row; a bridge with no entries is
listed as a name-only row so it stays visible.  JSON output is now an
object keyed by bridge name whose values are arrays of that bridge's
entries, e.g. {"br0": [...], "br1": [...]}; this replaces the bare
entries array previously returned for a single bridge.  Single-bridge
text output is unchanged.

Signed-off-by: Takeru Hayasaka <[email protected]>
---
v4:
- Rebased on current master; the NEWS entry now lives under the existing
  "ovs-appctl" section.  No functional changes.
v3:
- No arguments now shows all bridges, matching commands like fdb/flush
  (suggested by Ilya Maximets); one or more bridge names still restricts
  output to just those bridges.
- Replaced the v2 "bridge <name>" header line with a "Bridge" column,
  added only when more than one bridge is shown, so single-bridge output
  is byte-for-byte identical to before.  This reverts the test churn
  introduced in v2 (including tests/stp.at).
- The "Bridge" column is sized to the widest bridge name shown and
  groups each bridge's entries, printing the name only on the bridge's
  first row.  A bridge with no entries is listed as a name-only row, so
  that (as in JSON) an existing-but-empty bridge stays visible in a
  multi-bridge listing.  Added tests covering long names, a bridge with
  multiple entries, and an empty bridge.
- JSON output is now an object keyed by bridge name ({"br0": [...]})
  rather than an array of {"bridge", "entries"} objects.
- Trimmed the NEWS entry to a single item.
- Updated the ovs-vswitchd(8) man page to match the above.
v2:
- Moved the "bridge <name>" prefixing into the per-bridge text and JSON
  helpers (suggested by Mike Pattrick).
- Updated the unixctl usage hint and the ovs-vswitchd(8) man page.
- Added tests for an empty-FDB JSON response and for the error path when
  a requested bridge does not exist.

 NEWS                       |   2 +
 ofproto/ofproto-dpif.c     |  84 ++++++++++++++++---
 tests/ofproto-dpif.at      | 162 +++++++++++++++++++++++++++++--------
 vswitchd/ovs-vswitchd.8.in |  10 ++-
 4 files changed, 211 insertions(+), 47 deletions(-)

diff --git a/NEWS b/NEWS
index c9331321c0ad..608da24ca928 100644
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,8 @@ Post-v3.7.0
      * 'dpif-netdev/pmd-stats-show' command was removed in favor of the more
        informative and better structured 'dpif-netdev/pmd-perf-show', which
        now also provides statistics for the "main" thread.
+     * "fdb/show" now accepts multiple bridge names, and shows all bridges
+       when no bridge is specified.
    - Python:
      * The ovstest Python module is now removed.  It was only used by the
        ovs-test and ovs-l3ping utilities.
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 729bc6f1d4ac..0c970b55fd27 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -6165,11 +6165,11 @@ ofbundle_get_a_port(const struct ofbundle *bundle)
 
 static void
 ofproto_unixctl_fdb_show_text(const struct ofproto_dpif *ofproto,
-                              struct ds *ds)
+                              struct ds *ds, int name_width)
 {
     const struct mac_entry *e;
+    bool first = true;
 
-    ds_put_cstr(ds, " port  VLAN  MAC                Age\n");
     ovs_rwlock_rdlock(&ofproto->ml->rwlock);
     LIST_FOR_EACH (e, lru_node, &ofproto->ml->lrus) {
         struct ofbundle *bundle = mac_entry_get_port(ofproto->ml, e);
@@ -6178,6 +6178,12 @@ ofproto_unixctl_fdb_show_text(const struct ofproto_dpif 
*ofproto,
 
         ofputil_port_to_string(ofbundle_get_a_port(bundle)->up.ofp_port,
                 NULL, name, sizeof name);
+        if (name_width) {
+            /* Print the bridge name only on its first entry so that a
+             * bridge's rows read as a single group. */
+            ds_put_format(ds, " %-*s", name_width,
+                          first ? ofproto->up.name : "");
+        }
         ds_put_format(ds, "%5s  %4d  "ETH_ADDR_FMT"  ",
                 name, e->vlan, ETH_ADDR_ARGS(e->mac));
         if (MAC_ENTRY_AGE_STATIC_ENTRY == age) {
@@ -6185,6 +6191,12 @@ ofproto_unixctl_fdb_show_text(const struct ofproto_dpif 
*ofproto,
         } else {
             ds_put_format(ds, "%3d\n", age);
         }
+        first = false;
+    }
+    if (name_width && first) {
+        /* Bridge has no entries; still show its name so that it stays
+         * visible in a multi-bridge listing, matching JSON output. */
+        ds_put_format(ds, " %s\n", ofproto->up.name);
     }
     ovs_rwlock_unlock(&ofproto->ml->rwlock);
 }
@@ -6233,28 +6245,74 @@ done_unlock:
 }
 
 static void
-ofproto_unixctl_fdb_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
-                          const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
+ofproto_unixctl_fdb_show(struct unixctl_conn *conn, int argc,
+                          const char *argv[], void *aux OVS_UNUSED)
 {
-    const struct ofproto_dpif *ofproto = ofproto_dpif_lookup_by_name(argv[1]);
+    const struct ofproto_dpif **ofprotos;
+    size_t n = 0;
 
-    if (!ofproto) {
-        unixctl_command_reply_error(conn, "no such bridge");
-        return;
+    if (argc > 1) {
+        /* Validate all requested bridges up front so that a bad name
+         * produces an error with no partial output. */
+        ofprotos = xmalloc((argc - 1) * sizeof *ofprotos);
+        for (int i = 1; i < argc; i++) {
+            const struct ofproto_dpif *ofproto =
+                ofproto_dpif_lookup_by_name(argv[i]);
+
+            if (!ofproto) {
+                unixctl_command_reply_error(conn, "no such bridge");
+                free(ofprotos);
+                return;
+            }
+            ofprotos[n++] = ofproto;
+        }
+    } else {
+        /* No bridge specified: show all of them. */
+        struct ofproto_dpif *ofproto;
+
+        ofprotos = xmalloc(hmap_count(&all_ofproto_dpifs_by_name)
+                           * sizeof *ofprotos);
+        HMAP_FOR_EACH (ofproto, all_ofproto_dpifs_by_name_node,
+                       &all_ofproto_dpifs_by_name) {
+            ofprotos[n++] = ofproto;
+        }
     }
 
     if (unixctl_command_get_output_format(conn) == UNIXCTL_OUTPUT_FMT_JSON) {
-        struct json *fdb_entries;
+        struct json *obj = json_object_create();
 
-        ofproto_unixctl_fdb_show_json(ofproto, &fdb_entries);
-        unixctl_command_reply_json(conn, fdb_entries);
+        for (size_t i = 0; i < n; i++) {
+            struct json *entries;
+
+            ofproto_unixctl_fdb_show_json(ofprotos[i], &entries);
+            json_object_put(obj, ofprotos[i]->up.name, entries);
+        }
+
+        unixctl_command_reply_json(conn, obj);
     } else {
         struct ds ds = DS_EMPTY_INITIALIZER;
+        int name_width = 0;
+
+        if (n > 1) {
+            name_width = strlen("Bridge");
+            for (size_t i = 0; i < n; i++) {
+                int len = strlen(ofprotos[i]->up.name);
+
+                name_width = MAX(name_width, len);
+            }
+            ds_put_format(&ds, " %-*s", name_width, "Bridge");
+        }
+        ds_put_cstr(&ds, " port  VLAN  MAC                Age\n");
+
+        for (size_t i = 0; i < n; i++) {
+            ofproto_unixctl_fdb_show_text(ofprotos[i], &ds, name_width);
+        }
 
-        ofproto_unixctl_fdb_show_text(ofproto, &ds);
         unixctl_command_reply(conn, ds_cstr(&ds));
         ds_destroy(&ds);
     }
+
+    free(ofprotos);
 }
 
 static void
@@ -7122,7 +7180,7 @@ ofproto_unixctl_init(void)
                              ofproto_unixctl_fdb_delete, NULL);
     unixctl_command_register("fdb/flush", "[bridge]", 0, 1,
                              ofproto_unixctl_fdb_flush, NULL);
-    unixctl_command_register("fdb/show", "bridge", 1, 1,
+    unixctl_command_register("fdb/show", "[bridge...]", 0, INT_MAX,
                              ofproto_unixctl_fdb_show, NULL);
     unixctl_command_register("fdb/stats-clear", "[bridge]", 0, 1,
                              ofproto_unixctl_fdb_stats_clear, NULL);
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 9e64e8ee4bd7..56e3b8a65f97 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -7543,6 +7543,12 @@ add_of_ports br0 1 2 3
 
 
arp='eth_type(0x0806),arp(sip=192.168.0.1,tip=192.168.0.2,op=1,sha=50:54:00:00:00:05,tha=00:00:00:00:00:00)'
 
+dnl Check json output for an empty FDB.
+AT_CHECK([ovs-appctl --format json --pretty fdb/show br0], [0], [dnl
+[{
+  "br0": []}]
+])
+
 # Trace an ARP packet arriving on p3, to create a MAC learning entry.
 OFPROTO_TRACE(
   [ovs-dummy],
@@ -7574,17 +7580,18 @@ AT_CHECK_UNQUOTED([ovs-appctl fdb/show br0 | sed 
's/[[0-9]]\{1,\}$/?/'], [0], [d
 dnl Check json output.
 AT_CHECK([ovs-appctl --format json --pretty fdb/show br0 \
           | sed 's/"age": [[0-9]]*/"age": ?/g'], [0], [dnl
-[[
-  {
-    "age": ?,
-    "mac": "50:54:00:00:00:05",
-    "port": 3,
-    "vlan": 0},
-  {
-    "age": ?,
-    "mac": "50:54:00:00:00:06",
-    "port": 1,
-    "vlan": 0}]]
+[{
+  "br0": [
+    {
+      "age": ?,
+      "mac": "50:54:00:00:00:05",
+      "port": 3,
+      "vlan": 0},
+    {
+      "age": ?,
+      "mac": "50:54:00:00:00:06",
+      "port": 1,
+      "vlan": 0}]}]
 ])
 
 # Trace a packet arrival that updates the first learned MAC entry.
@@ -7639,6 +7646,96 @@ AT_CHECK_UNQUOTED([ovs-appctl fdb/show br1 | sed 
's/[[0-9]]\{1,\}$/?/'], [0], [d
     5     0  50:54:00:00:00:07    ?
 ])
 
+# Test fdb/show with multiple bridges.  A "Bridge" column groups each
+# bridge's entries, printing the name only on the bridge's first row.
+AT_CHECK_UNQUOTED([ovs-appctl fdb/show br0 br1 | sed 's/[[0-9]]\{1,\}$/?/'], 
[0], [dnl
+ Bridge port  VLAN  MAC                Age
+ br0       2     0  50:54:00:00:00:05    ?
+ br1       5     0  50:54:00:00:00:07    ?
+])
+
+# Test fdb/show without arguments: show all bridges.
+AT_CHECK_UNQUOTED([ovs-appctl fdb/show | sed 's/[[0-9]]\{1,\}$/?/' | sort], 
[0], [dnl
+ Bridge port  VLAN  MAC                Age
+ br0       2     0  50:54:00:00:00:05    ?
+ br1       5     0  50:54:00:00:00:07    ?
+])
+
+dnl Check json output with multiple bridges: keyed by bridge name.
+AT_CHECK([ovs-appctl --format json --pretty fdb/show br0 br1 \
+          | sed 's/"age": [[0-9]]*/"age": ?/g'], [0], [dnl
+[{
+  "br0": [
+    {
+      "age": ?,
+      "mac": "50:54:00:00:00:05",
+      "port": 2,
+      "vlan": 0}],
+  "br1": [
+    {
+      "age": ?,
+      "mac": "50:54:00:00:00:07",
+      "port": 5,
+      "vlan": 0}]}]
+])
+
+# Test fdb/show with a non-existent bridge mixed in: must reply with an
+# error and emit no partial output.
+AT_CHECK([ovs-appctl fdb/show br0 nonexistent], [2], [],
+  [no such bridge
+ovs-appctl: ovs-vswitchd: server returned an error
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif - fdb/show bridge column width])
+OVS_VSWITCHD_START
+AT_CHECK([ovs-vsctl -- add-br br-nexthop -- set bridge br-nexthop 
datapath-type=dummy])
+add_of_ports br0 1 3
+add_of_ports br-nexthop 2
+
+dnl br0 holds several MAC entries; br-nexthop holds one.
+AT_CHECK([ovs-appctl fdb/add br0 p1 0 50:54:00:00:00:05])
+AT_CHECK([ovs-appctl fdb/add br0 p3 0 50:54:00:00:00:06])
+AT_CHECK([ovs-appctl fdb/add br-nexthop p2 0 50:54:00:00:00:07])
+
+dnl The "Bridge" column widens to the longest bridge name shown.  A
+dnl bridge with several MAC entries prints its name only on the first
+dnl row; the remaining rows leave the column blank so the entries read
+dnl as one group.
+AT_CHECK([ovs-appctl fdb/show br0 br-nexthop], [0], [dnl
+ Bridge     port  VLAN  MAC                Age
+ br0           1     0  50:54:00:00:00:05  static
+               3     0  50:54:00:00:00:06  static
+ br-nexthop    2     0  50:54:00:00:00:07  static
+])
+
+dnl A bridge with no entries still shows a name-only row so it stays
+dnl visible in a multi-bridge listing (matching JSON, which lists []).
+AT_CHECK([ovs-vsctl -- add-br br-empty -- set bridge br-empty 
datapath-type=dummy])
+AT_CHECK([ovs-appctl fdb/show br0 br-empty], [0], [dnl
+ Bridge   port  VLAN  MAC                Age
+ br0         1     0  50:54:00:00:00:05  static
+             3     0  50:54:00:00:00:06  static
+ br-empty
+])
+AT_CHECK([ovs-appctl --format json --pretty fdb/show br0 br-empty], [0], [dnl
+[{
+  "br-empty": [],
+  "br0": [
+    {
+      "mac": "50:54:00:00:00:05",
+      "port": 1,
+      "static": true,
+      "vlan": 0},
+    {
+      "mac": "50:54:00:00:00:06",
+      "port": 3,
+      "static": true,
+      "vlan": 0}]}]
+])
+
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
@@ -8197,27 +8294,28 @@ AT_CHECK_UNQUOTED([ovs-appctl fdb/show br0 | sed 's/ 
*[[0-9]]\{1,\}$//' | grep -
 dnl Check json output.
 AT_CHECK([ovs-appctl --format json --pretty fdb/show br0 \
           | sed 's/"age": [[0-9]]*/"age": ?/g'], [0], [dnl
-[[
-  {
-    "age": ?,
-    "mac": "50:54:00:00:00:01",
-    "port": 1,
-    "vlan": 0},
-  {
-    "age": ?,
-    "mac": "50:54:00:00:00:02",
-    "port": 2,
-    "vlan": 0},
-  {
-    "mac": "50:54:00:00:01:01",
-    "port": 1,
-    "static": true,
-    "vlan": 0},
-  {
-    "mac": "50:54:00:00:02:02",
-    "port": 2,
-    "static": true,
-    "vlan": 0}]]
+[{
+  "br0": [
+    {
+      "age": ?,
+      "mac": "50:54:00:00:00:01",
+      "port": 1,
+      "vlan": 0},
+    {
+      "age": ?,
+      "mac": "50:54:00:00:00:02",
+      "port": 2,
+      "vlan": 0},
+    {
+      "mac": "50:54:00:00:01:01",
+      "port": 1,
+      "static": true,
+      "vlan": 0},
+    {
+      "mac": "50:54:00:00:02:02",
+      "port": 2,
+      "static": true,
+      "vlan": 0}]}]
 ])
 
 dnl Remove static mac entry.
diff --git a/vswitchd/ovs-vswitchd.8.in b/vswitchd/ovs-vswitchd.8.in
index b21b1f08e893..3d80bceb655b 100644
--- a/vswitchd/ovs-vswitchd.8.in
+++ b/vswitchd/ovs-vswitchd.8.in
@@ -176,10 +176,16 @@ Deletes \fImac\fR address from a \fIport\fR and 
\fIvlan\fR on a \fIbridge\fR.
 .IP "\fBfdb/flush\fR [\fIbridge\fR]"
 Flushes \fIbridge\fR MAC address learning table, or all learning tables
 if no \fIbridge\fR is given.
-.IP "\fBfdb/show\fR \fIbridge\fR"
+.IP "\fBfdb/show\fR [\fIbridge\fR...]"
 Lists each MAC address/VLAN pair learned by the specified \fIbridge\fR,
 along with the port on which it was learned and the age of the entry,
-in seconds.
+in seconds.  Multiple bridge names may be given to query several bridges
+in a single call, and if no bridge is given the tables of all bridges are
+shown.  When more than one bridge is shown, text output gains a leading
+"Bridge" column that groups each bridge's entries, printing the bridge
+name only on its first row; a bridge with no entries is listed as a
+name-only row.  JSON output is an object keyed by bridge name, whose
+values are arrays of that bridge's entries.
 .IP "\fBfdb/stats-clear\fR [\fIbridge\fR]"
 Clear \fIbridge\fR MAC address learning table statistics, or all
 statistics if no \fIbridge\fR is given.
-- 
2.43.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to