diff --git a/fpmsyncd/routesync.cpp b/fpmsyncd/routesync.cpp index 63b3fe55ffe..7199b01f64d 100644 --- a/fpmsyncd/routesync.cpp +++ b/fpmsyncd/routesync.cpp @@ -13,6 +13,7 @@ #include "fpmsyncd/fpm/fpm.h" #include "macaddress.h" #include "converter.h" +#include #include #include #include @@ -150,6 +151,105 @@ static string getProtocolString(int proto) return buffer; } +static bool parseRtAttrGateway(struct rtattr *gateway_attr, string &gateway) +{ + if (!gateway_attr) + { + return false; + } + + char gateway_buf[RouteSync::MAX_ADDR_SIZE + 1] = {}; + const void *gateway_data = RTA_DATA(gateway_attr); + const char *gateway_str = nullptr; + + if (RTA_PAYLOAD(gateway_attr) == IPV4_MAX_BYTE) + { + gateway_str = inet_ntop(AF_INET, gateway_data, gateway_buf, RouteSync::MAX_ADDR_SIZE); + } + else if (RTA_PAYLOAD(gateway_attr) == IPV6_MAX_BYTE) + { + gateway_str = inet_ntop(AF_INET6, gateway_data, gateway_buf, RouteSync::MAX_ADDR_SIZE); + } + else + { + SWSS_LOG_WARN("Received RTA_GATEWAY with unsupported payload length %zu", + static_cast(RTA_PAYLOAD(gateway_attr))); + return false; + } + + if (!gateway_str) + { + SWSS_LOG_WARN("Failed to parse RTA_GATEWAY address"); + return false; + } + + gateway = gateway_str; + return true; +} + +static bool parseRtAttrVia(struct rtattr *via_attr, string &gateway) +{ + if (!via_attr) + { + return false; + } + + size_t payload_len = static_cast(RTA_PAYLOAD(via_attr)); + size_t via_header_len = offsetof(struct rtvia, rtvia_addr); + size_t ipv4_addr_len = static_cast(IPV4_MAX_BYTE); + size_t ipv6_addr_len = static_cast(IPV6_MAX_BYTE); + if (payload_len <= via_header_len) + { + SWSS_LOG_WARN("Received RTA_VIA with unsupported payload length %zu", + payload_len); + return false; + } + + struct rtvia via = {}; + memcpy(&via, RTA_DATA(via_attr), via_header_len); + + char via_buf[RouteSync::MAX_ADDR_SIZE + 1] = {}; + const uint8_t *via_data = static_cast(RTA_DATA(via_attr)); + const void *via_addr = via_data + via_header_len; + size_t address_len = payload_len - via_header_len; + const char *via_str = nullptr; + + if (via.rtvia_family == AF_INET && address_len == ipv4_addr_len) + { + via_str = inet_ntop(AF_INET, via_addr, via_buf, RouteSync::MAX_ADDR_SIZE); + } + else if (via.rtvia_family == AF_INET6 && address_len == ipv6_addr_len) + { + via_str = inet_ntop(AF_INET6, via_addr, via_buf, RouteSync::MAX_ADDR_SIZE); + } + else + { + SWSS_LOG_WARN("Received RTA_VIA with unsupported family %d payload length %zu", + static_cast(via.rtvia_family), payload_len); + return false; + } + + if (!via_str) + { + SWSS_LOG_WARN("Failed to parse RTA_VIA address"); + return false; + } + + gateway = via_str; + return true; +} + +static bool parseRtAttrNexthop(struct rtattr *gateway_attr, struct rtattr *via_attr, + string &nexthop) +{ + if (parseRtAttrGateway(gateway_attr, nexthop)) + { + return true; + } + + return parseRtAttrVia(via_attr, nexthop); +} + /* Helper to create unique pointer with custom destructor */ template static decltype(auto) makeUniqueWithDestructor(T* ptr, F func) @@ -186,6 +286,8 @@ RouteSync::RouteSync(RedisPipeline *pipeline) : m_nl_sock = nl_socket_alloc(); nl_connect(m_nl_sock, NETLINK_ROUTE); rtnl_link_alloc_cache(m_nl_sock, AF_UNSPEC, &m_link_cache); + + m_appDb = make_shared("APPL_DB", 0); } void RouteSync::setRouteWithWarmRestart(FieldValueTupleWrapperBase & fvw, @@ -1039,7 +1141,8 @@ void RouteSync::onEvpnRouteMsg(struct nlmsghdr *h, int len) bool RouteSync::getSrv6SteerRouteNextHop(struct nlmsghdr *h, int received_bytes, struct rtattr *tb[], string &vpn_sid, - string &src_addr) + string &src_addr, string &nexthops, + string &ifnames) { uint16_t encap = 0; @@ -1055,10 +1158,23 @@ bool RouteSync::getSrv6SteerRouteNextHop(struct nlmsghdr *h, int received_bytes, NH_ENCAP_SRV6_ROUTE) { parseEncapSrv6SteerRoute(tb[RTA_ENCAP], vpn_sid, src_addr); + parseRtAttrNexthop(tb[RTA_GATEWAY], tb[RTA_VIA], nexthops); + } + + if (tb[RTA_OIF]) + { + char if_name[IFNAMSIZ] = {0}; + int index = *(int *)RTA_DATA(tb[RTA_OIF]); + + if (getIfName(index, if_name, IFNAMSIZ)) + { + ifnames = if_name; + } } - SWSS_LOG_DEBUG("Rx MsgType:%d encap:%d vpn_sid:%s src_addr:%s", + + SWSS_LOG_DEBUG("Rx MsgType:%d encap:%d vpn_sid:%s src_addr:%s nexthops:%s ifnames:%s", h->nlmsg_type, encap, vpn_sid.c_str(), - src_addr.c_str()); + src_addr.c_str(), nexthops.c_str(), ifnames.c_str()); if (vpn_sid.empty()) { @@ -1069,8 +1185,90 @@ bool RouteSync::getSrv6SteerRouteNextHop(struct nlmsghdr *h, int received_bytes, else { /* This is a multipath route */ - SWSS_LOG_NOTICE("Multipath SRv6 routes aren't supported"); - return false; + int len; + struct rtattr *subtb[RTA_MAX + 1]; + struct rtnexthop *rtnh = (struct rtnexthop *)RTA_DATA(tb[RTA_MULTIPATH]); + len = (int)RTA_PAYLOAD(tb[RTA_MULTIPATH]); + bool first_nh = true; + + for (;;) + { + uint16_t nh_encap = 0; + string nh_sid; + string nh_src_addr; + string nh_nexthop; + string nh_ifname; + + if (len < (int)sizeof(*rtnh) || rtnh->rtnh_len > len) + { + break; + } + + if (rtnh->rtnh_len > sizeof(*rtnh)) + { + memset(subtb, 0, sizeof(subtb)); + + netlink_parse_rtattr(subtb, RTA_MAX, RTNH_DATA(rtnh), + (int)(rtnh->rtnh_len - sizeof(*rtnh))); + + if (subtb[RTA_ENCAP_TYPE]) + { + nh_encap = *(uint16_t *)RTA_DATA(subtb[RTA_ENCAP_TYPE]); + } + + if (subtb[RTA_ENCAP] && subtb[RTA_ENCAP_TYPE] && + *(uint16_t *)RTA_DATA(subtb[RTA_ENCAP_TYPE]) == + NH_ENCAP_SRV6_ROUTE) + { + parseEncapSrv6SteerRoute(subtb[RTA_ENCAP], nh_sid, nh_src_addr); + parseRtAttrNexthop(subtb[RTA_GATEWAY], subtb[RTA_VIA], nh_nexthop); + } + + if (rtnh->rtnh_ifindex) + { + char if_name[IFNAMSIZ] = {0}; + + if (getIfName(rtnh->rtnh_ifindex, if_name, IFNAMSIZ)) + { + nh_ifname = if_name; + } + } + + SWSS_LOG_DEBUG("Multipath nexthop encap:%d nh_sid:%s nh_src_addr:%s nh_nexthop:%s nh_ifname:%s", + nh_encap, nh_sid.c_str(), nh_src_addr.c_str(), + nh_nexthop.c_str(), nh_ifname.c_str()); + + if (nh_sid.empty()) + { + SWSS_LOG_ERROR("Received an invalid SRv6 nexthop: SID is missing. Skipping."); + } + else + { + if (!first_nh) + { + vpn_sid += ","; + src_addr += ","; + nexthops += ","; + ifnames += ","; + } + + vpn_sid += nh_sid; + src_addr += nh_src_addr; + nexthops += nh_nexthop; + ifnames += nh_ifname; + + first_nh = false; + } + } + + if (rtnh->rtnh_len == 0) + { + break; + } + + len -= NLMSG_ALIGN(rtnh->rtnh_len); + rtnh = RTNH_NEXT(rtnh); + } } return true; @@ -1145,6 +1343,9 @@ RouteTableFieldValueTupleWrapper::fieldValueTupleVector() { if (nbZmqEnabled || includeEmptyFields || seg_src != string()) { fvVector.push_back(FieldValueTuple("seg_src", seg_src.c_str())); } + if (nbZmqEnabled || includeEmptyFields || vpn_sid != string()) { + fvVector.push_back(FieldValueTuple("vpn_sid", vpn_sid.c_str())); + } // Return value optimization will avoid copy of the following vector return fvVector; } @@ -1285,6 +1486,27 @@ Srv6SidListTableFieldValueTupleWrapper::fieldValueTupleVector() { } +bool RouteSync::getSrv6SidListsFromRoute(string &routeTableKey, string &sidlists) +{ + SWSS_LOG_ENTER(); + + Table routeTable{m_appDb.get(), APP_ROUTE_TABLE_NAME}; + + std::vector fieldValues; + routeTable.get(routeTableKey, fieldValues); + + for (auto iter : fieldValues) + { + if (fvField(iter) == "segment") + { + sidlists = fvValue(iter); + return true; + } + } + + return false; +} + void RouteSync::onSrv6SteerRouteMsg(struct nlmsghdr *h, int len) { @@ -1428,14 +1650,18 @@ void RouteSync::onSrv6SteerRouteMsg(struct nlmsghdr *h, int len) /* Get nexthop lists */ string vpn_sid_str; string src_addr_str; + string nexthop_str; + string ifname_str; bool ret; - ret = getSrv6SteerRouteNextHop(h, len, tb, vpn_sid_str, src_addr_str); + ret = getSrv6SteerRouteNextHop(h, len, tb, vpn_sid_str, src_addr_str, + nexthop_str, ifname_str); if (ret == false) { SWSS_LOG_NOTICE( - "SRv6 Route issue with RouteTable msg: %s vpn_sid:%s src_addr:%s", - destipprefix, vpn_sid_str.c_str(), src_addr_str.c_str()); + "SRv6 Route issue with RouteTable msg: %s vpn_sid:%s src_addr:%s nexthop:%s ifname:%s", + destipprefix, vpn_sid_str.c_str(), src_addr_str.c_str(), + nexthop_str.c_str(), ifname_str.c_str()); return; } @@ -1445,39 +1671,59 @@ void RouteSync::onSrv6SteerRouteMsg(struct nlmsghdr *h, int len) return; } + auto proto_str = getProtocolString(rtm->rtm_protocol); + bool is_bgp_srv6_route = (proto_str == "bgp"); + if (nlmsg_type == RTM_DELROUTE) { string routeTableKeyStr = string(routeTableKey); - string srv6SidListTableKey = vpn_sid_str; SWSS_LOG_INFO("SRV6 RouteTable del msg: %s", routeTableKeyStr.c_str()); + + if (!is_bgp_srv6_route) + { + getSrv6SidListsFromRoute(routeTableKeyStr, vpn_sid_str); + SWSS_LOG_INFO("Got Srv6 Sid list for route '%s' from route table: '%s'", + routeTableKeyStr.c_str(), vpn_sid_str.c_str()); + } + delWithWarmRestart( - RouteTableFieldValueTupleWrapper{std::move(routeTableKeyStr), std::string(), isNbZmqEnabled()}, + RouteTableFieldValueTupleWrapper{routeTableKeyStr, std::string(), isNbZmqEnabled()}, *m_routeTable); - auto it = m_srv6_sidlist_refcnt.find(srv6SidListTableKey); - if (it != m_srv6_sidlist_refcnt.end()) + if (is_bgp_srv6_route) { - assert (it->second > 0); - - /* Decrement the refcount for this SID list */ - (it->second)--; - SWSS_LOG_INFO("Refcount for SID list '%s' decreased to %u", - srv6SidListTableKey.c_str(), it->second); + return; + } - /* If the refcount drops to zero, remove the SID list from ApplDB */ - if (it->second == 0) + /* Delete SID lists from SRV6_SID_LIST_TABLE */ + vector sidlists = tokenize(vpn_sid_str, ','); + for (auto sidlist : sidlists) + { + auto it = m_srv6_sidlist_refcnt.find(sidlist); + if (it != m_srv6_sidlist_refcnt.end()) { - m_srv6SidListTable.del(srv6SidListTableKey); - SWSS_LOG_INFO("Refcount for SID list '%s' is zero. SID list removed from ApplDB", - srv6SidListTableKey.c_str()); + assert (it->second > 0); - m_srv6_sidlist_refcnt.erase(srv6SidListTableKey); + /* Decrement the refcount for this SID list */ + (it->second)--; + SWSS_LOG_INFO("Refcount for SID list '%s' decreased to %u", + sidlist.c_str(), it->second); + + /* If the refcount drops to zero, remove the SID list from ApplDB */ + if (it->second == 0) + { + m_srv6SidListTable.del(sidlist); + SWSS_LOG_INFO("Refcount for SID list '%s' is zero. SID list removed from ApplDB", + sidlist.c_str()); + + m_srv6_sidlist_refcnt.erase(sidlist); + } + } + else + { + SWSS_LOG_WARN("SID list '%s' not found in the map.", sidlist.c_str()); } - } - else - { - SWSS_LOG_WARN("SID list '%s' not found in the map.", srv6SidListTableKey.c_str()); } return; @@ -1485,40 +1731,105 @@ void RouteSync::onSrv6SteerRouteMsg(struct nlmsghdr *h, int len) else if (nlmsg_type == RTM_NEWROUTE) { string routeTableKeyStr = string(routeTableKey); - /* Write SID list to SRV6_SID_LIST_TABLE */ - - string srv6SidListTableKey = vpn_sid_str; - auto it = m_srv6_sidlist_refcnt.find(srv6SidListTableKey); - if (it != m_srv6_sidlist_refcnt.end()) - { - /* SID list already exists: just bump the refcount */ - (it->second)++; - SWSS_LOG_INFO("Refcount for SID list'%s' increased to %u", - srv6SidListTableKey.c_str(), it->second); - } - else + if (!is_bgp_srv6_route) { - /* First time we see this SID list: program it into ApplDB and initialize the refcount to 1 */ - Srv6SidListTableFieldValueTupleWrapper fvw{srv6SidListTableKey, isNbZmqEnabled()}; - fvw.path = vpn_sid_str; + /* + * If this route already exists in APP_DB, it is an update rather than a new + * route (e.g. a multipath route losing one of its nexthops). Decrement the + * refcounts for the old SID lists before adding the new SID lists below. + */ + string old_sidlists_str; + if (getSrv6SidListsFromRoute(routeTableKeyStr, old_sidlists_str)) + { + vector old_sidlists = tokenize(old_sidlists_str, ','); + for (auto& old_sidlist : old_sidlists) + { + auto it = m_srv6_sidlist_refcnt.find(old_sidlist); + if (it != m_srv6_sidlist_refcnt.end()) + { + assert (it->second > 0); + + /* Decrement the refcount for this SID list */ + (it->second)--; + SWSS_LOG_INFO("Route update: refcount for old SID list '%s' decreased to %u", + old_sidlist.c_str(), it->second); - setTable(fvw, m_srv6SidListTable); - SWSS_LOG_DEBUG("Srv6SidListTable set msg: %s path: %s", - srv6SidListTableKey.c_str(), vpn_sid_str.c_str()); + /* If the refcount drops to zero, remove the SID list from ApplDB */ + if (it->second == 0) + { + m_srv6SidListTable.del(old_sidlist); + SWSS_LOG_INFO("Route update: SID list '%s' removed from ApplDB (refcount zero)", + old_sidlist.c_str()); - m_srv6_sidlist_refcnt[srv6SidListTableKey] = 1; - SWSS_LOG_INFO("SID list '%s' created and refcount initialized to 1", - srv6SidListTableKey.c_str()); + m_srv6_sidlist_refcnt.erase(it); + } + } + else + { + SWSS_LOG_WARN("Route update: old SID list '%s' not found in refcount map", + old_sidlist.c_str()); + } + } + } + + /* Write SID lists to SRV6_SID_LIST_TABLE */ + vector sidlists = tokenize(vpn_sid_str, ','); + for (auto sidlist : sidlists) + { + auto it = m_srv6_sidlist_refcnt.find(sidlist); + if (it != m_srv6_sidlist_refcnt.end()) + { + /* SID list already exists: just bump the refcount */ + (it->second)++; + SWSS_LOG_INFO("Refcount for SID list'%s' increased to %u", + sidlist.c_str(), it->second); + } + else + { + /* First time we see this SID list: program it into ApplDB and initialize the refcount to 1 */ + Srv6SidListTableFieldValueTupleWrapper fvw{sidlist, isNbZmqEnabled()}; + fvw.path = sidlist; + + setTable(fvw, m_srv6SidListTable); + SWSS_LOG_DEBUG("Srv6SidListTable set msg: %s path: %s", + sidlist.c_str(), sidlist.c_str()); + + m_srv6_sidlist_refcnt[sidlist] = 1; + SWSS_LOG_INFO("SID list '%s' created and refcount initialized to 1", + sidlist.c_str()); + } + } } /* Write route to ROUTE_TABLE */ - SWSS_LOG_INFO("SRV6 RouteTable set msg: %s vpn_sid:%s src_addr:%s", + SWSS_LOG_INFO("SRV6 RouteTable set msg: %s vpn_sid:%s src_addr:%s nexthop:%s ifname:%s protocol:%s", routeTableKeyStr.c_str(), vpn_sid_str.c_str(), - src_addr_str.empty() ? "NONE" : src_addr_str.c_str()); - RouteTableFieldValueTupleWrapper rfvw{std::move(routeTableKeyStr), "", isNbZmqEnabled()}; - rfvw.segment = std::move(srv6SidListTableKey); + src_addr_str.empty() ? "NONE" : src_addr_str.c_str(), + nexthop_str.empty() ? "NONE" : nexthop_str.c_str(), + ifname_str.empty() ? "NONE" : ifname_str.c_str(), + proto_str.c_str()); + + RouteTableFieldValueTupleWrapper rfvw{std::move(routeTableKeyStr), + std::move(proto_str), + isNbZmqEnabled()}; + if (is_bgp_srv6_route) + { + rfvw.vpn_sid = std::move(vpn_sid_str); + if (!nexthop_str.empty()) + { + rfvw.nexthop = std::move(nexthop_str); + } + if (!ifname_str.empty()) + { + rfvw.ifname = std::move(ifname_str); + } + } + else + { + rfvw.segment = std::move(vpn_sid_str); + } if (!src_addr_str.empty()) { diff --git a/fpmsyncd/routesync.h b/fpmsyncd/routesync.h index d1a57ca27d1..bf88a874f4b 100644 --- a/fpmsyncd/routesync.h +++ b/fpmsyncd/routesync.h @@ -118,6 +118,7 @@ class RouteTableFieldValueTupleWrapper : public FieldValueTupleWrapperBase { string router_mac = string(); string segment = string(); string seg_src = string(); + string vpn_sid = string(); bool includeEmptyFields = false; }; @@ -267,6 +268,8 @@ class RouteSync : public NetMsg } private: + /* ApplDB connector */ + shared_ptr m_appDb; /* ZMQ client */ shared_ptr m_zmqClient; /* regular route table */ @@ -365,7 +368,8 @@ class RouteSync : public NetMsg string& intf_list); bool getSrv6SteerRouteNextHop(struct nlmsghdr *h, int received_bytes, - struct rtattr *tb[], string &vpn_sid, string &src_addr); + struct rtattr *tb[], string &vpn_sid, string &src_addr, + string &nexthops, string &ifnames); bool getSrv6VpnRouteNextHop(struct nlmsghdr *h, int received_bytes, struct rtattr *tb[], uint32_t &pic_id,uint32_t &nhg_id); @@ -414,6 +418,8 @@ class RouteSync : public NetMsg return m_zmqClient != nullptr; } + bool getSrv6SidListsFromRoute(string &routeTableKey, string &sidlists); + }; struct NextHopField { string nexthops; diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index 5ef3ba6f7ea..01a45c4b938 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -858,7 +858,7 @@ void RouteOrch::doTask(ConsumerBase& consumer) it = consumer.m_toSync.erase(it); continue; } - else if (alsv.size() != ipv.size()) + else if (!srv6_nh && (alsv.size() != ipv.size())) { SWSS_LOG_NOTICE("Route %s: resize ipv to match alsv, %zd -> %zd.", key.c_str(), ipv.size(), alsv.size()); ipv.resize(alsv.size()); diff --git a/tests/mock_tests/fpmsyncd/test_routesync.cpp b/tests/mock_tests/fpmsyncd/test_routesync.cpp index f22d462588c..a22361383ad 100644 --- a/tests/mock_tests/fpmsyncd/test_routesync.cpp +++ b/tests/mock_tests/fpmsyncd/test_routesync.cpp @@ -4655,8 +4655,8 @@ TEST_F(FpmSyncdResponseTest, TestRouteMsgWithZmqEnabled_AllFieldsIncluded) vector fvs; EXPECT_TRUE(route_table.get(test_destipprefix, fvs)); - // With ZMQ enabled, all 11 fields should be present (including empty ones) - EXPECT_EQ(fvs.size(), 11); + // With ZMQ enabled, all 12 fields should be present (including empty ones) + EXPECT_EQ(fvs.size(), 12); // Build a map for easier verification std::map fieldMap; @@ -4685,6 +4685,8 @@ TEST_F(FpmSyncdResponseTest, TestRouteMsgWithZmqEnabled_AllFieldsIncluded) EXPECT_EQ(fieldMap["segment"], ""); EXPECT_TRUE(fieldMap.count("seg_src") > 0); EXPECT_EQ(fieldMap["seg_src"], ""); + EXPECT_TRUE(fieldMap.count("vpn_sid") > 0); + EXPECT_EQ(fieldMap["vpn_sid"], ""); rtnl_route_put(test_route); @@ -4760,6 +4762,7 @@ TEST_F(FpmSyncdResponseTest, TestRouteMsgWithZmqDisabled_OnlyNonEmptyFields) EXPECT_EQ(fieldMap.count("router_mac"), 0); EXPECT_EQ(fieldMap.count("segment"), 0); EXPECT_EQ(fieldMap.count("seg_src"), 0); + EXPECT_EQ(fieldMap.count("vpn_sid"), 0); rtnl_route_put(test_route); } diff --git a/tests/test_srv6.py b/tests/test_srv6.py index 989a7aa89f9..bea13d6c93b 100644 --- a/tests/test_srv6.py +++ b/tests/test_srv6.py @@ -1509,6 +1509,13 @@ def create_vrf(self, vrf_name): def remove_vrf(self, vrf_name): self.cdb.delete_entry("VRF", vrf_name) + def set_interface_status(self, dvs, interface, admin_status): + tbl_name = "PORT" + tbl = swsscommon.Table(self.cdb.db_connection, tbl_name) + fvs = swsscommon.FieldValuePairs([("admin_status", "up")]) + tbl.set(interface, fvs) + time.sleep(1) + def setup_srv6(self, dvs): self.setup_db(dvs) @@ -1521,6 +1528,10 @@ def setup_srv6(self, dvs): dvs.runcmd("ip link add sr0 type dummy") dvs.runcmd("ip link set sr0 up") + # bring up interfaces + self.set_interface_status(dvs, "Ethernet104", "up") + self.set_interface_status(dvs, "Ethernet108", "up") + def teardown_srv6(self, dvs): # remove dummy interface sr0 dvs.runcmd("ip link del sr0 type dummy") @@ -1530,6 +1541,12 @@ def teardown_srv6(self, dvs): self.remove_vrf("Vrf13") self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER", len(initial_vrf_entries) - 1) + # put interfaces down + self.set_interface_status(dvs, "Ethernet104", "down") + self.set_interface_status(dvs, "Ethernet108", "down") + + time.sleep(3) + def test_AddRemoveSrv6SteeringRouteIpv4(self, dvs, testlog): _, output = dvs.runcmd(f"vtysh -c 'show zebra dplane providers'") @@ -1548,9 +1565,13 @@ def test_AddRemoveSrv6SteeringRouteIpv4(self, dvs, testlog): nexthop_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") route_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") sidlist_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST") + nexthop_group_member_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER") + nexthop_group_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP") # create v4 route with vpn sid - dvs.runcmd("ip route add 192.0.2.0/24 encap seg6 mode encap segs fc00:0:1:e000:: dev sr0 vrf Vrf13") + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"ip route 192.0.2.0/24 Ethernet104 vrf Vrf13 nexthop-vrf default segments fc00:0:1:e000::\"") + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"ip route 192.0.3.0/24 Ethernet104 vrf Vrf13 nexthop-vrf default segments fc00:0:1:e000::\"") + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"ip route 192.0.3.0/24 Ethernet108 vrf Vrf13 nexthop-vrf default segments fc00:0:3:e000::\"") time.sleep(3) @@ -1559,51 +1580,121 @@ def test_AddRemoveSrv6SteeringRouteIpv4(self, dvs, testlog): expected_fields = {"segment": "fc00:0:1:e000::", "seg_src": "fc00:0:2::1"} self.pdb.wait_for_field_match("ROUTE_TABLE", "Vrf13:192.0.2.0/24", expected_fields) + self.pdb.wait_for_entry("ROUTE_TABLE", "Vrf13:192.0.3.0/24") + expected_fields = {"segment": "fc00:0:1:e000::,fc00:0:3:e000::", "seg_src": "fc00:0:2::1,fc00:0:2::1"} + self.pdb.wait_for_field_match("ROUTE_TABLE", "Vrf13:192.0.3.0/24", expected_fields) + self.pdb.wait_for_entry("SRV6_SID_LIST_TABLE", "fc00:0:1:e000::") expected_fields = {"path": "fc00:0:1:e000::"} self.pdb.wait_for_field_match("SRV6_SID_LIST_TABLE", "fc00:0:1:e000::", expected_fields) + self.pdb.wait_for_entry("SRV6_SID_LIST_TABLE", "fc00:0:3:e000::") + expected_fields = {"path": "fc00:0:3:e000::"} + self.pdb.wait_for_field_match("SRV6_SID_LIST_TABLE", "fc00:0:3:e000::", expected_fields) + # verify that the route has been programmed into the ASIC self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL", len(tunnel_entries) + 1) - self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", len(nexthop_entries) + 1) - self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST", len(sidlist_entries) + 1) - self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY", len(route_entries) + 1) + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", len(nexthop_entries) + 2) + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST", len(sidlist_entries) + 2) + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY", len(route_entries) + 2) # get created entries - route_key = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY", route_entries) - nexthop_id = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nexthop_entries) - tunnel_id = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL", tunnel_entries) - sidlist_id = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST", sidlist_entries) + route_keys = get_created_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY", route_entries, 2) + nexthop_ids = get_created_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nexthop_entries, 2) + tunnel_ids = get_created_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL", tunnel_entries, 1) + sidlist_ids = get_created_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST", sidlist_entries, 2) + nexthop_group_member_ids = get_created_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER", nexthop_group_member_entries, 2) + nexthop_group_ids = get_created_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP", nexthop_group_entries, 1) + + sidlist_id_1 = None + sidlist_id_2 = None + nexthop_id_1 = None + nexthop_id_2 = None + nexthop_group_member_id_1 = None + nexthop_group_member_id_2 = None + tunnel_id = tunnel_ids[0] + nexthop_group_id = nexthop_group_ids[0] # check ASIC SAI_OBJECT_TYPE_SRV6_SIDLIST database tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST") - (status, fvs) = tbl.get(sidlist_id) - assert status == True - for fv in fvs: - if fv[0] == "SAI_SRV6_SIDLIST_ATTR_SEGMENT_LIST": - assert fv[1] == "1:fc00:0:1:e000::" - elif fv[0] == "SAI_SRV6_SIDLIST_ATTR_TYPE": - assert fv[1] == "SAI_SRV6_SIDLIST_TYPE_ENCAPS_RED" - - # check ASIC SAI_OBJECT_TYPE_ROUTE_ENTRY database - tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") - (status, fvs) = tbl.get(route_key) - assert status == True - for fv in fvs: - if fv[0] == "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID": - assert fv[1] == nexthop_id + for sidlist_id in sidlist_ids: + (status, fvs) = tbl.get(sidlist_id) + assert status == True + for fv in fvs: + if fv[0] == "SAI_SRV6_SIDLIST_ATTR_SEGMENT_LIST": + if fv[1] == "1:fc00:0:1:e000::": + sidlist_id_1 = sidlist_id + elif fv[1] in "1:fc00:0:3:e000::": + sidlist_id_2 = sidlist_id + else: + assert False, "Sidlist %s not expected" % fv[1] + elif fv[0] == "SAI_SRV6_SIDLIST_ATTR_TYPE": + assert fv[1] == "SAI_SRV6_SIDLIST_TYPE_ENCAPS_RED" + assert sidlist_id_1 is not None + assert sidlist_id_2 is not None # check ASIC SAI_OBJECT_TYPE_NEXT_HOP database tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") - (status, fvs) = tbl.get(nexthop_id) + for nexthop_id in nexthop_ids: + (status, fvs) = tbl.get(nexthop_id) + assert status == True + for fv in fvs: + if fv[0] == "SAI_NEXT_HOP_ATTR_TYPE": + assert fv[1] == "SAI_NEXT_HOP_TYPE_SRV6_SIDLIST" + if fv[0] == "SAI_NEXT_HOP_ATTR_SRV6_SIDLIST_ID": + if fv[1] == sidlist_id_1: + nexthop_id_1 = nexthop_id + elif fv[1] == sidlist_id_2: + nexthop_id_2 = nexthop_id + else: + assert False, "Nexthop with sidlist %s not expected" % fv[1] + elif fv[0] == "SAI_NEXT_HOP_ATTR_TUNNEL_ID": + assert fv[1] == tunnel_id + assert nexthop_id_1 is not None + assert nexthop_id_2 is not None + + # check ASIC SAI_OBJECT_TYPE_NEXT_HOP_GROUP database + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP") + (status, fvs) = tbl.get(nexthop_group_id) assert status == True for fv in fvs: - if fv[0] == "SAI_NEXT_HOP_ATTR_TYPE": - assert fv[1] == "SAI_NEXT_HOP_TYPE_SRV6_SIDLIST" - if fv[0] == "SAI_NEXT_HOP_ATTR_SRV6_SIDLIST_ID": - assert fv[1] == sidlist_id - elif fv[0] == "SAI_NEXT_HOP_ATTR_TUNNEL_ID": - assert fv[1] == tunnel_id + if fv[0] == "SAI_NEXT_HOP_GROUP_ATTR_TYPE": + assert fv[1] == "SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP" + + # check ASIC SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER database + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER") + for nexthop_group_member_id in nexthop_group_member_ids: + (status, fvs) = tbl.get(nexthop_group_member_id) + assert status == True + for fv in fvs: + if fv[0] == "SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID": + assert fv[1] == nexthop_group_id + if fv[0] == "SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID": + if fv[1] == nexthop_id_1: + nexthop_group_member_id_1 = nexthop_group_member_id + elif fv[1] == nexthop_id_2: + nexthop_group_member_id_2 = nexthop_group_member_id + else: + assert False, "Nexthop group member for nexthop id %s not expected" % fv[1] + assert nexthop_group_member_id_1 is not None + assert nexthop_group_member_id_2 is not None + + # check ASIC SAI_OBJECT_TYPE_ROUTE_ENTRY database + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for route_key in route_keys: + route_dest = json.loads(route_key)["dest"] + (status, fvs) = tbl.get(route_key) + assert status == True + for fv in fvs: + if fv[0] == "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID": + if route_dest == "192.0.2.0/24": + assert fv[1] == nexthop_id_1 + elif route_dest == "192.0.3.0/24": + assert fv[1] == nexthop_group_id + else: + assert False, "Route with destination %s not expected" % route_dest + assert nexthop_id_1 is not None + assert nexthop_group_id is not None # check ASIC SAI_OBJECT_TYPE_TUNNEL database tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL") @@ -1616,15 +1707,21 @@ def test_AddRemoveSrv6SteeringRouteIpv4(self, dvs, testlog): assert fv[1] == "fc00:0:2::1" # remove v4 route with vpn sid - dvs.runcmd("ip route del 192.0.2.0/24 encap seg6 mode encap segs fc00:0:1:e000:: dev sr0 vrf Vrf13") + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"no ip route 192.0.2.0/24 Ethernet104 vrf Vrf13 nexthop-vrf default segments fc00:0:1:e000::\"") + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"no ip route 192.0.3.0/24 Ethernet104 vrf Vrf13 nexthop-vrf default segments fc00:0:1:e000::\"") + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"no ip route 192.0.3.0/24 Ethernet108 vrf Vrf13 nexthop-vrf default segments fc00:0:3:e000::\"") time.sleep(3) # check application database self.pdb.wait_for_deleted_entry("ROUTE_TABLE", "Vrf13:192.0.2.0/24") + self.pdb.wait_for_deleted_entry("ROUTE_TABLE", "Vrf13:192.0.3.0/24") self.pdb.wait_for_deleted_entry("SRV6_SID_LIST_TABLE", "fc00:0:1:e000::") + self.pdb.wait_for_deleted_entry("SRV6_SID_LIST_TABLE", "fc00:0:3:e000::") # verify that the route has been removed from the ASIC + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP", len(nexthop_group_entries)) + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER", len(nexthop_group_member_entries)) self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", len(nexthop_entries)) self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL", len(tunnel_entries)) self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY", len(route_entries)) @@ -1632,6 +1729,9 @@ def test_AddRemoveSrv6SteeringRouteIpv4(self, dvs, testlog): # unconfigure srv6 locator dvs.runcmd("vtysh -c \"configure terminal\" -c \"segment-routing\" -c \"no srv6\"") + # unconfigure loopback source address + dvs.runcmd("vtysh -c \"configure terminal\" -c \"interface lo\" -c \"no ip address fc00:0:2::1/128\"") + self.teardown_srv6(dvs) def test_AddRemoveSrv6SteeringRouteIpv6(self, dvs, testlog): @@ -1652,9 +1752,13 @@ def test_AddRemoveSrv6SteeringRouteIpv6(self, dvs, testlog): nexthop_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") route_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") sidlist_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST") + nexthop_group_member_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER") + nexthop_group_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP") # create v6 route with vpn sid - dvs.runcmd("ip -6 route add 2001:db8:1:1::/64 encap seg6 mode encap segs fc00:0:1:e000:: dev sr0 vrf Vrf13") + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"ipv6 route 2001:db8:1:1::/64 Ethernet104 vrf Vrf13 nexthop-vrf default segments fc00:0:1:e000::\"") + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"ipv6 route 2001:db8:2:2::/64 Ethernet104 vrf Vrf13 nexthop-vrf default segments fc00:0:1:e000::\"") + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"ipv6 route 2001:db8:2:2::/64 Ethernet108 vrf Vrf13 nexthop-vrf default segments fc00:0:3:e000::\"") time.sleep(3) @@ -1663,21 +1767,207 @@ def test_AddRemoveSrv6SteeringRouteIpv6(self, dvs, testlog): expected_fields = {"segment": "fc00:0:1:e000::", "seg_src": "fc00:0:2::1"} self.pdb.wait_for_field_match("ROUTE_TABLE", "Vrf13:2001:db8:1:1::/64", expected_fields) + self.pdb.wait_for_entry("ROUTE_TABLE", "Vrf13:2001:db8:2:2::/64") + expected_fields = {"segment": "fc00:0:1:e000::,fc00:0:3:e000::", "seg_src": "fc00:0:2::1,fc00:0:2::1"} + self.pdb.wait_for_field_match("ROUTE_TABLE", "Vrf13:2001:db8:2:2::/64", expected_fields) + self.pdb.wait_for_entry("SRV6_SID_LIST_TABLE", "fc00:0:1:e000::") expected_fields = {"path": "fc00:0:1:e000::"} self.pdb.wait_for_field_match("SRV6_SID_LIST_TABLE", "fc00:0:1:e000::", expected_fields) + self.pdb.wait_for_entry("SRV6_SID_LIST_TABLE", "fc00:0:3:e000::") + expected_fields = {"path": "fc00:0:3:e000::"} + self.pdb.wait_for_field_match("SRV6_SID_LIST_TABLE", "fc00:0:3:e000::", expected_fields) + + # verify that the route has been programmed into the ASIC + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL", len(tunnel_entries) + 1) + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", len(nexthop_entries) + 2) + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST", len(sidlist_entries) + 2) + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY", len(route_entries) + 2) + + # get created entries + route_keys = get_created_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY", route_entries, 2) + nexthop_ids = get_created_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nexthop_entries, 2) + tunnel_ids = get_created_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL", tunnel_entries, 1) + sidlist_ids = get_created_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST", sidlist_entries, 2) + nexthop_group_member_ids = get_created_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER", nexthop_group_member_entries, 2) + nexthop_group_ids = get_created_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP", nexthop_group_entries, 1) + + sidlist_id_1 = None + sidlist_id_2 = None + nexthop_id_1 = None + nexthop_id_2 = None + nexthop_group_member_id_1 = None + nexthop_group_member_id_2 = None + tunnel_id = tunnel_ids[0] + nexthop_group_id = nexthop_group_ids[0] + + # check ASIC SAI_OBJECT_TYPE_SRV6_SIDLIST database + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST") + for sidlist_id in sidlist_ids: + (status, fvs) = tbl.get(sidlist_id) + assert status == True + for fv in fvs: + if fv[0] == "SAI_SRV6_SIDLIST_ATTR_SEGMENT_LIST": + if fv[1] == "1:fc00:0:1:e000::": + sidlist_id_1 = sidlist_id + elif fv[1] in "1:fc00:0:3:e000::": + sidlist_id_2 = sidlist_id + else: + assert False, "Sidlist %s not expected" % fv[1] + elif fv[0] == "SAI_SRV6_SIDLIST_ATTR_TYPE": + assert fv[1] == "SAI_SRV6_SIDLIST_TYPE_ENCAPS_RED" + assert sidlist_id_1 is not None + assert sidlist_id_2 is not None + + # check ASIC SAI_OBJECT_TYPE_NEXT_HOP database + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") + for nexthop_id in nexthop_ids: + (status, fvs) = tbl.get(nexthop_id) + assert status == True + for fv in fvs: + if fv[0] == "SAI_NEXT_HOP_ATTR_TYPE": + assert fv[1] == "SAI_NEXT_HOP_TYPE_SRV6_SIDLIST" + if fv[0] == "SAI_NEXT_HOP_ATTR_SRV6_SIDLIST_ID": + if fv[1] == sidlist_id_1: + nexthop_id_1 = nexthop_id + elif fv[1] == sidlist_id_2: + nexthop_id_2 = nexthop_id + else: + assert False, "Nexthop with sidlist %s not expected" % fv[1] + elif fv[0] == "SAI_NEXT_HOP_ATTR_TUNNEL_ID": + assert fv[1] == tunnel_id + assert nexthop_id_1 is not None + assert nexthop_id_2 is not None + + # check ASIC SAI_OBJECT_TYPE_NEXT_HOP_GROUP database + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP") + (status, fvs) = tbl.get(nexthop_group_id) + assert status == True + for fv in fvs: + if fv[0] == "SAI_NEXT_HOP_GROUP_ATTR_TYPE": + assert fv[1] == "SAI_NEXT_HOP_GROUP_TYPE_DYNAMIC_UNORDERED_ECMP" + + # check ASIC SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER database + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER") + for nexthop_group_member_id in nexthop_group_member_ids: + (status, fvs) = tbl.get(nexthop_group_member_id) + assert status == True + for fv in fvs: + if fv[0] == "SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID": + assert fv[1] == nexthop_group_id + if fv[0] == "SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID": + if fv[1] == nexthop_id_1: + nexthop_group_member_id_1 = nexthop_group_member_id + elif fv[1] == nexthop_id_2: + nexthop_group_member_id_2 = nexthop_group_member_id + else: + assert False, "Nexthop group member for nexthop id %s not expected" % fv[1] + assert nexthop_group_member_id_1 is not None + assert nexthop_group_member_id_2 is not None + + # check ASIC SAI_OBJECT_TYPE_ROUTE_ENTRY database + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for route_key in route_keys: + route_dest = json.loads(route_key)["dest"] + (status, fvs) = tbl.get(route_key) + assert status == True + for fv in fvs: + if fv[0] == "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID": + if route_dest == "2001:db8:1:1::/64": + assert fv[1] == nexthop_id_1 + elif route_dest == "2001:db8:2:2::/64": + assert fv[1] == nexthop_group_id + else: + assert False, "Route with destination %s not expected" % route_dest + assert nexthop_id_1 is not None + assert nexthop_group_id is not None + + # check ASIC SAI_OBJECT_TYPE_TUNNEL database + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL") + (status, fvs) = tbl.get(tunnel_id) + assert status == True + for fv in fvs: + if fv[0] == "SAI_TUNNEL_ATTR_TYPE": + assert fv[1] == "SAI_TUNNEL_TYPE_SRV6" + elif fv[0] == "SAI_TUNNEL_ATTR_ENCAP_SRC_IP": + assert fv[1] == "fc00:0:2::1" + + # remove v4 route with vpn sid + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"no ipv6 route 2001:db8:1:1::/64 Ethernet104 vrf Vrf13 nexthop-vrf default segments fc00:0:1:e000::\"") + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"no ipv6 route 2001:db8:2:2::/64 Ethernet104 vrf Vrf13 nexthop-vrf default segments fc00:0:1:e000::\"") + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"no ipv6 route 2001:db8:2:2::/64 Ethernet108 vrf Vrf13 nexthop-vrf default segments fc00:0:3:e000::\"") + + time.sleep(3) + + # check application database + self.pdb.wait_for_deleted_entry("ROUTE_TABLE", "Vrf13:2001:db8:1:1::/64") + self.pdb.wait_for_deleted_entry("ROUTE_TABLE", "Vrf13:2001:db8:2:2::/64") + self.pdb.wait_for_deleted_entry("SRV6_SID_LIST_TABLE", "fc00:0:1:e000::") + self.pdb.wait_for_deleted_entry("SRV6_SID_LIST_TABLE", "fc00:0:3:e000::") + + # verify that the route has been removed from the ASIC + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP", len(nexthop_group_entries)) + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER", len(nexthop_group_member_entries)) + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", len(nexthop_entries)) + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL", len(tunnel_entries)) + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY", len(route_entries)) + + # unconfigure srv6 locator + dvs.runcmd("vtysh -c \"configure terminal\" -c \"segment-routing\" -c \"no srv6\"") + + # unconfigure loopback source address + dvs.runcmd("vtysh -c \"configure terminal\" -c \"interface lo\" -c \"no ip address fc00:0:2::1/128\"") + + self.teardown_srv6(dvs) + + def test_AddRemoveSrv6SteeringRouteSidlistSharing(self, dvs, testlog): + + _, output = dvs.runcmd(f"vtysh -c 'show zebra dplane providers'") + if 'dplane_fpm_sonic' not in output: + pytest.skip("'dplane_fpm_sonic' required for this test is not available, skipping", allow_module_level=True) + + self.setup_srv6(dvs) + + dvs.runcmd("vtysh -c \"configure terminal\" -c \"interface lo\" -c \"ip address fcbb:bbbb:1::1/128\"") + + # save exist asic db entries + tunnel_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL") + nexthop_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") + route_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + sidlist_entries = get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST") + + # configure routes to steer prefixes over the SID list + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"ip route 192.0.2.0/24 sr0 vrf Vrf13 nexthop-vrf default segments fcbb:bbbb:2:3:e000::\"") + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"ip route 192.0.3.0/24 sr0 vrf Vrf13 nexthop-vrf default segments fcbb:bbbb:2:3:e000::\"") + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"ipv6 route 2001:db8:2:2::/64 sr0 vrf Vrf13 nexthop-vrf default segments fcbb:bbbb:2:3:e000::\"") + + time.sleep(3) + + # check application database + self.pdb.wait_for_entry("ROUTE_TABLE", "Vrf13:192.0.2.0/24") + expected_fields = {"segment": "fcbb:bbbb:2:3:e000::", "seg_src": "fcbb:bbbb:1::1"} + self.pdb.wait_for_field_match("ROUTE_TABLE", "Vrf13:192.0.2.0/24", expected_fields) + + self.pdb.wait_for_entry("SRV6_SID_LIST_TABLE", "fcbb:bbbb:2:3:e000::") + expected_fields = {"path": "fcbb:bbbb:2:3:e000::"} + self.pdb.wait_for_field_match("SRV6_SID_LIST_TABLE", "fcbb:bbbb:2:3:e000::", expected_fields) + # verify that the route has been programmed into the ASIC self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL", len(tunnel_entries) + 1) self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", len(nexthop_entries) + 1) self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST", len(sidlist_entries) + 1) - self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY", len(route_entries) + 1) + self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY", len(route_entries) + 3) # get created entries - route_key = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY", route_entries) - nexthop_id = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nexthop_entries) - tunnel_id = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL", tunnel_entries) - sidlist_id = get_created_entry(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST", sidlist_entries) + route_keys = get_created_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY", route_entries, 3) + nexthop_ids = get_created_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nexthop_entries, 1) + tunnel_ids = get_created_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL", tunnel_entries, 1) + sidlist_ids = get_created_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST", sidlist_entries, 1) + + nexthop_id = nexthop_ids[0] + tunnel_id = tunnel_ids[0] + sidlist_id = sidlist_ids[0] # check ASIC SAI_OBJECT_TYPE_SRV6_SIDLIST database tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST") @@ -1685,17 +1975,18 @@ def test_AddRemoveSrv6SteeringRouteIpv6(self, dvs, testlog): assert status == True for fv in fvs: if fv[0] == "SAI_SRV6_SIDLIST_ATTR_SEGMENT_LIST": - assert fv[1] == "1:fc00:0:1:e000::" + assert fv[1] == "1:fcbb:bbbb:2:3:e000::" elif fv[0] == "SAI_SRV6_SIDLIST_ATTR_TYPE": assert fv[1] == "SAI_SRV6_SIDLIST_TYPE_ENCAPS_RED" # check ASIC SAI_OBJECT_TYPE_ROUTE_ENTRY database tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") - (status, fvs) = tbl.get(route_key) - assert status == True - for fv in fvs: - if fv[0] == "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID": - assert fv[1] == nexthop_id + for route_key in route_keys: + (status, fvs) = tbl.get(route_key) + assert status == True + for fv in fvs: + if fv[0] == "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID": + assert fv[1] == nexthop_id # check ASIC SAI_OBJECT_TYPE_NEXT_HOP database tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") @@ -1717,16 +2008,20 @@ def test_AddRemoveSrv6SteeringRouteIpv6(self, dvs, testlog): if fv[0] == "SAI_TUNNEL_ATTR_TYPE": assert fv[1] == "SAI_TUNNEL_TYPE_SRV6" elif fv[0] == "SAI_TUNNEL_ATTR_ENCAP_SRC_IP": - assert fv[1] == "fc00:0:2::1" + assert fv[1] == "fcbb:bbbb:1::1" - # remove v4 route with vpn sid - dvs.runcmd("ip route del 2001:db8:1:1::/64 encap seg6 mode encap segs fc00:0:1:e000:: dev sr0 vrf Vrf13") + # remove routes + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"no ip route 192.0.2.0/24 sr0 vrf Vrf13 nexthop-vrf default segments fcbb:bbbb:2:3:e000::\"") + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"no ip route 192.0.3.0/24 sr0 vrf Vrf13 nexthop-vrf default segments fcbb:bbbb:2:3:e000::\"") + dvs.runcmd("vtysh -c \"configure terminal\" vtysh -c \"no ipv6 route 2001:db8:2:2::/64 sr0 vrf Vrf13 nexthop-vrf default segments fcbb:bbbb:2:3:e000::\"") time.sleep(3) # check application database - self.pdb.wait_for_deleted_entry("ROUTE_TABLE", "Vrf13:2001:db8:1:1::/64") - self.pdb.wait_for_deleted_entry("SRV6_SID_LIST_TABLE", "fc00:0:1:e000::") + self.pdb.wait_for_deleted_entry("ROUTE_TABLE", "Vrf13:192.0.2.0/24") + self.pdb.wait_for_deleted_entry("ROUTE_TABLE", "Vrf13:192.0.3.0/24") + self.pdb.wait_for_deleted_entry("ROUTE_TABLE", "Vrf13:2001:db8:2:2::/64") + self.pdb.wait_for_deleted_entry("SRV6_SID_LIST_TABLE", "fcbb:bbbb:1:e000::") # verify that the route has been removed from the ASIC self.adb.wait_for_n_keys("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", len(nexthop_entries)) @@ -1736,6 +2031,9 @@ def test_AddRemoveSrv6SteeringRouteIpv6(self, dvs, testlog): # unconfigure srv6 locator dvs.runcmd("vtysh -c \"configure terminal\" -c \"segment-routing\" -c \"no srv6\"") + # unconfigure loopback source address + dvs.runcmd("vtysh -c \"configure terminal\" -c \"interface lo\" -c \"no ip address fcbb:bbbb:1::1/128\"") + self.teardown_srv6(dvs) class TestSrv6Vpn(object):