From c39a4b1a6941a661a20a91b893c1d645c77eee94 Mon Sep 17 00:00:00 2001 From: Prince Sunny Date: Wed, 23 Dec 2020 09:28:54 -0800 Subject: [PATCH] Mux/IPTunnel orchagent changes (#1497) Introduce TunnelMgr Daemon and Mux orchagent Added support to enable/disable neighbors via NeighOrch Added support to create/remove nexthop tunnels Added support for ACL handling for Mux state --- cfgmgr/Makefile.am | 7 +- cfgmgr/tunnelmgr.cpp | 87 +++ cfgmgr/tunnelmgr.h | 23 + cfgmgr/tunnelmgrd.cpp | 86 +++ orchagent/Makefile.am | 3 +- orchagent/aclorch.cpp | 10 + orchagent/aclorch.h | 10 +- orchagent/muxorch.cpp | 996 ++++++++++++++++++++++++++++++++++ orchagent/muxorch.h | 249 +++++++++ orchagent/neighorch.cpp | 190 ++++--- orchagent/neighorch.h | 14 +- orchagent/orchdaemon.cpp | 16 + orchagent/orchdaemon.h | 1 + orchagent/request_parser.cpp | 3 + orchagent/request_parser.h | 7 + orchagent/tunneldecaporch.cpp | 208 ++++++- orchagent/tunneldecaporch.h | 25 +- tests/mock_tests/Makefile.am | 3 +- tests/test_mux.py | 262 +++++++++ 19 files changed, 2126 insertions(+), 74 deletions(-) create mode 100644 cfgmgr/tunnelmgr.cpp create mode 100644 cfgmgr/tunnelmgr.h create mode 100644 cfgmgr/tunnelmgrd.cpp create mode 100644 orchagent/muxorch.cpp create mode 100644 orchagent/muxorch.h create mode 100644 tests/test_mux.py diff --git a/cfgmgr/Makefile.am b/cfgmgr/Makefile.am index 57db48954d08..87902c36e554 100644 --- a/cfgmgr/Makefile.am +++ b/cfgmgr/Makefile.am @@ -3,7 +3,7 @@ CFLAGS_SAI = -I /usr/include/sai LIBNL_CFLAGS = -I/usr/include/libnl3 LIBNL_LIBS = -lnl-genl-3 -lnl-route-3 -lnl-3 -bin_PROGRAMS = vlanmgrd teammgrd portmgrd intfmgrd buffermgrd vrfmgrd nbrmgrd vxlanmgrd sflowmgrd natmgrd coppmgrd +bin_PROGRAMS = vlanmgrd teammgrd portmgrd intfmgrd buffermgrd vrfmgrd nbrmgrd vxlanmgrd sflowmgrd natmgrd coppmgrd tunnelmgrd cfgmgrdir = $(datadir)/swss @@ -76,3 +76,8 @@ coppmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) coppmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) coppmgrd_LDADD = -lswsscommon +tunnelmgrd_SOURCES = tunnelmgrd.cpp tunnelmgr.cpp $(top_srcdir)/orchagent/orch.cpp $(top_srcdir)/orchagent/request_parser.cpp shellcmd.h +tunnelmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) +tunnelmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) +tunnelmgrd_LDADD = -lswsscommon + diff --git a/cfgmgr/tunnelmgr.cpp b/cfgmgr/tunnelmgr.cpp new file mode 100644 index 000000000000..848b4dba6bab --- /dev/null +++ b/cfgmgr/tunnelmgr.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include + +#include "logger.h" +#include "tunnelmgr.h" + +using namespace std; +using namespace swss; + +TunnelMgr::TunnelMgr(DBConnector *cfgDb, DBConnector *appDb, std::string tableName) : + Orch(cfgDb, tableName), + m_appIpInIpTunnelTable(appDb, APP_TUNNEL_DECAP_TABLE_NAME) +{ +} + +void TunnelMgr::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + bool task_result = false; + + KeyOpFieldsValuesTuple t = it->second; + const vector& data = kfvFieldsValues(t); + + const std::string & op = kfvOp(t); + + if (op == SET_COMMAND) + { + for (auto idx : data) + { + const auto &field = fvField(idx); + const auto &value = fvValue(idx); + if (field == "tunnel_type") + { + if (value == "IPINIP") + { + task_result = doIpInIpTunnelTask(t); + } + } + } + } + else if (op == DEL_COMMAND) + { + /* TODO: Handle Tunnel delete for other tunnel types */ + task_result = doIpInIpTunnelTask(t); + } + else + { + SWSS_LOG_ERROR("Unknown operation: '%s'", op.c_str()); + } + + if (task_result == true) + { + it = consumer.m_toSync.erase(it); + } + else + { + ++it; + } + } +} + +bool TunnelMgr::doIpInIpTunnelTask(const KeyOpFieldsValuesTuple & t) +{ + SWSS_LOG_ENTER(); + + const std::string & TunnelName = kfvKey(t); + const std::string & op = kfvOp(t); + + if (op == SET_COMMAND) + { + m_appIpInIpTunnelTable.set(TunnelName, kfvFieldsValues(t)); + } + else + { + m_appIpInIpTunnelTable.del(TunnelName); + } + + SWSS_LOG_NOTICE("Tunnel %s task, op %s", TunnelName.c_str(), op.c_str()); + return true; +} diff --git a/cfgmgr/tunnelmgr.h b/cfgmgr/tunnelmgr.h new file mode 100644 index 000000000000..7c84e3e476f5 --- /dev/null +++ b/cfgmgr/tunnelmgr.h @@ -0,0 +1,23 @@ +#pragma once + +#include "dbconnector.h" +#include "producerstatetable.h" +#include "orch.h" + +namespace swss { + +class TunnelMgr : public Orch +{ +public: + TunnelMgr(DBConnector *cfgDb, DBConnector *appDb, std::string tableName); + using Orch::doTask; + +private: + void doTask(Consumer &consumer); + + bool doIpInIpTunnelTask(const KeyOpFieldsValuesTuple & t); + + ProducerStateTable m_appIpInIpTunnelTable; +}; + +} diff --git a/cfgmgr/tunnelmgrd.cpp b/cfgmgr/tunnelmgrd.cpp new file mode 100644 index 000000000000..ea9e0871237f --- /dev/null +++ b/cfgmgr/tunnelmgrd.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "dbconnector.h" +#include "select.h" +#include "exec.h" +#include "schema.h" +#include "tunnelmgr.h" + +using namespace std; +using namespace swss; + +/* select() function timeout retry time, in millisecond */ +#define SELECT_TIMEOUT 1000 + +/* + * Following global variables are defined here for the purpose of + * using existing Orch class which is to be refactored soon to + * eliminate the direct exposure of the global variables. + * + * Once Orch class refactoring is done, these global variables + * should be removed from here. + */ +int gBatchSize = 0; +bool gSwssRecord = false; +bool gLogRotate = false; +ofstream gRecordOfs; +string gRecordFile; +/* Global database mutex */ +mutex gDbMutex; + +int main(int argc, char **argv) +{ + Logger::linkToDbNative("tunnelmgrd"); + + SWSS_LOG_NOTICE("--- Starting Tunnelmgrd ---"); + + try + { + + DBConnector cfgDb("CONFIG_DB", 0); + DBConnector appDb("APPL_DB", 0); + + TunnelMgr tunnelmgr(&cfgDb, &appDb, CFG_TUNNEL_TABLE_NAME); + + std::vector cfgOrchList = {&tunnelmgr}; + + swss::Select s; + for (Orch *o : cfgOrchList) + { + s.addSelectables(o->getSelectables()); + } + + SWSS_LOG_NOTICE("starting main loop"); + while (true) + { + Selectable *sel; + int ret; + + ret = s.select(&sel, SELECT_TIMEOUT); + if (ret == Select::ERROR) + { + SWSS_LOG_NOTICE("Error: %s!", strerror(errno)); + continue; + } + if (ret == Select::TIMEOUT) + { + tunnelmgr.doTask(); + continue; + } + + auto *c = (Executor *)sel; + c->execute(); + } + } + catch(const std::exception &e) + { + SWSS_LOG_ERROR("Runtime error: %s", e.what()); + } + return -1; +} diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index 2aa0337448c4..e7e0cebd3818 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -60,7 +60,8 @@ orchagent_SOURCES = \ sfloworch.cpp \ chassisorch.cpp \ debugcounterorch.cpp \ - natorch.cpp + natorch.cpp \ + muxorch.cpp orchagent_SOURCES += flex_counter/flex_counter_manager.cpp flex_counter/flex_counter_stat_manager.cpp orchagent_SOURCES += debug_counter/debug_counter.cpp debug_counter/drop_counter.cpp diff --git a/orchagent/aclorch.cpp b/orchagent/aclorch.cpp index 946c952a8681..114dc99ee6da 100644 --- a/orchagent/aclorch.cpp +++ b/orchagent/aclorch.cpp @@ -1014,6 +1014,16 @@ bool AclRulePfcwd::validateAddMatch(string attr_name, string attr_value) return AclRule::validateAddMatch(attr_name, attr_value); } +AclRuleMux::AclRuleMux(AclOrch *aclOrch, string rule, string table, acl_table_type_t type, bool createCounter) : + AclRuleL3(aclOrch, rule, table, type, createCounter) +{ +} + +bool AclRuleMux::validateAddMatch(string attr_name, string attr_value) +{ + return AclRule::validateAddMatch(attr_name, attr_value); +} + AclRuleL3V6::AclRuleL3V6(AclOrch *aclOrch, string rule, string table, acl_table_type_t type) : AclRuleL3(aclOrch, rule, table, type) { diff --git a/orchagent/aclorch.h b/orchagent/aclorch.h index f0230dbb9a76..0e0a21262b7e 100644 --- a/orchagent/aclorch.h +++ b/orchagent/aclorch.h @@ -35,6 +35,7 @@ #define TABLE_TYPE_DTEL_FLOW_WATCHLIST "DTEL_FLOW_WATCHLIST" #define TABLE_TYPE_DTEL_DROP_WATCHLIST "DTEL_DROP_WATCHLIST" #define TABLE_TYPE_MCLAG "MCLAG" +#define TABLE_TYPE_MUX "MUX" #define RULE_PRIORITY "PRIORITY" #define MATCH_IN_PORTS "IN_PORTS" @@ -115,7 +116,8 @@ typedef enum ACL_TABLE_CTRLPLANE, ACL_TABLE_DTEL_FLOW_WATCHLIST, ACL_TABLE_DTEL_DROP_WATCHLIST, - ACL_TABLE_MCLAG + ACL_TABLE_MCLAG, + ACL_TABLE_MUX } acl_table_type_t; typedef map acl_table_type_lookup_t; @@ -272,6 +274,12 @@ class AclRulePfcwd: public AclRuleL3 bool validateAddMatch(string attr_name, string attr_value); }; +class AclRuleMux: public AclRuleL3 +{ +public: + AclRuleMux(AclOrch *m_pAclOrch, string rule, string table, acl_table_type_t type, bool createCounter = false); + bool validateAddMatch(string attr_name, string attr_value); +}; class AclRuleMirror: public AclRule { diff --git a/orchagent/muxorch.cpp b/orchagent/muxorch.cpp new file mode 100644 index 000000000000..eabb7ab396e5 --- /dev/null +++ b/orchagent/muxorch.cpp @@ -0,0 +1,996 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "sai.h" +#include "ipaddress.h" +#include "ipaddresses.h" +#include "orch.h" +#include "request_parser.h" +#include "muxorch.h" +#include "directory.h" +#include "swssnet.h" +#include "crmorch.h" +#include "neighorch.h" +#include "portsorch.h" +#include "aclorch.h" + +/* Global variables */ +extern Directory gDirectory; +extern CrmOrch *gCrmOrch; +extern NeighOrch *gNeighOrch; +extern AclOrch *gAclOrch; +extern PortsOrch *gPortsOrch; + +extern sai_object_id_t gVirtualRouterId; +extern sai_object_id_t gUnderlayIfId; +extern sai_object_id_t gSwitchId; +extern sai_route_api_t* sai_route_api; +extern sai_tunnel_api_t* sai_tunnel_api; +extern sai_next_hop_api_t* sai_next_hop_api; +extern sai_router_interface_api_t* sai_router_intfs_api; + +/* Constants */ +#define MUX_TUNNEL "MuxTunnel0" +#define MUX_ACL_TABLE_NAME "mux_acl_table"; +#define MUX_ACL_RULE_NAME "mux_acl_rule"; +#define MUX_HW_STATE_UNKNOWN "unknown" +#define MUX_HW_STATE_PENDING "pending" + +const map, MuxStateChange> muxStateTransition = +{ + { { MuxState::MUX_STATE_INIT, MuxState::MUX_STATE_ACTIVE}, MuxStateChange::MUX_STATE_INIT_ACTIVE + }, + + { { MuxState::MUX_STATE_INIT, MuxState::MUX_STATE_STANDBY}, MuxStateChange::MUX_STATE_INIT_STANDBY + }, + + { { MuxState::MUX_STATE_ACTIVE, MuxState::MUX_STATE_STANDBY}, MuxStateChange::MUX_STATE_ACTIVE_STANDBY + }, + + { { MuxState::MUX_STATE_STANDBY, MuxState::MUX_STATE_ACTIVE}, MuxStateChange::MUX_STATE_STANDBY_ACTIVE + }, +}; + +const map muxStateValToString = +{ + { MuxState::MUX_STATE_ACTIVE, "active" }, + { MuxState::MUX_STATE_STANDBY, "standby" }, + { MuxState::MUX_STATE_INIT, "init" }, + { MuxState::MUX_STATE_FAILED, "failed" }, + { MuxState::MUX_STATE_PENDING, "pending" }, +}; + +const map muxStateStringToVal = +{ + { "active", MuxState::MUX_STATE_ACTIVE }, + { "standby", MuxState::MUX_STATE_STANDBY }, + { "init", MuxState::MUX_STATE_INIT }, + { "failed", MuxState::MUX_STATE_FAILED }, + { "pending", MuxState::MUX_STATE_PENDING }, +}; + +static inline MuxStateChange mux_state_change (MuxState prev, MuxState curr) +{ + auto key = std::make_pair(prev, curr); + if (muxStateTransition.find(key) != muxStateTransition.end()) + { + return muxStateTransition.at(key); + } + + return MuxStateChange::MUX_STATE_UNKNOWN_STATE; +} + +static sai_status_t create_route(IpPrefix &pfx, sai_object_id_t nh) +{ + sai_route_entry_t route_entry; + route_entry.switch_id = gSwitchId; + route_entry.vr_id = gVirtualRouterId; + copy(route_entry.destination, pfx); + subnet(route_entry.destination, route_entry.destination); + + sai_attribute_t attr; + vector attrs; + + attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; + attr.value.s32 = SAI_PACKET_ACTION_FORWARD; + attrs.push_back(attr); + + attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + attr.value.oid = nh; + attrs.push_back(attr); + + sai_status_t status = sai_route_api->create_route_entry(&route_entry, (uint32_t)attrs.size(), attrs.data()); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create tunnel route %s,nh %" PRIx64 " rv:%d", + pfx.getIp().to_string().c_str(), nh, status); + return status; + } + + if (route_entry.destination.addr_family == SAI_IP_ADDR_FAMILY_IPV4) + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV4_ROUTE); + } + else + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); + } + + SWSS_LOG_NOTICE("Created tunnel route to %s ", pfx.to_string().c_str()); + return status; +} + +static sai_status_t remove_route(IpPrefix &pfx) +{ + sai_route_entry_t route_entry; + route_entry.switch_id = gSwitchId; + route_entry.vr_id = gVirtualRouterId; + copy(route_entry.destination, pfx); + subnet(route_entry.destination, route_entry.destination); + + sai_status_t status = sai_route_api->remove_route_entry(&route_entry); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove tunnel route %s, rv:%d", + pfx.getIp().to_string().c_str(), status); + return status; + } + + if (route_entry.destination.addr_family == SAI_IP_ADDR_FAMILY_IPV4) + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_ROUTE); + } + else + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); + } + + SWSS_LOG_NOTICE("Removed tunnel route to %s ", pfx.to_string().c_str()); + return status; +} + +static sai_object_id_t create_tunnel(const IpAddress* p_dst_ip, const IpAddress* p_src_ip) +{ + sai_status_t status; + + sai_attribute_t attr; + sai_object_id_t overlay_if; + vector tunnel_attrs; + vector overlay_intf_attrs; + + sai_attribute_t overlay_intf_attr; + overlay_intf_attr.id = SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID; + overlay_intf_attr.value.oid = gVirtualRouterId; + overlay_intf_attrs.push_back(overlay_intf_attr); + + overlay_intf_attr.id = SAI_ROUTER_INTERFACE_ATTR_TYPE; + overlay_intf_attr.value.s32 = SAI_ROUTER_INTERFACE_TYPE_LOOPBACK; + overlay_intf_attrs.push_back(overlay_intf_attr); + + status = sai_router_intfs_api->create_router_interface(&overlay_if, gSwitchId, (uint32_t)overlay_intf_attrs.size(), overlay_intf_attrs.data()); + if (status != SAI_STATUS_SUCCESS) + { + throw std::runtime_error("Can't create overlay interface"); + } + + attr.id = SAI_TUNNEL_ATTR_TYPE; + attr.value.s32 = SAI_TUNNEL_TYPE_IPINIP; + tunnel_attrs.push_back(attr); + + attr.id = SAI_TUNNEL_ATTR_OVERLAY_INTERFACE; + attr.value.oid = overlay_if; + tunnel_attrs.push_back(attr); + + attr.id = SAI_TUNNEL_ATTR_UNDERLAY_INTERFACE; + attr.value.oid = gUnderlayIfId; + tunnel_attrs.push_back(attr); + + attr.id = SAI_TUNNEL_ATTR_PEER_MODE; + attr.value.s32 = SAI_TUNNEL_PEER_MODE_P2P; + tunnel_attrs.push_back(attr); + + if (p_src_ip != nullptr) + { + attr.id = SAI_TUNNEL_ATTR_ENCAP_SRC_IP; + copy(attr.value.ipaddr, p_src_ip->to_string()); + tunnel_attrs.push_back(attr); + } + + if (p_dst_ip != nullptr) + { + attr.id = SAI_TUNNEL_ATTR_ENCAP_DST_IP; + copy(attr.value.ipaddr, p_dst_ip->to_string()); + tunnel_attrs.push_back(attr); + } + + sai_object_id_t tunnel_id; + status = sai_tunnel_api->create_tunnel(&tunnel_id, gSwitchId, (uint32_t)tunnel_attrs.size(), tunnel_attrs.data()); + if (status != SAI_STATUS_SUCCESS) + { + throw std::runtime_error("Can't create a tunnel object"); + } + + return tunnel_id; +} + +static sai_object_id_t create_nh_tunnel(sai_object_id_t tunnel_id, IpAddress& ipAddr) +{ + std::vector next_hop_attrs; + sai_attribute_t next_hop_attr; + + next_hop_attr.id = SAI_NEXT_HOP_ATTR_TYPE; + next_hop_attr.value.s32 = SAI_NEXT_HOP_TYPE_TUNNEL_ENCAP; + next_hop_attrs.push_back(next_hop_attr); + + sai_ip_address_t host_ip; + swss::copy(host_ip, ipAddr); + + next_hop_attr.id = SAI_NEXT_HOP_ATTR_IP; + next_hop_attr.value.ipaddr = host_ip; + next_hop_attrs.push_back(next_hop_attr); + + next_hop_attr.id = SAI_NEXT_HOP_ATTR_TUNNEL_ID; + next_hop_attr.value.oid = tunnel_id; + next_hop_attrs.push_back(next_hop_attr); + + sai_object_id_t next_hop_id = SAI_NULL_OBJECT_ID; + sai_status_t status = sai_next_hop_api->create_next_hop(&next_hop_id, gSwitchId, + static_cast(next_hop_attrs.size()), + next_hop_attrs.data()); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Tunnel NH create failed for ip %s", ipAddr.to_string().c_str()); + } + else + { + SWSS_LOG_NOTICE("Tunnel NH created for ip %s", ipAddr.to_string().c_str()); + + if (ipAddr.isV4()) + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEXTHOP); + } + else + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_NEXTHOP); + } + } + + return next_hop_id; +} + +static bool remove_nh_tunnel(sai_object_id_t nh_id, IpAddress& ipAddr) +{ + sai_status_t status = sai_next_hop_api->remove_next_hop(nh_id); + if (status != SAI_STATUS_SUCCESS) + { + if (status == SAI_STATUS_ITEM_NOT_FOUND) + { + SWSS_LOG_ERROR("Failed to locate next hop %s rv:%d", + ipAddr.to_string().c_str(), status); + } + else + { + SWSS_LOG_ERROR("Failed to remove next hop %s rv:%d", + ipAddr.to_string().c_str(), status); + return false; + } + } + else + { + SWSS_LOG_NOTICE("Tunnel NH removed for ip %s",ipAddr.to_string().c_str()); + + if (ipAddr.isV4()) + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEXTHOP); + } + else + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_NEXTHOP); + } + } + + return true; +} + +MuxCable::MuxCable(string name, IpPrefix& srv_ip4, IpPrefix& srv_ip6, IpAddress peer_ip) + :mux_name_(name), srv_ip4_(srv_ip4), srv_ip6_(srv_ip6), peer_ip4_(peer_ip) +{ + mux_orch_ = gDirectory.get(); + mux_cb_orch_ = gDirectory.get(); + mux_state_orch_ = gDirectory.get(); + + nbr_handler_ = std::make_unique (MuxNbrHandler()); + + state_machine_handlers_.insert(handler_pair(MUX_STATE_INIT_ACTIVE, &MuxCable::stateInitActive)); + state_machine_handlers_.insert(handler_pair(MUX_STATE_STANDBY_ACTIVE, &MuxCable::stateActive)); + state_machine_handlers_.insert(handler_pair(MUX_STATE_INIT_STANDBY, &MuxCable::stateStandby)); + state_machine_handlers_.insert(handler_pair(MUX_STATE_ACTIVE_STANDBY, &MuxCable::stateStandby)); +} + +bool MuxCable::stateInitActive() +{ + SWSS_LOG_INFO("Set state to Active from %s", muxStateValToString.at(state_).c_str()); + + if (!nbrHandler()) + { + return false; + } + + return true; +} + +bool MuxCable::stateActive() +{ + SWSS_LOG_INFO("Set state to Active for %s", mux_name_.c_str()); + + Port port; + if (!gPortsOrch->getPort(mux_name_, port)) + { + SWSS_LOG_NOTICE("Port %s not found in port table", mux_name_.c_str()); + return false; + } + + if (!aclHandler(port.m_port_id, false)) + { + SWSS_LOG_INFO("Remove ACL drop rule failed for %s", mux_name_.c_str()); + return false; + } + + if (!nbrHandler()) + { + return false; + } + + if (remove_route(srv_ip4_) != SAI_STATUS_SUCCESS) + { + return false; + } + + if (remove_route(srv_ip6_) != SAI_STATUS_SUCCESS) + { + return false; + } + + mux_orch_->removeNextHopTunnel(MUX_TUNNEL, peer_ip4_); + + return true; +} + +bool MuxCable::stateStandby() +{ + SWSS_LOG_INFO("Set state to Standby for %s", mux_name_.c_str()); + + Port port; + if (!gPortsOrch->getPort(mux_name_, port)) + { + SWSS_LOG_NOTICE("Port %s not found in port table", mux_name_.c_str()); + return false; + } + + sai_object_id_t nh = mux_orch_->createNextHopTunnel(MUX_TUNNEL, peer_ip4_); + + if (nh == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_INFO("Null NH object id, retry for %s", peer_ip4_.to_string().c_str()); + return false; + } + + if (create_route(srv_ip4_, nh) != SAI_STATUS_SUCCESS) + { + return false; + } + + if (create_route(srv_ip6_, nh) != SAI_STATUS_SUCCESS) + { + remove_route(srv_ip4_); + return false; + } + + if (!nbrHandler(false)) + { + remove_route(srv_ip4_); + remove_route(srv_ip6_); + return false; + } + + if (!aclHandler(port.m_port_id)) + { + remove_route(srv_ip4_); + remove_route(srv_ip6_); + SWSS_LOG_INFO("Add ACL drop rule failed for %s", mux_name_.c_str()); + return false; + } + + return true; +} + +void MuxCable::setState(string new_state) +{ + SWSS_LOG_NOTICE("[%s] Set MUX state from %s to %s", mux_name_.c_str(), + muxStateValToString.at(state_).c_str(), new_state.c_str()); + + MuxState ns = muxStateStringToVal.at(new_state); + + auto it = muxStateTransition.find(make_pair(state_, ns)); + + if (it == muxStateTransition.end()) + { + SWSS_LOG_ERROR("State transition from %s to %s is not-handled ", + muxStateValToString.at(state_).c_str(), new_state.c_str()); + return; + } + + mux_cb_orch_->updateMuxState(mux_name_, new_state); + + MuxState state = state_; + state_ = ns; + + st_chg_in_progress_ = true; + + if (!(this->*(state_machine_handlers_[it->second]))()) + { + //Reset back to original state + state_ = state; + st_chg_in_progress_ = false; + throw std::runtime_error("Failed to handle state transition"); + } + + st_chg_in_progress_ = false; + SWSS_LOG_INFO("Changed state to %s", new_state.c_str()); + + return; +} + +string MuxCable::getState() +{ + SWSS_LOG_INFO("Get state request for %s, state %s", + mux_name_.c_str(), muxStateValToString.at(state_).c_str()); + + return (muxStateValToString.at(state_)); +} + +bool MuxCable::aclHandler(sai_object_id_t port, bool add) +{ + if (add) + { + acl_handler_ = make_shared(port); + } + else + { + acl_handler_.reset(); + } + + return true; +} + +bool MuxCable::isIpInSubnet(IpAddress ip) +{ + if (ip.isV4()) + { + return (srv_ip4_.isAddressInSubnet(ip)); + } + else + { + return (srv_ip6_.isAddressInSubnet(ip)); + } +} + +bool MuxCable::nbrHandler(bool enable) +{ + if (enable) + { + return nbr_handler_->enable(); + } + else + { + return nbr_handler_->disable(); + } +} + +void MuxNbrHandler::update(IpAddress ip, string alias, bool add) +{ + if (add) + { + neighbors_.add(ip); + if (!alias.empty() && alias != alias_) + { + alias_ = alias; + } + } + else + { + neighbors_.remove(ip); + } +} + +bool MuxNbrHandler::enable() +{ + NeighborEntry neigh; + + auto it = neighbors_.getIpAddresses().begin(); + while (it != neighbors_.getIpAddresses().end()) + { + neigh = NeighborEntry(*it, alias_); + if (!gNeighOrch->enableNeighbor(neigh)) + { + return false; + } + it++; + } + + return true; +} + +bool MuxNbrHandler::disable() +{ + NeighborEntry neigh; + + auto it = neighbors_.getIpAddresses().begin(); + while (it != neighbors_.getIpAddresses().end()) + { + neigh = NeighborEntry(*it, alias_); + if (!gNeighOrch->disableNeighbor(neigh)) + { + return false; + } + it++; + } + + return true; +} + +std::map MuxAclHandler::acl_table_; + +MuxAclHandler::MuxAclHandler(sai_object_id_t port) +{ + SWSS_LOG_ENTER(); + + // There is one handler instance per MUX port + acl_table_type_t table_type = ACL_TABLE_MUX; + string table_name = MUX_ACL_TABLE_NAME; + string rule_name = MUX_ACL_RULE_NAME; + + port_ = port; + auto found = acl_table_.find(table_name); + if (found == acl_table_.end()) + { + SWSS_LOG_NOTICE("First time create for port %" PRIx64 "", port); + + // First time handling of Mux Table, create ACL table, and bind + createMuxAclTable(port, table_name); + shared_ptr newRule = + make_shared(gAclOrch, rule_name, table_name, table_type); + createMuxAclRule(newRule, table_name); + } + else + { + SWSS_LOG_NOTICE("Binding port %" PRIx64 "", port); + // Otherwise just bind ACL table with the port + found->second.bind(port); + } +} + +MuxAclHandler::~MuxAclHandler(void) +{ + SWSS_LOG_ENTER(); + string table_name = MUX_ACL_TABLE_NAME; + + SWSS_LOG_NOTICE("Un-Binding port %" PRIx64 "", port_); + + auto found = acl_table_.find(table_name); + found->second.unbind(port_); +} + +void MuxAclHandler::createMuxAclTable(sai_object_id_t port, string strTable) +{ + SWSS_LOG_ENTER(); + + auto inserted = acl_table_.emplace(piecewise_construct, + std::forward_as_tuple(strTable), + std::forward_as_tuple()); + + assert(inserted.second); + + AclTable& acl_table = inserted.first->second; + acl_table.type = ACL_TABLE_MUX; + acl_table.id = strTable; + acl_table.link(port); + acl_table.stage = ACL_STAGE_INGRESS; + gAclOrch->addAclTable(acl_table); +} + +void MuxAclHandler::createMuxAclRule(shared_ptr rule, string strTable) +{ + SWSS_LOG_ENTER(); + + string attr_name, attr_value; + + attr_name = RULE_PRIORITY; + attr_value = "999"; + rule->validateAddPriority(attr_name, attr_value); + + attr_name = ACTION_PACKET_ACTION; + attr_value = PACKET_ACTION_DROP; + rule->validateAddAction(attr_name, attr_value); + + gAclOrch->addAclRule(rule, strTable); +} + +sai_object_id_t MuxOrch::createNextHopTunnel(std::string tunnelKey, swss::IpAddress& ipAddr) +{ + auto it = mux_tunnel_nh_.find(ipAddr); + if (it != mux_tunnel_nh_.end()) + { + ++it->second.ref_count; + return it->second.nh_id; + } + + sai_object_id_t nh = create_nh_tunnel(mux_tunnel_id_, ipAddr); + + if (SAI_NULL_OBJECT_ID != nh) + { + mux_tunnel_nh_[ipAddr] = { nh, 1 }; + } + + return nh; +} + +bool MuxOrch::removeNextHopTunnel(std::string tunnelKey, swss::IpAddress& ipAddr) +{ + auto it = mux_tunnel_nh_.find(ipAddr); + if (it == mux_tunnel_nh_.end()) + { + SWSS_LOG_NOTICE("NH doesn't exist %s, ip %s", tunnelKey.c_str(), ipAddr.to_string().c_str()); + return true; + } + + auto ref_cnt = --it->second.ref_count; + + if (it->second.ref_count == 0) + { + if (!remove_nh_tunnel(it->second.nh_id, ipAddr)) + { + SWSS_LOG_INFO("NH tunnel remove failed %s, ip %s", + tunnelKey.c_str(), ipAddr.to_string().c_str()); + } + mux_tunnel_nh_.erase(ipAddr); + } + + SWSS_LOG_INFO("NH tunnel removed %s, ip %s or decremented to ref count %d", + tunnelKey.c_str(), ipAddr.to_string().c_str(), ref_cnt); + return true; +} + +MuxCable* MuxOrch::findMuxCableInSubnet(IpAddress ip) +{ + for (auto it = mux_cable_tb_.begin(); it != mux_cable_tb_.end(); it++) + { + MuxCable* ptr = it->second.get(); + if (ptr->isIpInSubnet(ip)) + { + return ptr; + } + } + + return nullptr; +} + +bool MuxOrch::isNeighborActive(IpAddress nbr, string alias) +{ + MuxCable* ptr = findMuxCableInSubnet(nbr); + + if (ptr) + { + return ptr->isActive(); + } + + return true; +} + +void MuxOrch::updateNeighbor(const NeighborUpdate& update) +{ + for (auto it = mux_cable_tb_.begin(); it != mux_cable_tb_.end(); it++) + { + MuxCable* ptr = it->second.get(); + if (ptr->isIpInSubnet(update.entry.ip_address)) + { + ptr->updateNeighbor(update.entry.ip_address, update.entry.alias, update.add); + } + } +} + +void MuxOrch::update(SubjectType type, void *cntx) +{ + SWSS_LOG_ENTER(); + + assert(cntx); + + switch(type) + { + case SUBJECT_TYPE_NEIGH_CHANGE: + { + NeighborUpdate *update = static_cast(cntx); + updateNeighbor(*update); + break; + } + default: + /* Received update in which we are not interested + * Ignore it + */ + return; + } +} + +MuxOrch::MuxOrch(DBConnector *db, const std::vector &tables, TunnelDecapOrch* decapOrch, NeighOrch* neighOrch) : + Orch2(db, tables, request_), + decap_orch_(decapOrch), + neigh_orch_(neighOrch) +{ + handler_map_.insert(handler_pair(CFG_MUX_CABLE_TABLE_NAME, &MuxOrch::handleMuxCfg)); + handler_map_.insert(handler_pair(CFG_PEER_SWITCH_TABLE_NAME, &MuxOrch::handlePeerSwitch)); + + neigh_orch_->attach(this); +} + +bool MuxOrch::handleMuxCfg(const Request& request) +{ + SWSS_LOG_ENTER(); + + auto srv_ip = request.getAttrIpPrefix("server_ipv4"); + auto srv_ip6 = request.getAttrIpPrefix("server_ipv6"); + + const auto& port_name = request.getKeyString(0); + auto op = request.getOperation(); + + if (op == SET_COMMAND) + { + if(isMuxExists(port_name)) + { + SWSS_LOG_ERROR("Mux for port '%s' already exists", port_name.c_str()); + return true; + } + + if (mux_peer_switch_.isZero()) + { + SWSS_LOG_ERROR("Peer switch addr not yet configured, port '%s'", port_name.c_str()); + return false; + } + + mux_cable_tb_[port_name] = std::make_unique + (MuxCable(port_name, srv_ip, srv_ip6, mux_peer_switch_)); + + SWSS_LOG_NOTICE("Mux entry for port '%s' was added", port_name.c_str()); + } + else + { + if(!isMuxExists(port_name)) + { + SWSS_LOG_ERROR("Mux for port '%s' does not exists", port_name.c_str()); + return true; + } + + mux_cable_tb_.erase(port_name); + + SWSS_LOG_NOTICE("Mux cable for port '%s' was removed", port_name.c_str()); + } + + return true; +} + +bool MuxOrch::handlePeerSwitch(const Request& request) +{ + SWSS_LOG_ENTER(); + + auto peer_ip = request.getAttrIP("address_ipv4"); + + const auto& peer_name = request.getKeyString(0); + auto op = request.getOperation(); + + if (op == SET_COMMAND) + { + // Create P2P tunnel when peer_ip is available. + IpAddresses dst_ips = decap_orch_->getDstIpAddresses(MUX_TUNNEL); + if (!dst_ips.getSize()) + { + SWSS_LOG_NOTICE("Mux tunnel not yet created for '%s' peer ip '%s'", + MUX_TUNNEL, peer_ip.to_string().c_str()); + return false; + } + + auto it = dst_ips.getIpAddresses().begin(); + const IpAddress& dst_ip = *it; + mux_tunnel_id_ = create_tunnel(&peer_ip, &dst_ip); + mux_peer_switch_ = peer_ip; + SWSS_LOG_NOTICE("Mux peer ip '%s' was added, peer name '%s'", + peer_ip.to_string().c_str(), peer_name.c_str()); + } + else + { + SWSS_LOG_NOTICE("Mux peer ip '%s' delete (Not Implemented), peer name '%s'", + peer_ip.to_string().c_str(), peer_name.c_str()); + } + + return true; +} + +bool MuxOrch::addOperation(const Request& request) +{ + SWSS_LOG_ENTER(); + + try + { + auto& tn = request.getTableName(); + if (handler_map_.find(tn) == handler_map_.end()) + { + SWSS_LOG_ERROR(" %s handler is not initialized", tn.c_str()); + return true; + } + + return ((this->*(handler_map_[tn]))(request)); + } + catch(std::runtime_error& _) + { + SWSS_LOG_ERROR("Mux add operation error %s ", _.what()); + return true; + } + + return true; +} + +bool MuxOrch::delOperation(const Request& request) +{ + SWSS_LOG_ENTER(); + + try + { + auto& tn = request.getTableName(); + if (handler_map_.find(tn) == handler_map_.end()) + { + SWSS_LOG_ERROR(" %s handler is not initialized", tn.c_str()); + return true; + } + + return ((this->*(handler_map_[tn]))(request)); + } + catch(std::runtime_error& _) + { + SWSS_LOG_ERROR("Mux del operation error %s ", _.what()); + return true; + } + + return true; +} + +MuxCableOrch::MuxCableOrch(DBConnector *db, const std::string& tableName): + Orch2(db, tableName, request_) +{ + mux_table_ = unique_ptr(new Table(db, APP_HW_MUX_CABLE_TABLE_NAME)); +} + +void MuxCableOrch::updateMuxState(string portName, string muxState) +{ + vector tuples; + FieldValueTuple tuple("state", muxState); + tuples.push_back(tuple); + mux_table_->set(portName, tuples); +} + +bool MuxCableOrch::addOperation(const Request& request) +{ + SWSS_LOG_ENTER(); + + auto port_name = request.getKeyString(0); + + MuxOrch* mux_orch = gDirectory.get(); + if (!mux_orch->isMuxExists(port_name)) + { + SWSS_LOG_WARN("Mux entry for port '%s' doesn't exist", port_name.c_str()); + return false; + } + + auto state = request.getAttrString("state"); + auto mux_obj = mux_orch->getMuxCable(port_name); + + try + { + mux_obj->setState(state); + } + catch(const std::runtime_error& error) + { + SWSS_LOG_ERROR("Error setting state %s for port %s. Error: %s", + state.c_str(), port_name.c_str(), error.what()); + return false; + } + + SWSS_LOG_NOTICE("Mux State set to %s for port %s", state.c_str(), port_name.c_str()); + + return true; +} + +bool MuxCableOrch::delOperation(const Request& request) +{ + SWSS_LOG_ENTER(); + + auto port_name = request.getKeyString(0); + + SWSS_LOG_NOTICE("Deleting Mux state entry for port %s not implemented", port_name.c_str()); + + return true; +} + +MuxStateOrch::MuxStateOrch(DBConnector *db, const std::string& tableName) : + Orch2(db, tableName, request_), + mux_state_table_(db, STATE_MUX_CABLE_TABLE_NAME) +{ + SWSS_LOG_ENTER(); +} + +void MuxStateOrch::updateMuxState(string portName, string muxState) +{ + vector tuples; + FieldValueTuple tuple("state", muxState); + tuples.push_back(tuple); + mux_state_table_.set(portName, tuples); +} + +bool MuxStateOrch::addOperation(const Request& request) +{ + SWSS_LOG_ENTER(); + + auto port_name = request.getKeyString(0); + + MuxOrch* mux_orch = gDirectory.get(); + if (!mux_orch->isMuxExists(port_name)) + { + SWSS_LOG_WARN("Mux entry for port '%s' doesn't exist", port_name.c_str()); + return false; + } + + auto hw_state = request.getAttrString("state"); + auto mux_obj = mux_orch->getMuxCable(port_name); + string mux_state; + + try + { + mux_state = mux_obj->getState(); + } + catch(const std::runtime_error& error) + { + SWSS_LOG_ERROR("Error getting state for port %s Error: %s", port_name.c_str(), error.what()); + return false; + } + + if (mux_obj->isStateChangeInProgress()) + { + SWSS_LOG_NOTICE("Mux state change for port '%s' is in-progress", port_name.c_str()); + return false; + } + + if (mux_state != hw_state) + { + mux_state = MUX_HW_STATE_UNKNOWN; + } + + SWSS_LOG_NOTICE("Setting State DB entry (hw state %s, mux state %s) for port %s", + hw_state.c_str(), mux_state.c_str(), port_name.c_str()); + + updateMuxState(port_name, mux_state); + + return true; +} + +bool MuxStateOrch::delOperation(const Request& request) +{ + SWSS_LOG_ENTER(); + + auto port_name = request.getKeyString(0); + + SWSS_LOG_NOTICE("Deleting state table entry for Mux %s not implemented", port_name.c_str()); + + return true; +} diff --git a/orchagent/muxorch.h b/orchagent/muxorch.h new file mode 100644 index 000000000000..2d3c15c1cd2d --- /dev/null +++ b/orchagent/muxorch.h @@ -0,0 +1,249 @@ +#pragma once + +#include +#include +#include +#include + +#include "request_parser.h" +#include "portsorch.h" +#include "tunneldecaporch.h" +#include "aclorch.h" + +enum MuxState +{ + MUX_STATE_INIT, + MUX_STATE_ACTIVE, + MUX_STATE_STANDBY, + MUX_STATE_PENDING, + MUX_STATE_FAILED, +}; + +enum MuxStateChange +{ + MUX_STATE_INIT_ACTIVE, + MUX_STATE_INIT_STANDBY, + MUX_STATE_ACTIVE_STANDBY, + MUX_STATE_STANDBY_ACTIVE, + MUX_STATE_UNKNOWN_STATE +}; + +// Forward Declarations +class MuxOrch; +class MuxCableOrch; +class MuxStateOrch; + +// Mux ACL Handler for adding/removing ACLs +class MuxAclHandler +{ +public: + MuxAclHandler(sai_object_id_t port); + ~MuxAclHandler(void); + +private: + void createMuxAclTable(sai_object_id_t port, string strTable); + void createMuxAclRule(shared_ptr rule, string strTable); + + // class shared dict: ACL table name -> ACL table + static std::map acl_table_; + sai_object_id_t port_ = SAI_NULL_OBJECT_ID; +}; + +// Mux Neighbor Handler for adding/removing neigbhors +class MuxNbrHandler +{ +public: + MuxNbrHandler() = default; + + bool enable(); + bool disable(); + void update(IpAddress, string alias = "", bool = true); + +private: + IpAddresses neighbors_; + string alias_; +}; + +// Mux Cable object +class MuxCable +{ +public: + MuxCable(string name, IpPrefix& srv_ip4, IpPrefix& srv_ip6, IpAddress peer_ip); + + bool isActive() const + { + return (state_ == MuxState::MUX_STATE_ACTIVE); + } + + using handler_pair = pair; + using state_machine_handlers = map; + + void setState(string state); + string getState(); + bool isStateChangeInProgress() { return st_chg_in_progress_; } + + bool isIpInSubnet(IpAddress ip); + void updateNeighbor(IpAddress ip, string alias, bool add) + { + nbr_handler_->update(ip, alias, add); + } + +private: + bool stateActive(); + bool stateInitActive(); + bool stateStandby(); + + bool aclHandler(sai_object_id_t, bool = true); + bool nbrHandler(bool = true); + + string mux_name_; + + MuxState state_ = MuxState::MUX_STATE_INIT; + bool st_chg_in_progress_ = false; + + IpPrefix srv_ip4_, srv_ip6_; + IpAddress peer_ip4_; + + MuxOrch *mux_orch_; + MuxCableOrch *mux_cb_orch_; + MuxStateOrch *mux_state_orch_; + + shared_ptr acl_handler_ = { nullptr }; + unique_ptr nbr_handler_; + state_machine_handlers state_machine_handlers_; +}; + +const request_description_t mux_cfg_request_description = { + { REQ_T_STRING }, + { + { "state", REQ_T_STRING }, + { "server_ipv4", REQ_T_IP_PREFIX }, + { "server_ipv6", REQ_T_IP_PREFIX }, + { "address_ipv4", REQ_T_IP }, + }, + { } +}; + +struct NHTunnel +{ + sai_object_id_t nh_id; + int ref_count; +}; + +typedef std::unique_ptr MuxCable_T; +typedef std::map MuxCableTb; +typedef std::map MuxTunnelNHs; + +class MuxCfgRequest : public Request +{ +public: + MuxCfgRequest() : Request(mux_cfg_request_description, '|') { } +}; + + +class MuxOrch : public Orch2, public Observer, public Subject +{ +public: + MuxOrch(DBConnector *db, const std::vector &tables, TunnelDecapOrch*, NeighOrch*); + + using handler_pair = pair; + using handler_map = map; + + bool isMuxExists(const std::string& portName) const + { + return mux_cable_tb_.find(portName) != std::end(mux_cable_tb_); + } + + MuxCable* getMuxCable(const std::string& portName) + { + return mux_cable_tb_.at(portName).get(); + } + + MuxCable* findMuxCableInSubnet(IpAddress); + bool isNeighborActive(IpAddress nbr, string alias); + void update(SubjectType, void *); + void updateNeighbor(const NeighborUpdate&); + + sai_object_id_t createNextHopTunnel(std::string tunnelKey, IpAddress& ipAddr); + bool removeNextHopTunnel(std::string tunnelKey, IpAddress& ipAddr); + +private: + virtual bool addOperation(const Request& request); + virtual bool delOperation(const Request& request); + + bool handleMuxCfg(const Request&); + bool handlePeerSwitch(const Request&); + + IpAddress mux_peer_switch_ = 0x0; + sai_object_id_t mux_tunnel_id_; + + MuxCableTb mux_cable_tb_; + MuxTunnelNHs mux_tunnel_nh_; + + handler_map handler_map_; + + TunnelDecapOrch *decap_orch_; + NeighOrch *neigh_orch_; + + MuxCfgRequest request_; +}; + +const request_description_t mux_cable_request_description = { + { REQ_T_STRING }, + { + { "state", REQ_T_STRING }, + }, + { "state" } +}; + +class MuxCableRequest : public Request +{ +public: + MuxCableRequest() : Request(mux_cable_request_description, ':') { } +}; + +class MuxCableOrch : public Orch2 +{ +public: + MuxCableOrch(DBConnector *db, const std::string& tableName); + + void updateMuxState(string portName, string muxState); + +private: + virtual bool addOperation(const Request& request); + virtual bool delOperation(const Request& request); + + unique_ptr
mux_table_; + MuxCableRequest request_; +}; + +const request_description_t mux_state_request_description = { + { REQ_T_STRING }, + { + { "state", REQ_T_STRING }, + { "read_side", REQ_T_STRING }, + { "active_side", REQ_T_STRING }, + }, + { "state" } +}; + +class MuxStateRequest : public Request +{ +public: + MuxStateRequest() : Request(mux_state_request_description, '|') { } +}; + +class MuxStateOrch : public Orch2 +{ +public: + MuxStateOrch(DBConnector *db, const std::string& tableName); + + void updateMuxState(string portName, string muxState); + +private: + virtual bool addOperation(const Request& request); + virtual bool delOperation(const Request& request); + + swss::Table mux_state_table_; + MuxStateRequest request_; +}; diff --git a/orchagent/neighorch.cpp b/orchagent/neighorch.cpp index a3ff4b197bb2..e2f0b7e006cc 100644 --- a/orchagent/neighorch.cpp +++ b/orchagent/neighorch.cpp @@ -5,6 +5,7 @@ #include "crmorch.h" #include "routeorch.h" #include "directory.h" +#include "muxorch.h" extern sai_neighbor_api_t* sai_neighbor_api; extern sai_next_hop_api_t* sai_next_hop_api; @@ -13,8 +14,8 @@ extern PortsOrch *gPortsOrch; extern sai_object_id_t gSwitchId; extern CrmOrch *gCrmOrch; extern RouteOrch *gRouteOrch; -extern Directory gDirectory; extern FgNhgOrch *gFgNhgOrch; +extern Directory gDirectory; const int neighorch_pri = 30; @@ -91,9 +92,9 @@ void NeighOrch::processFDBFlushUpdate(const FdbFlushUpdate& update) for (const auto &neighborEntry : m_syncdNeighbors) { if (neighborEntry.first.alias == vlan.m_alias && - neighborEntry.second == entry.mac) + neighborEntry.second.mac == entry.mac) { - resolveNeighborEntry(neighborEntry.first, neighborEntry.second); + resolveNeighborEntry(neighborEntry.first, neighborEntry.second.mac); } } } @@ -392,7 +393,7 @@ bool NeighOrch::getNeighborEntry(const NextHopKey &nexthop, NeighborEntry &neigh if (entry.first.ip_address == nexthop.ip_address && entry.first.alias == nexthop.alias) { neighborEntry = entry.first; - macAddress = entry.second; + macAddress = entry.second.mac; return true; } } @@ -474,7 +475,8 @@ void NeighOrch::doTask(Consumer &consumer) mac_address = MacAddress(fvValue(*i)); } - if (m_syncdNeighbors.find(neighbor_entry) == m_syncdNeighbors.end() || m_syncdNeighbors[neighbor_entry] != mac_address) + if (m_syncdNeighbors.find(neighbor_entry) == m_syncdNeighbors.end() + || m_syncdNeighbors[neighbor_entry].mac != mac_address) { if (addNeighbor(neighbor_entry, mac_address)) { @@ -548,13 +550,20 @@ bool NeighOrch::addNeighbor(const NeighborEntry &neighborEntry, const MacAddress neighbor_entry.switch_id = gSwitchId; copy(neighbor_entry.ip_address, ip_address); + vector neighbor_attrs; sai_attribute_t neighbor_attr; + neighbor_attr.id = SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS; memcpy(neighbor_attr.value.mac, macAddress.getMac(), 6); + neighbor_attrs.push_back(neighbor_attr); - if (m_syncdNeighbors.find(neighborEntry) == m_syncdNeighbors.end()) + MuxOrch* mux_orch = gDirectory.get(); + bool hw_config = isHwConfigured(neighborEntry); + + if (!hw_config && mux_orch->isNeighborActive(ip_address, alias)) { - status = sai_neighbor_api->create_neighbor_entry(&neighbor_entry, 1, &neighbor_attr); + status = sai_neighbor_api->create_neighbor_entry(&neighbor_entry, + (uint32_t)neighbor_attrs.size(), neighbor_attrs.data()); if (status != SAI_STATUS_SUCCESS) { if (status == SAI_STATUS_ITEM_ALREADY_EXISTS) @@ -606,8 +615,9 @@ bool NeighOrch::addNeighbor(const NeighborEntry &neighborEntry, const MacAddress return false; } + hw_config = true; } - else + else if (isHwConfigured(neighborEntry)) { status = sai_neighbor_api->set_neighbor_entry_attribute(&neighbor_entry, &neighbor_attr); if (status != SAI_STATUS_SUCCESS) @@ -619,7 +629,7 @@ bool NeighOrch::addNeighbor(const NeighborEntry &neighborEntry, const MacAddress SWSS_LOG_NOTICE("Updated neighbor %s on %s", macAddress.to_string().c_str(), alias.c_str()); } - m_syncdNeighbors[neighborEntry] = macAddress; + m_syncdNeighbors[neighborEntry] = { macAddress, hw_config }; NeighborUpdate update = { neighborEntry, macAddress, true }; notify(SUBJECT_TYPE_NEIGH_CHANGE, static_cast(&update)); @@ -627,7 +637,7 @@ bool NeighOrch::addNeighbor(const NeighborEntry &neighborEntry, const MacAddress return true; } -bool NeighOrch::removeNeighbor(const NeighborEntry &neighborEntry) +bool NeighOrch::removeNeighbor(const NeighborEntry &neighborEntry, bool disable) { SWSS_LOG_ENTER(); @@ -644,88 +654,146 @@ bool NeighOrch::removeNeighbor(const NeighborEntry &neighborEntry) if (m_syncdNextHops[nexthop].ref_count > 0) { SWSS_LOG_INFO("Failed to remove still referenced neighbor %s on %s", - m_syncdNeighbors[neighborEntry].to_string().c_str(), alias.c_str()); + m_syncdNeighbors[neighborEntry].mac.to_string().c_str(), alias.c_str()); return false; } - sai_object_id_t rif_id = m_intfsOrch->getRouterIntfsId(alias); + if (isHwConfigured(neighborEntry)) + { + sai_object_id_t rif_id = m_intfsOrch->getRouterIntfsId(alias); - sai_neighbor_entry_t neighbor_entry; - neighbor_entry.rif_id = rif_id; - neighbor_entry.switch_id = gSwitchId; - copy(neighbor_entry.ip_address, ip_address); + sai_neighbor_entry_t neighbor_entry; + neighbor_entry.rif_id = rif_id; + neighbor_entry.switch_id = gSwitchId; + copy(neighbor_entry.ip_address, ip_address); - sai_object_id_t next_hop_id = m_syncdNextHops[nexthop].next_hop_id; - status = sai_next_hop_api->remove_next_hop(next_hop_id); - if (status != SAI_STATUS_SUCCESS) - { - /* When next hop is not found, we continue to remove neighbor entry. */ - if (status == SAI_STATUS_ITEM_NOT_FOUND) + sai_object_id_t next_hop_id = m_syncdNextHops[nexthop].next_hop_id; + status = sai_next_hop_api->remove_next_hop(next_hop_id); + if (status != SAI_STATUS_SUCCESS) { - SWSS_LOG_ERROR("Failed to locate next hop %s on %s, rv:%d", - ip_address.to_string().c_str(), alias.c_str(), status); + /* When next hop is not found, we continue to remove neighbor entry. */ + if (status == SAI_STATUS_ITEM_NOT_FOUND) + { + SWSS_LOG_ERROR("Failed to locate next hop %s on %s, rv:%d", + ip_address.to_string().c_str(), alias.c_str(), status); + } + else + { + SWSS_LOG_ERROR("Failed to remove next hop %s on %s, rv:%d", + ip_address.to_string().c_str(), alias.c_str(), status); + return false; + } } - else + + if (status != SAI_STATUS_ITEM_NOT_FOUND) { - SWSS_LOG_ERROR("Failed to remove next hop %s on %s, rv:%d", - ip_address.to_string().c_str(), alias.c_str(), status); - return false; + if (neighbor_entry.ip_address.addr_family == SAI_IP_ADDR_FAMILY_IPV4) + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEXTHOP); + } + else + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_NEXTHOP); + } + } + + SWSS_LOG_NOTICE("Removed next hop %s on %s", + ip_address.to_string().c_str(), alias.c_str()); + + status = sai_neighbor_api->remove_neighbor_entry(&neighbor_entry); + if (status != SAI_STATUS_SUCCESS) + { + if (status == SAI_STATUS_ITEM_NOT_FOUND) + { + SWSS_LOG_ERROR("Failed to locate neigbor %s on %s, rv:%d", + m_syncdNeighbors[neighborEntry].mac.to_string().c_str(), alias.c_str(), status); + return true; + } + else + { + SWSS_LOG_ERROR("Failed to remove neighbor %s on %s, rv:%d", + m_syncdNeighbors[neighborEntry].mac.to_string().c_str(), alias.c_str(), status); + return false; + } } - } - if (status != SAI_STATUS_ITEM_NOT_FOUND) - { if (neighbor_entry.ip_address.addr_family == SAI_IP_ADDR_FAMILY_IPV4) { - gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEXTHOP); + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEIGHBOR); } else { - gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_NEXTHOP); + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_NEIGHBOR); } + + removeNextHop(ip_address, alias); + m_intfsOrch->decreaseRouterIntfsRefCount(alias); + } + + SWSS_LOG_NOTICE("Removed neighbor %s on %s", + m_syncdNeighbors[neighborEntry].mac.to_string().c_str(), alias.c_str()); + + /* Do not delete entry from cache if its disable request */ + if (disable) + { + m_syncdNeighbors[neighborEntry].hw_configured = false; + return true; } - SWSS_LOG_NOTICE("Removed next hop %s on %s", - ip_address.to_string().c_str(), alias.c_str()); + m_syncdNeighbors.erase(neighborEntry); + + NeighborUpdate update = { neighborEntry, MacAddress(), false }; + notify(SUBJECT_TYPE_NEIGH_CHANGE, static_cast(&update)); - status = sai_neighbor_api->remove_neighbor_entry(&neighbor_entry); - if (status != SAI_STATUS_SUCCESS) + return true; +} + +bool NeighOrch::isHwConfigured(const NeighborEntry& neighborEntry) +{ + if (m_syncdNeighbors.find(neighborEntry) == m_syncdNeighbors.end()) { - if (status == SAI_STATUS_ITEM_NOT_FOUND) - { - SWSS_LOG_ERROR("Failed to locate neigbor %s on %s, rv:%d", - m_syncdNeighbors[neighborEntry].to_string().c_str(), alias.c_str(), status); - return true; - } - else - { - SWSS_LOG_ERROR("Failed to remove neighbor %s on %s, rv:%d", - m_syncdNeighbors[neighborEntry].to_string().c_str(), alias.c_str(), status); - return false; - } + return false; } - if (neighbor_entry.ip_address.addr_family == SAI_IP_ADDR_FAMILY_IPV4) + return (m_syncdNeighbors[neighborEntry].hw_configured); +} + +bool NeighOrch::enableNeighbor(const NeighborEntry& neighborEntry) +{ + SWSS_LOG_NOTICE("Neighbor enable request for %s ", neighborEntry.ip_address.to_string().c_str()); + + if (m_syncdNeighbors.find(neighborEntry) == m_syncdNeighbors.end()) { - gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEIGHBOR); + SWSS_LOG_INFO("Neighbor %s not found", neighborEntry.ip_address.to_string().c_str()); + return true; } - else + + if (isHwConfigured(neighborEntry)) { - gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_NEIGHBOR); + SWSS_LOG_INFO("Neighbor %s is already programmed to HW", neighborEntry.ip_address.to_string().c_str()); + return true; } - SWSS_LOG_NOTICE("Removed neighbor %s on %s", - m_syncdNeighbors[neighborEntry].to_string().c_str(), alias.c_str()); + return addNeighbor(neighborEntry, m_syncdNeighbors[neighborEntry].mac); +} - m_syncdNeighbors.erase(neighborEntry); - m_intfsOrch->decreaseRouterIntfsRefCount(alias); +bool NeighOrch::disableNeighbor(const NeighborEntry& neighborEntry) +{ + SWSS_LOG_NOTICE("Neighbor disable request for %s ", neighborEntry.ip_address.to_string().c_str()); - NeighborUpdate update = { neighborEntry, MacAddress(), false }; - notify(SUBJECT_TYPE_NEIGH_CHANGE, static_cast(&update)); + if (m_syncdNeighbors.find(neighborEntry) == m_syncdNeighbors.end()) + { + SWSS_LOG_INFO("Neighbor %s not found", neighborEntry.ip_address.to_string().c_str()); + return true; + } - removeNextHop(ip_address, alias); + if (!isHwConfigured(neighborEntry)) + { + SWSS_LOG_INFO("Neighbor %s is not programmed to HW", neighborEntry.ip_address.to_string().c_str()); + return true; + } - return true; + return removeNeighbor(neighborEntry, true); } sai_object_id_t NeighOrch::addTunnelNextHop(const NextHopKey& nh) diff --git a/orchagent/neighorch.h b/orchagent/neighorch.h index e7b57b7dcab4..bd199efde550 100644 --- a/orchagent/neighorch.h +++ b/orchagent/neighorch.h @@ -23,8 +23,14 @@ struct NextHopEntry uint32_t nh_flags; // flags }; +struct NeighborData +{ + MacAddress mac; + bool hw_configured = false; // False means, entry is not written to HW +}; + /* NeighborTable: NeighborEntry, neighbor MAC address */ -typedef map NeighborTable; +typedef map NeighborTable; /* NextHopTable: NextHopKey, NextHopEntry */ typedef map NextHopTable; @@ -52,6 +58,10 @@ class NeighOrch : public Orch, public Subject, public Observer bool getNeighborEntry(const NextHopKey&, NeighborEntry&, MacAddress&); bool getNeighborEntry(const IpAddress&, NeighborEntry&, MacAddress&); + bool enableNeighbor(const NeighborEntry&); + bool disableNeighbor(const NeighborEntry&); + bool isHwConfigured(const NeighborEntry&); + sai_object_id_t addTunnelNextHop(const NextHopKey&); bool removeTunnelNextHop(const NextHopKey&); @@ -73,7 +83,7 @@ class NeighOrch : public Orch, public Subject, public Observer bool removeNextHop(const IpAddress&, const string&); bool addNeighbor(const NeighborEntry&, const MacAddress&); - bool removeNeighbor(const NeighborEntry&); + bool removeNeighbor(const NeighborEntry&, bool disable = false); bool setNextHopFlag(const NextHopKey &, const uint32_t); bool clearNextHopFlag(const NextHopKey &, const uint32_t); diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index f4ba694df0af..4612dc1ff11d 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -234,6 +234,19 @@ bool OrchDaemon::init() gNatOrch = new NatOrch(m_applDb, m_stateDb, nat_tables, gRouteOrch, gNeighOrch); + vector mux_tables = { + CFG_MUX_CABLE_TABLE_NAME, + CFG_PEER_SWITCH_TABLE_NAME + }; + MuxOrch *mux_orch = new MuxOrch(m_configDb, mux_tables, tunnel_decap_orch, gNeighOrch); + gDirectory.set(mux_orch); + + MuxCableOrch *mux_cb_orch = new MuxCableOrch(m_applDb, APP_MUX_CABLE_TABLE_NAME); + gDirectory.set(mux_cb_orch); + + MuxStateOrch *mux_st_orch = new MuxStateOrch(m_stateDb, STATE_HW_MUX_CABLE_TABLE_NAME); + gDirectory.set(mux_st_orch); + /* * The order of the orch list is important for state restore of warm start and * the queued processing in m_toSync map after gPortsOrch->allPortsReady() is set. @@ -291,6 +304,9 @@ bool OrchDaemon::init() m_orchList.push_back(vnet_rt_orch); m_orchList.push_back(gNatOrch); m_orchList.push_back(gFgNhgOrch); + m_orchList.push_back(mux_orch); + m_orchList.push_back(mux_cb_orch); + m_orchList.push_back(mux_st_orch); m_select = new Select(); diff --git a/orchagent/orchdaemon.h b/orchagent/orchdaemon.h index 3094692df69d..1215958a901a 100644 --- a/orchagent/orchdaemon.h +++ b/orchagent/orchdaemon.h @@ -31,6 +31,7 @@ #include "debugcounterorch.h" #include "directory.h" #include "natorch.h" +#include "muxorch.h" using namespace swss; diff --git a/orchagent/request_parser.cpp b/orchagent/request_parser.cpp index a08777ad1fb5..5b7824c3d71e 100644 --- a/orchagent/request_parser.cpp +++ b/orchagent/request_parser.cpp @@ -178,6 +178,9 @@ void Request::parseAttrs(const KeyOpFieldsValuesTuple& request) case REQ_T_IP: attr_item_ip_[fvField(*i)] = parseIpAddress(fvValue(*i)); break; + case REQ_T_IP_PREFIX: + attr_item_ip_prefix_[fvField(*i)] = parseIpPrefix(fvValue(*i)); + break; case REQ_T_UINT: attr_item_uint_[fvField(*i)] = parseUint(fvValue(*i)); break; diff --git a/orchagent/request_parser.h b/orchagent/request_parser.h index 8c06e37eb059..4445c5afbef7 100644 --- a/orchagent/request_parser.h +++ b/orchagent/request_parser.h @@ -117,6 +117,12 @@ class Request return attr_item_ip_.at(attr_name); } + swss::IpPrefix getAttrIpPrefix(const std::string& attr_name) const + { + assert(is_parsed_); + return attr_item_ip_prefix_.at(attr_name); + } + const uint64_t& getAttrUint(const std::string& attr_name) const { assert(is_parsed_); @@ -185,6 +191,7 @@ class Request std::unordered_map attr_item_packet_actions_; std::unordered_map attr_item_vlan_; std::unordered_map attr_item_ip_; + std::unordered_map attr_item_ip_prefix_; std::unordered_map attr_item_uint_; std::unordered_map> attr_item_set_; }; diff --git a/orchagent/tunneldecaporch.cpp b/orchagent/tunneldecaporch.cpp index a99c074993ac..9aad474a0ed6 100644 --- a/orchagent/tunneldecaporch.cpp +++ b/orchagent/tunneldecaporch.cpp @@ -2,16 +2,19 @@ #include #include "tunneldecaporch.h" #include "portsorch.h" +#include "crmorch.h" #include "logger.h" #include "swssnet.h" extern sai_tunnel_api_t* sai_tunnel_api; extern sai_router_interface_api_t* sai_router_intfs_api; +extern sai_next_hop_api_t* sai_next_hop_api; extern sai_object_id_t gVirtualRouterId; extern sai_object_id_t gUnderlayIfId; extern sai_object_id_t gSwitchId; extern PortsOrch* gPortsOrch; +extern CrmOrch* gCrmOrch; TunnelDecapOrch::TunnelDecapOrch(DBConnector *db, string tableName) : Orch(db, tableName) { @@ -41,6 +44,7 @@ void TunnelDecapOrch::doTask(Consumer& consumer) string tunnel_type; string dscp_mode; string ecn_mode; + string encap_ecn_mode; string ttl_mode; bool valid = true; @@ -125,6 +129,20 @@ void TunnelDecapOrch::doTask(Consumer& consumer) setTunnelAttribute(fvField(i), ecn_mode, tunnelTable.find(key)->second.tunnel_id); } } + else if (fvField(i) == "encap_ecn_mode") + { + encap_ecn_mode = fvValue(i); + if (encap_ecn_mode != "standard") + { + SWSS_LOG_ERROR("Only standard encap ecn mode is supported currently %s\n", ecn_mode.c_str()); + valid = false; + break; + } + if (exists) + { + setTunnelAttribute(fvField(i), encap_ecn_mode, tunnelTable.find(key)->second.tunnel_id); + } + } else if (fvField(i) == "ttl_mode") { ttl_mode = fvValue(i); @@ -144,7 +162,7 @@ void TunnelDecapOrch::doTask(Consumer& consumer) // create new tunnel if it doesn't exists already if (valid && !exists) { - if (addDecapTunnel(key, tunnel_type, ip_addresses, p_src_ip, dscp_mode, ecn_mode, ttl_mode)) + if (addDecapTunnel(key, tunnel_type, ip_addresses, p_src_ip, dscp_mode, ecn_mode, encap_ecn_mode, ttl_mode)) { SWSS_LOG_NOTICE("Tunnel(s) added to ASIC_DB."); } @@ -186,7 +204,7 @@ void TunnelDecapOrch::doTask(Consumer& consumer) * Return Values: * @return true on success and false if there's an error */ -bool TunnelDecapOrch::addDecapTunnel(string key, string type, IpAddresses dst_ip, IpAddress* p_src_ip, string dscp, string ecn, string ttl) +bool TunnelDecapOrch::addDecapTunnel(string key, string type, IpAddresses dst_ip, IpAddress* p_src_ip, string dscp, string ecn, string encap_ecn, string ttl) { SWSS_LOG_ENTER(); @@ -250,6 +268,16 @@ bool TunnelDecapOrch::addDecapTunnel(string key, string type, IpAddresses dst_ip } tunnel_attrs.push_back(attr); + if (!encap_ecn.empty()) + { + attr.id = SAI_TUNNEL_ATTR_ENCAP_ECN_MODE; + if (encap_ecn == "standard") + { + attr.value.s32 = SAI_TUNNEL_ENCAP_ECN_MODE_STANDARD; + tunnel_attrs.push_back(attr); + } + } + // ttl mode (uniform/pipe) attr.id = SAI_TUNNEL_ATTR_DECAP_TTL_MODE; if (ttl == "uniform") @@ -283,10 +311,7 @@ bool TunnelDecapOrch::addDecapTunnel(string key, string type, IpAddresses dst_ip return false; } - tunnelTable[key] = { tunnel_id, overlayIfId, {} }; - - // TODO: - // there should also be "business logic" for netbouncer in the "tunnel application" code, which is a different source file and daemon process + tunnelTable[key] = { tunnel_id, overlayIfId, dst_ip, {} }; // create a decap tunnel entry for every ip if (!addDecapTunnelTermEntries(key, dst_ip, tunnel_id)) @@ -409,6 +434,16 @@ bool TunnelDecapOrch::setTunnelAttribute(string field, string value, sai_object_ } } + if (field == "encap_ecn_mode") + { + // encap ecn mode (only standard is supported) + attr.id = SAI_TUNNEL_ATTR_ENCAP_ECN_MODE; + if (value == "standard") + { + attr.value.s32 = SAI_TUNNEL_ENCAP_ECN_MODE_STANDARD; + } + } + if (field == "ttl_mode") { // ttl mode (uniform/pipe) @@ -467,6 +502,7 @@ bool TunnelDecapOrch::setIpAttribute(string key, IpAddresses new_ip_addresses, s vector tunnel_term_info_copy(tunnel_info->tunnel_term_info); tunnel_info->tunnel_term_info.clear(); + tunnel_info->dst_ip_addrs = new_ip_addresses; // loop through original ips and remove ips not in the new ip_addresses for (auto it = tunnel_term_info_copy.begin(); it != tunnel_term_info_copy.end(); ++it) @@ -568,3 +604,163 @@ bool TunnelDecapOrch::removeDecapTunnelTermEntry(sai_object_id_t tunnel_term_id, SWSS_LOG_NOTICE("Removed decap tunnel term entry with ip address: %s", ip.c_str()); return true; } + +sai_object_id_t TunnelDecapOrch::getNextHopTunnel(std::string tunnelKey, IpAddress& ipAddr) +{ + auto nh = tunnelNhs.find(tunnelKey); + if (nh == tunnelNhs.end()) + { + return SAI_NULL_OBJECT_ID; + } + + auto it = nh->second.find(ipAddr); + if (it == nh->second.end()) + { + return SAI_NULL_OBJECT_ID; + } + + return nh->second[ipAddr].nh_id; +} + +int TunnelDecapOrch::incNextHopRef(std::string tunnelKey, IpAddress& ipAddr) +{ + return (++ tunnelNhs[tunnelKey][ipAddr].ref_count); +} + +int TunnelDecapOrch::decNextHopRef(std::string tunnelKey, IpAddress& ipAddr) +{ + return (-- tunnelNhs[tunnelKey][ipAddr].ref_count); +} + +sai_object_id_t TunnelDecapOrch::createNextHopTunnel(std::string tunnelKey, IpAddress& ipAddr) +{ + if (tunnelTable.find(tunnelKey) == tunnelTable.end()) + { + SWSS_LOG_ERROR("Tunnel not found %s", tunnelKey.c_str()); + return SAI_NULL_OBJECT_ID; + } + + sai_object_id_t nhid; + if ((nhid = getNextHopTunnel(tunnelKey, ipAddr)) != SAI_NULL_OBJECT_ID) + { + SWSS_LOG_INFO("NH tunnel already exist '%s'", ipAddr.to_string().c_str()); + incNextHopRef(tunnelKey, ipAddr); + return nhid; + } + + TunnelEntry *tunnel_info = &tunnelTable.find(tunnelKey)->second; + + std::vector next_hop_attrs; + sai_attribute_t next_hop_attr; + + next_hop_attr.id = SAI_NEXT_HOP_ATTR_TYPE; + next_hop_attr.value.s32 = SAI_NEXT_HOP_TYPE_TUNNEL_ENCAP; + next_hop_attrs.push_back(next_hop_attr); + + sai_ip_address_t host_ip; + swss::copy(host_ip, ipAddr); + + next_hop_attr.id = SAI_NEXT_HOP_ATTR_IP; + next_hop_attr.value.ipaddr = host_ip; + next_hop_attrs.push_back(next_hop_attr); + + next_hop_attr.id = SAI_NEXT_HOP_ATTR_TUNNEL_ID; + next_hop_attr.value.oid = tunnel_info->tunnel_id; + next_hop_attrs.push_back(next_hop_attr); + + sai_object_id_t next_hop_id = SAI_NULL_OBJECT_ID; + sai_status_t status = sai_next_hop_api->create_next_hop(&next_hop_id, gSwitchId, + static_cast(next_hop_attrs.size()), + next_hop_attrs.data()); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Tunnel NH create failed %s, ip %s", tunnelKey.c_str(), + ipAddr.to_string().c_str()); + } + else + { + SWSS_LOG_NOTICE("Tunnel NH created %s, ip %s", + tunnelKey.c_str(), ipAddr.to_string().c_str()); + + if (ipAddr.isV4()) + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEXTHOP); + } + else + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_NEXTHOP); + } + + tunnelNhs[tunnelKey][ipAddr] = { next_hop_id, 1 }; + } + + return next_hop_id; +} + +bool TunnelDecapOrch::removeNextHopTunnel(std::string tunnelKey, IpAddress& ipAddr) +{ + if (tunnelTable.find(tunnelKey) == tunnelTable.end()) + { + SWSS_LOG_ERROR("Tunnel not found %s", tunnelKey.c_str()); + return true; + } + + sai_object_id_t nhid; + if ((nhid = getNextHopTunnel(tunnelKey, ipAddr)) == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_ERROR("NH tunnel doesn't exist '%s'", ipAddr.to_string().c_str()); + return true; + } + + if (decNextHopRef(tunnelKey, ipAddr)) + { + SWSS_LOG_NOTICE("Tunnel NH referenced, decremented ref count %s, ip %s", + tunnelKey.c_str(), ipAddr.to_string().c_str()); + return true; + } + + sai_status_t status = sai_next_hop_api->remove_next_hop(nhid); + if (status != SAI_STATUS_SUCCESS) + { + if (status == SAI_STATUS_ITEM_NOT_FOUND) + { + SWSS_LOG_ERROR("Failed to locate next hop %s on %s, rv:%d", + ipAddr.to_string().c_str(), tunnelKey.c_str(), status); + } + else + { + SWSS_LOG_ERROR("Failed to remove next hop %s on %s, rv:%d", + ipAddr.to_string().c_str(), tunnelKey.c_str(), status); + return false; + } + } + else + { + SWSS_LOG_NOTICE("Tunnel NH removed %s, ip %s", + tunnelKey.c_str(), ipAddr.to_string().c_str()); + + if (ipAddr.isV4()) + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_NEXTHOP); + } + else + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_NEXTHOP); + } + } + + tunnelNhs[tunnelKey].erase(ipAddr); + + return true; +} + +IpAddresses TunnelDecapOrch::getDstIpAddresses(std::string tunnelKey) +{ + if (tunnelTable.find(tunnelKey) == tunnelTable.end()) + { + SWSS_LOG_ERROR("Tunnel not found %s", tunnelKey.c_str()); + return IpAddresses(); + } + + return tunnelTable[tunnelKey].dst_ip_addrs; +} diff --git a/orchagent/tunneldecaporch.h b/orchagent/tunneldecaporch.h index 31ca853e3d83..f7b5f923d997 100644 --- a/orchagent/tunneldecaporch.h +++ b/orchagent/tunneldecaporch.h @@ -19,25 +19,44 @@ struct TunnelEntry { sai_object_id_t tunnel_id; // tunnel id sai_object_id_t overlay_intf_id; // overlay interface id + swss::IpAddresses dst_ip_addrs; // destination ip addresses std::vector tunnel_term_info; // tunnel_entry ids related to the tunnel abd ips related to the tunnel (all ips for tunnel entries that refer to this tunnel) }; +struct NexthopTunnel +{ + sai_object_id_t nh_id; + int ref_count; +}; + /* TunnelTable: key string, tunnel object id */ typedef std::map TunnelTable; /* ExistingIps: ips that currently have term entries */ typedef std::unordered_set ExistingIps; +/* Nexthop IP to refcount map */ +typedef std::map Nexthop; + +/* Tunnel to nexthop maps */ +typedef std::map TunnelNhs; + class TunnelDecapOrch : public Orch { public: TunnelDecapOrch(swss::DBConnector *db, std::string tableName); + sai_object_id_t createNextHopTunnel(std::string tunnelKey, swss::IpAddress& ipAddr); + bool removeNextHopTunnel(std::string tunnelKey, swss::IpAddress& ipAddr); + swss::IpAddresses getDstIpAddresses(std::string tunnelKey); + private: TunnelTable tunnelTable; ExistingIps existingIps; + TunnelNhs tunnelNhs; - bool addDecapTunnel(std::string key, std::string type, swss::IpAddresses dst_ip, swss::IpAddress* p_src_ip, std::string dscp, std::string ecn, std::string ttl); + bool addDecapTunnel(std::string key, std::string type, swss::IpAddresses dst_ip, swss::IpAddress* p_src_ip, + std::string dscp, std::string ecn, std::string encap_ecn, std::string ttl); bool removeDecapTunnel(std::string key); bool addDecapTunnelTermEntries(std::string tunnelKey, swss::IpAddresses dst_ip, sai_object_id_t tunnel_id); @@ -46,6 +65,10 @@ class TunnelDecapOrch : public Orch bool setTunnelAttribute(std::string field, std::string value, sai_object_id_t existing_tunnel_id); bool setIpAttribute(std::string key, swss::IpAddresses new_ip_addresses, sai_object_id_t tunnel_id); + sai_object_id_t getNextHopTunnel(std::string tunnelKey, swss::IpAddress& ipAddr); + int incNextHopRef(std::string tunnelKey, swss::IpAddress& ipAddr); + int decNextHopRef(std::string tunnelKey, swss::IpAddress& ipAddr); + void doTask(Consumer& consumer); }; #endif diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 40cbbb4fcee1..252b7d9c889a 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -64,7 +64,8 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/chassisorch.cpp \ $(top_srcdir)/orchagent/sfloworch.cpp \ $(top_srcdir)/orchagent/debugcounterorch.cpp \ - $(top_srcdir)/orchagent/natorch.cpp + $(top_srcdir)/orchagent/natorch.cpp \ + $(top_srcdir)/orchagent/muxorch.cpp tests_SOURCES += $(FLEX_CTR_DIR)/flex_counter_manager.cpp $(FLEX_CTR_DIR)/flex_counter_stat_manager.cpp tests_SOURCES += $(DEBUG_CTR_DIR)/debug_counter.cpp $(DEBUG_CTR_DIR)/drop_counter.cpp diff --git a/tests/test_mux.py b/tests/test_mux.py new file mode 100644 index 000000000000..c0a0aa1db873 --- /dev/null +++ b/tests/test_mux.py @@ -0,0 +1,262 @@ +import time +import pytest + +from swsscommon import swsscommon + + +def create_fvs(**kwargs): + return swsscommon.FieldValuePairs(list(kwargs.items())) + +def create_entry(tbl, key, pairs): + fvs = swsscommon.FieldValuePairs(pairs) + tbl.set(key, fvs) + time.sleep(1) + +def create_entry_tbl(db, table, separator, key, pairs): + tbl = swsscommon.Table(db, table) + create_entry(tbl, key, pairs) + + +class TestMuxTunnelBase(object): + APP_TUNNEL_DECAP_TABLE_NAME = "TUNNEL_DECAP_TABLE" + ASIC_TUNNEL_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL" + ASIC_TUNNEL_TERM_ENTRIES = "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL_TERM_TABLE_ENTRY" + ASIC_RIF_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE" + ASIC_VRF_TABLE = "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER" + + ecn_modes_map = { + "standard" : "SAI_TUNNEL_DECAP_ECN_MODE_STANDARD", + "copy_from_outer": "SAI_TUNNEL_DECAP_ECN_MODE_COPY_FROM_OUTER" + } + + dscp_modes_map = { + "pipe" : "SAI_TUNNEL_DSCP_MODE_PIPE_MODEL", + "uniform" : "SAI_TUNNEL_DSCP_MODE_UNIFORM_MODEL" + } + + ttl_modes_map = { + "pipe" : "SAI_TUNNEL_TTL_MODE_PIPE_MODEL", + "uniform" : "SAI_TUNNEL_TTL_MODE_UNIFORM_MODEL" + } + + + def check_interface_exists_in_asicdb(self, asicdb, sai_oid): + if_table = swsscommon.Table(asicdb, self.ASIC_RIF_TABLE) + status, fvs = if_table.get(sai_oid) + return status + + def check_vr_exists_in_asicdb(self, asicdb, sai_oid): + vfr_table = swsscommon.Table(asicdb, self.ASIC_VRF_TABLE) + status, fvs = vfr_table.get(sai_oid) + return status + + def create_and_test_peer(self, db, asicdb, peer_name, peer_ip, src_ip): + """ Create PEER entry verify all needed enties in ASIC DB exists """ + + create_entry_tbl( + db, + "PEER_SWITCH", '|', "%s" % (peer_name), + [ + ("address_ipv4", peer_ip), + ] + ) + + time.sleep(2) + + # check asic db table + tunnel_table = swsscommon.Table(asicdb, self.ASIC_TUNNEL_TABLE) + + tunnels = tunnel_table.getKeys() + + # There will be two tunnels, one P2MP and another P2P + assert len(tunnels) == 2 + + p2p_obj = None + + for tunnel_sai_obj in tunnels: + status, fvs = tunnel_table.get(tunnel_sai_obj) + + assert status == True + + for field, value in fvs: + if field == "SAI_TUNNEL_ATTR_TYPE": + assert value == "SAI_TUNNEL_TYPE_IPINIP" + if field == "SAI_TUNNEL_ATTR_PEER_MODE": + if value == "SAI_TUNNEL_PEER_MODE_P2P": + p2p_obj = tunnel_sai_obj + + assert p2p_obj != None + + status, fvs = tunnel_table.get(p2p_obj) + + assert status == True + + for field, value in fvs: + if field == "SAI_TUNNEL_ATTR_TYPE": + assert value == "SAI_TUNNEL_TYPE_IPINIP" + elif field == "SAI_TUNNEL_ATTR_ENCAP_SRC_IP": + assert value == src_ip + elif field == "SAI_TUNNEL_ATTR_ENCAP_DST_IP": + assert value == peer_ip + elif field == "SAI_TUNNEL_ATTR_PEER_MODE": + assert value == "SAI_TUNNEL_PEER_MODE_P2P" + elif field == "SAI_TUNNEL_ATTR_OVERLAY_INTERFACE": + assert self.check_interface_exists_in_asicdb(asicdb, value) + elif field == "SAI_TUNNEL_ATTR_UNDERLAY_INTERFACE": + assert self.check_interface_exists_in_asicdb(asicdb, value) + else: + assert False, "Field %s is not tested" % field + + + def check_tunnel_termination_entry_exists_in_asicdb(self, asicdb, tunnel_sai_oid, dst_ips): + tunnel_term_table = swsscommon.Table(asicdb, self.ASIC_TUNNEL_TERM_ENTRIES) + + tunnel_term_entries = tunnel_term_table.getKeys() + assert len(tunnel_term_entries) == len(dst_ips) + + for term_entry in tunnel_term_entries: + status, fvs = tunnel_term_table.get(term_entry) + + assert status == True + assert len(fvs) == 5 + + for field, value in fvs: + if field == "SAI_TUNNEL_TERM_TABLE_ENTRY_ATTR_VR_ID": + assert self.check_vr_exists_in_asicdb(asicdb, value) + elif field == "SAI_TUNNEL_TERM_TABLE_ENTRY_ATTR_TYPE": + assert value == "SAI_TUNNEL_TERM_TABLE_ENTRY_TYPE_P2MP" + elif field == "SAI_TUNNEL_TERM_TABLE_ENTRY_ATTR_TUNNEL_TYPE": + assert value == "SAI_TUNNEL_TYPE_IPINIP" + elif field == "SAI_TUNNEL_TERM_TABLE_ENTRY_ATTR_ACTION_TUNNEL_ID": + assert value == tunnel_sai_oid + elif field == "SAI_TUNNEL_TERM_TABLE_ENTRY_ATTR_DST_IP": + assert value in dst_ips + else: + assert False, "Field %s is not tested" % field + + def create_and_test_tunnel(self, db, asicdb, tunnel_name, **kwargs): + """ Create tunnel and verify all needed enties in ASIC DB exists """ + + is_symmetric_tunnel = "src_ip" in kwargs; + + # create tunnel entry in DB + ps = swsscommon.ProducerStateTable(db, self.APP_TUNNEL_DECAP_TABLE_NAME) + + fvs = create_fvs(**kwargs) + + ps.set(tunnel_name, fvs) + + # wait till config will be applied + time.sleep(1) + + # check asic db table + tunnel_table = swsscommon.Table(asicdb, self.ASIC_TUNNEL_TABLE) + + tunnels = tunnel_table.getKeys() + assert len(tunnels) == 1 + + tunnel_sai_obj = tunnels[0] + + status, fvs = tunnel_table.get(tunnel_sai_obj) + + assert status == True + # 6 parameters to check in case of decap tunnel + # + 1 (SAI_TUNNEL_ATTR_ENCAP_SRC_IP) in case of symmetric tunnel + assert len(fvs) == 7 if is_symmetric_tunnel else 6 + + expected_ecn_mode = self.ecn_modes_map[kwargs["ecn_mode"]] + expected_dscp_mode = self.dscp_modes_map[kwargs["dscp_mode"]] + expected_ttl_mode = self.ttl_modes_map[kwargs["ttl_mode"]] + + for field, value in fvs: + if field == "SAI_TUNNEL_ATTR_TYPE": + assert value == "SAI_TUNNEL_TYPE_IPINIP" + elif field == "SAI_TUNNEL_ATTR_ENCAP_SRC_IP": + assert value == kwargs["src_ip"] + elif field == "SAI_TUNNEL_ATTR_DECAP_ECN_MODE": + assert value == expected_ecn_mode + elif field == "SAI_TUNNEL_ATTR_DECAP_TTL_MODE": + assert value == expected_ttl_mode + elif field == "SAI_TUNNEL_ATTR_DECAP_DSCP_MODE": + assert value == expected_dscp_mode + elif field == "SAI_TUNNEL_ATTR_OVERLAY_INTERFACE": + assert self.check_interface_exists_in_asicdb(asicdb, value) + elif field == "SAI_TUNNEL_ATTR_UNDERLAY_INTERFACE": + assert self.check_interface_exists_in_asicdb(asicdb, value) + else: + assert False, "Field %s is not tested" % field + + self.check_tunnel_termination_entry_exists_in_asicdb(asicdb, tunnel_sai_obj, kwargs["dst_ip"].split(",")) + + def remove_and_test_tunnel(self, db, asicdb, tunnel_name): + """ Removes tunnel and checks that ASIC db is clear""" + + tunnel_table = swsscommon.Table(asicdb, self.ASIC_TUNNEL_TABLE) + tunnel_term_table = swsscommon.Table(asicdb, self.ASIC_TUNNEL_TERM_ENTRIES) + tunnel_app_table = swsscommon.Table(asicdb, self.APP_TUNNEL_DECAP_TABLE_NAME) + + tunnels = tunnel_table.getKeys() + tunnel_sai_obj = tunnels[0] + + status, fvs = tunnel_table.get(tunnel_sai_obj) + + # get overlay loopback interface oid to check if it is deleted with the tunnel + overlay_infs_id = {f:v for f,v in fvs}["SAI_TUNNEL_ATTR_OVERLAY_INTERFACE"] + + ps = swsscommon.ProducerStateTable(db, self.APP_TUNNEL_DECAP_TABLE_NAME) + ps.set(tunnel_name, create_fvs(), 'DEL') + + # wait till config will be applied + time.sleep(1) + + assert len(tunnel_table.getKeys()) == 0 + assert len(tunnel_term_table.getKeys()) == 0 + assert len(tunnel_app_table.getKeys()) == 0 + assert not self.check_interface_exists_in_asicdb(asicdb, overlay_infs_id) + + def cleanup_left_over(self, db, asicdb): + """ Cleanup APP and ASIC tables """ + + tunnel_table = swsscommon.Table(asicdb, self.ASIC_TUNNEL_TABLE) + for key in tunnel_table.getKeys(): + tunnel_table._del(key) + + tunnel_term_table = swsscommon.Table(asicdb, self.ASIC_TUNNEL_TERM_ENTRIES) + for key in tunnel_term_table.getKeys(): + tunnel_term_table._del(key) + + tunnel_app_table = swsscommon.Table(asicdb, self.APP_TUNNEL_DECAP_TABLE_NAME) + for key in tunnel_app_table.getKeys(): + tunnel_table._del(key) + + +class TestMuxTunnel(TestMuxTunnelBase): + """ Tests for Mux tunnel creation and removal """ + + def test_Tunnel(self, dvs, testlog): + """ test IPv4 Mux tunnel creation """ + + db = swsscommon.DBConnector(swsscommon.APPL_DB, dvs.redis_sock, 0) + asicdb = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) + + self.cleanup_left_over(db, asicdb) + + # create tunnel IPv4 tunnel + self.create_and_test_tunnel(db, asicdb, tunnel_name="MuxTunnel0", tunnel_type="IPINIP", + dst_ip="10.1.0.32", dscp_mode="uniform", + ecn_mode="standard", ttl_mode="pipe") + + + def test_Peer(self, dvs, testlog): + """ test IPv4 Mux tunnel creation """ + + db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) + asicdb = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) + + self.create_and_test_peer(db, asicdb, "peer", "1.1.1.1", "10.1.0.32") + + +# Add Dummy always-pass test at end as workaroud +# for issue when Flaky fail on final test it invokes module tear-down before retrying +def test_nonflaky_dummy(): + pass