Skip to content

Commit 1aabc6c

Browse files
committed
portmgrd: prevent runtime failure in setting MTU on portchannel member (PR sonic-net#3432)
Prevent setting the PORT MTU on PortChannel members as it will likely fail and cause portmgrd to exit (The PortChannel itself is where the MTU gets set, not the PORT). The current code is setting a default value for an MTU (9100) even when its a PortChannel member, so this patch prevents that default value from being set. Also if a user were to incorrectly specify an MTU on a Port that is a member of the port channel via `config_db.json` this too would bring down portmgrd, so catch that and just emit a warning instead. The YANG model does NOT support checking for this. In order to not add much overhead for large port count systems, we are also lazily caching portchannel members and only using that cache on a new port being brought up or on failure to set an MTU. The current code *always* attempts to set an MTU on the PORT by setting a default here: https://github.com/sonic-net/sonic-swss/blob/c20902f3195b5bf8a941045e131aa1b863b69fd0/cfgmgr/portmgr.cpp#L163-L172 Then applies it here: https://github.com/sonic-net/sonic-swss/blob/c20902f3195b5bf8a941045e131aa1b863b69fd0/cfgmgr/portmgr.cpp#L222-L226 So it isn't crashing because the user configured the MTU in the PORT config, but rather because it is done by default when the port is created. (But it also would crash if a user set an MTU on a port which is bad since YANG doesn't do anything to prevent this). **NOTE**: this only appears to crash on a freshly loaded config at boot, if you take an existing running configuration and modify it to add a portchannel it works since the PORT is already provisioned so the default MTU setting path isn't taken in the above referenced code. This regression was caused by 8b99543 ... but just reverting that patch isn't the right solution. The startup logic does not appear to be proper as it tries to set a default MTU regardless if its valid to do so for the port or not. Logs show this issue which is a critical failure causing the entire switch to go down: ``` 2024 Dec 17 19:26:20.964259 sw1 INFO swss#supervisord: portmgrd RTNETLINK answers: Operation not permitted 2024 Dec 17 19:26:20.965353 sw1 ERR swss#portmgrd: :- main: Runtime error: /sbin/ip link set dev "Ethernet0" mtu "9100" : 2024 Dec 17 19:26:20.967020 sw1 INFO swss#supervisord 2024-12-17 19:26:20,966 WARN exited: portmgrd (exit status 255; not expected) ``` Signed-off-by: Brad House (@bradh352)
1 parent 62cf8ae commit 1aabc6c

3 files changed

Lines changed: 107 additions & 5 deletions

File tree

cfgmgr/portmgr.cpp

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "exec.h"
88
#include "shellcmd.h"
99
#include <swss/redisutility.h>
10+
#include <unordered_map>
1011

1112
using namespace std;
1213
using namespace swss;
@@ -22,7 +23,40 @@ PortMgr::PortMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, c
2223
{
2324
}
2425

25-
bool PortMgr::setPortMtu(const string &alias, const string &mtu)
26+
bool PortMgr::isLagMember(const std::string &alias, std::unordered_map<std::string, int> &lagMembers)
27+
{
28+
/* Lag members are lazily loaded on the first call to isLagMember and cached
29+
* within a variable inside of doTask() for future calls.
30+
*/
31+
if (lagMembers.empty())
32+
{
33+
vector<string> keys;
34+
m_cfgLagMemberTable.getKeys(keys);
35+
for (auto key: keys)
36+
{
37+
auto tokens = tokenize(key, config_db_key_delimiter);
38+
std::string member = tokens[1];
39+
if (!member.empty()) {
40+
lagMembers[member] = 1;
41+
}
42+
}
43+
44+
/* placeholder to state we already read lagmembers even though there are
45+
* none */
46+
if (lagMembers.empty()) {
47+
lagMembers["none"] = 1;
48+
}
49+
}
50+
51+
if (lagMembers.find(alias) != lagMembers.end())
52+
{
53+
return true;
54+
}
55+
56+
return false;
57+
}
58+
59+
bool PortMgr::setPortMtu(const string &alias, const string &mtu, std::unordered_map<std::string, int> &lagMembers)
2660
{
2761
stringstream cmd;
2862
string res, cmd_str;
@@ -43,6 +77,12 @@ bool PortMgr::setPortMtu(const string &alias, const string &mtu)
4377
SWSS_LOG_WARN("Setting mtu to alias:%s netdev failed with cmd:%s, rc:%d, error:%s", alias.c_str(), cmd_str.c_str(), ret, res.c_str());
4478
return false;
4579
}
80+
else if (isLagMember(alias, lagMembers))
81+
{
82+
// Catch user improperly specified an MTU on the PortChannel
83+
SWSS_LOG_WARN("Setting mtu to alias:%s which is a member of a PortChannel is invalid", alias.c_str());
84+
return false;
85+
}
4686
else
4787
{
4888
throw runtime_error(cmd_str + " : " + res);
@@ -128,10 +168,17 @@ void PortMgr::doSendToIngressPortTask(Consumer &consumer)
128168

129169
}
130170

171+
131172
void PortMgr::doTask(Consumer &consumer)
132173
{
133174
SWSS_LOG_ENTER();
134175

176+
/* Variable to lazily cache lag members upon first call into isLagMember(). We
177+
* don't want to always query for lag members if not needed, and we also don't
178+
* want to query it for each call to isLagMember() which may be on every port.
179+
*/
180+
std::unordered_map<std::string, int> lagMembers;
181+
135182
auto table = consumer.getTableName();
136183
if (table == CFG_SEND_TO_INGRESS_PORT_TABLE_NAME)
137184
{
@@ -156,6 +203,7 @@ void PortMgr::doTask(Consumer &consumer)
156203
bool portOk = isPortStateOk(alias);
157204

158205
string admin_status, mtu;
206+
bool isMtuSet = false;
159207
std::vector<FieldValueTuple> field_values;
160208

161209
bool configured = (m_portList.find(alias) != m_portList.end());
@@ -167,7 +215,6 @@ void PortMgr::doTask(Consumer &consumer)
167215
{
168216
admin_status = DEFAULT_ADMIN_STATUS_STR;
169217
mtu = DEFAULT_MTU_STR;
170-
171218
m_portList.insert(alias);
172219
}
173220
else if (!portOk)
@@ -181,6 +228,10 @@ void PortMgr::doTask(Consumer &consumer)
181228
if (fvField(i) == "mtu")
182229
{
183230
mtu = fvValue(i);
231+
/* mtu read might be "", so we can't just use .empty() to
232+
* know if its set. There's test cases that depend on this
233+
* logic ... */
234+
isMtuSet = true;
184235
}
185236
else if (fvField(i) == "admin_status")
186237
{
@@ -192,6 +243,11 @@ void PortMgr::doTask(Consumer &consumer)
192243
}
193244
}
194245

246+
/* Clear default MTU if LAG member as this will otherwise fail. */
247+
if (!isMtuSet && isLagMember(alias, lagMembers)) {
248+
mtu = "";
249+
}
250+
195251
if (!portOk)
196252
{
197253
// Port configuration is handled by the orchagent. If the configuration is written to the APP DB using
@@ -221,14 +277,14 @@ void PortMgr::doTask(Consumer &consumer)
221277

222278
if (!mtu.empty())
223279
{
224-
setPortMtu(alias, mtu);
225280
SWSS_LOG_NOTICE("Configure %s MTU to %s", alias.c_str(), mtu.c_str());
281+
setPortMtu(alias, mtu, lagMembers);
226282
}
227283

228284
if (!admin_status.empty())
229285
{
230-
setPortAdminStatus(alias, admin_status == "up");
231286
SWSS_LOG_NOTICE("Configure %s admin status to %s", alias.c_str(), admin_status.c_str());
287+
setPortAdminStatus(alias, admin_status == "up");
232288
}
233289
}
234290
else if (op == DEL_COMMAND)

cfgmgr/portmgr.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ class PortMgr : public Orch
3434
void doSendToIngressPortTask(Consumer &consumer);
3535
bool writeConfigToAppDb(const std::string &alias, const std::string &field, const std::string &value);
3636
bool writeConfigToAppDb(const std::string &alias, std::vector<FieldValueTuple> &field_values);
37-
bool setPortMtu(const std::string &alias, const std::string &mtu);
37+
bool setPortMtu(const std::string &alias, const std::string &mtu, std::unordered_map<std::string, int> &lagMembers);
3838
bool setPortAdminStatus(const std::string &alias, const bool up);
3939
bool isPortStateOk(const std::string &alias);
40+
bool isLagMember(const std::string &alias, std::unordered_map<std::string, int> &lagMembers);
4041
};
4142

4243
}

tests/test_portchannel.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,51 @@ def test_portchannel_member_netdev_oper_status(self, dvs, testlog):
464464
# wait for port-channel deletion
465465
time.sleep(1)
466466

467+
# Make sure if a PortChannel member port tries to set an MTU that it is
468+
# ignored and does not cause a runtime error.
469+
def test_portchannel_member_mtu(self, dvs, testlog):
470+
config_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0)
471+
app_db = swsscommon.DBConnector(swsscommon.APPL_DB, dvs.redis_sock, 0)
472+
473+
# create port-channel
474+
tbl = swsscommon.Table(config_db, "PORTCHANNEL")
475+
fvs = swsscommon.FieldValuePairs([("admin_status", "up"),("mtu", "9100"),("oper_status", "up")])
476+
tbl.set("PortChannel111", fvs)
477+
478+
# set port-channel oper status
479+
tbl = swsscommon.ProducerStateTable(app_db, "LAG_TABLE")
480+
fvs = swsscommon.FieldValuePairs([("admin_status", "up"),("mtu", "9100"),("oper_status", "up")])
481+
tbl.set("PortChannel111", fvs)
482+
483+
# add members to port-channel
484+
tbl = swsscommon.Table(config_db, "PORTCHANNEL_MEMBER")
485+
fvs = swsscommon.FieldValuePairs([("NULL", "NULL")])
486+
tbl.set("PortChannel111|Ethernet0", fvs)
487+
tbl.set("PortChannel111|Ethernet4", fvs)
488+
489+
# wait for port-channel netdev creation
490+
time.sleep(1)
491+
492+
tbl = swsscommon.Table(config_db, "PORT")
493+
fvs = swsscommon.FieldValuePairs([("mtu", "9100")])
494+
tbl.set("Ethernet0", fvs)
495+
496+
# wait for attempted configuration to be applied
497+
time.sleep(1)
498+
499+
# remove port-channel members
500+
tbl = swsscommon.Table(config_db, "PORTCHANNEL_MEMBER")
501+
tbl._del("PortChannel111|Ethernet0")
502+
tbl._del("PortChannel111|Ethernet4")
503+
504+
# remove port-channel
505+
tbl = swsscommon.Table(config_db, "PORTCHANNEL")
506+
tbl._del("PortChannel111")
507+
508+
# wait for port-channel deletion
509+
time.sleep(1)
510+
511+
467512
# Add Dummy always-pass test at end as workaroud
468513
# for issue when Flaky fail on final test it invokes module tear-down before retrying
469514
def test_nonflaky_dummy():

0 commit comments

Comments
 (0)