Merge pull request #5944 from yossigo/command-filtering

Command Filtering API
This commit is contained in:
Salvatore Sanfilippo 2019-03-22 17:43:49 +01:00 committed by GitHub
commit 5e8caca036
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 527 additions and 9 deletions

View File

@ -49,6 +49,8 @@ struct RedisModule {
list *types; /* Module data types. */
list *usedby; /* List of modules using APIs from this one. */
list *using; /* List of modules we use some APIs of. */
list *filters; /* List of filters the module has registered. */
int in_call; /* RM_Call() nesting level */
};
typedef struct RedisModule RedisModule;
@ -270,6 +272,25 @@ typedef struct RedisModuleDictIter {
raxIterator ri;
} RedisModuleDictIter;
typedef struct RedisModuleCommandFilterCtx {
RedisModuleString **argv;
int argc;
} RedisModuleCommandFilterCtx;
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
typedef struct RedisModuleCommandFilter {
/* The module that registered the filter */
RedisModule *module;
/* Filter callback function */
RedisModuleCommandFilterFunc callback;
/* REDISMODULE_CMDFILTER_* flags */
int flags;
} RedisModuleCommandFilter;
/* Registered filters */
static list *moduleCommandFilters;
/* --------------------------------------------------------------------------
* Prototypes
* -------------------------------------------------------------------------- */
@ -731,6 +752,7 @@ void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int api
module->types = listCreate();
module->usedby = listCreate();
module->using = listCreate();
module->filters = listCreate();
ctx->module = module;
}
@ -2724,12 +2746,6 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
RedisModuleCallReply *reply = NULL;
int replicate = 0; /* Replicate this command? */
cmd = lookupCommandByCString((char*)cmdname);
if (!cmd) {
errno = EINVAL;
return NULL;
}
/* Create the client and dispatch the command. */
va_start(ap, fmt);
c = createClient(-1);
@ -2743,11 +2759,25 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
c->db = ctx->client->db;
c->argv = argv;
c->argc = argc;
c->cmd = c->lastcmd = cmd;
if (ctx->module) ctx->module->in_call++;
/* We handle the above format error only when the client is setup so that
* we can free it normally. */
if (argv == NULL) goto cleanup;
/* Call command filters */
moduleCallCommandFilters(c);
/* Lookup command now, after filters had a chance to make modifications
* if necessary.
*/
cmd = lookupCommand(c->argv[0]->ptr);
if (!cmd) {
errno = EINVAL;
goto cleanup;
}
c->cmd = c->lastcmd = cmd;
/* Basic arity checks. */
if ((cmd->arity > 0 && cmd->arity != argc) || (argc < -cmd->arity)) {
errno = EINVAL;
@ -2797,6 +2827,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
autoMemoryAdd(ctx,REDISMODULE_AM_REPLY,reply);
cleanup:
if (ctx->module) ctx->module->in_call--;
freeClient(c);
return reply;
}
@ -4778,6 +4809,214 @@ int moduleUnregisterUsedAPI(RedisModule *module) {
return count;
}
/* Unregister all filters registered by a module.
* This is called when a module is being unloaded.
*
* Returns the number of filters unregistered. */
int moduleUnregisterFilters(RedisModule *module) {
listIter li;
listNode *ln;
int count = 0;
listRewind(module->filters,&li);
while((ln = listNext(&li))) {
RedisModuleCommandFilter *filter = ln->value;
listNode *ln = listSearchKey(moduleCommandFilters,filter);
if (ln) {
listDelNode(moduleCommandFilters,ln);
count++;
}
zfree(filter);
}
return count;
}
/* --------------------------------------------------------------------------
* Module Command Filter API
* -------------------------------------------------------------------------- */
/* Register a new command filter function.
*
* Command filtering makes it possible for modules to extend Redis by plugging
* into the execution flow of all commands.
*
* A registered filter gets called before Redis executes *any* command. This
* includes both core Redis commands and commands registered by any module. The
* filter applies in all execution paths including:
*
* 1. Invocation by a client.
* 2. Invocation through `RedisModule_Call()` by any module.
* 3. Invocation through Lua 'redis.call()`.
* 4. Replication of a command from a master.
*
* The filter executes in a special filter context, which is different and more
* limited than a RedisModuleCtx. Because the filter affects any command, it
* must be implemented in a very efficient way to reduce the performance impact
* on Redis. All Redis Module API calls that require a valid context (such as
* `RedisModule_Call()`, `RedisModule_OpenKey()`, etc.) are not supported in a
* filter context.
*
* The `RedisModuleCommandFilterCtx` can be used to inspect or modify the
* executed command and its arguments. As the filter executes before Redis
* begins processing the command, any change will affect the way the command is
* processed. For example, a module can override Redis commands this way:
*
* 1. Register a `MODULE.SET` command which implements an extended version of
* the Redis `SET` command.
* 2. Register a command filter which detects invocation of `SET` on a specific
* pattern of keys. Once detected, the filter will replace the first
* argument from `SET` to `MODULE.SET`.
* 3. When filter execution is complete, Redis considers the new command name
* and therefore executes the module's own command.
*
* Note that in the above use case, if `MODULE.SET` itself uses
* `RedisModule_Call()` the filter will be applied on that call as well. If
* that is not desired, the `REDISMODULE_CMDFILTER_NOSELF` flag can be set when
* registering the filter.
*
* The `REDISMODULE_CMDFILTER_NOSELF` flag prevents execution flows that
* originate from the module's own `RM_Call()` from reaching the filter. This
* flag is effective for all execution flows, including nested ones, as long as
* the execution begins from the module's command context or a thread-safe
* context that is associated with a blocking command.
*
* Detached thread-safe contexts are *not* associated with the module and cannot
* be protected by this flag.
*
* If multiple filters are registered (by the same or different modules), they
* are executed in the order of registration.
*/
RedisModuleCommandFilter *RM_RegisterCommandFilter(RedisModuleCtx *ctx, RedisModuleCommandFilterFunc callback, int flags) {
RedisModuleCommandFilter *filter = zmalloc(sizeof(*filter));
filter->module = ctx->module;
filter->callback = callback;
filter->flags = flags;
listAddNodeTail(moduleCommandFilters, filter);
listAddNodeTail(ctx->module->filters, filter);
return filter;
}
/* Unregister a command filter.
*/
int RM_UnregisterCommandFilter(RedisModuleCtx *ctx, RedisModuleCommandFilter *filter) {
listNode *ln;
/* A module can only remove its own filters */
if (filter->module != ctx->module) return REDISMODULE_ERR;
ln = listSearchKey(moduleCommandFilters,filter);
if (!ln) return REDISMODULE_ERR;
listDelNode(moduleCommandFilters,ln);
ln = listSearchKey(ctx->module->filters,filter);
if (!ln) return REDISMODULE_ERR; /* Shouldn't happen */
listDelNode(ctx->module->filters,ln);
return REDISMODULE_OK;
}
void moduleCallCommandFilters(client *c) {
if (listLength(moduleCommandFilters) == 0) return;
listIter li;
listNode *ln;
listRewind(moduleCommandFilters,&li);
RedisModuleCommandFilterCtx filter = {
.argv = c->argv,
.argc = c->argc
};
while((ln = listNext(&li))) {
RedisModuleCommandFilter *f = ln->value;
/* Skip filter if REDISMODULE_CMDFILTER_NOSELF is set and module is
* currently processing a command.
*/
if ((f->flags & REDISMODULE_CMDFILTER_NOSELF) && f->module->in_call) continue;
/* Call filter */
f->callback(&filter);
}
c->argv = filter.argv;
c->argc = filter.argc;
}
/* Return the number of arguments a filtered command has. The number of
* arguments include the command itself.
*/
int RM_CommandFilterArgsCount(RedisModuleCommandFilterCtx *fctx)
{
return fctx->argc;
}
/* Return the specified command argument. The first argument (position 0) is
* the command itself, and the rest are user-provided args.
*/
const RedisModuleString *RM_CommandFilterArgGet(RedisModuleCommandFilterCtx *fctx, int pos)
{
if (pos < 0 || pos >= fctx->argc) return NULL;
return fctx->argv[pos];
}
/* Modify the filtered command by inserting a new argument at the specified
* position. The specified RedisModuleString argument may be used by Redis
* after the filter context is destroyed, so it must not be auto-memory
* allocated, freed or used elsewhere.
*/
int RM_CommandFilterArgInsert(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg)
{
int i;
if (pos < 0 || pos > fctx->argc) return REDISMODULE_ERR;
fctx->argv = zrealloc(fctx->argv, (fctx->argc+1)*sizeof(RedisModuleString *));
for (i = fctx->argc; i > pos; i--) {
fctx->argv[i] = fctx->argv[i-1];
}
fctx->argv[pos] = arg;
fctx->argc++;
return REDISMODULE_OK;
}
/* Modify the filtered command by replacing an existing argument with a new one.
* The specified RedisModuleString argument may be used by Redis after the
* filter context is destroyed, so it must not be auto-memory allocated, freed
* or used elsewhere.
*/
int RM_CommandFilterArgReplace(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg)
{
if (pos < 0 || pos >= fctx->argc) return REDISMODULE_ERR;
decrRefCount(fctx->argv[pos]);
fctx->argv[pos] = arg;
return REDISMODULE_OK;
}
/* Modify the filtered command by deleting an argument at the specified
* position.
*/
int RM_CommandFilterArgDelete(RedisModuleCommandFilterCtx *fctx, int pos)
{
int i;
if (pos < 0 || pos >= fctx->argc) return REDISMODULE_ERR;
decrRefCount(fctx->argv[pos]);
for (i = pos; i < fctx->argc-1; i++) {
fctx->argv[i] = fctx->argv[i+1];
}
fctx->argc--;
return REDISMODULE_OK;
}
/* --------------------------------------------------------------------------
* Modules API internals
* -------------------------------------------------------------------------- */
@ -4824,6 +5063,9 @@ void moduleInitModulesSystem(void) {
moduleFreeContextReusedClient->flags |= CLIENT_MODULE;
moduleFreeContextReusedClient->user = NULL; /* root user. */
/* Set up filter list */
moduleCommandFilters = listCreate();
moduleRegisterCoreAPI();
if (pipe(server.module_blocked_pipe) == -1) {
serverLog(LL_WARNING,
@ -4873,6 +5115,7 @@ void moduleLoadFromQueue(void) {
void moduleFreeModuleStructure(struct RedisModule *module) {
listRelease(module->types);
listRelease(module->filters);
sdsfree(module->name);
zfree(module);
}
@ -4964,6 +5207,7 @@ int moduleUnload(sds name) {
moduleUnregisterCommands(module);
moduleUnregisterSharedAPI(module);
moduleUnregisterUsedAPI(module);
moduleUnregisterFilters(module);
/* Remove any notification subscribers this module might have */
moduleUnsubscribeNotifications(module);
@ -5228,4 +5472,11 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(DictCompare);
REGISTER_API(ExportSharedAPI);
REGISTER_API(GetSharedAPI);
REGISTER_API(RegisterCommandFilter);
REGISTER_API(UnregisterCommandFilter);
REGISTER_API(CommandFilterArgsCount);
REGISTER_API(CommandFilterArgGet);
REGISTER_API(CommandFilterArgInsert);
REGISTER_API(CommandFilterArgReplace);
REGISTER_API(CommandFilterArgDelete);
}

View File

@ -13,7 +13,7 @@ endif
.SUFFIXES: .c .so .xo .o
all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so
all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so hellofilter.so
.c.xo:
$(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
@ -46,6 +46,10 @@ hellotimer.so: hellotimer.xo
hellodict.xo: ../redismodule.h
hellodict.so: hellodict.xo
hellofilter.xo: ../redismodule.h
hellofilter.so: hellofilter.xo
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
testmodule.xo: ../redismodule.h

149
src/modules/hellofilter.c Normal file
View File

@ -0,0 +1,149 @@
#define REDISMODULE_EXPERIMENTAL_API
#include "../redismodule.h"
#include <string.h>
static RedisModuleString *log_key_name;
static const char log_command_name[] = "hellofilter.log";
static const char ping_command_name[] = "hellofilter.ping";
static const char unregister_command_name[] = "hellofilter.unregister";
static int in_log_command = 0;
static RedisModuleCommandFilter *filter = NULL;
int HelloFilter_UnregisterCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
(void) argc;
(void) argv;
RedisModule_ReplyWithLongLong(ctx,
RedisModule_UnregisterCommandFilter(ctx, filter));
return REDISMODULE_OK;
}
int HelloFilter_PingCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
(void) argc;
(void) argv;
RedisModuleCallReply *reply = RedisModule_Call(ctx, "ping", "c", "@log");
if (reply) {
RedisModule_ReplyWithCallReply(ctx, reply);
RedisModule_FreeCallReply(reply);
} else {
RedisModule_ReplyWithSimpleString(ctx, "Unknown command or invalid arguments");
}
return REDISMODULE_OK;
}
int HelloFilter_LogCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
RedisModuleString *s = RedisModule_CreateString(ctx, "", 0);
int i;
for (i = 1; i < argc; i++) {
size_t arglen;
const char *arg = RedisModule_StringPtrLen(argv[i], &arglen);
if (i > 1) RedisModule_StringAppendBuffer(ctx, s, " ", 1);
RedisModule_StringAppendBuffer(ctx, s, arg, arglen);
}
RedisModuleKey *log = RedisModule_OpenKey(ctx, log_key_name, REDISMODULE_WRITE|REDISMODULE_READ);
RedisModule_ListPush(log, REDISMODULE_LIST_HEAD, s);
RedisModule_CloseKey(log);
RedisModule_FreeString(ctx, s);
in_log_command = 1;
size_t cmdlen;
const char *cmdname = RedisModule_StringPtrLen(argv[1], &cmdlen);
RedisModuleCallReply *reply = RedisModule_Call(ctx, cmdname, "v", &argv[2], argc - 2);
if (reply) {
RedisModule_ReplyWithCallReply(ctx, reply);
RedisModule_FreeCallReply(reply);
} else {
RedisModule_ReplyWithSimpleString(ctx, "Unknown command or invalid arguments");
}
in_log_command = 0;
return REDISMODULE_OK;
}
void HelloFilter_CommandFilter(RedisModuleCommandFilterCtx *filter)
{
if (in_log_command) return; /* don't process our own RM_Call() from HelloFilter_LogCommand() */
/* Fun manipulations:
* - Remove @delme
* - Replace @replaceme
* - Append @insertbefore or @insertafter
* - Prefix with Log command if @log encounterd
*/
int log = 0;
int pos = 0;
while (pos < RedisModule_CommandFilterArgsCount(filter)) {
const RedisModuleString *arg = RedisModule_CommandFilterArgGet(filter, pos);
size_t arg_len;
const char *arg_str = RedisModule_StringPtrLen(arg, &arg_len);
if (arg_len == 6 && !memcmp(arg_str, "@delme", 6)) {
RedisModule_CommandFilterArgDelete(filter, pos);
continue;
}
if (arg_len == 10 && !memcmp(arg_str, "@replaceme", 10)) {
RedisModule_CommandFilterArgReplace(filter, pos,
RedisModule_CreateString(NULL, "--replaced--", 12));
} else if (arg_len == 13 && !memcmp(arg_str, "@insertbefore", 13)) {
RedisModule_CommandFilterArgInsert(filter, pos,
RedisModule_CreateString(NULL, "--inserted-before--", 19));
pos++;
} else if (arg_len == 12 && !memcmp(arg_str, "@insertafter", 12)) {
RedisModule_CommandFilterArgInsert(filter, pos + 1,
RedisModule_CreateString(NULL, "--inserted-after--", 18));
pos++;
} else if (arg_len == 4 && !memcmp(arg_str, "@log", 4)) {
log = 1;
}
pos++;
}
if (log) RedisModule_CommandFilterArgInsert(filter, 0,
RedisModule_CreateString(NULL, log_command_name, sizeof(log_command_name)-1));
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (RedisModule_Init(ctx,"hellofilter",1,REDISMODULE_APIVER_1)
== REDISMODULE_ERR) return REDISMODULE_ERR;
if (argc != 2) {
RedisModule_Log(ctx, "warning", "Log key name not specified");
return REDISMODULE_ERR;
}
long long noself = 0;
log_key_name = RedisModule_CreateStringFromString(ctx, argv[0]);
RedisModule_StringToLongLong(argv[1], &noself);
if (RedisModule_CreateCommand(ctx,log_command_name,
HelloFilter_LogCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,ping_command_name,
HelloFilter_PingCommand,"deny-oom",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,unregister_command_name,
HelloFilter_UnregisterCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if ((filter = RedisModule_RegisterCommandFilter(ctx, HelloFilter_CommandFilter,
noself ? REDISMODULE_CMDFILTER_NOSELF : 0))
== NULL) return REDISMODULE_ERR;
return REDISMODULE_OK;
}

View File

@ -133,6 +133,11 @@
* of timers that are going to expire, sorted by expire time. */
typedef uint64_t RedisModuleTimerID;
/* CommandFilter Flags */
/* Do filter RedisModule_Call() commands initiated by module itself. */
#define REDISMODULE_CMDFILTER_NOSELF (1<<0)
/* ------------------------- End of common defines ------------------------ */
#ifndef REDISMODULE_CORE
@ -151,6 +156,8 @@ typedef struct RedisModuleBlockedClient RedisModuleBlockedClient;
typedef struct RedisModuleClusterInfo RedisModuleClusterInfo;
typedef struct RedisModuleDict RedisModuleDict;
typedef struct RedisModuleDictIter RedisModuleDictIter;
typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx;
typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
@ -163,6 +170,7 @@ typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value
typedef void (*RedisModuleTypeFreeFunc)(void *value);
typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len);
typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
#define REDISMODULE_TYPE_METHOD_VERSION 1
typedef struct RedisModuleTypeMethods {
@ -339,6 +347,13 @@ void REDISMODULE_API_FUNC(RedisModule_SetDisconnectCallback)(RedisModuleBlockedC
void REDISMODULE_API_FUNC(RedisModule_SetClusterFlags)(RedisModuleCtx *ctx, uint64_t flags);
int REDISMODULE_API_FUNC(RedisModule_ExportSharedAPI)(RedisModuleCtx *ctx, const char *apiname, void *func);
void *REDISMODULE_API_FUNC(RedisModule_GetSharedAPI)(RedisModuleCtx *ctx, const char *apiname);
RedisModuleCommandFilter *REDISMODULE_API_FUNC(RedisModule_RegisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilterFunc cb, int flags);
int REDISMODULE_API_FUNC(RedisModule_UnregisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilter *filter);
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgsCount)(RedisModuleCommandFilterCtx *fctx);
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CommandFilterArgGet)(RedisModuleCommandFilterCtx *fctx, int pos);
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos);
#endif
/* This is included inline inside each Redis module. */
@ -502,6 +517,13 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(SetClusterFlags);
REDISMODULE_GET_API(ExportSharedAPI);
REDISMODULE_GET_API(GetSharedAPI);
REDISMODULE_GET_API(RegisterCommandFilter);
REDISMODULE_GET_API(UnregisterCommandFilter);
REDISMODULE_GET_API(CommandFilterArgsCount);
REDISMODULE_GET_API(CommandFilterArgGet);
REDISMODULE_GET_API(CommandFilterArgInsert);
REDISMODULE_GET_API(CommandFilterArgReplace);
REDISMODULE_GET_API(CommandFilterArgDelete);
#endif
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;

View File

@ -462,6 +462,11 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
c->argc = argc;
c->user = server.lua_caller->user;
/* Process module hooks */
moduleCallCommandFilters(c);
argv = c->argv;
argc = c->argc;
/* Log the command if debugging is active. */
if (ldb.active && ldb.step) {
sds cmdlog = sdsnew("<redis>");

View File

@ -3268,6 +3268,8 @@ void call(client *c, int flags) {
* other operations can be performed by the caller. Otherwise
* if C_ERR is returned the client was destroyed (i.e. after QUIT). */
int processCommand(client *c) {
moduleCallCommandFilters(c);
/* The QUIT command is handled separately. Normal command procs will
* go through checking for replication and QUIT will cause trouble
* when FORCE_REPLICATION is enabled and would be implemented in

View File

@ -1492,7 +1492,7 @@ size_t moduleCount(void);
void moduleAcquireGIL(void);
void moduleReleaseGIL(void);
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
void moduleCallCommandFilters(client *c);
/* Utils */
long long ustime(void);

View File

@ -0,0 +1,84 @@
set testmodule [file normalize src/modules/hellofilter.so]
start_server {tags {"modules"}} {
r module load $testmodule log-key 0
test {Command Filter handles redirected commands} {
r set mykey @log
r lrange log-key 0 -1
} "{set mykey @log}"
test {Command Filter can call RedisModule_CommandFilterArgDelete} {
r rpush mylist elem1 @delme elem2
r lrange mylist 0 -1
} {elem1 elem2}
test {Command Filter can call RedisModule_CommandFilterArgInsert} {
r del mylist
r rpush mylist elem1 @insertbefore elem2 @insertafter elem3
r lrange mylist 0 -1
} {elem1 --inserted-before-- @insertbefore elem2 @insertafter --inserted-after-- elem3}
test {Command Filter can call RedisModule_CommandFilterArgReplace} {
r del mylist
r rpush mylist elem1 @replaceme elem2
r lrange mylist 0 -1
} {elem1 --replaced-- elem2}
test {Command Filter applies on RM_Call() commands} {
r del log-key
r hellofilter.ping
r lrange log-key 0 -1
} "{ping @log}"
test {Command Filter applies on Lua redis.call()} {
r del log-key
r eval "redis.call('ping', '@log')" 0
r lrange log-key 0 -1
} "{ping @log}"
test {Command Filter applies on Lua redis.call() that calls a module} {
r del log-key
r eval "redis.call('hellofilter.ping')" 0
r lrange log-key 0 -1
} "{ping @log}"
test {Command Filter is unregistered implicitly on module unload} {
r del log-key
r module unload hellofilter
r set mykey @log
r lrange log-key 0 -1
} {}
r module load $testmodule log-key 0
test {Command Filter unregister works as expected} {
# Validate reloading succeeded
r del log-key
r set mykey @log
assert_equal "{set mykey @log}" [r lrange log-key 0 -1]
# Unregister
r hellofilter.unregister
r del log-key
r set mykey @log
r lrange log-key 0 -1
} {}
r module unload hellofilter
r module load $testmodule log-key 1
test {Command Filter REDISMODULE_CMDFILTER_NOSELF works as expected} {
r set mykey @log
assert_equal "{set mykey @log}" [r lrange log-key 0 -1]
r del log-key
r hellofilter.ping
assert_equal {} [r lrange log-key 0 -1]
r eval "redis.call('hellofilter.ping')" 0
assert_equal {} [r lrange log-key 0 -1]
}
}

View File

@ -63,6 +63,7 @@ set ::all_tests {
unit/lazyfree
unit/wait
unit/pendingquerybuf
modules/commandfilter
}
# Index to the next test to run in the ::all_tests list.
set ::next_test 0