diff --git a/common/schema.h b/common/schema.h index e71cc7c5d..2d807e43b 100644 --- a/common/schema.h +++ b/common/schema.h @@ -412,6 +412,7 @@ after libswsscommon deb make. #define STATE_FDB_TABLE_NAME "FDB_TABLE" #define STATE_WARM_RESTART_TABLE_NAME "WARM_RESTART_TABLE" #define STATE_WARM_RESTART_ENABLE_TABLE_NAME "WARM_RESTART_ENABLE_TABLE" +#define STATE_WARM_RESTART_REGISTRATION_TABLE_NAME "WARM_RESTART_REGISTRATION_TABLE" #define STATE_VRF_TABLE_NAME "VRF_TABLE" #define STATE_VRF_OBJECT_TABLE_NAME "VRF_OBJECT_TABLE" #define STATE_MGMT_PORT_TABLE_NAME "MGMT_PORT_TABLE" diff --git a/common/warm_restart.cpp b/common/warm_restart.cpp index 34da10667..79d479a6d 100644 --- a/common/warm_restart.cpp +++ b/common/warm_restart.cpp @@ -2,26 +2,68 @@ #include #include "logger.h" #include "schema.h" +#include "timestamp.h" #include "warm_restart.h" namespace swss { -const WarmStart::WarmStartStateNameMap WarmStart::warmStartStateNameMap = +const std::string WarmStart::kNsfManagerNotificationChannel = + "NSF_MANAGER_COMMON_NOTIFICATION_CHANNEL"; +const std::string WarmStart::kRegistrationFreezeKey = "freeze"; +const std::string WarmStart::kRegistrationCheckpointKey = "checkpoint"; +const std::string WarmStart::kRegistrationReconciliationKey = "reconciliation"; +const std::string WarmStart::kRegistrationTimestampKey = "timestamp"; + +const WarmStart::WarmStartStateNameMap* WarmStart::warmStartStateNameMap() { - {INITIALIZED, "initialized"}, - {RESTORED, "restored"}, - {REPLAYED, "replayed"}, - {RECONCILED, "reconciled"}, - {WSDISABLED, "disabled"}, - {WSUNKNOWN, "unknown"} -}; - -const WarmStart::DataCheckStateNameMap WarmStart::dataCheckStateNameMap = + static const auto* const warmStartStateNameMap = + new WarmStartStateNameMap({ + {INITIALIZED, "initialized"}, + {RESTORED, "restored"}, + {REPLAYED, "replayed"}, + {RECONCILED, "reconciled"}, + {WSDISABLED, "disabled"}, + {WSUNKNOWN, "unknown"}, + {FROZEN, "frozen"}, + {QUIESCENT, "quiescent"}, + {CHECKPOINTED, "checkpointed"}, + {FAILED, "failed"} + }); + return warmStartStateNameMap; +} + +const WarmStart::DataCheckStateNameMap* WarmStart::dataCheckStateNameMap() { - {CHECK_IGNORED, "ignored"}, - {CHECK_PASSED, "passed"}, - {CHECK_FAILED, "failed"} -}; + static const auto* const dataCheckStateNameMap = + new DataCheckStateNameMap({ + {CHECK_IGNORED, "ignored"}, + {CHECK_PASSED, "passed"}, + {CHECK_FAILED, "failed"} + }); + return dataCheckStateNameMap; +} + +const WarmStart::WarmBootNotificationNameMap* WarmStart::warmBootNotificationNameMap() +{ + static const auto* const warmBootNotificationNameMap = + new WarmBootNotificationNameMap({ + {WarmBootNotification::kFreeze, "freeze"}, + {WarmBootNotification::kUnfreeze, "unfreeze"}, + {WarmBootNotification::kCheckpoint, "checkpoint"}, + }); + return warmBootNotificationNameMap; +} + +const WarmStart::WarmBootNotificationReverseMap* WarmStart::warmBootNotificationReverseMap() +{ + static const auto* const warmBootNotificationReverseMap = + new WarmBootNotificationReverseMap({ + {"freeze", WarmBootNotification::kFreeze}, + {"unfreeze", WarmBootNotification::kUnfreeze}, + {"checkpoint", WarmBootNotification::kCheckpoint}, + }); + return warmBootNotificationReverseMap; +} WarmStart &WarmStart::getInstance(void) { @@ -44,6 +86,9 @@ void WarmStart::initialize(const std::string &app_name, return; } + warmStart.m_appName = app_name; + warmStart.m_dockerName = docker_name; + /* Use unix socket for db connection by default */ warmStart.m_stateDb = std::make_shared("STATE_DB", db_timeout, isTcpConn); @@ -58,6 +103,71 @@ void WarmStart::initialize(const std::string &app_name, std::unique_ptr(new Table(warmStart.m_cfgDb.get(), CFG_WARM_RESTART_TABLE_NAME)); warmStart.m_initialized = true; + warmStart.m_warmbootState = WSUNKNOWN; +} + +/* + * registerWarmBootInfo + * + * Register an application with NSF Manager. + * + * Returns: true on success, false otherwise. + * + * wait_for_freeze: if true, NSF Manager waits for application to freeze + * and become quiescent before proceeding to state + * verification and checkpointing + * wait_for_checkpoint: if true, NSF Manager waits for application to + * complete checkpointing before reboot + * wait_for_reconciliation: if true, NSF Manager waits for application to + * complete reconciliation before unfreeze + */ +bool WarmStart::registerWarmBootInfo(bool wait_for_freeze, + bool wait_for_checkpoint, + bool wait_for_reconciliation) { + auto& warmStart = getInstance(); + + if (!warmStart.m_initialized) { + SWSS_LOG_ERROR("registerWarmBootInfo called before initialized"); + return false; + } + + if (warmStart.m_dockerName.empty()) { + SWSS_LOG_ERROR("registerWarmBootInfo: m_dockerName is empty"); + return false; + } + + if (warmStart.m_appName.empty()) { + SWSS_LOG_ERROR("registerWarmBootInfo: m_appName is empty"); + return false; + } + + std::unique_ptr
stateWarmRestartRegistrationTable = + std::unique_ptr
( + new Table(warmStart.m_stateDb.get(), + STATE_WARM_RESTART_REGISTRATION_TABLE_NAME)); + + std::string separator = + TableBase::getTableSeparator(warmStart.m_stateDb->getDbId()); + std::string tableName = + warmStart.m_dockerName + separator + warmStart.m_appName; + + std::vector values; + + values.push_back(swss::FieldValueTuple(WarmStart::kRegistrationFreezeKey, + wait_for_freeze ? "true" : "false")); + values.push_back(swss::FieldValueTuple( + WarmStart::kRegistrationCheckpointKey, + wait_for_checkpoint ? "true" : "false")); + values.push_back(swss::FieldValueTuple( + WarmStart::kRegistrationReconciliationKey, + wait_for_reconciliation ? "true" : "false")); + values.push_back(swss::FieldValueTuple( + WarmStart::kRegistrationTimestampKey, + getTimestamp())); + + stateWarmRestartRegistrationTable->set(tableName, values); + + return true; } /* @@ -197,6 +307,13 @@ void WarmStart::getWarmStartState(const std::string &app_name, WarmStartState &s return; } + if (app_name == warmStart.m_appName && + warmStart.m_warmbootState != WSUNKNOWN) { + /* Cache is up-to-date. Read state from cache. */ + state = warmStart.m_warmbootState; + return; + } + warmStart.m_stateWarmRestartTable->hget(app_name, "state", statestr); /* If warm-start is enabled, state cannot be assumed as Reconciled @@ -204,7 +321,7 @@ void WarmStart::getWarmStartState(const std::string &app_name, WarmStartState &s */ state = WSUNKNOWN; - for (auto it = warmStartStateNameMap.begin(); it != warmStartStateNameMap.end(); it++) + for (auto it = warmStartStateNameMap()->begin(); it != warmStartStateNameMap()->end(); it++) { if (it->second == statestr) { @@ -212,7 +329,12 @@ void WarmStart::getWarmStartState(const std::string &app_name, WarmStartState &s break; } } - + if (app_name == warmStart.m_appName) + { + /* Update cache. */ + warmStart.m_warmbootState = state; + } + SWSS_LOG_INFO("%s warm start state get %s(%d)", app_name.c_str(), statestr.c_str(), state); @@ -226,11 +348,17 @@ void WarmStart::setWarmStartState(const std::string &app_name, WarmStartState st warmStart.m_stateWarmRestartTable->hset(app_name, "state", - warmStartStateNameMap.at(state).c_str()); + warmStartStateNameMap()->at(state).c_str()); + + if (app_name == warmStart.m_appName) + { + /* Update cache. */ + warmStart.m_warmbootState = state; + } SWSS_LOG_NOTICE("%s warm start state changed to %s", app_name.c_str(), - warmStartStateNameMap.at(state).c_str()); + warmStartStateNameMap()->at(state).c_str()); } // Set the WarmStart data check state for a particular application. @@ -246,12 +374,12 @@ void WarmStart::setDataCheckState(const std::string &app_name, DataCheckStage st } warmStart.m_stateWarmRestartTable->hset(app_name, stageField, - dataCheckStateNameMap.at(state).c_str()); + dataCheckStateNameMap()->at(state).c_str()); SWSS_LOG_NOTICE("%s %s result %s", app_name.c_str(), stageField.c_str(), - dataCheckStateNameMap.at(state).c_str()); + dataCheckStateNameMap()->at(state).c_str()); } WarmStart::DataCheckState WarmStart::getDataCheckState(const std::string &app_name, DataCheckStage stage) @@ -271,7 +399,7 @@ WarmStart::DataCheckState WarmStart::getDataCheckState(const std::string &app_na DataCheckState state = CHECK_IGNORED; - for (auto it = dataCheckStateNameMap.begin(); it != dataCheckStateNameMap.end(); it++) + for (auto it = dataCheckStateNameMap()->begin(); it != dataCheckStateNameMap()->end(); it++) { if (it->second == stateStr) { @@ -288,4 +416,4 @@ WarmStart::DataCheckState WarmStart::getDataCheckState(const std::string &app_na return state; } -} +} // namespace swss diff --git a/common/warm_restart.h b/common/warm_restart.h index 037628725..27d9e8a1a 100644 --- a/common/warm_restart.h +++ b/common/warm_restart.h @@ -13,6 +13,12 @@ namespace swss { class WarmStart { public: + static const std::string kNsfManagerNotificationChannel; + static const std::string kRegistrationFreezeKey; + static const std::string kRegistrationCheckpointKey; + static const std::string kRegistrationReconciliationKey; + static const std::string kRegistrationTimestampKey; + enum WarmStartState { INITIALIZED, @@ -21,6 +27,10 @@ class WarmStart RECONCILED, WSDISABLED, WSUNKNOWN, + FROZEN, + QUIESCENT, + CHECKPOINTED, + FAILED, }; enum DataCheckState @@ -36,11 +46,23 @@ class WarmStart STAGE_RESTORE, }; + enum class WarmBootNotification { + kFreeze, + kUnfreeze, + kCheckpoint, + }; + typedef std::map WarmStartStateNameMap; - static const WarmStartStateNameMap warmStartStateNameMap; + static const WarmStartStateNameMap* warmStartStateNameMap(); typedef std::map DataCheckStateNameMap; - static const DataCheckStateNameMap dataCheckStateNameMap; + static const DataCheckStateNameMap* dataCheckStateNameMap(); + + typedef std::map WarmBootNotificationNameMap; + static const WarmBootNotificationNameMap* warmBootNotificationNameMap(); + + typedef std::map WarmBootNotificationReverseMap; + static const WarmBootNotificationReverseMap* warmBootNotificationReverseMap(); static WarmStart &getInstance(void); @@ -49,6 +71,10 @@ class WarmStart unsigned int db_timeout = 0, bool isTcpConn = false); + static bool registerWarmBootInfo(bool wait_for_freeze, + bool wait_for_checkpoint, + bool wait_for_reconciliation); + static bool checkWarmStart(const std::string &app_name, const std::string &docker_name, const bool incr_restore_cnt = true); @@ -71,7 +97,7 @@ class WarmStart DataCheckState state); static DataCheckState getDataCheckState(const std::string &app_name, - DataCheckStage stage); + DataCheckStage stage); private: std::shared_ptr m_stateDb; std::shared_ptr m_cfgDb; @@ -81,6 +107,9 @@ class WarmStart bool m_initialized; bool m_enabled; bool m_systemWarmRebootEnabled; + std::string m_appName; + std::string m_dockerName; + WarmStartState m_warmbootState; }; } diff --git a/tests/warm_restart_ut.cpp b/tests/warm_restart_ut.cpp index 2cdaa7a2c..035e1bc53 100644 --- a/tests/warm_restart_ut.cpp +++ b/tests/warm_restart_ut.cpp @@ -1,8 +1,8 @@ #include #include "gtest/gtest.h" #include "common/dbconnector.h" -#include "common/table.h" #include "common/schema.h" +#include "common/table.h" #include "common/warm_restart.h" using namespace std; @@ -11,6 +11,14 @@ using namespace swss; static const string testAppName = "TestApp"; static const string testDockerName = "TestDocker"; +// This test must be executed before first successful call to initialize() +// The static elements of this class can only be initialized once. +TEST(WarmRestart, testRegisterWarmBootInfoNotInitialized) +{ + bool ret = WarmStart::registerWarmBootInfo(true,false,false); + EXPECT_FALSE(ret); +} + TEST(WarmRestart, checkWarmStart_and_State) { DBConnector stateDb("STATE_DB", 0, true); @@ -160,6 +168,49 @@ TEST(WarmRestart, getWarmStartTimer) EXPECT_EQ(timer, 5000u); } +TEST(WarmRestart, set_get_WarmStartState) +{ + DBConnector stateDb("STATE_DB", 0, true); + Table stateWarmRestartTable(&stateDb, STATE_WARM_RESTART_TABLE_NAME); + Table stateWarmRestartEnableTable(&stateDb, STATE_WARM_RESTART_ENABLE_TABLE_NAME); + + DBConnector configDb("CONFIG_DB", 0, true); + Table cfgWarmRestartTable(&configDb, CFG_WARM_RESTART_TABLE_NAME); + + //Clean up warm restart state for testAppName and warm restart config for testDockerName + stateWarmRestartTable.del(testAppName); + cfgWarmRestartTable.del(testDockerName); + + //Initialize WarmStart class for TestApp + WarmStart::initialize(testAppName, testDockerName, 0, true); + + WarmStart::WarmStartState warmStartStates[] = + { + WarmStart::INITIALIZED, + WarmStart::RESTORED, + WarmStart::REPLAYED, + WarmStart::RECONCILED, + WarmStart::WSDISABLED, + WarmStart::WSUNKNOWN, + WarmStart::FROZEN, + WarmStart::QUIESCENT, + WarmStart::CHECKPOINTED, + WarmStart::FAILED, + }; + + for (const auto &currState : warmStartStates) { + WarmStart::setWarmStartState(testAppName, currState); + + string state; + stateWarmRestartTable.hget(testAppName, "state", state); + EXPECT_EQ(state, WarmStart::warmStartStateNameMap()->at(currState).c_str()); + + WarmStart::WarmStartState ret_state; + WarmStart::getWarmStartState(testAppName, ret_state); + EXPECT_EQ(ret_state, currState); + } +} + TEST(WarmRestart, set_get_DataCheckState) { DBConnector stateDb("STATE_DB", 0, true); @@ -235,3 +286,79 @@ TEST(WarmRestart, set_get_DataCheckState) state = WarmStart::getDataCheckState(testAppName, WarmStart::STAGE_RESTORE); EXPECT_EQ(state, WarmStart::CHECK_FAILED); } + +TEST(WarmRestart, testNotificationMaps) +{ + WarmStart::WarmBootNotification warmBootNotifications[] = + { + WarmStart::WarmBootNotification::kFreeze, + WarmStart::WarmBootNotification::kUnfreeze, + WarmStart::WarmBootNotification::kCheckpoint, + }; + + for (const auto &currNotification : warmBootNotifications) { + std::string type = WarmStart::warmBootNotificationNameMap()->at(currNotification); + WarmStart::WarmBootNotification notification; + notification = WarmStart::warmBootNotificationReverseMap()->at(type); + EXPECT_EQ(notification, currNotification); + } +} + +TEST(WarmRestart, testRegisterWarmBootInfo) +{ + DBConnector stateDb("STATE_DB", 0, true); + Table stateWarmRestartRegTable(&stateDb, + STATE_WARM_RESTART_REGISTRATION_TABLE_NAME); + + std::string tableName = testDockerName + "|" + testAppName; + + //Clean up warm restart state for testAppName + stateWarmRestartRegTable.del(tableName); + + //Initialize WarmStart class for TestApp + WarmStart::initialize(testAppName, testDockerName, 0, true); + + bool ret = WarmStart::registerWarmBootInfo(true,false,false); + EXPECT_TRUE(ret); + + std::string value; + ret = stateWarmRestartRegTable.hget(tableName, + WarmStart::kRegistrationFreezeKey, value); + EXPECT_TRUE(ret); + EXPECT_EQ(value, "true"); + + ret = stateWarmRestartRegTable.hget( + tableName, WarmStart::kRegistrationCheckpointKey, value); + EXPECT_TRUE(ret); + EXPECT_EQ(value, "false"); + + ret = stateWarmRestartRegTable.hget( + tableName, WarmStart::kRegistrationReconciliationKey, value); + EXPECT_TRUE(ret); + EXPECT_EQ(value, "false"); + + ret = stateWarmRestartRegTable.hget( + tableName, WarmStart::kRegistrationTimestampKey, value); + EXPECT_TRUE(ret); + + ret = WarmStart::registerWarmBootInfo(false,true,false); + EXPECT_TRUE(ret); + + ret = stateWarmRestartRegTable.hget(tableName, + WarmStart::kRegistrationFreezeKey, value); + EXPECT_TRUE(ret); + EXPECT_EQ(value, "false"); + + ret = stateWarmRestartRegTable.hget( + tableName, WarmStart::kRegistrationCheckpointKey, value); + EXPECT_TRUE(ret); + EXPECT_EQ(value, "true"); + + ret = WarmStart::registerWarmBootInfo(false,false,true); + EXPECT_TRUE(ret); + + ret = stateWarmRestartRegTable.hget( + tableName, WarmStart::kRegistrationReconciliationKey, value); + EXPECT_TRUE(ret); + EXPECT_EQ(value, "true"); +}