diff --git a/syncd/AsicView.cpp b/syncd/AsicView.cpp index 43c06ffdae..64fde2949b 100644 --- a/syncd/AsicView.cpp +++ b/syncd/AsicView.cpp @@ -117,6 +117,11 @@ void AsicView::fromDump( m_soInsegs[o->m_str_object_id] = o; break; + case SAI_OBJECT_TYPE_MY_SID_ENTRY: + sai_deserialize_my_sid_entry(o->m_str_object_id, o->m_meta_key.objectkey.key.my_sid_entry); + m_soMySidEntries[o->m_str_object_id] = o; + break; + default: if (o->m_info->isnonobjectid) @@ -710,6 +715,10 @@ void AsicView::asicCreateObject( m_soInsegs[currentObj->m_str_object_id] = currentObj; break; + case SAI_OBJECT_TYPE_MY_SID_ENTRY: + m_soMySidEntries[currentObj->m_str_object_id] = currentObj; + break; + default: SWSS_LOG_THROW("unsupported object type: %s", @@ -858,6 +867,10 @@ void AsicView::asicRemoveObject( m_soInsegs.erase(currentObj->m_str_object_id); break; + case SAI_OBJECT_TYPE_MY_SID_ENTRY: + m_soMySidEntries.erase(currentObj->m_str_object_id); + break; + default: SWSS_LOG_THROW("unsupported object type: %s", diff --git a/syncd/AsicView.h b/syncd/AsicView.h index ce79758100..9f6a478855 100644 --- a/syncd/AsicView.h +++ b/syncd/AsicView.h @@ -277,6 +277,7 @@ namespace syncd StrObjectIdToSaiObjectHash m_soRoutes; StrObjectIdToSaiObjectHash m_soNatEntries; StrObjectIdToSaiObjectHash m_soInsegs; + StrObjectIdToSaiObjectHash m_soMySidEntries; StrObjectIdToSaiObjectHash m_soOids; StrObjectIdToSaiObjectHash m_soAll; diff --git a/syncd/BestCandidateFinder.cpp b/syncd/BestCandidateFinder.cpp index 1ff05c3560..f42aac7192 100644 --- a/syncd/BestCandidateFinder.cpp +++ b/syncd/BestCandidateFinder.cpp @@ -2274,6 +2274,83 @@ std::shared_ptr BestCandidateFinder::findCurrentBestMatchForInsegEntry( currentInsegObj->getObjectStatus()); } +/** + * @brief Find current best match for my sid entry. + * + * For my sid entry we don't need to iterate via all current my sid entries, we + * can do dictionary lookup, but we need to do smart trick, since temporary + * object was processed we just need to check whether VID in my_sid_entry struct + * is matched/final and it has RID assigned from current view. If RID exists, we + * can use that RID to get VID of current view, exchange in my_sid_entry struct + * and do dictionary lookup on serialized my_sid_entry. + * + * With this approach for many entries this is the quickest possible way. In + * case when RID doesn't exist, that means we have invalid my sid entry, so we + * must return null. + * + * @param temporaryObj Temporary object. + * + * @return Best match object if found or nullptr. + */ +std::shared_ptr BestCandidateFinder::findCurrentBestMatchForMySidEntry( + _In_ const std::shared_ptr &temporaryObj) +{ + SWSS_LOG_ENTER(); + + /* + * Make a copy here to not destroy object data, later + * on this data should be read only. + */ + + sai_object_meta_key_t mk = temporaryObj->m_meta_key; + + if (!exchangeTemporaryVidToCurrentVid(mk)) + { + /* + * Not all oids inside struct object were translated, so there is no + * matching object in current view, we need to return null. + */ + + return nullptr; + } + + std::string str_my_sid_entry = sai_serialize_my_sid_entry(mk.objectkey.key.my_sid_entry); + + /* + * Now when we have serialized my sid entry with temporary vr_id VID + * replaced to current vr_id VID we can do dictionary lookup for my sid entry. + */ + auto currentMySidIt = m_currentView.m_soMySidEntries.find(str_my_sid_entry); + + if (currentMySidIt == m_currentView.m_soMySidEntries.end()) + { + SWSS_LOG_DEBUG("unable to find my sid entry %s in current asic view", str_my_sid_entry.c_str()); + + return nullptr; + } + + /* + * We found the same my sid entry in current view! Just one extra check + * of object status if it's not processed yet. + */ + + auto currentMySidObj = currentMySidIt->second; + + if (currentMySidObj->getObjectStatus() == SAI_OBJECT_STATUS_NOT_PROCESSED) + { + return currentMySidObj; + } + + /* + * If we are here, that means this my sid entry was already processed, which + * can indicate a bug or somehow duplicated entries. + */ + + SWSS_LOG_THROW("found my sid entry %s in current view, but it status is %d, FATAL", + str_my_sid_entry.c_str(), + currentMySidObj->getObjectStatus()); +} + /** * @brief Find current best match for FDB. * @@ -2517,6 +2594,9 @@ std::shared_ptr BestCandidateFinder::findCurrentBestMatch( case SAI_OBJECT_TYPE_INSEG_ENTRY: return findCurrentBestMatchForInsegEntry(temporaryObj); + case SAI_OBJECT_TYPE_MY_SID_ENTRY: + return findCurrentBestMatchForMySidEntry(temporaryObj); + /* * We can have special case for switch since we know there should * be only one switch. diff --git a/syncd/BestCandidateFinder.h b/syncd/BestCandidateFinder.h index e1d8cd5980..2c3d7d94d7 100644 --- a/syncd/BestCandidateFinder.h +++ b/syncd/BestCandidateFinder.h @@ -132,6 +132,9 @@ namespace syncd std::shared_ptr findCurrentBestMatchForInsegEntry( _In_ const std::shared_ptr &temporaryObj); + std::shared_ptr findCurrentBestMatchForMySidEntry( + _In_ const std::shared_ptr &temporaryObj); + private: bool exchangeTemporaryVidToCurrentVid( diff --git a/syncd/ComparisonLogic.cpp b/syncd/ComparisonLogic.cpp index 9d2567c550..fd18ba92e7 100644 --- a/syncd/ComparisonLogic.cpp +++ b/syncd/ComparisonLogic.cpp @@ -1006,6 +1006,10 @@ void ComparisonLogic::createNewObjectFromTemporaryObject( currentObj->m_str_object_id = sai_serialize_inseg_entry(currentObj->m_meta_key.objectkey.key.inseg_entry); break; + case SAI_OBJECT_TYPE_MY_SID_ENTRY: + currentObj->m_str_object_id = sai_serialize_my_sid_entry(currentObj->m_meta_key.objectkey.key.my_sid_entry); + break; + default: SWSS_LOG_THROW("unexpected non object id type: %s", diff --git a/tests/aspell.en.pws b/tests/aspell.en.pws index 5eb0162a8d..0bc208f154 100644 --- a/tests/aspell.en.pws +++ b/tests/aspell.en.pws @@ -534,3 +534,4 @@ truthy glibc getopt optind +sid diff --git a/unittest/syncd/Makefile.am b/unittest/syncd/Makefile.am index 49cec2fc36..c42f6b75a3 100644 --- a/unittest/syncd/Makefile.am +++ b/unittest/syncd/Makefile.am @@ -9,6 +9,7 @@ tests_SOURCES = main.cpp \ MockHelper.cpp \ MockableSaiSwitchInterface.cpp \ TestBestCandidateFinder.cpp \ + TestMySidWarmboot.cpp \ TestAttrVersionChecker.cpp \ TestCommandLineOptions.cpp \ TestConcurrentQueue.cpp \ diff --git a/unittest/syncd/TestMySidWarmboot.cpp b/unittest/syncd/TestMySidWarmboot.cpp new file mode 100644 index 0000000000..cbeb478400 --- /dev/null +++ b/unittest/syncd/TestMySidWarmboot.cpp @@ -0,0 +1,244 @@ +#include "AsicView.h" +#include "BestCandidateFinder.h" +#include "SaiObj.h" + +#include "meta/sai_serialize.h" + +#include "swss/logger.h" + +#include + +#include +#include +#include + +using namespace syncd; + +namespace +{ + constexpr sai_object_id_t SWITCH_VID = 0x21000000000000; + constexpr sai_object_id_t VR_VID_CURRENT = 0x3000000000001; + constexpr sai_object_id_t VR_VID_TEMP = 0x3000000000002; + constexpr sai_object_id_t SWITCH_RID = 0x21000000000000; + constexpr sai_object_id_t VR_RID = 0x4000000000001; + + sai_my_sid_entry_t makeMySidEntry( + _In_ sai_object_id_t switchVid, + _In_ sai_object_id_t vrVid, + _In_ const char *sid = "3000:1:1::") + { + SWSS_LOG_ENTER(); + + sai_my_sid_entry_t entry; + + memset(&entry, 0, sizeof(entry)); + + entry.switch_id = switchVid; + entry.vr_id = vrVid; + entry.locator_block_len = 32; + entry.locator_node_len = 16; + entry.function_len = 0; + entry.args_len = 0; + + inet_pton(AF_INET6, sid, &entry.sid); + + return entry; + } + + std::string serializeMySidObjectId( + _In_ sai_object_id_t switchVid, + _In_ sai_object_id_t vrVid, + _In_ const char *sid = "3000:1:1::") + { + SWSS_LOG_ENTER(); + + return sai_serialize_my_sid_entry(makeMySidEntry(switchVid, vrVid, sid)); + } + + std::string mySidAsicKey( + _In_ sai_object_id_t switchVid, + _In_ sai_object_id_t vrVid, + _In_ const char *sid = "3000:1:1::") + { + SWSS_LOG_ENTER(); + + return "SAI_OBJECT_TYPE_MY_SID_ENTRY:" + serializeMySidObjectId(switchVid, vrVid, sid); + } + + swss::TableDump makeSwitchDump() + { + SWSS_LOG_ENTER(); + + return { + {"SAI_OBJECT_TYPE_SWITCH:oid:0x21000000000000", { + {"SAI_SWITCH_ATTR_ECMP_DEFAULT_HASH_SEED", "0"}, + {"SAI_SWITCH_ATTR_FDB_AGING_TIME", "600"}, + {"SAI_SWITCH_ATTR_INIT_SWITCH", "true"}, + {"SAI_SWITCH_ATTR_LAG_DEFAULT_HASH_SEED", "0"}, + {"SAI_SWITCH_ATTR_SRC_MAC_ADDRESS", "02:00:00:00:00:01"}, + }}, + }; + } + + swss::TableDump makeSwitchAndMySidDump( + _In_ sai_object_id_t vrVid, + _In_ const char *sid = "3000:1:1::") + { + SWSS_LOG_ENTER(); + + auto dump = makeSwitchDump(); + + dump[mySidAsicKey(SWITCH_VID, vrVid, sid)] = { + {"SAI_MY_SID_ENTRY_ATTR_ENDPOINT_BEHAVIOR", "SAI_MY_SID_ENTRY_ENDPOINT_BEHAVIOR_E"}, + }; + + return dump; + } + + void wireVidRidMaps( + _Inout_ AsicView ¤tView, + _Inout_ AsicView &temporaryView) + { + SWSS_LOG_ENTER(); + + temporaryView.m_vidToRid[SWITCH_VID] = SWITCH_RID; + temporaryView.m_vidToRid[VR_VID_TEMP] = VR_RID; + + currentView.m_ridToVid[SWITCH_RID] = SWITCH_VID; + currentView.m_ridToVid[VR_RID] = VR_VID_CURRENT; + } +} + +TEST(MySidWarmboot, asicView_fromDump_deserializes_my_sid_entry) +{ + AsicView view; + + ASSERT_NO_THROW(view.fromDump(makeSwitchAndMySidDump(VR_VID_CURRENT))); + + ASSERT_EQ(view.m_soMySidEntries.size(), 1u); + EXPECT_EQ(view.m_soAll.size(), 2u); + + auto obj = view.m_soMySidEntries.begin()->second; + + EXPECT_EQ(obj->getObjectType(), SAI_OBJECT_TYPE_MY_SID_ENTRY); + EXPECT_EQ(obj->m_str_object_id, serializeMySidObjectId(SWITCH_VID, VR_VID_CURRENT)); + EXPECT_EQ(obj->m_meta_key.objectkey.key.my_sid_entry.switch_id, SWITCH_VID); + EXPECT_EQ(obj->m_meta_key.objectkey.key.my_sid_entry.vr_id, VR_VID_CURRENT); + EXPECT_EQ(obj->m_meta_key.objectkey.key.my_sid_entry.locator_block_len, 32u); + EXPECT_EQ(obj->m_meta_key.objectkey.key.my_sid_entry.locator_node_len, 16u); +} + +TEST(MySidWarmboot, asicView_create_and_remove_my_sid_entry) +{ + AsicView view; + + view.fromDump(makeSwitchAndMySidDump(VR_VID_CURRENT)); + + ASSERT_EQ(view.m_soMySidEntries.size(), 1u); + auto obj = view.m_soMySidEntries.begin()->second; + + view.asicRemoveObject(obj); + EXPECT_TRUE(view.m_soMySidEntries.empty()); + + view.asicCreateObject(obj); + ASSERT_EQ(view.m_soMySidEntries.size(), 1u); + EXPECT_EQ(view.m_soMySidEntries.begin()->first, serializeMySidObjectId(SWITCH_VID, VR_VID_CURRENT)); +} + +TEST(MySidWarmboot, my_sid_entry_serializes_with_exchanged_vr_vid) +{ + /* + * ComparisonLogic rewrites the object id after translating struct VIDs + * from the temporary view to the current view. Validate that contract + * without calling private ComparisonLogic methods. + */ + auto entry = makeMySidEntry(SWITCH_VID, VR_VID_TEMP); + + entry.vr_id = VR_VID_CURRENT; + + EXPECT_EQ( + sai_serialize_my_sid_entry(entry), + serializeMySidObjectId(SWITCH_VID, VR_VID_CURRENT)); +} + +TEST(MySidWarmboot, bestCandidateFinder_matches_my_sid_entry_after_vid_exchange) +{ + AsicView currentView; + AsicView temporaryView; + + currentView.fromDump(makeSwitchAndMySidDump(VR_VID_CURRENT)); + temporaryView.fromDump(makeSwitchAndMySidDump(VR_VID_TEMP)); + + wireVidRidMaps(currentView, temporaryView); + + ASSERT_EQ(temporaryView.m_soMySidEntries.size(), 1u); + ASSERT_EQ(currentView.m_soMySidEntries.size(), 1u); + + auto tempObj = temporaryView.m_soMySidEntries.begin()->second; + auto currentObj = currentView.m_soMySidEntries.begin()->second; + + BestCandidateFinder bcf(currentView, temporaryView, nullptr); + + auto match = bcf.findCurrentBestMatch(tempObj); + + ASSERT_NE(match, nullptr); + EXPECT_EQ(match, currentObj); + EXPECT_EQ(match->getObjectStatus(), SAI_OBJECT_STATUS_NOT_PROCESSED); +} + +TEST(MySidWarmboot, bestCandidateFinder_returns_null_when_vid_mapping_missing) +{ + AsicView currentView; + AsicView temporaryView; + + currentView.fromDump(makeSwitchAndMySidDump(VR_VID_CURRENT)); + temporaryView.fromDump(makeSwitchAndMySidDump(VR_VID_TEMP)); + + ASSERT_EQ(temporaryView.m_soMySidEntries.size(), 1u); + auto tempObj = temporaryView.m_soMySidEntries.begin()->second; + + BestCandidateFinder bcf(currentView, temporaryView, nullptr); + + EXPECT_EQ(bcf.findCurrentBestMatch(tempObj), nullptr); +} + +TEST(MySidWarmboot, bestCandidateFinder_returns_null_when_entry_missing_in_current_view) +{ + AsicView currentView; + AsicView temporaryView; + + currentView.fromDump(makeSwitchAndMySidDump(VR_VID_CURRENT, "3000:2:2::")); + temporaryView.fromDump(makeSwitchAndMySidDump(VR_VID_TEMP, "3000:1:1::")); + + wireVidRidMaps(currentView, temporaryView); + + ASSERT_EQ(temporaryView.m_soMySidEntries.size(), 1u); + auto tempObj = temporaryView.m_soMySidEntries.begin()->second; + + BestCandidateFinder bcf(currentView, temporaryView, nullptr); + + EXPECT_EQ(bcf.findCurrentBestMatch(tempObj), nullptr); +} + +TEST(MySidWarmboot, bestCandidateFinder_throws_when_entry_already_processed) +{ + AsicView currentView; + AsicView temporaryView; + + currentView.fromDump(makeSwitchAndMySidDump(VR_VID_CURRENT)); + temporaryView.fromDump(makeSwitchAndMySidDump(VR_VID_TEMP)); + + wireVidRidMaps(currentView, temporaryView); + + ASSERT_EQ(temporaryView.m_soMySidEntries.size(), 1u); + ASSERT_EQ(currentView.m_soMySidEntries.size(), 1u); + + auto tempObj = temporaryView.m_soMySidEntries.begin()->second; + auto currentObj = currentView.m_soMySidEntries.begin()->second; + + currentObj->setObjectStatus(SAI_OBJECT_STATUS_FINAL); + + BestCandidateFinder bcf(currentView, temporaryView, nullptr); + + EXPECT_THROW(bcf.findCurrentBestMatch(tempObj), std::runtime_error); +}