diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index dea3d10262..b1508656b3 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -3,6 +3,7 @@ #include #include "routeorch.h" #include "nhgorch.h" +#include "tunneldecaporch.h" #include "cbf/cbfnhgorch.h" #include "logger.h" #include "flowcounterrouteorch.h" @@ -25,6 +26,7 @@ extern Directory gDirectory; extern NhgOrch *gNhgOrch; extern CbfNhgOrch *gCbfNhgOrch; extern FlowCounterRouteOrch *gFlowCounterRouteOrch; +extern TunnelDecapOrch *gTunneldecapOrch; extern size_t gMaxBulkSize; @@ -44,7 +46,8 @@ RouteOrch::RouteOrch(DBConnector *db, vector &tableNames, m_fgNhgOrch(fgNhgOrch), m_nextHopGroupCount(0), m_srv6Orch(srv6Orch), - m_resync(false) + m_resync(false), + m_appTunnelDecapTermProducer(db, APP_TUNNEL_DECAP_TERM_TABLE_NAME) { SWSS_LOG_ENTER(); @@ -2337,6 +2340,13 @@ bool RouteOrch::addRoutePost(const RouteBulkContext& ctx, const NextHopGroupKey m_syncdRoutes[vrf_id][ipPrefix] = RouteNhg(nextHops, ctx.nhg_index); + /* add subnet decap term for VIP route */ + const SubnetDecapConfig &config = gTunneldecapOrch->getSubnetDecapConfig(); + if (config.enable && isVipRoute(ipPrefix, nextHops)) + { + createVipRouteSubnetDecapTerm(ipPrefix); + } + // update routes to reflect mux state if (mux_orch->isMuxNexthops(nextHops)) { @@ -2567,6 +2577,10 @@ bool RouteOrch::removeRoutePost(const RouteBulkContext& ctx) /* Publish removal status, removes route entry from APPL STATE DB */ publishRouteState(ctx); + /* Remove the VIP route subnet decap term */ + removeVipRouteSubnetDecapTerm(ipPrefix); + + if (ipPrefix.isDefaultRoute() && vrf_id == gVirtualRouterId) { it_route_table->second[ipPrefix] = RouteNhg(); @@ -2741,3 +2755,52 @@ void RouteOrch::publishRouteState(const RouteBulkContext& ctx, const ReturnCode& m_publisher.publish(APP_ROUTE_TABLE_NAME, ctx.key, fvs, status, replace); } + +inline bool RouteOrch::isVipRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHops) +{ + bool res = true; + /* Ensure all next hops are vlan devices */ + for (const auto &nextHop : nextHops.getNextHops()) + { + res &= (!nextHop.alias.compare(0, strlen(VLAN_PREFIX), VLAN_PREFIX)); + } + /* Ensure the prefix is non-local */ + if (nextHops.getSize() == 1) + { + res &= (!m_intfsOrch->isPrefixSubnet(ipPrefix, nextHops.getNextHops().begin()->alias)); + } + return res; +} + +inline void RouteOrch::createVipRouteSubnetDecapTerm(const IpPrefix &ipPrefix) +{ + const SubnetDecapConfig &config = gTunneldecapOrch->getSubnetDecapConfig(); + if (!config.enable || m_SubnetDecapTermsCreated.find(ipPrefix) != m_SubnetDecapTermsCreated.end()) + { + return; + } + SWSS_LOG_NOTICE("Add subnet decap term for %s", ipPrefix.to_string().c_str()); + static const vector data = { + {"term_type", "MP2MP"}, + {"subnet_type", "vip"} + }; + string tunnel_name = ipPrefix.isV4() ? config.tunnel : config.tunnel_v6; + string key = tunnel_name + ":" + ipPrefix.to_string(); + m_appTunnelDecapTermProducer.set(key, data); + m_SubnetDecapTermsCreated.insert(ipPrefix); +} + +inline void RouteOrch::removeVipRouteSubnetDecapTerm(const IpPrefix &ipPrefix) +{ + auto it = m_SubnetDecapTermsCreated.find(ipPrefix); + if (it == m_SubnetDecapTermsCreated.end()) + { + return; + } + const SubnetDecapConfig &config = gTunneldecapOrch->getSubnetDecapConfig(); + SWSS_LOG_NOTICE("Remove subnet decap term for %s", ipPrefix.to_string().c_str()); + string tunnel_name = ipPrefix.isV4() ? config.tunnel : config.tunnel_v6; + string key = tunnel_name + ":" + ipPrefix.to_string(); + m_appTunnelDecapTermProducer.del(key); + m_SubnetDecapTermsCreated.erase(it); +} diff --git a/orchagent/routeorch.h b/orchagent/routeorch.h index b232137766..577d966a26 100644 --- a/orchagent/routeorch.h +++ b/orchagent/routeorch.h @@ -23,6 +23,7 @@ #define EUI64_INTF_ID_LEN 8 #define LOOPBACK_PREFIX "Loopback" +#define VLAN_PREFIX "Vlan" struct NextHopGroupMemberEntry { @@ -250,6 +251,9 @@ class RouteOrch : public Orch, public Subject std::set> m_bulkNhgReducedRefCnt; /* m_bulkNhgReducedRefCnt: nexthop, vrf_id */ + std::set m_SubnetDecapTermsCreated; + ProducerStateTable m_appTunnelDecapTermProducer; + NextHopObserverTable m_nextHopObservers; EntityBulker gRouteBulker; @@ -278,6 +282,10 @@ class RouteOrch : public Orch, public Subject void decNhgRefCount(const std::string& nhg_index); void publishRouteState(const RouteBulkContext& ctx, const ReturnCode& status = ReturnCode(SAI_STATUS_SUCCESS)); + + bool isVipRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHops); + void createVipRouteSubnetDecapTerm(const IpPrefix &ipPrefix); + void removeVipRouteSubnetDecapTerm(const IpPrefix &ipPrefix); }; #endif /* SWSS_ROUTEORCH_H */ diff --git a/tests/mock_tests/flowcounterrouteorch_ut.cpp b/tests/mock_tests/flowcounterrouteorch_ut.cpp index 521b040d07..1b031515b3 100644 --- a/tests/mock_tests/flowcounterrouteorch_ut.cpp +++ b/tests/mock_tests/flowcounterrouteorch_ut.cpp @@ -176,16 +176,18 @@ namespace flowcounterrouteorch_test ASSERT_EQ(gNeighOrch, nullptr); gNeighOrch = new NeighOrch(m_app_db.get(), APP_NEIGH_TABLE_NAME, gIntfsOrch, gFdbOrch, gPortsOrch, m_chassis_app_db.get()); + ASSERT_EQ(gTunneldecapOrch, nullptr); vector tunnel_tables = { APP_TUNNEL_DECAP_TABLE_NAME, APP_TUNNEL_DECAP_TERM_TABLE_NAME }; - auto* tunnel_decap_orch = new TunnelDecapOrch(m_app_db.get(), m_state_db.get(), m_config_db.get(), tunnel_tables); + gTunneldecapOrch = new TunnelDecapOrch(m_app_db.get(), m_state_db.get(), m_config_db.get(), tunnel_tables); + vector mux_tables = { CFG_MUX_CABLE_TABLE_NAME, CFG_PEER_SWITCH_TABLE_NAME }; - auto* mux_orch = new MuxOrch(m_config_db.get(), mux_tables, tunnel_decap_orch, gNeighOrch, gFdbOrch); + auto* mux_orch = new MuxOrch(m_config_db.get(), mux_tables, gTunneldecapOrch, gNeighOrch, gFdbOrch); gDirectory.set(mux_orch); ASSERT_EQ(gFgNhgOrch, nullptr); @@ -310,6 +312,9 @@ namespace flowcounterrouteorch_test delete gNeighOrch; gNeighOrch = nullptr; + delete gTunneldecapOrch; + gTunneldecapOrch = nullptr; + delete gFdbOrch; gFdbOrch = nullptr; diff --git a/tests/mock_tests/mock_orchagent_main.h b/tests/mock_tests/mock_orchagent_main.h index e44d8e1612..3437d7e22f 100644 --- a/tests/mock_tests/mock_orchagent_main.h +++ b/tests/mock_tests/mock_orchagent_main.h @@ -58,6 +58,7 @@ extern Srv6Orch *gSrv6Orch; extern BfdOrch *gBfdOrch; extern AclOrch *gAclOrch; extern PolicerOrch *gPolicerOrch; +extern TunnelDecapOrch *gTunneldecapOrch; extern Directory gDirectory; extern sai_acl_api_t *sai_acl_api; diff --git a/tests/mock_tests/routeorch_ut.cpp b/tests/mock_tests/routeorch_ut.cpp index 1d18dcc458..fe24cf1f29 100644 --- a/tests/mock_tests/routeorch_ut.cpp +++ b/tests/mock_tests/routeorch_ut.cpp @@ -222,16 +222,18 @@ namespace routeorch_test ASSERT_EQ(gNeighOrch, nullptr); gNeighOrch = new NeighOrch(m_app_db.get(), APP_NEIGH_TABLE_NAME, gIntfsOrch, gFdbOrch, gPortsOrch, m_chassis_app_db.get()); + ASSERT_EQ(gTunneldecapOrch, nullptr); vector tunnel_tables = { APP_TUNNEL_DECAP_TABLE_NAME, APP_TUNNEL_DECAP_TERM_TABLE_NAME }; - TunnelDecapOrch *tunnel_decap_orch = new TunnelDecapOrch(m_app_db.get(), m_state_db.get(), m_config_db.get(), tunnel_tables); + gTunneldecapOrch = new TunnelDecapOrch(m_app_db.get(), m_state_db.get(), m_config_db.get(), tunnel_tables); + vector mux_tables = { CFG_MUX_CABLE_TABLE_NAME, CFG_PEER_SWITCH_TABLE_NAME }; - MuxOrch *mux_orch = new MuxOrch(m_config_db.get(), mux_tables, tunnel_decap_orch, gNeighOrch, gFdbOrch); + MuxOrch *mux_orch = new MuxOrch(m_config_db.get(), mux_tables, gTunneldecapOrch, gNeighOrch, gFdbOrch); gDirectory.set(mux_orch); ASSERT_EQ(gFgNhgOrch, nullptr); @@ -348,6 +350,9 @@ namespace routeorch_test delete gNeighOrch; gNeighOrch = nullptr; + delete gTunneldecapOrch; + gTunneldecapOrch = nullptr; + delete gFdbOrch; gFdbOrch = nullptr; diff --git a/tests/test_route.py b/tests/test_route.py index 4219fe1d97..b815047d96 100644 --- a/tests/test_route.py +++ b/tests/test_route.py @@ -3,6 +3,7 @@ import time import json import pytest +import ipaddress from swsscommon import swsscommon from dvslib.dvs_common import wait_for_result @@ -1113,6 +1114,150 @@ def check_offloaded(): # make sure route suppression is disabled dvs.runcmd("config suppress-fib-pending disabled") + +class TestSubnetDecapVipRoute(TestRouteBase): + VLAN_ID = "1000" + VLAN_INTF = "Vlan1000" + SERV_IPV4 = "192.168.0.100" + SERV_IPV6 = "fc02:1000::100" + CFG_SUBNET_DECAP_TABLE_NAME = "SUBNET_DECAP" + APP_TUNNEL_DECAP_TERM_TABLE_NAME = "TUNNEL_DECAP_TERM_TABLE" + + def add_neighbor(self, dvs, ip, mac): + if ipaddress.ip_address(ip).version == 6: + dvs.runcmd("ip -6 neigh replace " + ip + " lladdr " + mac + " dev " + self.VLAN_INTF) + else: + dvs.runcmd("ip -4 neigh replace " + ip + " lladdr " + mac + " dev " + self.VLAN_INTF) + + def add_route(self, dvs, ip_prefix): + if ipaddress.ip_network(ip_prefix).version == 4: + dvs.runcmd( + 'vtysh -c "configure terminal" -c "ip route %s %s"' % (ip_prefix, self.SERV_IPV4) + ) + else: + dvs.runcmd( + 'vtysh -c "configure terminal" -c "ipv6 route %s %s"' % (ip_prefix, self.SERV_IPV6) + ) + + def remove_route(self, dvs, ip_prefix): + if ipaddress.ip_network(ip_prefix).version == 4: + dvs.runcmd( + 'vtysh -c "configure terminal" -c "no ip route %s %s"' % (ip_prefix, self.SERV_IPV4) + ) + else: + dvs.runcmd( + 'vtysh -c "configure terminal" -c "no ipv6 route %s %s"' % (ip_prefix, self.SERV_IPV6) + ) + + def validate_subnet_decap_term(self, dvs, ip_prefix_list): + tunnel_decap_term_app_table = swsscommon.Table(dvs.pdb, self.APP_TUNNEL_DECAP_TERM_TABLE_NAME) + decap_term_list = tunnel_decap_term_app_table.getKeys() + decap_term_prefix_list = [decap_term.split(":", 1)[1] for decap_term in decap_term_list] + + assert len(ip_prefix_list) == len(decap_term_prefix_list) + for ip_prefix in ip_prefix_list: + assert ip_prefix in decap_term_prefix_list + + for decap_term in decap_term_list: + _, fvs = tunnel_decap_term_app_table.get(decap_term) + decap_term_attrs = dict(fvs) + assert decap_term_attrs["term_type"] == "MP2MP" + assert decap_term_attrs["subnet_type"] == "vip" + + @pytest.fixture(scope="class", autouse=True) + def setup_vlan(self, dvs, dvs_vlan_manager, setup_subnet_decap): + dvs.setup_db() + + subnet_decap_config = { + "status": "enable", + "src_ip": "10.10.10.0/24", + "src_ip_v6": "20c1:ba8::/64" + } + setup_subnet_decap(subnet_decap_config) + + vlan = self.VLAN_ID + vlan_intf = self.VLAN_INTF + self.dvs_vlan.create_vlan(vlan) + vlan_oid = self.dvs_vlan.get_and_verify_vlan_ids(1)[0] + self.dvs_vlan.verify_vlan(vlan_oid, vlan) + + dvs.port_admin_set("Ethernet0", "up") + self.dvs_vlan.create_vlan_member(vlan, "Ethernet0") + self.dvs_vlan.verify_vlan_member(vlan_oid, "Ethernet0") + + dvs.add_ip_address(vlan_intf, "192.168.0.1/24") + dvs.add_ip_address(vlan_intf, "fc02:1000::1/64") + + yield + + dvs.remove_ip_address(vlan_intf, "192.168.0.1/24") + dvs.remove_ip_address(vlan_intf, "fc02:1000::1/64") + + self.dvs_vlan.remove_vlan_member(vlan, "Ethernet0") + self.dvs_vlan.get_and_verify_vlan_member_ids(0) + + time.sleep(2) + self.dvs_vlan.remove_vlan(vlan) + self.dvs_vlan.get_and_verify_vlan_ids(0) + + @pytest.fixture(scope="class", autouse=True) + def setup_server_neighbor(self, dvs, setup_vlan): + self.add_neighbor(dvs, self.SERV_IPV4, "00:00:00:00:00:01") + self.add_neighbor(dvs, self.SERV_IPV6, "00:00:00:00:00:01") + + @pytest.fixture(scope="class") + def setup_subnet_decap(self, dvs): + + def _apply_subnet_decap_config(subnet_decap_config): + """Apply subnet decap config to CONFIG_DB.""" + fvs = swsscommon.FieldValuePairs(list(subnet_decap_config.items())) + subnet_decap_tbl.set("AZURE", fvs) + + def _cleanup_subnet_decap_config(): + """Cleanup subnet decap config in CONFIG_DB.""" + for key in subnet_decap_tbl.getKeys(): + subnet_decap_tbl._del(key) + + configdb = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) + subnet_decap_tbl = swsscommon.Table(configdb, self.CFG_SUBNET_DECAP_TABLE_NAME) + _cleanup_subnet_decap_config() + + yield _apply_subnet_decap_config + + _cleanup_subnet_decap_config() + + def test_vip_route(self, dvs, setup_subnet_decap): + self.add_route(dvs, "10.10.20.0/24") + self.add_route(dvs, "2001:506:28:9d::/64") + + time.sleep(2) + self.validate_subnet_decap_term(dvs, ["10.10.20.0/24", "2001:506:28:9d::/64"]) + + self.remove_route(dvs, "10.10.20.0/24") + self.remove_route(dvs, "2001:506:28:9d::/64") + + time.sleep(2) + self.validate_subnet_decap_term(dvs, []) + + subnet_decap_config = { + "status": "disable", + "src_ip": "10.10.10.0/24", + "src_ip_v6": "20c1:ba8::/64" + } + setup_subnet_decap(subnet_decap_config) + + self.add_route(dvs, "10.10.20.0/24") + self.add_route(dvs, "2001:506:28:9d::/64") + + time.sleep(2) + self.validate_subnet_decap_term(dvs, []) + + self.remove_route(dvs, "10.10.20.0/24") + self.remove_route(dvs, "2001:506:28:9d::/64") + + time.sleep(2) + self.validate_subnet_decap_term(dvs, []) + # 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():