Add a dry run flag to RM_Call execution (#11158)

Add a new "D" flag to RM_Call which runs whatever verification the user requests,
but returns before the actual execution of the command.

It automatically enables returning error messages as CallReply objects to distinguish
success (NULL) from failure (CallReply returned).
This commit is contained in:
Shaya Potter 2022-09-05 16:19:32 +03:00 committed by GitHub
parent 22f763aa10
commit 87e7973c7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 46 additions and 1 deletions

View File

@ -357,6 +357,7 @@ typedef struct RedisModuleServerInfoData {
#define REDISMODULE_ARGV_NO_WRITES (1<<7)
#define REDISMODULE_ARGV_CALL_REPLIES_AS_ERRORS (1<<8)
#define REDISMODULE_ARGV_RESPECT_DENY_OOM (1<<9)
#define REDISMODULE_ARGV_DRY_RUN (1<<10)
/* Determine whether Redis should signalModifiedKey implicitly.
* In case 'ctx' has no 'module' member (and therefore no module->options),
@ -5703,6 +5704,8 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
if (flags) (*flags) |= REDISMODULE_ARGV_RESPECT_DENY_OOM;
} else if (*p == 'E') {
if (flags) (*flags) |= REDISMODULE_ARGV_CALL_REPLIES_AS_ERRORS;
} else if (*p == 'D') {
if (flags) (*flags) |= (REDISMODULE_ARGV_DRY_RUN | REDISMODULE_ARGV_CALL_REPLIES_AS_ERRORS);
} else {
goto fmterr;
}
@ -5754,6 +5757,10 @@ fmterr:
* invoking the command, the error is returned using errno mechanism.
* This flag allows to get the error also as an error CallReply with
* relevant error message.
* * 'D' -- A "Dry Run" mode. Return before executing the underlying call().
* If everything succeeded, it will return with a NULL, otherwise it will
* return with a CallReply object denoting the error, as if it was called with
* the 'E' code.
* * **...**: The actual arguments to the Redis command.
*
* On success a RedisModuleCallReply object is returned, otherwise
@ -6007,6 +6014,10 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
}
}
if (flags & REDISMODULE_ARGV_DRY_RUN) {
goto cleanup;
}
/* We need to use a global replication_allowed flag in order to prevent
* replication of nested RM_Calls. Example:
* 1. module1.foo does RM_Call of module2.bar without replication (i.e. no '!')

View File

@ -389,8 +389,42 @@ start_server {tags {"modules"}} {
# server is writable again
r set x y
} {OK}
}
start_server {tags {"modules"}} {
r module load $testmodule
test {test Dry Run - OK OOM/ACL} {
set x 5
r set x $x
catch {r test.rm_call_flags DMC set x 10} e
assert_match {*NULL reply returned*} $e
assert_equal [r get x] 5
}
test {test Dry Run - Fail OOM} {
set x 5
r set x $x
r config set maxmemory 1
catch {r test.rm_call_flags DM set x 10} e
assert_match {*OOM*} $e
assert_equal [r get x] $x
r config set maxmemory 0
} {OK} {needs:config-maxmemory}
test {test Dry Run - Fail ACL} {
set x 5
r set x $x
# deny all permissions besides the dryrun command
r acl setuser default resetkeys
catch {r test.rm_call_flags DC set x 10} e
assert_match {*ERR acl verification failed, can't access at least one of the keys*} $e
r acl setuser default +@all ~*
assert_equal [r get x] $x
}
test "Unload the module - misc" {
assert_equal {OK} [r module unload misc]
}
}
}