diff --git a/fdbsyncd/fdbsync.cpp b/fdbsyncd/fdbsync.cpp index 5a5caf5a89..0cdcc63214 100644 --- a/fdbsyncd/fdbsync.cpp +++ b/fdbsyncd/fdbsync.cpp @@ -25,6 +25,7 @@ FdbSync::FdbSync(RedisPipeline *pipelineAppDB, DBConnector *stateDb, DBConnector m_fdbTable(pipelineAppDB, APP_VXLAN_FDB_TABLE_NAME), 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_AppRestartAssist = new AppRestartAssist(pipelineAppDB, "fdbsyncd", "swss", DEFAULT_FDBSYNC_WARMSTART_TIMER); @@ -43,7 +44,6 @@ FdbSync::~FdbSync() } } - // Check if interface entries are restored in kernel bool FdbSync::isIntfRestoreDone() { @@ -180,6 +180,69 @@ void FdbSync::processStateFdb() } } +void FdbSync::processStateMclagRemoteFdb() +{ + struct m_fdb_info info; + std::deque entries; + + m_mclagRemoteFdbStateTable.pops(entries); + + int count =0 ; + for (auto entry: entries) + { + count++; + std::string key = kfvKey(entry); + std::string op = kfvOp(entry); + + std::size_t delimiter = key.find_first_of(":"); + auto vlan_name = key.substr(0, delimiter); + auto mac_address = key.substr(delimiter+1); + + info.vid = vlan_name; + info.mac = mac_address; + + if(op == "SET") + { + info.op_type = FDB_OPER_ADD ; + } + else + { + info.op_type = FDB_OPER_DEL ; + } + + SWSS_LOG_INFO("FDBSYNCD STATE FDB updates key=%s, operation=%s\n", key.c_str(), op.c_str()); + + for (auto i : kfvFieldsValues(entry)) + { + SWSS_LOG_INFO(" FDBSYNCD STATE FDB updates : " + "FvFiels %s, FvValues: %s \n", fvField(i).c_str(), fvValue(i).c_str()); + + if(fvField(i) == "port") + { + info.port_name = fvValue(i); + } + + if(fvField(i) == "type") + { + if(fvValue(i) == "dynamic") + { + info.type = FDB_TYPE_DYNAMIC; + } + else if (fvValue(i) == "static") + { + info.type = FDB_TYPE_STATIC; + } + } + } + + if (op != "SET" && macCheckSrcDB(&info) == false) + { + continue; + } + updateMclagRemoteMac(&info); + } +} + void FdbSync::macUpdateCache(struct m_fdb_info *info) { string key = info->vid + ":" + info->mac; @@ -189,6 +252,15 @@ void FdbSync::macUpdateCache(struct m_fdb_info *info) return; } +void FdbSync::macUpdateMclagRemoteCache(struct m_fdb_info *info) +{ + string key = info->vid + ":" + info->mac; + m_mclag_remote_fdb_mac[key].port_name = info->port_name; + m_mclag_remote_fdb_mac[key].type = info->type; + + return; +} + bool FdbSync::macCheckSrcDB(struct m_fdb_info *info) { string key = info->vid + ":" + info->mac; @@ -331,6 +403,84 @@ void FdbSync::addLocalMac(string key, string op) return; } +void FdbSync::updateMclagRemoteMac (struct m_fdb_info *info) +{ + char *op; + char *type; + string port_name = ""; + string key = info->vid + ":" + info->mac; + short fdb_type; /*dynamic or static*/ + + if (info->op_type == FDB_OPER_ADD) + { + macUpdateMclagRemoteCache(info); + op = "replace"; + port_name = info->port_name; + fdb_type = info->type; + } + else + { + op = "del"; + port_name = m_mclag_remote_fdb_mac[key].port_name; + fdb_type = m_mclag_remote_fdb_mac[key].type; + m_mclag_remote_fdb_mac.erase(key); + } + + if (fdb_type == FDB_TYPE_DYNAMIC) + { + type = "dynamic"; + } + else + { + type = "static"; + } + + const std::string cmds = std::string("") + + " bridge fdb " + op + " " + info->mac + " dev " + + port_name + " master " + type + " vlan " + info->vid.substr(4); + + std::string res; + int ret = swss::exec(cmds, res); + + SWSS_LOG_INFO("cmd:%s, res=%s, ret=%d", cmds.c_str(), res.c_str(), ret); + + return; +} + +void FdbSync::updateMclagRemoteMacPort(int ifindex, int vlan, std::string mac) +{ + string key = "Vlan" + to_string(vlan) + ":" + mac; + int type = 0; + string port_name = ""; + + SWSS_LOG_INFO("Updating Intf %d, Vlan:%d MAC:%s Key %s", ifindex, vlan, mac.c_str(), key.c_str()); + + if (m_mclag_remote_fdb_mac.find(key) != m_mclag_remote_fdb_mac.end()) + { + type = m_mclag_remote_fdb_mac[key].type; + port_name = m_mclag_remote_fdb_mac[key].port_name; + SWSS_LOG_INFO(" port %s, type %d\n", port_name.c_str(), type); + + if (type == FDB_TYPE_STATIC) + { + const std::string cmds = std::string("") + + " bridge fdb replace" + " " + mac + " dev " + + port_name + " master static vlan " + to_string(vlan); + + std::string res; + int ret = swss::exec(cmds, res); + if (ret != 0) + { + SWSS_LOG_NOTICE("Failed cmd:%s, res=%s, ret=%d", cmds.c_str(), res.c_str(), ret); + return; + } + + SWSS_LOG_NOTICE("Update cmd:%s, res=%s, ret=%d", cmds.c_str(), res.c_str(), ret); + } + } + return; +} + /* * This is a special case handling where mac is learned in the ASIC. * Then MAC is learned in the Kernel, Since this mac is learned in the Kernel @@ -573,6 +723,16 @@ void FdbSync::onMsgNbr(int nlmsg_type, struct nl_object *obj) if (isVxlanIntf == false) { + if (nlmsg_type == RTM_NEWNEIGH) + { + int vid = rtnl_neigh_get_vlan(neigh); + int state = rtnl_neigh_get_state(neigh); + if (state & NUD_PERMANENT) + { + updateMclagRemoteMacPort(ifindex, vid, macStr); + } + } + if (nlmsg_type != RTM_DELNEIGH) { return; diff --git a/fdbsyncd/fdbsync.h b/fdbsyncd/fdbsync.h index 927b95dd0b..ab55b91a31 100644 --- a/fdbsyncd/fdbsync.h +++ b/fdbsyncd/fdbsync.h @@ -64,6 +64,11 @@ class FdbSync : public NetMsg return &m_fdbStateTable; } + SubscriberStateTable *getMclagRemoteFdbStateTable() + { + return &m_mclagRemoteFdbStateTable; + } + SubscriberStateTable *getCfgEvpnNvoTable() { return &m_cfgEvpnNvoTable; @@ -71,6 +76,8 @@ class FdbSync : public NetMsg void processStateFdb(); + void processStateMclagRemoteFdb(); + void processCfgEvpnNvo(); bool m_reconcileDone = false; @@ -81,6 +88,7 @@ class FdbSync : public NetMsg ProducerStateTable m_fdbTable; ProducerStateTable m_imetTable; SubscriberStateTable m_fdbStateTable; + SubscriberStateTable m_mclagRemoteFdbStateTable; AppRestartAssist *m_AppRestartAssist; SubscriberStateTable m_cfgEvpnNvoTable; @@ -89,7 +97,9 @@ class FdbSync : public NetMsg std::string port_name; short type;/*dynamic or static*/ }; - std::unordered_map m_fdb_mac; + std::unordered_map m_fdb_mac; + + std::unordered_map m_mclag_remote_fdb_mac; void macDelVxlanEntry(std::string auxkey, struct m_fdb_info *info); @@ -103,6 +113,12 @@ class FdbSync : public NetMsg void macRefreshStateDB(int vlan, std::string kmac); + void updateMclagRemoteMac(struct m_fdb_info *info); + + void updateMclagRemoteMacPort(int ifindex, int vlan, std::string mac); + + void macUpdateMclagRemoteCache(struct m_fdb_info *info); + bool checkImetExist(std::string key, uint32_t vni); bool checkDelImet(std::string key, uint32_t vni); diff --git a/fdbsyncd/fdbsyncd.cpp b/fdbsyncd/fdbsyncd.cpp index 6b9724d89e..a83b2693e1 100644 --- a/fdbsyncd/fdbsyncd.cpp +++ b/fdbsyncd/fdbsyncd.cpp @@ -86,6 +86,7 @@ int main(int argc, char **argv) netlink.dumpRequest(RTM_GETNEIGH); s.addSelectable(sync.getFdbStateTable()); + s.addSelectable(sync.getMclagRemoteFdbStateTable()); s.addSelectable(sync.getCfgEvpnNvoTable()); while (true) { @@ -95,6 +96,10 @@ int main(int argc, char **argv) { sync.processStateFdb(); } + else if (temps == (Selectable *)sync.getMclagRemoteFdbStateTable()) + { + sync.processStateMclagRemoteFdb(); + } else if (temps == (Selectable *)sync.getCfgEvpnNvoTable()) { sync.processCfgEvpnNvo(); diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index 05abdfbcec..f4317134f6 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -64,6 +64,8 @@ orchagent_SOURCES = \ chassisorch.cpp \ debugcounterorch.cpp \ natorch.cpp \ + mlagorch.cpp \ + isolationgrouporch.cpp \ muxorch.cpp \ macsecorch.cpp \ lagid.cpp diff --git a/orchagent/fdborch.cpp b/orchagent/fdborch.cpp index 229dec0b15..6094c12c05 100644 --- a/orchagent/fdborch.cpp +++ b/orchagent/fdborch.cpp @@ -11,6 +11,7 @@ #include "crmorch.h" #include "notifier.h" #include "sai_serialize.h" +#include "mlagorch.h" #include "vxlanorch.h" #include "directory.h" @@ -19,14 +20,17 @@ extern sai_fdb_api_t *sai_fdb_api; extern sai_object_id_t gSwitchId; extern PortsOrch* gPortsOrch; extern CrmOrch * gCrmOrch; +extern MlagOrch* gMlagOrch; extern Directory gDirectory; const int FdbOrch::fdborch_pri = 20; -FdbOrch::FdbOrch(DBConnector* applDbConnector, vector appFdbTables, TableConnector stateDbFdbConnector, PortsOrch *port) : +FdbOrch::FdbOrch(DBConnector* applDbConnector, vector appFdbTables, + TableConnector stateDbFdbConnector, TableConnector stateDbMclagFdbConnector, PortsOrch *port) : Orch(applDbConnector, appFdbTables), m_portsOrch(port), - m_fdbStateTable(stateDbFdbConnector.first, stateDbFdbConnector.second) + m_fdbStateTable(stateDbFdbConnector.first, stateDbFdbConnector.second), + m_mclagFdbStateTable(stateDbMclagFdbConnector.first, stateDbMclagFdbConnector.second) { for(auto it: appFdbTables) { @@ -61,6 +65,7 @@ bool FdbOrch::bake() return true; } + bool FdbOrch::storeFdbEntryState(const FdbUpdate& update) { const FdbEntry& entry = update.entry; @@ -92,8 +97,11 @@ bool FdbOrch::storeFdbEntryState(const FdbUpdate& update) */ if (port.m_bridge_port_id == it->second.bridge_port_id) { - SWSS_LOG_INFO("FdbOrch notification: mac %s is duplicate", entry.mac.to_string().c_str()); - return false; + if (it->second.origin != FDB_ORIGIN_MCLAG_ADVERTIZED) + { + SWSS_LOG_INFO("FdbOrch notification: mac %s is duplicate", entry.mac.to_string().c_str()); + return false; + } } mac_move = true; oldFdbData = it->second; @@ -112,6 +120,13 @@ bool FdbOrch::storeFdbEntryState(const FdbUpdate& update) SWSS_LOG_INFO("m_entries size=%zu mac=%s port=0x%" PRIx64, m_entries.size(), entry.mac.to_string().c_str(), m_entries[entry].bridge_port_id); + if (mac_move && (oldFdbData.origin == FDB_ORIGIN_MCLAG_ADVERTIZED)) + { + SWSS_LOG_NOTICE("fdbEvent: FdbOrch MCLAG remote to local move delete mac from state MCLAG remote fdb %s table:" + "bv_id 0x%" PRIx64, entry.mac.to_string().c_str(), entry.bv_id); + + m_mclagFdbStateTable.del(key); + } // Write to StateDb std::vector fvs; fvs.push_back(FieldValueTuple("port", portName)); @@ -140,7 +155,15 @@ bool FdbOrch::storeFdbEntryState(const FdbUpdate& update) return false; } - if (oldFdbData.origin != FDB_ORIGIN_VXLAN_ADVERTIZED) + if (oldFdbData.origin == FDB_ORIGIN_MCLAG_ADVERTIZED) + { + SWSS_LOG_NOTICE("fdbEvent: FdbOrch MCLAG remote mac %s deleted, remove from state mclag remote fdb table:" + "bv_id 0x%" PRIx64, entry.mac.to_string().c_str(), entry.bv_id); + m_mclagFdbStateTable.del(key); + } + + if ((oldFdbData.origin == FDB_ORIGIN_LEARN) || + (oldFdbData.origin == FDB_ORIGIN_PROVISIONED)) { // Remove in StateDb for non advertised mac addresses m_fdbStateTable.del(key); @@ -205,10 +228,76 @@ void FdbOrch::update(sai_fdb_event_t type, auto existing_entry = m_entries.find(update.entry); if (existing_entry != m_entries.end()) { - SWSS_LOG_INFO("FdbOrch LEARN notification: mac %s is already in bv_id 0x%" + if (existing_entry->second.origin == FDB_ORIGIN_MCLAG_ADVERTIZED) + { + // If the bp is different MOVE the MAC entry. + if (existing_entry->second.bridge_port_id != bridge_port_id) + { + Port port; + SWSS_LOG_NOTICE("FdbOrch LEARN notification: mac %s is already in bv_id 0x%" PRIx64 "with different existing-bp 0x%" PRIx64 " new-bp:0x%" PRIx64, + update.entry.mac.to_string().c_str(), entry->bv_id, existing_entry->second.bridge_port_id, bridge_port_id); + if (!m_portsOrch->getPortByBridgePortId(existing_entry->second.bridge_port_id, port)) + { + SWSS_LOG_NOTICE("FdbOrch LEARN notification: Failed to get port by bridge port ID 0x%" PRIx64, existing_entry->second.bridge_port_id); + return; + } + else + { + port.m_fdb_count--; + m_portsOrch->setPort(port.m_alias, port); + vlan.m_fdb_count--; + m_portsOrch->setPort(vlan.m_alias, vlan); + } + // Continue to add (update/move) the MAC + } + else + { + SWSS_LOG_NOTICE("FdbOrch LEARN notification: mac %s is already in bv_id 0x%" PRIx64 "with same bp 0x%" PRIx64, + update.entry.mac.to_string().c_str(), entry->bv_id, existing_entry->second.bridge_port_id); + // Continue to move the MAC as local. + + // Existing MAC entry is on same VLAN, Port with Origin MCLAG(remote), its possible after the local learn MAC in + //the HW is updated to remote from FdbOrch, Update the MAC back to local in HW so that FdbOrch and HW is Sync and aging enabled. + sai_status_t status; + sai_fdb_entry_t fdb_entry; + fdb_entry.switch_id = gSwitchId; + memcpy(fdb_entry.mac_address, entry->mac_address, sizeof(sai_mac_t)); + fdb_entry.bv_id = entry->bv_id; + sai_attribute_t attr; + vector attrs; + + attr.id = SAI_FDB_ENTRY_ATTR_TYPE; + attr.value.s32 = SAI_FDB_ENTRY_TYPE_DYNAMIC; + attrs.push_back(attr); + + attr.id = SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID; + attr.value.oid = existing_entry->second.bridge_port_id; + attrs.push_back(attr); + + for(auto itr : attrs) + { + status = sai_fdb_api->set_fdb_entry_attribute(&fdb_entry, &itr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("macUpdate-Failed for MCLAG mac attr.id=0x%x for FDB %s in 0x%" PRIx64 "on %s, rv:%d", + itr.id, update.entry.mac.to_string().c_str(), entry->bv_id, update.port.m_alias.c_str(), status); + } + } + update.add = true; + update.type = "dynamic"; + storeFdbEntryState(update); + notify(SUBJECT_TYPE_FDB_CHANGE, &update); + + return; + } + } + else + { + SWSS_LOG_INFO("FdbOrch LEARN notification: mac %s is already in bv_id 0x%" PRIx64 "existing-bp 0x%" PRIx64 "new-bp:0x%" PRIx64, update.entry.mac.to_string().c_str(), entry->bv_id, existing_entry->second.bridge_port_id, bridge_port_id); - break; + } + break; } update.add = true; @@ -269,7 +358,7 @@ void FdbOrch::update(sai_fdb_event_t type, fdbData.remote_ip = existing_entry->second.remote_ip; fdbData.esi = existing_entry->second.esi; fdbData.vni = existing_entry->second.vni; - saved_fdb_entries[update.port.m_alias].push_back( + saved_fdb_entries[update.port.m_alias].push_back( {existing_entry->first.mac, vlan.m_vlan_info.vlan_id, fdbData}); } else @@ -300,6 +389,47 @@ void FdbOrch::update(sai_fdb_event_t type, } } + // If MAC is MCLAG remote do not delete for age event, Add the MAC back.. + if (existing_entry->second.origin == FDB_ORIGIN_MCLAG_ADVERTIZED) + { + sai_status_t status; + sai_fdb_entry_t fdb_entry; + + fdb_entry.switch_id = gSwitchId; + memcpy(fdb_entry.mac_address, entry->mac_address, sizeof(sai_mac_t)); + fdb_entry.bv_id = entry->bv_id; + + sai_attribute_t attr; + vector attrs; + + attr.id = SAI_FDB_ENTRY_ATTR_TYPE; + attr.value.s32 = SAI_FDB_ENTRY_TYPE_STATIC; + attrs.push_back(attr); + + attr.id = SAI_FDB_ENTRY_ATTR_ALLOW_MAC_MOVE; + attr.value.booldata = true; + attrs.push_back(attr); + + attr.id = SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID; + attr.value.oid = existing_entry->second.bridge_port_id; + attrs.push_back(attr); + + SWSS_LOG_NOTICE("fdbEvent: MAC age event received, MAC is MCLAG origin, added back" + "to HW type %s FDB %s in %s on %s", + existing_entry->second.type.c_str(), + update.entry.mac.to_string().c_str(), vlan.m_alias.c_str(), + update.port.m_alias.c_str()); + + status = sai_fdb_api->create_fdb_entry(&fdb_entry, (uint32_t)attrs.size(), attrs.data()); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create %s FDB %s in %s on %s, rv:%d", + existing_entry->second.type.c_str(), update.entry.mac.to_string().c_str(), + vlan.m_alias.c_str(), update.port.m_alias.c_str(), status); + } + return; + } + update.add = false; if (!update.port.m_alias.empty()) { @@ -401,10 +531,8 @@ void FdbOrch::update(sai_fdb_event_t type, storeFdbEntryState(update); - for (auto observer: m_observers) - { - observer->update(SUBJECT_TYPE_FDB_CHANGE, &update); - } + notify(SUBJECT_TYPE_FDB_CHANGE, &update); + } } else if (entry->bv_id == SAI_NULL_OBJECT_ID) @@ -424,11 +552,7 @@ void FdbOrch::update(sai_fdb_event_t type, update.add = false; storeFdbEntryState(update); - - for (auto observer: m_observers) - { - observer->update(SUBJECT_TYPE_FDB_CHANGE, &update); - } + notify(SUBJECT_TYPE_FDB_CHANGE, &update); } itr = next_item; } @@ -536,6 +660,10 @@ void FdbOrch::doTask(Consumer& consumer) origin = FDB_ORIGIN_VXLAN_ADVERTIZED; } + if (table_name == APP_MCLAG_FDB_TABLE_NAME) + { + origin = FDB_ORIGIN_MCLAG_ADVERTIZED; + } auto it = consumer.m_toSync.begin(); while (it != consumer.m_toSync.end()) @@ -632,8 +760,7 @@ void FdbOrch::doTask(Consumer& consumer) } } - /* FDB type is either dynamic or static */ - assert(type == "dynamic" || type == "static"); + assert(type == "dynamic" || type == "dynamic_local" || type == "static" ); if(origin == FDB_ORIGIN_VXLAN_ADVERTIZED) { @@ -656,14 +783,48 @@ void FdbOrch::doTask(Consumer& consumer) fdbData.esi = esi; fdbData.vni = vni; if (addFdbEntry(entry, port, fdbData)) + { + if (origin == FDB_ORIGIN_MCLAG_ADVERTIZED) + { + string key = "Vlan" + to_string(vlan.m_vlan_info.vlan_id) + ":" + entry.mac.to_string(); + if (type == "dynamic_local") + { + m_mclagFdbStateTable.del(key); + } + } + + if(origin == FDB_ORIGIN_VXLAN_ADVERTIZED) + { + VxlanTunnelOrch* tunnel_orch = gDirectory.get(); + + if(!remote_ip.length()) + { + it = consumer.m_toSync.erase(it); + continue; + } + port = tunnel_orch->getTunnelPortName(remote_ip); + } + + it = consumer.m_toSync.erase(it); + } else it++; } else if (op == DEL_COMMAND) { if (removeFdbEntry(entry, origin)) + { + if (origin == FDB_ORIGIN_MCLAG_ADVERTIZED) + { + string key = "Vlan" + to_string(vlan.m_vlan_info.vlan_id) + ":" + entry.mac.to_string(); + m_mclagFdbStateTable.del(key); + SWSS_LOG_NOTICE("fdbEvent: do Task Delete MCLAG FDB from state mclag remote fdb table: " + "Mac: %s Vlan: %d ",entry.mac.to_string().c_str(), vlan.m_vlan_info.vlan_id ); + } + it = consumer.m_toSync.erase(it); + } else it++; @@ -892,10 +1053,7 @@ void FdbOrch::notifyObserversFDBFlush(Port &port, sai_object_id_t& bvid) if (!flushUpdate.entries.empty()) { - for (auto observer: m_observers) - { - observer->update(SUBJECT_TYPE_FDB_FLUSH_CHANGE, &flushUpdate); - } + notify(SUBJECT_TYPE_FDB_FLUSH_CHANGE, &flushUpdate); } } @@ -1007,6 +1165,7 @@ bool FdbOrch::addFdbEntry(const FdbEntry& entry, const string& port_name, string oldType; FdbOrigin oldOrigin = FDB_ORIGIN_INVALID ; bool macUpdate = false; + auto it = m_entries.find(entry); if (it != m_entries.end()) { @@ -1065,6 +1224,20 @@ bool FdbOrch::addFdbEntry(const FdbEntry& entry, const string& port_name, it->second.remote_ip.c_str()); } } + 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")) + { + SWSS_LOG_INFO("FdbOrch: mac=%s %s port=%s type=%s origin=%d old_origin=%d" + " old_type=%s local mac exists," + " received dynamic_local from iccpd, ignore update", + entry.mac.to_string().c_str(), vlan.m_alias.c_str(), port_name.c_str(), + fdbData.type.c_str(), fdbData.origin, oldOrigin, oldType.c_str()); + + return true; + } + } + } else /* (fdbData.origin == oldOrigin) */ { @@ -1085,15 +1258,21 @@ bool FdbOrch::addFdbEntry(const FdbEntry& entry, const string& port_name, attr.id = SAI_FDB_ENTRY_ATTR_TYPE; if (fdbData.origin == FDB_ORIGIN_VXLAN_ADVERTIZED) { - attr.value.s32 = SAI_FDB_ENTRY_TYPE_STATIC; + attr.value.s32 = SAI_FDB_ENTRY_TYPE_STATIC; + } + else if (fdbData.origin == FDB_ORIGIN_MCLAG_ADVERTIZED) + { + attr.value.s32 = (fdbData.type == "dynamic_local") ? SAI_FDB_ENTRY_TYPE_DYNAMIC : SAI_FDB_ENTRY_TYPE_STATIC; } else { attr.value.s32 = (fdbData.type == "dynamic") ? SAI_FDB_ENTRY_TYPE_DYNAMIC : SAI_FDB_ENTRY_TYPE_STATIC; } + attrs.push_back(attr); - if ((fdbData.origin == FDB_ORIGIN_VXLAN_ADVERTIZED) && (fdbData.type == "dynamic")) + if ((fdbData.origin == FDB_ORIGIN_VXLAN_ADVERTIZED) || (fdbData.origin == FDB_ORIGIN_MCLAG_ADVERTIZED) + || (fdbData.type == "dynamic")) { attr.id = SAI_FDB_ENTRY_ATTR_ALLOW_MAC_MOVE; attr.value.booldata = true; @@ -1148,7 +1327,6 @@ bool FdbOrch::addFdbEntry(const FdbEntry& entry, const string& port_name, } } - if (macUpdate) { SWSS_LOG_INFO("MAC-Update FDB %s in %s on from-%s:to-%s from-%s:to-%s origin-%d-to-%d", @@ -1201,12 +1379,21 @@ bool FdbOrch::addFdbEntry(const FdbEntry& entry, const string& port_name, FdbData storeFdbData = fdbData; storeFdbData.bridge_port_id = port.m_bridge_port_id; + // overwrite the type and origin + if ((fdbData.origin == FDB_ORIGIN_MCLAG_ADVERTIZED) && (fdbData.type == "dynamic_local")) + { + //If the MAC is dynamic_local change the origin accordingly + //MAC is added/updated as dynamic to allow aging. + storeFdbData.origin = FDB_ORIGIN_LEARN; + storeFdbData.type = "dynamic"; + } m_entries[entry] = storeFdbData; string key = "Vlan" + to_string(vlan.m_vlan_info.vlan_id) + ":" + entry.mac.to_string(); - if (fdbData.origin != FDB_ORIGIN_VXLAN_ADVERTIZED) + if ((fdbData.origin != FDB_ORIGIN_MCLAG_ADVERTIZED) && + (fdbData.origin != FDB_ORIGIN_VXLAN_ADVERTIZED)) { /* State-DB is updated only for Local Mac addresses */ // Write to StateDb @@ -1218,7 +1405,9 @@ bool FdbOrch::addFdbEntry(const FdbEntry& entry, const string& port_name, fvs.push_back(FieldValueTuple("type", fdbData.type)); m_fdbStateTable.set(key, fvs); } - else if (macUpdate && (oldOrigin != FDB_ORIGIN_VXLAN_ADVERTIZED)) + + else if (macUpdate && (oldOrigin != FDB_ORIGIN_MCLAG_ADVERTIZED) && + (oldOrigin != FDB_ORIGIN_VXLAN_ADVERTIZED)) { /* origin is FDB_ORIGIN_ADVERTIZED and it is mac-update * so delete from StateDb since we only keep local fdbs @@ -1227,6 +1416,26 @@ bool FdbOrch::addFdbEntry(const FdbEntry& entry, const string& port_name, m_fdbStateTable.del(key); } + if ((fdbData.origin == FDB_ORIGIN_MCLAG_ADVERTIZED) && (fdbData.type != "dynamic_local")) + { + std::vector fvs; + fvs.push_back(FieldValueTuple("port", port_name)); + fvs.push_back(FieldValueTuple("type", fdbData.type)); + m_mclagFdbStateTable.set(key, fvs); + + SWSS_LOG_NOTICE("fdbEvent: AddFdbEntry: Add MCLAG MAC with state mclag remote fdb table " + "Mac: %s Vlan: %d port:%s type:%s", entry.mac.to_string().c_str(), + vlan.m_vlan_info.vlan_id, port_name.c_str(), fdbData.type.c_str()); + } + else if (macUpdate && (oldOrigin == FDB_ORIGIN_MCLAG_ADVERTIZED) && + (fdbData.origin != FDB_ORIGIN_MCLAG_ADVERTIZED)) + { + SWSS_LOG_NOTICE("fdbEvent: AddFdbEntry: del MCLAG MAC from state MCLAG remote fdb table " + "Mac: %s Vlan: %d port:%s type:%s", entry.mac.to_string().c_str(), + vlan.m_vlan_info.vlan_id, port_name.c_str(), fdbData.type.c_str()); + m_mclagFdbStateTable.del(key); + } + if (!macUpdate) { gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_FDB_ENTRY); @@ -1277,20 +1486,32 @@ bool FdbOrch::removeFdbEntry(const FdbEntry& entry, FdbOrigin origin) if (fdbData.origin != origin) { - /* When mac is moved from remote to local - * BGP will delete the mac from vxlan_fdb_table - * but we should not delete this mac here since now - * mac in orchagent represents locally learnt - */ - SWSS_LOG_INFO("FdbOrch RemoveFDBEntry: mac=%s fdb origin is different; found_origin:%d delete_origin:%d", - entry.mac.to_string().c_str(), fdbData.origin, origin); + if ((origin == FDB_ORIGIN_MCLAG_ADVERTIZED) && (fdbData.origin == FDB_ORIGIN_LEARN) && + (port.m_oper_status == SAI_PORT_OPER_STATUS_DOWN) && (gMlagOrch->isMlagInterface(port.m_alias))) + { + //check if the local MCLAG port is down, if yes then continue delete the local MAC + origin = FDB_ORIGIN_LEARN; + SWSS_LOG_INFO("FdbOrch RemoveFDBEntry: mac=%s fdb del origin is MCLAG; delete local mac as port %s is down", + entry.mac.to_string().c_str(), port.m_alias.c_str()); + } + else + { - /* We may still have the mac in saved-fdb probably due to unavailability - * of bridge-port. check whether the entry is in the saved fdb, - * if so delete it from there. */ - deleteFdbEntryFromSavedFDB(entry.mac, vlan.m_vlan_info.vlan_id, origin); + /* When mac is moved from remote to local + * BGP will delete the mac from vxlan_fdb_table + * but we should not delete this mac here since now + * mac in orchagent represents locally learnt + */ + SWSS_LOG_INFO("FdbOrch RemoveFDBEntry: mac=%s fdb origin is different; found_origin:%d delete_origin:%d", + entry.mac.to_string().c_str(), fdbData.origin, origin); - return true; + /* We may still have the mac in saved-fdb probably due to unavailability + * of bridge-port. check whether the entry is in the saved fdb, + * if so delete it from there. */ + deleteFdbEntryFromSavedFDB(entry.mac, vlan.m_vlan_info.vlan_id, origin); + + return true; + } } string key = "Vlan" + to_string(vlan.m_vlan_info.vlan_id) + ":" + entry.mac.to_string(); @@ -1323,7 +1544,7 @@ bool FdbOrch::removeFdbEntry(const FdbEntry& entry, FdbOrigin origin) (void)m_entries.erase(entry); // Remove in StateDb - if (fdbData.origin != FDB_ORIGIN_VXLAN_ADVERTIZED) + if ((fdbData.origin != FDB_ORIGIN_VXLAN_ADVERTIZED) && (fdbData.origin != FDB_ORIGIN_MCLAG_ADVERTIZED)) { m_fdbStateTable.del(key); } diff --git a/orchagent/fdborch.h b/orchagent/fdborch.h index 201493f574..82611e686f 100644 --- a/orchagent/fdborch.h +++ b/orchagent/fdborch.h @@ -10,7 +10,8 @@ enum FdbOrigin FDB_ORIGIN_INVALID = 0, FDB_ORIGIN_LEARN = 1, FDB_ORIGIN_PROVISIONED = 2, - FDB_ORIGIN_VXLAN_ADVERTIZED = 4 + FDB_ORIGIN_VXLAN_ADVERTIZED = 4, + FDB_ORIGIN_MCLAG_ADVERTIZED = 8 }; struct FdbEntry @@ -80,7 +81,8 @@ class FdbOrch: public Orch, public Subject, public Observer { public: - FdbOrch(DBConnector* applDbConnector, vector appFdbTables, TableConnector stateDbFdbConnector, PortsOrch *port); + FdbOrch(DBConnector* applDbConnector, vector appFdbTables, + TableConnector stateDbFdbConnector, TableConnector stateDbMclagFdbConnector, PortsOrch *port); ~FdbOrch() { @@ -105,6 +107,7 @@ class FdbOrch: public Orch, public Subject, public Observer fdb_entries_by_port_t saved_fdb_entries; vector m_appTables; Table m_fdbStateTable; + Table m_mclagFdbStateTable; NotificationConsumer* m_flushNotificationsConsumer; NotificationConsumer* m_fdbNotificationConsumer; diff --git a/orchagent/isolationgrouporch.cpp b/orchagent/isolationgrouporch.cpp new file mode 100644 index 0000000000..0138a6ab95 --- /dev/null +++ b/orchagent/isolationgrouporch.cpp @@ -0,0 +1,749 @@ +/* + * Copyright 2019 Broadcom. The term "Broadcom" refers to Broadcom Inc. + * and/or its subsidiaries. + * + * 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 "isolationgrouporch.h" +#include "converter.h" +#include "tokenize.h" +#include "portsorch.h" + +extern sai_object_id_t gSwitchId; +extern PortsOrch *gPortsOrch; +extern sai_isolation_group_api_t* sai_isolation_group_api; +extern sai_bridge_api_t *sai_bridge_api; +extern sai_port_api_t *sai_port_api; +extern IsoGrpOrch *gIsoGrpOrch; + +IsoGrpOrch::IsoGrpOrch(vector &connectors) : Orch(connectors) +{ + SWSS_LOG_ENTER(); + gPortsOrch->attach(this); +} + +IsoGrpOrch::~IsoGrpOrch() +{ + SWSS_LOG_ENTER(); +} + +shared_ptr +IsoGrpOrch::getIsolationGroup(string name) +{ + SWSS_LOG_ENTER(); + + shared_ptr ret = nullptr; + + auto grp = m_isolationGrps.find(name); + if (grp != m_isolationGrps.end()) + { + ret = grp->second; + } + + return ret; +} + +void +IsoGrpOrch::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + if (!gPortsOrch->allPortsReady()) + { + return; + } + + string table_name = consumer.getTableName(); + if (table_name == APP_ISOLATION_GROUP_TABLE_NAME) + { + doIsoGrpTblTask(consumer); + } + else + { + SWSS_LOG_ERROR("Invalid table %s", table_name.c_str()); + } +} + +void +IsoGrpOrch::doIsoGrpTblTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + isolation_group_status_t status = ISO_GRP_STATUS_SUCCESS; + + 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); + + SWSS_LOG_DEBUG("Op:%s IsoGrp:%s", op.c_str(), name.c_str()); + + if (op == SET_COMMAND) + { + isolation_group_type_t type = ISOLATION_GROUP_TYPE_INVALID; + string descr(""); + string bind_ports(""); + string mem_ports(""); + + for (auto itp : kfvFieldsValues(t)) + { + string attr_name = to_upper(fvField(itp)); + string attr_value = fvValue(itp); + + if (attr_name == ISOLATION_GRP_DESCRIPTION) + { + descr = attr_value; + } + else if (attr_name == ISOLATION_GRP_TYPE) + { + if (ISOLATION_GRP_TYPE_PORT == attr_value) + { + type = ISOLATION_GROUP_TYPE_PORT; + } + else if (ISOLATION_GRP_TYPE_BRIDGE_PORT == attr_value) + { + type = ISOLATION_GROUP_TYPE_BRIDGE_PORT; + } + else + SWSS_LOG_WARN("Attr:%s unknown type:%d", attr_name.c_str(), type); + } + else if (attr_name == ISOLATION_GRP_PORTS) + { + bind_ports = attr_value; + } + else if (attr_name == ISOLATION_GRP_MEMBERS) + { + mem_ports = attr_value; + } + else + SWSS_LOG_WARN("unknown Attr:%s ", attr_name.c_str()); + } + + status = addIsolationGroup(name, type, descr, bind_ports, mem_ports); + if (ISO_GRP_STATUS_SUCCESS == status) + { + auto grp = getIsolationGroup(name); + IsolationGroupUpdate update = {grp.get(), true}; + grp->notifyObservers(SUBJECT_TYPE_ISOLATION_GROUP_CHANGE, &update); + + grp->attach(this); + } + } + else + { + auto grp = getIsolationGroup(name); + if (grp) + { + grp->detach(this); + + /* Send a notification and see if observers want to detach */ + IsolationGroupUpdate update = {grp.get(), false}; + grp->notifyObservers(SUBJECT_TYPE_ISOLATION_GROUP_CHANGE, &update); + + // Finally delete it if it + status = delIsolationGroup(name); + } + } + + if (status != ISO_GRP_STATUS_RETRY) + { + it = consumer.m_toSync.erase(it); + } + else + { + it++; + } + } +} + +isolation_group_status_t +IsoGrpOrch::addIsolationGroup(string name, isolation_group_type_t type, string descr, string bindPorts, string memPorts) +{ + SWSS_LOG_ENTER(); + + isolation_group_status_t status = ISO_GRP_STATUS_SUCCESS; + + // Add Or Update + auto grp = getIsolationGroup(name); + if (!grp) + { + // Add Case + auto grp = make_shared(name, type, descr); + + status = grp->create(); + if (ISO_GRP_STATUS_SUCCESS != status) + { + return status; + } + grp->setMembers(memPorts); + grp->setBindPorts(bindPorts); + this->m_isolationGrps[name] = grp; + } + else if (grp->getType() == type) + { + grp->m_description = descr; + grp->setMembers(memPorts); + grp->setBindPorts(bindPorts); + } + else + { + SWSS_LOG_ERROR("Isolation group type update to %d not permitted", type); + status = ISO_GRP_STATUS_FAIL; + } + + return status; +} + +isolation_group_status_t +IsoGrpOrch::delIsolationGroup(string name) +{ + SWSS_LOG_ENTER(); + + auto grp = m_isolationGrps.find(name); + if (grp != m_isolationGrps.end()) + { + grp->second->destroy(); + m_isolationGrps.erase(name); + } + + return ISO_GRP_STATUS_SUCCESS; +} + + +void +IsoGrpOrch::update(SubjectType type, void *cntx) +{ + SWSS_LOG_ENTER(); + + if (type != SUBJECT_TYPE_BRIDGE_PORT_CHANGE) + { + return; + } + + for (auto kv : m_isolationGrps) + { + kv.second->update(type, cntx); + } +} + + +isolation_group_status_t +IsolationGroup::create() +{ + SWSS_LOG_ENTER(); + sai_attribute_t attr; + + attr.id = SAI_ISOLATION_GROUP_ATTR_TYPE; + if (ISOLATION_GROUP_TYPE_BRIDGE_PORT == m_type) + { + attr.value.s32 = SAI_ISOLATION_GROUP_TYPE_BRIDGE_PORT; + } + else + { + attr.value.s32 = SAI_ISOLATION_GROUP_TYPE_PORT; + } + + sai_status_t status = sai_isolation_group_api->create_isolation_group(&m_oid, gSwitchId, 1, &attr); + if (SAI_STATUS_SUCCESS != status) + { + SWSS_LOG_ERROR("Error %d creating isolation group %s", status, m_name.c_str()); + return ISO_GRP_STATUS_FAIL; + } + else + { + SWSS_LOG_NOTICE("Isolation group %s has oid 0x%" PRIx64 , m_name.c_str(), m_oid); + } + + return ISO_GRP_STATUS_SUCCESS; +} + +isolation_group_status_t +IsolationGroup::destroy() +{ + SWSS_LOG_ENTER(); + sai_attribute_t attr; + + // Remove all bindings + attr.value.oid = SAI_NULL_OBJECT_ID; + for (auto p : m_bind_ports) + { + Port port; + gPortsOrch->getPort(p, port); + if (ISOLATION_GROUP_TYPE_BRIDGE_PORT == m_type) + { + attr.id = SAI_BRIDGE_PORT_ATTR_ISOLATION_GROUP; + if (SAI_STATUS_SUCCESS != sai_bridge_api->set_bridge_port_attribute(port.m_bridge_port_id, &attr)) + { + SWSS_LOG_ERROR("Unable to del SAI_BRIDGE_PORT_ATTR_ISOLATION_GROUP from %s", p.c_str()); + } + else + { + SWSS_LOG_NOTICE("SAI_BRIDGE_PORT_ATTR_ISOLATION_GROUP removed from %s", p.c_str()); + } + } + else if (ISOLATION_GROUP_TYPE_PORT == m_type) + { + attr.id = SAI_PORT_ATTR_ISOLATION_GROUP; + if (SAI_STATUS_SUCCESS != sai_port_api->set_port_attribute( + (port.m_type == Port::PHY ? port.m_port_id : port.m_lag_id), + &attr)) + { + SWSS_LOG_ERROR("Unable to del SAI_PORT_ATTR_ISOLATION_GROUP from %s", p.c_str()); + } + else + { + SWSS_LOG_NOTICE("SAI_PORT_ATTR_ISOLATION_GROUP removed from %s", p.c_str()); + } + } + } + m_bind_ports.clear(); + m_pending_bind_ports.clear(); + + // Remove all members + for (auto &kv : m_members) + { + if (SAI_STATUS_SUCCESS != sai_isolation_group_api->remove_isolation_group_member(kv.second)) + { + SWSS_LOG_ERROR("Unable to delete isolation group member 0x%" PRIx64 " from %s: 0x%" PRIx64 " for port %s", + kv.second, + m_name.c_str(), + m_oid, + kv.first.c_str()); + } + else + { + SWSS_LOG_NOTICE("Isolation group member 0x%" PRIx64 " deleted from %s: 0x%" PRIx64 " for port %s", + kv.second, + m_name.c_str(), + m_oid, + kv.first.c_str()); + } + } + m_members.clear(); + + sai_status_t status = sai_isolation_group_api->remove_isolation_group(m_oid); + if (SAI_STATUS_SUCCESS != status) + { + SWSS_LOG_ERROR("Unable to delete isolation group %s with oid 0x%" PRIx64 , m_name.c_str(), m_oid); + } + else + { + SWSS_LOG_NOTICE("Isolation group %s with oid 0x%" PRIx64 " deleted", m_name.c_str(), m_oid); + } + m_oid = SAI_NULL_OBJECT_ID; + + return ISO_GRP_STATUS_SUCCESS; +} + +isolation_group_status_t +IsolationGroup::addMember(Port &port) +{ + SWSS_LOG_ENTER(); + sai_object_id_t port_id = SAI_NULL_OBJECT_ID; + + if (m_type == ISOLATION_GROUP_TYPE_BRIDGE_PORT) + { + port_id = port.m_bridge_port_id; + } + else if (m_type == ISOLATION_GROUP_TYPE_PORT) + { + port_id = (port.m_type == Port::PHY ? port.m_port_id : port.m_lag_id); + } + + if (SAI_NULL_OBJECT_ID == port_id) + { + SWSS_LOG_NOTICE("Port %s not ready for for isolation group %s of type %d", + port.m_alias.c_str(), + m_name.c_str(), + m_type); + + m_pending_members.push_back(port.m_alias); + + return ISO_GRP_STATUS_SUCCESS; + } + + if (m_members.find(port.m_alias) != m_members.end()) + { + SWSS_LOG_DEBUG("Port %s: 0x%" PRIx64 "already a member of %s", port.m_alias.c_str(), port_id, m_name.c_str()); + } + else + { + sai_object_id_t mem_id = SAI_NULL_OBJECT_ID; + sai_attribute_t mem_attr[2]; + sai_status_t status = SAI_STATUS_SUCCESS; + + mem_attr[0].id = SAI_ISOLATION_GROUP_MEMBER_ATTR_ISOLATION_GROUP_ID; + mem_attr[0].value.oid = m_oid; + mem_attr[1].id = SAI_ISOLATION_GROUP_MEMBER_ATTR_ISOLATION_OBJECT; + mem_attr[1].value.oid = port_id; + + status = sai_isolation_group_api->create_isolation_group_member(&mem_id, gSwitchId, 2, mem_attr); + if (SAI_STATUS_SUCCESS != status) + { + SWSS_LOG_ERROR("Unable to add %s: 0x%" PRIx64 " as member of %s:0x%" PRIx64 , port.m_alias.c_str(), port_id, + m_name.c_str(), m_oid); + return ISO_GRP_STATUS_FAIL; + } + else + { + m_members[port.m_alias] = mem_id; + SWSS_LOG_NOTICE("Port %s: 0x%" PRIx64 " added as member of %s: 0x%" PRIx64 "with oid 0x%" PRIx64, + port.m_alias.c_str(), + port_id, + m_name.c_str(), + m_oid, + mem_id); + } + } + + return ISO_GRP_STATUS_SUCCESS; +} + +isolation_group_status_t +IsolationGroup::delMember(Port &port, bool do_fwd_ref) +{ + SWSS_LOG_ENTER(); + + if (m_members.find(port.m_alias) == m_members.end()) + { + auto node = find(m_pending_members.begin(), m_pending_members.end(), port.m_alias); + if (node != m_pending_members.end()) + { + m_pending_members.erase(node); + } + + return ISO_GRP_STATUS_SUCCESS; + } + + sai_object_id_t mem_id = m_members[port.m_alias]; + sai_status_t status = SAI_STATUS_SUCCESS; + + status = sai_isolation_group_api->remove_isolation_group_member(mem_id); + if (SAI_STATUS_SUCCESS != status) + { + SWSS_LOG_ERROR("Unable to delete isolation group member 0x%" PRIx64 " for port %s and iso group %s 0x%" PRIx64 , + mem_id, + port.m_alias.c_str(), + m_name.c_str(), + m_oid); + + return ISO_GRP_STATUS_FAIL; + } + else + { + SWSS_LOG_NOTICE("Deleted isolation group member 0x%" PRIx64 "for port %s and iso group %s 0x%" PRIx64 , + mem_id, + port.m_alias.c_str(), + m_name.c_str(), + m_oid); + + m_members.erase(port.m_alias); + } + + if (do_fwd_ref) + { + m_pending_members.push_back(port.m_alias); + } + + return ISO_GRP_STATUS_SUCCESS; +} + +isolation_group_status_t +IsolationGroup::setMembers(string ports) +{ + SWSS_LOG_ENTER(); + auto port_list = tokenize(ports, ','); + set portList(port_list.begin(), port_list.end()); + vector old_members = m_pending_members; + + for (auto mem : m_members) + { + old_members.emplace_back(mem.first); + } + + for (auto alias : portList) + { + if ((0 == alias.find("Ethernet")) || (0 == alias.find("PortChannel"))) + { + auto iter = find(old_members.begin(), old_members.end(), alias); + if (iter != old_members.end()) + { + SWSS_LOG_NOTICE("Port %s already part of %s. No change", alias.c_str(), m_name.c_str()); + old_members.erase(iter); + } + else + { + Port port; + if (!gPortsOrch->getPort(alias, port)) + { + SWSS_LOG_NOTICE("Port %s not found. Added it to m_pending_members", alias.c_str()); + m_pending_members.emplace_back(alias); + continue; + } + addMember(port); + } + } + else + { + SWSS_LOG_ERROR("Port %s not supported", alias.c_str()); + continue; + } + } + + // Remove all the ports which are no longer needed + for (auto alias : old_members) + { + Port port; + if (!gPortsOrch->getPort(alias, port)) + { + SWSS_LOG_ERROR("Port %s not found", alias.c_str()); + m_pending_members.erase(find(m_pending_members.begin(), m_pending_members.end(), port.m_alias)); + } + else + { + delMember(port); + } + } + + return ISO_GRP_STATUS_SUCCESS; +} + +isolation_group_status_t +IsolationGroup::bind(Port &port) +{ + SWSS_LOG_ENTER(); + sai_attribute_t attr; + sai_status_t status = SAI_STATUS_SUCCESS; + + if (find(m_bind_ports.begin(), m_bind_ports.end(), port.m_alias) != m_bind_ports.end()) + { + SWSS_LOG_NOTICE("isolation group %s of type %d already bound to Port %s", + m_name.c_str(), + m_type, + port.m_alias.c_str()); + + return ISO_GRP_STATUS_SUCCESS; + } + + attr.value.oid = m_oid; + if (m_type == ISOLATION_GROUP_TYPE_BRIDGE_PORT) + { + if (port.m_bridge_port_id != SAI_NULL_OBJECT_ID) + { + attr.id = SAI_BRIDGE_PORT_ATTR_ISOLATION_GROUP; + status = sai_bridge_api->set_bridge_port_attribute(port.m_bridge_port_id, &attr); + if (SAI_STATUS_SUCCESS != status) + { + SWSS_LOG_ERROR("Unable to set attribute %d value 0x%" PRIx64 "to %s", + attr.id, + attr.value.oid, + port.m_alias.c_str()); + } + else + { + m_bind_ports.push_back(port.m_alias); + } + } + else + { + m_pending_bind_ports.push_back(port.m_alias); + SWSS_LOG_NOTICE("Port %s saved in pending bind ports for isolation group %s of type %d", + port.m_alias.c_str(), + m_name.c_str(), + m_type); + } + } + else if ((m_type == ISOLATION_GROUP_TYPE_PORT) && (port.m_type == Port::PHY)) + { + if (port.m_port_id != SAI_NULL_OBJECT_ID) + { + attr.id = SAI_PORT_ATTR_ISOLATION_GROUP; + status = sai_port_api->set_port_attribute(port.m_port_id, &attr); + if (SAI_STATUS_SUCCESS != status) + { + SWSS_LOG_ERROR("Unable to set attribute %d value 0x%" PRIx64 "to %s", + attr.id, + attr.value.oid, + port.m_alias.c_str()); + } + else + { + m_bind_ports.push_back(port.m_alias); + } + } + else + { + m_pending_bind_ports.push_back(port.m_alias); + SWSS_LOG_NOTICE("Port %s saved in pending bind ports for isolation group %s of type %d", + port.m_alias.c_str(), + m_name.c_str(), + m_type); + } + } + else + { + SWSS_LOG_ERROR("Invalid attribute type %d ", m_type); + return ISO_GRP_STATUS_INVALID_PARAM; + } + + return ISO_GRP_STATUS_SUCCESS; +} + +isolation_group_status_t +IsolationGroup::unbind(Port &port, bool do_fwd_ref) +{ + SWSS_LOG_ENTER(); + sai_attribute_t attr; + sai_status_t status = SAI_STATUS_SUCCESS; + + if (find(m_bind_ports.begin(), m_bind_ports.end(), port.m_alias) == m_bind_ports.end()) + { + auto node = find(m_pending_bind_ports.begin(), m_pending_bind_ports.end(), port.m_alias); + if (node != m_pending_bind_ports.end()) + { + m_pending_bind_ports.erase(node); + } + + return ISO_GRP_STATUS_SUCCESS; + } + + attr.value.oid = SAI_NULL_OBJECT_ID; + if (m_type == ISOLATION_GROUP_TYPE_BRIDGE_PORT) + { + attr.id = SAI_BRIDGE_PORT_ATTR_ISOLATION_GROUP; + status = sai_bridge_api->set_bridge_port_attribute(port.m_bridge_port_id, &attr); + } + else if ((m_type == ISOLATION_GROUP_TYPE_PORT) && (port.m_type == Port::PHY)) + { + attr.id = SAI_PORT_ATTR_ISOLATION_GROUP; + status = sai_port_api->set_port_attribute(port.m_port_id, &attr); + } + else + { + return ISO_GRP_STATUS_INVALID_PARAM; + } + + if (SAI_STATUS_SUCCESS != status) + { + SWSS_LOG_ERROR("Unable to set attribute %d value 0x%" PRIx64 "to %s", attr.id, attr.value.oid, port.m_alias.c_str()); + } + else + { + m_bind_ports.erase(find(m_bind_ports.begin(), m_bind_ports.end(), port.m_alias)); + } + + if (do_fwd_ref) + { + m_pending_bind_ports.push_back(port.m_alias); + } + + return ISO_GRP_STATUS_SUCCESS; +} + +isolation_group_status_t +IsolationGroup::setBindPorts(string ports) +{ + SWSS_LOG_ENTER(); + vector old_bindports = m_pending_bind_ports; + auto port_list = tokenize(ports, ','); + set portList(port_list.begin(), port_list.end()); + + 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"))) + { + auto iter = find(old_bindports.begin(), old_bindports.end(), alias); + if (iter != old_bindports.end()) + { + SWSS_LOG_NOTICE("%s is already bound to %s", m_name.c_str(), alias.c_str()); + old_bindports.erase(iter); + } + else + { + Port port; + if (!gPortsOrch->getPort(alias, port)) + { + SWSS_LOG_NOTICE("Port %s not found. Added it to m_pending_bind_ports", alias.c_str()); + m_pending_bind_ports.emplace_back(alias); + return ISO_GRP_STATUS_INVALID_PARAM; + } + bind(port); + } + } + else + { + return ISO_GRP_STATUS_INVALID_PARAM; + } + } + + // Remove all the ports which are no longer needed + for (auto alias : old_bindports) + { + Port port; + if (!gPortsOrch->getPort(alias, port)) + { + SWSS_LOG_ERROR("Port %s not found", alias.c_str()); + m_pending_bind_ports.erase(find(m_pending_bind_ports.begin(), m_pending_bind_ports.end(), port.m_alias)); + } + else + { + unbind(port); + } + } + + return ISO_GRP_STATUS_SUCCESS; +} + +void +IsolationGroup::update(SubjectType, void *cntx) +{ + PortUpdate *update = static_cast(cntx); + Port &port = update->port; + + if (update->add) + { + auto mem_node = find(m_pending_members.begin(), m_pending_members.end(), port.m_alias); + if (mem_node != m_pending_members.end()) + { + m_pending_members.erase(mem_node); + addMember(port); + } + + auto bind_node = find(m_pending_bind_ports.begin(), m_pending_bind_ports.end(), port.m_alias); + if (bind_node != m_pending_bind_ports.end()) + { + m_pending_bind_ports.erase(bind_node); + bind(port); + } + } + else + { + auto bind_node = find(m_bind_ports.begin(), m_bind_ports.end(), port.m_alias); + if (bind_node != m_bind_ports.end()) + { + unbind(port, true); + } + + auto mem_node = m_members.find(port.m_alias); + if (mem_node != m_members.end()) + { + delMember(port, true); + } + } +} diff --git a/orchagent/isolationgrouporch.h b/orchagent/isolationgrouporch.h new file mode 100644 index 0000000000..7bf8ef4c14 --- /dev/null +++ b/orchagent/isolationgrouporch.h @@ -0,0 +1,142 @@ +/* + * Copyright 2019 Broadcom. The term "Broadcom" refers to Broadcom Inc. + * and/or its subsidiaries. + * + * 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 __ISOLATIONGROUPORCH_H__ +#define __ISOLATIONGROUPORCH_H__ + +#include "orch.h" +#include "port.h" +#include "observer.h" + +#define ISOLATION_GRP_DESCRIPTION "DESCRIPTION" +#define ISOLATION_GRP_TYPE "TYPE" +#define ISOLATION_GRP_PORTS "PORTS" +#define ISOLATION_GRP_MEMBERS "MEMBERS" +#define ISOLATION_GRP_TYPE_PORT "port" +#define ISOLATION_GRP_TYPE_BRIDGE_PORT "bridge-port" + +typedef enum IsolationGroupType +{ + ISOLATION_GROUP_TYPE_INVALID, + ISOLATION_GROUP_TYPE_PORT, + ISOLATION_GROUP_TYPE_BRIDGE_PORT +} isolation_group_type_t; + +typedef enum IsolationGroupStatus +{ + ISO_GRP_STATUS_RETRY = -100, + ISO_GRP_STATUS_FAIL, + ISO_GRP_STATUS_INVALID_PARAM, + ISO_GRP_STATUS_SUCCESS = 0 +} isolation_group_status_t; + +class IsolationGroup: public Observer, public Subject +{ +public: + string m_description; + + IsolationGroup(string name, isolation_group_type_t type = ISOLATION_GROUP_TYPE_PORT, string description=""): + m_name(name), + m_description(description), + m_type(type), + m_oid(SAI_NULL_OBJECT_ID) + { + } + + // Create Isolation group in SAI + isolation_group_status_t create(); + + // Delete Isolation group in SAI + isolation_group_status_t destroy(); + + // Add Isolation group member + isolation_group_status_t addMember(Port &port); + + // Delete Isolation group member + isolation_group_status_t delMember(Port &port, bool do_fwd_ref=false); + + // Set Isolation group members to the input. May involve adding or deleting members + isolation_group_status_t setMembers(string ports); + + // Apply the Isolation group to all linked ports + isolation_group_status_t bind(Port &port); + + // Remove the Isolation group from all linked ports + isolation_group_status_t unbind(Port &port, bool do_fwd_ref=false); + + // Set Isolation group binding to the input. May involve bind + isolation_group_status_t setBindPorts(string ports); + + void update(SubjectType, void *); + + isolation_group_type_t + getType() + { + return m_type; + } + + void notifyObservers(SubjectType type, void *cntx) + { + this->notify(type, cntx); + } + +protected: + string m_name; + isolation_group_type_t m_type; + sai_object_id_t m_oid; + map m_members; // Members Name -> Member OID + vector m_bind_ports; // Ports in which this Iso Group is applied. + vector m_pending_members; + vector m_pending_bind_ports; +}; + +class IsoGrpOrch : public Orch, public Observer +{ +public: + IsoGrpOrch(vector &connectors); + + ~IsoGrpOrch(); + + shared_ptr + getIsolationGroup(string name); + + isolation_group_status_t + addIsolationGroup(string name, isolation_group_type_t type, string descr, string bindPorts, string memPorts); + + isolation_group_status_t + delIsolationGroup(string name); + + void update(SubjectType, void *); + +private: + void + doTask(Consumer &consumer); + + void + doIsoGrpTblTask(Consumer &consumer); + + map> m_isolationGrps; +}; + +struct IsolationGroupUpdate +{ + IsolationGroup *group; + bool add; +}; + + +#endif /* __ISOLATIONGROUPORCH_H__ */ diff --git a/orchagent/mlagorch.cpp b/orchagent/mlagorch.cpp new file mode 100644 index 0000000000..81fd169fe4 --- /dev/null +++ b/orchagent/mlagorch.cpp @@ -0,0 +1,250 @@ +/* + * Copyright 2019 Broadcom. The term "Broadcom" refers to Broadcom Inc. + * and/or its subsidiaries. + * + * 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 "portsorch.h" +#include "mlagorch.h" + +using namespace std; +using namespace swss; + +extern PortsOrch *gPortsOrch; +extern MlagOrch *gMlagOrch; + +MlagOrch::MlagOrch(DBConnector *db, vector &tableNames): + Orch(db, tableNames) +{ + SWSS_LOG_ENTER(); +} + +MlagOrch::~MlagOrch() +{ + SWSS_LOG_ENTER(); +} + +void MlagOrch::update(SubjectType type, void *cntx) +{ + SWSS_LOG_ENTER(); +} +//------------------------------------------------------------------ +//Private API section +//------------------------------------------------------------------ +void MlagOrch::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + if (!gPortsOrch->allPortsReady()) + { + return; + } + string table_name = consumer.getTableName(); + if (table_name == CFG_MCLAG_TABLE_NAME) + { + doMlagDomainTask(consumer); + } + else if (table_name == CFG_MCLAG_INTF_TABLE_NAME) + { + doMlagInterfaceTask(consumer); + } + else + { + SWSS_LOG_ERROR("MLAG receives invalid table %s", table_name.c_str()); + } +} + +//Only interest in peer-link info from MLAG domain table +void MlagOrch::doMlagDomainTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + string peer_link; + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + string op = kfvOp(t); + + if (op == SET_COMMAND) + { + for (auto i : kfvFieldsValues(t)) + { + if (fvField(i) == "peer_link") + { + peer_link = fvValue(i); + break; + } + } + if (!peer_link.empty()) + { + if (addIslInterface(peer_link)) + it = consumer.m_toSync.erase(it); + else + it++; + } + else + it = consumer.m_toSync.erase(it); + } + else if (op == DEL_COMMAND) + { + if (delIslInterface()) + it = consumer.m_toSync.erase(it); + else + it++; + } + else + { + SWSS_LOG_ERROR("MLAG receives unknown operation type %s", op.c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +//MLAG interface table key format: MCLAG_INTF_TABLE|mclag|ifname +void MlagOrch::doMlagInterfaceTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + size_t delimiter_pos; + string mlag_if_name; + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + string op = kfvOp(t); + string key = kfvKey(t); + + delimiter_pos = key.find_first_of("|"); + mlag_if_name = key.substr(delimiter_pos+1); + + if (op == SET_COMMAND) + { + if (addMlagInterface(mlag_if_name)) + it = consumer.m_toSync.erase(it); + else + it++; + } + else if (op == DEL_COMMAND) + { + if (delMlagInterface(mlag_if_name)) + it = consumer.m_toSync.erase(it); + else + it++; + } + else + { + SWSS_LOG_ERROR("MLAG receives unknown operation type %s", op.c_str()); + it = consumer.m_toSync.erase(it); + } + } +} + +bool MlagOrch::addIslInterface(string isl_name) +{ + Port isl_port; + MlagIslUpdate update; + + //No change + if ((m_isl_name == isl_name) || (isl_name.empty())) + return true; + + m_isl_name = isl_name; + + //Update observers + update.isl_name = isl_name; + update.is_add = true; + notify(SUBJECT_TYPE_MLAG_ISL_CHANGE, static_cast(&update)); + return true; +} + +bool MlagOrch::delIslInterface() +{ + MlagIslUpdate update; + + if (m_isl_name.empty()) + return true; + + update.isl_name = m_isl_name; + update.is_add = false; + + m_isl_name.clear(); + + //Notify observer + notify(SUBJECT_TYPE_MLAG_ISL_CHANGE, static_cast(&update)); + + return true; +} + +//Mlag interface can be added even before interface is configured +bool MlagOrch::addMlagInterface(string if_name) +{ + MlagIfUpdate update; + + //Duplicate add + if (m_mlagIntfs.find(if_name) != m_mlagIntfs.end()) + { + SWSS_LOG_ERROR("MLAG adds duplicate MLAG interface %s", if_name.c_str()); + } + else + { + + m_mlagIntfs.insert(if_name); + + //Notify observer + update.if_name = if_name; + update.is_add = true; + notify(SUBJECT_TYPE_MLAG_INTF_CHANGE, static_cast(&update)); + } + return true; + +} +bool MlagOrch::delMlagInterface(string if_name) +{ + MlagIfUpdate update; + + //Delete an unknown MLAG interface + if (m_mlagIntfs.find(if_name) == m_mlagIntfs.end()) + { + SWSS_LOG_ERROR("MLAG deletes unknown MLAG interface %s", if_name.c_str()); + } + else + { + m_mlagIntfs.erase(if_name); + + //Notify observers + update.if_name = if_name; + update.is_add = false; + notify(SUBJECT_TYPE_MLAG_INTF_CHANGE, static_cast(&update)); + } + return true; +} + +bool MlagOrch::isMlagInterface(string if_name) +{ + if (m_mlagIntfs.find(if_name) == m_mlagIntfs.end()) + return false; + else + return true; +} + +bool MlagOrch::isIslInterface(string if_name) +{ + if (m_isl_name == if_name) + return true; + else + return false; +} diff --git a/orchagent/mlagorch.h b/orchagent/mlagorch.h new file mode 100644 index 0000000000..27f4e9219b --- /dev/null +++ b/orchagent/mlagorch.h @@ -0,0 +1,66 @@ +/* + * Copyright 2019 Broadcom. The term "Broadcom" refers to Broadcom Inc. + * and/or its subsidiaries. + * + * 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 SWSS_MLAGORCH_H +#define SWSS_MLAGORCH_H + +#include +#include +#include "orch.h" +#include "port.h" + +struct MlagIfUpdate +{ + string if_name; + bool is_add; +}; + +struct MlagIslUpdate +{ + string isl_name; + bool is_add; +}; + +class MlagOrch: public Orch, public Observer, public Subject +{ +public: + MlagOrch(DBConnector *db, vector &tableNames); + ~MlagOrch(); + void update(SubjectType type, void *cntx); + bool isMlagInterface(string if_name); + bool isIslInterface(string if_name); + + const std::set& + getMlagIntfs() const + { + return m_mlagIntfs; + } + +private: + std::string m_isl_name; + std::set m_mlagIntfs; + + void doTask(Consumer &consumer); + void doMlagDomainTask(Consumer &consumer); + void doMlagInterfaceTask(Consumer &consumer); + bool addIslInterface(string isl_name); + bool delIslInterface(); + bool addMlagInterface(string if_name); + bool delMlagInterface(string if_name); +}; + +#endif /* SWSS_MLAGORCH_H */ diff --git a/orchagent/observer.h b/orchagent/observer.h index 76f00f1bfd..03668c1198 100644 --- a/orchagent/observer.h +++ b/orchagent/observer.h @@ -16,8 +16,14 @@ enum SubjectType SUBJECT_TYPE_MIRROR_SESSION_CHANGE, SUBJECT_TYPE_INT_SESSION_CHANGE, SUBJECT_TYPE_PORT_CHANGE, + SUBJECT_TYPE_BRIDGE_PORT_CHANGE, SUBJECT_TYPE_PORT_OPER_STATE_CHANGE, - SUBJECT_TYPE_FDB_FLUSH_CHANGE, + SUBJECT_TYPE_ISOLATION_GROUP_CHANGE, + SUBJECT_TYPE_ISOLATION_GROUP_MEMBER_CHANGE, + SUBJECT_TYPE_ISOLATION_GROUP_BINDING_CHANGE, + SUBJECT_TYPE_MLAG_INTF_CHANGE, + SUBJECT_TYPE_MLAG_ISL_CHANGE, + SUBJECT_TYPE_FDB_FLUSH_CHANGE }; class Observer diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 673c5f71ec..a546ffdac1 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -39,6 +39,8 @@ BufferOrch *gBufferOrch; SwitchOrch *gSwitchOrch; Directory gDirectory; NatOrch *gNatOrch; +MlagOrch *gMlagOrch; +IsoGrpOrch *gIsoGrpOrch; MACsecOrch *gMacsecOrch; bool gIsNatSupported = false; @@ -104,13 +106,15 @@ bool OrchDaemon::init() vector app_fdb_tables = { { APP_FDB_TABLE_NAME, FdbOrch::fdborch_pri}, - { APP_VXLAN_FDB_TABLE_NAME, FdbOrch::fdborch_pri} + { APP_VXLAN_FDB_TABLE_NAME, FdbOrch::fdborch_pri}, + { APP_MCLAG_FDB_TABLE_NAME, FdbOrch::fdborch_pri} }; gCrmOrch = new CrmOrch(m_configDb, CFG_CRM_TABLE_NAME); gPortsOrch = new PortsOrch(m_applDb, m_stateDb, ports_tables, m_chassisAppDb); TableConnector stateDbFdb(m_stateDb, STATE_FDB_TABLE_NAME); - gFdbOrch = new FdbOrch(m_applDb, app_fdb_tables, stateDbFdb, gPortsOrch); + TableConnector stateMclagDbFdb(m_stateDb, STATE_MCLAG_REMOTE_FDB_TABLE_NAME); + gFdbOrch = new FdbOrch(m_applDb, app_fdb_tables, stateDbFdb, stateMclagDbFdb, gPortsOrch); vector vnet_tables = { APP_VNET_RT_TABLE_NAME, @@ -326,6 +330,19 @@ bool OrchDaemon::init() } gAclOrch = new AclOrch(acl_table_connectors, gSwitchOrch, gPortsOrch, gMirrorOrch, gNeighOrch, gRouteOrch, dtel_orch); + vector mlag_tables = { + { CFG_MCLAG_TABLE_NAME }, + { CFG_MCLAG_INTF_TABLE_NAME } + }; + gMlagOrch = new MlagOrch(m_configDb, mlag_tables); + + TableConnector appDbIsoGrpTbl(m_applDb, APP_ISOLATION_GROUP_TABLE_NAME); + vector iso_grp_tbl_ctrs = { + appDbIsoGrpTbl + }; + + gIsoGrpOrch = new IsoGrpOrch(iso_grp_tbl_ctrs); + m_orchList.push_back(gFdbOrch); m_orchList.push_back(gMirrorOrch); m_orchList.push_back(gAclOrch); @@ -340,6 +357,8 @@ bool OrchDaemon::init() m_orchList.push_back(vnet_orch); m_orchList.push_back(vnet_rt_orch); m_orchList.push_back(gNatOrch); + m_orchList.push_back(gMlagOrch); + m_orchList.push_back(gIsoGrpOrch); m_orchList.push_back(gFgNhgOrch); m_orchList.push_back(mux_orch); m_orchList.push_back(mux_cb_orch); diff --git a/orchagent/orchdaemon.h b/orchagent/orchdaemon.h index 1829414265..bdcc053ed3 100644 --- a/orchagent/orchdaemon.h +++ b/orchagent/orchdaemon.h @@ -32,6 +32,8 @@ #include "debugcounterorch.h" #include "directory.h" #include "natorch.h" +#include "isolationgrouporch.h" +#include "mlagorch.h" #include "muxorch.h" #include "macsecorch.h" diff --git a/orchagent/portsorch.cpp b/orchagent/portsorch.cpp index 4ac36258b9..bf8b1ed557 100755 --- a/orchagent/portsorch.cpp +++ b/orchagent/portsorch.cpp @@ -4245,6 +4245,9 @@ bool PortsOrch::addBridgePort(Port &port) m_portList[port.m_alias] = port; SWSS_LOG_NOTICE("Add bridge port %s to default 1Q bridge", port.m_alias.c_str()); + PortUpdate update = { port, true }; + notify(SUBJECT_TYPE_BRIDGE_PORT_CHANGE, static_cast(&update)); + return true; } @@ -4298,6 +4301,10 @@ bool PortsOrch::removeBridgePort(Port &port) } port.m_bridge_port_id = SAI_NULL_OBJECT_ID; + /* Remove bridge port */ + PortUpdate update = { port, false }; + notify(SUBJECT_TYPE_BRIDGE_PORT_CHANGE, static_cast(&update)); + SWSS_LOG_NOTICE("Remove bridge port %s from default 1Q bridge", port.m_alias.c_str()); m_portList[port.m_alias] = port; diff --git a/orchagent/saihelper.cpp b/orchagent/saihelper.cpp index 15febdb518..561fd5df8d 100644 --- a/orchagent/saihelper.cpp +++ b/orchagent/saihelper.cpp @@ -62,6 +62,7 @@ sai_dtel_api_t* sai_dtel_api; sai_samplepacket_api_t* sai_samplepacket_api; sai_debug_counter_api_t* sai_debug_counter_api; sai_nat_api_t* sai_nat_api; +sai_isolation_group_api_t* sai_isolation_group_api; sai_system_port_api_t* sai_system_port_api; sai_macsec_api_t* sai_macsec_api; @@ -183,6 +184,7 @@ void initSaiApi() sai_api_query(SAI_API_SAMPLEPACKET, (void **)&sai_samplepacket_api); sai_api_query(SAI_API_DEBUG_COUNTER, (void **)&sai_debug_counter_api); sai_api_query(SAI_API_NAT, (void **)&sai_nat_api); + sai_api_query(SAI_API_ISOLATION_GROUP, (void **)&sai_isolation_group_api); sai_api_query(SAI_API_SYSTEM_PORT, (void **)&sai_system_port_api); sai_api_query(SAI_API_MACSEC, (void **)&sai_macsec_api); diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 30ab875f5d..69f25a6b0e 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -69,6 +69,8 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/debugcounterorch.cpp \ $(top_srcdir)/orchagent/natorch.cpp \ $(top_srcdir)/orchagent/muxorch.cpp \ + $(top_srcdir)/orchagent/mlagorch.cpp \ + $(top_srcdir)/orchagent/isolationgrouporch.cpp \ $(top_srcdir)/orchagent/macsecorch.cpp \ $(top_srcdir)/orchagent/lagid.cpp diff --git a/tests/mock_tests/aclorch_ut.cpp b/tests/mock_tests/aclorch_ut.cpp index 5d5fe5de64..4b1ce101ae 100644 --- a/tests/mock_tests/aclorch_ut.cpp +++ b/tests/mock_tests/aclorch_ut.cpp @@ -330,16 +330,18 @@ namespace aclorch_test ASSERT_EQ(gIntfsOrch, nullptr); gIntfsOrch = new IntfsOrch(m_app_db.get(), APP_INTF_TABLE_NAME, gVrfOrch, m_chassis_app_db.get()); - TableConnector applDbFdb(m_app_db.get(), APP_FDB_TABLE_NAME); - TableConnector stateDbFdb(m_state_db.get(), STATE_FDB_TABLE_NAME); + const int fdborch_pri = 20; vector app_fdb_tables = { { APP_FDB_TABLE_NAME, FdbOrch::fdborch_pri}, - { APP_VXLAN_FDB_TABLE_NAME, FdbOrch::fdborch_pri} + { APP_VXLAN_FDB_TABLE_NAME, FdbOrch::fdborch_pri}, + { APP_MCLAG_FDB_TABLE_NAME, fdborch_pri} }; - + + TableConnector stateDbFdb(m_state_db.get(), STATE_FDB_TABLE_NAME); + TableConnector stateMclagDbFdb(m_state_db.get(), STATE_MCLAG_REMOTE_FDB_TABLE_NAME); ASSERT_EQ(gFdbOrch, nullptr); - gFdbOrch = new FdbOrch(m_app_db.get(), app_fdb_tables, stateDbFdb, gPortsOrch); + gFdbOrch = new FdbOrch(m_app_db.get(), app_fdb_tables, stateDbFdb, stateMclagDbFdb, gPortsOrch); ASSERT_EQ(gNeighOrch, nullptr); gNeighOrch = new NeighOrch(m_app_db.get(), APP_NEIGH_TABLE_NAME, gIntfsOrch, gFdbOrch, gPortsOrch, m_chassis_app_db.get()); diff --git a/tests/test_mclag_cfg.py b/tests/test_mclag_cfg.py new file mode 100644 index 0000000000..0a79c767da --- /dev/null +++ b/tests/test_mclag_cfg.py @@ -0,0 +1,238 @@ +# Common file to test all MCLAG related changes +from swsscommon import swsscommon +import time +import re +import json +import pytest +import platform +from distutils.version import StrictVersion + + +def delete_table_keys(db, table): + tbl = swsscommon.Table(db, table) + keys = tbl.getKeys() + for key in keys: + tbl.delete(key) + +#check table entry exits with this key +def check_table_exists(db, table, key): + error_info = [ ] + tbl = swsscommon.Table(db, table) + keys = tbl.getKeys() + if key not in keys: + error_info.append("The table with desired key %s not found" % key) + return False, error_info + return True, error_info + +#check table entry doesn't exits with this key +def check_table_doesnt_exists(db, table, key): + error_info = [ ] + tbl = swsscommon.Table(db, table) + keys = tbl.getKeys() + if key in keys: + error_info.append("unexcpected: The table with desired key %s is found" % key) + return False, error_info + return True, error_info + + + + +# Test MCLAG Configs +class TestMclagConfig(object): + CFG_MCLAG_DOMAIN_TABLE = "MCLAG_DOMAIN" + CFG_MCLAG_INTERFACE_TABLE = "MCLAG_INTERFACE" + + PORTCHANNEL1 = "PortChannel11" + PORTCHANNEL2 = "PortChannel50" + PORTCHANNEL3 = "PortChannel51" + + MCLAG_DOMAIN_ID = "4095" + MCLAG_SRC_IP = "10.5.1.1" + MCLAG_PEER_IP = "10.5.1.2" + MCLAG_PEER_LINK = PORTCHANNEL1 + + MCLAG_DOMAIN_2 = "111" + + MCLAG_SESS_TMOUT_VALID_LIST = ["3","3600"] + MCLAG_KA_VALID_LIST = ["1","60"] + + MCLAG_KA_INVALID_LIST = ["0","61"] + MCLAG_SESS_TMOUT_INVALID_LIST = ["0","3601"] + + MCLAG_INTERFACE1 = PORTCHANNEL2 + MCLAG_INTERFACE2 = PORTCHANNEL3 + + + # Testcase 1 Verify Configuration of MCLAG Domain with src, peer ip and peer link config gets updated in CONFIG_DB + @pytest.mark.dev_sanity + def test_mclag_cfg_domain_add(self, dvs, testlog): + self.cfg_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) + + #cleanup existing entries + delete_table_keys(self.cfg_db, self.CFG_MCLAG_DOMAIN_TABLE) + delete_table_keys(self.cfg_db, self.CFG_MCLAG_INTERFACE_TABLE) + + cmd_string ="config mclag add {} {} {} {}".format(self.MCLAG_DOMAIN_ID, self.MCLAG_SRC_IP, self.MCLAG_PEER_IP, self.MCLAG_PEER_LINK) + dvs.runcmd(cmd_string) + time.sleep(2) + + #check whether domain cfg table contents are same as configured values + ok,error_info = dvs.all_table_entry_has(self.cfg_db, self.CFG_MCLAG_DOMAIN_TABLE, self.MCLAG_DOMAIN_ID, + [ + ("source_ip",self.MCLAG_SRC_IP), + ("peer_ip",self.MCLAG_PEER_IP), + ("peer_link",self.MCLAG_PEER_LINK) + ] + ) + assert ok,error_info + + # Testcase 2 Verify that second domain addition fails when there is already a domain configured + @pytest.mark.dev_sanity + def test_mclag_cfg_domain_add_2nd(self, dvs, testlog): + self.cfg_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) + cmd_string ="config mclag add {} {} {} {}".format(self.MCLAG_DOMAIN_2, self.MCLAG_SRC_IP, self.MCLAG_PEER_IP, self.MCLAG_PEER_LINK) + dvs.runcmd(cmd_string) + time.sleep(2) + + #check whether second domain config is not added to config db + key_string = self.MCLAG_DOMAIN_2 + ok,error_info = check_table_doesnt_exists(self.cfg_db, self.CFG_MCLAG_DOMAIN_TABLE, key_string) + assert ok,error_info + + + + # Testcase 3 Verify Configuration of MCLAG Interface to existing domain + @pytest.mark.dev_sanity + def test_mclag_cfg_intf_add(self, dvs, testlog): + self.cfg_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) + cmd_string ="config mclag member add {} {}".format(self.MCLAG_DOMAIN_ID, self.MCLAG_INTERFACE1) + dvs.runcmd(cmd_string) + time.sleep(2) + + #check whether mclag interface config is reflected + key_string = self.MCLAG_DOMAIN_ID + "|" + self.MCLAG_INTERFACE1 + ok,error_info = check_table_exists(self.cfg_db, self.CFG_MCLAG_INTERFACE_TABLE, key_string) + assert ok,error_info + + # Testcase 4 Verify remove and add mclag interface + @pytest.mark.dev_sanity + def test_mclag_cfg_intf_remove_and_add(self, dvs, testlog): + self.cfg_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) + + cmd_string ="config mclag member del {} {}".format(self.MCLAG_DOMAIN_ID, self.MCLAG_INTERFACE1) + dvs.runcmd(cmd_string) + time.sleep(2) + + #check whether mclag interface is removed + key_string = self.MCLAG_DOMAIN_ID + "|" + self.MCLAG_INTERFACE1 + ok,error_info = check_table_doesnt_exists(self.cfg_db, self.CFG_MCLAG_INTERFACE_TABLE, key_string) + assert ok,error_info + + #add different mclag interface + cmd_string ="config mclag member del {} {}".format(self.MCLAG_DOMAIN_ID, self.MCLAG_INTERFACE2) + dvs.runcmd(cmd_string) + time.sleep(2) + + #check whether new mclag interface is added + key_string = self.MCLAG_DOMAIN_ID + "|" + self.MCLAG_INTERFACE2 + ok,error_info = check_table_doesnt_exists(self.cfg_db, self.CFG_MCLAG_INTERFACE_TABLE, key_string) + assert ok,error_info + + # Testcase 5 Verify Configuration of valid values for session timeout + @pytest.mark.dev_sanity + def test_mclag_cfg_session_timeout_valid_values(self, dvs, testlog): + self.cfg_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) + + for value in self.MCLAG_SESS_TMOUT_VALID_LIST: + cmd_string ="config mclag session-timeout {} {}".format(self.MCLAG_DOMAIN_ID, value) + dvs.runcmd(cmd_string) + time.sleep(2) + + #check whether domain cfg table contents are same as configured values + ok,error_info = dvs.all_table_entry_has(self.cfg_db, self.CFG_MCLAG_DOMAIN_TABLE, self.MCLAG_DOMAIN_ID, + [ + ("source_ip",self.MCLAG_SRC_IP), + ("peer_ip",self.MCLAG_PEER_IP), + ("peer_link",self.MCLAG_PEER_LINK), + ("session_timeout",value) + ] + ) + assert ok,error_info + + # Testcase 6 Verify Configuration of valid values for KA timer + @pytest.mark.dev_sanity + def test_mclag_cfg_ka_valid_values(self, dvs, testlog): + self.cfg_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) + + for value in self.MCLAG_KA_VALID_LIST: + cmd_string ="config mclag keepalive-interval {} {}".format(self.MCLAG_DOMAIN_ID, value) + dvs.runcmd(cmd_string) + time.sleep(2) + + #check whether domain cfg table contents are same as configured values + ok,error_info = dvs.all_table_entry_has(self.cfg_db, self.CFG_MCLAG_DOMAIN_TABLE, self.MCLAG_DOMAIN_ID, + [ + ("source_ip",self.MCLAG_SRC_IP), + ("peer_ip",self.MCLAG_PEER_IP), + ("peer_link",self.MCLAG_PEER_LINK), + ("keepalive_interval",value) + ] + ) + assert ok,error_info + + + + # Testcase 7 Verify Configuration of invalid values for KA + @pytest.mark.dev_sanity + def test_mclag_cfg_ka_invalid_values(self, dvs, testlog): + self.cfg_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) + + for value in self.MCLAG_KA_INVALID_LIST: + cmd_string ="config mclag keepalive-interval {} {}".format(self.MCLAG_DOMAIN_ID, value) + dvs.runcmd(cmd_string) + time.sleep(2) + + #check whether domain cfg table contents are same as configured values + found,error_info = dvs.all_table_entry_has(self.cfg_db, self.CFG_MCLAG_DOMAIN_TABLE, self.MCLAG_DOMAIN_ID, + [ + ("keepalive_interval",value) + ] + ) + assert found == False, "invalid keepalive value %s written to CONFIG_DB" % value + + # Testcase 8 Verify Configuration of invalid values for session timeout + @pytest.mark.dev_sanity + def test_mclag_cfg_session_invalid_values(self, dvs, testlog): + self.cfg_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) + + for value in self.MCLAG_SESS_TMOUT_INVALID_LIST: + cmd_string ="config mclag session-timeout {} {}".format(self.MCLAG_DOMAIN_ID, value) + dvs.runcmd(cmd_string) + time.sleep(2) + + #check whether domain cfg table contents are same as configured values + found,error_info = dvs.all_table_entry_has(self.cfg_db, self.CFG_MCLAG_DOMAIN_TABLE, self.MCLAG_DOMAIN_ID, + [ + ("session_timeout",value) + ] + ) + assert found == False, "invalid keepalive value %s written to CONFIG_DB" % value + + # Testcase 9 Verify Deletion of MCLAG Domain + @pytest.mark.dev_sanity + def test_mclag_cfg_domain_del(self, dvs, testlog): + self.cfg_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) + + cmd_string ="config mclag del {}".format(self.MCLAG_DOMAIN_ID) + dvs.runcmd(cmd_string) + time.sleep(2) + + #check whether domain cfg table contents are same as configured values + ok, error_info = check_table_doesnt_exists(self.cfg_db, self.CFG_MCLAG_DOMAIN_TABLE, self.MCLAG_DOMAIN_ID) + assert ok,error_info + + #make sure mclag interface tables entries are also deleted when mclag domain is deleted + key_string = self.MCLAG_DOMAIN_ID + ok,error_info = check_table_doesnt_exists(self.cfg_db, self.CFG_MCLAG_INTERFACE_TABLE, key_string) + assert ok,error_info + diff --git a/tests/test_mclag_fdb.py b/tests/test_mclag_fdb.py new file mode 100644 index 0000000000..5049859437 --- /dev/null +++ b/tests/test_mclag_fdb.py @@ -0,0 +1,505 @@ +from swsscommon import swsscommon +import os +import sys +import time +import json +import pytest +from distutils.version import StrictVersion + +def create_entry(tbl, key, pairs): + fvs = swsscommon.FieldValuePairs(pairs) + tbl.set(key, fvs) + + time.sleep(1) + +def create_entry_tbl(db, table, key, pairs): + tbl = swsscommon.Table(db, table) + create_entry(tbl, key, pairs) + +def create_entry_pst(db, table, key, pairs): + tbl = swsscommon.ProducerStateTable(db, table) + create_entry(tbl, key, pairs) + +def delete_entry_pst(db, table, key): + tbl = swsscommon.ProducerStateTable(db, table) + tbl._del(key) + +def get_port_oid(dvs, port_name): + counters_db = swsscommon.DBConnector(swsscommon.COUNTERS_DB, dvs.redis_sock, 0) + port_map_tbl = swsscommon.Table(counters_db, 'COUNTERS_PORT_NAME_MAP') + for k in port_map_tbl.get('')[1]: + if k[0] == port_name: + return k[1] + return None + +def get_bridge_port_oid(dvs, port_oid): + tbl = swsscommon.Table(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT") + for key in tbl.getKeys(): + status, data = tbl.get(key) + assert status + values = dict(data) + if port_oid == values["SAI_BRIDGE_PORT_ATTR_PORT_ID"]: + return key + return None + +def create_port_channel(dvs, alias): + tbl = swsscommon.Table(dvs.cdb, "PORTCHANNEL") + fvs = swsscommon.FieldValuePairs([("admin_status", "up"), + ("mtu", "9100")]) + tbl.set(alias, fvs) + time.sleep(1) + +def remove_port_channel(dvs, alias): + tbl = swsscommon.Table(dvs.cdb, "PORTCHANNEL") + tbl._del(alias) + time.sleep(1) + +def add_port_channel_members(dvs, lag, members): + tbl = swsscommon.Table(dvs.cdb, "PORTCHANNEL_MEMBER") + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + for member in members: + tbl.set(lag + "|" + member, fvs) + time.sleep(1) + +def remove_port_channel_members(dvs, lag, members): + tbl = swsscommon.Table(dvs.cdb, "PORTCHANNEL_MEMBER") + for member in members: + tbl._del(lag + "|" + member) + time.sleep(1) + +def how_many_entries_exist(db, table): + tbl = swsscommon.Table(db, table) + return len(tbl.getKeys()) + +# Test-1 Verify basic config add + +@pytest.mark.dev_sanity +def test_mclagFdb_basic_config_add(dvs, testlog): + dvs.setup_db() + dvs.runcmd("sonic-clear fdb all") + time.sleep(2) + + vlan_before = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + bp_before = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT") + vm_before = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN_MEMBER") + + # create PortChannel0005 + create_port_channel(dvs, "PortChannel0005") + + # add members to PortChannel0005 + add_port_channel_members(dvs, "PortChannel0005", ["Ethernet4"]) + + # create PortChannel0006 + create_port_channel(dvs, "PortChannel0006") + + # add members to PortChannel0006 + add_port_channel_members(dvs, "PortChannel0006", ["Ethernet8"]) + + # create PortChannel0008 + create_port_channel(dvs, "PortChannel0008") + + # add members to PortChannel0008 + add_port_channel_members(dvs, "PortChannel0008", ["Ethernet12"]) + + # create vlan + dvs.create_vlan("200") + + # Add vlan members + dvs.create_vlan_member("200", "PortChannel0005") + dvs.create_vlan_member("200", "PortChannel0006") + dvs.create_vlan_member("200", "PortChannel0008") + + # check that the vlan information was propagated + vlan_after = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + bp_after = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT") + vm_after = how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN_MEMBER") + + assert vlan_after - vlan_before == 1, "The Vlan200 wasn't created" + assert bp_after - bp_before == 3, "The bridge port wasn't created" + assert vm_after - vm_before == 3, "The vlan member wasn't added" + +# Test-2 Remote Dynamic MAC Add + +@pytest.mark.dev_sanity +def test_mclagFdb_remote_dynamic_mac_add(dvs, testlog): + dvs.setup_db() + # create FDB entry in APP_DB MCLAG_FDB_TABLE + create_entry_pst( + dvs.pdb, + "MCLAG_FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + [ + ("port", "PortChannel0005"), + ("type", "dynamic"), + ] + ) + + # check that the FDB entry was inserted into ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 1, "The MCLAG fdb entry not inserted to ASIC" + + ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", + [("mac", "3C:85:99:5E:00:01"), ("bvid", str(dvs.getVlanOid("200")))], + [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_STATIC"), + ("SAI_FDB_ENTRY_ATTR_ALLOW_MAC_MOVE", "true")] + ) + + assert ok, str(extra) + +# Test-3 Remote Dynamic MAC Delete + +@pytest.mark.dev_sanity +def test_mclagFdb_remote_dynamic_mac_delete(dvs, testlog): + dvs.setup_db() + + delete_entry_pst( + dvs.pdb, + "MCLAG_FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + ) + + time.sleep(2) + # check that the FDB entry was deleted from ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 0, "The MCLAG fdb entry not deleted" + + +# Test-4 Remote Static MAC Add + +@pytest.mark.dev_sanity +def test_mclagFdb_remote_static_mac_add(dvs, testlog): + dvs.setup_db() + + create_entry_pst( + dvs.pdb, + "MCLAG_FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + [ + ("port", "PortChannel0005"), + ("type", "static"), + ] + ) + + # check that the FDB entry was inserted into ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 1, "The MCLAG static fdb entry not inserted to ASIC" + + ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", + [("mac", "3C:85:99:5E:00:01"), ("bvid", str(dvs.getVlanOid("200")))], + [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_STATIC")] + ) + + assert ok, str(extra) + +# Test-5 Remote Static MAC Del + +@pytest.mark.dev_sanity +def test_mclagFdb_remote_static_mac_del(dvs, testlog): + dvs.setup_db() + + delete_entry_pst( + dvs.pdb, + "MCLAG_FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + ) + + time.sleep(2) + # check that the FDB entry was deleted from ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 0, "The MCLAG static fdb entry not deleted" + + +# Test-6 Verify Remote to Local Move. + +@pytest.mark.dev_sanity +def test_mclagFdb_remote_to_local_mac_move(dvs, testlog): + dvs.setup_db() + + #Add remote MAC to MCLAG_FDB_TABLE on PortChannel0005 + create_entry_pst( + dvs.pdb, + "MCLAG_FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + [ + ("port", "PortChannel0005"), + ("type", "dynamic"), + ] + ) + + # check that the FDB entry inserted into ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 1, "The MCLAG fdb entry not inserted to ASIC" + + ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", + [("mac", "3C:85:99:5E:00:01"), ("bvid", str(dvs.getVlanOid("200")))], + [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_STATIC"), + ("SAI_FDB_ENTRY_ATTR_ALLOW_MAC_MOVE", "true")] + ) + + assert ok, str(extra) + + #Move MAC to PortChannel0008 on Local FDB_TABLE + create_entry_pst( + dvs.pdb, + "FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + [ + ("port", "PortChannel0008"), + ("type", "dynamic"), + ] + ) + + # check that the FDB entry was inserted into ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 1, "The fdb entry not inserted to ASIC" + + ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", + [("mac", "3C:85:99:5E:00:01"), ("bvid", str(dvs.getVlanOid("200")))], + [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_DYNAMIC")] + ) + + assert ok, str(extra) + +# Test-7 Verify Remote MAC del should not del local MAC after move + +@pytest.mark.dev_sanity +def test_mclagFdb_local_mac_move_del(dvs, testlog): + dvs.setup_db() + + #Cleanup the FDB from MCLAG_FDB_TABLE + delete_entry_pst( + dvs.pdb, + "MCLAG_FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + ) + + # Verify that the local FDB entry still inserted into ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 1, "The fdb entry not inserted to ASIC" + + ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", + [("mac", "3C:85:99:5E:00:01"), ("bvid", str(dvs.getVlanOid("200")))], + [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_DYNAMIC")] + ) + + assert ok, str(extra) + + + #delete the local FDB and Verify + delete_entry_pst( + dvs.pdb, + "FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + ) + + time.sleep(2) + # check that the FDB entry was deleted from ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 0, "The fdb entry was not deleted" + + +# Test-8 Verify Local to Remote Move. + +@pytest.mark.dev_sanity +def test_mclagFdb_local_to_remote_move(dvs, testlog): + dvs.setup_db() + + #Add local MAC to FDB_TABLE on PortChannel0008 + create_entry_pst( + dvs.pdb, + "FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + [ + ("port", "PortChannel0008"), + ("type", "dynamic"), + ] + ) + + # check that the FDB entry was inserted into ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 1, "The MCLAG fdb entry not inserted to ASIC" + + ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", + [("mac", "3C:85:99:5E:00:01"), ("bvid", str(dvs.getVlanOid("200")))], + [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_DYNAMIC")] + ) + + assert ok, str(extra) + + #Move MAC to PortChannel0005 on Remote FDB_TABLE + create_entry_pst( + dvs.pdb, + "MCLAG_FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + [ + ("port", "PortChannel0005"), + ("type", "dynamic"), + ] + ) + + # check that the FDB entry was inserted into ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 1, "The MCLAG fdb entry not inserted to ASIC" + + ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", + [("mac", "3C:85:99:5E:00:01"), ("bvid", str(dvs.getVlanOid("200")))], + [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_STATIC"), + ("SAI_FDB_ENTRY_ATTR_ALLOW_MAC_MOVE", "true")] + ) + + assert ok, str(extra) + +# Test-9 Verify local MAC del should not del remote MAC after move + +@pytest.mark.dev_sanity +def test_mclagFdb_remote_move_del(dvs, testlog): + dvs.setup_db() + + #Cleanup the local FDB + delete_entry_pst( + dvs.pdb, + "FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + ) + + # check that the remote FDB entry still inserted into ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 1, "The MCLAG fdb entry not inserted to ASIC" + + ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", + [("mac", "3C:85:99:5E:00:01"), ("bvid", str(dvs.getVlanOid("200")))], + [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_STATIC"), + ("SAI_FDB_ENTRY_ATTR_ALLOW_MAC_MOVE", "true")] + ) + + assert ok, str(extra) + + #delete the remote FDB and Verify + delete_entry_pst( + dvs.pdb, + "MCLAG_FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + ) + + time.sleep(2) + # check that the FDB entry was deleted from ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 0, "The fdb entry not deleted" + + + +# Test-10 Verify remote MAC move in remote node is updated locally. + +@pytest.mark.dev_sanity +def test_mclagFdb_remote_move_peer_node(dvs, testlog): + dvs.setup_db() + + #Add remote MAC to MCLAG_FDB_TABLE on PortChannel0005 + create_entry_pst( + dvs.pdb, + "MCLAG_FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + [ + ("port", "PortChannel0005"), + ("type", "dynamic"), + ] + ) + + # check that the FDB entry inserted into ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 1, "The MCLAG fdb entry not inserted to ASIC" + + ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", + [("mac", "3C:85:99:5E:00:01"), ("bvid", str(dvs.getVlanOid("200")))], + [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_STATIC"), + ("SAI_FDB_ENTRY_ATTR_ALLOW_MAC_MOVE", "true")] + ) + + assert ok, str(extra) + + # Move remote MAC in MCLAG_FDB_TABLE to PortChannel0006 + create_entry_pst( + dvs.pdb, + "MCLAG_FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + [ + ("port", "PortChannel0006"), + ("type", "dynamic"), + ] + ) + + # check that the FDB entry inserted into ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 1, "The MCLAG fdb entry not inserted to ASIC" + + ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", + [("mac", "3C:85:99:5E:00:01"), ("bvid", str(dvs.getVlanOid("200")))], + [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_STATIC"), + ("SAI_FDB_ENTRY_ATTR_ALLOW_MAC_MOVE", "true")] + ) + + assert ok, str(extra) + + #delete the remote FDB and Verify + delete_entry_pst( + dvs.pdb, + "MCLAG_FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + ) + + time.sleep(2) + # check that the FDB entry was deleted from ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 0, "The fdb entry not deleted" + + +# Test-11 Verify remote dynamic MAC move rejection in presecense of local static MAC. + +@pytest.mark.dev_sanity +def test_mclagFdb_static_mac_dynamic_move_reject(dvs, testlog): + dvs.setup_db() + + #Add local MAC to FDB_TABLE on PortChannel0008 + create_entry_pst( + dvs.pdb, + "FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + [ + ("port", "PortChannel0008"), + ("type", "static"), + ] + ) + + # check that the static FDB entry was inserted into ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 1, "The MCLAG fdb entry not inserted to ASIC" + + ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", + [("mac", "3C:85:99:5E:00:01"), ("bvid", str(dvs.getVlanOid("200")))], + [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_STATIC")] + ) + + assert ok, str(extra) + + #Add remote MAC to MCLAG_FDB_TABLE on PortChannel0005 + create_entry_pst( + dvs.pdb, + "MCLAG_FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + [ + ("port", "PortChannel0005"), + ("type", "dynamic"), + ] + ) + + # check that still static FDB entry is inserted into ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 1, "The MCLAG fdb entry not inserted to ASIC" + + ok, extra = dvs.is_fdb_entry_exists(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY", + [("mac", "3C:85:99:5E:00:01"), ("bvid", str(dvs.getVlanOid("200")))], + [("SAI_FDB_ENTRY_ATTR_TYPE", "SAI_FDB_ENTRY_TYPE_STATIC")] + ) + + assert ok, str(extra) + + delete_entry_pst( + dvs.pdb, + "FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + ) + + time.sleep(2) + # check that the FDB entry was deleted from ASIC DB + assert how_many_entries_exist(dvs.adb, "ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY") == 0, "The MCLAG static fdb entry not deleted" + + delete_entry_pst( + dvs.pdb, + "MCLAG_FDB_TABLE", "Vlan200:3C:85:99:5E:00:01", + ) + +# Test-12 Verify cleanup of the basic config. + +@pytest.mark.dev_sanity +def test_mclagFdb_basic_config_del(dvs, testlog): + dvs.setup_db() + + dvs.remove_vlan_member("200", "PortChannel0005") + dvs.remove_vlan_member("200", "PortChannel0006") + dvs.remove_vlan_member("200", "PortChannel0008") + dvs.remove_vlan("200") + remove_port_channel_members(dvs, "PortChannel0005", + ["Ethernet4"]) + remove_port_channel_members(dvs, "PortChannel0006", + ["Ethernet8"]) + remove_port_channel_members(dvs, "PortChannel0008", + ["Ethernet12"]) + remove_port_channel(dvs, "PortChannel0005") + remove_port_channel(dvs, "PortChannel0006") + remove_port_channel(dvs, "PortChannel0008") +