From 80fe084a9f963ab3ed25ff7cda1ffe8bb785b2f7 Mon Sep 17 00:00:00 2001 From: OleksiiKolevatovGL Date: Sat, 6 Jul 2024 15:24:41 +0300 Subject: [PATCH 1/5] Adjust fpmsyncd to requirements --- fpmsyncd/fpmlink.cpp | 5 ++ fpmsyncd/routesync.cpp | 149 +++++++++++++++++++++++++++++++++++++++-- fpmsyncd/routesync.h | 7 ++ 3 files changed, 157 insertions(+), 4 deletions(-) diff --git a/fpmsyncd/fpmlink.cpp b/fpmsyncd/fpmlink.cpp index 13d170a805c..2b0b13ed0b0 100644 --- a/fpmsyncd/fpmlink.cpp +++ b/fpmsyncd/fpmlink.cpp @@ -43,6 +43,11 @@ bool FpmLink::isRawProcessing(struct nlmsghdr *h) rtm = (struct rtmsg *)NLMSG_DATA(h); + if (h->nlmsg_type == RTM_NEWTFILTER || h->nlmsg_type == RTM_DELTFILTER) + { + return true; + } + if (h->nlmsg_type != RTM_NEWROUTE && h->nlmsg_type != RTM_DELROUTE) { return false; diff --git a/fpmsyncd/routesync.cpp b/fpmsyncd/routesync.cpp index 0f6ee41188a..fef255df91a 100644 --- a/fpmsyncd/routesync.cpp +++ b/fpmsyncd/routesync.cpp @@ -74,12 +74,13 @@ static decltype(auto) makeNlAddr(const T& ip) return makeUniqueWithDestructor(addr, nl_addr_put); } - RouteSync::RouteSync(RedisPipeline *pipeline) : m_routeTable(pipeline, APP_ROUTE_TABLE_NAME, true), m_label_routeTable(pipeline, APP_LABEL_ROUTE_TABLE_NAME, true), m_vnet_routeTable(pipeline, APP_VNET_RT_TABLE_NAME, true), m_vnet_tunnelTable(pipeline, APP_VNET_RT_TUNNEL_TABLE_NAME, true), + m_shlTable(pipeline, APP_EVPN_SH_TABLE_NAME, true), + m_shl_dfmodeTable(pipeline, APP_EVPN_DF_TABLE_NAME, true), m_warmStartHelper(pipeline, &m_routeTable, APP_ROUTE_TABLE_NAME, "bgp", "bgp"), m_nl_sock(NULL), m_link_cache(NULL) { @@ -584,12 +585,147 @@ void RouteSync::onEvpnRouteMsg(struct nlmsghdr *h, int len) return; } -void RouteSync::onMsgRaw(struct nlmsghdr *h) + +void RouteSync::onBridgePortMsg(struct nlmsghdr *h, int len) { + enum { + BRPORT_NON_DF, + BRPORT_SPH_FILTER_CNT, + BRPORT_SPH_FILTERS, + BRPORT_MAX + }; + + struct rtattr *tb[BRPORT_MAX] = {0}; + char ifname[IFNAMSIZ] = {0}; + int nlmsg_type = h->nlmsg_type; + string vteps_cnt_str, vteps_str; + bool df = false; + + struct tcmsg *tcm = (struct tcmsg *)NLMSG_DATA(h); + /* Parse attributes and extract fields of interest. */ + netlink_parse_rtattr(tb, BRPORT_MAX - 1, TCA_RTA(tcm), len); + + if (tcm->tcm_family != AF_BRIDGE) + return; + + if (!getIfName(tcm->tcm_ifindex, ifname, sizeof(ifname))) { + SWSS_LOG_ERROR("Failed to get ifname for index: %d", tcm->tcm_ifindex); + return; + } + + if (tb[BRPORT_SPH_FILTER_CNT]) + { + uint32_t vteps_cnt = *(uint32_t *)RTA_DATA(tb[BRPORT_SPH_FILTER_CNT]); + vteps_cnt_str = to_string(vteps_cnt); + } + + if (tb[BRPORT_SPH_FILTERS]) + { + const struct in_addr *vteps = (const struct in_addr *)RTA_DATA(tb[BRPORT_SPH_FILTERS]); + stringstream vteps_ss; + for (uint32_t i = 0; i < *(uint32_t *)RTA_DATA(tb[BRPORT_SPH_FILTER_CNT]); i++) + { + vteps_ss << inet_ntoa(vteps[i]); + if (i != *(uint32_t *)RTA_DATA(tb[BRPORT_SPH_FILTER_CNT]) - 1) + { + vteps_ss << ","; + } + } + vteps_str = vteps_ss.str(); + } + + if (tb[BRPORT_NON_DF]) + df = !(*(uint8_t *)RTA_DATA(tb[BRPORT_NON_DF])); + + if (nlmsg_type == RTM_NEWTFILTER && vteps_str.empty()) + return; + + bool warmRestartInProgress = m_warmStartHelper.inProgress(); + string key = ifname; + + if (nlmsg_type == RTM_DELTFILTER) + { + if (!warmRestartInProgress) + { + m_shlTable.del(key); + SWSS_LOG_INFO("EVPN_SPLIT_HORIZON_TABLE del msg: %s", key.c_str()); + + m_shl_dfmodeTable.del(key); + SWSS_LOG_INFO("EVPN_DF_TABLE del msg: %s", key.c_str()); + } + else + { + SWSS_LOG_INFO("Warm-Restart mode: Receiving delete msg: %s", + key.c_str()); + + vector shl_fvVector; + const KeyOpFieldsValuesTuple shl_kfv = std::make_tuple(key, + DEL_COMMAND, + shl_fvVector); + m_warmStartHelper.insertRefreshMap(shl_kfv); + + vector df_fvVector; + const KeyOpFieldsValuesTuple df_kfv = std::make_tuple(key, + DEL_COMMAND, + df_fvVector); + m_warmStartHelper.insertRefreshMap(df_kfv); + } + } + else + { + vector shf_fvVector; + FieldValueTuple fv("vteps", vteps_str); + shf_fvVector.push_back(fv); + + vector df_fvVector; + FieldValueTuple df_fv("df", df ? "true" : "false"); + df_fvVector.push_back(df_fv); + + if (!warmRestartInProgress) + { + m_shlTable.set(key, shf_fvVector); + m_shl_dfmodeTable.set(key, df_fvVector); + SWSS_LOG_INFO("EVPN_SPLIT_HORIZON_TABLE set msg: %s vteps %s", + key.c_str(), vteps_str.c_str()); + SWSS_LOG_INFO("EVPN_DF_TABLE set msg: %s df %s", + key.c_str(), df ? "true" : "false"); + } + + /* + * During Split Horizon Filtering (SHF)/Designated Forwarder (DF) status updates + * will be temporarily put on hold by the warm-restart logic. + */ + + else + { + SWSS_LOG_INFO("Warm-Restart mode: EVPN_SPLIT_HORIZON_TABLE set msg: %s vteps %s", + key.c_str(), vteps_str.c_str()); + + const KeyOpFieldsValuesTuple shf_kfv = std::make_tuple(key, + SET_COMMAND, + shf_fvVector); + m_warmStartHelper.insertRefreshMap(shf_kfv); + const KeyOpFieldsValuesTuple df_kfv = std::make_tuple(key, + SET_COMMAND, + df_fvVector); + m_warmStartHelper.insertRefreshMap(df_kfv); + } + } + + return; +} + + + + +void RouteSync::onMsgRaw(struct nlmsghdr *h) +{ int len; if ((h->nlmsg_type != RTM_NEWROUTE) - && (h->nlmsg_type != RTM_DELROUTE)) + && (h->nlmsg_type != RTM_DELROUTE) + && (h->nlmsg_type != RTM_NEWTFILTER) + && (h->nlmsg_type != RTM_DELTFILTER)) return; /* Length validity. */ len = (int)(h->nlmsg_len - NLMSG_LENGTH(sizeof(struct ndmsg))); @@ -600,7 +736,12 @@ void RouteSync::onMsgRaw(struct nlmsghdr *h) (size_t)NLMSG_LENGTH(sizeof(struct ndmsg))); return; } - onEvpnRouteMsg(h, len); + + if (h->nlmsg_type == RTM_NEWTFILTER || h->nlmsg_type == RTM_DELTFILTER) + onBridgePortMsg(h, len); + else + onEvpnRouteMsg(h, len); + return; } diff --git a/fpmsyncd/routesync.h b/fpmsyncd/routesync.h index eb07eb8f15b..277728413cf 100644 --- a/fpmsyncd/routesync.h +++ b/fpmsyncd/routesync.h @@ -75,6 +75,10 @@ class RouteSync : public NetMsg ProducerStateTable m_vnet_routeTable; /* vnet vxlan tunnel table */ ProducerStateTable m_vnet_tunnelTable; + /* regular bridge port table */ + ProducerStateTable m_shlTable; + /* regular df mode table */ + ProducerStateTable m_shl_dfmodeTable; struct nl_cache *m_link_cache; struct nl_sock *m_nl_sock; @@ -101,6 +105,9 @@ class RouteSync : public NetMsg /* Handle vnet route */ void onVnetRouteMsg(int nlmsg_type, struct nl_object *obj, string vnet); + /* Handle bridge port msg */ + void onBridgePortMsg(struct nlmsghdr *h, int len); + /* Get interface name based on interface index */ bool getIfName(int if_index, char *if_name, size_t name_len); From 0989d35badb66ceff8a9a289ff9136c628e2aee3 Mon Sep 17 00:00:00 2001 From: YaroslavFedoriachenkoGL Date: Sat, 6 Jul 2024 15:19:21 +0300 Subject: [PATCH 2/5] Adjust fdbsyncd to requirements --- fdbsyncd/fdbsync.cpp | 212 +++++++++++++++++++++++++++++++++++++----- fdbsyncd/fdbsync.h | 13 ++- fdbsyncd/fdbsyncd.cpp | 3 + 3 files changed, 205 insertions(+), 23 deletions(-) diff --git a/fdbsyncd/fdbsync.cpp b/fdbsyncd/fdbsync.cpp index 3c1fae145a9..a1930799702 100644 --- a/fdbsyncd/fdbsync.cpp +++ b/fdbsyncd/fdbsync.cpp @@ -26,13 +26,15 @@ FdbSync::FdbSync(RedisPipeline *pipelineAppDB, DBConnector *stateDb, DBConnector m_imetTable(pipelineAppDB, APP_VXLAN_REMOTE_VNI_TABLE_NAME), m_fdbStateTable(stateDb, STATE_FDB_TABLE_NAME), m_mclagRemoteFdbStateTable(stateDb, STATE_MCLAG_REMOTE_FDB_TABLE_NAME), - m_cfgEvpnNvoTable(config_db, CFG_VXLAN_EVPN_NVO_TABLE_NAME) + m_cfgEvpnNvoTable(config_db, CFG_VXLAN_EVPN_NVO_TABLE_NAME), + m_l2nhgTable(pipelineAppDB, APP_L2_NEXTHOP_GROUP_TABLE_NAME) { m_AppRestartAssist = new AppRestartAssist(pipelineAppDB, "fdbsyncd", "swss", DEFAULT_FDBSYNC_WARMSTART_TIMER); if (m_AppRestartAssist) { m_AppRestartAssist->registerAppTable(APP_VXLAN_FDB_TABLE_NAME, &m_fdbTable); m_AppRestartAssist->registerAppTable(APP_VXLAN_REMOTE_VNI_TABLE_NAME, &m_imetTable); + m_AppRestartAssist->registerAppTable(APP_L2_NEXTHOP_GROUP_TABLE_NAME, &m_l2nhgTable); } } @@ -275,11 +277,11 @@ bool FdbSync::macCheckSrcDB(struct m_fdb_info *info) void FdbSync::macDelVxlanEntry(string auxkey, struct m_fdb_info *info) { - std::string vtep = m_mac[auxkey].vtep; + std::string nh = m_mac[auxkey].nh; const std::string cmds = std::string("") + " bridge fdb del " + info->mac + " dev " - + m_mac[auxkey].ifname + " dst " + vtep + " vlan " + info->vid.substr(4); + + m_mac[auxkey].ifname + " dst " + nh + " vlan " + info->vid.substr(4); std::string res; int ret = swss::exec(cmds, res); @@ -618,22 +620,23 @@ void FdbSync::imetDelRoute(struct in_addr vtep, string vlan_str, uint32_t vni) void FdbSync::macDelVxlanDB(string key) { - string vtep = m_mac[key].vtep; + string nh_type = m_mac[key].nh_type; + string snh = m_mac[key].nh; string type; string vni = to_string(m_mac[key].vni); type = m_mac[key].type; std::vector fvVector; - FieldValueTuple rv("remote_vtep", vtep); + FieldValueTuple nh(nh_type, snh); FieldValueTuple t("type", type); FieldValueTuple v("vni", vni); - fvVector.push_back(rv); + fvVector.push_back(nh); fvVector.push_back(t); fvVector.push_back(v); - SWSS_LOG_NOTICE("%sVXLAN_FDB_TABLE: DEL_KEY %s vtep:%s type:%s", + SWSS_LOG_NOTICE("%sVXLAN_FDB_TABLE: DEL_KEY %s %s:%s type:%s", m_AppRestartAssist->isWarmStartInProgress() ? "WARM-RESTART:" : "" , - key.c_str(), vtep.c_str(), type.c_str()); + key.c_str(), nh_type.c_str(), snh.c_str(), type.c_str()); // If warmstart is in progress, we take all netlink changes into the cache map if (m_AppRestartAssist->isWarmStartInProgress()) @@ -647,25 +650,26 @@ void FdbSync::macDelVxlanDB(string key) } -void FdbSync::macAddVxlan(string key, struct in_addr vtep, string type, uint32_t vni, string intf_name) +void FdbSync::macAddVxlan(string key, struct in_addr vtep, uint32_t nh_id, string type, uint32_t vni, string intf_name) { - string svtep = inet_ntoa(vtep); + string nh_type = (vtep.s_addr != 0) ? "remote_vtep" : "nexthop_group"; + string snh = (vtep.s_addr != 0) ? inet_ntoa(vtep) : std::to_string(nh_id); string svni = to_string(vni); /* Update the DB with Vxlan MAC */ - m_mac[key] = {svtep, type, vni, intf_name}; + m_mac[key] = {snh, nh_type, type, vni, intf_name}; std::vector fvVector; - FieldValueTuple rv("remote_vtep", svtep); + FieldValueTuple nh(nh_type, snh); FieldValueTuple t("type", type); FieldValueTuple v("vni", svni); - fvVector.push_back(rv); + fvVector.push_back(nh); fvVector.push_back(t); fvVector.push_back(v); - SWSS_LOG_INFO("%sVXLAN_FDB_TABLE: ADD_KEY %s vtep:%s type:%s", + SWSS_LOG_INFO("%sVXLAN_FDB_TABLE: ADD_KEY %s %s:%s type:%s", m_AppRestartAssist->isWarmStartInProgress() ? "WARM-RESTART:" : "" , - key.c_str(), svtep.c_str(), type.c_str()); + key.c_str(), nh_type.c_str(), snh.c_str(), type.c_str()); // If warmstart is in progress, we take all netlink changes into the cache map if (m_AppRestartAssist->isWarmStartInProgress()) { @@ -682,7 +686,7 @@ void FdbSync::macDelVxlan(string key) { if (m_mac.find(key) != m_mac.end()) { - SWSS_LOG_INFO("DEL_KEY %s vtep:%s type:%s", key.c_str(), m_mac[key].vtep.c_str(), m_mac[key].type.c_str()); + SWSS_LOG_INFO("DEL_KEY %s %s:%s type:%s", key.c_str(), m_mac[key].nh_type.c_str(), m_mac[key].nh.c_str(), m_mac[key].type.c_str()); macDelVxlanDB(key); m_mac.erase(key); } @@ -695,7 +699,7 @@ void FdbSync::onMsgNbr(int nlmsg_type, struct nl_object *obj) struct rtnl_neigh *neigh = (struct rtnl_neigh *)obj; struct in_addr vtep = {0}; int vlan = 0, ifindex = 0; - uint32_t vni = 0; + uint32_t vni = 0, nh_id = 0; nl_addr *vtep_addr; string ifname; string key; @@ -775,7 +779,11 @@ void FdbSync::onMsgNbr(int nlmsg_type, struct nl_object *obj) vtep_addr = rtnl_neigh_get_dst(neigh); if (vtep_addr == NULL) { - return; + if (rtnl_neigh_get_nhid(neigh, &nh_id)) + { + return; + } + SWSS_LOG_INFO("Tunnel NH_ID %u", nh_id); } else { @@ -826,7 +834,7 @@ void FdbSync::onMsgNbr(int nlmsg_type, struct nl_object *obj) if (!delete_key) { - macAddVxlan(key, vtep, type, vni, ifname); + macAddVxlan(key, vtep, nh_id, type, vni, ifname); } else { @@ -865,10 +873,168 @@ void FdbSync::onMsgLink(int nlmsg_type, struct nl_object *obj) return; } +void FdbSync::onMsgNexthop(int nlmsg_type, struct nl_object *obj) +{ + struct rtnl_nh *nh = (struct rtnl_nh *)obj; + bool del_op = (nlmsg_type == RTM_DELNEXTHOP); + + if (rtnl_nh_get_id(nh) < 0) + { + SWSS_LOG_ERROR("Nexthop %d < 0", rtnl_nh_get_id(nh)); + return; + } + + if (!rtnl_nh_get_fdb(nh)) + { + SWSS_LOG_INFO("Unhandled non-fdb nexthop %d", rtnl_nh_get_id(nh)); + return; + } + + if (rtnl_nh_get_group_size(nh) >= 0) + { + if (del_op) { + delL2NexthopGroup(nh); + } + else + { + addL2NexthopGroup(nh); + } + } + else + { + if (del_op) { + delL2Nexthop(nh); + } + else + { + addL2Nexthop(nh); + } + } +} + +void FdbSync::delL2NexthopGroup(struct rtnl_nh *nh) +{ + std::vector fvVector; + int id = rtnl_nh_get_id(nh); + string key = to_string(id); + + flushNhgFDB(id); + SWSS_LOG_INFO("%sNEXTHOP_GROUP_TABLE: DEL key:%s", + m_AppRestartAssist->isWarmStartInProgress() ? "WARM-RESTART:" : "", + key.c_str()); + + if (m_AppRestartAssist->isWarmStartInProgress()) + { + m_AppRestartAssist->insertToMap(APP_L2_NEXTHOP_GROUP_TABLE_NAME, key, fvVector, true); + } + else + { + m_l2nhgTable.del(key); + } +} + +void FdbSync::flushNhgFDB(int id) +{ + for (auto it = m_mac.begin(); it != m_mac.end(); ) + { + const std::string& key = it->first; + const m_mac_info& value = it->second; + + if (value.nh_type == "nexthop_group" && value.nh == std::to_string(id)) + { + SWSS_LOG_INFO("DEL_KEY %s %s:%s type:%s", key.c_str(), m_mac[key].nh_type.c_str(), + m_mac[key].nh.c_str(), m_mac[key].type.c_str()); + macDelVxlanDB(key); + it = m_mac.erase(it); + } + else + { + ++it; + } + } +} + +void FdbSync::addL2NexthopGroup(struct rtnl_nh *nh) +{ + string ip_str; + string ifname_str; + std::vector fvVector; + int nhg_size = rtnl_nh_get_group_size(nh); + int id = rtnl_nh_get_id(nh); + string key = to_string(id); + string nhg_str = to_string(rtnl_nh_get_group_entry(nh, 0)); + + for (int i = 1; i < nhg_size; i++) + { + int nh_id = rtnl_nh_get_group_entry(nh, i); + nhg_str += "," + to_string(nh_id); + } + + FieldValueTuple fvnhg("nexthop_group", nhg_str); + fvVector.push_back(fvnhg); + + SWSS_LOG_INFO("%sL2_NEXTHOP_GROUP_TABLE: SET key:%s nexthop_group:%s", + m_AppRestartAssist->isWarmStartInProgress() ? "WARM-RESTART:" : "", + key.c_str(), nhg_str.c_str()); + + if (m_AppRestartAssist->isWarmStartInProgress()) + { + m_AppRestartAssist->insertToMap(APP_L2_NEXTHOP_GROUP_TABLE_NAME, key, fvVector, false); + } + else + { + m_l2nhgTable.set(key, fvVector); + } +} + +void FdbSync::delL2Nexthop(struct rtnl_nh *nh) +{ + std::vector fvVector; + int id = rtnl_nh_get_id(nh); + string key = to_string(id); + + SWSS_LOG_INFO("%ssAPP_L2_NEXTHOP_GROUP_TABLE_NAME: DEL key:%s", + m_AppRestartAssist->isWarmStartInProgress() ? "WARM-RESTART:" : "", key.c_str()); + + if (m_AppRestartAssist->isWarmStartInProgress()) + { + m_AppRestartAssist->insertToMap(APP_L2_NEXTHOP_GROUP_TABLE_NAME, key, fvVector, true); + } + else + { + m_l2nhgTable.del(key); + } +} + +void FdbSync::addL2Nexthop(struct rtnl_nh *nh) +{ + char gw_str[MAX_ADDR_SIZE + 1] = {0}; + std::vector fvVector; + int id = rtnl_nh_get_id(nh); + string key = to_string(id); + + nl_addr2str(rtnl_nh_get_gateway(nh), gw_str, MAX_ADDR_SIZE); + FieldValueTuple fvremote_vtep("remote_vtep", gw_str); + fvVector.push_back(fvremote_vtep); + + SWSS_LOG_INFO("%sAPP_L2_NEXTHOP_GROUP_TABLE_NAME: SET key:%s", + m_AppRestartAssist->isWarmStartInProgress() ? "WARM-RESTART:" : "", key.c_str()); + + if (m_AppRestartAssist->isWarmStartInProgress()) + { + m_AppRestartAssist->insertToMap(APP_L2_NEXTHOP_GROUP_TABLE_NAME, key, fvVector, false); + } + else + { + m_l2nhgTable.set(key, fvVector); + } +} + void FdbSync::onMsg(int nlmsg_type, struct nl_object *obj) { if ((nlmsg_type != RTM_NEWLINK) && - (nlmsg_type != RTM_NEWNEIGH) && (nlmsg_type != RTM_DELNEIGH)) + (nlmsg_type != RTM_NEWNEIGH) && (nlmsg_type != RTM_DELNEIGH) && + (nlmsg_type != RTM_NEWNEXTHOP) && (nlmsg_type != RTM_DELNEXTHOP)) { SWSS_LOG_DEBUG("netlink: unhandled event: %d", nlmsg_type); return; @@ -877,9 +1043,13 @@ void FdbSync::onMsg(int nlmsg_type, struct nl_object *obj) { onMsgLink(nlmsg_type, obj); } - else + else if (nlmsg_type == RTM_NEWNEIGH || nlmsg_type == RTM_DELNEIGH) { onMsgNbr(nlmsg_type, obj); } + else if (nlmsg_type == RTM_NEWNEXTHOP || nlmsg_type == RTM_DELNEXTHOP) + { + onMsgNexthop(nlmsg_type, obj); + } } diff --git a/fdbsyncd/fdbsync.h b/fdbsyncd/fdbsync.h index ab55b91a315..b1cdafa0e73 100644 --- a/fdbsyncd/fdbsync.h +++ b/fdbsyncd/fdbsync.h @@ -8,6 +8,7 @@ #include "subscriberstatetable.h" #include "netmsg.h" #include "warmRestartAssist.h" +#include /* * Default timer interval for fdbsyncd reconcillation @@ -91,6 +92,7 @@ class FdbSync : public NetMsg SubscriberStateTable m_mclagRemoteFdbStateTable; AppRestartAssist *m_AppRestartAssist; SubscriberStateTable m_cfgEvpnNvoTable; + ProducerStateTable m_l2nhgTable; struct m_local_fdb_info { @@ -125,7 +127,8 @@ class FdbSync : public NetMsg struct m_mac_info { - std::string vtep; + std::string nh; + std::string nh_type; std::string type; unsigned int vni; std::string ifname; @@ -146,13 +149,19 @@ class FdbSync : public NetMsg std::unordered_map m_intf_info; void addLocalMac(std::string key, std::string op); - void macAddVxlan(std::string key, struct in_addr vtep, std::string type, uint32_t vni, std::string intf_name); + void macAddVxlan(std::string key, struct in_addr vtep, uint32_t nh_id, std::string type, uint32_t vni, std::string intf_name); void macDelVxlan(std::string auxkey); void macDelVxlanDB(std::string key); void imetAddRoute(struct in_addr vtep, std::string ifname, uint32_t vni); void imetDelRoute(struct in_addr vtep, std::string ifname, uint32_t vni); + void onMsgNexthop(int nlmsg_type, struct nl_object *obj); void onMsgNbr(int nlmsg_type, struct nl_object *obj); void onMsgLink(int nlmsg_type, struct nl_object *obj); + void flushNhgFDB(int id); + void delL2NexthopGroup(struct rtnl_nh *nh); + void addL2NexthopGroup(struct rtnl_nh *nh); + void delL2Nexthop(struct rtnl_nh *nh); + void addL2Nexthop(struct rtnl_nh *nh); }; } diff --git a/fdbsyncd/fdbsyncd.cpp b/fdbsyncd/fdbsyncd.cpp index 4f9405cbfdd..01e3300e954 100644 --- a/fdbsyncd/fdbsyncd.cpp +++ b/fdbsyncd/fdbsyncd.cpp @@ -23,6 +23,8 @@ int main(int argc, char **argv) FdbSync sync(&pipelineAppDB, &stateDb, &config_db); + NetDispatcher::getInstance().registerMessageHandler(RTM_NEWNEXTHOP, &sync); + NetDispatcher::getInstance().registerMessageHandler(RTM_DELNEXTHOP, &sync); NetDispatcher::getInstance().registerMessageHandler(RTM_NEWNEIGH, &sync); NetDispatcher::getInstance().registerMessageHandler(RTM_DELNEIGH, &sync); NetDispatcher::getInstance().registerMessageHandler(RTM_NEWLINK, &sync); @@ -73,6 +75,7 @@ int main(int argc, char **argv) netlink.registerGroup(RTNLGRP_LINK); netlink.registerGroup(RTNLGRP_NEIGH); + netlink.registerGroup(RTNLGRP_NEXTHOP); SWSS_LOG_NOTICE("Listens to link and neigh messages..."); netlink.dumpRequest(RTM_GETLINK); s.addSelectable(&netlink); From e872d526f1ec3d2d0ba30abd0c82f5da5d4a5599 Mon Sep 17 00:00:00 2001 From: RuslanValovyiGL Date: Sat, 6 Jul 2024 15:43:08 +0300 Subject: [PATCH 3/5] Create new orchagent EvpnMhOrch Create new orchagent shlorch Create new orchagent l2nhgorch Fix orchagent fdborch --- orchagent/Makefile.am | 3 + orchagent/evpnmhorch.cpp | 153 +++++++++++++ orchagent/evpnmhorch.h | 43 ++++ orchagent/fdborch.cpp | 121 +++++++---- orchagent/fdborch.h | 1 + orchagent/isolationgrouporch.cpp | 16 +- orchagent/isolationgrouporch.h | 3 + orchagent/l2nhgorch.cpp | 363 +++++++++++++++++++++++++++++++ orchagent/l2nhgorch.h | 72 ++++++ orchagent/orchdaemon.cpp | 26 ++- orchagent/orchdaemon.h | 3 + orchagent/port.h | 2 + orchagent/portsorch.cpp | 40 +++- orchagent/portsorch.h | 4 + orchagent/saihelper.cpp | 3 + orchagent/shlorch.cpp | 306 ++++++++++++++++++++++++++ orchagent/shlorch.h | 58 +++++ 17 files changed, 1177 insertions(+), 40 deletions(-) create mode 100644 orchagent/evpnmhorch.cpp create mode 100644 orchagent/evpnmhorch.h create mode 100644 orchagent/l2nhgorch.cpp create mode 100644 orchagent/l2nhgorch.h create mode 100644 orchagent/shlorch.cpp create mode 100644 orchagent/shlorch.h diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index 44714f62a10..9a5907bf636 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -101,6 +101,9 @@ orchagent_SOURCES = \ natorch.cpp \ mlagorch.cpp \ isolationgrouporch.cpp \ + shlorch.cpp \ + l2nhgorch.cpp \ + evpnmhorch.cpp \ muxorch.cpp \ macsecorch.cpp \ lagid.cpp \ diff --git a/orchagent/evpnmhorch.cpp b/orchagent/evpnmhorch.cpp new file mode 100644 index 00000000000..ad8852fb5b5 --- /dev/null +++ b/orchagent/evpnmhorch.cpp @@ -0,0 +1,153 @@ +/* + * Copyright 2024 GlobalLogic. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "evpnmhorch.h" +#include "intfsorch.h" +#include "tokenize.h" + +using namespace std; +using namespace swss; + +extern PortsOrch* gPortsOrch; +extern sai_bridge_api_t* sai_bridge_api; + +EvpnMhOrch::EvpnMhOrch(DBConnector *db, const vector &tableNames) : Orch(db, tableNames) +{ + SWSS_LOG_ENTER(); +} + +EvpnMhOrch::~EvpnMhOrch() +{ + SWSS_LOG_ENTER(); +} + +void EvpnMhOrch::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + if (!gPortsOrch->allPortsReady()) + { + return; + } + + string table_name = consumer.getTableName(); + if (table_name == APP_EVPN_DF_TABLE_NAME) + { + doDfTask(consumer); + } + else + { + SWSS_LOG_ERROR("EvpnMhOrch receives invalid table %s", table_name.c_str()); + } +} + +void EvpnMhOrch::doDfTask(Consumer& consumer) +{ + SWSS_LOG_ENTER(); + + string attr_value; + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + + string op = kfvOp(t); + string key = kfvKey(t); + + size_t sep_loc = key.find(consumer.getConsumerTable()->getTableNameSeparator().c_str()); + string name = key.substr(0, sep_loc); + + if (op == SET_COMMAND) + { + for (auto i : kfvFieldsValues(t)) + { + string attr_name = fvField(i); + if (attr_name == DF_MODE_FIELD) + { + attr_value = fvValue(i); + break; + } + } + if (!attr_value.empty()) + { + if (setDfElection(name, attr_value=="false"?true:false)) + it = consumer.m_toSync.erase(it); + else + it++; + } + else + it = consumer.m_toSync.erase(it); + } + else if (op == DEL_COMMAND) + { + if (setDfElection(name, false)) + it = consumer.m_toSync.erase(it); + else + it++; + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s", op.c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +bool EvpnMhOrch::setDfElection(string lag_port, bool non_df_mode) +{ + SWSS_LOG_ENTER(); + bool status = true; + sai_attribute_t attr; + sai_status_t sai_status; + + do { + try { + Port port; + if (!gPortsOrch->getPort(lag_port, port)) + { + SWSS_LOG_ERROR("Failed to locate port %s", lag_port.c_str()); + status = false; + break; + } + + if (port.m_bridge_port_id) { + attr.id = SAI_BRIDGE_PORT_ATTR_NON_DF; + attr.value.booldata = non_df_mode; + sai_status = sai_bridge_api->set_bridge_port_attribute(port.m_bridge_port_id, &attr); + if (sai_status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to set df-mode attr to bridge port id 0x%lx " + "status %d", + port.m_bridge_port_id, + sai_status); + status = false; + break; + } + } + } + catch (exception &e) + { + SWSS_LOG_ERROR("Exception: %s", e.what()); + status = false; + } + } while(0); + + return status; +} diff --git a/orchagent/evpnmhorch.h b/orchagent/evpnmhorch.h new file mode 100644 index 00000000000..d2ee54b784c --- /dev/null +++ b/orchagent/evpnmhorch.h @@ -0,0 +1,43 @@ +/* + * Copyright 2024 GlobalLogic. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __EVPNMHORCH_H__ +#define __EVPNMHORCH_H__ + +#include "orch.h" +#include "observer.h" + +#include + +#define DF_MODE_FIELD "df" + +class EvpnMhOrch : public Orch +{ +public: + EvpnMhOrch(DBConnector *db, const vector &tableNames); + + ~EvpnMhOrch(); + +private: + void doTask(Consumer &consumer); + + void doDfTask(Consumer& consumer); + + bool setDfElection(string lag_port, bool non_df_mode); +}; + + +#endif /* __EVPNMHORCH_H__ */ diff --git a/orchagent/fdborch.cpp b/orchagent/fdborch.cpp index 03c854fee30..c02385537a5 100644 --- a/orchagent/fdborch.cpp +++ b/orchagent/fdborch.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -8,6 +9,7 @@ #include "logger.h" #include "tokenize.h" #include "fdborch.h" +#include "l2nhgorch.h" #include "crmorch.h" #include "notifier.h" #include "sai_serialize.h" @@ -18,9 +20,10 @@ extern sai_fdb_api_t *sai_fdb_api; extern sai_object_id_t gSwitchId; -extern CrmOrch * gCrmOrch; -extern MlagOrch* gMlagOrch; +extern CrmOrch *gCrmOrch; +extern MlagOrch *gMlagOrch; extern Directory gDirectory; +extern L2NhgOrch *gL2NhgOrch; const int FdbOrch::fdborch_pri = 20; @@ -112,6 +115,7 @@ bool FdbOrch::storeFdbEntryState(const FdbUpdate& update) fdbdata.sai_fdb_type = update.sai_fdb_type; fdbdata.origin = FDB_ORIGIN_LEARN; fdbdata.remote_ip = ""; + fdbdata.nhg_id = 0; fdbdata.esi = ""; fdbdata.vni = 0; @@ -454,6 +458,7 @@ void FdbOrch::update(sai_fdb_event_t type, fdbData.type = update.type; fdbData.origin = existing_entry->second.origin; fdbData.remote_ip = existing_entry->second.remote_ip; + fdbData.nhg_id = existing_entry->second.nhg_id; fdbData.esi = existing_entry->second.esi; fdbData.vni = existing_entry->second.vni; saved_fdb_entries[update.port.m_alias].push_back( @@ -769,6 +774,7 @@ void FdbOrch::doTask(Consumer& consumer) string port = ""; string type = "dynamic"; string remote_ip = ""; + uint32_t nhg_id = 0; string esi = ""; unsigned int vni = 0; string sticky = ""; @@ -803,6 +809,17 @@ void FdbOrch::doTask(Consumer& consumer) } } + if (fvField(i) == "nexthop_group") + { + try { + nhg_id = (unsigned int) stoi(fvValue(i)); + } catch(exception &e) { + SWSS_LOG_INFO("Invalid VNI in remote MAC %s", fvValue(i).c_str()); + nhg_id = 0; + break; + } + } + if (fvField(i) == "esi") { esi = fvValue(i); @@ -828,25 +845,32 @@ void FdbOrch::doTask(Consumer& consumer) { VxlanTunnelOrch* tunnel_orch = gDirectory.get(); - if (tunnel_orch->isDipTunnelsSupported()) + if (!nhg_id) { - if(!remote_ip.length()) + if (tunnel_orch->isDipTunnelsSupported()) { - it = consumer.m_toSync.erase(it); - continue; + if(!remote_ip.length()) + { + it = consumer.m_toSync.erase(it); + continue; + } + port = tunnel_orch->getTunnelPortName(remote_ip); + } + else + { + EvpnNvoOrch* evpn_nvo_orch = gDirectory.get(); + VxlanTunnel* sip_tunnel = evpn_nvo_orch->getEVPNVtep(); + if (sip_tunnel == NULL) + { + it = consumer.m_toSync.erase(it); + continue; + } + port = tunnel_orch->getTunnelPortName(sip_tunnel->getSrcIP().to_string(), true); } - port = tunnel_orch->getTunnelPortName(remote_ip); } else { - EvpnNvoOrch* evpn_nvo_orch = gDirectory.get(); - VxlanTunnel* sip_tunnel = evpn_nvo_orch->getEVPNVtep(); - if (sip_tunnel == NULL) - { - it = consumer.m_toSync.erase(it); - continue; - } - port = tunnel_orch->getTunnelPortName(sip_tunnel->getSrcIP().to_string(), true); + port = gL2NhgOrch->getL2EcmpGroupPortName(to_string(nhg_id)); } } @@ -856,6 +880,7 @@ void FdbOrch::doTask(Consumer& consumer) fdbData.type = type; fdbData.origin = origin; fdbData.remote_ip = remote_ip; + fdbData.nhg_id = nhg_id; fdbData.esi = esi; fdbData.vni = vni; fdbData.is_flush_pending = false; @@ -1238,13 +1263,14 @@ bool FdbOrch::addFdbEntry(const FdbEntry& entry, const string& port_name, Port vlan; Port port; string end_point_ip = ""; + sai_object_id_t m_bridge_port_id = 0; VxlanTunnelOrch* tunnel_orch = gDirectory.get(); SWSS_LOG_ENTER(); - SWSS_LOG_INFO("mac=%s bv_id=0x%" PRIx64 " port_name=%s type=%s origin=%d remote_ip=%s", + SWSS_LOG_INFO("mac=%s bv_id=0x%" PRIx64 " port_name=%s type=%s origin=%d remote_ip=%s nhg_id=%u", entry.mac.to_string().c_str(), entry.bv_id, port_name.c_str(), - fdbData.type.c_str(), fdbData.origin, fdbData.remote_ip.c_str()); + fdbData.type.c_str(), fdbData.origin, fdbData.remote_ip.c_str(), fdbData.nhg_id); if (!m_portsOrch->getPort(entry.bv_id, vlan)) { @@ -1252,8 +1278,14 @@ bool FdbOrch::addFdbEntry(const FdbEntry& entry, const string& port_name, return false; } + if (fdbData.nhg_id && !gL2NhgOrch->hasL2EcmpGroup(to_string(fdbData.nhg_id))) + { + SWSS_LOG_NOTICE("L2 ECMP group %u was not created", fdbData.nhg_id); + return false; + } + /* Retry until port is created */ - if (!m_portsOrch->getPort(port_name, port) || (port.m_bridge_port_id == SAI_NULL_OBJECT_ID)) + if (!m_portsOrch->getPort(port_name, port) || ((m_bridge_port_id = port.m_bridge_port_id) == SAI_NULL_OBJECT_ID)) { SWSS_LOG_INFO("Saving a fdb entry until port %s becomes active", port_name.c_str()); saved_fdb_entries[port_name].push_back({entry.mac, @@ -1270,10 +1302,13 @@ bool FdbOrch::addFdbEntry(const FdbEntry& entry, const string& port_name, /* Retry until port is member of vlan*/ if (!m_portsOrch->isVlanMember(vlan, port, end_point_ip)) { - SWSS_LOG_INFO("Saving a fdb entry until port %s becomes vlan %s member", port_name.c_str(), vlan.m_alias.c_str()); - saved_fdb_entries[port_name].push_back({entry.mac, - vlan.m_vlan_info.vlan_id, fdbData}); - return true; + if (!fdbData.nhg_id) + { + SWSS_LOG_INFO("Saving a fdb entry until port %s becomes vlan %s member", port_name.c_str(), vlan.m_alias.c_str()); + saved_fdb_entries[port_name].push_back({entry.mac, + vlan.m_vlan_info.vlan_id, fdbData}); + return true; + } } sai_status_t status; @@ -1298,11 +1333,14 @@ bool FdbOrch::addFdbEntry(const FdbEntry& entry, const string& port_name, if (!m_portsOrch->getPortByBridgePortId(it->second.bridge_port_id, oldPort)) { - SWSS_LOG_ERROR("Existing port 0x%" PRIx64 " details not found", it->second.bridge_port_id); - return false; + if (!fdbData.nhg_id) + { + SWSS_LOG_ERROR("Existing port 0x%" PRIx64 " details not found", it->second.bridge_port_id); + return false; + } } - if ((oldOrigin == fdbData.origin) && (oldType == fdbData.type) && (port.m_bridge_port_id == it->second.bridge_port_id) + if ((oldOrigin == fdbData.origin) && (oldType == fdbData.type) && (m_bridge_port_id == it->second.bridge_port_id) && (oldRemoteIp == fdbData.remote_ip)) { /* Duplicate Mac */ @@ -1350,7 +1388,7 @@ bool FdbOrch::addFdbEntry(const FdbEntry& entry, const string& port_name, } else if ((oldOrigin == FDB_ORIGIN_LEARN) && (fdbData.origin == FDB_ORIGIN_MCLAG_ADVERTIZED)) { - if ((port.m_bridge_port_id == it->second.bridge_port_id) && (oldType == "dynamic") && (fdbData.type == "dynamic_local")) + if ((m_bridge_port_id == it->second.bridge_port_id) && (oldType == "dynamic") && (fdbData.type == "dynamic_local")) { SWSS_LOG_INFO("FdbOrch: mac=%s %s port=%s type=%s origin=%d old_origin=%d" " old_type=%s local mac exists," @@ -1405,10 +1443,10 @@ bool FdbOrch::addFdbEntry(const FdbEntry& entry, const string& port_name, } attr.id = SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID; - attr.value.oid = port.m_bridge_port_id; + attr.value.oid = m_bridge_port_id; attrs.push_back(attr); - if (fdbData.origin == FDB_ORIGIN_VXLAN_ADVERTIZED) + if (fdbData.origin == FDB_ORIGIN_VXLAN_ADVERTIZED && !fdbData.remote_ip.empty()) { IpAddress remote = IpAddress(fdbData.remote_ip); sai_ip_address_t ipaddr; @@ -1472,12 +1510,15 @@ bool FdbOrch::addFdbEntry(const FdbEntry& entry, const string& port_name, } } } - if (oldPort.m_bridge_port_id != port.m_bridge_port_id) + if (oldPort.m_bridge_port_id != m_bridge_port_id) { oldPort.m_fdb_count--; m_portsOrch->setPort(oldPort.m_alias, oldPort); - port.m_fdb_count++; - m_portsOrch->setPort(port.m_alias, port); + if (m_bridge_port_id != SAI_NULL_OBJECT_ID) + { + port.m_fdb_count++; + m_portsOrch->setPort(port.m_alias, port); + } } } else @@ -1496,14 +1537,17 @@ bool FdbOrch::addFdbEntry(const FdbEntry& entry, const string& port_name, return parseHandleSaiStatusFailure(handle_status); } } - port.m_fdb_count++; - m_portsOrch->setPort(port.m_alias, port); + if (m_bridge_port_id != SAI_NULL_OBJECT_ID) + { + port.m_fdb_count++; + m_portsOrch->setPort(port.m_alias, port); + } vlan.m_fdb_count++; m_portsOrch->setPort(vlan.m_alias, vlan); } FdbData storeFdbData = fdbData; - storeFdbData.bridge_port_id = port.m_bridge_port_id; + storeFdbData.bridge_port_id = m_bridge_port_id; // overwrite the type and origin if ((fdbData.origin == FDB_ORIGIN_MCLAG_ADVERTIZED) && (fdbData.type == "dynamic_local")) { @@ -1591,7 +1635,7 @@ bool FdbOrch::removeFdbEntry(const FdbEntry& entry, FdbOrigin origin) SWSS_LOG_ENTER(); - SWSS_LOG_INFO("FdbOrch RemoveFDBEntry: mac=%s bv_id=0x%" PRIx64 "origin %d", entry.mac.to_string().c_str(), entry.bv_id, origin); + SWSS_LOG_INFO("FdbOrch RemoveFDBEntry: mac=%s bv_id=0x%" PRIx64 " origin %d", entry.mac.to_string().c_str(), entry.bv_id, origin); if (!m_portsOrch->getPort(entry.bv_id, vlan)) { @@ -1612,8 +1656,11 @@ bool FdbOrch::removeFdbEntry(const FdbEntry& entry, FdbOrigin origin) FdbData fdbData = it->second; if (!m_portsOrch->getPortByBridgePortId(fdbData.bridge_port_id, port)) { - SWSS_LOG_NOTICE("FdbOrch RemoveFDBEntry: Failed to locate port from bridge_port_id 0x%" PRIx64, fdbData.bridge_port_id); - return false; + if (!fdbData.nhg_id) + { + SWSS_LOG_NOTICE("FdbOrch RemoveFDBEntry: Failed to locate port from bridge_port_id 0x%" PRIx64, fdbData.bridge_port_id); + return false; + } } if (fdbData.origin != origin) diff --git a/orchagent/fdborch.h b/orchagent/fdborch.h index 9e71bc8c6bf..5df10f185cb 100644 --- a/orchagent/fdborch.h +++ b/orchagent/fdborch.h @@ -63,6 +63,7 @@ struct FdbData /* Remote FDB related info */ string remote_ip; string esi; + uint32_t nhg_id; unsigned int vni; sai_fdb_entry_type_t sai_fdb_type; }; diff --git a/orchagent/isolationgrouporch.cpp b/orchagent/isolationgrouporch.cpp index 0138a6ab959..a0b284754c2 100644 --- a/orchagent/isolationgrouporch.cpp +++ b/orchagent/isolationgrouporch.cpp @@ -523,6 +523,20 @@ IsolationGroup::setMembers(string ports) return ISO_GRP_STATUS_SUCCESS; } +vector +IsolationGroup::getMembers() +{ + SWSS_LOG_ENTER(); + vector members; + + for (auto mem : m_members) + { + members.emplace_back(mem.first); + } + + return members; +} + isolation_group_status_t IsolationGroup::bind(Port &port) { @@ -666,7 +680,7 @@ IsolationGroup::setBindPorts(string ports) old_bindports.insert(old_bindports.end(), m_bind_ports.begin(), m_bind_ports.end()); for (auto alias : portList) { - if ((0 == alias.find("Ethernet")) || (0 == alias.find("PortChannel"))) + if ((0 == alias.find("Ethernet")) || (0 == alias.find("PortChannel")) || (0 == alias.find("Port_EVPN_"))) { auto iter = find(old_bindports.begin(), old_bindports.end(), alias); if (iter != old_bindports.end()) diff --git a/orchagent/isolationgrouporch.h b/orchagent/isolationgrouporch.h index 7bf8ef4c140..c9d50517fe4 100644 --- a/orchagent/isolationgrouporch.h +++ b/orchagent/isolationgrouporch.h @@ -72,6 +72,9 @@ class IsolationGroup: public Observer, public Subject // Set Isolation group members to the input. May involve adding or deleting members isolation_group_status_t setMembers(string ports); + // Get Isolation group members. + vector getMembers(); + // Apply the Isolation group to all linked ports isolation_group_status_t bind(Port &port); diff --git a/orchagent/l2nhgorch.cpp b/orchagent/l2nhgorch.cpp new file mode 100644 index 00000000000..a482c54b0fb --- /dev/null +++ b/orchagent/l2nhgorch.cpp @@ -0,0 +1,363 @@ +/* + * Copyright 2024 GlobalLogic. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "l2nhgorch.h" +#include "directory.h" +#include "vxlanorch.h" +#include "tokenize.h" + +#define L2NHG_FIELD_REMOTE_VTEP "remote_vtep" +#define L2NHG_FIELD_NEXTHOP_GROUP "nexthop_group" + +#define L2_ECMP_GROUP_PORT_PREFIX "Port_L2_ECMP_Grp_" + +using namespace std; +using namespace swss; + +extern sai_l2_ecmp_group_api_t *sai_l2_ecmp_group_api; + +extern PortsOrch* gPortsOrch; +extern Directory gDirectory; +extern sai_object_id_t gSwitchId; + +uint64_t L2NhgOrch::m_max_group_count = 0; + +static bool getVxlanPortFromIP(string ip, Port& port) +{ + VxlanTunnelOrch* vxlan_orch = gDirectory.get(); + string vtep_port = vxlan_orch->getTunnelPortName(ip, false); + return gPortsOrch->getPort(vtep_port, port); +} + +L2NhgOrch::L2NhgOrch(DBConnector *db, const vector &tableNames) : Orch(db, tableNames) +{ + SWSS_LOG_ENTER(); + + /* + * Get the maximum number of L2 ECMP groups. + */ + if (sai_object_type_get_availability(gSwitchId, + SAI_OBJECT_TYPE_L2_ECMP_GROUP, + 0, + nullptr, + &m_max_group_count) != SAI_STATUS_SUCCESS) + { + SWSS_LOG_WARN("Switch does not support L2 ECMP Groups"); + m_max_group_count = 0; + } +} + +L2NhgOrch::~L2NhgOrch() +{ + SWSS_LOG_ENTER(); +} + +void L2NhgOrch::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + if (!gPortsOrch->allPortsReady()) + { + return; + } + + string table_name = consumer.getTableName(); + if (table_name == APP_L2_NEXTHOP_GROUP_TABLE_NAME) + doL2NhgTask(consumer); + else + SWSS_LOG_ERROR("L2NhgOrch receives invalid table %s", table_name.c_str()); +} + +void L2NhgOrch::doL2NhgTask(Consumer& consumer) +{ + SWSS_LOG_ENTER(); + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + bool is_group = false; + + string op = kfvOp(t); + string key = kfvKey(t); + + size_t sep_loc = key.find(consumer.getConsumerTable()->getTableNameSeparator().c_str()); + string nhid = key.substr(0, sep_loc); + + if (op == SET_COMMAND) + { + string fv_value; + for (auto i : kfvFieldsValues(t)) + { + string fv_name = fvField(i); + if (fv_name == L2NHG_FIELD_NEXTHOP_GROUP) + { + is_group = true; + fv_value = fvValue(i); + break; + } + else if (fv_name == L2NHG_FIELD_REMOTE_VTEP) + { + fv_value = fvValue(i); + try { + IpAddress valid_ip = IpAddress(fv_value); + (void)valid_ip; + } catch (exception &e) { + SWSS_LOG_ERROR("Invalid IP address in L2 Nexthop %s", fv_value.c_str()); + fv_value = ""; + } + break; + } + else + { + SWSS_LOG_ERROR("Unknown field %s", fv_name.c_str()); + } + } + if (!fv_value.empty()) + { + if (!is_group) + { + m_nh[nhid].ip = fv_value; + it = consumer.m_toSync.erase(it); + } + else + { + if (addL2NexthopGroup(nhid, fv_value)) + it = consumer.m_toSync.erase(it); + else + it++; + } + } + else + { + it = consumer.m_toSync.erase(it); + } + } + else if (op == DEL_COMMAND) + { + if (m_nh.count(nhid)) + { + if (delL2Nexthop(nhid)) + it = consumer.m_toSync.erase(it); + else + it++; + } + else if (m_nhg.count(nhid)) + { + if (delL2NexthopGroup(nhid)) + it = consumer.m_toSync.erase(it); + else + it++; + } + else + { + SWSS_LOG_ERROR("Can't delete L2NHG '%s': does not exist", nhid.c_str()); + it = consumer.m_toSync.erase(it); + } + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s", op.c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +bool L2NhgOrch::addL2NexthopGroup(string nhg_id, string nh_ids) +{ + vector v_new = tokenize(nh_ids, ','); + + vector v_del; + for (auto i : m_nhg[nhg_id].hops) + { + auto it = find(v_new.begin(), v_new.end(), i.first); + if (it == v_new.end()) + { + if (m_nh.count(i.first)) + v_del.push_back(i.first); + else + SWSS_LOG_ERROR("L2 Nexthop %s was not created", i.first.c_str()); + } + } + + vector v_add; + if (m_nhg[nhg_id].hops.empty()) + { + v_add = v_new; + if (addL2EcmpGroup(m_nhg[nhg_id].oid) != SAI_STATUS_SUCCESS) + { + m_nhg.erase(nhg_id); + return false; + } + } + else + { + for (string i : v_new) + { + if (m_nhg[nhg_id].hops.count(i) == 0) + { + if (m_nh.count(i)) + v_add.push_back(i); + else + SWSS_LOG_ERROR("L2 Nexthop %s was not created", i.c_str()); + } + } + } + + for (string i : v_del) + { + Port tunnel; + if (getVxlanPortFromIP(m_nh[i].ip, tunnel) && tunnel.m_tunnel_id != 0) + { + delL2EcmpGroupMember(m_nhg[nhg_id].hops[i]); + m_nhg[nhg_id].hops.erase(i); + } + else + { + SWSS_LOG_ERROR("P2P Tunnel to %s is does not exist", m_nh[i].ip.c_str()); + } + } + for (string i : v_add) + { + Port tunnel; + if (getVxlanPortFromIP(m_nh[i].ip, tunnel) && tunnel.m_tunnel_id != 0) + { + addL2EcmpGroupMember(m_nhg[nhg_id].oid, tunnel.m_tunnel_id, m_nhg[nhg_id].hops[i]); + } + else + { + SWSS_LOG_ERROR("P2P Tunnel to %s is does not exist", m_nh[i].ip.c_str()); + } + } + + Port grp_port; + string port_name = getL2EcmpGroupPortName(nhg_id); + if (!gPortsOrch->getPort(port_name, grp_port)) { + gPortsOrch->addL2EcmpGroup(port_name, m_nhg[nhg_id].oid); + gPortsOrch->getPort(port_name, grp_port); + gPortsOrch->addBridgePort(grp_port); + } + + return true; +} + +string L2NhgOrch::getL2EcmpGroupPortName(const std::string& nhg_id) +{ + std::string grpPortName; + grpPortName = L2_ECMP_GROUP_PORT_PREFIX + nhg_id; + return grpPortName; +} + +bool L2NhgOrch::delL2NexthopGroup(string nhg_id) +{ + if (m_nhg.count(nhg_id)) + { + for (auto it = m_nhg[nhg_id].hops.begin(); it != m_nhg[nhg_id].hops.end();) + { + delL2EcmpGroupMember(it->second); + it = m_nhg[nhg_id].hops.erase(it); + } + Port grp_port; + string port_name = getL2EcmpGroupPortName(nhg_id); + if (gPortsOrch->getPort(port_name, grp_port)) { + bool ret = gPortsOrch->removeBridgePort(grp_port); + if (!ret) + { + SWSS_LOG_ERROR("Remove Bridge port failed for L2 ECMP Grp %s", nhg_id.c_str()); + return false; + } + gPortsOrch->removeL2EcmpGroup(grp_port); + } + delL2EcmpGroup(m_nhg[nhg_id].oid); + m_nhg.erase(nhg_id); + } + return true; +} + +bool L2NhgOrch::delL2Nexthop(string nhid) +{ + auto has_hop = [&nhid](const auto &it) -> bool { return it.second.hops.count(nhid); }; + if (m_nh.count(nhid)) + { + if (count_if(m_nhg.begin(), m_nhg.end(), has_hop) > 0) + { + for (auto it = find_if(m_nhg.begin(), m_nhg.end(), has_hop); it != m_nhg.end(); ) + { + delL2EcmpGroupMember(it->second.hops[nhid]); + it->second.hops.erase(nhid); + it = find_if(it, m_nhg.end(), has_hop); + SWSS_LOG_NOTICE("L2 Nexthop %s is still referenced in grp %s", + nhid.c_str(), it->first.c_str()); + } + } + m_nh.erase(nhid); + } + return false; +} + +sai_status_t L2NhgOrch::addL2EcmpGroup(sai_object_id_t &oid) +{ + if (m_nhg.size() >= m_max_group_count) + { + SWSS_LOG_WARN("Failed to create L2 ECMP Group: hardware limit of groups reached (%luu)", + m_max_group_count); + return 0; + } + + sai_status_t status = sai_l2_ecmp_group_api->create_l2_ecmp_group(&oid, gSwitchId, 0, NULL); + if (status != SAI_STATUS_SUCCESS) + SWSS_LOG_ERROR("Failed to create an empty L2 ECMP Group: rc: %d", status); + + return status; +} + +void L2NhgOrch::delL2EcmpGroup(sai_object_id_t l2_ecmp_group_id) +{ + sai_status_t status = sai_l2_ecmp_group_api->remove_l2_ecmp_group(l2_ecmp_group_id); + if (status != SAI_STATUS_SUCCESS) + SWSS_LOG_ERROR("Failed to delete L2 ECMP Group 0x%" PRIx64 ": rc: %d", l2_ecmp_group_id, status); +} + +sai_status_t L2NhgOrch::addL2EcmpGroupMember(sai_object_id_t l2_ecmp_group_id, + sai_object_id_t tunnel_id, sai_object_id_t &oid) +{ + vector attrs; + sai_attribute_t attr; + attr.id = SAI_L2_ECMP_GROUP_MEMBER_ATTR_L2_ECMP_GROUP_ID; + attr.value.oid = l2_ecmp_group_id; + attrs.push_back(attr); + attr.id = SAI_L2_ECMP_GROUP_MEMBER_ATTR_TUNNEL_ID; + attr.value.oid = tunnel_id; + attrs.push_back(attr); + sai_status_t status = sai_l2_ecmp_group_api->create_l2_ecmp_group_member(&oid, gSwitchId, + (uint32_t)attrs.size(), + attrs.data()); + + if (status != SAI_STATUS_SUCCESS) + SWSS_LOG_ERROR("Failed to create L2 ECMP Group member for 0x%" PRIx64 ": rc: %d", tunnel_id, status); + + return status; +} + +void L2NhgOrch::delL2EcmpGroupMember(sai_object_id_t member_id) +{ + sai_status_t status = sai_l2_ecmp_group_api->remove_l2_ecmp_group_member(member_id); + if (status != SAI_STATUS_SUCCESS) + SWSS_LOG_ERROR("Failed to delete L2 ECMP Group member 0x%" PRIx64 ": rc: %d", member_id, status); +} diff --git a/orchagent/l2nhgorch.h b/orchagent/l2nhgorch.h new file mode 100644 index 00000000000..6b786a917ca --- /dev/null +++ b/orchagent/l2nhgorch.h @@ -0,0 +1,72 @@ +/* + * Copyright 2024 GlobalLogic. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __L2NHGORCH_H__ +#define __L2NHGORCH_H__ + +#include "orch.h" +#include "observer.h" + +class L2NhgOrch : public Orch +{ +public: + L2NhgOrch(DBConnector *db, const vector &tableNames); + + ~L2NhgOrch(); + + static uint64_t m_max_group_count; + string getL2EcmpGroupPortName(const std::string& nhg_id); + /* + * Check if the given next hop group index exists. + */ + inline bool hasL2EcmpGroup(const std::string &nhg_id) const + { + SWSS_LOG_ENTER(); + return m_nhg.find(nhg_id) != m_nhg.end(); + } + +private: + + struct l2nh_info + { + string ip; + int refcnt; + }; + unordered_map m_nh; + + struct l2nhg_info + { + map hops; + sai_object_id_t oid; + }; + unordered_map m_nhg; + + void doTask(Consumer &consumer); + void doL2NhgTask(Consumer &consumer); + + bool addL2NexthopGroup(string nhg_id, string nh_ids); + bool delL2Nexthop(string nhid); + bool delL2NexthopGroup(string nhg_id); + + sai_status_t addL2EcmpGroup(sai_object_id_t &oid); + sai_status_t addL2EcmpGroupMember(sai_object_id_t l2_ecmp_group_id, + sai_object_id_t tunnel_id, sai_object_id_t &oid); + void delL2EcmpGroup(sai_object_id_t l2_ecmp_group_id); + void delL2EcmpGroupMember(sai_object_id_t member_id); +}; + + +#endif /* __L2NHGORCH_H__ */ diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 047263c93a0..af354ce8f8f 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -55,6 +55,9 @@ NatOrch *gNatOrch; PolicerOrch *gPolicerOrch; MlagOrch *gMlagOrch; IsoGrpOrch *gIsoGrpOrch; +ShlOrch *gShlOrch; +L2NhgOrch *gL2NhgOrch; +EvpnMhOrch *gEvpnMhOrch; MACsecOrch *gMacsecOrch; CoppOrch *gCoppOrch; P4Orch *gP4Orch; @@ -250,7 +253,7 @@ bool OrchDaemon::init() NvgreTunnelMapOrch *nvgre_tunnel_map_orch = new NvgreTunnelMapOrch(m_configDb, CFG_NVGRE_TUNNEL_MAP_TABLE_NAME); gDirectory.set(nvgre_tunnel_map_orch); - vector dash_vnet_tables = { + vector dash_vnet_tables = { APP_DASH_VNET_TABLE_NAME, APP_DASH_VNET_MAPPING_TABLE_NAME }; @@ -466,6 +469,24 @@ bool OrchDaemon::init() gIsoGrpOrch = new IsoGrpOrch(iso_grp_tbl_ctrs); + vector evpn_sh_tables = { + { APP_EVPN_SH_TABLE_NAME } + }; + + gShlOrch = new ShlOrch(m_applDb, evpn_sh_tables); + + vector evpn_df_tables = { + { APP_EVPN_DF_TABLE_NAME } + }; + + gEvpnMhOrch = new EvpnMhOrch(m_applDb, evpn_df_tables); + + vector l2_nexthop_group_tables = { + APP_L2_NEXTHOP_GROUP_TABLE_NAME, + }; + + gL2NhgOrch = new L2NhgOrch(m_applDb, l2_nexthop_group_tables); + // // Policy Based Hashing (PBH) orchestrator // @@ -514,6 +535,9 @@ bool OrchDaemon::init() m_orchList.push_back(gNatOrch); m_orchList.push_back(gMlagOrch); m_orchList.push_back(gIsoGrpOrch); + m_orchList.push_back(gShlOrch); + m_orchList.push_back(gL2NhgOrch); + m_orchList.push_back(gEvpnMhOrch); m_orchList.push_back(gFgNhgOrch); m_orchList.push_back(mux_st_orch); m_orchList.push_back(nvgre_tunnel_orch); diff --git a/orchagent/orchdaemon.h b/orchagent/orchdaemon.h index 2473848bf52..bea78667fbc 100644 --- a/orchagent/orchdaemon.h +++ b/orchagent/orchdaemon.h @@ -47,6 +47,9 @@ #include "srv6orch.h" #include "nvgreorch.h" #include "twamporch.h" +#include "shlorch.h" +#include "l2nhgorch.h" +#include "evpnmhorch.h" #include "dash/dashaclorch.h" #include "dash/dashorch.h" #include "dash/dashrouteorch.h" diff --git a/orchagent/port.h b/orchagent/port.h index 0ae9b97b673..a83d85d2b12 100644 --- a/orchagent/port.h +++ b/orchagent/port.h @@ -88,6 +88,7 @@ class Port VLAN, LAG, TUNNEL, + L2_ECMP_GROUP, SUBPORT, SYSTEM, UNKNOWN @@ -150,6 +151,7 @@ class Port sai_object_id_t m_lag_id = 0; sai_object_id_t m_lag_member_id = 0; sai_object_id_t m_tunnel_id = 0; + sai_object_id_t m_l2_ecmp_group_id = 0; sai_object_id_t m_ingress_acl_table_group_id = 0; sai_object_id_t m_egress_acl_table_group_id = 0; sai_object_id_t m_parent_port_id = 0; diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index fc19e250099..2acae023f1f 100644 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -2508,7 +2508,7 @@ bool PortsOrch::setHostIntfsStripTag(Port &port, sai_hostif_vlan_tag_t strip) SWSS_LOG_ENTER(); vector portv; - if(port.m_type == Port::TUNNEL) + if (port.m_type == Port::TUNNEL || port.m_type == Port::L2_ECMP_GROUP) { return true; } @@ -6092,6 +6092,20 @@ bool PortsOrch::addBridgePort(Port &port) attr.value.oid = m_default1QBridge; attrs.push_back(attr); } + else if (port.m_type == Port::L2_ECMP_GROUP) + { + attr.id = SAI_BRIDGE_PORT_ATTR_TYPE; + attr.value.s32 = SAI_BRIDGE_PORT_TYPE_L2_ECMP_GROUP; + attrs.push_back(attr); + + attr.id = SAI_BRIDGE_PORT_ATTR_L2_ECMP_GROUP_ID; + attr.value.oid = port.m_l2_ecmp_group_id; + attrs.push_back(attr); + + attr.id = SAI_BRIDGE_PORT_ATTR_BRIDGE_ID; + attr.value.oid = m_default1QBridge; + attrs.push_back(attr); + } else { SWSS_LOG_ERROR("Failed to add bridge port %s to default 1Q bridge, invalid port type %d", @@ -7219,6 +7233,30 @@ bool PortsOrch::removeTunnel(Port tunnel) return true; } +bool PortsOrch::addL2EcmpGroup(string alias, sai_object_id_t oid) +{ + SWSS_LOG_ENTER(); + + Port l2_ecmp_group(alias, Port::L2_ECMP_GROUP); + + l2_ecmp_group.m_l2_ecmp_group_id = oid; + l2_ecmp_group.m_learn_mode = SAI_BRIDGE_PORT_FDB_LEARNING_MODE_DISABLE; + m_portList[alias] = l2_ecmp_group; + + SWSS_LOG_INFO("Create a port for L2 ECMP Group %s oid:%" PRIx64, alias.c_str(), oid); + + return true; +} + +bool PortsOrch::removeL2EcmpGroup(Port l2_ecmp_group) +{ + SWSS_LOG_ENTER(); + + m_portList.erase(l2_ecmp_group.m_alias); + + return true; +} + void PortsOrch::generateQueueMap(map queuesStateVector) { if (m_isQueueMapGenerated) diff --git a/orchagent/portsorch.h b/orchagent/portsorch.h index 3ae283fb80c..c16efd69295 100644 --- a/orchagent/portsorch.h +++ b/orchagent/portsorch.h @@ -210,6 +210,10 @@ class PortsOrch : public Orch, public Subject bool addTunnel(string tunnel,sai_object_id_t, bool learning=true); bool removeTunnel(Port tunnel); + + bool addL2EcmpGroup(string alias, sai_object_id_t oid); + bool removeL2EcmpGroup(Port l2_ecmp_group); + bool addBridgePort(Port &port); bool removeBridgePort(Port &port); bool addVlanMember(Port &vlan, Port &port, string& tagging_mode, string end_point_ip = ""); diff --git a/orchagent/saihelper.cpp b/orchagent/saihelper.cpp index 1265364d977..ebb998ed520 100644 --- a/orchagent/saihelper.cpp +++ b/orchagent/saihelper.cpp @@ -73,6 +73,7 @@ sai_l2mc_group_api_t* sai_l2mc_group_api; sai_counter_api_t* sai_counter_api; sai_bfd_api_t* sai_bfd_api; sai_my_mac_api_t* sai_my_mac_api; +sai_l2_ecmp_group_api_t* sai_l2_ecmp_group_api; sai_generic_programmable_api_t* sai_generic_programmable_api; sai_dash_acl_api_t* sai_dash_acl_api; sai_dash_vnet_api_t sai_dash_vnet_api; @@ -220,6 +221,7 @@ void initSaiApi() sai_api_query(SAI_API_COUNTER, (void **)&sai_counter_api); sai_api_query(SAI_API_BFD, (void **)&sai_bfd_api); sai_api_query(SAI_API_MY_MAC, (void **)&sai_my_mac_api); + sai_api_query(SAI_API_L2_ECMP_GROUP, (void **)&sai_l2_ecmp_group_api); sai_api_query(SAI_API_GENERIC_PROGRAMMABLE, (void **)&sai_generic_programmable_api); sai_api_query((sai_api_t)SAI_API_DASH_ACL, (void**)&sai_dash_acl_api); sai_api_query((sai_api_t)SAI_API_DASH_VNET, (void**)&sai_dash_vnet_api); @@ -270,6 +272,7 @@ void initSaiApi() sai_log_set(SAI_API_COUNTER, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_BFD, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_MY_MAC, SAI_LOG_LEVEL_NOTICE); + sai_log_set(SAI_API_L2_ECMP_GROUP, SAI_LOG_LEVEL_NOTICE); 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); diff --git a/orchagent/shlorch.cpp b/orchagent/shlorch.cpp new file mode 100644 index 00000000000..e7edee822d9 --- /dev/null +++ b/orchagent/shlorch.cpp @@ -0,0 +1,306 @@ +/* + * Copyright 2024 GlobalLogic. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "shlorch.h" +#include "intfsorch.h" +#include "directory.h" +#include "vxlanorch.h" +#include "tokenize.h" + +using namespace std; +using namespace swss; + +extern PortsOrch* gPortsOrch; +extern Directory gDirectory; +extern sai_object_id_t gSwitchId; +extern sai_isolation_group_api_t* sai_isolation_group_api; +extern sai_vlan_api_t *sai_vlan_api; + +ShlOrch::ShlOrch(DBConnector *db, const vector &tableNames) : Orch(db, tableNames) +{ + SWSS_LOG_ENTER(); +} + +ShlOrch::~ShlOrch() +{ + SWSS_LOG_ENTER(); +} + +void ShlOrch::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + if (!gPortsOrch->allPortsReady()) + { + return; + } + string table_name = consumer.getTableName(); + if (table_name == APP_EVPN_SH_TABLE_NAME) + { + doShlTask(consumer); + } + else + { + SWSS_LOG_ERROR("SHL receives invalid table %s", table_name.c_str()); + } +} + +void ShlOrch::doShlTask(Consumer& consumer) +{ + SWSS_LOG_ENTER(); + + string attr_value; + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + + string op = kfvOp(t); + string key = kfvKey(t); + + size_t sep_loc = key.find(consumer.getConsumerTable()->getTableNameSeparator().c_str()); + string name = key.substr(0, sep_loc); + + if (op == SET_COMMAND) + { + for (auto i : kfvFieldsValues(t)) + { + string attr_name = fvField(i); + if (attr_name == SHL_VTEPS) + { + attr_value = fvValue(i); + break; + } + } + if (!attr_value.empty()) + { + if (addSplitHorizonList(name, attr_value)) + it = consumer.m_toSync.erase(it); + else + it++; + } + else + it = consumer.m_toSync.erase(it); + } + else if (op == DEL_COMMAND) + { + if (delSplitHorizonList(name)) + it = consumer.m_toSync.erase(it); + else + it++; + } + else + { + SWSS_LOG_ERROR("Unknown operation type %s", op.c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +shared_ptr +ShlOrch::getIsolationGroup(string name_iso_grp) +{ + SWSS_LOG_ENTER(); + shared_ptr ret = nullptr; + + auto grp = m_ShlGrps.find(name_iso_grp); + if (grp != m_ShlGrps.end()) + { + ret = grp->second; + } + + return ret; +} + +bool ShlOrch::addIsolationGroupMember(string owner_port, string member_port) +{ + SWSS_LOG_ENTER(); + + string name_iso_grp = ISO_GRP_PREFIX + owner_port; + bool status = true; + do { + auto grp = getIsolationGroup(name_iso_grp); + if (!grp) + { + auto grp = make_shared(name_iso_grp, + ISOLATION_GROUP_TYPE_BRIDGE_PORT, + "EVPN ES isolation group"); + + isolation_group_status_t result = grp->create(); + if (ISO_GRP_STATUS_SUCCESS != result) + { + status = false; + break; + } + grp->setMembers(member_port); + result = grp->setBindPorts(owner_port); + if (ISO_GRP_STATUS_SUCCESS != result) + { + if (ISO_GRP_STATUS_INVALID_PARAM == result) + { + SWSS_LOG_ERROR("Invalid param: %s", owner_port.c_str()); + } + status = false; + break; + } + m_ShlGrps[name_iso_grp] = grp; + m_ShlGrp_members[member_port].push_back(owner_port); + } + else if (grp->getType() == ISOLATION_GROUP_TYPE_BRIDGE_PORT) + { + Port port; + if (!gPortsOrch->getPort(member_port, port)) + { + SWSS_LOG_ERROR("Port %s not found", member_port.c_str()); + } + else { + grp->addMember(port); + m_ShlGrp_members[member_port].push_back(owner_port); + } + } + else + { + SWSS_LOG_ERROR("Isolation group type update to %d not permitted", + ISOLATION_GROUP_TYPE_BRIDGE_PORT); + status = false; + } + } while(0); + + return status; +} + +bool ShlOrch::delIsolationGroupMember(string owner_port, string member_port) +{ + SWSS_LOG_ENTER(); + + string name_iso_grp = ISO_GRP_PREFIX + owner_port; + auto grp = getIsolationGroup(name_iso_grp); + if (grp) + { + vector members = grp->getMembers(); + Port port; + if (!gPortsOrch->getPort(member_port, port)) + { + SWSS_LOG_ERROR("Port %s not found", port.m_alias.c_str()); + } + else { + grp->delMember(port); + } + auto it = std::find(members.begin(), members.end(), member_port); + + if (it != members.end()) { + members.erase(it); + } + + if (!members.size()) + { + grp->destroy(); + m_ShlGrps.erase(name_iso_grp); + } + + return true; + } + SWSS_LOG_ERROR("Failed to delete isolation group %s member %s", + name_iso_grp.c_str(), member_port.c_str()); + + return false; +} + +bool ShlOrch::addSplitHorizonList(string lag_port, string vteps) +{ + SWSS_LOG_ENTER(); + bool status = true; + Port port; + if (!gPortsOrch->getPort(lag_port, port)) + { + SWSS_LOG_ERROR("Failed to locate port %s", lag_port.c_str()); + return false; + } + + try { + VxlanTunnelOrch* vxlan_orch = gDirectory.get(); + + vector hosts = tokenize(vteps, ','); + for( size_t i = 0; i < hosts.size(); i++) + { + string vtep_port = vxlan_orch->getTunnelPortName(hosts.at(i), false); + hosts.at(i) = vtep_port; + + Port port_vtep; + if (gPortsOrch->getPort(vtep_port, port_vtep)) + { + if (!addIsolationGroupMember(vtep_port, lag_port)) + { + SWSS_LOG_ERROR("Failed to add member %s to isolation group %s", + lag_port.c_str(), vtep_port.c_str()); + } + } + else { + SWSS_LOG_ERROR("Unknown VTEP %s", vtep_port.c_str()); + } + } + + vector non_exist_members; + for (auto vtep_port : m_ShlGrp_members[lag_port]) + { + auto it = find(hosts.begin(), hosts.end(), vtep_port); + if(it == hosts.end()) + { + non_exist_members.push_back(vtep_port); + } + } + + for (auto vtep_port : non_exist_members) + { + if (!delIsolationGroupMember(vtep_port, lag_port)) { + SWSS_LOG_ERROR("Failed to del member %s from isolation group %s", + lag_port.c_str(), vtep_port.c_str()); + } + auto it = find(m_ShlGrp_members[lag_port].begin(), m_ShlGrp_members[lag_port].end(), vtep_port); + if(it != m_ShlGrp_members[lag_port].end()) + { + m_ShlGrp_members[lag_port].erase(it); + } + } + + } + catch (exception &e) + { + SWSS_LOG_ERROR("Exception: %s", e.what()); + status = false; + } + + return status; +} + +bool ShlOrch::delSplitHorizonList(string lag_port) +{ + SWSS_LOG_ENTER(); + bool status = true; + + for (auto vtep_port : m_ShlGrp_members[lag_port]) + { + delIsolationGroupMember(vtep_port, lag_port); + } + m_ShlGrp_members[lag_port].clear(); + + return status; +} + diff --git a/orchagent/shlorch.h b/orchagent/shlorch.h new file mode 100644 index 00000000000..e229be50d7e --- /dev/null +++ b/orchagent/shlorch.h @@ -0,0 +1,58 @@ +/* + * Copyright 2024 GlobalLogic. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __SHLORCH_H__ +#define __SHLORCH_H__ + +#include "orch.h" +#include "observer.h" + +#include + +#define SHL_VTEPS "vteps" +#define ISO_GRP_PREFIX "IsoGrp_" + +class ShlOrch : public Orch +{ +public: + ShlOrch(DBConnector *db, const vector &tableNames); + + ~ShlOrch(); + +private: + void doTask(Consumer &consumer); + + void doShlTask(Consumer& consumer); + void doDfTask(Consumer& consumer); + + shared_ptr + getIsolationGroup(string name_iso_grp); + + bool addIsolationGroupMember(string own_port, string member_port); + bool delIsolationGroupMember(string own_port, string member_port); + + bool addSplitHorizonList(string lag_name, string vteps); + bool delSplitHorizonList(string key); + + bool addDfElection(string lag_name, bool df_mode); + bool delDfElection(string key); + + map> m_ShlGrps; + map> m_ShlGrp_members; +}; + + +#endif /* __SHLORCH_H__ */ From 0f09331515a6f7c6be2a5273f51736b9b80d110e Mon Sep 17 00:00:00 2001 From: YaroslavFedoriachenkoGL Date: Sat, 6 Jul 2024 15:15:45 +0300 Subject: [PATCH 4/5] Add unittests --- tests/mock_tests/Makefile.am | 3 + .../fdborch/flush_syncd_notif_ut.cpp | 1 + tests/test_evpn_multihoming.py | 191 +++++++++++ tests/test_fdbnhg.py | 114 +++++++ tests/test_l2_nexthop_group.py | 310 ++++++++++++++++++ 5 files changed, 619 insertions(+) create mode 100644 tests/test_evpn_multihoming.py create mode 100644 tests/test_fdbnhg.py create mode 100644 tests/test_l2_nexthop_group.py diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index c3a305b1ebd..5cb49be9237 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -118,6 +118,9 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/muxorch.cpp \ $(top_srcdir)/orchagent/mlagorch.cpp \ $(top_srcdir)/orchagent/isolationgrouporch.cpp \ + $(top_srcdir)/orchagent/shlorch.cpp \ + $(top_srcdir)/orchagent/l2nhgorch.cpp \ + $(top_srcdir)/orchagent/evpnmhorch.cpp \ $(top_srcdir)/orchagent/macsecorch.cpp \ $(top_srcdir)/orchagent/lagid.cpp \ $(top_srcdir)/orchagent/bfdorch.cpp \ diff --git a/tests/mock_tests/fdborch/flush_syncd_notif_ut.cpp b/tests/mock_tests/fdborch/flush_syncd_notif_ut.cpp index eab62450f45..262a6edac99 100644 --- a/tests/mock_tests/fdborch/flush_syncd_notif_ut.cpp +++ b/tests/mock_tests/fdborch/flush_syncd_notif_ut.cpp @@ -523,6 +523,7 @@ namespace fdb_syncd_flush_test fdbData.type = "dynamic"; fdbData.origin = FDB_ORIGIN_VXLAN_ADVERTIZED; fdbData.remote_ip = "1.1.1.1"; + fdbData.nhg_id = 0; fdbData.esi = ""; fdbData.vni = 100; FdbEntry entry; diff --git a/tests/test_evpn_multihoming.py b/tests/test_evpn_multihoming.py new file mode 100644 index 00000000000..3e843634eeb --- /dev/null +++ b/tests/test_evpn_multihoming.py @@ -0,0 +1,191 @@ +from swsscommon import swsscommon +from evpn_tunnel import VxlanTunnel,VxlanEvpnHelper + +import time +import pytest + +app_df_name = "EVPN_DF_TABLE" +app_shl_name = "EVPN_SPLIT_HORIZON_TABLE" +asic_isolation_name = "ASIC_STATE:SAI_OBJECT_TYPE_ISOLATION_GROUP" +asic_brport_name = "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT" +asic_tunnel_name = "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL" +tunnel_src_ip = '10.0.0.2' +portchannel = 'PortChannel0' +vtep = '2.2.2.2' +tunnel_name = 'tunnel_2' +remote_ip_6 = "2.2.2.2" +map_name = 'map_100_100' +vlan_name = "Vlan100" +vni = "100" +nvo_name = "nvo1" + + +class TestEvpnMultihoming(object): + def setup_db(self, dvs): + self.app_db = swsscommon.DBConnector(swsscommon.APPL_DB, dvs.redis_sock, 0) + self.asic_db = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) + self.conf_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) + self.helper = VxlanEvpnHelper() + self.vxlan_obj = VxlanTunnel() + + def create_portchannel(self, portchannel_name): + self.helper.create_entry_tbl( + self.conf_db, + "PORTCHANNEL", portchannel_name, + [ + ("admin_status", "up"), + ("fast_rate", "false"), + ("lacp_key", "auto"), + ("min_links", "1"), + ("mtu", "9100"), + ], + ) + time.sleep(2) + + def create_shl_appl_table(self, portchannel_name, vteps): + self.helper.create_entry_pst( + self.app_db, + app_shl_name, portchannel_name, + [ + ("vteps", vteps), + ], + ) + time.sleep(2) + + def create_df_appl_table(self, portchannel_name, is_df): + is_df_str = "true" if is_df else "false" + self.helper.create_entry_pst( + self.app_db, + app_df_name, portchannel_name, + [ + ("df", is_df_str), + ], + ) + time.sleep(2) + + def create_vlan_member(self, vlan, interface): + self.helper.create_entry_tbl( + self.conf_db, + "VLAN_MEMBER", vlan + "|" + interface, + [ + ("tagging_mode", "untagged") + ], + ) + time.sleep(1) + + def check_shl_appl_table(self, portchannel_name, vteps): + exp_attr = { + "vteps": vteps + } + self.helper.check_object(self.app_db, app_shl_name, portchannel_name, exp_attr) + + def check_df_appl_table(self, portchannel_name, is_df): + df_str = "true" if is_df else "false" + + exp_attr = { + "df": df_str, + } + self.helper.check_object(self.app_db, app_df_name, portchannel_name, exp_attr) + + def check_tunnel_ip(self, oid, ip): + tbl = swsscommon.Table(self.asic_db, asic_tunnel_name) + (status, fvs) = tbl.get(oid) + assert status + values = dict(fvs) + assert "SAI_TUNNEL_ATTR_ENCAP_DST_IP" in values + assert values["SAI_TUNNEL_ATTR_ENCAP_DST_IP"] == ip + + def check_df_asic_table(self, is_df): + entry_created = False + + tbl = swsscommon.Table(self.asic_db, asic_brport_name) + for key in tbl.getKeys(): + (status, fvs) = tbl.get(key) + assert status + values = dict(fvs) + + if "SAI_BRIDGE_PORT_ATTR_NON_DF" not in values: + continue + + # Note: SAI attirbute value is inverted + assert values["SAI_BRIDGE_PORT_ATTR_NON_DF"] != is_df + + entry_created = True + + assert entry_created, f"{asic_brport_name}: DF-election entry not present" + + def remove_shf_appl_table(self, portchannel_name): + self.helper.delete_entry_pst( + self.app_db, + app_shl_name, portchannel_name, + ) + time.sleep(2) + + def remove_df_appl_table(self, portchannel_name): + self.helper.delete_entry_pst( + self.app_db, + app_df_name, portchannel_name, + ) + time.sleep(2) + + def check_shf_asic_db_entries_deleted(self): + tbl = swsscommon.Table(self.asic_db, asic_isolation_name) + entries = tbl.getKeys() + assert len(entries) == 0, f"{asic_isolation_name} entries not deleted from ASIC_DB" + + # Test 1 - DF-election + @pytest.mark.parametrize("is_df", [True, False]) + def test_df_election(self, dvs, testlog, is_df): + self.setup_db(dvs) + + self.create_portchannel(portchannel) + self.vxlan_obj.create_vlan1(dvs, vlan_name) + self.vxlan_obj.check_vlan_obj(dvs, vni) + self.create_vlan_member(vlan_name, portchannel) + + self.create_df_appl_table(portchannel, is_df) + self.check_df_appl_table(portchannel, is_df) + + self.check_df_asic_table(is_df) + + self.remove_df_appl_table(portchannel) + self.check_df_asic_table(is_df) + + # Test 2 - Split Horizon Filtering + def test_split_horizon_filtering(self, dvs, testlog): + self.setup_db(dvs) + + self.create_portchannel(portchannel) + self.vxlan_obj.create_vlan1(dvs, vlan_name) + self.vxlan_obj.check_vlan_obj(dvs, vni) + self.vxlan_obj.create_vxlan_tunnel(dvs, tunnel_name, tunnel_src_ip) + self.vxlan_obj.create_evpn_nvo(dvs, nvo_name, tunnel_name) + self.vxlan_obj.create_vxlan_tunnel_map(dvs, tunnel_name, map_name, vni, vlan_name) + self.vxlan_obj.create_evpn_remote_vni(dvs, vlan_name, remote_ip_6, vni) + time.sleep(1) + + self.create_shl_appl_table(portchannel, vtep) + self.check_shl_appl_table(portchannel, vtep) + + tbl = swsscommon.Table(self.asic_db, asic_isolation_name) + entries = tbl.getKeys() + assert len(entries) == 1 + (status, fvs) = tbl.get(entries[0]) + assert status + for fv in fvs: + if fv[0] == "SAI_ISOLATION_GROUP_ATTR_TYPE": + assert fv[1] == "SAI_ISOLATION_GROUP_TYPE_BRIDGE_PORT" + + tbl = swsscommon.Table(self.asic_db, asic_brport_name) + for key in tbl.getKeys(): + (status, fvs) = tbl.get(key) + assert status + values = dict(fvs) + if "SAI_BRIDGE_PORT_ATTR_ISOLATION_GROUP" not in values: + continue + assert "SAI_BRIDGE_PORT_ATTR_TUNNEL_ID" in values + oid = values["SAI_BRIDGE_PORT_ATTR_TUNNEL_ID"] + self.check_tunnel_ip(oid, vtep) + + self.remove_shf_appl_table(portchannel) + self.check_shf_asic_db_entries_deleted() diff --git a/tests/test_fdbnhg.py b/tests/test_fdbnhg.py new file mode 100644 index 00000000000..4776b410864 --- /dev/null +++ b/tests/test_fdbnhg.py @@ -0,0 +1,114 @@ +import pytest +from swsscommon import swsscommon +from typing import Dict, List + +tunnel_nh_id1 = '5' +tunnel_nh_id2 = '6' +tunnel_nh_ip1 = '10.5.5.5' +tunnel_nh_ip2 = '10.6.6.6' +tunnel_nhg_id = '536870913' + +app_fdb_name = 'VXLAN_FDB_TABLE:' +tunnel_device = 'vtep-100' +tunnel_vlan = 'Vlan100' +tunnel_remote_fdb = '12:34:55:12:34:56' +tunnel_remote_fdb_type = 'extern_learn' +tunnel_vni = '1000' +tunnel_src_ip = '1.1.1.1' +dstport = '4789' +tunnel_remote_fdb_type_static = 'static' + + +def create_fdb_nexthop(dvs, id: str, ip: str) -> Dict[str, str]: + dvs.runcmd(f"ip nexthop add id {id} via {ip} fdb") + fvs = {"remote_vtep": ip} + dvs.get_app_db().wait_for_exact_match(swsscommon.APP_L2_NEXTHOP_GROUP_TABLE_NAME, id, fvs) + + +def create_fdb_nexthop_group(dvs, id: str, nh_ids: List[str]) -> Dict[str, str]: + dvs.runcmd(f"ip nexthop add id {id} group {'/'.join(nh_ids)} fdb") + fvs = {"nexthop_group": ','.join(nh_ids)} + dvs.get_app_db().wait_for_exact_match(swsscommon.APP_L2_NEXTHOP_GROUP_TABLE_NAME, id, fvs) + + +def remove_l2_nexthop(dvs, id: str): + dvs.runcmd(f"ip nexthop del id {id}") + return dvs.get_app_db().wait_for_deleted_entry(swsscommon.APP_L2_NEXTHOP_GROUP_TABLE_NAME, id) + + +class TestFdbNhg(object): + @pytest.fixture + def setup_teardown_test(self, dvs): + dvs.setup_db() + yield + # ip nexthop objects are not disposed of otherwise upon test failure + dvs.runcmd("ip nexthop flush") + + def test_add_nexthop(self, dvs, testlog, setup_teardown_test): + create_fdb_nexthop(dvs, tunnel_nh_id1, tunnel_nh_ip1) + remove_l2_nexthop(dvs, tunnel_nh_id1) + + def test_add_nexthop_group_1_nh(self, dvs, testlog, setup_teardown_test): + create_fdb_nexthop(dvs, tunnel_nh_id1, tunnel_nh_ip1) + create_fdb_nexthop_group(dvs, tunnel_nhg_id, [tunnel_nh_id1]) + + remove_l2_nexthop(dvs, tunnel_nhg_id) + remove_l2_nexthop(dvs, tunnel_nh_id1) + + def test_add_nexthop_group_2_nh(self, dvs, testlog, setup_teardown_test): + create_fdb_nexthop(dvs, tunnel_nh_id1, tunnel_nh_ip1) + create_fdb_nexthop(dvs, tunnel_nh_id2, tunnel_nh_ip2) + create_fdb_nexthop_group(dvs, tunnel_nhg_id, [tunnel_nh_id1, tunnel_nh_id2]) + + remove_l2_nexthop(dvs, tunnel_nhg_id) + remove_l2_nexthop(dvs, tunnel_nh_id1) + remove_l2_nexthop(dvs, tunnel_nh_id2) + + def test_add_fdb_nexthop_group(self, dvs, testlog, setup_teardown_test): + create_fdb_nexthop(dvs, tunnel_nh_id1, tunnel_nh_ip1) + create_fdb_nexthop(dvs, tunnel_nh_id2, tunnel_nh_ip2) + create_fdb_nexthop_group(dvs, tunnel_nhg_id, [tunnel_nh_id1, tunnel_nh_id2]) + + dvs.runcmd(f"ip link add {tunnel_device} type vxlan id {tunnel_vni} local {tunnel_src_ip} dstport {dstport}") + dvs.runcmd(f"ip link set up {tunnel_device}") + dvs.runcmd(f"bridge fdb add {tunnel_remote_fdb} dev {tunnel_device} nhid {tunnel_nhg_id} self {tunnel_remote_fdb_type}") + + # Check in the APP DB for the FDB entry to be present APP_VXLAN_FDB_TABLE_NAME "APP_VXLAN_FDB_TABLE_NAME" + # check application database + fvs = dvs.get_app_db().wait_for_entry(app_fdb_name+tunnel_vlan, tunnel_remote_fdb) + assert len(fvs) == 3 + assert fvs.get("nexthop_group") == tunnel_nhg_id + assert fvs.get("type") == tunnel_remote_fdb_type_static + assert fvs.get("vni") == tunnel_vni + + # Remove the fdb entry, and check the APP_DB + dvs.runcmd(f"bridge fdb del {tunnel_remote_fdb} dev {tunnel_device} nhid {tunnel_nhg_id} self {tunnel_remote_fdb_type}") + intf_entries = dvs.get_app_db().wait_for_deleted_entry(app_fdb_name+tunnel_vlan, tunnel_remote_fdb) + + remove_l2_nexthop(dvs, tunnel_nhg_id) + remove_l2_nexthop(dvs, tunnel_nh_id1) + remove_l2_nexthop(dvs, tunnel_nh_id2) + + def test_del_fdb_nhg_via_nhg_del(self, dvs, testlog, setup_teardown_test): + dvs.runcmd("swssloglevel -l INFO -c fpmsyncd") + dvs.runcmd("swssloglevel -l INFO -c fdbsyncd") + create_fdb_nexthop(dvs, tunnel_nh_id1, tunnel_nh_ip1) + create_fdb_nexthop(dvs, tunnel_nh_id2, tunnel_nh_ip2) + create_fdb_nexthop_group(dvs, tunnel_nhg_id, [tunnel_nh_id1, tunnel_nh_id2]) + + dvs.runcmd(f"ip link add {tunnel_device} type vxlan id {tunnel_vni} local {tunnel_src_ip} dstport {dstport}") + dvs.runcmd(f"ip link set up {tunnel_device}") + dvs.runcmd(f"bridge fdb add {tunnel_remote_fdb} dev {tunnel_device} nhid {tunnel_nhg_id} self {tunnel_remote_fdb_type}") + + # Check in the APP DB for the FDB entry to be present APP_VXLAN_FDB_TABLE_NAME "APP_VXLAN_FDB_TABLE_NAME" + # check application database + fvs = dvs.get_app_db().wait_for_entry(app_fdb_name+tunnel_vlan, tunnel_remote_fdb) + assert len(fvs) == 3 + assert fvs.get("nexthop_group") == tunnel_nhg_id + assert fvs.get("type") == tunnel_remote_fdb_type_static + assert fvs.get("vni") == tunnel_vni + + remove_l2_nexthop(dvs, tunnel_nh_id1) + remove_l2_nexthop(dvs, tunnel_nh_id2) + dvs.get_app_db().wait_for_deleted_entry(swsscommon.APP_L2_NEXTHOP_GROUP_TABLE_NAME, tunnel_nhg_id) + dvs.get_app_db().wait_for_deleted_entry(app_fdb_name+tunnel_vlan, tunnel_remote_fdb) diff --git a/tests/test_l2_nexthop_group.py b/tests/test_l2_nexthop_group.py new file mode 100644 index 00000000000..98aacbb52c0 --- /dev/null +++ b/tests/test_l2_nexthop_group.py @@ -0,0 +1,310 @@ +import pytest +import time +from swsscommon import swsscommon +from evpn_tunnel import VxlanTunnel, VxlanEvpnHelper +from typing import List, Dict, Tuple + +tunnel_name = 'tunnel_2' +vni_id = '1000' +vlan_id = '100' +vlan = 'Vlan100' +vlanlist = [vlan_id] +vnilist = [vni_id] +map_name = 'map_1000_100' +src_ip = '6.6.6.6' +dst_ip_1 = '7.7.7.7' +dst_ip_2 = '8.8.8.8' +dst_ip_3 = '9.9.9.9' +nvo_name = 'nvo1' + +nh_id_1 = '11' +nh_id_2 = '12' +nh_id_3 = '13' + +nhg_id_1 = '123' +nhg_id_2 = '321' + +l2_ecmp_grp_tb = "ASIC_STATE:SAI_OBJECT_TYPE_L2_ECMP_GROUP" +l2_ecmp_grp_member_tb = "ASIC_STATE:SAI_OBJECT_TYPE_L2_ECMP_GROUP_MEMBER" +tunnel_tb = "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL" +bridge_port_tb = "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT" + + +def create_entry(tbl, key, pairs): + fvs = swsscommon.FieldValuePairs(pairs) + tbl.set(key, fvs) + + +def remove_entry(tbl, key): + tbl._del(key) + + +def create_entry_pst(db, table, key, pairs): + tbl = swsscommon.ProducerStateTable(db, table) + create_entry(tbl, key, pairs) + + +def remove_entry_pst(db, table, key): + tbl = swsscommon.ProducerStateTable(db, table) + remove_entry(tbl, key) + + +class TestL2NhgOrch(object): + + def get_vxlan_obj(self): + return VxlanTunnel() + + @pytest.fixture + def setup_vxlan(self, dvs): + dvs.setup_db() + vxlan_obj = self.get_vxlan_obj() + self.helper = VxlanEvpnHelper() + + dvs.runcmd("swssloglevel -l INFO -c orchagent") + vxlan_obj.fetch_exist_entries(dvs) + + vxlan_obj.create_vlan1(dvs, vlan) + vxlan_obj.create_vxlan_tunnel(dvs, tunnel_name, src_ip) + vxlan_obj.create_evpn_nvo(dvs, nvo_name, tunnel_name) + vxlan_obj.create_vxlan_tunnel_map(dvs, tunnel_name, map_name, vni_id, vlan) + vxlan_obj.create_evpn_remote_vni(dvs, vlan, dst_ip_1, vni_id) + vxlan_obj.create_evpn_remote_vni(dvs, vlan, dst_ip_2, vni_id) + vxlan_obj.create_evpn_remote_vni(dvs, vlan, dst_ip_3, vni_id) + yield + vxlan_obj.remove_evpn_remote_vni(dvs, vlan, dst_ip_3) + vxlan_obj.remove_evpn_remote_vni(dvs, vlan, dst_ip_2) + vxlan_obj.remove_evpn_remote_vni(dvs, vlan, dst_ip_1) + vxlan_obj.remove_vxlan_tunnel_map(dvs, tunnel_name, map_name, vni_id, vlan) + vxlan_obj.remove_evpn_nvo(dvs, nvo_name) + vxlan_obj.remove_vxlan_tunnel(dvs, tunnel_name) + vxlan_obj.remove_vlan(dvs, vlan_id) + time.sleep(5) + + def create_l2_nexthop(self, dvs, id: str, ip: str): + create_entry_pst(dvs.pdb, swsscommon.APP_L2_NEXTHOP_GROUP_TABLE_NAME, + id, [("remote_vtep", ip)]) + + def create_l2_nexthop_group(self, dvs, id: str, nh_ids: List[str], nh_ips: List[str]): + old_grps = dvs.get_asic_db().get_keys(l2_ecmp_grp_tb) + old_members = dvs.get_asic_db().get_keys(l2_ecmp_grp_member_tb) + old_bps = dvs.get_asic_db().get_keys(bridge_port_tb) + + create_entry_pst(dvs.pdb, swsscommon.APP_L2_NEXTHOP_GROUP_TABLE_NAME, + id, [("nexthop_group", ",".join(nh_ids))]) + + l2_ecmp_grps = dvs.get_asic_db().wait_for_n_keys(l2_ecmp_grp_tb, len(old_grps) + 1) + assert len(l2_ecmp_grps) == len(old_grps) + 1 + l2_ecmp_grp_m = dvs.get_asic_db().wait_for_n_keys(l2_ecmp_grp_member_tb, len(old_members) + len(nh_ids)) + assert len(l2_ecmp_grp_m) == len(old_members) + len(nh_ids) + bridge_ports = dvs.get_asic_db().wait_for_n_keys(bridge_port_tb, len(old_bps) + 1) + assert len(bridge_ports) == len(old_bps) + 1 + + new_nhg = list(set(l2_ecmp_grps) - set(old_grps))[0] + new_nh = list(set(l2_ecmp_grp_m) - set(old_members)) + new_bp_key = list(set(bridge_ports) - set(old_bps))[0] + + for nh in [dvs.get_asic_db().get_entry(l2_ecmp_grp_member_tb, k) for k in new_nh]: + assert nh['SAI_L2_ECMP_GROUP_MEMBER_ATTR_L2_ECMP_GROUP_ID'] == new_nhg + tun = dvs.get_asic_db().get_entry(tunnel_tb, nh['SAI_L2_ECMP_GROUP_MEMBER_ATTR_TUNNEL_ID']) + assert tun['SAI_TUNNEL_ATTR_TYPE'] == "SAI_TUNNEL_TYPE_VXLAN" + assert tun['SAI_TUNNEL_ATTR_PEER_MODE'] == "SAI_TUNNEL_PEER_MODE_P2P" + assert tun['SAI_TUNNEL_ATTR_ENCAP_SRC_IP'] == src_ip + assert tun['SAI_TUNNEL_ATTR_ENCAP_DST_IP'] in nh_ips + + new_bp = dvs.get_asic_db().get_entry(bridge_port_tb, new_bp_key) + assert new_bp['SAI_BRIDGE_PORT_ATTR_TYPE'] == 'SAI_BRIDGE_PORT_TYPE_L2_ECMP_GROUP' + assert 'SAI_BRIDGE_PORT_ATTR_L2_ECMP_GROUP_ID' in new_bp + assert new_bp['SAI_BRIDGE_PORT_ATTR_L2_ECMP_GROUP_ID'] == new_nhg + + return new_nhg, new_nh, new_bp_key + + def add_l2_nexthop_to_group(self, dvs, id: str, new_nh_ids: List[str], new_nh_ips: List[str], + nhg_oid: str, old_nh_oids: List[str]) -> Tuple[str]: + old_members = dvs.get_asic_db().get_keys(l2_ecmp_grp_member_tb) + + len_new_members = len(old_members) + (len(new_nh_ids) - len(old_nh_oids)) + + create_entry_pst(dvs.pdb, swsscommon.APP_L2_NEXTHOP_GROUP_TABLE_NAME, + id, [("nexthop_group", ",".join(new_nh_ids))]) + + l2_ecmp_grp_m = dvs.get_asic_db().wait_for_n_keys(l2_ecmp_grp_member_tb, len_new_members) + assert len(l2_ecmp_grp_m) == len_new_members + + new_nh = list(set(l2_ecmp_grp_m) - set(old_members)) + + for nh, oid in [(dvs.get_asic_db().get_entry(l2_ecmp_grp_member_tb, k), k) for k in new_nh]: + assert nh['SAI_L2_ECMP_GROUP_MEMBER_ATTR_L2_ECMP_GROUP_ID'] == nhg_oid + tun = dvs.get_asic_db().get_entry(tunnel_tb, nh['SAI_L2_ECMP_GROUP_MEMBER_ATTR_TUNNEL_ID']) + assert tun['SAI_TUNNEL_ATTR_TYPE'] == "SAI_TUNNEL_TYPE_VXLAN" + assert tun['SAI_TUNNEL_ATTR_PEER_MODE'] == "SAI_TUNNEL_PEER_MODE_P2P" + assert tun['SAI_TUNNEL_ATTR_ENCAP_SRC_IP'] == src_ip + assert tun['SAI_TUNNEL_ATTR_ENCAP_DST_IP'] in new_nh_ips + + return new_nh + + def remove_l2_nexthop_from_group(self, dvs, id: str, new_nh_ids: List[str], new_nh_ips: List[str], + nhg_oid: str, old_nh_oids: List[str]) -> Tuple[str]: + old_members = dvs.get_asic_db().get_keys(l2_ecmp_grp_member_tb) + len_new_members = len(old_members) - (len(old_nh_oids) - len(new_nh_ids)) + + create_entry_pst(dvs.pdb, swsscommon.APP_L2_NEXTHOP_GROUP_TABLE_NAME, + id, [("nexthop_group", ",".join(new_nh_ids))]) + + l2_ecmp_grp_m = dvs.get_asic_db().wait_for_n_keys(l2_ecmp_grp_member_tb, len_new_members) + assert len(l2_ecmp_grp_m) == len_new_members + + new_nh = list() + for k in dvs.get_asic_db().get_keys(l2_ecmp_grp_member_tb): + if dvs.get_asic_db().get_entry(l2_ecmp_grp_member_tb, k)['SAI_L2_ECMP_GROUP_MEMBER_ATTR_L2_ECMP_GROUP_ID'] == nhg_oid: + new_nh.append(k) + + for nh, oid in [(dvs.get_asic_db().get_entry(l2_ecmp_grp_member_tb, k), k) for k in new_nh]: + assert nh['SAI_L2_ECMP_GROUP_MEMBER_ATTR_L2_ECMP_GROUP_ID'] == nhg_oid + tun = dvs.get_asic_db().get_entry(tunnel_tb, nh['SAI_L2_ECMP_GROUP_MEMBER_ATTR_TUNNEL_ID']) + assert tun['SAI_TUNNEL_ATTR_TYPE'] == "SAI_TUNNEL_TYPE_VXLAN" + assert tun['SAI_TUNNEL_ATTR_PEER_MODE'] == "SAI_TUNNEL_PEER_MODE_P2P" + assert tun['SAI_TUNNEL_ATTR_ENCAP_SRC_IP'] == src_ip + assert tun['SAI_TUNNEL_ATTR_ENCAP_DST_IP'] in new_nh_ips + + return new_nh + + def remove_l2_nexthop_group(self, dvs, nhg_id: str, nhg_oid: str, + nh_oids: List[str], bp_oid: str): + remove_entry_pst(dvs.pdb, swsscommon.APP_L2_NEXTHOP_GROUP_TABLE_NAME, nhg_id) + dvs.get_asic_db().wait_for_deleted_entry(l2_ecmp_grp_tb, nhg_id) + for oid in nh_oids: + dvs.get_asic_db().wait_for_deleted_entry(l2_ecmp_grp_member_tb, oid) + dvs.get_asic_db().wait_for_deleted_entry(bridge_port_tb, bp_oid) + + def remove_l2_nexthop(self, dvs, nh_id: str, nh_oid: str = ""): + remove_entry_pst(dvs.pdb, swsscommon.APP_L2_NEXTHOP_GROUP_TABLE_NAME, nh_id) + if nh_oid: + dvs.get_asic_db().wait_for_deleted_entry(l2_ecmp_grp_member_tb, nh_oid) + + # ---------------------------------------------------------------------------------------- + + def test_create_l2_nexthop_group_1_nh(self, dvs, testlog, setup_vxlan): + self.create_l2_nexthop(dvs, nh_id_1, dst_ip_1) + nhg_oid, nh_oids, bp_oid = self.create_l2_nexthop_group(dvs, nhg_id_1, [nh_id_1], [dst_ip_1]) + self.remove_l2_nexthop_group(dvs, nhg_id_1, nhg_oid, nh_oids, bp_oid) + self.remove_l2_nexthop(dvs, nh_id_1) + + def test_create_l2_nexthop_group_2_nh(self, dvs, testlog, setup_vxlan): + self.create_l2_nexthop(dvs, nh_id_1, dst_ip_1) + self.create_l2_nexthop(dvs, nh_id_2, dst_ip_2) + nhg_oid, nh_oids, bp_oid = self.create_l2_nexthop_group( + dvs, nhg_id_1, [nh_id_1, nh_id_2], [dst_ip_1, dst_ip_2]) + self.remove_l2_nexthop_group(dvs, nhg_id_1, nhg_oid, nh_oids, bp_oid) + self.remove_l2_nexthop(dvs, nh_id_2) + self.remove_l2_nexthop(dvs, nh_id_1) + + def test_add_nexthop_to_l2_nhg(self, dvs, testlog, setup_vxlan): + self.create_l2_nexthop(dvs, nh_id_1, dst_ip_1) + nhg_oid, nh_oids, bp_oid = self.create_l2_nexthop_group(dvs, nhg_id_1, [nh_id_1], [dst_ip_1]) + + self.create_l2_nexthop(dvs, nh_id_2, dst_ip_2) + + nh_oids = list(nh_oids) + nh_oids.extend(self.add_l2_nexthop_to_group(dvs, nhg_id_1, [nh_id_1, nh_id_2], [dst_ip_1, dst_ip_2], + nhg_oid, nh_oids)) + + self.remove_l2_nexthop_group(dvs, nhg_id_1, nhg_oid, nh_oids, bp_oid) + self.remove_l2_nexthop(dvs, nh_id_2) + self.remove_l2_nexthop(dvs, nh_id_1) + + def test_remove_nexthop_from_l2_nhg(self, dvs, testlog, setup_vxlan): + self.create_l2_nexthop(dvs, nh_id_1, dst_ip_1) + self.create_l2_nexthop(dvs, nh_id_2, dst_ip_2) + nhg_oid, nh_oids, bp_oid = self.create_l2_nexthop_group(dvs, nhg_id_1, [nh_id_1, nh_id_2], [dst_ip_1, dst_ip_2]) + + nh_oids = self.remove_l2_nexthop_from_group(dvs, nhg_id_1, [nh_id_2], [dst_ip_2], nhg_oid, nh_oids) + + self.remove_l2_nexthop_group(dvs, nhg_id_1, nhg_oid, nh_oids, bp_oid) + self.remove_l2_nexthop(dvs, nh_id_2) + self.remove_l2_nexthop(dvs, nh_id_1) + + def test_create_multiple_l2_nhgs(self, dvs, testlog, setup_vxlan): + self.create_l2_nexthop(dvs, nh_id_1, dst_ip_1) + self.create_l2_nexthop(dvs, nh_id_2, dst_ip_2) + self.create_l2_nexthop(dvs, nh_id_3, dst_ip_3) + + nhg_oid_1, nh_oids_1, bp_oid_1 = self.create_l2_nexthop_group( + dvs, nhg_id_1, [nh_id_1, nh_id_2], [dst_ip_1, dst_ip_2]) + + nhg_oid_2, nh_oids_2, bp_oid_2 = self.create_l2_nexthop_group( + dvs, nhg_id_2, [nh_id_3], [dst_ip_3]) + + self.remove_l2_nexthop_group(dvs, nhg_id_2, nhg_oid_2, nh_oids_2, bp_oid_2) + self.remove_l2_nexthop_group(dvs, nhg_id_1, nhg_oid_1, nh_oids_1, bp_oid_1) + + self.remove_l2_nexthop(dvs, nh_id_3) + self.remove_l2_nexthop(dvs, nh_id_2) + self.remove_l2_nexthop(dvs, nh_id_1) + + def test_create_multiple_l2_nhgs_shared_nh(self, dvs, testlog, setup_vxlan): + self.create_l2_nexthop(dvs, nh_id_1, dst_ip_1) + self.create_l2_nexthop(dvs, nh_id_2, dst_ip_2) + self.create_l2_nexthop(dvs, nh_id_3, dst_ip_3) + + nhg_oid_1, nh_oids_1, bp_oid_1 = self.create_l2_nexthop_group( + dvs, nhg_id_1, [nh_id_1, nh_id_2], [dst_ip_1, dst_ip_2]) + + nhg_oid_2, nh_oids_2, bp_oid_2 = self.create_l2_nexthop_group( + dvs, nhg_id_2, [nh_id_2, nh_id_3], [dst_ip_2, dst_ip_3]) + + self.remove_l2_nexthop_group(dvs, nhg_id_2, nhg_oid_2, nh_oids_2, bp_oid_2) + self.remove_l2_nexthop_group(dvs, nhg_id_1, nhg_oid_1, nh_oids_1, bp_oid_1) + + self.remove_l2_nexthop(dvs, nh_id_3) + self.remove_l2_nexthop(dvs, nh_id_2) + self.remove_l2_nexthop(dvs, nh_id_1) + + def test_add_shared_nh_to_l2_nhgs(self, dvs, testlog, setup_vxlan): + self.create_l2_nexthop(dvs, nh_id_1, dst_ip_1) + self.create_l2_nexthop(dvs, nh_id_2, dst_ip_2) + self.create_l2_nexthop(dvs, nh_id_3, dst_ip_3) + + nhg_oid_1, nh_oids_1, bp_oid_1 = self.create_l2_nexthop_group( + dvs, nhg_id_1, [nh_id_1], [dst_ip_1]) + + nhg_oid_2, nh_oids_2, bp_oid_2 = self.create_l2_nexthop_group( + dvs, nhg_id_2, [nh_id_2], [dst_ip_2]) + + nh_oids_1 = list(nh_oids_1) + nh_oids_1.extend(self.add_l2_nexthop_to_group(dvs, nhg_id_1, [nh_id_1, nh_id_3], [dst_ip_1, dst_ip_3], + nhg_oid_1, nh_oids_1)) + + nh_oids_2 = list(nh_oids_2) + nh_oids_2.extend(self.add_l2_nexthop_to_group(dvs, nhg_id_2, [nh_id_2, nh_id_3], [dst_ip_2, dst_ip_3], + nhg_oid_2, nh_oids_2)) + + self.remove_l2_nexthop_group(dvs, nhg_id_2, nhg_oid_2, nh_oids_2, bp_oid_2) + self.remove_l2_nexthop_group(dvs, nhg_id_1, nhg_oid_1, nh_oids_1, bp_oid_1) + + self.remove_l2_nexthop(dvs, nh_id_3) + self.remove_l2_nexthop(dvs, nh_id_2) + self.remove_l2_nexthop(dvs, nh_id_1) + + def test_remove_shared_nh_from_l2_nhgs(self, dvs, testlog, setup_vxlan): + self.create_l2_nexthop(dvs, nh_id_1, dst_ip_1) + self.create_l2_nexthop(dvs, nh_id_2, dst_ip_2) + self.create_l2_nexthop(dvs, nh_id_3, dst_ip_3) + + nhg_oid_1, nh_oids_1, bp_oid_1 = self.create_l2_nexthop_group( + dvs, nhg_id_1, [nh_id_1, nh_id_3], [dst_ip_1, dst_ip_3]) + + nhg_oid_2, nh_oids_2, bp_oid_2 = self.create_l2_nexthop_group( + dvs, nhg_id_2, [nh_id_2, nh_id_3], [dst_ip_2, dst_ip_3]) + + nh_oids_1 = self.remove_l2_nexthop_from_group(dvs, nhg_id_1, [nh_id_1], [dst_ip_1], + nhg_oid_1, nh_oids_1) + + nh_oids_2 = self.remove_l2_nexthop_from_group(dvs, nhg_id_2, [nh_id_2], [dst_ip_2], + nhg_oid_2, nh_oids_2) + + self.remove_l2_nexthop_group(dvs, nhg_id_2, nhg_oid_2, nh_oids_2, bp_oid_2) + self.remove_l2_nexthop_group(dvs, nhg_id_1, nhg_oid_1, nh_oids_1, bp_oid_1) + + self.remove_l2_nexthop(dvs, nh_id_3) + self.remove_l2_nexthop(dvs, nh_id_2) + self.remove_l2_nexthop(dvs, nh_id_1) From 891d9dc26169d8a13b30f17a4c5d06c4241cf0bf Mon Sep 17 00:00:00 2001 From: RuslanValovyiGL Date: Fri, 12 Jul 2024 11:45:11 +0300 Subject: [PATCH 5/5] Fixed Worm-reboot for SHL and DF --- fpmsyncd/fpmsyncd.cpp | 194 ++++++++++++++++++++++++++++++++++++++++- fpmsyncd/routesync.cpp | 79 +++++++++++++---- fpmsyncd/routesync.h | 4 + 3 files changed, 261 insertions(+), 16 deletions(-) diff --git a/fpmsyncd/fpmsyncd.cpp b/fpmsyncd/fpmsyncd.cpp index 5e16a6a6ca4..0a8190b80f2 100644 --- a/fpmsyncd/fpmsyncd.cpp +++ b/fpmsyncd/fpmsyncd.cpp @@ -98,7 +98,19 @@ int main(int argc, char **argv) SelectableTimer eoiuCheckTimer(timespec{0, 0}); // After eoiu flags are detected, start a hold timer before starting reconciliation. SelectableTimer eoiuHoldTimer(timespec{0, 0}); - + + SelectableTimer warmStartShlTimer(timespec{0, 0}); + // Before eoiu flags detected, check them periodically. It also stop upon detection of reconciliation done. + SelectableTimer eoiuCheckShlTimer(timespec{0, 0}); + // After eoiu flags are detected, start a hold timer before starting reconciliation. + SelectableTimer eoiuHoldShlTimer(timespec{0, 0}); + + SelectableTimer warmStartDfTimer(timespec{0, 0}); + // Before eoiu flags detected, check them periodically. It also stop upon detection of reconciliation done. + SelectableTimer eoiuCheckDfTimer(timespec{0, 0}); + // After eoiu flags are detected, start a hold timer before starting reconciliation. + SelectableTimer eoiuHoldDfTimer(timespec{0, 0}); + /* * Pipeline should be flushed right away to deal with state pending * from previous try/catch iterations. @@ -152,6 +164,74 @@ int main(int argc, char **argv) sync.m_warmStartHelper.setState(WarmStart::WSDISABLED); } + /* If warm-restart feature is enabled, execute 'restoration' logic */ + bool warmStartShlEnabled = sync.m_warmStartHelperShl.checkAndStart(); + if (warmStartShlEnabled) + { + /* Obtain warm-restart timer defined for routing application */ + time_t warmRestartIval = sync.m_warmStartHelperShl.getRestartTimer(); + if (!warmRestartIval) + { + warmStartShlTimer.setInterval(timespec{DEFAULT_ROUTING_RESTART_INTERVAL, 0}); + } + else + { + warmStartShlTimer.setInterval(timespec{warmRestartIval, 0}); + } + + /* Execute restoration instruction and kick off warm-restart timer */ + if (sync.m_warmStartHelperShl.runRestoration()) + { + warmStartShlTimer.start(); + s.addSelectable(&warmStartShlTimer); + SWSS_LOG_NOTICE("Warm-Restart Shl timer started."); + } + + // Also start periodic eoiu check timer, first wait 5 seconds, then check every 1 second + eoiuCheckShlTimer.setInterval(timespec{5, 0}); + eoiuCheckShlTimer.start(); + s.addSelectable(&eoiuCheckShlTimer); + SWSS_LOG_NOTICE("Warm-Restart eoiuCheckShlTimer timer started."); + } + else + { + sync.m_warmStartHelperShl.setState(WarmStart::WSDISABLED); + } + + /* If warm-restart feature is enabled, execute 'restoration' logic */ + bool warmStartDfEnabled = sync.m_warmStartHelperDf.checkAndStart(); + if (warmStartDfEnabled) + { + /* Obtain warm-restart timer defined for routing application */ + time_t warmRestartIval = sync.m_warmStartHelperDf.getRestartTimer(); + if (!warmRestartIval) + { + warmStartDfTimer.setInterval(timespec{DEFAULT_ROUTING_RESTART_INTERVAL, 0}); + } + else + { + warmStartDfTimer.setInterval(timespec{warmRestartIval, 0}); + } + + /* Execute restoration instruction and kick off warm-restart timer */ + if (sync.m_warmStartHelperDf.runRestoration()) + { + warmStartDfTimer.start(); + s.addSelectable(&warmStartDfTimer); + SWSS_LOG_NOTICE("Warm-Restart Df timer started."); + } + + // Also start periodic eoiu check timer, first wait 5 seconds, then check every 1 second + eoiuCheckDfTimer.setInterval(timespec{5, 0}); + eoiuCheckDfTimer.start(); + s.addSelectable(&eoiuCheckDfTimer); + SWSS_LOG_NOTICE("Warm-Restart eoiuCheckDfTimer timer started."); + } + else + { + sync.m_warmStartHelperDf.setState(WarmStart::WSDISABLED); + } + while (true) { Selectable *temps; @@ -216,6 +296,108 @@ int main(int argc, char **argv) s.removeSelectable(&eoiuCheckTimer); } } + else if (temps == &warmStartShlTimer || temps == &eoiuHoldShlTimer) + { + if (temps == &warmStartShlTimer) + { + SWSS_LOG_NOTICE("Warm-Restart SHL timer expired."); + } + else + { + SWSS_LOG_NOTICE("Warm-Restart SHL EOIU hold timer expired."); + } + + sync.onWarmStartShlEnd(); + + // remove the one-shot timer. + s.removeSelectable(temps); + pipeline.flush(); + SWSS_LOG_DEBUG("Pipeline flushed"); + } + else if (temps == &eoiuCheckShlTimer) + { + if (sync.m_warmStartHelperShl.inProgress()) + { + if (eoiuFlagsSet(bgpStateTable)) + { + /* Obtain eoiu hold timer defined for bgp docker */ + uintmax_t eoiuHoldIval = WarmStart::getWarmStartTimer("eoiu_hold", "bgp"); + if (!eoiuHoldIval) + { + eoiuHoldShlTimer.setInterval(timespec{DEFAULT_EOIU_HOLD_INTERVAL, 0}); + eoiuHoldIval = DEFAULT_EOIU_HOLD_INTERVAL; + } + else + { + eoiuHoldTimer.setInterval(timespec{(time_t)eoiuHoldIval, 0}); + } + eoiuHoldShlTimer.start(); + s.addSelectable(&eoiuHoldShlTimer); + SWSS_LOG_NOTICE("Warm-Restart started SHL EOIU hold timer which is to expire in %" PRIuMAX " seconds.", eoiuHoldIval); + s.removeSelectable(&eoiuCheckTimer); + continue; + } + eoiuCheckShlTimer.setInterval(timespec{1, 0}); + // re-start eoiu check timer + eoiuCheckShlTimer.start(); + SWSS_LOG_DEBUG("Warm-Restart SHL eoiuCheckTimer restarted"); + } + else + { + s.removeSelectable(&eoiuCheckShlTimer); + } + } + else if (temps == &warmStartDfTimer || temps == &eoiuHoldDfTimer) + { + if (temps == &warmStartDfTimer) + { + SWSS_LOG_NOTICE("Warm-Restart DF timer expired."); + } + else + { + SWSS_LOG_NOTICE("Warm-Restart DF EOIU hold timer expired."); + } + + sync.onWarmStartDfEnd(); + + // remove the one-shot timer. + s.removeSelectable(temps); + pipeline.flush(); + SWSS_LOG_DEBUG("Pipeline flushed"); + } + else if (temps == &eoiuCheckDfTimer) + { + if (sync.m_warmStartHelperDf.inProgress()) + { + if (eoiuFlagsSet(bgpStateTable)) + { + /* Obtain eoiu hold timer defined for bgp docker */ + uintmax_t eoiuHoldIval = WarmStart::getWarmStartTimer("eoiu_hold", "bgp"); + if (!eoiuHoldIval) + { + eoiuHoldDfTimer.setInterval(timespec{DEFAULT_EOIU_HOLD_INTERVAL, 0}); + eoiuHoldIval = DEFAULT_EOIU_HOLD_INTERVAL; + } + else + { + eoiuHoldDfTimer.setInterval(timespec{(time_t)eoiuHoldIval, 0}); + } + eoiuHoldDfTimer.start(); + s.addSelectable(&eoiuHoldTimer); + SWSS_LOG_NOTICE("Warm-Restart started Df EOIU hold timer which is to expire in %" PRIuMAX " seconds.", eoiuHoldIval); + s.removeSelectable(&eoiuCheckDfTimer); + continue; + } + eoiuCheckDfTimer.setInterval(timespec{1, 0}); + // re-start eoiu check timer + eoiuCheckDfTimer.start(); + SWSS_LOG_DEBUG("Warm-Restart Df eoiuCheckTimer restarted"); + } + else + { + s.removeSelectable(&eoiuCheckTimer); + } + } else if (temps == &deviceMetadataTableSubscriber) { std::deque keyOpFvsQueue; @@ -289,6 +471,16 @@ int main(int argc, char **argv) pipeline.flush(); SWSS_LOG_DEBUG("Pipeline flushed"); } + else if (!warmStartShlEnabled || sync.m_warmStartHelperShl.isReconciled()) + { + pipeline.flush(); + SWSS_LOG_DEBUG("Pipeline flushed SHL"); + } + else if (!warmStartDfEnabled || sync.m_warmStartHelperDf.isReconciled()) + { + pipeline.flush(); + SWSS_LOG_DEBUG("Pipeline flushed DF"); + } } } catch (FpmLink::FpmConnectionClosedException &e) diff --git a/fpmsyncd/routesync.cpp b/fpmsyncd/routesync.cpp index fef255df91a..615e77d207c 100644 --- a/fpmsyncd/routesync.cpp +++ b/fpmsyncd/routesync.cpp @@ -82,6 +82,8 @@ RouteSync::RouteSync(RedisPipeline *pipeline) : m_shlTable(pipeline, APP_EVPN_SH_TABLE_NAME, true), m_shl_dfmodeTable(pipeline, APP_EVPN_DF_TABLE_NAME, true), m_warmStartHelper(pipeline, &m_routeTable, APP_ROUTE_TABLE_NAME, "bgp", "bgp"), + m_warmStartHelperShl(pipeline, &m_shlTable, APP_EVPN_SH_TABLE_NAME, "bgp", "bgp"), + m_warmStartHelperDf(pipeline, &m_shl_dfmodeTable, APP_EVPN_DF_TABLE_NAME, "bgp", "bgp"), m_nl_sock(NULL), m_link_cache(NULL) { m_nl_sock = nl_socket_alloc(); @@ -640,35 +642,44 @@ void RouteSync::onBridgePortMsg(struct nlmsghdr *h, int len) if (nlmsg_type == RTM_NEWTFILTER && vteps_str.empty()) return; - bool warmRestartInProgress = m_warmStartHelper.inProgress(); + bool warmRestartShlInProgress = m_warmStartHelperShl.inProgress(); + bool warmRestartDfInProgress = m_warmStartHelperDf.inProgress(); string key = ifname; if (nlmsg_type == RTM_DELTFILTER) { - if (!warmRestartInProgress) + if (!warmRestartShlInProgress) { m_shlTable.del(key); SWSS_LOG_INFO("EVPN_SPLIT_HORIZON_TABLE del msg: %s", key.c_str()); - - m_shl_dfmodeTable.del(key); - SWSS_LOG_INFO("EVPN_DF_TABLE del msg: %s", key.c_str()); } else { - SWSS_LOG_INFO("Warm-Restart mode: Receiving delete msg: %s", + SWSS_LOG_INFO("Warm-Restart mode SHL: Receiving delete msg: %s", key.c_str()); vector shl_fvVector; const KeyOpFieldsValuesTuple shl_kfv = std::make_tuple(key, DEL_COMMAND, shl_fvVector); - m_warmStartHelper.insertRefreshMap(shl_kfv); + m_warmStartHelperShl.insertRefreshMap(shl_kfv); + } + + if (!warmRestartDfInProgress) + { + m_shl_dfmodeTable.del(key); + SWSS_LOG_INFO("EVPN_DF_TABLE del msg: %s", key.c_str()); + } + else + { + SWSS_LOG_INFO("Warm-Restart mode DF: Receiving delete msg: %s", + key.c_str()); vector df_fvVector; const KeyOpFieldsValuesTuple df_kfv = std::make_tuple(key, DEL_COMMAND, df_fvVector); - m_warmStartHelper.insertRefreshMap(df_kfv); + m_warmStartHelperDf.insertRefreshMap(df_kfv); } } else @@ -681,14 +692,11 @@ void RouteSync::onBridgePortMsg(struct nlmsghdr *h, int len) FieldValueTuple df_fv("df", df ? "true" : "false"); df_fvVector.push_back(df_fv); - if (!warmRestartInProgress) + if (!warmRestartShlInProgress) { m_shlTable.set(key, shf_fvVector); - m_shl_dfmodeTable.set(key, df_fvVector); SWSS_LOG_INFO("EVPN_SPLIT_HORIZON_TABLE set msg: %s vteps %s", key.c_str(), vteps_str.c_str()); - SWSS_LOG_INFO("EVPN_DF_TABLE set msg: %s df %s", - key.c_str(), df ? "true" : "false"); } /* @@ -698,17 +706,36 @@ void RouteSync::onBridgePortMsg(struct nlmsghdr *h, int len) else { - SWSS_LOG_INFO("Warm-Restart mode: EVPN_SPLIT_HORIZON_TABLE set msg: %s vteps %s", + SWSS_LOG_INFO("Warm-Restart mode: EVPN_SPLIT_HORIZON_TABLE SHL set msg: %s vteps %s", key.c_str(), vteps_str.c_str()); const KeyOpFieldsValuesTuple shf_kfv = std::make_tuple(key, SET_COMMAND, shf_fvVector); - m_warmStartHelper.insertRefreshMap(shf_kfv); + m_warmStartHelperShl.insertRefreshMap(shf_kfv); + } + + if (!warmRestartDfInProgress) + { + m_shl_dfmodeTable.set(key, df_fvVector); + SWSS_LOG_INFO("EVPN_DF_TABLE set msg: %s df %s", + key.c_str(), df ? "true" : "false"); + } + + /* + * During Split Horizon Filtering (SHF)/Designated Forwarder (DF) status updates + * will be temporarily put on hold by the warm-restart logic. + */ + + else + { + SWSS_LOG_INFO("Warm-Restart mode: EVPN_SPLIT_HORIZON_TABLE DF set msg: %s DF - %s", + key.c_str(), df ? "true" : "false"); + const KeyOpFieldsValuesTuple df_kfv = std::make_tuple(key, SET_COMMAND, df_fvVector); - m_warmStartHelper.insertRefreshMap(df_kfv); + m_warmStartHelperDf.insertRefreshMap(df_kfv); } } @@ -1653,3 +1680,25 @@ void RouteSync::onWarmStartEnd(DBConnector& applStateDb) SWSS_LOG_NOTICE("Warm-Restart reconciliation processed."); } } + +void RouteSync::onWarmStartShlEnd() +{ + SWSS_LOG_ENTER(); + + if (m_warmStartHelperShl.inProgress()) + { + m_warmStartHelperShl.reconcile(); + SWSS_LOG_NOTICE("Warm-Restart reconciliation SHL processed."); + } +} + +void RouteSync::onWarmStartDfEnd() +{ + SWSS_LOG_ENTER(); + + if (m_warmStartHelperDf.inProgress()) + { + m_warmStartHelperDf.reconcile(); + SWSS_LOG_NOTICE("Warm-Restart reconciliation DF processed."); + } +} diff --git a/fpmsyncd/routesync.h b/fpmsyncd/routesync.h index 277728413cf..a17320cdbd3 100644 --- a/fpmsyncd/routesync.h +++ b/fpmsyncd/routesync.h @@ -50,6 +50,8 @@ class RouteSync : public NetMsg void onRouteResponse(const std::string& key, const std::vector& fieldValues); void onWarmStartEnd(swss::DBConnector& applStateDb); + void onWarmStartShlEnd(); + void onWarmStartDfEnd(); /* Mark all routes from DB with offloaded flag */ void markRoutesOffloaded(swss::DBConnector& db); @@ -65,6 +67,8 @@ class RouteSync : public NetMsg } WarmStartHelper m_warmStartHelper; + WarmStartHelper m_warmStartHelperShl; + WarmStartHelper m_warmStartHelperDf; private: /* regular route table */