From 37c197d00d42d205243852827de63244c053e7df 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 | 3 +- orchagent/crmorch.cpp | 70 ++++ orchagent/crmorch.h | 2 + orchagent/neighorch.cpp | 19 + orchagent/neighorch.h | 1 + orchagent/nexthopgroupkey.h | 39 +- orchagent/nexthopkey.h | 62 ++- orchagent/orchdaemon.cpp | 12 +- orchagent/orchdaemon.h | 1 + orchagent/routeorch.cpp | 136 ++++++- 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, 1357 insertions(+), 47 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 47fc9cd32c0e..de89c02f9eea 100644 --- a/doc/swss-schema.md +++ b/doc/swss-schema.md @@ -167,6 +167,8 @@ and reflects the LAG ports into the redis under: `LAG_TABLE::port` blackhole = BIT ; Set to 1 if this route is a blackhole (or null0) weight = weight_list ; List of weights. nexthop_group = string ; index within the NEXTHOP_GROUP_TABLE, used instead of nexthop and intf fields + segment = string ; SRV6 segment name + seg_src = string ; ipv6 address for SRV6 tunnel source --------------------------------------------- @@ -203,6 +205,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 bc58bbbbf912..cf789b027ffa 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -82,7 +82,8 @@ orchagent_SOURCES = \ muxorch.cpp \ macsecorch.cpp \ lagid.cpp \ - bfdorch.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 eeb9add77832..8c651fffc7aa 100644 --- a/orchagent/crmorch.cpp +++ b/orchagent/crmorch.cpp @@ -43,6 +43,8 @@ const map crmResTypeNameMap = { CrmResourceType::CRM_DNAT_ENTRY, "DNAT_ENTRY" }, { CrmResourceType::CRM_MPLS_INSEG, "MPLS_INSEG" }, { CrmResourceType::CRM_MPLS_NEXTHOP, "MPLS_NEXTHOP" }, + { CrmResourceType::CRM_SRV6_MY_SID_ENTRY, "SRV6_MY_SID_ENTRY" }, + { CrmResourceType::CRM_SRV6_NEXTHOP, "SRV6_NEXTHOP" }, }; const map crmResSaiAvailAttrMap = @@ -65,6 +67,8 @@ const map crmResSaiAvailAttrMap = { CrmResourceType::CRM_DNAT_ENTRY, SAI_SWITCH_ATTR_AVAILABLE_DNAT_ENTRY }, { CrmResourceType::CRM_MPLS_INSEG, SAI_OBJECT_TYPE_INSEG_ENTRY }, { CrmResourceType::CRM_MPLS_NEXTHOP, SAI_SWITCH_ATTR_AVAILABLE_IPV4_NEXTHOP_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 = @@ -87,6 +91,8 @@ const map crmThreshTypeResMap = { "dnat_entry_threshold_type", CrmResourceType::CRM_DNAT_ENTRY }, { "mpls_inseg_threshold_type", CrmResourceType::CRM_MPLS_INSEG }, { "mpls_nexthop_threshold_type", CrmResourceType::CRM_MPLS_NEXTHOP }, + { "srv6_my_sid_entry_threshold_type", CrmResourceType::CRM_SRV6_MY_SID_ENTRY }, + { "srv6_nexthop_threshold_type", CrmResourceType::CRM_SRV6_NEXTHOP }, }; const map crmThreshLowResMap = @@ -109,6 +115,8 @@ const map crmThreshLowResMap = {"dnat_entry_low_threshold", CrmResourceType::CRM_DNAT_ENTRY }, {"mpls_inseg_low_threshold", CrmResourceType::CRM_MPLS_INSEG }, {"mpls_nexthop_low_threshold", CrmResourceType::CRM_MPLS_NEXTHOP }, + {"srv6_my_sid_entry_low_threshold", CrmResourceType::CRM_SRV6_MY_SID_ENTRY }, + {"srv6_nexthop_low_threshold", CrmResourceType::CRM_SRV6_NEXTHOP }, }; const map crmThreshHighResMap = @@ -131,6 +139,8 @@ const map crmThreshHighResMap = {"dnat_entry_high_threshold", CrmResourceType::CRM_DNAT_ENTRY }, {"mpls_inseg_high_threshold", CrmResourceType::CRM_MPLS_INSEG }, {"mpls_nexthop_high_threshold", CrmResourceType::CRM_MPLS_NEXTHOP }, + {"srv6_my_sid_entry_high_threshold", CrmResourceType::CRM_SRV6_MY_SID_ENTRY }, + {"srv6_nexthop_high_threshold", CrmResourceType::CRM_SRV6_NEXTHOP }, }; const map crmThreshTypeMap = @@ -160,6 +170,8 @@ const map crmAvailCntsTableMap = { "crm_stats_dnat_entry_available", CrmResourceType::CRM_DNAT_ENTRY }, { "crm_stats_mpls_inseg_available", CrmResourceType::CRM_MPLS_INSEG }, { "crm_stats_mpls_nexthop_available", CrmResourceType::CRM_MPLS_NEXTHOP }, + { "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 = @@ -182,6 +194,8 @@ const map crmUsedCntsTableMap = { "crm_stats_dnat_entry_used", CrmResourceType::CRM_DNAT_ENTRY }, { "crm_stats_mpls_inseg_used", CrmResourceType::CRM_MPLS_INSEG }, { "crm_stats_mpls_nexthop_used", CrmResourceType::CRM_MPLS_NEXTHOP }, + { "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): @@ -614,6 +628,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 resource type %u. Unknown resource type.\n", static_cast(res.first)); return; diff --git a/orchagent/crmorch.h b/orchagent/crmorch.h index d8b5522d3a87..02184763981b 100644 --- a/orchagent/crmorch.h +++ b/orchagent/crmorch.h @@ -30,6 +30,8 @@ enum class CrmResourceType CRM_DNAT_ENTRY, CRM_MPLS_INSEG, CRM_MPLS_NEXTHOP, + CRM_SRV6_MY_SID_ENTRY, + CRM_SRV6_NEXTHOP, }; enum class CrmThresholdType diff --git a/orchagent/neighorch.cpp b/orchagent/neighorch.cpp index 23d7a38b3b0b..c1eba4c0e341 100644 --- a/orchagent/neighorch.cpp +++ b/orchagent/neighorch.cpp @@ -1702,3 +1702,22 @@ bool NeighOrch::updateVoqNeighborEncapIndex(const NeighborEntry &neighborEntry, 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 7a9a1fa330e2..6587606168fe 100644 --- a/orchagent/neighorch.h +++ b/orchagent/neighorch.h @@ -79,6 +79,7 @@ class NeighOrch : public Orch, public Subject, public Observer bool delInbandNeighbor(string alias, IpAddress ip_address); 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 caba479012d2..7943c1d865e3 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,20 +21,36 @@ 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); + } } } NextHopGroupKey(const std::string &nexthops, const std::string &weights) { m_overlay_nexthops = false; + m_srv6_nexthops = false; std::vector nhv = tokenize(nexthops, NHG_DELIMITER); std::vector wtv = tokenize(weights, NHG_DELIMITER); bool set_weight = wtv.size() == nhv.size(); @@ -184,8 +201,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(); } @@ -199,6 +216,11 @@ class NextHopGroupKey return m_overlay_nexthops; } + inline bool is_srv6_nexthop() const + { + return m_srv6_nexthops; + } + void clear() { m_nexthops.clear(); @@ -207,6 +229,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 0e4e77f046c9..c5396902eff7 100644 --- a/orchagent/nexthopkey.h +++ b/orchagent/nexthopkey.h @@ -20,6 +20,8 @@ struct NextHopKey MacAddress mac_address; // Overlay Nexthop MAC. LabelStack label_stack; // MPLS label stack uint32_t weight; // NH weight for NHGs + string srv6_segment; // SRV6 segment string + string srv6_source; // SRV6 source address NextHopKey() : weight(0) {} NextHopKey(const std::string &str, const std::string &alias) : @@ -61,25 +63,43 @@ struct NextHopKey } weight = 0; } - 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); } - std::string ip_str = parseMplsNextHop(str); - auto keys = tokenize(ip_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 + { + std::string ip_str = parseMplsNextHop(str); + auto keys = tokenize(ip_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]; - weight = 0; } NextHopKey(const IpAddress &ip, const MacAddress &mac, const uint32_t &vni, bool overlay_nh) : ip_address(ip), alias(""), vni(vni), mac_address(mac){} @@ -91,8 +111,12 @@ struct NextHopKey return str; } - 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 str = formatMplsNextHop(); str += (ip_address.to_string() + NH_DELIMITER + alias + NH_DELIMITER + std::to_string(vni) + NH_DELIMITER + mac_address.to_string()); @@ -101,15 +125,16 @@ struct NextHopKey bool operator<(const NextHopKey &o) const { - return tie(ip_address, alias, label_stack, vni, mac_address) < - tie(o.ip_address, o.alias, o.label_stack, o.vni, o.mac_address); + return tie(ip_address, alias, label_stack, vni, mac_address, srv6_segment, srv6_source) < + tie(o.ip_address, o.alias, o.label_stack, 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) && (label_stack == o.label_stack) && - (vni == o.vni) && (mac_address == o.mac_address); + (vni == o.vni) && (mac_address == o.mac_address) && + (srv6_segment == o.srv6_segment) && (srv6_source == o.srv6_source); } bool operator!=(const NextHopKey &o) const @@ -119,7 +144,7 @@ struct NextHopKey bool isIntfNextHop() const { - return (ip_address.isZero()); + return (ip_address.isZero() && !isSrv6NextHop()); } bool isMplsNextHop() const @@ -127,6 +152,11 @@ struct NextHopKey return (!label_stack.empty()); } + bool isSrv6NextHop() const + { + return (srv6_segment != ""); + } + std::string parseMplsNextHop(const std::string& str) { // parseMplsNextHop initializes MPLS-related member data of the NextHopKey diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index 9ef6cb07f33a..b990fa2a2a7e 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -45,6 +45,7 @@ MlagOrch *gMlagOrch; IsoGrpOrch *gIsoGrpOrch; MACsecOrch *gMacsecOrch; BfdOrch *gBfdOrch; +Srv6Orch *gSrv6Orch; bool gIsNatSupported = false; @@ -160,12 +161,19 @@ bool OrchDaemon::init() gFgNhgOrch = new FgNhgOrch(m_configDb, m_applDb, m_stateDb, fgnhg_tables, gNeighOrch, gIntfsOrch, vrf_orch); gDirectory.set(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); + const int routeorch_pri = 5; vector route_tables = { { APP_ROUTE_TABLE_NAME, routeorch_pri }, { APP_LABEL_ROUTE_TABLE_NAME, routeorch_pri } }; - gRouteOrch = new RouteOrch(m_applDb, route_tables, gSwitchOrch, gNeighOrch, gIntfsOrch, vrf_orch, gFgNhgOrch); + gRouteOrch = new RouteOrch(m_applDb, route_tables, gSwitchOrch, gNeighOrch, gIntfsOrch, vrf_orch, gFgNhgOrch, gSrv6Orch); gNhgOrch = new NhgOrch(m_applDb, APP_NEXTHOP_GROUP_TABLE_NAME); CoppOrch *copp_orch = new CoppOrch(m_applDb, APP_COPP_TABLE_NAME); @@ -302,7 +310,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, mux_orch, mux_cb_orch, gIntfsOrch, gNeighOrch, gNhgOrch, gRouteOrch, copp_orch, qos_orch, wm_orch, policer_orch, tunnel_decap_orch, sflow_orch, debug_counter_orch, gMacsecOrch, gBfdOrch}; + m_orchList = { gSwitchOrch, gCrmOrch, gPortsOrch, gBufferOrch, mux_orch, mux_cb_orch, gIntfsOrch, gNeighOrch, gNhgOrch, gRouteOrch, copp_orch, qos_orch, wm_orch, policer_orch, tunnel_decap_orch, sflow_orch, debug_counter_orch, gMacsecOrch, gBfdOrch, 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 2e88447ce75b..4be9b7eb9ba4 100644 --- a/orchagent/orchdaemon.h +++ b/orchagent/orchdaemon.h @@ -39,6 +39,7 @@ #include "muxorch.h" #include "macsecorch.h" #include "bfdorch.h" +#include "srv6orch.h" using namespace swss; diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index 04c9a083af67..66c6dc5dc095 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -27,7 +27,7 @@ extern size_t gMaxBulkSize; #define DEFAULT_NUMBER_OF_ECMP_GROUPS 128 #define DEFAULT_MAX_ECMP_GROUP_SIZE 32 -RouteOrch::RouteOrch(DBConnector *db, vector &tableNames, SwitchOrch *switchOrch, NeighOrch *neighOrch, IntfsOrch *intfsOrch, VRFOrch *vrfOrch, FgNhgOrch *fgNhgOrch) : +RouteOrch::RouteOrch(DBConnector *db, vector &tableNames, SwitchOrch *switchOrch, NeighOrch *neighOrch, IntfsOrch *intfsOrch, VRFOrch *vrfOrch, FgNhgOrch *fgNhgOrch, Srv6Orch *srv6Orch) : gRouteBulker(sai_route_api, gMaxBulkSize), gLabelRouteBulker(sai_mpls_api, gMaxBulkSize), gNextHopGroupMemberBulker(sai_next_hop_group_api, gSwitchId, gMaxBulkSize), @@ -38,6 +38,7 @@ RouteOrch::RouteOrch(DBConnector *db, vector &tableNames, m_vrfOrch(vrfOrch), m_fgNhgOrch(fgNhgOrch), m_nextHopGroupCount(0), + m_srv6Orch(srv6Orch), m_resync(false) { SWSS_LOG_ENTER(); @@ -561,6 +562,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)) { @@ -589,6 +593,14 @@ void RouteOrch::doTask(Consumer& consumer) if (fvField(i) == "nexthop_group") nhg_index = fvValue(i); + + if (fvField(i) == "segment") { + srv6_segments = fvValue(i); + srv6_nh = true; + } + + if (fvField(i) == "seg_src") + srv6_source = fvValue(i); } /* @@ -615,6 +627,8 @@ void RouteOrch::doTask(Consumer& consumer) vector vni_labelv; vector rmacv; NextHopGroupKey& nhg = ctx.nhg; + vector srv6_segv; + vector srv6_src; /* Check if the next hop group is owned by the NhgOrch. */ if (nhg_index.empty()) @@ -624,6 +638,8 @@ void RouteOrch::doTask(Consumer& consumer) mpls_nhv = tokenize(mpls_nhs, ','); vni_labelv = tokenize(vni_labels, ','); rmacv = tokenize(remote_macs, ','); + srv6_segv = tokenize(srv6_segments, ','); + srv6_src = tokenize(srv6_source, ','); /* * For backward compatibility, adjust ip string from old format to @@ -632,7 +648,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); @@ -688,6 +704,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) { for (uint32_t i = 0; i < ipv.size(); i++) @@ -714,7 +754,7 @@ void RouteOrch::doTask(Consumer& consumer) nhg_str += 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); } } else @@ -893,6 +933,20 @@ void RouteOrch::doTask(Consumer& consumer) { removeOverlayNextHops(it_nhg.second, it_nhg.first); } + else 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.first); + } + 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.first].ref_count == 0) { removeNextHopGroup(it_nhg.first); @@ -1145,6 +1199,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; } @@ -1276,6 +1331,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()); @@ -1344,8 +1400,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)) { @@ -1355,11 +1410,11 @@ 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()); } } } @@ -1370,6 +1425,19 @@ bool RouteOrch::removeNextHopGroup(const NextHopGroupKey &nexthops) m_neighOrch->removeMplsNextHop(it); } } + + 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; @@ -1523,6 +1591,7 @@ bool RouteOrch::addRoute(RouteBulkContext& ctx, const NextHopGroupKey &nextHops) bool curNhgIsFineGrained = false; bool isFineGrainedNextHopIdChanged = false; bool blackhole = false; + bool srv6_nh = false; if (m_syncdRoutes.find(vrf_id) == m_syncdRoutes.end()) { @@ -1535,6 +1604,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)) @@ -1612,19 +1686,28 @@ bool RouteOrch::addRoute(RouteBulkContext& ctx, const NextHopGroupKey &nextHops) /* IP neighbor is not yet resolved */ 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; } } @@ -1644,6 +1727,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)) { @@ -1654,17 +1747,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; } } @@ -1995,6 +2088,10 @@ bool RouteOrch::addRoutePost(const RouteBulkContext& ctx, const NextHopGroupKey SWSS_LOG_NOTICE("Update overlay Nexthop %s", ol_nextHops.to_string().c_str()); m_bulkNhgReducedRefCnt.emplace(ol_nextHops, vrf_id); } + else if (ol_nextHops.is_srv6_nexthop()) + { + m_srv6Orch->removeSrv6Nexthops(ol_nextHops); + } else if (ol_nextHops.getSize() == 1) { RouteKey r_key = { vrf_id, ipPrefix }; @@ -2038,7 +2135,7 @@ bool RouteOrch::addRoutePost(const RouteBulkContext& ctx, const NextHopGroupKey ipPrefix.to_string().c_str(), nextHops.to_string().c_str()); } - if (ctx.nhg_index.empty() && nextHops.getSize() == 1 && !nextHops.is_overlay_nexthop()) + if (ctx.nhg_index.empty() && nextHops.getSize() == 1 && !nextHops.is_overlay_nexthop() && !nextHops.is_srv6_nexthop()) { RouteKey r_key = { vrf_id, ipPrefix }; auto nexthop = NextHopKey(nextHops.to_string()); @@ -2230,6 +2327,11 @@ bool RouteOrch::removeRoutePost(const RouteBulkContext& ctx) { m_neighOrch->removeMplsNextHop(nexthop); } + else if (nexthop.isSrv6NextHop() && + (m_neighOrch->getNextHopRefCount(nexthop) == 0)) + { + m_srv6Orch->removeSrv6Nexthops(it_route->second.nhg_key); + } RouteKey r_key = { vrf_id, ipPrefix }; removeNextHopRoute(nexthop, r_key); @@ -2322,11 +2424,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 3162331820a8..60af5c756ad3 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" @@ -168,7 +169,7 @@ struct LabelRouteBulkContext class RouteOrch : public Orch, public Subject { public: - RouteOrch(DBConnector *db, vector &tableNames, SwitchOrch *switchOrch, NeighOrch *neighOrch, IntfsOrch *intfsOrch, VRFOrch *vrfOrch, FgNhgOrch *fgNhgOrch); + RouteOrch(DBConnector *db, vector &tableNames, SwitchOrch *switchOrch, NeighOrch *neighOrch, IntfsOrch *intfsOrch, VRFOrch *vrfOrch, FgNhgOrch *fgNhgOrch, Srv6Orch *srv6Orch); bool hasNextHopGroup(const NextHopGroupKey&) const; sai_object_id_t getNextHopGroupId(const NextHopGroupKey&); @@ -216,6 +217,7 @@ class RouteOrch : public Orch, public Subject IntfsOrch *m_intfsOrch; VRFOrch *m_vrfOrch; FgNhgOrch *m_fgNhgOrch; + Srv6Orch *m_srv6Orch; unsigned int m_nextHopGroupCount; unsigned int m_maxNextHopGroupCount; diff --git a/orchagent/saihelper.cpp b/orchagent/saihelper.cpp index c1c2c0b8ea6b..90578dc8a94a 100644 --- a/orchagent/saihelper.cpp +++ b/orchagent/saihelper.cpp @@ -66,6 +66,7 @@ sai_nat_api_t* sai_nat_api; sai_isolation_group_api_t* sai_isolation_group_api; sai_system_port_api_t* sai_system_port_api; sai_macsec_api_t* sai_macsec_api; +sai_srv6_api_t** sai_srv6_api;; sai_l2mc_group_api_t* sai_l2mc_group_api; sai_bfd_api_t* sai_bfd_api; @@ -191,6 +192,7 @@ void initSaiApi() sai_api_query(SAI_API_ISOLATION_GROUP, (void **)&sai_isolation_group_api); sai_api_query(SAI_API_SYSTEM_PORT, (void **)&sai_system_port_api); sai_api_query(SAI_API_MACSEC, (void **)&sai_macsec_api); + sai_api_query(SAI_API_SRV6, (void **)&sai_srv6_api); sai_api_query(SAI_API_L2MC_GROUP, (void **)&sai_l2mc_group_api); sai_api_query(SAI_API_BFD, (void **)&sai_bfd_api); @@ -225,6 +227,7 @@ void initSaiApi() sai_log_set((sai_api_t)SAI_API_NAT, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_SYSTEM_PORT, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_MACSEC, SAI_LOG_LEVEL_NOTICE); + sai_log_set(SAI_API_SRV6, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_L2MC_GROUP, SAI_LOG_LEVEL_NOTICE); sai_log_set(SAI_API_BFD, SAI_LOG_LEVEL_NOTICE); } diff --git a/orchagent/srv6orch.cpp b/orchagent/srv6orch.cpp new file mode 100644 index 000000000000..5081e06b6f7d --- /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 000000000000..989737a99842 --- /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 c75a9ad3ca10..f1d02898f9e6 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -78,7 +78,8 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/isolationgrouporch.cpp \ $(top_srcdir)/orchagent/macsecorch.cpp \ $(top_srcdir)/orchagent/lagid.cpp \ - $(top_srcdir)/orchagent/bfdorch.cpp + $(top_srcdir)/orchagent/bfdorch.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 8d7b3b662319..d95ae2f87e06 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; @@ -356,13 +357,20 @@ 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); const int routeorch_pri = 5; vector route_tables = { { APP_ROUTE_TABLE_NAME, routeorch_pri }, { APP_LABEL_ROUTE_TABLE_NAME, routeorch_pri } }; - gRouteOrch = new RouteOrch(m_app_db.get(), route_tables, gSwitchOrch, gNeighOrch, gIntfsOrch, gVrfOrch, gFgNhgOrch); + gRouteOrch = new RouteOrch(m_app_db.get(), route_tables, gSwitchOrch, gNeighOrch, gIntfsOrch, gVrfOrch, gFgNhgOrch, gSrv6Orch); PolicerOrch *policer_orch = new PolicerOrch(m_config_db.get(), "POLICER"); @@ -404,6 +412,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 000000000000..0d134acc2bee --- /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