From aa0206c1f8f579ba25b04b95e39ca688da99ca29 Mon Sep 17 00:00:00 2001 From: Kumaresh Perumal Date: Thu, 11 Nov 2021 10:05:20 -0800 Subject: [PATCH] [SRV6] Sonic-swss changes for SRV6 (#1964) *RouteOrch changes to trigger SRV6 nexthops and update route entries *SRV6Orch changes to create SRV6 nexthops, tunnel and SRV6 MY_SID_ENTRY objects --- doc/swss-schema.md | 17 + orchagent/Makefile.am | 2 + orchagent/crmorch.cpp | 70 ++++ orchagent/crmorch.h | 2 + orchagent/neighorch.cpp | 18 + orchagent/neighorch.h | 1 + orchagent/nexthopgroupkey.h | 38 +- orchagent/nexthopkey.h | 57 ++- orchagent/orchdaemon.cpp | 13 +- orchagent/orchdaemon.h | 1 + orchagent/routeorch.cpp | 142 ++++++- orchagent/routeorch.h | 4 +- orchagent/saihelper.cpp | 3 + orchagent/srv6orch.cpp | 655 ++++++++++++++++++++++++++++++++ orchagent/srv6orch.h | 105 +++++ tests/mock_tests/Makefile.am | 3 +- tests/mock_tests/aclorch_ut.cpp | 12 +- tests/test_srv6.py | 260 +++++++++++++ 18 files changed, 1361 insertions(+), 42 deletions(-) create mode 100644 orchagent/srv6orch.cpp create mode 100644 orchagent/srv6orch.h create mode 100644 tests/test_srv6.py diff --git a/doc/swss-schema.md b/doc/swss-schema.md index 7f25803a28..265b9be20b 100644 --- a/doc/swss-schema.md +++ b/doc/swss-schema.md @@ -161,6 +161,8 @@ and reflects the LAG ports into the redis under: `LAG_TABLE::port` nexthop = *prefix, ;IP addresses separated “,” (empty indicates no gateway) intf = ifindex? PORT_TABLE.key ; zero or more separated by “,” (zero indicates no interface) blackhole = BIT ; Set to 1 if this route is a blackhole (or null0) + segment = string ; SRV6 segment name + seg_src = string ; ipv6 address for SRV6 tunnel source --------------------------------------------- ### NEIGH_TABLE @@ -173,6 +175,21 @@ and reflects the LAG ports into the redis under: `LAG_TABLE::port` neigh = 12HEXDIG ; mac address of the neighbor family = "IPv4" / "IPv6" ; address family +--------------------------------------------- +### SRV6_SID_LIST_TABLE + ; Stores IPV6 prefixes for a SRV6 segment name + key = ROUTE_TABLE:segment ; SRV6 segment name + ; field = value + path = STRING ; Comma-separated list of IPV6 prefixes for a SRV6 segment + +--------------------------------------------- +### SRV6_MY_SID_TABLE + ; Stores SRV6 MY_SID table entries and associated actions + key = STRING ; SRV6 MY_SID prefix string + ; field = value + action = STRING ; MY_SID actions like "end", "end.dt46" + vrf = STRING ; VRF string for END.DT46 or END.DT4 or END.DT6 + --------------------------------------------- ### FDB_TABLE diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index 88f69a9275..d794a55e1a 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -62,6 +62,8 @@ orchagent_SOURCES = \ debugcounterorch.cpp \ natorch.cpp \ muxorch.cpp + bfdorch.cpp \ + srv6orch.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/crmorch.cpp b/orchagent/crmorch.cpp index 659d35fb72..d3c3ef1b5d 100644 --- a/orchagent/crmorch.cpp +++ b/orchagent/crmorch.cpp @@ -41,6 +41,8 @@ const map crmResTypeNameMap = { CrmResourceType::CRM_IPMC_ENTRY, "IPMC_ENTRY" }, { CrmResourceType::CRM_SNAT_ENTRY, "SNAT_ENTRY" }, { CrmResourceType::CRM_DNAT_ENTRY, "DNAT_ENTRY" } + { CrmResourceType::CRM_SRV6_MY_SID_ENTRY, "SRV6_MY_SID_ENTRY" }, + { CrmResourceType::CRM_SRV6_NEXTHOP, "SRV6_NEXTHOP" }, }; const map crmResSaiAvailAttrMap = @@ -61,6 +63,8 @@ const map crmResSaiAvailAttrMap = { CrmResourceType::CRM_IPMC_ENTRY, SAI_SWITCH_ATTR_AVAILABLE_IPMC_ENTRY}, { CrmResourceType::CRM_SNAT_ENTRY, SAI_SWITCH_ATTR_AVAILABLE_SNAT_ENTRY }, { CrmResourceType::CRM_DNAT_ENTRY, SAI_SWITCH_ATTR_AVAILABLE_DNAT_ENTRY } + { CrmResourceType::CRM_SRV6_MY_SID_ENTRY, SAI_OBJECT_TYPE_MY_SID_ENTRY }, + { CrmResourceType::CRM_SRV6_NEXTHOP, SAI_SWITCH_ATTR_AVAILABLE_IPV6_NEXTHOP_ENTRY }, }; const map crmThreshTypeResMap = @@ -81,6 +85,8 @@ const map crmThreshTypeResMap = { "ipmc_entry_threshold_type", CrmResourceType::CRM_IPMC_ENTRY }, { "snat_entry_threshold_type", CrmResourceType::CRM_SNAT_ENTRY }, { "dnat_entry_threshold_type", CrmResourceType::CRM_DNAT_ENTRY } + { "srv6_my_sid_entry_threshold_type", CrmResourceType::CRM_SRV6_MY_SID_ENTRY }, + { "srv6_nexthop_threshold_type", CrmResourceType::CRM_SRV6_NEXTHOP }, }; const map crmThreshLowResMap = @@ -101,6 +107,8 @@ const map crmThreshLowResMap = {"ipmc_entry_low_threshold", CrmResourceType::CRM_IPMC_ENTRY }, {"snat_entry_low_threshold", CrmResourceType::CRM_SNAT_ENTRY }, {"dnat_entry_low_threshold", CrmResourceType::CRM_DNAT_ENTRY } + {"srv6_my_sid_entry_low_threshold", CrmResourceType::CRM_SRV6_MY_SID_ENTRY }, + {"srv6_nexthop_low_threshold", CrmResourceType::CRM_SRV6_NEXTHOP }, }; const map crmThreshHighResMap = @@ -121,6 +129,8 @@ const map crmThreshHighResMap = {"ipmc_entry_high_threshold", CrmResourceType::CRM_IPMC_ENTRY }, {"snat_entry_high_threshold", CrmResourceType::CRM_SNAT_ENTRY }, {"dnat_entry_high_threshold", CrmResourceType::CRM_DNAT_ENTRY } + {"srv6_my_sid_entry_high_threshold", CrmResourceType::CRM_SRV6_MY_SID_ENTRY }, + {"srv6_nexthop_high_threshold", CrmResourceType::CRM_SRV6_NEXTHOP }, }; const map crmThreshTypeMap = @@ -148,6 +158,8 @@ const map crmAvailCntsTableMap = { "crm_stats_ipmc_entry_available", CrmResourceType::CRM_IPMC_ENTRY }, { "crm_stats_snat_entry_available", CrmResourceType::CRM_SNAT_ENTRY }, { "crm_stats_dnat_entry_available", CrmResourceType::CRM_DNAT_ENTRY } + { "crm_stats_srv6_my_sid_entry_available", CrmResourceType::CRM_SRV6_MY_SID_ENTRY }, + { "crm_stats_srv6_nexthop_available", CrmResourceType::CRM_SRV6_NEXTHOP }, }; const map crmUsedCntsTableMap = @@ -168,6 +180,8 @@ const map crmUsedCntsTableMap = { "crm_stats_ipmc_entry_used", CrmResourceType::CRM_IPMC_ENTRY }, { "crm_stats_snat_entry_used", CrmResourceType::CRM_SNAT_ENTRY }, { "crm_stats_dnat_entry_used", CrmResourceType::CRM_DNAT_ENTRY } + { "crm_stats_srv6_my_sid_entry_used", CrmResourceType::CRM_SRV6_MY_SID_ENTRY }, + { "crm_stats_srv6_nexthop_used", CrmResourceType::CRM_SRV6_NEXTHOP }, }; CrmOrch::CrmOrch(DBConnector *db, string tableName): @@ -530,6 +544,62 @@ void CrmOrch::getResAvailableCounters() break; } + case CrmResourceType::CRM_SRV6_MY_SID_ENTRY: + { + sai_object_type_t objType = static_cast(crmResSaiAvailAttrMap.at(res.first)); + uint64_t availCount = 0; + sai_status_t status = sai_object_type_get_availability(gSwitchId, objType, 0, nullptr, &availCount); + if (status != SAI_STATUS_SUCCESS) + { + if ((status == SAI_STATUS_NOT_SUPPORTED) || + (status == SAI_STATUS_NOT_IMPLEMENTED) || + SAI_STATUS_IS_ATTR_NOT_SUPPORTED(status) || + SAI_STATUS_IS_ATTR_NOT_IMPLEMENTED(status)) + { + // mark unsupported resources + res.second.resStatus = CrmResourceStatus::CRM_RES_NOT_SUPPORTED; + SWSS_LOG_NOTICE("CRM Resource %s not supported", crmResTypeNameMap.at(res.first).c_str()); + break; + } + SWSS_LOG_ERROR("Failed to get availability for object_type %u , rv:%d", objType, status); + break; + } + + res.second.countersMap[CRM_COUNTERS_TABLE_KEY].availableCounter = static_cast(availCount); + + break; + } + + case CrmResourceType::CRM_SRV6_NEXTHOP: + { + sai_object_type_t objType = static_cast(crmResSaiAvailAttrMap.at(res.first)); + sai_attribute_t attr; + uint64_t availCount = 0; + + attr.id = SAI_NEXT_HOP_ATTR_TYPE; + attr.value.s32 = SAI_NEXT_HOP_TYPE_SRV6_SIDLIST; + sai_status_t status = sai_object_type_get_availability(gSwitchId, objType, 1, &attr, &availCount); + if (status != SAI_STATUS_SUCCESS) + { + if ((status == SAI_STATUS_NOT_SUPPORTED) || + (status == SAI_STATUS_NOT_IMPLEMENTED) || + SAI_STATUS_IS_ATTR_NOT_SUPPORTED(status) || + SAI_STATUS_IS_ATTR_NOT_IMPLEMENTED(status)) + { + // mark unsupported resources + res.second.resStatus = CrmResourceStatus::CRM_RES_NOT_SUPPORTED; + SWSS_LOG_NOTICE("CRM Resource %s not supported", crmResTypeNameMap.at(res.first).c_str()); + break; + } + SWSS_LOG_ERROR("Failed to get availability for object_type %u , rv:%d", objType, status); + break; + } + + res.second.countersMap[CRM_COUNTERS_TABLE_KEY].availableCounter = static_cast(availCount); + + break; + } + default: SWSS_LOG_ERROR("Failed to get CRM attribute %u. Unknown attribute.\n", attr.id); return; diff --git a/orchagent/crmorch.h b/orchagent/crmorch.h index 7c093ffb24..98e0f691c4 100644 --- a/orchagent/crmorch.h +++ b/orchagent/crmorch.h @@ -28,6 +28,8 @@ enum class CrmResourceType CRM_IPMC_ENTRY, CRM_SNAT_ENTRY, CRM_DNAT_ENTRY, + CRM_SRV6_MY_SID_ENTRY, + CRM_SRV6_NEXTHOP, }; enum class CrmThresholdType diff --git a/orchagent/neighorch.cpp b/orchagent/neighorch.cpp index e4ca212e74..39cace09a7 100644 --- a/orchagent/neighorch.cpp +++ b/orchagent/neighorch.cpp @@ -943,3 +943,21 @@ bool NeighOrch::removeTunnelNextHop(const NextHopKey& nh) return true; } +void NeighOrch::updateSrv6Nexthop(const NextHopKey &nh, const sai_object_id_t &nh_id) +{ + if (nh_id != SAI_NULL_OBJECT_ID) + { + NextHopEntry next_hop_entry; + next_hop_entry.next_hop_id = nh_id; + next_hop_entry.ref_count = 0; + next_hop_entry.nh_flags = 0; + m_syncdNextHops[nh] = next_hop_entry; + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_SRV6_NEXTHOP); + } + else + { + assert(m_syncdNextHops[nh].ref_count == 0); + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_SRV6_NEXTHOP); + m_syncdNextHops.erase(nh); + } +} diff --git a/orchagent/neighorch.h b/orchagent/neighorch.h index f201677380..230db25778 100644 --- a/orchagent/neighorch.h +++ b/orchagent/neighorch.h @@ -72,6 +72,7 @@ class NeighOrch : public Orch, public Subject, public Observer void update(SubjectType, void *); void resolveNeighbor(const NeighborEntry &); + void updateSrv6Nexthop(const NextHopKey &, const sai_object_id_t &); private: PortsOrch *m_portsOrch; diff --git a/orchagent/nexthopgroupkey.h b/orchagent/nexthopgroupkey.h index 22e75de551..b0dea89e49 100644 --- a/orchagent/nexthopgroupkey.h +++ b/orchagent/nexthopgroupkey.h @@ -12,6 +12,7 @@ class NextHopGroupKey NextHopGroupKey(const std::string &nexthops) { m_overlay_nexthops = false; + m_srv6_nexthops = false; auto nhv = tokenize(nexthops, NHG_DELIMITER); for (const auto &nh : nhv) { @@ -20,14 +21,29 @@ class NextHopGroupKey } /* ip_string|if_alias|vni|router_mac separated by ',' */ - NextHopGroupKey(const std::string &nexthops, bool overlay_nh) + NextHopGroupKey(const std::string &nexthops, bool overlay_nh, bool srv6_nh) { - m_overlay_nexthops = true; - auto nhv = tokenize(nexthops, NHG_DELIMITER); - for (const auto &nh_str : nhv) + if (overlay_nh) { - auto nh = NextHopKey(nh_str, overlay_nh); - m_nexthops.insert(nh); + m_overlay_nexthops = true; + m_srv6_nexthops = false; + auto nhv = tokenize(nexthops, NHG_DELIMITER); + for (const auto &nh_str : nhv) + { + auto nh = NextHopKey(nh_str, overlay_nh, srv6_nh); + m_nexthops.insert(nh); + } + } + else if (srv6_nh) + { + m_overlay_nexthops = false; + m_srv6_nexthops = true; + auto nhv = tokenize(nexthops, NHG_DELIMITER); + for (const auto &nh_str : nhv) + { + auto nh = NextHopKey(nh_str, overlay_nh, srv6_nh); + m_nexthops.insert(nh); + } } } @@ -137,8 +153,8 @@ class NextHopGroupKey { nhs_str += NHG_DELIMITER; } - if (m_overlay_nexthops) { - nhs_str += it->to_string(m_overlay_nexthops); + if (m_overlay_nexthops || m_srv6_nexthops) { + nhs_str += it->to_string(m_overlay_nexthops, m_srv6_nexthops); } else { nhs_str += it->to_string(); } @@ -152,6 +168,11 @@ class NextHopGroupKey return m_overlay_nexthops; } + inline bool is_srv6_nexthop() const + { + return m_srv6_nexthops; + } + void clear() { m_nexthops.clear(); @@ -160,6 +181,7 @@ class NextHopGroupKey private: std::set m_nexthops; bool m_overlay_nexthops; + bool m_srv6_nexthops; }; #endif /* SWSS_NEXTHOPGROUPKEY_H */ diff --git a/orchagent/nexthopkey.h b/orchagent/nexthopkey.h index 69a94505ae..c36ae66d18 100644 --- a/orchagent/nexthopkey.h +++ b/orchagent/nexthopkey.h @@ -15,6 +15,8 @@ struct NextHopKey string alias; // incoming interface alias uint32_t vni; // Encap VNI overlay nexthop MacAddress mac_address; // Overlay Nexthop MAC. + string srv6_segment; // SRV6 segment string + string srv6_source; // SRV6 source address NextHopKey() = default; NextHopKey(const std::string &ipstr, const std::string &alias) : ip_address(ipstr), alias(alias), vni(0), mac_address() {} @@ -49,23 +51,42 @@ struct NextHopKey throw std::invalid_argument(err); } } - NextHopKey(const std::string &str, bool overlay_nh) + NextHopKey(const std::string &str, bool overlay_nh, bool srv6_nh) { if (str.find(NHG_DELIMITER) != string::npos) { std::string err = "Error converting " + str + " to NextHop"; throw std::invalid_argument(err); } - auto keys = tokenize(str, NH_DELIMITER); - if (keys.size() != 4) + if (srv6_nh == true) { - std::string err = "Error converting " + str + " to NextHop"; - throw std::invalid_argument(err); + weight = 0; + vni = 0; + weight = 0; + auto keys = tokenize(str, NH_DELIMITER); + if (keys.size() != 3) + { + std::string err = "Error converting " + str + " to Nexthop"; + throw std::invalid_argument(err); + } + ip_address = keys[0]; + srv6_segment = keys[1]; + srv6_source = keys[2]; + } + else + { + auto keys = tokenize(str, NH_DELIMITER); + if (keys.size() != 4) + { + std::string err = "Error converting " + str + " to NextHop"; + throw std::invalid_argument(err); + } + ip_address = keys[0]; + alias = keys[1]; + vni = static_cast(std::stoul(keys[2])); + mac_address = keys[3]; + weight = 0; } - ip_address = keys[0]; - alias = keys[1]; - vni = static_cast(std::stoul(keys[2])); - mac_address = keys[3]; } const std::string to_string() const @@ -73,20 +94,25 @@ struct NextHopKey return ip_address.to_string() + NH_DELIMITER + alias; } - const std::string to_string(bool overlay_nh) const + const std::string to_string(bool overlay_nh, bool srv6_nh) const { + if (srv6_nh) + { + return ip_address.to_string() + NH_DELIMITER + srv6_segment + NH_DELIMITER + srv6_source; + } std::string s_vni = std::to_string(vni); return ip_address.to_string() + NH_DELIMITER + alias + NH_DELIMITER + s_vni + NH_DELIMITER + mac_address.to_string(); } bool operator<(const NextHopKey &o) const { - return tie(ip_address, alias, vni, mac_address) < tie(o.ip_address, o.alias, o.vni, o.mac_address); + return tie(ip_address, alias, vni, mac_address, srv6_segment, srv6_source) < tie(o.ip_address, o.alias, o.vni, o.mac_address, o.srv6_segment, o.srv6_source); } bool operator==(const NextHopKey &o) const { - return (ip_address == o.ip_address) && (alias == o.alias) && (vni == o.vni) && (mac_address == o.mac_address); + return (ip_address == o.ip_address) && (alias == o.alias) && (vni == o.vni) && (mac_address == o.mac_address) && + (srv6_segment == o.srv6_segment) && (srv6_source == o.srv6_source); } bool operator!=(const NextHopKey &o) const @@ -96,7 +122,12 @@ struct NextHopKey bool isIntfNextHop() const { - return (ip_address.getV4Addr() == 0); + return ((ip_address.getV4Addr() == 0) && !isSrv6NextHop()); + } + + bool isSrv6NextHop() const + { + return (srv6_segment != ""); } }; diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index eab7ef4d61..b1ea75ee50 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -38,6 +38,7 @@ BufferOrch *gBufferOrch; SwitchOrch *gSwitchOrch; Directory gDirectory; NatOrch *gNatOrch; +Srv6Orch *gSrv6Orch; bool gIsNatSupported = false; @@ -147,7 +148,15 @@ bool OrchDaemon::init() gFgNhgOrch = new FgNhgOrch(m_configDb, m_applDb, m_stateDb, fgnhg_tables, gNeighOrch, gIntfsOrch, vrf_orch); gDirectory.set(gFgNhgOrch); - gRouteOrch = new RouteOrch(m_applDb, APP_ROUTE_TABLE_NAME, gSwitchOrch, gNeighOrch, gIntfsOrch, vrf_orch, gFgNhgOrch); + + vector srv6_tables = { + APP_SRV6_SID_LIST_TABLE_NAME, + APP_SRV6_MY_SID_TABLE_NAME + }; + gSrv6Orch = new Srv6Orch(m_applDb, srv6_tables, gSwitchOrch, vrf_orch, gNeighOrch); + gDirectory.set(gSrv6Orch); + + gRouteOrch = new RouteOrch(m_applDb, APP_ROUTE_TABLE_NAME, gSwitchOrch, gNeighOrch, gIntfsOrch, vrf_orch, gFgNhgOrch, gSrv6Orch); CoppOrch *copp_orch = new CoppOrch(m_applDb, APP_COPP_TABLE_NAME); TunnelDecapOrch *tunnel_decap_orch = new TunnelDecapOrch(m_applDb, APP_TUNNEL_DECAP_TABLE_NAME); @@ -271,7 +280,7 @@ bool OrchDaemon::init() * when iterating ConsumerMap. This is ensured implicitly by the order of keys in ordered map. * For cases when Orch has to process tables in specific order, like PortsOrch during warm start, it has to override Orch::doTask() */ - m_orchList = { gSwitchOrch, gCrmOrch, gPortsOrch, gBufferOrch, gIntfsOrch, gNeighOrch, gRouteOrch, copp_orch, tunnel_decap_orch, qos_orch, wm_orch, policer_orch, sflow_orch, debug_counter_orch}; + m_orchList = { gSwitchOrch, gCrmOrch, gPortsOrch, gBufferOrch, gIntfsOrch, gNeighOrch, gRouteOrch, copp_orch, tunnel_decap_orch, qos_orch, wm_orch, policer_orch, sflow_orch, debug_counter_orch, gSrv6Orch}; bool initialize_dtel = false; if (platform == BFN_PLATFORM_SUBSTRING || platform == VS_PLATFORM_SUBSTRING) diff --git a/orchagent/orchdaemon.h b/orchagent/orchdaemon.h index 1215958a90..0ab069c6f6 100644 --- a/orchagent/orchdaemon.h +++ b/orchagent/orchdaemon.h @@ -32,6 +32,7 @@ #include "directory.h" #include "natorch.h" #include "muxorch.h" +#include "srv6orch.h" using namespace swss; diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index b99abbc252..c56c8bc642 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -26,7 +26,7 @@ extern size_t gMaxBulkSize; const int routeorch_pri = 5; -RouteOrch::RouteOrch(DBConnector *db, string tableName, SwitchOrch *switchOrch, NeighOrch *neighOrch, IntfsOrch *intfsOrch, VRFOrch *vrfOrch, FgNhgOrch *fgNhgOrch) : +RouteOrch::RouteOrch(DBConnector *db, string tableName, SwitchOrch *switchOrch, NeighOrch *neighOrch, IntfsOrch *intfsOrch, VRFOrch *vrfOrch, FgNhgOrch *fgNhgOrch, Srv6Orch *srv6Orch) : gRouteBulker(sai_route_api, gMaxBulkSize), gNextHopGroupMemberBulker(sai_next_hop_group_api, gSwitchId, gMaxBulkSize), Orch(db, tableName, routeorch_pri), @@ -36,6 +36,7 @@ RouteOrch::RouteOrch(DBConnector *db, string tableName, SwitchOrch *switchOrch, m_vrfOrch(vrfOrch), m_fgNhgOrch(fgNhgOrch), m_nextHopGroupCount(0), + m_srv6Orch(srv6Orch), m_resync(false) { SWSS_LOG_ENTER(); @@ -529,6 +530,9 @@ void RouteOrch::doTask(Consumer& consumer) bool& excp_intfs_flag = ctx.excp_intfs_flag; bool overlay_nh = false; bool blackhole = false; + string srv6_segments; + string srv6_source; + bool srv6_nh = false; for (auto i : kfvFieldsValues(t)) { @@ -548,13 +552,24 @@ void RouteOrch::doTask(Consumer& consumer) if (fvField(i) == "blackhole") blackhole = fvValue(i) == "true"; + if (fvField(i) == "segment") { + srv6_segments = fvValue(i); + srv6_nh = true; + } + + if (fvField(i) == "seg_src") + srv6_source = fvValue(i); } vector& ipv = ctx.ipv; + vector srv6_segv; + vector srv6_src; ipv = tokenize(ips, ','); vector alsv = tokenize(aliases, ','); vector vni_labelv = tokenize(vni_labels, ','); vector rmacv = tokenize(remote_macs, ','); + srv6_segv = tokenize(srv6_segments, ','); + srv6_src = tokenize(srv6_source, ','); /* * For backward compatibility, adjust ip string from old format to @@ -563,7 +578,7 @@ void RouteOrch::doTask(Consumer& consumer) /* Resize the ip vector to match ifname vector * as tokenize(",", ',') will miss the last empty segment. */ - if (alsv.size() == 0 && !blackhole) + if (alsv.size() == 0 && !blackhole && !srv6_nh) { SWSS_LOG_WARN("Skip the route %s, for it has an empty ifname field.", key.c_str()); it = consumer.m_toSync.erase(it); @@ -620,6 +635,30 @@ void RouteOrch::doTask(Consumer& consumer) { nhg = NextHopGroupKey(); } + else if (srv6_nh == true) + { + string ip; + if (ipv.empty()) + { + ip = "0.0.0.0"; + } + else + { + SWSS_LOG_ERROR("For SRV6 nexthop ipv should be empty"); + it = consumer.m_toSync.erase(it); + continue; + } + nhg_str = ip + NH_DELIMITER + srv6_segv[0] + NH_DELIMITER + srv6_src[0]; + + for (uint32_t i = 1; i < srv6_segv.size(); i++) + { + nhg_str += NHG_DELIMITER + ip; + nhg_str += NH_DELIMITER + srv6_segv[i]; + nhg_str += NH_DELIMITER + srv6_src[i]; + } + nhg = NextHopGroupKey(nhg_str, overlay_nh, srv6_nh); + SWSS_LOG_INFO("SRV6 route with nhg %s", nhg.to_string().c_str()); + } else if (overlay_nh == false) { if (alsv[0] == "tun0" && !(IpAddress(ipv[0]).isZero())) @@ -648,7 +687,7 @@ void RouteOrch::doTask(Consumer& consumer) nhg_str += NHG_DELIMITER + ipv[i] + NH_DELIMITER + "vni" + alsv[i] + NH_DELIMITER + vni_labelv[i] + NH_DELIMITER + rmacv[i]; } - nhg = NextHopGroupKey(nhg_str, overlay_nh); + nhg = NextHopGroupKey(nhg_str, overlay_nh, srv6_nh); } if (ipv.size() == 1 && IpAddress(ipv[0]).isZero()) @@ -798,7 +837,21 @@ void RouteOrch::doTask(Consumer& consumer) /* Remove next hop group if the reference count decreases to zero */ for (auto it_nhg = m_bulkNhgReducedRefCnt.begin(); it_nhg != m_bulkNhgReducedRefCnt.end(); it_nhg++) { - if (m_syncdNextHopGroups[*it_nhg].ref_count == 0) + if (it_nhg.first.is_srv6_nexthop()) + { + if(it_nhg.first.getSize() > 1) + { + if(m_syncdNextHopGroups[it_nhg.first].ref_count == 0) + { + removeNextHopGroup(*it_nhg); + } + else + { + SWSS_LOG_ERROR("SRV6 ECMP %s REF count is not zero", it_nhg.first.to_string().c_str()); + } + } + } + else if (m_syncdNextHopGroups[*it_nhg].ref_count == 0) { removeNextHopGroup(*it_nhg); } @@ -1051,6 +1104,7 @@ bool RouteOrch::addNextHopGroup(const NextHopGroupKey &nexthops) // skip next hop group member create for neighbor from down port if (m_neighOrch->isNextHopFlagSet(it, NHFLAGS_IFDOWN)) { + SWSS_LOG_INFO("Interface down for NH %s, skip this NH", it.to_string().c_str()); continue; } @@ -1171,6 +1225,7 @@ bool RouteOrch::removeNextHopGroup(const NextHopGroupKey &nexthops) auto next_hop_group_entry = m_syncdNextHopGroups.find(nexthops); sai_status_t status; bool overlay_nh = nexthops.is_overlay_nexthop(); + bool srv6_nh = nexthops.is_srv6_nexthop(); assert(next_hop_group_entry != m_syncdNextHopGroups.end()); @@ -1231,7 +1286,7 @@ bool RouteOrch::removeNextHopGroup(const NextHopGroupKey &nexthops) for (auto it : next_hop_set) { m_neighOrch->decreaseNextHopRefCount(it); - if (overlay_nh && !m_neighOrch->getNextHopRefCount(it)) + if (overlay_nh && !srv6_nh && !m_neighOrch->getNextHopRefCount(it)) { if(!m_neighOrch->removeTunnelNextHop(it)) { @@ -1241,15 +1296,28 @@ bool RouteOrch::removeNextHopGroup(const NextHopGroupKey &nexthops) { m_neighOrch->removeOverlayNextHop(it); SWSS_LOG_INFO("Tunnel Nexthop %s delete success", nexthops.to_string().c_str()); - SWSS_LOG_INFO("delete remote vtep %s", it.to_string(true).c_str()); + SWSS_LOG_INFO("delete remote vtep %s", it.to_string(overlay_nh, srv6_nh).c_str()); status = deleteRemoteVtep(SAI_NULL_OBJECT_ID, it); if (status == false) { - SWSS_LOG_ERROR("Failed to delete remote vtep %s ecmp", it.to_string(true).c_str()); + SWSS_LOG_ERROR("Failed to delete remote vtep %s ecmp", it.to_string(overlay_nh, srv6_nh).c_str()); } } } } + + if (srv6_nh) + { + if (!m_srv6Orch->removeSrv6Nexthops(nexthops)) + { + SWSS_LOG_ERROR("Failed to remove Srv6 Nexthop %s", nexthops.to_string().c_str()); + } + else + { + SWSS_LOG_INFO("Remove ECMP Srv6 nexthops %s", nexthops.to_string().c_str()); + } + } + m_syncdNextHopGroups.erase(nexthops); return true; @@ -1351,6 +1419,7 @@ bool RouteOrch::addRoute(RouteBulkContext& ctx, const NextHopGroupKey &nextHops) bool curNhgIsFineGrained = false; bool prevNhgWasFineGrained = false; bool blackhole = false; + bool srv6_nh = false; if (m_syncdRoutes.find(vrf_id) == m_syncdRoutes.end()) { @@ -1363,6 +1432,11 @@ bool RouteOrch::addRoute(RouteBulkContext& ctx, const NextHopGroupKey &nextHops) overlay_nh = true; } + if (nextHops.is_srv6_nexthop()) + { + srv6_nh = true; + } + auto it_route = m_syncdRoutes.at(vrf_id).find(ipPrefix); if (m_fgNhgOrch->isRouteFineGrained(vrf_id, ipPrefix, nextHops)) @@ -1416,19 +1490,28 @@ bool RouteOrch::addRoute(RouteBulkContext& ctx, const NextHopGroupKey &nextHops) } else { - if(overlay_nh) + if(overlay_nh && !srv6_nh) { - SWSS_LOG_INFO("create remote vtep %s", nexthop.to_string(overlay_nh).c_str()); + SWSS_LOG_INFO("create remote vtep %s", nexthop.to_string(overlay_nh, srv6_nh).c_str()); status = createRemoteVtep(vrf_id, nexthop); if (status == false) { - SWSS_LOG_ERROR("Failed to create remote vtep %s", nexthop.to_string(overlay_nh).c_str()); + SWSS_LOG_ERROR("Failed to create remote vtep %s", nexthop.to_string(overlay_nh, srv6_nh).c_str()); return false; } next_hop_id = m_neighOrch->addTunnelNextHop(nexthop); if (next_hop_id == SAI_NULL_OBJECT_ID) { - SWSS_LOG_ERROR("Failed to create Tunnel Nexthop %s", nexthop.to_string(overlay_nh).c_str()); + SWSS_LOG_ERROR("Failed to create Tunnel Nexthop %s", nexthop.to_string(overlay_nh, srv6_nh).c_str()); + return false; + } + } + else if (srv6_nh) + { + SWSS_LOG_INFO("Single NH: create srv6 nexthop %s", nextHops.to_string().c_str()); + if (!m_srv6Orch->srv6Nexthops(nextHops, next_hop_id)) + { + SWSS_LOG_ERROR("Failed to create SRV6 nexthop %s", nextHops.to_string().c_str()); return false; } } @@ -1448,6 +1531,16 @@ bool RouteOrch::addRoute(RouteBulkContext& ctx, const NextHopGroupKey &nextHops) /* Check if there is already an existing next hop group */ if (!hasNextHopGroup(nextHops)) { + if(srv6_nh) + { + sai_object_id_t temp_nh_id; + SWSS_LOG_INFO("ECMP SRV6 NH: create srv6 nexthops %s", nextHops.to_string().c_str()); + if(!m_srv6Orch->srv6Nexthops(nextHops, temp_nh_id)) + { + SWSS_LOG_ERROR("Failed to create SRV6 nexthops for %s", nextHops.to_string().c_str()); + return false; + } + } /* Try to create a new next hop group */ if (!addNextHopGroup(nextHops)) { @@ -1469,17 +1562,17 @@ bool RouteOrch::addRoute(RouteBulkContext& ctx, const NextHopGroupKey &nextHops) { if(overlay_nh) { - SWSS_LOG_INFO("create remote vtep %s ecmp", nextHop.to_string(overlay_nh).c_str()); + SWSS_LOG_INFO("create remote vtep %s ecmp", nextHop.to_string(overlay_nh, srv6_nh).c_str()); status = createRemoteVtep(vrf_id, nextHop); if (status == false) { - SWSS_LOG_ERROR("Failed to create remote vtep %s ecmp", nextHop.to_string(overlay_nh).c_str()); + SWSS_LOG_ERROR("Failed to create remote vtep %s ecmp", nextHop.to_string(overlay_nh, srv6_nh).c_str()); return false; } next_hop_id = m_neighOrch->addTunnelNextHop(nextHop); if (next_hop_id == SAI_NULL_OBJECT_ID) { - SWSS_LOG_ERROR("Failed to create Tunnel Nexthop %s", nextHop.to_string(overlay_nh).c_str()); + SWSS_LOG_ERROR("Failed to create Tunnel Nexthop %s", nextHop.to_string(overlay_nh, srv6_nh).c_str()); return false; } } @@ -1796,6 +1889,10 @@ bool RouteOrch::addRoutePost(const RouteBulkContext& ctx, const NextHopGroupKey SWSS_LOG_NOTICE("Update overlay Nexthop %s", ol_nextHops.to_string().c_str()); removeOverlayNextHops(vrf_id, ol_nextHops); } + else if (ol_nextHops.is_srv6_nexthop()) + { + m_srv6Orch->removeSrv6Nexthops(ol_nextHops); + } } if (blackhole) @@ -1958,6 +2055,19 @@ bool RouteOrch::removeRoutePost(const RouteBulkContext& ctx) SWSS_LOG_NOTICE("Remove overlay Nexthop %s", ol_nextHops.to_string().c_str()); removeOverlayNextHops(vrf_id, ol_nextHops); } + /* + * Additionally check if the NH has label and its ref count == 0, then + * remove the label next hop. + */ + else if (it_route->second.nhg_key.getSize() == 1) + { + const NextHopKey& nexthop = *it_route->second.nhg_key.getNextHops().begin(); + if (nexthop.isSrv6NextHop() && + (m_neighOrch->getNextHopRefCount(nexthop) == 0)) + { + m_srv6Orch->removeSrv6Nexthops(it_route->second.nhg_key); + } + } } SWSS_LOG_INFO("Remove route %s with next hop(s) %s", @@ -2046,11 +2156,11 @@ bool RouteOrch::removeOverlayNextHops(sai_object_id_t vrf_id, const NextHopGroup { m_neighOrch->removeOverlayNextHop(tunnel_nh); SWSS_LOG_INFO("Tunnel Nexthop %s delete success", ol_nextHops.to_string().c_str()); - SWSS_LOG_INFO("delete remote vtep %s", tunnel_nh.to_string(true).c_str()); + SWSS_LOG_INFO("delete remote vtep %s", tunnel_nh.to_string(true, false).c_str()); status = deleteRemoteVtep(vrf_id, tunnel_nh); if (status == false) { - SWSS_LOG_ERROR("Failed to delete remote vtep %s ecmp", tunnel_nh.to_string(true).c_str()); + SWSS_LOG_ERROR("Failed to delete remote vtep %s ecmp", tunnel_nh.to_string(true, false).c_str()); return false; } } diff --git a/orchagent/routeorch.h b/orchagent/routeorch.h index 9c9967a9cd..bc9eee4ec2 100644 --- a/orchagent/routeorch.h +++ b/orchagent/routeorch.h @@ -7,6 +7,7 @@ #include "intfsorch.h" #include "neighorch.h" #include "vxlanorch.h" +#include "srv6orch.h" #include "ipaddress.h" #include "ipaddresses.h" @@ -92,7 +93,7 @@ struct RouteBulkContext class RouteOrch : public Orch, public Subject { public: - RouteOrch(DBConnector *db, string tableName, SwitchOrch *switchOrch, NeighOrch *neighOrch, IntfsOrch *intfsOrch, VRFOrch *vrfOrch, FgNhgOrch *fgNhgOrch); + RouteOrch(DBConnector *db, string tableName, SwitchOrch *switchOrch, NeighOrch *neighOrch, IntfsOrch *intfsOrch, VRFOrch *vrfOrch, FgNhgOrch *fgNhgOrch, Srv6Orch *srv6Orch); bool hasNextHopGroup(const NextHopGroupKey&) const; sai_object_id_t getNextHopGroupId(const NextHopGroupKey&); @@ -131,6 +132,7 @@ class RouteOrch : public Orch, public Subject IntfsOrch *m_intfsOrch; VRFOrch *m_vrfOrch; FgNhgOrch *m_fgNhgOrch; + Srv6Orch *m_srv6Orch; int m_nextHopGroupCount; int m_maxNextHopGroupCount; diff --git a/orchagent/saihelper.cpp b/orchagent/saihelper.cpp index 9e0faa2fe9..d37edbd5c0 100644 --- a/orchagent/saihelper.cpp +++ b/orchagent/saihelper.cpp @@ -61,6 +61,7 @@ sai_dtel_api_t* sai_dtel_api; sai_samplepacket_api_t* sai_samplepacket_api; sai_debug_counter_api_t* sai_debug_counter_api; sai_nat_api_t* sai_nat_api; +sai_srv6_api_t** sai_srv6_api; extern sai_object_id_t gSwitchId; extern bool gSairedisRecord; @@ -179,6 +180,7 @@ void initSaiApi() sai_api_query(SAI_API_SAMPLEPACKET, (void **)&sai_samplepacket_api); sai_api_query(SAI_API_DEBUG_COUNTER, (void **)&sai_debug_counter_api); sai_api_query(SAI_API_NAT, (void **)&sai_nat_api); + sai_api_query(SAI_API_SRV6, (void **)&sai_srv6_api); sai_log_set(SAI_API_SWITCH, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_BRIDGE, SAI_LOG_LEVEL_NOTICE); @@ -207,6 +209,7 @@ void initSaiApi() sai_log_set(SAI_API_SAMPLEPACKET, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_DEBUG_COUNTER, SAI_LOG_LEVEL_NOTICE); sai_log_set((sai_api_t)SAI_API_NAT, SAI_LOG_LEVEL_NOTICE); + sai_log_set(SAI_API_SRV6, SAI_LOG_LEVEL_NOTICE); } void initSaiRedis(const string &record_location) diff --git a/orchagent/srv6orch.cpp b/orchagent/srv6orch.cpp new file mode 100644 index 0000000000..5081e06b6f --- /dev/null +++ b/orchagent/srv6orch.cpp @@ -0,0 +1,655 @@ +#include +#include + +#include "routeorch.h" +#include "logger.h" +#include "srv6orch.h" +#include "sai_serialize.h" +#include "crmorch.h" + +using namespace std; +using namespace swss; + +extern sai_object_id_t gSwitchId; +extern sai_object_id_t gVirtualRouterId; +extern sai_object_id_t gUnderlayIfId; +extern sai_srv6_api_t* sai_srv6_api; +extern sai_tunnel_api_t* sai_tunnel_api; +extern sai_next_hop_api_t* sai_next_hop_api; + +extern RouteOrch *gRouteOrch; +extern CrmOrch *gCrmOrch; + +void Srv6Orch::srv6TunnelUpdateNexthops(const string srv6_source, const NextHopKey nhkey, bool insert) +{ + if (insert) + { + srv6_tunnel_table_[srv6_source].nexthops.insert(nhkey); + } + else + { + srv6_tunnel_table_[srv6_source].nexthops.erase(nhkey); + } +} + +size_t Srv6Orch::srv6TunnelNexthopSize(const string srv6_source) +{ + return srv6_tunnel_table_[srv6_source].nexthops.size(); +} + +bool Srv6Orch::createSrv6Tunnel(const string srv6_source) +{ + SWSS_LOG_ENTER(); + vector tunnel_attrs; + sai_attribute_t attr; + sai_status_t status; + sai_object_id_t tunnel_id; + + if (srv6_tunnel_table_.find(srv6_source) != srv6_tunnel_table_.end()) + { + SWSS_LOG_INFO("Tunnel exists for the source %s", srv6_source.c_str()); + return true; + } + + SWSS_LOG_INFO("Create tunnel for the source %s", srv6_source.c_str()); + attr.id = SAI_TUNNEL_ATTR_TYPE; + attr.value.s32 = SAI_TUNNEL_TYPE_SRV6; + tunnel_attrs.push_back(attr); + attr.id = SAI_TUNNEL_ATTR_UNDERLAY_INTERFACE; + attr.value.oid = gUnderlayIfId; + tunnel_attrs.push_back(attr); + + IpAddress src_ip(srv6_source); + sai_ip_address_t ipaddr; + ipaddr.addr_family = SAI_IP_ADDR_FAMILY_IPV6; + memcpy(ipaddr.addr.ip6, src_ip.getV6Addr(), sizeof(ipaddr.addr.ip6)); + attr.id = SAI_TUNNEL_ATTR_ENCAP_SRC_IP; + attr.value.ipaddr = ipaddr; + tunnel_attrs.push_back(attr); + + status = sai_tunnel_api->create_tunnel(&tunnel_id, gSwitchId, (uint32_t)tunnel_attrs.size(), tunnel_attrs.data()); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create tunnel for %s", srv6_source.c_str()); + return false; + } + srv6_tunnel_table_[srv6_source].tunnel_object_id = tunnel_id; + return true; +} + +bool Srv6Orch::srv6NexthopExists(const NextHopKey &nhKey) +{ + SWSS_LOG_ENTER(); + if (srv6_nexthop_table_.find(nhKey) != srv6_nexthop_table_.end()) + { + return true; + } + else + { + return false; + } +} + +bool Srv6Orch::removeSrv6Nexthops(const NextHopGroupKey &nhg) +{ + SWSS_LOG_ENTER(); + + for (auto &sr_nh : nhg.getNextHops()) + { + string srv6_source, segname; + sai_status_t status = SAI_STATUS_SUCCESS; + srv6_source = sr_nh.srv6_source; + segname = sr_nh.srv6_segment; + + SWSS_LOG_NOTICE("SRV6 Nexthop %s refcount %d", sr_nh.to_string(false,true).c_str(), m_neighOrch->getNextHopRefCount(sr_nh)); + if (m_neighOrch->getNextHopRefCount(sr_nh) == 0) + { + status = sai_next_hop_api->remove_next_hop(srv6_nexthop_table_[sr_nh]); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove SRV6 nexthop %s", sr_nh.to_string(false,true).c_str()); + return false; + } + + /* Update nexthop in SID table after deleting the nexthop */ + SWSS_LOG_INFO("Seg %s nexthop refcount %zu", + segname.c_str(), + sid_table_[segname].nexthops.size()); + if (sid_table_[segname].nexthops.find(sr_nh) != sid_table_[segname].nexthops.end()) + { + sid_table_[segname].nexthops.erase(sr_nh); + } + m_neighOrch->updateSrv6Nexthop(sr_nh, 0); + srv6_nexthop_table_.erase(sr_nh); + + /* Delete NH from the tunnel map */ + SWSS_LOG_INFO("Delete NH %s from tunnel map", + sr_nh.to_string(false, true).c_str()); + srv6TunnelUpdateNexthops(srv6_source, sr_nh, false); + } + + size_t tunnel_nhs = srv6TunnelNexthopSize(srv6_source); + if (tunnel_nhs == 0) + { + status = sai_tunnel_api->remove_tunnel(srv6_tunnel_table_[srv6_source].tunnel_object_id); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove SRV6 tunnel object for source %s", srv6_source.c_str()); + return false; + } + srv6_tunnel_table_.erase(srv6_source); + } + else + { + SWSS_LOG_INFO("Nexthops referencing this tunnel object %s: %zu", srv6_source.c_str(),tunnel_nhs); + } + } + return true; +} + +bool Srv6Orch::createSrv6Nexthop(const NextHopKey &nh) +{ + SWSS_LOG_ENTER(); + string srv6_segment = nh.srv6_segment; + string srv6_source = nh.srv6_source; + + if (srv6NexthopExists(nh)) + { + SWSS_LOG_INFO("SRV6 nexthop already created for %s", nh.to_string(false,true).c_str()); + return true; + } + sai_object_id_t srv6_object_id = sid_table_[srv6_segment].sid_object_id; + sai_object_id_t srv6_tunnel_id = srv6_tunnel_table_[srv6_source].tunnel_object_id; + + if (srv6_object_id == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_ERROR("segment object doesn't exist for segment %s", srv6_segment.c_str()); + return false; + } + + if (srv6_tunnel_id == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_ERROR("tunnel object doesn't exist for source %s", srv6_source.c_str()); + return false; + } + SWSS_LOG_INFO("Create srv6 nh for tunnel src %s with seg %s", srv6_source.c_str(), srv6_segment.c_str()); + vector nh_attrs; + sai_object_id_t nexthop_id; + sai_attribute_t attr; + sai_status_t status; + + attr.id = SAI_NEXT_HOP_ATTR_TYPE; + attr.value.s32 = SAI_NEXT_HOP_TYPE_SRV6_SIDLIST; + nh_attrs.push_back(attr); + + attr.id = SAI_NEXT_HOP_ATTR_SRV6_SIDLIST_ID; + attr.value.oid = srv6_object_id; + nh_attrs.push_back(attr); + + attr.id = SAI_NEXT_HOP_ATTR_TUNNEL_ID; + attr.value.oid = srv6_tunnel_id; + nh_attrs.push_back(attr); + + status = sai_next_hop_api->create_next_hop(&nexthop_id, gSwitchId, + (uint32_t)nh_attrs.size(), + nh_attrs.data()); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create srv6 nexthop for %s", nh.to_string(false,true).c_str()); + return false; + } + m_neighOrch->updateSrv6Nexthop(nh, nexthop_id); + srv6_nexthop_table_[nh] = nexthop_id; + sid_table_[srv6_segment].nexthops.insert(nh); + srv6TunnelUpdateNexthops(srv6_source, nh, true); + return true; +} + +bool Srv6Orch::srv6Nexthops(const NextHopGroupKey &nhgKey, sai_object_id_t &nexthop_id) +{ + SWSS_LOG_ENTER(); + set nexthops = nhgKey.getNextHops(); + string srv6_source; + string srv6_segment; + + for (auto nh : nexthops) + { + srv6_source = nh.srv6_source; + if (!createSrv6Tunnel(srv6_source)) + { + SWSS_LOG_ERROR("Failed to create tunnel for source %s", srv6_source.c_str()); + return false; + } + if (!createSrv6Nexthop(nh)) + { + SWSS_LOG_ERROR("Failed to create SRV6 nexthop %s", nh.to_string(false,true).c_str()); + return false; + } + } + + if (nhgKey.getSize() == 1) + { + NextHopKey nhkey(nhgKey.to_string(), false, true); + nexthop_id = srv6_nexthop_table_[nhkey]; + } + return true; +} + +bool Srv6Orch::createUpdateSidList(const string sid_name, const string sid_list) +{ + SWSS_LOG_ENTER(); + bool exists = (sid_table_.find(sid_name) != sid_table_.end()); + sai_segment_list_t segment_list; + vectorsid_ips = tokenize(sid_list, SID_LIST_DELIMITER); + sai_object_id_t segment_oid; + segment_list.count = (uint32_t)sid_ips.size(); + if (segment_list.count == 0) + { + SWSS_LOG_ERROR("segment list count is zero, skip"); + return true; + } + SWSS_LOG_INFO("Segment count %d", segment_list.count); + segment_list.list = new sai_ip6_t[segment_list.count]; + uint32_t index = 0; + + for (string ip_str : sid_ips) + { + IpPrefix ip(ip_str); + SWSS_LOG_INFO("Segment %s, count %d", ip.to_string().c_str(), segment_list.count); + memcpy(segment_list.list[index++], ip.getIp().getV6Addr(), 16); + } + sai_attribute_t attr; + sai_status_t status; + if (!exists) + { + /* Create sidlist object with list of ipv6 prefixes */ + SWSS_LOG_INFO("Create SID list"); + vector attributes; + attr.id = SAI_SRV6_SIDLIST_ATTR_SEGMENT_LIST; + attr.value.segmentlist.list = segment_list.list; + attr.value.segmentlist.count = segment_list.count; + attributes.push_back(attr); + + attr.id = SAI_SRV6_SIDLIST_ATTR_TYPE; + attr.value.s32 = SAI_SRV6_SIDLIST_TYPE_ENCAPS_RED; + attributes.push_back(attr); + status = sai_srv6_api->create_srv6_sidlist(&segment_oid, gSwitchId, (uint32_t) attributes.size(), attributes.data()); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create srv6 sidlist object, rv %d", status); + return false; + } + sid_table_[sid_name].sid_object_id = segment_oid; + } + else + { + SWSS_LOG_INFO("Set SID list"); + + /* Update sidlist object with new set of ipv6 addresses */ + attr.id = SAI_SRV6_SIDLIST_ATTR_SEGMENT_LIST; + attr.value.segmentlist.list = segment_list.list; + attr.value.segmentlist.count = segment_list.count; + segment_oid = (sid_table_.find(sid_name)->second).sid_object_id; + status = sai_srv6_api->set_srv6_sidlist_attribute(segment_oid, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to set srv6 sidlist object with new segments, rv %d", status); + return false; + } + } + delete segment_list.list; + return true; +} + +bool Srv6Orch::deleteSidList(const string sid_name) +{ + SWSS_LOG_ENTER(); + sai_status_t status = SAI_STATUS_SUCCESS; + if (sid_table_.find(sid_name) == sid_table_.end()) + { + SWSS_LOG_ERROR("segment name %s doesn't exist", sid_name.c_str()); + return false; + } + + if (sid_table_[sid_name].nexthops.size() > 1) + { + SWSS_LOG_NOTICE("segment object %s referenced by other nexthops: count %zu, not deleting", + sid_name.c_str(), sid_table_[sid_name].nexthops.size()); + return false; + } + SWSS_LOG_INFO("Remove sid list, segname %s", sid_name.c_str()); + status = sai_srv6_api->remove_srv6_sidlist(sid_table_[sid_name].sid_object_id); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to delete SRV6 sidlist object for %s", sid_name.c_str()); + return false; + } + sid_table_.erase(sid_name); + return true; +} + +void Srv6Orch::doTaskSidTable(const KeyOpFieldsValuesTuple & tuple) +{ + SWSS_LOG_ENTER(); + string sid_name = kfvKey(tuple); + string op = kfvOp(tuple); + string sid_list; + + for (auto i : kfvFieldsValues(tuple)) + { + if (fvField(i) == "path") + { + sid_list = fvValue(i); + } + } + if (op == SET_COMMAND) + { + if (!createUpdateSidList(sid_name, sid_list)) + { + SWSS_LOG_ERROR("Failed to process sid %s", sid_name.c_str()); + } + } + else if (op == DEL_COMMAND) + { + if (!deleteSidList(sid_name)) + { + SWSS_LOG_ERROR("Failed to delete sid %s", sid_name.c_str()); + } + } else { + SWSS_LOG_ERROR("Invalid command"); + } +} + +bool Srv6Orch::mySidExists(string my_sid_string) +{ + if (srv6_my_sid_table_.find(my_sid_string) != srv6_my_sid_table_.end()) + { + return true; + } + return false; +} + +bool Srv6Orch::sidEntryEndpointBehavior(string action, sai_my_sid_entry_endpoint_behavior_t &end_behavior, + sai_my_sid_entry_endpoint_behavior_flavor_t &end_flavor) +{ + if (action == "end") + { + end_behavior = SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_E; + end_flavor = SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_FLAVOR_PSP_AND_USD; + } + else if (action == "end.x") + { + end_behavior = SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_X; + end_flavor = SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_FLAVOR_PSP_AND_USD; + } + else if (action == "end.t") + { + end_behavior = SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_T; + end_flavor = SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_FLAVOR_PSP_AND_USD; + } + else if (action == "end.dx6") + { + end_behavior = SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_DX6; + } + else if (action == "end.dx4") + { + end_behavior = SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_DX4; + } + else if (action == "end.dt4") + { + end_behavior = SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_DT4; + } + else if (action == "end.dt6") + { + end_behavior = SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_DT6; + } + else if (action == "end.dt46") + { + end_behavior = SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_DT46; + } + else if (action == "end.b6.encaps") + { + end_behavior = SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_B6_ENCAPS; + } + else if (action == "end.b6.encaps.red") + { + end_behavior = SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_B6_ENCAPS_RED; + } + else if (action == "end.b6.insert") + { + end_behavior = SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_B6_INSERT; + } + else if (action == "end.b6.insert.red") + { + end_behavior = SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_B6_INSERT_RED; + } + else + { + SWSS_LOG_ERROR("Invalid endpoing behavior function"); + return false; + } + return true; +} + +bool Srv6Orch::mySidVrfRequired(const sai_my_sid_entry_endpoint_behavior_t end_behavior) +{ + if (end_behavior == SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_T || + end_behavior == SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_DT4 || + end_behavior == SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_DT6 || + end_behavior == SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_DT46) + { + return true; + } + return false; +} + +bool Srv6Orch::createUpdateMysidEntry(string my_sid_string, const string dt_vrf, const string end_action) +{ + SWSS_LOG_ENTER(); + vector attributes; + sai_attribute_t attr; + string key_string = my_sid_string; + sai_my_sid_entry_endpoint_behavior_t end_behavior; + sai_my_sid_entry_endpoint_behavior_flavor_t end_flavor = SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_FLAVOR_PSP_AND_USD; + + bool entry_exists = false; + if (mySidExists(key_string)) + { + entry_exists = true; + } + + sai_my_sid_entry_t my_sid_entry; + if (!entry_exists) + { + vectorkeys = tokenize(my_sid_string, MY_SID_KEY_DELIMITER); + + my_sid_entry.vr_id = gVirtualRouterId; + my_sid_entry.switch_id = gSwitchId; + my_sid_entry.locator_block_len = (uint8_t)stoi(keys[0]); + my_sid_entry.locator_node_len = (uint8_t)stoi(keys[1]); + my_sid_entry.function_len = (uint8_t)stoi(keys[2]); + my_sid_entry.args_len = (uint8_t)stoi(keys[3]); + size_t keylen = keys[0].length()+keys[1].length()+keys[2].length()+keys[3].length() + 4; + my_sid_string.erase(0, keylen); + string my_sid = my_sid_string; + SWSS_LOG_INFO("MY SID STRING %s", my_sid.c_str()); + IpAddress address(my_sid); + memcpy(my_sid_entry.sid, address.getV6Addr(), sizeof(my_sid_entry.sid)); + } + else + { + my_sid_entry = srv6_my_sid_table_[key_string].entry; + } + + SWSS_LOG_INFO("MySid: sid %s, action %s, vrf %s, block %d, node %d, func %d, arg %d dt_vrf %s", + my_sid_string.c_str(), end_action.c_str(), dt_vrf.c_str(),my_sid_entry.locator_block_len, my_sid_entry.locator_node_len, + my_sid_entry.function_len, my_sid_entry.args_len, dt_vrf.c_str()); + + if (sidEntryEndpointBehavior(end_action, end_behavior, end_flavor) != true) + { + SWSS_LOG_ERROR("Invalid my_sid action %s", end_action.c_str()); + return false; + } + sai_attribute_t vrf_attr; + bool vrf_update = false; + if (mySidVrfRequired(end_behavior)) + { + sai_object_id_t dt_vrf_id; + SWSS_LOG_INFO("DT VRF name %s", dt_vrf.c_str()); + if (m_vrfOrch->isVRFexists(dt_vrf)) + { + SWSS_LOG_INFO("VRF %s exists in DB", dt_vrf.c_str()); + dt_vrf_id = m_vrfOrch->getVRFid(dt_vrf); + if(dt_vrf_id == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_ERROR("VRF object not created for DT VRF %s", dt_vrf.c_str()); + return false; + } + } + else + { + SWSS_LOG_ERROR("VRF %s doesn't exist in DB", dt_vrf.c_str()); + return false; + } + vrf_attr.id = SAI_MY_SID_ENTRY_ATTR_VRF; + vrf_attr.value.oid = dt_vrf_id; + attributes.push_back(vrf_attr); + vrf_update = true; + } + attr.id = SAI_MY_SID_ENTRY_ATTR_ENDPOINT_BEHAVIOR; + attr.value.s32 = end_behavior; + attributes.push_back(attr); + + attr.id = SAI_MY_SID_ENTRY_ATTR_ENDPOINT_BEHAVIOR_FLAVOR; + attr.value.s32 = end_flavor; + attributes.push_back(attr); + + sai_status_t status = SAI_STATUS_SUCCESS; + if (!entry_exists) + { + status = sai_srv6_api->create_my_sid_entry(&my_sid_entry, (uint32_t) attributes.size(), attributes.data()); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create my_sid entry %s, rv %d", key_string.c_str(), status); + return false; + } + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_SRV6_MY_SID_ENTRY); + } + else + { + if (vrf_update) + { + status = sai_srv6_api->set_my_sid_entry_attribute(&my_sid_entry, &vrf_attr); + if(status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to update VRF to my_sid_entry %s, rv %d", key_string.c_str(), status); + return false; + } + } + } + SWSS_LOG_INFO("Store keystring %s in cache", key_string.c_str()); + if(vrf_update) + { + m_vrfOrch->increaseVrfRefCount(dt_vrf); + srv6_my_sid_table_[key_string].endVrfString = dt_vrf; + } + srv6_my_sid_table_[key_string].endBehavior = end_behavior; + srv6_my_sid_table_[key_string].entry = my_sid_entry; + + return true; +} + +bool Srv6Orch::deleteMysidEntry(const string my_sid_string) +{ + sai_status_t status = SAI_STATUS_SUCCESS; + if (!mySidExists(my_sid_string)) + { + SWSS_LOG_ERROR("My_sid_entry doesn't exist for %s", my_sid_string.c_str()); + return false; + } + sai_my_sid_entry_t my_sid_entry = srv6_my_sid_table_[my_sid_string].entry; + + SWSS_LOG_NOTICE("MySid Delete: sid %s", my_sid_string.c_str()); + status = sai_srv6_api->remove_my_sid_entry(&my_sid_entry); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to delete my_sid entry rv %d", status); + return false; + } + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_SRV6_MY_SID_ENTRY); + + /* Decrease VRF refcount */ + if (mySidVrfRequired(srv6_my_sid_table_[my_sid_string].endBehavior)) + { + m_vrfOrch->decreaseVrfRefCount(srv6_my_sid_table_[my_sid_string].endVrfString); + } + srv6_my_sid_table_.erase(my_sid_string); + return true; +} + +void Srv6Orch::doTaskMySidTable(const KeyOpFieldsValuesTuple & tuple) +{ + SWSS_LOG_ENTER(); + string op = kfvOp(tuple); + string end_action, dt_vrf; + + /* Key for mySid : block_len:node_len:function_len:args_len:sid-ip */ + string keyString = kfvKey(tuple); + + for (auto i : kfvFieldsValues(tuple)) + { + if (fvField(i) == "action") + { + end_action = fvValue(i); + } + if(fvField(i) == "vrf") + { + dt_vrf = fvValue(i); + } + } + if (op == SET_COMMAND) + { + if(!createUpdateMysidEntry(keyString, dt_vrf, end_action)) + { + SWSS_LOG_ERROR("Failed to create/update my_sid entry for sid %s", keyString.c_str()); + return; + } + } + else if(op == DEL_COMMAND) + { + if(!deleteMysidEntry(keyString)) + { + SWSS_LOG_ERROR("Failed to delete my_sid entry for sid %s", keyString.c_str()); + return; + } + } + else + { + SWSS_LOG_ERROR("Invalid command"); + } +} + +void Srv6Orch::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + const string &table_name = consumer.getTableName(); + auto it = consumer.m_toSync.begin(); + while(it != consumer.m_toSync.end()) + { + auto t = it->second; + SWSS_LOG_INFO("table name : %s",table_name.c_str()); + if (table_name == APP_SRV6_SID_LIST_TABLE_NAME) + { + doTaskSidTable(t); + } + else if (table_name == APP_SRV6_MY_SID_TABLE_NAME) + { + doTaskMySidTable(t); + } + else + { + SWSS_LOG_ERROR("Unknown table : %s",table_name.c_str()); + } + consumer.m_toSync.erase(it++); + } +} diff --git a/orchagent/srv6orch.h b/orchagent/srv6orch.h new file mode 100644 index 0000000000..989737a998 --- /dev/null +++ b/orchagent/srv6orch.h @@ -0,0 +1,105 @@ +#ifndef SWSS_SRV6ORCH_H +#define SWSS_SRV6ORCH_H + +#include +#include +#include +#include + +#include "dbconnector.h" +#include "orch.h" +#include "observer.h" +#include "switchorch.h" +#include "portsorch.h" +#include "vrforch.h" +#include "redisapi.h" +#include "intfsorch.h" +#include "nexthopgroupkey.h" +#include "nexthopkey.h" +#include "neighorch.h" +#include "producerstatetable.h" + +#include "ipaddress.h" +#include "ipaddresses.h" +#include "ipprefix.h" + +using namespace std; +using namespace swss; + +struct SidTableEntry +{ + sai_object_id_t sid_object_id; // SRV6 SID list object id + set nexthops; // number of nexthops referencing the object +}; + +struct SidTunnelEntry +{ + sai_object_id_t tunnel_object_id; // SRV6 tunnel object id + set nexthops; // SRV6 Nexthops using the tunnel object. +}; + +struct MySidEntry +{ + sai_my_sid_entry_t entry; + sai_my_sid_entry_endpoint_behavior_t endBehavior; + string endVrfString; // Used for END.T, END.DT4, END.DT6 and END.DT46, +}; + +typedef unordered_map SidTable; +typedef unordered_map Srv6TunnelTable; +typedef map Srv6NextHopTable; +typedef unordered_map Srv6MySidTable; + +#define SID_LIST_DELIMITER ',' +#define MY_SID_KEY_DELIMITER ':' +class Srv6Orch : public Orch +{ + public: + Srv6Orch(DBConnector *applDb, vector &tableNames, SwitchOrch *switchOrch, VRFOrch *vrfOrch, NeighOrch *neighOrch): + Orch(applDb, tableNames), + m_vrfOrch(vrfOrch), + m_switchOrch(switchOrch), + m_neighOrch(neighOrch), + m_sidTable(applDb, APP_SRV6_SID_LIST_TABLE_NAME), + m_mysidTable(applDb, APP_SRV6_MY_SID_TABLE_NAME) + { + } + ~Srv6Orch() + { + + } + bool srv6Nexthops(const NextHopGroupKey &nextHops, sai_object_id_t &next_hop_id); + bool removeSrv6Nexthops(const NextHopGroupKey &nhg); + void update(SubjectType, void *); + + private: + void doTask(Consumer &consumer); + void doTaskSidTable(const KeyOpFieldsValuesTuple &tuple); + void doTaskMySidTable(const KeyOpFieldsValuesTuple &tuple); + bool createUpdateSidList(const string seg_name, const string ips); + bool deleteSidList(const string seg_name); + bool createSrv6Tunnel(const string srv6_source); + bool createSrv6Nexthop(const NextHopKey &nh); + bool srv6NexthopExists(const NextHopKey &nh); + bool createUpdateMysidEntry(string my_sid_string, const string vrf, const string end_action); + bool deleteMysidEntry(const string my_sid_string); + bool sidEntryEndpointBehavior(const string action, sai_my_sid_entry_endpoint_behavior_t &end_behavior, + sai_my_sid_entry_endpoint_behavior_flavor_t &end_flavor); + bool mySidExists(const string mysid_string); + bool mySidVrfRequired(const sai_my_sid_entry_endpoint_behavior_t end_behavior); + void srv6TunnelUpdateNexthops(const string srv6_source, const NextHopKey nhkey, bool insert); + size_t srv6TunnelNexthopSize(const string srv6_source); + + + ProducerStateTable m_sidTable; + ProducerStateTable m_mysidTable; + SidTable sid_table_; + Srv6TunnelTable srv6_tunnel_table_; + Srv6NextHopTable srv6_nexthop_table_; + Srv6MySidTable srv6_my_sid_table_; + VRFOrch *m_vrfOrch; + SwitchOrch *m_switchOrch; + NeighOrch *m_neighOrch; +}; + +#endif // SWSS_SRV6ORCH_H diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 143a3faab0..88848d97bd 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -66,7 +66,8 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/sfloworch.cpp \ $(top_srcdir)/orchagent/debugcounterorch.cpp \ $(top_srcdir)/orchagent/natorch.cpp \ - $(top_srcdir)/orchagent/muxorch.cpp + $(top_srcdir)/orchagent/muxorch.cpp \ + $(top_srcdir)/orchagent/srv6orch.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/mock_tests/aclorch_ut.cpp b/tests/mock_tests/aclorch_ut.cpp index aa2a515040..564e6b9132 100644 --- a/tests/mock_tests/aclorch_ut.cpp +++ b/tests/mock_tests/aclorch_ut.cpp @@ -9,6 +9,7 @@ extern RouteOrch *gRouteOrch; extern IntfsOrch *gIntfsOrch; extern NeighOrch *gNeighOrch; extern FgNhgOrch *gFgNhgOrch; +extern Srv6Orch *gSrv6Orch; extern FdbOrch *gFdbOrch; extern MirrorOrch *gMirrorOrch; @@ -348,8 +349,15 @@ namespace aclorch_test }; gFgNhgOrch = new FgNhgOrch(m_config_db.get(), m_app_db.get(), m_state_db.get(), fgnhg_tables, gNeighOrch, gIntfsOrch, gVrfOrch); + ASSERT_EQ(gSrv6Orch, nullptr); + vector srv6_tables = { + APP_SRV6_SID_LIST_TABLE_NAME, + APP_SRV6_MY_SID_TABLE_NAME + }; + gSrv6Orch = new Srv6Orch(m_app_db.get(), srv6_tables, gSwitchOrch, gVrfOrch, gNeighOrch); + ASSERT_EQ(gRouteOrch, nullptr); - gRouteOrch = new RouteOrch(m_app_db.get(), APP_ROUTE_TABLE_NAME, gSwitchOrch, gNeighOrch, gIntfsOrch, gVrfOrch, gFgNhgOrch); + gRouteOrch = new RouteOrch(m_app_db.get(), APP_ROUTE_TABLE_NAME, gSwitchOrch, gNeighOrch, gIntfsOrch, gVrfOrch, gFgNhgOrch, gSrv6Orch); PolicerOrch *policer_orch = new PolicerOrch(m_config_db.get(), "POLICER"); @@ -391,6 +399,8 @@ namespace aclorch_test gPortsOrch = nullptr; delete gFgNhgOrch; gFgNhgOrch = nullptr; + delete gSrv6Orch; + gSrv6Orch = nullptr; auto status = sai_switch_api->remove_switch(gSwitchId); ASSERT_EQ(status, SAI_STATUS_SUCCESS); diff --git a/tests/test_srv6.py b/tests/test_srv6.py new file mode 100644 index 0000000000..0d134acc2b --- /dev/null +++ b/tests/test_srv6.py @@ -0,0 +1,260 @@ +import os +import re +import time +import json +import pytest + +from swsscommon import swsscommon +from dvslib.dvs_common import wait_for_result + +def get_exist_entries(db, table): + tbl = swsscommon.Table(db, table) + return set(tbl.getKeys()) + +def get_created_entry(db, table, existed_entries): + tbl = swsscommon.Table(db, table) + entries = set(tbl.getKeys()) + new_entries = list(entries - existed_entries) + assert len(new_entries) == 1, "Wrong number of created entries." + return new_entries[0] + +class TestSrv6Mysid(object): + def setup_db(self, dvs): + self.pdb = dvs.get_app_db() + self.adb = dvs.get_asic_db() + self.cdb = dvs.get_config_db() + + def create_vrf(self, vrf_name): + table = "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER" + existed_entries = get_exist_entries(self.adb.db_connection, table) + + self.cdb.create_entry("VRF", vrf_name, {"empty": "empty"}) + + self.adb.wait_for_n_keys(table, len(existed_entries) + 1) + return get_created_entry(self.adb.db_connection, table, existed_entries) + + def remove_vrf(self, vrf_name): + self.cdb.delete_entry("VRF", vrf_name) + + def create_mysid(self, mysid, fvs): + table = "ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY" + existed_entries = get_exist_entries(self.adb.db_connection, table) + + tbl = swsscommon.ProducerStateTable(self.pdb.db_connection, "SRV6_MY_SID_TABLE") + tbl.set(mysid, fvs) + + self.adb.wait_for_n_keys(table, len(existed_entries) + 1) + return get_created_entry(self.adb.db_connection, table, existed_entries) + + def remove_mysid(self, mysid): + tbl = swsscommon.ProducerStateTable(self.pdb.db_connection, "SRV6_MY_SID_TABLE") + tbl._del(mysid) + + def test_mysid(self, dvs, testlog): + self.setup_db(dvs) + + # create MySID entries + mysid1='16:8:8:8:baba:2001:10::' + mysid2='16:8:8:8:baba:2001:20::' + + # create MySID END + fvs = swsscommon.FieldValuePairs([('action', 'end')]) + key = self.create_mysid(mysid1, fvs) + + # check ASIC MySID database + mysid = json.loads(key) + assert mysid["sid"] == "baba:2001:10::" + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY") + (status, fvs) = tbl.get(key) + assert status == True + for fv in fvs: + if fv[0] == "SAI_MY_SID_ENTRY_ATTR_ENDPOINT_BEHAVIOR": + assert fv[1] == "SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_E" + + # create vrf + vrf_id = self.create_vrf("VrfDt46") + + # create MySID END.DT46 + fvs = swsscommon.FieldValuePairs([('action', 'end.dt46'), ('vrf', 'VrfDt46')]) + key = self.create_mysid(mysid2, fvs) + + # check ASIC MySID database + mysid = json.loads(key) + assert mysid["sid"] == "baba:2001:20::" + tbl = swsscommon.Table(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_MY_SID_ENTRY") + (status, fvs) = tbl.get(key) + assert status == True + for fv in fvs: + if fv[0] == "SAI_MY_SID_ENTRY_ATTR_VRF": + assert fv[1] == vrf_id + elif fv[0] == "SAI_MY_SID_ENTRY_ATTR_ENDPOINT_BEHAVIOR": + assert fv[1] == "SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_DT46" + + # delete MySID + self.remove_mysid(mysid1) + self.remove_mysid(mysid2) + + # remove vrf + self.remove_vrf("VrfDt46") + + +class TestSrv6(object): + def setup_db(self, dvs): + self.pdb = dvs.get_app_db() + self.adb = dvs.get_asic_db() + self.cdb = dvs.get_config_db() + + def create_sidlist(self, segname, ips): + table = "ASIC_STATE:SAI_OBJECT_TYPE_SRV6_SIDLIST" + existed_entries = get_exist_entries(self.adb.db_connection, table) + + fvs=swsscommon.FieldValuePairs([('path', ips)]) + segtbl = swsscommon.ProducerStateTable(self.pdb.db_connection, "SRV6_SID_LIST_TABLE") + segtbl.set(segname, fvs) + + self.adb.wait_for_n_keys(table, len(existed_entries) + 1) + return get_created_entry(self.adb.db_connection, table, existed_entries) + + def remove_sidlist(self, segname): + segtbl = swsscommon.ProducerStateTable(self.pdb.db_connection, "SRV6_SID_LIST_TABLE") + segtbl._del(segname) + + def create_srv6_route(self, routeip,segname,segsrc): + table = "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY" + existed_entries = get_exist_entries(self.adb.db_connection, table) + + fvs=swsscommon.FieldValuePairs([('seg_src',segsrc),('segment',segname)]) + routetbl = swsscommon.ProducerStateTable(self.pdb.db_connection, "ROUTE_TABLE") + routetbl.set(routeip,fvs) + + self.adb.wait_for_n_keys(table, len(existed_entries) + 1) + return get_created_entry(self.adb.db_connection, table, existed_entries) + + def remove_srv6_route(self, routeip): + routetbl = swsscommon.ProducerStateTable(self.pdb.db_connection, "ROUTE_TABLE") + routetbl._del(routeip) + + def check_deleted_route_entries(self, destinations): + def _access_function(): + route_entries = self.adb.get_keys("ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + route_destinations = [json.loads(route_entry)["dest"] for route_entry in route_entries] + return (all(destination not in route_destinations for destination in destinations), None) + + wait_for_result(_access_function) + + def add_neighbor(self, interface, ip, mac): + fvs=swsscommon.FieldValuePairs([("neigh", mac)]) + neightbl = swsscommon.Table(self.cdb.db_connection, "NEIGH") + neightbl.set(interface + "|" +ip, fvs) + time.sleep(1) + + def remove_neighbor(self, interface,ip): + neightbl = swsscommon.Table(self.cdb.db_connection, "NEIGH") + neightbl._del(interface + "|" + ip) + time.sleep(1) + + def test_srv6(self, dvs, testlog): + self.setup_db(dvs) + dvs.setup_db() + + # 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") + + + # bring up interfacee + dvs.set_interface_status("Ethernet104", "up") + dvs.set_interface_status("Ethernet112", "up") + dvs.set_interface_status("Ethernet120", "up") + + # add neighbors + self.add_neighbor("Ethernet104", "baba:2001:10::", "00:00:00:01:02:01") + self.add_neighbor("Ethernet112", "baba:2002:10::", "00:00:00:01:02:02") + self.add_neighbor("Ethernet120", "baba:2003:10::", "00:00:00:01:02:03") + + # create seg lists + sidlist_id = self.create_sidlist('seg1', 'baba:2001:10::,baba:2001:20::') + + # 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] == "2:baba:2001:10::,baba:2001:20::" + elif fv[0] == "SAI_SRV6_SIDLIST_ATTR_TYPE": + assert fv[1] == "SAI_SRV6_SIDLIST_TYPE_ENCAPS_RED" + + + # create v4 route with single sidlists + route_key = self.create_srv6_route('20.20.20.20/32','seg1','1001:2000::1') + 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) + + # 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 + + # 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) + assert status == True + for fv in fvs: + 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 + + # 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] == "1001:2000::1" + + + # create 2nd seg lists + self.create_sidlist('seg2', 'baba:2002:10::,baba:2002:20::') + # create 3rd seg lists + self.create_sidlist('seg3', 'baba:2003:10::,baba:2003:20::') + + # create 2nd v4 route with single sidlists + self.create_srv6_route('20.20.20.21/32','seg2','1001:2000::1') + # create 3rd v4 route with single sidlists + self.create_srv6_route('20.20.20.22/32','seg3','1001:2000::1') + + # remove routes + self.remove_srv6_route('20.20.20.20/32') + self.check_deleted_route_entries('20.20.20.20/32') + self.remove_srv6_route('20.20.20.21/32') + self.check_deleted_route_entries('20.20.20.21/32') + self.remove_srv6_route('20.20.20.22/32') + self.check_deleted_route_entries('20.20.20.22/32') + + # remove sid lists + self.remove_sidlist('seg1') + self.remove_sidlist('seg2') + self.remove_sidlist('seg3') + + # remove neighbors + self.remove_neighbor("Ethernet104", "baba:2001:10::") + self.remove_neighbor("Ethernet112", "baba:2002:10::") + self.remove_neighbor("Ethernet120", "baba:2003:10::") + + # check if asic db entries are all restored + assert tunnel_entries == get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_TUNNEL") + assert nexthop_entries == get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") + assert route_entries == get_exist_entries(self.adb.db_connection, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + +# 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