mirror of https://github.com/redis/redis.git
Module set/get config API (#14051)
CI / test-ubuntu-latest (push) Waiting to run
Details
CI / test-sanitizer-address (push) Waiting to run
Details
CI / build-debian-old (push) Waiting to run
Details
CI / build-macos-latest (push) Waiting to run
Details
CI / build-32bit (push) Waiting to run
Details
CI / build-libc-malloc (push) Waiting to run
Details
CI / build-centos-jemalloc (push) Waiting to run
Details
CI / build-old-chain-jemalloc (push) Waiting to run
Details
Codecov / code-coverage (push) Waiting to run
Details
External Server Tests / test-external-standalone (push) Waiting to run
Details
External Server Tests / test-external-cluster (push) Waiting to run
Details
External Server Tests / test-external-nodebug (push) Waiting to run
Details
Spellcheck / Spellcheck (push) Waiting to run
Details
CI / test-ubuntu-latest (push) Waiting to run
Details
CI / test-sanitizer-address (push) Waiting to run
Details
CI / build-debian-old (push) Waiting to run
Details
CI / build-macos-latest (push) Waiting to run
Details
CI / build-32bit (push) Waiting to run
Details
CI / build-libc-malloc (push) Waiting to run
Details
CI / build-centos-jemalloc (push) Waiting to run
Details
CI / build-old-chain-jemalloc (push) Waiting to run
Details
Codecov / code-coverage (push) Waiting to run
Details
External Server Tests / test-external-standalone (push) Waiting to run
Details
External Server Tests / test-external-cluster (push) Waiting to run
Details
External Server Tests / test-external-nodebug (push) Waiting to run
Details
Spellcheck / Spellcheck (push) Waiting to run
Details
# Problem Some redis modules need to call `CONFIG GET/SET` commands. Server may be ran with `rename-command CONFIG ""`(or something similar) which leads to the module being unable to access the config. # Solution Added new API functions for use by modules ``` RedisModuleConfigIterator* RedisModule_GetConfigIterator(RedisModuleCtx *ctx, const char *pattern); void RedisModule_ReleaseConfigIterator(RedisModuleCtx *ctx, RedisModuleConfigIterator *iter); const char *RedisModule_ConfigIteratorNext(RedisModuleConfigIterator *iter); int RedisModule_GetConfigType(const char *name, RedisModuleConfigType *res); int RedisModule_GetBoolConfig(RedisModuleCtx *ctx, const char *name, int *res); int RedisModule_GetConfig(RedisModuleCtx *ctx, const char *name, RedisModuleString **res); int RedisModule_GetEnumConfig(RedisModuleCtx *ctx, const char *name, RedisModuleString **res); int RedisModule_GetNumericConfig(RedisModuleCtx *ctx, const char *name, long long *res); int RedisModule_SetBoolConfig(RedisModuleCtx *ctx, const char *name, int value, RedisModuleString **err); int RedisModule_SetConfig(RedisModuleCtx *ctx, const char *name, RedisModuleString *value, RedisModuleString **err); int RedisModule_SetEnumConfig(RedisModuleCtx *ctx, const char *name, RedisModuleString *value, RedisModuleString **err); int RedisModule_SetNumericConfig(RedisModuleCtx *ctx, const char *name, long long value, RedisModuleString **err); ``` ## Implementation The work is mostly done inside `config.c` as I didn't want to expose the config dict outside of it. That means each of these module functions has a corresponding method in `config.c` that actually does the job. F.e `RedisModule_SetEnumConfig` calls `moduleSetEnumConfig` which is implemented in `config.c` ## Notes Also, refactored `configSetCommand` and `restoreBackupConfig` functions for the following reasons: - code and logic is now way more clear in `configSetCommand`. Only caveat here is removal of an optimization that skipped running apply functions that already have ran in favour of code clarity. - Both functions needlessly separated logic for module configs and normal configs whereas no such separation is needed. This also had the side effect of removing some allocations. - `restoreBackupConfig` now has clearer interface and can be reused with ease. One of the places I reused it is for the individual `moduleSet*Config` functions, each of which needs the restoration functionality but for a single config only. ## Future Additionally, a couple considerations were made for potentially extending the API in the future - if need be an API for atomically setting multiple config values can be added - `RedisModule_SetConfigsTranscationStart/End` or similar that can be put around `RedisModule_Set*Config` calls. - if performance is an issue an API `RedisModule_GetConfigIteratorNextWithTypehint` or similar may be added in order not to incur the additional cost of calling `RedisModule_GetConfigType`. --------- Co-authored-by: Oran Agra <oran@redislabs.com>
This commit is contained in:
parent
5b7eec4c81
commit
15706f2e82
|
@ -58,4 +58,5 @@ $TCLSH tests/test_helper.tcl \
|
|||
--single unit/moduleapi/rdbloadsave \
|
||||
--single unit/moduleapi/crash \
|
||||
--single unit/moduleapi/internalsecret \
|
||||
--single unit/moduleapi/configaccess \
|
||||
"${@}"
|
||||
|
|
372
src/config.c
372
src/config.c
|
@ -764,7 +764,12 @@ int performModuleConfigSetDefaultFromName(sds name, const char **err) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void restoreBackupConfig(standardConfig **set_configs, sds *old_values, int count, apply_fn *apply_fns, list *module_configs) {
|
||||
static int configNeedsApply(standardConfig *config) {
|
||||
return ((config->flags & MODULE_CONFIG) && moduleConfigNeedsApply(config->privdata)) ||
|
||||
config->interface.apply;
|
||||
}
|
||||
|
||||
static void restoreBackupConfig(standardConfig **set_configs, sds *old_values, int count) {
|
||||
int i;
|
||||
const char *errstr = "unknown error";
|
||||
/* Set all backup values */
|
||||
|
@ -773,16 +778,19 @@ static void restoreBackupConfig(standardConfig **set_configs, sds *old_values, i
|
|||
serverLog(LL_WARNING, "Failed restoring failed CONFIG SET command. Error setting %s to '%s': %s",
|
||||
set_configs[i]->name, old_values[i], errstr);
|
||||
}
|
||||
/* Apply backup */
|
||||
if (apply_fns) {
|
||||
for (i = 0; i < count && apply_fns[i] != NULL; i++) {
|
||||
if (!apply_fns[i](&errstr))
|
||||
serverLog(LL_WARNING, "Failed applying restored failed CONFIG SET command: %s", errstr);
|
||||
}
|
||||
}
|
||||
if (module_configs) {
|
||||
if (!moduleConfigApplyConfig(module_configs, &errstr, NULL))
|
||||
serverLog(LL_WARNING, "Failed applying restored failed CONFIG SET command: %s", errstr);
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (!configNeedsApply(set_configs[i])) continue;
|
||||
|
||||
int applyres = 0;
|
||||
if (set_configs[i]->flags & MODULE_CONFIG)
|
||||
applyres = moduleConfigApply(set_configs[i]->privdata, &errstr);
|
||||
else
|
||||
applyres = set_configs[i]->interface.apply(&errstr);
|
||||
|
||||
if (!applyres)
|
||||
serverLog(LL_WARNING, "Failed applying restored CONFIG SET command. Error applying %s: %s",
|
||||
set_configs[i]->name, errstr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -795,14 +803,12 @@ void configSetCommand(client *c) {
|
|||
const char *invalid_arg_name = NULL;
|
||||
const char *err_arg_name = NULL;
|
||||
standardConfig **set_configs; /* TODO: make this a dict for better performance */
|
||||
list *module_configs_apply;
|
||||
const char **config_names;
|
||||
sds *new_values;
|
||||
sds *old_values = NULL;
|
||||
apply_fn *apply_fns; /* TODO: make this a set for better performance */
|
||||
int config_count, i, j;
|
||||
int invalid_args = 0, deny_loading_error = 0;
|
||||
int *config_map_fns;
|
||||
int *config_changed;
|
||||
|
||||
/* Make sure we have an even number of arguments: conf-val pairs */
|
||||
if (c->argc & 1) {
|
||||
|
@ -811,13 +817,11 @@ void configSetCommand(client *c) {
|
|||
}
|
||||
config_count = (c->argc - 2) / 2;
|
||||
|
||||
module_configs_apply = listCreate();
|
||||
set_configs = zcalloc(sizeof(standardConfig*)*config_count);
|
||||
config_names = zcalloc(sizeof(char*)*config_count);
|
||||
new_values = zmalloc(sizeof(sds*)*config_count);
|
||||
old_values = zcalloc(sizeof(sds*)*config_count);
|
||||
apply_fns = zcalloc(sizeof(apply_fn)*config_count);
|
||||
config_map_fns = zmalloc(sizeof(int)*config_count);
|
||||
config_changed = zmalloc(sizeof(int)*config_count);
|
||||
|
||||
/* Find all relevant configs */
|
||||
for (i = 0; i < config_count; i++) {
|
||||
|
@ -883,46 +887,34 @@ void configSetCommand(client *c) {
|
|||
for (i = 0; i < config_count; i++) {
|
||||
int res = performInterfaceSet(set_configs[i], new_values[i], &errstr);
|
||||
if (!res) {
|
||||
restoreBackupConfig(set_configs, old_values, i+1, NULL, NULL);
|
||||
restoreBackupConfig(set_configs, old_values, i+1);
|
||||
err_arg_name = set_configs[i]->name;
|
||||
goto err;
|
||||
} else if (res == 1) {
|
||||
/* A new value was set, if this config has an apply function then store it for execution later */
|
||||
if (set_configs[i]->flags & MODULE_CONFIG) {
|
||||
addModuleConfigApply(module_configs_apply, set_configs[i]->privdata);
|
||||
} else if (set_configs[i]->interface.apply) {
|
||||
/* Check if this apply function is already stored */
|
||||
int exists = 0;
|
||||
for (j = 0; apply_fns[j] != NULL && j <= i; j++) {
|
||||
if (apply_fns[j] == set_configs[i]->interface.apply) {
|
||||
exists = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Apply function not stored, store it */
|
||||
if (!exists) {
|
||||
apply_fns[j] = set_configs[i]->interface.apply;
|
||||
config_map_fns[j] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (res == 1) config_changed[i] = 1;
|
||||
else config_changed[i] = 0;
|
||||
}
|
||||
|
||||
/* Apply all configs after being set */
|
||||
for (i = 0; i < config_count && apply_fns[i] != NULL; i++) {
|
||||
if (!apply_fns[i](&errstr)) {
|
||||
serverLog(LL_WARNING, "Failed applying new configuration. Possibly related to new %s setting. Restoring previous settings.", set_configs[config_map_fns[i]]->name);
|
||||
restoreBackupConfig(set_configs, old_values, config_count, apply_fns, NULL);
|
||||
err_arg_name = set_configs[config_map_fns[i]]->name;
|
||||
/* Apply all configs that need it */
|
||||
for (i = 0; i < config_count; i++) {
|
||||
if (!config_changed[i]) continue;
|
||||
|
||||
/* A new value was set, if this config has an apply function try to apply
|
||||
* it and restore if apply fails. */
|
||||
if (!configNeedsApply(set_configs[i])) continue;
|
||||
|
||||
int res = 0;
|
||||
if (set_configs[i]->flags & MODULE_CONFIG)
|
||||
res = moduleConfigApply(set_configs[i]->privdata, &errstr);
|
||||
else
|
||||
res = set_configs[i]->interface.apply(&errstr);
|
||||
|
||||
if (!res) {
|
||||
restoreBackupConfig(set_configs, old_values, config_count);
|
||||
err_arg_name = set_configs[i]->name;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
/* Apply all module configs that were set. */
|
||||
if (!moduleConfigApplyConfig(module_configs_apply, &errstr, &err_arg_name)) {
|
||||
serverLogRaw(LL_WARNING, "Failed applying new module configuration. Restoring previous settings.");
|
||||
restoreBackupConfig(set_configs, old_values, config_count, apply_fns, module_configs_apply);
|
||||
goto err;
|
||||
}
|
||||
|
||||
RedisModuleConfigChangeV1 cc = {.num_changes = config_count, .config_names = config_names};
|
||||
moduleFireServerEvent(REDISMODULE_EVENT_CONFIG, REDISMODULE_SUBEVENT_CONFIG_CHANGE, &cc);
|
||||
|
@ -947,9 +939,7 @@ end:
|
|||
for (i = 0; i < config_count; i++)
|
||||
sdsfree(old_values[i]);
|
||||
zfree(old_values);
|
||||
zfree(apply_fns);
|
||||
zfree(config_map_fns);
|
||||
listRelease(module_configs_apply);
|
||||
zfree(config_changed);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
|
@ -1826,9 +1816,7 @@ static void boolConfigInit(standardConfig *config) {
|
|||
*config->data.yesno.config = config->data.yesno.default_value;
|
||||
}
|
||||
|
||||
static int boolConfigSet(standardConfig *config, sds *argv, int argc, const char **err) {
|
||||
UNUSED(argc);
|
||||
int yn = yesnotoi(argv[0]);
|
||||
static int boolConfigSetInternal(standardConfig *config, int yn, const char **err) {
|
||||
if (yn == -1) {
|
||||
*err = "argument must be 'yes' or 'no'";
|
||||
return 0;
|
||||
|
@ -1844,6 +1832,13 @@ static int boolConfigSet(standardConfig *config, sds *argv, int argc, const char
|
|||
return 1;
|
||||
}
|
||||
return (config->flags & VOLATILE_CONFIG) ? 1 : 2;
|
||||
|
||||
}
|
||||
|
||||
static int boolConfigSet(standardConfig *config, sds *argv, int argc, const char **err) {
|
||||
UNUSED(argc);
|
||||
int yn = yesnotoi(argv[0]);
|
||||
return boolConfigSetInternal(config, yn, err);
|
||||
}
|
||||
|
||||
static sds boolConfigGet(standardConfig *config) {
|
||||
|
@ -1874,12 +1869,11 @@ static void stringConfigInit(standardConfig *config) {
|
|||
*config->data.string.config = (config->data.string.convert_empty_to_null && !config->data.string.default_value) ? NULL : zstrdup(config->data.string.default_value);
|
||||
}
|
||||
|
||||
static int stringConfigSet(standardConfig *config, sds *argv, int argc, const char **err) {
|
||||
UNUSED(argc);
|
||||
if (config->data.string.is_valid_fn && !config->data.string.is_valid_fn(argv[0], err))
|
||||
static int stringConfigSetInternal(standardConfig *config, char *str, const char **err) {
|
||||
if (config->data.string.is_valid_fn && !config->data.string.is_valid_fn(str, err))
|
||||
return 0;
|
||||
char *prev = *config->data.string.config;
|
||||
char *new = (config->data.string.convert_empty_to_null && !argv[0][0]) ? NULL : argv[0];
|
||||
char *new = (config->data.string.convert_empty_to_null && !str[0]) ? NULL : str;
|
||||
if (new != prev && (new == NULL || prev == NULL || strcmp(prev, new))) {
|
||||
*config->data.string.config = new != NULL ? zstrdup(new) : NULL;
|
||||
zfree(prev);
|
||||
|
@ -1888,6 +1882,11 @@ static int stringConfigSet(standardConfig *config, sds *argv, int argc, const ch
|
|||
return (config->flags & VOLATILE_CONFIG) ? 1 : 2;
|
||||
}
|
||||
|
||||
static int stringConfigSet(standardConfig *config, sds *argv, int argc, const char **err) {
|
||||
UNUSED(argc);
|
||||
return stringConfigSetInternal(config, argv[0], err);
|
||||
}
|
||||
|
||||
static sds stringConfigGet(standardConfig *config) {
|
||||
return sdsnew(*config->data.string.config ? *config->data.string.config : "");
|
||||
}
|
||||
|
@ -2096,6 +2095,10 @@ static int numericBoundaryCheck(standardConfig *config, long long ll, const char
|
|||
config->data.numeric.numeric_type == NUMERIC_TYPE_UINT ||
|
||||
config->data.numeric.numeric_type == NUMERIC_TYPE_SIZE_T) {
|
||||
/* Boundary check for unsigned types */
|
||||
if (ll < 0) {
|
||||
*err = "argument must be greater or equal to 0";
|
||||
return 0;
|
||||
}
|
||||
unsigned long long ull = ll;
|
||||
unsigned long long upper_bound = config->data.numeric.upper_bound;
|
||||
unsigned long long lower_bound = config->data.numeric.lower_bound;
|
||||
|
@ -2183,19 +2186,14 @@ static int numericParseString(standardConfig *config, sds value, const char **er
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int numericConfigSet(standardConfig *config, sds *argv, int argc, const char **err) {
|
||||
UNUSED(argc);
|
||||
long long ll, prev = 0;
|
||||
|
||||
if (!numericParseString(config, argv[0], err, &ll))
|
||||
return 0;
|
||||
|
||||
static int numericConfigSetInternal(standardConfig *config, long long ll, const char **err) {
|
||||
if (!numericBoundaryCheck(config, ll, err))
|
||||
return 0;
|
||||
|
||||
if (config->data.numeric.is_valid_fn && !config->data.numeric.is_valid_fn(ll, err))
|
||||
return 0;
|
||||
|
||||
long long prev = 0;
|
||||
GET_NUMERIC_TYPE(prev)
|
||||
if (prev != ll) {
|
||||
return setNumericType(config, ll, err);
|
||||
|
@ -2204,6 +2202,16 @@ static int numericConfigSet(standardConfig *config, sds *argv, int argc, const c
|
|||
return (config->flags & VOLATILE_CONFIG) ? 1 : 2;
|
||||
}
|
||||
|
||||
static int numericConfigSet(standardConfig *config, sds *argv, int argc, const char **err) {
|
||||
UNUSED(argc);
|
||||
long long ll;
|
||||
|
||||
if (!numericParseString(config, argv[0], err, &ll))
|
||||
return 0;
|
||||
|
||||
return numericConfigSetInternal(config, ll, err);
|
||||
}
|
||||
|
||||
static sds numericConfigGet(standardConfig *config) {
|
||||
char buf[128];
|
||||
|
||||
|
@ -3442,6 +3450,238 @@ void addModuleNumericConfig(sds name, sds alias, int flags, void *privdata, long
|
|||
}
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* API for modules to access the config
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* If a config with the given `name` does not exist or is not mutable, return
|
||||
* NULL, else return the config. */
|
||||
static standardConfig *getMutableConfig(client *c, const sds name, const char **errstr) {
|
||||
standardConfig *config = lookupConfig(name);
|
||||
|
||||
if (!config) {
|
||||
if (errstr) *errstr = "Config name not found";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (config->flags & IMMUTABLE_CONFIG ||
|
||||
(config->flags & PROTECTED_CONFIG &&
|
||||
!allowProtectedAction(server.enable_protected_configs, c))) {
|
||||
if (errstr) *errstr = config->flags & IMMUTABLE_CONFIG ? "Config is immutable" : "Config is protected";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (server.loading && config->flags & DENY_LOADING_CONFIG) {
|
||||
if (errstr) *errstr = "Config is not allowed during loading";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
dictIterator *moduleGetConfigIterator(void) {
|
||||
return dictGetIterator(configs);
|
||||
}
|
||||
|
||||
const char *moduleConfigIteratorNext(dictIterator **iter, sds pattern, int is_glob, configType *typehint) {
|
||||
if (*iter == NULL) return NULL;
|
||||
|
||||
standardConfig *config = NULL;
|
||||
|
||||
/* Special case for non-glob patterns - we only need to check if the config
|
||||
* exists and return it. That save us iteration cycles. */
|
||||
if (pattern && !is_glob) {
|
||||
/* Release the iterator so we stop the iteration at this point */
|
||||
dictReleaseIterator(*iter);
|
||||
*iter = NULL;
|
||||
|
||||
dictEntry *de = dictFind(configs, pattern);
|
||||
if (!de) return NULL;
|
||||
config = dictGetVal(de);
|
||||
if (typehint) *typehint = config->type;
|
||||
return config->name;
|
||||
}
|
||||
|
||||
dictEntry *de = NULL;
|
||||
while ((de = dictNext(*iter)) != NULL) {
|
||||
config = dictGetVal(de);
|
||||
|
||||
/* Note that hidden configs require an exact match (not a pattern) */
|
||||
if (config->flags & HIDDEN_CONFIG) continue;
|
||||
|
||||
if (!pattern || stringmatch(pattern, config->name, 1))
|
||||
break;
|
||||
}
|
||||
if (!de) return NULL;
|
||||
if (typehint) *typehint = config->type;
|
||||
return config->name;
|
||||
}
|
||||
|
||||
int moduleGetConfigType(sds name, configType *res) {
|
||||
standardConfig *config = lookupConfig(name);
|
||||
if (!config) return 0;
|
||||
if (res) *res = config->type;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int moduleGetBoolConfig(sds name, int *res) {
|
||||
standardConfig *config = lookupConfig(name);
|
||||
if (!config) return 0;
|
||||
if (config->type != BOOL_CONFIG) return 0;
|
||||
|
||||
if (res == NULL) return 1;
|
||||
|
||||
if (config->flags & MODULE_CONFIG)
|
||||
*res = getModuleBoolConfig(config->privdata);
|
||||
else
|
||||
*res = *config->data.yesno.config;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int moduleGetStringConfig(sds name, sds *res) {
|
||||
standardConfig *config = lookupConfig(name);
|
||||
if (!config) return 0;
|
||||
|
||||
if (res == NULL) return 1;
|
||||
|
||||
*res = config->interface.get(config);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int moduleGetEnumConfig(sds name, sds *res) {
|
||||
standardConfig *config = lookupConfig(name);
|
||||
if (!config) return 0;
|
||||
if (config->type != ENUM_CONFIG) return 0;
|
||||
|
||||
if (res != NULL) *res = enumConfigGet(config);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int moduleGetNumericConfig(sds name, long long *res) {
|
||||
standardConfig *config = lookupConfig(name);
|
||||
if (!config) return 0;
|
||||
if (config->type != NUMERIC_CONFIG) return 0;
|
||||
|
||||
if (res == NULL) return 1;
|
||||
|
||||
if (config->flags & MODULE_CONFIG)
|
||||
*res = getModuleNumericConfig(config->privdata);
|
||||
else
|
||||
GET_NUMERIC_TYPE(*res);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int configApply(standardConfig *config, sds old_value, const char **err) {
|
||||
if (!configNeedsApply(config)) return 1;
|
||||
|
||||
int res = 0;
|
||||
if (config->flags & MODULE_CONFIG)
|
||||
res = moduleConfigApply(config->privdata, err);
|
||||
else
|
||||
res = config->interface.apply(err);
|
||||
|
||||
if (res) return res;
|
||||
|
||||
/* Apply failed - restore old value and apply it again since we don't know
|
||||
* the side effects of the failed apply. */
|
||||
restoreBackupConfig(&config, &old_value, 1);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int moduleSetBoolConfig(client *c, sds name, int val, const char **err) {
|
||||
standardConfig *config = getMutableConfig(c, name, err);
|
||||
if (!config) return 0;
|
||||
if (config->type != BOOL_CONFIG) return 0;
|
||||
|
||||
/* Sanitize input */
|
||||
if (val != 0 && val != 1) val = -1;
|
||||
|
||||
sds old_value = config->interface.get(config);
|
||||
int res = boolConfigSetInternal(config, val, err);
|
||||
|
||||
/* We can't be sure if value was changed but setting still failed so we need
|
||||
* to restore the old value */
|
||||
if (!res)
|
||||
restoreBackupConfig(&config, &old_value, 1);
|
||||
else
|
||||
res = configApply(config, old_value, err);
|
||||
|
||||
if (old_value) sdsfree(old_value);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int moduleSetStringConfig(client *c, sds name, const char *val, const char **err) {
|
||||
standardConfig *config = getMutableConfig(c, name, err);
|
||||
if (!config) return 0;
|
||||
|
||||
sds old_value = config->interface.get(config);
|
||||
|
||||
int res = 0;
|
||||
if (config->type == STRING_CONFIG)
|
||||
res = stringConfigSetInternal(config, (char *)val, err);
|
||||
else {
|
||||
sds sdsval = sdsnew(val);
|
||||
res = performInterfaceSet(config, sdsval, err);
|
||||
sdsfree(sdsval);
|
||||
}
|
||||
|
||||
/* We can't be sure if value was changed but setting still failed so we need
|
||||
* to restore the old value */
|
||||
if (!res)
|
||||
restoreBackupConfig(&config, &old_value, 1);
|
||||
else
|
||||
res = configApply(config, old_value, err);
|
||||
|
||||
if (old_value) sdsfree(old_value);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int moduleSetEnumConfig(client *c, sds name, sds *vals, int vals_cnt, const char **err) {
|
||||
standardConfig *config = getMutableConfig(c, name, err);
|
||||
if (!config) return 0;
|
||||
if (config->type != ENUM_CONFIG) return 0;
|
||||
|
||||
sds old_value = config->interface.get(config);
|
||||
int res = enumConfigSet(config, vals, vals_cnt, err);
|
||||
|
||||
/* We can't be sure if value was changed but setting still failed so we need
|
||||
* to restore the old value */
|
||||
if (!res)
|
||||
restoreBackupConfig(&config, &old_value, 1);
|
||||
else
|
||||
res = configApply(config, old_value, err);
|
||||
|
||||
if (old_value) sdsfree(old_value);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int moduleSetNumericConfig(client *c, sds name, long long val, const char **err) {
|
||||
standardConfig *config = getMutableConfig(c, name, err);
|
||||
if (config->type != NUMERIC_CONFIG) return 0;
|
||||
|
||||
sds old_value = config->interface.get(config);
|
||||
int res = numericConfigSetInternal(config, val, err);
|
||||
|
||||
/* We can't be sure if value was changed but setting still failed so we need
|
||||
* to restore the old value */
|
||||
if (!res)
|
||||
restoreBackupConfig(&config, &old_value, 1);
|
||||
else
|
||||
res = configApply(config, old_value, err);
|
||||
|
||||
if (old_value) sdsfree(old_value);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* CONFIG HELP
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
|
335
src/module.c
335
src/module.c
|
@ -92,6 +92,7 @@ struct AutoMemEntry {
|
|||
#define REDISMODULE_AM_FREED 3 /* Explicitly freed by user already. */
|
||||
#define REDISMODULE_AM_DICT 4
|
||||
#define REDISMODULE_AM_INFO 5
|
||||
#define REDISMODULE_AM_CONFIG 6
|
||||
|
||||
/* The pool allocator block. Redis Modules can allocate memory via this special
|
||||
* allocator that will automatically release it all once the callback returns.
|
||||
|
@ -373,6 +374,12 @@ typedef struct RedisModuleServerInfoData {
|
|||
rax *rax; /* parsed info data. */
|
||||
} RedisModuleServerInfoData;
|
||||
|
||||
typedef struct RedisModuleConfigIterator {
|
||||
dictIterator *di; /* Iterator for the configs dict. */
|
||||
sds pattern; /* Pattern to filter configs by name. */
|
||||
int is_glob; /* Is the pattern a glob-pattern or a fixed string? */
|
||||
} RedisModuleConfigIterator;
|
||||
|
||||
/* Flags for moduleCreateArgvFromUserFormat(). */
|
||||
#define REDISMODULE_ARGV_REPLICATE (1<<0)
|
||||
#define REDISMODULE_ARGV_NO_AOF (1<<1)
|
||||
|
@ -489,6 +496,7 @@ static void zsetKeyReset(RedisModuleKey *key);
|
|||
static void moduleInitKeyTypeSpecific(RedisModuleKey *key);
|
||||
void RM_FreeDict(RedisModuleCtx *ctx, RedisModuleDict *d);
|
||||
void RM_FreeServerInfo(RedisModuleCtx *ctx, RedisModuleServerInfoData *data);
|
||||
void RM_ConfigIteratorRelease(RedisModuleCtx *ctx, RedisModuleConfigIterator *iter);
|
||||
|
||||
/* Helpers for RM_SetCommandInfo. */
|
||||
static int moduleValidateCommandInfo(const RedisModuleCommandInfo *info);
|
||||
|
@ -2609,6 +2617,7 @@ void autoMemoryCollect(RedisModuleCtx *ctx) {
|
|||
case REDISMODULE_AM_KEY: RM_CloseKey(ptr); break;
|
||||
case REDISMODULE_AM_DICT: RM_FreeDict(NULL,ptr); break;
|
||||
case REDISMODULE_AM_INFO: RM_FreeServerInfo(NULL,ptr); break;
|
||||
case REDISMODULE_AM_CONFIG: RM_ConfigIteratorRelease(NULL, ptr); break;
|
||||
}
|
||||
}
|
||||
ctx->flags |= REDISMODULE_CTX_AUTO_MEMORY;
|
||||
|
@ -13013,26 +13022,46 @@ void addModuleConfigApply(list *module_configs, ModuleConfig *module_config) {
|
|||
listAddNodeTail(module_configs, module_config);
|
||||
}
|
||||
|
||||
/* Call apply on a module config. Assumes module_config->apply_fn != NULL! */
|
||||
int moduleConfigApplyInternal(ModuleConfig *module_config, const char **err) {
|
||||
RedisModuleCtx ctx;
|
||||
RedisModuleString *error = NULL;
|
||||
|
||||
moduleCreateContext(&ctx, module_config->module, REDISMODULE_CTX_NONE);
|
||||
if (module_config->apply_fn(&ctx, module_config->privdata, &error)) {
|
||||
propagateErrorString(error, err);
|
||||
moduleFreeContext(&ctx);
|
||||
return 0;
|
||||
}
|
||||
moduleFreeContext(&ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Call apply on a single module config. */
|
||||
int moduleConfigApply(ModuleConfig *module_config, const char **err) {
|
||||
if (module_config->apply_fn == NULL) return 1;
|
||||
return moduleConfigApplyInternal(module_config, err);
|
||||
}
|
||||
|
||||
int moduleConfigNeedsApply(ModuleConfig *config) {
|
||||
return config->apply_fn != NULL;
|
||||
}
|
||||
|
||||
/* Call apply on all module configs specified in set, if an apply function was specified at registration time. */
|
||||
int moduleConfigApplyConfig(list *module_configs, const char **err, const char **err_arg_name) {
|
||||
if (!listLength(module_configs)) return 1;
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
ModuleConfig *module_config;
|
||||
RedisModuleString *error = NULL;
|
||||
RedisModuleCtx ctx;
|
||||
|
||||
listRewind(module_configs, &li);
|
||||
while ((ln = listNext(&li))) {
|
||||
module_config = listNodeValue(ln);
|
||||
moduleCreateContext(&ctx, module_config->module, REDISMODULE_CTX_NONE);
|
||||
if (module_config->apply_fn(&ctx, module_config->privdata, &error)) {
|
||||
/* We know apply_fn is not NULL so skip the check */
|
||||
if (!moduleConfigApplyInternal(module_config, err)) {
|
||||
if (err_arg_name) *err_arg_name = module_config->name;
|
||||
propagateErrorString(error, err);
|
||||
moduleFreeContext(&ctx);
|
||||
return 0;
|
||||
}
|
||||
moduleFreeContext(&ctx);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
@ -13513,6 +13542,286 @@ const char* RM_GetInternalSecret(RedisModuleCtx *ctx, size_t *len) {
|
|||
return secret;
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* ## Config access API
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Get an iterator to all configs.
|
||||
* Optional `ctx` can be provided if use of auto-memory is desired.
|
||||
* Optional `pattern` can be provided to filter configs by name. If `pattern` is
|
||||
* NULL all configs will be returned.
|
||||
*
|
||||
* The returned iterator can be used to iterate over all configs using
|
||||
* RedisModule_ConfigIteratorNext().
|
||||
*
|
||||
* Example usage:
|
||||
* ```
|
||||
* // Below is same as RedisModule_ConfigIteratorCreate(ctx, NULL)
|
||||
* RedisModuleConfigIterator *iter = RedisModule_ConfigIteratorCreate(ctx, "*");
|
||||
* const char *config_name = NULL;
|
||||
* while ((config_name = RedisModule_ConfigIteratorNext(iter)) != NULL) {
|
||||
* RedisModuleString *value = NULL;
|
||||
* if (RedisModule_ConfigGet(ctx, config_name, &value) == REDISMODULE_OK) {
|
||||
* // Do something with `value`...
|
||||
* RedisModule_FreeString(ctx, value);
|
||||
* }
|
||||
* }
|
||||
* RedisModule_ConfigIteratorRelease(ctx, iter);
|
||||
*
|
||||
* // Or optionally one can check the type to get the config value directly
|
||||
* // via the appropriate API in case performance is of consideration
|
||||
* iter = RedisModule_ConfigIteratorCreate(ctx, "*");
|
||||
* while ((config_name = RedisModule_ConfigIteratorNext(iter)) != NULL) {
|
||||
* RedisModuleConfigType type = RedisModule_ConfigGetType(config_name);
|
||||
* if (type == REDISMODULE_CONFIG_TYPE_STRING) {
|
||||
* RedisModuleString *value;
|
||||
* RedisModule_ConfigGet(ctx, config_name, &value);
|
||||
* // Do something with `value`...
|
||||
* RedisModule_FreeString(ctx, value);
|
||||
* } if (type == REDISMODULE_CONFIG_TYPE_NUMERIC) {
|
||||
* long long value;
|
||||
* RedisModule_ConfigGetNumeric(ctx, config_name, &value);
|
||||
* // Do something with `value`...
|
||||
* } else if (type == REDISMODULE_CONFIG_TYPE_BOOL) {
|
||||
* int value;
|
||||
* RedisModule_ConfigGetBool(ctx, config_name, &value);
|
||||
* // Do something with `value`...
|
||||
* } else if (type == REDISMODULE_CONFIG_TYPE_ENUM) {
|
||||
* RedisModuleString *value;
|
||||
* RedisModule_ConfigGetEnum(ctx, config_name, &value);
|
||||
* // Do something with `value`...
|
||||
* RedisModule_Free(value);
|
||||
* }
|
||||
* }
|
||||
* RedisModule_ConfigIteratorRelease(ctx, iter);
|
||||
* ```
|
||||
*
|
||||
* Returns a pointer to RedisModuleConfigIterator. Unless auto-memory is enabled
|
||||
* the caller is responsible for freeing the iterator using
|
||||
* RedisModule_ConfigIteratorRelease(). */
|
||||
RedisModuleConfigIterator *RM_ConfigIteratorCreate(RedisModuleCtx *ctx, const char *pattern) {
|
||||
RedisModuleConfigIterator *iter = RM_Alloc(sizeof(*iter));
|
||||
|
||||
iter->di = moduleGetConfigIterator();
|
||||
if (pattern != NULL) {
|
||||
iter->pattern = sdsnew(pattern);
|
||||
iter->is_glob = (strpbrk(pattern, "*?[") != NULL);
|
||||
} else
|
||||
iter->pattern = NULL;
|
||||
|
||||
if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_CONFIG, iter);
|
||||
return iter;
|
||||
}
|
||||
|
||||
/* Release the iterator returned by RedisModule_ConfigIteratorCreate(). If auto-memory
|
||||
* is enabled and manual release is needed one must pass the same RedisModuleCtx
|
||||
* that was used to create the iterator. */
|
||||
void RM_ConfigIteratorRelease(RedisModuleCtx *ctx, RedisModuleConfigIterator *iter) {
|
||||
if (ctx != NULL) autoMemoryFreed(ctx,REDISMODULE_AM_CONFIG,iter);
|
||||
if (iter->di) dictReleaseIterator(iter->di);
|
||||
sdsfree(iter->pattern);
|
||||
RM_Free(iter);
|
||||
}
|
||||
|
||||
static RedisModuleConfigType convertToRedisModuleConfigType(configType type) {
|
||||
switch (type) {
|
||||
case BOOL_CONFIG:
|
||||
return REDISMODULE_CONFIG_TYPE_BOOL;
|
||||
case NUMERIC_CONFIG:
|
||||
return REDISMODULE_CONFIG_TYPE_NUMERIC;
|
||||
case STRING_CONFIG:
|
||||
case SDS_CONFIG:
|
||||
case SPECIAL_CONFIG:
|
||||
return REDISMODULE_CONFIG_TYPE_STRING;
|
||||
case ENUM_CONFIG:
|
||||
return REDISMODULE_CONFIG_TYPE_ENUM;
|
||||
default:
|
||||
serverAssert(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the type of a config as RedisModuleConfigType. One may use this in order
|
||||
* to get or set the values of the config with the appropriate function if the
|
||||
* generic RedisModule_ConfigGet and RedisModule_ConfigSet APIs are performing
|
||||
* poorly.
|
||||
*
|
||||
* Intended usage of this function is when iteration over the configs is
|
||||
* performed. See RedisModule_ConfigIteratorNext() for example usage. If setting
|
||||
* or getting individual configs one can check the config type by hand in
|
||||
* redis.conf (or via other sources if config is added by a module) and use the
|
||||
* appropriate function without the need to call this function.
|
||||
*
|
||||
* Explanation of config types:
|
||||
* - REDISMODULE_CONFIG_TYPE_BOOL: Config is a boolean. One can use RedisModule_Config(Get/Set)Bool
|
||||
* - REDISMODULE_CONFIG_TYPE_NUMERIC: Config is a numeric value. One can use RedisModule_Config(Get/Set)Numeric
|
||||
* - REDISMODULE_CONFIG_TYPE_STRING: Config is a string. One can use the generic RedisModule_Config(Get/Set)
|
||||
* - REDISMODULE_CONFIG_TYPE_ENUM: Config is an enum. One can use RedisModule_Config(Get/Set)Enum
|
||||
*
|
||||
* If a config with the given name exists `res` is populated with its type, else
|
||||
* REDISMODULE_ERR is returned. */
|
||||
int RM_ConfigGetType(const char *name, RedisModuleConfigType *res) {
|
||||
sds config_name = sdsnew(name);
|
||||
configType type;
|
||||
int ret = moduleGetConfigType(config_name, &type);
|
||||
sdsfree(config_name);
|
||||
|
||||
if (!ret)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
*res = convertToRedisModuleConfigType(type);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Go to the next element of the config iterator.
|
||||
*
|
||||
* Returns the name of the next config, or NULL if there are no more configs.
|
||||
* Returned string is non-owning and thus should not be freed.
|
||||
* If a pattern was provided when creating the iterator, only configs matching
|
||||
* the pattern will be returned.
|
||||
*
|
||||
* See RedisModule_ConfigIteratorCreate() for example usage. */
|
||||
const char *RM_ConfigIteratorNext(RedisModuleConfigIterator *iter) {
|
||||
return moduleConfigIteratorNext(&iter->di, iter->pattern, iter->is_glob, NULL);
|
||||
}
|
||||
|
||||
/* Get the value of a config as a string. This function can be used to get the
|
||||
* value of any config, regardless of its type.
|
||||
*
|
||||
* The string is allocated by the module and must be freed by the caller unless
|
||||
* auto memory is enabled.
|
||||
*
|
||||
* If the config does not exist, REDISMODULE_ERR is returned, else REDISMODULE_OK
|
||||
* is returned and `res` is populated with the value. */
|
||||
int RM_ConfigGet(RedisModuleCtx *ctx, const char *name, RedisModuleString **res) {
|
||||
sds config_name = sdsnew(name);
|
||||
sds res_sds = NULL;
|
||||
int ret = moduleGetStringConfig(config_name, &res_sds);
|
||||
sdsfree(config_name);
|
||||
if (ret)
|
||||
*res = RM_CreateString(ctx, res_sds, sdslen(res_sds));
|
||||
sdsfree(res_sds);
|
||||
return ret ? REDISMODULE_OK : REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
/* Get the value of a bool config.
|
||||
*
|
||||
* If the config does not exist or is not a bool config, REDISMODULE_ERR is
|
||||
* returned, else REDISMODULE_OK is returned and `res` is populated with the
|
||||
* value. */
|
||||
int RM_ConfigGetBool(RedisModuleCtx *ctx, const char *name, int *res) {
|
||||
UNUSED(ctx);
|
||||
sds config_name = sdsnew(name);
|
||||
int ret = moduleGetBoolConfig(config_name, res);
|
||||
sdsfree(config_name);
|
||||
return ret ? REDISMODULE_OK : REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
/* Get the value of an enum config.
|
||||
*
|
||||
* If the config does not exist or is not an enum config, REDISMODULE_ERR is
|
||||
* returned, else REDISMODULE_OK is returned and `res` is populated with the value.
|
||||
* If the config has multiple arguments they are returned as a space-separated
|
||||
* string. */
|
||||
int RM_ConfigGetEnum(RedisModuleCtx *ctx, const char *name, RedisModuleString **res) {
|
||||
sds config_name = sdsnew(name);
|
||||
sds res_sds = NULL;
|
||||
int ret = moduleGetEnumConfig(config_name, &res_sds);
|
||||
sdsfree(config_name);
|
||||
if (ret)
|
||||
*res = RM_CreateString(ctx, res_sds, sdslen(res_sds));
|
||||
sdsfree(res_sds);
|
||||
return ret ? REDISMODULE_OK : REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
/* Get the value of a numeric config.
|
||||
*
|
||||
* If the config does not exist or is not a numeric config, REDISMODULE_ERR is
|
||||
* returned, else REDISMODULE_OK is returned and `res` is populated with the
|
||||
* value. */
|
||||
int RM_ConfigGetNumeric(RedisModuleCtx *ctx, const char *name, long long *res) {
|
||||
UNUSED(ctx);
|
||||
sds config_name = sdsnew(name);
|
||||
int ret = moduleGetNumericConfig(config_name, res);
|
||||
sdsfree(config_name);
|
||||
return ret ? REDISMODULE_OK : REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
/* Set the value of a config.
|
||||
*
|
||||
* This function can be used to set the value of any config, regardless of its
|
||||
* type. If the config is multi-argument, the value must be a space-separated
|
||||
* string.
|
||||
*
|
||||
* If the value failed to be set REDISMODULE_ERR will be returned and if `err`
|
||||
* is not NULL, it will be populated with an error message. */
|
||||
int RM_ConfigSet(RedisModuleCtx *ctx, const char *name, RedisModuleString *value, RedisModuleString **err) {
|
||||
sds config_name = sdsnew(name);
|
||||
const char *cerr = NULL;
|
||||
const char *val = RM_StringPtrLen(value, NULL);
|
||||
int res = moduleSetStringConfig(ctx->client, config_name, val, &cerr);
|
||||
sdsfree(config_name);
|
||||
if (err && cerr)
|
||||
*err = RM_CreateString(ctx, cerr, strlen(cerr));
|
||||
return (res == 0 ? REDISMODULE_ERR : REDISMODULE_OK);
|
||||
}
|
||||
|
||||
/* Set the value of a bool config.
|
||||
*
|
||||
* See RedisModule_ConfigSet for return value. */
|
||||
int RM_ConfigSetBool(RedisModuleCtx *ctx, const char *name, int value, RedisModuleString **err) {
|
||||
const char *cerr = NULL;
|
||||
sds config_name = sdsnew(name);
|
||||
int res = moduleSetBoolConfig(ctx->client, config_name, value, &cerr);
|
||||
sdsfree(config_name);
|
||||
if (err && cerr)
|
||||
*err = RM_CreateString(ctx, cerr, strlen(cerr));
|
||||
return (res == 0 ? REDISMODULE_ERR : REDISMODULE_OK);
|
||||
}
|
||||
|
||||
/* Set the value of an enum config.
|
||||
*
|
||||
* If the config is multi-argument the value parameter must be a space-separated
|
||||
* string.
|
||||
*
|
||||
* See RedisModule_ConfigSet for return value. */
|
||||
int RM_ConfigSetEnum(RedisModuleCtx *ctx, const char *name, RedisModuleString *value, RedisModuleString **err) {
|
||||
sds config_name = sdsnew(name);
|
||||
const char *cerr = NULL;
|
||||
|
||||
size_t len;
|
||||
const char *val = RM_StringPtrLen(value, &len);
|
||||
sds sds_val = sdsnewlen(val, len);
|
||||
|
||||
int vals_cnt = 0;
|
||||
sds *vals = sdssplitlen(val, sdslen(sds_val), " ", 1, &vals_cnt);
|
||||
|
||||
int res = moduleSetEnumConfig(ctx->client, config_name, vals, vals_cnt, &cerr);
|
||||
|
||||
sdsfreesplitres(vals, vals_cnt);
|
||||
sdsfree(sds_val);
|
||||
sdsfree(config_name);
|
||||
if (err && cerr)
|
||||
*err = RM_CreateString(ctx, cerr, strlen(cerr));
|
||||
return (res == 0 ? REDISMODULE_ERR : REDISMODULE_OK);
|
||||
}
|
||||
|
||||
/* Set the value of a numeric config.
|
||||
* If the value passed is meant to be a percentage, it should be passed as a
|
||||
* negative value.
|
||||
*
|
||||
* See RedisModule_ConfigSet for return value. */
|
||||
int RM_ConfigSetNumeric(RedisModuleCtx *ctx, const char *name, long long value, RedisModuleString **err) {
|
||||
sds config_name = sdsnew(name);
|
||||
const char *cerr = NULL;
|
||||
int res = moduleSetNumericConfig(ctx->client, config_name, value, &cerr);
|
||||
sdsfree(config_name);
|
||||
if (err && cerr)
|
||||
*err = RM_CreateString(ctx, cerr, strlen(cerr));
|
||||
return (res == 0 ? REDISMODULE_ERR : REDISMODULE_OK);
|
||||
}
|
||||
|
||||
/* Redis MODULE command.
|
||||
*
|
||||
* MODULE LIST
|
||||
|
@ -14550,4 +14859,16 @@ void moduleRegisterCoreAPI(void) {
|
|||
REGISTER_API(RdbLoad);
|
||||
REGISTER_API(RdbSave);
|
||||
REGISTER_API(GetInternalSecret);
|
||||
REGISTER_API(ConfigIteratorCreate);
|
||||
REGISTER_API(ConfigIteratorRelease);
|
||||
REGISTER_API(ConfigIteratorNext);
|
||||
REGISTER_API(ConfigGetType);
|
||||
REGISTER_API(ConfigGet);
|
||||
REGISTER_API(ConfigGetBool);
|
||||
REGISTER_API(ConfigGetEnum);
|
||||
REGISTER_API(ConfigGetNumeric);
|
||||
REGISTER_API(ConfigSet);
|
||||
REGISTER_API(ConfigSetBool);
|
||||
REGISTER_API(ConfigSetEnum);
|
||||
REGISTER_API(ConfigSetNumeric);
|
||||
}
|
||||
|
|
|
@ -830,6 +830,13 @@ typedef enum {
|
|||
REDISMODULE_ACL_LOG_CHANNEL /* Channel authorization failure */
|
||||
} RedisModuleACLLogEntryReason;
|
||||
|
||||
typedef enum {
|
||||
REDISMODULE_CONFIG_TYPE_STRING = 0,
|
||||
REDISMODULE_CONFIG_TYPE_ENUM,
|
||||
REDISMODULE_CONFIG_TYPE_NUMERIC,
|
||||
REDISMODULE_CONFIG_TYPE_BOOL,
|
||||
} RedisModuleConfigType;
|
||||
|
||||
/* Incomplete structures needed by both the core and modules. */
|
||||
typedef struct RedisModuleIO RedisModuleIO;
|
||||
typedef struct RedisModuleDigest RedisModuleDigest;
|
||||
|
@ -891,6 +898,7 @@ typedef struct RedisModuleScanCursor RedisModuleScanCursor;
|
|||
typedef struct RedisModuleUser RedisModuleUser;
|
||||
typedef struct RedisModuleKeyOptCtx RedisModuleKeyOptCtx;
|
||||
typedef struct RedisModuleRdbStream RedisModuleRdbStream;
|
||||
typedef struct RedisModuleConfigIterator RedisModuleConfigIterator;
|
||||
|
||||
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
|
||||
|
@ -1333,6 +1341,18 @@ REDISMODULE_API void (*RedisModule_RdbStreamFree)(RedisModuleRdbStream *stream)
|
|||
REDISMODULE_API int (*RedisModule_RdbLoad)(RedisModuleCtx *ctx, RedisModuleRdbStream *stream, int flags) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_RdbSave)(RedisModuleCtx *ctx, RedisModuleRdbStream *stream, int flags) REDISMODULE_ATTR;
|
||||
REDISMODULE_API const char * (*RedisModule_GetInternalSecret)(RedisModuleCtx *ctx, size_t *len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleConfigIterator * (*RedisModule_ConfigIteratorCreate)(RedisModuleCtx *ctx, const char *pattern) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_ConfigIteratorRelease)(RedisModuleCtx *ctx, RedisModuleConfigIterator *iter) REDISMODULE_ATTR;
|
||||
REDISMODULE_API const char * (*RedisModule_ConfigIteratorNext)(RedisModuleConfigIterator *iter) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ConfigGetType)(const char *name, RedisModuleConfigType *res) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ConfigGet)(RedisModuleCtx *ctx, const char *name, RedisModuleString **res) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ConfigGetBool)(RedisModuleCtx *ctx, const char *name, int *res) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ConfigGetEnum)(RedisModuleCtx *ctx, const char *name, RedisModuleString **res) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ConfigGetNumeric)(RedisModuleCtx *ctx, const char *name, long long *res) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ConfigSet)(RedisModuleCtx *ctx, const char *name, RedisModuleString *value, RedisModuleString **err) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ConfigSetBool)(RedisModuleCtx *ctx, const char *name, int value, RedisModuleString **err) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ConfigSetEnum)(RedisModuleCtx *ctx, const char *name, RedisModuleString *value, RedisModuleString **err) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ConfigSetNumeric)(RedisModuleCtx *ctx, const char *name, long long value, RedisModuleString **err) REDISMODULE_ATTR;
|
||||
|
||||
#define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX)
|
||||
|
||||
|
@ -1708,6 +1728,18 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||
REDISMODULE_GET_API(RdbLoad);
|
||||
REDISMODULE_GET_API(RdbSave);
|
||||
REDISMODULE_GET_API(GetInternalSecret);
|
||||
REDISMODULE_GET_API(ConfigIteratorCreate);
|
||||
REDISMODULE_GET_API(ConfigIteratorRelease);
|
||||
REDISMODULE_GET_API(ConfigIteratorNext);
|
||||
REDISMODULE_GET_API(ConfigGetType);
|
||||
REDISMODULE_GET_API(ConfigGet);
|
||||
REDISMODULE_GET_API(ConfigGetBool);
|
||||
REDISMODULE_GET_API(ConfigGetEnum);
|
||||
REDISMODULE_GET_API(ConfigGetNumeric);
|
||||
REDISMODULE_GET_API(ConfigSet);
|
||||
REDISMODULE_GET_API(ConfigSetBool);
|
||||
REDISMODULE_GET_API(ConfigSetEnum);
|
||||
REDISMODULE_GET_API(ConfigSetNumeric);
|
||||
|
||||
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
||||
RedisModule_SetModuleAttribs(ctx,name,ver,apiver);
|
||||
|
|
15
src/server.h
15
src/server.h
|
@ -3609,7 +3609,9 @@ void addModuleStringConfig(sds name, sds alias, int flags, void *privdata, sds d
|
|||
void addModuleEnumConfig(sds name, sds alias, int flags, void *privdata, int default_val, configEnum *enum_vals, int num_enum_vals);
|
||||
void addModuleNumericConfig(sds name, sds alias, 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 moduleConfigApply(ModuleConfig *module_config, const char **err);
|
||||
int moduleConfigApplyConfig(list *module_configs, const char **err, const char **err_arg_name);
|
||||
int moduleConfigNeedsApply(ModuleConfig *config);
|
||||
int getModuleBoolConfig(ModuleConfig *module_config);
|
||||
int setModuleBoolConfig(ModuleConfig *config, int val, const char **err);
|
||||
sds getModuleStringConfig(ModuleConfig *module_config);
|
||||
|
@ -3619,6 +3621,19 @@ 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);
|
||||
|
||||
/* API for modules to access config values. */
|
||||
dictIterator *moduleGetConfigIterator(void);
|
||||
const char *moduleConfigIteratorNext(dictIterator **iter, sds pattern, int is_glob, configType *typehint);
|
||||
int moduleGetConfigType(sds name, configType *res);
|
||||
int moduleGetBoolConfig(sds name, int *res);
|
||||
int moduleGetStringConfig(sds name, sds *res);
|
||||
int moduleGetEnumConfig(sds name, sds *res);
|
||||
int moduleGetNumericConfig(sds name, long long *res);
|
||||
int moduleSetBoolConfig(client *c, sds name, int val, const char **err);
|
||||
int moduleSetStringConfig(client *c, sds name, const char *val, const char **err);
|
||||
int moduleSetEnumConfig(client *c, sds name, sds *vals, int vals_cnt, const char **err);
|
||||
int moduleSetNumericConfig(client *c, sds name, long long val, const char **err);
|
||||
|
||||
/* db.c -- Keyspace access API */
|
||||
void updateKeysizesHist(redisDb *db, int didx, uint32_t type, int64_t oldLen, int64_t newLen);
|
||||
void dbgAssertKeysizesHist(redisDb *db);
|
||||
|
|
|
@ -82,7 +82,8 @@ TEST_MODULES = \
|
|||
moduleauthtwo.so \
|
||||
rdbloadsave.so \
|
||||
crash.so \
|
||||
internalsecret.so
|
||||
internalsecret.so \
|
||||
configaccess.so
|
||||
|
||||
.PHONY: all
|
||||
|
||||
|
|
|
@ -0,0 +1,345 @@
|
|||
#include "redismodule.h"
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
/* See moduleconfigs.c for registering module configs. We need to register some
|
||||
* module configs with our module in order to test the interaction between
|
||||
* module configs and the RM_Get/Set*Config APIs. */
|
||||
int configaccess_bool;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* Test command for RM_GetConfigType */
|
||||
int TestGetConfigType_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) {
|
||||
return RedisModule_WrongArity(ctx);
|
||||
}
|
||||
|
||||
size_t len;
|
||||
const char *config_name = RedisModule_StringPtrLen(argv[1], &len);
|
||||
|
||||
RedisModuleConfigType type;
|
||||
int res = RedisModule_ConfigGetType(config_name, &type);
|
||||
if (res == REDISMODULE_ERR) {
|
||||
RedisModule_ReplyWithError(ctx, "ERR Config does not exist");
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
const char *type_str;
|
||||
switch (type) {
|
||||
case REDISMODULE_CONFIG_TYPE_BOOL:
|
||||
type_str = "bool";
|
||||
break;
|
||||
case REDISMODULE_CONFIG_TYPE_NUMERIC:
|
||||
type_str = "numeric";
|
||||
break;
|
||||
case REDISMODULE_CONFIG_TYPE_STRING:
|
||||
type_str = "string";
|
||||
break;
|
||||
case REDISMODULE_CONFIG_TYPE_ENUM:
|
||||
type_str = "enum";
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
|
||||
RedisModule_ReplyWithSimpleString(ctx, type_str);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Test command for config iteration */
|
||||
int TestConfigIteration_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
|
||||
if (argc > 2) {
|
||||
return RedisModule_WrongArity(ctx);
|
||||
}
|
||||
|
||||
const char *pattern = NULL;
|
||||
if (argc == 2) {
|
||||
pattern = RedisModule_StringPtrLen(argv[1], NULL);
|
||||
}
|
||||
|
||||
RedisModuleConfigIterator *iter = RedisModule_ConfigIteratorCreate(ctx, pattern);
|
||||
if (!iter) {
|
||||
RedisModule_ReplyWithError(ctx, "ERR Failed to get config iterator");
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
/* Start array reply for the configs */
|
||||
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
|
||||
|
||||
/* Iterate through the dictionary */
|
||||
const char *config_name = NULL;
|
||||
long count = 0;
|
||||
while ((config_name = RedisModule_ConfigIteratorNext(iter)) != NULL) {
|
||||
RedisModuleString *value = NULL;
|
||||
RedisModule_ConfigGet(ctx, config_name, &value);
|
||||
|
||||
RedisModule_ReplyWithArray(ctx, 2);
|
||||
RedisModule_ReplyWithStringBuffer(ctx, config_name, strlen(config_name));
|
||||
RedisModule_ReplyWithString(ctx, value);
|
||||
|
||||
RedisModule_FreeString(ctx, value);
|
||||
++count;
|
||||
}
|
||||
RedisModule_ReplySetArrayLength(ctx, count);
|
||||
|
||||
/* Free the iterator */
|
||||
RedisModule_ConfigIteratorRelease(ctx, iter);
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Test command for RM_GetBoolConfig */
|
||||
int TestGetBoolConfig_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) {
|
||||
return RedisModule_WrongArity(ctx);
|
||||
}
|
||||
|
||||
size_t len;
|
||||
const char *config_name = RedisModule_StringPtrLen(argv[1], &len);
|
||||
|
||||
int value;
|
||||
if (RedisModule_ConfigGetBool(ctx, config_name, &value) == REDISMODULE_ERR) {
|
||||
RedisModule_ReplyWithError(ctx, "ERR Failed to get bool config");
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
RedisModule_ReplyWithLongLong(ctx, value);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Test command for RM_GetNumericConfig */
|
||||
int TestGetNumericConfig_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) {
|
||||
return RedisModule_WrongArity(ctx);
|
||||
}
|
||||
|
||||
size_t len;
|
||||
const char *config_name = RedisModule_StringPtrLen(argv[1], &len);
|
||||
|
||||
long long value;
|
||||
if (RedisModule_ConfigGetNumeric(ctx, config_name, &value) == REDISMODULE_ERR) {
|
||||
RedisModule_ReplyWithError(ctx, "ERR Failed to get numeric config");
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
RedisModule_ReplyWithLongLong(ctx, value);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Test command for RM_GetConfig */
|
||||
int TestGetConfig_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) {
|
||||
return RedisModule_WrongArity(ctx);
|
||||
}
|
||||
|
||||
size_t len;
|
||||
const char *config_name = RedisModule_StringPtrLen(argv[1], &len);
|
||||
|
||||
RedisModuleString *value;
|
||||
if (RedisModule_ConfigGet(ctx, config_name, &value) == REDISMODULE_ERR) {
|
||||
RedisModule_ReplyWithError(ctx, "ERR Failed to get string config");
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
RedisModule_ReplyWithString(ctx, value);
|
||||
RedisModule_FreeString(ctx,value);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Test command for RM_GetEnumConfig */
|
||||
int TestGetEnumConfig_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) {
|
||||
return RedisModule_WrongArity(ctx);
|
||||
}
|
||||
|
||||
size_t len;
|
||||
const char *config_name = RedisModule_StringPtrLen(argv[1], &len);
|
||||
|
||||
RedisModuleString *value;
|
||||
if (RedisModule_ConfigGetEnum(ctx, config_name, &value) == REDISMODULE_ERR) {
|
||||
RedisModule_ReplyWithError(ctx, "ERR Failed to get enum name config");
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
RedisModule_ReplyWithString(ctx, value);
|
||||
RedisModule_Free(value);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Test command for RM_SetBoolConfig */
|
||||
int TestSetBoolConfig_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 3) {
|
||||
return RedisModule_WrongArity(ctx);
|
||||
}
|
||||
|
||||
size_t name_len, value_len;
|
||||
const char *config_name = RedisModule_StringPtrLen(argv[1], &name_len);
|
||||
const char *config_value = RedisModule_StringPtrLen(argv[2], &value_len);
|
||||
|
||||
int bool_value;
|
||||
if (!strcasecmp(config_value, "yes")) {
|
||||
bool_value = 1;
|
||||
} else if (!strcasecmp(config_value, "no")) {
|
||||
bool_value = 0;
|
||||
} else {
|
||||
bool_value = -1;
|
||||
}
|
||||
|
||||
RedisModuleString *error = NULL;
|
||||
int result = RedisModule_ConfigSetBool(ctx, config_name, bool_value, &error);
|
||||
if (result == REDISMODULE_ERR) {
|
||||
RedisModule_ReplyWithErrorFormat(ctx, "ERR Failed to set bool config %s: %s", config_name, RedisModule_StringPtrLen(error, NULL));
|
||||
RedisModule_FreeString(ctx, error);
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Test command for RM_SetNumericConfig */
|
||||
int TestSetNumericConfig_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 3) {
|
||||
return RedisModule_WrongArity(ctx);
|
||||
}
|
||||
|
||||
size_t name_len;
|
||||
const char *config_name = RedisModule_StringPtrLen(argv[1], &name_len);
|
||||
|
||||
long long value;
|
||||
if (RedisModule_StringToLongLong(argv[2], &value) != REDISMODULE_OK) {
|
||||
RedisModule_ReplyWithError(ctx, "ERR Invalid numeric value");
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
RedisModuleString *error = NULL;
|
||||
int result = RedisModule_ConfigSetNumeric(ctx, config_name, value, &error);
|
||||
if (result == REDISMODULE_OK) {
|
||||
RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
} else {
|
||||
RedisModule_ReplyWithErrorFormat(ctx, "ERR Failed to set numeric config %s: %s", config_name, RedisModule_StringPtrLen(error, NULL));
|
||||
RedisModule_FreeString(ctx, error);
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Test command for RM_SetConfig */
|
||||
int TestSetConfig_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 3) {
|
||||
return RedisModule_WrongArity(ctx);
|
||||
}
|
||||
|
||||
size_t name_len;
|
||||
const char *config_name = RedisModule_StringPtrLen(argv[1], &name_len);
|
||||
|
||||
RedisModuleString *error = NULL;
|
||||
int result = RedisModule_ConfigSet(ctx, config_name, argv[2], &error);
|
||||
if (result == REDISMODULE_OK) {
|
||||
RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
} else {
|
||||
RedisModule_ReplyWithErrorFormat(ctx, "ERR Failed to set string config %s: %s", config_name, RedisModule_StringPtrLen(error, NULL));
|
||||
RedisModule_FreeString(ctx, error);
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Test command for RM_SetEnumConfig with name */
|
||||
int TestSetEnumConfig_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc < 3) {
|
||||
return RedisModule_WrongArity(ctx);
|
||||
}
|
||||
|
||||
const char *config_name = RedisModule_StringPtrLen(argv[1], NULL);
|
||||
|
||||
RedisModuleString *error = NULL;
|
||||
int result = RedisModule_ConfigSetEnum(ctx, config_name, argv[2], &error);
|
||||
|
||||
if (result == REDISMODULE_OK) {
|
||||
RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
} else {
|
||||
RedisModule_ReplyWithErrorFormat(ctx, "ERR Failed to set enum config %s: %s", config_name, RedisModule_StringPtrLen(error, NULL));
|
||||
RedisModule_FreeString(ctx, error);
|
||||
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, "configaccess", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "configaccess.getconfigs",
|
||||
TestConfigIteration_RedisCommand, "readonly", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "configaccess.getbool",
|
||||
TestGetBoolConfig_RedisCommand, "readonly", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "configaccess.getnumeric",
|
||||
TestGetNumericConfig_RedisCommand, "readonly", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "configaccess.get",
|
||||
TestGetConfig_RedisCommand, "readonly", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "configaccess.getenum",
|
||||
TestGetEnumConfig_RedisCommand, "readonly", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "configaccess.setbool",
|
||||
TestSetBoolConfig_RedisCommand, "write", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "configaccess.setnumeric",
|
||||
TestSetNumericConfig_RedisCommand, "write", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "configaccess.set",
|
||||
TestSetConfig_RedisCommand, "write", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "configaccess.setenum",
|
||||
TestSetEnumConfig_RedisCommand, "write", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "configaccess.getconfigtype", TestGetConfigType_RedisCommand, "readonly", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_RegisterBoolConfig(ctx, "bool", 1, REDISMODULE_CONFIG_DEFAULT,
|
||||
getBoolConfigCommand, setBoolConfigCommand, NULL, &configaccess_bool) == REDISMODULE_ERR) {
|
||||
RedisModule_Log(ctx, "warning", "Failed to register configaccess_bool");
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
RedisModule_Log(ctx, "debug", "Loading configaccess module configuration");
|
||||
if (RedisModule_LoadConfigs(ctx) == REDISMODULE_ERR) {
|
||||
RedisModule_Log(ctx, "warning", "Failed to load configaccess module configuration");
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
set testmodule [file normalize tests/modules/configaccess.so]
|
||||
set othermodule [file normalize tests/modules/moduleconfigs.so]
|
||||
|
||||
start_server {tags {"modules"}} {
|
||||
r module load $testmodule
|
||||
r module loadex $othermodule CONFIG moduleconfigs.mutable_bool yes
|
||||
|
||||
test {Test module config get with standard Redis configs} {
|
||||
# Test getting standard Redis configs of different types
|
||||
set maxmemory [r config get maxmemory]
|
||||
assert_equal [lindex $maxmemory 1] [r configaccess.getnumeric maxmemory]
|
||||
|
||||
set port [r config get port]
|
||||
assert_equal [lindex $port 1] [r configaccess.getnumeric port]
|
||||
|
||||
set appendonly [r config get appendonly]
|
||||
assert_equal [string is true [lindex $appendonly 1]] [r configaccess.getbool appendonly]
|
||||
|
||||
# Test string config
|
||||
set logfile [r config get logfile]
|
||||
assert_equal [lindex $logfile 1] [r configaccess.get logfile]
|
||||
|
||||
# Test SDS config
|
||||
set requirepass [r config get requirepass]
|
||||
assert_equal [lindex $requirepass 1] [r configaccess.get requirepass]
|
||||
|
||||
# Test special config
|
||||
set oom_score_adj_values [r config get oom-score-adj-values]
|
||||
assert_equal [lindex $oom_score_adj_values 1] [r configaccess.get oom-score-adj-values]
|
||||
|
||||
set maxmemory_policy_name [r configaccess.getenum maxmemory-policy]
|
||||
assert_equal [lindex [r config get maxmemory-policy] 1] $maxmemory_policy_name
|
||||
|
||||
# Test percent config
|
||||
r config set maxmemory 100000
|
||||
r configaccess.setnumeric maxmemory-clients -50
|
||||
assert_equal [lindex [r config get maxmemory-clients] 1] 50%
|
||||
|
||||
# Test multi-argument enum config
|
||||
r config set moduleconfigs.flags "one two four"
|
||||
assert_equal "five two" [r configaccess.getenum moduleconfigs.flags]
|
||||
|
||||
# Test getting multi-argument enum config via generic get
|
||||
r config set moduleconfigs.flags "two four"
|
||||
assert_equal "two four" [r configaccess.get moduleconfigs.flags]
|
||||
}
|
||||
|
||||
test {Test module config get with non-existent configs} {
|
||||
# Test getting non-existent configs
|
||||
catch {r configaccess.getnumeric nonexistent_config} err
|
||||
assert_match "ERR*" $err
|
||||
|
||||
catch {r configaccess.getbool nonexistent_config} err
|
||||
assert_match "ERR*" $err
|
||||
|
||||
catch {r configaccess.get nonexistent_config} err
|
||||
assert_match "ERR*" $err
|
||||
|
||||
catch {r configaccess.getenum nonexistent_config} err
|
||||
assert_match "ERR*" $err
|
||||
}
|
||||
|
||||
test {Test module config set with standard Redis configs} {
|
||||
# Test setting numeric config
|
||||
set old_maxmemory_samples [r config get maxmemory-samples]
|
||||
r configaccess.setnumeric maxmemory-samples 10
|
||||
assert_equal "maxmemory-samples 10" [r config get maxmemory-samples]
|
||||
r config set maxmemory-samples [lindex $old_maxmemory_samples 1]
|
||||
|
||||
# Test setting bool config
|
||||
set old_protected_mode [r config get protected-mode]
|
||||
r configaccess.setbool protected-mode no
|
||||
assert_equal "protected-mode no" [r config get protected-mode]
|
||||
r config set protected-mode [lindex $old_protected_mode 1]
|
||||
|
||||
# Test setting string config
|
||||
set old_masteruser [r config get masteruser]
|
||||
r configaccess.set masteruser "__newmasteruser__"
|
||||
assert_equal "__newmasteruser__" [lindex [r config get masteruser] 1]
|
||||
r config set masteruser [lindex $old_masteruser 1]
|
||||
|
||||
# Test setting enum config
|
||||
set old_loglevel [r config get loglevel]
|
||||
r config set loglevel "notice" ; # Set to some value we are sure is different than the one tested
|
||||
r configaccess.setenum loglevel warning
|
||||
assert_equal "loglevel warning" [r config get loglevel]
|
||||
r config set loglevel [lindex $old_loglevel 1]
|
||||
|
||||
# Test setting multi-argument enum config
|
||||
r config set moduleconfigs.flags "one two four"
|
||||
assert_equal "moduleconfigs.flags {five two}" [r config get moduleconfigs.flags]
|
||||
r configaccess.setenum moduleconfigs.flags "two four"
|
||||
assert_equal "moduleconfigs.flags {two four}" [r config get moduleconfigs.flags]
|
||||
|
||||
# Test setting multi-argument enum config via generic set
|
||||
r config set moduleconfigs.flags "one two four"
|
||||
assert_equal "moduleconfigs.flags {five two}" [r config get moduleconfigs.flags]
|
||||
r configaccess.set moduleconfigs.flags "two four"
|
||||
assert_equal "moduleconfigs.flags {two four}" [r config get moduleconfigs.flags]
|
||||
}
|
||||
|
||||
test {Test module config set with module configs} {
|
||||
# Test setting module bool config
|
||||
assert_equal "OK" [r configaccess.setbool configaccess.bool no]
|
||||
assert_equal "configaccess.bool no" [r config get configaccess.bool]
|
||||
|
||||
# Test setting module bool config from another module
|
||||
assert_equal "OK" [r configaccess.setbool moduleconfigs.mutable_bool no]
|
||||
assert_equal "moduleconfigs.mutable_bool no" [r config get moduleconfigs.mutable_bool]
|
||||
|
||||
# Test setting module numeric config
|
||||
assert_equal "OK" [r configaccess.setnumeric moduleconfigs.numeric 100]
|
||||
assert_equal "moduleconfigs.numeric 100" [r config get moduleconfigs.numeric]
|
||||
|
||||
# Test setting module enum config
|
||||
assert_equal "OK" [r configaccess.setenum moduleconfigs.enum "five"]
|
||||
assert_equal "moduleconfigs.enum five" [r config get moduleconfigs.enum]
|
||||
}
|
||||
|
||||
test {Test module config set with error cases} {
|
||||
# Test setting a non-existent config
|
||||
catch {r configaccess.setbool nonexistent_config yes} err
|
||||
assert_match "*ERR*" $err
|
||||
|
||||
# Test setting a read-only config
|
||||
catch {r configaccess.setbool moduleconfigs.immutable_bool yes} err
|
||||
assert_match "*ERR*" $err
|
||||
|
||||
# Test setting an enum config with invalid value
|
||||
catch {r configaccess.setenumname moduleconfigs.enum "invalid_value"} err
|
||||
assert_match "*ERR*" $err
|
||||
|
||||
# Test setting a numeric config with out-of-range value
|
||||
catch {r configaccess.setnumeric moduleconfigs.numeric 5000} err
|
||||
assert_match "*ERR*" $err
|
||||
}
|
||||
|
||||
test {Test module get all configs} {
|
||||
# Get all configs using the module command
|
||||
set all_configs [r configaccess.getconfigs]
|
||||
|
||||
# Verify the number of configs matches the number of configs returned
|
||||
# by Redis's native CONFIG GET command.
|
||||
set all_configs_std_pairs [llength [r config get *]]
|
||||
|
||||
# When comparing with the standard CONFIG GET command, we need to divide
|
||||
# by 2 because the standard command returns a flattened array of
|
||||
# key-value pairs whereas our testing command returns an array of pairs.
|
||||
assert_equal [llength $all_configs] [expr $all_configs_std_pairs / 2]
|
||||
|
||||
# Verify all the configs are present in both replies.
|
||||
foreach config_pair $all_configs {
|
||||
assert_equal 2 [llength $config_pair]
|
||||
set config_name [lindex $config_pair 0]
|
||||
set config_value [lindex $config_pair 1]
|
||||
|
||||
# Verify that we can get this config using standard config get
|
||||
set redis_config [r config get $config_name]
|
||||
assert {[llength $redis_config] != 0}
|
||||
|
||||
assert_equal $config_value [lindex $redis_config 1]
|
||||
}
|
||||
|
||||
# Test that module configs are also included
|
||||
set found_module_config 0
|
||||
foreach config_pair $all_configs {
|
||||
set config_name [lindex $config_pair 0]
|
||||
if {$config_name eq "configaccess.bool"} {
|
||||
set found_module_config 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assert {$found_module_config == 1}
|
||||
|
||||
# Test pattern matching
|
||||
set moduleconfigs_count [r configaccess.getconfigs "moduleconfigs.*"]
|
||||
assert_equal 7 [llength $moduleconfigs_count]
|
||||
|
||||
set memoryconfigs_count [r configaccess.getconfigs "*memory"]
|
||||
assert_equal 3 [llength $memoryconfigs_count]
|
||||
}
|
||||
|
||||
test {Test module config type detection} {
|
||||
# Test getting config types for different config types
|
||||
assert_equal "bool" [r configaccess.getconfigtype appendonly]
|
||||
assert_equal "numeric" [r configaccess.getconfigtype port]
|
||||
assert_equal "string" [r configaccess.getconfigtype logfile]
|
||||
assert_equal "enum" [r configaccess.getconfigtype maxmemory-policy]
|
||||
|
||||
# Test with module config
|
||||
assert_equal "bool" [r configaccess.getconfigtype configaccess.bool]
|
||||
|
||||
# Test with non-existent config
|
||||
catch {r configaccess.getconfigtype nonexistent_config} err
|
||||
assert_match "ERR Config does not exist" $err
|
||||
}
|
||||
|
||||
test {Test config rollback on apply} {
|
||||
set og_port [lindex [r config get port] 1]
|
||||
|
||||
set used_port [find_available_port $::baseport $::portcount]
|
||||
|
||||
# Run a dummy server on used_port so we know we can't configure redis to
|
||||
# use it. It's ok for this to fail because that means used_port is invalid
|
||||
# anyway
|
||||
catch {set sockfd [socket -server dummy_accept -myaddr 127.0.0.1 $used_port]} e
|
||||
if {$::verbose} { puts "dummy_accept: $e" }
|
||||
|
||||
# Try to listen on the used port, pass some more configs to make sure the
|
||||
# returned failure message is for the first bad config and everything is rolled back.
|
||||
assert_error "ERR Failed to set numeric config port: Unable to listen on this port*" {
|
||||
eval "r configaccess.setnumeric port $used_port"
|
||||
}
|
||||
|
||||
assert_equal [lindex [r config get port] 1] $og_port
|
||||
close $sockfd
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue