common/dbconnector.h (257 lines of code) (raw):
#ifndef __DBCONNECTOR__
#define __DBCONNECTOR__
#include <string>
#include <vector>
#include <unordered_map>
#include <utility>
#include <map>
#include <memory>
#include <mutex>
#include <boost/functional/hash.hpp>
#include <boost/algorithm/string.hpp>
#include <hiredis/hiredis.h>
#include "rediscommand.h"
#include "redisreply.h"
#define EMPTY_NAMESPACE std::string()
#define EMPTY_CONTAINERNAME std::string()
namespace swss {
class DBConnector;
class PubSub;
class RedisInstInfo
{
public:
std::string unixSocketPath;
std::string hostname;
int port;
};
class SonicDBInfo
{
public:
std::string instName;
int dbId;
std::string separator;
};
struct SonicDBKey
{
/* Container name is used to identify the container that the DB instance is running in.
* Namespace is used to identify the network namespace that the DB instance is running in.
* In our design, we allow multiple containers to share a same namespace.
* So, this combination of container name and namespace is used to uniquely identify a DB instance.
* If the namespace is empty, it means the DB instance is running in the default(host) namespace.
* If the container name is empty, it for adapting the old design that only one DB instance is
* running in a namespace.
*/
std::string containerName;
std::string netns;
SonicDBKey() = default;
SonicDBKey(const std::string &ns) : netns(ns) {}
bool operator==(const SonicDBKey& other) const
{
return containerName == other.containerName && netns == other.netns;
}
bool isEmpty() const
{
return containerName.empty() && netns.empty();
}
std::string toString() const
{
std::vector<std::string> buffer;
buffer.push_back(containerName);
buffer.push_back(netns);
return boost::algorithm::join(buffer, ":");
}
};
struct SonicDBKeyHash
{
std::size_t operator()(const SonicDBKey& k) const
{
std::size_t seed = 0;
boost::hash_combine(seed, k.containerName);
boost::hash_combine(seed, k.netns);
return seed;
}
};
class SonicDBConfig
{
public:
static constexpr const char *DEFAULT_SONIC_DB_CONFIG_FILE = "/var/run/redis/sonic-db/database_config.json";
static constexpr const char *DEFAULT_SONIC_DB_GLOBAL_CONFIG_FILE = "/var/run/redis/sonic-db/database_global.json";
static void initialize(const std::string &file = DEFAULT_SONIC_DB_CONFIG_FILE);
#if defined(SWIG) && defined(SWIGPYTHON)
%pythoncode %{
## TODO: the python function and C++ one is not on-par
@staticmethod
def load_sonic_db_config(sonic_db_file_path=DEFAULT_SONIC_DB_CONFIG_FILE):
SonicDBConfig.initialize(sonic_db_file_path)
%}
#endif
static void initializeGlobalConfig(const std::string &file = DEFAULT_SONIC_DB_GLOBAL_CONFIG_FILE);
#if defined(SWIG) && defined(SWIGPYTHON)
%pythoncode %{
## TODO: the python function and C++ one is not on-par
@staticmethod
def load_sonic_global_db_config(global_db_file_path=DEFAULT_SONIC_DB_GLOBAL_CONFIG_FILE, namespace=None):
SonicDBConfig.initializeGlobalConfig(global_db_file_path)
%}
#endif
static void reset();
static void validateNamespace(const std::string &netns);
static std::string getDbInst(const std::string &dbName, const std::string &netns = EMPTY_NAMESPACE, const std::string &containerName=EMPTY_CONTAINERNAME);
static std::string getDbInst(const std::string &dbName, const SonicDBKey &key);
static int getDbId(const std::string &dbName, const std::string &netns = EMPTY_NAMESPACE, const std::string &containerName=EMPTY_CONTAINERNAME);
static int getDbId(const std::string &dbName, const SonicDBKey &key);
static std::string getSeparator(const std::string &dbName, const std::string &netns = EMPTY_NAMESPACE, const std::string &containerName=EMPTY_CONTAINERNAME);
static std::string getSeparator(const std::string &dbName, const SonicDBKey &key);
static std::string getSeparator(int dbId, const std::string &netns = EMPTY_NAMESPACE, const std::string &containerName=EMPTY_CONTAINERNAME);
static std::string getSeparator(int dbId, const SonicDBKey &key);
static std::string getSeparator(const DBConnector* db);
static std::string getDbSock(const std::string &dbName, const std::string &netns = EMPTY_NAMESPACE, const std::string &containerName=EMPTY_CONTAINERNAME);
static std::string getDbSock(const std::string &dbName, const SonicDBKey &key);
static std::string getDbHostname(const std::string &dbName, const std::string &netns = EMPTY_NAMESPACE, const std::string &containerName=EMPTY_CONTAINERNAME);
static std::string getDbHostname(const std::string &dbName, const SonicDBKey &key);
static int getDbPort(const std::string &dbName, const std::string &netns = EMPTY_NAMESPACE, const std::string &containerName=EMPTY_CONTAINERNAME);
static int getDbPort(const std::string &dbName, const SonicDBKey &key);
static std::vector<std::string> getNamespaces();
static std::vector<SonicDBKey> getDbKeys();
#if defined(SWIG) && defined(SWIGPYTHON)
%pythoncode %{
## TODO: the python function and C++ one is not on-par
@staticmethod
def get_ns_list():
return SonicDBConfig.getNamespaces()
%}
#endif
static std::vector<std::string> getDbList(const std::string &netns = EMPTY_NAMESPACE, const std::string &containerName=EMPTY_CONTAINERNAME);
static std::vector<std::string> getDbList(const SonicDBKey &key);
static bool isInit() { return m_init; };
static bool isGlobalInit() { return m_global_init; };
static std::map<std::string, RedisInstInfo> getInstanceList(const std::string &netns = EMPTY_NAMESPACE, const std::string &containerName=EMPTY_CONTAINERNAME);
static std::map<std::string, RedisInstInfo> getInstanceList(const SonicDBKey &key);
private:
static std::recursive_mutex m_db_info_mutex;
// { {containerName, namespace}, { instName, { unix_socket_path, hostname, port } } }
static std::unordered_map<SonicDBKey, std::map<std::string, RedisInstInfo>, SonicDBKeyHash> m_inst_info;
// { {containerName, namespace}, { dbName, {instName, dbId, separator} } }
static std::unordered_map<SonicDBKey, std::unordered_map<std::string, SonicDBInfo>, SonicDBKeyHash> m_db_info;
// { {containerName, namespace}, { dbId, separator } }
static std::unordered_map<SonicDBKey, std::unordered_map<int, std::string>, SonicDBKeyHash> m_db_separator;
static bool m_init;
static bool m_global_init;
static void parseDatabaseConfig(const std::string &file,
std::map<std::string, RedisInstInfo> &inst_entry,
std::unordered_map<std::string, SonicDBInfo> &db_entry,
std::unordered_map<int, std::string> &separator_entry);
static RedisInstInfo& getRedisInfo(const std::string &dbName, const SonicDBKey &key);
static SonicDBInfo& getDbInfo(const std::string &dbName, const SonicDBKey &key);
};
class RedisContext
{
public:
static constexpr const char *DEFAULT_UNIXSOCKET = "/var/run/redis/redis.sock";
/*
* Connect to Redis DB wither with a hostname:port or unix socket
* Select the database index provided by "db"
*
* Timeout - The time in milisecond until exception is been thrown. For
* infinite wait, set this value to 0
*/
RedisContext(const RedisContext &other);
RedisContext& operator=(const RedisContext&) = delete;
~RedisContext();
redisContext *getContext() const;
/*
* Assign a name to the Redis client used for this connection
* This is helpful when debugging Redis clients using `redis-cli client list`
*/
void setClientName(const std::string& clientName);
std::string getClientName();
protected:
RedisContext();
void initContext(const char *host, int port, const timeval *tv);
void initContext(const char *path, const timeval *tv);
void setContext(redisContext *ctx);
private:
redisContext *m_conn;
};
class DBConnector : public RedisContext
{
public:
static constexpr const char *DEFAULT_UNIXSOCKET = "/var/run/redis/redis.sock";
/*
* Connect to Redis DB wither with a hostname:port or unix socket
* Select the database index provided by "db"
*
* Timeout - The time in milisecond until exception is been thrown. For
* infinite wait, set this value to 0
*/
explicit DBConnector(const DBConnector &other);
DBConnector(int dbId, const RedisContext &ctx);
DBConnector(int dbId, const std::string &hostname, int port, unsigned int timeout_ms);
DBConnector(int dbId, const std::string &unixPath, unsigned int timeout_ms);
DBConnector(const std::string &dbName, unsigned int timeout_ms, bool isTcpConn = false);
DBConnector(const std::string &dbName, unsigned int timeout_ms, bool isTcpConn, const std::string &netns);
DBConnector(const std::string &dbName, unsigned int timeout_ms, bool isTcpConn, const SonicDBKey &key);
DBConnector& operator=(const DBConnector&) = delete;
int getDbId() const;
std::string getDbName() const;
std::string getNamespace() const;
#if defined(SWIG) && defined(SWIGPYTHON)
%pythoncode %{
namespace = property(getNamespace)
%}
#endif
SonicDBKey getDBKey() const;
static void select(DBConnector *db);
/* Create new context to DB */
DBConnector *newConnector(unsigned int timeout) const;
#ifndef SWIG
__attribute__((deprecated))
#endif
PubSub *pubsub();
int64_t del(const std::string &key);
#if defined(SWIG) && defined(SWIGPYTHON)
// SWIG interface file (.i) globally rename map C++ `del` to python `delete`,
// but applications already followed the old behavior of auto renamed `_del`.
// So we implemented old behavior for backward compatibility
// TODO: remove this function after applications use the function name `delete`
%pythoncode %{
def _del(self, *args, **kwargs):
return self.delete(*args, **kwargs)
%}
#endif
bool exists(const std::string &key);
int64_t hdel(const std::string &key, const std::string &field);
int64_t hdel(const std::string &key, const std::vector<std::string> &fields);
void del(const std::vector<std::string>& keys);
template <typename ReturnType=std::unordered_map<std::string, std::string>>
ReturnType hgetall(const std::string &key);
#ifndef SWIG
template <typename OutputIterator>
void hgetall(const std::string &key, OutputIterator result);
#endif
std::vector<std::string> keys(const std::string &key);
std::pair<int, std::vector<std::string>> scan(int cursor = 0, const char *match = "", uint32_t count = 10);
bool set(const std::string &key, const std::string &value);
bool set(const std::string &key, int value);
void hset(const std::string &key, const std::string &field, const std::string &value);
template<typename InputIterator>
void hmset(const std::string &key, InputIterator start, InputIterator stop);
void hmset(const std::unordered_map<std::string, std::vector<std::pair<std::string, std::string>>>& multiHash);
std::shared_ptr<std::string> get(const std::string &key);
std::shared_ptr<std::string> hget(const std::string &key, const std::string &field);
bool hexists(const std::string &key, const std::string &field);
int64_t incr(const std::string &key);
int64_t decr(const std::string &key);
int64_t rpush(const std::string &list, const std::string &item);
std::shared_ptr<std::string> blpop(const std::string &list, int timeout);
void subscribe(const std::string &pattern);
void psubscribe(const std::string &pattern);
void punsubscribe(const std::string &pattern);
int64_t publish(const std::string &channel, const std::string &message);
void config_set(const std::string &key, const std::string &value);
bool flushdb();
std::map<std::string, std::map<std::string, std::map<std::string, std::string>>> getall();
private:
void setNamespace(const std::string &netns);
void setDBKey(const SonicDBKey &key);
int m_dbId;
std::string m_dbName;
SonicDBKey m_key;
};
template <typename ReturnType>
ReturnType DBConnector::hgetall(const std::string &key)
{
ReturnType map;
hgetall(key, std::inserter(map, map.end()));
return map;
}
#ifndef SWIG
template<typename OutputIterator>
void DBConnector::hgetall(const std::string &key, OutputIterator result)
{
RedisCommand shgetall;
shgetall.format("HGETALL %s", key.c_str());
RedisReply r(this, shgetall, REDIS_REPLY_ARRAY);
auto ctx = r.getContext();
for (unsigned int i = 0; i < ctx->elements; i += 2)
{
*result = std::make_pair(ctx->element[i]->str, ctx->element[i+1]->str);
++result;
}
}
#endif
template<typename InputIterator>
void DBConnector::hmset(const std::string &key, InputIterator start, InputIterator stop)
{
RedisCommand shset;
shset.formatHSET(key, start, stop);
RedisReply r(this, shset, REDIS_REPLY_INTEGER);
}
}
#endif