Unverified Commit bda9d74d authored by Nick Chun's avatar Nick Chun Committed by GitHub
Browse files

Module Configurations (#10285)



This feature adds the ability to add four different types (Bool, Numeric,
String, Enum) of configurations to a module to be accessed via the redis
config file, and the CONFIG command.

**Configuration Names**:

We impose a restriction that a module configuration always starts with the
module name and contains a '.' followed by the config name. If a module passes
"config1" as the name to a register function, it will be registered as MODULENAME.config1.

**Configuration Persistence**:

Module Configurations exist only as long as a module is loaded. If a module is
unloaded, the configurations are removed.
There is now also a minimal core API for removal of standardConfig objects
from configs by name.

**Get and Set Callbacks**:

Storage of config values is owned by the module that registers them, and provides
callbacks for Redis to access and manipulate the values.
This is exposed through a GET and SET callback.

The get callback returns a typed value of the config to redis. The callback takes
the name of the configuration, and also a privdata pointer. Note that these only
take the CONFIGNAME portion of the config, not the entire MODULENAME.CONFIGNAME.

```
 typedef RedisModuleString * (*RedisModuleConfigGetStringFunc)(const char *name, void *privdata);
 typedef long long (*RedisModuleConfigGetNumericFunc)(const char *name, void *privdata);
 typedef int (*RedisModuleConfigGetBoolFunc)(const char *name, void *privdata);
 typedef int (*RedisModuleConfigGetEnumFunc)(const char *name, void *privdata);
```

Configs must also must specify a set callback, i.e. what to do on a CONFIG SET XYZ 123
or when loading configurations from cli/.conf file matching these typedefs. *name* is
again just the CONFIGNAME portion, *val* is the parsed value from the core,
*privdata* is the registration time privdata pointer, and *err* is for providing errors to a client.

```
typedef int (*RedisModuleConfigSetStringFunc)(const char *name, RedisModuleString *val, void *privdata, RedisModuleString **err);
typedef int (*RedisModuleConfigSetNumericFunc)(const char *name, long long val, void *privdata, RedisModuleString **err);
typedef int (*RedisModuleConfigSetBoolFunc)(const char *name, int val, void *privdata, RedisModuleString **err);
typedef int (*RedisModuleConfigSetEnumFunc)(const char *name, int val, void *privdata, RedisModuleString **err);
```

Modules can also specify an optional apply callback that will be called after
value(s) have been set via CONFIG SET:

```
typedef int (*RedisModuleConfigApplyFunc)(RedisModuleCtx *ctx, void *privdata, RedisModuleString **err);
```

**Flags:**
We expose 7 new flags to the module, which are used as part of the config registration.

```
#define REDISMODULE_CONFIG_MODIFIABLE 0 /* This is the default for a module config. */
#define REDISMODULE_CONFIG_IMMUTABLE (1ULL<<0) /* Can this value only be set at startup? */
#define REDISMODULE_CONFIG_SENSITIVE (1ULL<<1) /* Does this value contain sensitive information */
#define REDISMODULE_CONFIG_HIDDEN (1ULL<<4) /* This config is hidden in `config get <pattern>` (used for tests/debugging) */
#define REDISMODULE_CONFIG_PROTECTED (1ULL<<5) /* Becomes immutable if enable-protected-configs is enabled. */
#define REDISMODULE_CONFIG_DENY_LOADING (1ULL<<6) /* This config is forbidden during loading. */
/* Numeric Specific Configs */
#define REDISMODULE_CONFIG_MEMORY (1ULL<<7) /* Indicates if this value can be set as a memory value */
```

**Module Registration APIs**:

```
int (*RedisModule_RegisterBoolConfig)(RedisModuleCtx *ctx, char *name, int default_val, unsigned int flags, RedisModuleConfigGetBoolFunc getfn, RedisModuleConfigSetBoolFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata);
int (*RedisModule_RegisterNumericConfig)(RedisModuleCtx *ctx, const char *name, long long default_val, unsigned int flags, long long min, long long max, RedisModuleConfigGetNumericFunc getfn, RedisModuleConfigSetNumericFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata);
int (*RedisModule_RegisterStringConfig)(RedisModuleCtx *ctx, const char *name, const char *default_val, unsigned int flags, RedisModuleConfigGetStringFunc getfn, RedisModuleConfigSetStringFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata);
int (*RedisModule_RegisterEnumConfig)(RedisModuleCtx *ctx, const char *name, int default_val, unsigned int flags, const char **enum_values, const int *int_values, int num_enum_vals, RedisModuleConfigGetEnumFunc getfn, RedisModuleConfigSetEnumFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata);
int (*RedisModule_LoadConfigs)(RedisModuleCtx *ctx);
```

The module name will be auto appended along with a "." to the front of the name of the config.

**What RM_Register[...]Config does**:

A RedisModule struct now keeps a list of ModuleConfig objects which look like:
```
typedef struct ModuleConfig {
    sds name; /* Name of config without the module name appended to the front */
    void *privdata; /* Optional data passed into the module config callbacks */
    union get_fn { /* The get callback specificed by the module */
        RedisModuleConfigGetStringFunc get_string;
        RedisModuleConfigGetNumericFunc get_numeric;
        RedisModuleConfigGetBoolFunc get_bool;
        RedisModuleConfigGetEnumFunc get_enum;
    } get_fn;
    union set_fn { /* The set callback specified by the module */
        RedisModuleConfigSetStringFunc set_string;
        RedisModuleConfigSetNumericFunc set_numeric;
        RedisModuleConfigSetBoolFunc set_bool;
        RedisModuleConfigSetEnumFunc set_enum;
    } set_fn;
    RedisModuleConfigApplyFunc apply_fn;
    RedisModule *module;
} ModuleConfig;
```
It also registers a standardConfig in the configs array, with a pointer to the
ModuleConfig object associated with it.

**What happens on a CONFIG GET/SET MODULENAME.MODULECONFIG:**

For CONFIG SET, we do the same parsing as is done in config.c and pass that
as the argument to the module set callback. For CONFIG GET, we call the
module get callback and return that value to config.c to return to a client.

**CONFIG REWRITE**:

Starting up a server with module configurations in a .conf file but no module load
directive will fail. The flip side is also true, specifying a module load and a bunch
of module configurations will load those configurations in using the module defined
set callbacks on a RM_LoadConfigs call. Configs being rewritten works the same
way as it does for standard configs, as the module has the ability to specify a
default value. If a module is unloaded with configurations specified in the .conf file
those configurations will be commented out from the .conf file on the next config rewrite.

**RM_LoadConfigs:**

`RedisModule_LoadConfigs(RedisModuleCtx *ctx);`

This last API is used to make configs available within the onLoad() after they have
been registered. The expected usage is that a module will register all of its configs,
then call LoadConfigs to trigger all of the set callbacks, and then can error out if any
of them were malformed. LoadConfigs will attempt to set all configs registered to
either a .conf file argument/loadex argument or their default value if an argument is
not specified. **LoadConfigs is a required function if configs are registered.
** Also note that LoadConfigs **does not** call the apply callbacks, but a module
can do that directly after the LoadConfigs call.

**New Command: MODULE LOADEX [CONFIG NAME VALUE] [ARGS ...]:**

This command provides the ability to provide startup context information to a module.
LOADEX stands for "load extended" similar to GETEX. Note that provided config
names need the full MODULENAME.MODULECONFIG name. Any additional
arguments a module might want are intended to be specified after ARGS.
Everything after ARGS is passed to onLoad as RedisModuleString **argv.
Co-authored-by: default avatarMadelyn Olson <madelyneolson@gmail.com>
Co-authored-by: default avatarMadelyn Olson <matolson@amazon.com>
Co-authored-by: default avatarsundb <sundbcn@gmail.com>
Co-authored-by: default avatarMadelyn Olson <34459052+madolson@users.noreply.github.com>
Co-authored-by: default avatarOran Agra <oran@redislabs.com>
Co-authored-by: default avatarYossi Gottlieb <yossigo@gmail.com>
parent e2fa6aa1
...@@ -20,6 +20,7 @@ $TCLSH tests/test_helper.tcl \ ...@@ -20,6 +20,7 @@ $TCLSH tests/test_helper.tcl \
--single unit/moduleapi/fork \ --single unit/moduleapi/fork \
--single unit/moduleapi/testrdb \ --single unit/moduleapi/testrdb \
--single unit/moduleapi/infotest \ --single unit/moduleapi/infotest \
--single unit/moduleapi/moduleconfigs \
--single unit/moduleapi/infra \ --single unit/moduleapi/infra \
--single unit/moduleapi/propagate \ --single unit/moduleapi/propagate \
--single unit/moduleapi/hooks \ --single unit/moduleapi/hooks \
......
...@@ -4776,6 +4776,35 @@ struct redisCommandArg MODULE_LOAD_Args[] = { ...@@ -4776,6 +4776,35 @@ struct redisCommandArg MODULE_LOAD_Args[] = {
{0} {0}
}; };
/********** MODULE LOADEX ********************/
/* MODULE LOADEX history */
#define MODULE_LOADEX_History NULL
/* MODULE LOADEX tips */
#define MODULE_LOADEX_tips NULL
/* MODULE LOADEX configs argument table */
struct redisCommandArg MODULE_LOADEX_configs_Subargs[] = {
{"name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE},
{"value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE},
{0}
};
/* MODULE LOADEX args argument table */
struct redisCommandArg MODULE_LOADEX_args_Subargs[] = {
{"arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE},
{0}
};
/* MODULE LOADEX argument table */
struct redisCommandArg MODULE_LOADEX_Args[] = {
{"path",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE},
{"configs",ARG_TYPE_BLOCK,-1,"CONFIG",NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,.subargs=MODULE_LOADEX_configs_Subargs},
{"args",ARG_TYPE_BLOCK,-1,"ARGS",NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,.subargs=MODULE_LOADEX_args_Subargs},
{0}
};
/********** MODULE UNLOAD ********************/ /********** MODULE UNLOAD ********************/
/* MODULE UNLOAD history */ /* MODULE UNLOAD history */
...@@ -4795,6 +4824,7 @@ struct redisCommand MODULE_Subcommands[] = { ...@@ -4795,6 +4824,7 @@ struct redisCommand MODULE_Subcommands[] = {
{"help","Show helpful text about the different subcommands","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,MODULE_HELP_History,MODULE_HELP_tips,moduleCommand,2,CMD_LOADING|CMD_STALE,0}, {"help","Show helpful text about the different subcommands","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,MODULE_HELP_History,MODULE_HELP_tips,moduleCommand,2,CMD_LOADING|CMD_STALE,0},
{"list","List all modules loaded by the server","O(N) where N is the number of loaded modules.","4.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,MODULE_LIST_History,MODULE_LIST_tips,moduleCommand,2,CMD_ADMIN|CMD_NOSCRIPT,0}, {"list","List all modules loaded by the server","O(N) where N is the number of loaded modules.","4.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,MODULE_LIST_History,MODULE_LIST_tips,moduleCommand,2,CMD_ADMIN|CMD_NOSCRIPT,0},
{"load","Load a module","O(1)","4.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,MODULE_LOAD_History,MODULE_LOAD_tips,moduleCommand,-3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_NOSCRIPT|CMD_PROTECTED,0,.args=MODULE_LOAD_Args}, {"load","Load a module","O(1)","4.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,MODULE_LOAD_History,MODULE_LOAD_tips,moduleCommand,-3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_NOSCRIPT|CMD_PROTECTED,0,.args=MODULE_LOAD_Args},
{"loadex","Load a module with extended parameters","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,MODULE_LOADEX_History,MODULE_LOADEX_tips,moduleCommand,-3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_NOSCRIPT|CMD_PROTECTED,0,.args=MODULE_LOADEX_Args},
{"unload","Unload a module","O(1)","4.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,MODULE_UNLOAD_History,MODULE_UNLOAD_tips,moduleCommand,3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_NOSCRIPT|CMD_PROTECTED,0,.args=MODULE_UNLOAD_Args}, {"unload","Unload a module","O(1)","4.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,MODULE_UNLOAD_History,MODULE_UNLOAD_tips,moduleCommand,3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_NOSCRIPT|CMD_PROTECTED,0,.args=MODULE_UNLOAD_Args},
{0} {0}
}; };
......
{
"LOADEX": {
"summary": "Load a module with extended parameters",
"complexity": "O(1)",
"group": "server",
"since": "7.0.0",
"arity": -3,
"container": "MODULE",
"function": "moduleCommand",
"command_flags": [
"NO_ASYNC_LOADING",
"ADMIN",
"NOSCRIPT",
"PROTECTED"
],
"arguments": [
{
"name": "path",
"type": "string"
},
{
"name": "configs",
"token": "CONFIG",
"type": "block",
"multiple": true,
"optional": true,
"arguments": [
{
"name": "name",
"type": "string"
},
{
"name": "value",
"type": "string"
}
]
},
{
"name": "args",
"token": "ARGS",
"type": "block",
"multiple": true,
"optional": true,
"arguments": [
{
"name": "arg",
"type": "string"
}
]
}
]
}
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
...@@ -80,6 +80,15 @@ ...@@ -80,6 +80,15 @@
#define REDISMODULE_HASH_EXISTS (1<<3) #define REDISMODULE_HASH_EXISTS (1<<3)
#define REDISMODULE_HASH_COUNT_ALL (1<<4) #define REDISMODULE_HASH_COUNT_ALL (1<<4)
#define REDISMODULE_CONFIG_DEFAULT 0 /* This is the default for a module config. */
#define REDISMODULE_CONFIG_IMMUTABLE (1ULL<<0) /* Can this value only be set at startup? */
#define REDISMODULE_CONFIG_SENSITIVE (1ULL<<1) /* Does this value contain sensitive information */
#define REDISMODULE_CONFIG_HIDDEN (1ULL<<4) /* This config is hidden in `config get <pattern>` (used for tests/debugging) */
#define REDISMODULE_CONFIG_PROTECTED (1ULL<<5) /* Becomes immutable if enable-protected-configs is enabled. */
#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 */
/* StreamID type. */ /* StreamID type. */
typedef struct RedisModuleStreamID { typedef struct RedisModuleStreamID {
uint64_t ms; uint64_t ms;
...@@ -807,6 +816,15 @@ typedef void (*RedisModuleScanCB)(RedisModuleCtx *ctx, RedisModuleString *keynam ...@@ -807,6 +816,15 @@ typedef void (*RedisModuleScanCB)(RedisModuleCtx *ctx, RedisModuleString *keynam
typedef void (*RedisModuleScanKeyCB)(RedisModuleKey *key, RedisModuleString *field, RedisModuleString *value, void *privdata); typedef void (*RedisModuleScanKeyCB)(RedisModuleKey *key, RedisModuleString *field, RedisModuleString *value, void *privdata);
typedef void (*RedisModuleUserChangedFunc) (uint64_t client_id, void *privdata); typedef void (*RedisModuleUserChangedFunc) (uint64_t client_id, void *privdata);
typedef int (*RedisModuleDefragFunc)(RedisModuleDefragCtx *ctx); typedef int (*RedisModuleDefragFunc)(RedisModuleDefragCtx *ctx);
typedef RedisModuleString * (*RedisModuleConfigGetStringFunc)(const char *name, void *privdata);
typedef long long (*RedisModuleConfigGetNumericFunc)(const char *name, void *privdata);
typedef int (*RedisModuleConfigGetBoolFunc)(const char *name, void *privdata);
typedef int (*RedisModuleConfigGetEnumFunc)(const char *name, void *privdata);
typedef int (*RedisModuleConfigSetStringFunc)(const char *name, RedisModuleString *val, void *privdata, RedisModuleString **err);
typedef int (*RedisModuleConfigSetNumericFunc)(const char *name, long long val, void *privdata, RedisModuleString **err);
typedef int (*RedisModuleConfigSetBoolFunc)(const char *name, int val, void *privdata, RedisModuleString **err);
typedef int (*RedisModuleConfigSetEnumFunc)(const char *name, int val, void *privdata, RedisModuleString **err);
typedef int (*RedisModuleConfigApplyFunc)(RedisModuleCtx *ctx, void *privdata, RedisModuleString **err);
typedef struct RedisModuleTypeMethods { typedef struct RedisModuleTypeMethods {
uint64_t version; uint64_t version;
...@@ -1159,6 +1177,11 @@ REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromDefragCtx) ...@@ -1159,6 +1177,11 @@ REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromDefragCtx)
REDISMODULE_API int (*RedisModule_EventLoopAdd)(int fd, int mask, RedisModuleEventLoopFunc func, void *user_data) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_EventLoopAdd)(int fd, int mask, RedisModuleEventLoopFunc func, void *user_data) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_EventLoopDel)(int fd, int mask) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_EventLoopDel)(int fd, int mask) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_EventLoopAddOneShot)(RedisModuleEventLoopOneShotFunc func, void *user_data) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_EventLoopAddOneShot)(RedisModuleEventLoopOneShotFunc func, void *user_data) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_RegisterBoolConfig)(RedisModuleCtx *ctx, char *name, int default_val, unsigned int flags, RedisModuleConfigGetBoolFunc getfn, RedisModuleConfigSetBoolFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_RegisterNumericConfig)(RedisModuleCtx *ctx, const char *name, long long default_val, unsigned int flags, long long min, long long max, RedisModuleConfigGetNumericFunc getfn, RedisModuleConfigSetNumericFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_RegisterStringConfig)(RedisModuleCtx *ctx, const char *name, const char *default_val, unsigned int flags, RedisModuleConfigGetStringFunc getfn, RedisModuleConfigSetStringFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_RegisterEnumConfig)(RedisModuleCtx *ctx, const char *name, int default_val, unsigned int flags, const char **enum_values, const int *int_values, int num_enum_vals, RedisModuleConfigGetEnumFunc getfn, RedisModuleConfigSetEnumFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_LoadConfigs)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
#define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX) #define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX)
...@@ -1483,6 +1506,11 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int ...@@ -1483,6 +1506,11 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(EventLoopAdd); REDISMODULE_GET_API(EventLoopAdd);
REDISMODULE_GET_API(EventLoopDel); REDISMODULE_GET_API(EventLoopDel);
REDISMODULE_GET_API(EventLoopAddOneShot); REDISMODULE_GET_API(EventLoopAddOneShot);
REDISMODULE_GET_API(RegisterBoolConfig);
REDISMODULE_GET_API(RegisterNumericConfig);
REDISMODULE_GET_API(RegisterStringConfig);
REDISMODULE_GET_API(RegisterEnumConfig);
REDISMODULE_GET_API(LoadConfigs);
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR; if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
RedisModule_SetModuleAttribs(ctx,name,ver,apiver); RedisModule_SetModuleAttribs(ctx,name,ver,apiver);
......
...@@ -391,7 +391,6 @@ void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privd ...@@ -391,7 +391,6 @@ void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privd
sentinelRedisInstance *sentinelGetMasterByName(char *name); sentinelRedisInstance *sentinelGetMasterByName(char *name);
char *sentinelGetSubjectiveLeader(sentinelRedisInstance *master); char *sentinelGetSubjectiveLeader(sentinelRedisInstance *master);
char *sentinelGetObjectiveLeader(sentinelRedisInstance *master); char *sentinelGetObjectiveLeader(sentinelRedisInstance *master);
int yesnotoi(char *s);
void instanceLinkConnectionError(const redisAsyncContext *c); void instanceLinkConnectionError(const redisAsyncContext *c);
const char *sentinelRedisInstanceTypeStr(sentinelRedisInstance *ri); const char *sentinelRedisInstanceTypeStr(sentinelRedisInstance *ri);
void sentinelAbortFailover(sentinelRedisInstance *ri); void sentinelAbortFailover(sentinelRedisInstance *ri);
......
...@@ -760,6 +760,8 @@ struct RedisModule { ...@@ -760,6 +760,8 @@ struct RedisModule {
list *usedby; /* List of modules using APIs from this one. */ list *usedby; /* List of modules using APIs from this one. */
list *using; /* List of modules we use some APIs of. */ list *using; /* List of modules we use some APIs of. */
list *filters; /* List of filters the module has registered. */ list *filters; /* List of filters the module has registered. */
list *module_configs; /* List of configurations the module has registered */
int configs_initialized; /* Have the module configurations been initialized? */
int in_call; /* RM_Call() nesting level */ int in_call; /* RM_Call() nesting level */
int in_hook; /* Hooks callback nesting level for this module (0 or 1). */ int in_hook; /* Hooks callback nesting level for this module (0 or 1). */
int options; /* Module options and capabilities. */ int options; /* Module options and capabilities. */
...@@ -1474,6 +1476,7 @@ struct redisServer { ...@@ -1474,6 +1476,7 @@ struct redisServer {
dict *moduleapi; /* Exported core APIs dictionary for modules. */ dict *moduleapi; /* Exported core APIs dictionary for modules. */
dict *sharedapi; /* Like moduleapi but containing the APIs that dict *sharedapi; /* Like moduleapi but containing the APIs that
modules share with each other. */ modules share with each other. */
dict *module_configs_queue; /* Dict that stores module configurations from .conf file until after modules are loaded during startup or arguments to loadex. */
list *loadmodule_queue; /* List of modules to load at startup. */ list *loadmodule_queue; /* List of modules to load at startup. */
int module_pipe[2]; /* Pipe used to awake the event loop by module threads. */ int module_pipe[2]; /* Pipe used to awake the event loop by module threads. */
pid_t child_pid; /* PID of current child */ pid_t child_pid; /* PID of current child */
...@@ -2338,7 +2341,8 @@ int populateArgsStructure(struct redisCommandArg *args); ...@@ -2338,7 +2341,8 @@ int populateArgsStructure(struct redisCommandArg *args);
void moduleInitModulesSystem(void); void moduleInitModulesSystem(void);
void moduleInitModulesSystemLast(void); void moduleInitModulesSystemLast(void);
void modulesCron(void); void modulesCron(void);
int moduleLoad(const char *path, void **argv, int argc); int moduleLoad(const char *path, void **argv, int argc, int is_loadex);
int moduleUnload(sds name);
void moduleLoadFromQueue(void); void moduleLoadFromQueue(void);
int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result); int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
int moduleGetCommandChannelsViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result); int moduleGetCommandChannelsViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
...@@ -2970,6 +2974,40 @@ int keyspaceEventsStringToFlags(char *classes); ...@@ -2970,6 +2974,40 @@ int keyspaceEventsStringToFlags(char *classes);
sds keyspaceEventsFlagsToString(int flags); sds keyspaceEventsFlagsToString(int flags);
/* Configuration */ /* Configuration */
/* Configuration Flags */
#define MODIFIABLE_CONFIG 0 /* This is the implied default for a standard
* config, which is mutable. */
#define IMMUTABLE_CONFIG (1ULL<<0) /* Can this value only be set at startup? */
#define SENSITIVE_CONFIG (1ULL<<1) /* Does this value contain sensitive information */
#define DEBUG_CONFIG (1ULL<<2) /* Values that are useful for debugging. */
#define MULTI_ARG_CONFIG (1ULL<<3) /* This config receives multiple arguments. */
#define HIDDEN_CONFIG (1ULL<<4) /* This config is hidden in `config get <pattern>` (used for tests/debugging) */
#define PROTECTED_CONFIG (1ULL<<5) /* Becomes immutable if enable-protected-configs is enabled. */
#define DENY_LOADING_CONFIG (1ULL<<6) /* This config is forbidden during loading. */
#define ALIAS_CONFIG (1ULL<<7) /* For configs with multiple names, this flag is set on the alias. */
#define MODULE_CONFIG (1ULL<<8) /* This config is a module config */
#define INTEGER_CONFIG 0 /* No flags means a simple integer configuration */
#define MEMORY_CONFIG (1<<0) /* Indicates if this value can be loaded as a memory value */
#define PERCENT_CONFIG (1<<1) /* Indicates if this value can be loaded as a percent (and stored as a negative int) */
#define OCTAL_CONFIG (1<<2) /* This value uses octal representation */
/* Enum Configs contain an array of configEnum objects that match a string with an integer. */
typedef struct configEnum {
char *name;
int val;
} configEnum;
/* Type of configuration. */
typedef enum {
BOOL_CONFIG,
NUMERIC_CONFIG,
STRING_CONFIG,
SDS_CONFIG,
ENUM_CONFIG,
SPECIAL_CONFIG,
} configType;
void loadServerConfig(char *filename, char config_from_stdin, char *options); void loadServerConfig(char *filename, char config_from_stdin, char *options);
void appendServerSaveParams(time_t seconds, int changes); void appendServerSaveParams(time_t seconds, int changes);
void resetServerSaveParams(void); void resetServerSaveParams(void);
...@@ -2978,9 +3016,29 @@ void rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *opti ...@@ -2978,9 +3016,29 @@ void rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *opti
void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, const char *option); void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, const char *option);
int rewriteConfig(char *path, int force_write); int rewriteConfig(char *path, int force_write);
void initConfigValues(); void initConfigValues();
void removeConfig(sds name);
sds getConfigDebugInfo(); sds getConfigDebugInfo();
int allowProtectedAction(int config, client *c); int allowProtectedAction(int config, client *c);
/* Module Configuration */
typedef struct ModuleConfig ModuleConfig;
int performModuleConfigSetFromName(sds name, sds value, const char **err);
int performModuleConfigSetDefaultFromName(sds name, const char **err);
void addModuleBoolConfig(const char *module_name, const char *name, int flags, void *privdata, int default_val);
void addModuleStringConfig(const char *module_name, const char *name, int flags, void *privdata, sds default_val);
void addModuleEnumConfig(const char *module_name, const char *name, int flags, void *privdata, int default_val, configEnum *enum_vals);
void addModuleNumericConfig(const char *module_name, const char *name, int flags, void *privdata, long long default_val, int conf_flags, long long lower, long long upper);
void addModuleConfigApply(list *module_configs, ModuleConfig *module_config);
int moduleConfigApplyConfig(list *module_configs, const char **err, const char **err_arg_name);
int getModuleBoolConfig(ModuleConfig *module_config);
int setModuleBoolConfig(ModuleConfig *config, int val, const char **err);
sds getModuleStringConfig(ModuleConfig *module_config);
int setModuleStringConfig(ModuleConfig *config, sds strval, const char **err);
int getModuleEnumConfig(ModuleConfig *module_config);
int setModuleEnumConfig(ModuleConfig *config, int val, const char **err);
long long getModuleNumericConfig(ModuleConfig *module_config);
int setModuleNumericConfig(ModuleConfig *config, long long val, const char **err);
/* db.c -- Keyspace access API */ /* db.c -- Keyspace access API */
int removeExpire(redisDb *db, robj *key); int removeExpire(redisDb *db, robj *key);
void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj); void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj);
......
...@@ -75,6 +75,7 @@ int string2d(const char *s, size_t slen, double *dp); ...@@ -75,6 +75,7 @@ int string2d(const char *s, size_t slen, double *dp);
int trimDoubleString(char *buf, size_t len); int trimDoubleString(char *buf, size_t len);
int d2string(char *buf, size_t len, double value); int d2string(char *buf, size_t len, double value);
int ld2string(char *buf, size_t len, long double value, ld2string_mode mode); int ld2string(char *buf, size_t len, long double value, ld2string_mode mode);
int yesnotoi(char *s);
sds getAbsolutePath(char *filename); sds getAbsolutePath(char *filename);
long getTimeZone(void); long getTimeZone(void);
int pathIsBaseName(char *path); int pathIsBaseName(char *path);
......
...@@ -54,7 +54,9 @@ TEST_MODULES = \ ...@@ -54,7 +54,9 @@ TEST_MODULES = \
subcommands.so \ subcommands.so \
reply.so \ reply.so \
cmdintrospection.so \ cmdintrospection.so \
eventloop.so eventloop.so \
moduleconfigs.so \
moduleconfigstwo.so
.PHONY: all .PHONY: all
......
#include "redismodule.h"
#include <strings.h>
int mutable_bool_val;
int immutable_bool_val;
long long longval;
long long memval;
RedisModuleString *strval = NULL;
int enumval;
/* 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
* use names if they wanted, and store anything in privdata. */
int getBoolConfigCommand(const char *name, void *privdata) {
REDISMODULE_NOT_USED(name);
return (*(int *)privdata);
}
int setBoolConfigCommand(const char *name, int new, void *privdata, RedisModuleString **err) {
REDISMODULE_NOT_USED(name);
REDISMODULE_NOT_USED(err);
*(int *)privdata = new;
return REDISMODULE_OK;
}
long long getNumericConfigCommand(const char *name, void *privdata) {
REDISMODULE_NOT_USED(name);
return (*(long long *) privdata);
}
int setNumericConfigCommand(const char *name, long long new, void *privdata, RedisModuleString **err) {
REDISMODULE_NOT_USED(name);
REDISMODULE_NOT_USED(err);
*(long long *)privdata = new;
return REDISMODULE_OK;
}
RedisModuleString *getStringConfigCommand(const char *name, void *privdata) {
REDISMODULE_NOT_USED(name);
REDISMODULE_NOT_USED(privdata);
return strval;
}
int setStringConfigCommand(const char *name, RedisModuleString *new, void *privdata, RedisModuleString **err) {
REDISMODULE_NOT_USED(name);
REDISMODULE_NOT_USED(err);
REDISMODULE_NOT_USED(privdata);
size_t len;
if (!strcasecmp(RedisModule_StringPtrLen(new, &len), "rejectisfreed")) {
*err = RedisModule_CreateString(NULL, "Cannot set string to 'rejectisfreed'", 36);
return REDISMODULE_ERR;
}
RedisModule_Free(strval);
RedisModule_RetainString(NULL, new);
strval = new;
return REDISMODULE_OK;
}
int getEnumConfigCommand(const char *name, void *privdata) {
REDISMODULE_NOT_USED(name);
REDISMODULE_NOT_USED(privdata);
return enumval;
}
int setEnumConfigCommand(const char *name, int val, void *privdata, RedisModuleString **err) {
REDISMODULE_NOT_USED(name);
REDISMODULE_NOT_USED(err);
REDISMODULE_NOT_USED(privdata);
enumval = val;
return REDISMODULE_OK;
}
int boolApplyFunc(RedisModuleCtx *ctx, void *privdata, RedisModuleString **err) {
REDISMODULE_NOT_USED(ctx);
REDISMODULE_NOT_USED(privdata);
if (mutable_bool_val && immutable_bool_val) {
*err = RedisModule_CreateString(NULL, "Bool configs cannot both be yes.", 32);
return REDISMODULE_ERR;
}
return REDISMODULE_OK;
}
int longlongApplyFunc(RedisModuleCtx *ctx, void *privdata, RedisModuleString **err) {
REDISMODULE_NOT_USED(ctx);
REDISMODULE_NOT_USED(privdata);
if (longval == memval) {
*err = RedisModule_CreateString(NULL, "These configs cannot equal each other.", 38);
return REDISMODULE_ERR;
}
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
if (RedisModule_Init(ctx, "moduleconfigs", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) return REDISMODULE_ERR;
if (RedisModule_RegisterBoolConfig(ctx, "mutable_bool", 1, REDISMODULE_CONFIG_DEFAULT, getBoolConfigCommand, setBoolConfigCommand, boolApplyFunc, &mutable_bool_val) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
/* Immutable config here. */
if (RedisModule_RegisterBoolConfig(ctx, "immutable_bool", 0, REDISMODULE_CONFIG_IMMUTABLE, getBoolConfigCommand, setBoolConfigCommand, boolApplyFunc, &immutable_bool_val) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
if (RedisModule_RegisterStringConfig(ctx, "string", "secret password", REDISMODULE_CONFIG_DEFAULT, getStringConfigCommand, setStringConfigCommand, NULL, NULL) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
/* On the stack to make sure we're copying them. */
const char *enum_vals[3] = {"one", "two", "three"};
const int int_vals[3] = {0, 2, 4};
if (RedisModule_RegisterEnumConfig(ctx, "enum", 0, REDISMODULE_CONFIG_DEFAULT, enum_vals, int_vals, 3, getEnumConfigCommand, setEnumConfigCommand, NULL, NULL) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
/* Memory config here. */
if (RedisModule_RegisterNumericConfig(ctx, "memory_numeric", 1024, REDISMODULE_CONFIG_DEFAULT | REDISMODULE_CONFIG_MEMORY, 0, 3000000, getNumericConfigCommand, setNumericConfigCommand, longlongApplyFunc, &memval) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
if (RedisModule_RegisterNumericConfig(ctx, "numeric", -1, REDISMODULE_CONFIG_DEFAULT, -5, 2000, getNumericConfigCommand, setNumericConfigCommand, longlongApplyFunc, &longval) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
size_t len;
if (argc && !strcasecmp(RedisModule_StringPtrLen(argv[0], &len), "noload")) {
return REDISMODULE_OK;
} else if (RedisModule_LoadConfigs(ctx) == REDISMODULE_ERR) {
if (strval) {
RedisModule_FreeString(ctx, strval);
strval = NULL;
}
return REDISMODULE_ERR;
}
return REDISMODULE_OK;
}
int RedisModule_OnUnload(RedisModuleCtx *ctx) {
REDISMODULE_NOT_USED(ctx);
if (strval) RedisModule_FreeString(ctx, strval);
return REDISMODULE_OK;
}
\ No newline at end of file
#include "redismodule.h"
#include <strings.h>
/* Second module configs module, for testing.
* Need to make sure that multiple modules with configs don't interfere with each other */
int bool_config;
int getBoolConfigCommand(const char *name, void *privdata) {
REDISMODULE_NOT_USED(privdata);
if (!strcasecmp(name, "test")) {
return bool_config;
}
return 0;
}
int setBoolConfigCommand(const char *name, int new, void *privdata, RedisModuleString **err) {
REDISMODULE_NOT_USED(privdata);
REDISMODULE_NOT_USED(err);
if (!strcasecmp(name, "test")) {
bool_config = new;
return REDISMODULE_OK;
}
return REDISMODULE_ERR;
}
/* No arguments are expected */
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
if (RedisModule_Init(ctx, "configs", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) return REDISMODULE_ERR;
if (RedisModule_RegisterBoolConfig(ctx, "test", 1, REDISMODULE_CONFIG_DEFAULT, getBoolConfigCommand, setBoolConfigCommand, NULL, &argc) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
if (RedisModule_LoadConfigs(ctx) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
return REDISMODULE_OK;
}
\ No newline at end of file
set testmodule [file normalize tests/modules/moduleconfigs.so]
set testmoduletwo [file normalize tests/modules/moduleconfigstwo.so]
start_server {tags {"modules"}} {
r module load $testmodule
test {Config get commands work} {
# Make sure config get module config works
assert_equal [lindex [lindex [r module list] 0] 1] moduleconfigs
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.memory_numeric] "moduleconfigs.memory_numeric 1024"
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.numeric] "moduleconfigs.numeric -1"
}
test {Config set commands work} {
# Make sure that config sets work during runtime
r config set moduleconfigs.mutable_bool no
assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no"
r config set moduleconfigs.memory_numeric 1mb
assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 1048576"
r config set moduleconfigs.string wafflewednesdays
assert_equal [r config get moduleconfigs.string] "moduleconfigs.string wafflewednesdays"
r config set moduleconfigs.string \x73\x75\x70\x65\x72\x20\x00\x73\x65\x63\x72\x65\x74\x20\x70\x61\x73\x73\x77\x6f\x72\x64
assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {super \0secret password}"
r config set moduleconfigs.enum two
assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two"
r config set moduleconfigs.numeric -2
assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -2"
}
test {Immutable flag works properly and rejected strings dont leak} {
# Configs flagged immutable should not allow sets
catch {[r config set moduleconfigs.immutable_bool yes]} e
assert_match {*can't set immutable config*} $e
catch {[r config set moduleconfigs.string rejectisfreed]} e
assert_match {*Cannot set string to 'rejectisfreed'*} $e
}
test {Numeric limits work properly} {
# Configs over/under the limit shouldn't be allowed, and memory configs should only take memory values
catch {[r config set moduleconfigs.memory_numeric 200gb]} e
assert_match {*argument must be between*} $e
catch {[r config set moduleconfigs.memory_numeric -5]} e
assert_match {*argument must be a memory value*} $e
catch {[r config set moduleconfigs.numeric -10]} e
assert_match {*argument must be between*} $e
}
test {Enums only able to be set to passed in values} {
# Module authors specify what values are valid for enums, check that only those values are ok on a set
catch {[r config set moduleconfigs.enum four]} e
assert_match {*argument must be one of the following*} $e
}
test {Unload removes module configs} {
r module unload moduleconfigs
assert_equal [r config get moduleconfigs.*] ""
r module load $testmodule
# these should have reverted back to their module specified values
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.memory_numeric] "moduleconfigs.memory_numeric 1024"
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.numeric] "moduleconfigs.numeric -1"
r module unload moduleconfigs
}
test {test loadex functionality} {
r module loadex $testmodule CONFIG moduleconfigs.mutable_bool no CONFIG moduleconfigs.immutable_bool yes CONFIG moduleconfigs.memory_numeric 2mb CONFIG moduleconfigs.string tclortickle
assert_equal [lindex [lindex [r module list] 0] 1] moduleconfigs
assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no"
assert_equal [r config get moduleconfigs.immutable_bool] "moduleconfigs.immutable_bool yes"
assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 2097152"
assert_equal [r config get moduleconfigs.string] "moduleconfigs.string tclortickle"
# 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.numeric] "moduleconfigs.numeric -1"
}
test {apply function works} {
catch {[r config set moduleconfigs.mutable_bool yes]} e
assert_match {*Bool configs*} $e
assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no"
catch {[r config set moduleconfigs.memory_numeric 1000 moduleconfigs.numeric 1000]} e
assert_match {*cannot equal*} $e
assert_equal [r config get moduleconfigs.memory_numeric] "moduleconfigs.memory_numeric 2097152"
assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1"
r module unload moduleconfigs
}
test {test double config argument to loadex} {
r module loadex $testmodule CONFIG moduleconfigs.mutable_bool yes CONFIG moduleconfigs.mutable_bool no
assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no"
r module unload moduleconfigs
}
test {missing loadconfigs call} {
catch {[r module loadex $testmodule CONFIG moduleconfigs.string "cool" ARGS noload]} e
assert_match {*ERR*} $e
}
test {test loadex rejects bad configs} {
# Bad config 200gb is over the limit
catch {[r module loadex $testmodule CONFIG moduleconfigs.memory_numeric 200gb ARGS]} e
assert_match {*ERR*} $e
# We should completely remove all configs on a failed load
assert_equal [r config get moduleconfigs.*] ""
# No value for config, should error out
catch {[r module loadex $testmodule CONFIG moduleconfigs.mutable_bool CONFIG moduleconfigs.enum two ARGS]} e
assert_match {*ERR*} $e
assert_equal [r config get moduleconfigs.*] ""
# Asan will catch this if this string is not freed
catch {[r module loadex $testmodule CONFIG moduleconfigs.string rejectisfreed]}
assert_match {*ERR*} $e
assert_equal [r config get moduleconfigs.*] ""
# test we can't set random configs
catch {[r module loadex $testmodule CONFIG maxclients 333]}
assert_match {*ERR*} $e
assert_equal [r config get moduleconfigs.*] ""
assert_not_equal [r config get maxclients] "maxclients 333"
# test we can't set other module's configs
r module load $testmoduletwo
catch {[r module loadex $testmodule CONFIG configs.test no]}
assert_match {*ERR*} $e
assert_equal [r config get configs.test] "configs.test yes"
r module unload configs
}
test {test config rewrite with dynamic load} {
#translates to: super \0secret password
r module loadex $testmodule CONFIG moduleconfigs.string \x73\x75\x70\x65\x72\x20\x00\x73\x65\x63\x72\x65\x74\x20\x70\x61\x73\x73\x77\x6f\x72\x64 ARGS
assert_equal [lindex [lindex [r module list] 0] 1] moduleconfigs
assert_equal [r config get moduleconfigs.string] "moduleconfigs.string {super \0secret password}"
r config set moduleconfigs.mutable_bool yes
r config set moduleconfigs.memory_numeric 750
r config set moduleconfigs.enum two
r config rewrite
restart_server 0 true false
# Ensure configs we rewrote are present and that the conf file is readable
assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool yes"
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.enum] "moduleconfigs.enum two"
assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1"
r module unload moduleconfigs
}
test {test multiple modules with configs} {
r module load $testmodule
r module loadex $testmoduletwo CONFIG configs.test 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.memory_numeric] "moduleconfigs.memory_numeric 1024"
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.numeric] "moduleconfigs.numeric -1"
assert_equal [r config get configs.test] "configs.test yes"
r config set moduleconfigs.mutable_bool no
r config set moduleconfigs.string nice
r config set moduleconfigs.enum two
r config set configs.test no
assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no"
assert_equal [r config get moduleconfigs.string] "moduleconfigs.string nice"
assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two"
assert_equal [r config get configs.test] "configs.test no"
r config rewrite
# test we can load from conf file with multiple different modules.
restart_server 0 true false
assert_equal [r config get moduleconfigs.mutable_bool] "moduleconfigs.mutable_bool no"
assert_equal [r config get moduleconfigs.string] "moduleconfigs.string nice"
assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two"
assert_equal [r config get configs.test] "configs.test no"
r module unload moduleconfigs
r module unload configs
}
test {test 1.module load 2.config rewrite 3.module unload 4.config rewrite works} {
# Configs need to be removed from the old config file in this case.
r module loadex $testmodule CONFIG moduleconfigs.memory_numeric 500 ARGS
assert_equal [lindex [lindex [r module list] 0] 1] moduleconfigs
r config rewrite
r module unload moduleconfigs
r config rewrite
restart_server 0 true false
# Ensure configs we rewrote are no longer present
assert_equal [r config get moduleconfigs.*] ""
}
test {startup moduleconfigs} {
# No loadmodule directive
set nomodload [start_server [list overrides [list moduleconfigs.string "hello"]]]
wait_for_condition 100 50 {
! [is_alive $nomodload]
} else {
fail "startup should've failed with no load and module configs supplied"
}
set stdout [dict get $nomodload stdout]
assert_equal [count_message_lines $stdout "Module Configuration detected without loadmodule directive or no ApplyConfig call: aborting"] 1
# Bad config value
set badconfig [start_server [list overrides [list loadmodule "$testmodule" moduleconfigs.string "rejectisfreed"]]]
wait_for_condition 100 50 {
! [is_alive $badconfig]
} else {
fail "startup with bad moduleconfigs should've failed"
}
set stdout [dict get $badconfig stdout]
assert_equal [count_message_lines $stdout "Issue during loading of configuration moduleconfigs.string : Cannot set string to 'rejectisfreed'"] 1
set noload [start_server [list overrides [list loadmodule "$testmodule noload" moduleconfigs.string "hello"]]]
wait_for_condition 100 50 {
! [is_alive $noload]
} else {
fail "startup with moduleconfigs and no loadconfigs call should've failed"
}
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
start_server [list overrides [list loadmodule "$testmodule" moduleconfigs.string "bootedup" moduleconfigs.enum two]] {
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.immutable_bool] "moduleconfigs.immutable_bool no"
assert_equal [r config get moduleconfigs.enum] "moduleconfigs.enum two"
assert_equal [r config get moduleconfigs.numeric] "moduleconfigs.numeric -1"
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