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