From 90612d2969ad21eecf43e15ef589f81f02d5f14e Mon Sep 17 00:00:00 2001 From: Sudharsan Dhamal Gopalarathnam Date: Tue, 10 Dec 2024 09:48:45 -0800 Subject: [PATCH 1/9] Fixing fpmsyncd to handle scaled nexthops (#3361) What I did Fixing fpmsyncd to handle scaled nexthops(e.g. 256 nexthops). In this scenario the max message len is increased to accommodate zebra message size. In addition initialized nlmsg_set_default_size to allow netlink to process the increased message size as well without which libnl would crash. Why I did it To support increased nexthops. --- fpmsyncd/fpm/fpm.h | 2 +- fpmsyncd/fpmsyncd.cpp | 1 + fpmsyncd/routesync.cpp | 13 +++++++- tests/mock_tests/fake_netlink.cpp | 33 ++++++++++++++++++++ tests/mock_tests/fpmsyncd/test_routesync.cpp | 17 ++++++++++ 5 files changed, 64 insertions(+), 2 deletions(-) diff --git a/fpmsyncd/fpm/fpm.h b/fpmsyncd/fpm/fpm.h index 8af9b30ae9..11f13b168a 100644 --- a/fpmsyncd/fpm/fpm.h +++ b/fpmsyncd/fpm/fpm.h @@ -92,7 +92,7 @@ /* * Largest message that can be sent to or received from the FPM. */ -#define FPM_MAX_MSG_LEN 4096 +#define FPM_MAX_MSG_LEN 16384 /* * Header that precedes each fpm message to/from the FPM. diff --git a/fpmsyncd/fpmsyncd.cpp b/fpmsyncd/fpmsyncd.cpp index c3479efc84..f01bebe203 100644 --- a/fpmsyncd/fpmsyncd.cpp +++ b/fpmsyncd/fpmsyncd.cpp @@ -98,6 +98,7 @@ int main(int argc, char **argv) NetDispatcher::getInstance().registerMessageHandler(RTM_DELLINK, &sync); rtnl_route_read_protocol_names(DefaultRtProtoPath); + nlmsg_set_default_size(FPM_MAX_MSG_LEN); std::string suppressionEnabledStr; deviceMetadataTable.hget("localhost", "suppress-fib-pending", suppressionEnabledStr); diff --git a/fpmsyncd/routesync.cpp b/fpmsyncd/routesync.cpp index 4d6aa708ab..cea63dc42f 100644 --- a/fpmsyncd/routesync.cpp +++ b/fpmsyncd/routesync.cpp @@ -2235,12 +2235,23 @@ bool RouteSync::sendOffloadReply(struct nlmsghdr* hdr) bool RouteSync::sendOffloadReply(struct rtnl_route* route_obj) { SWSS_LOG_ENTER(); + int ret = 0; nl_msg* msg{}; - rtnl_route_build_add_request(route_obj, NLM_F_CREATE, &msg); + ret = rtnl_route_build_add_request(route_obj, NLM_F_CREATE, &msg); + if (ret !=0) + { + SWSS_LOG_ERROR("Route build add returned %d", ret); + return false; + } auto nlMsg = makeUniqueWithDestructor(msg, nlmsg_free); + if (nlMsg.get() == NULL) + { + SWSS_LOG_ERROR("Error in allocation for sending offload reply"); + return false; + } return sendOffloadReply(nlmsg_hdr(nlMsg.get())); } diff --git a/tests/mock_tests/fake_netlink.cpp b/tests/mock_tests/fake_netlink.cpp index 2370e13129..54eb197493 100644 --- a/tests/mock_tests/fake_netlink.cpp +++ b/tests/mock_tests/fake_netlink.cpp @@ -1,5 +1,6 @@ #include #include +#include static rtnl_link* g_fakeLink = [](){ auto fakeLink = rtnl_link_alloc(); @@ -7,6 +8,8 @@ static rtnl_link* g_fakeLink = [](){ return fakeLink; }(); +extern int rt_build_ret; +extern bool nlmsg_alloc_ret; extern "C" { @@ -15,4 +18,34 @@ struct rtnl_link* rtnl_link_get_by_name(struct nl_cache *cache, const char *name return g_fakeLink; } +static int build_route_msg(struct rtnl_route *tmpl, int cmd, int flags, + struct nl_msg **result) +{ + struct nl_msg *msg; + int err; + if (!(msg = nlmsg_alloc_simple(cmd, flags))) + return -NLE_NOMEM; + if ((err = rtnl_route_build_msg(msg, tmpl)) < 0) { + nlmsg_free(msg); + return err; + } + *result = msg; + return 0; +} + +int rtnl_route_build_add_request(struct rtnl_route *tmpl, int flags, + struct nl_msg **result) +{ + if (rt_build_ret != 0) + { + return rt_build_ret; + } + else if (!nlmsg_alloc_ret) + { + *result = NULL; + return 0; + } + return build_route_msg(tmpl, RTM_NEWROUTE, NLM_F_CREATE | flags, + result); +} } diff --git a/tests/mock_tests/fpmsyncd/test_routesync.cpp b/tests/mock_tests/fpmsyncd/test_routesync.cpp index a8de78859f..b1c23aca85 100644 --- a/tests/mock_tests/fpmsyncd/test_routesync.cpp +++ b/tests/mock_tests/fpmsyncd/test_routesync.cpp @@ -12,6 +12,8 @@ using namespace swss; using ::testing::_; +int rt_build_ret = 0; +bool nlmsg_alloc_ret = true; class MockRouteSync : public RouteSync { public: @@ -232,3 +234,18 @@ TEST_F(FpmSyncdResponseTest, testEvpn) ASSERT_EQ(value.get(), "0xc8"); } + +TEST_F(FpmSyncdResponseTest, testSendOffloadReply) +{ + + rt_build_ret = 1; + rtnl_route* routeObject{}; + + + ASSERT_EQ(m_routeSync.sendOffloadReply(routeObject), false); + rt_build_ret = 0; + nlmsg_alloc_ret = false; + ASSERT_EQ(m_routeSync.sendOffloadReply(routeObject), false); + nlmsg_alloc_ret = true; + +} From fa2b5304420ea11a7191210c2689631007335d60 Mon Sep 17 00:00:00 2001 From: Stephen Sun <5379172+stephenxs@users.noreply.github.com> Date: Wed, 11 Dec 2024 02:01:31 +0800 Subject: [PATCH 2/9] Do not start counter polling for queues with zero buffer profiles if create_only_config_db_buffers is enabled (#3360) What I did Do not start counter-polling for queues with zero buffer profiles if create_only_config_db_buffers is enabled. Why I did it If create_only_config_db_buffers is enabled, counters will be polled only on queues with a buffer configured based on the hypothesis that a customer must configure a buffer profile before using the queue. The system performance can be optimized by reducing the number of counter polled. However, zero buffer profiles are configured on queues of inactive ports to reclaim unused buffers. Those queues are not used by customers. Hence, we do not need to start counter polling for queues with such buffer profiles configured. --- orchagent/bufferorch.cpp | 60 +++++++++++++++++++++++++++++------ orchagent/bufferorch.h | 1 + orchagent/flexcounterorch.cpp | 8 ++--- 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/orchagent/bufferorch.cpp b/orchagent/bufferorch.cpp index ccbc8e9b25..fb38cfe447 100644 --- a/orchagent/bufferorch.cpp +++ b/orchagent/bufferorch.cpp @@ -360,6 +360,25 @@ const object_reference_map &BufferOrch::getBufferPoolNameOidMap(void) return *m_buffer_type_maps[APP_BUFFER_POOL_TABLE_NAME]; } +void BufferOrch::getBufferObjectsWithNonZeroProfile(vector &nonZeroQueues, const string &table) +{ + for (auto &&queueRef: (*m_buffer_type_maps[table])) + { + for (auto &&profileRef: queueRef.second.m_objsReferencingByMe) + { + if (profileRef.second.find("_zero_") == std::string::npos) + { + SWSS_LOG_INFO("Selected key %s with profile %s", queueRef.first.c_str(), profileRef.second.c_str()); + nonZeroQueues.push_back(queueRef.first); + } + else + { + SWSS_LOG_INFO("Skipped key %s with profile %s", queueRef.first.c_str(), profileRef.second.c_str()); + } + } + } +} + task_process_status BufferOrch::processBufferPool(KeyOpFieldsValuesTuple &tuple) { SWSS_LOG_ENTER(); @@ -797,6 +816,9 @@ task_process_status BufferOrch::processQueue(KeyOpFieldsValuesTuple &tuple) sai_uint32_t range_low, range_high; bool need_update_sai = true; bool local_port = false; + bool counter_was_added = false; + bool counter_needs_to_add = false; + string old_buffer_profile_name; string local_port_name; SWSS_LOG_DEBUG("Processing:%s", key.c_str()); @@ -856,7 +878,6 @@ task_process_status BufferOrch::processQueue(KeyOpFieldsValuesTuple &tuple) return task_process_status::task_failed; } - string old_buffer_profile_name; if (doesObjectExist(m_buffer_type_maps, APP_BUFFER_QUEUE_TABLE_NAME, key, buffer_profile_field_name, old_buffer_profile_name) && (old_buffer_profile_name == buffer_profile_name)) { @@ -874,11 +895,14 @@ task_process_status BufferOrch::processQueue(KeyOpFieldsValuesTuple &tuple) SWSS_LOG_NOTICE("Set buffer queue %s to %s", key.c_str(), buffer_profile_name.c_str()); setObjectReference(m_buffer_type_maps, APP_BUFFER_QUEUE_TABLE_NAME, key, buffer_profile_field_name, buffer_profile_name); + + // Counter operation + counter_needs_to_add = buffer_profile_name.find("_zero_") == std::string::npos; + SWSS_LOG_INFO("%s to create counter for %s with new profile %s", counter_needs_to_add ? "Need" : "No need", key.c_str(), buffer_profile_name.c_str()); } else if (op == DEL_COMMAND) { - auto &typemap = (*m_buffer_type_maps[APP_BUFFER_QUEUE_TABLE_NAME]); - if (typemap.find(key) == typemap.end()) + if (!doesObjectExist(m_buffer_type_maps, APP_BUFFER_QUEUE_TABLE_NAME, key, buffer_profile_field_name, old_buffer_profile_name)) { SWSS_LOG_INFO("%s doesn't not exist, don't need to notfiy SAI", key.c_str()); need_update_sai = false; @@ -887,6 +911,7 @@ task_process_status BufferOrch::processQueue(KeyOpFieldsValuesTuple &tuple) SWSS_LOG_NOTICE("Remove buffer queue %s", key.c_str()); removeObject(m_buffer_type_maps, APP_BUFFER_QUEUE_TABLE_NAME, key); m_partiallyAppliedQueues.erase(key); + counter_needs_to_add = false; } else { @@ -894,6 +919,9 @@ task_process_status BufferOrch::processQueue(KeyOpFieldsValuesTuple &tuple) return task_process_status::task_invalid_entry; } + counter_was_added = !old_buffer_profile_name.empty() && old_buffer_profile_name.find("_zero_") == std::string::npos; + SWSS_LOG_INFO("%s to remove counter for %s with old profile %s", counter_was_added ? "Need" : "No need", key.c_str(), old_buffer_profile_name.c_str()); + sai_attribute_t attr; attr.id = SAI_QUEUE_ATTR_BUFFER_PROFILE_ID; attr.value.oid = sai_buffer_profile; @@ -963,14 +991,16 @@ task_process_status BufferOrch::processQueue(KeyOpFieldsValuesTuple &tuple) { auto flexCounterOrch = gDirectory.get(); auto queues = tokens[1]; - if (op == SET_COMMAND && + if (!counter_was_added && counter_needs_to_add && (flexCounterOrch->getQueueCountersState() || flexCounterOrch->getQueueWatermarkCountersState())) { + SWSS_LOG_INFO("Creating counters for %s %zd", port_name.c_str(), ind); gPortsOrch->createPortBufferQueueCounters(port, queues); } - else if (op == DEL_COMMAND && + else if (counter_was_added && !counter_needs_to_add && (flexCounterOrch->getQueueCountersState() || flexCounterOrch->getQueueWatermarkCountersState())) { + SWSS_LOG_INFO("Removing counters for %s %zd", port_name.c_str(), ind); gPortsOrch->removePortBufferQueueCounters(port, queues); } } @@ -1057,6 +1087,9 @@ task_process_status BufferOrch::processPriorityGroup(KeyOpFieldsValuesTuple &tup vector tokens; sai_uint32_t range_low, range_high; bool need_update_sai = true; + bool counter_was_added = false; + bool counter_needs_to_add = false; + string old_buffer_profile_name; SWSS_LOG_DEBUG("processing:%s", key.c_str()); tokens = tokenize(key, delimiter); @@ -1089,7 +1122,6 @@ task_process_status BufferOrch::processPriorityGroup(KeyOpFieldsValuesTuple &tup return task_process_status::task_failed; } - string old_buffer_profile_name; if (doesObjectExist(m_buffer_type_maps, APP_BUFFER_PG_TABLE_NAME, key, buffer_profile_field_name, old_buffer_profile_name) && (old_buffer_profile_name == buffer_profile_name)) { @@ -1100,11 +1132,14 @@ task_process_status BufferOrch::processPriorityGroup(KeyOpFieldsValuesTuple &tup SWSS_LOG_NOTICE("Set buffer PG %s to %s", key.c_str(), buffer_profile_name.c_str()); setObjectReference(m_buffer_type_maps, APP_BUFFER_PG_TABLE_NAME, key, buffer_profile_field_name, buffer_profile_name); + + // Counter operation + counter_needs_to_add = buffer_profile_name.find("_zero_") == std::string::npos; + SWSS_LOG_INFO("%s to create counter for priority group %s with new profile %s", counter_needs_to_add ? "Need" : "No need", key.c_str(), buffer_profile_name.c_str()); } else if (op == DEL_COMMAND) { - auto &typemap = (*m_buffer_type_maps[APP_BUFFER_PG_TABLE_NAME]); - if (typemap.find(key) == typemap.end()) + if (!doesObjectExist(m_buffer_type_maps, APP_BUFFER_PG_TABLE_NAME, key, buffer_profile_field_name, old_buffer_profile_name)) { SWSS_LOG_INFO("%s doesn't not exist, don't need to notfiy SAI", key.c_str()); need_update_sai = false; @@ -1119,6 +1154,9 @@ task_process_status BufferOrch::processPriorityGroup(KeyOpFieldsValuesTuple &tup return task_process_status::task_invalid_entry; } + counter_was_added = !old_buffer_profile_name.empty() && old_buffer_profile_name.find("_zero_") == std::string::npos; + SWSS_LOG_INFO("%s to remove counter for priority group %s with old profile %s", counter_was_added ? "Need" : "No need", key.c_str(), old_buffer_profile_name.c_str()); + sai_attribute_t attr; attr.id = SAI_INGRESS_PRIORITY_GROUP_ATTR_BUFFER_PROFILE; attr.value.oid = sai_buffer_profile; @@ -1161,14 +1199,16 @@ task_process_status BufferOrch::processPriorityGroup(KeyOpFieldsValuesTuple &tup { auto flexCounterOrch = gDirectory.get(); auto pgs = tokens[1]; - if (op == SET_COMMAND && + if (!counter_was_added && counter_needs_to_add && (flexCounterOrch->getPgCountersState() || flexCounterOrch->getPgWatermarkCountersState())) { + SWSS_LOG_INFO("Creating counters for priority group %s %zd", port_name.c_str(), ind); gPortsOrch->createPortBufferPgCounters(port, pgs); } - else if (op == DEL_COMMAND && + else if (counter_was_added && !counter_needs_to_add && (flexCounterOrch->getPgCountersState() || flexCounterOrch->getPgWatermarkCountersState())) { + SWSS_LOG_INFO("Removing counters for priority group %s %zd", port_name.c_str(), ind); gPortsOrch->removePortBufferPgCounters(port, pgs); } } diff --git a/orchagent/bufferorch.h b/orchagent/bufferorch.h index f78fe05318..3d255b87dd 100644 --- a/orchagent/bufferorch.h +++ b/orchagent/bufferorch.h @@ -38,6 +38,7 @@ class BufferOrch : public Orch static type_map m_buffer_type_maps; void generateBufferPoolWatermarkCounterIdList(void); const object_reference_map &getBufferPoolNameOidMap(void); + void getBufferObjectsWithNonZeroProfile(vector &nonZeroQueues, const string &table); private: typedef task_process_status (BufferOrch::*buffer_table_handler)(KeyOpFieldsValuesTuple &tuple); diff --git a/orchagent/flexcounterorch.cpp b/orchagent/flexcounterorch.cpp index b8918d0ac9..2832f0bd12 100644 --- a/orchagent/flexcounterorch.cpp +++ b/orchagent/flexcounterorch.cpp @@ -370,11 +370,11 @@ map FlexCounterOrch::getQueueConfigurations() } std::vector portQueueKeys; - m_bufferQueueConfigTable.getKeys(portQueueKeys); + gBufferOrch->getBufferObjectsWithNonZeroProfile(portQueueKeys, APP_BUFFER_QUEUE_TABLE_NAME); for (const auto& portQueueKey : portQueueKeys) { - auto toks = tokenize(portQueueKey, '|'); + auto toks = tokenize(portQueueKey, ':'); if (toks.size() != 2) { SWSS_LOG_ERROR("Invalid BUFFER_QUEUE key: [%s]", portQueueKey.c_str()); @@ -439,11 +439,11 @@ map FlexCounterOrch::getPgConfigurations() } std::vector portPgKeys; - m_bufferPgConfigTable.getKeys(portPgKeys); + gBufferOrch->getBufferObjectsWithNonZeroProfile(portPgKeys, APP_BUFFER_PG_TABLE_NAME); for (const auto& portPgKey : portPgKeys) { - auto toks = tokenize(portPgKey, '|'); + auto toks = tokenize(portPgKey, ':'); if (toks.size() != 2) { SWSS_LOG_ERROR("Invalid BUFFER_PG key: [%s]", portPgKey.c_str()); 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 3/9] 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; } From 664a56ce4eeb60cfcce2911913d6b3eaa4ab15e0 Mon Sep 17 00:00:00 2001 From: Ze Gan Date: Tue, 17 Dec 2024 09:45:10 -0800 Subject: [PATCH 4/9] [misc]: Add a dev folder for local development (#3368) What I did Add a dev folder, which includes a docker-compose environment for local development. Why I did it A developer has to download and install all dependences and setup the development environment if he would like to develop this submodule. This PR is for relieving the developer from the tedious setup process. --- configure.ac | 1 + dev/Dockerfile.yml | 92 ++++++++++++++++++++++++++++++++++++++++ dev/docker-compose.yml | 18 ++++++++ dev/download_artifact.sh | 46 ++++++++++++++++++++ 4 files changed, 157 insertions(+) create mode 100644 dev/Dockerfile.yml create mode 100644 dev/docker-compose.yml create mode 100755 dev/download_artifact.sh diff --git a/configure.ac b/configure.ac index e24f69887d..145231749c 100644 --- a/configure.ac +++ b/configure.ac @@ -106,6 +106,7 @@ CFLAGS_COMMON+=" -Wvariadic-macros" CFLAGS_COMMON+=" -Wno-switch-default" CFLAGS_COMMON+=" -Wno-long-long" CFLAGS_COMMON+=" -Wno-redundant-decls" +CFLAGS_COMMON+=" -Wno-error=missing-field-initializers" # Code testing coverage with gcov AC_MSG_CHECKING(whether to build with gcov testing) diff --git a/dev/Dockerfile.yml b/dev/Dockerfile.yml new file mode 100644 index 0000000000..acb0d9054b --- /dev/null +++ b/dev/Dockerfile.yml @@ -0,0 +1,92 @@ +ARG DEBIAN_VERSION="bookworm" +FROM sonicdev-microsoft.azurecr.io:443/sonic-slave-${DEBIAN_VERSION}:latest + +ARG UID=1000 +ARG GID=1000 + +RUN groupadd -g ${GID} sonicdev && \ + useradd -u ${UID} -g ${GID} -ms /bin/bash sonicdev + +RUN mkdir -p /workspace && \ + mkdir -p /workspace/debs && \ + mkdir -p /workspace/tools && \ + chown -R sonicdev:sonicdev /workspace + +ENV PATH="${PATH}:/workspace/tools" + +RUN apt-get update && \ + sudo apt-get install -y \ + libhiredis-dev \ + libzmq3-dev \ + swig4.0 \ + libdbus-1-dev \ + libteam-dev \ + protobuf-compiler \ + libprotobuf-dev && \ + sudo pip3 install lcov_cobertura + +COPY dev/download_artifact.sh /workspace/tools/download_artifact.sh + +WORKDIR /workspace/debs + +ARG BRANCH_NAME="master" +ARG PLATFORM="amd64" +ARG DEBIAN_VERSION + +# SWSS COMMON + +ARG SWSS_COMMON_PROJECT_NAME="Azure.sonic-swss-common" +ARG SWSS_COMMON_ARTIFACT_NAME="sonic-swss-common" +ARG SWSS_COMMON_FILE_PATHS="/libswsscommon_1.0.0_${PLATFORM}.deb /libswsscommon-dev_1.0.0_${PLATFORM}.deb" + +RUN download_artifact.sh "${SWSS_COMMON_PROJECT_NAME}" "${BRANCH_NAME}" "${SWSS_COMMON_ARTIFACT_NAME}" "${SWSS_COMMON_FILE_PATHS}" + +# SAIREDIS + +ARG SAIREDIS_PROJECT_NAME="Azure.sonic-sairedis" +ARG SAIREDIS_ARTIFACT_NAME="sonic-sairedis" +ARG SAIREDIS_FILE_PATHS="\ + /libsaivs_1.0.0_${PLATFORM}.deb \ + /libsaivs-dev_1.0.0_${PLATFORM}.deb \ + /libsairedis_1.0.0_${PLATFORM}.deb \ + /libsairedis-dev_1.0.0_${PLATFORM}.deb \ + /libsaimetadata_1.0.0_${PLATFORM}.deb \ + /libsaimetadata-dev_1.0.0_${PLATFORM}.deb \ + /syncd-vs_1.0.0_${PLATFORM}.deb \ + " + +RUN download_artifact.sh "${SAIREDIS_PROJECT_NAME}" "${BRANCH_NAME}" "${SAIREDIS_ARTIFACT_NAME}" "${SAIREDIS_FILE_PATHS}" + +# COMMON LIB + +ARG COMMON_LIB_PROJECT_NAME="Azure.sonic-buildimage.common_libs" +ARG COMMON_LIB_ARTIFACT_NAME="common-lib" +ARG COMMON_LIB_FILE_PATHS="\ + /target/debs/${DEBIAN_VERSION}/libnl-3-200_3.7.0-0.2%2Bb1sonic1_${PLATFORM}.deb \ + /target/debs/${DEBIAN_VERSION}/libnl-3-dev_3.7.0-0.2%2Bb1sonic1_${PLATFORM}.deb \ + /target/debs/${DEBIAN_VERSION}/libnl-genl-3-200_3.7.0-0.2%2Bb1sonic1_${PLATFORM}.deb \ + /target/debs/${DEBIAN_VERSION}/libnl-genl-3-dev_3.7.0-0.2%2Bb1sonic1_${PLATFORM}.deb \ + /target/debs/${DEBIAN_VERSION}/libnl-route-3-200_3.7.0-0.2%2Bb1sonic1_${PLATFORM}.deb \ + /target/debs/${DEBIAN_VERSION}/libnl-route-3-dev_3.7.0-0.2%2Bb1sonic1_${PLATFORM}.deb \ + /target/debs/${DEBIAN_VERSION}/libnl-nf-3-200_3.7.0-0.2%2Bb1sonic1_${PLATFORM}.deb \ + /target/debs/${DEBIAN_VERSION}/libnl-nf-3-dev_3.7.0-0.2%2Bb1sonic1_${PLATFORM}.deb \ + /target/debs/${DEBIAN_VERSION}/libyang_1.0.73_${PLATFORM}.deb \ + " + +RUN download_artifact.sh "${COMMON_LIB_PROJECT_NAME}" "${BRANCH_NAME}" "${COMMON_LIB_ARTIFACT_NAME}" "${COMMON_LIB_FILE_PATHS}" + +# DASH API + +ARG DASH_API_PROJECT_NAME="sonic-net.sonic-dash-api" +ARG DASH_API_ARTIFACT_NAME="sonic-dash-api" +ARG DASH_API_FILE_PATHS="/libdashapi_1.0.0_${PLATFORM}.deb" + +RUN download_artifact.sh "${DASH_API_PROJECT_NAME}" "${BRANCH_NAME}" "${DASH_API_ARTIFACT_NAME}" "${DASH_API_FILE_PATHS}" + +RUN dpkg -i *.deb + +WORKDIR /workspace + +USER sonicdev + +ENTRYPOINT [ "bash" ] diff --git a/dev/docker-compose.yml b/dev/docker-compose.yml new file mode 100644 index 0000000000..ce51eb6781 --- /dev/null +++ b/dev/docker-compose.yml @@ -0,0 +1,18 @@ +services: + sonicdev: + container_name: sonicdev + build: + context: .. + dockerfile: dev/Dockerfile.yml + args: + - DEBIAN_VERSION + - UID + - GID + - BRANCH_NAME + - PLATFORM + volumes: + - ..:/workspace/sonic-swss + init: true + privileged: true + working_dir: /workspace/sonic-swss + diff --git a/dev/download_artifact.sh b/dev/download_artifact.sh new file mode 100755 index 0000000000..282ca50475 --- /dev/null +++ b/dev/download_artifact.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# download_artifact.sh +# +# targetPaths: space separated list of target paths to download from the artifact +# e.g. +# ./download_artifact.sh "Azure.sonic-swss-common" "master" "sonic-swss-common" "/libswsscommon-dev_1.0.0_amd64.deb /libswsscommon_1.0.0_amd64.deb" + +set -x -e + +pipelineName=${1} +branchName=${2} +artifactName=${3} +targetPaths=${4} + +queryPipelinesUrl="https://dev.azure.com/mssonic/build/_apis/pipelines" + +definitions=$(curl -s "${queryPipelinesUrl}" | jq -r ".value[] | select (.name == \"${pipelineName}\").id") + +queryBuildsUrl="https://dev.azure.com/mssonic/build/_apis/build/builds?definitions=${definitions}&branchName=refs/heads/${branchName}&resultFilter=succeeded&statusFilter=completed&api-version=6.0" + +buildId=$(curl -s ${queryBuildsUrl} | jq -r '.value[0].id') + +queryArtifactUrl="https://dev.azure.com/mssonic/build/_apis/build/builds/${buildId}/artifacts?artifactName=${artifactName}&api-version=6.0" + +function download_artifact { + + target_path=${1} + output_file=$(sed 's/.*\///' <<< ${target_path}) + + download_artifact_url=$(curl -s ${queryArtifactUrl} | jq -r '.resource.downloadUrl') + download_artifact_url=$(sed 's/zip$/file/' <<< ${download_artifact_url}) + download_artifact_url="$download_artifact_url&subPath=${target_path}" + + wget -O ${output_file} ${download_artifact_url} +} + +function download_artifacts { + target_paths_array=(${targetPaths}) + for target_path in "${target_paths_array[@]}" + do + download_artifact ${target_path} + done +} + +download_artifacts From 4a3cf82f26eaa239af3579f1e53f94aa414930b2 Mon Sep 17 00:00:00 2001 From: anilkpan <64167306+anilkpan@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:15:08 -0800 Subject: [PATCH 5/9] Vlanmgr and Fdborch changes for PAC (#3143) Added Vlanmgr and Fdborch changes to support PAC. The following PAC operations are supported to be passed down from Vlanmgr to OA for updating the ASIC DB: Port learning mode FDB entry VLAN membership --- cfgmgr/vlanmgr.cpp | 272 +++++++++++++++++++++++++++++++++++++++++- cfgmgr/vlanmgr.h | 10 +- cfgmgr/vlanmgrd.cpp | 8 +- orchagent/fdborch.cpp | 10 +- orchagent/fdborch.h | 1 + orchagent/orch.cpp | 14 +++ orchagent/orch.h | 2 + tests/test_pac.py | 209 ++++++++++++++++++++++++++++++++ 8 files changed, 520 insertions(+), 6 deletions(-) create mode 100644 tests/test_pac.py diff --git a/cfgmgr/vlanmgr.cpp b/cfgmgr/vlanmgr.cpp index ffef1e4148..0dc8bb57bf 100644 --- a/cfgmgr/vlanmgr.cpp +++ b/cfgmgr/vlanmgr.cpp @@ -21,8 +21,9 @@ using namespace swss; extern MacAddress gMacAddress; -VlanMgr::VlanMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, const vector &tableNames) : - Orch(cfgDb, tableNames), +VlanMgr::VlanMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, const vector &tableNames, + const vector &stateTableNames) : + Orch(cfgDb, stateDb, tableNames, stateTableNames), m_cfgVlanTable(cfgDb, CFG_VLAN_TABLE_NAME), m_cfgVlanMemberTable(cfgDb, CFG_VLAN_MEMBER_TABLE_NAME), m_statePortTable(stateDb, STATE_PORT_TABLE_NAME), @@ -31,6 +32,8 @@ VlanMgr::VlanMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, c m_stateVlanMemberTable(stateDb, STATE_VLAN_MEMBER_TABLE_NAME), m_appVlanTableProducer(appDb, APP_VLAN_TABLE_NAME), m_appVlanMemberTableProducer(appDb, APP_VLAN_MEMBER_TABLE_NAME), + m_appFdbTableProducer(appDb, APP_FDB_TABLE_NAME), + m_appPortTableProducer(appDb, APP_PORT_TABLE_NAME), replayDone(false) { SWSS_LOG_ENTER(); @@ -643,6 +646,7 @@ void VlanMgr::doVlanMemberTask(Consumer &consumer) m_stateVlanMemberTable.set(kfvKey(t), fvVector); m_vlanMemberReplay.erase(kfvKey(t)); + m_PortVlanMember[port_alias][vlan_alias] = tagging_mode; } else { @@ -661,6 +665,7 @@ void VlanMgr::doVlanMemberTask(Consumer &consumer) key += port_alias; m_appVlanMemberTableProducer.del(key); m_stateVlanMemberTable.del(kfvKey(t)); + m_PortVlanMember[port_alias].erase(vlan_alias); } else { @@ -687,6 +692,257 @@ void VlanMgr::doVlanMemberTask(Consumer &consumer) } } +void VlanMgr::doVlanPacPortTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + auto &t = it->second; + string alias = kfvKey(t); + string op = kfvOp(t); + + SWSS_LOG_DEBUG("processing %s operation %s", alias.c_str(), + op.empty() ? "none" : op.c_str()); + + if (op == SET_COMMAND) + { + string learn_mode; + for (auto i : kfvFieldsValues(t)) + { + if (fvField(i) == "learn_mode") + { + learn_mode = fvValue(i); + } + } + if (!learn_mode.empty()) + { + SWSS_LOG_NOTICE("set port learn mode port %s learn_mode %s\n", alias.c_str(), learn_mode.c_str()); + vector fvVector; + FieldValueTuple portLearnMode("learn_mode", learn_mode); + fvVector.push_back(portLearnMode); + m_appPortTableProducer.set(alias, fvVector); + } + } + else if (op == DEL_COMMAND) + { + if (isMemberStateOk(alias)) + { + vector fvVector; + FieldValueTuple portLearnMode("learn_mode", "hardware"); + fvVector.push_back(portLearnMode); + m_appPortTableProducer.set(alias, fvVector); + } + } + it = consumer.m_toSync.erase(it); + } +} + +void VlanMgr::doVlanPacFdbTask(Consumer &consumer) +{ + auto it = consumer.m_toSync.begin(); + + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + + /* format: | */ + vector keys = tokenize(kfvKey(t), config_db_key_delimiter, 1); + /* keys[0] is vlan as (Vlan10) and keys[1] is mac as (00-00-00-00-00-00) */ + string op = kfvOp(t); + + SWSS_LOG_NOTICE("VlanMgr process static MAC vlan: %s mac: %s ", keys[0].c_str(), keys[1].c_str()); + + int vlan_id; + vlan_id = stoi(keys[0].substr(4)); + + if (!m_vlans.count(keys[0])) + { + SWSS_LOG_NOTICE("Vlan %s not available yet, mac %s", keys[0].c_str(), keys[1].c_str()); + it++; + continue; + } + + MacAddress mac = MacAddress(keys[1]); + + string key = VLAN_PREFIX + to_string(vlan_id); + key += DEFAULT_KEY_SEPARATOR; + key += mac.to_string(); + + if (op == SET_COMMAND) + { + string port, discard = "false", type = "static"; + for (auto i : kfvFieldsValues(t)) + { + if (fvField(i) == "port") + { + port = fvValue(i); + } + if (fvField(i) == "discard") + { + discard = fvValue(i); + } + if (fvField(i) == "type") + { + type = fvValue(i); + } + } + SWSS_LOG_NOTICE("PAC FDB SET %s port %s discard %s type %s\n", + key.c_str(), port.c_str(), discard.c_str(), type.c_str()); + vector fvVector; + FieldValueTuple p("port", port); + fvVector.push_back(p); + FieldValueTuple t("type", type); + fvVector.push_back(t); + FieldValueTuple d("discard", discard); + fvVector.push_back(d); + + m_appFdbTableProducer.set(key, fvVector); + } + else if (op == DEL_COMMAND) + { + m_appFdbTableProducer.del(key); + } + it = consumer.m_toSync.erase(it); + } +} + +void VlanMgr::doVlanPacVlanMemberTask(Consumer &consumer) +{ + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + auto &t = it->second; + + string key = kfvKey(t); + + key = key.substr(4); + size_t found = key.find(CONFIGDB_KEY_SEPARATOR); + int vlan_id = 0; + string vlan_alias, port_alias; + if (found != string::npos) + { + vlan_id = stoi(key.substr(0, found)); + port_alias = key.substr(found+1); + } + + vlan_alias = VLAN_PREFIX + to_string(vlan_id); + string op = kfvOp(t); + + if (op == SET_COMMAND) + { + /* Don't proceed if member port/lag is not ready yet */ + if (!isMemberStateOk(port_alias) || !isVlanStateOk(vlan_alias)) + { + SWSS_LOG_DEBUG("%s not ready, delaying", kfvKey(t).c_str()); + it++; + continue; + } + string tagging_mode = "untagged"; + auto vlans = m_PortVlanMember[port_alias]; + for (const auto& vlan : vlans) + { + string vlan_alias = vlan.first; + removePortFromVlan(port_alias, vlan_alias); + } + SWSS_LOG_NOTICE("Add Vlan Member key: %s", kfvKey(t).c_str()); + if (addHostVlanMember(vlan_id, port_alias, tagging_mode)) + { + key = VLAN_PREFIX + to_string(vlan_id); + key += DEFAULT_KEY_SEPARATOR; + key += port_alias; + vector fvVector = kfvFieldsValues(t); + FieldValueTuple s("dynamic", "yes"); + fvVector.push_back(s); + m_appVlanMemberTableProducer.set(key, fvVector); + + vector fvVector1; + FieldValueTuple s1("state", "ok"); + fvVector.push_back(s1); + m_stateVlanMemberTable.set(kfvKey(t), fvVector); + } + } + else if (op == DEL_COMMAND) + { + if (isVlanMemberStateOk(kfvKey(t))) + { + SWSS_LOG_NOTICE("Remove Vlan Member key: %s", kfvKey(t).c_str()); + removeHostVlanMember(vlan_id, port_alias); + key = VLAN_PREFIX + to_string(vlan_id); + key += DEFAULT_KEY_SEPARATOR; + key += port_alias; + m_appVlanMemberTableProducer.del(key); + m_stateVlanMemberTable.del(kfvKey(t)); + } + + auto vlans = m_PortVlanMember[port_alias]; + for (const auto& vlan : vlans) + { + string vlan_alias = vlan.first; + string tagging_mode = vlan.second; + SWSS_LOG_NOTICE("Add Vlan Member vlan: %s port %s tagging_mode %s", + vlan_alias.c_str(), port_alias.c_str(), tagging_mode.c_str()); + addPortToVlan(port_alias, vlan_alias, tagging_mode); + } + } + /* Other than the case of member port/lag is not ready, no retry will be performed */ + it = consumer.m_toSync.erase(it); + } +} + +void VlanMgr::addPortToVlan(const std::string& membername, const std::string& vlan_alias, + const std::string& tagging_mode) +{ + SWSS_LOG_NOTICE("member %s vlan %s tagging_mode %s", + membername.c_str(), vlan_alias.c_str(), tagging_mode.c_str()); + int vlan_id = stoi(vlan_alias.substr(4)); + if (addHostVlanMember(vlan_id, membername, tagging_mode)) + { + std::string key = VLAN_PREFIX + to_string(vlan_id); + key += DEFAULT_KEY_SEPARATOR; + key += membername; + vector fvVector; + FieldValueTuple s("tagging_mode", tagging_mode); + fvVector.push_back(s); + FieldValueTuple s1("dynamic", "no"); + fvVector.push_back(s1); + SWSS_LOG_INFO("key: %s\n", key.c_str()); + m_appVlanMemberTableProducer.set(key, fvVector); + + vector fvVector1; + FieldValueTuple s2("state", "ok"); + fvVector1.push_back(s2); + key = VLAN_PREFIX + to_string(vlan_id); + key += '|'; + key += membername; + m_stateVlanMemberTable.set(key, fvVector1); + } +} + +void VlanMgr::removePortFromVlan(const std::string& membername, const std::string& vlan_alias) +{ + SWSS_LOG_NOTICE("member %s vlan %s", + membername.c_str(), vlan_alias.c_str()); + int vlan_id = stoi(vlan_alias.substr(4)); + std::string key = VLAN_PREFIX + to_string(vlan_id); + key += '|'; + key += membername; + if (isVlanMemberStateOk(key)) + { + key = VLAN_PREFIX + to_string(vlan_id); + key += ':'; + key += membername; + SWSS_LOG_INFO("key: %s\n", key.c_str()); + m_appVlanMemberTableProducer.del(key); + + key = VLAN_PREFIX + to_string(vlan_id); + key += '|'; + key += membername; + m_stateVlanMemberTable.del(key); + } +} + void VlanMgr::doTask(Consumer &consumer) { SWSS_LOG_ENTER(); @@ -701,6 +957,18 @@ void VlanMgr::doTask(Consumer &consumer) { doVlanMemberTask(consumer); } + else if (table_name == STATE_OPER_PORT_TABLE_NAME) + { + doVlanPacPortTask(consumer); + } + else if (table_name == STATE_OPER_FDB_TABLE_NAME) + { + doVlanPacFdbTask(consumer); + } + else if (table_name == STATE_OPER_VLAN_MEMBER_TABLE_NAME) + { + doVlanPacVlanMemberTask(consumer); + } else { SWSS_LOG_ERROR("Unknown config table %s ", table_name.c_str()); diff --git a/cfgmgr/vlanmgr.h b/cfgmgr/vlanmgr.h index 8cf467f41c..7fce59ce65 100644 --- a/cfgmgr/vlanmgr.h +++ b/cfgmgr/vlanmgr.h @@ -14,11 +14,13 @@ namespace swss { class VlanMgr : public Orch { public: - VlanMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, const std::vector &tableNames); + VlanMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, const std::vector &tableNames, + const std::vector &stateTableNames); using Orch::doTask; private: ProducerStateTable m_appVlanTableProducer, m_appVlanMemberTableProducer; + ProducerStateTable m_appFdbTableProducer, m_appPortTableProducer; Table m_cfgVlanTable, m_cfgVlanMemberTable; Table m_statePortTable, m_stateLagTable; Table m_stateVlanTable, m_stateVlanMemberTable; @@ -26,6 +28,7 @@ class VlanMgr : public Orch std::set m_vlanReplay; std::set m_vlanMemberReplay; bool replayDone; + std::unordered_map> m_PortVlanMember; void doTask(Consumer &consumer); void doVlanTask(Consumer &consumer); @@ -43,6 +46,11 @@ class VlanMgr : public Orch bool isVlanStateOk(const std::string &alias); bool isVlanMacOk(); bool isVlanMemberStateOk(const std::string &vlanMemberKey); + void doVlanPacPortTask(Consumer &consumer); + void doVlanPacFdbTask(Consumer &consumer); + void doVlanPacVlanMemberTask(Consumer &consumer); + void addPortToVlan(const std::string& port_alias, const std::string& vlan_alias, const std::string& tagging_mode); + void removePortFromVlan(const std::string& port_alias, const std::string& vlan_alias); }; } diff --git a/cfgmgr/vlanmgrd.cpp b/cfgmgr/vlanmgrd.cpp index 84bc19cf08..d430063247 100644 --- a/cfgmgr/vlanmgrd.cpp +++ b/cfgmgr/vlanmgrd.cpp @@ -36,7 +36,11 @@ int main(int argc, char **argv) CFG_VLAN_TABLE_NAME, CFG_VLAN_MEMBER_TABLE_NAME, }; - + vector state_vlan_tables = { + STATE_OPER_PORT_TABLE_NAME, + STATE_OPER_FDB_TABLE_NAME, + STATE_OPER_VLAN_MEMBER_TABLE_NAME + }; DBConnector cfgDb("CONFIG_DB", 0); DBConnector appDb("APPL_DB", 0); DBConnector stateDb("STATE_DB", 0); @@ -58,7 +62,7 @@ int main(int argc, char **argv) } gMacAddress = MacAddress(it->second); - VlanMgr vlanmgr(&cfgDb, &appDb, &stateDb, cfg_vlan_tables); + VlanMgr vlanmgr(&cfgDb, &appDb, &stateDb, cfg_vlan_tables, state_vlan_tables); std::vector cfgOrchList = {&vlanmgr}; diff --git a/orchagent/fdborch.cpp b/orchagent/fdborch.cpp index 5dbaf291f8..98236be7d5 100644 --- a/orchagent/fdborch.cpp +++ b/orchagent/fdborch.cpp @@ -772,6 +772,7 @@ void FdbOrch::doTask(Consumer& consumer) string esi = ""; unsigned int vni = 0; string sticky = ""; + string discard = "false"; for (auto i : kfvFieldsValues(t)) { @@ -784,6 +785,10 @@ void FdbOrch::doTask(Consumer& consumer) { type = fvValue(i); } + if (fvField(i) == "discard") + { + discard = fvValue(i); + } if(origin == FDB_ORIGIN_VXLAN_ADVERTIZED) { @@ -859,6 +864,7 @@ void FdbOrch::doTask(Consumer& consumer) fdbData.esi = esi; fdbData.vni = vni; fdbData.is_flush_pending = false; + fdbData.discard = discard; if (addFdbEntry(entry, port, fdbData)) { if (origin == FDB_ORIGIN_MCLAG_ADVERTIZED) @@ -1480,7 +1486,9 @@ bool FdbOrch::addFdbEntry(const FdbEntry& entry, const string& port_name, attrs.push_back(attr); } } - + attr.id = SAI_FDB_ENTRY_ATTR_PACKET_ACTION; + attr.value.s32 = (fdbData.discard == "true") ? SAI_PACKET_ACTION_DROP: SAI_PACKET_ACTION_FORWARD; + attrs.push_back(attr); if (macUpdate) { SWSS_LOG_INFO("MAC-Update FDB %s in %s on from-%s:to-%s from-%s:to-%s origin-%d-to-%d", diff --git a/orchagent/fdborch.h b/orchagent/fdborch.h index a5a5fcd0e0..ef912b8400 100644 --- a/orchagent/fdborch.h +++ b/orchagent/fdborch.h @@ -65,6 +65,7 @@ struct FdbData string esi; unsigned int vni; sai_fdb_entry_type_t sai_fdb_type; + string discard; }; struct SavedFdbEntry diff --git a/orchagent/orch.cpp b/orchagent/orch.cpp index 6c6b2afa78..edcda386bd 100644 --- a/orchagent/orch.cpp +++ b/orchagent/orch.cpp @@ -30,6 +30,20 @@ Orch::Orch(DBConnector *db, const vector &tableNames) } } +Orch::Orch(swss::DBConnector *db1, swss::DBConnector *db2, + const std::vector &tableNames_1, const std::vector &tableNames_2) +{ + for(auto it : tableNames_1) + { + addConsumer(db1, it, default_orch_pri); + } + + for(auto it : tableNames_2) + { + addConsumer(db2, it, default_orch_pri); + } +} + Orch::Orch(DBConnector *db, const vector &tableNames_with_pri) { for (const auto& it : tableNames_with_pri) diff --git a/orchagent/orch.h b/orchagent/orch.h index e960db7e48..cca6b62fae 100644 --- a/orchagent/orch.h +++ b/orchagent/orch.h @@ -221,6 +221,8 @@ class Orch public: Orch(swss::DBConnector *db, const std::string tableName, int pri = default_orch_pri); Orch(swss::DBConnector *db, const std::vector &tableNames); + Orch(swss::DBConnector *db1, swss::DBConnector *db2, + const std::vector &tableNames_1, const std::vector &tableNames_2); Orch(swss::DBConnector *db, const std::vector &tableNameWithPri); Orch(const std::vector& tables); virtual ~Orch() = default; diff --git a/tests/test_pac.py b/tests/test_pac.py new file mode 100644 index 0000000000..a913fddfc9 --- /dev/null +++ b/tests/test_pac.py @@ -0,0 +1,209 @@ +import time + +from swsscommon import swsscommon + +def create_entry(tbl, key, pairs): + fvs = swsscommon.FieldValuePairs(pairs) + tbl.set(key, fvs) + + # FIXME: better to wait until DB create them + time.sleep(1) + +def remove_entry(tbl, key): + tbl._del(key) + time.sleep(1) + +def create_entry_tbl(db, table, key, pairs): + tbl = swsscommon.Table(db, table) + create_entry(tbl, key, pairs) + +def remove_entry_tbl(db, table, key): + tbl = swsscommon.Table(db, table) + remove_entry(tbl, key) + +def create_entry_pst(db, table, key, pairs): + tbl = swsscommon.ProducerStateTable(db, table) + create_entry(tbl, key, pairs) + +def how_many_entries_exist(db, table): + tbl = swsscommon.Table(db, table) + return len(tbl.getKeys()) + +def get_port_oid(db, port_name): + port_map_tbl = swsscommon.Table(db, 'COUNTERS_PORT_NAME_MAP') + for k in port_map_tbl.get('')[1]: + if k[0] == port_name: + return k[1] + return None + +def get_bridge_port_oid(db, port_oid): + tbl = swsscommon.Table(db, "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT") + for key in tbl.getKeys(): + status, data = tbl.get(key) + assert status + values = dict(data) + if port_oid == values["SAI_BRIDGE_PORT_ATTR_PORT_ID"]: + return key + return None + +def check_learn_mode_in_asicdb(db, interface_oid, learn_mode): + # Get bridge port oid + bridge_port_oid = get_bridge_port_oid(db, interface_oid) + assert bridge_port_oid is not None + + tbl = swsscommon.Table(db, "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT") + (status, fvs) = tbl.get(bridge_port_oid) + assert status == True + values = dict(fvs) + if values["SAI_BRIDGE_PORT_ATTR_FDB_LEARNING_MODE"] == learn_mode: + return True + else: + return False + +class TestPac(object): + def test_PacvlanMemberAndFDBAddRemove(self, dvs, testlog): + dvs.setup_db() + time.sleep(2) + + vlan_before = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + bp_before = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT") + vm_before = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN_MEMBER") + + # create vlan + dvs.create_vlan("2") + time.sleep(1) + + # Get bvid from vlanid + ok, bvid = dvs.get_vlan_oid(dvs.adb, "2") + assert ok, bvid + + dvs.create_vlan("3") + time.sleep(1) + + # create vlan member + dvs.create_vlan_member("3", "Ethernet0") + time.sleep(1) + + # create a Vlan member entry in Oper State DB + create_entry_tbl( + dvs.sdb, + "OPER_VLAN_MEMBER", "Vlan2|Ethernet0", + [ + ("tagging_mode", "untagged"), + ] + ) + + # check that the vlan information was propagated + vlan_after = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + bp_after = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT") + vm_after = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN_MEMBER") + + assert vlan_after - vlan_before == 2, "The Vlan2 wasn't created" + assert bp_after - bp_before == 1, "The bridge port wasn't created" + assert vm_after - vm_before == 1, "The vlan member wasn't added" + + # Add FDB entry in Oper State DB + create_entry_tbl( + dvs.sdb, + "OPER_FDB", "Vlan2|00:00:00:00:00:01", + [ + ("port", "Ethernet0"), + ("type", "dynamic"), + ("discard", "false"), + ] + ) + # Get mapping between interface name and its bridge port_id + iface_2_bridge_port_id = dvs.get_map_iface_bridge_port_id(dvs.adb) + + # check that the FDB entry was inserted into ASIC DB + ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", + [("mac", "00:00:00:00:00:01"), ("bvid", bvid)], + [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_DYNAMIC"), + ("SAI_FDB_ENTRY_ATTR_PACKET_ACTION", "SAI_PACKET_ACTION_FORWARD"), + ("SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID", iface_2_bridge_port_id["Ethernet0"])]) + + assert ok, str(extra) + + # Remove FDB entry in Oper State DB + remove_entry_tbl( + dvs.sdb, + "OPER_FDB", "Vlan2|00:00:00:00:00:01" + ) + + # check that the FDB entry was removed from ASIC DB + ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", + [("mac", "00:00:00:00:00:01"), ("bvid", bvid)], []) + assert ok == False, "The fdb entry still exists in ASIC" + + # remove Vlan member entry in Oper State DB + remove_entry_tbl( + dvs.sdb, + "OPER_VLAN_MEMBER", "Vlan2|Ethernet0" + ) + # check that the vlan information was propagated + vlan_after = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + bp_after = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT") + vm_after = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN_MEMBER") + + assert vlan_after - vlan_before == 2, "The Vlan2 wasn't created" + assert bp_after - bp_before == 1, "The bridge port wasn't created" + assert vm_after - vm_before == 1, "The vlan member wasn't added" + + dvs.remove_vlan("2") + dvs.remove_vlan_member("3", "Ethernet0") + dvs.remove_vlan("3") + + vlan_after = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + bp_after = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT") + vm_after = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN_MEMBER") + + assert vlan_after - vlan_before == 0, "The Vlan2 wasn't removed" + assert bp_after - bp_before == 0, "The bridge port wasn't removed" + assert vm_after - vm_before == 0, "The vlan member wasn't removed" + + def test_PacPortLearnMode(self, dvs, testlog): + dvs.setup_db() + time.sleep(2) + + # create vlan + dvs.create_vlan("2") + time.sleep(1) + + # create vlan member + dvs.create_vlan_member("2", "Ethernet0") + time.sleep(1) + + cntdb = swsscommon.DBConnector(swsscommon.COUNTERS_DB, dvs.redis_sock, 0) + # get port oid + port_oid = get_port_oid(cntdb, "Ethernet0") + assert port_oid is not None + + # check asicdb before setting mac learn mode; The default learn_mode value is SAI_BRIDGE_PORT_FDB_LEARNING_MODE_HW. + status = check_learn_mode_in_asicdb(dvs.adb, port_oid, "SAI_BRIDGE_PORT_FDB_LEARNING_MODE_HW") + assert status == True + + # Set port learn mode to CPU + create_entry_tbl( + dvs.sdb, + "OPER_PORT", "Ethernet0", + [ + ("learn_mode", "cpu_trap"), + ] + ) + status = check_learn_mode_in_asicdb(dvs.adb, port_oid, "SAI_BRIDGE_PORT_FDB_LEARNING_MODE_CPU_TRAP") + assert status == True + + # Set port learn mode back to default + remove_entry_tbl( + dvs.sdb, + "OPER_PORT", "Ethernet0" + ) + status = check_learn_mode_in_asicdb(dvs.adb, port_oid, "SAI_BRIDGE_PORT_FDB_LEARNING_MODE_HW") + assert status == True + dvs.remove_vlan_member("2", "Ethernet0") + dvs.remove_vlan("2") + +# Add Dummy always-pass test at end as workaroud +# for issue when Flaky fail on final test it invokes module tear-down before retrying +def test_nonflaky_dummy(): + pass From 0083f7f674135adfcea765899dd05491b86f26d5 Mon Sep 17 00:00:00 2001 From: abdosi <58047199+abdosi@users.noreply.github.com> Date: Wed, 18 Dec 2024 23:06:03 +0530 Subject: [PATCH 6/9] vlanmgrd not to throw exception for Portchannel ip link add because of race condition with PortChannel removal. (#3426) * Fix ip link add command exception for portchannel being added to vlan in case where portchannel removal can cause reace-condition with being added as member to the vlan. --- cfgmgr/vlanmgr.cpp | 8 ++++++- tests/conftest.py | 15 +++++++++++++ tests/test_vlan.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/cfgmgr/vlanmgr.cpp b/cfgmgr/vlanmgr.cpp index 0dc8bb57bf..09f9d3e9f3 100644 --- a/cfgmgr/vlanmgr.cpp +++ b/cfgmgr/vlanmgr.cpp @@ -232,10 +232,16 @@ bool VlanMgr::addHostVlanMember(int vlan_id, const string &port_alias, const str } catch (const std::runtime_error& e) { - if (!isMemberStateOk(port_alias)) + // Race conidtion can happen with portchannel removal might happen + // but state db is not updated yet so we can do retry instead of sending exception + if (!port_alias.compare(0, strlen(LAG_PREFIX), LAG_PREFIX)) + { return false; + } else + { EXEC_WITH_ERROR_THROW(cmds.str(), res); + } } return true; diff --git a/tests/conftest.py b/tests/conftest.py index abf9955cd7..20e3a219d1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -737,6 +737,21 @@ def stop_zebra(self): self.runcmd(['sh', '-c', 'pkill -9 zebra']) time.sleep(5) + def stop_teamsyncd(self): + self.runcmd(['sh', '-c', 'pkill -9 teamsyncd']) + + time.sleep(5) + + def start_teamsyncd(self): + self.runcmd(['sh', '-c', 'supervisorctl start teamsyncd']) + + time.sleep(5) + + def restart_teammgrd(self): + self.runcmd(['sh', '-c', 'supervisorctl restart teammgrd']) + + time.sleep(5) + # deps: warm_reboot def start_fpmsyncd(self): self.runcmd(['sh', '-c', 'supervisorctl start fpmsyncd']) diff --git a/tests/test_vlan.py b/tests/test_vlan.py index 28d3de3a29..03755cda04 100644 --- a/tests/test_vlan.py +++ b/tests/test_vlan.py @@ -233,8 +233,58 @@ def test_AddPortChannelToVlan(self, dvs): self.dvs_vlan.create_vlan(vlan) self.dvs_vlan.get_and_verify_vlan_ids(1) + + self.dvs_vlan.create_vlan_member(vlan, lag_interface, "tagged") + self.dvs_vlan.get_and_verify_vlan_member_ids(1) + + self.dvs_vlan.remove_vlan_member(vlan, lag_interface) + self.dvs_vlan.get_and_verify_vlan_member_ids(0) + + self.dvs_vlan.remove_vlan(vlan) + self.dvs_vlan.get_and_verify_vlan_ids(0) + + self.dvs_lag.remove_port_channel_member(lag_id, lag_member) + self.dvs_vlan.asic_db.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_LAG_MEMBER", 0) + + self.dvs_lag.remove_port_channel(lag_id) + self.dvs_vlan.asic_db.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_LAG", 0) + + def test_AddPortChannelToVlanRaceCondition(self, dvs): + + vlan = "2" + lag_member = "Ethernet0" + lag_id = "0001" + lag_interface = "PortChannel{}".format(lag_id) + + self.dvs_lag.create_port_channel(lag_id) + lag_entries = self.dvs_vlan.asic_db.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_LAG", 1) + + self.dvs_lag.create_port_channel_member(lag_id, lag_member) + + # Verify the LAG has been initialized properly + lag_member_entries = self.dvs_vlan.asic_db.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_LAG_MEMBER", 1) + fvs = self.dvs_vlan.asic_db.wait_for_entry("ASIC_STATE:SAI_OBJECT_TYPE_LAG_MEMBER", lag_member_entries[0]) + assert len(fvs) == 4 + assert fvs.get("SAI_LAG_MEMBER_ATTR_LAG_ID") == lag_entries[0] + assert self.dvs_vlan.asic_db.port_to_id_map[fvs.get("SAI_LAG_MEMBER_ATTR_PORT_ID")] == lag_member + + self.dvs_vlan.create_vlan(vlan) + self.dvs_vlan.get_and_verify_vlan_ids(1) + # Kill teamsyncd + dvs.stop_teamsyncd() + + # Delete netdevice + dvs.runcmd("ip link del PortChannel" + lag_id) self.dvs_vlan.create_vlan_member(vlan, lag_interface, "tagged") + + self.dvs_vlan.get_and_verify_vlan_member_ids(0) + #Start teamsyncd + dvs.start_teamsyncd() + + #Start teammgrd + dvs.restart_teammgrd() + self.dvs_vlan.get_and_verify_vlan_member_ids(1) self.dvs_vlan.remove_vlan_member(vlan, lag_interface) @@ -249,6 +299,8 @@ def test_AddPortChannelToVlan(self, dvs): self.dvs_lag.remove_port_channel(lag_id) self.dvs_vlan.asic_db.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_LAG", 0) + + def test_AddVlanMemberWithNonExistVlan(self, dvs): vlan = "2" From 8465c1db68f44034adda55d86cbddfab244766e7 Mon Sep 17 00:00:00 2001 From: abdosi <58047199+abdosi@users.noreply.github.com> Date: Wed, 18 Dec 2024 23:09:21 +0530 Subject: [PATCH 7/9] Added change not to create ECMP Group in SAI and program the route if none of ECMP members are active/link-up (#3394) What I did: Added change not to create ECMP Group in SAI and program the route if none of the ECMP members are active/link-up. Also do not program the Temp Route if Neigh is not active (Link Down) Also as part of this change if Route is not programmed and if we remove that route than decrement VRF Reference count in removeRoute as removeRoutePost will not be called in this case. Why I did: In scale setup of T2 it's possible all links can go down simultaneously which case we can get Route messages with all nexthops being in down state. In such case we might create empty Nexthop Group in SAI for the given route which causes not needed SAI call for Nexthop Group creation and also create traffic blackhole for the route where that route can still forward traffic via default route if eligible/applicable. Also in this case no point to add Temp Route if neighbor is link down. --- orchagent/routeorch.cpp | 26 ++++++++++++--- tests/mock_tests/routeorch_ut.cpp | 1 + tests/test_sub_port_intf.py | 53 +++++++++++++++++++------------ 3 files changed, 54 insertions(+), 26 deletions(-) diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index 2903cd0342..ef6da962e3 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -1332,7 +1332,11 @@ bool RouteOrch::addNextHopGroup(const NextHopGroupKey &nexthops) nhopgroup_shared_set[next_hop_id].insert(it); } } - + if (!next_hop_ids.size()) + { + SWSS_LOG_INFO("Skipping creation of nexthop group as none of nexthop are active"); + return false; + } sai_attribute_t nhg_attr; vector nhg_attrs; @@ -1717,9 +1721,15 @@ void RouteOrch::addTempRoute(RouteBulkContext& ctx, const NextHopGroupKey &nextH SWSS_LOG_INFO("Failed to get next hop %s for %s", (*it).to_string().c_str(), ipPrefix.to_string().c_str()); it = next_hop_set.erase(it); + continue; } - else - it++; + if(m_neighOrch->isNextHopFlagSet(*it, NHFLAGS_IFDOWN)) + { + SWSS_LOG_INFO("Interface down for NH %s, skip this NH", (*it).to_string().c_str()); + it = next_hop_set.erase(it); + continue; + } + it++; } /* Return if next_hop_set is empty */ @@ -2423,8 +2433,14 @@ bool RouteOrch::removeRoute(RouteBulkContext& ctx) size_t creating = gRouteBulker.creating_entries_count(route_entry); if (it_route == it_route_table->second.end() && creating == 0) { - SWSS_LOG_INFO("Failed to find route entry, vrf_id 0x%" PRIx64 ", prefix %s\n", vrf_id, - ipPrefix.to_string().c_str()); + if (it_route_table->second.size() == 0) + { + m_syncdRoutes.erase(vrf_id); + m_vrfOrch->decreaseVrfRefCount(vrf_id); + } + SWSS_LOG_INFO("Failed to find route entry, vrf_id 0x%" PRIx64 ", prefix %s\n", vrf_id, + ipPrefix.to_string().c_str()); + return true; } diff --git a/tests/mock_tests/routeorch_ut.cpp b/tests/mock_tests/routeorch_ut.cpp index fe24cf1f29..043b7b6b0a 100644 --- a/tests/mock_tests/routeorch_ut.cpp +++ b/tests/mock_tests/routeorch_ut.cpp @@ -281,6 +281,7 @@ namespace routeorch_test for (const auto &it : ports) { portTable.set(it.first, it.second); + portTable.set(it.first, {{ "oper_status", "up" }}); } // Set PortConfigDone diff --git a/tests/test_sub_port_intf.py b/tests/test_sub_port_intf.py index ec76ec13bb..3c9edea5c6 100644 --- a/tests/test_sub_port_intf.py +++ b/tests/test_sub_port_intf.py @@ -393,7 +393,7 @@ def get_default_vrf_oid(self): assert len(oids) == 1, "Wrong # of default vrfs: %d, expected #: 1." % (len(oids)) return oids[0] - def get_ip_prefix_nhg_oid(self, ip_prefix, vrf_oid=None): + def get_ip_prefix_nhg_oid(self, ip_prefix, vrf_oid=None, prefix_present=True): if vrf_oid is None: vrf_oid = self.default_vrf_oid @@ -407,18 +407,24 @@ def _access_function(): route_entry_found = True assert route_entry_key["vr"] == vrf_oid break - - return (route_entry_found, raw_route_entry_key) + if prefix_present: + return (route_entry_found, raw_route_entry_key) + else: + return (not route_entry_found, None) (route_entry_found, raw_route_entry_key) = wait_for_result(_access_function) - fvs = self.asic_db.get_entry(ASIC_ROUTE_ENTRY_TABLE, raw_route_entry_key) + if not prefix_present: + assert raw_route_entry_key == None + return None + else: + fvs = self.asic_db.get_entry(ASIC_ROUTE_ENTRY_TABLE, raw_route_entry_key) - nhg_oid = fvs.get("SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID", "") - assert nhg_oid != "" - assert nhg_oid != "oid:0x0" + nhg_oid = fvs.get("SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID", "") + assert nhg_oid != "" + assert nhg_oid != "oid:0x0" - return nhg_oid + return nhg_oid def check_sub_port_intf_key_existence(self, db, table_name, key): db.wait_for_matching_keys(table_name, [key]) @@ -1543,21 +1549,26 @@ def _test_sub_port_intf_oper_down_with_pending_neigh_route_tasks(self, dvs, sub_ self.add_route_appl_db(ip_prefix, nhop_ips, ifnames, vrf_name) # Verify route entry created in ASIC_DB and get next hop group oid - nhg_oid = self.get_ip_prefix_nhg_oid(ip_prefix, vrf_oid) - - # Verify next hop group of the specified oid created in ASIC_DB - self.check_sub_port_intf_key_existence(self.asic_db, ASIC_NEXT_HOP_GROUP_TABLE, nhg_oid) + nhg_oid = self.get_ip_prefix_nhg_oid(ip_prefix, vrf_oid, prefix_present = i < (nhop_num - 1)) - # Verify next hop group member # created in ASIC_DB - nhg_member_oids = self.asic_db.wait_for_n_keys(ASIC_NEXT_HOP_GROUP_MEMBER_TABLE, - (nhop_num - 1) - i if create_intf_on_parent_port == False else ((nhop_num - 1) - i) * 2) + if i < (nhop_num - 1): + # Verify next hop group of the specified oid created in ASIC_DB + self.check_sub_port_intf_key_existence(self.asic_db, ASIC_NEXT_HOP_GROUP_TABLE, nhg_oid) - # Verify that next hop group members all belong to the next hop group of the specified oid - fv_dict = { - "SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID": nhg_oid, - } - for nhg_member_oid in nhg_member_oids: - self.check_sub_port_intf_fvs(self.asic_db, ASIC_NEXT_HOP_GROUP_MEMBER_TABLE, nhg_member_oid, fv_dict) + # Verify next hop group member # created in ASIC_DB + nhg_member_oids = self.asic_db.wait_for_n_keys(ASIC_NEXT_HOP_GROUP_MEMBER_TABLE, + (nhop_num - 1) - i if create_intf_on_parent_port == False \ + else ((nhop_num - 1) - i) * 2) + + # Verify that next hop group members all belong to the next hop group of the specified oid + fv_dict = { + "SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID": nhg_oid, + } + for nhg_member_oid in nhg_member_oids: + self.check_sub_port_intf_fvs(self.asic_db, ASIC_NEXT_HOP_GROUP_MEMBER_TABLE, nhg_member_oid, fv_dict) + else: + assert nhg_oid == None + self.asic_db.wait_for_n_keys(ASIC_NEXT_HOP_GROUP_MEMBER_TABLE, 0) nhop_cnt = len(self.asic_db.get_keys(ASIC_NEXT_HOP_TABLE)) # Remove next hop objects on sub port interfaces From fef15434e50c339e73551528122d6eb937504470 Mon Sep 17 00:00:00 2001 From: prabhataravind <108555774+prabhataravind@users.noreply.github.com> Date: Wed, 18 Dec 2024 09:44:24 -0800 Subject: [PATCH 8/9] [dash]: Set SAI_DIRECTION_LOOKUP_ENTRY_ATTR_DASH_ENI_MAC_OVERRIDE_TYPE to dst mac (#3423) What I did Set SAI_DIRECTION_LOOKUP_ENTRY_ATTR_DASH_ENI_MAC_OVERRIDE_TYPE to SAI_DASH_ENI_MAC_OVERRIDE_TYPE_DST_MACby default. This can be changed to be configurable once other vnet scenarios are onboarded on smartswitch. Why I did it For direction lookup to work properly for PL scenarios on smartswitch, ENI mac needs to match dst MAC. --- orchagent/dash/dashorch.cpp | 10 +++++++++- tests/dash/test_dash_vnet.py | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/orchagent/dash/dashorch.cpp b/orchagent/dash/dashorch.cpp index 03bb69be4b..d9aac53ce4 100644 --- a/orchagent/dash/dashorch.cpp +++ b/orchagent/dash/dashorch.cpp @@ -140,11 +140,19 @@ bool DashOrch::addApplianceEntry(const string& appliance_id, const dash::applian } sai_direction_lookup_entry_t direction_lookup_entry; + vector direction_lookup_attrs; direction_lookup_entry.switch_id = gSwitchId; direction_lookup_entry.vni = entry.vm_vni(); appliance_attr.id = SAI_DIRECTION_LOOKUP_ENTRY_ATTR_ACTION; appliance_attr.value.u32 = SAI_DIRECTION_LOOKUP_ENTRY_ACTION_SET_OUTBOUND_DIRECTION; - status = sai_dash_direction_lookup_api->create_direction_lookup_entry(&direction_lookup_entry, attr_count, &appliance_attr); + direction_lookup_attrs.push_back(appliance_attr); + + appliance_attr.id = SAI_DIRECTION_LOOKUP_ENTRY_ATTR_DASH_ENI_MAC_OVERRIDE_TYPE; + appliance_attr.value.u32 = SAI_DASH_ENI_MAC_OVERRIDE_TYPE_DST_MAC; + direction_lookup_attrs.push_back(appliance_attr); + + status = sai_dash_direction_lookup_api->create_direction_lookup_entry(&direction_lookup_entry, + (uint32_t)direction_lookup_attrs.size(), direction_lookup_attrs.data()); if (status != SAI_STATUS_SUCCESS) { SWSS_LOG_ERROR("Failed to create direction lookup entry for %s", appliance_id.c_str()); diff --git a/tests/dash/test_dash_vnet.py b/tests/dash/test_dash_vnet.py index fa8f457bb8..8409db7ce6 100644 --- a/tests/dash/test_dash_vnet.py +++ b/tests/dash/test_dash_vnet.py @@ -44,6 +44,7 @@ def test_appliance(self, dash_db: DashDB): direction_keys = dash_db.wait_for_asic_db_keys(ASIC_DIRECTION_LOOKUP_TABLE) dl_attrs = dash_db.get_asic_db_entry(ASIC_DIRECTION_LOOKUP_TABLE, direction_keys[0]) assert_sai_attribute_exists("SAI_DIRECTION_LOOKUP_ENTRY_ATTR_ACTION", dl_attrs, "SAI_DIRECTION_LOOKUP_ENTRY_ACTION_SET_OUTBOUND_DIRECTION") + assert_sai_attribute_exists("SAI_DIRECTION_LOOKUP_ENTRY_ATTR_DASH_ENI_MAC_OVERRIDE_TYPE", dl_attrs, "SAI_DASH_ENI_MAC_OVERRIDE_TYPE_DST_MAC") vip_keys = dash_db.wait_for_asic_db_keys(ASIC_VIP_TABLE) vip_attrs = dash_db.get_asic_db_entry(ASIC_VIP_TABLE, vip_keys[0]) From eae729e22864ba31a412efcaa575fc0d61a2b25c Mon Sep 17 00:00:00 2001 From: Brad House Date: Thu, 19 Dec 2024 12:23:55 -0500 Subject: [PATCH 9/9] [orchagent] fix "parsePortConfig: Unknown field(mode)" (#3405) When using "mode" as defined in https://github.com/sonic-net/sonic-buildimage/blob/master/src/sonic-yang-models/yang-models/sonic-port.yang this warning is emitted for each interface: ``` WARNING swss#orchagent: :- parsePortConfig: Unknown field(mode): skipping ... ``` It appears this field is not needed in orchagent at all, so we need to at least recognize it as valid then skip processing. --- orchagent/port/porthlpr.cpp | 6 ++++++ orchagent/port/portschema.h | 1 + 2 files changed, 7 insertions(+) diff --git a/orchagent/port/porthlpr.cpp b/orchagent/port/porthlpr.cpp index 181fef9f69..3adb7ba9e9 100644 --- a/orchagent/port/porthlpr.cpp +++ b/orchagent/port/porthlpr.cpp @@ -1229,6 +1229,12 @@ bool PortHelper::parsePortConfig(PortConfig &port) const return false; } } + else if (field == PORT_MODE) + { + /* Placeholder to prevent warning. Not needed to be parsed here. + * Setting exists in sonic-port.yang with possible values: routed|access|trunk + */ + } else { SWSS_LOG_WARN("Unknown field(%s): skipping ...", field.c_str()); diff --git a/orchagent/port/portschema.h b/orchagent/port/portschema.h index 8dd7f79200..3e48b16d8c 100644 --- a/orchagent/port/portschema.h +++ b/orchagent/port/portschema.h @@ -101,3 +101,4 @@ #define PORT_SUPPRESS_THRESHOLD "suppress_threshold" #define PORT_REUSE_THRESHOLD "reuse_threshold" #define PORT_FLAP_PENALTY "flap_penalty" +#define PORT_MODE "mode"