diff --git a/common/dbconnector.cpp b/common/dbconnector.cpp index dc080d9b2..5ad6d0c94 100644 --- a/common/dbconnector.cpp +++ b/common/dbconnector.cpp @@ -16,17 +16,11 @@ using namespace std; namespace swss { -void SonicDBConfig::initialize(const string &file) +void SonicDBConfig::parseDatabaseConfig(const string &file, + std::unordered_map &inst_entry, + std::unordered_map &db_entry, + std::unordered_map &separator_entry) { - - SWSS_LOG_ENTER(); - - if (m_init) - { - SWSS_LOG_ERROR("SonicDBConfig already initialized"); - throw runtime_error("SonicDBConfig already initialized"); - } - ifstream i(file); if (i.good()) { @@ -40,7 +34,7 @@ void SonicDBConfig::initialize(const string &file) string socket = it.value().at("unix_socket_path"); string hostname = it.value().at("hostname"); int port = it.value().at("port"); - m_inst_info[instName] = {socket, {hostname, port}}; + inst_entry[instName] = {socket, hostname, port}; } for (auto it = j["DATABASES"].begin(); it!= j["DATABASES"].end(); it++) @@ -49,12 +43,12 @@ void SonicDBConfig::initialize(const string &file) string instName = it.value().at("instance"); int dbId = it.value().at("id"); string separator = it.value().at("separator"); - m_db_info[dbName] = {instName, dbId, separator}; + db_entry[dbName] = {instName, dbId, separator}; - m_db_separator.emplace(dbId, separator); + separator_entry.emplace(dbId, separator); } - m_init = true; } + catch (domain_error& e) { SWSS_LOG_ERROR("key doesn't exist in json object, NULL value has no iterator >> %s\n", e.what()); @@ -73,32 +67,152 @@ void SonicDBConfig::initialize(const string &file) } } -string SonicDBConfig::getDbInst(const string &dbName) +void SonicDBConfig::initializeGlobalConfig(const string &file) +{ + std::string local_file, dir_name, ns_name; + std::unordered_map db_entry; + std::unordered_map inst_entry; + std::unordered_map separator_entry; + + SWSS_LOG_ENTER(); + + if (m_global_init) + { + SWSS_LOG_ERROR("SonicDBConfig Global config is already initialized"); + return; + } + + + ifstream i(file); + if (i.good()) + { + local_file = dir_name = std::string(); + + // Get the directory name from the file path given as input. + std::string::size_type pos = file.rfind("/"); + if( pos != std::string::npos) + { + dir_name = file.substr(0,pos+1); + } + + try + { + json j; + i >> j; + + for (auto& element : j["INCLUDES"]) + { + local_file.append(dir_name); + local_file.append(element["include"]); + + if(element["namespace"].empty()) + { + // If database_config.json is already initlized via SonicDBConfig::initialize + // skip initializing it here again. + if(m_init) + { + local_file.clear(); + continue; + } + ns_name = EMPTY_NAMESPACE; + } + else + { + ns_name = element["namespace"]; + } + + parseDatabaseConfig(local_file, inst_entry, db_entry, separator_entry); + m_inst_info[ns_name] = inst_entry; + m_db_info[ns_name] = db_entry; + m_db_separator[ns_name] = separator_entry; + + inst_entry.clear(); + db_entry.clear(); + separator_entry.clear(); + local_file.clear(); + } + } + + catch (domain_error& e) + { + SWSS_LOG_ERROR("key doesn't exist in json object, NULL value has no iterator >> %s\n", e.what()); + throw runtime_error("key doesn't exist in json object, NULL value has no iterator >> " + string(e.what())); + } + catch (exception &e) + { + SWSS_LOG_ERROR("Sonic database config file syntax error >> %s\n", e.what()); + throw runtime_error("Sonic database config file syntax error >> " + string(e.what())); + } + } + else + { + SWSS_LOG_ERROR("Sonic database global config file doesn't exist at %s\n", file.c_str()); + throw runtime_error("Sonic database global config file doesn't exist at " + file); + } + + // Set it as the global config file is already parsed and init done. + m_global_init = true; + + // Make regular init also done + m_init = true; +} + +void SonicDBConfig::initialize(const string &file, const string &nameSpace) +{ + std::unordered_map db_entry; + std::unordered_map inst_entry; + std::unordered_map separator_entry; + + SWSS_LOG_ENTER(); + + if (m_init) + { + SWSS_LOG_ERROR("SonicDBConfig already initialized"); + throw runtime_error("SonicDBConfig already initialized"); + } + + // namespace string is empty, use the file given as input to parse. + if(nameSpace.empty()) + { + parseDatabaseConfig(file, inst_entry, db_entry, separator_entry); + m_inst_info[EMPTY_NAMESPACE] = inst_entry; + m_db_info[EMPTY_NAMESPACE] = db_entry; + m_db_separator[EMPTY_NAMESPACE] = separator_entry; + } + else + // namespace is not empty, use DEFAULT_SONIC_DB_GLOBAL_CONFIG_FILE. + initializeGlobalConfig(); + + // Set it as the config file is already parsed and init done. + m_init = true; +} + +string SonicDBConfig::getDbInst(const string &dbName, const string &nameSpace) { if (!m_init) - initialize(); - return m_db_info.at(dbName).instName; + initialize(DEFAULT_SONIC_DB_CONFIG_FILE, nameSpace); + return m_db_info[nameSpace].at(dbName).instName; } -int SonicDBConfig::getDbId(const string &dbName) +int SonicDBConfig::getDbId(const string &dbName, const string &nameSpace) { if (!m_init) - initialize(); - return m_db_info.at(dbName).dbId; + initialize(DEFAULT_SONIC_DB_CONFIG_FILE, nameSpace); + return m_db_info[nameSpace].at(dbName).dbId; } -string SonicDBConfig::getSeparator(const string &dbName) +string SonicDBConfig::getSeparator(const string &dbName, const string &nameSpace) { if (!m_init) - initialize(); - return m_db_info.at(dbName).separator; + initialize(DEFAULT_SONIC_DB_CONFIG_FILE, nameSpace); + return m_db_info[nameSpace].at(dbName).separator; } -string SonicDBConfig::getSeparator(int dbId) +string SonicDBConfig::getSeparator(int dbId, const string &nameSpace) { if (!m_init) - initialize(); - return m_db_separator.at(dbId); + initialize(DEFAULT_SONIC_DB_CONFIG_FILE, nameSpace); + return m_db_separator[nameSpace].at(dbId); } string SonicDBConfig::getSeparator(const DBConnector* db) @@ -109,42 +223,61 @@ string SonicDBConfig::getSeparator(const DBConnector* db) } string dbName = db->getDbName(); + string nameSpace = db->getNamespace(); if (dbName.empty()) { - return getSeparator(db->getDbId()); + return getSeparator(db->getDbId(), nameSpace); } else { - return getSeparator(dbName); + return getSeparator(dbName, nameSpace); } } -string SonicDBConfig::getDbSock(const string &dbName) +string SonicDBConfig::getDbSock(const string &dbName, const string &nameSpace) { if (!m_init) - initialize(); - return m_inst_info.at(getDbInst(dbName)).first; + initialize(DEFAULT_SONIC_DB_CONFIG_FILE, nameSpace); + return m_inst_info[nameSpace].at(getDbInst(dbName)).unixSocketPath; } -string SonicDBConfig::getDbHostname(const string &dbName) +string SonicDBConfig::getDbHostname(const string &dbName, const string &nameSpace) { if (!m_init) - initialize(); - return m_inst_info.at(getDbInst(dbName)).second.first; + initialize(DEFAULT_SONIC_DB_CONFIG_FILE, nameSpace); + return m_inst_info[nameSpace].at(getDbInst(dbName)).hostname; } -int SonicDBConfig::getDbPort(const string &dbName) +int SonicDBConfig::getDbPort(const string &dbName, const string &nameSpace) { if (!m_init) - initialize(); - return m_inst_info.at(getDbInst(dbName)).second.second; + initialize(DEFAULT_SONIC_DB_CONFIG_FILE, nameSpace); + return m_inst_info[nameSpace].at(getDbInst(dbName)).port; +} + +vector SonicDBConfig::getNamespaces() +{ + vector list; + + if (!m_global_init) + initializeGlobalConfig(); + + // This API returns back non-empty namespaces. + for (auto it = m_inst_info.cbegin(); it != m_inst_info.cend(); ++it) { + if(!((it->first).empty())) + list.push_back(it->first); + } + + return list; } constexpr const char *SonicDBConfig::DEFAULT_SONIC_DB_CONFIG_FILE; -unordered_map>> SonicDBConfig::m_inst_info; -unordered_map SonicDBConfig::m_db_info; -unordered_map SonicDBConfig::m_db_separator; +constexpr const char *SonicDBConfig::DEFAULT_SONIC_DB_GLOBAL_CONFIG_FILE; +unordered_map> SonicDBConfig::m_inst_info; +unordered_map> SonicDBConfig::m_db_info; +unordered_map> SonicDBConfig::m_db_separator; bool SonicDBConfig::m_init = false; +bool SonicDBConfig::m_global_init = false; constexpr const char *DBConnector::DEFAULT_UNIXSOCKET; @@ -164,7 +297,8 @@ DBConnector::~DBConnector() DBConnector::DBConnector(int dbId, const string& hostname, int port, unsigned int timeout) : - m_dbId(dbId) + m_dbId(dbId), + m_namespace(EMPTY_NAMESPACE) { struct timeval tv = {0, (suseconds_t)timeout * 1000}; @@ -181,7 +315,8 @@ DBConnector::DBConnector(int dbId, const string& hostname, int port, } DBConnector::DBConnector(int dbId, const string& unixPath, unsigned int timeout) : - m_dbId(dbId) + m_dbId(dbId), + m_namespace(EMPTY_NAMESPACE) { struct timeval tv = {0, (suseconds_t)timeout * 1000}; @@ -192,30 +327,31 @@ DBConnector::DBConnector(int dbId, const string& unixPath, unsigned int timeout) if (m_conn->err) throw system_error(make_error_code(errc::address_not_available), - "Unable to connect to redis (unixs-socket)"); + "Unable to connect to redis (unix-socket)"); select(this); } -DBConnector::DBConnector(const string& dbName, unsigned int timeout, bool isTcpConn) - : m_dbId(SonicDBConfig::getDbId(dbName)) +DBConnector::DBConnector(const string& dbName, unsigned int timeout, bool isTcpConn, const string& nameSpace) + : m_dbId(SonicDBConfig::getDbId(dbName, nameSpace)) , m_dbName(dbName) + , m_namespace(nameSpace) { struct timeval tv = {0, (suseconds_t)timeout * 1000}; if (timeout) { if (isTcpConn) - m_conn = redisConnectWithTimeout(SonicDBConfig::getDbHostname(dbName).c_str(), SonicDBConfig::getDbPort(dbName), tv); + m_conn = redisConnectWithTimeout(SonicDBConfig::getDbHostname(dbName, nameSpace).c_str(), SonicDBConfig::getDbPort(dbName, nameSpace), tv); else - m_conn = redisConnectUnixWithTimeout(SonicDBConfig::getDbSock(dbName).c_str(), tv); + m_conn = redisConnectUnixWithTimeout(SonicDBConfig::getDbSock(dbName, nameSpace).c_str(), tv); } else { if (isTcpConn) - m_conn = redisConnect(SonicDBConfig::getDbHostname(dbName).c_str(), SonicDBConfig::getDbPort(dbName)); + m_conn = redisConnect(SonicDBConfig::getDbHostname(dbName, nameSpace).c_str(), SonicDBConfig::getDbPort(dbName, nameSpace)); else - m_conn = redisConnectUnix(SonicDBConfig::getDbSock(dbName).c_str()); + m_conn = redisConnectUnix(SonicDBConfig::getDbSock(dbName, nameSpace).c_str()); } if (m_conn->err) @@ -240,19 +376,15 @@ string DBConnector::getDbName() const return m_dbName; } +string DBConnector::getNamespace() const +{ + return m_namespace; +} + DBConnector *DBConnector::newConnector(unsigned int timeout) const { DBConnector *ret; - if (getContext()->connection_type == REDIS_CONN_TCP) - ret = new DBConnector(getDbId(), - getContext()->tcp.host, - getContext()->tcp.port, - timeout); - else - ret = new DBConnector(getDbId(), - getContext()->unix_sock.path, - timeout); - ret->m_dbName = m_dbName; + ret = new DBConnector(getDbName(), timeout, (getContext()->connection_type == REDIS_CONN_TCP), getNamespace()); return ret; } diff --git a/common/dbconnector.h b/common/dbconnector.h index 62fbbde96..4bb6b8612 100644 --- a/common/dbconnector.h +++ b/common/dbconnector.h @@ -7,11 +7,20 @@ #include #include +#define EMPTY_NAMESPACE std::string() namespace swss { class DBConnector; +class RedisInstInfo +{ +public: + std::string unixSocketPath; + std::string hostname; + int port; +}; + class SonicDBInfo { public: @@ -23,26 +32,35 @@ class SonicDBInfo class SonicDBConfig { public: - static void initialize(const std::string &file = DEFAULT_SONIC_DB_CONFIG_FILE); - static std::string getDbInst(const std::string &dbName); - static int getDbId(const std::string &dbName); - static std::string getSeparator(const std::string &dbName); - static std::string getSeparator(int dbId); + static void initialize(const std::string &file = DEFAULT_SONIC_DB_CONFIG_FILE, const std::string &nameSpace = EMPTY_NAMESPACE); + static void initializeGlobalConfig(const std::string &file = DEFAULT_SONIC_DB_GLOBAL_CONFIG_FILE); + static std::string getDbInst(const std::string &dbName, const std::string &nameSpace = EMPTY_NAMESPACE); + static int getDbId(const std::string &dbName, const std::string &nameSpace = EMPTY_NAMESPACE); + static std::string getSeparator(const std::string &dbName, const std::string &nameSpace = EMPTY_NAMESPACE); + static std::string getSeparator(int dbId, const std::string &nameSpace = EMPTY_NAMESPACE); static std::string getSeparator(const DBConnector* db); - static std::string getDbSock(const std::string &dbName); - static std::string getDbHostname(const std::string &dbName); - static int getDbPort(const std::string &dbName); + static std::string getDbSock(const std::string &dbName, const std::string &nameSpace = EMPTY_NAMESPACE); + static std::string getDbHostname(const std::string &dbName, const std::string &nameSpace = EMPTY_NAMESPACE); + static int getDbPort(const std::string &dbName, const std::string &nameSpace = EMPTY_NAMESPACE); + static std::vector getNamespaces(); static bool isInit() { return m_init; }; + static bool isGlobalInit() { return m_global_init; }; private: static constexpr const char *DEFAULT_SONIC_DB_CONFIG_FILE = "/var/run/redis/sonic-db/database_config.json"; - // { instName, { unix_socket_path, {hostname, port} } } - static std::unordered_map>> m_inst_info; - // { dbName, {instName, dbId} } - static std::unordered_map m_db_info; - // { dbIp, separator } - static std::unordered_map m_db_separator; + static constexpr const char *DEFAULT_SONIC_DB_GLOBAL_CONFIG_FILE = "/var/run/redis/sonic-db/database_global.json"; + // { namespace { instName, { unix_socket_path, hostname, port } } } + static std::unordered_map> m_inst_info; + // { namespace, { dbName, {instName, dbId, separator} } } + static std::unordered_map> m_db_info; + // { namespace, { dbId, separator } } + static std::unordered_map> m_db_separator; static bool m_init; + static bool m_global_init; + static void parseDatabaseConfig(const std::string &file, + std::unordered_map &inst_entry, + std::unordered_map &db_entry, + std::unordered_map &separator_entry); }; class DBConnector @@ -59,13 +77,14 @@ class DBConnector */ DBConnector(int dbId, const std::string &hostname, int port, unsigned int timeout); DBConnector(int dbId, const std::string &unixPath, unsigned int timeout); - DBConnector(const std::string &dbName, unsigned int timeout, bool isTcpConn = false); + DBConnector(const std::string &dbName, unsigned int timeout, bool isTcpConn = false, const std::string &nameSpace = EMPTY_NAMESPACE); ~DBConnector(); redisContext *getContext() const; int getDbId() const; std::string getDbName() const; + std::string getNamespace() const; static void select(DBConnector *db); @@ -84,6 +103,7 @@ class DBConnector redisContext *m_conn; int m_dbId; std::string m_dbName; + std::string m_namespace; }; } diff --git a/common/redisselect.cpp b/common/redisselect.cpp index 465428220..4c76f19f8 100644 --- a/common/redisselect.cpp +++ b/common/redisselect.cpp @@ -16,6 +16,15 @@ int RedisSelect::getFd() { return m_subscribe->getContext()->fd; } +int RedisSelect::getDbConnectorId() +{ + return m_subscribe->getDbId(); +} + +std::string RedisSelect::getDbNamespace() +{ + return m_subscribe->getNamespace(); +} uint64_t RedisSelect::readData() { diff --git a/common/redisselect.h b/common/redisselect.h index 97a70b12b..6c291a4dd 100644 --- a/common/redisselect.h +++ b/common/redisselect.h @@ -20,6 +20,8 @@ class RedisSelect : public Selectable bool hasCachedData() override; bool initializedWithData() override; void updateAfterRead() override; + int getDbConnectorId() override; + std::string getDbNamespace() override; /* Create a new redisContext, SELECT DB and SUBSCRIBE */ void subscribe(DBConnector* db, const std::string &channelName); diff --git a/common/selectable.h b/common/selectable.h index 268b933c8..7de56685a 100644 --- a/common/selectable.h +++ b/common/selectable.h @@ -58,6 +58,16 @@ class Selectable return m_priority; } + virtual int getDbConnectorId() + { + return 0; + } + + virtual std::string getDbNamespace() + { + return std::string(); + } + private: friend class Select; diff --git a/tests/Makefile.am b/tests/Makefile.am index a059ac792..9fbf48ead 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -28,6 +28,7 @@ tests_SOURCES = redis_ut.cpp \ warm_restart_ut.cpp \ redis_multi_db_ut.cpp \ logger_ut.cpp \ + redis_multi_ns_ut.cpp \ fdb_flush.cpp \ main.cpp diff --git a/tests/main.cpp b/tests/main.cpp index 89c4bdb29..2f8c6417f 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -7,6 +7,8 @@ using namespace swss; string existing_file = "./tests/redis_multi_db_ut_config/database_config.json"; string nonexisting_file = "./tests/redis_multi_db_ut_config/database_config_nonexisting.json"; +string global_existing_file = "./tests/redis_multi_db_ut_config/database_global.json"; +string global_nonexisting_file = "./tests/redis_multi_db_ut_config/database_global_nonexisting.json"; class SwsscommonEnvironment : public ::testing::Environment { public: @@ -32,6 +34,28 @@ class SwsscommonEnvironment : public ::testing::Environment { SonicDBConfig::initialize(existing_file); cout<<"INIT: load local db config file, isInit = "< +#include +#include "gtest/gtest.h" +#include "common/dbconnector.h" +#include "common/json.hpp" +#include + +using namespace std; +using namespace swss; +using json = nlohmann::json; + +extern string global_existing_file; + +TEST(DBConnector, multi_ns_test) +{ + std::string local_file, dir_name, ns_name; + vector namespaces; + vector ns_names; + + // load global config file again, should throw exception with init already done + try + { + cout<<"INIT: loading local config file again"<> g; + + // Get the directory name from the file path given as input. + std::string::size_type pos = global_existing_file.rfind("/"); + dir_name = global_existing_file.substr(0,pos+1); + + for (auto& element : g["INCLUDES"]) + { + local_file.append(dir_name); + local_file.append(element["include"]); + + if(element["namespace"].empty()) + { + ns_name = EMPTY_NAMESPACE; + } + else + { + ns_name = element["namespace"]; + namespaces.push_back(ns_name); + } + + // parse config file + ifstream i(local_file); + + if (i.good()) + { + json j; + i >> j; + unordered_map m_inst_info; + for (auto it = j["INSTANCES"].begin(); it!= j["INSTANCES"].end(); it++) + { + string instName = it.key(); + string socket = it.value().at("unix_socket_path"); + string hostname = it.value().at("hostname"); + int port = it.value().at("port"); + m_inst_info[instName] = {socket, hostname, port}; + } + + for (auto it = j["DATABASES"].begin(); it!= j["DATABASES"].end(); it++) + { + string dbName = it.key(); + string instName = it.value().at("instance"); + int dbId = it.value().at("id"); + cout<<"testing "<