diff --git a/common/Makefile.am b/common/Makefile.am index e4f290a5..9ff83ada 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -65,6 +65,7 @@ common_libswsscommon_la_SOURCES = \ common/countertable.cpp \ common/redisutility.cpp \ common/restart_waiter.cpp \ + common/profileprovider.cpp \ common/redis_table_waiter.cpp common_libswsscommon_la_CXXFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(LIBNL_CFLAGS) $(CODE_COVERAGE_CXXFLAGS) @@ -73,7 +74,7 @@ common_libswsscommon_la_LIBADD = -lpthread $(LIBNL_LIBS) $(CODE_COVERAGE_LIBS) - common_swssloglevel_SOURCES = \ common/loglevel.cpp \ - common/loglevel_util.cpp + common/loglevel_util.cpp common_swssloglevel_CXXFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CODE_COVERAGE_CXXFLAGS) common_swssloglevel_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CODE_COVERAGE_CPPFLAGS) diff --git a/common/profileprovider.cpp b/common/profileprovider.cpp new file mode 100644 index 00000000..9481a76e --- /dev/null +++ b/common/profileprovider.cpp @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "profileprovider.h" +#include "logger.h" +#include "table.h" +#include "schema.h" + +using namespace std; +using namespace swss; + +ProfileProvider& ProfileProvider::instance() +{ + static ProfileProvider instance; + return instance; +} + +shared_ptr ProfileProvider::getConfig(const string &table, const string &key, const string &field, DBConnector* cfgDbConnector) +{ + assert(!table.empty()); + assert(!key.empty()); + assert(!field.empty()); + + auto staticConfig = getConfigs(table, key, cfgDbConnector); + auto result = staticConfig.find(field); + if (result != staticConfig.end()) + { + return make_shared(result->second); + } + + // Config not found is different with 'empty' config + return nullptr; +} + +bool ProfileProvider::appendConfigs(const string &table, const string &key, vector > &values, DBConnector* cfgDbConnector) +{ + assert(!table.empty()); + assert(!key.empty()); + + SWSS_LOG_DEBUG("DefaultValueProvider::AppendDefaultValues %s %s\n", table.c_str(), key.c_str()); + + auto staticConfig = getConfigs(table, key, cfgDbConnector); + + map existedValues; + for (auto& fieldValuePair : values) + { + existedValues.emplace(fieldValuePair.first, fieldValuePair.second); + } + + bool appendValues = false; + for (auto& fieldValuePair : staticConfig) + { + auto findresult = existedValues.find(fieldValuePair.first); + if (findresult == existedValues.end()) + { + appendValues = true; + values.emplace_back(fieldValuePair.first, fieldValuePair.second); + } + } + + return appendValues; +} + +map ProfileProvider::getConfigs(const string &table, const string &key, DBConnector* cfgDbConnector) +{ + if (itemDeleted(table, key, cfgDbConnector)) + { + SWSS_LOG_DEBUG("DefaultValueProvider::GetConfigs item %s %s deleted.\n", table.c_str(), key.c_str()); + map map; + return map; + } + + auto& staticCfgDbConnector = getStaticCfgDBConnector(cfgDbConnector); + auto itemkey = getKeyName(table, key, &staticCfgDbConnector); + return staticCfgDbConnector.hgetall>(itemkey); +} + +map>> ProfileProvider::getConfigs(DBConnector* cfgDbConnector) +{ + auto& staticCfgDbConnector = getStaticCfgDBConnector(cfgDbConnector); + auto configs = staticCfgDbConnector.getall(); + + // If a profile item mark as 'deleted', it's shoud not exist in result. + list> deletedItems; + for(auto const& tableItem: configs) + { + auto table = tableItem.first; + for(auto const& item: tableItem.second) + { + auto key = item.first; + if (itemDeleted(table, key, cfgDbConnector)) + { + SWSS_LOG_DEBUG("DefaultValueProvider::GetConfigs item %s %s deleted.\n", table.c_str(), key.c_str()); + deletedItems.push_back(make_pair(table, key)); + } + } + } + + for(auto const& deletedItem: deletedItems) + { + auto table = deletedItem.first; + auto key = deletedItem.second; + SWSS_LOG_DEBUG("DefaultValueProvider::GetConfigs remove deleted item %s %s from result.\n", table.c_str(), key.c_str()); + configs[table].erase(key); + } + + return configs; +} + +vector ProfileProvider::getKeys(const string &table, DBConnector* cfgDbConnector) +{ + auto& staticCfgDbConnector = getStaticCfgDBConnector(cfgDbConnector); + auto pattern = getKeyName(table, "*", &staticCfgDbConnector); + auto keys = staticCfgDbConnector.keys(pattern); + + const auto separator = SonicDBConfig::getSeparator(&staticCfgDbConnector); + vector result; + for(auto const& itemKey: keys) + { + size_t pos = itemKey.find(separator); + if (pos == string::npos) + { + SWSS_LOG_DEBUG("DefaultValueProvider::GetConfigs can't find separator %s in %s.\n", separator.c_str(), itemKey.c_str()); + continue; + } + + auto row = itemKey.substr(pos + 1); + if (!itemDeleted(table, row, cfgDbConnector)) + { + result.push_back(row); + } + else + { + SWSS_LOG_DEBUG("DefaultValueProvider::GetConfigs item %s %s deleted.\n", table.c_str(), row.c_str()); + } + } + + return result; +} + +ProfileProvider::ProfileProvider() +{ +} + +ProfileProvider::~ProfileProvider() +{ +} + +bool ProfileProvider::tryRevertItem(const string &table, const string &key, DBConnector* cfgDbConnector) +{ + if (itemDeleted(table, key, cfgDbConnector)) + { + revertItem(table, key, cfgDbConnector); + return true; + } + + return false; +} + +bool ProfileProvider::tryDeleteItem(const string &table, const string &key, DBConnector* cfgDbConnector) +{ + if (!itemDeleted(table, key, cfgDbConnector)) + { + deleteItem(table, key, cfgDbConnector); + return true; + } + + return false; +} + +bool ProfileProvider::itemDeleted(const string &table, const string &key, DBConnector* cfgDbConnector) +{ + auto deletedkey = getDeletedKeyName(table, key, cfgDbConnector); + return cfgDbConnector->exists(deletedkey) == true; +} + +void ProfileProvider::deleteItem(const string &table, const string &key, DBConnector* cfgDbConnector) +{ + auto deletedkey = getDeletedKeyName(table, key, cfgDbConnector); + // Only need deletedkey to mark the item is deleted. + cfgDbConnector->hset(deletedkey, "", ""); +} + +void ProfileProvider::revertItem(const string &table, const string &key, DBConnector* cfgDbConnector) +{ + auto deletedkey = getDeletedKeyName(table, key,cfgDbConnector); + cfgDbConnector->del(deletedkey); +} + +DBConnector& ProfileProvider::getStaticCfgDBConnector(DBConnector* cfgDbConnector) +{ + auto ns = cfgDbConnector->getNamespace(); + auto result = m_staticCfgDBMap.find(ns); + if (result != m_staticCfgDBMap.end()) + { + return result->second; + } + + // Create new DBConnector instance to PROFILE_DB + const string staticDbName = "PROFILE_DB"; + m_staticCfgDBMap.emplace(piecewise_construct, + forward_as_tuple(ns), + forward_as_tuple(staticDbName, SonicDBConfig::getDbId(staticDbName, ns), true, ns)); + + result = m_staticCfgDBMap.find(ns); + return result->second; +} \ No newline at end of file diff --git a/common/profileprovider.h b/common/profileprovider.h new file mode 100644 index 00000000..46a47f7e --- /dev/null +++ b/common/profileprovider.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include "common/table.h" +#include "common/dbconnector.h" +#include "common/converter.h" + +namespace swss { + +const std::string DELETED_KEY_SEPARATOR = "_"; + +class ProfileProvider +{ +public: + static ProfileProvider& instance(); + + bool appendConfigs(const std::string &table, const std::string &key, std::vector > &values, DBConnector* cfgDbConnector); + + std::shared_ptr getConfig(const std::string &table, const std::string &key, const std::string &field, DBConnector* cfgDbConnector); + + std::map getConfigs(const std::string &table, const std::string &key, DBConnector* cfgDbConnector); + + std::map>> getConfigs(DBConnector* cfgDbConnector); + + std::vector getKeys(const std::string &table, DBConnector* cfgDbConnector); + + bool tryRevertItem(const std::string &table, const std::string &key, DBConnector* cfgDbConnector); + + bool tryDeleteItem(const std::string &table, const std::string &key, DBConnector* cfgDbConnector); + + std::string getDeletedKeyName(const std::string &table, const std::string &key, DBConnector* dbConnector) + { + auto itemKey = to_upper(table) + DELETED_KEY_SEPARATOR + key; + return getKeyName(PROFILE_DELETE_TABLE, itemKey, dbConnector); + } + +private: + ProfileProvider(); + ~ProfileProvider(); + + std::string getKeyName(const std::string &table, const std::string &key, DBConnector* dbConnector) + { + const auto separator = SonicDBConfig::getSeparator(dbConnector); + // Profile DB follow Config DB: table name is case insensetive. + return to_upper(table) + separator + key; + } + + bool itemDeleted(const std::string &table, const std::string &key, DBConnector* cfgDbConnector); + + void deleteItem(const std::string &table, const std::string &key, DBConnector* cfgDbConnector); + + void revertItem(const std::string &table, const std::string &key, DBConnector* cfgDbConnector); + + DBConnector& getStaticCfgDBConnector(DBConnector* cfgDbConnector); + + std::map m_staticCfgDBMap; +}; + +} diff --git a/common/schema.h b/common/schema.h index 5087a53f..bc8270b4 100644 --- a/common/schema.h +++ b/common/schema.h @@ -493,6 +493,10 @@ namespace swss { #define STATE_VNET_MONITOR_TABLE_NAME "VNET_MONITOR_TABLE" +/***** PROFILE DATABASE *****/ + +#define PROFILE_DELETE_TABLE "PROFILE_DELETE" + /***** MISC *****/ #define IPV4_NAME "IPv4" diff --git a/pyext/swsscommon.i b/pyext/swsscommon.i index 266f296d..8475157c 100644 --- a/pyext/swsscommon.i +++ b/pyext/swsscommon.i @@ -31,6 +31,7 @@ #include "consumertablebase.h" #include "consumerstatetable.h" #include "producertable.h" +#include "profileprovider.h" #include "consumertable.h" #include "subscriberstatetable.h" #include "decoratorsubscriberstatetable.h" @@ -163,6 +164,7 @@ T castSelectableObj(swss::Selectable *temp) %include "defaultvalueprovider.h" %include "sonicv2connector.h" %include "pubsub.h" +%include "profileprovider.h" %include "selectable.h" %include "select.h" %include "rediscommand.h" diff --git a/tests/Makefile.am b/tests/Makefile.am index 4912ea12..13c114d7 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -38,6 +38,7 @@ tests_tests_SOURCES = tests/redis_ut.cpp \ tests/events_ut.cpp \ tests/restart_waiter_ut.cpp \ tests/redis_table_waiter_ut.cpp \ + tests/profileprovider_ut.cpp \ tests/main.cpp tests_tests_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(LIBNL_CFLAGS) diff --git a/tests/defaultvalueprovider_ut.cpp b/tests/defaultvalueprovider_ut.cpp index 14c21853..20d6b957 100644 --- a/tests/defaultvalueprovider_ut.cpp +++ b/tests/defaultvalueprovider_ut.cpp @@ -4,8 +4,8 @@ using namespace std; using namespace swss; -string profile_table = "INTERFACE"; -string profile_key = "TEST_INTERFACE"; +static string profile_table = "INTERFACE"; +static string profile_key = "TEST_INTERFACE"; class MockDefaultValueProvider : public DefaultValueProvider { diff --git a/tests/profileprovider_ut.cpp b/tests/profileprovider_ut.cpp new file mode 100644 index 00000000..098ff8cb --- /dev/null +++ b/tests/profileprovider_ut.cpp @@ -0,0 +1,103 @@ +#include "gtest/gtest.h" +#include "common/profileprovider.h" +#include "common/dbconnector.h" +#include "common/configdb.h" + +using namespace std; +using namespace swss; + +static string profile_table = "INTERFACE"; +static string profile_key = "TEST_INTERFACE"; +static string profile_field = "profile"; +static string profile_value = "value"; + +void clearDB(const string &dbName) +{ + DBConnector db(dbName, 0, true); + RedisReply r(&db, "FLUSHALL", REDIS_REPLY_STATUS); + r.checkStatusOK(); +} + +void initializeProfileDB() +{ + clearDB("PROFILE_DB"); + clearDB("CONFIG_DB"); + + auto db = ConfigDBConnector_Native(); + db.db_connect("PROFILE_DB"); + map profile_map = { + { profile_field, profile_value } + }; + db.set_entry(profile_table, profile_key, profile_map); + + auto item = db.get_entry(profile_table, profile_key); + EXPECT_EQ(item[profile_field], profile_value); +} + +TEST(DECORATOR, GetConfigs) +{ + initializeProfileDB(); + + DBConnector db("CONFIG_DB", 0, true); + + auto configs = ProfileProvider::instance().getConfigs(&db); + EXPECT_EQ(configs[profile_table][profile_key][profile_field], profile_value); + + auto item = ProfileProvider::instance().getConfigs(profile_table, profile_key, &db); + EXPECT_EQ(item[profile_field], profile_value); + + auto valueptr = ProfileProvider::instance().getConfig(profile_table, profile_key, profile_field, &db); + EXPECT_EQ(*valueptr, profile_value); + + auto keys = ProfileProvider::instance().getKeys(profile_table, &db); + EXPECT_EQ(keys.size(), 1); + EXPECT_EQ(keys[0], profile_key); +} + +TEST(DECORATOR, DeleteAndRevertProfile) +{ + initializeProfileDB(); + + DBConnector db("CONFIG_DB", 0, true); + auto connector = ConfigDBConnector_Native(); + connector.connect(false); + + // test delete + bool result = ProfileProvider::instance().tryDeleteItem(profile_table, profile_key, &db); + EXPECT_EQ(result, true); + + auto deletedItems = connector.get_keys(PROFILE_DELETE_TABLE, false); + EXPECT_EQ(deletedItems.size(), 1); + + auto configs = ProfileProvider::instance().getConfigs(&db); + EXPECT_EQ(configs[profile_table].find(profile_key), configs[profile_table].end()); + + auto item = ProfileProvider::instance().getConfigs(profile_table, profile_key, &db); + EXPECT_EQ(item.size(), 0); + + auto valueptr = ProfileProvider::instance().getConfig(profile_table, profile_key, profile_field, &db); + EXPECT_EQ(valueptr, nullptr); + + auto keys = ProfileProvider::instance().getKeys(profile_table, &db); + EXPECT_EQ(keys.size(), 0); + + // test revert + result = ProfileProvider::instance().tryRevertItem(profile_table, profile_key, &db); + EXPECT_EQ(result, true); + + deletedItems = connector.get_keys(PROFILE_DELETE_TABLE, false); + EXPECT_EQ(deletedItems.size(), 0); + + configs = ProfileProvider::instance().getConfigs(&db); + EXPECT_EQ(configs[profile_table][profile_key][profile_field], profile_value); + + item = ProfileProvider::instance().getConfigs(profile_table, profile_key, &db); + EXPECT_EQ(item[profile_field], profile_value); + + valueptr = ProfileProvider::instance().getConfig(profile_table, profile_key, profile_field, &db); + EXPECT_EQ(*valueptr, profile_value); + + keys = ProfileProvider::instance().getKeys(profile_table, &db); + EXPECT_EQ(keys.size(), 1); + EXPECT_EQ(keys[0], profile_key); +} diff --git a/tests/redis_multi_db_ut_config/database_config.json b/tests/redis_multi_db_ut_config/database_config.json index 141bb65f..7ce7820a 100644 --- a/tests/redis_multi_db_ut_config/database_config.json +++ b/tests/redis_multi_db_ut_config/database_config.json @@ -92,6 +92,11 @@ "separator": ":", "instance": "redis" }, + "PROFILE_DB" : { + "id" : 12, + "separator": "|", + "instance" : "redis" + }, "STATE_DB2" : { "id" : 13, "separator": "|", diff --git a/tests/redis_multi_db_ut_config/database_config0.json b/tests/redis_multi_db_ut_config/database_config0.json index 3d9287e8..45606fc3 100644 --- a/tests/redis_multi_db_ut_config/database_config0.json +++ b/tests/redis_multi_db_ut_config/database_config0.json @@ -72,6 +72,11 @@ "separator": ":", "instance": "redis" }, + "PROFILE_DB" : { + "id" : 12, + "separator": "|", + "instance" : "redis" + }, "STATE_DB2" : { "id" : 13, "separator": "|", diff --git a/tests/redis_multi_db_ut_config/database_config1.json b/tests/redis_multi_db_ut_config/database_config1.json index 58dfb7f7..03637eb8 100644 --- a/tests/redis_multi_db_ut_config/database_config1.json +++ b/tests/redis_multi_db_ut_config/database_config1.json @@ -72,6 +72,11 @@ "separator": ":", "instance": "redis" }, + "PROFILE_DB" : { + "id" : 12, + "separator": "|", + "instance" : "redis" + }, "STATE_DB2" : { "id" : 13, "separator": "|",