diff --git a/common/Makefile.am b/common/Makefile.am index 4c400556d..9b7b39e73 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -22,7 +22,7 @@ dist_swsscommon_DATA = $(EXTRA_CONF_DIST) bin_PROGRAMS = swssloglevel if DEBUG -DBGFLAGS = -ggdb -DDEBUG +DBGFLAGS = -ggdb -DDEBUG -g -rdynamic else DBGFLAGS = -g -DNDEBUG endif @@ -32,7 +32,9 @@ libswsscommon_la_SOURCES = \ redisreply.cpp \ configdb.cpp \ dbconnector.cpp \ + dbdecorator.cpp \ dbinterface.cpp \ + defaultvalueprovider.cpp \ sonicv2connector.cpp \ table.cpp \ json.cpp \ diff --git a/common/configdb.cpp b/common/configdb.cpp index 6d8747bf3..84906c40c 100644 --- a/common/configdb.cpp +++ b/common/configdb.cpp @@ -9,10 +9,23 @@ using namespace std; using namespace swss; ConfigDBConnector_Native::ConfigDBConnector_Native(bool use_unix_socket_path, const char *netns) + : ConfigDBConnector_Native(false, use_unix_socket_path, netns) +{ +} + +ConfigDBConnector_Native::ConfigDBConnector_Native(bool get_default_value, bool use_unix_socket_path, const char *netns) : SonicV2Connector_Native(use_unix_socket_path, netns) , m_table_name_separator("|") , m_key_separator("|") + , m_get_default_value(get_default_value) { + // TODO: [Hua] remove this because only for demo#ifdef DEBUG +#ifdef DEBUG + if (DefaultValueProvider::FeatureEnabledByEnvironmentVariable()) + { + m_get_default_value = true; + } +#endif } void ConfigDBConnector_Native::db_connect(string db_name, bool wait_for_init, bool retry_on) @@ -20,10 +33,17 @@ void ConfigDBConnector_Native::db_connect(string db_name, bool wait_for_init, bo m_db_name = db_name; m_key_separator = m_table_name_separator = get_db_separator(db_name); SonicV2Connector_Native::connect(m_db_name, retry_on); + + // attach decorator to client + auto& client = get_redis_client(m_db_name); + if (m_get_default_value) + { + auto decorator = ConfigDBReadDecorator::Create(m_table_name_separator); + client.setDBDecorator(decorator); + } if (wait_for_init) { - auto& client = get_redis_client(m_db_name); auto pubsub = client.pubsub(); auto initialized = client.get(INIT_INDICATOR); if (!initialized || initialized->empty()) @@ -280,7 +300,12 @@ std::string ConfigDBConnector_Native::getDbName() const } ConfigDBPipeConnector_Native::ConfigDBPipeConnector_Native(bool use_unix_socket_path, const char *netns) - : ConfigDBConnector_Native(use_unix_socket_path, netns) + : ConfigDBConnector_Native(false, use_unix_socket_path, netns) +{ +} + +ConfigDBPipeConnector_Native::ConfigDBPipeConnector_Native(bool get_default_value, bool use_unix_socket_path, const char *netns) + : ConfigDBConnector_Native(get_default_value, use_unix_socket_path, netns) { } @@ -490,6 +515,13 @@ int ConfigDBPipeConnector_Native::_get_config(DBConnector& client, RedisTransact string value = r.getChild(i+1)->str; dataentry.emplace(field, value); } + + // Because run Redis command with pipe not use DBConnector, so need decorate result here. + auto& db_decorator = client.getDBDecorator(ReadDecorator); + if (db_decorator != nullptr) + { + db_decorator->decorate(key, dataentry); + } } return cur; } diff --git a/common/configdb.h b/common/configdb.h index d41a49cec..43302ad20 100644 --- a/common/configdb.h +++ b/common/configdb.h @@ -12,7 +12,11 @@ class ConfigDBConnector_Native : public SonicV2Connector_Native public: static constexpr const char *INIT_INDICATOR = "CONFIG_DB_INITIALIZED"; +#ifndef SWIG + [[deprecated("Please use ConfigDBConnector_Native(bool use_unix_socket_path = false, const char *netns = "", bool get_default_value = false) instead.")]] +#endif ConfigDBConnector_Native(bool use_unix_socket_path = false, const char *netns = ""); + ConfigDBConnector_Native(bool get_default_value, bool use_unix_socket_path = false, const char *netns = ""); void db_connect(std::string db_name, bool wait_for_init = false, bool retry_on = false); void connect(bool wait_for_init = true, bool retry_on = false); @@ -35,6 +39,7 @@ class ConfigDBConnector_Native : public SonicV2Connector_Native std::string m_key_separator = "|"; std::string m_db_name; + bool m_get_default_value; }; #ifdef SWIG @@ -48,7 +53,12 @@ class ConfigDBConnector_Native : public SonicV2Connector_Native raise ValueError('decode_responses must be True if specified, False is not supported') if namespace is None: namespace = '' - super(ConfigDBConnector, self).__init__(use_unix_socket_path = use_unix_socket_path, namespace = namespace) + + get_default_value = False + if 'get_default_value' in kwargs and kwargs.pop('get_default_value') == True: + get_default_value = True + + super(ConfigDBConnector, self).__init__(get_default_value = get_default_value, use_unix_socket_path = use_unix_socket_path, namespace = namespace) # Trick: to achieve static/instance method "overload", we must use initize the function in ctor # ref: https://stackoverflow.com/a/28766809/2514803 @@ -242,7 +252,11 @@ class ConfigDBConnector_Native : public SonicV2Connector_Native class ConfigDBPipeConnector_Native: public ConfigDBConnector_Native { public: +#ifndef SWIG + [[deprecated("Please use ConfigDBPipeConnector_Native(bool get_default_value, bool use_unix_socket_path = false, const char *netns = "") instead.")]] +#endif ConfigDBPipeConnector_Native(bool use_unix_socket_path = false, const char *netns = ""); + ConfigDBPipeConnector_Native(bool get_default_value, bool use_unix_socket_path = false, const char *netns = ""); void set_entry(std::string table, std::string key, const std::map& data) override; void mod_config(const std::map>>& data) override; diff --git a/common/dbconnector.cpp b/common/dbconnector.cpp index 13f90df96..f9a771dd4 100644 --- a/common/dbconnector.cpp +++ b/common/dbconnector.cpp @@ -9,6 +9,7 @@ #include "logger.h" #include "common/dbconnector.h" +#include "common/dbdecorator.h" #include "common/redisreply.h" #include "common/redisapi.h" #include "common/pubsub.h" @@ -511,6 +512,7 @@ DBConnector::DBConnector(const DBConnector &other) : RedisContext(other) , m_dbId(other.m_dbId) , m_namespace(other.m_namespace) + , m_db_decorators(other.m_db_decorators) { select(this); } @@ -607,6 +609,7 @@ DBConnector *DBConnector::newConnector(unsigned int timeout) const ret->m_dbName = m_dbName; ret->setNamespace(getNamespace()); + ret->m_db_decorators = m_db_decorators; return ret; } @@ -780,7 +783,15 @@ shared_ptr DBConnector::hget(const string &key, const string &field) if (reply->type == REDIS_REPLY_NIL) { - return shared_ptr(NULL); + auto dbdecortor = this->getDBDecorator(ReadDecorator); + if (dbdecortor) + { + return dbdecortor->decorate(key, field); + } + else + { + return shared_ptr(NULL); + } } if (reply->type == REDIS_REPLY_STRING) @@ -915,3 +926,90 @@ void DBConnector::del(const std::vector& keys) RedisReply r(this, command, REDIS_REPLY_NIL); } + + +const std::shared_ptr DBConnector::setDBDecorator(std::shared_ptr &db_decorator) +{ + auto type = db_decorator->type(); + auto existed = getDBDecorator(type); + m_db_decorators[type] = db_decorator; + return existed; +} + +const std::shared_ptr DBConnector::getDBDecorator(swss::DBDecoratorType type) const +{ + auto existed = m_db_decorators.find(type); + std::shared_ptr existedDecorator = nullptr; + if (existed != m_db_decorators.end()) { + existedDecorator = existed->second; + } + + return existedDecorator; +} + +const DecoratorMapping &DBConnector::getDBDecorators() const +{ + return m_db_decorators; +} + +// TODO: Need discussion connector design, remove following code after discussion. +/* +CfgDBConnector::CfgDBConnector(const DBConnector &other, bool getDefaultValue) + : DBConnector(other) + , m_get_default_value(getDefaultValue) +{ + if (getDefaultValue) + { + m_db_decorator = make_shared(); + } +} + +CfgDBConnector::CfgDBConnector(int dbId, const RedisContext &ctx, bool getDefaultValue) + : DBConnector(dbId, ctx) + , m_get_default_value(getDefaultValue) +{ + if (getDefaultValue) + { + m_db_decorator = make_shared(); + } +} + +CfgDBConnector::CfgDBConnector(int dbId, const std::string &hostname, int port, unsigned int timeout, bool getDefaultValue) + : DBConnector(dbId, hostname, port, timeout) + , m_get_default_value(getDefaultValue) +{ + if (getDefaultValue) + { + m_db_decorator = make_shared(); + } +} + +CfgDBConnector::CfgDBConnector(const std::string &dbName, unsigned int timeout, bool getDefaultValue, bool isTcpConn) + : DBConnector(dbName, timeout, isTcpConn) + , m_get_default_value(getDefaultValue) +{ + if (getDefaultValue) + { + m_db_decorator = make_shared(); + } +} + +CfgDBConnector::CfgDBConnector(const std::string &dbName, unsigned int timeout, bool isTcpConn, const std::string &netns, bool getDefaultValue) + : DBConnector(dbName, timeout, isTcpConn, netns) + , m_get_default_value(getDefaultValue) +{ + if (getDefaultValue) + { + m_db_decorator = make_shared(); + } +} + +CfgDBConnector::~CfgDBConnector() +{ +} + +std::shared_ptr CfgDBConnector::getDBDecortor() const +{ + return m_db_decorator; +} +*/ diff --git a/common/dbconnector.h b/common/dbconnector.h index bdd410f55..462684714 100644 --- a/common/dbconnector.h +++ b/common/dbconnector.h @@ -9,6 +9,7 @@ #include #include +#include "dbdecorator.h" #include "rediscommand.h" #include "redisreply.h" #define EMPTY_NAMESPACE std::string() @@ -18,6 +19,8 @@ namespace swss { class DBConnector; class PubSub; +typedef std::map > DecoratorMapping; + class RedisInstInfo { public: @@ -243,6 +246,13 @@ class DBConnector : public RedisContext bool flushdb(); + // every DBDecoratorType can ony have 1 decorator, set new one will return old one. + const std::shared_ptr setDBDecorator(std::shared_ptr &db_decorator); + + const std::shared_ptr getDBDecorator(swss::DBDecoratorType type) const; + + const DecoratorMapping &getDBDecorators() const; + private: void setNamespace(const std::string &netns); @@ -251,6 +261,8 @@ class DBConnector : public RedisContext std::string m_namespace; std::string m_shaRedisMulti; + + DecoratorMapping m_db_decorators; }; template @@ -276,6 +288,12 @@ void DBConnector::hgetall(const std::string &key, OutputIterator result) *result = std::make_pair(ctx->element[i]->str, ctx->element[i+1]->str); ++result; } + + auto dbdecortor = this->getDBDecorator(ReadDecorator); + if (dbdecortor) + { + dbdecortor->decorate(key, ctx, result); + } } #endif @@ -287,5 +305,28 @@ void DBConnector::hmset(const std::string &key, InputIterator start, InputIterat RedisReply r(this, shmset, REDIS_REPLY_STATUS); } +/* +// TODO: need more discussion about the API design, use may both want get/not get default vaule when running time +class CfgDBConnector : public DBConnector +{ + explicit CfgDBConnector(const DBConnector &other, bool getDefaultValue); + CfgDBConnector(int dbId, const RedisContext &ctx, bool getDefaultValue); + CfgDBConnector(int dbId, const std::string &hostname, int port, unsigned int timeout, bool getDefaultValue); + CfgDBConnector(int dbId, const std::string &unixPath, unsigned int timeout, bool getDefaultValue); + CfgDBConnector(const std::string &dbName, unsigned int timeout, bool getDefaultValue, bool isTcpConn = false); + CfgDBConnector(const std::string &dbName, unsigned int timeout, bool isTcpConn, const std::string &netns, bool getDefaultValue); + CfgDBConnector& operator=(const DBConnector&) = delete; + CfgDBConnector& operator=(const CfgDBConnector&) = delete; + + ~CfgDBConnector(); + + std::shared_ptr getDBDecortor() const override; + +private: + bool m_get_default_value; + std::shared_ptr m_db_decorator; +}; +*/ + } #endif diff --git a/common/dbdecorator.cpp b/common/dbdecorator.cpp new file mode 100644 index 000000000..304e34e01 --- /dev/null +++ b/common/dbdecorator.cpp @@ -0,0 +1,122 @@ +#include +#include +#include + +#include "defaultvalueprovider.h" +#include "dbdecorator.h" +#include "logger.h" +#include "table.h" + +using namespace std; +using namespace swss; + +#define POS(TUPLE) get<0>(TUPLE) +#define TABLE(TUPLE) get<1>(TUPLE) +#define ROW(TUPLE) get<2>(TUPLE) + +ConfigDBReadDecorator::ConfigDBReadDecorator(string separator) + :m_separator(separator) +{ +} + +DBDecoratorType ConfigDBReadDecorator::type() +{ + return ReadDecorator; +} + +template +void ConfigDBReadDecorator::_decorate(const std::string &key, OutputIterator &result) +{ + auto tableAndRow = getTableAndRow(key); + if (POS(tableAndRow) == string::npos) + { + return; + } + + DefaultValueProvider::Instance().AppendDefaultValues(TABLE(tableAndRow), ROW(tableAndRow), result); +} + +void ConfigDBReadDecorator::decorate(const string &key, vector &result) +{ + _decorate(key, result); +} + +void ConfigDBReadDecorator::decorate(const string &key, map &result) +{ + _decorate(key, result); +} + +template +void ConfigDBReadDecorator::_decorate(const std::string &key, redisReply *&ctx, OutputIterator &result) +{ + auto tableAndRow = getTableAndRow(key); + if (POS(tableAndRow) == string::npos) + { + return; + } + + // When DB ID is CONFIG_DB, append default value to config DB result. + map valuesWithDefault; + map existedValues; + for (unsigned int i = 0; i < ctx->elements; i += 2) + { + existedValues[ctx->element[i]->str] = ctx->element[i+1]->str; + valuesWithDefault[ctx->element[i]->str] = ctx->element[i+1]->str; + } + + DefaultValueProvider::Instance().AppendDefaultValues(TABLE(tableAndRow), ROW(tableAndRow), valuesWithDefault); + + for (auto& fieldValuePair : valuesWithDefault) + { + auto findResult = existedValues.find(fieldValuePair.first); + if (findResult == existedValues.end()) + { + *result = make_pair(fieldValuePair.first, fieldValuePair.second); + ++result; + } + } +} + +void ConfigDBReadDecorator::decorate(const string &key, redisReply *&ctx, insert_iterator > &result) +{ + _decorate(key, ctx, result); +} + +void ConfigDBReadDecorator::decorate(const string &key, redisReply *&ctx, insert_iterator > &result) +{ + _decorate(key, ctx, result); +} + +shared_ptr ConfigDBReadDecorator::decorate(const string &key, const string &field) +{ + auto tableAndRow = getTableAndRow(key); + if (POS(tableAndRow) == string::npos) + { + return shared_ptr(NULL); + } + + return DefaultValueProvider::Instance().GetDefaultValue(TABLE(tableAndRow), ROW(tableAndRow), field); +} + +tuple ConfigDBReadDecorator::getTableAndRow(const string &key) +{ + string table; + string row; + size_t pos = key.find(m_separator); + if (pos == string::npos) + { + SWSS_LOG_WARN("Table::get key for config DB is %s, can't find a sepreator\n", key.c_str()); + } + else + { + table = key.substr(0, pos); + row = key.substr(pos + 1); + } + + return tuple(pos, table, row); +} + +shared_ptr ConfigDBReadDecorator::Create(string separator) +{ + return shared_ptr(new ConfigDBReadDecorator(separator)); +} \ No newline at end of file diff --git a/common/dbdecorator.h b/common/dbdecorator.h new file mode 100644 index 000000000..3c90b02fb --- /dev/null +++ b/common/dbdecorator.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class redisReply; + +namespace swss { + +// Decorator only designed for swss-common developer, not for user of swss-common. +// Suggest every decorator type only have 1 decorator class. +// And in DBConnector, every decorator type can only attach 1 instance, attach new instance will return old one. +enum DBDecoratorType +{ + ReadDecorator +}; + +class DBDecorator +{ +public: + virtual DBDecoratorType type() = 0; + virtual void decorate(const std::string &key, std::vector > &result) = 0; + virtual void decorate(const std::string &key, std::map &result) = 0; + virtual void decorate(const std::string &key, redisReply *&ctx, std::insert_iterator > &result) = 0; + virtual void decorate(const std::string &key, redisReply *&ctx, std::insert_iterator > &result) = 0; + virtual std::shared_ptr decorate(const std::string &key, const std::string &field) = 0; +}; + +class ConfigDBReadDecorator : public DBDecorator +{ +public: + static std::shared_ptr Create(std::string separator); + + DBDecoratorType type() override; + void decorate(const std::string &key, std::vector > &result) override; + void decorate(const std::string &key, std::map &result) override; + void decorate(const std::string &key, redisReply *&ctx, std::insert_iterator > &result) override; + void decorate(const std::string &key, redisReply *&ctx, std::insert_iterator > &result) override; + std::shared_ptr decorate(const std::string &key, const std::string &field) override; + +private: + // Because derived class shared_ptr issue, not allow create ConfigDBReadDecorator, please use Create() + ConfigDBReadDecorator(std::string separator); + std::tuple getTableAndRow(const std::string &key); + + // decorate will be invoke in template method, but virtual method not support template, so create internal method for common code. + template + void _decorate(const std::string &key, redisReply *&ctx, OutputIterator &result); + + template + void _decorate(const std::string &key, OutputIterator &result); + + std::string m_separator; +}; + +} diff --git a/common/defaultvalueprovider.cpp b/common/defaultvalueprovider.cpp new file mode 100644 index 000000000..68a081815 --- /dev/null +++ b/common/defaultvalueprovider.cpp @@ -0,0 +1,506 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "defaultvalueprovider.h" +#include "logger.h" +#include "table.h" + +using namespace std; +using namespace swss; + +#ifdef DEBUG +#include +#include +#define TRACE_STACK_SIZE 30 +[[noreturn]] void sigv_handler(int sig) { + void *stackInfo[TRACE_STACK_SIZE]; + size_t stackInfoSize = backtrace(stackInfo, TRACE_STACK_SIZE); + + // print out all the frames to stderr + cerr << "Error: signal " << sig << ":\n" << endl; + backtrace_symbols_fd(stackInfo, (int)stackInfoSize, STDERR_FILENO); + exit(1); +} +#endif + +[[noreturn]] void ThrowRunTimeError(string message) +{ + SWSS_LOG_ERROR("DefaultValueProvider: %s", message.c_str()); + throw std::runtime_error(message); +} + +TableInfoBase::TableInfoBase() +{ + // C++ need this empty ctor +} + +std::shared_ptr TableInfoBase::GetDefaultValue(const std::string &row, const std::string &field) +{ + assert(!row.empty()); + assert(!field.empty()); + + SWSS_LOG_DEBUG("TableInfoBase::GetDefaultValue %s %s\n", row.c_str(), field.c_str()); + FieldDefaultValueMapping *fieldMappingPtr; + if (!FindFieldMappingByKey(row, &fieldMappingPtr)) { + SWSS_LOG_DEBUG("Can't found default value mapping for row %s\n", row.c_str()); + return nullptr; + } + + auto fieldData = fieldMappingPtr->find(field); + if (fieldData == fieldMappingPtr->end()) + { + SWSS_LOG_DEBUG("Can't found default value for field %s\n", field.c_str()); + return nullptr; + } + + SWSS_LOG_DEBUG("Found default value for field %s=%s\n", field.c_str(), fieldData->second.c_str()); + return std::make_shared(fieldData->second); +} + +// existedValues and targetValues can be same container. +void TableInfoBase::AppendDefaultValues(const string &row, FieldValueMapping& existedValues, FieldValueMapping& targetValues) +{ + assert(!row.empty()); + + SWSS_LOG_DEBUG("TableInfoBase::AppendDefaultValues %s\n", row.c_str()); + FieldDefaultValueMapping *fieldMappingPtr; + if (!FindFieldMappingByKey(row, &fieldMappingPtr)) { + SWSS_LOG_DEBUG("Can't found default value mapping for row %s\n", row.c_str()); + return; + } + + for (auto &defaultValue : *fieldMappingPtr) + { + auto fieldData = existedValues.find(defaultValue.first); + if (fieldData != existedValues.end()) + { + // ignore when a field already has value in existedValues + continue; + } + + SWSS_LOG_DEBUG("Append default value: %s=%s\n",defaultValue.first.c_str(), defaultValue.second.c_str()); + targetValues.emplace(defaultValue.first, defaultValue.second); + } +} + +TableInfoDict::TableInfoDict(KeyInfoToDefaultValueInfoMapping &fieldInfoMapping) +{ + for (auto& fieldMapping : fieldInfoMapping) + { + // KeyInfo.first is key value + string keyValue = fieldMapping.first.first; + m_defaultValueMapping.emplace(keyValue, fieldMapping.second); + } +} + +bool TableInfoDict::FindFieldMappingByKey(const string &row, FieldDefaultValueMapping ** foundedMappingPtr) +{ + assert(!row.empty()); + assert(foundedMappingPtr != nullptr); + + SWSS_LOG_DEBUG("TableInfoDict::FindFieldMappingByKey %s\n", row.c_str()); + auto keyResult = m_defaultValueMapping.find(row); + *foundedMappingPtr = keyResult->second.get(); + return keyResult == m_defaultValueMapping.end(); +} + +TableInfoSingleList::TableInfoSingleList(KeyInfoToDefaultValueInfoMapping &fieldInfoMapping) +{ + m_defaultValueMapping = fieldInfoMapping.begin()->second; +} + +bool TableInfoSingleList::FindFieldMappingByKey(const string &row, FieldDefaultValueMapping ** foundedMappingPtr) +{ + assert(!row.empty()); + assert(foundedMappingPtr != nullptr); + + SWSS_LOG_DEBUG("TableInfoSingleList::FindFieldMappingByKey %s\n", row.c_str()); + *foundedMappingPtr = m_defaultValueMapping.get(); + return true; +} + +TableInfoMultipleList::TableInfoMultipleList(KeyInfoToDefaultValueInfoMapping &fieldInfoMapping) +{ + for (auto& fieldMapping : fieldInfoMapping) + { + // KeyInfo.second is field count + int fieldCount = fieldMapping.first.second; + m_defaultValueMapping.emplace(fieldCount, fieldMapping.second); + } +} + +bool TableInfoMultipleList::FindFieldMappingByKey(const string &row, FieldDefaultValueMapping ** foundedMappingPtr) +{ + assert(!row.empty()); + assert(foundedMappingPtr != nullptr); + + SWSS_LOG_DEBUG("TableInfoMultipleList::FindFieldMappingByKey %s\n", row.c_str()); + int fieldCount = (int)std::count(row.begin(), row.end(), '|') + 1; + auto keyInfo = m_defaultValueMapping.find(fieldCount); + + // when not found, key_info still a valied iterator + *foundedMappingPtr = keyInfo->second.get(); + + // return false when not found + return keyInfo != m_defaultValueMapping.end(); +} + +DefaultValueProvider& DefaultValueProvider::Instance() +{ + static DefaultValueProvider instance; + if (instance.m_context == nullptr) + { + instance.Initialize(); + } + + return instance; +} + +shared_ptr DefaultValueProvider::FindDefaultValueInfo(const std::string &table) +{ + assert(!table.empty()); + + SWSS_LOG_DEBUG("DefaultValueProvider::FindDefaultValueInfo %s\n", table.c_str()); + auto findResult = m_defaultValueMapping.find(table); + if (findResult == m_defaultValueMapping.end()) + { + SWSS_LOG_DEBUG("Not found default value info for %s\n", table.c_str()); + return nullptr; + } + + return findResult->second; +} + +std::shared_ptr DefaultValueProvider::GetDefaultValue(const std::string &table, const std::string &row, std::string field) +{ + assert(!table.empty()); + assert(!row.empty()); + assert(!field.empty()); + + SWSS_LOG_DEBUG("DefaultValueProvider::GetDefaultValue %s %s %s\n", table.c_str(), row.c_str(), field.c_str()); +#ifdef DEBUG + if (!FeatureEnabledByEnvironmentVariable()) + { + return nullptr; + } +#endif + + auto defaultValueInfo = FindDefaultValueInfo(table); + if (defaultValueInfo == nullptr) + { + SWSS_LOG_DEBUG("Not found default value info for %s\n", table.c_str()); + return nullptr; + } + + return defaultValueInfo->GetDefaultValue(row, field); +} + +void DefaultValueProvider::AppendDefaultValues(const std::string &table, const std::string &row, std::vector > &values) +{ + assert(!table.empty()); + assert(!row.empty()); + + SWSS_LOG_DEBUG("DefaultValueProvider::AppendDefaultValues %s %s\n", table.c_str(), row.c_str()); +#ifdef DEBUG + if (!FeatureEnabledByEnvironmentVariable()) + { + return; + } +#endif + + auto defaultValueInfo = FindDefaultValueInfo(table); + if (defaultValueInfo == nullptr) + { + SWSS_LOG_DEBUG("Not found default value info for %s\n", table.c_str()); + return; + } + + map existedValues; + map defaultValues; + for (auto& fieldValuePair : values) + { + existedValues.emplace(fieldValuePair.first, fieldValuePair.second); + } + + defaultValueInfo->AppendDefaultValues(row, existedValues, defaultValues); + + for (auto& fieldValuePair : defaultValues) + { + values.emplace_back(fieldValuePair.first, fieldValuePair.second); + } +} + +void DefaultValueProvider::AppendDefaultValues(const string &table, const string &row, FieldValueMapping& values) +{ + assert(!table.empty()); + assert(!row.empty()); + + SWSS_LOG_DEBUG("DefaultValueProvider::AppendDefaultValues %s %s\n", table.c_str(), row.c_str()); +#ifdef DEBUG + if (!FeatureEnabledByEnvironmentVariable()) + { + return; + } +#endif + + auto defaultValueInfo = FindDefaultValueInfo(table); + if (defaultValueInfo == nullptr) + { + SWSS_LOG_DEBUG("Not found default value info for %s\n", table.c_str()); + return; + } + + defaultValueInfo->AppendDefaultValues(row, values, values); +} + +DefaultValueProvider::DefaultValueProvider() +{ +#ifdef DEBUG + // initialize crash event handler for debug. + signal(SIGSEGV, sigv_handler); +#endif +} + + +DefaultValueProvider::~DefaultValueProvider() +{ + if (m_context) + { + // set private_destructor to NULL because no any private data + ly_ctx_destroy(m_context, NULL); + } +} + +void DefaultValueProvider::Initialize(char* modulePath) +{ + assert(modulePath != nullptr && strlen(modulePath) != 0); + assert(m_context == nullptr); + + DIR *moduleDir = opendir(modulePath); + if (!moduleDir) + { + ThrowRunTimeError("Open Yang model path " + string(modulePath) + " failed"); + } + + m_context = ly_ctx_new(modulePath, LY_CTX_ALLIMPLEMENTED); + struct dirent *subDir; + while ((subDir = readdir(moduleDir)) != NULL) + { + if (subDir->d_type == DT_REG) + { + SWSS_LOG_DEBUG("file name: %s\n", subDir->d_name); + string fileName(subDir->d_name); + int pos = (int)fileName.find(".yang"); + string moduleName = fileName.substr(0, pos); + + const struct lys_module *module = ly_ctx_load_module( + m_context, + moduleName.c_str(), + EMPTY_STR); // Use EMPTY_STR to revision to load the latest revision + if (module->data == NULL) + { + // Not every yang file should contains yang model + SWSS_LOG_WARN("Yang file %s does not contains model %s.\n", subDir->d_name, moduleName.c_str()); + continue; + } + + struct lys_node *topLevelNode = module->data; + while (topLevelNode) + { + if (topLevelNode->nodetype != LYS_CONTAINER) + { + SWSS_LOG_DEBUG("ignore top level element %s, tyoe %d\n",topLevelNode->name, topLevelNode->nodetype); + // Config DB table schema is defined by top level container + topLevelNode = topLevelNode->next; + continue; + } + + SWSS_LOG_DEBUG("top level container: %s\n",topLevelNode->name); + auto container = topLevelNode->child; + while (container) + { + SWSS_LOG_DEBUG("container name: %s\n",container->name); + + AppendTableInfoToMapping(container); + container = container->next; + } + + topLevelNode = topLevelNode->next; + } + } + } + closedir(moduleDir); +} + +std::shared_ptr DefaultValueProvider::GetKeyInfo(struct lys_node* tableChildNode) +{ + assert(tableChildNode != nullptr); + SWSS_LOG_DEBUG("DefaultValueProvider::GetKeyInfo %s\n",tableChildNode->name); + + int keyFieldCount = 0; + string keyValue = ""; + if (tableChildNode->nodetype == LYS_LIST) + { + SWSS_LOG_DEBUG("Child list: %s\n",tableChildNode->name); + + // when a top level container contains list, the key defined by the 'keys' field. + struct lys_node_list *listNode = (struct lys_node_list*)tableChildNode; + string key(listNode->keys_str); + keyFieldCount = (int)std::count(key.begin(), key.end(), '|') + 1; + } + else if (tableChildNode->nodetype == LYS_CONTAINER) + { + SWSS_LOG_DEBUG("Child container name: %s\n",tableChildNode->name); + + // when a top level container not contains any list, the key is child container name + keyValue = string(tableChildNode->name); + } + else + { + SWSS_LOG_DEBUG("Ignore child element: %s\n",tableChildNode->name); + return nullptr; + } + + return make_shared(keyValue, keyFieldCount); +} + +FieldDefaultValueMappingPtr DefaultValueProvider::GetDefaultValueInfo(struct lys_node* tableChildNode) +{ + assert(tableChildNode != nullptr); + SWSS_LOG_DEBUG("DefaultValueProvider::GetDefaultValueInfo %s\n",tableChildNode->name); + + auto field = tableChildNode->child; + auto fieldMapping = make_shared(); + while (field) + { + if (field->nodetype == LYS_LEAF) + { + SWSS_LOG_DEBUG("leaf field: %s\n",field->name); + struct lys_node_leaf *leafNode = (struct lys_node_leaf*)field; + if (leafNode->dflt) + { + SWSS_LOG_DEBUG("field: %s, default: %s\n",leafNode->name, leafNode->dflt); + fieldMapping->emplace(string(leafNode->name), string(leafNode->dflt)); + } + } + else if (field->nodetype == LYS_CHOICE) + { + SWSS_LOG_DEBUG("choice field: %s\n",field->name); + struct lys_node_choice *choiceNode = (struct lys_node_choice *)field; + if (choiceNode->dflt) + { + // TODO: convert choice default value to string + SWSS_LOG_DEBUG("choice field: %s, default: TBD\n",choiceNode->name); + } + } + else if (field->nodetype == LYS_LEAFLIST) + { + SWSS_LOG_DEBUG("list field: %s\n",field->name); + struct lys_node_leaflist *listNode = (struct lys_node_leaflist *)field; + if (listNode->dflt) + { + // TODO: convert list default value to json string + SWSS_LOG_DEBUG("list field: %s, default: TBD\n",listNode->name); + } + } +#ifdef DEBUG + else + { + SWSS_LOG_DEBUG("Field %s with type %d does not support default value\n",field->name, field->nodetype); + } +#endif + + field = field->next; + } + + return fieldMapping; +} + +int DefaultValueProvider::BuildFieldMappingList(struct lys_node* table, KeyInfoToDefaultValueInfoMapping &fieldInfoMapping) +{ + assert(table != nullptr); + + int childListCount = 0; + auto nextChild = table->child; + while (nextChild) + { + // get key from schema + auto keyInfo = GetKeyInfo(nextChild); + if (keyInfo == nullptr) + { + nextChild = nextChild->next; + continue; + } + else if (keyInfo->second != 0) + { + // when key field count not 0, it's a list node. + childListCount++; + } + + // get field name to default value mappings from schema + fieldInfoMapping.emplace(*keyInfo, GetDefaultValueInfo(nextChild)); + + nextChild = nextChild->next; + } + + return childListCount; +} + +// Load default value info from yang model and append to default value mapping +void DefaultValueProvider::AppendTableInfoToMapping(struct lys_node* table) +{ + assert(table != nullptr); + + SWSS_LOG_DEBUG("DefaultValueProvider::AppendTableInfoToMapping table name: %s\n",table->name); + KeyInfoToDefaultValueInfoMapping fieldInfoMapping; + int listCount = BuildFieldMappingList(table, fieldInfoMapping); + + // create container data by list count + TableInfoBase* tableInfoPtr = nullptr; + switch (listCount) + { + case 0: + { + tableInfoPtr = new TableInfoDict(fieldInfoMapping); + } + break; + + case 1: + { + tableInfoPtr = new TableInfoSingleList(fieldInfoMapping); + } + break; + + default: + { + tableInfoPtr = new TableInfoMultipleList(fieldInfoMapping); + } + break; + } + + m_defaultValueMapping.emplace(string(table->name), shared_ptr(tableInfoPtr)); +} + +#ifdef DEBUG +bool DefaultValueProvider::FeatureEnabledByEnvironmentVariable() +{ + const char* showDefault = getenv("CONFIG_DB_DEFAULT_VALUE"); + if (showDefault == nullptr || strcmp(showDefault, "TRUE") != 0) + { + // enable feature with "export CONFIG_DB_DEFAULT_VALUE=TRUE" + SWSS_LOG_DEBUG("enable feature with \"export CONFIG_DB_DEFAULT_VALUE=TRUE\"\n"); + return false; + } + + return true; +} +#endif diff --git a/common/defaultvalueprovider.h b/common/defaultvalueprovider.h new file mode 100644 index 000000000..b6fe89568 --- /dev/null +++ b/common/defaultvalueprovider.h @@ -0,0 +1,112 @@ +#pragma once + +#include +#include +#include + +#define DEFAULT_YANG_MODULE_PATH "/usr/local/yang-models" +#define EMPTY_STR "" + +struct ly_ctx; + +// Key information +typedef std::pair KeyInfo; + +// Field name to default value mapping +typedef std::map FieldDefaultValueMapping; +typedef std::map FieldValueMapping; +typedef std::shared_ptr FieldDefaultValueMappingPtr; + +// Key info to default value info mapping +typedef std::map KeyInfoToDefaultValueInfoMapping; + +namespace swss { + +class TableInfoBase +{ +public: + TableInfoBase(); + + void AppendDefaultValues(const std::string &row, FieldValueMapping& sourceValues, FieldValueMapping& targetValues); + + std::shared_ptr GetDefaultValue(const std::string &row, const std::string &field); + +protected: + virtual bool FindFieldMappingByKey(const std::string &row, FieldDefaultValueMapping ** foundedMappingPtr) = 0; +}; + +class TableInfoDict : public TableInfoBase +{ +public: + TableInfoDict(KeyInfoToDefaultValueInfoMapping &fieldInfoMapping); + +private: + // Mapping: key value -> field -> default + std::map m_defaultValueMapping; + + bool FindFieldMappingByKey(const std::string &row, FieldDefaultValueMapping ** foundedMappingPtr); +}; + +class TableInfoSingleList : public TableInfoBase +{ +public: + TableInfoSingleList(KeyInfoToDefaultValueInfoMapping &fieldInfoMapping); + +private: + // Mapping: field -> default + FieldDefaultValueMappingPtr m_defaultValueMapping; + + bool FindFieldMappingByKey(const std::string &row, FieldDefaultValueMapping ** foundedMappingPtr); +}; + +struct TableInfoMultipleList : public TableInfoBase +{ +public: + TableInfoMultipleList(KeyInfoToDefaultValueInfoMapping &fieldInfoMapping); + +private: + // Mapping: key field count -> field -> default + std::map m_defaultValueMapping; + + bool FindFieldMappingByKey(const std::string &row, FieldDefaultValueMapping ** foundedMappingPtr); +}; + +class DefaultValueProvider +{ +public: + static DefaultValueProvider& Instance(); + + void AppendDefaultValues(const std::string &table, const std::string &row, FieldValueMapping& values); + + void AppendDefaultValues(const std::string &table, const std::string &row, std::vector > &values); + + std::shared_ptr GetDefaultValue(const std::string &table, const std::string &row, std::string field); + +#ifdef DEBUG + static bool FeatureEnabledByEnvironmentVariable(); +#endif + +private: + DefaultValueProvider(); + ~DefaultValueProvider(); + + // libyang context + struct ly_ctx *m_context = nullptr; + + // The table name to table default value info mapping + std::map > m_defaultValueMapping; + + void Initialize(char* modulePath = DEFAULT_YANG_MODULE_PATH); + + // Load default value info from yang model and append to default value mapping + void AppendTableInfoToMapping(struct lys_node* table); + + std::shared_ptr FindDefaultValueInfo(const std::string &table); + + int BuildFieldMappingList(struct lys_node* table, KeyInfoToDefaultValueInfoMapping& fieldMappingList); + + std::shared_ptr GetKeyInfo(struct lys_node* table_child_node); + FieldDefaultValueMappingPtr GetDefaultValueInfo(struct lys_node* tableChildNode); +}; + +} diff --git a/common/table.cpp b/common/table.cpp index cc2234d07..9535ae8de 100644 --- a/common/table.cpp +++ b/common/table.cpp @@ -1,6 +1,7 @@ #include #include +#include "common/dbdecorator.h" #include "common/table.h" #include "common/logger.h" #include "common/redisreply.h" @@ -85,6 +86,13 @@ bool Table::get(const string &key, vector &values) reply->element[i + 1]->str); } + // Decorate result because result is not from DBConnection, so it's not decorated + auto& db_decorator = m_pipe->getDBConnector()->getDBDecorator(ReadDecorator); + if (db_decorator != nullptr) + { + db_decorator->decorate(key, values); + } + return true; } diff --git a/configure.ac b/configure.ac index ff909b1cc..3c2e05055 100644 --- a/configure.ac +++ b/configure.ac @@ -48,6 +48,7 @@ fi AC_PATH_PROGS(SWIG, [swig4.0 swig3.0 swig]) CFLAGS_COMMON="" +CFLAGS_COMMON+=" -lyang" CFLAGS_COMMON+=" -ansi" CFLAGS_COMMON+=" -fPIC" CFLAGS_COMMON+=" -std=c++11" diff --git a/pyext/swsscommon.i b/pyext/swsscommon.i index ebf3e65fd..0e9cf3fc7 100644 --- a/pyext/swsscommon.i +++ b/pyext/swsscommon.i @@ -14,6 +14,7 @@ #include "schema.h" #include "dbconnector.h" +#include "dbdecorator.h" #include "dbinterface.h" #include "sonicv2connector.h" #include "pubsub.h" @@ -47,6 +48,9 @@ %include %include +%shared_ptr(swss::DBDecorator); +%shared_ptr(swss::ConfigDBReadDecorator); + %template(FieldValuePair) std::pair; %template(FieldValuePairs) std::vector>; %template(FieldValueMap) std::map; @@ -150,6 +154,7 @@ T castSelectableObj(swss::Selectable *temp) %include "schema.h" %include "dbconnector.h" +%include "dbdecorator.h" %include "sonicv2connector.h" %include "pubsub.h" %include "selectable.h"