1/* Redis CLI (command line interface)
2 *
3 * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * * Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * * Neither the name of Redis nor the names of its contributors may be used
15 * to endorse or promote products derived from this software without
16 * specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "fmacros.h"
32#include "version.h"
33
34#include <stdio.h>
35#include <string.h>
36#include <stdlib.h>
37#include <signal.h>
38#include <unistd.h>
39#include <time.h>
40#include <ctype.h>
41#include <errno.h>
42#include <sys/stat.h>
43#include <sys/time.h>
44#include <assert.h>
45#include <fcntl.h>
46#include <limits.h>
47#include <math.h>
48
49#include <hiredis.h>
50#ifdef USE_OPENSSL
51#include <openssl/ssl.h>
52#include <openssl/err.h>
53#include <hiredis_ssl.h>
54#endif
55#include <sdscompat.h> /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */
56#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
57#include "dict.h"
58#include "adlist.h"
59#include "zmalloc.h"
60#include "linenoise.h"
61#include "help.h" /* Used for backwards-compatibility with pre-7.0 servers that don't support COMMAND DOCS. */
62#include "anet.h"
63#include "ae.h"
64#include "cli_common.h"
65#include "mt19937-64.h"
66
67#define UNUSED(V) ((void) V)
68
69#define OUTPUT_STANDARD 0
70#define OUTPUT_RAW 1
71#define OUTPUT_CSV 2
72#define OUTPUT_JSON 3
73#define OUTPUT_QUOTED_JSON 4
74#define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */
75#define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
76#define REDIS_CLI_HISTFILE_ENV "REDISCLI_HISTFILE"
77#define REDIS_CLI_HISTFILE_DEFAULT ".rediscli_history"
78#define REDIS_CLI_RCFILE_ENV "REDISCLI_RCFILE"
79#define REDIS_CLI_RCFILE_DEFAULT ".redisclirc"
80#define REDIS_CLI_AUTH_ENV "REDISCLI_AUTH"
81#define REDIS_CLI_CLUSTER_YES_ENV "REDISCLI_CLUSTER_YES"
82
83#define CLUSTER_MANAGER_SLOTS 16384
84#define CLUSTER_MANAGER_PORT_INCR 10000 /* same as CLUSTER_PORT_INCR */
85#define CLUSTER_MANAGER_MIGRATE_TIMEOUT 60000
86#define CLUSTER_MANAGER_MIGRATE_PIPELINE 10
87#define CLUSTER_MANAGER_REBALANCE_THRESHOLD 2
88
89#define CLUSTER_MANAGER_INVALID_HOST_ARG \
90 "[ERR] Invalid arguments: you need to pass either a valid " \
91 "address (ie. 120.0.0.1:7000) or space separated IP " \
92 "and port (ie. 120.0.0.1 7000)\n"
93#define CLUSTER_MANAGER_MODE() (config.cluster_manager_command.name != NULL)
94#define CLUSTER_MANAGER_MASTERS_COUNT(nodes, replicas) (nodes/(replicas + 1))
95#define CLUSTER_MANAGER_COMMAND(n,...) \
96 (redisCommand(n->context, __VA_ARGS__))
97
98#define CLUSTER_MANAGER_NODE_ARRAY_FREE(array) zfree(array->alloc)
99
100#define CLUSTER_MANAGER_PRINT_REPLY_ERROR(n, err) \
101 clusterManagerLogErr("Node %s:%d replied with error:\n%s\n", \
102 n->ip, n->port, err);
103
104#define clusterManagerLogInfo(...) \
105 clusterManagerLog(CLUSTER_MANAGER_LOG_LVL_INFO,__VA_ARGS__)
106
107#define clusterManagerLogErr(...) \
108 clusterManagerLog(CLUSTER_MANAGER_LOG_LVL_ERR,__VA_ARGS__)
109
110#define clusterManagerLogWarn(...) \
111 clusterManagerLog(CLUSTER_MANAGER_LOG_LVL_WARN,__VA_ARGS__)
112
113#define clusterManagerLogOk(...) \
114 clusterManagerLog(CLUSTER_MANAGER_LOG_LVL_SUCCESS,__VA_ARGS__)
115
116#define CLUSTER_MANAGER_FLAG_MYSELF 1 << 0
117#define CLUSTER_MANAGER_FLAG_SLAVE 1 << 1
118#define CLUSTER_MANAGER_FLAG_FRIEND 1 << 2
119#define CLUSTER_MANAGER_FLAG_NOADDR 1 << 3
120#define CLUSTER_MANAGER_FLAG_DISCONNECT 1 << 4
121#define CLUSTER_MANAGER_FLAG_FAIL 1 << 5
122
123#define CLUSTER_MANAGER_CMD_FLAG_FIX 1 << 0
124#define CLUSTER_MANAGER_CMD_FLAG_SLAVE 1 << 1
125#define CLUSTER_MANAGER_CMD_FLAG_YES 1 << 2
126#define CLUSTER_MANAGER_CMD_FLAG_AUTOWEIGHTS 1 << 3
127#define CLUSTER_MANAGER_CMD_FLAG_EMPTYMASTER 1 << 4
128#define CLUSTER_MANAGER_CMD_FLAG_SIMULATE 1 << 5
129#define CLUSTER_MANAGER_CMD_FLAG_REPLACE 1 << 6
130#define CLUSTER_MANAGER_CMD_FLAG_COPY 1 << 7
131#define CLUSTER_MANAGER_CMD_FLAG_COLOR 1 << 8
132#define CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS 1 << 9
133#define CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS 1 << 10
134#define CLUSTER_MANAGER_CMD_FLAG_MASTERS_ONLY 1 << 11
135#define CLUSTER_MANAGER_CMD_FLAG_SLAVES_ONLY 1 << 12
136
137#define CLUSTER_MANAGER_OPT_GETFRIENDS 1 << 0
138#define CLUSTER_MANAGER_OPT_COLD 1 << 1
139#define CLUSTER_MANAGER_OPT_UPDATE 1 << 2
140#define CLUSTER_MANAGER_OPT_QUIET 1 << 6
141#define CLUSTER_MANAGER_OPT_VERBOSE 1 << 7
142
143#define CLUSTER_MANAGER_LOG_LVL_INFO 1
144#define CLUSTER_MANAGER_LOG_LVL_WARN 2
145#define CLUSTER_MANAGER_LOG_LVL_ERR 3
146#define CLUSTER_MANAGER_LOG_LVL_SUCCESS 4
147
148#define CLUSTER_JOIN_CHECK_AFTER 20
149
150#define LOG_COLOR_BOLD "29;1m"
151#define LOG_COLOR_RED "31;1m"
152#define LOG_COLOR_GREEN "32;1m"
153#define LOG_COLOR_YELLOW "33;1m"
154#define LOG_COLOR_RESET "0m"
155
156/* cliConnect() flags. */
157#define CC_FORCE (1<<0) /* Re-connect if already connected. */
158#define CC_QUIET (1<<1) /* Don't log connecting errors. */
159
160/* DNS lookup */
161#define NET_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46 */
162
163/* --latency-dist palettes. */
164int spectrum_palette_color_size = 19;
165int spectrum_palette_color[] = {0,233,234,235,237,239,241,243,245,247,144,143,142,184,226,214,208,202,196};
166
167int spectrum_palette_mono_size = 13;
168int spectrum_palette_mono[] = {0,233,234,235,237,239,241,243,245,247,249,251,253};
169
170/* The actual palette in use. */
171int *spectrum_palette;
172int spectrum_palette_size;
173
174/* Dict Helpers */
175static uint64_t dictSdsHash(const void *key);
176static int dictSdsKeyCompare(dict *d, const void *key1,
177 const void *key2);
178static void dictSdsDestructor(dict *d, void *val);
179static void dictListDestructor(dict *d, void *val);
180
181/* Command documentation info used for help output */
182struct commandDocs {
183 char *name;
184 char *params; /* A string describing the syntax of the command arguments. */
185 char *summary;
186 char *group;
187 char *since;
188};
189
190/* Cluster Manager Command Info */
191typedef struct clusterManagerCommand {
192 char *name;
193 int argc;
194 char **argv;
195 sds stdin_arg; /* arg from stdin. (-X option) */
196 int flags;
197 int replicas;
198 char *from;
199 char *to;
200 char **weight;
201 int weight_argc;
202 char *master_id;
203 int slots;
204 int timeout;
205 int pipeline;
206 float threshold;
207 char *backup_dir;
208 char *from_user;
209 char *from_pass;
210 int from_askpass;
211} clusterManagerCommand;
212
213static int createClusterManagerCommand(char *cmdname, int argc, char **argv);
214
215
216static redisContext *context;
217static struct config {
218 cliConnInfo conn_info;
219 char *hostsocket;
220 int tls;
221 cliSSLconfig sslconfig;
222 long repeat;
223 long interval;
224 int dbnum; /* db num currently selected */
225 int interactive;
226 int shutdown;
227 int monitor_mode;
228 int pubsub_mode;
229 int blocking_state_aborted; /* used to abort monitor_mode and pubsub_mode. */
230 int latency_mode;
231 int latency_dist_mode;
232 int latency_history;
233 int lru_test_mode;
234 long long lru_test_sample_size;
235 int cluster_mode;
236 int cluster_reissue_command;
237 int cluster_send_asking;
238 int slave_mode;
239 int pipe_mode;
240 int pipe_timeout;
241 int getrdb_mode;
242 int get_functions_rdb_mode;
243 int stat_mode;
244 int scan_mode;
245 int intrinsic_latency_mode;
246 int intrinsic_latency_duration;
247 sds pattern;
248 char *rdb_filename;
249 int bigkeys;
250 int memkeys;
251 unsigned memkeys_samples;
252 int hotkeys;
253 int stdin_lastarg; /* get last arg from stdin. (-x option) */
254 int stdin_tag_arg; /* get <tag> arg from stdin. (-X option) */
255 char *stdin_tag_name; /* Placeholder(tag name) for user input. */
256 int askpass;
257 int quoted_input; /* Force input args to be treated as quoted strings */
258 int output; /* output mode, see OUTPUT_* defines */
259 int push_output; /* Should we display spontaneous PUSH replies */
260 sds mb_delim;
261 sds cmd_delim;
262 char prompt[128];
263 char *eval;
264 int eval_ldb;
265 int eval_ldb_sync; /* Ask for synchronous mode of the Lua debugger. */
266 int eval_ldb_end; /* Lua debugging session ended. */
267 int enable_ldb_on_eval; /* Handle manual SCRIPT DEBUG + EVAL commands. */
268 int last_cmd_type;
269 int verbose;
270 int set_errcode;
271 clusterManagerCommand cluster_manager_command;
272 int no_auth_warning;
273 int resp2;
274 int resp3; /* value of 1: specified explicitly, value of 2: implicit like --json option */
275 int in_multi;
276 int pre_multi_dbnum;
277} config;
278
279/* User preferences. */
280static struct pref {
281 int hints;
282} pref;
283
284static volatile sig_atomic_t force_cancel_loop = 0;
285static void usage(int err);
286static void slaveMode(void);
287char *redisGitSHA1(void);
288char *redisGitDirty(void);
289static int cliConnect(int flags);
290
291static char *getInfoField(char *info, char *field);
292static long getLongInfoField(char *info, char *field);
293
294/*------------------------------------------------------------------------------
295 * Utility functions
296 *--------------------------------------------------------------------------- */
297
298static void cliPushHandler(void *, void *);
299
300uint16_t crc16(const char *buf, int len);
301
302static long long ustime(void) {
303 struct timeval tv;
304 long long ust;
305
306 gettimeofday(&tv, NULL);
307 ust = ((long long)tv.tv_sec)*1000000;
308 ust += tv.tv_usec;
309 return ust;
310}
311
312static long long mstime(void) {
313 return ustime()/1000;
314}
315
316static void cliRefreshPrompt(void) {
317 if (config.eval_ldb) return;
318
319 sds prompt = sdsempty();
320 if (config.hostsocket != NULL) {
321 prompt = sdscatfmt(prompt,"redis %s",config.hostsocket);
322 } else {
323 char addr[256];
324 anetFormatAddr(addr, sizeof(addr), config.conn_info.hostip, config.conn_info.hostport);
325 prompt = sdscatlen(prompt,addr,strlen(addr));
326 }
327
328 /* Add [dbnum] if needed */
329 if (config.dbnum != 0)
330 prompt = sdscatfmt(prompt,"[%i]",config.dbnum);
331
332 /* Add TX if in transaction state*/
333 if (config.in_multi)
334 prompt = sdscatlen(prompt,"(TX)",4);
335
336 /* Copy the prompt in the static buffer. */
337 prompt = sdscatlen(prompt,"> ",2);
338 snprintf(config.prompt,sizeof(config.prompt),"%s",prompt);
339 sdsfree(prompt);
340}
341
342/* Return the name of the dotfile for the specified 'dotfilename'.
343 * Normally it just concatenates user $HOME to the file specified
344 * in 'dotfilename'. However if the environment variable 'envoverride'
345 * is set, its value is taken as the path.
346 *
347 * The function returns NULL (if the file is /dev/null or cannot be
348 * obtained for some error), or an SDS string that must be freed by
349 * the user. */
350static sds getDotfilePath(char *envoverride, char *dotfilename) {
351 char *path = NULL;
352 sds dotPath = NULL;
353
354 /* Check the env for a dotfile override. */
355 path = getenv(envoverride);
356 if (path != NULL && *path != '\0') {
357 if (!strcmp("/dev/null", path)) {
358 return NULL;
359 }
360
361 /* If the env is set, return it. */
362 dotPath = sdsnew(path);
363 } else {
364 char *home = getenv("HOME");
365 if (home != NULL && *home != '\0') {
366 /* If no override is set use $HOME/<dotfilename>. */
367 dotPath = sdscatprintf(sdsempty(), "%s/%s", home, dotfilename);
368 }
369 }
370 return dotPath;
371}
372
373static uint64_t dictSdsHash(const void *key) {
374 return dictGenHashFunction((unsigned char*)key, sdslen((char*)key));
375}
376
377static int dictSdsKeyCompare(dict *d, const void *key1, const void *key2)
378{
379 int l1,l2;
380 UNUSED(d);
381
382 l1 = sdslen((sds)key1);
383 l2 = sdslen((sds)key2);
384 if (l1 != l2) return 0;
385 return memcmp(key1, key2, l1) == 0;
386}
387
388static void dictSdsDestructor(dict *d, void *val)
389{
390 UNUSED(d);
391 sdsfree(val);
392}
393
394void dictListDestructor(dict *d, void *val)
395{
396 UNUSED(d);
397 listRelease((list*)val);
398}
399
400/*------------------------------------------------------------------------------
401 * Help functions
402 *--------------------------------------------------------------------------- */
403
404#define CLI_HELP_COMMAND 1
405#define CLI_HELP_GROUP 2
406
407typedef struct {
408 int type;
409 int argc;
410 sds *argv;
411 sds full;
412
413 /* Only used for help on commands */
414 struct commandDocs org;
415} helpEntry;
416
417static helpEntry *helpEntries = NULL;
418static int helpEntriesLen = 0;
419
420static sds cliVersion(void) {
421 sds version;
422 version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION);
423
424 /* Add git commit and working tree status when available */
425 if (strtoll(redisGitSHA1(),NULL,16)) {
426 version = sdscatprintf(version, " (git:%s", redisGitSHA1());
427 if (strtoll(redisGitDirty(),NULL,10))
428 version = sdscatprintf(version, "-dirty");
429 version = sdscat(version, ")");
430 }
431 return version;
432}
433
434/* For backwards compatibility with pre-7.0 servers. Initializes command help. */
435static void cliOldInitHelp(void) {
436 int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp);
437 int groupslen = sizeof(commandGroups)/sizeof(char*);
438 int i, len, pos = 0;
439 helpEntry tmp;
440
441 helpEntriesLen = len = commandslen+groupslen;
442 helpEntries = zmalloc(sizeof(helpEntry)*len);
443
444 for (i = 0; i < groupslen; i++) {
445 tmp.argc = 1;
446 tmp.argv = zmalloc(sizeof(sds));
447 tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]);
448 tmp.full = tmp.argv[0];
449 tmp.type = CLI_HELP_GROUP;
450 tmp.org.name = NULL;
451 tmp.org.params = NULL;
452 tmp.org.summary = NULL;
453 tmp.org.since = NULL;
454 tmp.org.group = NULL;
455 helpEntries[pos++] = tmp;
456 }
457
458 for (i = 0; i < commandslen; i++) {
459 tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc);
460 tmp.full = sdsnew(commandHelp[i].name);
461 tmp.type = CLI_HELP_COMMAND;
462 tmp.org.name = commandHelp[i].name;
463 tmp.org.params = commandHelp[i].params;
464 tmp.org.summary = commandHelp[i].summary;
465 tmp.org.since = commandHelp[i].since;
466 tmp.org.group = commandGroups[commandHelp[i].group];
467 helpEntries[pos++] = tmp;
468 }
469}
470
471/* For backwards compatibility with pre-7.0 servers.
472 * cliOldInitHelp() setups the helpEntries array with the command and group
473 * names from the help.h file. However the Redis instance we are connecting
474 * to may support more commands, so this function integrates the previous
475 * entries with additional entries obtained using the COMMAND command
476 * available in recent versions of Redis. */
477static void cliOldIntegrateHelp(void) {
478 if (cliConnect(CC_QUIET) == REDIS_ERR) return;
479
480 redisReply *reply = redisCommand(context, "COMMAND");
481 if(reply == NULL || reply->type != REDIS_REPLY_ARRAY) return;
482
483 /* Scan the array reported by COMMAND and fill only the entries that
484 * don't already match what we have. */
485 for (size_t j = 0; j < reply->elements; j++) {
486 redisReply *entry = reply->element[j];
487 if (entry->type != REDIS_REPLY_ARRAY || entry->elements < 4 ||
488 entry->element[0]->type != REDIS_REPLY_STRING ||
489 entry->element[1]->type != REDIS_REPLY_INTEGER ||
490 entry->element[3]->type != REDIS_REPLY_INTEGER) return;
491 char *cmdname = entry->element[0]->str;
492 int i;
493
494 for (i = 0; i < helpEntriesLen; i++) {
495 helpEntry *he = helpEntries+i;
496 if (!strcasecmp(he->argv[0],cmdname))
497 break;
498 }
499 if (i != helpEntriesLen) continue;
500
501 helpEntriesLen++;
502 helpEntries = zrealloc(helpEntries,sizeof(helpEntry)*helpEntriesLen);
503 helpEntry *new = helpEntries+(helpEntriesLen-1);
504
505 new->argc = 1;
506 new->argv = zmalloc(sizeof(sds));
507 new->argv[0] = sdsnew(cmdname);
508 new->full = new->argv[0];
509 new->type = CLI_HELP_COMMAND;
510 sdstoupper(new->argv[0]);
511
512 new->org.name = new->argv[0];
513 new->org.params = sdsempty();
514 int args = llabs(entry->element[1]->integer);
515 args--; /* Remove the command name itself. */
516 if (entry->element[3]->integer == 1) {
517 new->org.params = sdscat(new->org.params,"key ");
518 args--;
519 }
520 while(args-- > 0) new->org.params = sdscat(new->org.params,"arg ");
521 if (entry->element[1]->integer < 0)
522 new->org.params = sdscat(new->org.params,"...options...");
523 new->org.summary = "Help not available";
524 new->org.since = "Not known";
525 new->org.group = commandGroups[0];
526 }
527 freeReplyObject(reply);
528}
529
530/* Concatenate a string to an sds string, but if it's empty substitute double quote marks. */
531static sds sdscat_orempty(sds params, char *value) {
532 if (value[0] == '\0') {
533 return sdscat(params, "\"\"");
534 }
535 return sdscat(params, value);
536}
537
538static sds cliAddArgument(sds params, redisReply *argMap);
539
540/* Concatenate a list of arguments to the parameter string, separated by a separator string. */
541static sds cliConcatArguments(sds params, redisReply *arguments, char *separator) {
542 for (size_t j = 0; j < arguments->elements; j++) {
543 params = cliAddArgument(params, arguments->element[j]);
544 if (j != arguments->elements - 1) {
545 params = sdscat(params, separator);
546 }
547 }
548 return params;
549}
550
551/* Add an argument to the parameter string. */
552static sds cliAddArgument(sds params, redisReply *argMap) {
553 char *name = NULL;
554 char *type = NULL;
555 int optional = 0;
556 int multiple = 0;
557 int multipleToken = 0;
558 redisReply *arguments = NULL;
559 sds tokenPart = sdsempty();
560 sds repeatPart = sdsempty();
561
562 /* First read the fields describing the argument. */
563 if (argMap->type != REDIS_REPLY_MAP && argMap->type != REDIS_REPLY_ARRAY) {
564 return params;
565 }
566 for (size_t i = 0; i < argMap->elements; i += 2) {
567 assert(argMap->element[i]->type == REDIS_REPLY_STRING);
568 char *key = argMap->element[i]->str;
569 if (!strcmp(key, "name")) {
570 assert(argMap->element[i + 1]->type == REDIS_REPLY_STRING);
571 name = argMap->element[i + 1]->str;
572 } else if (!strcmp(key, "token")) {
573 assert(argMap->element[i + 1]->type == REDIS_REPLY_STRING);
574 char *token = argMap->element[i + 1]->str;
575 tokenPart = sdscat_orempty(tokenPart, token);
576 } else if (!strcmp(key, "type")) {
577 assert(argMap->element[i + 1]->type == REDIS_REPLY_STRING);
578 type = argMap->element[i + 1]->str;
579 } else if (!strcmp(key, "arguments")) {
580 arguments = argMap->element[i + 1];
581 } else if (!strcmp(key, "flags")) {
582 redisReply *flags = argMap->element[i + 1];
583 assert(flags->type == REDIS_REPLY_SET || flags->type == REDIS_REPLY_ARRAY);
584 for (size_t j = 0; j < flags->elements; j++) {
585 assert(flags->element[j]->type == REDIS_REPLY_STATUS);
586 char *flag = flags->element[j]->str;
587 if (!strcmp(flag, "optional")) {
588 optional = 1;
589 } else if (!strcmp(flag, "multiple")) {
590 multiple = 1;
591 } else if (!strcmp(flag, "multiple_token")) {
592 multipleToken = 1;
593 }
594 }
595 }
596 }
597
598 /* Then build the "repeating part" of the argument string. */
599 if (!strcmp(type, "key") ||
600 !strcmp(type, "string") ||
601 !strcmp(type, "integer") ||
602 !strcmp(type, "double") ||
603 !strcmp(type, "pattern") ||
604 !strcmp(type, "unix-time") ||
605 !strcmp(type, "token"))
606 {
607 repeatPart = sdscat_orempty(repeatPart, name);
608 } else if (!strcmp(type, "oneof")) {
609 repeatPart = cliConcatArguments(repeatPart, arguments, "|");
610 } else if (!strcmp(type, "block")) {
611 repeatPart = cliConcatArguments(repeatPart, arguments, " ");
612 } else if (strcmp(type, "pure-token") != 0) {
613 fprintf(stderr, "Unknown type '%s' set for argument '%s'\n", type, name);
614 }
615
616 /* Finally, build the parameter string. */
617 if (tokenPart[0] != '\0' && strcmp(type, "pure-token") != 0) {
618 tokenPart = sdscat(tokenPart, " ");
619 }
620 if (optional) {
621 params = sdscat(params, "[");
622 }
623 params = sdscat(params, tokenPart);
624 params = sdscat(params, repeatPart);
625 if (multiple) {
626 params = sdscat(params, " [");
627 if (multipleToken) {
628 params = sdscat(params, tokenPart);
629 }
630 params = sdscat(params, repeatPart);
631 params = sdscat(params, " ...]");
632 }
633 if (optional) {
634 params = sdscat(params, "]");
635 }
636 sdsfree(tokenPart);
637 sdsfree(repeatPart);
638 return params;
639}
640
641/* Fill in the fields of a help entry for the command/subcommand name. */
642static void cliFillInCommandHelpEntry(helpEntry *help, char *cmdname, char *subcommandname) {
643 help->argc = subcommandname ? 2 : 1;
644 help->argv = zmalloc(sizeof(sds) * help->argc);
645 help->argv[0] = sdsnew(cmdname);
646 sdstoupper(help->argv[0]);
647 if (subcommandname) {
648 /* Subcommand name is two words separated by a pipe character. */
649 help->argv[1] = sdsnew(strchr(subcommandname, '|') + 1);
650 sdstoupper(help->argv[1]);
651 }
652 sds fullname = sdsnew(help->argv[0]);
653 if (subcommandname) {
654 fullname = sdscat(fullname, " ");
655 fullname = sdscat(fullname, help->argv[1]);
656 }
657 help->full = fullname;
658 help->type = CLI_HELP_COMMAND;
659
660 help->org.name = help->full;
661 help->org.params = sdsempty();
662 help->org.since = NULL;
663}
664
665/* Initialize a command help entry for the command/subcommand described in 'specs'.
666 * 'next' points to the next help entry to be filled in.
667 * 'groups' is a set of command group names to be filled in.
668 * Returns a pointer to the next available position in the help entries table.
669 * If the command has subcommands, this is called recursively for the subcommands.
670 */
671static helpEntry *cliInitCommandHelpEntry(char *cmdname, char *subcommandname,
672 helpEntry *next, redisReply *specs,
673 dict *groups) {
674 helpEntry *help = next++;
675 cliFillInCommandHelpEntry(help, cmdname, subcommandname);
676
677 assert(specs->type == REDIS_REPLY_MAP || specs->type == REDIS_REPLY_ARRAY);
678 for (size_t j = 0; j < specs->elements; j += 2) {
679 assert(specs->element[j]->type == REDIS_REPLY_STRING);
680 char *key = specs->element[j]->str;
681 if (!strcmp(key, "summary")) {
682 redisReply *reply = specs->element[j + 1];
683 assert(reply->type == REDIS_REPLY_STRING);
684 help->org.summary = sdsnew(reply->str);
685 } else if (!strcmp(key, "since")) {
686 redisReply *reply = specs->element[j + 1];
687 assert(reply->type == REDIS_REPLY_STRING);
688 help->org.since = sdsnew(reply->str);
689 } else if (!strcmp(key, "group")) {
690 redisReply *reply = specs->element[j + 1];
691 assert(reply->type == REDIS_REPLY_STRING);
692 help->org.group = sdsnew(reply->str);
693 sds group = sdsdup(help->org.group);
694 if (dictAdd(groups, group, NULL) != DICT_OK) {
695 sdsfree(group);
696 }
697 } else if (!strcmp(key, "arguments")) {
698 redisReply *args = specs->element[j + 1];
699 assert(args->type == REDIS_REPLY_ARRAY);
700 help->org.params = cliConcatArguments(help->org.params, args, " ");
701 } else if (!strcmp(key, "subcommands")) {
702 redisReply *subcommands = specs->element[j + 1];
703 assert(subcommands->type == REDIS_REPLY_MAP || subcommands->type == REDIS_REPLY_ARRAY);
704 for (size_t i = 0; i < subcommands->elements; i += 2) {
705 assert(subcommands->element[i]->type == REDIS_REPLY_STRING);
706 char *subcommandname = subcommands->element[i]->str;
707 redisReply *subcommand = subcommands->element[i + 1];
708 assert(subcommand->type == REDIS_REPLY_MAP || subcommand->type == REDIS_REPLY_ARRAY);
709 next = cliInitCommandHelpEntry(cmdname, subcommandname, next, subcommand, groups);
710 }
711 }
712 }
713 return next;
714}
715
716/* Returns the total number of commands and subcommands in the command docs table. */
717static size_t cliCountCommands(redisReply* commandTable) {
718 size_t numCommands = commandTable->elements / 2;
719
720 /* The command docs table maps command names to a map of their specs. */
721 for (size_t i = 0; i < commandTable->elements; i += 2) {
722 assert(commandTable->element[i]->type == REDIS_REPLY_STRING); /* Command name. */
723 assert(commandTable->element[i + 1]->type == REDIS_REPLY_MAP ||
724 commandTable->element[i + 1]->type == REDIS_REPLY_ARRAY);
725 redisReply *map = commandTable->element[i + 1];
726 for (size_t j = 0; j < map->elements; j += 2) {
727 assert(map->element[j]->type == REDIS_REPLY_STRING);
728 char *key = map->element[j]->str;
729 if (!strcmp(key, "subcommands")) {
730 redisReply *subcommands = map->element[j + 1];
731 assert(subcommands->type == REDIS_REPLY_MAP || subcommands->type == REDIS_REPLY_ARRAY);
732 numCommands += subcommands->elements / 2;
733 }
734 }
735 }
736 return numCommands;
737}
738
739/* Comparator for sorting help table entries. */
740int helpEntryCompare(const void *entry1, const void *entry2) {
741 helpEntry *i1 = (helpEntry *)entry1;
742 helpEntry *i2 = (helpEntry *)entry2;
743 return strcmp(i1->full, i2->full);
744}
745
746/* Initializes command help entries for command groups.
747 * Called after the command help entries have already been filled in.
748 * Extends the help table with new entries for the command groups.
749 */
750void cliInitGroupHelpEntries(dict *groups) {
751 dictIterator *iter = dictGetIterator(groups);
752 dictEntry *entry;
753 helpEntry tmp;
754
755 int numGroups = dictSize(groups);
756 int pos = helpEntriesLen;
757 helpEntriesLen += numGroups;
758 helpEntries = zrealloc(helpEntries, sizeof(helpEntry)*helpEntriesLen);
759
760 for (entry = dictNext(iter); entry != NULL; entry = dictNext(iter)) {
761 tmp.argc = 1;
762 tmp.argv = zmalloc(sizeof(sds));
763 tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",(char *)entry->key);
764 tmp.full = tmp.argv[0];
765 tmp.type = CLI_HELP_GROUP;
766 tmp.org.name = NULL;
767 tmp.org.params = NULL;
768 tmp.org.summary = NULL;
769 tmp.org.since = NULL;
770 tmp.org.group = NULL;
771 helpEntries[pos++] = tmp;
772 }
773 dictReleaseIterator(iter);
774}
775
776/* Initializes help entries for all commands in the COMMAND DOCS reply. */
777void cliInitCommandHelpEntries(redisReply *commandTable, dict *groups) {
778 helpEntry *next = helpEntries;
779 for (size_t i = 0; i < commandTable->elements; i += 2) {
780 assert(commandTable->element[i]->type == REDIS_REPLY_STRING);
781 char *cmdname = commandTable->element[i]->str;
782
783 assert(commandTable->element[i + 1]->type == REDIS_REPLY_MAP ||
784 commandTable->element[i + 1]->type == REDIS_REPLY_ARRAY);
785 redisReply *cmdspecs = commandTable->element[i + 1];
786 next = cliInitCommandHelpEntry(cmdname, NULL, next, cmdspecs, groups);
787 }
788}
789
790/* cliInitHelp() sets up the helpEntries array with the command and group
791 * names and command descriptions obtained using the COMMAND DOCS command.
792 */
793static void cliInitHelp(void) {
794 /* Dict type for a set of strings, used to collect names of command groups. */
795 dictType groupsdt = {
796 dictSdsHash, /* hash function */
797 NULL, /* key dup */
798 NULL, /* val dup */
799 dictSdsKeyCompare, /* key compare */
800 dictSdsDestructor, /* key destructor */
801 NULL, /* val destructor */
802 NULL /* allow to expand */
803 };
804 redisReply *commandTable;
805 dict *groups;
806
807 if (cliConnect(CC_QUIET) == REDIS_ERR) {
808 /* Can not connect to the server, but we still want to provide
809 * help, generate it only from the old help.h data instead. */
810 cliOldInitHelp();
811 return;
812 }
813 commandTable = redisCommand(context, "COMMAND DOCS");
814 if (commandTable == NULL || commandTable->type == REDIS_REPLY_ERROR) {
815 /* New COMMAND DOCS subcommand not supported - generate help from old help.h data instead. */
816 freeReplyObject(commandTable);
817 cliOldInitHelp();
818 cliOldIntegrateHelp();
819 return;
820 };
821 if (commandTable->type != REDIS_REPLY_MAP && commandTable->type != REDIS_REPLY_ARRAY) return;
822
823 /* Scan the array reported by COMMAND DOCS and fill in the entries */
824 helpEntriesLen = cliCountCommands(commandTable);
825 helpEntries = zmalloc(sizeof(helpEntry)*helpEntriesLen);
826
827 groups = dictCreate(&groupsdt);
828 cliInitCommandHelpEntries(commandTable, groups);
829 cliInitGroupHelpEntries(groups);
830
831 qsort(helpEntries, helpEntriesLen, sizeof(helpEntry), helpEntryCompare);
832 freeReplyObject(commandTable);
833 dictRelease(groups);
834}
835
836/* Output command help to stdout. */
837static void cliOutputCommandHelp(struct commandDocs *help, int group) {
838 printf("\r\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params);
839 printf(" \x1b[33msummary:\x1b[0m %s\r\n", help->summary);
840 if (help->since != NULL) {
841 printf(" \x1b[33msince:\x1b[0m %s\r\n", help->since);
842 }
843 if (group) {
844 printf(" \x1b[33mgroup:\x1b[0m %s\r\n", help->group);
845 }
846}
847
848/* Print generic help. */
849static void cliOutputGenericHelp(void) {
850 sds version = cliVersion();
851 printf(
852 "redis-cli %s\n"
853 "To get help about Redis commands type:\n"
854 " \"help @<group>\" to get a list of commands in <group>\n"
855 " \"help <command>\" for help on <command>\n"
856 " \"help <tab>\" to get a list of possible help topics\n"
857 " \"quit\" to exit\n"
858 "\n"
859 "To set redis-cli preferences:\n"
860 " \":set hints\" enable online hints\n"
861 " \":set nohints\" disable online hints\n"
862 "Set your preferences in ~/.redisclirc\n",
863 version
864 );
865 sdsfree(version);
866}
867
868/* Output all command help, filtering by group or command name. */
869static void cliOutputHelp(int argc, char **argv) {
870 int i, j;
871 char *group = NULL;
872 helpEntry *entry;
873 struct commandDocs *help;
874
875 if (argc == 0) {
876 cliOutputGenericHelp();
877 return;
878 } else if (argc > 0 && argv[0][0] == '@') {
879 group = argv[0]+1;
880 }
881
882 if (helpEntries == NULL) {
883 /* Initialize the help using the results of the COMMAND command.
884 * In case we are using redis-cli help XXX, we need to init it. */
885 cliInitHelp();
886 }
887
888 assert(argc > 0);
889 for (i = 0; i < helpEntriesLen; i++) {
890 entry = &helpEntries[i];
891 if (entry->type != CLI_HELP_COMMAND) continue;
892
893 help = &entry->org;
894 if (group == NULL) {
895 /* Compare all arguments */
896 if (argc <= entry->argc) {
897 for (j = 0; j < argc; j++) {
898 if (strcasecmp(argv[j],entry->argv[j]) != 0) break;
899 }
900 if (j == argc) {
901 cliOutputCommandHelp(help,1);
902 }
903 }
904 } else if (strcasecmp(group, help->group) == 0) {
905 cliOutputCommandHelp(help,0);
906 }
907 }
908 printf("\r\n");
909}
910
911/* Linenoise completion callback. */
912static void completionCallback(const char *buf, linenoiseCompletions *lc) {
913 size_t startpos = 0;
914 int mask;
915 int i;
916 size_t matchlen;
917 sds tmp;
918
919 if (strncasecmp(buf,"help ",5) == 0) {
920 startpos = 5;
921 while (isspace(buf[startpos])) startpos++;
922 mask = CLI_HELP_COMMAND | CLI_HELP_GROUP;
923 } else {
924 mask = CLI_HELP_COMMAND;
925 }
926
927 for (i = 0; i < helpEntriesLen; i++) {
928 if (!(helpEntries[i].type & mask)) continue;
929
930 matchlen = strlen(buf+startpos);
931 if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) {
932 tmp = sdsnewlen(buf,startpos);
933 tmp = sdscat(tmp,helpEntries[i].full);
934 linenoiseAddCompletion(lc,tmp);
935 sdsfree(tmp);
936 }
937 }
938}
939
940/* Linenoise hints callback. */
941static char *hintsCallback(const char *buf, int *color, int *bold) {
942 if (!pref.hints) return NULL;
943
944 int i, rawargc, argc, buflen = strlen(buf), matchlen = 0;
945 sds *rawargv, *argv = sdssplitargs(buf,&argc);
946 int endspace = buflen && isspace(buf[buflen-1]);
947 helpEntry *entry = NULL;
948
949 /* Check if the argument list is empty and return ASAP. */
950 if (argc == 0) {
951 sdsfreesplitres(argv,argc);
952 return NULL;
953 }
954
955 /* Search longest matching prefix command */
956 for (i = 0; i < helpEntriesLen; i++) {
957 if (!(helpEntries[i].type & CLI_HELP_COMMAND)) continue;
958
959 rawargv = sdssplitargs(helpEntries[i].full,&rawargc);
960 if (rawargc <= argc) {
961 int j;
962 for (j = 0; j < rawargc; j++) {
963 if (strcasecmp(rawargv[j],argv[j])) {
964 break;
965 }
966 }
967 if (j == rawargc && rawargc > matchlen) {
968 matchlen = rawargc;
969 entry = &helpEntries[i];
970 }
971 }
972 sdsfreesplitres(rawargv,rawargc);
973 }
974 sdsfreesplitres(argv,argc);
975
976 if (entry) {
977 *color = 90;
978 *bold = 0;
979 sds hint = sdsnew(entry->org.params);
980
981 /* Remove arguments from the returned hint to show only the
982 * ones the user did not yet type. */
983 int toremove = argc-matchlen;
984 while(toremove > 0 && sdslen(hint)) {
985 if (hint[0] == '[') break;
986 if (hint[0] == ' ') toremove--;
987 sdsrange(hint,1,-1);
988 }
989
990 /* Add an initial space if needed. */
991 if (!endspace) {
992 sds newhint = sdsnewlen(" ",1);
993 newhint = sdscatsds(newhint,hint);
994 sdsfree(hint);
995 hint = newhint;
996 }
997
998 return hint;
999 }
1000 return NULL;
1001}
1002
1003static void freeHintsCallback(void *ptr) {
1004 sdsfree(ptr);
1005}
1006
1007/*------------------------------------------------------------------------------
1008 * Networking / parsing
1009 *--------------------------------------------------------------------------- */
1010
1011/* Send AUTH command to the server */
1012static int cliAuth(redisContext *ctx, char *user, char *auth) {
1013 redisReply *reply;
1014 if (auth == NULL) return REDIS_OK;
1015
1016 if (user == NULL)
1017 reply = redisCommand(ctx,"AUTH %s",auth);
1018 else
1019 reply = redisCommand(ctx,"AUTH %s %s",user,auth);
1020
1021 if (reply == NULL) {
1022 fprintf(stderr, "\nI/O error\n");
1023 return REDIS_ERR;
1024 }
1025
1026 int result = REDIS_OK;
1027 if (reply->type == REDIS_REPLY_ERROR) {
1028 result = REDIS_ERR;
1029 fprintf(stderr, "AUTH failed: %s\n", reply->str);
1030 }
1031 freeReplyObject(reply);
1032 return result;
1033}
1034
1035/* Send SELECT input_dbnum to the server */
1036static int cliSelect(void) {
1037 redisReply *reply;
1038 if (config.conn_info.input_dbnum == config.dbnum) return REDIS_OK;
1039
1040 reply = redisCommand(context,"SELECT %d",config.conn_info.input_dbnum);
1041 if (reply == NULL) {
1042 fprintf(stderr, "\nI/O error\n");
1043 return REDIS_ERR;
1044 }
1045
1046 int result = REDIS_OK;
1047 if (reply->type == REDIS_REPLY_ERROR) {
1048 result = REDIS_ERR;
1049 fprintf(stderr,"SELECT %d failed: %s\n",config.conn_info.input_dbnum,reply->str);
1050 } else {
1051 config.dbnum = config.conn_info.input_dbnum;
1052 cliRefreshPrompt();
1053 }
1054 freeReplyObject(reply);
1055 return result;
1056}
1057
1058/* Select RESP3 mode if redis-cli was started with the -3 option. */
1059static int cliSwitchProto(void) {
1060 redisReply *reply;
1061 if (!config.resp3 || config.resp2) return REDIS_OK;
1062
1063 reply = redisCommand(context,"HELLO 3");
1064 if (reply == NULL) {
1065 fprintf(stderr, "\nI/O error\n");
1066 return REDIS_ERR;
1067 }
1068
1069 int result = REDIS_OK;
1070 if (reply->type == REDIS_REPLY_ERROR) {
1071 fprintf(stderr,"HELLO 3 failed: %s\n",reply->str);
1072 if (config.resp3 == 1) {
1073 result = REDIS_ERR;
1074 } else if (config.resp3 == 2) {
1075 result = REDIS_OK;
1076 }
1077 }
1078 freeReplyObject(reply);
1079 return result;
1080}
1081
1082/* Connect to the server. It is possible to pass certain flags to the function:
1083 * CC_FORCE: The connection is performed even if there is already
1084 * a connected socket.
1085 * CC_QUIET: Don't print errors if connection fails. */
1086static int cliConnect(int flags) {
1087 if (context == NULL || flags & CC_FORCE) {
1088 if (context != NULL) {
1089 redisFree(context);
1090 config.dbnum = 0;
1091 config.in_multi = 0;
1092 cliRefreshPrompt();
1093 }
1094
1095 /* Do not use hostsocket when we got redirected in cluster mode */
1096 if (config.hostsocket == NULL ||
1097 (config.cluster_mode && config.cluster_reissue_command)) {
1098 context = redisConnect(config.conn_info.hostip,config.conn_info.hostport);
1099 } else {
1100 context = redisConnectUnix(config.hostsocket);
1101 }
1102
1103 if (!context->err && config.tls) {
1104 const char *err = NULL;
1105 if (cliSecureConnection(context, config.sslconfig, &err) == REDIS_ERR && err) {
1106 fprintf(stderr, "Could not negotiate a TLS connection: %s\n", err);
1107 redisFree(context);
1108 context = NULL;
1109 return REDIS_ERR;
1110 }
1111 }
1112
1113 if (context->err) {
1114 if (!(flags & CC_QUIET)) {
1115 fprintf(stderr,"Could not connect to Redis at ");
1116 if (config.hostsocket == NULL ||
1117 (config.cluster_mode && config.cluster_reissue_command))
1118 {
1119 fprintf(stderr, "%s:%d: %s\n",
1120 config.conn_info.hostip,config.conn_info.hostport,context->errstr);
1121 } else {
1122 fprintf(stderr,"%s: %s\n",
1123 config.hostsocket,context->errstr);
1124 }
1125 }
1126 redisFree(context);
1127 context = NULL;
1128 return REDIS_ERR;
1129 }
1130
1131
1132 /* Set aggressive KEEP_ALIVE socket option in the Redis context socket
1133 * in order to prevent timeouts caused by the execution of long
1134 * commands. At the same time this improves the detection of real
1135 * errors. */
1136 anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
1137
1138 /* Do AUTH, select the right DB, switch to RESP3 if needed. */
1139 if (cliAuth(context, config.conn_info.user, config.conn_info.auth) != REDIS_OK)
1140 return REDIS_ERR;
1141 if (cliSelect() != REDIS_OK)
1142 return REDIS_ERR;
1143 if (cliSwitchProto() != REDIS_OK)
1144 return REDIS_ERR;
1145 }
1146
1147 /* Set a PUSH handler if configured to do so. */
1148 if (config.push_output) {
1149 redisSetPushCallback(context, cliPushHandler);
1150 }
1151
1152 return REDIS_OK;
1153}
1154
1155/* In cluster, if server replies ASK, we will redirect to a different node.
1156 * Before sending the real command, we need to send ASKING command first. */
1157static int cliSendAsking() {
1158 redisReply *reply;
1159
1160 config.cluster_send_asking = 0;
1161 if (context == NULL) {
1162 return REDIS_ERR;
1163 }
1164 reply = redisCommand(context,"ASKING");
1165 if (reply == NULL) {
1166 fprintf(stderr, "\nI/O error\n");
1167 return REDIS_ERR;
1168 }
1169 int result = REDIS_OK;
1170 if (reply->type == REDIS_REPLY_ERROR) {
1171 result = REDIS_ERR;
1172 fprintf(stderr,"ASKING failed: %s\n",reply->str);
1173 }
1174 freeReplyObject(reply);
1175 return result;
1176}
1177
1178static void cliPrintContextError(void) {
1179 if (context == NULL) return;
1180 fprintf(stderr,"Error: %s\n",context->errstr);
1181}
1182
1183static int isInvalidateReply(redisReply *reply) {
1184 return reply->type == REDIS_REPLY_PUSH && reply->elements == 2 &&
1185 reply->element[0]->type == REDIS_REPLY_STRING &&
1186 !strncmp(reply->element[0]->str, "invalidate", 10) &&
1187 reply->element[1]->type == REDIS_REPLY_ARRAY;
1188}
1189
1190/* Special display handler for RESP3 'invalidate' messages.
1191 * This function does not validate the reply, so it should
1192 * already be confirmed correct */
1193static sds cliFormatInvalidateTTY(redisReply *r) {
1194 sds out = sdsnew("-> invalidate: ");
1195
1196 for (size_t i = 0; i < r->element[1]->elements; i++) {
1197 redisReply *key = r->element[1]->element[i];
1198 assert(key->type == REDIS_REPLY_STRING);
1199
1200 out = sdscatfmt(out, "'%s'", key->str, key->len);
1201 if (i < r->element[1]->elements - 1)
1202 out = sdscatlen(out, ", ", 2);
1203 }
1204
1205 return sdscatlen(out, "\n", 1);
1206}
1207
1208/* Returns non-zero if cliFormatReplyTTY renders the reply in multiple lines. */
1209static int cliIsMultilineValueTTY(redisReply *r) {
1210 switch (r->type) {
1211 case REDIS_REPLY_ARRAY:
1212 case REDIS_REPLY_SET:
1213 case REDIS_REPLY_PUSH:
1214 if (r->elements == 0) return 0;
1215 if (r->elements > 1) return 1;
1216 return cliIsMultilineValueTTY(r->element[0]);
1217 case REDIS_REPLY_MAP:
1218 if (r->elements == 0) return 0;
1219 if (r->elements > 2) return 1;
1220 return cliIsMultilineValueTTY(r->element[1]);
1221 default:
1222 return 0;
1223 }
1224}
1225
1226static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
1227 sds out = sdsempty();
1228 switch (r->type) {
1229 case REDIS_REPLY_ERROR:
1230 out = sdscatprintf(out,"(error) %s\n", r->str);
1231 break;
1232 case REDIS_REPLY_STATUS:
1233 out = sdscat(out,r->str);
1234 out = sdscat(out,"\n");
1235 break;
1236 case REDIS_REPLY_INTEGER:
1237 out = sdscatprintf(out,"(integer) %lld\n",r->integer);
1238 break;
1239 case REDIS_REPLY_DOUBLE:
1240 out = sdscatprintf(out,"(double) %s\n",r->str);
1241 break;
1242 case REDIS_REPLY_STRING:
1243 case REDIS_REPLY_VERB:
1244 /* If you are producing output for the standard output we want
1245 * a more interesting output with quoted characters and so forth,
1246 * unless it's a verbatim string type. */
1247 if (r->type == REDIS_REPLY_STRING) {
1248 out = sdscatrepr(out,r->str,r->len);
1249 out = sdscat(out,"\n");
1250 } else {
1251 out = sdscatlen(out,r->str,r->len);
1252 out = sdscat(out,"\n");
1253 }
1254 break;
1255 case REDIS_REPLY_NIL:
1256 out = sdscat(out,"(nil)\n");
1257 break;
1258 case REDIS_REPLY_BOOL:
1259 out = sdscat(out,r->integer ? "(true)\n" : "(false)\n");
1260 break;
1261 case REDIS_REPLY_ARRAY:
1262 case REDIS_REPLY_MAP:
1263 case REDIS_REPLY_SET:
1264 case REDIS_REPLY_PUSH:
1265 if (r->elements == 0) {
1266 if (r->type == REDIS_REPLY_ARRAY)
1267 out = sdscat(out,"(empty array)\n");
1268 else if (r->type == REDIS_REPLY_MAP)
1269 out = sdscat(out,"(empty hash)\n");
1270 else if (r->type == REDIS_REPLY_SET)
1271 out = sdscat(out,"(empty set)\n");
1272 else if (r->type == REDIS_REPLY_PUSH)
1273 out = sdscat(out,"(empty push)\n");
1274 else
1275 out = sdscat(out,"(empty aggregate type)\n");
1276 } else {
1277 unsigned int i, idxlen = 0;
1278 char _prefixlen[16];
1279 char _prefixfmt[16];
1280 sds _prefix;
1281 sds tmp;
1282
1283 /* Calculate chars needed to represent the largest index */
1284 i = r->elements;
1285 if (r->type == REDIS_REPLY_MAP) i /= 2;
1286 do {
1287 idxlen++;
1288 i /= 10;
1289 } while(i);
1290
1291 /* Prefix for nested multi bulks should grow with idxlen+2 spaces */
1292 memset(_prefixlen,' ',idxlen+2);
1293 _prefixlen[idxlen+2] = '\0';
1294 _prefix = sdscat(sdsnew(prefix),_prefixlen);
1295
1296 /* Setup prefix format for every entry */
1297 char numsep;
1298 if (r->type == REDIS_REPLY_SET) numsep = '~';
1299 else if (r->type == REDIS_REPLY_MAP) numsep = '#';
1300 else numsep = ')';
1301 snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%ud%c ",idxlen,numsep);
1302
1303 for (i = 0; i < r->elements; i++) {
1304 unsigned int human_idx = (r->type == REDIS_REPLY_MAP) ?
1305 i/2 : i;
1306 human_idx++; /* Make it 1-based. */
1307
1308 /* Don't use the prefix for the first element, as the parent
1309 * caller already prepended the index number. */
1310 out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,human_idx);
1311
1312 /* Format the multi bulk entry */
1313 tmp = cliFormatReplyTTY(r->element[i],_prefix);
1314 out = sdscatlen(out,tmp,sdslen(tmp));
1315 sdsfree(tmp);
1316
1317 /* For maps, format the value as well. */
1318 if (r->type == REDIS_REPLY_MAP) {
1319 i++;
1320 sdsrange(out,0,-2);
1321 out = sdscat(out," => ");
1322 if (cliIsMultilineValueTTY(r->element[i])) {
1323 /* linebreak before multiline value to fix alignment */
1324 out = sdscat(out, "\n");
1325 out = sdscat(out, _prefix);
1326 }
1327 tmp = cliFormatReplyTTY(r->element[i],_prefix);
1328 out = sdscatlen(out,tmp,sdslen(tmp));
1329 sdsfree(tmp);
1330 }
1331 }
1332 sdsfree(_prefix);
1333 }
1334 break;
1335 default:
1336 fprintf(stderr,"Unknown reply type: %d\n", r->type);
1337 exit(1);
1338 }
1339 return out;
1340}
1341
1342int isColorTerm(void) {
1343 char *t = getenv("TERM");
1344 return t != NULL && strstr(t,"xterm") != NULL;
1345}
1346
1347/* Helper function for sdsCatColorizedLdbReply() appending colorize strings
1348 * to an SDS string. */
1349sds sdscatcolor(sds o, char *s, size_t len, char *color) {
1350 if (!isColorTerm()) return sdscatlen(o,s,len);
1351
1352 int bold = strstr(color,"bold") != NULL;
1353 int ccode = 37; /* Defaults to white. */
1354 if (strstr(color,"red")) ccode = 31;
1355 else if (strstr(color,"green")) ccode = 32;
1356 else if (strstr(color,"yellow")) ccode = 33;
1357 else if (strstr(color,"blue")) ccode = 34;
1358 else if (strstr(color,"magenta")) ccode = 35;
1359 else if (strstr(color,"cyan")) ccode = 36;
1360 else if (strstr(color,"white")) ccode = 37;
1361
1362 o = sdscatfmt(o,"\033[%i;%i;49m",bold,ccode);
1363 o = sdscatlen(o,s,len);
1364 o = sdscat(o,"\033[0m");
1365 return o;
1366}
1367
1368/* Colorize Lua debugger status replies according to the prefix they
1369 * have. */
1370sds sdsCatColorizedLdbReply(sds o, char *s, size_t len) {
1371 char *color = "white";
1372
1373 if (strstr(s,"<debug>")) color = "bold";
1374 if (strstr(s,"<redis>")) color = "green";
1375 if (strstr(s,"<reply>")) color = "cyan";
1376 if (strstr(s,"<error>")) color = "red";
1377 if (strstr(s,"<hint>")) color = "bold";
1378 if (strstr(s,"<value>") || strstr(s,"<retval>")) color = "magenta";
1379 if (len > 4 && isdigit(s[3])) {
1380 if (s[1] == '>') color = "yellow"; /* Current line. */
1381 else if (s[2] == '#') color = "bold"; /* Break point. */
1382 }
1383 return sdscatcolor(o,s,len,color);
1384}
1385
1386static sds cliFormatReplyRaw(redisReply *r) {
1387 sds out = sdsempty(), tmp;
1388 size_t i;
1389
1390 switch (r->type) {
1391 case REDIS_REPLY_NIL:
1392 /* Nothing... */
1393 break;
1394 case REDIS_REPLY_ERROR:
1395 out = sdscatlen(out,r->str,r->len);
1396 out = sdscatlen(out,"\n",1);
1397 break;
1398 case REDIS_REPLY_STATUS:
1399 case REDIS_REPLY_STRING:
1400 case REDIS_REPLY_VERB:
1401 if (r->type == REDIS_REPLY_STATUS && config.eval_ldb) {
1402 /* The Lua debugger replies with arrays of simple (status)
1403 * strings. We colorize the output for more fun if this
1404 * is a debugging session. */
1405
1406 /* Detect the end of a debugging session. */
1407 if (strstr(r->str,"<endsession>") == r->str) {
1408 config.enable_ldb_on_eval = 0;
1409 config.eval_ldb = 0;
1410 config.eval_ldb_end = 1; /* Signal the caller session ended. */
1411 config.output = OUTPUT_STANDARD;
1412 cliRefreshPrompt();
1413 } else {
1414 out = sdsCatColorizedLdbReply(out,r->str,r->len);
1415 }
1416 } else {
1417 out = sdscatlen(out,r->str,r->len);
1418 }
1419 break;
1420 case REDIS_REPLY_BOOL:
1421 out = sdscat(out,r->integer ? "(true)" : "(false)");
1422 break;
1423 case REDIS_REPLY_INTEGER:
1424 out = sdscatprintf(out,"%lld",r->integer);
1425 break;
1426 case REDIS_REPLY_DOUBLE:
1427 out = sdscatprintf(out,"%s",r->str);
1428 break;
1429 case REDIS_REPLY_SET:
1430 case REDIS_REPLY_ARRAY:
1431 case REDIS_REPLY_PUSH:
1432 for (i = 0; i < r->elements; i++) {
1433 if (i > 0) out = sdscat(out,config.mb_delim);
1434 tmp = cliFormatReplyRaw(r->element[i]);
1435 out = sdscatlen(out,tmp,sdslen(tmp));
1436 sdsfree(tmp);
1437 }
1438 break;
1439 case REDIS_REPLY_MAP:
1440 for (i = 0; i < r->elements; i += 2) {
1441 if (i > 0) out = sdscat(out,config.mb_delim);
1442 tmp = cliFormatReplyRaw(r->element[i]);
1443 out = sdscatlen(out,tmp,sdslen(tmp));
1444 sdsfree(tmp);
1445
1446 out = sdscatlen(out," ",1);
1447 tmp = cliFormatReplyRaw(r->element[i+1]);
1448 out = sdscatlen(out,tmp,sdslen(tmp));
1449 sdsfree(tmp);
1450 }
1451 break;
1452 default:
1453 fprintf(stderr,"Unknown reply type: %d\n", r->type);
1454 exit(1);
1455 }
1456 return out;
1457}
1458
1459static sds cliFormatReplyCSV(redisReply *r) {
1460 unsigned int i;
1461
1462 sds out = sdsempty();
1463 switch (r->type) {
1464 case REDIS_REPLY_ERROR:
1465 out = sdscat(out,"ERROR,");
1466 out = sdscatrepr(out,r->str,strlen(r->str));
1467 break;
1468 case REDIS_REPLY_STATUS:
1469 out = sdscatrepr(out,r->str,r->len);
1470 break;
1471 case REDIS_REPLY_INTEGER:
1472 out = sdscatprintf(out,"%lld",r->integer);
1473 break;
1474 case REDIS_REPLY_DOUBLE:
1475 out = sdscatprintf(out,"%s",r->str);
1476 break;
1477 case REDIS_REPLY_STRING:
1478 case REDIS_REPLY_VERB:
1479 out = sdscatrepr(out,r->str,r->len);
1480 break;
1481 case REDIS_REPLY_NIL:
1482 out = sdscat(out,"NULL");
1483 break;
1484 case REDIS_REPLY_BOOL:
1485 out = sdscat(out,r->integer ? "true" : "false");
1486 break;
1487 case REDIS_REPLY_ARRAY:
1488 case REDIS_REPLY_SET:
1489 case REDIS_REPLY_PUSH:
1490 case REDIS_REPLY_MAP: /* CSV has no map type, just output flat list. */
1491 for (i = 0; i < r->elements; i++) {
1492 sds tmp = cliFormatReplyCSV(r->element[i]);
1493 out = sdscatlen(out,tmp,sdslen(tmp));
1494 if (i != r->elements-1) out = sdscat(out,",");
1495 sdsfree(tmp);
1496 }
1497 break;
1498 default:
1499 fprintf(stderr,"Unknown reply type: %d\n", r->type);
1500 exit(1);
1501 }
1502 return out;
1503}
1504
1505/* Append specified buffer to out and return it, using required JSON output
1506 * mode. */
1507static sds jsonStringOutput(sds out, const char *p, int len, int mode) {
1508 if (mode == OUTPUT_JSON) {
1509 return escapeJsonString(out, p, len);
1510 } else if (mode == OUTPUT_QUOTED_JSON) {
1511 /* Need to double-quote backslashes */
1512 sds tmp = sdscatrepr(sdsempty(), p, len);
1513 int tmplen = sdslen(tmp);
1514 char *n = tmp;
1515 while (tmplen--) {
1516 if (*n == '\\') out = sdscatlen(out, "\\\\", 2);
1517 else out = sdscatlen(out, n, 1);
1518 n++;
1519 }
1520
1521 sdsfree(tmp);
1522 return out;
1523 } else {
1524 assert(0);
1525 }
1526}
1527
1528static sds cliFormatReplyJson(sds out, redisReply *r, int mode) {
1529 unsigned int i;
1530
1531 switch (r->type) {
1532 case REDIS_REPLY_ERROR:
1533 out = sdscat(out,"error:");
1534 out = jsonStringOutput(out,r->str,strlen(r->str),mode);
1535 break;
1536 case REDIS_REPLY_STATUS:
1537 out = jsonStringOutput(out,r->str,r->len,mode);
1538 break;
1539 case REDIS_REPLY_INTEGER:
1540 out = sdscatprintf(out,"%lld",r->integer);
1541 break;
1542 case REDIS_REPLY_DOUBLE:
1543 out = sdscatprintf(out,"%s",r->str);
1544 break;
1545 case REDIS_REPLY_STRING:
1546 case REDIS_REPLY_VERB:
1547 out = jsonStringOutput(out,r->str,r->len,mode);
1548 break;
1549 case REDIS_REPLY_NIL:
1550 out = sdscat(out,"null");
1551 break;
1552 case REDIS_REPLY_BOOL:
1553 out = sdscat(out,r->integer ? "true" : "false");
1554 break;
1555 case REDIS_REPLY_ARRAY:
1556 case REDIS_REPLY_SET:
1557 case REDIS_REPLY_PUSH:
1558 out = sdscat(out,"[");
1559 for (i = 0; i < r->elements; i++ ) {
1560 out = cliFormatReplyJson(out,r->element[i],mode);
1561 if (i != r->elements-1) out = sdscat(out,",");
1562 }
1563 out = sdscat(out,"]");
1564 break;
1565 case REDIS_REPLY_MAP:
1566 out = sdscat(out,"{");
1567 for (i = 0; i < r->elements; i += 2) {
1568 redisReply *key = r->element[i];
1569 if (key->type == REDIS_REPLY_ERROR ||
1570 key->type == REDIS_REPLY_STATUS ||
1571 key->type == REDIS_REPLY_STRING ||
1572 key->type == REDIS_REPLY_VERB)
1573 {
1574 out = cliFormatReplyJson(out,key,mode);
1575 } else {
1576 /* According to JSON spec, JSON map keys must be strings,
1577 * and in RESP3, they can be other types.
1578 * The first one(cliFormatReplyJson) is to convert non string type to string
1579 * The Second one(escapeJsonString) is to escape the converted string */
1580 sds keystr = cliFormatReplyJson(sdsempty(),key,mode);
1581 if (keystr[0] == '"') out = sdscatsds(out,keystr);
1582 else out = sdscatfmt(out,"\"%S\"",keystr);
1583 sdsfree(keystr);
1584 }
1585 out = sdscat(out,":");
1586
1587 out = cliFormatReplyJson(out,r->element[i+1],mode);
1588 if (i != r->elements-2) out = sdscat(out,",");
1589 }
1590 out = sdscat(out,"}");
1591 break;
1592 default:
1593 fprintf(stderr,"Unknown reply type: %d\n", r->type);
1594 exit(1);
1595 }
1596 return out;
1597}
1598
1599/* Generate reply strings in various output modes */
1600static sds cliFormatReply(redisReply *reply, int mode, int verbatim) {
1601 sds out;
1602
1603 if (verbatim) {
1604 out = cliFormatReplyRaw(reply);
1605 } else if (mode == OUTPUT_STANDARD) {
1606 out = cliFormatReplyTTY(reply, "");
1607 } else if (mode == OUTPUT_RAW) {
1608 out = cliFormatReplyRaw(reply);
1609 out = sdscatsds(out, config.cmd_delim);
1610 } else if (mode == OUTPUT_CSV) {
1611 out = cliFormatReplyCSV(reply);
1612 out = sdscatlen(out, "\n", 1);
1613 } else if (mode == OUTPUT_JSON || mode == OUTPUT_QUOTED_JSON) {
1614 out = cliFormatReplyJson(sdsempty(), reply, mode);
1615 out = sdscatlen(out, "\n", 1);
1616 } else {
1617 fprintf(stderr, "Error: Unknown output encoding %d\n", mode);
1618 exit(1);
1619 }
1620
1621 return out;
1622}
1623
1624/* Output any spontaneous PUSH reply we receive */
1625static void cliPushHandler(void *privdata, void *reply) {
1626 UNUSED(privdata);
1627 sds out;
1628
1629 if (config.output == OUTPUT_STANDARD && isInvalidateReply(reply)) {
1630 out = cliFormatInvalidateTTY(reply);
1631 } else {
1632 out = cliFormatReply(reply, config.output, 0);
1633 }
1634
1635 fwrite(out, sdslen(out), 1, stdout);
1636
1637 freeReplyObject(reply);
1638 sdsfree(out);
1639}
1640
1641static int cliReadReply(int output_raw_strings) {
1642 void *_reply;
1643 redisReply *reply;
1644 sds out = NULL;
1645 int output = 1;
1646
1647 if (redisGetReply(context,&_reply) != REDIS_OK) {
1648 if (config.blocking_state_aborted) {
1649 config.blocking_state_aborted = 0;
1650 config.monitor_mode = 0;
1651 config.pubsub_mode = 0;
1652 return cliConnect(CC_FORCE);
1653 }
1654
1655 if (config.shutdown) {
1656 redisFree(context);
1657 context = NULL;
1658 return REDIS_OK;
1659 }
1660 if (config.interactive) {
1661 /* Filter cases where we should reconnect */
1662 if (context->err == REDIS_ERR_IO &&
1663 (errno == ECONNRESET || errno == EPIPE))
1664 return REDIS_ERR;
1665 if (context->err == REDIS_ERR_EOF)
1666 return REDIS_ERR;
1667 }
1668 cliPrintContextError();
1669 exit(1);
1670 return REDIS_ERR; /* avoid compiler warning */
1671 }
1672
1673 reply = (redisReply*)_reply;
1674
1675 config.last_cmd_type = reply->type;
1676
1677 /* Check if we need to connect to a different node and reissue the
1678 * request. */
1679 if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR &&
1680 (!strncmp(reply->str,"MOVED ",6) || !strncmp(reply->str,"ASK ",4)))
1681 {
1682 char *p = reply->str, *s;
1683 int slot;
1684
1685 output = 0;
1686 /* Comments show the position of the pointer as:
1687 *
1688 * [S] for pointer 's'
1689 * [P] for pointer 'p'
1690 */
1691 s = strchr(p,' '); /* MOVED[S]3999 127.0.0.1:6381 */
1692 p = strchr(s+1,' '); /* MOVED[S]3999[P]127.0.0.1:6381 */
1693 *p = '\0';
1694 slot = atoi(s+1);
1695 s = strrchr(p+1,':'); /* MOVED 3999[P]127.0.0.1[S]6381 */
1696 *s = '\0';
1697 sdsfree(config.conn_info.hostip);
1698 config.conn_info.hostip = sdsnew(p+1);
1699 config.conn_info.hostport = atoi(s+1);
1700 if (config.interactive)
1701 printf("-> Redirected to slot [%d] located at %s:%d\n",
1702 slot, config.conn_info.hostip, config.conn_info.hostport);
1703 config.cluster_reissue_command = 1;
1704 if (!strncmp(reply->str,"ASK ",4)) {
1705 config.cluster_send_asking = 1;
1706 }
1707 cliRefreshPrompt();
1708 } else if (!config.interactive && config.set_errcode &&
1709 reply->type == REDIS_REPLY_ERROR)
1710 {
1711 fprintf(stderr,"%s\n",reply->str);
1712 exit(1);
1713 return REDIS_ERR; /* avoid compiler warning */
1714 }
1715
1716 if (output) {
1717 out = cliFormatReply(reply, config.output, output_raw_strings);
1718 fwrite(out,sdslen(out),1,stdout);
1719 fflush(stdout);
1720 sdsfree(out);
1721 }
1722 freeReplyObject(reply);
1723 return REDIS_OK;
1724}
1725
1726static int cliSendCommand(int argc, char **argv, long repeat) {
1727 char *command = argv[0];
1728 size_t *argvlen;
1729 int j, output_raw;
1730
1731 if (context == NULL) return REDIS_ERR;
1732
1733 output_raw = 0;
1734 if (!strcasecmp(command,"info") ||
1735 !strcasecmp(command,"lolwut") ||
1736 (argc >= 2 && !strcasecmp(command,"debug") &&
1737 !strcasecmp(argv[1],"htstats")) ||
1738 (argc >= 2 && !strcasecmp(command,"debug") &&
1739 !strcasecmp(argv[1],"htstats-key")) ||
1740 (argc >= 2 && !strcasecmp(command,"debug") &&
1741 !strcasecmp(argv[1],"client-eviction")) ||
1742 (argc >= 2 && !strcasecmp(command,"memory") &&
1743 (!strcasecmp(argv[1],"malloc-stats") ||
1744 !strcasecmp(argv[1],"doctor"))) ||
1745 (argc == 2 && !strcasecmp(command,"cluster") &&
1746 (!strcasecmp(argv[1],"nodes") ||
1747 !strcasecmp(argv[1],"info"))) ||
1748 (argc >= 2 && !strcasecmp(command,"client") &&
1749 (!strcasecmp(argv[1],"list") ||
1750 !strcasecmp(argv[1],"info"))) ||
1751 (argc == 3 && !strcasecmp(command,"latency") &&
1752 !strcasecmp(argv[1],"graph")) ||
1753 (argc == 2 && !strcasecmp(command,"latency") &&
1754 !strcasecmp(argv[1],"doctor")) ||
1755 /* Format PROXY INFO command for Redis Cluster Proxy:
1756 * https://github.com/artix75/redis-cluster-proxy */
1757 (argc >= 2 && !strcasecmp(command,"proxy") &&
1758 !strcasecmp(argv[1],"info")))
1759 {
1760 output_raw = 1;
1761 }
1762
1763 if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
1764 if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
1765 if (!strcasecmp(command,"subscribe") ||
1766 !strcasecmp(command,"psubscribe") ||
1767 !strcasecmp(command,"ssubscribe")) config.pubsub_mode = 1;
1768 if (!strcasecmp(command,"sync") ||
1769 !strcasecmp(command,"psync")) config.slave_mode = 1;
1770
1771 /* When the user manually calls SCRIPT DEBUG, setup the activation of
1772 * debugging mode on the next eval if needed. */
1773 if (argc == 3 && !strcasecmp(argv[0],"script") &&
1774 !strcasecmp(argv[1],"debug"))
1775 {
1776 if (!strcasecmp(argv[2],"yes") || !strcasecmp(argv[2],"sync")) {
1777 config.enable_ldb_on_eval = 1;
1778 } else {
1779 config.enable_ldb_on_eval = 0;
1780 }
1781 }
1782
1783 /* Actually activate LDB on EVAL if needed. */
1784 if (!strcasecmp(command,"eval") && config.enable_ldb_on_eval) {
1785 config.eval_ldb = 1;
1786 config.output = OUTPUT_RAW;
1787 }
1788
1789 /* Setup argument length */
1790 argvlen = zmalloc(argc*sizeof(size_t));
1791 for (j = 0; j < argc; j++)
1792 argvlen[j] = sdslen(argv[j]);
1793
1794 /* Negative repeat is allowed and causes infinite loop,
1795 works well with the interval option. */
1796 while(repeat < 0 || repeat-- > 0) {
1797 redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
1798 if (config.monitor_mode) {
1799 do {
1800 if (cliReadReply(output_raw) != REDIS_OK) {
1801 cliPrintContextError();
1802 exit(1);
1803 }
1804 fflush(stdout);
1805
1806 /* This happens when the MONITOR command returns an error. */
1807 if (config.last_cmd_type == REDIS_REPLY_ERROR)
1808 config.monitor_mode = 0;
1809 } while(config.monitor_mode);
1810 zfree(argvlen);
1811 return REDIS_OK;
1812 }
1813
1814 if (config.pubsub_mode) {
1815 if (config.output != OUTPUT_RAW)
1816 printf("Reading messages... (press Ctrl-C to quit)\n");
1817
1818 /* Unset our default PUSH handler so this works in RESP2/RESP3 */
1819 redisSetPushCallback(context, NULL);
1820
1821 while (config.pubsub_mode) {
1822 if (cliReadReply(output_raw) != REDIS_OK) {
1823 cliPrintContextError();
1824 exit(1);
1825 }
1826 fflush(stdout); /* Make it grep friendly */
1827 if (!config.pubsub_mode || config.last_cmd_type == REDIS_REPLY_ERROR) {
1828 if (config.push_output) {
1829 redisSetPushCallback(context, cliPushHandler);
1830 }
1831 config.pubsub_mode = 0;
1832 }
1833 }
1834 continue;
1835 }
1836
1837 if (config.slave_mode) {
1838 printf("Entering replica output mode... (press Ctrl-C to quit)\n");
1839 slaveMode();
1840 config.slave_mode = 0;
1841 zfree(argvlen);
1842 return REDIS_ERR; /* Error = slaveMode lost connection to master */
1843 }
1844
1845 if (cliReadReply(output_raw) != REDIS_OK) {
1846 zfree(argvlen);
1847 return REDIS_ERR;
1848 } else {
1849 /* Store database number when SELECT was successfully executed. */
1850 if (!strcasecmp(command,"select") && argc == 2 &&
1851 config.last_cmd_type != REDIS_REPLY_ERROR)
1852 {
1853 config.conn_info.input_dbnum = config.dbnum = atoi(argv[1]);
1854 cliRefreshPrompt();
1855 } else if (!strcasecmp(command,"auth") && (argc == 2 || argc == 3)) {
1856 cliSelect();
1857 } else if (!strcasecmp(command,"multi") && argc == 1 &&
1858 config.last_cmd_type != REDIS_REPLY_ERROR)
1859 {
1860 config.in_multi = 1;
1861 config.pre_multi_dbnum = config.dbnum;
1862 cliRefreshPrompt();
1863 } else if (!strcasecmp(command,"exec") && argc == 1 && config.in_multi) {
1864 config.in_multi = 0;
1865 if (config.last_cmd_type == REDIS_REPLY_ERROR ||
1866 config.last_cmd_type == REDIS_REPLY_NIL)
1867 {
1868 config.conn_info.input_dbnum = config.dbnum = config.pre_multi_dbnum;
1869 }
1870 cliRefreshPrompt();
1871 } else if (!strcasecmp(command,"discard") && argc == 1 &&
1872 config.last_cmd_type != REDIS_REPLY_ERROR)
1873 {
1874 config.in_multi = 0;
1875 config.conn_info.input_dbnum = config.dbnum = config.pre_multi_dbnum;
1876 cliRefreshPrompt();
1877 } else if (!strcasecmp(command,"reset") && argc == 1 &&
1878 config.last_cmd_type != REDIS_REPLY_ERROR) {
1879 config.in_multi = 0;
1880 config.dbnum = 0;
1881 config.conn_info.input_dbnum = 0;
1882 config.resp3 = 0;
1883 cliRefreshPrompt();
1884 }
1885 }
1886 if (config.cluster_reissue_command){
1887 /* If we need to reissue the command, break to prevent a
1888 further 'repeat' number of dud interactions */
1889 break;
1890 }
1891 if (config.interval) usleep(config.interval);
1892 fflush(stdout); /* Make it grep friendly */
1893 }
1894
1895 zfree(argvlen);
1896 return REDIS_OK;
1897}
1898
1899/* Send a command reconnecting the link if needed. */
1900static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ...) {
1901 redisReply *reply = NULL;
1902 int tries = 0;
1903 va_list ap;
1904
1905 assert(!c->err);
1906 while(reply == NULL) {
1907 while (c->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
1908 printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */
1909 printf("Reconnecting... %d\r", ++tries);
1910 fflush(stdout);
1911
1912 redisFree(c);
1913 c = redisConnect(config.conn_info.hostip,config.conn_info.hostport);
1914 if (!c->err && config.tls) {
1915 const char *err = NULL;
1916 if (cliSecureConnection(c, config.sslconfig, &err) == REDIS_ERR && err) {
1917 fprintf(stderr, "TLS Error: %s\n", err);
1918 exit(1);
1919 }
1920 }
1921 usleep(1000000);
1922 }
1923
1924 va_start(ap,fmt);
1925 reply = redisvCommand(c,fmt,ap);
1926 va_end(ap);
1927
1928 if (c->err && !(c->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
1929 fprintf(stderr, "Error: %s\n", c->errstr);
1930 exit(1);
1931 } else if (tries > 0) {
1932 printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */
1933 }
1934 }
1935
1936 context = c;
1937 return reply;
1938}
1939
1940/*------------------------------------------------------------------------------
1941 * User interface
1942 *--------------------------------------------------------------------------- */
1943
1944static int parseOptions(int argc, char **argv) {
1945 int i;
1946
1947 for (i = 1; i < argc; i++) {
1948 int lastarg = i==argc-1;
1949
1950 if (!strcmp(argv[i],"-h") && !lastarg) {
1951 sdsfree(config.conn_info.hostip);
1952 config.conn_info.hostip = sdsnew(argv[++i]);
1953 } else if (!strcmp(argv[i],"-h") && lastarg) {
1954 usage(0);
1955 } else if (!strcmp(argv[i],"--help")) {
1956 usage(0);
1957 } else if (!strcmp(argv[i],"-x")) {
1958 config.stdin_lastarg = 1;
1959 } else if (!strcmp(argv[i], "-X") && !lastarg) {
1960 config.stdin_tag_arg = 1;
1961 config.stdin_tag_name = argv[++i];
1962 } else if (!strcmp(argv[i],"-p") && !lastarg) {
1963 config.conn_info.hostport = atoi(argv[++i]);
1964 } else if (!strcmp(argv[i],"-s") && !lastarg) {
1965 config.hostsocket = argv[++i];
1966 } else if (!strcmp(argv[i],"-r") && !lastarg) {
1967 config.repeat = strtoll(argv[++i],NULL,10);
1968 } else if (!strcmp(argv[i],"-i") && !lastarg) {
1969 double seconds = atof(argv[++i]);
1970 config.interval = seconds*1000000;
1971 } else if (!strcmp(argv[i],"-n") && !lastarg) {
1972 config.conn_info.input_dbnum = atoi(argv[++i]);
1973 } else if (!strcmp(argv[i], "--no-auth-warning")) {
1974 config.no_auth_warning = 1;
1975 } else if (!strcmp(argv[i], "--askpass")) {
1976 config.askpass = 1;
1977 } else if ((!strcmp(argv[i],"-a") || !strcmp(argv[i],"--pass"))
1978 && !lastarg)
1979 {
1980 config.conn_info.auth = sdsnew(argv[++i]);
1981 } else if (!strcmp(argv[i],"--user") && !lastarg) {
1982 config.conn_info.user = sdsnew(argv[++i]);
1983 } else if (!strcmp(argv[i],"-u") && !lastarg) {
1984 parseRedisUri(argv[++i],"redis-cli",&config.conn_info,&config.tls);
1985 } else if (!strcmp(argv[i],"--raw")) {
1986 config.output = OUTPUT_RAW;
1987 } else if (!strcmp(argv[i],"--no-raw")) {
1988 config.output = OUTPUT_STANDARD;
1989 } else if (!strcmp(argv[i],"--quoted-input")) {
1990 config.quoted_input = 1;
1991 } else if (!strcmp(argv[i],"--csv")) {
1992 config.output = OUTPUT_CSV;
1993 } else if (!strcmp(argv[i],"--json")) {
1994 /* Not overwrite explicit value by -3 */
1995 if (config.resp3 == 0) {
1996 config.resp3 = 2;
1997 }
1998 config.output = OUTPUT_JSON;
1999 } else if (!strcmp(argv[i],"--quoted-json")) {
2000 /* Not overwrite explicit value by -3*/
2001 if (config.resp3 == 0) {
2002 config.resp3 = 2;
2003 }
2004 config.output = OUTPUT_QUOTED_JSON;
2005 } else if (!strcmp(argv[i],"--latency")) {
2006 config.latency_mode = 1;
2007 } else if (!strcmp(argv[i],"--latency-dist")) {
2008 config.latency_dist_mode = 1;
2009 } else if (!strcmp(argv[i],"--mono")) {
2010 spectrum_palette = spectrum_palette_mono;
2011 spectrum_palette_size = spectrum_palette_mono_size;
2012 } else if (!strcmp(argv[i],"--latency-history")) {
2013 config.latency_mode = 1;
2014 config.latency_history = 1;
2015 } else if (!strcmp(argv[i],"--lru-test") && !lastarg) {
2016 config.lru_test_mode = 1;
2017 config.lru_test_sample_size = strtoll(argv[++i],NULL,10);
2018 } else if (!strcmp(argv[i],"--slave")) {
2019 config.slave_mode = 1;
2020 } else if (!strcmp(argv[i],"--replica")) {
2021 config.slave_mode = 1;
2022 } else if (!strcmp(argv[i],"--stat")) {
2023 config.stat_mode = 1;
2024 } else if (!strcmp(argv[i],"--scan")) {
2025 config.scan_mode = 1;
2026 } else if (!strcmp(argv[i],"--pattern") && !lastarg) {
2027 sdsfree(config.pattern);
2028 config.pattern = sdsnew(argv[++i]);
2029 } else if (!strcmp(argv[i],"--quoted-pattern") && !lastarg) {
2030 sdsfree(config.pattern);
2031 config.pattern = unquoteCString(argv[++i]);
2032 if (!config.pattern) {
2033 fprintf(stderr,"Invalid quoted string specified for --quoted-pattern.\n");
2034 exit(1);
2035 }
2036 } else if (!strcmp(argv[i],"--intrinsic-latency") && !lastarg) {
2037 config.intrinsic_latency_mode = 1;
2038 config.intrinsic_latency_duration = atoi(argv[++i]);
2039 } else if (!strcmp(argv[i],"--rdb") && !lastarg) {
2040 config.getrdb_mode = 1;
2041 config.rdb_filename = argv[++i];
2042 } else if (!strcmp(argv[i],"--functions-rdb") && !lastarg) {
2043 config.get_functions_rdb_mode = 1;
2044 config.rdb_filename = argv[++i];
2045 } else if (!strcmp(argv[i],"--pipe")) {
2046 config.pipe_mode = 1;
2047 } else if (!strcmp(argv[i],"--pipe-timeout") && !lastarg) {
2048 config.pipe_timeout = atoi(argv[++i]);
2049 } else if (!strcmp(argv[i],"--bigkeys")) {
2050 config.bigkeys = 1;
2051 } else if (!strcmp(argv[i],"--memkeys")) {
2052 config.memkeys = 1;
2053 config.memkeys_samples = 0; /* use redis default */
2054 } else if (!strcmp(argv[i],"--memkeys-samples")) {
2055 config.memkeys = 1;
2056 config.memkeys_samples = atoi(argv[++i]);
2057 } else if (!strcmp(argv[i],"--hotkeys")) {
2058 config.hotkeys = 1;
2059 } else if (!strcmp(argv[i],"--eval") && !lastarg) {
2060 config.eval = argv[++i];
2061 } else if (!strcmp(argv[i],"--ldb")) {
2062 config.eval_ldb = 1;
2063 config.output = OUTPUT_RAW;
2064 } else if (!strcmp(argv[i],"--ldb-sync-mode")) {
2065 config.eval_ldb = 1;
2066 config.eval_ldb_sync = 1;
2067 config.output = OUTPUT_RAW;
2068 } else if (!strcmp(argv[i],"-c")) {
2069 config.cluster_mode = 1;
2070 } else if (!strcmp(argv[i],"-d") && !lastarg) {
2071 sdsfree(config.mb_delim);
2072 config.mb_delim = sdsnew(argv[++i]);
2073 } else if (!strcmp(argv[i],"-D") && !lastarg) {
2074 sdsfree(config.cmd_delim);
2075 config.cmd_delim = sdsnew(argv[++i]);
2076 } else if (!strcmp(argv[i],"-e")) {
2077 config.set_errcode = 1;
2078 } else if (!strcmp(argv[i],"--verbose")) {
2079 config.verbose = 1;
2080 } else if (!strcmp(argv[i],"--cluster") && !lastarg) {
2081 if (CLUSTER_MANAGER_MODE()) usage(1);
2082 char *cmd = argv[++i];
2083 int j = i;
2084 while (j < argc && argv[j][0] != '-') j++;
2085 if (j > i) j--;
2086 int err = createClusterManagerCommand(cmd, j - i, argv + i + 1);
2087 if (err) exit(err);
2088 i = j;
2089 } else if (!strcmp(argv[i],"--cluster") && lastarg) {
2090 usage(1);
2091 } else if ((!strcmp(argv[i],"--cluster-only-masters"))) {
2092 config.cluster_manager_command.flags |=
2093 CLUSTER_MANAGER_CMD_FLAG_MASTERS_ONLY;
2094 } else if ((!strcmp(argv[i],"--cluster-only-replicas"))) {
2095 config.cluster_manager_command.flags |=
2096 CLUSTER_MANAGER_CMD_FLAG_SLAVES_ONLY;
2097 } else if (!strcmp(argv[i],"--cluster-replicas") && !lastarg) {
2098 config.cluster_manager_command.replicas = atoi(argv[++i]);
2099 } else if (!strcmp(argv[i],"--cluster-master-id") && !lastarg) {
2100 config.cluster_manager_command.master_id = argv[++i];
2101 } else if (!strcmp(argv[i],"--cluster-from") && !lastarg) {
2102 config.cluster_manager_command.from = argv[++i];
2103 } else if (!strcmp(argv[i],"--cluster-to") && !lastarg) {
2104 config.cluster_manager_command.to = argv[++i];
2105 } else if (!strcmp(argv[i],"--cluster-from-user") && !lastarg) {
2106 config.cluster_manager_command.from_user = argv[++i];
2107 } else if (!strcmp(argv[i],"--cluster-from-pass") && !lastarg) {
2108 config.cluster_manager_command.from_pass = argv[++i];
2109 } else if (!strcmp(argv[i], "--cluster-from-askpass")) {
2110 config.cluster_manager_command.from_askpass = 1;
2111 } else if (!strcmp(argv[i],"--cluster-weight") && !lastarg) {
2112 if (config.cluster_manager_command.weight != NULL) {
2113 fprintf(stderr, "WARNING: you cannot use --cluster-weight "
2114 "more than once.\n"
2115 "You can set more weights by adding them "
2116 "as a space-separated list, ie:\n"
2117 "--cluster-weight n1=w n2=w\n");
2118 exit(1);
2119 }
2120 int widx = i + 1;
2121 char **weight = argv + widx;
2122 int wargc = 0;
2123 for (; widx < argc; widx++) {
2124 if (strstr(argv[widx], "--") == argv[widx]) break;
2125 if (strchr(argv[widx], '=') == NULL) break;
2126 wargc++;
2127 }
2128 if (wargc > 0) {
2129 config.cluster_manager_command.weight = weight;
2130 config.cluster_manager_command.weight_argc = wargc;
2131 i += wargc;
2132 }
2133 } else if (!strcmp(argv[i],"--cluster-slots") && !lastarg) {
2134 config.cluster_manager_command.slots = atoi(argv[++i]);
2135 } else if (!strcmp(argv[i],"--cluster-timeout") && !lastarg) {
2136 config.cluster_manager_command.timeout = atoi(argv[++i]);
2137 } else if (!strcmp(argv[i],"--cluster-pipeline") && !lastarg) {
2138 config.cluster_manager_command.pipeline = atoi(argv[++i]);
2139 } else if (!strcmp(argv[i],"--cluster-threshold") && !lastarg) {
2140 config.cluster_manager_command.threshold = atof(argv[++i]);
2141 } else if (!strcmp(argv[i],"--cluster-yes")) {
2142 config.cluster_manager_command.flags |=
2143 CLUSTER_MANAGER_CMD_FLAG_YES;
2144 } else if (!strcmp(argv[i],"--cluster-simulate")) {
2145 config.cluster_manager_command.flags |=
2146 CLUSTER_MANAGER_CMD_FLAG_SIMULATE;
2147 } else if (!strcmp(argv[i],"--cluster-replace")) {
2148 config.cluster_manager_command.flags |=
2149 CLUSTER_MANAGER_CMD_FLAG_REPLACE;
2150 } else if (!strcmp(argv[i],"--cluster-copy")) {
2151 config.cluster_manager_command.flags |=
2152 CLUSTER_MANAGER_CMD_FLAG_COPY;
2153 } else if (!strcmp(argv[i],"--cluster-slave")) {
2154 config.cluster_manager_command.flags |=
2155 CLUSTER_MANAGER_CMD_FLAG_SLAVE;
2156 } else if (!strcmp(argv[i],"--cluster-use-empty-masters")) {
2157 config.cluster_manager_command.flags |=
2158 CLUSTER_MANAGER_CMD_FLAG_EMPTYMASTER;
2159 } else if (!strcmp(argv[i],"--cluster-search-multiple-owners")) {
2160 config.cluster_manager_command.flags |=
2161 CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS;
2162 } else if (!strcmp(argv[i],"--cluster-fix-with-unreachable-masters")) {
2163 config.cluster_manager_command.flags |=
2164 CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS;
2165#ifdef USE_OPENSSL
2166 } else if (!strcmp(argv[i],"--tls")) {
2167 config.tls = 1;
2168 } else if (!strcmp(argv[i],"--sni") && !lastarg) {
2169 config.sslconfig.sni = argv[++i];
2170 } else if (!strcmp(argv[i],"--cacertdir") && !lastarg) {
2171 config.sslconfig.cacertdir = argv[++i];
2172 } else if (!strcmp(argv[i],"--cacert") && !lastarg) {
2173 config.sslconfig.cacert = argv[++i];
2174 } else if (!strcmp(argv[i],"--cert") && !lastarg) {
2175 config.sslconfig.cert = argv[++i];
2176 } else if (!strcmp(argv[i],"--key") && !lastarg) {
2177 config.sslconfig.key = argv[++i];
2178 } else if (!strcmp(argv[i],"--tls-ciphers") && !lastarg) {
2179 config.sslconfig.ciphers = argv[++i];
2180 } else if (!strcmp(argv[i],"--insecure")) {
2181 config.sslconfig.skip_cert_verify = 1;
2182 #ifdef TLS1_3_VERSION
2183 } else if (!strcmp(argv[i],"--tls-ciphersuites") && !lastarg) {
2184 config.sslconfig.ciphersuites = argv[++i];
2185 #endif
2186#endif
2187 } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
2188 sds version = cliVersion();
2189 printf("redis-cli %s\n", version);
2190 sdsfree(version);
2191 exit(0);
2192 } else if (!strcmp(argv[i],"-2")) {
2193 config.resp2 = 1;
2194 } else if (!strcmp(argv[i],"-3")) {
2195 config.resp3 = 1;
2196 } else if (!strcmp(argv[i],"--show-pushes") && !lastarg) {
2197 char *argval = argv[++i];
2198 if (!strncasecmp(argval, "n", 1)) {
2199 config.push_output = 0;
2200 } else if (!strncasecmp(argval, "y", 1)) {
2201 config.push_output = 1;
2202 } else {
2203 fprintf(stderr, "Unknown --show-pushes value '%s' "
2204 "(valid: '[y]es', '[n]o')\n", argval);
2205 }
2206 } else if (CLUSTER_MANAGER_MODE() && argv[i][0] != '-') {
2207 if (config.cluster_manager_command.argc == 0) {
2208 int j = i + 1;
2209 while (j < argc && argv[j][0] != '-') j++;
2210 int cmd_argc = j - i;
2211 config.cluster_manager_command.argc = cmd_argc;
2212 config.cluster_manager_command.argv = argv + i;
2213 if (cmd_argc > 1) i = j - 1;
2214 }
2215 } else {
2216 if (argv[i][0] == '-') {
2217 fprintf(stderr,
2218 "Unrecognized option or bad number of args for: '%s'\n",
2219 argv[i]);
2220 exit(1);
2221 } else {
2222 /* Likely the command name, stop here. */
2223 break;
2224 }
2225 }
2226 }
2227
2228 if (config.hostsocket && config.cluster_mode) {
2229 fprintf(stderr,"Options -c and -s are mutually exclusive.\n");
2230 exit(1);
2231 }
2232
2233 if (config.resp2 && config.resp3 == 1) {
2234 fprintf(stderr,"Options -2 and -3 are mutually exclusive.\n");
2235 exit(1);
2236 }
2237
2238 /* --ldb requires --eval. */
2239 if (config.eval_ldb && config.eval == NULL) {
2240 fprintf(stderr,"Options --ldb and --ldb-sync-mode require --eval.\n");
2241 fprintf(stderr,"Try %s --help for more information.\n", argv[0]);
2242 exit(1);
2243 }
2244
2245 if (!config.no_auth_warning && config.conn_info.auth != NULL) {
2246 fputs("Warning: Using a password with '-a' or '-u' option on the command"
2247 " line interface may not be safe.\n", stderr);
2248 }
2249
2250 if (config.get_functions_rdb_mode && config.getrdb_mode) {
2251 fprintf(stderr,"Option --functions-rdb and --rdb are mutually exclusive.\n");
2252 exit(1);
2253 }
2254
2255 if (config.stdin_lastarg && config.stdin_tag_arg) {
2256 fprintf(stderr, "Options -x and -X are mutually exclusive.\n");
2257 exit(1);
2258 }
2259
2260 return i;
2261}
2262
2263static void parseEnv() {
2264 /* Set auth from env, but do not overwrite CLI arguments if passed */
2265 char *auth = getenv(REDIS_CLI_AUTH_ENV);
2266 if (auth != NULL && config.conn_info.auth == NULL) {
2267 config.conn_info.auth = auth;
2268 }
2269
2270 char *cluster_yes = getenv(REDIS_CLI_CLUSTER_YES_ENV);
2271 if (cluster_yes != NULL && !strcmp(cluster_yes, "1")) {
2272 config.cluster_manager_command.flags |= CLUSTER_MANAGER_CMD_FLAG_YES;
2273 }
2274}
2275
2276static void usage(int err) {
2277 sds version = cliVersion();
2278 FILE *target = err ? stderr: stdout;
2279 fprintf(target,
2280"redis-cli %s\n"
2281"\n"
2282"Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n"
2283" -h <hostname> Server hostname (default: 127.0.0.1).\n"
2284" -p <port> Server port (default: 6379).\n"
2285" -s <socket> Server socket (overrides hostname and port).\n"
2286" -a <password> Password to use when connecting to the server.\n"
2287" You can also use the " REDIS_CLI_AUTH_ENV " environment\n"
2288" variable to pass this password more safely\n"
2289" (if both are used, this argument takes precedence).\n"
2290" --user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n"
2291" --pass <password> Alias of -a for consistency with the new --user option.\n"
2292" --askpass Force user to input password with mask from STDIN.\n"
2293" If this argument is used, '-a' and " REDIS_CLI_AUTH_ENV "\n"
2294" environment variable will be ignored.\n"
2295" -u <uri> Server URI.\n"
2296" -r <repeat> Execute specified command N times.\n"
2297" -i <interval> When -r is used, waits <interval> seconds per command.\n"
2298" It is possible to specify sub-second times like -i 0.1.\n"
2299" This interval is also used in --scan and --stat per cycle.\n"
2300" and in --bigkeys, --memkeys, and --hotkeys per 100 cycles.\n"
2301" -n <db> Database number.\n"
2302" -2 Start session in RESP2 protocol mode.\n"
2303" -3 Start session in RESP3 protocol mode.\n"
2304" -x Read last argument from STDIN (see example below).\n"
2305" -X Read <tag> argument from STDIN (see example below).\n"
2306" -d <delimiter> Delimiter between response bulks for raw formatting (default: \\n).\n"
2307" -D <delimiter> Delimiter between responses for raw formatting (default: \\n).\n"
2308" -c Enable cluster mode (follow -ASK and -MOVED redirections).\n"
2309" -e Return exit error code when command execution fails.\n"
2310#ifdef USE_OPENSSL
2311" --tls Establish a secure TLS connection.\n"
2312" --sni <host> Server name indication for TLS.\n"
2313" --cacert <file> CA Certificate file to verify with.\n"
2314" --cacertdir <dir> Directory where trusted CA certificates are stored.\n"
2315" If neither cacert nor cacertdir are specified, the default\n"
2316" system-wide trusted root certs configuration will apply.\n"
2317" --insecure Allow insecure TLS connection by skipping cert validation.\n"
2318" --cert <file> Client certificate to authenticate with.\n"
2319" --key <file> Private key file to authenticate with.\n"
2320" --tls-ciphers <list> Sets the list of preferred ciphers (TLSv1.2 and below)\n"
2321" in order of preference from highest to lowest separated by colon (\":\").\n"
2322" See the ciphers(1ssl) manpage for more information about the syntax of this string.\n"
2323#ifdef TLS1_3_VERSION
2324" --tls-ciphersuites <list> Sets the list of preferred ciphersuites (TLSv1.3)\n"
2325" in order of preference from highest to lowest separated by colon (\":\").\n"
2326" See the ciphers(1ssl) manpage for more information about the syntax of this string,\n"
2327" and specifically for TLSv1.3 ciphersuites.\n"
2328#endif
2329#endif
2330" --raw Use raw formatting for replies (default when STDOUT is\n"
2331" not a tty).\n"
2332" --no-raw Force formatted output even when STDOUT is not a tty.\n"
2333" --quoted-input Force input to be handled as quoted strings.\n"
2334" --csv Output in CSV format.\n"
2335" --json Output in JSON format (default RESP3, use -2 if you want to use with RESP2).\n"
2336" --quoted-json Same as --json, but produce ASCII-safe quoted strings, not Unicode.\n"
2337" --show-pushes <yn> Whether to print RESP3 PUSH messages. Enabled by default when\n"
2338" STDOUT is a tty but can be overridden with --show-pushes no.\n"
2339" --stat Print rolling stats about server: mem, clients, ...\n",version);
2340
2341 fprintf(target,
2342" --latency Enter a special mode continuously sampling latency.\n"
2343" If you use this mode in an interactive session it runs\n"
2344" forever displaying real-time stats. Otherwise if --raw or\n"
2345" --csv is specified, or if you redirect the output to a non\n"
2346" TTY, it samples the latency for 1 second (you can use\n"
2347" -i to change the interval), then produces a single output\n"
2348" and exits.\n"
2349" --latency-history Like --latency but tracking latency changes over time.\n"
2350" Default time interval is 15 sec. Change it using -i.\n"
2351" --latency-dist Shows latency as a spectrum, requires xterm 256 colors.\n"
2352" Default time interval is 1 sec. Change it using -i.\n"
2353" --lru-test <keys> Simulate a cache workload with an 80-20 distribution.\n"
2354" --replica Simulate a replica showing commands received from the master.\n"
2355" --rdb <filename> Transfer an RDB dump from remote server to local file.\n"
2356" Use filename of \"-\" to write to stdout.\n"
2357" --functions-rdb <filename> Like --rdb but only get the functions (not the keys)\n"
2358" when getting the RDB dump file.\n"
2359" --pipe Transfer raw Redis protocol from stdin to server.\n"
2360" --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
2361" no reply is received within <n> seconds.\n"
2362" Default timeout: %d. Use 0 to wait forever.\n",
2363 REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
2364 fprintf(target,
2365" --bigkeys Sample Redis keys looking for keys with many elements (complexity).\n"
2366" --memkeys Sample Redis keys looking for keys consuming a lot of memory.\n"
2367" --memkeys-samples <n> Sample Redis keys looking for keys consuming a lot of memory.\n"
2368" And define number of key elements to sample\n"
2369" --hotkeys Sample Redis keys looking for hot keys.\n"
2370" only works when maxmemory-policy is *lfu.\n"
2371" --scan List all keys using the SCAN command.\n"
2372" --pattern <pat> Keys pattern when using the --scan, --bigkeys or --hotkeys\n"
2373" options (default: *).\n"
2374" --quoted-pattern <pat> Same as --pattern, but the specified string can be\n"
2375" quoted, in order to pass an otherwise non binary-safe string.\n"
2376" --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n"
2377" The test will run for the specified amount of seconds.\n"
2378" --eval <file> Send an EVAL command using the Lua script at <file>.\n"
2379" --ldb Used with --eval enable the Redis Lua debugger.\n"
2380" --ldb-sync-mode Like --ldb but uses the synchronous Lua debugger, in\n"
2381" this mode the server is blocked and script changes are\n"
2382" not rolled back from the server memory.\n"
2383" --cluster <command> [args...] [opts...]\n"
2384" Cluster Manager command and arguments (see below).\n"
2385" --verbose Verbose mode.\n"
2386" --no-auth-warning Don't show warning message when using password on command\n"
2387" line interface.\n"
2388" --help Output this help and exit.\n"
2389" --version Output version and exit.\n"
2390"\n");
2391 /* Using another fprintf call to avoid -Woverlength-strings compile warning */
2392 fprintf(target,
2393"Cluster Manager Commands:\n"
2394" Use --cluster help to list all available cluster manager commands.\n"
2395"\n"
2396"Examples:\n"
2397" cat /etc/passwd | redis-cli -x set mypasswd\n"
2398" redis-cli -D \"\" --raw dump key > key.dump && redis-cli -X dump_tag restore key2 0 dump_tag replace < key.dump\n"
2399" redis-cli -r 100 lpush mylist x\n"
2400" redis-cli -r 100 -i 1 info | grep used_memory_human:\n"
2401" redis-cli --quoted-input set '\"null-\\x00-separated\"' value\n"
2402" redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n"
2403" redis-cli --scan --pattern '*:12345*'\n"
2404"\n"
2405" (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n"
2406"\n"
2407"When no command is given, redis-cli starts in interactive mode.\n"
2408"Type \"help\" in interactive mode for information on available commands\n"
2409"and settings.\n"
2410"\n");
2411 sdsfree(version);
2412 exit(err);
2413}
2414
2415static int confirmWithYes(char *msg, int ignore_force) {
2416 /* if --cluster-yes option is set and ignore_force is false,
2417 * do not prompt for an answer */
2418 if (!ignore_force &&
2419 (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_YES)) {
2420 return 1;
2421 }
2422
2423 printf("%s (type 'yes' to accept): ", msg);
2424 fflush(stdout);
2425 char buf[4];
2426 int nread = read(fileno(stdin),buf,4);
2427 buf[3] = '\0';
2428 return (nread != 0 && !strcmp("yes", buf));
2429}
2430
2431static int issueCommandRepeat(int argc, char **argv, long repeat) {
2432 /* In Lua debugging mode, we want to pass the "help" to Redis to get
2433 * it's own HELP message, rather than handle it by the CLI, see ldbRepl.
2434 *
2435 * For the normal Redis HELP, we can process it without a connection. */
2436 if (!config.eval_ldb &&
2437 (!strcasecmp(argv[0],"help") || !strcasecmp(argv[0],"?")))
2438 {
2439 cliOutputHelp(--argc, ++argv);
2440 return REDIS_OK;
2441 }
2442
2443 while (1) {
2444 if (config.cluster_reissue_command || context == NULL ||
2445 context->err == REDIS_ERR_IO || context->err == REDIS_ERR_EOF)
2446 {
2447 if (cliConnect(CC_FORCE) != REDIS_OK) {
2448 cliPrintContextError();
2449 config.cluster_reissue_command = 0;
2450 return REDIS_ERR;
2451 }
2452 }
2453 config.cluster_reissue_command = 0;
2454 if (config.cluster_send_asking) {
2455 if (cliSendAsking() != REDIS_OK) {
2456 cliPrintContextError();
2457 return REDIS_ERR;
2458 }
2459 }
2460 if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
2461 cliPrintContextError();
2462 redisFree(context);
2463 context = NULL;
2464 return REDIS_ERR;
2465 }
2466
2467 /* Issue the command again if we got redirected in cluster mode */
2468 if (config.cluster_mode && config.cluster_reissue_command) {
2469 continue;
2470 }
2471 break;
2472 }
2473 return REDIS_OK;
2474}
2475
2476static int issueCommand(int argc, char **argv) {
2477 return issueCommandRepeat(argc, argv, config.repeat);
2478}
2479
2480/* Split the user provided command into multiple SDS arguments.
2481 * This function normally uses sdssplitargs() from sds.c which is able
2482 * to understand "quoted strings", escapes and so forth. However when
2483 * we are in Lua debugging mode and the "eval" command is used, we want
2484 * the remaining Lua script (after "e " or "eval ") to be passed verbatim
2485 * as a single big argument. */
2486static sds *cliSplitArgs(char *line, int *argc) {
2487 if (config.eval_ldb && (strstr(line,"eval ") == line ||
2488 strstr(line,"e ") == line))
2489 {
2490 sds *argv = sds_malloc(sizeof(sds)*2);
2491 *argc = 2;
2492 int len = strlen(line);
2493 int elen = line[1] == ' ' ? 2 : 5; /* "e " or "eval "? */
2494 argv[0] = sdsnewlen(line,elen-1);
2495 argv[1] = sdsnewlen(line+elen,len-elen);
2496 return argv;
2497 } else {
2498 return sdssplitargs(line,argc);
2499 }
2500}
2501
2502/* Set the CLI preferences. This function is invoked when an interactive
2503 * ":command" is called, or when reading ~/.redisclirc file, in order to
2504 * set user preferences. */
2505void cliSetPreferences(char **argv, int argc, int interactive) {
2506 if (!strcasecmp(argv[0],":set") && argc >= 2) {
2507 if (!strcasecmp(argv[1],"hints")) pref.hints = 1;
2508 else if (!strcasecmp(argv[1],"nohints")) pref.hints = 0;
2509 else {
2510 printf("%sunknown redis-cli preference '%s'\n",
2511 interactive ? "" : ".redisclirc: ",
2512 argv[1]);
2513 }
2514 } else {
2515 printf("%sunknown redis-cli internal command '%s'\n",
2516 interactive ? "" : ".redisclirc: ",
2517 argv[0]);
2518 }
2519}
2520
2521/* Load the ~/.redisclirc file if any. */
2522void cliLoadPreferences(void) {
2523 sds rcfile = getDotfilePath(REDIS_CLI_RCFILE_ENV,REDIS_CLI_RCFILE_DEFAULT);
2524 if (rcfile == NULL) return;
2525 FILE *fp = fopen(rcfile,"r");
2526 char buf[1024];
2527
2528 if (fp) {
2529 while(fgets(buf,sizeof(buf),fp) != NULL) {
2530 sds *argv;
2531 int argc;
2532
2533 argv = sdssplitargs(buf,&argc);
2534 if (argc > 0) cliSetPreferences(argv,argc,0);
2535 sdsfreesplitres(argv,argc);
2536 }
2537 fclose(fp);
2538 }
2539 sdsfree(rcfile);
2540}
2541
2542/* Some commands can include sensitive information and shouldn't be put in the
2543 * history file. Currently these commands are include:
2544 * - AUTH
2545 * - ACL SETUSER
2546 * - CONFIG SET masterauth/masteruser/requirepass
2547 * - HELLO with [AUTH username password]
2548 * - MIGRATE with [AUTH password] or [AUTH2 username password] */
2549static int isSensitiveCommand(int argc, char **argv) {
2550 if (!strcasecmp(argv[0],"auth")) {
2551 return 1;
2552 } else if (argc > 1 &&
2553 !strcasecmp(argv[0],"acl") &&
2554 !strcasecmp(argv[1],"setuser"))
2555 {
2556 return 1;
2557 } else if (argc > 2 &&
2558 !strcasecmp(argv[0],"config") &&
2559 !strcasecmp(argv[1],"set") && (
2560 !strcasecmp(argv[2],"masterauth") ||
2561 !strcasecmp(argv[2],"masteruser") ||
2562 !strcasecmp(argv[2],"requirepass")))
2563 {
2564 return 1;
2565 /* HELLO [protover [AUTH username password] [SETNAME clientname]] */
2566 } else if (argc > 4 && !strcasecmp(argv[0],"hello")) {
2567 for (int j = 2; j < argc; j++) {
2568 int moreargs = argc - 1 - j;
2569 if (!strcasecmp(argv[j],"AUTH") && moreargs >= 2) {
2570 return 1;
2571 } else if (!strcasecmp(argv[j],"SETNAME") && moreargs) {
2572 j++;
2573 } else {
2574 return 0;
2575 }
2576 }
2577 /* MIGRATE host port key|"" destination-db timeout [COPY] [REPLACE]
2578 * [AUTH password] [AUTH2 username password] [KEYS key [key ...]] */
2579 } else if (argc > 7 && !strcasecmp(argv[0], "migrate")) {
2580 for (int j = 6; j < argc; j++) {
2581 int moreargs = argc - 1 - j;
2582 if (!strcasecmp(argv[j],"auth") && moreargs) {
2583 return 1;
2584 } else if (!strcasecmp(argv[j],"auth2") && moreargs >= 2) {
2585 return 1;
2586 } else if (!strcasecmp(argv[j],"keys") && moreargs) {
2587 return 0;
2588 }
2589 }
2590 }
2591 return 0;
2592}
2593
2594static void repl(void) {
2595 sds historyfile = NULL;
2596 int history = 0;
2597 char *line;
2598 int argc;
2599 sds *argv;
2600
2601 /* There is no need to initialize redis HELP when we are in lua debugger mode.
2602 * It has its own HELP and commands (COMMAND or COMMAND DOCS will fail and got nothing).
2603 * We will initialize the redis HELP after the Lua debugging session ended.*/
2604 if (!config.eval_ldb) {
2605 /* Initialize the help using the results of the COMMAND command. */
2606 cliInitHelp();
2607 }
2608
2609 config.interactive = 1;
2610 linenoiseSetMultiLine(1);
2611 linenoiseSetCompletionCallback(completionCallback);
2612 linenoiseSetHintsCallback(hintsCallback);
2613 linenoiseSetFreeHintsCallback(freeHintsCallback);
2614
2615 /* Only use history and load the rc file when stdin is a tty. */
2616 if (isatty(fileno(stdin))) {
2617 historyfile = getDotfilePath(REDIS_CLI_HISTFILE_ENV,REDIS_CLI_HISTFILE_DEFAULT);
2618 //keep in-memory history always regardless if history file can be determined
2619 history = 1;
2620 if (historyfile != NULL) {
2621 linenoiseHistoryLoad(historyfile);
2622 }
2623 cliLoadPreferences();
2624 }
2625
2626 cliRefreshPrompt();
2627 while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
2628 if (line[0] != '\0') {
2629 long repeat = 1;
2630 int skipargs = 0;
2631 char *endptr = NULL;
2632
2633 argv = cliSplitArgs(line,&argc);
2634 if (argv == NULL) {
2635 printf("Invalid argument(s)\n");
2636 fflush(stdout);
2637 if (history) linenoiseHistoryAdd(line);
2638 if (historyfile) linenoiseHistorySave(historyfile);
2639 linenoiseFree(line);
2640 continue;
2641 } else if (argc == 0) {
2642 sdsfreesplitres(argv,argc);
2643 linenoiseFree(line);
2644 continue;
2645 }
2646
2647 /* check if we have a repeat command option and
2648 * need to skip the first arg */
2649 errno = 0;
2650 repeat = strtol(argv[0], &endptr, 10);
2651 if (argc > 1 && *endptr == '\0') {
2652 if (errno == ERANGE || errno == EINVAL || repeat <= 0) {
2653 fputs("Invalid redis-cli repeat command option value.\n", stdout);
2654 sdsfreesplitres(argv, argc);
2655 linenoiseFree(line);
2656 continue;
2657 }
2658 skipargs = 1;
2659 } else {
2660 repeat = 1;
2661 }
2662
2663 if (!isSensitiveCommand(argc - skipargs, argv + skipargs)) {
2664 if (history) linenoiseHistoryAdd(line);
2665 if (historyfile) linenoiseHistorySave(historyfile);
2666 }
2667
2668 if (strcasecmp(argv[0],"quit") == 0 ||
2669 strcasecmp(argv[0],"exit") == 0)
2670 {
2671 exit(0);
2672 } else if (argv[0][0] == ':') {
2673 cliSetPreferences(argv,argc,1);
2674 sdsfreesplitres(argv,argc);
2675 linenoiseFree(line);
2676 continue;
2677 } else if (strcasecmp(argv[0],"restart") == 0) {
2678 if (config.eval) {
2679 config.eval_ldb = 1;
2680 config.output = OUTPUT_RAW;
2681 sdsfreesplitres(argv,argc);
2682 linenoiseFree(line);
2683 return; /* Return to evalMode to restart the session. */
2684 } else {
2685 printf("Use 'restart' only in Lua debugging mode.\n");
2686 fflush(stdout);
2687 }
2688 } else if (argc == 3 && !strcasecmp(argv[0],"connect")) {
2689 sdsfree(config.conn_info.hostip);
2690 config.conn_info.hostip = sdsnew(argv[1]);
2691 config.conn_info.hostport = atoi(argv[2]);
2692 cliRefreshPrompt();
2693 cliConnect(CC_FORCE);
2694 } else if (argc == 1 && !strcasecmp(argv[0],"clear")) {
2695 linenoiseClearScreen();
2696 } else {
2697 long long start_time = mstime(), elapsed;
2698
2699 issueCommandRepeat(argc-skipargs, argv+skipargs, repeat);
2700
2701 /* If our debugging session ended, show the EVAL final
2702 * reply. */
2703 if (config.eval_ldb_end) {
2704 config.eval_ldb_end = 0;
2705 cliReadReply(0);
2706 printf("\n(Lua debugging session ended%s)\n\n",
2707 config.eval_ldb_sync ? "" :
2708 " -- dataset changes rolled back");
2709 cliInitHelp();
2710 }
2711
2712 elapsed = mstime()-start_time;
2713 if (elapsed >= 500 &&
2714 config.output == OUTPUT_STANDARD)
2715 {
2716 printf("(%.2fs)\n",(double)elapsed/1000);
2717 }
2718 }
2719 /* Free the argument vector */
2720 sdsfreesplitres(argv,argc);
2721 }
2722 /* linenoise() returns malloc-ed lines like readline() */
2723 linenoiseFree(line);
2724 }
2725 exit(0);
2726}
2727
2728static int noninteractive(int argc, char **argv) {
2729 int retval = 0;
2730 sds *sds_args = getSdsArrayFromArgv(argc, argv, config.quoted_input);
2731
2732 if (!sds_args) {
2733 printf("Invalid quoted string\n");
2734 return 1;
2735 }
2736
2737 if (config.stdin_lastarg) {
2738 sds_args = sds_realloc(sds_args, (argc + 1) * sizeof(sds));
2739 sds_args[argc] = readArgFromStdin();
2740 argc++;
2741 } else if (config.stdin_tag_arg) {
2742 int i = 0, tag_match = 0;
2743
2744 for (; i < argc; i++) {
2745 if (strcmp(config.stdin_tag_name, sds_args[i]) != 0) continue;
2746
2747 tag_match = 1;
2748 sdsfree(sds_args[i]);
2749 sds_args[i] = readArgFromStdin();
2750 break;
2751 }
2752
2753 if (!tag_match) {
2754 sdsfreesplitres(sds_args, argc);
2755 fprintf(stderr, "Using -X option but stdin tag not match.\n");
2756 return 1;
2757 }
2758 }
2759
2760 retval = issueCommand(argc, sds_args);
2761 sdsfreesplitres(sds_args, argc);
2762 return retval == REDIS_OK ? 0 : 1;
2763}
2764
2765/*------------------------------------------------------------------------------
2766 * Eval mode
2767 *--------------------------------------------------------------------------- */
2768
2769static int evalMode(int argc, char **argv) {
2770 sds script = NULL;
2771 FILE *fp;
2772 char buf[1024];
2773 size_t nread;
2774 char **argv2;
2775 int j, got_comma, keys;
2776 int retval = REDIS_OK;
2777
2778 while(1) {
2779 if (config.eval_ldb) {
2780 printf(
2781 "Lua debugging session started, please use:\n"
2782 "quit -- End the session.\n"
2783 "restart -- Restart the script in debug mode again.\n"
2784 "help -- Show Lua script debugging commands.\n\n"
2785 );
2786 }
2787
2788 sdsfree(script);
2789 script = sdsempty();
2790 got_comma = 0;
2791 keys = 0;
2792
2793 /* Load the script from the file, as an sds string. */
2794 fp = fopen(config.eval,"r");
2795 if (!fp) {
2796 fprintf(stderr,
2797 "Can't open file '%s': %s\n", config.eval, strerror(errno));
2798 exit(1);
2799 }
2800 while((nread = fread(buf,1,sizeof(buf),fp)) != 0) {
2801 script = sdscatlen(script,buf,nread);
2802 }
2803 fclose(fp);
2804
2805 /* If we are debugging a script, enable the Lua debugger. */
2806 if (config.eval_ldb) {
2807 redisReply *reply = redisCommand(context,
2808 config.eval_ldb_sync ?
2809 "SCRIPT DEBUG sync": "SCRIPT DEBUG yes");
2810 if (reply) freeReplyObject(reply);
2811 }
2812
2813 /* Create our argument vector */
2814 argv2 = zmalloc(sizeof(sds)*(argc+3));
2815 argv2[0] = sdsnew("EVAL");
2816 argv2[1] = script;
2817 for (j = 0; j < argc; j++) {
2818 if (!got_comma && argv[j][0] == ',' && argv[j][1] == 0) {
2819 got_comma = 1;
2820 continue;
2821 }
2822 argv2[j+3-got_comma] = sdsnew(argv[j]);
2823 if (!got_comma) keys++;
2824 }
2825 argv2[2] = sdscatprintf(sdsempty(),"%d",keys);
2826
2827 /* Call it */
2828 int eval_ldb = config.eval_ldb; /* Save it, may be reverted. */
2829 retval = issueCommand(argc+3-got_comma, argv2);
2830 if (eval_ldb) {
2831 if (!config.eval_ldb) {
2832 /* If the debugging session ended immediately, there was an
2833 * error compiling the script. Show it and they don't enter
2834 * the REPL at all. */
2835 printf("Eval debugging session can't start:\n");
2836 cliReadReply(0);
2837 break; /* Return to the caller. */
2838 } else {
2839 strncpy(config.prompt,"lua debugger> ",sizeof(config.prompt));
2840 repl();
2841 /* Restart the session if repl() returned. */
2842 cliConnect(CC_FORCE);
2843 printf("\n");
2844 }
2845 } else {
2846 break; /* Return to the caller. */
2847 }
2848 }
2849 return retval == REDIS_OK ? 0 : 1;
2850}
2851
2852/*------------------------------------------------------------------------------
2853 * Cluster Manager
2854 *--------------------------------------------------------------------------- */
2855
2856/* The Cluster Manager global structure */
2857static struct clusterManager {
2858 list *nodes; /* List of nodes in the configuration. */
2859 list *errors;
2860 int unreachable_masters; /* Masters we are not able to reach. */
2861} cluster_manager;
2862
2863/* Used by clusterManagerFixSlotsCoverage */
2864dict *clusterManagerUncoveredSlots = NULL;
2865
2866typedef struct clusterManagerNode {
2867 redisContext *context;
2868 sds name;
2869 char *ip;
2870 int port;
2871 int bus_port; /* cluster-port */
2872 uint64_t current_epoch;
2873 time_t ping_sent;
2874 time_t ping_recv;
2875 int flags;
2876 list *flags_str; /* Flags string representations */
2877 sds replicate; /* Master ID if node is a slave */
2878 int dirty; /* Node has changes that can be flushed */
2879 uint8_t slots[CLUSTER_MANAGER_SLOTS];
2880 int slots_count;
2881 int replicas_count;
2882 list *friends;
2883 sds *migrating; /* An array of sds where even strings are slots and odd
2884 * strings are the destination node IDs. */
2885 sds *importing; /* An array of sds where even strings are slots and odd
2886 * strings are the source node IDs. */
2887 int migrating_count; /* Length of the migrating array (migrating slots*2) */
2888 int importing_count; /* Length of the importing array (importing slots*2) */
2889 float weight; /* Weight used by rebalance */
2890 int balance; /* Used by rebalance */
2891} clusterManagerNode;
2892
2893/* Data structure used to represent a sequence of cluster nodes. */
2894typedef struct clusterManagerNodeArray {
2895 clusterManagerNode **nodes; /* Actual nodes array */
2896 clusterManagerNode **alloc; /* Pointer to the allocated memory */
2897 int len; /* Actual length of the array */
2898 int count; /* Non-NULL nodes count */
2899} clusterManagerNodeArray;
2900
2901/* Used for the reshard table. */
2902typedef struct clusterManagerReshardTableItem {
2903 clusterManagerNode *source;
2904 int slot;
2905} clusterManagerReshardTableItem;
2906
2907/* Info about a cluster internal link. */
2908
2909typedef struct clusterManagerLink {
2910 sds node_name;
2911 sds node_addr;
2912 int connected;
2913 int handshaking;
2914} clusterManagerLink;
2915
2916static dictType clusterManagerDictType = {
2917 dictSdsHash, /* hash function */
2918 NULL, /* key dup */
2919 NULL, /* val dup */
2920 dictSdsKeyCompare, /* key compare */
2921 NULL, /* key destructor */
2922 dictSdsDestructor, /* val destructor */
2923 NULL /* allow to expand */
2924};
2925
2926static dictType clusterManagerLinkDictType = {
2927 dictSdsHash, /* hash function */
2928 NULL, /* key dup */
2929 NULL, /* val dup */
2930 dictSdsKeyCompare, /* key compare */
2931 dictSdsDestructor, /* key destructor */
2932 dictListDestructor, /* val destructor */
2933 NULL /* allow to expand */
2934};
2935
2936typedef int clusterManagerCommandProc(int argc, char **argv);
2937typedef int (*clusterManagerOnReplyError)(redisReply *reply,
2938 clusterManagerNode *n, int bulk_idx);
2939
2940/* Cluster Manager helper functions */
2941
2942static clusterManagerNode *clusterManagerNewNode(char *ip, int port, int bus_port);
2943static clusterManagerNode *clusterManagerNodeByName(const char *name);
2944static clusterManagerNode *clusterManagerNodeByAbbreviatedName(const char *n);
2945static void clusterManagerNodeResetSlots(clusterManagerNode *node);
2946static int clusterManagerNodeIsCluster(clusterManagerNode *node, char **err);
2947static void clusterManagerPrintNotClusterNodeError(clusterManagerNode *node,
2948 char *err);
2949static int clusterManagerNodeLoadInfo(clusterManagerNode *node, int opts,
2950 char **err);
2951static int clusterManagerLoadInfoFromNode(clusterManagerNode *node);
2952static int clusterManagerNodeIsEmpty(clusterManagerNode *node, char **err);
2953static int clusterManagerGetAntiAffinityScore(clusterManagerNodeArray *ipnodes,
2954 int ip_count, clusterManagerNode ***offending, int *offending_len);
2955static void clusterManagerOptimizeAntiAffinity(clusterManagerNodeArray *ipnodes,
2956 int ip_count);
2957static sds clusterManagerNodeInfo(clusterManagerNode *node, int indent);
2958static void clusterManagerShowNodes(void);
2959static void clusterManagerShowClusterInfo(void);
2960static int clusterManagerFlushNodeConfig(clusterManagerNode *node, char **err);
2961static void clusterManagerWaitForClusterJoin(void);
2962static int clusterManagerCheckCluster(int quiet);
2963static void clusterManagerLog(int level, const char* fmt, ...);
2964static int clusterManagerIsConfigConsistent(void);
2965static dict *clusterManagerGetLinkStatus(void);
2966static void clusterManagerOnError(sds err);
2967static void clusterManagerNodeArrayInit(clusterManagerNodeArray *array,
2968 int len);
2969static void clusterManagerNodeArrayReset(clusterManagerNodeArray *array);
2970static void clusterManagerNodeArrayShift(clusterManagerNodeArray *array,
2971 clusterManagerNode **nodeptr);
2972static void clusterManagerNodeArrayAdd(clusterManagerNodeArray *array,
2973 clusterManagerNode *node);
2974
2975/* Cluster Manager commands. */
2976
2977static int clusterManagerCommandCreate(int argc, char **argv);
2978static int clusterManagerCommandAddNode(int argc, char **argv);
2979static int clusterManagerCommandDeleteNode(int argc, char **argv);
2980static int clusterManagerCommandInfo(int argc, char **argv);
2981static int clusterManagerCommandCheck(int argc, char **argv);
2982static int clusterManagerCommandFix(int argc, char **argv);
2983static int clusterManagerCommandReshard(int argc, char **argv);
2984static int clusterManagerCommandRebalance(int argc, char **argv);
2985static int clusterManagerCommandSetTimeout(int argc, char **argv);
2986static int clusterManagerCommandImport(int argc, char **argv);
2987static int clusterManagerCommandCall(int argc, char **argv);
2988static int clusterManagerCommandHelp(int argc, char **argv);
2989static int clusterManagerCommandBackup(int argc, char **argv);
2990
2991typedef struct clusterManagerCommandDef {
2992 char *name;
2993 clusterManagerCommandProc *proc;
2994 int arity;
2995 char *args;
2996 char *options;
2997} clusterManagerCommandDef;
2998
2999clusterManagerCommandDef clusterManagerCommands[] = {
3000 {"create", clusterManagerCommandCreate, -2, "host1:port1 ... hostN:portN",
3001 "replicas <arg>"},
3002 {"check", clusterManagerCommandCheck, -1, "<host:port> or <host> <port> - separated by either colon or space",
3003 "search-multiple-owners"},
3004 {"info", clusterManagerCommandInfo, -1, "<host:port> or <host> <port> - separated by either colon or space", NULL},
3005 {"fix", clusterManagerCommandFix, -1, "<host:port> or <host> <port> - separated by either colon or space",
3006 "search-multiple-owners,fix-with-unreachable-masters"},
3007 {"reshard", clusterManagerCommandReshard, -1, "<host:port> or <host> <port> - separated by either colon or space",
3008 "from <arg>,to <arg>,slots <arg>,yes,timeout <arg>,pipeline <arg>,"
3009 "replace"},
3010 {"rebalance", clusterManagerCommandRebalance, -1, "<host:port> or <host> <port> - separated by either colon or space",
3011 "weight <node1=w1...nodeN=wN>,use-empty-masters,"
3012 "timeout <arg>,simulate,pipeline <arg>,threshold <arg>,replace"},
3013 {"add-node", clusterManagerCommandAddNode, 2,
3014 "new_host:new_port existing_host:existing_port", "slave,master-id <arg>"},
3015 {"del-node", clusterManagerCommandDeleteNode, 2, "host:port node_id",NULL},
3016 {"call", clusterManagerCommandCall, -2,
3017 "host:port command arg arg .. arg", "only-masters,only-replicas"},
3018 {"set-timeout", clusterManagerCommandSetTimeout, 2,
3019 "host:port milliseconds", NULL},
3020 {"import", clusterManagerCommandImport, 1, "host:port",
3021 "from <arg>,from-user <arg>,from-pass <arg>,from-askpass,copy,replace"},
3022 {"backup", clusterManagerCommandBackup, 2, "host:port backup_directory",
3023 NULL},
3024 {"help", clusterManagerCommandHelp, 0, NULL, NULL}
3025};
3026
3027typedef struct clusterManagerOptionDef {
3028 char *name;
3029 char *desc;
3030} clusterManagerOptionDef;
3031
3032clusterManagerOptionDef clusterManagerOptions[] = {
3033 {"--cluster-yes", "Automatic yes to cluster commands prompts"}
3034};
3035
3036static void getRDB(clusterManagerNode *node);
3037
3038static int createClusterManagerCommand(char *cmdname, int argc, char **argv) {
3039 clusterManagerCommand *cmd = &config.cluster_manager_command;
3040 cmd->name = cmdname;
3041 cmd->argc = argc;
3042 cmd->argv = argc ? argv : NULL;
3043 if (isColorTerm()) cmd->flags |= CLUSTER_MANAGER_CMD_FLAG_COLOR;
3044
3045 if (config.stdin_lastarg) {
3046 char **new_argv = zmalloc(sizeof(char*) * (cmd->argc+1));
3047 memcpy(new_argv, cmd->argv, sizeof(char*) * cmd->argc);
3048
3049 cmd->stdin_arg = readArgFromStdin();
3050 new_argv[cmd->argc++] = cmd->stdin_arg;
3051 cmd->argv = new_argv;
3052 } else if (config.stdin_tag_arg) {
3053 int i = 0, tag_match = 0;
3054 cmd->stdin_arg = readArgFromStdin();
3055
3056 for (; i < argc; i++) {
3057 if (strcmp(argv[i], config.stdin_tag_name) != 0) continue;
3058
3059 tag_match = 1;
3060 cmd->argv[i] = (char *)cmd->stdin_arg;
3061 break;
3062 }
3063
3064 if (!tag_match) {
3065 sdsfree(cmd->stdin_arg);
3066 fprintf(stderr, "Using -X option but stdin tag not match.\n");
3067 return 1;
3068 }
3069 }
3070
3071 return 0;
3072}
3073
3074static clusterManagerCommandProc *validateClusterManagerCommand(void) {
3075 int i, commands_count = sizeof(clusterManagerCommands) /
3076 sizeof(clusterManagerCommandDef);
3077 clusterManagerCommandProc *proc = NULL;
3078 char *cmdname = config.cluster_manager_command.name;
3079 int argc = config.cluster_manager_command.argc;
3080 for (i = 0; i < commands_count; i++) {
3081 clusterManagerCommandDef cmddef = clusterManagerCommands[i];
3082 if (!strcmp(cmddef.name, cmdname)) {
3083 if ((cmddef.arity > 0 && argc != cmddef.arity) ||
3084 (cmddef.arity < 0 && argc < (cmddef.arity * -1))) {
3085 fprintf(stderr, "[ERR] Wrong number of arguments for "
3086 "specified --cluster sub command\n");
3087 return NULL;
3088 }
3089 proc = cmddef.proc;
3090 }
3091 }
3092 if (!proc) fprintf(stderr, "Unknown --cluster subcommand\n");
3093 return proc;
3094}
3095
3096static int parseClusterNodeAddress(char *addr, char **ip_ptr, int *port_ptr,
3097 int *bus_port_ptr)
3098{
3099 /* ip:port[@bus_port] */
3100 char *c = strrchr(addr, '@');
3101 if (c != NULL) {
3102 *c = '\0';
3103 if (bus_port_ptr != NULL)
3104 *bus_port_ptr = atoi(c + 1);
3105 }
3106 c = strrchr(addr, ':');
3107 if (c != NULL) {
3108 *c = '\0';
3109 *ip_ptr = addr;
3110 *port_ptr = atoi(++c);
3111 } else return 0;
3112 return 1;
3113}
3114
3115/* Get host ip and port from command arguments. If only one argument has
3116 * been provided it must be in the form of 'ip:port', elsewhere
3117 * the first argument must be the ip and the second one the port.
3118 * If host and port can be detected, it returns 1 and it stores host and
3119 * port into variables referenced by 'ip_ptr' and 'port_ptr' pointers,
3120 * elsewhere it returns 0. */
3121static int getClusterHostFromCmdArgs(int argc, char **argv,
3122 char **ip_ptr, int *port_ptr) {
3123 int port = 0;
3124 char *ip = NULL;
3125 if (argc == 1) {
3126 char *addr = argv[0];
3127 if (!parseClusterNodeAddress(addr, &ip, &port, NULL)) return 0;
3128 } else {
3129 ip = argv[0];
3130 port = atoi(argv[1]);
3131 }
3132 if (!ip || !port) return 0;
3133 else {
3134 *ip_ptr = ip;
3135 *port_ptr = port;
3136 }
3137 return 1;
3138}
3139
3140static void freeClusterManagerNodeFlags(list *flags) {
3141 listIter li;
3142 listNode *ln;
3143 listRewind(flags, &li);
3144 while ((ln = listNext(&li)) != NULL) {
3145 sds flag = ln->value;
3146 sdsfree(flag);
3147 }
3148 listRelease(flags);
3149}
3150
3151static void freeClusterManagerNode(clusterManagerNode *node) {
3152 if (node->context != NULL) redisFree(node->context);
3153 if (node->friends != NULL) {
3154 listIter li;
3155 listNode *ln;
3156 listRewind(node->friends,&li);
3157 while ((ln = listNext(&li)) != NULL) {
3158 clusterManagerNode *fn = ln->value;
3159 freeClusterManagerNode(fn);
3160 }
3161 listRelease(node->friends);
3162 node->friends = NULL;
3163 }
3164 if (node->name != NULL) sdsfree(node->name);
3165 if (node->replicate != NULL) sdsfree(node->replicate);
3166 if ((node->flags & CLUSTER_MANAGER_FLAG_FRIEND) && node->ip)
3167 sdsfree(node->ip);
3168 int i;
3169 if (node->migrating != NULL) {
3170 for (i = 0; i < node->migrating_count; i++) sdsfree(node->migrating[i]);
3171 zfree(node->migrating);
3172 }
3173 if (node->importing != NULL) {
3174 for (i = 0; i < node->importing_count; i++) sdsfree(node->importing[i]);
3175 zfree(node->importing);
3176 }
3177 if (node->flags_str != NULL) {
3178 freeClusterManagerNodeFlags(node->flags_str);
3179 node->flags_str = NULL;
3180 }
3181 zfree(node);
3182}
3183
3184static void freeClusterManager(void) {
3185 listIter li;
3186 listNode *ln;
3187 if (cluster_manager.nodes != NULL) {
3188 listRewind(cluster_manager.nodes,&li);
3189 while ((ln = listNext(&li)) != NULL) {
3190 clusterManagerNode *n = ln->value;
3191 freeClusterManagerNode(n);
3192 }
3193 listRelease(cluster_manager.nodes);
3194 cluster_manager.nodes = NULL;
3195 }
3196 if (cluster_manager.errors != NULL) {
3197 listRewind(cluster_manager.errors,&li);
3198 while ((ln = listNext(&li)) != NULL) {
3199 sds err = ln->value;
3200 sdsfree(err);
3201 }
3202 listRelease(cluster_manager.errors);
3203 cluster_manager.errors = NULL;
3204 }
3205 if (clusterManagerUncoveredSlots != NULL)
3206 dictRelease(clusterManagerUncoveredSlots);
3207}
3208
3209static clusterManagerNode *clusterManagerNewNode(char *ip, int port, int bus_port) {
3210 clusterManagerNode *node = zmalloc(sizeof(*node));
3211 node->context = NULL;
3212 node->name = NULL;
3213 node->ip = ip;
3214 node->port = port;
3215 /* We don't need to know the bus_port, at this point this value may be wrong.
3216 * If it is used, it will be corrected in clusterManagerLoadInfoFromNode. */
3217 node->bus_port = bus_port ? bus_port : port + CLUSTER_MANAGER_PORT_INCR;
3218 node->current_epoch = 0;
3219 node->ping_sent = 0;
3220 node->ping_recv = 0;
3221 node->flags = 0;
3222 node->flags_str = NULL;
3223 node->replicate = NULL;
3224 node->dirty = 0;
3225 node->friends = NULL;
3226 node->migrating = NULL;
3227 node->importing = NULL;
3228 node->migrating_count = 0;
3229 node->importing_count = 0;
3230 node->replicas_count = 0;
3231 node->weight = 1.0f;
3232 node->balance = 0;
3233 clusterManagerNodeResetSlots(node);
3234 return node;
3235}
3236
3237static sds clusterManagerGetNodeRDBFilename(clusterManagerNode *node) {
3238 assert(config.cluster_manager_command.backup_dir);
3239 sds filename = sdsnew(config.cluster_manager_command.backup_dir);
3240 if (filename[sdslen(filename) - 1] != '/')
3241 filename = sdscat(filename, "/");
3242 filename = sdscatprintf(filename, "redis-node-%s-%d-%s.rdb", node->ip,
3243 node->port, node->name);
3244 return filename;
3245}
3246
3247/* Check whether reply is NULL or its type is REDIS_REPLY_ERROR. In the
3248 * latest case, if the 'err' arg is not NULL, it gets allocated with a copy
3249 * of reply error (it's up to the caller function to free it), elsewhere
3250 * the error is directly printed. */
3251static int clusterManagerCheckRedisReply(clusterManagerNode *n,
3252 redisReply *r, char **err)
3253{
3254 int is_err = 0;
3255 if (!r || (is_err = (r->type == REDIS_REPLY_ERROR))) {
3256 if (is_err) {
3257 if (err != NULL) {
3258 *err = zmalloc((r->len + 1) * sizeof(char));
3259 strcpy(*err, r->str);
3260 } else CLUSTER_MANAGER_PRINT_REPLY_ERROR(n, r->str);
3261 }
3262 return 0;
3263 }
3264 return 1;
3265}
3266
3267/* Call MULTI command on a cluster node. */
3268static int clusterManagerStartTransaction(clusterManagerNode *node) {
3269 redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "MULTI");
3270 int success = clusterManagerCheckRedisReply(node, reply, NULL);
3271 if (reply) freeReplyObject(reply);
3272 return success;
3273}
3274
3275/* Call EXEC command on a cluster node. */
3276static int clusterManagerExecTransaction(clusterManagerNode *node,
3277 clusterManagerOnReplyError onerror)
3278{
3279 redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "EXEC");
3280 int success = clusterManagerCheckRedisReply(node, reply, NULL);
3281 if (success) {
3282 if (reply->type != REDIS_REPLY_ARRAY) {
3283 success = 0;
3284 goto cleanup;
3285 }
3286 size_t i;
3287 for (i = 0; i < reply->elements; i++) {
3288 redisReply *r = reply->element[i];
3289 char *err = NULL;
3290 success = clusterManagerCheckRedisReply(node, r, &err);
3291 if (!success && onerror) success = onerror(r, node, i);
3292 if (err) {
3293 if (!success)
3294 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
3295 zfree(err);
3296 }
3297 if (!success) break;
3298 }
3299 }
3300cleanup:
3301 if (reply) freeReplyObject(reply);
3302 return success;
3303}
3304
3305static int clusterManagerNodeConnect(clusterManagerNode *node) {
3306 if (node->context) redisFree(node->context);
3307 node->context = redisConnect(node->ip, node->port);
3308 if (!node->context->err && config.tls) {
3309 const char *err = NULL;
3310 if (cliSecureConnection(node->context, config.sslconfig, &err) == REDIS_ERR && err) {
3311 fprintf(stderr,"TLS Error: %s\n", err);
3312 redisFree(node->context);
3313 node->context = NULL;
3314 return 0;
3315 }
3316 }
3317 if (node->context->err) {
3318 fprintf(stderr,"Could not connect to Redis at ");
3319 fprintf(stderr,"%s:%d: %s\n", node->ip, node->port,
3320 node->context->errstr);
3321 redisFree(node->context);
3322 node->context = NULL;
3323 return 0;
3324 }
3325 /* Set aggressive KEEP_ALIVE socket option in the Redis context socket
3326 * in order to prevent timeouts caused by the execution of long
3327 * commands. At the same time this improves the detection of real
3328 * errors. */
3329 anetKeepAlive(NULL, node->context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
3330 if (config.conn_info.auth) {
3331 redisReply *reply;
3332 if (config.conn_info.user == NULL)
3333 reply = redisCommand(node->context,"AUTH %s", config.conn_info.auth);
3334 else
3335 reply = redisCommand(node->context,"AUTH %s %s",
3336 config.conn_info.user,config.conn_info.auth);
3337 int ok = clusterManagerCheckRedisReply(node, reply, NULL);
3338 if (reply != NULL) freeReplyObject(reply);
3339 if (!ok) return 0;
3340 }
3341 return 1;
3342}
3343
3344static void clusterManagerRemoveNodeFromList(list *nodelist,
3345 clusterManagerNode *node) {
3346 listIter li;
3347 listNode *ln;
3348 listRewind(nodelist, &li);
3349 while ((ln = listNext(&li)) != NULL) {
3350 if (node == ln->value) {
3351 listDelNode(nodelist, ln);
3352 break;
3353 }
3354 }
3355}
3356
3357/* Return the node with the specified name (ID) or NULL. */
3358static clusterManagerNode *clusterManagerNodeByName(const char *name) {
3359 if (cluster_manager.nodes == NULL) return NULL;
3360 clusterManagerNode *found = NULL;
3361 sds lcname = sdsempty();
3362 lcname = sdscpy(lcname, name);
3363 sdstolower(lcname);
3364 listIter li;
3365 listNode *ln;
3366 listRewind(cluster_manager.nodes, &li);
3367 while ((ln = listNext(&li)) != NULL) {
3368 clusterManagerNode *n = ln->value;
3369 if (n->name && !sdscmp(n->name, lcname)) {
3370 found = n;
3371 break;
3372 }
3373 }
3374 sdsfree(lcname);
3375 return found;
3376}
3377
3378/* Like clusterManagerNodeByName but the specified name can be just the first
3379 * part of the node ID as long as the prefix in unique across the
3380 * cluster.
3381 */
3382static clusterManagerNode *clusterManagerNodeByAbbreviatedName(const char*name)
3383{
3384 if (cluster_manager.nodes == NULL) return NULL;
3385 clusterManagerNode *found = NULL;
3386 sds lcname = sdsempty();
3387 lcname = sdscpy(lcname, name);
3388 sdstolower(lcname);
3389 listIter li;
3390 listNode *ln;
3391 listRewind(cluster_manager.nodes, &li);
3392 while ((ln = listNext(&li)) != NULL) {
3393 clusterManagerNode *n = ln->value;
3394 if (n->name &&
3395 strstr(n->name, lcname) == n->name) {
3396 found = n;
3397 break;
3398 }
3399 }
3400 sdsfree(lcname);
3401 return found;
3402}
3403
3404static void clusterManagerNodeResetSlots(clusterManagerNode *node) {
3405 memset(node->slots, 0, sizeof(node->slots));
3406 node->slots_count = 0;
3407}
3408
3409/* Call "INFO" redis command on the specified node and return the reply. */
3410static redisReply *clusterManagerGetNodeRedisInfo(clusterManagerNode *node,
3411 char **err)
3412{
3413 redisReply *info = CLUSTER_MANAGER_COMMAND(node, "INFO");
3414 if (err != NULL) *err = NULL;
3415 if (info == NULL) return NULL;
3416 if (info->type == REDIS_REPLY_ERROR) {
3417 if (err != NULL) {
3418 *err = zmalloc((info->len + 1) * sizeof(char));
3419 strcpy(*err, info->str);
3420 }
3421 freeReplyObject(info);
3422 return NULL;
3423 }
3424 return info;
3425}
3426
3427static int clusterManagerNodeIsCluster(clusterManagerNode *node, char **err) {
3428 redisReply *info = clusterManagerGetNodeRedisInfo(node, err);
3429 if (info == NULL) return 0;
3430 int is_cluster = (int) getLongInfoField(info->str, "cluster_enabled");
3431 freeReplyObject(info);
3432 return is_cluster;
3433}
3434
3435/* Checks whether the node is empty. Node is considered not-empty if it has
3436 * some key or if it already knows other nodes */
3437static int clusterManagerNodeIsEmpty(clusterManagerNode *node, char **err) {
3438 redisReply *info = clusterManagerGetNodeRedisInfo(node, err);
3439 int is_empty = 1;
3440 if (info == NULL) return 0;
3441 if (strstr(info->str, "db0:") != NULL) {
3442 is_empty = 0;
3443 goto result;
3444 }
3445 freeReplyObject(info);
3446 info = CLUSTER_MANAGER_COMMAND(node, "CLUSTER INFO");
3447 if (err != NULL) *err = NULL;
3448 if (!clusterManagerCheckRedisReply(node, info, err)) {
3449 is_empty = 0;
3450 goto result;
3451 }
3452 long known_nodes = getLongInfoField(info->str, "cluster_known_nodes");
3453 is_empty = (known_nodes == 1);
3454result:
3455 freeReplyObject(info);
3456 return is_empty;
3457}
3458
3459/* Return the anti-affinity score, which is a measure of the amount of
3460 * violations of anti-affinity in the current cluster layout, that is, how
3461 * badly the masters and slaves are distributed in the different IP
3462 * addresses so that slaves of the same master are not in the master
3463 * host and are also in different hosts.
3464 *
3465 * The score is calculated as follows:
3466 *
3467 * SAME_AS_MASTER = 10000 * each slave in the same IP of its master.
3468 * SAME_AS_SLAVE = 1 * each slave having the same IP as another slave
3469 of the same master.
3470 * FINAL_SCORE = SAME_AS_MASTER + SAME_AS_SLAVE
3471 *
3472 * So a greater score means a worse anti-affinity level, while zero
3473 * means perfect anti-affinity.
3474 *
3475 * The anti affinity optimization will try to get a score as low as
3476 * possible. Since we do not want to sacrifice the fact that slaves should
3477 * not be in the same host as the master, we assign 10000 times the score
3478 * to this violation, so that we'll optimize for the second factor only
3479 * if it does not impact the first one.
3480 *
3481 * The ipnodes argument is an array of clusterManagerNodeArray, one for
3482 * each IP, while ip_count is the total number of IPs in the configuration.
3483 *
3484 * The function returns the above score, and the list of
3485 * offending slaves can be stored into the 'offending' argument,
3486 * so that the optimizer can try changing the configuration of the
3487 * slaves violating the anti-affinity goals. */
3488static int clusterManagerGetAntiAffinityScore(clusterManagerNodeArray *ipnodes,
3489 int ip_count, clusterManagerNode ***offending, int *offending_len)
3490{
3491 int score = 0, i, j;
3492 int node_len = cluster_manager.nodes->len;
3493 clusterManagerNode **offending_p = NULL;
3494 if (offending != NULL) {
3495 *offending = zcalloc(node_len * sizeof(clusterManagerNode*));
3496 offending_p = *offending;
3497 }
3498 /* For each set of nodes in the same host, split by
3499 * related nodes (masters and slaves which are involved in
3500 * replication of each other) */
3501 for (i = 0; i < ip_count; i++) {
3502 clusterManagerNodeArray *node_array = &(ipnodes[i]);
3503 dict *related = dictCreate(&clusterManagerDictType);
3504 char *ip = NULL;
3505 for (j = 0; j < node_array->len; j++) {
3506 clusterManagerNode *node = node_array->nodes[j];
3507 if (node == NULL) continue;
3508 if (!ip) ip = node->ip;
3509 sds types;
3510 /* We always use the Master ID as key. */
3511 sds key = (!node->replicate ? node->name : node->replicate);
3512 assert(key != NULL);
3513 dictEntry *entry = dictFind(related, key);
3514 if (entry) types = sdsdup((sds) dictGetVal(entry));
3515 else types = sdsempty();
3516 /* Master type 'm' is always set as the first character of the
3517 * types string. */
3518 if (node->replicate) types = sdscat(types, "s");
3519 else {
3520 sds s = sdscatsds(sdsnew("m"), types);
3521 sdsfree(types);
3522 types = s;
3523 }
3524 dictReplace(related, key, types);
3525 }
3526 /* Now it's trivial to check, for each related group having the
3527 * same host, what is their local score. */
3528 dictIterator *iter = dictGetIterator(related);
3529 dictEntry *entry;
3530 while ((entry = dictNext(iter)) != NULL) {
3531 sds types = (sds) dictGetVal(entry);
3532 sds name = (sds) dictGetKey(entry);
3533 int typeslen = sdslen(types);
3534 if (typeslen < 2) continue;
3535 if (types[0] == 'm') score += (10000 * (typeslen - 1));
3536 else score += (1 * typeslen);
3537 if (offending == NULL) continue;
3538 /* Populate the list of offending nodes. */
3539 listIter li;
3540 listNode *ln;
3541 listRewind(cluster_manager.nodes, &li);
3542 while ((ln = listNext(&li)) != NULL) {
3543 clusterManagerNode *n = ln->value;
3544 if (n->replicate == NULL) continue;
3545 if (!strcmp(n->replicate, name) && !strcmp(n->ip, ip)) {
3546 *(offending_p++) = n;
3547 if (offending_len != NULL) (*offending_len)++;
3548 break;
3549 }
3550 }
3551 }
3552 //if (offending_len != NULL) *offending_len = offending_p - *offending;
3553 dictReleaseIterator(iter);
3554 dictRelease(related);
3555 }
3556 return score;
3557}
3558
3559static void clusterManagerOptimizeAntiAffinity(clusterManagerNodeArray *ipnodes,
3560 int ip_count)
3561{
3562 clusterManagerNode **offenders = NULL;
3563 int score = clusterManagerGetAntiAffinityScore(ipnodes, ip_count,
3564 NULL, NULL);
3565 if (score == 0) goto cleanup;
3566 clusterManagerLogInfo(">>> Trying to optimize slaves allocation "
3567 "for anti-affinity\n");
3568 int node_len = cluster_manager.nodes->len;
3569 int maxiter = 500 * node_len; // Effort is proportional to cluster size...
3570 srand(time(NULL));
3571 while (maxiter > 0) {
3572 int offending_len = 0;
3573 if (offenders != NULL) {
3574 zfree(offenders);
3575 offenders = NULL;
3576 }
3577 score = clusterManagerGetAntiAffinityScore(ipnodes,
3578 ip_count,
3579 &offenders,
3580 &offending_len);
3581 if (score == 0 || offending_len == 0) break; // Optimal anti affinity reached
3582 /* We'll try to randomly swap a slave's assigned master causing
3583 * an affinity problem with another random slave, to see if we
3584 * can improve the affinity. */
3585 int rand_idx = rand() % offending_len;
3586 clusterManagerNode *first = offenders[rand_idx],
3587 *second = NULL;
3588 clusterManagerNode **other_replicas = zcalloc((node_len - 1) *
3589 sizeof(*other_replicas));
3590 int other_replicas_count = 0;
3591 listIter li;
3592 listNode *ln;
3593 listRewind(cluster_manager.nodes, &li);
3594 while ((ln = listNext(&li)) != NULL) {
3595 clusterManagerNode *n = ln->value;
3596 if (n != first && n->replicate != NULL)
3597 other_replicas[other_replicas_count++] = n;
3598 }
3599 if (other_replicas_count == 0) {
3600 zfree(other_replicas);
3601 break;
3602 }
3603 rand_idx = rand() % other_replicas_count;
3604 second = other_replicas[rand_idx];
3605 char *first_master = first->replicate,
3606 *second_master = second->replicate;
3607 first->replicate = second_master, first->dirty = 1;
3608 second->replicate = first_master, second->dirty = 1;
3609 int new_score = clusterManagerGetAntiAffinityScore(ipnodes,
3610 ip_count,
3611 NULL, NULL);
3612 /* If the change actually makes thing worse, revert. Otherwise
3613 * leave as it is because the best solution may need a few
3614 * combined swaps. */
3615 if (new_score > score) {
3616 first->replicate = first_master;
3617 second->replicate = second_master;
3618 }
3619 zfree(other_replicas);
3620 maxiter--;
3621 }
3622 score = clusterManagerGetAntiAffinityScore(ipnodes, ip_count, NULL, NULL);
3623 char *msg;
3624 int perfect = (score == 0);
3625 int log_level = (perfect ? CLUSTER_MANAGER_LOG_LVL_SUCCESS :
3626 CLUSTER_MANAGER_LOG_LVL_WARN);
3627 if (perfect) msg = "[OK] Perfect anti-affinity obtained!";
3628 else if (score >= 10000)
3629 msg = ("[WARNING] Some slaves are in the same host as their master");
3630 else
3631 msg=("[WARNING] Some slaves of the same master are in the same host");
3632 clusterManagerLog(log_level, "%s\n", msg);
3633cleanup:
3634 zfree(offenders);
3635}
3636
3637/* Return a representable string of the node's flags */
3638static sds clusterManagerNodeFlagString(clusterManagerNode *node) {
3639 sds flags = sdsempty();
3640 if (!node->flags_str) return flags;
3641 int empty = 1;
3642 listIter li;
3643 listNode *ln;
3644 listRewind(node->flags_str, &li);
3645 while ((ln = listNext(&li)) != NULL) {
3646 sds flag = ln->value;
3647 if (strcmp(flag, "myself") == 0) continue;
3648 if (!empty) flags = sdscat(flags, ",");
3649 flags = sdscatfmt(flags, "%S", flag);
3650 empty = 0;
3651 }
3652 return flags;
3653}
3654
3655/* Return a representable string of the node's slots */
3656static sds clusterManagerNodeSlotsString(clusterManagerNode *node) {
3657 sds slots = sdsempty();
3658 int first_range_idx = -1, last_slot_idx = -1, i;
3659 for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
3660 int has_slot = node->slots[i];
3661 if (has_slot) {
3662 if (first_range_idx == -1) {
3663 if (sdslen(slots)) slots = sdscat(slots, ",");
3664 first_range_idx = i;
3665 slots = sdscatfmt(slots, "[%u", i);
3666 }
3667 last_slot_idx = i;
3668 } else {
3669 if (last_slot_idx >= 0) {
3670 if (first_range_idx == last_slot_idx)
3671 slots = sdscat(slots, "]");
3672 else slots = sdscatfmt(slots, "-%u]", last_slot_idx);
3673 }
3674 last_slot_idx = -1;
3675 first_range_idx = -1;
3676 }
3677 }
3678 if (last_slot_idx >= 0) {
3679 if (first_range_idx == last_slot_idx) slots = sdscat(slots, "]");
3680 else slots = sdscatfmt(slots, "-%u]", last_slot_idx);
3681 }
3682 return slots;
3683}
3684
3685static sds clusterManagerNodeGetJSON(clusterManagerNode *node,
3686 unsigned long error_count)
3687{
3688 sds json = sdsempty();
3689 sds replicate = sdsempty();
3690 if (node->replicate)
3691 replicate = sdscatprintf(replicate, "\"%s\"", node->replicate);
3692 else
3693 replicate = sdscat(replicate, "null");
3694 sds slots = clusterManagerNodeSlotsString(node);
3695 sds flags = clusterManagerNodeFlagString(node);
3696 char *p = slots;
3697 while ((p = strchr(p, '-')) != NULL)
3698 *(p++) = ',';
3699 json = sdscatprintf(json,
3700 " {\n"
3701 " \"name\": \"%s\",\n"
3702 " \"host\": \"%s\",\n"
3703 " \"port\": %d,\n"
3704 " \"replicate\": %s,\n"
3705 " \"slots\": [%s],\n"
3706 " \"slots_count\": %d,\n"
3707 " \"flags\": \"%s\",\n"
3708 " \"current_epoch\": %llu",
3709 node->name,
3710 node->ip,
3711 node->port,
3712 replicate,
3713 slots,
3714 node->slots_count,
3715 flags,
3716 (unsigned long long)node->current_epoch
3717 );
3718 if (error_count > 0) {
3719 json = sdscatprintf(json, ",\n \"cluster_errors\": %lu",
3720 error_count);
3721 }
3722 if (node->migrating_count > 0 && node->migrating != NULL) {
3723 int i = 0;
3724 sds migrating = sdsempty();
3725 for (; i < node->migrating_count; i += 2) {
3726 sds slot = node->migrating[i];
3727 sds dest = node->migrating[i + 1];
3728 if (slot && dest) {
3729 if (sdslen(migrating) > 0) migrating = sdscat(migrating, ",");
3730 migrating = sdscatfmt(migrating, "\"%S\": \"%S\"", slot, dest);
3731 }
3732 }
3733 if (sdslen(migrating) > 0)
3734 json = sdscatfmt(json, ",\n \"migrating\": {%S}", migrating);
3735 sdsfree(migrating);
3736 }
3737 if (node->importing_count > 0 && node->importing != NULL) {
3738 int i = 0;
3739 sds importing = sdsempty();
3740 for (; i < node->importing_count; i += 2) {
3741 sds slot = node->importing[i];
3742 sds from = node->importing[i + 1];
3743 if (slot && from) {
3744 if (sdslen(importing) > 0) importing = sdscat(importing, ",");
3745 importing = sdscatfmt(importing, "\"%S\": \"%S\"", slot, from);
3746 }
3747 }
3748 if (sdslen(importing) > 0)
3749 json = sdscatfmt(json, ",\n \"importing\": {%S}", importing);
3750 sdsfree(importing);
3751 }
3752 json = sdscat(json, "\n }");
3753 sdsfree(replicate);
3754 sdsfree(slots);
3755 sdsfree(flags);
3756 return json;
3757}
3758
3759
3760/* -----------------------------------------------------------------------------
3761 * Key space handling
3762 * -------------------------------------------------------------------------- */
3763
3764/* We have 16384 hash slots. The hash slot of a given key is obtained
3765 * as the least significant 14 bits of the crc16 of the key.
3766 *
3767 * However if the key contains the {...} pattern, only the part between
3768 * { and } is hashed. This may be useful in the future to force certain
3769 * keys to be in the same node (assuming no resharding is in progress). */
3770static unsigned int clusterManagerKeyHashSlot(char *key, int keylen) {
3771 int s, e; /* start-end indexes of { and } */
3772
3773 for (s = 0; s < keylen; s++)
3774 if (key[s] == '{') break;
3775
3776 /* No '{' ? Hash the whole key. This is the base case. */
3777 if (s == keylen) return crc16(key,keylen) & 0x3FFF;
3778
3779 /* '{' found? Check if we have the corresponding '}'. */
3780 for (e = s+1; e < keylen; e++)
3781 if (key[e] == '}') break;
3782
3783 /* No '}' or nothing between {} ? Hash the whole key. */
3784 if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF;
3785
3786 /* If we are here there is both a { and a } on its right. Hash
3787 * what is in the middle between { and }. */
3788 return crc16(key+s+1,e-s-1) & 0x3FFF;
3789}
3790
3791/* Return a string representation of the cluster node. */
3792static sds clusterManagerNodeInfo(clusterManagerNode *node, int indent) {
3793 sds info = sdsempty();
3794 sds spaces = sdsempty();
3795 int i;
3796 for (i = 0; i < indent; i++) spaces = sdscat(spaces, " ");
3797 if (indent) info = sdscat(info, spaces);
3798 int is_master = !(node->flags & CLUSTER_MANAGER_FLAG_SLAVE);
3799 char *role = (is_master ? "M" : "S");
3800 sds slots = NULL;
3801 if (node->dirty && node->replicate != NULL)
3802 info = sdscatfmt(info, "S: %S %s:%u", node->name, node->ip, node->port);
3803 else {
3804 slots = clusterManagerNodeSlotsString(node);
3805 sds flags = clusterManagerNodeFlagString(node);
3806 info = sdscatfmt(info, "%s: %S %s:%u\n"
3807 "%s slots:%S (%u slots) "
3808 "%S",
3809 role, node->name, node->ip, node->port, spaces,
3810 slots, node->slots_count, flags);
3811 sdsfree(slots);
3812 sdsfree(flags);
3813 }
3814 if (node->replicate != NULL)
3815 info = sdscatfmt(info, "\n%s replicates %S", spaces, node->replicate);
3816 else if (node->replicas_count)
3817 info = sdscatfmt(info, "\n%s %U additional replica(s)",
3818 spaces, node->replicas_count);
3819 sdsfree(spaces);
3820 return info;
3821}
3822
3823static void clusterManagerShowNodes(void) {
3824 listIter li;
3825 listNode *ln;
3826 listRewind(cluster_manager.nodes, &li);
3827 while ((ln = listNext(&li)) != NULL) {
3828 clusterManagerNode *node = ln->value;
3829 sds info = clusterManagerNodeInfo(node, 0);
3830 printf("%s\n", (char *) info);
3831 sdsfree(info);
3832 }
3833}
3834
3835static void clusterManagerShowClusterInfo(void) {
3836 int masters = 0;
3837 int keys = 0;
3838 listIter li;
3839 listNode *ln;
3840 listRewind(cluster_manager.nodes, &li);
3841 while ((ln = listNext(&li)) != NULL) {
3842 clusterManagerNode *node = ln->value;
3843 if (!(node->flags & CLUSTER_MANAGER_FLAG_SLAVE)) {
3844 if (!node->name) continue;
3845 int replicas = 0;
3846 int dbsize = -1;
3847 char name[9];
3848 memcpy(name, node->name, 8);
3849 name[8] = '\0';
3850 listIter ri;
3851 listNode *rn;
3852 listRewind(cluster_manager.nodes, &ri);
3853 while ((rn = listNext(&ri)) != NULL) {
3854 clusterManagerNode *n = rn->value;
3855 if (n == node || !(n->flags & CLUSTER_MANAGER_FLAG_SLAVE))
3856 continue;
3857 if (n->replicate && !strcmp(n->replicate, node->name))
3858 replicas++;
3859 }
3860 redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "DBSIZE");
3861 if (reply != NULL && reply->type == REDIS_REPLY_INTEGER)
3862 dbsize = reply->integer;
3863 if (dbsize < 0) {
3864 char *err = "";
3865 if (reply != NULL && reply->type == REDIS_REPLY_ERROR)
3866 err = reply->str;
3867 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
3868 if (reply != NULL) freeReplyObject(reply);
3869 return;
3870 };
3871 if (reply != NULL) freeReplyObject(reply);
3872 printf("%s:%d (%s...) -> %d keys | %d slots | %d slaves.\n",
3873 node->ip, node->port, name, dbsize,
3874 node->slots_count, replicas);
3875 masters++;
3876 keys += dbsize;
3877 }
3878 }
3879 clusterManagerLogOk("[OK] %d keys in %d masters.\n", keys, masters);
3880 float keys_per_slot = keys / (float) CLUSTER_MANAGER_SLOTS;
3881 printf("%.2f keys per slot on average.\n", keys_per_slot);
3882}
3883
3884/* Flush dirty slots configuration of the node by calling CLUSTER ADDSLOTS */
3885static int clusterManagerAddSlots(clusterManagerNode *node, char**err)
3886{
3887 redisReply *reply = NULL;
3888 void *_reply = NULL;
3889 int success = 1;
3890 /* First two args are used for the command itself. */
3891 int argc = node->slots_count + 2;
3892 sds *argv = zmalloc(argc * sizeof(*argv));
3893 size_t *argvlen = zmalloc(argc * sizeof(*argvlen));
3894 argv[0] = "CLUSTER";
3895 argv[1] = "ADDSLOTS";
3896 argvlen[0] = 7;
3897 argvlen[1] = 8;
3898 *err = NULL;
3899 int i, argv_idx = 2;
3900 for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
3901 if (argv_idx >= argc) break;
3902 if (node->slots[i]) {
3903 argv[argv_idx] = sdsfromlonglong((long long) i);
3904 argvlen[argv_idx] = sdslen(argv[argv_idx]);
3905 argv_idx++;
3906 }
3907 }
3908 if (argv_idx == 2) {
3909 success = 0;
3910 goto cleanup;
3911 }
3912 redisAppendCommandArgv(node->context,argc,(const char**)argv,argvlen);
3913 if (redisGetReply(node->context, &_reply) != REDIS_OK) {
3914 success = 0;
3915 goto cleanup;
3916 }
3917 reply = (redisReply*) _reply;
3918 success = clusterManagerCheckRedisReply(node, reply, err);
3919cleanup:
3920 zfree(argvlen);
3921 if (argv != NULL) {
3922 for (i = 2; i < argc; i++) sdsfree(argv[i]);
3923 zfree(argv);
3924 }
3925 if (reply != NULL) freeReplyObject(reply);
3926 return success;
3927}
3928
3929/* Get the node the slot is assigned to from the point of view of node *n.
3930 * If the slot is unassigned or if the reply is an error, return NULL.
3931 * Use the **err argument in order to check whether the slot is unassigned
3932 * or the reply resulted in an error. */
3933static clusterManagerNode *clusterManagerGetSlotOwner(clusterManagerNode *n,
3934 int slot, char **err)
3935{
3936 assert(slot >= 0 && slot < CLUSTER_MANAGER_SLOTS);
3937 clusterManagerNode *owner = NULL;
3938 redisReply *reply = CLUSTER_MANAGER_COMMAND(n, "CLUSTER SLOTS");
3939 if (clusterManagerCheckRedisReply(n, reply, err)) {
3940 assert(reply->type == REDIS_REPLY_ARRAY);
3941 size_t i;
3942 for (i = 0; i < reply->elements; i++) {
3943 redisReply *r = reply->element[i];
3944 assert(r->type == REDIS_REPLY_ARRAY && r->elements >= 3);
3945 int from, to;
3946 from = r->element[0]->integer;
3947 to = r->element[1]->integer;
3948 if (slot < from || slot > to) continue;
3949 redisReply *nr = r->element[2];
3950 assert(nr->type == REDIS_REPLY_ARRAY && nr->elements >= 2);
3951 char *name = NULL;
3952 if (nr->elements >= 3)
3953 name = nr->element[2]->str;
3954 if (name != NULL)
3955 owner = clusterManagerNodeByName(name);
3956 else {
3957 char *ip = nr->element[0]->str;
3958 assert(ip != NULL);
3959 int port = (int) nr->element[1]->integer;
3960 listIter li;
3961 listNode *ln;
3962 listRewind(cluster_manager.nodes, &li);
3963 while ((ln = listNext(&li)) != NULL) {
3964 clusterManagerNode *nd = ln->value;
3965 if (strcmp(nd->ip, ip) == 0 && port == nd->port) {
3966 owner = nd;
3967 break;
3968 }
3969 }
3970 }
3971 if (owner) break;
3972 }
3973 }
3974 if (reply) freeReplyObject(reply);
3975 return owner;
3976}
3977
3978/* Set slot status to "importing" or "migrating" */
3979static int clusterManagerSetSlot(clusterManagerNode *node1,
3980 clusterManagerNode *node2,
3981 int slot, const char *status, char **err) {
3982 redisReply *reply = CLUSTER_MANAGER_COMMAND(node1, "CLUSTER "
3983 "SETSLOT %d %s %s",
3984 slot, status,
3985 (char *) node2->name);
3986 if (err != NULL) *err = NULL;
3987 if (!reply) {
3988 if (err) *err = zstrdup("CLUSTER SETSLOT failed to run");
3989 return 0;
3990 }
3991 int success = 1;
3992 if (reply->type == REDIS_REPLY_ERROR) {
3993 success = 0;
3994 if (err != NULL) {
3995 *err = zmalloc((reply->len + 1) * sizeof(char));
3996 strcpy(*err, reply->str);
3997 } else CLUSTER_MANAGER_PRINT_REPLY_ERROR(node1, reply->str);
3998 goto cleanup;
3999 }
4000cleanup:
4001 freeReplyObject(reply);
4002 return success;
4003}
4004
4005static int clusterManagerClearSlotStatus(clusterManagerNode *node, int slot) {
4006 redisReply *reply = CLUSTER_MANAGER_COMMAND(node,
4007 "CLUSTER SETSLOT %d %s", slot, "STABLE");
4008 int success = clusterManagerCheckRedisReply(node, reply, NULL);
4009 if (reply) freeReplyObject(reply);
4010 return success;
4011}
4012
4013static int clusterManagerDelSlot(clusterManagerNode *node, int slot,
4014 int ignore_unassigned_err)
4015{
4016 redisReply *reply = CLUSTER_MANAGER_COMMAND(node,
4017 "CLUSTER DELSLOTS %d", slot);
4018 char *err = NULL;
4019 int success = clusterManagerCheckRedisReply(node, reply, &err);
4020 if (!success && reply && reply->type == REDIS_REPLY_ERROR &&
4021 ignore_unassigned_err)
4022 {
4023 char *get_owner_err = NULL;
4024 clusterManagerNode *assigned_to =
4025 clusterManagerGetSlotOwner(node, slot, &get_owner_err);
4026 if (!assigned_to) {
4027 if (get_owner_err == NULL) success = 1;
4028 else {
4029 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, get_owner_err);
4030 zfree(get_owner_err);
4031 }
4032 }
4033 }
4034 if (!success && err != NULL) {
4035 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
4036 zfree(err);
4037 }
4038 if (reply) freeReplyObject(reply);
4039 return success;
4040}
4041
4042static int clusterManagerAddSlot(clusterManagerNode *node, int slot) {
4043 redisReply *reply = CLUSTER_MANAGER_COMMAND(node,
4044 "CLUSTER ADDSLOTS %d", slot);
4045 int success = clusterManagerCheckRedisReply(node, reply, NULL);
4046 if (reply) freeReplyObject(reply);
4047 return success;
4048}
4049
4050static signed int clusterManagerCountKeysInSlot(clusterManagerNode *node,
4051 int slot)
4052{
4053 redisReply *reply = CLUSTER_MANAGER_COMMAND(node,
4054 "CLUSTER COUNTKEYSINSLOT %d", slot);
4055 int count = -1;
4056 int success = clusterManagerCheckRedisReply(node, reply, NULL);
4057 if (success && reply->type == REDIS_REPLY_INTEGER) count = reply->integer;
4058 if (reply) freeReplyObject(reply);
4059 return count;
4060}
4061
4062static int clusterManagerBumpEpoch(clusterManagerNode *node) {
4063 redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "CLUSTER BUMPEPOCH");
4064 int success = clusterManagerCheckRedisReply(node, reply, NULL);
4065 if (reply) freeReplyObject(reply);
4066 return success;
4067}
4068
4069/* Callback used by clusterManagerSetSlotOwner transaction. It should ignore
4070 * errors except for ADDSLOTS errors.
4071 * Return 1 if the error should be ignored. */
4072static int clusterManagerOnSetOwnerErr(redisReply *reply,
4073 clusterManagerNode *n, int bulk_idx)
4074{
4075 UNUSED(reply);
4076 UNUSED(n);
4077 /* Only raise error when ADDSLOTS fail (bulk_idx == 1). */
4078 return (bulk_idx != 1);
4079}
4080
4081static int clusterManagerSetSlotOwner(clusterManagerNode *owner,
4082 int slot,
4083 int do_clear)
4084{
4085 int success = clusterManagerStartTransaction(owner);
4086 if (!success) return 0;
4087 /* Ensure the slot is not already assigned. */
4088 clusterManagerDelSlot(owner, slot, 1);
4089 /* Add the slot and bump epoch. */
4090 clusterManagerAddSlot(owner, slot);
4091 if (do_clear) clusterManagerClearSlotStatus(owner, slot);
4092 clusterManagerBumpEpoch(owner);
4093 success = clusterManagerExecTransaction(owner, clusterManagerOnSetOwnerErr);
4094 return success;
4095}
4096
4097/* Get the hash for the values of the specified keys in *keys_reply for the
4098 * specified nodes *n1 and *n2, by calling DEBUG DIGEST-VALUE redis command
4099 * on both nodes. Every key with same name on both nodes but having different
4100 * values will be added to the *diffs list. Return 0 in case of reply
4101 * error. */
4102static int clusterManagerCompareKeysValues(clusterManagerNode *n1,
4103 clusterManagerNode *n2,
4104 redisReply *keys_reply,
4105 list *diffs)
4106{
4107 size_t i, argc = keys_reply->elements + 2;
4108 static const char *hash_zero = "0000000000000000000000000000000000000000";
4109 char **argv = zcalloc(argc * sizeof(char *));
4110 size_t *argv_len = zcalloc(argc * sizeof(size_t));
4111 argv[0] = "DEBUG";
4112 argv_len[0] = 5;
4113 argv[1] = "DIGEST-VALUE";
4114 argv_len[1] = 12;
4115 for (i = 0; i < keys_reply->elements; i++) {
4116 redisReply *entry = keys_reply->element[i];
4117 int idx = i + 2;
4118 argv[idx] = entry->str;
4119 argv_len[idx] = entry->len;
4120 }
4121 int success = 0;
4122 void *_reply1 = NULL, *_reply2 = NULL;
4123 redisReply *r1 = NULL, *r2 = NULL;
4124 redisAppendCommandArgv(n1->context,argc, (const char**)argv,argv_len);
4125 success = (redisGetReply(n1->context, &_reply1) == REDIS_OK);
4126 if (!success) goto cleanup;
4127 r1 = (redisReply *) _reply1;
4128 redisAppendCommandArgv(n2->context,argc, (const char**)argv,argv_len);
4129 success = (redisGetReply(n2->context, &_reply2) == REDIS_OK);
4130 if (!success) goto cleanup;
4131 r2 = (redisReply *) _reply2;
4132 success = (r1->type != REDIS_REPLY_ERROR && r2->type != REDIS_REPLY_ERROR);
4133 if (r1->type == REDIS_REPLY_ERROR) {
4134 CLUSTER_MANAGER_PRINT_REPLY_ERROR(n1, r1->str);
4135 success = 0;
4136 }
4137 if (r2->type == REDIS_REPLY_ERROR) {
4138 CLUSTER_MANAGER_PRINT_REPLY_ERROR(n2, r2->str);
4139 success = 0;
4140 }
4141 if (!success) goto cleanup;
4142 assert(keys_reply->elements == r1->elements &&
4143 keys_reply->elements == r2->elements);
4144 for (i = 0; i < keys_reply->elements; i++) {
4145 char *key = keys_reply->element[i]->str;
4146 char *hash1 = r1->element[i]->str;
4147 char *hash2 = r2->element[i]->str;
4148 /* Ignore keys that don't exist in both nodes. */
4149 if (strcmp(hash1, hash_zero) == 0 || strcmp(hash2, hash_zero) == 0)
4150 continue;
4151 if (strcmp(hash1, hash2) != 0) listAddNodeTail(diffs, key);
4152 }
4153cleanup:
4154 if (r1) freeReplyObject(r1);
4155 if (r2) freeReplyObject(r2);
4156 zfree(argv);
4157 zfree(argv_len);
4158 return success;
4159}
4160
4161/* Migrate keys taken from reply->elements. It returns the reply from the
4162 * MIGRATE command, or NULL if something goes wrong. If the argument 'dots'
4163 * is not NULL, a dot will be printed for every migrated key. */
4164static redisReply *clusterManagerMigrateKeysInReply(clusterManagerNode *source,
4165 clusterManagerNode *target,
4166 redisReply *reply,
4167 int replace, int timeout,
4168 char *dots)
4169{
4170 redisReply *migrate_reply = NULL;
4171 char **argv = NULL;
4172 size_t *argv_len = NULL;
4173 int c = (replace ? 8 : 7);
4174 if (config.conn_info.auth) c += 2;
4175 if (config.conn_info.user) c += 1;
4176 size_t argc = c + reply->elements;
4177 size_t i, offset = 6; // Keys Offset
4178 argv = zcalloc(argc * sizeof(char *));
4179 argv_len = zcalloc(argc * sizeof(size_t));
4180 char portstr[255];
4181 char timeoutstr[255];
4182 snprintf(portstr, 10, "%d", target->port);
4183 snprintf(timeoutstr, 10, "%d", timeout);
4184 argv[0] = "MIGRATE";
4185 argv_len[0] = 7;
4186 argv[1] = target->ip;
4187 argv_len[1] = strlen(target->ip);
4188 argv[2] = portstr;
4189 argv_len[2] = strlen(portstr);
4190 argv[3] = "";
4191 argv_len[3] = 0;
4192 argv[4] = "0";
4193 argv_len[4] = 1;
4194 argv[5] = timeoutstr;
4195 argv_len[5] = strlen(timeoutstr);
4196 if (replace) {
4197 argv[offset] = "REPLACE";
4198 argv_len[offset] = 7;
4199 offset++;
4200 }
4201 if (config.conn_info.auth) {
4202 if (config.conn_info.user) {
4203 argv[offset] = "AUTH2";
4204 argv_len[offset] = 5;
4205 offset++;
4206 argv[offset] = config.conn_info.user;
4207 argv_len[offset] = strlen(config.conn_info.user);
4208 offset++;
4209 argv[offset] = config.conn_info.auth;
4210 argv_len[offset] = strlen(config.conn_info.auth);
4211 offset++;
4212 } else {
4213 argv[offset] = "AUTH";
4214 argv_len[offset] = 4;
4215 offset++;
4216 argv[offset] = config.conn_info.auth;
4217 argv_len[offset] = strlen(config.conn_info.auth);
4218 offset++;
4219 }
4220 }
4221 argv[offset] = "KEYS";
4222 argv_len[offset] = 4;
4223 offset++;
4224 for (i = 0; i < reply->elements; i++) {
4225 redisReply *entry = reply->element[i];
4226 size_t idx = i + offset;
4227 assert(entry->type == REDIS_REPLY_STRING);
4228 argv[idx] = (char *) sdsnewlen(entry->str, entry->len);
4229 argv_len[idx] = entry->len;
4230 if (dots) dots[i] = '.';
4231 }
4232 if (dots) dots[reply->elements] = '\0';
4233 void *_reply = NULL;
4234 redisAppendCommandArgv(source->context,argc,
4235 (const char**)argv,argv_len);
4236 int success = (redisGetReply(source->context, &_reply) == REDIS_OK);
4237 for (i = 0; i < reply->elements; i++) sdsfree(argv[i + offset]);
4238 if (!success) goto cleanup;
4239 migrate_reply = (redisReply *) _reply;
4240cleanup:
4241 zfree(argv);
4242 zfree(argv_len);
4243 return migrate_reply;
4244}
4245
4246/* Migrate all keys in the given slot from source to target.*/
4247static int clusterManagerMigrateKeysInSlot(clusterManagerNode *source,
4248 clusterManagerNode *target,
4249 int slot, int timeout,
4250 int pipeline, int verbose,
4251 char **err)
4252{
4253 int success = 1;
4254 int do_fix = config.cluster_manager_command.flags &
4255 CLUSTER_MANAGER_CMD_FLAG_FIX;
4256 int do_replace = config.cluster_manager_command.flags &
4257 CLUSTER_MANAGER_CMD_FLAG_REPLACE;
4258 while (1) {
4259 char *dots = NULL;
4260 redisReply *reply = NULL, *migrate_reply = NULL;
4261 reply = CLUSTER_MANAGER_COMMAND(source, "CLUSTER "
4262 "GETKEYSINSLOT %d %d", slot,
4263 pipeline);
4264 success = (reply != NULL);
4265 if (!success) return 0;
4266 if (reply->type == REDIS_REPLY_ERROR) {
4267 success = 0;
4268 if (err != NULL) {
4269 *err = zmalloc((reply->len + 1) * sizeof(char));
4270 strcpy(*err, reply->str);
4271 CLUSTER_MANAGER_PRINT_REPLY_ERROR(source, *err);
4272 }
4273 goto next;
4274 }
4275 assert(reply->type == REDIS_REPLY_ARRAY);
4276 size_t count = reply->elements;
4277 if (count == 0) {
4278 freeReplyObject(reply);
4279 break;
4280 }
4281 if (verbose) dots = zmalloc((count+1) * sizeof(char));
4282 /* Calling MIGRATE command. */
4283 migrate_reply = clusterManagerMigrateKeysInReply(source, target,
4284 reply, 0, timeout,
4285 dots);
4286 if (migrate_reply == NULL) goto next;
4287 if (migrate_reply->type == REDIS_REPLY_ERROR) {
4288 int is_busy = strstr(migrate_reply->str, "BUSYKEY") != NULL;
4289 int not_served = 0;
4290 if (!is_busy) {
4291 /* Check if the slot is unassigned (not served) in the
4292 * source node's configuration. */
4293 char *get_owner_err = NULL;
4294 clusterManagerNode *served_by =
4295 clusterManagerGetSlotOwner(source, slot, &get_owner_err);
4296 if (!served_by) {
4297 if (get_owner_err == NULL) not_served = 1;
4298 else {
4299 CLUSTER_MANAGER_PRINT_REPLY_ERROR(source,
4300 get_owner_err);
4301 zfree(get_owner_err);
4302 }
4303 }
4304 }
4305 /* Try to handle errors. */
4306 if (is_busy || not_served) {
4307 /* If the key's slot is not served, try to assign slot
4308 * to the target node. */
4309 if (do_fix && not_served) {
4310 clusterManagerLogWarn("*** Slot was not served, setting "
4311 "owner to node %s:%d.\n",
4312 target->ip, target->port);
4313 clusterManagerSetSlot(source, target, slot, "node", NULL);
4314 }
4315 /* If the key already exists in the target node (BUSYKEY),
4316 * check whether its value is the same in both nodes.
4317 * In case of equal values, retry migration with the
4318 * REPLACE option.
4319 * In case of different values:
4320 * - If the migration is requested by the fix command, stop
4321 * and warn the user.
4322 * - In other cases (ie. reshard), proceed only if the user
4323 * launched the command with the --cluster-replace option.*/
4324 if (is_busy) {
4325 clusterManagerLogWarn("\n*** Target key exists\n");
4326 if (!do_replace) {
4327 clusterManagerLogWarn("*** Checking key values on "
4328 "both nodes...\n");
4329 list *diffs = listCreate();
4330 success = clusterManagerCompareKeysValues(source,
4331 target, reply, diffs);
4332 if (!success) {
4333 clusterManagerLogErr("*** Value check failed!\n");
4334 listRelease(diffs);
4335 goto next;
4336 }
4337 if (listLength(diffs) > 0) {
4338 success = 0;
4339 clusterManagerLogErr(
4340 "*** Found %d key(s) in both source node and "
4341 "target node having different values.\n"
4342 " Source node: %s:%d\n"
4343 " Target node: %s:%d\n"
4344 " Keys(s):\n",
4345 listLength(diffs),
4346 source->ip, source->port,
4347 target->ip, target->port);
4348 listIter dli;
4349 listNode *dln;
4350 listRewind(diffs, &dli);
4351 while((dln = listNext(&dli)) != NULL) {
4352 char *k = dln->value;
4353 clusterManagerLogErr(" - %s\n", k);
4354 }
4355 clusterManagerLogErr("Please fix the above key(s) "
4356 "manually and try again "
4357 "or relaunch the command \n"
4358 "with --cluster-replace "
4359 "option to force key "
4360 "overriding.\n");
4361 listRelease(diffs);
4362 goto next;
4363 }
4364 listRelease(diffs);
4365 }
4366 clusterManagerLogWarn("*** Replacing target keys...\n");
4367 }
4368 freeReplyObject(migrate_reply);
4369 migrate_reply = clusterManagerMigrateKeysInReply(source,
4370 target,
4371 reply,
4372 is_busy,
4373 timeout,
4374 NULL);
4375 success = (migrate_reply != NULL &&
4376 migrate_reply->type != REDIS_REPLY_ERROR);
4377 } else success = 0;
4378 if (!success) {
4379 if (migrate_reply != NULL) {
4380 if (err) {
4381 *err = zmalloc((migrate_reply->len + 1) * sizeof(char));
4382 strcpy(*err, migrate_reply->str);
4383 }
4384 printf("\n");
4385 CLUSTER_MANAGER_PRINT_REPLY_ERROR(source,
4386 migrate_reply->str);
4387 }
4388 goto next;
4389 }
4390 }
4391 if (verbose) {
4392 printf("%s", dots);
4393 fflush(stdout);
4394 }
4395next:
4396 if (reply != NULL) freeReplyObject(reply);
4397 if (migrate_reply != NULL) freeReplyObject(migrate_reply);
4398 if (dots) zfree(dots);
4399 if (!success) break;
4400 }
4401 return success;
4402}
4403
4404/* Move slots between source and target nodes using MIGRATE.
4405 *
4406 * Options:
4407 * CLUSTER_MANAGER_OPT_VERBOSE -- Print a dot for every moved key.
4408 * CLUSTER_MANAGER_OPT_COLD -- Move keys without opening slots /
4409 * reconfiguring the nodes.
4410 * CLUSTER_MANAGER_OPT_UPDATE -- Update node->slots for source/target nodes.
4411 * CLUSTER_MANAGER_OPT_QUIET -- Don't print info messages.
4412*/
4413static int clusterManagerMoveSlot(clusterManagerNode *source,
4414 clusterManagerNode *target,
4415 int slot, int opts, char**err)
4416{
4417 if (!(opts & CLUSTER_MANAGER_OPT_QUIET)) {
4418 printf("Moving slot %d from %s:%d to %s:%d: ", slot, source->ip,
4419 source->port, target->ip, target->port);
4420 fflush(stdout);
4421 }
4422 if (err != NULL) *err = NULL;
4423 int pipeline = config.cluster_manager_command.pipeline,
4424 timeout = config.cluster_manager_command.timeout,
4425 print_dots = (opts & CLUSTER_MANAGER_OPT_VERBOSE),
4426 option_cold = (opts & CLUSTER_MANAGER_OPT_COLD),
4427 success = 1;
4428 if (!option_cold) {
4429 success = clusterManagerSetSlot(target, source, slot,
4430 "importing", err);
4431 if (!success) return 0;
4432 success = clusterManagerSetSlot(source, target, slot,
4433 "migrating", err);
4434 if (!success) return 0;
4435 }
4436 success = clusterManagerMigrateKeysInSlot(source, target, slot, timeout,
4437 pipeline, print_dots, err);
4438 if (!(opts & CLUSTER_MANAGER_OPT_QUIET)) printf("\n");
4439 if (!success) return 0;
4440 if (!option_cold) {
4441 /* Set the new node as the owner of the slot in all the known nodes.
4442 *
4443 * We inform the target node first. It will propagate the information to
4444 * the rest of the cluster.
4445 *
4446 * If we inform any other node first, it can happen that the target node
4447 * crashes before it is set as the new owner and then the slot is left
4448 * without an owner which results in redirect loops. See issue #7116. */
4449 success = clusterManagerSetSlot(target, target, slot, "node", err);
4450 if (!success) return 0;
4451
4452 /* Inform the source node. If the source node has just lost its last
4453 * slot and the target node has already informed the source node, the
4454 * source node has turned itself into a replica. This is not an error in
4455 * this scenario so we ignore it. See issue #9223. */
4456 success = clusterManagerSetSlot(source, target, slot, "node", err);
4457 const char *acceptable = "ERR Please use SETSLOT only with masters.";
4458 if (!success && err && !strncmp(*err, acceptable, strlen(acceptable))) {
4459 zfree(*err);
4460 *err = NULL;
4461 } else if (!success && err) {
4462 return 0;
4463 }
4464
4465 /* We also inform the other nodes to avoid redirects in case the target
4466 * node is slow to propagate the change to the entire cluster. */
4467 listIter li;
4468 listNode *ln;
4469 listRewind(cluster_manager.nodes, &li);
4470 while ((ln = listNext(&li)) != NULL) {
4471 clusterManagerNode *n = ln->value;
4472 if (n == target || n == source) continue; /* already done */
4473 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
4474 success = clusterManagerSetSlot(n, target, slot, "node", err);
4475 if (!success) return 0;
4476 }
4477 }
4478 /* Update the node logical config */
4479 if (opts & CLUSTER_MANAGER_OPT_UPDATE) {
4480 source->slots[slot] = 0;
4481 target->slots[slot] = 1;
4482 }
4483 return 1;
4484}
4485
4486/* Flush the dirty node configuration by calling replicate for slaves or
4487 * adding the slots defined in the masters. */
4488static int clusterManagerFlushNodeConfig(clusterManagerNode *node, char **err) {
4489 if (!node->dirty) return 0;
4490 redisReply *reply = NULL;
4491 int is_err = 0, success = 1;
4492 if (err != NULL) *err = NULL;
4493 if (node->replicate != NULL) {
4494 reply = CLUSTER_MANAGER_COMMAND(node, "CLUSTER REPLICATE %s",
4495 node->replicate);
4496 if (reply == NULL || (is_err = (reply->type == REDIS_REPLY_ERROR))) {
4497 if (is_err && err != NULL) {
4498 *err = zmalloc((reply->len + 1) * sizeof(char));
4499 strcpy(*err, reply->str);
4500 }
4501 success = 0;
4502 /* If the cluster did not already joined it is possible that
4503 * the slave does not know the master node yet. So on errors
4504 * we return ASAP leaving the dirty flag set, to flush the
4505 * config later. */
4506 goto cleanup;
4507 }
4508 } else {
4509 int added = clusterManagerAddSlots(node, err);
4510 if (!added || *err != NULL) success = 0;
4511 }
4512 node->dirty = 0;
4513cleanup:
4514 if (reply != NULL) freeReplyObject(reply);
4515 return success;
4516}
4517
4518/* Wait until the cluster configuration is consistent. */
4519static void clusterManagerWaitForClusterJoin(void) {
4520 printf("Waiting for the cluster to join\n");
4521 int counter = 0,
4522 check_after = CLUSTER_JOIN_CHECK_AFTER +
4523 (int)(listLength(cluster_manager.nodes) * 0.15f);
4524 while(!clusterManagerIsConfigConsistent()) {
4525 printf(".");
4526 fflush(stdout);
4527 sleep(1);
4528 if (++counter > check_after) {
4529 dict *status = clusterManagerGetLinkStatus();
4530 dictIterator *iter = NULL;
4531 if (status != NULL && dictSize(status) > 0) {
4532 printf("\n");
4533 clusterManagerLogErr("Warning: %d node(s) may "
4534 "be unreachable\n", dictSize(status));
4535 iter = dictGetIterator(status);
4536 dictEntry *entry;
4537 while ((entry = dictNext(iter)) != NULL) {
4538 sds nodeaddr = (sds) dictGetKey(entry);
4539 char *node_ip = NULL;
4540 int node_port = 0, node_bus_port = 0;
4541 list *from = (list *) dictGetVal(entry);
4542 if (parseClusterNodeAddress(nodeaddr, &node_ip,
4543 &node_port, &node_bus_port) && node_bus_port) {
4544 clusterManagerLogErr(" - The port %d of node %s may "
4545 "be unreachable from:\n",
4546 node_bus_port, node_ip);
4547 } else {
4548 clusterManagerLogErr(" - Node %s may be unreachable "
4549 "from:\n", nodeaddr);
4550 }
4551 listIter li;
4552 listNode *ln;
4553 listRewind(from, &li);
4554 while ((ln = listNext(&li)) != NULL) {
4555 sds from_addr = ln->value;
4556 clusterManagerLogErr(" %s\n", from_addr);
4557 sdsfree(from_addr);
4558 }
4559 clusterManagerLogErr("Cluster bus ports must be reachable "
4560 "by every node.\nRemember that "
4561 "cluster bus ports are different "
4562 "from standard instance ports.\n");
4563 listEmpty(from);
4564 }
4565 }
4566 if (iter != NULL) dictReleaseIterator(iter);
4567 if (status != NULL) dictRelease(status);
4568 counter = 0;
4569 }
4570 }
4571 printf("\n");
4572}
4573
4574/* Load node's cluster configuration by calling "CLUSTER NODES" command.
4575 * Node's configuration (name, replicate, slots, ...) is then updated.
4576 * If CLUSTER_MANAGER_OPT_GETFRIENDS flag is set into 'opts' argument,
4577 * and node already knows other nodes, the node's friends list is populated
4578 * with the other nodes info. */
4579static int clusterManagerNodeLoadInfo(clusterManagerNode *node, int opts,
4580 char **err)
4581{
4582 redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "CLUSTER NODES");
4583 int success = 1;
4584 *err = NULL;
4585 if (!clusterManagerCheckRedisReply(node, reply, err)) {
4586 success = 0;
4587 goto cleanup;
4588 }
4589 int getfriends = (opts & CLUSTER_MANAGER_OPT_GETFRIENDS);
4590 char *lines = reply->str, *p, *line;
4591 while ((p = strstr(lines, "\n")) != NULL) {
4592 *p = '\0';
4593 line = lines;
4594 lines = p + 1;
4595 char *name = NULL, *addr = NULL, *flags = NULL, *master_id = NULL,
4596 *ping_sent = NULL, *ping_recv = NULL, *config_epoch = NULL,
4597 *link_status = NULL;
4598 UNUSED(link_status);
4599 int i = 0;
4600 while ((p = strchr(line, ' ')) != NULL) {
4601 *p = '\0';
4602 char *token = line;
4603 line = p + 1;
4604 switch(i++){
4605 case 0: name = token; break;
4606 case 1: addr = token; break;
4607 case 2: flags = token; break;
4608 case 3: master_id = token; break;
4609 case 4: ping_sent = token; break;
4610 case 5: ping_recv = token; break;
4611 case 6: config_epoch = token; break;
4612 case 7: link_status = token; break;
4613 }
4614 if (i == 8) break; // Slots
4615 }
4616 if (!flags) {
4617 success = 0;
4618 goto cleanup;
4619 }
4620
4621 char *ip = NULL;
4622 int port = 0, bus_port = 0;
4623 if (addr == NULL || !parseClusterNodeAddress(addr, &ip, &port, &bus_port)) {
4624 fprintf(stderr, "Error: invalid CLUSTER NODES reply\n");
4625 success = 0;
4626 goto cleanup;
4627 }
4628
4629 int myself = (strstr(flags, "myself") != NULL);
4630 clusterManagerNode *currentNode = NULL;
4631 if (myself) {
4632 /* bus-port could be wrong, correct it here, see clusterManagerNewNode. */
4633 node->bus_port = bus_port;
4634 node->flags |= CLUSTER_MANAGER_FLAG_MYSELF;
4635 currentNode = node;
4636 clusterManagerNodeResetSlots(node);
4637 if (i == 8) {
4638 int remaining = strlen(line);
4639 while (remaining > 0) {
4640 p = strchr(line, ' ');
4641 if (p == NULL) p = line + remaining;
4642 remaining -= (p - line);
4643
4644 char *slotsdef = line;
4645 *p = '\0';
4646 if (remaining) {
4647 line = p + 1;
4648 remaining--;
4649 } else line = p;
4650 char *dash = NULL;
4651 if (slotsdef[0] == '[') {
4652 slotsdef++;
4653 if ((p = strstr(slotsdef, "->-"))) { // Migrating
4654 *p = '\0';
4655 p += 3;
4656 char *closing_bracket = strchr(p, ']');
4657 if (closing_bracket) *closing_bracket = '\0';
4658 sds slot = sdsnew(slotsdef);
4659 sds dst = sdsnew(p);
4660 node->migrating_count += 2;
4661 node->migrating = zrealloc(node->migrating,
4662 (node->migrating_count * sizeof(sds)));
4663 node->migrating[node->migrating_count - 2] =
4664 slot;
4665 node->migrating[node->migrating_count - 1] =
4666 dst;
4667 } else if ((p = strstr(slotsdef, "-<-"))) {//Importing
4668 *p = '\0';
4669 p += 3;
4670 char *closing_bracket = strchr(p, ']');
4671 if (closing_bracket) *closing_bracket = '\0';
4672 sds slot = sdsnew(slotsdef);
4673 sds src = sdsnew(p);
4674 node->importing_count += 2;
4675 node->importing = zrealloc(node->importing,
4676 (node->importing_count * sizeof(sds)));
4677 node->importing[node->importing_count - 2] =
4678 slot;
4679 node->importing[node->importing_count - 1] =
4680 src;
4681 }
4682 } else if ((dash = strchr(slotsdef, '-')) != NULL) {
4683 p = dash;
4684 int start, stop;
4685 *p = '\0';
4686 start = atoi(slotsdef);
4687 stop = atoi(p + 1);
4688 node->slots_count += (stop - (start - 1));
4689 while (start <= stop) node->slots[start++] = 1;
4690 } else if (p > slotsdef) {
4691 node->slots[atoi(slotsdef)] = 1;
4692 node->slots_count++;
4693 }
4694 }
4695 }
4696 node->dirty = 0;
4697 } else if (!getfriends) {
4698 if (!(node->flags & CLUSTER_MANAGER_FLAG_MYSELF)) continue;
4699 else break;
4700 } else {
4701 currentNode = clusterManagerNewNode(sdsnew(ip), port, bus_port);
4702 currentNode->flags |= CLUSTER_MANAGER_FLAG_FRIEND;
4703 if (node->friends == NULL) node->friends = listCreate();
4704 listAddNodeTail(node->friends, currentNode);
4705 }
4706 if (name != NULL) {
4707 if (currentNode->name) sdsfree(currentNode->name);
4708 currentNode->name = sdsnew(name);
4709 }
4710 if (currentNode->flags_str != NULL)
4711 freeClusterManagerNodeFlags(currentNode->flags_str);
4712 currentNode->flags_str = listCreate();
4713 int flag_len;
4714 while ((flag_len = strlen(flags)) > 0) {
4715 sds flag = NULL;
4716 char *fp = strchr(flags, ',');
4717 if (fp) {
4718 *fp = '\0';
4719 flag = sdsnew(flags);
4720 flags = fp + 1;
4721 } else {
4722 flag = sdsnew(flags);
4723 flags += flag_len;
4724 }
4725 if (strcmp(flag, "noaddr") == 0)
4726 currentNode->flags |= CLUSTER_MANAGER_FLAG_NOADDR;
4727 else if (strcmp(flag, "disconnected") == 0)
4728 currentNode->flags |= CLUSTER_MANAGER_FLAG_DISCONNECT;
4729 else if (strcmp(flag, "fail") == 0)
4730 currentNode->flags |= CLUSTER_MANAGER_FLAG_FAIL;
4731 else if (strcmp(flag, "slave") == 0) {
4732 currentNode->flags |= CLUSTER_MANAGER_FLAG_SLAVE;
4733 if (master_id != NULL) {
4734 if (currentNode->replicate) sdsfree(currentNode->replicate);
4735 currentNode->replicate = sdsnew(master_id);
4736 }
4737 }
4738 listAddNodeTail(currentNode->flags_str, flag);
4739 }
4740 if (config_epoch != NULL)
4741 currentNode->current_epoch = atoll(config_epoch);
4742 if (ping_sent != NULL) currentNode->ping_sent = atoll(ping_sent);
4743 if (ping_recv != NULL) currentNode->ping_recv = atoll(ping_recv);
4744 if (!getfriends && myself) break;
4745 }
4746cleanup:
4747 if (reply) freeReplyObject(reply);
4748 return success;
4749}
4750
4751/* Retrieves info about the cluster using argument 'node' as the starting
4752 * point. All nodes will be loaded inside the cluster_manager.nodes list.
4753 * Warning: if something goes wrong, it will free the starting node before
4754 * returning 0. */
4755static int clusterManagerLoadInfoFromNode(clusterManagerNode *node) {
4756 if (node->context == NULL && !clusterManagerNodeConnect(node)) {
4757 freeClusterManagerNode(node);
4758 return 0;
4759 }
4760 char *e = NULL;
4761 if (!clusterManagerNodeIsCluster(node, &e)) {
4762 clusterManagerPrintNotClusterNodeError(node, e);
4763 if (e) zfree(e);
4764 freeClusterManagerNode(node);
4765 return 0;
4766 }
4767 e = NULL;
4768 if (!clusterManagerNodeLoadInfo(node, CLUSTER_MANAGER_OPT_GETFRIENDS, &e)) {
4769 if (e) {
4770 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, e);
4771 zfree(e);
4772 }
4773 freeClusterManagerNode(node);
4774 return 0;
4775 }
4776 listIter li;
4777 listNode *ln;
4778 if (cluster_manager.nodes != NULL) {
4779 listRewind(cluster_manager.nodes, &li);
4780 while ((ln = listNext(&li)) != NULL)
4781 freeClusterManagerNode((clusterManagerNode *) ln->value);
4782 listRelease(cluster_manager.nodes);
4783 }
4784 cluster_manager.nodes = listCreate();
4785 listAddNodeTail(cluster_manager.nodes, node);
4786 if (node->friends != NULL) {
4787 listRewind(node->friends, &li);
4788 while ((ln = listNext(&li)) != NULL) {
4789 clusterManagerNode *friend = ln->value;
4790 if (!friend->ip || !friend->port) goto invalid_friend;
4791 if (!friend->context && !clusterManagerNodeConnect(friend))
4792 goto invalid_friend;
4793 e = NULL;
4794 if (clusterManagerNodeLoadInfo(friend, 0, &e)) {
4795 if (friend->flags & (CLUSTER_MANAGER_FLAG_NOADDR |
4796 CLUSTER_MANAGER_FLAG_DISCONNECT |
4797 CLUSTER_MANAGER_FLAG_FAIL))
4798 {
4799 goto invalid_friend;
4800 }
4801 listAddNodeTail(cluster_manager.nodes, friend);
4802 } else {
4803 clusterManagerLogErr("[ERR] Unable to load info for "
4804 "node %s:%d\n",
4805 friend->ip, friend->port);
4806 goto invalid_friend;
4807 }
4808 continue;
4809invalid_friend:
4810 if (!(friend->flags & CLUSTER_MANAGER_FLAG_SLAVE))
4811 cluster_manager.unreachable_masters++;
4812 freeClusterManagerNode(friend);
4813 }
4814 listRelease(node->friends);
4815 node->friends = NULL;
4816 }
4817 // Count replicas for each node
4818 listRewind(cluster_manager.nodes, &li);
4819 while ((ln = listNext(&li)) != NULL) {
4820 clusterManagerNode *n = ln->value;
4821 if (n->replicate != NULL) {
4822 clusterManagerNode *master = clusterManagerNodeByName(n->replicate);
4823 if (master == NULL) {
4824 clusterManagerLogWarn("*** WARNING: %s:%d claims to be "
4825 "slave of unknown node ID %s.\n",
4826 n->ip, n->port, n->replicate);
4827 } else master->replicas_count++;
4828 }
4829 }
4830 return 1;
4831}
4832
4833/* Compare functions used by various sorting operations. */
4834int clusterManagerSlotCompare(const void *slot1, const void *slot2) {
4835 const char **i1 = (const char **)slot1;
4836 const char **i2 = (const char **)slot2;
4837 return strcmp(*i1, *i2);
4838}
4839
4840int clusterManagerSlotCountCompareDesc(const void *n1, const void *n2) {
4841 clusterManagerNode *node1 = *((clusterManagerNode **) n1);
4842 clusterManagerNode *node2 = *((clusterManagerNode **) n2);
4843 return node2->slots_count - node1->slots_count;
4844}
4845
4846int clusterManagerCompareNodeBalance(const void *n1, const void *n2) {
4847 clusterManagerNode *node1 = *((clusterManagerNode **) n1);
4848 clusterManagerNode *node2 = *((clusterManagerNode **) n2);
4849 return node1->balance - node2->balance;
4850}
4851
4852static sds clusterManagerGetConfigSignature(clusterManagerNode *node) {
4853 sds signature = NULL;
4854 int node_count = 0, i = 0, name_len = 0;
4855 char **node_configs = NULL;
4856 redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "CLUSTER NODES");
4857 if (reply == NULL || reply->type == REDIS_REPLY_ERROR)
4858 goto cleanup;
4859 char *lines = reply->str, *p, *line;
4860 while ((p = strstr(lines, "\n")) != NULL) {
4861 i = 0;
4862 *p = '\0';
4863 line = lines;
4864 lines = p + 1;
4865 char *nodename = NULL;
4866 int tot_size = 0;
4867 while ((p = strchr(line, ' ')) != NULL) {
4868 *p = '\0';
4869 char *token = line;
4870 line = p + 1;
4871 if (i == 0) {
4872 nodename = token;
4873 tot_size = (p - token);
4874 name_len = tot_size++; // Make room for ':' in tot_size
4875 }
4876 if (++i == 8) break;
4877 }
4878 if (i != 8) continue;
4879 if (nodename == NULL) continue;
4880 int remaining = strlen(line);
4881 if (remaining == 0) continue;
4882 char **slots = NULL;
4883 int c = 0;
4884 while (remaining > 0) {
4885 p = strchr(line, ' ');
4886 if (p == NULL) p = line + remaining;
4887 int size = (p - line);
4888 remaining -= size;
4889 tot_size += size;
4890 char *slotsdef = line;
4891 *p = '\0';
4892 if (remaining) {
4893 line = p + 1;
4894 remaining--;
4895 } else line = p;
4896 if (slotsdef[0] != '[') {
4897 c++;
4898 slots = zrealloc(slots, (c * sizeof(char *)));
4899 slots[c - 1] = slotsdef;
4900 }
4901 }
4902 if (c > 0) {
4903 if (c > 1)
4904 qsort(slots, c, sizeof(char *), clusterManagerSlotCompare);
4905 node_count++;
4906 node_configs =
4907 zrealloc(node_configs, (node_count * sizeof(char *)));
4908 /* Make room for '|' separators. */
4909 tot_size += (sizeof(char) * (c - 1));
4910 char *cfg = zmalloc((sizeof(char) * tot_size) + 1);
4911 memcpy(cfg, nodename, name_len);
4912 char *sp = cfg + name_len;
4913 *(sp++) = ':';
4914 for (i = 0; i < c; i++) {
4915 if (i > 0) *(sp++) = ',';
4916 int slen = strlen(slots[i]);
4917 memcpy(sp, slots[i], slen);
4918 sp += slen;
4919 }
4920 *(sp++) = '\0';
4921 node_configs[node_count - 1] = cfg;
4922 }
4923 zfree(slots);
4924 }
4925 if (node_count > 0) {
4926 if (node_count > 1) {
4927 qsort(node_configs, node_count, sizeof(char *),
4928 clusterManagerSlotCompare);
4929 }
4930 signature = sdsempty();
4931 for (i = 0; i < node_count; i++) {
4932 if (i > 0) signature = sdscatprintf(signature, "%c", '|');
4933 signature = sdscatfmt(signature, "%s", node_configs[i]);
4934 }
4935 }
4936cleanup:
4937 if (reply != NULL) freeReplyObject(reply);
4938 if (node_configs != NULL) {
4939 for (i = 0; i < node_count; i++) zfree(node_configs[i]);
4940 zfree(node_configs);
4941 }
4942 return signature;
4943}
4944
4945static int clusterManagerIsConfigConsistent(void) {
4946 if (cluster_manager.nodes == NULL) return 0;
4947 int consistent = (listLength(cluster_manager.nodes) <= 1);
4948 // If the Cluster has only one node, it's always consistent
4949 if (consistent) return 1;
4950 sds first_cfg = NULL;
4951 listIter li;
4952 listNode *ln;
4953 listRewind(cluster_manager.nodes, &li);
4954 while ((ln = listNext(&li)) != NULL) {
4955 clusterManagerNode *node = ln->value;
4956 sds cfg = clusterManagerGetConfigSignature(node);
4957 if (cfg == NULL) {
4958 consistent = 0;
4959 break;
4960 }
4961 if (first_cfg == NULL) first_cfg = cfg;
4962 else {
4963 consistent = !sdscmp(first_cfg, cfg);
4964 sdsfree(cfg);
4965 if (!consistent) break;
4966 }
4967 }
4968 if (first_cfg != NULL) sdsfree(first_cfg);
4969 return consistent;
4970}
4971
4972static list *clusterManagerGetDisconnectedLinks(clusterManagerNode *node) {
4973 list *links = NULL;
4974 redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "CLUSTER NODES");
4975 if (!clusterManagerCheckRedisReply(node, reply, NULL)) goto cleanup;
4976 links = listCreate();
4977 char *lines = reply->str, *p, *line;
4978 while ((p = strstr(lines, "\n")) != NULL) {
4979 int i = 0;
4980 *p = '\0';
4981 line = lines;
4982 lines = p + 1;
4983 char *nodename = NULL, *addr = NULL, *flags = NULL, *link_status = NULL;
4984 while ((p = strchr(line, ' ')) != NULL) {
4985 *p = '\0';
4986 char *token = line;
4987 line = p + 1;
4988 if (i == 0) nodename = token;
4989 else if (i == 1) addr = token;
4990 else if (i == 2) flags = token;
4991 else if (i == 7) link_status = token;
4992 else if (i == 8) break;
4993 i++;
4994 }
4995 if (i == 7) link_status = line;
4996 if (nodename == NULL || addr == NULL || flags == NULL ||
4997 link_status == NULL) continue;
4998 if (strstr(flags, "myself") != NULL) continue;
4999 int disconnected = ((strstr(flags, "disconnected") != NULL) ||
5000 (strstr(link_status, "disconnected")));
5001 int handshaking = (strstr(flags, "handshake") != NULL);
5002 if (disconnected || handshaking) {
5003 clusterManagerLink *link = zmalloc(sizeof(*link));
5004 link->node_name = sdsnew(nodename);
5005 link->node_addr = sdsnew(addr);
5006 link->connected = 0;
5007 link->handshaking = handshaking;
5008 listAddNodeTail(links, link);
5009 }
5010 }
5011cleanup:
5012 if (reply != NULL) freeReplyObject(reply);
5013 return links;
5014}
5015
5016/* Check for disconnected cluster links. It returns a dict whose keys
5017 * are the unreachable node addresses and the values are lists of
5018 * node addresses that cannot reach the unreachable node. */
5019static dict *clusterManagerGetLinkStatus(void) {
5020 if (cluster_manager.nodes == NULL) return NULL;
5021 dict *status = dictCreate(&clusterManagerLinkDictType);
5022 listIter li;
5023 listNode *ln;
5024 listRewind(cluster_manager.nodes, &li);
5025 while ((ln = listNext(&li)) != NULL) {
5026 clusterManagerNode *node = ln->value;
5027 list *links = clusterManagerGetDisconnectedLinks(node);
5028 if (links) {
5029 listIter lli;
5030 listNode *lln;
5031 listRewind(links, &lli);
5032 while ((lln = listNext(&lli)) != NULL) {
5033 clusterManagerLink *link = lln->value;
5034 list *from = NULL;
5035 dictEntry *entry = dictFind(status, link->node_addr);
5036 if (entry) from = dictGetVal(entry);
5037 else {
5038 from = listCreate();
5039 dictAdd(status, sdsdup(link->node_addr), from);
5040 }
5041 sds myaddr = sdsempty();
5042 myaddr = sdscatfmt(myaddr, "%s:%u", node->ip, node->port);
5043 listAddNodeTail(from, myaddr);
5044 sdsfree(link->node_name);
5045 sdsfree(link->node_addr);
5046 zfree(link);
5047 }
5048 listRelease(links);
5049 }
5050 }
5051 return status;
5052}
5053
5054/* Add the error string to cluster_manager.errors and print it. */
5055static void clusterManagerOnError(sds err) {
5056 if (cluster_manager.errors == NULL)
5057 cluster_manager.errors = listCreate();
5058 listAddNodeTail(cluster_manager.errors, err);
5059 clusterManagerLogErr("%s\n", (char *) err);
5060}
5061
5062/* Check the slots coverage of the cluster. The 'all_slots' argument must be
5063 * and array of 16384 bytes. Every covered slot will be set to 1 in the
5064 * 'all_slots' array. The function returns the total number if covered slots.*/
5065static int clusterManagerGetCoveredSlots(char *all_slots) {
5066 if (cluster_manager.nodes == NULL) return 0;
5067 listIter li;
5068 listNode *ln;
5069 listRewind(cluster_manager.nodes, &li);
5070 int totslots = 0, i;
5071 while ((ln = listNext(&li)) != NULL) {
5072 clusterManagerNode *node = ln->value;
5073 for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
5074 if (node->slots[i] && !all_slots[i]) {
5075 all_slots[i] = 1;
5076 totslots++;
5077 }
5078 }
5079 }
5080 return totslots;
5081}
5082
5083static void clusterManagerPrintSlotsList(list *slots) {
5084 clusterManagerNode n = {0};
5085 listIter li;
5086 listNode *ln;
5087 listRewind(slots, &li);
5088 while ((ln = listNext(&li)) != NULL) {
5089 int slot = atoi(ln->value);
5090 if (slot >= 0 && slot < CLUSTER_MANAGER_SLOTS)
5091 n.slots[slot] = 1;
5092 }
5093 sds nodeslist = clusterManagerNodeSlotsString(&n);
5094 printf("%s\n", nodeslist);
5095 sdsfree(nodeslist);
5096}
5097
5098/* Return the node, among 'nodes' with the greatest number of keys
5099 * in the specified slot. */
5100static clusterManagerNode * clusterManagerGetNodeWithMostKeysInSlot(list *nodes,
5101 int slot,
5102 char **err)
5103{
5104 clusterManagerNode *node = NULL;
5105 int numkeys = 0;
5106 listIter li;
5107 listNode *ln;
5108 listRewind(nodes, &li);
5109 if (err) *err = NULL;
5110 while ((ln = listNext(&li)) != NULL) {
5111 clusterManagerNode *n = ln->value;
5112 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE || n->replicate)
5113 continue;
5114 redisReply *r =
5115 CLUSTER_MANAGER_COMMAND(n, "CLUSTER COUNTKEYSINSLOT %d", slot);
5116 int success = clusterManagerCheckRedisReply(n, r, err);
5117 if (success) {
5118 if (r->integer > numkeys || node == NULL) {
5119 numkeys = r->integer;
5120 node = n;
5121 }
5122 }
5123 if (r != NULL) freeReplyObject(r);
5124 /* If the reply contains errors */
5125 if (!success) {
5126 if (err != NULL && *err != NULL)
5127 CLUSTER_MANAGER_PRINT_REPLY_ERROR(n, err);
5128 node = NULL;
5129 break;
5130 }
5131 }
5132 return node;
5133}
5134
5135/* This function returns the master that has the least number of replicas
5136 * in the cluster. If there are multiple masters with the same smaller
5137 * number of replicas, one at random is returned. */
5138
5139static clusterManagerNode *clusterManagerNodeWithLeastReplicas() {
5140 clusterManagerNode *node = NULL;
5141 int lowest_count = 0;
5142 listIter li;
5143 listNode *ln;
5144 listRewind(cluster_manager.nodes, &li);
5145 while ((ln = listNext(&li)) != NULL) {
5146 clusterManagerNode *n = ln->value;
5147 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
5148 if (node == NULL || n->replicas_count < lowest_count) {
5149 node = n;
5150 lowest_count = n->replicas_count;
5151 }
5152 }
5153 return node;
5154}
5155
5156/* This function returns a random master node, return NULL if none */
5157
5158static clusterManagerNode *clusterManagerNodeMasterRandom() {
5159 int master_count = 0;
5160 int idx;
5161 listIter li;
5162 listNode *ln;
5163 listRewind(cluster_manager.nodes, &li);
5164 while ((ln = listNext(&li)) != NULL) {
5165 clusterManagerNode *n = ln->value;
5166 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
5167 master_count++;
5168 }
5169
5170 assert(master_count > 0);
5171 srand(time(NULL));
5172 idx = rand() % master_count;
5173 listRewind(cluster_manager.nodes, &li);
5174 while ((ln = listNext(&li)) != NULL) {
5175 clusterManagerNode *n = ln->value;
5176 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
5177 if (!idx--) {
5178 return n;
5179 }
5180 }
5181 /* Can not be reached */
5182 assert(0);
5183}
5184
5185static int clusterManagerFixSlotsCoverage(char *all_slots) {
5186 int force_fix = config.cluster_manager_command.flags &
5187 CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS;
5188
5189 if (cluster_manager.unreachable_masters > 0 && !force_fix) {
5190 clusterManagerLogWarn("*** Fixing slots coverage with %d unreachable masters is dangerous: redis-cli will assume that slots about masters that are not reachable are not covered, and will try to reassign them to the reachable nodes. This can cause data loss and is rarely what you want to do. If you really want to proceed use the --cluster-fix-with-unreachable-masters option.\n", cluster_manager.unreachable_masters);
5191 exit(1);
5192 }
5193
5194 int i, fixed = 0;
5195 list *none = NULL, *single = NULL, *multi = NULL;
5196 clusterManagerLogInfo(">>> Fixing slots coverage...\n");
5197 for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
5198 int covered = all_slots[i];
5199 if (!covered) {
5200 sds slot = sdsfromlonglong((long long) i);
5201 list *slot_nodes = listCreate();
5202 sds slot_nodes_str = sdsempty();
5203 listIter li;
5204 listNode *ln;
5205 listRewind(cluster_manager.nodes, &li);
5206 while ((ln = listNext(&li)) != NULL) {
5207 clusterManagerNode *n = ln->value;
5208 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE || n->replicate)
5209 continue;
5210 redisReply *reply = CLUSTER_MANAGER_COMMAND(n,
5211 "CLUSTER GETKEYSINSLOT %d %d", i, 1);
5212 if (!clusterManagerCheckRedisReply(n, reply, NULL)) {
5213 fixed = -1;
5214 if (reply) freeReplyObject(reply);
5215 goto cleanup;
5216 }
5217 assert(reply->type == REDIS_REPLY_ARRAY);
5218 if (reply->elements > 0) {
5219 listAddNodeTail(slot_nodes, n);
5220 if (listLength(slot_nodes) > 1)
5221 slot_nodes_str = sdscat(slot_nodes_str, ", ");
5222 slot_nodes_str = sdscatfmt(slot_nodes_str,
5223 "%s:%u", n->ip, n->port);
5224 }
5225 freeReplyObject(reply);
5226 }
5227 sdsfree(slot_nodes_str);
5228 dictAdd(clusterManagerUncoveredSlots, slot, slot_nodes);
5229 }
5230 }
5231
5232 /* For every slot, take action depending on the actual condition:
5233 * 1) No node has keys for this slot.
5234 * 2) A single node has keys for this slot.
5235 * 3) Multiple nodes have keys for this slot. */
5236 none = listCreate();
5237 single = listCreate();
5238 multi = listCreate();
5239 dictIterator *iter = dictGetIterator(clusterManagerUncoveredSlots);
5240 dictEntry *entry;
5241 while ((entry = dictNext(iter)) != NULL) {
5242 sds slot = (sds) dictGetKey(entry);
5243 list *nodes = (list *) dictGetVal(entry);
5244 switch (listLength(nodes)){
5245 case 0: listAddNodeTail(none, slot); break;
5246 case 1: listAddNodeTail(single, slot); break;
5247 default: listAddNodeTail(multi, slot); break;
5248 }
5249 }
5250 dictReleaseIterator(iter);
5251
5252 /* we want explicit manual confirmation from users for all the fix cases */
5253 int ignore_force = 1;
5254
5255 /* Handle case "1": keys in no node. */
5256 if (listLength(none) > 0) {
5257 printf("The following uncovered slots have no keys "
5258 "across the cluster:\n");
5259 clusterManagerPrintSlotsList(none);
5260 if (confirmWithYes("Fix these slots by covering with a random node?",
5261 ignore_force)) {
5262 listIter li;
5263 listNode *ln;
5264 listRewind(none, &li);
5265 while ((ln = listNext(&li)) != NULL) {
5266 sds slot = ln->value;
5267 int s = atoi(slot);
5268 clusterManagerNode *n = clusterManagerNodeMasterRandom();
5269 clusterManagerLogInfo(">>> Covering slot %s with %s:%d\n",
5270 slot, n->ip, n->port);
5271 if (!clusterManagerSetSlotOwner(n, s, 0)) {
5272 fixed = -1;
5273 goto cleanup;
5274 }
5275 /* Since CLUSTER ADDSLOTS succeeded, we also update the slot
5276 * info into the node struct, in order to keep it synced */
5277 n->slots[s] = 1;
5278 fixed++;
5279 }
5280 }
5281 }
5282
5283 /* Handle case "2": keys only in one node. */
5284 if (listLength(single) > 0) {
5285 printf("The following uncovered slots have keys in just one node:\n");
5286 clusterManagerPrintSlotsList(single);
5287 if (confirmWithYes("Fix these slots by covering with those nodes?",
5288 ignore_force)) {
5289 listIter li;
5290 listNode *ln;
5291 listRewind(single, &li);
5292 while ((ln = listNext(&li)) != NULL) {
5293 sds slot = ln->value;
5294 int s = atoi(slot);
5295 dictEntry *entry = dictFind(clusterManagerUncoveredSlots, slot);
5296 assert(entry != NULL);
5297 list *nodes = (list *) dictGetVal(entry);
5298 listNode *fn = listFirst(nodes);
5299 assert(fn != NULL);
5300 clusterManagerNode *n = fn->value;
5301 clusterManagerLogInfo(">>> Covering slot %s with %s:%d\n",
5302 slot, n->ip, n->port);
5303 if (!clusterManagerSetSlotOwner(n, s, 0)) {
5304 fixed = -1;
5305 goto cleanup;
5306 }
5307 /* Since CLUSTER ADDSLOTS succeeded, we also update the slot
5308 * info into the node struct, in order to keep it synced */
5309 n->slots[atoi(slot)] = 1;
5310 fixed++;
5311 }
5312 }
5313 }
5314
5315 /* Handle case "3": keys in multiple nodes. */
5316 if (listLength(multi) > 0) {
5317 printf("The following uncovered slots have keys in multiple nodes:\n");
5318 clusterManagerPrintSlotsList(multi);
5319 if (confirmWithYes("Fix these slots by moving keys "
5320 "into a single node?", ignore_force)) {
5321 listIter li;
5322 listNode *ln;
5323 listRewind(multi, &li);
5324 while ((ln = listNext(&li)) != NULL) {
5325 sds slot = ln->value;
5326 dictEntry *entry = dictFind(clusterManagerUncoveredSlots, slot);
5327 assert(entry != NULL);
5328 list *nodes = (list *) dictGetVal(entry);
5329 int s = atoi(slot);
5330 clusterManagerNode *target =
5331 clusterManagerGetNodeWithMostKeysInSlot(nodes, s, NULL);
5332 if (target == NULL) {
5333 fixed = -1;
5334 goto cleanup;
5335 }
5336 clusterManagerLogInfo(">>> Covering slot %s moving keys "
5337 "to %s:%d\n", slot,
5338 target->ip, target->port);
5339 if (!clusterManagerSetSlotOwner(target, s, 1)) {
5340 fixed = -1;
5341 goto cleanup;
5342 }
5343 /* Since CLUSTER ADDSLOTS succeeded, we also update the slot
5344 * info into the node struct, in order to keep it synced */
5345 target->slots[atoi(slot)] = 1;
5346 listIter nli;
5347 listNode *nln;
5348 listRewind(nodes, &nli);
5349 while ((nln = listNext(&nli)) != NULL) {
5350 clusterManagerNode *src = nln->value;
5351 if (src == target) continue;
5352 /* Assign the slot to target node in the source node. */
5353 if (!clusterManagerSetSlot(src, target, s, "NODE", NULL))
5354 fixed = -1;
5355 if (fixed < 0) goto cleanup;
5356 /* Set the source node in 'importing' state
5357 * (even if we will actually migrate keys away)
5358 * in order to avoid receiving redirections
5359 * for MIGRATE. */
5360 if (!clusterManagerSetSlot(src, target, s,
5361 "IMPORTING", NULL)) fixed = -1;
5362 if (fixed < 0) goto cleanup;
5363 int opts = CLUSTER_MANAGER_OPT_VERBOSE |
5364 CLUSTER_MANAGER_OPT_COLD;
5365 if (!clusterManagerMoveSlot(src, target, s, opts, NULL)) {
5366 fixed = -1;
5367 goto cleanup;
5368 }
5369 if (!clusterManagerClearSlotStatus(src, s))
5370 fixed = -1;
5371 if (fixed < 0) goto cleanup;
5372 }
5373 fixed++;
5374 }
5375 }
5376 }
5377cleanup:
5378 if (none) listRelease(none);
5379 if (single) listRelease(single);
5380 if (multi) listRelease(multi);
5381 return fixed;
5382}
5383
5384/* Slot 'slot' was found to be in importing or migrating state in one or
5385 * more nodes. This function fixes this condition by migrating keys where
5386 * it seems more sensible. */
5387static int clusterManagerFixOpenSlot(int slot) {
5388 int force_fix = config.cluster_manager_command.flags &
5389 CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS;
5390
5391 if (cluster_manager.unreachable_masters > 0 && !force_fix) {
5392 clusterManagerLogWarn("*** Fixing open slots with %d unreachable masters is dangerous: redis-cli will assume that slots about masters that are not reachable are not covered, and will try to reassign them to the reachable nodes. This can cause data loss and is rarely what you want to do. If you really want to proceed use the --cluster-fix-with-unreachable-masters option.\n", cluster_manager.unreachable_masters);
5393 exit(1);
5394 }
5395
5396 clusterManagerLogInfo(">>> Fixing open slot %d\n", slot);
5397 /* Try to obtain the current slot owner, according to the current
5398 * nodes configuration. */
5399 int success = 1;
5400 list *owners = listCreate(); /* List of nodes claiming some ownership.
5401 it could be stating in the configuration
5402 to have the node ownership, or just
5403 holding keys for such slot. */
5404 list *migrating = listCreate();
5405 list *importing = listCreate();
5406 sds migrating_str = sdsempty();
5407 sds importing_str = sdsempty();
5408 clusterManagerNode *owner = NULL; /* The obvious slot owner if any. */
5409
5410 /* Iterate all the nodes, looking for potential owners of this slot. */
5411 listIter li;
5412 listNode *ln;
5413 listRewind(cluster_manager.nodes, &li);
5414 while ((ln = listNext(&li)) != NULL) {
5415 clusterManagerNode *n = ln->value;
5416 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
5417 if (n->slots[slot]) {
5418 listAddNodeTail(owners, n);
5419 } else {
5420 redisReply *r = CLUSTER_MANAGER_COMMAND(n,
5421 "CLUSTER COUNTKEYSINSLOT %d", slot);
5422 success = clusterManagerCheckRedisReply(n, r, NULL);
5423 if (success && r->integer > 0) {
5424 clusterManagerLogWarn("*** Found keys about slot %d "
5425 "in non-owner node %s:%d!\n", slot,
5426 n->ip, n->port);
5427 listAddNodeTail(owners, n);
5428 }
5429 if (r) freeReplyObject(r);
5430 if (!success) goto cleanup;
5431 }
5432 }
5433
5434 /* If we have only a single potential owner for this slot,
5435 * set it as "owner". */
5436 if (listLength(owners) == 1) owner = listFirst(owners)->value;
5437
5438 /* Scan the list of nodes again, in order to populate the
5439 * list of nodes in importing or migrating state for
5440 * this slot. */
5441 listRewind(cluster_manager.nodes, &li);
5442 while ((ln = listNext(&li)) != NULL) {
5443 clusterManagerNode *n = ln->value;
5444 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
5445 int is_migrating = 0, is_importing = 0;
5446 if (n->migrating) {
5447 for (int i = 0; i < n->migrating_count; i += 2) {
5448 sds migrating_slot = n->migrating[i];
5449 if (atoi(migrating_slot) == slot) {
5450 char *sep = (listLength(migrating) == 0 ? "" : ",");
5451 migrating_str = sdscatfmt(migrating_str, "%s%s:%u",
5452 sep, n->ip, n->port);
5453 listAddNodeTail(migrating, n);
5454 is_migrating = 1;
5455 break;
5456 }
5457 }
5458 }
5459 if (!is_migrating && n->importing) {
5460 for (int i = 0; i < n->importing_count; i += 2) {
5461 sds importing_slot = n->importing[i];
5462 if (atoi(importing_slot) == slot) {
5463 char *sep = (listLength(importing) == 0 ? "" : ",");
5464 importing_str = sdscatfmt(importing_str, "%s%s:%u",
5465 sep, n->ip, n->port);
5466 listAddNodeTail(importing, n);
5467 is_importing = 1;
5468 break;
5469 }
5470 }
5471 }
5472
5473 /* If the node is neither migrating nor importing and it's not
5474 * the owner, then is added to the importing list in case
5475 * it has keys in the slot. */
5476 if (!is_migrating && !is_importing && n != owner) {
5477 redisReply *r = CLUSTER_MANAGER_COMMAND(n,
5478 "CLUSTER COUNTKEYSINSLOT %d", slot);
5479 success = clusterManagerCheckRedisReply(n, r, NULL);
5480 if (success && r->integer > 0) {
5481 clusterManagerLogWarn("*** Found keys about slot %d "
5482 "in node %s:%d!\n", slot, n->ip,
5483 n->port);
5484 char *sep = (listLength(importing) == 0 ? "" : ",");
5485 importing_str = sdscatfmt(importing_str, "%s%s:%u",
5486 sep, n->ip, n->port);
5487 listAddNodeTail(importing, n);
5488 }
5489 if (r) freeReplyObject(r);
5490 if (!success) goto cleanup;
5491 }
5492 }
5493 if (sdslen(migrating_str) > 0)
5494 printf("Set as migrating in: %s\n", migrating_str);
5495 if (sdslen(importing_str) > 0)
5496 printf("Set as importing in: %s\n", importing_str);
5497
5498 /* If there is no slot owner, set as owner the node with the biggest
5499 * number of keys, among the set of migrating / importing nodes. */
5500 if (owner == NULL) {
5501 clusterManagerLogInfo(">>> No single clear owner for the slot, "
5502 "selecting an owner by # of keys...\n");
5503 owner = clusterManagerGetNodeWithMostKeysInSlot(cluster_manager.nodes,
5504 slot, NULL);
5505 // If we still don't have an owner, we can't fix it.
5506 if (owner == NULL) {
5507 clusterManagerLogErr("[ERR] Can't select a slot owner. "
5508 "Impossible to fix.\n");
5509 success = 0;
5510 goto cleanup;
5511 }
5512
5513 // Use ADDSLOTS to assign the slot.
5514 clusterManagerLogWarn("*** Configuring %s:%d as the slot owner\n",
5515 owner->ip, owner->port);
5516 success = clusterManagerClearSlotStatus(owner, slot);
5517 if (!success) goto cleanup;
5518 success = clusterManagerSetSlotOwner(owner, slot, 0);
5519 if (!success) goto cleanup;
5520 /* Since CLUSTER ADDSLOTS succeeded, we also update the slot
5521 * info into the node struct, in order to keep it synced */
5522 owner->slots[slot] = 1;
5523 /* Remove the owner from the list of migrating/importing
5524 * nodes. */
5525 clusterManagerRemoveNodeFromList(migrating, owner);
5526 clusterManagerRemoveNodeFromList(importing, owner);
5527 }
5528
5529 /* If there are multiple owners of the slot, we need to fix it
5530 * so that a single node is the owner and all the other nodes
5531 * are in importing state. Later the fix can be handled by one
5532 * of the base cases above.
5533 *
5534 * Note that this case also covers multiple nodes having the slot
5535 * in migrating state, since migrating is a valid state only for
5536 * slot owners. */
5537 if (listLength(owners) > 1) {
5538 /* Owner cannot be NULL at this point, since if there are more owners,
5539 * the owner has been set in the previous condition (owner == NULL). */
5540 assert(owner != NULL);
5541 listRewind(owners, &li);
5542 while ((ln = listNext(&li)) != NULL) {
5543 clusterManagerNode *n = ln->value;
5544 if (n == owner) continue;
5545 success = clusterManagerDelSlot(n, slot, 1);
5546 if (!success) goto cleanup;
5547 n->slots[slot] = 0;
5548 /* Assign the slot to the owner in the node 'n' configuration.' */
5549 success = clusterManagerSetSlot(n, owner, slot, "node", NULL);
5550 if (!success) goto cleanup;
5551 success = clusterManagerSetSlot(n, owner, slot, "importing", NULL);
5552 if (!success) goto cleanup;
5553 /* Avoid duplicates. */
5554 clusterManagerRemoveNodeFromList(importing, n);
5555 listAddNodeTail(importing, n);
5556 /* Ensure that the node is not in the migrating list. */
5557 clusterManagerRemoveNodeFromList(migrating, n);
5558 }
5559 }
5560 int move_opts = CLUSTER_MANAGER_OPT_VERBOSE;
5561
5562 /* Case 1: The slot is in migrating state in one node, and in
5563 * importing state in 1 node. That's trivial to address. */
5564 if (listLength(migrating) == 1 && listLength(importing) == 1) {
5565 clusterManagerNode *src = listFirst(migrating)->value;
5566 clusterManagerNode *dst = listFirst(importing)->value;
5567 clusterManagerLogInfo(">>> Case 1: Moving slot %d from "
5568 "%s:%d to %s:%d\n", slot,
5569 src->ip, src->port, dst->ip, dst->port);
5570 move_opts |= CLUSTER_MANAGER_OPT_UPDATE;
5571 success = clusterManagerMoveSlot(src, dst, slot, move_opts, NULL);
5572 }
5573
5574 /* Case 2: There are multiple nodes that claim the slot as importing,
5575 * they probably got keys about the slot after a restart so opened
5576 * the slot. In this case we just move all the keys to the owner
5577 * according to the configuration. */
5578 else if (listLength(migrating) == 0 && listLength(importing) > 0) {
5579 clusterManagerLogInfo(">>> Case 2: Moving all the %d slot keys to its "
5580 "owner %s:%d\n", slot, owner->ip, owner->port);
5581 move_opts |= CLUSTER_MANAGER_OPT_COLD;
5582 listRewind(importing, &li);
5583 while ((ln = listNext(&li)) != NULL) {
5584 clusterManagerNode *n = ln->value;
5585 if (n == owner) continue;
5586 success = clusterManagerMoveSlot(n, owner, slot, move_opts, NULL);
5587 if (!success) goto cleanup;
5588 clusterManagerLogInfo(">>> Setting %d as STABLE in "
5589 "%s:%d\n", slot, n->ip, n->port);
5590 success = clusterManagerClearSlotStatus(n, slot);
5591 if (!success) goto cleanup;
5592 }
5593 /* Since the slot has been moved in "cold" mode, ensure that all the
5594 * other nodes update their own configuration about the slot itself. */
5595 listRewind(cluster_manager.nodes, &li);
5596 while ((ln = listNext(&li)) != NULL) {
5597 clusterManagerNode *n = ln->value;
5598 if (n == owner) continue;
5599 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
5600 success = clusterManagerSetSlot(n, owner, slot, "NODE", NULL);
5601 if (!success) goto cleanup;
5602 }
5603 }
5604
5605 /* Case 3: The slot is in migrating state in one node but multiple
5606 * other nodes claim to be in importing state and don't have any key in
5607 * the slot. We search for the importing node having the same ID as
5608 * the destination node of the migrating node.
5609 * In that case we move the slot from the migrating node to this node and
5610 * we close the importing states on all the other importing nodes.
5611 * If no importing node has the same ID as the destination node of the
5612 * migrating node, the slot's state is closed on both the migrating node
5613 * and the importing nodes. */
5614 else if (listLength(migrating) == 1 && listLength(importing) > 1) {
5615 int try_to_fix = 1;
5616 clusterManagerNode *src = listFirst(migrating)->value;
5617 clusterManagerNode *dst = NULL;
5618 sds target_id = NULL;
5619 for (int i = 0; i < src->migrating_count; i += 2) {
5620 sds migrating_slot = src->migrating[i];
5621 if (atoi(migrating_slot) == slot) {
5622 target_id = src->migrating[i + 1];
5623 break;
5624 }
5625 }
5626 assert(target_id != NULL);
5627 listIter li;
5628 listNode *ln;
5629 listRewind(importing, &li);
5630 while ((ln = listNext(&li)) != NULL) {
5631 clusterManagerNode *n = ln->value;
5632 int count = clusterManagerCountKeysInSlot(n, slot);
5633 if (count > 0) {
5634 try_to_fix = 0;
5635 break;
5636 }
5637 if (strcmp(n->name, target_id) == 0) dst = n;
5638 }
5639 if (!try_to_fix) goto unhandled_case;
5640 if (dst != NULL) {
5641 clusterManagerLogInfo(">>> Case 3: Moving slot %d from %s:%d to "
5642 "%s:%d and closing it on all the other "
5643 "importing nodes.\n",
5644 slot, src->ip, src->port,
5645 dst->ip, dst->port);
5646 /* Move the slot to the destination node. */
5647 success = clusterManagerMoveSlot(src, dst, slot, move_opts, NULL);
5648 if (!success) goto cleanup;
5649 /* Close slot on all the other importing nodes. */
5650 listRewind(importing, &li);
5651 while ((ln = listNext(&li)) != NULL) {
5652 clusterManagerNode *n = ln->value;
5653 if (dst == n) continue;
5654 success = clusterManagerClearSlotStatus(n, slot);
5655 if (!success) goto cleanup;
5656 }
5657 } else {
5658 clusterManagerLogInfo(">>> Case 3: Closing slot %d on both "
5659 "migrating and importing nodes.\n", slot);
5660 /* Close the slot on both the migrating node and the importing
5661 * nodes. */
5662 success = clusterManagerClearSlotStatus(src, slot);
5663 if (!success) goto cleanup;
5664 listRewind(importing, &li);
5665 while ((ln = listNext(&li)) != NULL) {
5666 clusterManagerNode *n = ln->value;
5667 success = clusterManagerClearSlotStatus(n, slot);
5668 if (!success) goto cleanup;
5669 }
5670 }
5671 } else {
5672 int try_to_close_slot = (listLength(importing) == 0 &&
5673 listLength(migrating) == 1);
5674 if (try_to_close_slot) {
5675 clusterManagerNode *n = listFirst(migrating)->value;
5676 if (!owner || owner != n) {
5677 redisReply *r = CLUSTER_MANAGER_COMMAND(n,
5678 "CLUSTER GETKEYSINSLOT %d %d", slot, 10);
5679 success = clusterManagerCheckRedisReply(n, r, NULL);
5680 if (r) {
5681 if (success) try_to_close_slot = (r->elements == 0);
5682 freeReplyObject(r);
5683 }
5684 if (!success) goto cleanup;
5685 }
5686 }
5687 /* Case 4: There are no slots claiming to be in importing state, but
5688 * there is a migrating node that actually don't have any key or is the
5689 * slot owner. We can just close the slot, probably a reshard
5690 * interrupted in the middle. */
5691 if (try_to_close_slot) {
5692 clusterManagerNode *n = listFirst(migrating)->value;
5693 clusterManagerLogInfo(">>> Case 4: Closing slot %d on %s:%d\n",
5694 slot, n->ip, n->port);
5695 redisReply *r = CLUSTER_MANAGER_COMMAND(n, "CLUSTER SETSLOT %d %s",
5696 slot, "STABLE");
5697 success = clusterManagerCheckRedisReply(n, r, NULL);
5698 if (r) freeReplyObject(r);
5699 if (!success) goto cleanup;
5700 } else {
5701unhandled_case:
5702 success = 0;
5703 clusterManagerLogErr("[ERR] Sorry, redis-cli can't fix this slot "
5704 "yet (work in progress). Slot is set as "
5705 "migrating in %s, as importing in %s, "
5706 "owner is %s:%d\n", migrating_str,
5707 importing_str, owner->ip, owner->port);
5708 }
5709 }
5710cleanup:
5711 listRelease(owners);
5712 listRelease(migrating);
5713 listRelease(importing);
5714 sdsfree(migrating_str);
5715 sdsfree(importing_str);
5716 return success;
5717}
5718
5719static int clusterManagerFixMultipleSlotOwners(int slot, list *owners) {
5720 clusterManagerLogInfo(">>> Fixing multiple owners for slot %d...\n", slot);
5721 int success = 0;
5722 assert(listLength(owners) > 1);
5723 clusterManagerNode *owner = clusterManagerGetNodeWithMostKeysInSlot(owners,
5724 slot,
5725 NULL);
5726 if (!owner) owner = listFirst(owners)->value;
5727 clusterManagerLogInfo(">>> Setting slot %d owner: %s:%d\n",
5728 slot, owner->ip, owner->port);
5729 /* Set the slot owner. */
5730 if (!clusterManagerSetSlotOwner(owner, slot, 0)) return 0;
5731 listIter li;
5732 listNode *ln;
5733 listRewind(cluster_manager.nodes, &li);
5734 /* Update configuration in all the other master nodes by assigning the slot
5735 * itself to the new owner, and by eventually migrating keys if the node
5736 * has keys for the slot. */
5737 while ((ln = listNext(&li)) != NULL) {
5738 clusterManagerNode *n = ln->value;
5739 if (n == owner) continue;
5740 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
5741 int count = clusterManagerCountKeysInSlot(n, slot);
5742 success = (count >= 0);
5743 if (!success) break;
5744 clusterManagerDelSlot(n, slot, 1);
5745 if (!clusterManagerSetSlot(n, owner, slot, "node", NULL)) return 0;
5746 if (count > 0) {
5747 int opts = CLUSTER_MANAGER_OPT_VERBOSE |
5748 CLUSTER_MANAGER_OPT_COLD;
5749 success = clusterManagerMoveSlot(n, owner, slot, opts, NULL);
5750 if (!success) break;
5751 }
5752 }
5753 return success;
5754}
5755
5756static int clusterManagerCheckCluster(int quiet) {
5757 listNode *ln = listFirst(cluster_manager.nodes);
5758 if (!ln) return 0;
5759 clusterManagerNode *node = ln->value;
5760 clusterManagerLogInfo(">>> Performing Cluster Check (using node %s:%d)\n",
5761 node->ip, node->port);
5762 int result = 1, consistent = 0;
5763 int do_fix = config.cluster_manager_command.flags &
5764 CLUSTER_MANAGER_CMD_FLAG_FIX;
5765 if (!quiet) clusterManagerShowNodes();
5766 consistent = clusterManagerIsConfigConsistent();
5767 if (!consistent) {
5768 sds err = sdsnew("[ERR] Nodes don't agree about configuration!");
5769 clusterManagerOnError(err);
5770 result = 0;
5771 } else {
5772 clusterManagerLogOk("[OK] All nodes agree about slots "
5773 "configuration.\n");
5774 }
5775 /* Check open slots */
5776 clusterManagerLogInfo(">>> Check for open slots...\n");
5777 listIter li;
5778 listRewind(cluster_manager.nodes, &li);
5779 int i;
5780 dict *open_slots = NULL;
5781 while ((ln = listNext(&li)) != NULL) {
5782 clusterManagerNode *n = ln->value;
5783 if (n->migrating != NULL) {
5784 if (open_slots == NULL)
5785 open_slots = dictCreate(&clusterManagerDictType);
5786 sds errstr = sdsempty();
5787 errstr = sdscatprintf(errstr,
5788 "[WARNING] Node %s:%d has slots in "
5789 "migrating state ",
5790 n->ip,
5791 n->port);
5792 for (i = 0; i < n->migrating_count; i += 2) {
5793 sds slot = n->migrating[i];
5794 dictReplace(open_slots, slot, sdsdup(n->migrating[i + 1]));
5795 char *fmt = (i > 0 ? ",%S" : "%S");
5796 errstr = sdscatfmt(errstr, fmt, slot);
5797 }
5798 errstr = sdscat(errstr, ".");
5799 clusterManagerOnError(errstr);
5800 }
5801 if (n->importing != NULL) {
5802 if (open_slots == NULL)
5803 open_slots = dictCreate(&clusterManagerDictType);
5804 sds errstr = sdsempty();
5805 errstr = sdscatprintf(errstr,
5806 "[WARNING] Node %s:%d has slots in "
5807 "importing state ",
5808 n->ip,
5809 n->port);
5810 for (i = 0; i < n->importing_count; i += 2) {
5811 sds slot = n->importing[i];
5812 dictReplace(open_slots, slot, sdsdup(n->importing[i + 1]));
5813 char *fmt = (i > 0 ? ",%S" : "%S");
5814 errstr = sdscatfmt(errstr, fmt, slot);
5815 }
5816 errstr = sdscat(errstr, ".");
5817 clusterManagerOnError(errstr);
5818 }
5819 }
5820 if (open_slots != NULL) {
5821 result = 0;
5822 dictIterator *iter = dictGetIterator(open_slots);
5823 dictEntry *entry;
5824 sds errstr = sdsnew("[WARNING] The following slots are open: ");
5825 i = 0;
5826 while ((entry = dictNext(iter)) != NULL) {
5827 sds slot = (sds) dictGetKey(entry);
5828 char *fmt = (i++ > 0 ? ",%S" : "%S");
5829 errstr = sdscatfmt(errstr, fmt, slot);
5830 }
5831 clusterManagerLogErr("%s.\n", (char *) errstr);
5832 sdsfree(errstr);
5833 if (do_fix) {
5834 /* Fix open slots. */
5835 dictReleaseIterator(iter);
5836 iter = dictGetIterator(open_slots);
5837 while ((entry = dictNext(iter)) != NULL) {
5838 sds slot = (sds) dictGetKey(entry);
5839 result = clusterManagerFixOpenSlot(atoi(slot));
5840 if (!result) break;
5841 }
5842 }
5843 dictReleaseIterator(iter);
5844 dictRelease(open_slots);
5845 }
5846 clusterManagerLogInfo(">>> Check slots coverage...\n");
5847 char slots[CLUSTER_MANAGER_SLOTS];
5848 memset(slots, 0, CLUSTER_MANAGER_SLOTS);
5849 int coverage = clusterManagerGetCoveredSlots(slots);
5850 if (coverage == CLUSTER_MANAGER_SLOTS) {
5851 clusterManagerLogOk("[OK] All %d slots covered.\n",
5852 CLUSTER_MANAGER_SLOTS);
5853 } else {
5854 sds err = sdsempty();
5855 err = sdscatprintf(err, "[ERR] Not all %d slots are "
5856 "covered by nodes.\n",
5857 CLUSTER_MANAGER_SLOTS);
5858 clusterManagerOnError(err);
5859 result = 0;
5860 if (do_fix/* && result*/) {
5861 dictType dtype = clusterManagerDictType;
5862 dtype.keyDestructor = dictSdsDestructor;
5863 dtype.valDestructor = dictListDestructor;
5864 clusterManagerUncoveredSlots = dictCreate(&dtype);
5865 int fixed = clusterManagerFixSlotsCoverage(slots);
5866 if (fixed > 0) result = 1;
5867 }
5868 }
5869 int search_multiple_owners = config.cluster_manager_command.flags &
5870 CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS;
5871 if (search_multiple_owners) {
5872 /* Check whether there are multiple owners, even when slots are
5873 * fully covered and there are no open slots. */
5874 clusterManagerLogInfo(">>> Check for multiple slot owners...\n");
5875 int slot = 0, slots_with_multiple_owners = 0;
5876 for (; slot < CLUSTER_MANAGER_SLOTS; slot++) {
5877 listIter li;
5878 listNode *ln;
5879 listRewind(cluster_manager.nodes, &li);
5880 list *owners = listCreate();
5881 while ((ln = listNext(&li)) != NULL) {
5882 clusterManagerNode *n = ln->value;
5883 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
5884 if (n->slots[slot]) listAddNodeTail(owners, n);
5885 else {
5886 /* Nodes having keys for the slot will be considered
5887 * owners too. */
5888 int count = clusterManagerCountKeysInSlot(n, slot);
5889 if (count > 0) listAddNodeTail(owners, n);
5890 }
5891 }
5892 if (listLength(owners) > 1) {
5893 result = 0;
5894 clusterManagerLogErr("[WARNING] Slot %d has %d owners:\n",
5895 slot, listLength(owners));
5896 listRewind(owners, &li);
5897 while ((ln = listNext(&li)) != NULL) {
5898 clusterManagerNode *n = ln->value;
5899 clusterManagerLogErr(" %s:%d\n", n->ip, n->port);
5900 }
5901 slots_with_multiple_owners++;
5902 if (do_fix) {
5903 result = clusterManagerFixMultipleSlotOwners(slot, owners);
5904 if (!result) {
5905 clusterManagerLogErr("Failed to fix multiple owners "
5906 "for slot %d\n", slot);
5907 listRelease(owners);
5908 break;
5909 } else slots_with_multiple_owners--;
5910 }
5911 }
5912 listRelease(owners);
5913 }
5914 if (slots_with_multiple_owners == 0)
5915 clusterManagerLogOk("[OK] No multiple owners found.\n");
5916 }
5917 return result;
5918}
5919
5920static clusterManagerNode *clusterNodeForResharding(char *id,
5921 clusterManagerNode *target,
5922 int *raise_err)
5923{
5924 clusterManagerNode *node = NULL;
5925 const char *invalid_node_msg = "*** The specified node (%s) is not known "
5926 "or not a master, please retry.\n";
5927 node = clusterManagerNodeByName(id);
5928 *raise_err = 0;
5929 if (!node || node->flags & CLUSTER_MANAGER_FLAG_SLAVE) {
5930 clusterManagerLogErr(invalid_node_msg, id);
5931 *raise_err = 1;
5932 return NULL;
5933 } else if (target != NULL) {
5934 if (!strcmp(node->name, target->name)) {
5935 clusterManagerLogErr( "*** It is not possible to use "
5936 "the target node as "
5937 "source node.\n");
5938 return NULL;
5939 }
5940 }
5941 return node;
5942}
5943
5944static list *clusterManagerComputeReshardTable(list *sources, int numslots) {
5945 list *moved = listCreate();
5946 int src_count = listLength(sources), i = 0, tot_slots = 0, j;
5947 clusterManagerNode **sorted = zmalloc(src_count * sizeof(*sorted));
5948 listIter li;
5949 listNode *ln;
5950 listRewind(sources, &li);
5951 while ((ln = listNext(&li)) != NULL) {
5952 clusterManagerNode *node = ln->value;
5953 tot_slots += node->slots_count;
5954 sorted[i++] = node;
5955 }
5956 qsort(sorted, src_count, sizeof(clusterManagerNode *),
5957 clusterManagerSlotCountCompareDesc);
5958 for (i = 0; i < src_count; i++) {
5959 clusterManagerNode *node = sorted[i];
5960 float n = ((float) numslots / tot_slots * node->slots_count);
5961 if (i == 0) n = ceil(n);
5962 else n = floor(n);
5963 int max = (int) n, count = 0;
5964 for (j = 0; j < CLUSTER_MANAGER_SLOTS; j++) {
5965 int slot = node->slots[j];
5966 if (!slot) continue;
5967 if (count >= max || (int)listLength(moved) >= numslots) break;
5968 clusterManagerReshardTableItem *item = zmalloc(sizeof(*item));
5969 item->source = node;
5970 item->slot = j;
5971 listAddNodeTail(moved, item);
5972 count++;
5973 }
5974 }
5975 zfree(sorted);
5976 return moved;
5977}
5978
5979static void clusterManagerShowReshardTable(list *table) {
5980 listIter li;
5981 listNode *ln;
5982 listRewind(table, &li);
5983 while ((ln = listNext(&li)) != NULL) {
5984 clusterManagerReshardTableItem *item = ln->value;
5985 clusterManagerNode *n = item->source;
5986 printf(" Moving slot %d from %s\n", item->slot, (char *) n->name);
5987 }
5988}
5989
5990static void clusterManagerReleaseReshardTable(list *table) {
5991 if (table != NULL) {
5992 listIter li;
5993 listNode *ln;
5994 listRewind(table, &li);
5995 while ((ln = listNext(&li)) != NULL) {
5996 clusterManagerReshardTableItem *item = ln->value;
5997 zfree(item);
5998 }
5999 listRelease(table);
6000 }
6001}
6002
6003static void clusterManagerLog(int level, const char* fmt, ...) {
6004 int use_colors =
6005 (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_COLOR);
6006 if (use_colors) {
6007 printf("\033[");
6008 switch (level) {
6009 case CLUSTER_MANAGER_LOG_LVL_INFO: printf(LOG_COLOR_BOLD); break;
6010 case CLUSTER_MANAGER_LOG_LVL_WARN: printf(LOG_COLOR_YELLOW); break;
6011 case CLUSTER_MANAGER_LOG_LVL_ERR: printf(LOG_COLOR_RED); break;
6012 case CLUSTER_MANAGER_LOG_LVL_SUCCESS: printf(LOG_COLOR_GREEN); break;
6013 default: printf(LOG_COLOR_RESET); break;
6014 }
6015 }
6016 va_list ap;
6017 va_start(ap, fmt);
6018 vprintf(fmt, ap);
6019 va_end(ap);
6020 if (use_colors) printf("\033[" LOG_COLOR_RESET);
6021}
6022
6023static void clusterManagerNodeArrayInit(clusterManagerNodeArray *array,
6024 int alloc_len)
6025{
6026 array->nodes = zcalloc(alloc_len * sizeof(clusterManagerNode*));
6027 array->alloc = array->nodes;
6028 array->len = alloc_len;
6029 array->count = 0;
6030}
6031
6032/* Reset array->nodes to the original array allocation and re-count non-NULL
6033 * nodes. */
6034static void clusterManagerNodeArrayReset(clusterManagerNodeArray *array) {
6035 if (array->nodes > array->alloc) {
6036 array->len = array->nodes - array->alloc;
6037 array->nodes = array->alloc;
6038 array->count = 0;
6039 int i = 0;
6040 for(; i < array->len; i++) {
6041 if (array->nodes[i] != NULL) array->count++;
6042 }
6043 }
6044}
6045
6046/* Shift array->nodes and store the shifted node into 'nodeptr'. */
6047static void clusterManagerNodeArrayShift(clusterManagerNodeArray *array,
6048 clusterManagerNode **nodeptr)
6049{
6050 assert(array->len > 0);
6051 /* If the first node to be shifted is not NULL, decrement count. */
6052 if (*array->nodes != NULL) array->count--;
6053 /* Store the first node to be shifted into 'nodeptr'. */
6054 *nodeptr = *array->nodes;
6055 /* Shift the nodes array and decrement length. */
6056 array->nodes++;
6057 array->len--;
6058}
6059
6060static void clusterManagerNodeArrayAdd(clusterManagerNodeArray *array,
6061 clusterManagerNode *node)
6062{
6063 assert(array->len > 0);
6064 assert(node != NULL);
6065 assert(array->count < array->len);
6066 array->nodes[array->count++] = node;
6067}
6068
6069static void clusterManagerPrintNotEmptyNodeError(clusterManagerNode *node,
6070 char *err)
6071{
6072 char *msg;
6073 if (err) msg = err;
6074 else {
6075 msg = "is not empty. Either the node already knows other "
6076 "nodes (check with CLUSTER NODES) or contains some "
6077 "key in database 0.";
6078 }
6079 clusterManagerLogErr("[ERR] Node %s:%d %s\n", node->ip, node->port, msg);
6080}
6081
6082static void clusterManagerPrintNotClusterNodeError(clusterManagerNode *node,
6083 char *err)
6084{
6085 char *msg = (err ? err : "is not configured as a cluster node.");
6086 clusterManagerLogErr("[ERR] Node %s:%d %s\n", node->ip, node->port, msg);
6087}
6088
6089/* Execute redis-cli in Cluster Manager mode */
6090static void clusterManagerMode(clusterManagerCommandProc *proc) {
6091 int argc = config.cluster_manager_command.argc;
6092 char **argv = config.cluster_manager_command.argv;
6093 cluster_manager.nodes = NULL;
6094 int success = proc(argc, argv);
6095
6096 /* Initialized in createClusterManagerCommand. */
6097 if (config.stdin_lastarg) {
6098 zfree(config.cluster_manager_command.argv);
6099 sdsfree(config.cluster_manager_command.stdin_arg);
6100 } else if (config.stdin_tag_arg) {
6101 sdsfree(config.cluster_manager_command.stdin_arg);
6102 }
6103 freeClusterManager();
6104
6105 exit(success ? 0 : 1);
6106}
6107
6108/* Cluster Manager Commands */
6109
6110static int clusterManagerCommandCreate(int argc, char **argv) {
6111 int i, j, success = 1;
6112 cluster_manager.nodes = listCreate();
6113 for (i = 0; i < argc; i++) {
6114 char *addr = argv[i];
6115 char *ip = NULL;
6116 int port = 0;
6117 if (!parseClusterNodeAddress(addr, &ip, &port, NULL)) {
6118 fprintf(stderr, "Invalid address format: %s\n", addr);
6119 return 0;
6120 }
6121
6122 clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
6123 if (!clusterManagerNodeConnect(node)) {
6124 freeClusterManagerNode(node);
6125 return 0;
6126 }
6127 char *err = NULL;
6128 if (!clusterManagerNodeIsCluster(node, &err)) {
6129 clusterManagerPrintNotClusterNodeError(node, err);
6130 if (err) zfree(err);
6131 freeClusterManagerNode(node);
6132 return 0;
6133 }
6134 err = NULL;
6135 if (!clusterManagerNodeLoadInfo(node, 0, &err)) {
6136 if (err) {
6137 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
6138 zfree(err);
6139 }
6140 freeClusterManagerNode(node);
6141 return 0;
6142 }
6143 err = NULL;
6144 if (!clusterManagerNodeIsEmpty(node, &err)) {
6145 clusterManagerPrintNotEmptyNodeError(node, err);
6146 if (err) zfree(err);
6147 freeClusterManagerNode(node);
6148 return 0;
6149 }
6150 listAddNodeTail(cluster_manager.nodes, node);
6151 }
6152 int node_len = cluster_manager.nodes->len;
6153 int replicas = config.cluster_manager_command.replicas;
6154 int masters_count = CLUSTER_MANAGER_MASTERS_COUNT(node_len, replicas);
6155 if (masters_count < 3) {
6156 clusterManagerLogErr(
6157 "*** ERROR: Invalid configuration for cluster creation.\n"
6158 "*** Redis Cluster requires at least 3 master nodes.\n"
6159 "*** This is not possible with %d nodes and %d replicas per node.",
6160 node_len, replicas);
6161 clusterManagerLogErr("\n*** At least %d nodes are required.\n",
6162 3 * (replicas + 1));
6163 return 0;
6164 }
6165 clusterManagerLogInfo(">>> Performing hash slots allocation "
6166 "on %d nodes...\n", node_len);
6167 int interleaved_len = 0, ip_count = 0;
6168 clusterManagerNode **interleaved = zcalloc(node_len*sizeof(**interleaved));
6169 char **ips = zcalloc(node_len * sizeof(char*));
6170 clusterManagerNodeArray *ip_nodes = zcalloc(node_len * sizeof(*ip_nodes));
6171 listIter li;
6172 listNode *ln;
6173 listRewind(cluster_manager.nodes, &li);
6174 while ((ln = listNext(&li)) != NULL) {
6175 clusterManagerNode *n = ln->value;
6176 int found = 0;
6177 for (i = 0; i < ip_count; i++) {
6178 char *ip = ips[i];
6179 if (!strcmp(ip, n->ip)) {
6180 found = 1;
6181 break;
6182 }
6183 }
6184 if (!found) {
6185 ips[ip_count++] = n->ip;
6186 }
6187 clusterManagerNodeArray *node_array = &(ip_nodes[i]);
6188 if (node_array->nodes == NULL)
6189 clusterManagerNodeArrayInit(node_array, node_len);
6190 clusterManagerNodeArrayAdd(node_array, n);
6191 }
6192 while (interleaved_len < node_len) {
6193 for (i = 0; i < ip_count; i++) {
6194 clusterManagerNodeArray *node_array = &(ip_nodes[i]);
6195 if (node_array->count > 0) {
6196 clusterManagerNode *n = NULL;
6197 clusterManagerNodeArrayShift(node_array, &n);
6198 interleaved[interleaved_len++] = n;
6199 }
6200 }
6201 }
6202 clusterManagerNode **masters = interleaved;
6203 interleaved += masters_count;
6204 interleaved_len -= masters_count;
6205 float slots_per_node = CLUSTER_MANAGER_SLOTS / (float) masters_count;
6206 long first = 0;
6207 float cursor = 0.0f;
6208 for (i = 0; i < masters_count; i++) {
6209 clusterManagerNode *master = masters[i];
6210 long last = lround(cursor + slots_per_node - 1);
6211 if (last > CLUSTER_MANAGER_SLOTS || i == (masters_count - 1))
6212 last = CLUSTER_MANAGER_SLOTS - 1;
6213 if (last < first) last = first;
6214 printf("Master[%d] -> Slots %ld - %ld\n", i, first, last);
6215 master->slots_count = 0;
6216 for (j = first; j <= last; j++) {
6217 master->slots[j] = 1;
6218 master->slots_count++;
6219 }
6220 master->dirty = 1;
6221 first = last + 1;
6222 cursor += slots_per_node;
6223 }
6224
6225 /* Rotating the list sometimes helps to get better initial
6226 * anti-affinity before the optimizer runs. */
6227 clusterManagerNode *first_node = interleaved[0];
6228 for (i = 0; i < (interleaved_len - 1); i++)
6229 interleaved[i] = interleaved[i + 1];
6230 interleaved[interleaved_len - 1] = first_node;
6231 int assign_unused = 0, available_count = interleaved_len;
6232assign_replicas:
6233 for (i = 0; i < masters_count; i++) {
6234 clusterManagerNode *master = masters[i];
6235 int assigned_replicas = 0;
6236 while (assigned_replicas < replicas) {
6237 if (available_count == 0) break;
6238 clusterManagerNode *found = NULL, *slave = NULL;
6239 int firstNodeIdx = -1;
6240 for (j = 0; j < interleaved_len; j++) {
6241 clusterManagerNode *n = interleaved[j];
6242 if (n == NULL) continue;
6243 if (strcmp(n->ip, master->ip)) {
6244 found = n;
6245 interleaved[j] = NULL;
6246 break;
6247 }
6248 if (firstNodeIdx < 0) firstNodeIdx = j;
6249 }
6250 if (found) slave = found;
6251 else if (firstNodeIdx >= 0) {
6252 slave = interleaved[firstNodeIdx];
6253 interleaved_len -= (firstNodeIdx + 1);
6254 interleaved += (firstNodeIdx + 1);
6255 }
6256 if (slave != NULL) {
6257 assigned_replicas++;
6258 available_count--;
6259 if (slave->replicate) sdsfree(slave->replicate);
6260 slave->replicate = sdsnew(master->name);
6261 slave->dirty = 1;
6262 } else break;
6263 printf("Adding replica %s:%d to %s:%d\n", slave->ip, slave->port,
6264 master->ip, master->port);
6265 if (assign_unused) break;
6266 }
6267 }
6268 if (!assign_unused && available_count > 0) {
6269 assign_unused = 1;
6270 printf("Adding extra replicas...\n");
6271 goto assign_replicas;
6272 }
6273 for (i = 0; i < ip_count; i++) {
6274 clusterManagerNodeArray *node_array = ip_nodes + i;
6275 clusterManagerNodeArrayReset(node_array);
6276 }
6277 clusterManagerOptimizeAntiAffinity(ip_nodes, ip_count);
6278 clusterManagerShowNodes();
6279 int ignore_force = 0;
6280 if (confirmWithYes("Can I set the above configuration?", ignore_force)) {
6281 listRewind(cluster_manager.nodes, &li);
6282 while ((ln = listNext(&li)) != NULL) {
6283 clusterManagerNode *node = ln->value;
6284 char *err = NULL;
6285 int flushed = clusterManagerFlushNodeConfig(node, &err);
6286 if (!flushed && node->dirty && !node->replicate) {
6287 if (err != NULL) {
6288 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
6289 zfree(err);
6290 }
6291 success = 0;
6292 goto cleanup;
6293 } else if (err != NULL) zfree(err);
6294 }
6295 clusterManagerLogInfo(">>> Nodes configuration updated\n");
6296 clusterManagerLogInfo(">>> Assign a different config epoch to "
6297 "each node\n");
6298 int config_epoch = 1;
6299 listRewind(cluster_manager.nodes, &li);
6300 while ((ln = listNext(&li)) != NULL) {
6301 clusterManagerNode *node = ln->value;
6302 redisReply *reply = NULL;
6303 reply = CLUSTER_MANAGER_COMMAND(node,
6304 "cluster set-config-epoch %d",
6305 config_epoch++);
6306 if (reply != NULL) freeReplyObject(reply);
6307 }
6308 clusterManagerLogInfo(">>> Sending CLUSTER MEET messages to join "
6309 "the cluster\n");
6310 clusterManagerNode *first = NULL;
6311 char first_ip[NET_IP_STR_LEN]; /* first->ip may be a hostname */
6312 listRewind(cluster_manager.nodes, &li);
6313 while ((ln = listNext(&li)) != NULL) {
6314 clusterManagerNode *node = ln->value;
6315 if (first == NULL) {
6316 first = node;
6317 /* Although hiredis supports connecting to a hostname, CLUSTER
6318 * MEET requires an IP address, so we do a DNS lookup here. */
6319 if (anetResolve(NULL, first->ip, first_ip, sizeof(first_ip), ANET_NONE)
6320 == ANET_ERR)
6321 {
6322 fprintf(stderr, "Invalid IP address or hostname specified: %s\n", first->ip);
6323 success = 0;
6324 goto cleanup;
6325 }
6326 continue;
6327 }
6328 redisReply *reply = NULL;
6329 if (first->bus_port == 0 || (first->bus_port == first->port + CLUSTER_MANAGER_PORT_INCR)) {
6330 /* CLUSTER MEET bus-port parameter was added in 4.0.
6331 * So if (bus_port == 0) or (bus_port == port + CLUSTER_MANAGER_PORT_INCR),
6332 * we just call CLUSTER MEET with 2 arguments, using the old form. */
6333 reply = CLUSTER_MANAGER_COMMAND(node, "cluster meet %s %d",
6334 first->ip, first->port);
6335 } else {
6336 reply = CLUSTER_MANAGER_COMMAND(node, "cluster meet %s %d %d",
6337 first->ip, first->port, first->bus_port);
6338 }
6339 int is_err = 0;
6340 if (reply != NULL) {
6341 if ((is_err = reply->type == REDIS_REPLY_ERROR))
6342 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, reply->str);
6343 freeReplyObject(reply);
6344 } else {
6345 is_err = 1;
6346 fprintf(stderr, "Failed to send CLUSTER MEET command.\n");
6347 }
6348 if (is_err) {
6349 success = 0;
6350 goto cleanup;
6351 }
6352 }
6353 /* Give one second for the join to start, in order to avoid that
6354 * waiting for cluster join will find all the nodes agree about
6355 * the config as they are still empty with unassigned slots. */
6356 sleep(1);
6357 clusterManagerWaitForClusterJoin();
6358 /* Useful for the replicas */
6359 listRewind(cluster_manager.nodes, &li);
6360 while ((ln = listNext(&li)) != NULL) {
6361 clusterManagerNode *node = ln->value;
6362 if (!node->dirty) continue;
6363 char *err = NULL;
6364 int flushed = clusterManagerFlushNodeConfig(node, &err);
6365 if (!flushed && !node->replicate) {
6366 if (err != NULL) {
6367 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
6368 zfree(err);
6369 }
6370 success = 0;
6371 goto cleanup;
6372 } else if (err != NULL) {
6373 zfree(err);
6374 }
6375 }
6376 // Reset Nodes
6377 listRewind(cluster_manager.nodes, &li);
6378 clusterManagerNode *first_node = NULL;
6379 while ((ln = listNext(&li)) != NULL) {
6380 clusterManagerNode *node = ln->value;
6381 if (!first_node) first_node = node;
6382 else freeClusterManagerNode(node);
6383 }
6384 listEmpty(cluster_manager.nodes);
6385 if (!clusterManagerLoadInfoFromNode(first_node)) {
6386 success = 0;
6387 goto cleanup;
6388 }
6389 clusterManagerCheckCluster(0);
6390 }
6391cleanup:
6392 /* Free everything */
6393 zfree(masters);
6394 zfree(ips);
6395 for (i = 0; i < node_len; i++) {
6396 clusterManagerNodeArray *node_array = ip_nodes + i;
6397 CLUSTER_MANAGER_NODE_ARRAY_FREE(node_array);
6398 }
6399 zfree(ip_nodes);
6400 return success;
6401}
6402
6403static int clusterManagerCommandAddNode(int argc, char **argv) {
6404 int success = 1;
6405 redisReply *reply = NULL;
6406 redisReply *function_restore_reply = NULL;
6407 redisReply *function_list_reply = NULL;
6408 char *ref_ip = NULL, *ip = NULL;
6409 int ref_port = 0, port = 0;
6410 if (!getClusterHostFromCmdArgs(argc - 1, argv + 1, &ref_ip, &ref_port))
6411 goto invalid_args;
6412 if (!getClusterHostFromCmdArgs(1, argv, &ip, &port))
6413 goto invalid_args;
6414 clusterManagerLogInfo(">>> Adding node %s:%d to cluster %s:%d\n", ip, port,
6415 ref_ip, ref_port);
6416 // Check the existing cluster
6417 clusterManagerNode *refnode = clusterManagerNewNode(ref_ip, ref_port, 0);
6418 if (!clusterManagerLoadInfoFromNode(refnode)) return 0;
6419 if (!clusterManagerCheckCluster(0)) return 0;
6420
6421 /* If --cluster-master-id was specified, try to resolve it now so that we
6422 * abort before starting with the node configuration. */
6423 clusterManagerNode *master_node = NULL;
6424 if (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_SLAVE) {
6425 char *master_id = config.cluster_manager_command.master_id;
6426 if (master_id != NULL) {
6427 master_node = clusterManagerNodeByName(master_id);
6428 if (master_node == NULL) {
6429 clusterManagerLogErr("[ERR] No such master ID %s\n", master_id);
6430 return 0;
6431 }
6432 } else {
6433 master_node = clusterManagerNodeWithLeastReplicas();
6434 assert(master_node != NULL);
6435 printf("Automatically selected master %s:%d\n", master_node->ip,
6436 master_node->port);
6437 }
6438 }
6439
6440 // Add the new node
6441 clusterManagerNode *new_node = clusterManagerNewNode(ip, port, 0);
6442 int added = 0;
6443 if (!clusterManagerNodeConnect(new_node)) {
6444 clusterManagerLogErr("[ERR] Sorry, can't connect to node %s:%d\n",
6445 ip, port);
6446 success = 0;
6447 goto cleanup;
6448 }
6449 char *err = NULL;
6450 if (!(success = clusterManagerNodeIsCluster(new_node, &err))) {
6451 clusterManagerPrintNotClusterNodeError(new_node, err);
6452 if (err) zfree(err);
6453 goto cleanup;
6454 }
6455 if (!clusterManagerNodeLoadInfo(new_node, 0, &err)) {
6456 if (err) {
6457 CLUSTER_MANAGER_PRINT_REPLY_ERROR(new_node, err);
6458 zfree(err);
6459 }
6460 success = 0;
6461 goto cleanup;
6462 }
6463 if (!(success = clusterManagerNodeIsEmpty(new_node, &err))) {
6464 clusterManagerPrintNotEmptyNodeError(new_node, err);
6465 if (err) zfree(err);
6466 goto cleanup;
6467 }
6468 clusterManagerNode *first = listFirst(cluster_manager.nodes)->value;
6469 listAddNodeTail(cluster_manager.nodes, new_node);
6470 added = 1;
6471
6472 if (!master_node) {
6473 /* Send functions to the new node, if new node is a replica it will get the functions from its primary. */
6474 clusterManagerLogInfo(">>> Getting functions from cluster\n");
6475 reply = CLUSTER_MANAGER_COMMAND(refnode, "FUNCTION DUMP");
6476 if (!clusterManagerCheckRedisReply(refnode, reply, &err)) {
6477 clusterManagerLogInfo(">>> Failed retrieving Functions from the cluster, "
6478 "skip this step as Redis version do not support function command (error = '%s')\n", err? err : "NULL reply");
6479 if (err) zfree(err);
6480 } else {
6481 assert(reply->type == REDIS_REPLY_STRING);
6482 clusterManagerLogInfo(">>> Send FUNCTION LIST to %s:%d to verify there is no functions in it\n", ip, port);
6483 function_list_reply = CLUSTER_MANAGER_COMMAND(new_node, "FUNCTION LIST");
6484 if (!clusterManagerCheckRedisReply(new_node, function_list_reply, &err)) {
6485 clusterManagerLogErr(">>> Failed on CLUSTER LIST (error = '%s')\r\n", err? err : "NULL reply");
6486 if (err) zfree(err);
6487 success = 0;
6488 goto cleanup;
6489 }
6490 assert(function_list_reply->type == REDIS_REPLY_ARRAY);
6491 if (function_list_reply->elements > 0) {
6492 clusterManagerLogErr(">>> New node already contains functions and can not be added to the cluster. Use FUNCTION FLUSH and try again.\r\n");
6493 success = 0;
6494 goto cleanup;
6495 }
6496 clusterManagerLogInfo(">>> Send FUNCTION RESTORE to %s:%d\n", ip, port);
6497 function_restore_reply = CLUSTER_MANAGER_COMMAND(new_node, "FUNCTION RESTORE %b", reply->str, reply->len);
6498 if (!clusterManagerCheckRedisReply(new_node, function_restore_reply, &err)) {
6499 clusterManagerLogErr(">>> Failed loading functions to the new node (error = '%s')\r\n", err? err : "NULL reply");
6500 if (err) zfree(err);
6501 success = 0;
6502 goto cleanup;
6503 }
6504 }
6505 }
6506
6507 if (reply) freeReplyObject(reply);
6508
6509 // Send CLUSTER MEET command to the new node
6510 clusterManagerLogInfo(">>> Send CLUSTER MEET to node %s:%d to make it "
6511 "join the cluster.\n", ip, port);
6512 /* CLUSTER MEET requires an IP address, so we do a DNS lookup here. */
6513 char first_ip[NET_IP_STR_LEN];
6514 if (anetResolve(NULL, first->ip, first_ip, sizeof(first_ip), ANET_NONE) == ANET_ERR) {
6515 fprintf(stderr, "Invalid IP address or hostname specified: %s\n", first->ip);
6516 success = 0;
6517 goto cleanup;
6518 }
6519
6520 if (first->bus_port == 0 || (first->bus_port == first->port + CLUSTER_MANAGER_PORT_INCR)) {
6521 /* CLUSTER MEET bus-port parameter was added in 4.0.
6522 * So if (bus_port == 0) or (bus_port == port + CLUSTER_MANAGER_PORT_INCR),
6523 * we just call CLUSTER MEET with 2 arguments, using the old form. */
6524 reply = CLUSTER_MANAGER_COMMAND(new_node, "CLUSTER MEET %s %d",
6525 first_ip, first->port);
6526 } else {
6527 reply = CLUSTER_MANAGER_COMMAND(new_node, "CLUSTER MEET %s %d %d",
6528 first->ip, first->port, first->bus_port);
6529 }
6530
6531 if (!(success = clusterManagerCheckRedisReply(new_node, reply, NULL)))
6532 goto cleanup;
6533
6534 /* Additional configuration is needed if the node is added as a slave. */
6535 if (master_node) {
6536 sleep(1);
6537 clusterManagerWaitForClusterJoin();
6538 clusterManagerLogInfo(">>> Configure node as replica of %s:%d.\n",
6539 master_node->ip, master_node->port);
6540 freeReplyObject(reply);
6541 reply = CLUSTER_MANAGER_COMMAND(new_node, "CLUSTER REPLICATE %s",
6542 master_node->name);
6543 if (!(success = clusterManagerCheckRedisReply(new_node, reply, NULL)))
6544 goto cleanup;
6545 }
6546 clusterManagerLogOk("[OK] New node added correctly.\n");
6547cleanup:
6548 if (!added && new_node) freeClusterManagerNode(new_node);
6549 if (reply) freeReplyObject(reply);
6550 if (function_restore_reply) freeReplyObject(function_restore_reply);
6551 if (function_list_reply) freeReplyObject(function_list_reply);
6552 return success;
6553invalid_args:
6554 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
6555 return 0;
6556}
6557
6558static int clusterManagerCommandDeleteNode(int argc, char **argv) {
6559 UNUSED(argc);
6560 int success = 1;
6561 int port = 0;
6562 char *ip = NULL;
6563 if (!getClusterHostFromCmdArgs(1, argv, &ip, &port)) goto invalid_args;
6564 char *node_id = argv[1];
6565 clusterManagerLogInfo(">>> Removing node %s from cluster %s:%d\n",
6566 node_id, ip, port);
6567 clusterManagerNode *ref_node = clusterManagerNewNode(ip, port, 0);
6568 clusterManagerNode *node = NULL;
6569
6570 // Load cluster information
6571 if (!clusterManagerLoadInfoFromNode(ref_node)) return 0;
6572
6573 // Check if the node exists and is not empty
6574 node = clusterManagerNodeByName(node_id);
6575 if (node == NULL) {
6576 clusterManagerLogErr("[ERR] No such node ID %s\n", node_id);
6577 return 0;
6578 }
6579 if (node->slots_count != 0) {
6580 clusterManagerLogErr("[ERR] Node %s:%d is not empty! Reshard data "
6581 "away and try again.\n", node->ip, node->port);
6582 return 0;
6583 }
6584
6585 // Send CLUSTER FORGET to all the nodes but the node to remove
6586 clusterManagerLogInfo(">>> Sending CLUSTER FORGET messages to the "
6587 "cluster...\n");
6588 listIter li;
6589 listNode *ln;
6590 listRewind(cluster_manager.nodes, &li);
6591 while ((ln = listNext(&li)) != NULL) {
6592 clusterManagerNode *n = ln->value;
6593 if (n == node) continue;
6594 if (n->replicate && !strcasecmp(n->replicate, node_id)) {
6595 // Reconfigure the slave to replicate with some other node
6596 clusterManagerNode *master = clusterManagerNodeWithLeastReplicas();
6597 assert(master != NULL);
6598 clusterManagerLogInfo(">>> %s:%d as replica of %s:%d\n",
6599 n->ip, n->port, master->ip, master->port);
6600 redisReply *r = CLUSTER_MANAGER_COMMAND(n, "CLUSTER REPLICATE %s",
6601 master->name);
6602 success = clusterManagerCheckRedisReply(n, r, NULL);
6603 if (r) freeReplyObject(r);
6604 if (!success) return 0;
6605 }
6606 redisReply *r = CLUSTER_MANAGER_COMMAND(n, "CLUSTER FORGET %s",
6607 node_id);
6608 success = clusterManagerCheckRedisReply(n, r, NULL);
6609 if (r) freeReplyObject(r);
6610 if (!success) return 0;
6611 }
6612
6613 /* Finally send CLUSTER RESET to the node. */
6614 clusterManagerLogInfo(">>> Sending CLUSTER RESET SOFT to the "
6615 "deleted node.\n");
6616 redisReply *r = redisCommand(node->context, "CLUSTER RESET %s", "SOFT");
6617 success = clusterManagerCheckRedisReply(node, r, NULL);
6618 if (r) freeReplyObject(r);
6619 return success;
6620invalid_args:
6621 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
6622 return 0;
6623}
6624
6625static int clusterManagerCommandInfo(int argc, char **argv) {
6626 int port = 0;
6627 char *ip = NULL;
6628 if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) goto invalid_args;
6629 clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
6630 if (!clusterManagerLoadInfoFromNode(node)) return 0;
6631 clusterManagerShowClusterInfo();
6632 return 1;
6633invalid_args:
6634 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
6635 return 0;
6636}
6637
6638static int clusterManagerCommandCheck(int argc, char **argv) {
6639 int port = 0;
6640 char *ip = NULL;
6641 if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) goto invalid_args;
6642 clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
6643 if (!clusterManagerLoadInfoFromNode(node)) return 0;
6644 clusterManagerShowClusterInfo();
6645 return clusterManagerCheckCluster(0);
6646invalid_args:
6647 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
6648 return 0;
6649}
6650
6651static int clusterManagerCommandFix(int argc, char **argv) {
6652 config.cluster_manager_command.flags |= CLUSTER_MANAGER_CMD_FLAG_FIX;
6653 return clusterManagerCommandCheck(argc, argv);
6654}
6655
6656static int clusterManagerCommandReshard(int argc, char **argv) {
6657 int port = 0;
6658 char *ip = NULL;
6659 if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) goto invalid_args;
6660 clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
6661 if (!clusterManagerLoadInfoFromNode(node)) return 0;
6662 clusterManagerCheckCluster(0);
6663 if (cluster_manager.errors && listLength(cluster_manager.errors) > 0) {
6664 fflush(stdout);
6665 fprintf(stderr,
6666 "*** Please fix your cluster problems before resharding\n");
6667 return 0;
6668 }
6669 int slots = config.cluster_manager_command.slots;
6670 if (!slots) {
6671 while (slots <= 0 || slots > CLUSTER_MANAGER_SLOTS) {
6672 printf("How many slots do you want to move (from 1 to %d)? ",
6673 CLUSTER_MANAGER_SLOTS);
6674 fflush(stdout);
6675 char buf[6];
6676 int nread = read(fileno(stdin),buf,6);
6677 if (nread <= 0) continue;
6678 int last_idx = nread - 1;
6679 if (buf[last_idx] != '\n') {
6680 int ch;
6681 while ((ch = getchar()) != '\n' && ch != EOF) {}
6682 }
6683 buf[last_idx] = '\0';
6684 slots = atoi(buf);
6685 }
6686 }
6687 char buf[255];
6688 char *to = config.cluster_manager_command.to,
6689 *from = config.cluster_manager_command.from;
6690 while (to == NULL) {
6691 printf("What is the receiving node ID? ");
6692 fflush(stdout);
6693 int nread = read(fileno(stdin),buf,255);
6694 if (nread <= 0) continue;
6695 int last_idx = nread - 1;
6696 if (buf[last_idx] != '\n') {
6697 int ch;
6698 while ((ch = getchar()) != '\n' && ch != EOF) {}
6699 }
6700 buf[last_idx] = '\0';
6701 if (strlen(buf) > 0) to = buf;
6702 }
6703 int raise_err = 0;
6704 clusterManagerNode *target = clusterNodeForResharding(to, NULL, &raise_err);
6705 if (target == NULL) return 0;
6706 list *sources = listCreate();
6707 list *table = NULL;
6708 int all = 0, result = 1;
6709 if (from == NULL) {
6710 printf("Please enter all the source node IDs.\n");
6711 printf(" Type 'all' to use all the nodes as source nodes for "
6712 "the hash slots.\n");
6713 printf(" Type 'done' once you entered all the source nodes IDs.\n");
6714 while (1) {
6715 printf("Source node #%lu: ", listLength(sources) + 1);
6716 fflush(stdout);
6717 int nread = read(fileno(stdin),buf,255);
6718 if (nread <= 0) continue;
6719 int last_idx = nread - 1;
6720 if (buf[last_idx] != '\n') {
6721 int ch;
6722 while ((ch = getchar()) != '\n' && ch != EOF) {}
6723 }
6724 buf[last_idx] = '\0';
6725 if (!strcmp(buf, "done")) break;
6726 else if (!strcmp(buf, "all")) {
6727 all = 1;
6728 break;
6729 } else {
6730 clusterManagerNode *src =
6731 clusterNodeForResharding(buf, target, &raise_err);
6732 if (src != NULL) listAddNodeTail(sources, src);
6733 else if (raise_err) {
6734 result = 0;
6735 goto cleanup;
6736 }
6737 }
6738 }
6739 } else {
6740 char *p;
6741 while((p = strchr(from, ',')) != NULL) {
6742 *p = '\0';
6743 if (!strcmp(from, "all")) {
6744 all = 1;
6745 break;
6746 } else {
6747 clusterManagerNode *src =
6748 clusterNodeForResharding(from, target, &raise_err);
6749 if (src != NULL) listAddNodeTail(sources, src);
6750 else if (raise_err) {
6751 result = 0;
6752 goto cleanup;
6753 }
6754 }
6755 from = p + 1;
6756 }
6757 /* Check if there's still another source to process. */
6758 if (!all && strlen(from) > 0) {
6759 if (!strcmp(from, "all")) all = 1;
6760 if (!all) {
6761 clusterManagerNode *src =
6762 clusterNodeForResharding(from, target, &raise_err);
6763 if (src != NULL) listAddNodeTail(sources, src);
6764 else if (raise_err) {
6765 result = 0;
6766 goto cleanup;
6767 }
6768 }
6769 }
6770 }
6771 listIter li;
6772 listNode *ln;
6773 if (all) {
6774 listEmpty(sources);
6775 listRewind(cluster_manager.nodes, &li);
6776 while ((ln = listNext(&li)) != NULL) {
6777 clusterManagerNode *n = ln->value;
6778 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE || n->replicate)
6779 continue;
6780 if (!sdscmp(n->name, target->name)) continue;
6781 listAddNodeTail(sources, n);
6782 }
6783 }
6784 if (listLength(sources) == 0) {
6785 fprintf(stderr, "*** No source nodes given, operation aborted.\n");
6786 result = 0;
6787 goto cleanup;
6788 }
6789 printf("\nReady to move %d slots.\n", slots);
6790 printf(" Source nodes:\n");
6791 listRewind(sources, &li);
6792 while ((ln = listNext(&li)) != NULL) {
6793 clusterManagerNode *src = ln->value;
6794 sds info = clusterManagerNodeInfo(src, 4);
6795 printf("%s\n", info);
6796 sdsfree(info);
6797 }
6798 printf(" Destination node:\n");
6799 sds info = clusterManagerNodeInfo(target, 4);
6800 printf("%s\n", info);
6801 sdsfree(info);
6802 table = clusterManagerComputeReshardTable(sources, slots);
6803 printf(" Resharding plan:\n");
6804 clusterManagerShowReshardTable(table);
6805 if (!(config.cluster_manager_command.flags &
6806 CLUSTER_MANAGER_CMD_FLAG_YES))
6807 {
6808 printf("Do you want to proceed with the proposed "
6809 "reshard plan (yes/no)? ");
6810 fflush(stdout);
6811 char buf[4];
6812 int nread = read(fileno(stdin),buf,4);
6813 buf[3] = '\0';
6814 if (nread <= 0 || strcmp("yes", buf) != 0) {
6815 result = 0;
6816 goto cleanup;
6817 }
6818 }
6819 int opts = CLUSTER_MANAGER_OPT_VERBOSE;
6820 listRewind(table, &li);
6821 while ((ln = listNext(&li)) != NULL) {
6822 clusterManagerReshardTableItem *item = ln->value;
6823 char *err = NULL;
6824 result = clusterManagerMoveSlot(item->source, target, item->slot,
6825 opts, &err);
6826 if (!result) {
6827 if (err != NULL) {
6828 clusterManagerLogErr("clusterManagerMoveSlot failed: %s\n", err);
6829 zfree(err);
6830 }
6831 goto cleanup;
6832 }
6833 }
6834cleanup:
6835 listRelease(sources);
6836 clusterManagerReleaseReshardTable(table);
6837 return result;
6838invalid_args:
6839 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
6840 return 0;
6841}
6842
6843static int clusterManagerCommandRebalance(int argc, char **argv) {
6844 int port = 0;
6845 char *ip = NULL;
6846 clusterManagerNode **weightedNodes = NULL;
6847 list *involved = NULL;
6848 if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) goto invalid_args;
6849 clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
6850 if (!clusterManagerLoadInfoFromNode(node)) return 0;
6851 int result = 1, i;
6852 if (config.cluster_manager_command.weight != NULL) {
6853 for (i = 0; i < config.cluster_manager_command.weight_argc; i++) {
6854 char *name = config.cluster_manager_command.weight[i];
6855 char *p = strchr(name, '=');
6856 if (p == NULL) {
6857 clusterManagerLogErr("*** invalid input %s\n", name);
6858 result = 0;
6859 goto cleanup;
6860 }
6861 *p = '\0';
6862 float w = atof(++p);
6863 clusterManagerNode *n = clusterManagerNodeByAbbreviatedName(name);
6864 if (n == NULL) {
6865 clusterManagerLogErr("*** No such master node %s\n", name);
6866 result = 0;
6867 goto cleanup;
6868 }
6869 n->weight = w;
6870 }
6871 }
6872 float total_weight = 0;
6873 int nodes_involved = 0;
6874 int use_empty = config.cluster_manager_command.flags &
6875 CLUSTER_MANAGER_CMD_FLAG_EMPTYMASTER;
6876 involved = listCreate();
6877 listIter li;
6878 listNode *ln;
6879 listRewind(cluster_manager.nodes, &li);
6880 /* Compute the total cluster weight. */
6881 while ((ln = listNext(&li)) != NULL) {
6882 clusterManagerNode *n = ln->value;
6883 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE || n->replicate)
6884 continue;
6885 if (!use_empty && n->slots_count == 0) {
6886 n->weight = 0;
6887 continue;
6888 }
6889 total_weight += n->weight;
6890 nodes_involved++;
6891 listAddNodeTail(involved, n);
6892 }
6893 weightedNodes = zmalloc(nodes_involved * sizeof(clusterManagerNode *));
6894 if (weightedNodes == NULL) goto cleanup;
6895 /* Check cluster, only proceed if it looks sane. */
6896 clusterManagerCheckCluster(1);
6897 if (cluster_manager.errors && listLength(cluster_manager.errors) > 0) {
6898 clusterManagerLogErr("*** Please fix your cluster problems "
6899 "before rebalancing\n");
6900 result = 0;
6901 goto cleanup;
6902 }
6903 /* Calculate the slots balance for each node. It's the number of
6904 * slots the node should lose (if positive) or gain (if negative)
6905 * in order to be balanced. */
6906 int threshold_reached = 0, total_balance = 0;
6907 float threshold = config.cluster_manager_command.threshold;
6908 i = 0;
6909 listRewind(involved, &li);
6910 while ((ln = listNext(&li)) != NULL) {
6911 clusterManagerNode *n = ln->value;
6912 weightedNodes[i++] = n;
6913 int expected = (int) (((float)CLUSTER_MANAGER_SLOTS / total_weight) *
6914 n->weight);
6915 n->balance = n->slots_count - expected;
6916 total_balance += n->balance;
6917 /* Compute the percentage of difference between the
6918 * expected number of slots and the real one, to see
6919 * if it's over the threshold specified by the user. */
6920 int over_threshold = 0;
6921 if (threshold > 0) {
6922 if (n->slots_count > 0) {
6923 float err_perc = fabs((100-(100.0*expected/n->slots_count)));
6924 if (err_perc > threshold) over_threshold = 1;
6925 } else if (expected > 1) {
6926 over_threshold = 1;
6927 }
6928 }
6929 if (over_threshold) threshold_reached = 1;
6930 }
6931 if (!threshold_reached) {
6932 clusterManagerLogWarn("*** No rebalancing needed! "
6933 "All nodes are within the %.2f%% threshold.\n",
6934 config.cluster_manager_command.threshold);
6935 goto cleanup;
6936 }
6937 /* Because of rounding, it is possible that the balance of all nodes
6938 * summed does not give 0. Make sure that nodes that have to provide
6939 * slots are always matched by nodes receiving slots. */
6940 while (total_balance > 0) {
6941 listRewind(involved, &li);
6942 while ((ln = listNext(&li)) != NULL) {
6943 clusterManagerNode *n = ln->value;
6944 if (n->balance <= 0 && total_balance > 0) {
6945 n->balance--;
6946 total_balance--;
6947 }
6948 }
6949 }
6950 /* Sort nodes by their slots balance. */
6951 qsort(weightedNodes, nodes_involved, sizeof(clusterManagerNode *),
6952 clusterManagerCompareNodeBalance);
6953 clusterManagerLogInfo(">>> Rebalancing across %d nodes. "
6954 "Total weight = %.2f\n",
6955 nodes_involved, total_weight);
6956 if (config.verbose) {
6957 for (i = 0; i < nodes_involved; i++) {
6958 clusterManagerNode *n = weightedNodes[i];
6959 printf("%s:%d balance is %d slots\n", n->ip, n->port, n->balance);
6960 }
6961 }
6962 /* Now we have at the start of the 'sn' array nodes that should get
6963 * slots, at the end nodes that must give slots.
6964 * We take two indexes, one at the start, and one at the end,
6965 * incrementing or decrementing the indexes accordingly til we
6966 * find nodes that need to get/provide slots. */
6967 int dst_idx = 0;
6968 int src_idx = nodes_involved - 1;
6969 int simulate = config.cluster_manager_command.flags &
6970 CLUSTER_MANAGER_CMD_FLAG_SIMULATE;
6971 while (dst_idx < src_idx) {
6972 clusterManagerNode *dst = weightedNodes[dst_idx];
6973 clusterManagerNode *src = weightedNodes[src_idx];
6974 int db = abs(dst->balance);
6975 int sb = abs(src->balance);
6976 int numslots = (db < sb ? db : sb);
6977 if (numslots > 0) {
6978 printf("Moving %d slots from %s:%d to %s:%d\n", numslots,
6979 src->ip,
6980 src->port,
6981 dst->ip,
6982 dst->port);
6983 /* Actually move the slots. */
6984 list *lsrc = listCreate(), *table = NULL;
6985 listAddNodeTail(lsrc, src);
6986 table = clusterManagerComputeReshardTable(lsrc, numslots);
6987 listRelease(lsrc);
6988 int table_len = (int) listLength(table);
6989 if (!table || table_len != numslots) {
6990 clusterManagerLogErr("*** Assertion failed: Reshard table "
6991 "!= number of slots");
6992 result = 0;
6993 goto end_move;
6994 }
6995 if (simulate) {
6996 for (i = 0; i < table_len; i++) printf("#");
6997 } else {
6998 int opts = CLUSTER_MANAGER_OPT_QUIET |
6999 CLUSTER_MANAGER_OPT_UPDATE;
7000 listRewind(table, &li);
7001 while ((ln = listNext(&li)) != NULL) {
7002 clusterManagerReshardTableItem *item = ln->value;
7003 char *err;
7004 result = clusterManagerMoveSlot(item->source,
7005 dst,
7006 item->slot,
7007 opts, &err);
7008 if (!result) {
7009 clusterManagerLogErr("*** clusterManagerMoveSlot: %s\n", err);
7010 zfree(err);
7011 goto end_move;
7012 }
7013 printf("#");
7014 fflush(stdout);
7015 }
7016
7017 }
7018 printf("\n");
7019end_move:
7020 clusterManagerReleaseReshardTable(table);
7021 if (!result) goto cleanup;
7022 }
7023 /* Update nodes balance. */
7024 dst->balance += numslots;
7025 src->balance -= numslots;
7026 if (dst->balance == 0) dst_idx++;
7027 if (src->balance == 0) src_idx --;
7028 }
7029cleanup:
7030 if (involved != NULL) listRelease(involved);
7031 if (weightedNodes != NULL) zfree(weightedNodes);
7032 return result;
7033invalid_args:
7034 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
7035 return 0;
7036}
7037
7038static int clusterManagerCommandSetTimeout(int argc, char **argv) {
7039 UNUSED(argc);
7040 int port = 0;
7041 char *ip = NULL;
7042 if (!getClusterHostFromCmdArgs(1, argv, &ip, &port)) goto invalid_args;
7043 int timeout = atoi(argv[1]);
7044 if (timeout < 100) {
7045 fprintf(stderr, "Setting a node timeout of less than 100 "
7046 "milliseconds is a bad idea.\n");
7047 return 0;
7048 }
7049 // Load cluster information
7050 clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
7051 if (!clusterManagerLoadInfoFromNode(node)) return 0;
7052 int ok_count = 0, err_count = 0;
7053
7054 clusterManagerLogInfo(">>> Reconfiguring node timeout in every "
7055 "cluster node...\n");
7056 listIter li;
7057 listNode *ln;
7058 listRewind(cluster_manager.nodes, &li);
7059 while ((ln = listNext(&li)) != NULL) {
7060 clusterManagerNode *n = ln->value;
7061 char *err = NULL;
7062 redisReply *reply = CLUSTER_MANAGER_COMMAND(n, "CONFIG %s %s %d",
7063 "SET",
7064 "cluster-node-timeout",
7065 timeout);
7066 if (reply == NULL) goto reply_err;
7067 int ok = clusterManagerCheckRedisReply(n, reply, &err);
7068 freeReplyObject(reply);
7069 if (!ok) goto reply_err;
7070 reply = CLUSTER_MANAGER_COMMAND(n, "CONFIG %s", "REWRITE");
7071 if (reply == NULL) goto reply_err;
7072 ok = clusterManagerCheckRedisReply(n, reply, &err);
7073 freeReplyObject(reply);
7074 if (!ok) goto reply_err;
7075 clusterManagerLogWarn("*** New timeout set for %s:%d\n", n->ip,
7076 n->port);
7077 ok_count++;
7078 continue;
7079reply_err:;
7080 int need_free = 0;
7081 if (err == NULL) err = "";
7082 else need_free = 1;
7083 clusterManagerLogErr("ERR setting node-timeout for %s:%d: %s\n", n->ip,
7084 n->port, err);
7085 if (need_free) zfree(err);
7086 err_count++;
7087 }
7088 clusterManagerLogInfo(">>> New node timeout set. %d OK, %d ERR.\n",
7089 ok_count, err_count);
7090 return 1;
7091invalid_args:
7092 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
7093 return 0;
7094}
7095
7096static int clusterManagerCommandImport(int argc, char **argv) {
7097 int success = 1;
7098 int port = 0, src_port = 0;
7099 char *ip = NULL, *src_ip = NULL;
7100 char *invalid_args_msg = NULL;
7101 sds cmdfmt = NULL;
7102 if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) {
7103 invalid_args_msg = CLUSTER_MANAGER_INVALID_HOST_ARG;
7104 goto invalid_args;
7105 }
7106 if (config.cluster_manager_command.from == NULL) {
7107 invalid_args_msg = "[ERR] Option '--cluster-from' is required for "
7108 "subcommand 'import'.\n";
7109 goto invalid_args;
7110 }
7111 char *src_host[] = {config.cluster_manager_command.from};
7112 if (!getClusterHostFromCmdArgs(1, src_host, &src_ip, &src_port)) {
7113 invalid_args_msg = "[ERR] Invalid --cluster-from host. You need to "
7114 "pass a valid address (ie. 120.0.0.1:7000).\n";
7115 goto invalid_args;
7116 }
7117 clusterManagerLogInfo(">>> Importing data from %s:%d to cluster %s:%d\n",
7118 src_ip, src_port, ip, port);
7119
7120 clusterManagerNode *refnode = clusterManagerNewNode(ip, port, 0);
7121 if (!clusterManagerLoadInfoFromNode(refnode)) return 0;
7122 if (!clusterManagerCheckCluster(0)) return 0;
7123 char *reply_err = NULL;
7124 redisReply *src_reply = NULL;
7125 // Connect to the source node.
7126 redisContext *src_ctx = redisConnect(src_ip, src_port);
7127 if (src_ctx->err) {
7128 success = 0;
7129 fprintf(stderr,"Could not connect to Redis at %s:%d: %s.\n", src_ip,
7130 src_port, src_ctx->errstr);
7131 goto cleanup;
7132 }
7133 // Auth for the source node.
7134 char *from_user = config.cluster_manager_command.from_user;
7135 char *from_pass = config.cluster_manager_command.from_pass;
7136 if (cliAuth(src_ctx, from_user, from_pass) == REDIS_ERR) {
7137 success = 0;
7138 goto cleanup;
7139 }
7140
7141 src_reply = reconnectingRedisCommand(src_ctx, "INFO");
7142 if (!src_reply || src_reply->type == REDIS_REPLY_ERROR) {
7143 if (src_reply && src_reply->str) reply_err = src_reply->str;
7144 success = 0;
7145 goto cleanup;
7146 }
7147 if (getLongInfoField(src_reply->str, "cluster_enabled")) {
7148 clusterManagerLogErr("[ERR] The source node should not be a "
7149 "cluster node.\n");
7150 success = 0;
7151 goto cleanup;
7152 }
7153 freeReplyObject(src_reply);
7154 src_reply = reconnectingRedisCommand(src_ctx, "DBSIZE");
7155 if (!src_reply || src_reply->type == REDIS_REPLY_ERROR) {
7156 if (src_reply && src_reply->str) reply_err = src_reply->str;
7157 success = 0;
7158 goto cleanup;
7159 }
7160 int size = src_reply->integer, i;
7161 clusterManagerLogWarn("*** Importing %d keys from DB 0\n", size);
7162
7163 // Build a slot -> node map
7164 clusterManagerNode *slots_map[CLUSTER_MANAGER_SLOTS];
7165 memset(slots_map, 0, sizeof(slots_map));
7166 listIter li;
7167 listNode *ln;
7168 for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
7169 listRewind(cluster_manager.nodes, &li);
7170 while ((ln = listNext(&li)) != NULL) {
7171 clusterManagerNode *n = ln->value;
7172 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
7173 if (n->slots_count == 0) continue;
7174 if (n->slots[i]) {
7175 slots_map[i] = n;
7176 break;
7177 }
7178 }
7179 }
7180 cmdfmt = sdsnew("MIGRATE %s %d %s %d %d");
7181 if (config.conn_info.auth) {
7182 if (config.conn_info.user) {
7183 cmdfmt = sdscatfmt(cmdfmt," AUTH2 %s %s", config.conn_info.user, config.conn_info.auth);
7184 } else {
7185 cmdfmt = sdscatfmt(cmdfmt," AUTH %s", config.conn_info.auth);
7186 }
7187 }
7188
7189 if (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_COPY)
7190 cmdfmt = sdscat(cmdfmt," COPY");
7191 if (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_REPLACE)
7192 cmdfmt = sdscat(cmdfmt," REPLACE");
7193
7194 /* Use SCAN to iterate over the keys, migrating to the
7195 * right node as needed. */
7196 int cursor = -999, timeout = config.cluster_manager_command.timeout;
7197 while (cursor != 0) {
7198 if (cursor < 0) cursor = 0;
7199 freeReplyObject(src_reply);
7200 src_reply = reconnectingRedisCommand(src_ctx, "SCAN %d COUNT %d",
7201 cursor, 1000);
7202 if (!src_reply || src_reply->type == REDIS_REPLY_ERROR) {
7203 if (src_reply && src_reply->str) reply_err = src_reply->str;
7204 success = 0;
7205 goto cleanup;
7206 }
7207 assert(src_reply->type == REDIS_REPLY_ARRAY);
7208 assert(src_reply->elements >= 2);
7209 assert(src_reply->element[1]->type == REDIS_REPLY_ARRAY);
7210 if (src_reply->element[0]->type == REDIS_REPLY_STRING)
7211 cursor = atoi(src_reply->element[0]->str);
7212 else if (src_reply->element[0]->type == REDIS_REPLY_INTEGER)
7213 cursor = src_reply->element[0]->integer;
7214 int keycount = src_reply->element[1]->elements;
7215 for (i = 0; i < keycount; i++) {
7216 redisReply *kr = src_reply->element[1]->element[i];
7217 assert(kr->type == REDIS_REPLY_STRING);
7218 char *key = kr->str;
7219 uint16_t slot = clusterManagerKeyHashSlot(key, kr->len);
7220 clusterManagerNode *target = slots_map[slot];
7221 printf("Migrating %s to %s:%d: ", key, target->ip, target->port);
7222 redisReply *r = reconnectingRedisCommand(src_ctx, cmdfmt,
7223 target->ip, target->port,
7224 key, 0, timeout);
7225 if (!r || r->type == REDIS_REPLY_ERROR) {
7226 if (r && r->str) {
7227 clusterManagerLogErr("Source %s:%d replied with "
7228 "error:\n%s\n", src_ip, src_port,
7229 r->str);
7230 }
7231 success = 0;
7232 }
7233 freeReplyObject(r);
7234 if (!success) goto cleanup;
7235 clusterManagerLogOk("OK\n");
7236 }
7237 }
7238cleanup:
7239 if (reply_err)
7240 clusterManagerLogErr("Source %s:%d replied with error:\n%s\n",
7241 src_ip, src_port, reply_err);
7242 if (src_ctx) redisFree(src_ctx);
7243 if (src_reply) freeReplyObject(src_reply);
7244 if (cmdfmt) sdsfree(cmdfmt);
7245 return success;
7246invalid_args:
7247 fprintf(stderr, "%s", invalid_args_msg);
7248 return 0;
7249}
7250
7251static int clusterManagerCommandCall(int argc, char **argv) {
7252 int port = 0, i;
7253 char *ip = NULL;
7254 if (!getClusterHostFromCmdArgs(1, argv, &ip, &port)) goto invalid_args;
7255 clusterManagerNode *refnode = clusterManagerNewNode(ip, port, 0);
7256 if (!clusterManagerLoadInfoFromNode(refnode)) return 0;
7257 argc--;
7258 argv++;
7259 size_t *argvlen = zmalloc(argc*sizeof(size_t));
7260 clusterManagerLogInfo(">>> Calling");
7261 for (i = 0; i < argc; i++) {
7262 argvlen[i] = strlen(argv[i]);
7263 printf(" %s", argv[i]);
7264 }
7265 printf("\n");
7266 listIter li;
7267 listNode *ln;
7268 listRewind(cluster_manager.nodes, &li);
7269 while ((ln = listNext(&li)) != NULL) {
7270 clusterManagerNode *n = ln->value;
7271 if ((config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_MASTERS_ONLY)
7272 && (n->replicate != NULL)) continue; // continue if node is slave
7273 if ((config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_SLAVES_ONLY)
7274 && (n->replicate == NULL)) continue; // continue if node is master
7275 if (!n->context && !clusterManagerNodeConnect(n)) continue;
7276 redisReply *reply = NULL;
7277 redisAppendCommandArgv(n->context, argc, (const char **) argv, argvlen);
7278 int status = redisGetReply(n->context, (void **)(&reply));
7279 if (status != REDIS_OK || reply == NULL )
7280 printf("%s:%d: Failed!\n", n->ip, n->port);
7281 else {
7282 sds formatted_reply = cliFormatReplyRaw(reply);
7283 printf("%s:%d: %s\n", n->ip, n->port, (char *) formatted_reply);
7284 sdsfree(formatted_reply);
7285 }
7286 if (reply != NULL) freeReplyObject(reply);
7287 }
7288 zfree(argvlen);
7289 return 1;
7290invalid_args:
7291 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
7292 return 0;
7293}
7294
7295static int clusterManagerCommandBackup(int argc, char **argv) {
7296 UNUSED(argc);
7297 int success = 1, port = 0;
7298 char *ip = NULL;
7299 if (!getClusterHostFromCmdArgs(1, argv, &ip, &port)) goto invalid_args;
7300 clusterManagerNode *refnode = clusterManagerNewNode(ip, port, 0);
7301 if (!clusterManagerLoadInfoFromNode(refnode)) return 0;
7302 int no_issues = clusterManagerCheckCluster(0);
7303 int cluster_errors_count = (no_issues ? 0 :
7304 listLength(cluster_manager.errors));
7305 config.cluster_manager_command.backup_dir = argv[1];
7306 /* TODO: check if backup_dir is a valid directory. */
7307 sds json = sdsnew("[\n");
7308 int first_node = 0;
7309 listIter li;
7310 listNode *ln;
7311 listRewind(cluster_manager.nodes, &li);
7312 while ((ln = listNext(&li)) != NULL) {
7313 if (!first_node) first_node = 1;
7314 else json = sdscat(json, ",\n");
7315 clusterManagerNode *node = ln->value;
7316 sds node_json = clusterManagerNodeGetJSON(node, cluster_errors_count);
7317 json = sdscat(json, node_json);
7318 sdsfree(node_json);
7319 if (node->replicate)
7320 continue;
7321 clusterManagerLogInfo(">>> Node %s:%d -> Saving RDB...\n",
7322 node->ip, node->port);
7323 fflush(stdout);
7324 getRDB(node);
7325 }
7326 json = sdscat(json, "\n]");
7327 sds jsonpath = sdsnew(config.cluster_manager_command.backup_dir);
7328 if (jsonpath[sdslen(jsonpath) - 1] != '/')
7329 jsonpath = sdscat(jsonpath, "/");
7330 jsonpath = sdscat(jsonpath, "nodes.json");
7331 fflush(stdout);
7332 clusterManagerLogInfo("Saving cluster configuration to: %s\n", jsonpath);
7333 FILE *out = fopen(jsonpath, "w+");
7334 if (!out) {
7335 clusterManagerLogErr("Could not save nodes to: %s\n", jsonpath);
7336 success = 0;
7337 goto cleanup;
7338 }
7339 fputs(json, out);
7340 fclose(out);
7341cleanup:
7342 sdsfree(json);
7343 sdsfree(jsonpath);
7344 if (success) {
7345 if (!no_issues) {
7346 clusterManagerLogWarn("*** Cluster seems to have some problems, "
7347 "please be aware of it if you're going "
7348 "to restore this backup.\n");
7349 }
7350 clusterManagerLogOk("[OK] Backup created into: %s\n",
7351 config.cluster_manager_command.backup_dir);
7352 } else clusterManagerLogOk("[ERR] Failed to back cluster!\n");
7353 return success;
7354invalid_args:
7355 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
7356 return 0;
7357}
7358
7359static int clusterManagerCommandHelp(int argc, char **argv) {
7360 UNUSED(argc);
7361 UNUSED(argv);
7362 int commands_count = sizeof(clusterManagerCommands) /
7363 sizeof(clusterManagerCommandDef);
7364 int i = 0, j;
7365 fprintf(stdout, "Cluster Manager Commands:\n");
7366 int padding = 15;
7367 for (; i < commands_count; i++) {
7368 clusterManagerCommandDef *def = &(clusterManagerCommands[i]);
7369 int namelen = strlen(def->name), padlen = padding - namelen;
7370 fprintf(stdout, " %s", def->name);
7371 for (j = 0; j < padlen; j++) fprintf(stdout, " ");
7372 fprintf(stdout, "%s\n", (def->args ? def->args : ""));
7373 if (def->options != NULL) {
7374 int optslen = strlen(def->options);
7375 char *p = def->options, *eos = p + optslen;
7376 char *comma = NULL;
7377 while ((comma = strchr(p, ',')) != NULL) {
7378 int deflen = (int)(comma - p);
7379 char buf[255];
7380 memcpy(buf, p, deflen);
7381 buf[deflen] = '\0';
7382 for (j = 0; j < padding; j++) fprintf(stdout, " ");
7383 fprintf(stdout, " --cluster-%s\n", buf);
7384 p = comma + 1;
7385 if (p >= eos) break;
7386 }
7387 if (p < eos) {
7388 for (j = 0; j < padding; j++) fprintf(stdout, " ");
7389 fprintf(stdout, " --cluster-%s\n", p);
7390 }
7391 }
7392 }
7393 fprintf(stdout, "\nFor check, fix, reshard, del-node, set-timeout, "
7394 "info, rebalance, call, import, backup you "
7395 "can specify the host and port of any working node in "
7396 "the cluster.\n");
7397
7398 int options_count = sizeof(clusterManagerOptions) /
7399 sizeof(clusterManagerOptionDef);
7400 i = 0;
7401 fprintf(stdout, "\nCluster Manager Options:\n");
7402 for (; i < options_count; i++) {
7403 clusterManagerOptionDef *def = &(clusterManagerOptions[i]);
7404 int namelen = strlen(def->name), padlen = padding - namelen;
7405 fprintf(stdout, " %s", def->name);
7406 for (j = 0; j < padlen; j++) fprintf(stdout, " ");
7407 fprintf(stdout, "%s\n", def->desc);
7408 }
7409
7410 fprintf(stdout, "\n");
7411 return 0;
7412}
7413
7414/*------------------------------------------------------------------------------
7415 * Latency and latency history modes
7416 *--------------------------------------------------------------------------- */
7417
7418static void latencyModePrint(long long min, long long max, double avg, long long count) {
7419 if (config.output == OUTPUT_STANDARD) {
7420 printf("min: %lld, max: %lld, avg: %.2f (%lld samples)",
7421 min, max, avg, count);
7422 fflush(stdout);
7423 } else if (config.output == OUTPUT_CSV) {
7424 printf("%lld,%lld,%.2f,%lld\n", min, max, avg, count);
7425 } else if (config.output == OUTPUT_RAW) {
7426 printf("%lld %lld %.2f %lld\n", min, max, avg, count);
7427 } else if (config.output == OUTPUT_JSON) {
7428 printf("{\"min\": %lld, \"max\": %lld, \"avg\": %.2f, \"count\": %lld}\n", min, max, avg, count);
7429 }
7430}
7431
7432#define LATENCY_SAMPLE_RATE 10 /* milliseconds. */
7433#define LATENCY_HISTORY_DEFAULT_INTERVAL 15000 /* milliseconds. */
7434static void latencyMode(void) {
7435 redisReply *reply;
7436 long long start, latency, min = 0, max = 0, tot = 0, count = 0;
7437 long long history_interval =
7438 config.interval ? config.interval/1000 :
7439 LATENCY_HISTORY_DEFAULT_INTERVAL;
7440 double avg;
7441 long long history_start = mstime();
7442
7443 /* Set a default for the interval in case of --latency option
7444 * with --raw, --csv or when it is redirected to non tty. */
7445 if (config.interval == 0) {
7446 config.interval = 1000;
7447 } else {
7448 config.interval /= 1000; /* We need to convert to milliseconds. */
7449 }
7450
7451 if (!context) exit(1);
7452 while(1) {
7453 start = mstime();
7454 reply = reconnectingRedisCommand(context,"PING");
7455 if (reply == NULL) {
7456 fprintf(stderr,"\nI/O error\n");
7457 exit(1);
7458 }
7459 latency = mstime()-start;
7460 freeReplyObject(reply);
7461 count++;
7462 if (count == 1) {
7463 min = max = tot = latency;
7464 avg = (double) latency;
7465 } else {
7466 if (latency < min) min = latency;
7467 if (latency > max) max = latency;
7468 tot += latency;
7469 avg = (double) tot/count;
7470 }
7471
7472 if (config.output == OUTPUT_STANDARD) {
7473 printf("\x1b[0G\x1b[2K"); /* Clear the line. */
7474 latencyModePrint(min,max,avg,count);
7475 } else {
7476 if (config.latency_history) {
7477 latencyModePrint(min,max,avg,count);
7478 } else if (mstime()-history_start > config.interval) {
7479 latencyModePrint(min,max,avg,count);
7480 exit(0);
7481 }
7482 }
7483
7484 if (config.latency_history && mstime()-history_start > history_interval)
7485 {
7486 printf(" -- %.2f seconds range\n", (float)(mstime()-history_start)/1000);
7487 history_start = mstime();
7488 min = max = tot = count = 0;
7489 }
7490 usleep(LATENCY_SAMPLE_RATE * 1000);
7491 }
7492}
7493
7494/*------------------------------------------------------------------------------
7495 * Latency distribution mode -- requires 256 colors xterm
7496 *--------------------------------------------------------------------------- */
7497
7498#define LATENCY_DIST_DEFAULT_INTERVAL 1000 /* milliseconds. */
7499
7500/* Structure to store samples distribution. */
7501struct distsamples {
7502 long long max; /* Max latency to fit into this interval (usec). */
7503 long long count; /* Number of samples in this interval. */
7504 int character; /* Associated character in visualization. */
7505};
7506
7507/* Helper function for latencyDistMode(). Performs the spectrum visualization
7508 * of the collected samples targeting an xterm 256 terminal.
7509 *
7510 * Takes an array of distsamples structures, ordered from smaller to bigger
7511 * 'max' value. Last sample max must be 0, to mean that it holds all the
7512 * samples greater than the previous one, and is also the stop sentinel.
7513 *
7514 * "tot' is the total number of samples in the different buckets, so it
7515 * is the SUM(samples[i].count) for i to 0 up to the max sample.
7516 *
7517 * As a side effect the function sets all the buckets count to 0. */
7518void showLatencyDistSamples(struct distsamples *samples, long long tot) {
7519 int j;
7520
7521 /* We convert samples into an index inside the palette
7522 * proportional to the percentage a given bucket represents.
7523 * This way intensity of the different parts of the spectrum
7524 * don't change relative to the number of requests, which avoids to
7525 * pollute the visualization with non-latency related info. */
7526 printf("\033[38;5;0m"); /* Set foreground color to black. */
7527 for (j = 0; ; j++) {
7528 int coloridx =
7529 ceil((double) samples[j].count / tot * (spectrum_palette_size-1));
7530 int color = spectrum_palette[coloridx];
7531 printf("\033[48;5;%dm%c", (int)color, samples[j].character);
7532 samples[j].count = 0;
7533 if (samples[j].max == 0) break; /* Last sample. */
7534 }
7535 printf("\033[0m\n");
7536 fflush(stdout);
7537}
7538
7539/* Show the legend: different buckets values and colors meaning, so
7540 * that the spectrum is more easily readable. */
7541void showLatencyDistLegend(void) {
7542 int j;
7543
7544 printf("---------------------------------------------\n");
7545 printf(". - * # .01 .125 .25 .5 milliseconds\n");
7546 printf("1,2,3,...,9 from 1 to 9 milliseconds\n");
7547 printf("A,B,C,D,E 10,20,30,40,50 milliseconds\n");
7548 printf("F,G,H,I,J .1,.2,.3,.4,.5 seconds\n");
7549 printf("K,L,M,N,O,P,Q,? 1,2,4,8,16,30,60,>60 seconds\n");
7550 printf("From 0 to 100%%: ");
7551 for (j = 0; j < spectrum_palette_size; j++) {
7552 printf("\033[48;5;%dm ", spectrum_palette[j]);
7553 }
7554 printf("\033[0m\n");
7555 printf("---------------------------------------------\n");
7556}
7557
7558static void latencyDistMode(void) {
7559 redisReply *reply;
7560 long long start, latency, count = 0;
7561 long long history_interval =
7562 config.interval ? config.interval/1000 :
7563 LATENCY_DIST_DEFAULT_INTERVAL;
7564 long long history_start = ustime();
7565 int j, outputs = 0;
7566
7567 struct distsamples samples[] = {
7568 /* We use a mostly logarithmic scale, with certain linear intervals
7569 * which are more interesting than others, like 1-10 milliseconds
7570 * range. */
7571 {10,0,'.'}, /* 0.01 ms */
7572 {125,0,'-'}, /* 0.125 ms */
7573 {250,0,'*'}, /* 0.25 ms */
7574 {500,0,'#'}, /* 0.5 ms */
7575 {1000,0,'1'}, /* 1 ms */
7576 {2000,0,'2'}, /* 2 ms */
7577 {3000,0,'3'}, /* 3 ms */
7578 {4000,0,'4'}, /* 4 ms */
7579 {5000,0,'5'}, /* 5 ms */
7580 {6000,0,'6'}, /* 6 ms */
7581 {7000,0,'7'}, /* 7 ms */
7582 {8000,0,'8'}, /* 8 ms */
7583 {9000,0,'9'}, /* 9 ms */
7584 {10000,0,'A'}, /* 10 ms */
7585 {20000,0,'B'}, /* 20 ms */
7586 {30000,0,'C'}, /* 30 ms */
7587 {40000,0,'D'}, /* 40 ms */
7588 {50000,0,'E'}, /* 50 ms */
7589 {100000,0,'F'}, /* 0.1 s */
7590 {200000,0,'G'}, /* 0.2 s */
7591 {300000,0,'H'}, /* 0.3 s */
7592 {400000,0,'I'}, /* 0.4 s */
7593 {500000,0,'J'}, /* 0.5 s */
7594 {1000000,0,'K'}, /* 1 s */
7595 {2000000,0,'L'}, /* 2 s */
7596 {4000000,0,'M'}, /* 4 s */
7597 {8000000,0,'N'}, /* 8 s */
7598 {16000000,0,'O'}, /* 16 s */
7599 {30000000,0,'P'}, /* 30 s */
7600 {60000000,0,'Q'}, /* 1 minute */
7601 {0,0,'?'}, /* > 1 minute */
7602 };
7603
7604 if (!context) exit(1);
7605 while(1) {
7606 start = ustime();
7607 reply = reconnectingRedisCommand(context,"PING");
7608 if (reply == NULL) {
7609 fprintf(stderr,"\nI/O error\n");
7610 exit(1);
7611 }
7612 latency = ustime()-start;
7613 freeReplyObject(reply);
7614 count++;
7615
7616 /* Populate the relevant bucket. */
7617 for (j = 0; ; j++) {
7618 if (samples[j].max == 0 || latency <= samples[j].max) {
7619 samples[j].count++;
7620 break;
7621 }
7622 }
7623
7624 /* From time to time show the spectrum. */
7625 if (count && (ustime()-history_start)/1000 > history_interval) {
7626 if ((outputs++ % 20) == 0)
7627 showLatencyDistLegend();
7628 showLatencyDistSamples(samples,count);
7629 history_start = ustime();
7630 count = 0;
7631 }
7632 usleep(LATENCY_SAMPLE_RATE * 1000);
7633 }
7634}
7635
7636/*------------------------------------------------------------------------------
7637 * Slave mode
7638 *--------------------------------------------------------------------------- */
7639
7640#define RDB_EOF_MARK_SIZE 40
7641
7642int sendReplconf(const char* arg1, const char* arg2) {
7643 int res = 1;
7644 fprintf(stderr, "sending REPLCONF %s %s\n", arg1, arg2);
7645 redisReply *reply = redisCommand(context, "REPLCONF %s %s", arg1, arg2);
7646
7647 /* Handle any error conditions */
7648 if(reply == NULL) {
7649 fprintf(stderr, "\nI/O error\n");
7650 exit(1);
7651 } else if(reply->type == REDIS_REPLY_ERROR) {
7652 /* non fatal, old versions may not support it */
7653 fprintf(stderr, "REPLCONF %s error: %s\n", arg1, reply->str);
7654 res = 0;
7655 }
7656 freeReplyObject(reply);
7657 return res;
7658}
7659
7660void sendCapa() {
7661 sendReplconf("capa", "eof");
7662}
7663
7664void sendRdbOnly(void) {
7665 sendReplconf("rdb-only", "1");
7666}
7667
7668/* Read raw bytes through a redisContext. The read operation is not greedy
7669 * and may not fill the buffer entirely.
7670 */
7671static ssize_t readConn(redisContext *c, char *buf, size_t len)
7672{
7673 return c->funcs->read(c, buf, len);
7674}
7675
7676/* Sends SYNC and reads the number of bytes in the payload. Used both by
7677 * slaveMode() and getRDB().
7678 * returns 0 in case an EOF marker is used. */
7679unsigned long long sendSync(redisContext *c, char *out_eof) {
7680 /* To start we need to send the SYNC command and return the payload.
7681 * The hiredis client lib does not understand this part of the protocol
7682 * and we don't want to mess with its buffers, so everything is performed
7683 * using direct low-level I/O. */
7684 char buf[4096], *p;
7685 ssize_t nread;
7686
7687 /* Send the SYNC command. */
7688 if (cliWriteConn(c, "SYNC\r\n", 6) != 6) {
7689 fprintf(stderr,"Error writing to master\n");
7690 exit(1);
7691 }
7692
7693 /* Read $<payload>\r\n, making sure to read just up to "\n" */
7694 p = buf;
7695 while(1) {
7696 nread = readConn(c,p,1);
7697 if (nread <= 0) {
7698 fprintf(stderr,"Error reading bulk length while SYNCing\n");
7699 exit(1);
7700 }
7701 if (*p == '\n' && p != buf) break;
7702 if (*p != '\n') p++;
7703 }
7704 *p = '\0';
7705 if (buf[0] == '-') {
7706 fprintf(stderr, "SYNC with master failed: %s\n", buf);
7707 exit(1);
7708 }
7709 if (strncmp(buf+1,"EOF:",4) == 0 && strlen(buf+5) >= RDB_EOF_MARK_SIZE) {
7710 memcpy(out_eof, buf+5, RDB_EOF_MARK_SIZE);
7711 return 0;
7712 }
7713 return strtoull(buf+1,NULL,10);
7714}
7715
7716static void slaveMode(void) {
7717 static char eofmark[RDB_EOF_MARK_SIZE];
7718 static char lastbytes[RDB_EOF_MARK_SIZE];
7719 static int usemark = 0;
7720 unsigned long long payload = sendSync(context,eofmark);
7721 char buf[1024];
7722 int original_output = config.output;
7723
7724 if (payload == 0) {
7725 payload = ULLONG_MAX;
7726 memset(lastbytes,0,RDB_EOF_MARK_SIZE);
7727 usemark = 1;
7728 fprintf(stderr,"SYNC with master, discarding "
7729 "bytes of bulk transfer until EOF marker...\n");
7730 } else {
7731 fprintf(stderr,"SYNC with master, discarding %llu "
7732 "bytes of bulk transfer...\n", payload);
7733 }
7734
7735
7736 /* Discard the payload. */
7737 while(payload) {
7738 ssize_t nread;
7739
7740 nread = readConn(context,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload);
7741 if (nread <= 0) {
7742 fprintf(stderr,"Error reading RDB payload while SYNCing\n");
7743 exit(1);
7744 }
7745 payload -= nread;
7746
7747 if (usemark) {
7748 /* Update the last bytes array, and check if it matches our delimiter.*/
7749 if (nread >= RDB_EOF_MARK_SIZE) {
7750 memcpy(lastbytes,buf+nread-RDB_EOF_MARK_SIZE,RDB_EOF_MARK_SIZE);
7751 } else {
7752 int rem = RDB_EOF_MARK_SIZE-nread;
7753 memmove(lastbytes,lastbytes+nread,rem);
7754 memcpy(lastbytes+rem,buf,nread);
7755 }
7756 if (memcmp(lastbytes,eofmark,RDB_EOF_MARK_SIZE) == 0)
7757 break;
7758 }
7759 }
7760
7761 if (usemark) {
7762 unsigned long long offset = ULLONG_MAX - payload;
7763 fprintf(stderr,"SYNC done after %llu bytes. Logging commands from master.\n", offset);
7764 /* put the slave online */
7765 sleep(1);
7766 sendReplconf("ACK", "0");
7767 } else
7768 fprintf(stderr,"SYNC done. Logging commands from master.\n");
7769
7770 /* Now we can use hiredis to read the incoming protocol. */
7771 config.output = OUTPUT_CSV;
7772 while (cliReadReply(0) == REDIS_OK);
7773 config.output = original_output;
7774}
7775
7776/*------------------------------------------------------------------------------
7777 * RDB transfer mode
7778 *--------------------------------------------------------------------------- */
7779
7780/* This function implements --rdb, so it uses the replication protocol in order
7781 * to fetch the RDB file from a remote server. */
7782static void getRDB(clusterManagerNode *node) {
7783 int fd;
7784 redisContext *s;
7785 char *filename;
7786 if (node != NULL) {
7787 assert(node->context);
7788 s = node->context;
7789 filename = clusterManagerGetNodeRDBFilename(node);
7790 } else {
7791 s = context;
7792 filename = config.rdb_filename;
7793 }
7794 static char eofmark[RDB_EOF_MARK_SIZE];
7795 static char lastbytes[RDB_EOF_MARK_SIZE];
7796 static int usemark = 0;
7797 unsigned long long payload = sendSync(s, eofmark);
7798 char buf[4096];
7799
7800 if (payload == 0) {
7801 payload = ULLONG_MAX;
7802 memset(lastbytes,0,RDB_EOF_MARK_SIZE);
7803 usemark = 1;
7804 fprintf(stderr,"SYNC sent to master, writing bytes of bulk transfer "
7805 "until EOF marker to '%s'\n", filename);
7806 } else {
7807 fprintf(stderr,"SYNC sent to master, writing %llu bytes to '%s'\n",
7808 payload, filename);
7809 }
7810
7811 int write_to_stdout = !strcmp(filename,"-");
7812 /* Write to file. */
7813 if (write_to_stdout) {
7814 fd = STDOUT_FILENO;
7815 } else {
7816 fd = open(filename, O_CREAT|O_WRONLY, 0644);
7817 if (fd == -1) {
7818 fprintf(stderr, "Error opening '%s': %s\n", filename,
7819 strerror(errno));
7820 exit(1);
7821 }
7822 }
7823
7824 while(payload) {
7825 ssize_t nread, nwritten;
7826
7827 nread = readConn(s,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload);
7828 if (nread <= 0) {
7829 fprintf(stderr,"I/O Error reading RDB payload from socket\n");
7830 exit(1);
7831 }
7832 nwritten = write(fd, buf, nread);
7833 if (nwritten != nread) {
7834 fprintf(stderr,"Error writing data to file: %s\n",
7835 (nwritten == -1) ? strerror(errno) : "short write");
7836 exit(1);
7837 }
7838 payload -= nread;
7839
7840 if (usemark) {
7841 /* Update the last bytes array, and check if it matches our delimiter.*/
7842 if (nread >= RDB_EOF_MARK_SIZE) {
7843 memcpy(lastbytes,buf+nread-RDB_EOF_MARK_SIZE,RDB_EOF_MARK_SIZE);
7844 } else {
7845 int rem = RDB_EOF_MARK_SIZE-nread;
7846 memmove(lastbytes,lastbytes+nread,rem);
7847 memcpy(lastbytes+rem,buf,nread);
7848 }
7849 if (memcmp(lastbytes,eofmark,RDB_EOF_MARK_SIZE) == 0)
7850 break;
7851 }
7852 }
7853 if (usemark) {
7854 payload = ULLONG_MAX - payload - RDB_EOF_MARK_SIZE;
7855 if (!write_to_stdout && ftruncate(fd, payload) == -1)
7856 fprintf(stderr,"ftruncate failed: %s.\n", strerror(errno));
7857 fprintf(stderr,"Transfer finished with success after %llu bytes\n", payload);
7858 } else {
7859 fprintf(stderr,"Transfer finished with success.\n");
7860 }
7861 redisFree(s); /* Close the connection ASAP as fsync() may take time. */
7862 if (node)
7863 node->context = NULL;
7864 if (!write_to_stdout && fsync(fd) == -1) {
7865 fprintf(stderr,"Fail to fsync '%s': %s\n", filename, strerror(errno));
7866 exit(1);
7867 }
7868 close(fd);
7869 if (node) {
7870 sdsfree(filename);
7871 return;
7872 }
7873 exit(0);
7874}
7875
7876/*------------------------------------------------------------------------------
7877 * Bulk import (pipe) mode
7878 *--------------------------------------------------------------------------- */
7879
7880#define PIPEMODE_WRITE_LOOP_MAX_BYTES (128*1024)
7881static void pipeMode(void) {
7882 long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0;
7883 char obuf[1024*16]; /* Output buffer */
7884 char aneterr[ANET_ERR_LEN];
7885 redisReply *reply;
7886 int eof = 0; /* True once we consumed all the standard input. */
7887 int done = 0;
7888 char magic[20]; /* Special reply we recognize. */
7889 time_t last_read_time = time(NULL);
7890
7891 srand(time(NULL));
7892
7893 /* Use non blocking I/O. */
7894 if (anetNonBlock(aneterr,context->fd) == ANET_ERR) {
7895 fprintf(stderr, "Can't set the socket in non blocking mode: %s\n",
7896 aneterr);
7897 exit(1);
7898 }
7899
7900 context->flags &= ~REDIS_BLOCK;
7901
7902 /* Transfer raw protocol and read replies from the server at the same
7903 * time. */
7904 while(!done) {
7905 int mask = AE_READABLE;
7906
7907 if (!eof || obuf_len != 0) mask |= AE_WRITABLE;
7908 mask = aeWait(context->fd,mask,1000);
7909
7910 /* Handle the readable state: we can read replies from the server. */
7911 if (mask & AE_READABLE) {
7912 int read_error = 0;
7913
7914 do {
7915 if (!read_error && redisBufferRead(context) == REDIS_ERR) {
7916 read_error = 1;
7917 }
7918
7919 reply = NULL;
7920 if (redisGetReply(context, (void **) &reply) == REDIS_ERR) {
7921 fprintf(stderr, "Error reading replies from server\n");
7922 exit(1);
7923 }
7924 if (reply) {
7925 last_read_time = time(NULL);
7926 if (reply->type == REDIS_REPLY_ERROR) {
7927 fprintf(stderr,"%s\n", reply->str);
7928 errors++;
7929 } else if (eof && reply->type == REDIS_REPLY_STRING &&
7930 reply->len == 20) {
7931 /* Check if this is the reply to our final ECHO
7932 * command. If so everything was received
7933 * from the server. */
7934 if (memcmp(reply->str,magic,20) == 0) {
7935 printf("Last reply received from server.\n");
7936 done = 1;
7937 replies--;
7938 }
7939 }
7940 replies++;
7941 freeReplyObject(reply);
7942 }
7943 } while(reply);
7944
7945 /* Abort on read errors. We abort here because it is important
7946 * to consume replies even after a read error: this way we can
7947 * show a potential problem to the user. */
7948 if (read_error) exit(1);
7949 }
7950
7951 /* Handle the writable state: we can send protocol to the server. */
7952 if (mask & AE_WRITABLE) {
7953 ssize_t loop_nwritten = 0;
7954
7955 while(1) {
7956 /* Transfer current buffer to server. */
7957 if (obuf_len != 0) {
7958 ssize_t nwritten = cliWriteConn(context,obuf+obuf_pos,obuf_len);
7959
7960 if (nwritten == -1) {
7961 if (errno != EAGAIN && errno != EINTR) {
7962 fprintf(stderr, "Error writing to the server: %s\n",
7963 strerror(errno));
7964 exit(1);
7965 } else {
7966 nwritten = 0;
7967 }
7968 }
7969 obuf_len -= nwritten;
7970 obuf_pos += nwritten;
7971 loop_nwritten += nwritten;
7972 if (obuf_len != 0) break; /* Can't accept more data. */
7973 }
7974 if (context->err) {
7975 fprintf(stderr, "Server I/O Error: %s\n", context->errstr);
7976 exit(1);
7977 }
7978 /* If buffer is empty, load from stdin. */
7979 if (obuf_len == 0 && !eof) {
7980 ssize_t nread = read(STDIN_FILENO,obuf,sizeof(obuf));
7981
7982 if (nread == 0) {
7983 /* The ECHO sequence starts with a "\r\n" so that if there
7984 * is garbage in the protocol we read from stdin, the ECHO
7985 * will likely still be properly formatted.
7986 * CRLF is ignored by Redis, so it has no effects. */
7987 char echo[] =
7988 "\r\n*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n";
7989 int j;
7990
7991 eof = 1;
7992 /* Everything transferred, so we queue a special
7993 * ECHO command that we can match in the replies
7994 * to make sure everything was read from the server. */
7995 for (j = 0; j < 20; j++)
7996 magic[j] = rand() & 0xff;
7997 memcpy(echo+21,magic,20);
7998 memcpy(obuf,echo,sizeof(echo)-1);
7999 obuf_len = sizeof(echo)-1;
8000 obuf_pos = 0;
8001 printf("All data transferred. Waiting for the last reply...\n");
8002 } else if (nread == -1) {
8003 fprintf(stderr, "Error reading from stdin: %s\n",
8004 strerror(errno));
8005 exit(1);
8006 } else {
8007 obuf_len = nread;
8008 obuf_pos = 0;
8009 }
8010 }
8011 if ((obuf_len == 0 && eof) ||
8012 loop_nwritten > PIPEMODE_WRITE_LOOP_MAX_BYTES) break;
8013 }
8014 }
8015
8016 /* Handle timeout, that is, we reached EOF, and we are not getting
8017 * replies from the server for a few seconds, nor the final ECHO is
8018 * received. */
8019 if (eof && config.pipe_timeout > 0 &&
8020 time(NULL)-last_read_time > config.pipe_timeout)
8021 {
8022 fprintf(stderr,"No replies for %d seconds: exiting.\n",
8023 config.pipe_timeout);
8024 errors++;
8025 break;
8026 }
8027 }
8028 printf("errors: %lld, replies: %lld\n", errors, replies);
8029 if (errors)
8030 exit(1);
8031 else
8032 exit(0);
8033}
8034
8035/*------------------------------------------------------------------------------
8036 * Find big keys
8037 *--------------------------------------------------------------------------- */
8038
8039static redisReply *sendScan(unsigned long long *it) {
8040 redisReply *reply;
8041
8042 if (config.pattern)
8043 reply = redisCommand(context, "SCAN %llu MATCH %b",
8044 *it, config.pattern, sdslen(config.pattern));
8045 else
8046 reply = redisCommand(context,"SCAN %llu",*it);
8047
8048 /* Handle any error conditions */
8049 if(reply == NULL) {
8050 fprintf(stderr, "\nI/O error\n");
8051 exit(1);
8052 } else if(reply->type == REDIS_REPLY_ERROR) {
8053 fprintf(stderr, "SCAN error: %s\n", reply->str);
8054 exit(1);
8055 } else if(reply->type != REDIS_REPLY_ARRAY) {
8056 fprintf(stderr, "Non ARRAY response from SCAN!\n");
8057 exit(1);
8058 } else if(reply->elements != 2) {
8059 fprintf(stderr, "Invalid element count from SCAN!\n");
8060 exit(1);
8061 }
8062
8063 /* Validate our types are correct */
8064 assert(reply->element[0]->type == REDIS_REPLY_STRING);
8065 assert(reply->element[1]->type == REDIS_REPLY_ARRAY);
8066
8067 /* Update iterator */
8068 *it = strtoull(reply->element[0]->str, NULL, 10);
8069
8070 return reply;
8071}
8072
8073static int getDbSize(void) {
8074 redisReply *reply;
8075 int size;
8076
8077 reply = redisCommand(context, "DBSIZE");
8078
8079 if (reply == NULL) {
8080 fprintf(stderr, "\nI/O error\n");
8081 exit(1);
8082 } else if (reply->type == REDIS_REPLY_ERROR) {
8083 fprintf(stderr, "Couldn't determine DBSIZE: %s\n", reply->str);
8084 exit(1);
8085 } else if (reply->type != REDIS_REPLY_INTEGER) {
8086 fprintf(stderr, "Non INTEGER response from DBSIZE!\n");
8087 exit(1);
8088 }
8089
8090 /* Grab the number of keys and free our reply */
8091 size = reply->integer;
8092 freeReplyObject(reply);
8093
8094 return size;
8095}
8096
8097typedef struct {
8098 char *name;
8099 char *sizecmd;
8100 char *sizeunit;
8101 unsigned long long biggest;
8102 unsigned long long count;
8103 unsigned long long totalsize;
8104 sds biggest_key;
8105} typeinfo;
8106
8107typeinfo type_string = { "string", "STRLEN", "bytes" };
8108typeinfo type_list = { "list", "LLEN", "items" };
8109typeinfo type_set = { "set", "SCARD", "members" };
8110typeinfo type_hash = { "hash", "HLEN", "fields" };
8111typeinfo type_zset = { "zset", "ZCARD", "members" };
8112typeinfo type_stream = { "stream", "XLEN", "entries" };
8113typeinfo type_other = { "other", NULL, "?" };
8114
8115static typeinfo* typeinfo_add(dict *types, char* name, typeinfo* type_template) {
8116 typeinfo *info = zmalloc(sizeof(typeinfo));
8117 *info = *type_template;
8118 info->name = sdsnew(name);
8119 dictAdd(types, info->name, info);
8120 return info;
8121}
8122
8123void type_free(dict *d, void* val) {
8124 typeinfo *info = val;
8125 UNUSED(d);
8126 if (info->biggest_key)
8127 sdsfree(info->biggest_key);
8128 sdsfree(info->name);
8129 zfree(info);
8130}
8131
8132static dictType typeinfoDictType = {
8133 dictSdsHash, /* hash function */
8134 NULL, /* key dup */
8135 NULL, /* val dup */
8136 dictSdsKeyCompare, /* key compare */
8137 NULL, /* key destructor (owned by the value)*/
8138 type_free, /* val destructor */
8139 NULL /* allow to expand */
8140};
8141
8142static void getKeyTypes(dict *types_dict, redisReply *keys, typeinfo **types) {
8143 redisReply *reply;
8144 unsigned int i;
8145
8146 /* Pipeline TYPE commands */
8147 for(i=0;i<keys->elements;i++) {
8148 const char* argv[] = {"TYPE", keys->element[i]->str};
8149 size_t lens[] = {4, keys->element[i]->len};
8150 redisAppendCommandArgv(context, 2, argv, lens);
8151 }
8152
8153 /* Retrieve types */
8154 for(i=0;i<keys->elements;i++) {
8155 if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
8156 fprintf(stderr, "Error getting type for key '%s' (%d: %s)\n",
8157 keys->element[i]->str, context->err, context->errstr);
8158 exit(1);
8159 } else if(reply->type != REDIS_REPLY_STATUS) {
8160 if(reply->type == REDIS_REPLY_ERROR) {
8161 fprintf(stderr, "TYPE returned an error: %s\n", reply->str);
8162 } else {
8163 fprintf(stderr,
8164 "Invalid reply type (%d) for TYPE on key '%s'!\n",
8165 reply->type, keys->element[i]->str);
8166 }
8167 exit(1);
8168 }
8169
8170 sds typereply = sdsnew(reply->str);
8171 dictEntry *de = dictFind(types_dict, typereply);
8172 sdsfree(typereply);
8173 typeinfo *type = NULL;
8174 if (de)
8175 type = dictGetVal(de);
8176 else if (strcmp(reply->str, "none")) /* create new types for modules, (but not for deleted keys) */
8177 type = typeinfo_add(types_dict, reply->str, &type_other);
8178 types[i] = type;
8179 freeReplyObject(reply);
8180 }
8181}
8182
8183static void getKeySizes(redisReply *keys, typeinfo **types,
8184 unsigned long long *sizes, int memkeys,
8185 unsigned memkeys_samples)
8186{
8187 redisReply *reply;
8188 unsigned int i;
8189
8190 /* Pipeline size commands */
8191 for(i=0;i<keys->elements;i++) {
8192 /* Skip keys that disappeared between SCAN and TYPE (or unknown types when not in memkeys mode) */
8193 if(!types[i] || (!types[i]->sizecmd && !memkeys))
8194 continue;
8195
8196 if (!memkeys) {
8197 const char* argv[] = {types[i]->sizecmd, keys->element[i]->str};
8198 size_t lens[] = {strlen(types[i]->sizecmd), keys->element[i]->len};
8199 redisAppendCommandArgv(context, 2, argv, lens);
8200 } else if (memkeys_samples==0) {
8201 const char* argv[] = {"MEMORY", "USAGE", keys->element[i]->str};
8202 size_t lens[] = {6, 5, keys->element[i]->len};
8203 redisAppendCommandArgv(context, 3, argv, lens);
8204 } else {
8205 sds samplesstr = sdsfromlonglong(memkeys_samples);
8206 const char* argv[] = {"MEMORY", "USAGE", keys->element[i]->str, "SAMPLES", samplesstr};
8207 size_t lens[] = {6, 5, keys->element[i]->len, 7, sdslen(samplesstr)};
8208 redisAppendCommandArgv(context, 5, argv, lens);
8209 sdsfree(samplesstr);
8210 }
8211 }
8212
8213 /* Retrieve sizes */
8214 for(i=0;i<keys->elements;i++) {
8215 /* Skip keys that disappeared between SCAN and TYPE (or unknown types when not in memkeys mode) */
8216 if(!types[i] || (!types[i]->sizecmd && !memkeys)) {
8217 sizes[i] = 0;
8218 continue;
8219 }
8220
8221 /* Retrieve size */
8222 if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
8223 fprintf(stderr, "Error getting size for key '%s' (%d: %s)\n",
8224 keys->element[i]->str, context->err, context->errstr);
8225 exit(1);
8226 } else if(reply->type != REDIS_REPLY_INTEGER) {
8227 /* Theoretically the key could have been removed and
8228 * added as a different type between TYPE and SIZE */
8229 fprintf(stderr,
8230 "Warning: %s on '%s' failed (may have changed type)\n",
8231 !memkeys? types[i]->sizecmd: "MEMORY USAGE",
8232 keys->element[i]->str);
8233 sizes[i] = 0;
8234 } else {
8235 sizes[i] = reply->integer;
8236 }
8237
8238 freeReplyObject(reply);
8239 }
8240}
8241
8242static void longStatLoopModeStop(int s) {
8243 UNUSED(s);
8244 force_cancel_loop = 1;
8245}
8246
8247static void findBigKeys(int memkeys, unsigned memkeys_samples) {
8248 unsigned long long sampled = 0, total_keys, totlen=0, *sizes=NULL, it=0, scan_loops = 0;
8249 redisReply *reply, *keys;
8250 unsigned int arrsize=0, i;
8251 dictIterator *di;
8252 dictEntry *de;
8253 typeinfo **types = NULL;
8254 double pct;
8255
8256 dict *types_dict = dictCreate(&typeinfoDictType);
8257 typeinfo_add(types_dict, "string", &type_string);
8258 typeinfo_add(types_dict, "list", &type_list);
8259 typeinfo_add(types_dict, "set", &type_set);
8260 typeinfo_add(types_dict, "hash", &type_hash);
8261 typeinfo_add(types_dict, "zset", &type_zset);
8262 typeinfo_add(types_dict, "stream", &type_stream);
8263
8264 signal(SIGINT, longStatLoopModeStop);
8265 /* Total keys pre scanning */
8266 total_keys = getDbSize();
8267
8268 /* Status message */
8269 printf("\n# Scanning the entire keyspace to find biggest keys as well as\n");
8270 printf("# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec\n");
8271 printf("# per 100 SCAN commands (not usually needed).\n\n");
8272
8273 /* SCAN loop */
8274 do {
8275 /* Calculate approximate percentage completion */
8276 pct = 100 * (double)sampled/total_keys;
8277
8278 /* Grab some keys and point to the keys array */
8279 reply = sendScan(&it);
8280 scan_loops++;
8281 keys = reply->element[1];
8282
8283 /* Reallocate our type and size array if we need to */
8284 if(keys->elements > arrsize) {
8285 types = zrealloc(types, sizeof(typeinfo*)*keys->elements);
8286 sizes = zrealloc(sizes, sizeof(unsigned long long)*keys->elements);
8287
8288 if(!types || !sizes) {
8289 fprintf(stderr, "Failed to allocate storage for keys!\n");
8290 exit(1);
8291 }
8292
8293 arrsize = keys->elements;
8294 }
8295
8296 /* Retrieve types and then sizes */
8297 getKeyTypes(types_dict, keys, types);
8298 getKeySizes(keys, types, sizes, memkeys, memkeys_samples);
8299
8300 /* Now update our stats */
8301 for(i=0;i<keys->elements;i++) {
8302 typeinfo *type = types[i];
8303 /* Skip keys that disappeared between SCAN and TYPE */
8304 if(!type)
8305 continue;
8306
8307 type->totalsize += sizes[i];
8308 type->count++;
8309 totlen += keys->element[i]->len;
8310 sampled++;
8311
8312 if(type->biggest<sizes[i]) {
8313 /* Keep track of biggest key name for this type */
8314 if (type->biggest_key)
8315 sdsfree(type->biggest_key);
8316 type->biggest_key = sdscatrepr(sdsempty(), keys->element[i]->str, keys->element[i]->len);
8317 if(!type->biggest_key) {
8318 fprintf(stderr, "Failed to allocate memory for key!\n");
8319 exit(1);
8320 }
8321
8322 printf(
8323 "[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n",
8324 pct, type->name, type->biggest_key, sizes[i],
8325 !memkeys? type->sizeunit: "bytes");
8326
8327 /* Keep track of the biggest size for this type */
8328 type->biggest = sizes[i];
8329 }
8330
8331 /* Update overall progress */
8332 if(sampled % 1000000 == 0) {
8333 printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled);
8334 }
8335 }
8336
8337 /* Sleep if we've been directed to do so */
8338 if (config.interval && (scan_loops % 100) == 0) {
8339 usleep(config.interval);
8340 }
8341
8342 freeReplyObject(reply);
8343 } while(force_cancel_loop == 0 && it != 0);
8344
8345 if(types) zfree(types);
8346 if(sizes) zfree(sizes);
8347
8348 /* We're done */
8349 printf("\n-------- summary -------\n\n");
8350 if (force_cancel_loop) printf("[%05.2f%%] ", pct);
8351 printf("Sampled %llu keys in the keyspace!\n", sampled);
8352 printf("Total key length in bytes is %llu (avg len %.2f)\n\n",
8353 totlen, totlen ? (double)totlen/sampled : 0);
8354
8355 /* Output the biggest keys we found, for types we did find */
8356 di = dictGetIterator(types_dict);
8357 while ((de = dictNext(di))) {
8358 typeinfo *type = dictGetVal(de);
8359 if(type->biggest_key) {
8360 printf("Biggest %6s found '%s' has %llu %s\n", type->name, type->biggest_key,
8361 type->biggest, !memkeys? type->sizeunit: "bytes");
8362 }
8363 }
8364 dictReleaseIterator(di);
8365
8366 printf("\n");
8367
8368 di = dictGetIterator(types_dict);
8369 while ((de = dictNext(di))) {
8370 typeinfo *type = dictGetVal(de);
8371 printf("%llu %ss with %llu %s (%05.2f%% of keys, avg size %.2f)\n",
8372 type->count, type->name, type->totalsize, !memkeys? type->sizeunit: "bytes",
8373 sampled ? 100 * (double)type->count/sampled : 0,
8374 type->count ? (double)type->totalsize/type->count : 0);
8375 }
8376 dictReleaseIterator(di);
8377
8378 dictRelease(types_dict);
8379
8380 /* Success! */
8381 exit(0);
8382}
8383
8384static void getKeyFreqs(redisReply *keys, unsigned long long *freqs) {
8385 redisReply *reply;
8386 unsigned int i;
8387
8388 /* Pipeline OBJECT freq commands */
8389 for(i=0;i<keys->elements;i++) {
8390 const char* argv[] = {"OBJECT", "FREQ", keys->element[i]->str};
8391 size_t lens[] = {6, 4, keys->element[i]->len};
8392 redisAppendCommandArgv(context, 3, argv, lens);
8393 }
8394
8395 /* Retrieve freqs */
8396 for(i=0;i<keys->elements;i++) {
8397 if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
8398 sds keyname = sdscatrepr(sdsempty(), keys->element[i]->str, keys->element[i]->len);
8399 fprintf(stderr, "Error getting freq for key '%s' (%d: %s)\n",
8400 keyname, context->err, context->errstr);
8401 sdsfree(keyname);
8402 exit(1);
8403 } else if(reply->type != REDIS_REPLY_INTEGER) {
8404 if(reply->type == REDIS_REPLY_ERROR) {
8405 fprintf(stderr, "Error: %s\n", reply->str);
8406 exit(1);
8407 } else {
8408 sds keyname = sdscatrepr(sdsempty(), keys->element[i]->str, keys->element[i]->len);
8409 fprintf(stderr, "Warning: OBJECT freq on '%s' failed (may have been deleted)\n", keyname);
8410 sdsfree(keyname);
8411 freqs[i] = 0;
8412 }
8413 } else {
8414 freqs[i] = reply->integer;
8415 }
8416 freeReplyObject(reply);
8417 }
8418}
8419
8420#define HOTKEYS_SAMPLE 16
8421static void findHotKeys(void) {
8422 redisReply *keys, *reply;
8423 unsigned long long counters[HOTKEYS_SAMPLE] = {0};
8424 sds hotkeys[HOTKEYS_SAMPLE] = {NULL};
8425 unsigned long long sampled = 0, total_keys, *freqs = NULL, it = 0, scan_loops = 0;
8426 unsigned int arrsize = 0, i, k;
8427 double pct;
8428
8429 signal(SIGINT, longStatLoopModeStop);
8430 /* Total keys pre scanning */
8431 total_keys = getDbSize();
8432
8433 /* Status message */
8434 printf("\n# Scanning the entire keyspace to find hot keys as well as\n");
8435 printf("# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec\n");
8436 printf("# per 100 SCAN commands (not usually needed).\n\n");
8437
8438 /* SCAN loop */
8439 do {
8440 /* Calculate approximate percentage completion */
8441 pct = 100 * (double)sampled/total_keys;
8442
8443 /* Grab some keys and point to the keys array */
8444 reply = sendScan(&it);
8445 scan_loops++;
8446 keys = reply->element[1];
8447
8448 /* Reallocate our freqs array if we need to */
8449 if(keys->elements > arrsize) {
8450 freqs = zrealloc(freqs, sizeof(unsigned long long)*keys->elements);
8451
8452 if(!freqs) {
8453 fprintf(stderr, "Failed to allocate storage for keys!\n");
8454 exit(1);
8455 }
8456
8457 arrsize = keys->elements;
8458 }
8459
8460 getKeyFreqs(keys, freqs);
8461
8462 /* Now update our stats */
8463 for(i=0;i<keys->elements;i++) {
8464 sampled++;
8465 /* Update overall progress */
8466 if(sampled % 1000000 == 0) {
8467 printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled);
8468 }
8469
8470 /* Use eviction pool here */
8471 k = 0;
8472 while (k < HOTKEYS_SAMPLE && freqs[i] > counters[k]) k++;
8473 if (k == 0) continue;
8474 k--;
8475 if (k == 0 || counters[k] == 0) {
8476 sdsfree(hotkeys[k]);
8477 } else {
8478 sdsfree(hotkeys[0]);
8479 memmove(counters,counters+1,sizeof(counters[0])*k);
8480 memmove(hotkeys,hotkeys+1,sizeof(hotkeys[0])*k);
8481 }
8482 counters[k] = freqs[i];
8483 hotkeys[k] = sdscatrepr(sdsempty(), keys->element[i]->str, keys->element[i]->len);
8484 printf(
8485 "[%05.2f%%] Hot key '%s' found so far with counter %llu\n",
8486 pct, hotkeys[k], freqs[i]);
8487 }
8488
8489 /* Sleep if we've been directed to do so */
8490 if (config.interval && (scan_loops % 100) == 0) {
8491 usleep(config.interval);
8492 }
8493
8494 freeReplyObject(reply);
8495 } while(force_cancel_loop ==0 && it != 0);
8496
8497 if (freqs) zfree(freqs);
8498
8499 /* We're done */
8500 printf("\n-------- summary -------\n\n");
8501 if(force_cancel_loop)printf("[%05.2f%%] ",pct);
8502 printf("Sampled %llu keys in the keyspace!\n", sampled);
8503
8504 for (i=1; i<= HOTKEYS_SAMPLE; i++) {
8505 k = HOTKEYS_SAMPLE - i;
8506 if(counters[k]>0) {
8507 printf("hot key found with counter: %llu\tkeyname: %s\n", counters[k], hotkeys[k]);
8508 sdsfree(hotkeys[k]);
8509 }
8510 }
8511
8512 exit(0);
8513}
8514
8515/*------------------------------------------------------------------------------
8516 * Stats mode
8517 *--------------------------------------------------------------------------- */
8518
8519/* Return the specified INFO field from the INFO command output "info".
8520 * A new buffer is allocated for the result, that needs to be free'd.
8521 * If the field is not found NULL is returned. */
8522static char *getInfoField(char *info, char *field) {
8523 char *p = strstr(info,field);
8524 char *n1, *n2;
8525 char *result;
8526
8527 if (!p) return NULL;
8528 p += strlen(field)+1;
8529 n1 = strchr(p,'\r');
8530 n2 = strchr(p,',');
8531 if (n2 && n2 < n1) n1 = n2;
8532 result = zmalloc(sizeof(char)*(n1-p)+1);
8533 memcpy(result,p,(n1-p));
8534 result[n1-p] = '\0';
8535 return result;
8536}
8537
8538/* Like the above function but automatically convert the result into
8539 * a long. On error (missing field) LONG_MIN is returned. */
8540static long getLongInfoField(char *info, char *field) {
8541 char *value = getInfoField(info,field);
8542 long l;
8543
8544 if (!value) return LONG_MIN;
8545 l = strtol(value,NULL,10);
8546 zfree(value);
8547 return l;
8548}
8549
8550/* Convert number of bytes into a human readable string of the form:
8551 * 100B, 2G, 100M, 4K, and so forth. */
8552void bytesToHuman(char *s, long long n) {
8553 double d;
8554
8555 if (n < 0) {
8556 *s = '-';
8557 s++;
8558 n = -n;
8559 }
8560 if (n < 1024) {
8561 /* Bytes */
8562 sprintf(s,"%lldB",n);
8563 return;
8564 } else if (n < (1024*1024)) {
8565 d = (double)n/(1024);
8566 sprintf(s,"%.2fK",d);
8567 } else if (n < (1024LL*1024*1024)) {
8568 d = (double)n/(1024*1024);
8569 sprintf(s,"%.2fM",d);
8570 } else if (n < (1024LL*1024*1024*1024)) {
8571 d = (double)n/(1024LL*1024*1024);
8572 sprintf(s,"%.2fG",d);
8573 }
8574}
8575
8576static void statMode(void) {
8577 redisReply *reply;
8578 long aux, requests = 0;
8579 int i = 0;
8580
8581 while(1) {
8582 char buf[64];
8583 int j;
8584
8585 reply = reconnectingRedisCommand(context,"INFO");
8586 if (reply == NULL) {
8587 fprintf(stderr, "\nI/O error\n");
8588 exit(1);
8589 } else if (reply->type == REDIS_REPLY_ERROR) {
8590 fprintf(stderr, "ERROR: %s\n", reply->str);
8591 exit(1);
8592 }
8593
8594 if ((i++ % 20) == 0) {
8595 printf(
8596"------- data ------ --------------------- load -------------------- - child -\n"
8597"keys mem clients blocked requests connections \n");
8598 }
8599
8600 /* Keys */
8601 aux = 0;
8602 for (j = 0; j < 20; j++) {
8603 long k;
8604
8605 sprintf(buf,"db%d:keys",j);
8606 k = getLongInfoField(reply->str,buf);
8607 if (k == LONG_MIN) continue;
8608 aux += k;
8609 }
8610 sprintf(buf,"%ld",aux);
8611 printf("%-11s",buf);
8612
8613 /* Used memory */
8614 aux = getLongInfoField(reply->str,"used_memory");
8615 bytesToHuman(buf,aux);
8616 printf("%-8s",buf);
8617
8618 /* Clients */
8619 aux = getLongInfoField(reply->str,"connected_clients");
8620 sprintf(buf,"%ld",aux);
8621 printf(" %-8s",buf);
8622
8623 /* Blocked (BLPOPPING) Clients */
8624 aux = getLongInfoField(reply->str,"blocked_clients");
8625 sprintf(buf,"%ld",aux);
8626 printf("%-8s",buf);
8627
8628 /* Requests */
8629 aux = getLongInfoField(reply->str,"total_commands_processed");
8630 sprintf(buf,"%ld (+%ld)",aux,requests == 0 ? 0 : aux-requests);
8631 printf("%-19s",buf);
8632 requests = aux;
8633
8634 /* Connections */
8635 aux = getLongInfoField(reply->str,"total_connections_received");
8636 sprintf(buf,"%ld",aux);
8637 printf(" %-12s",buf);
8638
8639 /* Children */
8640 aux = getLongInfoField(reply->str,"bgsave_in_progress");
8641 aux |= getLongInfoField(reply->str,"aof_rewrite_in_progress") << 1;
8642 aux |= getLongInfoField(reply->str,"loading") << 2;
8643 switch(aux) {
8644 case 0: break;
8645 case 1:
8646 printf("SAVE");
8647 break;
8648 case 2:
8649 printf("AOF");
8650 break;
8651 case 3:
8652 printf("SAVE+AOF");
8653 break;
8654 case 4:
8655 printf("LOAD");
8656 break;
8657 }
8658
8659 printf("\n");
8660 freeReplyObject(reply);
8661 usleep(config.interval);
8662 }
8663}
8664
8665/*------------------------------------------------------------------------------
8666 * Scan mode
8667 *--------------------------------------------------------------------------- */
8668
8669static void scanMode(void) {
8670 redisReply *reply;
8671 unsigned long long cur = 0;
8672 signal(SIGINT, longStatLoopModeStop);
8673 do {
8674 reply = sendScan(&cur);
8675 for (unsigned int j = 0; j < reply->element[1]->elements; j++) {
8676 if (config.output == OUTPUT_STANDARD) {
8677 sds out = sdscatrepr(sdsempty(), reply->element[1]->element[j]->str,
8678 reply->element[1]->element[j]->len);
8679 printf("%s\n", out);
8680 sdsfree(out);
8681 } else {
8682 printf("%s\n", reply->element[1]->element[j]->str);
8683 }
8684 }
8685 freeReplyObject(reply);
8686 if (config.interval) usleep(config.interval);
8687 } while(force_cancel_loop == 0 && cur != 0);
8688
8689 exit(0);
8690}
8691
8692/*------------------------------------------------------------------------------
8693 * LRU test mode
8694 *--------------------------------------------------------------------------- */
8695
8696/* Return an integer from min to max (both inclusive) using a power-law
8697 * distribution, depending on the value of alpha: the greater the alpha
8698 * the more bias towards lower values.
8699 *
8700 * With alpha = 6.2 the output follows the 80-20 rule where 20% of
8701 * the returned numbers will account for 80% of the frequency. */
8702long long powerLawRand(long long min, long long max, double alpha) {
8703 double pl, r;
8704
8705 max += 1;
8706 r = ((double)rand()) / RAND_MAX;
8707 pl = pow(
8708 ((pow(max,alpha+1) - pow(min,alpha+1))*r + pow(min,alpha+1)),
8709 (1.0/(alpha+1)));
8710 return (max-1-(long long)pl)+min;
8711}
8712
8713/* Generates a key name among a set of lru_test_sample_size keys, using
8714 * an 80-20 distribution. */
8715void LRUTestGenKey(char *buf, size_t buflen) {
8716 snprintf(buf, buflen, "lru:%lld",
8717 powerLawRand(1, config.lru_test_sample_size, 6.2));
8718}
8719
8720#define LRU_CYCLE_PERIOD 1000 /* 1000 milliseconds. */
8721#define LRU_CYCLE_PIPELINE_SIZE 250
8722static void LRUTestMode(void) {
8723 redisReply *reply;
8724 char key[128];
8725 long long start_cycle;
8726 int j;
8727
8728 srand(time(NULL)^getpid());
8729 while(1) {
8730 /* Perform cycles of 1 second with 50% writes and 50% reads.
8731 * We use pipelining batching writes / reads N times per cycle in order
8732 * to fill the target instance easily. */
8733 start_cycle = mstime();
8734 long long hits = 0, misses = 0;
8735 while(mstime() - start_cycle < LRU_CYCLE_PERIOD) {
8736 /* Write cycle. */
8737 for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
8738 char val[6];
8739 val[5] = '\0';
8740 for (int i = 0; i < 5; i++) val[i] = 'A'+rand()%('z'-'A');
8741 LRUTestGenKey(key,sizeof(key));
8742 redisAppendCommand(context, "SET %s %s",key,val);
8743 }
8744 for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++)
8745 redisGetReply(context, (void**)&reply);
8746
8747 /* Read cycle. */
8748 for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
8749 LRUTestGenKey(key,sizeof(key));
8750 redisAppendCommand(context, "GET %s",key);
8751 }
8752 for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
8753 if (redisGetReply(context, (void**)&reply) == REDIS_OK) {
8754 switch(reply->type) {
8755 case REDIS_REPLY_ERROR:
8756 fprintf(stderr, "%s\n", reply->str);
8757 break;
8758 case REDIS_REPLY_NIL:
8759 misses++;
8760 break;
8761 default:
8762 hits++;
8763 break;
8764 }
8765 }
8766 }
8767
8768 if (context->err) {
8769 fprintf(stderr,"I/O error during LRU test\n");
8770 exit(1);
8771 }
8772 }
8773 /* Print stats. */
8774 printf(
8775 "%lld Gets/sec | Hits: %lld (%.2f%%) | Misses: %lld (%.2f%%)\n",
8776 hits+misses,
8777 hits, (double)hits/(hits+misses)*100,
8778 misses, (double)misses/(hits+misses)*100);
8779 }
8780 exit(0);
8781}
8782
8783/*------------------------------------------------------------------------------
8784 * Intrinsic latency mode.
8785 *
8786 * Measure max latency of a running process that does not result from
8787 * syscalls. Basically this software should provide a hint about how much
8788 * time the kernel leaves the process without a chance to run.
8789 *--------------------------------------------------------------------------- */
8790
8791/* This is just some computation the compiler can't optimize out.
8792 * Should run in less than 100-200 microseconds even using very
8793 * slow hardware. Runs in less than 10 microseconds in modern HW. */
8794unsigned long compute_something_fast(void) {
8795 unsigned char s[256], i, j, t;
8796 int count = 1000, k;
8797 unsigned long output = 0;
8798
8799 for (k = 0; k < 256; k++) s[k] = k;
8800
8801 i = 0;
8802 j = 0;
8803 while(count--) {
8804 i++;
8805 j = j + s[i];
8806 t = s[i];
8807 s[i] = s[j];
8808 s[j] = t;
8809 output += s[(s[i]+s[j])&255];
8810 }
8811 return output;
8812}
8813
8814static void sigIntHandler(int s) {
8815 UNUSED(s);
8816
8817 if (config.monitor_mode || config.pubsub_mode) {
8818 close(context->fd);
8819 context->fd = REDIS_INVALID_FD;
8820 config.blocking_state_aborted = 1;
8821 } else {
8822 exit(1);
8823 }
8824}
8825
8826static void intrinsicLatencyMode(void) {
8827 long long test_end, run_time, max_latency = 0, runs = 0;
8828
8829 run_time = (long long)config.intrinsic_latency_duration * 1000000;
8830 test_end = ustime() + run_time;
8831 signal(SIGINT, longStatLoopModeStop);
8832
8833 while(1) {
8834 long long start, end, latency;
8835
8836 start = ustime();
8837 compute_something_fast();
8838 end = ustime();
8839 latency = end-start;
8840 runs++;
8841 if (latency <= 0) continue;
8842
8843 /* Reporting */
8844 if (latency > max_latency) {
8845 max_latency = latency;
8846 printf("Max latency so far: %lld microseconds.\n", max_latency);
8847 }
8848
8849 double avg_us = (double)run_time/runs;
8850 double avg_ns = avg_us * 1e3;
8851 if (force_cancel_loop || end > test_end) {
8852 printf("\n%lld total runs "
8853 "(avg latency: "
8854 "%.4f microseconds / %.2f nanoseconds per run).\n",
8855 runs, avg_us, avg_ns);
8856 printf("Worst run took %.0fx longer than the average latency.\n",
8857 max_latency / avg_us);
8858 exit(0);
8859 }
8860 }
8861}
8862
8863static sds askPassword(const char *msg) {
8864 linenoiseMaskModeEnable();
8865 sds auth = linenoise(msg);
8866 linenoiseMaskModeDisable();
8867 return auth;
8868}
8869
8870/*------------------------------------------------------------------------------
8871 * Program main()
8872 *--------------------------------------------------------------------------- */
8873
8874int main(int argc, char **argv) {
8875 int firstarg;
8876 struct timeval tv;
8877
8878 memset(&config.sslconfig, 0, sizeof(config.sslconfig));
8879 config.conn_info.hostip = sdsnew("127.0.0.1");
8880 config.conn_info.hostport = 6379;
8881 config.hostsocket = NULL;
8882 config.repeat = 1;
8883 config.interval = 0;
8884 config.dbnum = 0;
8885 config.conn_info.input_dbnum = 0;
8886 config.interactive = 0;
8887 config.shutdown = 0;
8888 config.monitor_mode = 0;
8889 config.pubsub_mode = 0;
8890 config.blocking_state_aborted = 0;
8891 config.latency_mode = 0;
8892 config.latency_dist_mode = 0;
8893 config.latency_history = 0;
8894 config.lru_test_mode = 0;
8895 config.lru_test_sample_size = 0;
8896 config.cluster_mode = 0;
8897 config.cluster_send_asking = 0;
8898 config.slave_mode = 0;
8899 config.getrdb_mode = 0;
8900 config.get_functions_rdb_mode = 0;
8901 config.stat_mode = 0;
8902 config.scan_mode = 0;
8903 config.intrinsic_latency_mode = 0;
8904 config.pattern = NULL;
8905 config.rdb_filename = NULL;
8906 config.pipe_mode = 0;
8907 config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT;
8908 config.bigkeys = 0;
8909 config.hotkeys = 0;
8910 config.stdin_lastarg = 0;
8911 config.stdin_tag_arg = 0;
8912 config.stdin_tag_name = NULL;
8913 config.conn_info.auth = NULL;
8914 config.askpass = 0;
8915 config.conn_info.user = NULL;
8916 config.eval = NULL;
8917 config.eval_ldb = 0;
8918 config.eval_ldb_end = 0;
8919 config.eval_ldb_sync = 0;
8920 config.enable_ldb_on_eval = 0;
8921 config.last_cmd_type = -1;
8922 config.verbose = 0;
8923 config.set_errcode = 0;
8924 config.no_auth_warning = 0;
8925 config.in_multi = 0;
8926 config.cluster_manager_command.name = NULL;
8927 config.cluster_manager_command.argc = 0;
8928 config.cluster_manager_command.argv = NULL;
8929 config.cluster_manager_command.stdin_arg = NULL;
8930 config.cluster_manager_command.flags = 0;
8931 config.cluster_manager_command.replicas = 0;
8932 config.cluster_manager_command.from = NULL;
8933 config.cluster_manager_command.to = NULL;
8934 config.cluster_manager_command.from_user = NULL;
8935 config.cluster_manager_command.from_pass = NULL;
8936 config.cluster_manager_command.from_askpass = 0;
8937 config.cluster_manager_command.weight = NULL;
8938 config.cluster_manager_command.weight_argc = 0;
8939 config.cluster_manager_command.slots = 0;
8940 config.cluster_manager_command.timeout = CLUSTER_MANAGER_MIGRATE_TIMEOUT;
8941 config.cluster_manager_command.pipeline = CLUSTER_MANAGER_MIGRATE_PIPELINE;
8942 config.cluster_manager_command.threshold =
8943 CLUSTER_MANAGER_REBALANCE_THRESHOLD;
8944 config.cluster_manager_command.backup_dir = NULL;
8945 pref.hints = 1;
8946
8947 spectrum_palette = spectrum_palette_color;
8948 spectrum_palette_size = spectrum_palette_color_size;
8949
8950 if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL)) {
8951 config.output = OUTPUT_RAW;
8952 config.push_output = 0;
8953 } else {
8954 config.output = OUTPUT_STANDARD;
8955 config.push_output = 1;
8956 }
8957 config.mb_delim = sdsnew("\n");
8958 config.cmd_delim = sdsnew("\n");
8959
8960 firstarg = parseOptions(argc,argv);
8961 argc -= firstarg;
8962 argv += firstarg;
8963
8964 parseEnv();
8965
8966 if (config.askpass) {
8967 config.conn_info.auth = askPassword("Please input password: ");
8968 }
8969
8970 if (config.cluster_manager_command.from_askpass) {
8971 config.cluster_manager_command.from_pass = askPassword(
8972 "Please input import source node password: ");
8973 }
8974
8975#ifdef USE_OPENSSL
8976 if (config.tls) {
8977 cliSecureInit();
8978 }
8979#endif
8980
8981 gettimeofday(&tv, NULL);
8982 init_genrand64(((long long) tv.tv_sec * 1000000 + tv.tv_usec) ^ getpid());
8983
8984 /* Cluster Manager mode */
8985 if (CLUSTER_MANAGER_MODE()) {
8986 clusterManagerCommandProc *proc = validateClusterManagerCommand();
8987 if (!proc) {
8988 exit(1);
8989 }
8990 clusterManagerMode(proc);
8991 }
8992
8993 /* Latency mode */
8994 if (config.latency_mode) {
8995 if (cliConnect(0) == REDIS_ERR) exit(1);
8996 latencyMode();
8997 }
8998
8999 /* Latency distribution mode */
9000 if (config.latency_dist_mode) {
9001 if (cliConnect(0) == REDIS_ERR) exit(1);
9002 latencyDistMode();
9003 }
9004
9005 /* Slave mode */
9006 if (config.slave_mode) {
9007 if (cliConnect(0) == REDIS_ERR) exit(1);
9008 sendCapa();
9009 sendReplconf("rdb-filter-only", "");
9010 slaveMode();
9011 }
9012
9013 /* Get RDB/functions mode. */
9014 if (config.getrdb_mode || config.get_functions_rdb_mode) {
9015 if (cliConnect(0) == REDIS_ERR) exit(1);
9016 sendCapa();
9017 sendRdbOnly();
9018 if (config.get_functions_rdb_mode && !sendReplconf("rdb-filter-only", "functions")) {
9019 fprintf(stderr, "Failed requesting functions only RDB from server, aborting\n");
9020 exit(1);
9021 }
9022 getRDB(NULL);
9023 }
9024
9025 /* Pipe mode */
9026 if (config.pipe_mode) {
9027 if (cliConnect(0) == REDIS_ERR) exit(1);
9028 pipeMode();
9029 }
9030
9031 /* Find big keys */
9032 if (config.bigkeys) {
9033 if (cliConnect(0) == REDIS_ERR) exit(1);
9034 findBigKeys(0, 0);
9035 }
9036
9037 /* Find large keys */
9038 if (config.memkeys) {
9039 if (cliConnect(0) == REDIS_ERR) exit(1);
9040 findBigKeys(1, config.memkeys_samples);
9041 }
9042
9043 /* Find hot keys */
9044 if (config.hotkeys) {
9045 if (cliConnect(0) == REDIS_ERR) exit(1);
9046 findHotKeys();
9047 }
9048
9049 /* Stat mode */
9050 if (config.stat_mode) {
9051 if (cliConnect(0) == REDIS_ERR) exit(1);
9052 if (config.interval == 0) config.interval = 1000000;
9053 statMode();
9054 }
9055
9056 /* Scan mode */
9057 if (config.scan_mode) {
9058 if (cliConnect(0) == REDIS_ERR) exit(1);
9059 scanMode();
9060 }
9061
9062 /* LRU test mode */
9063 if (config.lru_test_mode) {
9064 if (cliConnect(0) == REDIS_ERR) exit(1);
9065 LRUTestMode();
9066 }
9067
9068 /* Intrinsic latency mode */
9069 if (config.intrinsic_latency_mode) intrinsicLatencyMode();
9070
9071 /* Start interactive mode when no command is provided */
9072 if (argc == 0 && !config.eval) {
9073 /* Ignore SIGPIPE in interactive mode to force a reconnect */
9074 signal(SIGPIPE, SIG_IGN);
9075 signal(SIGINT, sigIntHandler);
9076
9077 /* Note that in repl mode we don't abort on connection error.
9078 * A new attempt will be performed for every command send. */
9079 cliConnect(0);
9080 repl();
9081 }
9082
9083 /* Otherwise, we have some arguments to execute */
9084 if (config.eval) {
9085 if (cliConnect(0) != REDIS_OK) exit(1);
9086 return evalMode(argc,argv);
9087 } else {
9088 cliConnect(CC_QUIET);
9089 return noninteractive(argc,argv);
9090 }
9091}
9092