mirror of
https://github.com/dragonflydb/dragonfly.git
synced 2025-05-11 10:25:47 +02:00
feat(server): Add support for command aliasing (#4932)
Add support for command aliasing using command_alias flag Signed-off-by: Abhijat Malviya <abhijat@dragonflydb.io>
This commit is contained in:
parent
7ffe812967
commit
0fafa21722
10 changed files with 124 additions and 84 deletions
|
@ -591,7 +591,7 @@ void AclFamily::DryRun(CmdArgList args, const CommandContext& cmd_cntx) {
|
||||||
|
|
||||||
string command = absl::AsciiStrToUpper(ArgS(args, 1));
|
string command = absl::AsciiStrToUpper(ArgS(args, 1));
|
||||||
auto* cid = cmd_registry_->Find(command);
|
auto* cid = cmd_registry_->Find(command);
|
||||||
if (!cid) {
|
if (!cid || cid->IsAlias()) {
|
||||||
auto error = absl::StrCat("Command '", command, "' not found");
|
auto error = absl::StrCat("Command '", command, "' not found");
|
||||||
rb->SendError(error);
|
rb->SendError(error);
|
||||||
return;
|
return;
|
||||||
|
@ -1062,7 +1062,7 @@ std::pair<AclFamily::OptCommand, bool> AclFamily::MaybeParseAclCommand(
|
||||||
std::string_view command) const {
|
std::string_view command) const {
|
||||||
if (absl::StartsWith(command, "+")) {
|
if (absl::StartsWith(command, "+")) {
|
||||||
auto res = cmd_registry_->Find(command.substr(1));
|
auto res = cmd_registry_->Find(command.substr(1));
|
||||||
if (!res) {
|
if (!res || res->IsAlias()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
std::pair<size_t, uint64_t> cmd{res->GetFamily(), res->GetBitIndex()};
|
std::pair<size_t, uint64_t> cmd{res->GetFamily(), res->GetBitIndex()};
|
||||||
|
@ -1071,7 +1071,7 @@ std::pair<AclFamily::OptCommand, bool> AclFamily::MaybeParseAclCommand(
|
||||||
|
|
||||||
if (absl::StartsWith(command, "-")) {
|
if (absl::StartsWith(command, "-")) {
|
||||||
auto res = cmd_registry_->Find(command.substr(1));
|
auto res = cmd_registry_->Find(command.substr(1));
|
||||||
if (!res) {
|
if (!res || res->IsAlias()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
std::pair<size_t, uint64_t> cmd{res->GetFamily(), res->GetBitIndex()};
|
std::pair<size_t, uint64_t> cmd{res->GetFamily(), res->GetBitIndex()};
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
using namespace testing;
|
using namespace testing;
|
||||||
|
|
||||||
ABSL_DECLARE_FLAG(std::vector<std::string>, rename_command);
|
ABSL_DECLARE_FLAG(std::vector<std::string>, rename_command);
|
||||||
|
ABSL_DECLARE_FLAG(std::vector<std::string>, command_alias);
|
||||||
|
|
||||||
namespace dfly {
|
namespace dfly {
|
||||||
|
|
||||||
|
@ -29,6 +30,7 @@ class AclFamilyTest : public BaseFamilyTest {
|
||||||
class AclFamilyTestRename : public BaseFamilyTest {
|
class AclFamilyTestRename : public BaseFamilyTest {
|
||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
absl::SetFlag(&FLAGS_rename_command, {"ACL=ROCKS"});
|
absl::SetFlag(&FLAGS_rename_command, {"ACL=ROCKS"});
|
||||||
|
absl::SetFlag(&FLAGS_command_alias, {"___SET=SET"});
|
||||||
ResetService();
|
ResetService();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -538,4 +540,22 @@ TEST_F(AclFamilyTest, TestPubSub) {
|
||||||
EXPECT_THAT(vec[9], "resetchannels &foo");
|
EXPECT_THAT(vec[9], "resetchannels &foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(AclFamilyTest, TestAlias) {
|
||||||
|
auto resp = Run({"ACL", "SETUSER", "luke", "+___SET"});
|
||||||
|
EXPECT_THAT(resp, ErrArg("ERR Unrecognized parameter +___SET"));
|
||||||
|
|
||||||
|
resp = Run({"ACL", "SETUSER", "leia", "-___SET"});
|
||||||
|
EXPECT_THAT(resp, ErrArg("ERR Unrecognized parameter -___SET"));
|
||||||
|
|
||||||
|
resp = Run({"ACL", "SETUSER", "anakin", "+SET"});
|
||||||
|
EXPECT_EQ(resp, "OK");
|
||||||
|
|
||||||
|
resp = Run({"ACL", "SETUSER", "jarjar", "allcommands"});
|
||||||
|
EXPECT_EQ(resp, "OK");
|
||||||
|
|
||||||
|
resp = Run({"ACL", "DRYRUN", "jarjar", "___SET"});
|
||||||
|
EXPECT_THAT(resp, ErrArg("ERR Command '___SET' not found"));
|
||||||
|
EXPECT_EQ(Run({"ACL", "DRYRUN", "jarjar", "SET"}), "OK");
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace dfly
|
} // namespace dfly
|
||||||
|
|
|
@ -66,6 +66,10 @@ bool ValidateCommand(const std::vector<uint64_t>& acl_commands, const CommandId&
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (id.IsAlias()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::pair<bool, AclLog::Reason> auth_res;
|
std::pair<bool, AclLog::Reason> auth_res;
|
||||||
|
|
||||||
if (id.IsPubSub() || id.IsShardedPSub()) {
|
if (id.IsPubSub() || id.IsShardedPSub()) {
|
||||||
|
|
|
@ -22,14 +22,16 @@ using namespace std;
|
||||||
ABSL_FLAG(vector<string>, rename_command, {},
|
ABSL_FLAG(vector<string>, rename_command, {},
|
||||||
"Change the name of commands, format is: <cmd1_name>=<cmd1_new_name>, "
|
"Change the name of commands, format is: <cmd1_name>=<cmd1_new_name>, "
|
||||||
"<cmd2_name>=<cmd2_new_name>");
|
"<cmd2_name>=<cmd2_new_name>");
|
||||||
ABSL_FLAG(vector<string>, command_alias, {},
|
|
||||||
"Add an alias for given commands, format is: <alias>=<original>, "
|
|
||||||
"<alias>=<original>");
|
|
||||||
ABSL_FLAG(vector<string>, restricted_commands, {},
|
ABSL_FLAG(vector<string>, restricted_commands, {},
|
||||||
"Commands restricted to connections on the admin port");
|
"Commands restricted to connections on the admin port");
|
||||||
|
|
||||||
ABSL_FLAG(vector<string>, oom_deny_commands, {},
|
ABSL_FLAG(vector<string>, oom_deny_commands, {},
|
||||||
"Additinal commands that will be marked as denyoom");
|
"Additinal commands that will be marked as denyoom");
|
||||||
|
|
||||||
|
ABSL_FLAG(vector<string>, command_alias, {},
|
||||||
|
"Add an alias for given command(s), format is: <alias>=<original>, <alias>=<original>. "
|
||||||
|
"Aliases must be set identically on replicas, if applicable");
|
||||||
|
|
||||||
namespace dfly {
|
namespace dfly {
|
||||||
|
|
||||||
using namespace facade;
|
using namespace facade;
|
||||||
|
@ -75,16 +77,17 @@ uint32_t ImplicitAclCategories(uint32_t mask) {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
absl::flat_hash_map<std::string, std::string> ParseCmdlineArgMap(
|
using CmdLineMapping = absl::flat_hash_map<std::string, std::string>;
|
||||||
const absl::Flag<std::vector<std::string>>& flag, const bool allow_duplicates = false) {
|
|
||||||
|
CmdLineMapping ParseCmdlineArgMap(const absl::Flag<std::vector<std::string>>& flag) {
|
||||||
const auto& mappings = absl::GetFlag(flag);
|
const auto& mappings = absl::GetFlag(flag);
|
||||||
absl::flat_hash_map<std::string, std::string> parsed_mappings;
|
CmdLineMapping parsed_mappings;
|
||||||
parsed_mappings.reserve(mappings.size());
|
parsed_mappings.reserve(mappings.size());
|
||||||
|
|
||||||
for (const std::string& mapping : mappings) {
|
for (const std::string& mapping : mappings) {
|
||||||
std::vector<std::string_view> kv = absl::StrSplit(mapping, '=');
|
absl::InlinedVector<std::string_view, 2> kv = absl::StrSplit(mapping, '=');
|
||||||
if (kv.size() != 2) {
|
if (kv.size() != 2) {
|
||||||
LOG(ERROR) << "Malformed command " << mapping << " for " << flag.Name()
|
LOG(ERROR) << "Malformed command '" << mapping << "' for " << flag.Name()
|
||||||
<< ", expected key=value";
|
<< ", expected key=value";
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
@ -97,8 +100,7 @@ absl::flat_hash_map<std::string, std::string> ParseCmdlineArgMap(
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool inserted = parsed_mappings.emplace(std::move(key), std::move(value)).second;
|
if (!parsed_mappings.emplace(std::move(key), std::move(value)).second) {
|
||||||
if (!allow_duplicates && !inserted) {
|
|
||||||
LOG(ERROR) << "Duplicate insert to " << flag.Name() << " not allowed";
|
LOG(ERROR) << "Duplicate insert to " << flag.Name() << " not allowed";
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
@ -106,6 +108,19 @@ absl::flat_hash_map<std::string, std::string> ParseCmdlineArgMap(
|
||||||
return parsed_mappings;
|
return parsed_mappings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CmdLineMapping OriginalToAliasMap() {
|
||||||
|
CmdLineMapping original_to_alias;
|
||||||
|
CmdLineMapping alias_to_original = ParseCmdlineArgMap(FLAGS_command_alias);
|
||||||
|
original_to_alias.reserve(alias_to_original.size());
|
||||||
|
std::for_each(std::make_move_iterator(alias_to_original.begin()),
|
||||||
|
std::make_move_iterator(alias_to_original.end()),
|
||||||
|
[&original_to_alias](auto&& pair) {
|
||||||
|
original_to_alias.emplace(std::move(pair.second), std::move(pair.first));
|
||||||
|
});
|
||||||
|
|
||||||
|
return original_to_alias;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
CommandId::CommandId(const char* name, uint32_t mask, int8_t arity, int8_t first_key,
|
CommandId::CommandId(const char* name, uint32_t mask, int8_t arity, int8_t first_key,
|
||||||
|
@ -115,6 +130,17 @@ CommandId::CommandId(const char* name, uint32_t mask, int8_t arity, int8_t first
|
||||||
implicit_acl_ = !acl_categories.has_value();
|
implicit_acl_ = !acl_categories.has_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CommandId CommandId::Clone(const std::string_view name) const {
|
||||||
|
CommandId cloned =
|
||||||
|
CommandId{name.data(), opt_mask_, arity_, first_key_, last_key_, acl_categories_};
|
||||||
|
cloned.handler_ = handler_;
|
||||||
|
cloned.opt_mask_ = opt_mask_ | CO::HIDDEN;
|
||||||
|
cloned.acl_categories_ = acl_categories_;
|
||||||
|
cloned.implicit_acl_ = implicit_acl_;
|
||||||
|
cloned.is_alias_ = true;
|
||||||
|
return cloned;
|
||||||
|
}
|
||||||
|
|
||||||
bool CommandId::IsTransactional() const {
|
bool CommandId::IsTransactional() const {
|
||||||
if (first_key_ > 0 || (opt_mask_ & CO::GLOBAL_TRANS) || (opt_mask_ & CO::NO_KEY_TRANSACTIONAL))
|
if (first_key_ > 0 || (opt_mask_ & CO::GLOBAL_TRANS) || (opt_mask_ & CO::NO_KEY_TRANSACTIONAL))
|
||||||
return true;
|
return true;
|
||||||
|
@ -130,8 +156,7 @@ bool CommandId::IsMultiTransactional() const {
|
||||||
return CO::IsTransKind(name()) || CO::IsEvalKind(name());
|
return CO::IsTransKind(name()) || CO::IsEvalKind(name());
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t CommandId::Invoke(CmdArgList args, const CommandContext& cmd_cntx,
|
uint64_t CommandId::Invoke(CmdArgList args, const CommandContext& cmd_cntx) const {
|
||||||
std::string_view orig_cmd_name) const {
|
|
||||||
int64_t before = absl::GetCurrentTimeNanos();
|
int64_t before = absl::GetCurrentTimeNanos();
|
||||||
handler_(args, cmd_cntx);
|
handler_(args, cmd_cntx);
|
||||||
int64_t after = absl::GetCurrentTimeNanos();
|
int64_t after = absl::GetCurrentTimeNanos();
|
||||||
|
@ -139,7 +164,7 @@ uint64_t CommandId::Invoke(CmdArgList args, const CommandContext& cmd_cntx,
|
||||||
ServerState* ss = ServerState::tlocal(); // Might have migrated thread, read after invocation
|
ServerState* ss = ServerState::tlocal(); // Might have migrated thread, read after invocation
|
||||||
int64_t execution_time_usec = (after - before) / 1000;
|
int64_t execution_time_usec = (after - before) / 1000;
|
||||||
|
|
||||||
auto& ent = command_stats_[ss->thread_index()][orig_cmd_name];
|
auto& ent = command_stats_[ss->thread_index()];
|
||||||
|
|
||||||
++ent.first;
|
++ent.first;
|
||||||
ent.second += execution_time_usec;
|
ent.second += execution_time_usec;
|
||||||
|
@ -169,7 +194,6 @@ optional<facade::ErrorReply> CommandId::Validate(CmdArgList tail_args) const {
|
||||||
|
|
||||||
CommandRegistry::CommandRegistry() {
|
CommandRegistry::CommandRegistry() {
|
||||||
cmd_rename_map_ = ParseCmdlineArgMap(FLAGS_rename_command);
|
cmd_rename_map_ = ParseCmdlineArgMap(FLAGS_rename_command);
|
||||||
cmd_aliases_ = ParseCmdlineArgMap(FLAGS_command_alias, true);
|
|
||||||
|
|
||||||
for (string name : GetFlag(FLAGS_restricted_commands)) {
|
for (string name : GetFlag(FLAGS_restricted_commands)) {
|
||||||
restricted_cmds_.emplace(AsciiStrToUpper(name));
|
restricted_cmds_.emplace(AsciiStrToUpper(name));
|
||||||
|
@ -181,9 +205,20 @@ CommandRegistry::CommandRegistry() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandRegistry::Init(unsigned int thread_count) {
|
void CommandRegistry::Init(unsigned int thread_count) {
|
||||||
|
const CmdLineMapping original_to_alias = OriginalToAliasMap();
|
||||||
|
absl::flat_hash_map<std::string, CommandId> alias_to_command_id;
|
||||||
|
alias_to_command_id.reserve(original_to_alias.size());
|
||||||
for (auto& [_, cmd] : cmd_map_) {
|
for (auto& [_, cmd] : cmd_map_) {
|
||||||
cmd.Init(thread_count);
|
cmd.Init(thread_count);
|
||||||
|
if (auto it = original_to_alias.find(cmd.name()); it != original_to_alias.end()) {
|
||||||
|
auto alias_cmd = cmd.Clone(it->second);
|
||||||
|
alias_cmd.Init(thread_count);
|
||||||
|
alias_to_command_id.insert({it->second, std::move(alias_cmd)});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
std::copy(std::make_move_iterator(alias_to_command_id.begin()),
|
||||||
|
std::make_move_iterator(alias_to_command_id.end()),
|
||||||
|
std::inserter(cmd_map_, cmd_map_.end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
|
CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
|
||||||
|
@ -212,7 +247,7 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
|
||||||
|
|
||||||
if (!is_sub_command || absl::StartsWith(cmd.name(), "ACL")) {
|
if (!is_sub_command || absl::StartsWith(cmd.name(), "ACL")) {
|
||||||
cmd.SetBitIndex(1ULL << bit_index_);
|
cmd.SetBitIndex(1ULL << bit_index_);
|
||||||
family_of_commands_.back().push_back(std::string(k));
|
family_of_commands_.back().emplace_back(k);
|
||||||
++bit_index_;
|
++bit_index_;
|
||||||
} else {
|
} else {
|
||||||
DCHECK(absl::StartsWith(k, family_of_commands_.back().back()));
|
DCHECK(absl::StartsWith(k, family_of_commands_.back().back()));
|
||||||
|
@ -266,10 +301,6 @@ std::pair<const CommandId*, ArgSlice> CommandRegistry::FindExtended(string_view
|
||||||
return {res, tail_args};
|
return {res, tail_args};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CommandRegistry::IsAlias(std::string_view cmd) const {
|
|
||||||
return cmd_aliases_.contains(cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace CO {
|
namespace CO {
|
||||||
|
|
||||||
const char* OptName(CO::CommandOpt fl) {
|
const char* OptName(CO::CommandOpt fl) {
|
||||||
|
|
|
@ -71,9 +71,8 @@ static_assert(!IsEvalKind(""));
|
||||||
|
|
||||||
}; // namespace CO
|
}; // namespace CO
|
||||||
|
|
||||||
// Per thread vector of command stats. Each entry is:
|
// Per thread vector of command stats. Each entry is {cmd_calls, cmd_latency_agg in usec}.
|
||||||
// command invocation string -> {cmd_calls, cmd_latency_agg in usec}.
|
using CmdCallStats = std::pair<uint64_t, uint64_t>;
|
||||||
using CmdCallStats = absl::flat_hash_map<std::string, std::pair<uint64_t, uint64_t>>;
|
|
||||||
|
|
||||||
struct CommandContext {
|
struct CommandContext {
|
||||||
CommandContext(Transaction* _tx, facade::SinkReplyBuilder* _rb, ConnectionContext* cntx)
|
CommandContext(Transaction* _tx, facade::SinkReplyBuilder* _rb, ConnectionContext* cntx)
|
||||||
|
@ -94,6 +93,8 @@ class CommandId : public facade::CommandId {
|
||||||
|
|
||||||
CommandId(CommandId&&) = default;
|
CommandId(CommandId&&) = default;
|
||||||
|
|
||||||
|
[[nodiscard]] CommandId Clone(std::string_view name) const;
|
||||||
|
|
||||||
void Init(unsigned thread_count) {
|
void Init(unsigned thread_count) {
|
||||||
command_stats_ = std::make_unique<CmdCallStats[]>(thread_count);
|
command_stats_ = std::make_unique<CmdCallStats[]>(thread_count);
|
||||||
}
|
}
|
||||||
|
@ -103,10 +104,8 @@ class CommandId : public facade::CommandId {
|
||||||
using ArgValidator = fu2::function_base<true, true, fu2::capacity_default, false, false,
|
using ArgValidator = fu2::function_base<true, true, fu2::capacity_default, false, false,
|
||||||
std::optional<facade::ErrorReply>(CmdArgList) const>;
|
std::optional<facade::ErrorReply>(CmdArgList) const>;
|
||||||
|
|
||||||
// Invokes the command handler. Returns the invoke time in usec. The invoked_by parameter is set
|
// Returns the invoke time in usec.
|
||||||
// to the string passed in by user, if available. If not set, defaults to command name.
|
uint64_t Invoke(CmdArgList args, const CommandContext& cmd_cntx) const;
|
||||||
uint64_t Invoke(CmdArgList args, const CommandContext& cmd_cntx,
|
|
||||||
std::string_view orig_cmd_name) const;
|
|
||||||
|
|
||||||
// Returns error if validation failed, otherwise nullopt
|
// Returns error if validation failed, otherwise nullopt
|
||||||
std::optional<facade::ErrorReply> Validate(CmdArgList tail_args) const;
|
std::optional<facade::ErrorReply> Validate(CmdArgList tail_args) const;
|
||||||
|
@ -144,7 +143,7 @@ class CommandId : public facade::CommandId {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResetStats(unsigned thread_index) {
|
void ResetStats(unsigned thread_index) {
|
||||||
command_stats_[thread_index].clear();
|
command_stats_[thread_index] = {0, 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
CmdCallStats GetStats(unsigned thread_index) const {
|
CmdCallStats GetStats(unsigned thread_index) const {
|
||||||
|
@ -156,11 +155,16 @@ class CommandId : public facade::CommandId {
|
||||||
acl_categories_ |= mask;
|
acl_categories_ |= mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsAlias() const {
|
||||||
|
return is_alias_;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool implicit_acl_;
|
bool implicit_acl_;
|
||||||
std::unique_ptr<CmdCallStats[]> command_stats_;
|
std::unique_ptr<CmdCallStats[]> command_stats_;
|
||||||
Handler3 handler_;
|
Handler3 handler_;
|
||||||
ArgValidator validator_;
|
ArgValidator validator_;
|
||||||
|
bool is_alias_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
class CommandRegistry {
|
class CommandRegistry {
|
||||||
|
@ -172,16 +176,8 @@ class CommandRegistry {
|
||||||
CommandRegistry& operator<<(CommandId cmd);
|
CommandRegistry& operator<<(CommandId cmd);
|
||||||
|
|
||||||
const CommandId* Find(std::string_view cmd) const {
|
const CommandId* Find(std::string_view cmd) const {
|
||||||
if (const auto it = cmd_map_.find(cmd); it != cmd_map_.end()) {
|
auto it = cmd_map_.find(cmd);
|
||||||
return &it->second;
|
return it == cmd_map_.end() ? nullptr : &it->second;
|
||||||
}
|
|
||||||
|
|
||||||
if (const auto it = cmd_aliases_.find(cmd); it != cmd_aliases_.end()) {
|
|
||||||
if (const auto alias_lookup = cmd_map_.find(it->second); alias_lookup != cmd_map_.end()) {
|
|
||||||
return &alias_lookup->second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandId* Find(std::string_view cmd) {
|
CommandId* Find(std::string_view cmd) {
|
||||||
|
@ -203,17 +199,13 @@ class CommandRegistry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MergeCallStats(
|
void MergeCallStats(unsigned thread_index,
|
||||||
unsigned thread_index,
|
std::function<void(std::string_view, const CmdCallStats&)> cb) const {
|
||||||
std::function<void(std::string_view, const CmdCallStats::mapped_type&)> cb) const {
|
for (const auto& k_v : cmd_map_) {
|
||||||
for (const auto& [_, cmd_id] : cmd_map_) {
|
auto src = k_v.second.GetStats(thread_index);
|
||||||
for (const auto& [cmd_name, call_stats] : cmd_id.GetStats(thread_index)) {
|
if (src.first == 0)
|
||||||
if (call_stats.first == 0) {
|
continue;
|
||||||
continue;
|
cb(k_v.second.name(), src);
|
||||||
}
|
|
||||||
|
|
||||||
cb(cmd_name, call_stats);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,16 +219,9 @@ class CommandRegistry {
|
||||||
std::pair<const CommandId*, facade::ArgSlice> FindExtended(std::string_view cmd,
|
std::pair<const CommandId*, facade::ArgSlice> FindExtended(std::string_view cmd,
|
||||||
facade::ArgSlice tail_args) const;
|
facade::ArgSlice tail_args) const;
|
||||||
|
|
||||||
bool IsAlias(std::string_view cmd) const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
absl::flat_hash_map<std::string, CommandId> cmd_map_;
|
absl::flat_hash_map<std::string, CommandId> cmd_map_;
|
||||||
absl::flat_hash_map<std::string, std::string> cmd_rename_map_;
|
absl::flat_hash_map<std::string, std::string> cmd_rename_map_;
|
||||||
// Stores a mapping from alias to original command. During the find operation, the first lookup is
|
|
||||||
// done in the cmd_map_, then in the alias map. This results in two lookups but only for commands
|
|
||||||
// which are not in original map, ie either typos or aliases. While it would be faster, we cannot
|
|
||||||
// store iterators into cmd_map_ here as they may be invalidated on rehashing.
|
|
||||||
absl::flat_hash_map<std::string, std::string> cmd_aliases_;
|
|
||||||
absl::flat_hash_set<std::string> restricted_cmds_;
|
absl::flat_hash_set<std::string> restricted_cmds_;
|
||||||
absl::flat_hash_set<std::string> oomdeny_cmds_;
|
absl::flat_hash_set<std::string> oomdeny_cmds_;
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ extern "C" {
|
||||||
|
|
||||||
#include <absl/strings/ascii.h>
|
#include <absl/strings/ascii.h>
|
||||||
#include <absl/strings/str_join.h>
|
#include <absl/strings/str_join.h>
|
||||||
#include <absl/strings/str_split.h>
|
#include <absl/strings/strip.h>
|
||||||
#include <gmock/gmock.h>
|
#include <gmock/gmock.h>
|
||||||
#include <reflex/matcher.h>
|
#include <reflex/matcher.h>
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ extern "C" {
|
||||||
#include "base/gtest.h"
|
#include "base/gtest.h"
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
#include "facade/facade_test.h"
|
#include "facade/facade_test.h"
|
||||||
|
#include "server/conn_context.h"
|
||||||
#include "server/main_service.h"
|
#include "server/main_service.h"
|
||||||
#include "server/test_utils.h"
|
#include "server/test_utils.h"
|
||||||
|
|
||||||
|
@ -24,9 +25,9 @@ ABSL_DECLARE_FLAG(float, mem_defrag_threshold);
|
||||||
ABSL_DECLARE_FLAG(float, mem_defrag_waste_threshold);
|
ABSL_DECLARE_FLAG(float, mem_defrag_waste_threshold);
|
||||||
ABSL_DECLARE_FLAG(uint32_t, mem_defrag_check_sec_interval);
|
ABSL_DECLARE_FLAG(uint32_t, mem_defrag_check_sec_interval);
|
||||||
ABSL_DECLARE_FLAG(std::vector<std::string>, rename_command);
|
ABSL_DECLARE_FLAG(std::vector<std::string>, rename_command);
|
||||||
ABSL_DECLARE_FLAG(std::vector<std::string>, command_alias);
|
|
||||||
ABSL_DECLARE_FLAG(bool, lua_resp2_legacy_float);
|
ABSL_DECLARE_FLAG(bool, lua_resp2_legacy_float);
|
||||||
ABSL_DECLARE_FLAG(double, eviction_memory_budget_threshold);
|
ABSL_DECLARE_FLAG(double, eviction_memory_budget_threshold);
|
||||||
|
ABSL_DECLARE_FLAG(std::vector<std::string>, command_alias);
|
||||||
|
|
||||||
namespace dfly {
|
namespace dfly {
|
||||||
|
|
||||||
|
@ -118,7 +119,8 @@ class DflyRenameCommandTest : public DflyEngineTest {
|
||||||
&FLAGS_rename_command,
|
&FLAGS_rename_command,
|
||||||
std::vector<std::string>({"flushall=myflushall", "flushdb=", "ping=abcdefghijklmnop"}));
|
std::vector<std::string>({"flushall=myflushall", "flushdb=", "ping=abcdefghijklmnop"}));
|
||||||
}
|
}
|
||||||
absl::FlagSaver saver_;
|
|
||||||
|
absl::FlagSaver _saver;
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(DflyRenameCommandTest, RenameCommand) {
|
TEST_F(DflyRenameCommandTest, RenameCommand) {
|
||||||
|
@ -848,9 +850,7 @@ TEST_F(DflyEngineTest, CommandMetricLabels) {
|
||||||
class DflyCommandAliasTest : public DflyEngineTest {
|
class DflyCommandAliasTest : public DflyEngineTest {
|
||||||
protected:
|
protected:
|
||||||
DflyCommandAliasTest() {
|
DflyCommandAliasTest() {
|
||||||
// Test an interaction of rename and alias, where we rename and then add an alias on the rename
|
absl::SetFlag(&FLAGS_command_alias, {"___set=set", "___ping=ping"});
|
||||||
absl::SetFlag(&FLAGS_rename_command, {"ping=gnip"});
|
|
||||||
absl::SetFlag(&FLAGS_command_alias, {"___set=set", "___ping=gnip"});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
absl::FlagSaver saver_;
|
absl::FlagSaver saver_;
|
||||||
|
@ -861,19 +861,26 @@ TEST_F(DflyCommandAliasTest, Aliasing) {
|
||||||
EXPECT_EQ(Run({"___SET", "a", "b"}), "OK");
|
EXPECT_EQ(Run({"___SET", "a", "b"}), "OK");
|
||||||
EXPECT_EQ(Run({"GET", "foo"}), "bar");
|
EXPECT_EQ(Run({"GET", "foo"}), "bar");
|
||||||
EXPECT_EQ(Run({"GET", "a"}), "b");
|
EXPECT_EQ(Run({"GET", "a"}), "b");
|
||||||
// test the alias
|
|
||||||
EXPECT_EQ(Run({"___ping"}), "PONG");
|
EXPECT_EQ(Run({"___ping"}), "PONG");
|
||||||
// test the rename
|
|
||||||
EXPECT_EQ(Run({"gnip"}), "PONG");
|
|
||||||
// the original command is not accessible
|
|
||||||
EXPECT_THAT(Run({"PING"}), ErrArg("unknown command `PING`"));
|
|
||||||
|
|
||||||
const Metrics metrics = GetMetrics();
|
Metrics metrics = GetMetrics();
|
||||||
const auto& stats = metrics.cmd_stats_map;
|
const auto& stats = metrics.cmd_stats_map;
|
||||||
|
|
||||||
EXPECT_THAT(stats, Contains(Pair("___set", Key(1))));
|
EXPECT_THAT(stats, Contains(Pair("___set", Key(1))));
|
||||||
EXPECT_THAT(stats, Contains(Pair("set", Key(1))));
|
EXPECT_THAT(stats, Contains(Pair("set", Key(1))));
|
||||||
EXPECT_THAT(stats, Contains(Pair("___ping", Key(1))));
|
EXPECT_THAT(stats, Contains(Pair("___ping", Key(1))));
|
||||||
EXPECT_THAT(stats, Contains(Pair("get", Key(2))));
|
EXPECT_THAT(stats, Contains(Pair("get", Key(2))));
|
||||||
|
|
||||||
|
// test stats within multi-exec
|
||||||
|
EXPECT_EQ(Run({"multi"}), "OK");
|
||||||
|
EXPECT_EQ(Run({"___set", "a", "x"}), "QUEUED");
|
||||||
|
EXPECT_EQ(Run({"exec"}), "OK");
|
||||||
|
|
||||||
|
metrics = GetMetrics();
|
||||||
|
EXPECT_THAT(metrics.cmd_stats_map, Contains(Pair("___set", Key(2))));
|
||||||
|
EXPECT_THAT(metrics.cmd_stats_map, Contains(Pair("set", Key(1))));
|
||||||
|
EXPECT_THAT(metrics.cmd_stats_map, Contains(Pair("multi", Key(1))));
|
||||||
|
EXPECT_THAT(metrics.cmd_stats_map, Contains(Pair("exec", Key(1))));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace dfly
|
} // namespace dfly
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "server/main_service.h"
|
#include "server/main_service.h"
|
||||||
|
|
||||||
|
#include "absl/strings/str_split.h"
|
||||||
#include "facade/resp_expr.h"
|
#include "facade/resp_expr.h"
|
||||||
#include "util/fibers/synchronization.h"
|
#include "util/fibers/synchronization.h"
|
||||||
|
|
||||||
|
@ -1246,13 +1247,7 @@ void Service::DispatchCommand(ArgSlice args, SinkReplyBuilder* builder,
|
||||||
|
|
||||||
dfly_cntx->cid = cid;
|
dfly_cntx->cid = cid;
|
||||||
|
|
||||||
// If cmd is an alias, pass it to Invoke so the stats are updated against the alias. By defaults
|
if (!InvokeCmd(cid, args_no_cmd, builder, dfly_cntx)) {
|
||||||
// stats will be updated for cid.name
|
|
||||||
std::optional<std::string_view> orig_cmd_name = std::nullopt;
|
|
||||||
if (registry_.IsAlias(cmd)) {
|
|
||||||
orig_cmd_name = cmd;
|
|
||||||
}
|
|
||||||
if (!InvokeCmd(cid, args_no_cmd, builder, dfly_cntx, orig_cmd_name)) {
|
|
||||||
builder->SendError("Internal Error");
|
builder->SendError("Internal Error");
|
||||||
builder->CloseConnection();
|
builder->CloseConnection();
|
||||||
}
|
}
|
||||||
|
@ -1313,7 +1308,7 @@ OpResult<void> OpTrackKeys(const OpArgs slice_args, const facade::Connection::We
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Service::InvokeCmd(const CommandId* cid, CmdArgList tail_args, SinkReplyBuilder* builder,
|
bool Service::InvokeCmd(const CommandId* cid, CmdArgList tail_args, SinkReplyBuilder* builder,
|
||||||
ConnectionContext* cntx, std::optional<std::string_view> orig_cmd_name) {
|
ConnectionContext* cntx) {
|
||||||
DCHECK(cid);
|
DCHECK(cid);
|
||||||
DCHECK(!cid->Validate(tail_args));
|
DCHECK(!cid->Validate(tail_args));
|
||||||
|
|
||||||
|
@ -1362,8 +1357,7 @@ bool Service::InvokeCmd(const CommandId* cid, CmdArgList tail_args, SinkReplyBui
|
||||||
auto last_error = builder->ConsumeLastError();
|
auto last_error = builder->ConsumeLastError();
|
||||||
DCHECK(last_error.empty());
|
DCHECK(last_error.empty());
|
||||||
try {
|
try {
|
||||||
invoke_time_usec = cid->Invoke(tail_args, CommandContext{tx, builder, cntx},
|
invoke_time_usec = cid->Invoke(tail_args, CommandContext{tx, builder, cntx});
|
||||||
orig_cmd_name.value_or(cid->name()));
|
|
||||||
} catch (std::exception& e) {
|
} catch (std::exception& e) {
|
||||||
LOG(ERROR) << "Internal error, system probably unstable " << e.what();
|
LOG(ERROR) << "Internal error, system probably unstable " << e.what();
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -45,8 +45,7 @@ class Service : public facade::ServiceInterface {
|
||||||
|
|
||||||
// Check VerifyCommandExecution and invoke command with args
|
// Check VerifyCommandExecution and invoke command with args
|
||||||
bool InvokeCmd(const CommandId* cid, CmdArgList tail_args, facade::SinkReplyBuilder* builder,
|
bool InvokeCmd(const CommandId* cid, CmdArgList tail_args, facade::SinkReplyBuilder* builder,
|
||||||
ConnectionContext* reply_cntx,
|
ConnectionContext* reply_cntx);
|
||||||
std::optional<std::string_view> orig_cmd_name = std::nullopt);
|
|
||||||
|
|
||||||
// Verify command can be executed now (check out of memory), always called immediately before
|
// Verify command can be executed now (check out of memory), always called immediately before
|
||||||
// execution
|
// execution
|
||||||
|
|
|
@ -2219,8 +2219,7 @@ Metrics ServerFamily::GetMetrics(Namespace* ns) const {
|
||||||
|
|
||||||
uint64_t start = absl::GetCurrentTimeNanos();
|
uint64_t start = absl::GetCurrentTimeNanos();
|
||||||
|
|
||||||
auto cmd_stat_cb = [&dest = result.cmd_stats_map](string_view name,
|
auto cmd_stat_cb = [&dest = result.cmd_stats_map](string_view name, const CmdCallStats& stat) {
|
||||||
const CmdCallStats::mapped_type& stat) {
|
|
||||||
auto& [calls, sum] = dest[absl::AsciiStrToLower(name)];
|
auto& [calls, sum] = dest[absl::AsciiStrToLower(name)];
|
||||||
calls += stat.first;
|
calls += stat.first;
|
||||||
sum += stat.second;
|
sum += stat.second;
|
||||||
|
|
|
@ -2959,6 +2959,7 @@ async def test_preempt_in_atomic_section_of_heartbeat(df_factory: DflyInstanceFa
|
||||||
await fill_task
|
await fill_task
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip("temporarily skipped")
|
||||||
async def test_bug_in_json_memory_tracking(df_factory: DflyInstanceFactory):
|
async def test_bug_in_json_memory_tracking(df_factory: DflyInstanceFactory):
|
||||||
"""
|
"""
|
||||||
This test reproduces a bug in the JSON memory tracking.
|
This test reproduces a bug in the JSON memory tracking.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue