common/table.cpp (214 lines of code) (raw):

#include <hiredis/hiredis.h> #include <system_error> #include "common/table.h" #include "common/logger.h" #include "common/redisreply.h" #include "common/rediscommand.h" #include "common/redisapi.h" #include <nlohmann/json.hpp> using namespace std; using namespace swss; using json = nlohmann::json; // NOTE: Vertical bar ('|') is the new standard for table name separator // moving forward. We plan to eventually deprecate the colon separator // and transition all databases to use the vertical bar. const std::string TableBase::TABLE_NAME_SEPARATOR_COLON = ":"; const std::string TableBase::TABLE_NAME_SEPARATOR_VBAR = "|"; // NOTE: this map is deprecated and will be removed in the future, and new DBs should instead be added to database_config.json // Currently it is not used in C++ projects, only used in pyext python, only for backward compatibility purpose const TableNameSeparatorMap TableBase::tableNameSeparatorMap = { { APPL_DB, TABLE_NAME_SEPARATOR_COLON }, { ASIC_DB, TABLE_NAME_SEPARATOR_COLON }, { COUNTERS_DB, TABLE_NAME_SEPARATOR_COLON }, { LOGLEVEL_DB, TABLE_NAME_SEPARATOR_COLON }, { CONFIG_DB, TABLE_NAME_SEPARATOR_VBAR }, { PFC_WD_DB, TABLE_NAME_SEPARATOR_COLON }, { FLEX_COUNTER_DB, TABLE_NAME_SEPARATOR_COLON }, { STATE_DB, TABLE_NAME_SEPARATOR_VBAR }, { APPL_STATE_DB, TABLE_NAME_SEPARATOR_COLON } }; Table::Table(const DBConnector *db, const string &tableName) : Table(new RedisPipeline(db, 1), tableName, false) { m_pipeowned = true; } Table::Table(RedisPipeline *pipeline, const string &tableName, bool buffered) : TableBase(tableName, SonicDBConfig::getSeparator(pipeline->getDBConnector())) , m_buffered(buffered) , m_pipeowned(false) , m_pipe(pipeline) { } Table::~Table() { if (m_pipeowned) { delete m_pipe; } } void Table::setBuffered(bool buffered) { m_buffered = buffered; } void Table::flush() { m_pipe->flush(); } bool Table::get(const string &key, vector<FieldValueTuple> &values) { RedisCommand hgetall_key; hgetall_key.format("HGETALL %s", getKeyName(key).c_str()); RedisReply r = m_pipe->push(hgetall_key, REDIS_REPLY_ARRAY); redisReply *reply = r.getContext(); values.clear(); if (!reply->elements) return false; if (reply->elements & 1) throw system_error(make_error_code(errc::address_not_available), "Unable to connect netlink socket"); for (unsigned int i = 0; i < reply->elements; i += 2) { values.emplace_back(stripSpecialSym(reply->element[i]->str), string(reply->element[i + 1]->str, reply->element[i + 1]->len)); } return true; } bool Table::hget(const string &key, const std::string &field, std::string &value) { RedisCommand hget_entry; hget_entry.format("HGET %s %s", getKeyName(key).c_str(), field.c_str()); RedisReply r = m_pipe->push(hget_entry); redisReply *reply = r.getContext(); if (reply->type == REDIS_REPLY_NIL) { value.clear(); return false; } if (reply->type != REDIS_REPLY_STRING) throw system_error(make_error_code(errc::io_error), "Got unexpected reply type"); value = stripSpecialSym(reply->str); return true; } void Table::hset(const string &key, const std::string &field, const std::string &value, const string& /*op*/, const string& /*prefix*/) { RedisCommand cmd; cmd.formatHSET(getKeyName(key), field, value); m_pipe->push(cmd, REDIS_REPLY_INTEGER); if (!m_buffered) { m_pipe->flush(); } } void Table::set(const string &key, const vector<FieldValueTuple> &values, const string &op, const string &prefix) { set(key, values, op, prefix, DEFAULT_DB_TTL); } // TODO: Implement this without overloading(add an additional ttl param // to existing set() command once sonic-swss's mock_table.cpp and other // dependencies can be updated to use the extended new default set()) void Table::set(const string &key, const vector<FieldValueTuple> &values, const string &op, const string &prefix, const int64_t &ttl) { if (values.size() == 0) return; RedisCommand cmd; cmd.formatHSET(getKeyName(key), values.begin(), values.end()); m_pipe->push(cmd, REDIS_REPLY_INTEGER); if (ttl != DEFAULT_DB_TTL) { // Configure the expire time for the entry that was just added cmd.formatEXPIRE(getKeyName(key), ttl); m_pipe->push(cmd, REDIS_REPLY_INTEGER); } if (!m_buffered) { m_pipe->flush(); } } bool Table::ttl(const string &key, int64_t &reply_value) { RedisCommand cmd_ttl; cmd_ttl.formatTTL(getKeyName(key)); RedisReply r = m_pipe->push(cmd_ttl); redisReply *reply = r.getContext(); if (reply != NULL) { reply_value = reply->integer; return true; } else { return false; } } void Table::del(const string &key, const string& /* op */, const string& /*prefix*/) { RedisCommand del_key; del_key.format("DEL %s", getKeyName(key).c_str()); m_pipe->push(del_key, REDIS_REPLY_INTEGER); } void Table::hdel(const string &key, const string &field, const string& /* op */, const string& /*prefix*/) { RedisCommand cmd; cmd.formatHDEL(getKeyName(key), field); m_pipe->push(cmd, REDIS_REPLY_INTEGER); } void TableEntryEnumerable::getContent(vector<KeyOpFieldsValuesTuple> &tuples) { vector<string> keys; getKeys(keys); tuples.clear(); for (const auto &key: keys) { vector<FieldValueTuple> values; string op = ""; get(key, values); tuples.emplace_back(key, op, values); } } void Table::getKeys(vector<string> &keys) { RedisCommand keys_cmd; keys_cmd.format("KEYS %s%s*", getTableName().c_str(), getTableNameSeparator().c_str()); RedisReply r = m_pipe->push(keys_cmd, REDIS_REPLY_ARRAY); redisReply *reply = r.getContext(); keys.clear(); for (unsigned int i = 0; i < reply->elements; i++) { string key = reply->element[i]->str; keys.push_back(key.substr(getTableName().length()+1)); } } void Table::dump(TableDump& tableDump) { SWSS_LOG_ENTER(); SWSS_LOG_TIMER("getting"); lazyLoadRedisScriptFile(m_pipe->getDBConnector(), "table_dump.lua", m_shaDump); RedisCommand command; command.format("EVALSHA %s 1 %s ''", m_shaDump.c_str(), getTableName().c_str()); RedisReply r = m_pipe->push(command, REDIS_REPLY_STRING); auto ctx = r.getContext(); std::string data = ctx->str; json j = json::parse(data); size_t tableNameLen = getTableName().length() + 1; // + ":" for (json::iterator it = j.begin(); it != j.end(); ++it) { TableMap map; json jj = it.value(); for (json::iterator itt = jj.begin(); itt != jj.end(); ++itt) { if (itt.key() == "NULL") { continue; } map[itt.key()] = itt.value(); } std::string key = it.key().substr(tableNameLen); tableDump[key] = map; } } string Table::stripSpecialSym(const string &key) { size_t pos = key.find('@'); if (pos != key.npos) { return key.substr(0, pos); } return key; }