From patchwork Fri Sep 19 05:29:29 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Changliang Wu X-Patchwork-Id: 2139320 X-Patchwork-Delegate: aconole@redhat.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=smartx-com.20230601.gappssmtp.com header.i=@smartx-com.20230601.gappssmtp.com header.a=rsa-sha256 header.s=20230601 header.b=jcyngNI3; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::138; helo=smtp1.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4cSh0B48DZz1yFx for ; Fri, 19 Sep 2025 15:29:44 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id ACFD280879; Fri, 19 Sep 2025 05:29:42 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id Inej5YaQpuP8; Fri, 19 Sep 2025 05:29:40 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=140.211.9.56; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp1.osuosl.org 9556A8084A Authentication-Results: smtp1.osuosl.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=smartx-com.20230601.gappssmtp.com header.i=@smartx-com.20230601.gappssmtp.com header.a=rsa-sha256 header.s=20230601 header.b=jcyngNI3 Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp1.osuosl.org (Postfix) with ESMTPS id 9556A8084A; Fri, 19 Sep 2025 05:29:40 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 6F915C04FB; Fri, 19 Sep 2025 05:29:40 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138]) by lists.linuxfoundation.org (Postfix) with ESMTP id 1018DC04FA for ; Fri, 19 Sep 2025 05:29:39 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id F092B8084A for ; Fri, 19 Sep 2025 05:29:38 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 8YSwy1x_8jmJ for ; Fri, 19 Sep 2025 05:29:37 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=2607:f8b0:4864:20::62a; helo=mail-pl1-x62a.google.com; envelope-from=changliang.wu@smartx.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp1.osuosl.org D1C2A80833 Authentication-Results: smtp1.osuosl.org; dmarc=pass (p=none dis=none) header.from=smartx.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp1.osuosl.org D1C2A80833 Received: from mail-pl1-x62a.google.com (mail-pl1-x62a.google.com [IPv6:2607:f8b0:4864:20::62a]) by smtp1.osuosl.org (Postfix) with ESMTPS id D1C2A80833 for ; Fri, 19 Sep 2025 05:29:36 +0000 (UTC) Received: by mail-pl1-x62a.google.com with SMTP id d9443c01a7336-2698e4795ebso14704725ad.0 for ; Thu, 18 Sep 2025 22:29:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=smartx-com.20230601.gappssmtp.com; s=20230601; t=1758259775; x=1758864575; darn=openvswitch.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=0pcYw3qG8pBUdkI01F5x7/ZS4Y7k9njwoD1kG0YETsc=; b=jcyngNI3SO8/0lvkXOXiK4GIYMyQWrfy/D0YBvq4RysYCL9EE65gjMk43GT+O9PJ1T wgN5OIG0+h8tmEGq1ElvDaKx/vnx4CDFPOphKfS5P/tyMMYlyf1dPO/JBnJgVj2Coy74 u14A8jE1p3cVDRJmmG1C/pk06DX9A0t9giPVxXSaSJgeXVoRlPu5lYg6aEHdet+FiyQO x4MuiyLvtnCOYkKeziD5s5mbJElgYVl2baYQKCEO6J40Iyt5kVy3z2OdbP4nIb/F22XL VOX06iaWLhekCnlWCcnhEqypBTtuUfc/nPCvAnkL1eKtvGINpWuFgxUizw3VIFud2SU1 Rkyw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758259775; x=1758864575; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=0pcYw3qG8pBUdkI01F5x7/ZS4Y7k9njwoD1kG0YETsc=; b=dtBTxHgIjBBOindnARBihq8PjxtYGYS46ChrhRIF3AqgwlXIP+XYfKJ6+0j5LjhkzQ NNpiPdYPEUocCMDktWRqeqKU8/af0lMJEW8SBN6xQeDolSrhzRMDYxjh69q72qy/vNi6 UBm87D5HYsIDYA6GJzbV3uCn5BpC1/CUGKSGfYq2l709SBNNU8SQo0sD50hunsAON8sp Nn8vuEEA6arr+PIC7l2QmX4VC7AtNcTJImkfkQKIhNzxRngw/JQQ61gWJyu73hIMUc5e cRkm0r4/q/44LTdshazDf/NRELSAu4XkOieG+TrI0E2E9RTm+9m+sFP/Xzna0eAQCjvt zGww== X-Gm-Message-State: AOJu0Yzt7o+vj4XTFlqo9tWcDTr5uBop0o6bOFQw4eAR9mEj8umygW5d laHnZ2smYSkcXPlJCP8thhqwRDYw2ZnWqXx2y4DfwexVbBbCUFLxQ3kpfvO6pKJzfDiGyP5OE/V gZ2dC/KibGA== X-Gm-Gg: ASbGncv0MjCMO4UeGA+L1ceAMn5MepqbNzEfjlHebDosO473wW1Pvap0tH1mp5K+qJi YRo46Pl6R2ERp7JU2D4LTCRWfe36fTtfo7ktG84lx/BNgWs2ANOECi1aMRrqPa44TRaZX8U4UDR Zs8WdgLkwHtCr2jYLrAQLaqa4ri1wWlxt0kTgb0VM2EFQH98jv/1IEUsRo0ial+ew8JQqMdp3Il 0LQnBw2YuZh+cwYgGF6VYplVI+N4njx+jPpR0Qx/gGl06HAunjIzlU+T1bt65Wg7c/ZYxc5gi4u T/vZZWsjLHRbPjEHKzUYlx+tWj6NzZUAupXiBudwo1/NdvPqutWVpt3Xj3ymbLn042/PtsHgTdb Yx5jsKFLPyzGX20aJ+5aTBX+1yolPYHUj5aDjuOgyP7YdusqMS7LSpAw+OpDLPLKi+5PCVG0a/Z mmpjQGo1o06JQElQ== X-Google-Smtp-Source: AGHT+IFIwjqF/hu9eORb75Q8h6stM6J0AudYG6SkskM1QgVQmQgxj1y1Rndse1akeV3+J/qAqTS2ww== X-Received: by 2002:a17:902:fb83:b0:25c:38be:748f with SMTP id d9443c01a7336-269ba3ec961mr18530705ad.9.1758259774809; Thu, 18 Sep 2025 22:29:34 -0700 (PDT) Received: from localhost.localdomain ([193.246.161.124]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-b55100df86bsm2328823a12.23.2025.09.18.22.29.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 18 Sep 2025 22:29:34 -0700 (PDT) From: Changliang Wu To: dev@openvswitch.org Date: Fri, 19 Sep 2025 13:29:29 +0800 Message-ID: <20250919052929.498183-1-changliang.wu@smartx.com> X-Mailer: git-send-email 2.43.5 MIME-Version: 1.0 Subject: [ovs-dev] [PATCH] lacp: Restore lacp status when service reload manually. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" On most switches, if STP is enabled and the port connected to OVS is not in edge mode, when OVS service restart and negotiate LACP, which will trigger STP recalculation. During this time, traffic on the LACP interface will be blocked for several seconds(up to 5s+). Test in Mellanox Switch OvS LACP: Fast Mode Switch LACP | STP Mode | Downtime ------------|-------------|--------- Fast/Slow | on - normal | 3.1s Fast/Slow | off / edge | 0.2s This patch propose a solution, similar to flow restore, 1. save LACP negotiation status 2. pause LACP process 3. restart OvS service 4. inject LACP status into vswitchd 5. resume LACP process Test Results with this patch Switch LACP | STP Mode | Downtime ------------|-------------|--------- Fast | on - normal | 0.3s Slow | on - normal | 0.0s Fast/Slow | off / edge | 0.0s Signed-off-by: Changliang Wu --- lib/lacp.c | 272 +++++++++++++++++++++++++++++++++++++++++ ofproto/ofproto-dpif.c | 5 +- ofproto/ofproto.c | 14 +++ ofproto/ofproto.h | 2 + utilities/ovs-lib.in | 46 ++++++- utilities/ovs-save | 159 ++++++++++++++++++++++++ vswitchd/bridge.c | 5 + 7 files changed, 499 insertions(+), 4 deletions(-) diff --git a/lib/lacp.c b/lib/lacp.c index 3252f17eb..251f63dbe 100644 --- a/lib/lacp.c +++ b/lib/lacp.c @@ -25,6 +25,7 @@ #include "dp-packet.h" #include "ovs-atomic.h" #include "packets.h" +#include "openvswitch/ofp-parse.h" #include "openvswitch/poll-loop.h" #include "seq.h" #include "openvswitch/shash.h" @@ -168,6 +169,7 @@ static bool member_may_enable__(struct member *) OVS_REQUIRES(mutex); static unixctl_cb_func lacp_unixctl_show; static unixctl_cb_func lacp_unixctl_show_stats; +static unixctl_cb_func lacp_unixctl_set; /* Populates 'pdu' with a LACP PDU comprised of 'actor' and 'partner'. */ static void @@ -229,6 +231,8 @@ lacp_init(void) lacp_unixctl_show, NULL); unixctl_command_register("lacp/show-stats", "[port]", 0, 1, lacp_unixctl_show_stats, NULL); + unixctl_command_register("lacp/set", "[port]", 3, INT_MAX, + lacp_unixctl_set, NULL); } static void @@ -940,6 +944,34 @@ lacp_find(const char *name) OVS_REQUIRES(mutex) return NULL; } +static struct member * +lacp_member_find(struct lacp *lacp, const char *name) OVS_REQUIRES(mutex) +{ + struct member *member; + + HMAP_FOR_EACH_SAFE (member, node, &lacp->members) { + if (!strcmp(member->name, name)) { + return member; + } + } + + return NULL; +} + +static struct member * +lacp_member_find_by_key(struct lacp *lacp, uint16_t key) OVS_REQUIRES(mutex) +{ + struct member *member; + + HMAP_FOR_EACH_SAFE (member, node, &lacp->members) { + if (member->port_id == key) { + return member; + } + } + + return NULL; +} + static void ds_put_lacp_state(struct ds *ds, uint8_t state) { @@ -1219,5 +1251,245 @@ lacp_get_member_stats(const struct lacp *lacp, const void *member_, } ovs_mutex_unlock(&mutex); return ret; +} + +static void +lacp_port_set(struct unixctl_conn *conn, struct lacp *lacp, const char *parms) + OVS_REQUIRES(mutex) +{ + char *value; + char *key; + char **p = (char **)&parms; + while (ofputil_parse_key_value(p, &key, &value)) { + lacp->update = false; + uint32_t tmp; + if (nullable_string_is_equal(key, "status")) { + if (strstr(value, "active")) { + lacp->active = true; + } + if (strstr(value, "negotiated")) { + lacp->negotiated = true; + } + + } else if (nullable_string_is_equal(key, "sys_id")) { + if (!eth_addr_from_string(value, &lacp->sys_id)) { + unixctl_command_reply_error(conn, "invalid port sys_id"); + return; + } + } else if (nullable_string_is_equal(key, "sys_priority")) { + if (!str_to_uint(value, 10, &tmp)) { + unixctl_command_reply_error(conn, "invalid port sys_prority"); + return; + } + lacp->sys_priority = tmp; + } else if (nullable_string_is_equal(key, "key")) { + if (!str_to_uint(value, 10, &tmp)) { + unixctl_command_reply_error(conn, "invalid port key"); + return; + } + lacp->key_member = lacp_member_find_by_key(lacp, tmp); + } + } +} + +static uint8_t +lacp_state_from_str(const char *s) +{ + uint8_t state = 0; + if (strstr(s, "activity")) { + state |= LACP_STATE_ACT; + } + if (strstr(s, "timeout")) { + state |= LACP_STATE_TIME; + } + if (strstr(s, "aggregation")) { + state |= LACP_STATE_AGG; + } + if (strstr(s, "synchronized")) { + state |= LACP_STATE_SYNC; + } + if (strstr(s, "collecting")) { + state |= LACP_STATE_COL; + } + if (strstr(s, "distributing")) { + state |= LACP_STATE_DIST; + } + if (strstr(s, "defaulted")) { + state |= LACP_STATE_DEF; + } + if (strstr(s, "expired")) { + state |= LACP_STATE_EXP; + } + return state; +} +static void +lacp_member_set(struct unixctl_conn *conn, struct member *member, + const char *op, const char *parms) OVS_REQUIRES(mutex) +{ + char *value; + char *key; + char **p = (char **)&parms; + if (nullable_string_is_equal(op, "member")) { + while (ofputil_parse_key_value(p, &key, &value)) { + uint32_t tmp; + if (nullable_string_is_equal(key, "port_id")) { + if (!str_to_uint(value, 10, &tmp)) { + unixctl_command_reply_error(conn, "invalid member " + "port_id"); + return; + } + member->port_id = tmp; + } else if (nullable_string_is_equal(key, "port_priority")) { + if (!str_to_uint(value, 10, &tmp)) { + unixctl_command_reply_error(conn, "invalid member " + "port_priority"); + return; + } + member->port_priority = tmp; + } else if (nullable_string_is_equal(key, "status")) { + if (strstr(value, "current")) { + member->status = LACP_CURRENT; + member->carrier_up = true; + } else if (strstr(value, "expired")) { + member->status = LACP_EXPIRED; + } else if (strstr(value, "defaulted")) { + member->status = LACP_DEFAULTED; + } + + if (strstr(value, "attached")) { + member->attached = true; + } + } + } + } else if (nullable_string_is_equal(op, "actor")) { + while (ofputil_parse_key_value(p, &key, &value)) { + uint32_t tmp; + if (nullable_string_is_equal(key, "sys_id")) { + struct eth_addr addr; + if (!eth_addr_from_string(value, &addr)) { + unixctl_command_reply_error(conn, "invalid member " + "actor sys_id"); + return; + } + member->ntt_actor.sys_id = addr; + } else if (nullable_string_is_equal(key, "sys_priority")) { + if (!str_to_uint(value, 10, &tmp)) { + unixctl_command_reply_error(conn, "invalid member " + "actor sys_priority"); + return; + } + member->ntt_actor.sys_priority = htons(tmp); + } else if (nullable_string_is_equal(key, "port_id")) { + if (!str_to_uint(value, 10, &tmp)) { + unixctl_command_reply_error(conn, "invalid member " + "actor port_id"); + return; + } + member->ntt_actor.port_id = htons(tmp); + } else if (nullable_string_is_equal(key, "port_priority")) { + if (!str_to_uint(value, 10, &tmp)) { + unixctl_command_reply_error(conn, "invalid member " + "actor port_priority"); + return; + } + member->ntt_actor.port_priority = htons(tmp); + } else if (nullable_string_is_equal(key, "key")) { + if (!str_to_uint(value, 10, &tmp)) { + unixctl_command_reply_error(conn, "invalid member " + "actor key"); + return; + } + member->ntt_actor.key = htons(tmp); + } else if (nullable_string_is_equal(key, "state")) { + member->ntt_actor.state = lacp_state_from_str(value); + VLOG_INFO("member->ntt_actor.state (%p) (%d)", + &(member->ntt_actor.state), member->ntt_actor.state); + } + } + } else if (nullable_string_is_equal(op, "partner")) { + while (ofputil_parse_key_value(p, &key, &value)) { + uint32_t tmp; + if (nullable_string_is_equal(key, "sys_id")) { + struct eth_addr addr; + if (!eth_addr_from_string(value, &addr)) { + unixctl_command_reply_error(conn, "invalid member " + "partner sys_id"); + return; + } + member->partner.sys_id = addr; + } else if (nullable_string_is_equal(key, "sys_priority")) { + if (!str_to_uint(value, 10, &tmp)) { + unixctl_command_reply_error(conn, "invalid member " + "partner sys_priority"); + return; + } + member->partner.sys_priority = htons(tmp); + } else if (nullable_string_is_equal(key, "port_id")) { + if (!str_to_uint(value, 10, &tmp)) { + unixctl_command_reply_error(conn, "invalid member " + "partner port_id"); + return; + } + member->partner.port_id = htons(tmp); + } else if (nullable_string_is_equal(key, "port_priority")) { + if (!str_to_uint(value, 10, &tmp)) { + unixctl_command_reply_error(conn, "invalid member " + "partner port_priority"); + return; + } + member->partner.port_priority = htons(tmp); + } else if (nullable_string_is_equal(key, "key")) { + if (!str_to_uint(value, 10, &tmp)) { + unixctl_command_reply_error(conn, "invalid member " + "partner key"); + return; + } + member->partner.key = htons(tmp); + } else if (nullable_string_is_equal(key, "state")) { + member->partner.state = lacp_state_from_str(value); + } + } + } else { + unixctl_command_reply_error(conn, "invalid member op"); + } + timer_set_duration(&member->tx, LACP_FAST_TIME_TX); + timer_set_duration(&member->rx, LACP_FAST_TIME_TX); +} + +static void +lacp_unixctl_set(struct unixctl_conn *conn, int argc, const char *argv[], + void *aux OVS_UNUSED) OVS_EXCLUDED(mutex) +{ + struct member *member; + struct lacp *lacp; + + lacp_lock(); + lacp = lacp_find(argv[1]); + if (!lacp) { + unixctl_command_reply_error(conn, "lacp port not found"); + goto out; + } + + if (nullable_string_is_equal(argv[2], "port")) { + lacp_port_set(conn, lacp, argv[3]); + } else if (nullable_string_is_equal(argv[2], "member")) { + member = lacp_member_find(lacp, argv[3]); + if (!member) { + unixctl_command_reply_error(conn, "lacp member not found"); + goto out; + } + if (argc < 5) { + unixctl_command_reply_error(conn, "invalid member parms"); + goto out; + } + lacp_member_set(conn, member, argv[4], argv[5]); + } else { + unixctl_command_reply_error(conn, "invalid op type"); + goto out; + } + +out: + unixctl_command_reply(conn, NULL); + lacp_unlock(); } diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index ed9e44ce2..28657f0e8 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -41,6 +41,7 @@ #include "nx-match.h" #include "odp-util.h" #include "odp-execute.h" +#include "ofproto/ofproto.h" #include "ofproto/ofproto-dpif.h" #include "ofproto/ofproto-provider.h" #include "ofproto-dpif-ipfix.h" @@ -3702,7 +3703,6 @@ send_pdu_cb(void *port_, const void *pdu, size_t pdu_size) struct ofport_dpif *port = port_; struct eth_addr ea; int error; - error = netdev_get_etheraddr(port->up.netdev, &ea); if (!error) { struct dp_packet packet; @@ -3781,6 +3781,9 @@ bundle_send_learning_packets(struct ofbundle *bundle) static void bundle_run(struct ofbundle *bundle) { + if (bundle->lacp && ofproto_get_lacp_restore_wait()) { + return; + } if (bundle->lacp) { lacp_run(bundle->lacp, send_pdu_cb); } diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c index 6fa18228b..6f587c21f 100644 --- a/ofproto/ofproto.c +++ b/ofproto/ofproto.c @@ -326,6 +326,8 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); /* The default value of true waits for flow restore. */ static bool flow_restore_wait = true; +/* The default value of true waits for lacp restore. */ +static bool lacp_restore_wait = true; /* Must be called to initialize the ofproto library. * @@ -997,6 +999,18 @@ ofproto_get_flow_restore_wait(void) return flow_restore_wait; } +void +ofproto_set_lacp_restore_wait(bool lacp_restore_wait_db) +{ + lacp_restore_wait = lacp_restore_wait_db; +} + +bool +ofproto_get_lacp_restore_wait(void) +{ + return lacp_restore_wait; +} + /* Retrieve datapath capabilities. */ void ofproto_get_datapath_cap(const char *datapath_type, struct smap *dp_cap) diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h index 3f85509a1..e8e69b0da 100644 --- a/ofproto/ofproto.h +++ b/ofproto/ofproto.h @@ -382,6 +382,8 @@ int ofproto_set_local_sample(struct ofproto *ofproto, size_t n_options); void ofproto_set_flow_restore_wait(bool flow_restore_wait_db); bool ofproto_get_flow_restore_wait(void); +void ofproto_set_lacp_restore_wait(bool lacp_restore_wait_db); +bool ofproto_get_lacp_restore_wait(void); int ofproto_set_stp(struct ofproto *, const struct ofproto_stp_settings *); int ofproto_get_stp_status(struct ofproto *, struct ofproto_stp_status *); diff --git a/utilities/ovs-lib.in b/utilities/ovs-lib.in index dded0b7c7..a845ac915 100644 --- a/utilities/ovs-lib.in +++ b/utilities/ovs-lib.in @@ -587,12 +587,21 @@ ovs_save () { [ -z "${bridges}" ] && return 0 } +ovs_save_lacp () { + $datadir/scripts/ovs-save save-lacp > "${script_lacp}" + chmod +x "${script_lacp}" +} + save_flows_if_required () { if test X"$DELETE_BRIDGES" != Xyes; then action "Saving flows" ovs_save save-flows "${script_flows}" fi } +save_lacp () { + action "Saving lacp" ovs_save_lacp +} + save_interfaces () { "$datadir/scripts/ovs-save" save-interfaces ${ifaces} \ > "${script_interfaces}" @@ -604,6 +613,12 @@ flow_restore_wait () { fi } +lacp_restore_wait () { + if test X"${OVS_VSWITCHD:-yes}" = Xyes; then + ovs_vsctl set open_vswitch . other_config:lacp-restore-wait="true" + fi +} + flow_restore_complete () { if test X"${OVS_VSWITCHD:-yes}" = Xyes; then ovs_vsctl --if-exists remove open_vswitch . other_config \ @@ -611,11 +626,23 @@ flow_restore_complete () { fi } +lacp_restore_complete () { + if test X"${OVS_VSWITCHD:-yes}" = Xyes; then + ovs_vsctl --if-exists remove open_vswitch . other_config \ + lacp-restore-wait="true" + fi +} + restore_flows () { [ -x "${script_flows}" ] && \ action "Restoring saved flows" "${script_flows}" } +restore_lacp () { + [ -x "${script_lacp}" ] && \ + action "Restoring saved lacp" "${script_lacp}" +} + restore_interfaces () { [ ! -x "${script_interfaces}" ] && return 0 action "Restoring interface configuration" "${script_interfaces}" @@ -633,7 +660,8 @@ restore_interfaces () { init_restore_scripts () { script_interfaces=`mktemp` script_flows=`mktemp` - trap 'rm -f "${script_interfaces}" "${script_flows}"' 0 + script_lacp=`mktemp` + trap 'rm -f "${script_interfaces}" "${script_flows}" "${script_lacp}"' 0 } force_reload_kmod () { @@ -648,6 +676,7 @@ force_reload_kmod () { init_restore_scripts save_flows_if_required + save_lacp # Restart the database first, since a large database may take a # while to load, and we want to minimize forwarding disruption. @@ -682,11 +711,16 @@ force_reload_kmod () { # Start vswitchd by asking it to wait till flow restore is finished. flow_restore_wait + lacp_restore_wait start_forwarding || return 1 - # Restore saved flows and inform vswitchd that we are done. + # Restore saved resources and inform vswitchd that we are done. restore_flows + restore_lacp + flow_restore_complete + lacp_restore_complete + add_managers restore_interfaces @@ -704,6 +738,7 @@ restart () { init_restore_scripts if test X"${OVS_VSWITCHD:-yes}" = Xyes; then save_flows_if_required + save_lacp fi fi @@ -716,10 +751,15 @@ restart () { # Start vswitchd by asking it to wait till flow restore is finished. flow_restore_wait + lacp_restore_wait start_forwarding || return 1 - # Restore saved flows and inform vswitchd that we are done. + # Restore saved resources and inform vswitchd that we are done. restore_flows + restore_lacp + flow_restore_complete + lacp_restore_complete + add_managers } diff --git a/utilities/ovs-save b/utilities/ovs-save index 67092ecf7..792d5daee 100755 --- a/utilities/ovs-save +++ b/utilities/ovs-save @@ -33,6 +33,8 @@ Commands: configuration. save-flows Outputs a shell script on stdout that will restore OpenFlow flows of each Open vSwitch bridge. + save-lacp Outputs a shell script on stdout that will restore + lacp info of each lacp bond port. This script is meant as a helper for the Open vSwitch init script commands. EOF } @@ -159,6 +161,158 @@ save_flows () { echo "rm -rf \"$workdir\"" } +save_lacp () { + if (ovs-appctl --version) > /dev/null 2>&1; then :; else + echo "$0: ovs-ofctl not found in $PATH" >&2 + exit 1 + fi + + case `ovs-appctl version | sed 1q` in + "ovs-vswitchd (Open vSwitch) 1."*.*) + return + ;; + esac + + SP="[[:space:]]*" + BOND_NAME_PATTERN="s/^----${SP}([^[:space:]]+)${SP}----.*/\1/" + STATUS_PATTERN="s/^status:${SP}(.*)/\1/" + SYS_ID_PATTERN="s/^sys_id:${SP}(.*)/\1/" + SYS_PRIORITY_PATTERN="s/^sys_priority:${SP}(.*)/\1/" + AGGREGATION_KEY_PATTERN="s/^aggregation${SP}key:${SP}(.*)/\1/" + LACP_TIME_PATTERN="s/^lacp_time:${SP}(.*)/\1/" + MEMBER_PATTERN="s/^(member|slave):${SP}([^:]+):${SP}(.*)/\2/" + MEMBER_STATUS_PATTERN="s/^(member|slave):${SP}([^:]+):${SP}(.*)/\3/" + PORT_ID_PATTERN="s/^port_id:${SP}(.*)/\1/" + PORT_PRIORITY_PATTERN="s/^port_priority:${SP}(.*)/\1/" + MAY_ENABLE_PATTERN="s/^may_enable:${SP}(.*)/\1/" + ACTOR_SYS_ID_PATTERN="s/^actor${SP}sys_id:${SP}(.*)/\1/" + ACTOR_SYS_PRIORITY_PATTERN="s/^actor${SP}sys_priority:${SP}(.*)/\1/" + ACTOR_PORT_ID_PATTERN="s/^actor${SP}port_id:${SP}(.*)/\1/" + ACTOR_PORT_PRIORITY_PATTERN="s/^actor${SP}port_priority:${SP}(.*)/\1/" + ACTOR_KEY_PATTERN="s/^actor${SP}key:${SP}(.*)/\1/" + ACTOR_STATE_PATTERN="s/^actor${SP}state:${SP}(.*)/\1/" + PARTNER_SYS_ID_PATTERN="s/^partner${SP}sys_id:${SP}(.*)/\1/" + PARTNER_SYS_PRIORITY_PATTERN="s/^partner${SP}sys_priority:${SP}(.*)/\1/" + PARTNER_PORT_ID_PATTERN="s/^partner${SP}port_id:${SP}(.*)/\1/" + PARTNER_PORT_PRIORITY_PATTERN="s/^partner${SP}port_priority:${SP}(.*)/\1/" + PARTNER_KEY_PATTERN="s/^partner${SP}key:${SP}(.*)/\1/" + PARTNER_STATE_PATTERN="s/^partner${SP}state:${SP}(.*)/\1/" + tmpfile=$(mktemp) + ovs-appctl lacp/show | while IFS= read -r line; do + t=$(echo "$line" | sed -E 's/^[[:space:]]+//') + case "$t" in + ----*----*) + BOND_NAME=$(echo "$t" | sed -E "$BOND_NAME_PATTERN") + ;; + status:${SP}) + STATUS=$(echo "$t" | sed -E "$STATUS_PATTERN" | sed 's/ /+/g') + ;; + sys_id:${SP}) + SYS_ID=$(echo "$t" | sed -E "$SYS_ID_PATTERN") + ;; + sys_priority:${SP}) + SYS_PRIORITY=$(echo "$t" | sed -E "$SYS_PRIORITY_PATTERN") + ;; + aggregation${SP}key:${SP}) + AGGREGATION_KEY=$(echo "$t" | + sed -E "$AGGREGATION_KEY_PATTERN") + ;; + lacp_time:${SP}) + LACP_TIME=$(echo "$t" | sed -E "$LACP_TIME_PATTERN") + cmd="ovs-appctl lacp/set $BOND_NAME port status=$STATUS," + cmd="${cmd}sys_id=$SYS_ID,sys_priority=$SYS_PRIORITY," + cmd="${cmd}key=$AGGREGATION_KEY,lacp_time=$LACP_TIME" + echo "$cmd" >> "$tmpfile" + ;; + member:${SP}|slave:${SP}) + # Extract member name and status + MEMBER_NAME=$(echo "$t" | sed -E "$MEMBER_PATTERN") + MEMBER_STATUS=$(echo "$t" | + sed -E "$MEMBER_STATUS_PATTERN" | sed 's/ /+/g') + ;; + port_id:${SP}) + PORT_ID=$(echo "$t" | sed -E "$PORT_ID_PATTERN") + ;; + port_priority:${SP}) + PORT_PRIORITY=$(echo "$t" | sed -E "$PORT_PRIORITY_PATTERN") + ;; + may_enable:${SP}) + MAY_ENABLE=$(echo "$t" | sed -E "$MAY_ENABLE_PATTERN") + # Print member information + cmd="ovs-appctl lacp/set $BOND_NAME member $MEMBER_NAME member" + cmd="${cmd} status=$MEMBER_STATUS,port_id=$PORT_ID," + cmd="${cmd}port_priority=$PORT_PRIORITY,may_enable=$MAY_ENABLE" + echo "$cmd" >> "$tmpfile" + ;; + actor${SP}sys_id:${SP}) + ACTOR_SYS_ID=$(echo "$t" | sed -E "$ACTOR_SYS_ID_PATTERN") + ;; + actor${SP}sys_priority:${SP}) + ACTOR_SYS_PRIORITY=$(echo "$t" | + sed -E "$ACTOR_SYS_PRIORITY_PATTERN") + ;; + actor${SP}port_id:${SP}) + ACTOR_PORT_ID=$(echo "$t" | sed -E "$ACTOR_PORT_ID_PATTERN") + ;; + actor${SP}port_priority:${SP}) + ACTOR_PORT_PRIORITY=$(echo "$t" | + sed -E "$ACTOR_PORT_PRIORITY_PATTERN") + ;; + actor${SP}key:${SP}) + ACTOR_KEY=$(echo "$t" | sed -E "$ACTOR_KEY_PATTERN") + ;; + actor${SP}state:${SP}) + ACTOR_STATE=$(echo "$t" | + sed -E "$ACTOR_STATE_PATTERN" | sed 's/ /+/g') + # Print actor information + cmd="ovs-appctl lacp/set $BOND_NAME member $MEMBER_NAME actor" + cmd="${cmd} sys_id=$ACTOR_SYS_ID," + cmd="${cmd}sys_priority=$ACTOR_SYS_PRIORITY," + cmd="${cmd}port_id=$ACTOR_PORT_ID," + cmd="${cmd}port_priority=$ACTOR_PORT_PRIORITY," + cmd="${cmd}key=$ACTOR_KEY,state=$ACTOR_STATE" + echo "$cmd" >> "$tmpfile" + ;; + partner${SP}sys_id:${SP}) + PARTNER_SYS_ID=$(echo "$t" | + sed -E "$PARTNER_SYS_ID_PATTERN") + ;; + partner${SP}sys_priority:${SP}) + PARTNER_SYS_PRIORITY=$(echo "$t" | + sed -E "$PARTNER_SYS_PRIORITY_PATTERN") + ;; + partner${SP}port_id:${SP}) + PARTNER_PORT_ID=$(echo "$t" | + sed -E "$PARTNER_PORT_ID_PATTERN") + ;; + partner${SP}port_priority:${SP}) + PARTNER_PORT_PRIORITY=$(echo "$t" | + sed -E "$PARTNER_PORT_PRIORITY_PATTERN") + ;; + partner${SP}key:${SP}) + PARTNER_KEY=$(echo "$t" | sed -E "$PARTNER_KEY_PATTERN") + ;; + partner${SP}state:${SP}) + PARTNER_STATE=$(echo "$t" | + sed -E "$PARTNER_STATE_PATTERN" | sed 's/ /+/g') + # Print partner information + cmd="ovs-appctl lacp/set $BOND_NAME" + cmd="${cmd} member $MEMBER_NAME partner" + cmd="${cmd} sys_id=$PARTNER_SYS_ID," + cmd="${cmd}sys_priority=$PARTNER_SYS_PRIORITY," + cmd="${cmd}port_id=$PARTNER_PORT_ID," + cmd="${cmd}port_priority=$PARTNER_PORT_PRIORITY," + cmd="${cmd}key=$PARTNER_KEY,state=$PARTNER_STATE" + echo "$cmd" >> "$tmpfile" + ;; + esac + done + sed -i '1!G;h;$!d' "$tmpfile" + cat "$tmpfile" + rm -f "$tmpfile" +} + + while [ $# -ne 0 ] do case $1 in @@ -172,6 +326,11 @@ do save_interfaces "$@" exit 0 ;; + "save-lacp") + shift + save_lacp "$@" + exit 0 + ;; -h | --help) usage exit 0 diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c index 456b784c0..e2c523051 100644 --- a/vswitchd/bridge.c +++ b/vswitchd/bridge.c @@ -3415,6 +3415,11 @@ bridge_run(void) "flow-restore-wait", false)); } + if (cfg && ofproto_get_lacp_restore_wait()) { + ofproto_set_lacp_restore_wait(smap_get_bool(&cfg->other_config, + "lacp-restore-wait", false)); + } + bridge_run__(); /* Re-configure SSL/TLS. We do this on every trip through the main loop,