"src/vscode:/vscode.git/clone" did not exist on "bc1ef48e5667d945d920ca8da7dd7f799148991d"
Unverified Commit df1890ef authored by Wen Hui's avatar Wen Hui Committed by GitHub
Browse files

Allow SENTINEL CONFIG SET and SENTINEL CONFIG GET to handle multiple parameters. (#10362)



Extend SENTINEL CONFIG SET and SENTINEL CONFIG GET to be
compatible with variadic CONFIG SET and CONFIG GET and allow multiple
parameters to be modified in a single call atomically.
Co-authored-by: default avatarOran Agra <oran@redislabs.com>
parent fd566f40
{
"CONFIG": {
"summary": "Configures Redis Sentinel.",
"complexity": "O(1)",
"complexity": "O(N) when N is the number of configuration parameters provided",
"group": "sentinel",
"since": "6.2.0",
"arity": -4,
"container": "SENTINEL",
"function": "sentinelCommand",
"history": [
[
"7.2.0",
"Added the ability to set and get multiple parameters in one call."
]
],
"command_flags": [
"ADMIN",
"SENTINEL",
......@@ -42,7 +48,7 @@
"type": "string"
},
"announce-port": {
"type": "integer"
"type": "string"
},
"sentinel-user": {
"type": "string"
......@@ -87,6 +93,7 @@
"name":"set",
"token":"SET",
"type":"block",
"multiple": true,
"arguments":[
{
"name":"parameter",
......@@ -101,7 +108,8 @@
{
"token":"GET",
"name":"parameter",
"type":"string"
"type":"string",
"multiple": true
}
]
}
......
......@@ -3178,6 +3178,13 @@ void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
}
/* =========================== SENTINEL command ============================= */
static void populateDict(dict *options_dict, char **options) {
for (int i=0; options[i]; i++) {
sds option = sdsnew(options[i]);
if (dictAdd(options_dict, option, NULL)==DICT_ERR)
sdsfree(option);
}
}
const char* getLogLevel(void) {
switch (server.verbosity) {
......@@ -3189,52 +3196,112 @@ const char* getLogLevel(void) {
return "unknown";
}
/* SENTINEL CONFIG SET <option> <value>*/
/* SENTINEL CONFIG SET option value [option value ...] */
void sentinelConfigSetCommand(client *c) {
robj *o = c->argv[3];
robj *val = c->argv[4];
long long numval;
int drop_conns = 0;
char *option;
robj *val;
char *options[] = {
"announce-ip",
"sentinel-user",
"sentinel-pass",
"resolve-hostnames",
"announce-port",
"announce-hostnames",
"loglevel",
NULL};
static dict *options_dict = NULL;
if (!options_dict) {
options_dict = dictCreate(&stringSetDictType);
populateDict(options_dict, options);
}
dict *set_configs = dictCreate(&stringSetDictType);
/* Validate arguments are valid */
for (int i = 3; i < c->argc; i++) {
option = c->argv[i]->ptr;
/* Validate option is valid */
if (dictFind(options_dict, option) == NULL) {
addReplyErrorFormat(c, "Invalid argument '%s' to SENTINEL CONFIG SET", option);
goto exit;
}
if (!strcasecmp(o->ptr, "resolve-hostnames")) {
if ((numval = yesnotoi(val->ptr)) == -1) goto badfmt;
sentinel.resolve_hostnames = numval;
} else if (!strcasecmp(o->ptr, "announce-hostnames")) {
if ((numval = yesnotoi(val->ptr)) == -1) goto badfmt;
sentinel.announce_hostnames = numval;
} else if (!strcasecmp(o->ptr, "announce-ip")) {
if (sentinel.announce_ip) sdsfree(sentinel.announce_ip);
sentinel.announce_ip = sdsnew(val->ptr);
} else if (!strcasecmp(o->ptr, "announce-port")) {
if (getLongLongFromObject(val, &numval) == C_ERR ||
numval < 0 || numval > 65535)
goto badfmt;
sentinel.announce_port = numval;
} else if (!strcasecmp(o->ptr, "sentinel-user")) {
sdsfree(sentinel.sentinel_auth_user);
sentinel.sentinel_auth_user = sdslen(val->ptr) == 0 ?
NULL : sdsdup(val->ptr);
drop_conns = 1;
} else if (!strcasecmp(o->ptr, "sentinel-pass")) {
sdsfree(sentinel.sentinel_auth_pass);
sentinel.sentinel_auth_pass = sdslen(val->ptr) == 0 ?
NULL : sdsdup(val->ptr);
drop_conns = 1;
} else if (!strcasecmp(o->ptr, "loglevel")) {
if (!strcasecmp(val->ptr, "debug"))
server.verbosity = LL_DEBUG;
else if (!strcasecmp(val->ptr, "verbose"))
server.verbosity = LL_VERBOSE;
else if (!strcasecmp(val->ptr, "notice"))
server.verbosity = LL_NOTICE;
else if (!strcasecmp(val->ptr, "warning"))
server.verbosity = LL_WARNING;
else
goto badfmt;
} else {
addReplyErrorFormat(c, "Invalid argument '%s' to SENTINEL CONFIG SET",
(char *) o->ptr);
return;
/* Check duplicates */
if (dictFind(set_configs, option) != NULL) {
addReplyErrorFormat(c, "Duplicate argument '%s' to SENTINEL CONFIG SET", option);
goto exit;
}
serverAssert(dictAdd(set_configs, sdsnew(option), NULL) == C_OK);
/* Validate argument */
if (i + 1 == c->argc) {
addReplyErrorFormat(c, "Missing argument '%s' value", option);
goto exit;
}
val = c->argv[++i];
if (!strcasecmp(option, "resolve-hostnames")) {
if ((yesnotoi(val->ptr)) == -1) goto badfmt;
} else if (!strcasecmp(option, "announce-hostnames")) {
if ((yesnotoi(val->ptr)) == -1) goto badfmt;
} else if (!strcasecmp(option, "announce-port")) {
if (getLongLongFromObject(val, &numval) == C_ERR ||
numval < 0 || numval > 65535) goto badfmt;
} else if (!strcasecmp(option, "loglevel")) {
if (!(!strcasecmp(val->ptr, "debug") || !strcasecmp(val->ptr, "verbose") ||
!strcasecmp(val->ptr, "notice") || !strcasecmp(val->ptr, "warning"))) goto badfmt;
}
}
/* Apply changes */
for (int i = 3; i < c->argc; i++) {
int moreargs = (c->argc-1) - i;
option = c->argv[i]->ptr;
if (!strcasecmp(option, "loglevel") && moreargs > 0) {
val = c->argv[++i];
if (!strcasecmp(val->ptr, "debug"))
server.verbosity = LL_DEBUG;
else if (!strcasecmp(val->ptr, "verbose"))
server.verbosity = LL_VERBOSE;
else if (!strcasecmp(val->ptr, "notice"))
server.verbosity = LL_NOTICE;
else if (!strcasecmp(val->ptr, "warning"))
server.verbosity = LL_WARNING;
} else if (!strcasecmp(option, "resolve-hostnames") && moreargs > 0) {
val = c->argv[++i];
numval = yesnotoi(val->ptr);
sentinel.resolve_hostnames = numval;
} else if (!strcasecmp(option, "announce-hostnames") && moreargs > 0) {
val = c->argv[++i];
numval = yesnotoi(val->ptr);
sentinel.announce_hostnames = numval;
} else if (!strcasecmp(option, "announce-ip") && moreargs > 0) {
val = c->argv[++i];
if (sentinel.announce_ip) sdsfree(sentinel.announce_ip);
sentinel.announce_ip = sdsnew(val->ptr);
} else if (!strcasecmp(option, "announce-port") && moreargs > 0) {
val = c->argv[++i];
getLongLongFromObject(val, &numval);
sentinel.announce_port = numval;
} else if (!strcasecmp(option, "sentinel-user") && moreargs > 0) {
val = c->argv[++i];
sdsfree(sentinel.sentinel_auth_user);
sentinel.sentinel_auth_user = sdslen(val->ptr) == 0 ?
NULL : sdsdup(val->ptr);
drop_conns = 1;
} else if (!strcasecmp(option, "sentinel-pass") && moreargs > 0) {
val = c->argv[++i];
sdsfree(sentinel.sentinel_auth_pass);
sentinel.sentinel_auth_pass = sdslen(val->ptr) == 0 ?
NULL : sdsdup(val->ptr);
drop_conns = 1;
} else {
/* Should never reach here */
serverAssert(0);
}
}
sentinelFlushConfigAndReply(c);
......@@ -3243,61 +3310,72 @@ void sentinelConfigSetCommand(client *c) {
if (drop_conns)
sentinelDropConnections();
exit:
dictRelease(set_configs);
return;
badfmt:
addReplyErrorFormat(c, "Invalid value '%s' to SENTINEL CONFIG SET '%s'",
(char *) val->ptr, (char *) o->ptr);
(char *) val->ptr, option);
dictRelease(set_configs);
}
/* SENTINEL CONFIG GET <option> */
/* SENTINEL CONFIG GET <option> [<option> ...] */
void sentinelConfigGetCommand(client *c) {
robj *o = c->argv[3];
const char *pattern = o->ptr;
char *pattern;
void *replylen = addReplyDeferredLen(c);
int matches = 0;
if (stringmatch(pattern,"resolve-hostnames",1)) {
addReplyBulkCString(c,"resolve-hostnames");
addReplyBulkCString(c,sentinel.resolve_hostnames ? "yes" : "no");
matches++;
}
if (stringmatch(pattern, "announce-hostnames", 1)) {
addReplyBulkCString(c,"announce-hostnames");
addReplyBulkCString(c,sentinel.announce_hostnames ? "yes" : "no");
matches++;
}
if (stringmatch(pattern, "announce-ip", 1)) {
addReplyBulkCString(c,"announce-ip");
addReplyBulkCString(c,sentinel.announce_ip ? sentinel.announce_ip : "");
matches++;
}
if (stringmatch(pattern, "announce-port", 1)) {
addReplyBulkCString(c, "announce-port");
addReplyBulkLongLong(c, sentinel.announce_port);
matches++;
}
if (stringmatch(pattern, "sentinel-user", 1)) {
addReplyBulkCString(c, "sentinel-user");
addReplyBulkCString(c, sentinel.sentinel_auth_user ? sentinel.sentinel_auth_user : "");
matches++;
}
if (stringmatch(pattern, "sentinel-pass", 1)) {
addReplyBulkCString(c, "sentinel-pass");
addReplyBulkCString(c, sentinel.sentinel_auth_pass ? sentinel.sentinel_auth_pass : "");
matches++;
}
if (stringmatch(pattern, "loglevel", 1)) {
addReplyBulkCString(c, "loglevel");
addReplyBulkCString(c, getLogLevel());
matches++;
/* Create a dictionary to store the input configs,to avoid adding duplicate twice */
dict *d = dictCreate(&externalStringType);
for (int i = 3; i < c->argc; i++) {
pattern = c->argv[i]->ptr;
/* If the string doesn't contain glob patterns and available in dictionary, don't look further, just continue. */
if (!strpbrk(pattern, "[*?") && dictFind(d, pattern)) continue;
/* we want to print all the matched patterns and avoid printing duplicates twice */
if (stringmatch(pattern,"resolve-hostnames",1) && !dictFind(d, "resolve-hostnames")) {
addReplyBulkCString(c,"resolve-hostnames");
addReplyBulkCString(c,sentinel.resolve_hostnames ? "yes" : "no");
dictAdd(d, "resolve-hostnames", NULL);
matches++;
}
if (stringmatch(pattern, "announce-hostnames", 1) && !dictFind(d, "announce-hostnames")) {
addReplyBulkCString(c,"announce-hostnames");
addReplyBulkCString(c,sentinel.announce_hostnames ? "yes" : "no");
dictAdd(d, "announce-hostnames", NULL);
matches++;
}
if (stringmatch(pattern, "announce-ip", 1) && !dictFind(d, "announce-ip")) {
addReplyBulkCString(c,"announce-ip");
addReplyBulkCString(c,sentinel.announce_ip ? sentinel.announce_ip : "");
dictAdd(d, "announce-ip", NULL);
matches++;
}
if (stringmatch(pattern, "announce-port", 1) && !dictFind(d, "announce-port")) {
addReplyBulkCString(c, "announce-port");
addReplyBulkLongLong(c, sentinel.announce_port);
dictAdd(d, "announce-port", NULL);
matches++;
}
if (stringmatch(pattern, "sentinel-user", 1) && !dictFind(d, "sentinel-user")) {
addReplyBulkCString(c, "sentinel-user");
addReplyBulkCString(c, sentinel.sentinel_auth_user ? sentinel.sentinel_auth_user : "");
dictAdd(d, "sentinel-user", NULL);
matches++;
}
if (stringmatch(pattern, "sentinel-pass", 1) && !dictFind(d, "sentinel-pass")) {
addReplyBulkCString(c, "sentinel-pass");
addReplyBulkCString(c, sentinel.sentinel_auth_pass ? sentinel.sentinel_auth_pass : "");
dictAdd(d, "sentinel-pass", NULL);
matches++;
}
if (stringmatch(pattern, "loglevel", 1) && !dictFind(d, "loglevel")) {
addReplyBulkCString(c, "loglevel");
addReplyBulkCString(c, getLogLevel());
dictAdd(d, "loglevel", NULL);
matches++;
}
}
dictRelease(d);
setDeferredMapLen(c, replylen, matches);
}
......@@ -3787,9 +3865,9 @@ void sentinelCommand(client *c) {
" Check if the current Sentinel configuration is able to reach the quorum",
" needed to failover a master and the majority needed to authorize the",
" failover.",
"CONFIG SET <param> <value>",
"CONFIG SET param value [param value ...]",
" Set a global Sentinel configuration parameter.",
"CONFIG GET <param>",
"CONFIG GET <param> [param param param ...]",
" Get global Sentinel configuration parameter.",
"DEBUG [<param> <value> ...]",
" Show a list of configurable time parameters and their values (milliseconds).",
......@@ -4042,12 +4120,12 @@ NULL
sentinelSetCommand(c);
} else if (!strcasecmp(c->argv[1]->ptr,"config")) {
if (c->argc < 4) goto numargserr;
if (!strcasecmp(c->argv[2]->ptr,"set") && c->argc == 5)
if (!strcasecmp(c->argv[2]->ptr,"set") && c->argc >= 5)
sentinelConfigSetCommand(c);
else if (!strcasecmp(c->argv[2]->ptr,"get") && c->argc == 4)
else if (!strcasecmp(c->argv[2]->ptr,"get") && c->argc >= 4)
sentinelConfigGetCommand(c);
else
addReplyError(c, "Only SENTINEL CONFIG GET <option> / SET <option> <value> are supported.");
addReplyError(c, "Only SENTINEL CONFIG GET <param> [<param> <param> ...]/ SET <param> <value> [<param> <value> ...] are supported.");
} else if (!strcasecmp(c->argv[1]->ptr,"info-cache")) {
/* SENTINEL INFO-CACHE <name> */
if (c->argc < 2) goto numargserr;
......
source "../tests/includes/init-tests.tcl"
test "SENTINEL CONFIG SET and SENTINEL CONFIG GET handles multiple variables" {
foreach_sentinel_id id {
assert_equal {OK} [S $id SENTINEL CONFIG SET resolve-hostnames yes announce-port 1234]
}
assert_match {*yes*1234*} [S 1 SENTINEL CONFIG GET resolve-hostnames announce-port]
assert_match {announce-port 1234} [S 1 SENTINEL CONFIG GET announce-port]
}
test "SENTINEL CONFIG GET for duplicate and unknown variables" {
assert_equal {OK} [S 1 SENTINEL CONFIG SET resolve-hostnames yes announce-port 1234]
assert_match {resolve-hostnames yes} [S 1 SENTINEL CONFIG GET resolve-hostnames resolve-hostnames does-not-exist]
}
test "SENTINEL CONFIG GET for patterns" {
assert_equal {OK} [S 1 SENTINEL CONFIG SET loglevel notice announce-port 1234 announce-hostnames yes ]
assert_match {loglevel notice} [S 1 SENTINEL CONFIG GET log* *level loglevel]
assert_match {announce-hostnames yes announce-ip*announce-port 1234} [S 1 SENTINEL CONFIG GET announce*]
}
test "SENTINEL CONFIG SET duplicate variables" {
catch {[S 1 SENTINEL CONFIG SET resolve-hostnames yes announce-port 1234 announce-port 100]} e
if {![string match "*Duplicate argument*" $e]} {
fail "Should give wrong arity error"
}
}
test "SENTINEL CONFIG SET, one option does not exist" {
foreach_sentinel_id id {
assert_equal {OK} [S $id SENTINEL CONFIG SET announce-port 111]
catch {[S $id SENTINEL CONFIG SET does-not-exist yes announce-port 1234]} e
if {![string match "*Invalid argument*" $e]} {
fail "Should give Invalid argument error"
}
}
# The announce-port should not be set to 1234 as it was called with a wrong argument
assert_match {*111*} [S 1 SENTINEL CONFIG GET announce-port]
}
test "SENTINEL CONFIG SET, one option with wrong value" {
foreach_sentinel_id id {
assert_equal {OK} [S $id SENTINEL CONFIG SET resolve-hostnames no]
catch {[S $id SENTINEL CONFIG SET announce-port -1234 resolve-hostnames yes]} e
if {![string match "*Invalid value*" $e]} {
fail "Expected to return Invalid value error"
}
}
# The resolve-hostnames should not be set to yes as it was called after an argument with an invalid value
assert_match {*no*} [S 1 SENTINEL CONFIG GET resolve-hostnames]
}
test "SENTINEL CONFIG SET, wrong number of arguments" {
catch {[S 1 SENTINEL CONFIG SET resolve-hostnames yes announce-port 1234 announce-ip]} e
if {![string match "*Missing argument*" $e]} {
fail "Expected to return Missing argument error"
}
}
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