From 5a8d403d344ea9f4569ed99250b7f57cdcc31e99 Mon Sep 17 00:00:00 2001 From: Divya Kumaran Chandralekha <66686927+divyachandralekha@users.noreply.github.com> Date: Fri, 13 Dec 2024 10:41:28 +0530 Subject: [PATCH] PVST feature support (#3425) --- cfgmgr/Makefile.am | 10 +- cfgmgr/stpmgr.cpp | 1093 ++++++++++++++++++++++++ cfgmgr/stpmgr.h | 225 +++++ cfgmgr/stpmgrd.cpp | 119 +++ configure.ac | 7 +- orchagent/Makefile.am | 3 +- orchagent/fdborch.cpp | 29 + orchagent/fdborch.h | 1 + orchagent/orchdaemon.cpp | 12 +- orchagent/orchdaemon.h | 1 + orchagent/p4orch/tests/mock_sai_stp.h | 110 +++ orchagent/port.h | 3 + orchagent/portsorch.cpp | 11 + orchagent/saihelper.cpp | 3 + orchagent/stporch.cpp | 514 +++++++++++ orchagent/stporch.h | 53 ++ tests/mock_tests/Makefile.am | 4 +- tests/mock_tests/mock_orchagent_main.h | 5 + tests/mock_tests/stporch_ut.cpp | 246 ++++++ tests/mock_tests/ut_saihelper.cpp | 3 +- 20 files changed, 2446 insertions(+), 6 deletions(-) create mode 100644 cfgmgr/stpmgr.cpp create mode 100644 cfgmgr/stpmgr.h create mode 100644 cfgmgr/stpmgrd.cpp create mode 100644 orchagent/p4orch/tests/mock_sai_stp.h create mode 100644 orchagent/stporch.cpp create mode 100644 orchagent/stporch.h create mode 100644 tests/mock_tests/stporch_ut.cpp diff --git a/cfgmgr/Makefile.am b/cfgmgr/Makefile.am index 45afff7e9b..096c222418 100644 --- a/cfgmgr/Makefile.am +++ b/cfgmgr/Makefile.am @@ -5,7 +5,7 @@ LIBNL_LIBS = -lnl-genl-3 -lnl-route-3 -lnl-3 SAIMETA_LIBS = -lsaimeta -lsaimetadata -lzmq COMMON_LIBS = -lswsscommon -lpthread -bin_PROGRAMS = vlanmgrd teammgrd portmgrd intfmgrd buffermgrd vrfmgrd nbrmgrd vxlanmgrd sflowmgrd natmgrd coppmgrd tunnelmgrd macsecmgrd fabricmgrd +bin_PROGRAMS = vlanmgrd teammgrd portmgrd intfmgrd buffermgrd vrfmgrd nbrmgrd vxlanmgrd sflowmgrd natmgrd coppmgrd tunnelmgrd macsecmgrd fabricmgrd stpmgrd cfgmgrdir = $(datadir)/swss @@ -101,6 +101,12 @@ macsecmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CF macsecmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) macsecmgrd_LDADD = $(LDFLAGS_ASAN) $(COMMON_LIBS) $(SAIMETA_LIBS) + +stpmgrd_SOURCES = stpmgrd.cpp stpmgr.cpp $(COMMON_ORCH_SOURCE) shellcmd.h +stpmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) +stpmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) +stpmgrd_LDADD = $(LDFLAGS_ASAN) $(COMMON_LIBS) $(SAIMETA_LIBS) + if GCOV_ENABLED vlanmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp teammgrd_SOURCES += ../gcovpreload/gcovpreload.cpp @@ -116,6 +122,7 @@ natmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp coppmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp tunnelmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp macsecmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp +stpmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp endif if ASAN_ENABLED @@ -133,5 +140,6 @@ coppmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp tunnelmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp macsecmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp fabricmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp +stpmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp endif diff --git a/cfgmgr/stpmgr.cpp b/cfgmgr/stpmgr.cpp new file mode 100644 index 0000000000..77991e21b0 --- /dev/null +++ b/cfgmgr/stpmgr.cpp @@ -0,0 +1,1093 @@ +#include "exec.h" +#include "stpmgr.h" +#include "logger.h" +#include "tokenize.h" +#include "warm_restart.h" + +#include +#include +#include +#include + +#include +#include +#include + +using namespace std; +using namespace swss; + +StpMgr::StpMgr(DBConnector *confDb, DBConnector *applDb, DBConnector *statDb, + const vector &tables) : + Orch(tables), + m_cfgStpGlobalTable(confDb, CFG_STP_GLOBAL_TABLE_NAME), + m_cfgStpVlanTable(confDb, CFG_STP_VLAN_TABLE_NAME), + m_cfgStpVlanPortTable(confDb, CFG_STP_VLAN_PORT_TABLE_NAME), + m_cfgStpPortTable(confDb, CFG_STP_PORT_TABLE_NAME), + m_cfgLagMemberTable(confDb, CFG_LAG_MEMBER_TABLE_NAME), + m_cfgVlanMemberTable(confDb, CFG_VLAN_MEMBER_TABLE_NAME), + m_stateVlanTable(statDb, STATE_VLAN_TABLE_NAME), + m_stateLagTable(statDb, STATE_LAG_TABLE_NAME), + m_stateStpTable(statDb, STATE_STP_TABLE_NAME), + m_stateVlanMemberTable(statDb, STATE_VLAN_MEMBER_TABLE_NAME) +{ + SWSS_LOG_ENTER(); + l2ProtoEnabled = L2_NONE; + + stpGlobalTask = stpVlanTask = stpVlanPortTask = stpPortTask = false; + + // Initialize all VLANs to Invalid instance + fill_n(m_vlanInstMap, MAX_VLANS, INVALID_INSTANCE); + + int ret = system("ebtables -D FORWARD -d 01:00:0c:cc:cc:cd -j DROP"); + SWSS_LOG_DEBUG("ebtables ret %d", ret); +} + +void StpMgr::doTask(Consumer &consumer) +{ + auto table = consumer.getTableName(); + + SWSS_LOG_INFO("Get task from table %s", table.c_str()); + + if (table == CFG_STP_GLOBAL_TABLE_NAME) + doStpGlobalTask(consumer); + else if (table == CFG_STP_VLAN_TABLE_NAME) + doStpVlanTask(consumer); + else if (table == CFG_STP_VLAN_PORT_TABLE_NAME) + doStpVlanPortTask(consumer); + else if (table == CFG_STP_PORT_TABLE_NAME) + doStpPortTask(consumer); + else if (table == CFG_LAG_MEMBER_TABLE_NAME) + doLagMemUpdateTask(consumer); + else if (table == STATE_VLAN_MEMBER_TABLE_NAME) + doVlanMemUpdateTask(consumer); + else + SWSS_LOG_ERROR("Invalid table %s", table.c_str()); +} + +void StpMgr::doStpGlobalTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + if (stpGlobalTask == false) + stpGlobalTask = true; + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + STP_BRIDGE_CONFIG_MSG msg; + memset(&msg, 0, sizeof(STP_BRIDGE_CONFIG_MSG)); + + KeyOpFieldsValuesTuple t = it->second; + + string key = kfvKey(t); + string op = kfvOp(t); + + SWSS_LOG_INFO("STP global key %s op %s", key.c_str(), op.c_str()); + if (op == SET_COMMAND) + { + msg.opcode = STP_SET_COMMAND; + for (auto i : kfvFieldsValues(t)) + { + SWSS_LOG_DEBUG("Field: %s Val %s", fvField(i).c_str(), fvValue(i).c_str()); + if (fvField(i) == "mode") + { + if (fvValue(i) == "pvst") + { + if (l2ProtoEnabled == L2_NONE) + { + const std::string cmd = std::string("") + + " ebtables -A FORWARD -d 01:00:0c:cc:cc:cd -j DROP"; + std::string res; + int ret = swss::exec(cmd, res); + if (ret != 0) + SWSS_LOG_ERROR("ebtables add failed %d", ret); + + l2ProtoEnabled = L2_PVSTP; + } + msg.stp_mode = L2_PVSTP; + } + else + SWSS_LOG_ERROR("Error invalid mode %s", fvValue(i).c_str()); + } + else if (fvField(i) == "rootguard_timeout") + { + msg.rootguard_timeout = stoi(fvValue(i).c_str()); + } + } + + memcpy(msg.base_mac_addr, macAddress.getMac(), 6); + } + else if (op == DEL_COMMAND) + { + msg.opcode = STP_DEL_COMMAND; + l2ProtoEnabled = L2_NONE; + + //Free Up all instances + FREE_ALL_INST_ID(); + + // Initialize all VLANs to Invalid instance + fill_n(m_vlanInstMap, MAX_VLANS, INVALID_INSTANCE); + + const std::string cmd = std::string("") + + " ebtables -D FORWARD -d 01:00:0c:cc:cc:cd -j DROP"; + std::string res; + int ret = swss::exec(cmd, res); + if (ret != 0) + SWSS_LOG_ERROR("ebtables del failed %d", ret); + } + + sendMsgStpd(STP_BRIDGE_CONFIG, sizeof(msg), (void *)&msg); + + it = consumer.m_toSync.erase(it); + } +} + +void StpMgr::doStpVlanTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + if (stpGlobalTask == false || (stpPortTask == false && !isStpPortEmpty())) + return; + + if (stpVlanTask == false) + stpVlanTask = true; + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + STP_VLAN_CONFIG_MSG *msg = NULL; + uint32_t len = 0; + bool stpEnable = false; + uint8_t newInstance = 0; + int instId, forwardDelay, helloTime, maxAge, priority, portCnt = 0; + instId = forwardDelay = helloTime = maxAge = priority = portCnt = 0; + + KeyOpFieldsValuesTuple t = it->second; + + string key = kfvKey(t); + string op = kfvOp(t); + + string vlanKey = key.substr(4); // Remove Vlan prefix + int vlan_id = stoi(vlanKey.c_str()); + + SWSS_LOG_INFO("STP vlan key %s op %s", key.c_str(), op.c_str()); + if (op == SET_COMMAND) + { + if (l2ProtoEnabled == L2_NONE || !isVlanStateOk(key)) + { + // Wait till STP is configured + it++; + continue; + } + + for (auto i : kfvFieldsValues(t)) + { + SWSS_LOG_DEBUG("Field: %s Val: %s", fvField(i).c_str(), fvValue(i).c_str()); + + if (fvField(i) == "enabled") + { + stpEnable = (fvValue(i) == "true") ? true : false; + } + else if (fvField(i) == "forward_delay") + { + forwardDelay = stoi(fvValue(i).c_str()); + } + else if (fvField(i) == "hello_time") + { + helloTime = stoi(fvValue(i).c_str()); + } + else if (fvField(i) == "max_age") + { + maxAge = stoi(fvValue(i).c_str()); + } + else if (fvField(i) == "priority") + { + priority = stoi(fvValue(i).c_str()); + } + } + } + else if (op == DEL_COMMAND) + { + stpEnable = false; + if (l2ProtoEnabled == L2_NONE) + { + it = consumer.m_toSync.erase(it); + continue; + } + } + + len = sizeof(STP_VLAN_CONFIG_MSG); + if (stpEnable == true) + { + vector port_list; + if (m_vlanInstMap[vlan_id] == INVALID_INSTANCE) + { + /* VLAN is being added to the instance. Get all members for VLAN Mapping*/ + if (l2ProtoEnabled == L2_PVSTP) + { + newInstance = 1; + instId = allocL2Instance(vlan_id); + if (instId == -1) + { + SWSS_LOG_ERROR("Couldnt allocate instance to VLAN %d", vlan_id); + it = consumer.m_toSync.erase(it); + continue; + } + + portCnt = getAllVlanMem(key, port_list); + SWSS_LOG_DEBUG("Port count %d", portCnt); + } + + len += (uint32_t)(portCnt * sizeof(PORT_ATTR)); + } + + msg = (STP_VLAN_CONFIG_MSG *)calloc(1, len); + if (!msg) + { + SWSS_LOG_ERROR("mem failed for vlan %d", vlan_id); + return; + } + + msg->opcode = STP_SET_COMMAND; + msg->vlan_id = vlan_id; + msg->newInstance = newInstance; + msg->inst_id = m_vlanInstMap[vlan_id]; + msg->forward_delay = forwardDelay; + msg->hello_time = helloTime; + msg->max_age = maxAge; + msg->priority = priority; + msg->count = portCnt; + + if(msg->count) + { + int i = 0; + PORT_ATTR *attr = msg->port_list; + for (auto p = port_list.begin(); p != port_list.end(); p++) + { + attr[i].mode = p->mode; + attr[i].enabled = p->enabled; + strncpy(attr[i].intf_name, p->intf_name, IFNAMSIZ); + SWSS_LOG_DEBUG("MemIntf: %s", p->intf_name); + i++; + } + } + } + else + { + if (m_vlanInstMap[vlan_id] == INVALID_INSTANCE) + { + // Already deallocated. NoOp. This can happen when STP + // is disabled on a VLAN more than once + it = consumer.m_toSync.erase(it); + continue; + } + + msg = (STP_VLAN_CONFIG_MSG *)calloc(1, len); + if (!msg) + { + SWSS_LOG_ERROR("mem failed for vlan %d", vlan_id); + return; + } + + msg->opcode = STP_DEL_COMMAND; + msg->inst_id = m_vlanInstMap[vlan_id]; + + deallocL2Instance(vlan_id); + } + + sendMsgStpd(STP_VLAN_CONFIG, len, (void *)msg); + if (msg) + free(msg); + + it = consumer.m_toSync.erase(it); + } +} + +void StpMgr::processStpVlanPortAttr(const string op, uint32_t vlan_id, const string intfName, + vector&tupEntry) +{ + STP_VLAN_PORT_CONFIG_MSG msg; + memset(&msg, 0, sizeof(STP_VLAN_PORT_CONFIG_MSG)); + + msg.vlan_id = vlan_id; + msg.inst_id = m_vlanInstMap[vlan_id]; + strncpy(msg.intf_name, intfName.c_str(), IFNAMSIZ-1); + + if (op == SET_COMMAND) + { + msg.opcode = STP_SET_COMMAND; + msg.priority = -1; + + for (auto i : tupEntry) + { + SWSS_LOG_DEBUG("Field: %s Val: %s", fvField(i).c_str(), fvValue(i).c_str()); + if (fvField(i) == "path_cost") + { + msg.path_cost = stoi(fvValue(i).c_str()); + } + else if (fvField(i) == "priority") + { + msg.priority = stoi(fvValue(i).c_str()); + } + } + } + else if (op == DEL_COMMAND) + { + msg.opcode = STP_DEL_COMMAND; + } + + sendMsgStpd(STP_VLAN_PORT_CONFIG, sizeof(msg), (void *)&msg); +} + +void StpMgr::doStpVlanPortTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + if (stpGlobalTask == false || stpVlanTask == false || stpPortTask == false) + return; + + if (stpVlanPortTask == false) + stpVlanPortTask = true; + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + STP_VLAN_PORT_CONFIG_MSG msg; + memset(&msg, 0, sizeof(STP_VLAN_PORT_CONFIG_MSG)); + + KeyOpFieldsValuesTuple t = it->second; + + string key = kfvKey(t); + string op = kfvOp(t); + + string vlanKey = key.substr(4); // Remove VLAN keyword + size_t found = vlanKey.find(CONFIGDB_KEY_SEPARATOR); + + int vlan_id; + string intfName; + if (found != string::npos) + { + vlan_id = stoi(vlanKey.substr(0, found)); + intfName = vlanKey.substr(found+1); + } + else + { + SWSS_LOG_ERROR("Invalid key format %s", kfvKey(t).c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + SWSS_LOG_INFO("STP vlan intf key:%s op:%s", key.c_str(), op.c_str()); + + if (op == SET_COMMAND) + { + if ((l2ProtoEnabled == L2_NONE) || (m_vlanInstMap[vlan_id] == INVALID_INSTANCE)) + { + // Wait till STP/VLAN is configured + it++; + continue; + } + } + else + { + if (l2ProtoEnabled == L2_NONE || (m_vlanInstMap[vlan_id] == INVALID_INSTANCE)) + { + it = consumer.m_toSync.erase(it); + continue; + } + } + + if (isLagEmpty(intfName)) + { + // Lag has no member. Process when first member is added/deleted + it = consumer.m_toSync.erase(it); + continue; + } + + processStpVlanPortAttr(op, vlan_id, intfName, kfvFieldsValues(t)); + + it = consumer.m_toSync.erase(it); + } +} + +void StpMgr::processStpPortAttr(const string op, vector&tupEntry, const string intfName) +{ + STP_PORT_CONFIG_MSG *msg; + uint32_t len = 0; + int vlanCnt = 0; + vector vlan_list; + + if (op == SET_COMMAND) + vlanCnt = getAllPortVlan(intfName, vlan_list); + + len = (uint32_t)(sizeof(STP_PORT_CONFIG_MSG) + (vlanCnt * sizeof(VLAN_ATTR))); + msg = (STP_PORT_CONFIG_MSG *)calloc(1, len); + if (!msg) + { + SWSS_LOG_ERROR("mem failed for %s", intfName.c_str()); + return; + } + + strncpy(msg->intf_name, intfName.c_str(), IFNAMSIZ-1); + msg->count = vlanCnt; + SWSS_LOG_INFO("Vlan count %d", vlanCnt); + + if(msg->count) + { + int i = 0; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Waddress-of-packed-member" + VLAN_ATTR *attr = msg->vlan_list; +#pragma GCC diagnostic pop + for (auto p = vlan_list.begin(); p != vlan_list.end(); p++) + { + attr[i].inst_id = p->inst_id; + attr[i].mode = p->mode; + SWSS_LOG_DEBUG("Inst:%d Mode:%d", p->inst_id, p->mode); + i++; + } + } + + if (op == SET_COMMAND) + { + msg->opcode = STP_SET_COMMAND; + msg->priority = -1; + + for (auto i : tupEntry) + { + SWSS_LOG_DEBUG("Field: %s Val: %s", fvField(i).c_str(), fvValue(i).c_str()); + if (fvField(i) == "enabled") + { + msg->enabled = (fvValue(i) == "true") ? 1 : 0; + } + else if (fvField(i) == "root_guard") + { + msg->root_guard = (fvValue(i) == "true") ? 1 : 0; + } + else if (fvField(i) == "bpdu_guard") + { + msg->bpdu_guard = (fvValue(i) == "true") ? 1 : 0; + } + else if (fvField(i) == "bpdu_guard_do_disable") + { + msg->bpdu_guard_do_disable = (fvValue(i) == "true") ? 1 : 0; + } + else if (fvField(i) == "path_cost") + { + msg->path_cost = stoi(fvValue(i).c_str()); + } + else if (fvField(i) == "priority") + { + msg->priority = stoi(fvValue(i).c_str()); + } + else if (fvField(i) == "portfast") + { + msg->portfast = (fvValue(i) == "true") ? 1 : 0; + } + else if (fvField(i) == "uplink_fast") + { + msg->uplink_fast = (fvValue(i) == "true") ? 1 : 0; + } + } + } + else if (op == DEL_COMMAND) + { + msg->opcode = STP_DEL_COMMAND; + msg->enabled = 0; + } + + sendMsgStpd(STP_PORT_CONFIG, len, (void *)msg); + if (msg) + free(msg); +} + +void StpMgr::doStpPortTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + if (stpGlobalTask == false) + return; + + if (stpPortTask == false) + stpPortTask = true; + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + + string key = kfvKey(t); + string op = kfvOp(t); + + if (isLagEmpty(key)) + { + it = consumer.m_toSync.erase(it); + continue; + } + + if (op == SET_COMMAND) + { + if (l2ProtoEnabled == L2_NONE) + { + // Wait till STP is configured + it++; + continue; + } + } + else + { + if (l2ProtoEnabled == L2_NONE) + { + it = consumer.m_toSync.erase(it); + continue; + } + } + + SWSS_LOG_INFO("STP port key:%s op:%s", key.c_str(), op.c_str()); + processStpPortAttr(op, kfvFieldsValues(t), key); + + it = consumer.m_toSync.erase(it); + } +} + +void StpMgr::doVlanMemUpdateTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + STP_VLAN_MEM_CONFIG_MSG msg; + memset(&msg, 0, sizeof(STP_VLAN_MEM_CONFIG_MSG)); + + KeyOpFieldsValuesTuple t = it->second; + + auto key = kfvKey(t); + auto op = kfvOp(t); + + string vlanKey = key.substr(4); // Remove Vlan prefix + size_t found = vlanKey.find(CONFIGDB_KEY_SEPARATOR); + + int vlan_id; + string intfName; + if (found != string::npos) + { + vlan_id = stoi(vlanKey.substr(0, found)); + intfName = vlanKey.substr(found+1); + } + else + { + SWSS_LOG_ERROR("Invalid key format. No member port is presented: %s", kfvKey(t).c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + SWSS_LOG_INFO("STP vlan mem key:%s op:%s inst:%d", key.c_str(), op.c_str(), m_vlanInstMap[vlan_id]); + // If STP is running on this VLAN, notify STPd + if (m_vlanInstMap[vlan_id] != INVALID_INSTANCE && !isLagEmpty(intfName)) + { + int8_t tagging_mode = TAGGED_MODE; + + if (op == SET_COMMAND) + { + tagging_mode = getVlanMemMode(key); + if (tagging_mode == INVALID_MODE) + { + SWSS_LOG_ERROR("invalid mode %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + SWSS_LOG_DEBUG("mode %d key %s", tagging_mode, key.c_str()); + + msg.enabled = isStpEnabled(intfName); + + vector stpVlanPortEntry; + if (m_cfgStpVlanPortTable.get(key, stpVlanPortEntry)) + { + for (auto entry : stpVlanPortEntry) + { + if (entry.first == "priority") + msg.priority = stoi(entry.second); + else if (entry.first == "path_cost") + msg.path_cost = stoi(entry.second); + } + } + } + + msg.opcode = (op == SET_COMMAND) ? STP_SET_COMMAND : STP_DEL_COMMAND; + msg.vlan_id = vlan_id; + msg.inst_id = m_vlanInstMap[vlan_id]; + msg.mode = tagging_mode; + msg.priority = -1; + msg.path_cost = 0; + + strncpy(msg.intf_name, intfName.c_str(), IFNAMSIZ-1); + + sendMsgStpd(STP_VLAN_MEM_CONFIG, sizeof(msg), (void *)&msg); + } + + it = consumer.m_toSync.erase(it); + } +} + +void StpMgr::doLagMemUpdateTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + bool notifyStpd = false; + + auto key = kfvKey(t); + auto op = kfvOp(t); + + string po_name; + string po_mem; + size_t found = key.find(CONFIGDB_KEY_SEPARATOR); + + if (found != string::npos) + { + po_name = key.substr(0, found); + po_mem = key.substr(found+1); + } + else + { + SWSS_LOG_ERROR("Invalid key format %s", key.c_str()); + it = consumer.m_toSync.erase(it); + continue; + } + + if (op == SET_COMMAND) + { + if (!isLagStateOk(po_name)) + { + it++; + continue; + } + + auto elm = m_lagMap.find(po_name); + if (elm == m_lagMap.end()) + { + // First Member added to the LAG + m_lagMap[po_name] = 1; + notifyStpd = true; + } + else + { + elm->second++; + } + } + else if (op == DEL_COMMAND) + { + auto elm = m_lagMap.find(po_name); + if (elm != m_lagMap.end()) + { + elm->second--; + + if (elm->second == 0) + { + // Last Member deleted from the LAG + m_lagMap.erase(po_name); + //notifyStpd = true; + } + } + else + SWSS_LOG_ERROR("PO not found %s", po_name.c_str()); + } + + if (notifyStpd && l2ProtoEnabled != L2_NONE) + { + vector vlan_list; + vector tupEntry; + + if (m_cfgStpPortTable.get(po_name, tupEntry)) + { + //Push STP_PORT configs for this port + processStpPortAttr(op, tupEntry, po_name); + + getAllPortVlan(po_name, vlan_list); + //Push STP_VLAN_PORT configs for this port + for (auto p = vlan_list.begin(); p != vlan_list.end(); p++) + { + vector vlanPortTup; + + string vlanPortKey = "Vlan" + to_string(p->vlan_id) + "|" + po_name; + if (m_cfgStpVlanPortTable.get(vlanPortKey, vlanPortTup)) + processStpVlanPortAttr(op, p->vlan_id, po_name, vlanPortTup); + } + } + } + + SWSS_LOG_DEBUG("LagMap"); + for (auto itr = m_lagMap.begin(); itr != m_lagMap.end(); ++itr) { + SWSS_LOG_DEBUG("PO: %s Cnt:%d", itr->first.c_str(), itr->second); + } + + it = consumer.m_toSync.erase(it); + } +} + +void StpMgr::ipcInitStpd() +{ + int ret; + struct sockaddr_un addr; + + unlink(STPMGRD_SOCK_NAME); + // create socket + stpd_fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (!stpd_fd) { + SWSS_LOG_ERROR("socket error %s", strerror(errno)); + return; + } + + // setup socket address structure + bzero(&addr, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, STPMGRD_SOCK_NAME, sizeof(addr.sun_path)-1); + + ret = (int)bind(stpd_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)); + if (ret == -1) + { + SWSS_LOG_ERROR("ipc bind error %s", strerror(errno)); + close(stpd_fd); + return; + } +} + +int StpMgr::allocL2Instance(uint32_t vlan_id) +{ + int idx = 0; + + if (!IS_INST_ID_AVAILABLE()) + { + SWSS_LOG_ERROR("No instance available"); + return -1; + } + + if (l2ProtoEnabled == L2_PVSTP) + { + GET_FIRST_FREE_INST_ID(idx); + } + else + { + SWSS_LOG_ERROR("invalid proto %d for vlan %d", l2ProtoEnabled, vlan_id); + return -1; + } + + //Set VLAN to Instance mapping + m_vlanInstMap[vlan_id] = idx; + SWSS_LOG_INFO("Allocated Id: %d Vlan %d", m_vlanInstMap[vlan_id], vlan_id); + + return idx; +} + +void StpMgr::deallocL2Instance(uint32_t vlan_id) +{ + int idx = 0; + + if (l2ProtoEnabled == L2_PVSTP) + { + idx = m_vlanInstMap[vlan_id]; + FREE_INST_ID(idx); + } + else + { + SWSS_LOG_ERROR("invalid proto %d for vlan %d", l2ProtoEnabled, vlan_id); + } + + m_vlanInstMap[vlan_id] = INVALID_INSTANCE; + SWSS_LOG_INFO("Deallocated Id: %d Vlan %d", m_vlanInstMap[vlan_id], vlan_id); +} + + +int StpMgr::getAllVlanMem(const string &vlanKey, vector&port_list) +{ + PORT_ATTR port_id; + vector vmEntry; + + vector vmKeys; + m_stateVlanMemberTable.getKeys(vmKeys); + + SWSS_LOG_INFO("VLAN Key: %s", vlanKey.c_str()); + for (auto key : vmKeys) + { + size_t found = key.find(CONFIGDB_KEY_SEPARATOR); //split VLAN and interface + + string vlanName; + string intfName; + if (found != string::npos) + { + vlanName = key.substr(0, found); + intfName = key.substr(found+1); + } + else + { + SWSS_LOG_ERROR("Invalid Key: %s", key.c_str()); + continue; + } + + if (vlanKey == vlanName && !isLagEmpty(intfName)) + { + port_id.mode = getVlanMemMode(key); + if (port_id.mode == INVALID_MODE) + { + SWSS_LOG_ERROR("invalid mode %s", key.c_str()); + continue; + } + + port_id.enabled = isStpEnabled(intfName); + strncpy(port_id.intf_name, intfName.c_str(), IFNAMSIZ-1); + port_list.push_back(port_id); + SWSS_LOG_DEBUG("MemIntf: %s", intfName.c_str()); + } + } + + return (int)port_list.size(); +} + +int StpMgr::getAllPortVlan(const string &intfKey, vector&vlan_list) +{ + VLAN_ATTR vlan; + vector vmEntry; + + vector vmKeys; + m_stateVlanMemberTable.getKeys(vmKeys); + + SWSS_LOG_INFO("Intf Key: %s", intfKey.c_str()); + for (auto key : vmKeys) + { + string vlanKey = key.substr(4); // Remove Vlan prefix + size_t found = vlanKey.find(CONFIGDB_KEY_SEPARATOR); //split VLAN and interface + SWSS_LOG_DEBUG("Vlan mem Key: %s", key.c_str()); + + int vlan_id; + string intfName; + if (found != string::npos) + { + vlan_id = stoi(vlanKey.substr(0, found)); + intfName = vlanKey.substr(found+1); + + if (intfName == intfKey) + { + if (m_vlanInstMap[vlan_id] != INVALID_INSTANCE) + { + vlan.mode = getVlanMemMode(key); + if (vlan.mode == INVALID_MODE) + { + SWSS_LOG_ERROR("invalid mode %s", key.c_str()); + continue; + } + + vlan.vlan_id = vlan_id; + vlan.inst_id = m_vlanInstMap[vlan_id]; + vlan_list.push_back(vlan); + SWSS_LOG_DEBUG("Matched vlan key: %s intf key %s", intfName.c_str(), intfKey.c_str()); + } + } + } + } + + return (int)vlan_list.size(); +} + +// Send Message to STPd +int StpMgr::sendMsgStpd(STP_MSG_TYPE msgType, uint32_t msgLen, void *data) +{ + STP_IPC_MSG *tx_msg; + size_t len = 0; + struct sockaddr_un addr; + int rc; + + len = msgLen + (offsetof(struct STP_IPC_MSG, data)); + SWSS_LOG_INFO("tx_msg len %d msglen %d", (int)len, msgLen); + + tx_msg = (STP_IPC_MSG *)calloc(1, len); + if (tx_msg == NULL) + { + SWSS_LOG_ERROR("tx_msg mem alloc error\n"); + return -1; + } + + tx_msg->msg_type = msgType; + tx_msg->msg_len = msgLen; + memcpy(tx_msg->data, data, msgLen); + + bzero(&addr, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, STPD_SOCK_NAME, sizeof(addr.sun_path)-1); + + rc = (int)sendto(stpd_fd, (void*)tx_msg, len, 0, (struct sockaddr *)&addr, sizeof(addr)); + if (rc == -1) + { + SWSS_LOG_ERROR("tx_msg send error\n"); + } + else + { + SWSS_LOG_INFO("tx_msg sent %d", rc); + } + + free(tx_msg); + return rc; +} + +bool StpMgr::isPortInitDone(DBConnector *app_db) +{ + bool portInit = 0; + long cnt = 0; + + while(!portInit) { + Table portTable(app_db, APP_PORT_TABLE_NAME); + std::vector tuples; + portInit = portTable.get("PortInitDone", tuples); + + if(portInit) + break; + sleep(1); + cnt++; + } + SWSS_LOG_NOTICE("PORT_INIT_DONE : %d %ld", portInit, cnt); + return portInit; +} + +bool StpMgr::isVlanStateOk(const string &alias) +{ + vector temp; + + if (!alias.compare(0, strlen(VLAN_PREFIX), VLAN_PREFIX)) + { + if (m_stateVlanTable.get(alias, temp)) + { + SWSS_LOG_DEBUG("%s is ready", alias.c_str()); + return true; + } + } + SWSS_LOG_DEBUG("%s is not ready", alias.c_str()); + return false; +} + +bool StpMgr::isLagStateOk(const string &alias) +{ + vector temp; + + if (m_stateLagTable.get(alias, temp)) + { + SWSS_LOG_DEBUG("%s is ready", alias.c_str()); + return true; + } + + SWSS_LOG_DEBUG("%s is not ready", alias.c_str()); + return false; +} + +bool StpMgr::isLagEmpty(const string &key) +{ + size_t po_find = key.find("PortChannel"); + if (po_find != string::npos) + { + // If Lag, check if members present + auto elm = m_lagMap.find(key); + if (elm == m_lagMap.end()) + { + // Lag has no member + SWSS_LOG_DEBUG("%s empty", key.c_str()); + return true; + } + SWSS_LOG_DEBUG("%s not empty", key.c_str()); + } + // Else: Interface not PO + + return false; +} + +bool StpMgr::isStpPortEmpty() +{ + vector portKeys; + m_cfgStpPortTable.getKeys(portKeys); + + if (portKeys.empty()) + { + SWSS_LOG_NOTICE("stp port empty"); + return true; + } + + SWSS_LOG_NOTICE("stp port not empty"); + return false; +} + +bool StpMgr::isStpEnabled(const string &intf_name) +{ + vector temp; + + if (m_cfgStpPortTable.get(intf_name, temp)) + { + for (auto entry : temp) + { + if (entry.first == "enabled" && entry.second == "true") + { + SWSS_LOG_NOTICE("STP enabled on %s", intf_name.c_str()); + return true; + } + } + } + + SWSS_LOG_NOTICE("STP NOT enabled on %s", intf_name.c_str()); + return false; +} + +int8_t StpMgr::getVlanMemMode(const string &key) +{ + int8_t mode = -1; + vector vmEntry; + + if (m_cfgVlanMemberTable.get(key, vmEntry)) + { + for (auto entry : vmEntry) + { + if (entry.first == "tagging_mode") + mode = (entry.second == "untagged") ? UNTAGGED_MODE : TAGGED_MODE; + SWSS_LOG_INFO("mode %d for %s", mode, key.c_str()); + } + } + else + SWSS_LOG_ERROR("config vlan_member table fetch failed %s", key.c_str()); + + return mode; +} + +uint16_t StpMgr::getStpMaxInstances(void) +{ + vector vmEntry; + uint16_t max_delay = 60; + string key; + + key = "GLOBAL"; + + while(max_delay) + { + if (m_stateStpTable.get(key, vmEntry)) + { + for (auto entry : vmEntry) + { + if (entry.first == "max_stp_inst") + { + max_stp_instances = (uint16_t)stoi(entry.second.c_str()); + SWSS_LOG_NOTICE("max stp instance %d count %d", max_stp_instances, (60-max_delay)); + } + } + break; + } + sleep(1); + max_delay--; + } + + if(max_stp_instances == 0) + { + max_stp_instances = STP_DEFAULT_MAX_INSTANCES; + SWSS_LOG_NOTICE("set default max stp instance %d", max_stp_instances); + } + + return max_stp_instances; +} diff --git a/cfgmgr/stpmgr.h b/cfgmgr/stpmgr.h new file mode 100644 index 0000000000..8eb7ffdf80 --- /dev/null +++ b/cfgmgr/stpmgr.h @@ -0,0 +1,225 @@ +#ifndef __STPMGR__ +#define __STPMGR__ + +#include +#include +#include +#include +#include +#include + +#include "dbconnector.h" +#include "netmsg.h" +#include "orch.h" +#include "producerstatetable.h" +#include +#include + +#define STPMGRD_SOCK_NAME "/var/run/stpmgrd.sock" + +#define TAGGED_MODE 1 +#define UNTAGGED_MODE 0 +#define INVALID_MODE -1 + +#define MAX_VLANS 4096 + +// Maximum number of instances supported +#define L2_INSTANCE_MAX MAX_VLANS +#define STP_DEFAULT_MAX_INSTANCES 255 +#define INVALID_INSTANCE -1 + + +#define GET_FIRST_FREE_INST_ID(_idx) \ + while (_idx < (int)l2InstPool.size() && l2InstPool.test(_idx)) ++_idx; \ + l2InstPool.set(_idx) + +#define FREE_INST_ID(_idx) l2InstPool.reset(_idx) + +#define FREE_ALL_INST_ID() l2InstPool.reset() + +#define IS_INST_ID_AVAILABLE() (l2InstPool.count() < max_stp_instances) ? true : false + +#define STPD_SOCK_NAME "/var/run/stpipc.sock" + +typedef enum L2_PROTO_MODE{ + L2_NONE, + L2_PVSTP, +}L2_PROTO_MODE; + +typedef enum STP_MSG_TYPE { + STP_INVALID_MSG, + STP_INIT_READY, + STP_BRIDGE_CONFIG, + STP_VLAN_CONFIG, + STP_VLAN_PORT_CONFIG, + STP_PORT_CONFIG, + STP_VLAN_MEM_CONFIG, + STP_STPCTL_MSG, + STP_MAX_MSG +}STP_MSG_TYPE; + +typedef enum STP_CTL_TYPE { + STP_CTL_HELP, + STP_CTL_DUMP_ALL, + STP_CTL_DUMP_GLOBAL, + STP_CTL_DUMP_VLAN_ALL, + STP_CTL_DUMP_VLAN, + STP_CTL_DUMP_INTF, + STP_CTL_SET_LOG_LVL, + STP_CTL_DUMP_NL_DB, + STP_CTL_DUMP_NL_DB_INTF, + STP_CTL_DUMP_LIBEV_STATS, + STP_CTL_SET_DBG, + STP_CTL_CLEAR_ALL, + STP_CTL_CLEAR_VLAN, + STP_CTL_CLEAR_INTF, + STP_CTL_CLEAR_VLAN_INTF, + STP_CTL_MAX +}STP_CTL_TYPE; + +typedef struct STP_IPC_MSG { + int msg_type; + unsigned int msg_len; + char data[0]; +}STP_IPC_MSG; + +#define STP_SET_COMMAND 1 +#define STP_DEL_COMMAND 0 + +typedef struct STP_INIT_READY_MSG { + uint8_t opcode; // enable/disable + uint16_t max_stp_instances; +}__attribute__ ((packed))STP_INIT_READY_MSG; + +typedef struct STP_BRIDGE_CONFIG_MSG { + uint8_t opcode; // enable/disable + uint8_t stp_mode; + int rootguard_timeout; + uint8_t base_mac_addr[6]; +}__attribute__ ((packed))STP_BRIDGE_CONFIG_MSG; + +typedef struct PORT_ATTR { + char intf_name[IFNAMSIZ]; + int8_t mode; + uint8_t enabled; +}PORT_ATTR; + +typedef struct STP_VLAN_CONFIG_MSG { + uint8_t opcode; // enable/disable + uint8_t newInstance; + int vlan_id; + int inst_id; + int forward_delay; + int hello_time; + int max_age; + int priority; + int count; + PORT_ATTR port_list[0]; +}__attribute__ ((packed))STP_VLAN_CONFIG_MSG; + +typedef struct STP_VLAN_PORT_CONFIG_MSG { + uint8_t opcode; // enable/disable + int vlan_id; + char intf_name[IFNAMSIZ]; + int inst_id; + int path_cost; + int priority; +}__attribute__ ((packed))STP_VLAN_PORT_CONFIG_MSG; + +typedef struct VLAN_ATTR { + int inst_id; + int vlan_id; + int8_t mode; +}VLAN_ATTR; + +typedef struct STP_PORT_CONFIG_MSG { + uint8_t opcode; // enable/disable + char intf_name[IFNAMSIZ]; + uint8_t enabled; + uint8_t root_guard; + uint8_t bpdu_guard; + uint8_t bpdu_guard_do_disable; + uint8_t portfast; + uint8_t uplink_fast; + int path_cost; + int priority; + int count; + VLAN_ATTR vlan_list[0]; +}__attribute__ ((packed))STP_PORT_CONFIG_MSG; + +typedef struct STP_VLAN_MEM_CONFIG_MSG { + uint8_t opcode; // enable/disable + int vlan_id; + int inst_id; + char intf_name[IFNAMSIZ]; + uint8_t enabled; + int8_t mode; + int path_cost; + int priority; +}__attribute__ ((packed))STP_VLAN_MEM_CONFIG_MSG; + +namespace swss { + +class StpMgr : public Orch +{ +public: + StpMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, + const std::vector &tables); + + using Orch::doTask; + void ipcInitStpd(); + int sendMsgStpd(STP_MSG_TYPE msgType, uint32_t msgLen, void *data); + MacAddress macAddress; + bool isPortInitDone(DBConnector *app_db); + uint16_t getStpMaxInstances(void); + +private: + Table m_cfgStpGlobalTable; + Table m_cfgStpVlanTable; + Table m_cfgStpVlanPortTable; + Table m_cfgStpPortTable; + Table m_cfgLagMemberTable; + Table m_cfgVlanMemberTable; + Table m_stateVlanTable; + Table m_stateVlanMemberTable; + Table m_stateLagTable; + Table m_stateStpTable; + + std::bitset l2InstPool; + int stpd_fd; + enum L2_PROTO_MODE l2ProtoEnabled; + int m_vlanInstMap[MAX_VLANS]; + bool portCfgDone; + uint16_t max_stp_instances; + std::map m_lagMap; + + bool stpGlobalTask; + bool stpVlanTask; + bool stpVlanPortTask; + bool stpPortTask; + + void doTask(Consumer &consumer); + void doStpGlobalTask(Consumer &consumer); + void doStpVlanTask(Consumer &consumer); + void doStpVlanPortTask(Consumer &consumer); + void doStpPortTask(Consumer &consumer); + void doVlanMemUpdateTask(Consumer &consumer); + void doLagMemUpdateTask(Consumer &consumer); + + bool isVlanStateOk(const std::string &alias); + bool isLagStateOk(const std::string &alias); + bool isStpPortEmpty(); + bool isStpEnabled(const std::string &intf_name); + int getAllVlanMem(const std::string &vlanKey, std::vector&port_list); + int getAllPortVlan(const std::string &intfKey, std::vector&vlan_list); + int8_t getVlanMemMode(const std::string &key); + int allocL2Instance(uint32_t vlan_id); + void deallocL2Instance(uint32_t vlan_id); + bool isLagEmpty(const std::string &key); + void processStpPortAttr(const std::string op, std::vector&tupEntry, const std::string intfName); + void processStpVlanPortAttr(const std::string op, uint32_t vlan_id, const std::string intfName, + std::vector&tupEntry); +}; + +} +#endif diff --git a/cfgmgr/stpmgrd.cpp b/cfgmgr/stpmgrd.cpp new file mode 100644 index 0000000000..bd20b2a3fc --- /dev/null +++ b/cfgmgr/stpmgrd.cpp @@ -0,0 +1,119 @@ +#include + +#include "stpmgr.h" +#include "netdispatcher.h" +#include "netlink.h" +#include "select.h" +#include "warm_restart.h" + +using namespace std; +using namespace swss; + +bool gSwssRecord = false; +bool gLogRotate = false; +ofstream gRecordOfs; +string gRecordFile; + +#define SELECT_TIMEOUT 1000 + +int main(int argc, char **argv) +{ + Logger::linkToDbNative("stpmgrd"); + SWSS_LOG_ENTER(); + + SWSS_LOG_NOTICE("--- Starting stpmgrd ---"); + + if (fopen("/stpmgrd_dbg_reload", "r")) + { + Logger::setMinPrio(Logger::SWSS_DEBUG); + } + + try + { + DBConnector conf_db(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector app_db(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + DBConnector state_db(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0); + + WarmStart::initialize("stpmgrd", "stpd"); + WarmStart::checkWarmStart("stpmgrd", "stpd"); + + // Config DB Tables + TableConnector conf_stp_global_table(&conf_db, CFG_STP_GLOBAL_TABLE_NAME); + TableConnector conf_stp_vlan_table(&conf_db, CFG_STP_VLAN_TABLE_NAME); + TableConnector conf_stp_vlan_port_table(&conf_db, CFG_STP_VLAN_PORT_TABLE_NAME); + TableConnector conf_stp_port_table(&conf_db, CFG_STP_PORT_TABLE_NAME); + + // VLAN DB Tables + TableConnector state_vlan_member_table(&state_db, STATE_VLAN_MEMBER_TABLE_NAME); + + // LAG Tables + TableConnector conf_lag_member_table(&conf_db, CFG_LAG_MEMBER_TABLE_NAME); + + vector tables = { + conf_stp_global_table, + conf_stp_vlan_table, + conf_stp_vlan_port_table, + conf_stp_port_table, + conf_lag_member_table, + state_vlan_member_table + }; + + + StpMgr stpmgr(&conf_db, &app_db, &state_db, tables); + + // Open a Unix Domain Socket with STPd for communication + stpmgr.ipcInitStpd(); + stpmgr.isPortInitDone(&app_db); + + // Get max STP instances from state DB and send to stpd + STP_INIT_READY_MSG msg; + memset(&msg, 0, sizeof(STP_INIT_READY_MSG)); + msg.max_stp_instances = stpmgr.getStpMaxInstances(); + stpmgr.sendMsgStpd(STP_INIT_READY, sizeof(msg), (void *)&msg); + + // Get Base MAC + Table table(&conf_db, "DEVICE_METADATA"); + std::vector ovalues; + table.get("localhost", ovalues); + auto it = std::find_if( ovalues.begin(), ovalues.end(), [](const FieldValueTuple& t){ return t.first == "mac";} ); + if ( it == ovalues.end() ) { + throw runtime_error("couldn't find MAC address of the device from config DB"); + } + stpmgr.macAddress = MacAddress(it->second); + + vector cfgOrchList = {&stpmgr}; + + Select s; + for (Orch *o: cfgOrchList) + { + s.addSelectables(o->getSelectables()); + } + + while (true) + { + Selectable *sel; + int ret; + + ret = s.select(&sel, SELECT_TIMEOUT); + if (ret == Select::ERROR) + { + SWSS_LOG_NOTICE("Error: %s!", strerror(errno)); + continue; + } + if (ret == Select::TIMEOUT) + { + stpmgr.doTask(); + continue; + } + + auto *c = (Executor *)sel; + c->execute(); + } + } + catch (const exception &e) + { + SWSS_LOG_ERROR("Runtime error: %s", e.what()); + } + + return -1; +} diff --git a/configure.ac b/configure.ac index 6edc02da91..e24f69887d 100644 --- a/configure.ac +++ b/configure.ac @@ -20,6 +20,11 @@ AC_CHECK_LIB([team], [team_alloc], PKG_CHECK_MODULES([JANSSON], [jansson]) +AC_CHECK_FILE([/usr/include/stp_ipc.h], + AM_CONDITIONAL(HAVE_STP, true), + [AC_MSG_WARN([stp is not installed.]) + AM_CONDITIONAL(HAVE_STP, false)]) + AC_CHECK_LIB([sai], [sai_object_type_query], AM_CONDITIONAL(HAVE_SAI, true), [AC_MSG_WARN([libsai is not installed.]) @@ -54,7 +59,7 @@ AC_CHECK_LIB([nl-genl-3], [nl_socket_get_cb]) AC_CHECK_LIB([nl-route-3], [rtnl_route_nh_get_encap_mpls_dst]) AC_CHECK_LIB([nl-nf-3], [nfnl_connect]) -CFLAGS_COMMON="-std=c++14 -Wall -fPIC -Wno-write-strings -I/usr/include/swss" +CFLAGS_COMMON="-std=c++14 -Wall -fPIC -Wno-write-strings -I/usr/include/swss -I/usr/include" AC_ARG_WITH(libnl-3.0-inc, [ --with-libnl-3.0-inc=DIR diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index becdb9db89..f94e6d3bfa 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -114,7 +114,8 @@ orchagent_SOURCES = \ dash/dashaclgroupmgr.cpp \ dash/dashtagmgr.cpp \ dash/pbutils.cpp \ - twamporch.cpp + twamporch.cpp \ + stporch.cpp orchagent_SOURCES += flex_counter/flex_counter_manager.cpp flex_counter/flex_counter_stat_manager.cpp flex_counter/flow_counter_handler.cpp flex_counter/flowcounterrouteorch.cpp orchagent_SOURCES += debug_counter/debug_counter.cpp debug_counter/drop_counter.cpp diff --git a/orchagent/fdborch.cpp b/orchagent/fdborch.cpp index 03c854fee3..5dbaf291f8 100644 --- a/orchagent/fdborch.cpp +++ b/orchagent/fdborch.cpp @@ -1138,6 +1138,35 @@ void FdbOrch::flushFDBEntries(sai_object_id_t bridge_port_oid, } } } +void FdbOrch::flushFdbByVlan(const string &alias) +{ + sai_status_t status; + swss::Port vlan; + sai_attribute_t vlan_attr[2]; + + if (!m_portsOrch->getPort(alias, vlan)) + { + return; + } + + vlan_attr[0].id = SAI_FDB_FLUSH_ATTR_BV_ID; + vlan_attr[0].value.oid = vlan.m_vlan_info.vlan_oid; + vlan_attr[1].id = SAI_FDB_FLUSH_ATTR_ENTRY_TYPE; + vlan_attr[1].value.s32 = SAI_FDB_FLUSH_ENTRY_TYPE_DYNAMIC; + status = sai_fdb_api->flush_fdb_entries(gSwitchId, 2, vlan_attr); + + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Flush fdb failed, return code %x", status); + } + else + { + SWSS_LOG_INFO("Flush by vlan %s vlan_oid 0x%" PRIx64 "", + alias.c_str(), vlan.m_vlan_info.vlan_oid); + } + + return; +} void FdbOrch::notifyObserversFDBFlush(Port &port, sai_object_id_t& bvid) { diff --git a/orchagent/fdborch.h b/orchagent/fdborch.h index 9e71bc8c6b..a5a5fcd0e0 100644 --- a/orchagent/fdborch.h +++ b/orchagent/fdborch.h @@ -102,6 +102,7 @@ class FdbOrch: public Orch, public Subject, public Observer static const int fdborch_pri; void flushFDBEntries(sai_object_id_t bridge_port_oid, sai_object_id_t vlan_oid); + void flushFdbByVlan(const string &); void notifyObserversFDBFlush(Port &p, sai_object_id_t&); private: diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 30d7df8e72..13ef89c487 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -11,6 +11,7 @@ #define SAI_SWITCH_ATTR_CUSTOM_RANGE_BASE SAI_SWITCH_ATTR_CUSTOM_RANGE_START #include "sairedis.h" #include "chassisorch.h" +#include "stporch.h" using namespace std; using namespace swss; @@ -64,6 +65,7 @@ FlowCounterRouteOrch *gFlowCounterRouteOrch; DebugCounterOrch *gDebugCounterOrch; MonitorOrch *gMonitorOrch; TunnelDecapOrch *gTunneldecapOrch; +StpOrch *gStpOrch; MuxOrch *gMuxOrch; bool gIsNatSupported = false; @@ -167,6 +169,14 @@ bool OrchDaemon::init() gFlowCounterRouteOrch = new FlowCounterRouteOrch(m_configDb, route_pattern_tables); gDirectory.set(gFlowCounterRouteOrch); + vector stp_tables = { + APP_STP_VLAN_INSTANCE_TABLE_NAME, + APP_STP_PORT_STATE_TABLE_NAME, + APP_STP_FASTAGEING_FLUSH_TABLE_NAME + }; + gStpOrch = new StpOrch(m_applDb, m_stateDb, stp_tables); + gDirectory.set(gStpOrch); + vector vnet_tables = { APP_VNET_RT_TABLE_NAME, APP_VNET_RT_TUNNEL_TABLE_NAME @@ -420,7 +430,7 @@ bool OrchDaemon::init() * when iterating ConsumerMap. This is ensured implicitly by the order of keys in ordered map. * For cases when Orch has to process tables in specific order, like PortsOrch during warm start, it has to override Orch::doTask() */ - m_orchList = { gSwitchOrch, gCrmOrch, gPortsOrch, gBufferOrch, gFlowCounterRouteOrch, gIntfsOrch, gNeighOrch, gNhgMapOrch, gNhgOrch, gCbfNhgOrch, gRouteOrch, gCoppOrch, gQosOrch, wm_orch, gPolicerOrch, gTunneldecapOrch, sflow_orch, gDebugCounterOrch, gMacsecOrch, bgp_global_state_orch, gBfdOrch, gSrv6Orch, gMuxOrch, mux_cb_orch, gMonitorOrch}; + m_orchList = { gSwitchOrch, gCrmOrch, gPortsOrch, gBufferOrch, gFlowCounterRouteOrch, gIntfsOrch, gNeighOrch, gNhgMapOrch, gNhgOrch, gCbfNhgOrch, gRouteOrch, gCoppOrch, gQosOrch, wm_orch, gPolicerOrch, gTunneldecapOrch, sflow_orch, gDebugCounterOrch, gMacsecOrch, bgp_global_state_orch, gBfdOrch, gSrv6Orch, gMuxOrch, mux_cb_orch, gMonitorOrch, gStpOrch}; bool initialize_dtel = false; if (platform == BFN_PLATFORM_SUBSTRING || platform == VS_PLATFORM_SUBSTRING) diff --git a/orchagent/orchdaemon.h b/orchagent/orchdaemon.h index 2473848bf5..6a1c0b999a 100644 --- a/orchagent/orchdaemon.h +++ b/orchagent/orchdaemon.h @@ -47,6 +47,7 @@ #include "srv6orch.h" #include "nvgreorch.h" #include "twamporch.h" +#include "stporch.h" #include "dash/dashaclorch.h" #include "dash/dashorch.h" #include "dash/dashrouteorch.h" diff --git a/orchagent/p4orch/tests/mock_sai_stp.h b/orchagent/p4orch/tests/mock_sai_stp.h new file mode 100644 index 0000000000..5ac6397c9c --- /dev/null +++ b/orchagent/p4orch/tests/mock_sai_stp.h @@ -0,0 +1,110 @@ +#ifndef MOCK_SAI_STP_H +#define MOCK_SAI_STP_H + +#include +extern "C" +{ +#include "sai.h" +} + +// Mock class for SAI STP APIs +class MockSaiStp { +public: + // Mock method for creating an STP instance + MOCK_METHOD4(create_stp, + sai_status_t(_Out_ sai_object_id_t *stp_instance_id, + _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list)); + + // Mock method for removing an STP instance + MOCK_METHOD1(remove_stp, sai_status_t(_In_ sai_object_id_t stp_instance_id)); + + // Mock method for setting STP instance attributes + MOCK_METHOD2(set_stp_attribute, + sai_status_t(_In_ sai_object_id_t stp_instance_id, + _In_ const sai_attribute_t *attr)); + + // Mock method for getting STP instance attributes + MOCK_METHOD3(get_stp_attribute, + sai_status_t(_Out_ sai_object_id_t stp_instance_id, + _In_ uint32_t attr_count, + _In_ sai_attribute_t *attr_list)); + + // Mock method for creating an STP port + MOCK_METHOD4(create_stp_port, + sai_status_t(_Out_ sai_object_id_t *stp_port_id, + _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list)); + + // Mock method for removing an STP port + MOCK_METHOD1(remove_stp_port, + sai_status_t(_In_ sai_object_id_t stp_port_id)); + + // Mock method for setting STP port attributes + MOCK_METHOD2(set_stp_port_attribute, + sai_status_t(_Out_ sai_object_id_t stp_port_id, + _In_ const sai_attribute_t *attr)); + + // Mock method for getting STP port attributes + MOCK_METHOD3(get_stp_port_attribute, + sai_status_t(_Out_ sai_object_id_t stp_port_id, + _In_ uint32_t attr_count, + _In_ sai_attribute_t *attr_list)); +}; + +// Global mock object for SAI STP APIs +MockSaiStp *mock_sai_stp; + +sai_status_t mock_create_stp(_Out_ sai_object_id_t *stp_instance_id, + _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_stp->create_stp(stp_instance_id, switch_id, attr_count, attr_list); +} + +sai_status_t mock_remove_stp(_In_ sai_object_id_t stp_instance_id) +{ + return mock_sai_stp->remove_stp(stp_instance_id); +} + +sai_status_t mock_set_stp_attribute(_In_ sai_object_id_t stp_instance_id, _In_ const sai_attribute_t *attr) +{ + return mock_sai_stp->set_stp_attribute(stp_instance_id, attr); +} + +sai_status_t mock_get_stp_attribute(_Out_ sai_object_id_t stp_instance_id, + _In_ uint32_t attr_count, _Inout_ sai_attribute_t *attr_list) +{ + return mock_sai_stp->get_stp_attribute(stp_instance_id, attr_count, attr_list); +} +sai_status_t mock_create_stp_port(_Out_ sai_object_id_t *stp_port_id, + _In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, + _In_ const sai_attribute_t *attr_list) +{ + return mock_sai_stp->create_stp_port(stp_port_id, switch_id,attr_count, attr_list); +} + +sai_status_t mock_remove_stp_port(_In_ sai_object_id_t stp_port_id) +{ + return mock_sai_stp->remove_stp_port(stp_port_id); +} + +sai_status_t mock_set_stp_port_attribute(_In_ sai_object_id_t stp_port_id, + _In_ const sai_attribute_t *attr) +{ + return mock_sai_stp->set_stp_port_attribute(stp_port_id, attr); +} + +sai_status_t mock_get_stp_port_attribute(_Out_ sai_object_id_t stp_port_id, + _In_ uint32_t attr_count, + _Inout_ sai_attribute_t *attr_list) +{ + return mock_sai_stp->get_stp_port_attribute(stp_port_id, attr_count, attr_list); +} + +#endif // MOCK_SAI_STP_H + diff --git a/orchagent/port.h b/orchagent/port.h index 1c7c34999f..946b562aec 100644 --- a/orchagent/port.h +++ b/orchagent/port.h @@ -76,6 +76,7 @@ struct SystemLagInfo int32_t spa_id = 0; }; +typedef std::map stp_port_ids_t; class PortOperErrorEvent { public: @@ -234,6 +235,8 @@ class Port sai_object_id_t m_system_side_id = 0; sai_object_id_t m_line_side_id = 0; + stp_port_ids_t m_stp_port_ids; //STP Port object ids for each STP instance + sai_int16_t m_stp_id = -1; //STP instance for the VLAN /* Port oper error status to event map*/ std::unordered_map m_portOperErrorToEvent; diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index ac3524d006..4a96d74473 100644 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -7,6 +7,7 @@ #include "directory.h" #include "subintf.h" #include "notifications.h" +#include "stporch.h" #include #include @@ -55,6 +56,7 @@ extern CrmOrch *gCrmOrch; extern BufferOrch *gBufferOrch; extern FdbOrch *gFdbOrch; extern SwitchOrch *gSwitchOrch; +extern StpOrch *gStpOrch; extern Directory gDirectory; extern sai_system_port_api_t *sai_system_port_api; extern string gMySwitchType; @@ -6245,6 +6247,9 @@ bool PortsOrch::removeBridgePort(Port &port) hostif_vlan_tag[SAI_HOSTIF_VLAN_TAG_STRIP], port.m_alias.c_str()); return false; } + + /* Remove STP ports before bridge port deletion*/ + gStpOrch->removeStpPorts(port); //Flush the FDB entires corresponding to the port gFdbOrch->flushFDBEntries(port.m_bridge_port_id, SAI_NULL_OBJECT_ID); @@ -6387,6 +6392,12 @@ bool PortsOrch::removeVlan(Port vlan) return false; } + /* If STP instance is associated with VLAN remove VLAN from STP before deletion */ + if(vlan.m_stp_id != -1) + { + gStpOrch->removeVlanFromStpInstance(vlan.m_alias, 0); + } + sai_status_t status = sai_vlan_api->remove_vlan(vlan.m_vlan_info.vlan_oid); if (status != SAI_STATUS_SUCCESS) { diff --git a/orchagent/saihelper.cpp b/orchagent/saihelper.cpp index 27ac9463cb..e7cf7fb018 100644 --- a/orchagent/saihelper.cpp +++ b/orchagent/saihelper.cpp @@ -86,6 +86,7 @@ sai_dash_vip_api_t* sai_dash_vip_api; sai_dash_direction_lookup_api_t* sai_dash_direction_lookup_api; sai_twamp_api_t* sai_twamp_api; sai_tam_api_t* sai_tam_api; +sai_stp_api_t* sai_stp_api; extern sai_object_id_t gSwitchId; extern bool gTraditionalFlexCounter; @@ -234,6 +235,7 @@ void initSaiApi() sai_api_query((sai_api_t)SAI_API_DASH_DIRECTION_LOOKUP, (void**)&sai_dash_direction_lookup_api); sai_api_query(SAI_API_TWAMP, (void **)&sai_twamp_api); sai_api_query(SAI_API_TAM, (void **)&sai_tam_api); + sai_api_query(SAI_API_STP, (void **)&sai_stp_api); sai_log_set(SAI_API_SWITCH, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_BRIDGE, SAI_LOG_LEVEL_NOTICE); @@ -275,6 +277,7 @@ void initSaiApi() sai_log_set(SAI_API_GENERIC_PROGRAMMABLE, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_TWAMP, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_TAM, SAI_LOG_LEVEL_NOTICE); + sai_log_set(SAI_API_STP, SAI_LOG_LEVEL_NOTICE); } void initFlexCounterTables() diff --git a/orchagent/stporch.cpp b/orchagent/stporch.cpp new file mode 100644 index 0000000000..38f502f321 --- /dev/null +++ b/orchagent/stporch.cpp @@ -0,0 +1,514 @@ +#include +#include "portsorch.h" +#include "logger.h" +#include "fdborch.h" +#include "stporch.h" + +extern sai_stp_api_t *sai_stp_api; +extern sai_vlan_api_t *sai_vlan_api; +extern sai_switch_api_t *sai_switch_api; + +extern FdbOrch *gFdbOrch; +extern PortsOrch *gPortsOrch; + +extern sai_object_id_t gSwitchId; + +StpOrch::StpOrch(DBConnector * db, DBConnector * stateDb, vector &tableNames) : + Orch(db, tableNames) +{ + SWSS_LOG_ENTER(); + + sai_attribute_t attr; + sai_status_t status; + + m_stpTable = unique_ptr(new Table(stateDb, STATE_STP_TABLE_NAME)); + + vector attrs; + attr.id = SAI_SWITCH_ATTR_DEFAULT_STP_INST_ID; + attrs.push_back(attr); + + status = sai_switch_api->get_switch_attribute(gSwitchId, (uint32_t)attrs.size(), attrs.data()); + if (status != SAI_STATUS_SUCCESS) + { + throw runtime_error("StpOrch initialization failure"); + } + + m_defaultStpId = attrs[0].value.oid; +}; + + +sai_object_id_t StpOrch::getStpInstanceOid(sai_uint16_t stp_instance) +{ + std::map::iterator it; + + it = m_stpInstToOid.find(stp_instance); + if (it == m_stpInstToOid.end()) + { + return SAI_NULL_OBJECT_ID; + } + + return it->second; +} + +sai_object_id_t StpOrch::addStpInstance(sai_uint16_t stp_instance) +{ + sai_object_id_t stp_oid; + sai_attribute_t attr; + + attr.id = 0; + attr.value.u32 = 0; + + sai_status_t status = sai_stp_api->create_stp(&stp_oid, gSwitchId, 0, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create STP instance %u status %u", stp_instance, status); + return SAI_NULL_OBJECT_ID; + } + + m_stpInstToOid[stp_instance] = stp_oid; + SWSS_LOG_INFO("Added STP instance:%hu oid:%" PRIx64 "", stp_instance, stp_oid); + return stp_oid; +} + +bool StpOrch::removeStpInstance(sai_uint16_t stp_instance) +{ + sai_object_id_t stp_oid; + + stp_oid = getStpInstanceOid(stp_instance); + if (stp_oid == SAI_NULL_OBJECT_ID) + { + return false; + } + + /* Remove all STP ports before deleting the STP instance */ + auto portList = gPortsOrch->getAllPorts(); + for (auto &it: portList) + { + auto &port = it.second; + if (port.m_type == Port::PHY || port.m_type == Port::LAG) + { + if(port.m_stp_port_ids.find(stp_instance) == port.m_stp_port_ids.end()) + continue; + + removeStpPort(port, stp_instance); + } + } + + sai_status_t status = sai_stp_api->remove_stp(stp_oid); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove STP instance %u oid %" PRIx64 "status %u", stp_instance, stp_oid, status); + return false; + } + + m_stpInstToOid.erase(stp_instance); + SWSS_LOG_INFO("Removed STP instance:%hu oid:%" PRIx64 "", stp_instance, stp_oid); + return true; +} + +bool StpOrch::addVlanToStpInstance(string vlan_alias, sai_uint16_t stp_instance) +{ + SWSS_LOG_ENTER(); + + Port vlan; + sai_object_id_t stp_oid; + sai_attribute_t attr; + + if (!gPortsOrch->getPort(vlan_alias, vlan)) + { + return false; + } + + stp_oid = getStpInstanceOid(stp_instance); + if (stp_oid == SAI_NULL_OBJECT_ID) + { + stp_oid = addStpInstance(stp_instance); + if(stp_oid == SAI_NULL_OBJECT_ID) + return false; + } + + attr.id = SAI_VLAN_ATTR_STP_INSTANCE; + attr.value.oid = stp_oid; + + sai_status_t status = sai_vlan_api->set_vlan_attribute(vlan.m_vlan_info.vlan_oid, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to add VLAN %s to STP instance:%hu status %u", vlan_alias.c_str(), stp_instance, status); + return false; + } + + vlan.m_stp_id = stp_instance; + gPortsOrch->setPort(vlan_alias, vlan); + SWSS_LOG_INFO("Add VLAN %s to STP instance:%hu m_stp_id:%d", vlan_alias.c_str(), stp_instance, vlan.m_stp_id); + return true; +} + +bool StpOrch::removeVlanFromStpInstance(string vlan_alias, sai_uint16_t stp_instance) +{ + SWSS_LOG_ENTER(); + + Port vlan; + sai_attribute_t attr; + + if (!gPortsOrch->getPort(vlan_alias, vlan)) + { + return false; + } + + attr.id = SAI_VLAN_ATTR_STP_INSTANCE; + attr.value.oid = m_defaultStpId; + + sai_status_t status = sai_vlan_api->set_vlan_attribute(vlan.m_vlan_info.vlan_oid, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to add VLAN %s to STP instance:%d status %u", vlan_alias.c_str(), vlan.m_stp_id, status); + return false; + } + + SWSS_LOG_INFO("Remove %s from instance:%d add instance:%" PRIx64 "", vlan_alias.c_str(), vlan.m_stp_id, m_defaultStpId); + + removeStpInstance(vlan.m_stp_id); + vlan.m_stp_id = -1; + gPortsOrch->setPort(vlan_alias, vlan); + return true; +} + +/* If STP Port exists return else create a new STP Port */ +sai_object_id_t StpOrch::addStpPort(Port &port, sai_uint16_t stp_instance) +{ + sai_object_id_t stp_port_id = SAI_NULL_OBJECT_ID; + sai_object_id_t stp_id = SAI_NULL_OBJECT_ID; + sai_attribute_t attr[3]; + + if(port.m_stp_port_ids.find(stp_instance) != port.m_stp_port_ids.end()) + { + return port.m_stp_port_ids[stp_instance]; + } + + if(port.m_bridge_port_id == SAI_NULL_OBJECT_ID) + { + gPortsOrch->addBridgePort(port); + + if(port.m_bridge_port_id == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_ERROR("Failed to add STP port %s invalid bridge port id STP instance %d", port.m_alias.c_str(), stp_instance); + return SAI_NULL_OBJECT_ID; + } + } + attr[0].id = SAI_STP_PORT_ATTR_BRIDGE_PORT; + attr[0].value.oid = port.m_bridge_port_id; + + stp_id = getStpInstanceOid(stp_instance); + if(stp_id == SAI_NULL_OBJECT_ID) + { + stp_id = addStpInstance(stp_instance); + if(stp_id == SAI_NULL_OBJECT_ID) + { + return SAI_NULL_OBJECT_ID; + } + } + + attr[1].id = SAI_STP_PORT_ATTR_STP; + attr[1].value.oid = stp_id; + + attr[2].id = SAI_STP_PORT_ATTR_STATE; + attr[2].value.s32 = SAI_STP_PORT_STATE_BLOCKING; + + sai_status_t status = sai_stp_api->create_stp_port(&stp_port_id, gSwitchId, 3, attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to add STP port %s instance %d status %u", port.m_alias.c_str(), stp_instance, status); + return SAI_NULL_OBJECT_ID; + } + + SWSS_LOG_INFO("Add STP port %s instance %d oid %" PRIx64 " size %zu", port.m_alias.c_str(), stp_instance, stp_port_id, port.m_stp_port_ids.size()); + port.m_stp_port_ids[stp_instance] = stp_port_id; + gPortsOrch->setPort(port.m_alias, port); + return stp_port_id; +} + +bool StpOrch::removeStpPort(Port &port, sai_uint16_t stp_instance) +{ + if(port.m_stp_port_ids.find(stp_instance) == port.m_stp_port_ids.end()) + { + /* Deletion could have already happened as part of other flows, so ignore this msg*/ + return true; + } + + sai_status_t status = sai_stp_api->remove_stp_port(port.m_stp_port_ids[stp_instance]); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove STP port %s instance %d oid %" PRIx64 " status %x", port.m_alias.c_str(), stp_instance, + port.m_stp_port_ids[stp_instance], status); + return false; + } + + SWSS_LOG_INFO("Remove STP port %s instance %d oid %" PRIx64 " size %zu", port.m_alias.c_str(), stp_instance, + port.m_stp_port_ids[stp_instance], port.m_stp_port_ids.size()); + port.m_stp_port_ids.erase(stp_instance); + gPortsOrch->setPort(port.m_alias, port); + return true; +} + +bool StpOrch::removeStpPorts(Port &port) +{ + if(port.m_stp_port_ids.empty()) + return true; + + for(auto stp_port_id: port.m_stp_port_ids) + { + uint16_t stp_instance = stp_port_id.first; + sai_object_id_t stp_port_oid = stp_port_id.second; + + if(stp_port_oid == SAI_NULL_OBJECT_ID) + { + continue; + } + + sai_status_t status = sai_stp_api->remove_stp_port(stp_port_oid); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove STP port %s instance %d oid %" PRIx64 " status %x", port.m_alias.c_str(), stp_instance, stp_port_oid, status); + } + else + { + SWSS_LOG_INFO("Remove STP port %s instance %d oid %" PRIx64 "", port.m_alias.c_str(), stp_instance, stp_port_oid); + } + } + + port.m_stp_port_ids.clear(); + gPortsOrch->setPort(port.m_alias, port); + return true; +} + +sai_stp_port_state_t StpOrch::getStpSaiState(sai_uint8_t stp_state) +{ + sai_stp_port_state_t state = SAI_STP_PORT_STATE_BLOCKING; + + switch(stp_state) + { + case STP_STATE_DISABLED: + case STP_STATE_BLOCKING: + case STP_STATE_LISTENING: + state = SAI_STP_PORT_STATE_BLOCKING; + break; + + case STP_STATE_LEARNING: + state = SAI_STP_PORT_STATE_LEARNING; + break; + + case STP_STATE_FORWARDING: + state = SAI_STP_PORT_STATE_FORWARDING; + break; + } + return state; +} + +bool StpOrch::updateStpPortState(Port &port, sai_uint16_t stp_instance, sai_uint8_t stp_state) +{ + sai_attribute_t attr[1]; + sai_object_id_t stp_port_oid; + + stp_port_oid = addStpPort(port, stp_instance); + if (stp_port_oid == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_ERROR("Failed to get STP port oid port %s instance %d state %d ", port.m_alias.c_str(), stp_instance, stp_state); + return true; + } + attr[0].id = SAI_STP_PORT_ATTR_STATE; + attr[0].value.u32 = getStpSaiState(stp_state); + + sai_status_t status = sai_stp_api->set_stp_port_attribute(stp_port_oid, attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to set STP port state %s instance %d state %d status %x", port.m_alias.c_str(), stp_instance, stp_state, status); + return false; + } + + SWSS_LOG_INFO("Set STP port state %s instance %d state %d ", port.m_alias.c_str(), stp_instance, stp_state); + + return true; +} + +bool StpOrch::stpVlanFdbFlush(string vlan_alias) +{ + SWSS_LOG_ENTER(); + + Port vlan; + + if (!gPortsOrch->getPort(vlan_alias, vlan)) + { + return false; + } + + gFdbOrch->flushFdbByVlan(vlan_alias); + + SWSS_LOG_INFO("Set STP FDB flush vlan %s ", vlan_alias.c_str()); + return true; +} + +void StpOrch::doStpTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + auto &t = it->second; + + string vlan_alias = kfvKey(t); + string op = kfvOp(t); + + if (op == SET_COMMAND) + { + uint16_t instance = STP_INVALID_INSTANCE; + + for (auto i : kfvFieldsValues(t)) + { + if (fvField(i) == "stp_instance") + { + instance = (uint16_t)std::stoi(fvValue(i)); + } + } + + if(instance == STP_INVALID_INSTANCE) + { + SWSS_LOG_ERROR("No instance found for VLAN %s", vlan_alias.c_str()); + } + else + { + if(!addVlanToStpInstance(vlan_alias, instance)) + { + it++; + continue; + } + } + } + else if (op == DEL_COMMAND) + { + if(!removeVlanFromStpInstance(vlan_alias, 0)) + { + it++; + continue; + } + } + it = consumer.m_toSync.erase(it); + } +} + +void StpOrch::doStpPortStateTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + auto &t = it->second; + string key = kfvKey(t); + size_t found = key.find(':'); + /* Return if the format of key is wrong */ + if (found == string::npos) + { + return; + } + string port_alias = key.substr(0, found); + string stp_instance = key.substr(found+1); + uint16_t instance = (uint16_t)std::stoi(stp_instance); + Port port; + + if (!gPortsOrch->getPort(port_alias, port)) + { + return; + } + + string op = kfvOp(t); + + if (op == SET_COMMAND) + { + uint8_t state = STP_STATE_INVALID; + + for (auto i : kfvFieldsValues(t)) + { + if (fvField(i) == "state") + { + state = (uint8_t)std::stoi(fvValue(i)); + } + } + if(state != STP_STATE_INVALID) + { + if(!updateStpPortState(port, instance, state)) + { + it++; + continue; + } + } + } + else if (op == DEL_COMMAND) + { + if(!removeStpPort(port, instance)) + { + it++; + continue; + } + } + it = consumer.m_toSync.erase(it); + } +} + +void StpOrch::doStpFastageTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + for (auto it = consumer.m_toSync.begin(); it != consumer.m_toSync.end(); ) + { + auto &t = it->second; + string op = kfvOp(t); + string vlan_alias = kfvKey(t); + + if (op == SET_COMMAND) + { + string state; + for (auto i : kfvFieldsValues(t)) + { + if (fvField(i) == "state") + state = fvValue(i); + } + + if(state.compare("true") == 0) + { + stpVlanFdbFlush(vlan_alias); + } + } + else if (op == DEL_COMMAND) + { + // no operation + } + + it = consumer.m_toSync.erase(it); + } +} + +void StpOrch::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + if (!gPortsOrch->allPortsReady()) + { + return; + } + + string table_name = consumer.getTableName(); + if (table_name == APP_STP_VLAN_INSTANCE_TABLE_NAME) + { + doStpTask(consumer); + } + else if (table_name == APP_STP_PORT_STATE_TABLE_NAME) + { + doStpPortStateTask(consumer); + } + else if (table_name == APP_STP_FASTAGEING_FLUSH_TABLE_NAME) + { + doStpFastageTask(consumer); + } +} + diff --git a/orchagent/stporch.h b/orchagent/stporch.h new file mode 100644 index 0000000000..154ac7695b --- /dev/null +++ b/orchagent/stporch.h @@ -0,0 +1,53 @@ +#ifndef SWSS_STPORCH_H +#define SWSS_STPORCH_H + +#include +#include +#include "orch.h" + +#define STP_INVALID_INSTANCE 0xFFFF + +typedef enum _stp_state +{ + STP_STATE_DISABLED = 0, + STP_STATE_BLOCKING = 1, + STP_STATE_LISTENING = 2, + STP_STATE_LEARNING = 3, + STP_STATE_FORWARDING = 4, + STP_STATE_INVALID = 5 +}stp_state; + + +class StpOrch : public Orch +{ +public: + StpOrch(DBConnector *db, DBConnector *stateDb, vector &tableNames); + bool stpVlanFdbFlush(string vlan_alias); + bool updateMaxStpInstance(uint32_t max_stp_instance); + bool removeStpPorts(Port &port); + bool removeVlanFromStpInstance(string vlan, sai_uint16_t stp_instance); + +private: + unique_ptr
m_stpTable; + std::map m_stpInstToOid;//Mapping from STP instance id to corresponding object id + sai_object_id_t m_defaultStpId; + + void doStpTask(Consumer &consumer); + void doStpPortStateTask(Consumer &consumer); + void doStpFastageTask(Consumer &consumer); + void doStpVlanIntfFlushTask(Consumer &consumer); + + sai_object_id_t addStpInstance(sai_uint16_t stp_instance); + bool removeStpInstance(sai_uint16_t stp_instance); + bool addVlanToStpInstance(string vlan, sai_uint16_t stp_instance); + sai_object_id_t getStpInstanceOid(sai_uint16_t stp_instance); + + sai_object_id_t addStpPort(Port &port, sai_uint16_t stp_instance); + bool removeStpPort(Port &port, sai_uint16_t stp_instance); + sai_stp_port_state_t getStpSaiState(sai_uint8_t stp_state); + bool updateStpPortState(Port &port, sai_uint16_t stp_instance, sai_uint8_t stp_state); + + void doTask(Consumer& consumer); +}; +#endif /* SWSS_STPORCH_H */ + diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 126f4e88c5..72f0fdf6ee 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -63,6 +63,7 @@ tests_SOURCES = aclorch_ut.cpp \ neighorch_ut.cpp \ dashorch_ut.cpp \ twamporch_ut.cpp \ + stporch_ut.cpp \ flexcounter_ut.cpp \ mock_orch_test.cpp \ $(top_srcdir)/warmrestart/warmRestartHelper.cpp \ @@ -139,7 +140,8 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/warmrestart/warmRestartAssist.cpp \ $(top_srcdir)/orchagent/dash/pbutils.cpp \ $(top_srcdir)/cfgmgr/coppmgr.cpp \ - $(top_srcdir)/orchagent/twamporch.cpp + $(top_srcdir)/orchagent/twamporch.cpp \ + $(top_srcdir)/orchagent/stporch.cpp tests_SOURCES += $(FLEX_CTR_DIR)/flex_counter_manager.cpp $(FLEX_CTR_DIR)/flex_counter_stat_manager.cpp $(FLEX_CTR_DIR)/flow_counter_handler.cpp $(FLEX_CTR_DIR)/flowcounterrouteorch.cpp tests_SOURCES += $(DEBUG_CTR_DIR)/debug_counter.cpp $(DEBUG_CTR_DIR)/drop_counter.cpp diff --git a/tests/mock_tests/mock_orchagent_main.h b/tests/mock_tests/mock_orchagent_main.h index f2469a09ef..5ffda0dd41 100644 --- a/tests/mock_tests/mock_orchagent_main.h +++ b/tests/mock_tests/mock_orchagent_main.h @@ -29,6 +29,9 @@ #include "nhgorch.h" #include "copporch.h" #include "twamporch.h" +#define private public +#include "stporch.h" +#undef private #include "directory.h" extern int gBatchSize; @@ -60,6 +63,7 @@ extern BfdOrch *gBfdOrch; extern AclOrch *gAclOrch; extern PolicerOrch *gPolicerOrch; extern TunnelDecapOrch *gTunneldecapOrch; +extern StpOrch *gStpOrch; extern Directory gDirectory; extern sai_acl_api_t *sai_acl_api; @@ -94,3 +98,4 @@ extern sai_tam_api_t* sai_tam_api; extern sai_dash_vip_api_t* sai_dash_vip_api; extern sai_dash_direction_lookup_api_t* sai_dash_direction_lookup_api; extern sai_dash_eni_api_t* sai_dash_eni_api; +extern sai_stp_api_t* sai_stp_api; diff --git a/tests/mock_tests/stporch_ut.cpp b/tests/mock_tests/stporch_ut.cpp new file mode 100644 index 0000000000..847e02b304 --- /dev/null +++ b/tests/mock_tests/stporch_ut.cpp @@ -0,0 +1,246 @@ +#include +#include + +#define private public // make Directory::m_values available to clean it. +#include "directory.h" +#undef private +#define protected public +#include "orch.h" +#undef protected +#include "ut_helper.h" +#include "dbconnector.h" +#include "mock_orchagent_main.h" +#include "mock_sai_api.h" +#include "mock_orch_test.h" +#include "mock_table.h" +#define private public +#include "stporch.h" +#undef private +#include "mock_sai_stp.h" + + +namespace stporch_test +{ + using namespace std; + using namespace swss; + using namespace mock_orch_test; + using ::testing::StrictMock; + + using ::testing::_; + using ::testing::Return; + + sai_status_t _ut_stub_sai_set_vlan_attribute(_In_ sai_object_id_t vlan_oid, + _In_ const sai_attribute_t *attr) + { + return SAI_STATUS_SUCCESS; + } + + sai_status_t _ut_stub_sai_flush_fdb_entries(_In_ sai_object_id_t switch_id, + _In_ uint32_t attr_count, _In_ const sai_attribute_t *attr_list) + { + return SAI_STATUS_SUCCESS; + } + + class StpOrchTest : public MockOrchTest { + protected: + void ApplyInitialConfigs() + { + Table port_table = Table(m_app_db.get(), APP_PORT_TABLE_NAME); + Table vlan_table = Table(m_app_db.get(), APP_VLAN_TABLE_NAME); + Table vlan_member_table = Table(m_app_db.get(), APP_VLAN_MEMBER_TABLE_NAME); + + auto ports = ut_helper::getInitialSaiPorts(); + port_table.set(ETHERNET0, ports[ETHERNET0]); + port_table.set(ETHERNET4, ports[ETHERNET4]); + port_table.set(ETHERNET8, ports[ETHERNET8]); + port_table.set("PortConfigDone", { { "count", to_string(1) } }); + port_table.set("PortInitDone", { {} }); + + vlan_table.set(VLAN_1000, { { "admin_status", "up" }, + { "mtu", "9100" }, + { "mac", "00:aa:bb:cc:dd:ee" } }); + vlan_member_table.set( + VLAN_1000 + vlan_member_table.getTableNameSeparator() + ETHERNET0, + { { "tagging_mode", "untagged" } }); + + gPortsOrch->addExistingData(&port_table); + gPortsOrch->addExistingData(&vlan_table); + gPortsOrch->addExistingData(&vlan_member_table); + static_cast(gPortsOrch)->doTask(); + } + void PostSetUp() override + { + vector tableNames = + {"STP_TABLE", + "STP_VLAN_INSTANCE_TABLE", + "STP_PORT_STATE_TABLE", + "STP_FASTAGEING_FLUSH_TABLE"}; + gStpOrch = new StpOrch(m_app_db.get(), m_state_db.get(), tableNames); + } + void PreTearDown() override + { + delete gStpOrch; + gStpOrch = nullptr; + } + + sai_stp_api_t ut_sai_stp_api; + sai_stp_api_t *org_sai_stp_api; + + void _hook_sai_stp_api() + { + ut_sai_stp_api = *sai_stp_api; + org_sai_stp_api = sai_stp_api; + sai_stp_api = &ut_sai_stp_api; + } + + void _unhook_sai_stp_api() + { + sai_stp_api = org_sai_stp_api; + } + + sai_vlan_api_t ut_sai_vlan_api; + sai_vlan_api_t *org_sai_vlan_api; + + void _hook_sai_vlan_api() + { + ut_sai_vlan_api = *sai_vlan_api; + org_sai_vlan_api = sai_vlan_api; + ut_sai_vlan_api.set_vlan_attribute = _ut_stub_sai_set_vlan_attribute; + sai_vlan_api = &ut_sai_vlan_api; + } + + void _unhook_sai_vlan_api() + { + sai_vlan_api = org_sai_vlan_api; + } + + sai_fdb_api_t ut_sai_fdb_api; + sai_fdb_api_t *org_sai_fdb_api; + void _hook_sai_fdb_api() + { + ut_sai_fdb_api = *sai_fdb_api; + org_sai_fdb_api = sai_fdb_api; + ut_sai_fdb_api.flush_fdb_entries = _ut_stub_sai_flush_fdb_entries; + sai_fdb_api = &ut_sai_fdb_api; + } + + void _unhook_sai_fdb_api() + { + sai_fdb_api = org_sai_fdb_api; + } + }; + + TEST_F(StpOrchTest, TestAddRemoveStpPort) { + _hook_sai_stp_api(); + _hook_sai_vlan_api(); + _hook_sai_fdb_api(); + + StrictMock mock_sai_stp_; + mock_sai_stp = &mock_sai_stp_; + sai_stp_api->create_stp = mock_create_stp; + sai_stp_api->remove_stp = mock_remove_stp; + sai_stp_api->create_stp_port = mock_create_stp_port; + sai_stp_api->remove_stp_port = mock_remove_stp_port; + sai_stp_api->set_stp_port_attribute = mock_set_stp_port_attribute; + + Port port; + Port port1; + sai_uint16_t stp_instance = 1; + sai_object_id_t stp_port_oid = 67890; + sai_object_id_t stp_oid = 98765; + bool result; + + ASSERT_TRUE(gPortsOrch->getPort(ETHERNET0, port)); + ASSERT_TRUE(gPortsOrch->getPort(ETHERNET4, port1)); + + EXPECT_CALL(mock_sai_stp_, + create_stp(_, _, _, _)).WillOnce(::testing::DoAll(::testing::SetArgPointee<0>(stp_oid), + ::testing::Return(SAI_STATUS_SUCCESS))); + result = gStpOrch->addVlanToStpInstance(VLAN_1000, stp_instance); + ASSERT_TRUE(result); + + EXPECT_CALL(mock_sai_stp_, + create_stp_port(_, _, 3, _)).WillOnce(::testing::DoAll(::testing::SetArgPointee<0>(stp_port_oid), + ::testing::Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_stp_, + set_stp_port_attribute(_,_)).WillOnce(::testing::Return(SAI_STATUS_SUCCESS)); + port.m_bridge_port_id = 1234; + result = gStpOrch->updateStpPortState(port, stp_instance, STP_STATE_FORWARDING); + ASSERT_TRUE(result); + + result = gStpOrch->stpVlanFdbFlush(VLAN_1000); + ASSERT_TRUE(result); + + EXPECT_CALL(mock_sai_stp_, + remove_stp_port(_)).WillOnce(::testing::Return(SAI_STATUS_SUCCESS)); + result = gStpOrch->removeStpPort(port, stp_instance); + ASSERT_TRUE(result); + + EXPECT_CALL(mock_sai_stp_, + create_stp_port(_, _, 3, _)).WillOnce(::testing::DoAll(::testing::SetArgPointee<0>(stp_port_oid), + ::testing::Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_stp_, + set_stp_port_attribute(_,_)).WillOnce(::testing::Return(SAI_STATUS_SUCCESS)); + port1.m_bridge_port_id = 1111; + result = gStpOrch->updateStpPortState(port1, stp_instance, STP_STATE_BLOCKING); + ASSERT_TRUE(result); + + EXPECT_CALL(mock_sai_stp_, + remove_stp_port(_)).WillOnce(::testing::Return(SAI_STATUS_SUCCESS)); + result = gStpOrch->removeStpPorts(port1); + ASSERT_TRUE(result); + + EXPECT_CALL(mock_sai_stp_, + remove_stp(_)).WillOnce(::testing::Return(SAI_STATUS_SUCCESS)); + result = gStpOrch->removeVlanFromStpInstance(VLAN_1000, stp_instance); + ASSERT_TRUE(result); + + std::deque entries; + entries.push_back({"Vlan1000", "SET", { {"stp_instance", "1"}}}); + EXPECT_CALL(mock_sai_stp_, + create_stp(_, _, _, _)).WillOnce(::testing::DoAll(::testing::SetArgPointee<0>(stp_oid), + ::testing::Return(SAI_STATUS_SUCCESS))); + + auto consumer = dynamic_cast(gStpOrch->getExecutor("STP_VLAN_INSTANCE_TABLE")); + consumer->addToSync(entries); + static_cast(gStpOrch)->doTask(); + + entries.clear(); + EXPECT_CALL(mock_sai_stp_, + create_stp_port(_, _, 3, _)).WillOnce(::testing::DoAll(::testing::SetArgPointee<0>(stp_port_oid), + ::testing::Return(SAI_STATUS_SUCCESS))); + EXPECT_CALL(mock_sai_stp_, + set_stp_port_attribute(_,_)).WillOnce(::testing::Return(SAI_STATUS_SUCCESS)); + entries.push_back({"Ethernet0:1", "SET", { {"state", "4"}}}); + consumer = dynamic_cast(gStpOrch->getExecutor("STP_PORT_STATE_TABLE")); + consumer->addToSync(entries); + static_cast(gStpOrch)->doTask(); + + entries.clear(); + entries.push_back({"Ethernet0:1", "SET", { {"state", "true"}}}); + consumer = dynamic_cast(gStpOrch->getExecutor("STP_FASTAGEING_FLUSH_TABLE")); + consumer->addToSync(entries); + static_cast(gStpOrch)->doTask(); + + + entries.clear(); + entries.push_back({"Ethernet0:1", "DEL", { {} }}); + EXPECT_CALL(mock_sai_stp_, + remove_stp_port(_)).WillOnce(::testing::Return(SAI_STATUS_SUCCESS)); + consumer = dynamic_cast(gStpOrch->getExecutor("STP_PORT_STATE_TABLE")); + consumer->addToSync(entries); + static_cast(gStpOrch)->doTask(); + + entries.clear(); + entries.push_back({"Vlan1000", "DEL", { {} }}); + EXPECT_CALL(mock_sai_stp_, + remove_stp(_)).WillOnce(::testing::Return(SAI_STATUS_SUCCESS)); + consumer = dynamic_cast(gStpOrch->getExecutor("STP_VLAN_INSTANCE_TABLE")); + consumer->addToSync(entries); + static_cast(gStpOrch)->doTask(); + + _unhook_sai_stp_api(); + _unhook_sai_vlan_api(); + _unhook_sai_fdb_api(); + } +} \ No newline at end of file diff --git a/tests/mock_tests/ut_saihelper.cpp b/tests/mock_tests/ut_saihelper.cpp index 269f54f06b..4f7d7e714b 100644 --- a/tests/mock_tests/ut_saihelper.cpp +++ b/tests/mock_tests/ut_saihelper.cpp @@ -94,7 +94,7 @@ namespace ut_helper sai_api_query((sai_api_t)SAI_API_DASH_VIP, (void**)&sai_dash_vip_api); sai_api_query((sai_api_t)SAI_API_DASH_DIRECTION_LOOKUP, (void**)&sai_dash_direction_lookup_api); sai_api_query((sai_api_t)SAI_API_DASH_ENI, (void**)&sai_dash_eni_api); - + sai_api_query(SAI_API_STP, (void**)&sai_stp_api); return SAI_STATUS_SUCCESS; } @@ -128,6 +128,7 @@ namespace ut_helper sai_dash_vip_api = nullptr; sai_dash_direction_lookup_api = nullptr; sai_dash_eni_api = nullptr; + sai_stp_api = nullptr; return SAI_STATUS_SUCCESS; }