summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/commands/sentinel-config.json14
-rw-r--r--src/sentinel.c262
-rw-r--r--tests/sentinel/tests/15-config-set-config-get.tcl58
3 files changed, 239 insertions, 95 deletions
diff --git a/src/commands/sentinel-config.json b/src/commands/sentinel-config.json
index ecc317603..6e3075f7c 100644
--- a/src/commands/sentinel-config.json
+++ b/src/commands/sentinel-config.json
@@ -1,12 +1,18 @@
{
"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
}
]
}
diff --git a/src/sentinel.c b/src/sentinel.c
index 326def135..6a1cd1777 100644
--- a/src/sentinel.c
+++ b/src/sentinel.c
@@ -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;
diff --git a/tests/sentinel/tests/15-config-set-config-get.tcl b/tests/sentinel/tests/15-config-set-config-get.tcl
new file mode 100644
index 000000000..f9831f8e8
--- /dev/null
+++ b/tests/sentinel/tests/15-config-set-config-get.tcl
@@ -0,0 +1,58 @@
+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"
+ }
+}