common/redisapi.h (118 lines of code) (raw):
#pragma once
#include <unistd.h>
#include <poll.h>
#include <cstring>
#include <stdexcept>
#include <vector>
#include <set>
#include <fstream>
#include <algorithm>
#include "logger.h"
#include "rediscommand.h"
#include "redisreply.h"
#include "dbconnector.h"
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
namespace swss {
static inline std::string loadRedisScript(RedisContext* ctx, const std::string& script)
{
SWSS_LOG_ENTER();
RedisCommand loadcmd;
loadcmd.format("SCRIPT LOAD %s", script.c_str());
RedisReply r(ctx, loadcmd, REDIS_REPLY_STRING);
std::string sha = r.getReply<std::string>();
SWSS_LOG_INFO("lua script %s loaded, sha: %s", script.c_str(), sha.c_str());
return sha;
}
// Hit Redis bug: Lua redis() command arguments must be strings or integers,
// however, an empty string is not accepted
// Bypass by prefix a dummy char and remove it in the lua script
static inline std::string encodeLuaArgument(const std::string& arg)
{
return '`' + arg;
}
inline bool fileExists(const std::string& name)
{
return access(name.c_str(), F_OK) != -1;
}
inline std::string readTextFile(const std::string& path)
{
std::ifstream ifs(path);
if (ifs.good())
{
return std::string((std::istreambuf_iterator<char>(ifs)),
(std::istreambuf_iterator<char>()));
}
SWSS_LOG_THROW("failed to read file: '%s': %s", path.c_str(), strerror(errno));
}
static inline std::string loadLuaScript(const std::string& path)
{
SWSS_LOG_ENTER();
return readTextFile("/usr/share/swss/" + path);
}
static inline std::set<std::string> runRedisScript(RedisContext &ctx, const std::string& sha,
const std::vector<std::string>& keys, const std::vector<std::string>& argv)
{
SWSS_LOG_ENTER();
std::vector<std::string> args;
// Prepare EVALSHA command
// Format is following:
// EVALSHA <sha> <size of KEYS> <KEYS> <ARGV>
args.push_back("EVALSHA");
args.push_back(sha);
args.push_back(std::to_string(keys.size()));
args.insert(args.end(), keys.begin(), keys.end());
args.insert(args.end(), argv.begin(), argv.end());
args.push_back("''");
RedisCommand command;
command.format(args);
std::set<std::string> ret;
try
{
RedisReply r(&ctx, command);
auto reply = r.getContext();
SWSS_LOG_DEBUG("Running lua script %s", sha.c_str());
if (reply->type == REDIS_REPLY_NIL)
{
SWSS_LOG_ERROR("Got EMPTY response type from redis %d", reply->type);
}
else if (reply->type == REDIS_REPLY_INTEGER)
{
SWSS_LOG_DEBUG("Got INTEGER response type from redis %d", reply->type);
ret.emplace(std::to_string(reply->integer));
}
else if (reply->type != REDIS_REPLY_ARRAY)
{
SWSS_LOG_ERROR("Got invalid response type from redis %d", reply->type);
}
else
{
for (size_t i = 0; i < reply->elements; i++)
{
SWSS_LOG_DEBUG("Got element %zu %s", i, reply->element[i]->str);
ret.emplace(reply->element[i]->str);
}
}
}
catch (const std::exception& e)
{
SWSS_LOG_ERROR("Caught exception while running Redis lua script: %s", e.what());
}
catch(...)
{
SWSS_LOG_ERROR("Caught exception while running Redis lua script");
}
return ret;
}
static inline int peekRedisContext(redisContext *c)
{
pollfd fd;
fd.fd = c->fd;
fd.events = POLLIN;
int rc = poll(&fd, 1, 0);
return rc;
}
static inline void lazyLoadRedisScriptFile(RedisContext* ctx, std::string luaPath, std::string &sha)
{
if (sha.empty())
{
std::string luaScript = loadLuaScript(luaPath);
sha = loadRedisScript(ctx, luaScript);
}
}
}