Unverified Commit 81926254 authored by Oran Agra's avatar Oran Agra Committed by GitHub
Browse files

Add module API flag for using enum configs as bit flags (#10643)

Enables registration of an enum config that'll let the user pass multiple keywords that
will be combined with `|` as flags into the integer config value.

```
    const char *enum_vals[] = {"none", "one", "two", "three"};
    const int int_vals[] = {0, 1, 2, 4};

    if (RedisModule_RegisterEnumConfig(ctx, "flags", 3, REDISMODULE_CONFIG_DEFAULT | REDISMODULE_CONFIG_BITFLAGS, enum_vals, int_vals, 4, getFlagsConfigCommand, setFlagsConfigCommand, NULL, NULL) == REDISMODULE_ERR) {
        return REDISMODULE_ERR;
    }
```
doing:
`config set moduleconfigs.flags "two three"` will result in 6 being passed to`setFlagsConfigCommand`.
parent 46ec6ad9
...@@ -547,12 +547,15 @@ void loadServerConfigFromString(char *config) { ...@@ -547,12 +547,15 @@ void loadServerConfigFromString(char *config) {
} else if (!strcasecmp(argv[0],"loadmodule") && argc >= 2) { } else if (!strcasecmp(argv[0],"loadmodule") && argc >= 2) {
queueLoadModule(argv[1],&argv[2],argc-2); queueLoadModule(argv[1],&argv[2],argc-2);
} else if (strchr(argv[0], '.')) { } else if (strchr(argv[0], '.')) {
if (argc != 2) { if (argc < 2) {
err = "Module config specified without value"; err = "Module config specified without value";
goto loaderr; goto loaderr;
} }
sds name = sdsdup(argv[0]); sds name = sdsdup(argv[0]);
if (!dictReplace(server.module_configs_queue, name, sdsdup(argv[1]))) sdsfree(name); sds val = sdsdup(argv[1]);
for (int i = 2; i < argc; i++)
val = sdscatfmt(val, " %S", argv[i]);
if (!dictReplace(server.module_configs_queue, name, val)) sdsfree(name);
} else if (!strcasecmp(argv[0],"sentinel")) { } else if (!strcasecmp(argv[0],"sentinel")) {
/* argc == 1 is handled by main() as we need to enter the sentinel /* argc == 1 is handled by main() as we need to enter the sentinel
* mode ASAP. */ * mode ASAP. */
......
...@@ -11325,6 +11325,7 @@ int moduleVerifyConfigFlags(unsigned int flags, configType type) { ...@@ -11325,6 +11325,7 @@ int moduleVerifyConfigFlags(unsigned int flags, configType type) {
| REDISMODULE_CONFIG_HIDDEN | REDISMODULE_CONFIG_HIDDEN
| REDISMODULE_CONFIG_PROTECTED | REDISMODULE_CONFIG_PROTECTED
| REDISMODULE_CONFIG_DENY_LOADING | REDISMODULE_CONFIG_DENY_LOADING
| REDISMODULE_CONFIG_BITFLAGS
| REDISMODULE_CONFIG_MEMORY))) { | REDISMODULE_CONFIG_MEMORY))) {
serverLogRaw(LL_WARNING, "Invalid flag(s) for configuration"); serverLogRaw(LL_WARNING, "Invalid flag(s) for configuration");
return REDISMODULE_ERR; return REDISMODULE_ERR;
...@@ -11333,6 +11334,10 @@ int moduleVerifyConfigFlags(unsigned int flags, configType type) { ...@@ -11333,6 +11334,10 @@ int moduleVerifyConfigFlags(unsigned int flags, configType type) {
serverLogRaw(LL_WARNING, "Numeric flag provided for non-numeric configuration."); serverLogRaw(LL_WARNING, "Numeric flag provided for non-numeric configuration.");
return REDISMODULE_ERR; return REDISMODULE_ERR;
} }
if (type != ENUM_CONFIG && flags & REDISMODULE_CONFIG_BITFLAGS) {
serverLogRaw(LL_WARNING, "Enum flag provided for non-enum configuration.");
return REDISMODULE_ERR;
}
return REDISMODULE_OK; return REDISMODULE_OK;
} }
...@@ -11534,6 +11539,12 @@ unsigned int maskModuleNumericConfigFlags(unsigned int flags) { ...@@ -11534,6 +11539,12 @@ unsigned int maskModuleNumericConfigFlags(unsigned int flags) {
return new_flags; return new_flags;
} }
unsigned int maskModuleEnumConfigFlags(unsigned int flags) {
unsigned int new_flags = 0;
if (flags & REDISMODULE_CONFIG_BITFLAGS) new_flags |= MULTI_ARG_CONFIG;
return new_flags;
}
/* Create a string config that Redis users can interact with via the Redis config file, /* Create a string config that Redis users can interact with via the Redis config file,
* `CONFIG SET`, `CONFIG GET`, and `CONFIG REWRITE` commands. * `CONFIG SET`, `CONFIG GET`, and `CONFIG REWRITE` commands.
* *
...@@ -11573,6 +11584,7 @@ unsigned int maskModuleNumericConfigFlags(unsigned int flags) { ...@@ -11573,6 +11584,7 @@ unsigned int maskModuleNumericConfigFlags(unsigned int flags) {
* * REDISMODULE_CONFIG_PROTECTED: This config will be only be modifiable based off the value of enable-protected-configs. * * REDISMODULE_CONFIG_PROTECTED: This config will be only be modifiable based off the value of enable-protected-configs.
* * REDISMODULE_CONFIG_DENY_LOADING: This config is not modifiable while the server is loading data. * * REDISMODULE_CONFIG_DENY_LOADING: This config is not modifiable while the server is loading data.
* * REDISMODULE_CONFIG_MEMORY: For numeric configs, this config will convert data unit notations into their byte equivalent. * * REDISMODULE_CONFIG_MEMORY: For numeric configs, this config will convert data unit notations into their byte equivalent.
* * REDISMODULE_CONFIG_BITFLAGS: For enum configs, this config will allow multiple entries to be combined as bit flags.
* *
* Default values are used on startup to set the value if it is not provided via the config file * Default values are used on startup to set the value if it is not provided via the config file
* or command line. Default values are also used to compare to on a config rewrite. * or command line. Default values are also used to compare to on a config rewrite.
...@@ -11688,7 +11700,7 @@ int RM_RegisterEnumConfig(RedisModuleCtx *ctx, const char *name, int default_val ...@@ -11688,7 +11700,7 @@ int RM_RegisterEnumConfig(RedisModuleCtx *ctx, const char *name, int default_val
enum_vals[num_enum_vals].name = NULL; enum_vals[num_enum_vals].name = NULL;
enum_vals[num_enum_vals].val = 0; enum_vals[num_enum_vals].val = 0;
listAddNodeTail(module->module_configs, new_config); listAddNodeTail(module->module_configs, new_config);
flags = maskModuleConfigFlags(flags); flags = maskModuleConfigFlags(flags) | maskModuleEnumConfigFlags(flags);
addModuleEnumConfig(module->name, name, flags, new_config, default_val, enum_vals); addModuleEnumConfig(module->name, name, flags, new_config, default_val, enum_vals);
return REDISMODULE_OK; return REDISMODULE_OK;
} }
......
...@@ -88,6 +88,7 @@ ...@@ -88,6 +88,7 @@
#define REDISMODULE_CONFIG_DENY_LOADING (1ULL<<6) /* This config is forbidden during loading. */ #define REDISMODULE_CONFIG_DENY_LOADING (1ULL<<6) /* This config is forbidden during loading. */
#define REDISMODULE_CONFIG_MEMORY (1ULL<<7) /* Indicates if this value can be set as a memory value */ #define REDISMODULE_CONFIG_MEMORY (1ULL<<7) /* Indicates if this value can be set as a memory value */
#define REDISMODULE_CONFIG_BITFLAGS (1ULL<<8) /* Indicates if this value can be set as a multiple enum values */
/* StreamID type. */ /* StreamID type. */
typedef struct RedisModuleStreamID { typedef struct RedisModuleStreamID {
......
...@@ -6,6 +6,7 @@ long long longval; ...@@ -6,6 +6,7 @@ long long longval;
long long memval; long long memval;
RedisModuleString *strval = NULL; RedisModuleString *strval = NULL;
int enumval; int enumval;
int flagsval;
/* Series of get and set callbacks for each type of config, these rely on the privdata ptr /* Series of get and set callbacks for each type of config, these rely on the privdata ptr
* to point to the config, and they register the configs as such. Note that one could also just * to point to the config, and they register the configs as such. Note that one could also just
...@@ -68,6 +69,20 @@ int setEnumConfigCommand(const char *name, int val, void *privdata, RedisModuleS ...@@ -68,6 +69,20 @@ int setEnumConfigCommand(const char *name, int val, void *privdata, RedisModuleS
return REDISMODULE_OK; return REDISMODULE_OK;
} }
int getFlagsConfigCommand(const char *name, void *privdata) {
REDISMODULE_NOT_USED(name);
REDISMODULE_NOT_USED(privdata);
return flagsval;
}
int setFlagsConfigCommand(const char *name, int val, void *privdata, RedisModuleString **err) {
REDISMODULE_NOT_USED(name);
REDISMODULE_NOT_USED(err);
REDISMODULE_NOT_USED(privdata);
flagsval = val;
return REDISMODULE_OK;
}
int boolApplyFunc(RedisModuleCtx *ctx, void *privdata, RedisModuleString **err) { int boolApplyFunc(RedisModuleCtx *ctx, void *privdata, RedisModuleString **err) {
REDISMODULE_NOT_USED(ctx); REDISMODULE_NOT_USED(ctx);
REDISMODULE_NOT_USED(privdata); REDISMODULE_NOT_USED(privdata);
...@@ -106,10 +121,13 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -106,10 +121,13 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
} }
/* On the stack to make sure we're copying them. */ /* On the stack to make sure we're copying them. */
const char *enum_vals[3] = {"one", "two", "three"}; const char *enum_vals[] = {"none", "one", "two", "three"};
const int int_vals[3] = {0, 2, 4}; const int int_vals[] = {0, 1, 2, 4};
if (RedisModule_RegisterEnumConfig(ctx, "enum", 0, REDISMODULE_CONFIG_DEFAULT, enum_vals, int_vals, 3, getEnumConfigCommand, setEnumConfigCommand, NULL, NULL) == REDISMODULE_ERR) { if (RedisModule_RegisterEnumConfig(ctx, "enum", 1, REDISMODULE_CONFIG_DEFAULT, enum_vals, int_vals, 4, getEnumConfigCommand, setEnumConfigCommand, NULL, NULL) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
if (RedisModule_RegisterEnumConfig(ctx, "flags", 3, REDISMODULE_CONFIG_DEFAULT | REDISMODULE_CONFIG_BITFLAGS, enum_vals, int_vals, 4, getFlagsConfigCommand, setFlagsConfigCommand, NULL, NULL) == REDISMODULE_ERR) {
return REDISMODULE_ERR; return REDISMODULE_ERR;
} }
/* Memory config here. */ /* Memory config here. */
......
...@@ -11,6 +11,7 @@ start_server {tags {"modules"}} { ...@@ -11,6 +11,7 @@ start_server {tags {"modules"}} {
assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1024" assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1024"
assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {secret password}" assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {secret password}"
assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one" assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one"
assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {one two}"
assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1"
} }
...@@ -29,6 +30,10 @@ start_server {tags {"modules"}} { ...@@ -29,6 +30,10 @@ start_server {tags {"modules"}} {
assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {super \0secret password}" assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {super \0secret password}"
r config set moduleconfigs.enum two r config set moduleconfigs.enum two
assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two" assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two"
r config set moduleconfigs.flags two
assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags two"
r config set moduleconfigs.flags "two three"
assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {two three}"
r config set moduleconfigs.numeric -2 r config set moduleconfigs.numeric -2
assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -2" assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -2"
} }
...@@ -67,6 +72,7 @@ start_server {tags {"modules"}} { ...@@ -67,6 +72,7 @@ start_server {tags {"modules"}} {
assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1024" assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1024"
assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {secret password}" assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {secret password}"
assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one" assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one"
assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {one two}"
assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1"
r module unload moduleconfigs r module unload moduleconfigs
} }
...@@ -80,6 +86,7 @@ start_server {tags {"modules"}} { ...@@ -80,6 +86,7 @@ start_server {tags {"modules"}} {
assert_equal [r config get moduleconfigs.string] "moduleconfigs.string tclortickle" assert_equal [r config get moduleconfigs.string] "moduleconfigs.string tclortickle"
# Configs that were not changed should still be their module specified value # Configs that were not changed should still be their module specified value
assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one" assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum one"
assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {one two}"
assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1"
} }
...@@ -140,6 +147,7 @@ start_server {tags {"modules"}} { ...@@ -140,6 +147,7 @@ start_server {tags {"modules"}} {
r config set moduleconfigs.mutable_bool yes r config set moduleconfigs.mutable_bool yes
r config set moduleconfigs.memory_numeric 750 r config set moduleconfigs.memory_numeric 750
r config set moduleconfigs.enum two r config set moduleconfigs.enum two
r config set moduleconfigs.flags "two three"
r config rewrite r config rewrite
restart_server 0 true false restart_server 0 true false
# Ensure configs we rewrote are present and that the conf file is readable # Ensure configs we rewrote are present and that the conf file is readable
...@@ -147,6 +155,7 @@ start_server {tags {"modules"}} { ...@@ -147,6 +155,7 @@ start_server {tags {"modules"}} {
assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 750" assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 750"
assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {super \0secret password}" assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {super \0secret password}"
assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two" assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two"
assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {two three}"
assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1"
r module unload moduleconfigs r module unload moduleconfigs
} }
...@@ -221,11 +230,12 @@ start_server {tags {"modules"}} { ...@@ -221,11 +230,12 @@ start_server {tags {"modules"}} {
set stdout [dict get $noload stdout] set stdout [dict get $noload stdout]
assert_equal [count_message_lines $stdout "Module Configurations were not set, likely a missing LoadConfigs call. Unloading the module."] 1 assert_equal [count_message_lines $stdout "Module Configurations were not set, likely a missing LoadConfigs call. Unloading the module."] 1
start_server [list overrides [list loadmodule "$testmodule" moduleconfigs.string "bootedup" moduleconfigs.enum two]] { start_server [list overrides [list loadmodule "$testmodule" moduleconfigs.string "bootedup" moduleconfigs.enum two moduleconfigs.flags "two three"]] {
assert_equal [r config get moduleconfigs.string] "moduleconfigs.string bootedup" assert_equal [r config get moduleconfigs.string] "moduleconfigs.string bootedup"
assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool yes" assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool yes"
assert_equal [r config get moduleconfigs.immutable_bool] "moduleconfigs.immutable_bool no" assert_equal [r config get moduleconfigs.immutable_bool] "moduleconfigs.immutable_bool no"
assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two" assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two"
assert_equal [r config get moduleconfigs.flags] "moduleconfigs.flags {two three}"
assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1" assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1"
assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1024" assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1024"
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment