Ilya Maximets <i.maxim...@ovn.org> writes: > On 7/20/23 16:55, Aaron Conole wrote: >> Ilya Maximets <i.maxim...@ovn.org> writes: >> >>> OVS configuration is based on port names and OpenFlow port numbers. >>> Names are stored in the database and translated later to OF ports. >>> On the datapath level, each port has a name and a datapath port number. >>> Port name in the database has to match datapath port name, unless it's >>> a tunnel port. >>> >>> If a datapath port is renamed with 'ip link set DEV name NAME', >>> ovs-vswitchd will wake up, destroy all the OpenFlow-related structures >>> and clean other things up. This is because the port no longer >>> represents the port from a database due to a name difference. >>> >>> However, ovs-vswitch will not actually remove the port from the >>> datapath, because it thinks that this port is no longer there. This >>> is happening because lookup is performed by name and the name have >>> changed. As a result we have a port in a datapath that is not related >>> to any port known to ovs-vswitchd and ovs-vswitchd can't remove it. >>> This port also occupies a datapath port number and prevents the port >>> to be added back with a new name. >>> >>> Fix that by performing lookup by a datapath port number during the port >>> destruction. The name was used only to avoid spurious warnings in a >>> normal case where the port was successfully deleted by other parts of >>> OVS. Adding an extra flag to avoid these warnings instead. >>> >>> Fixes: 02f8d6460afd ("ofproto-dpif: Query port existence by name to prevent >>> warnings.") >>> Reported-at: https://github.com/openvswitch/ovs-issues/issues/284 >>> Signed-off-by: Ilya Maximets <i.maxim...@ovn.org> >>> --- >>> lib/dpctl.c | 2 +- >>> lib/dpif.c | 13 +++++---- >>> lib/dpif.h | 2 +- >>> ofproto/ofproto-dpif.c | 14 ++++++---- >>> tests/system-interface.at | 57 +++++++++++++++++++++++++++++++++++++++ >>> 5 files changed, 76 insertions(+), 12 deletions(-) >>> >>> diff --git a/lib/dpctl.c b/lib/dpctl.c >>> index 4394653ab..79b82a176 100644 >>> --- a/lib/dpctl.c >>> +++ b/lib/dpctl.c >>> @@ -673,7 +673,7 @@ show_dpif(struct dpif *dpif, struct dpctl_params >>> *dpctl_p) >>> } >>> >>> for (int i = 0; i < n_port_nos; i++) { >>> - if (dpif_port_query_by_number(dpif, port_nos[i], &dpif_port)) { >>> + if (dpif_port_query_by_number(dpif, port_nos[i], &dpif_port, >>> true)) { >>> continue; >>> } >>> >>> diff --git a/lib/dpif.c b/lib/dpif.c >>> index b1cbf39c4..29041ab91 100644 >>> --- a/lib/dpif.c >>> +++ b/lib/dpif.c >>> @@ -705,13 +705,14 @@ dpif_port_set_config(struct dpif *dpif, odp_port_t >>> port_no, >>> * initializes '*port' appropriately; on failure, returns a positive errno >>> * value. >>> * >>> - * Retuns ENODEV if the port doesn't exist. >>> + * Retuns ENODEV if the port doesn't exist. Will not log a warning in this >>> + * case unless 'warn_if_not_found' is true. >>> * >>> * The caller owns the data in 'port' and must free it with >>> * dpif_port_destroy() when it is no longer needed. */ >>> int >>> dpif_port_query_by_number(const struct dpif *dpif, odp_port_t port_no, >>> - struct dpif_port *port) >>> + struct dpif_port *port, bool warn_if_not_found) >>> { >>> int error = dpif->dpif_class->port_query_by_number(dpif, port_no, >>> port); >>> if (!error) { >>> @@ -719,8 +720,10 @@ dpif_port_query_by_number(const struct dpif *dpif, >>> odp_port_t port_no, >>> dpif_name(dpif), port_no, port->name); >>> } else { >>> memset(port, 0, sizeof *port); >>> - VLOG_WARN_RL(&error_rl, "%s: failed to query port %"PRIu32": %s", >>> - dpif_name(dpif), port_no, ovs_strerror(error)); >>> + VLOG_RL(&error_rl, >>> + (error == ENODEV && !warn_if_not_found) ? VLL_DBG : >>> VLL_WARN, >>> + "%s: failed to query port %"PRIu32": %s", >>> + dpif_name(dpif), port_no, ovs_strerror(error)); >> >> Do we want to log at all in the case that we didn't set >> warn_if_not_found? > > It's slightly useful for debug purposes. E.g. it was useful to see > that the function was executed while developing this fix.
Okay. >> Should we go on the dpmsg_rl instead of error_rl in >> that case? > > I guess, I just mimicked what dpif_port_query_by_name() is doing. > It's currently using error_rl for both cases as well. I suppose, > the '/* Not really much point in logging many dpif errors. */' kind > of still applies. But we can change to use different limits for > different log levels. What do you think? I guess the log should generally be infrequent enough that it shouldn't hit the error rate limit too often - most of the code here gets executed in the "even slower" path. I don't have a strong enough opinion either way. If you decide to change it with a v2, include: Acked-by: Aaron Conole <acon...@redhat.com> >> >>> } >>> return error; >>> } >>> @@ -788,7 +791,7 @@ dpif_port_get_name(struct dpif *dpif, odp_port_t >>> port_no, >>> >>> ovs_assert(name_size > 0); >>> >>> - error = dpif_port_query_by_number(dpif, port_no, &port); >>> + error = dpif_port_query_by_number(dpif, port_no, &port, true); >>> if (!error) { >>> ovs_strlcpy(name, port.name, name_size); >>> dpif_port_destroy(&port); >>> diff --git a/lib/dpif.h b/lib/dpif.h >>> index 9e9d0aa1b..0f2dc2ef3 100644 >>> --- a/lib/dpif.h >>> +++ b/lib/dpif.h >>> @@ -463,7 +463,7 @@ void dpif_port_clone(struct dpif_port *, const struct >>> dpif_port *); >>> void dpif_port_destroy(struct dpif_port *); >>> bool dpif_port_exists(const struct dpif *dpif, const char *devname); >>> int dpif_port_query_by_number(const struct dpif *, odp_port_t port_no, >>> - struct dpif_port *); >>> + struct dpif_port *, bool warn_if_not_found); >>> int dpif_port_query_by_name(const struct dpif *, const char *devname, >>> struct dpif_port *); >>> int dpif_port_get_name(struct dpif *, odp_port_t port_no, >>> diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c >>> index fad7342b0..e22ca757a 100644 >>> --- a/ofproto/ofproto-dpif.c >>> +++ b/ofproto/ofproto-dpif.c >>> @@ -2161,8 +2161,7 @@ port_destruct(struct ofport *port_, bool del) >>> struct ofproto_dpif *ofproto = ofproto_dpif_cast(port->up.ofproto); >>> const char *devname = netdev_get_name(port->up.netdev); >>> const char *netdev_type = netdev_get_type(port->up.netdev); >>> - char namebuf[NETDEV_VPORT_NAME_BUFSIZE]; >>> - const char *dp_port_name; >>> + struct dpif_port dpif_port; >>> >>> ofproto->backer->need_revalidate = REV_RECONFIGURE; >>> xlate_txn_start(); >>> @@ -2176,9 +2175,13 @@ port_destruct(struct ofport *port_, bool del) >>> del = dpif_cleanup_required(ofproto->backer->dpif); >>> } >>> >>> - dp_port_name = netdev_vport_get_dpif_port(port->up.netdev, namebuf, >>> - sizeof namebuf); >>> - if (del && dpif_port_exists(ofproto->backer->dpif, dp_port_name)) { >>> + /* Don't try to delete ports that are not part of the datapath. */ >>> + if (del && port->odp_port == ODPP_NONE) { >>> + del = false; >>> + } >>> + >>> + if (del && !dpif_port_query_by_number(ofproto->backer->dpif, >>> + port->odp_port, &dpif_port, >>> false)) { >>> /* The underlying device is still there, so delete it. This >>> * happens when the ofproto is being destroyed, since the caller >>> * assumes that removal of attached ports will happen as part of >>> @@ -2186,6 +2189,7 @@ port_destruct(struct ofport *port_, bool del) >>> if (!port->is_tunnel) { >>> dpif_port_del(ofproto->backer->dpif, port->odp_port, false); >>> } >>> + dpif_port_destroy(&dpif_port); >>> } else if (del) { >>> /* The underlying device is already deleted (e.g. tunctl -d). >>> * Calling dpif_port_remove to do local cleanup for the netdev */ >>> diff --git a/tests/system-interface.at b/tests/system-interface.at >>> index 148f011c7..d4ee5c46b 100644 >>> --- a/tests/system-interface.at >>> +++ b/tests/system-interface.at >>> @@ -123,6 +123,63 @@ AT_CHECK([ip link show | grep " genev_sys_[[0-9]]*: .* >>> ovs-system " | diff -u - >>> OVS_TRAFFIC_VSWITCHD_STOP >>> AT_CLEANUP >>> >>> +AT_SETUP([interface - datapath port rename]) >>> +OVS_TRAFFIC_VSWITCHD_START() >>> + >>> +dnl Not relevant for userspace datapath. >>> +AT_SKIP_IF([! ovs-appctl dpctl/show | grep -q ovs-system]) >>> + >>> +AT_CHECK([ip link add ovs-veth0 type veth peer name ovs-veth1]) >>> +dnl We will rename ovs-veth0, so removing the peer on exit. >>> +on_exit 'ip link del ovs-veth1' >>> + >>> +AT_CHECK([ovs-vsctl add-port br0 ovs-veth0]) >>> + >>> +OVS_WAIT_UNTIL([ip link show | grep -q "ovs-veth0.* ovs-system "]) >>> + >>> +AT_CHECK([ovs-appctl dpctl/show | grep port], [0], [dnl >>> + port 0: ovs-system (internal) >>> + port 1: br0 (internal) >>> + port 2: ovs-veth0 >>> +]) >>> + >>> +dnl Rename the interface while attached to OVS. >>> +AT_CHECK([ip l set ovs-veth0 name ovs-new-port]) >>> + >>> +dnl Wait for the port to be detached from the OVS datapath. >>> +OVS_WAIT_UNTIL([ip link show | grep "ovs-new-port" | grep -v "ovs-system"]) >>> + >>> +dnl Check that database indicates the error. >>> +AT_CHECK([ovs-vsctl get interface ovs-veth0 error], [0], [dnl >>> +"could not open network device ovs-veth0 (No such device)" >>> +]) >>> + >>> +dnl Check that the port is no longer in the datapath. >>> +AT_CHECK([ovs-appctl dpctl/show | grep port], [0], [dnl >>> + port 0: ovs-system (internal) >>> + port 1: br0 (internal) >>> +]) >>> + >>> +dnl Rename the interface back and check that it is in use again. >>> +AT_CHECK([ip l set ovs-new-port name ovs-veth0]) >>> + >>> +OVS_WAIT_UNTIL([ip link show | grep -q "ovs-veth0.* ovs-system "]) >>> + >>> +AT_CHECK([ovs-vsctl get interface ovs-veth0 error], [0], [dnl >>> +[[]] >>> +]) >>> + >>> +AT_CHECK([ovs-appctl dpctl/show | grep port], [0], [dnl >>> + port 0: ovs-system (internal) >>> + port 1: br0 (internal) >>> + port 2: ovs-veth0 >>> +]) >>> + >>> +OVS_TRAFFIC_VSWITCHD_STOP([" >>> + /could not open network device ovs-veth0 (No such device)/d >>> +"]) >>> +AT_CLEANUP >>> + >>> AT_SETUP([interface - current speed]) >>> AT_SKIP_IF([test $HAVE_ETHTOOL = "no"]) >>> OVS_TRAFFIC_VSWITCHD_START() >> _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev