From b5a879e1c2df3dc83cbc17e0a8203decd2dc98ac Mon Sep 17 00:00:00 2001 From: filipe oliveira Date: Tue, 14 Sep 2021 17:45:06 +0100 Subject: [PATCH] Added URI support to redis-benchmark (cli and benchmark share the same uri-parsing methods) (#9314) - Add `-u ` command line option to support `redis://` URI scheme. - included server connection information object (`struct cliConnInfo`), used to describe an ip:port pair, db num user input, and user:pass to avoid a large number of function arguments. - Using sds on connection info strings for redis-benchmark/redis-cli Co-authored-by: yoav-steinberg --- src/cli_common.c | 111 ++++++++++++++ src/cli_common.h | 13 ++ src/redis-benchmark.c | 73 ++++----- src/redis-cli.c | 210 +++++++------------------- tests/integration/redis-benchmark.tcl | 106 ++++++------- tests/support/benchmark.tcl | 14 ++ 6 files changed, 280 insertions(+), 247 deletions(-) diff --git a/src/cli_common.c b/src/cli_common.c index ee10d324b..75eec4911 100644 --- a/src/cli_common.c +++ b/src/cli_common.c @@ -38,12 +38,15 @@ #include /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */ #include /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */ #include +#include +#include #ifdef USE_OPENSSL #include #include #include #endif +#define UNUSED(V) ((void) V) /* Wrapper around redisSecureConnection to avoid hiredis_ssl dependencies if * not building with TLS support. @@ -259,3 +262,111 @@ sds unquoteCString(char *str) { return res; } + + +/* URL-style percent decoding. */ +#define isHexChar(c) (isdigit(c) || ((c) >= 'a' && (c) <= 'f')) +#define decodeHexChar(c) (isdigit(c) ? (c) - '0' : (c) - 'a' + 10) +#define decodeHex(h, l) ((decodeHexChar(h) << 4) + decodeHexChar(l)) + +static sds percentDecode(const char *pe, size_t len) { + const char *end = pe + len; + sds ret = sdsempty(); + const char *curr = pe; + + while (curr < end) { + if (*curr == '%') { + if ((end - curr) < 2) { + fprintf(stderr, "Incomplete URI encoding\n"); + exit(1); + } + + char h = tolower(*(++curr)); + char l = tolower(*(++curr)); + if (!isHexChar(h) || !isHexChar(l)) { + fprintf(stderr, "Illegal character in URI encoding\n"); + exit(1); + } + char c = decodeHex(h, l); + ret = sdscatlen(ret, &c, 1); + curr++; + } else { + ret = sdscatlen(ret, curr++, 1); + } + } + + return ret; +} + +/* Parse a URI and extract the server connection information. + * URI scheme is based on the the provisional specification[1] excluding support + * for query parameters. Valid URIs are: + * scheme: "redis://" + * authority: [[ ":"] "@"] [ [":" ]] + * path: ["/" []] + * + * [1]: https://www.iana.org/assignments/uri-schemes/prov/redis */ +void parseRedisUri(const char *uri, const char* tool_name, cliConnInfo *connInfo, int *tls_flag) { +#ifdef USE_OPENSSL + UNUSED(tool_name); +#else + UNUSED(tls_flag); +#endif + + const char *scheme = "redis://"; + const char *tlsscheme = "rediss://"; + const char *curr = uri; + const char *end = uri + strlen(uri); + const char *userinfo, *username, *port, *host, *path; + + /* URI must start with a valid scheme. */ + if (!strncasecmp(tlsscheme, curr, strlen(tlsscheme))) { +#ifdef USE_OPENSSL + *tls_flag = 1; + curr += strlen(tlsscheme); +#else + fprintf(stderr,"rediss:// is only supported when %s is compiled with OpenSSL\n", tool_name); + exit(1); +#endif + } else if (!strncasecmp(scheme, curr, strlen(scheme))) { + curr += strlen(scheme); + } else { + fprintf(stderr,"Invalid URI scheme\n"); + exit(1); + } + if (curr == end) return; + + /* Extract user info. */ + if ((userinfo = strchr(curr,'@'))) { + if ((username = strchr(curr, ':')) && username < userinfo) { + connInfo->user = percentDecode(curr, username - curr); + curr = username + 1; + } + + connInfo->auth = percentDecode(curr, userinfo - curr); + curr = userinfo + 1; + } + if (curr == end) return; + + /* Extract host and port. */ + path = strchr(curr, '/'); + if (*curr != '/') { + host = path ? path - 1 : end; + if ((port = strchr(curr, ':'))) { + connInfo->hostport = atoi(port + 1); + host = port - 1; + } + connInfo->hostip = sdsnewlen(curr, host - curr + 1); + } + curr = path ? path + 1 : end; + if (curr == end) return; + + /* Extract database number. */ + connInfo->input_dbnum = atoi(curr); +} + +void freeCliConnInfo(cliConnInfo connInfo){ + if (connInfo.hostip) sdsfree(connInfo.hostip); + if (connInfo.auth) sdsfree(connInfo.auth); + if (connInfo.user) sdsfree(connInfo.user); +} diff --git a/src/cli_common.h b/src/cli_common.h index 239f51808..1cb76c6b9 100644 --- a/src/cli_common.h +++ b/src/cli_common.h @@ -23,6 +23,16 @@ typedef struct cliSSLconfig { char* ciphersuites; } cliSSLconfig; + +/* server connection information object, used to describe an ip:port pair, db num user input, and user:pass. */ +typedef struct cliConnInfo { + char *hostip; + int hostport; + int input_dbnum; + char *auth; + char *user; +} cliConnInfo; + int cliSecureConnection(redisContext *c, cliSSLconfig config, const char **err); ssize_t cliWriteConn(redisContext *c, const char *buf, size_t buf_len); @@ -35,4 +45,7 @@ sds *getSdsArrayFromArgv(int argc,char **argv, int quoted); sds unquoteCString(char *str); +void parseRedisUri(const char *uri, const char* tool_name, cliConnInfo *connInfo, int *tls_flag); + +void freeCliConnInfo(cliConnInfo connInfo); #endif /* __CLICOMMON_H */ diff --git a/src/redis-benchmark.c b/src/redis-benchmark.c index eba804793..54f4c5387 100644 --- a/src/redis-benchmark.c +++ b/src/redis-benchmark.c @@ -81,8 +81,7 @@ struct redisConfig; static struct config { aeEventLoop *el; - const char *hostip; - int hostport; + cliConnInfo conn_info; const char *hostsocket; int tls; struct cliSSLconfig sslconfig; @@ -108,12 +107,9 @@ static struct config { int csv; int loop; int idlemode; - int dbnum; - sds dbnumstr; + sds input_dbnumstr; char *tests; int stdinarg; /* get last arg from stdin. (-x option) */ - char *auth; - const char *user; int precision; int num_threads; struct benchmarkThread **threads; @@ -287,12 +283,12 @@ static redisContext *getRedisContext(const char *ip, int port, goto cleanup; } } - if (config.auth == NULL) + if (config.conn_info.auth == NULL) return ctx; - if (config.user == NULL) - reply = redisCommand(ctx,"AUTH %s", config.auth); + if (config.conn_info.user == NULL) + reply = redisCommand(ctx,"AUTH %s", config.conn_info.auth); else - reply = redisCommand(ctx,"AUTH %s %s", config.user, config.auth); + reply = redisCommand(ctx,"AUTH %s %s", config.conn_info.user, config.conn_info.auth); if (reply != NULL) { if (reply->type == REDIS_REPLY_ERROR) { if (hostsocket == NULL) @@ -677,8 +673,8 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) { c->cluster_node = NULL; if (config.hostsocket == NULL || is_cluster_client) { if (!is_cluster_client) { - ip = config.hostip; - port = config.hostport; + ip = config.conn_info.hostip; + port = config.conn_info.hostport; } else { int node_idx = 0; if (config.num_threads < config.cluster_node_count) @@ -722,14 +718,14 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) { * These commands are discarded after the first response, so if the client is * reused the commands will not be used again. */ c->prefix_pending = 0; - if (config.auth) { + if (config.conn_info.auth) { char *buf = NULL; int len; - if (config.user == NULL) - len = redisFormatCommand(&buf, "AUTH %s", config.auth); + if (config.conn_info.user == NULL) + len = redisFormatCommand(&buf, "AUTH %s", config.conn_info.auth); else len = redisFormatCommand(&buf, "AUTH %s %s", - config.user, config.auth); + config.conn_info.user, config.conn_info.auth); c->obuf = sdscatlen(c->obuf, buf, len); free(buf); c->prefix_pending++; @@ -747,9 +743,9 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) { * buffer with the SELECT command, that will be discarded the first * time the replies are received, so if the client is reused the * SELECT command will not be used again. */ - if (config.dbnum != 0 && !is_cluster_client) { + if (config.conn_info.input_dbnum != 0 && !is_cluster_client) { c->obuf = sdscatprintf(c->obuf,"*2\r\n$6\r\nSELECT\r\n$%d\r\n%s\r\n", - (int)sdslen(config.dbnumstr),config.dbnumstr); + (int)sdslen(config.input_dbnumstr),config.input_dbnumstr); c->prefix_pending++; } c->prefixlen = sdslen(c->obuf); @@ -1082,9 +1078,9 @@ static void freeClusterNode(clusterNode *node) { zfree(node->importing); } /* If the node is not the reference node, that uses the address from - * config.hostip and config.hostport, then the node ip has been + * config.conn_info.hostip and config.conn_info.hostport, then the node ip has been * allocated by fetchClusterConfiguration, so it must be freed. */ - if (node->ip && strcmp(node->ip, config.hostip) != 0) sdsfree(node->ip); + if (node->ip && strcmp(node->ip, config.conn_info.hostip) != 0) sdsfree(node->ip); if (node->redis_config != NULL) freeRedisConfig(node->redis_config); zfree(node->slots); zfree(node); @@ -1113,12 +1109,12 @@ static int fetchClusterConfiguration() { int success = 1; redisContext *ctx = NULL; redisReply *reply = NULL; - ctx = getRedisContext(config.hostip, config.hostport, config.hostsocket); + ctx = getRedisContext(config.conn_info.hostip, config.conn_info.hostport, config.hostsocket); if (ctx == NULL) { exit(1); } - clusterNode *firstNode = createClusterNode((char *) config.hostip, - config.hostport); + clusterNode *firstNode = createClusterNode((char *) config.conn_info.hostip, + config.conn_info.hostport); if (!firstNode) {success = 0; goto cleanup;} reply = redisCommand(ctx, "CLUSTER NODES"); success = (reply != NULL); @@ -1127,7 +1123,7 @@ static int fetchClusterConfiguration() { if (!success) { if (config.hostsocket == NULL) { fprintf(stderr, "Cluster node %s:%d replied with error:\n%s\n", - config.hostip, config.hostport, reply->str); + config.conn_info.hostip, config.conn_info.hostport, reply->str); } else { fprintf(stderr, "Cluster node %s replied with error:\n%s\n", config.hostsocket, reply->str); @@ -1425,10 +1421,10 @@ int parseOptions(int argc, char **argv) { config.keepalive = atoi(argv[++i]); } else if (!strcmp(argv[i],"-h")) { if (lastarg) goto invalid; - config.hostip = strdup(argv[++i]); + config.conn_info.hostip = sdsnew(argv[++i]); } else if (!strcmp(argv[i],"-p")) { if (lastarg) goto invalid; - config.hostport = atoi(argv[++i]); + config.conn_info.hostport = atoi(argv[++i]); } else if (!strcmp(argv[i],"-s")) { if (lastarg) goto invalid; config.hostsocket = strdup(argv[++i]); @@ -1436,10 +1432,13 @@ int parseOptions(int argc, char **argv) { config.stdinarg = 1; } else if (!strcmp(argv[i],"-a") ) { if (lastarg) goto invalid; - config.auth = strdup(argv[++i]); + config.conn_info.auth = sdsnew(argv[++i]); } else if (!strcmp(argv[i],"--user")) { if (lastarg) goto invalid; - config.user = argv[++i]; + config.conn_info.user = sdsnew(argv[++i]); + } else if (!strcmp(argv[i],"-u") && !lastarg) { + parseRedisUri(argv[++i],"redis-benchmark",&config.conn_info,&config.tls); + config.input_dbnumstr = sdsfromlonglong(config.conn_info.input_dbnum); } else if (!strcmp(argv[i],"-d")) { if (lastarg) goto invalid; config.datasize = atoi(argv[++i]); @@ -1485,8 +1484,8 @@ int parseOptions(int argc, char **argv) { sdstolower(config.tests); } else if (!strcmp(argv[i],"--dbnum")) { if (lastarg) goto invalid; - config.dbnum = atoi(argv[++i]); - config.dbnumstr = sdsfromlonglong(config.dbnum); + config.conn_info.input_dbnum = atoi(argv[++i]); + config.input_dbnumstr = sdsfromlonglong(config.conn_info.input_dbnum); } else if (!strcmp(argv[i],"--precision")) { if (lastarg) goto invalid; config.precision = atoi(argv[++i]); @@ -1561,6 +1560,7 @@ usage: " -s Server socket (overrides host and port)\n" " -a Password for Redis Auth\n" " --user Used to send ACL style 'AUTH username pass'. Needs -a.\n" +" -u Server URI.\n" " -c Number of parallel connections (default 50)\n" " -n Total number of requests (default 100000)\n" " -d Data size of SET/GET value in bytes (default 3)\n" @@ -1720,13 +1720,13 @@ int main(int argc, char **argv) { config.loop = 0; config.idlemode = 0; config.clients = listCreate(); - config.hostip = "127.0.0.1"; - config.hostport = 6379; + config.conn_info.hostip = "127.0.0.1"; + config.conn_info.hostport = 6379; config.hostsocket = NULL; config.tests = NULL; - config.dbnum = 0; + config.conn_info.input_dbnum = 0; config.stdinarg = 0; - config.auth = NULL; + config.conn_info.auth = NULL; config.precision = DEFAULT_LATENCY_PRECISION; config.num_threads = 0; config.threads = NULL; @@ -1759,7 +1759,7 @@ int main(int argc, char **argv) { if (!fetchClusterConfiguration() || !config.cluster_nodes) { if (!config.hostsocket) { fprintf(stderr, "Failed to fetch cluster configuration from " - "%s:%d\n", config.hostip, config.hostport); + "%s:%d\n", config.conn_info.hostip, config.conn_info.hostport); } else { fprintf(stderr, "Failed to fetch cluster configuration from " "%s\n", config.hostsocket); @@ -1795,7 +1795,7 @@ int main(int argc, char **argv) { config.num_threads = config.cluster_node_count; } else { config.redis_config = - getRedisConfig(config.hostip, config.hostport, config.hostsocket); + getRedisConfig(config.conn_info.hostip, config.conn_info.hostport, config.hostsocket); if (config.redis_config == NULL) { fprintf(stderr, "WARNING: Could not fetch server CONFIG\n"); } @@ -2007,6 +2007,7 @@ int main(int argc, char **argv) { } while(config.loop); zfree(data); + freeCliConnInfo(config.conn_info); if (config.redis_config != NULL) freeRedisConfig(config.redis_config); return 0; diff --git a/src/redis-cli.c b/src/redis-cli.c index 682ca437c..7144d2bc2 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -200,15 +200,13 @@ static void createClusterManagerCommand(char *cmdname, int argc, char **argv); static redisContext *context; static struct config { - char *hostip; - int hostport; + cliConnInfo conn_info; char *hostsocket; int tls; cliSSLconfig sslconfig; long repeat; long interval; int dbnum; /* db num currently selected */ - int input_dbnum; /* db num user input */ int interactive; int shutdown; int monitor_mode; @@ -237,9 +235,7 @@ static struct config { unsigned memkeys_samples; int hotkeys; int stdinarg; /* get last arg from stdin. (-x option) */ - char *auth; int askpass; - char *user; int quoted_input; /* Force input args to be treated as quoted strings */ int output; /* output mode, see OUTPUT_* defines */ int push_output; /* Should we display spontaneous PUSH replies */ @@ -306,7 +302,7 @@ static void cliRefreshPrompt(void) { prompt = sdscatfmt(prompt,"redis %s",config.hostsocket); } else { char addr[256]; - anetFormatAddr(addr, sizeof(addr), config.hostip, config.hostport); + anetFormatAddr(addr, sizeof(addr), config.conn_info.hostip, config.conn_info.hostport); prompt = sdscatlen(prompt,addr,strlen(addr)); } @@ -355,102 +351,6 @@ static sds getDotfilePath(char *envoverride, char *dotfilename) { return dotPath; } -/* URL-style percent decoding. */ -#define isHexChar(c) (isdigit(c) || (c >= 'a' && c <= 'f')) -#define decodeHexChar(c) (isdigit(c) ? c - '0' : c - 'a' + 10) -#define decodeHex(h, l) ((decodeHexChar(h) << 4) + decodeHexChar(l)) - -static sds percentDecode(const char *pe, size_t len) { - const char *end = pe + len; - sds ret = sdsempty(); - const char *curr = pe; - - while (curr < end) { - if (*curr == '%') { - if ((end - curr) < 2) { - fprintf(stderr, "Incomplete URI encoding\n"); - exit(1); - } - - char h = tolower(*(++curr)); - char l = tolower(*(++curr)); - if (!isHexChar(h) || !isHexChar(l)) { - fprintf(stderr, "Illegal character in URI encoding\n"); - exit(1); - } - char c = decodeHex(h, l); - ret = sdscatlen(ret, &c, 1); - curr++; - } else { - ret = sdscatlen(ret, curr++, 1); - } - } - - return ret; -} - -/* Parse a URI and extract the server connection information. - * URI scheme is based on the the provisional specification[1] excluding support - * for query parameters. Valid URIs are: - * scheme: "redis://" - * authority: [[ ":"] "@"] [ [":" ]] - * path: ["/" []] - * - * [1]: https://www.iana.org/assignments/uri-schemes/prov/redis */ -static void parseRedisUri(const char *uri) { - - const char *scheme = "redis://"; - const char *tlsscheme = "rediss://"; - const char *curr = uri; - const char *end = uri + strlen(uri); - const char *userinfo, *username, *port, *host, *path; - - /* URI must start with a valid scheme. */ - if (!strncasecmp(tlsscheme, curr, strlen(tlsscheme))) { -#ifdef USE_OPENSSL - config.tls = 1; - curr += strlen(tlsscheme); -#else - fprintf(stderr,"rediss:// is only supported when redis-cli is compiled with OpenSSL\n"); - exit(1); -#endif - } else if (!strncasecmp(scheme, curr, strlen(scheme))) { - curr += strlen(scheme); - } else { - fprintf(stderr,"Invalid URI scheme\n"); - exit(1); - } - if (curr == end) return; - - /* Extract user info. */ - if ((userinfo = strchr(curr,'@'))) { - if ((username = strchr(curr, ':')) && username < userinfo) { - config.user = percentDecode(curr, username - curr); - curr = username + 1; - } - - config.auth = percentDecode(curr, userinfo - curr); - curr = userinfo + 1; - } - if (curr == end) return; - - /* Extract host and port. */ - path = strchr(curr, '/'); - if (*curr != '/') { - host = path ? path - 1 : end; - if ((port = strchr(curr, ':'))) { - config.hostport = atoi(port + 1); - host = port - 1; - } - config.hostip = sdsnewlen(curr, host - curr + 1); - } - curr = path ? path + 1 : end; - if (curr == end) return; - - /* Extract database number. */ - config.input_dbnum = atoi(curr); -} - static uint64_t dictSdsHash(const void *key) { return dictGenHashFunction((unsigned char*)key, sdslen((char*)key)); } @@ -802,9 +702,9 @@ static int cliAuth(redisContext *ctx, char *user, char *auth) { /* Send SELECT input_dbnum to the server */ static int cliSelect(void) { redisReply *reply; - if (config.input_dbnum == config.dbnum) return REDIS_OK; + if (config.conn_info.input_dbnum == config.dbnum) return REDIS_OK; - reply = redisCommand(context,"SELECT %d",config.input_dbnum); + reply = redisCommand(context,"SELECT %d",config.conn_info.input_dbnum); if (reply == NULL) { fprintf(stderr, "\nI/O error\n"); return REDIS_ERR; @@ -813,9 +713,9 @@ static int cliSelect(void) { int result = REDIS_OK; if (reply->type == REDIS_REPLY_ERROR) { result = REDIS_ERR; - fprintf(stderr,"SELECT %d failed: %s\n",config.input_dbnum,reply->str); + fprintf(stderr,"SELECT %d failed: %s\n",config.conn_info.input_dbnum,reply->str); } else { - config.dbnum = config.input_dbnum; + config.dbnum = config.conn_info.input_dbnum; cliRefreshPrompt(); } freeReplyObject(reply); @@ -858,7 +758,7 @@ static int cliConnect(int flags) { /* Do not use hostsocket when we got redirected in cluster mode */ if (config.hostsocket == NULL || (config.cluster_mode && config.cluster_reissue_command)) { - context = redisConnect(config.hostip,config.hostport); + context = redisConnect(config.conn_info.hostip,config.conn_info.hostport); } else { context = redisConnectUnix(config.hostsocket); } @@ -880,7 +780,7 @@ static int cliConnect(int flags) { (config.cluster_mode && config.cluster_reissue_command)) { fprintf(stderr, "%s:%d: %s\n", - config.hostip,config.hostport,context->errstr); + config.conn_info.hostip,config.conn_info.hostport,context->errstr); } else { fprintf(stderr,"%s: %s\n", config.hostsocket,context->errstr); @@ -899,7 +799,7 @@ static int cliConnect(int flags) { anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL); /* Do AUTH, select the right DB, switch to RESP3 if needed. */ - if (cliAuth(context, config.user, config.auth) != REDIS_OK) + if (cliAuth(context, config.conn_info.user, config.conn_info.auth) != REDIS_OK) return REDIS_ERR; if (cliSelect() != REDIS_OK) return REDIS_ERR; @@ -1337,12 +1237,12 @@ static int cliReadReply(int output_raw_strings) { slot = atoi(s+1); s = strrchr(p+1,':'); /* MOVED 3999[P]127.0.0.1[S]6381 */ *s = '\0'; - sdsfree(config.hostip); - config.hostip = sdsnew(p+1); - config.hostport = atoi(s+1); + sdsfree(config.conn_info.hostip); + config.conn_info.hostip = sdsnew(p+1); + config.conn_info.hostport = atoi(s+1); if (config.interactive) printf("-> Redirected to slot [%d] located at %s:%d\n", - slot, config.hostip, config.hostport); + slot, config.conn_info.hostip, config.conn_info.hostport); config.cluster_reissue_command = 1; if (!strncmp(reply->str,"ASK ",4)) { config.cluster_send_asking = 1; @@ -1486,7 +1386,7 @@ static int cliSendCommand(int argc, char **argv, long repeat) { if (!strcasecmp(command,"select") && argc == 2 && config.last_cmd_type != REDIS_REPLY_ERROR) { - config.input_dbnum = config.dbnum = atoi(argv[1]); + config.conn_info.input_dbnum = config.dbnum = atoi(argv[1]); cliRefreshPrompt(); } else if (!strcasecmp(command,"auth") && (argc == 2 || argc == 3)) { cliSelect(); @@ -1501,20 +1401,20 @@ static int cliSendCommand(int argc, char **argv, long repeat) { if (config.last_cmd_type == REDIS_REPLY_ERROR || config.last_cmd_type == REDIS_REPLY_NIL) { - config.input_dbnum = config.dbnum = config.pre_multi_dbnum; + config.conn_info.input_dbnum = config.dbnum = config.pre_multi_dbnum; } cliRefreshPrompt(); } else if (!strcasecmp(command,"discard") && argc == 1 && config.last_cmd_type != REDIS_REPLY_ERROR) { config.in_multi = 0; - config.input_dbnum = config.dbnum = config.pre_multi_dbnum; + config.conn_info.input_dbnum = config.dbnum = config.pre_multi_dbnum; cliRefreshPrompt(); } else if (!strcasecmp(command,"reset") && argc == 1 && config.last_cmd_type != REDIS_REPLY_ERROR) { config.in_multi = 0; config.dbnum = 0; - config.input_dbnum = 0; + config.conn_info.input_dbnum = 0; config.resp3 = 0; cliRefreshPrompt(); } @@ -1546,7 +1446,7 @@ static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, .. fflush(stdout); redisFree(c); - c = redisConnect(config.hostip,config.hostport); + c = redisConnect(config.conn_info.hostip,config.conn_info.hostport); if (!c->err && config.tls) { const char *err = NULL; if (cliSecureConnection(c, config.sslconfig, &err) == REDIS_ERR && err) { @@ -1584,8 +1484,8 @@ static int parseOptions(int argc, char **argv) { int lastarg = i==argc-1; if (!strcmp(argv[i],"-h") && !lastarg) { - sdsfree(config.hostip); - config.hostip = sdsnew(argv[++i]); + sdsfree(config.conn_info.hostip); + config.conn_info.hostip = sdsnew(argv[++i]); } else if (!strcmp(argv[i],"-h") && lastarg) { usage(0); } else if (!strcmp(argv[i],"--help")) { @@ -1593,7 +1493,7 @@ static int parseOptions(int argc, char **argv) { } else if (!strcmp(argv[i],"-x")) { config.stdinarg = 1; } else if (!strcmp(argv[i],"-p") && !lastarg) { - config.hostport = atoi(argv[++i]); + config.conn_info.hostport = atoi(argv[++i]); } else if (!strcmp(argv[i],"-s") && !lastarg) { config.hostsocket = argv[++i]; } else if (!strcmp(argv[i],"-r") && !lastarg) { @@ -1602,7 +1502,7 @@ static int parseOptions(int argc, char **argv) { double seconds = atof(argv[++i]); config.interval = seconds*1000000; } else if (!strcmp(argv[i],"-n") && !lastarg) { - config.input_dbnum = atoi(argv[++i]); + config.conn_info.input_dbnum = atoi(argv[++i]); } else if (!strcmp(argv[i], "--no-auth-warning")) { config.no_auth_warning = 1; } else if (!strcmp(argv[i], "--askpass")) { @@ -1610,11 +1510,11 @@ static int parseOptions(int argc, char **argv) { } else if ((!strcmp(argv[i],"-a") || !strcmp(argv[i],"--pass")) && !lastarg) { - config.auth = argv[++i]; + config.conn_info.auth = sdsnew(argv[++i]); } else if (!strcmp(argv[i],"--user") && !lastarg) { - config.user = argv[++i]; + config.conn_info.user = sdsnew(argv[++i]); } else if (!strcmp(argv[i],"-u") && !lastarg) { - parseRedisUri(argv[++i]); + parseRedisUri(argv[++i],"redis-cli",&config.conn_info,&config.tls); } else if (!strcmp(argv[i],"--raw")) { config.output = OUTPUT_RAW; } else if (!strcmp(argv[i],"--no-raw")) { @@ -1852,7 +1752,7 @@ static int parseOptions(int argc, char **argv) { exit(1); } - if (!config.no_auth_warning && config.auth != NULL) { + if (!config.no_auth_warning && config.conn_info.auth != NULL) { fputs("Warning: Using a password with '-a' or '-u' option on the command" " line interface may not be safe.\n", stderr); } @@ -1863,8 +1763,8 @@ static int parseOptions(int argc, char **argv) { static void parseEnv() { /* Set auth from env, but do not overwrite CLI arguments if passed */ char *auth = getenv(REDIS_CLI_AUTH_ENV); - if (auth != NULL && config.auth == NULL) { - config.auth = auth; + if (auth != NULL && config.conn_info.auth == NULL) { + config.conn_info.auth = auth; } char *cluster_yes = getenv(REDIS_CLI_CLUSTER_YES_ENV); @@ -2261,9 +2161,9 @@ static void repl(void) { printf("Use 'restart' only in Lua debugging mode."); } } else if (argc == 3 && !strcasecmp(argv[0],"connect")) { - sdsfree(config.hostip); - config.hostip = sdsnew(argv[1]); - config.hostport = atoi(argv[2]); + sdsfree(config.conn_info.hostip); + config.conn_info.hostip = sdsnew(argv[1]); + config.conn_info.hostport = atoi(argv[2]); cliRefreshPrompt(); cliConnect(CC_FORCE); } else if (argc == 1 && !strcasecmp(argv[0],"clear")) { @@ -2850,13 +2750,13 @@ static int clusterManagerNodeConnect(clusterManagerNode *node) { * commands. At the same time this improves the detection of real * errors. */ anetKeepAlive(NULL, node->context->fd, REDIS_CLI_KEEPALIVE_INTERVAL); - if (config.auth) { + if (config.conn_info.auth) { redisReply *reply; - if (config.user == NULL) - reply = redisCommand(node->context,"AUTH %s", config.auth); + if (config.conn_info.user == NULL) + reply = redisCommand(node->context,"AUTH %s", config.conn_info.auth); else reply = redisCommand(node->context,"AUTH %s %s", - config.user,config.auth); + config.conn_info.user,config.conn_info.auth); int ok = clusterManagerCheckRedisReply(node, reply, NULL); if (reply != NULL) freeReplyObject(reply); if (!ok) return 0; @@ -3691,8 +3591,8 @@ static redisReply *clusterManagerMigrateKeysInReply(clusterManagerNode *source, char **argv = NULL; size_t *argv_len = NULL; int c = (replace ? 8 : 7); - if (config.auth) c += 2; - if (config.user) c += 1; + if (config.conn_info.auth) c += 2; + if (config.conn_info.user) c += 1; size_t argc = c + reply->elements; size_t i, offset = 6; // Keys Offset argv = zcalloc(argc * sizeof(char *)); @@ -3718,23 +3618,23 @@ static redisReply *clusterManagerMigrateKeysInReply(clusterManagerNode *source, argv_len[offset] = 7; offset++; } - if (config.auth) { - if (config.user) { + if (config.conn_info.auth) { + if (config.conn_info.user) { argv[offset] = "AUTH2"; argv_len[offset] = 5; offset++; - argv[offset] = config.user; - argv_len[offset] = strlen(config.user); + argv[offset] = config.conn_info.user; + argv_len[offset] = strlen(config.conn_info.user); offset++; - argv[offset] = config.auth; - argv_len[offset] = strlen(config.auth); + argv[offset] = config.conn_info.auth; + argv_len[offset] = strlen(config.conn_info.auth); offset++; } else { argv[offset] = "AUTH"; argv_len[offset] = 4; offset++; - argv[offset] = config.auth; - argv_len[offset] = strlen(config.auth); + argv[offset] = config.conn_info.auth; + argv_len[offset] = strlen(config.conn_info.auth); offset++; } } @@ -6603,11 +6503,11 @@ static int clusterManagerCommandImport(int argc, char **argv) { } } cmdfmt = sdsnew("MIGRATE %s %d %s %d %d"); - if (config.auth) { - if (config.user) { - cmdfmt = sdscatfmt(cmdfmt," AUTH2 %s %s", config.user, config.auth); + if (config.conn_info.auth) { + if (config.conn_info.user) { + cmdfmt = sdscatfmt(cmdfmt," AUTH2 %s %s", config.conn_info.user, config.conn_info.auth); } else { - cmdfmt = sdscatfmt(cmdfmt," AUTH %s", config.auth); + cmdfmt = sdscatfmt(cmdfmt," AUTH %s", config.conn_info.auth); } } @@ -8288,13 +8188,13 @@ int main(int argc, char **argv) { struct timeval tv; memset(&config.sslconfig, 0, sizeof(config.sslconfig)); - config.hostip = sdsnew("127.0.0.1"); - config.hostport = 6379; + config.conn_info.hostip = sdsnew("127.0.0.1"); + config.conn_info.hostport = 6379; config.hostsocket = NULL; config.repeat = 1; config.interval = 0; config.dbnum = 0; - config.input_dbnum = 0; + config.conn_info.input_dbnum = 0; config.interactive = 0; config.shutdown = 0; config.monitor_mode = 0; @@ -8319,9 +8219,9 @@ int main(int argc, char **argv) { config.bigkeys = 0; config.hotkeys = 0; config.stdinarg = 0; - config.auth = NULL; + config.conn_info.auth = NULL; config.askpass = 0; - config.user = NULL; + config.conn_info.user = NULL; config.eval = NULL; config.eval_ldb = 0; config.eval_ldb_end = 0; @@ -8372,7 +8272,7 @@ int main(int argc, char **argv) { parseEnv(); if (config.askpass) { - config.auth = askPassword("Please input password: "); + config.conn_info.auth = askPassword("Please input password: "); } if (config.cluster_manager_command.from_askpass) { diff --git a/tests/integration/redis-benchmark.tcl b/tests/integration/redis-benchmark.tcl index 9a39c1a5f..6aa4c7882 100644 --- a/tests/integration/redis-benchmark.tcl +++ b/tests/integration/redis-benchmark.tcl @@ -5,34 +5,54 @@ proc cmdstat {cmd} { return [cmdrstat $cmd r] } +# common code to reset stats, flush the db and run redis-benchmark +proc common_bench_setup {cmd} { + r config resetstat + r flushall + if {[catch { exec {*}$cmd } error]} { + set first_line [lindex [split $error "\n"] 0] + puts [colorstr red "redis-benchmark non zero code. first line: $first_line"] + fail "redis-benchmark non zero code. first line: $first_line" + } +} + +# we use this extra asserts on a simple set,get test for features like uri parsing +# and other simple flag related tests +proc default_set_get_checks {} { + assert_match {*calls=10,*} [cmdstat set] + assert_match {*calls=10,*} [cmdstat get] + # assert one of the non benchmarked commands is not present + assert_match {} [cmdstat lrange] +} + start_server {tags {"benchmark network external:skip"}} { start_server {} { set master_host [srv 0 host] set master_port [srv 0 port] test {benchmark: set,get} { - r config resetstat - r flushall set cmd [redisbenchmark $master_host $master_port "-c 5 -n 10 -t set,get"] - if {[catch { exec {*}$cmd } error]} { - set first_line [lindex [split $error "\n"] 0] - puts [colorstr red "redis-benchmark non zero code. first line: $first_line"] - fail "redis-benchmark non zero code. first line: $first_line" - } - assert_match {*calls=10,*} [cmdstat set] - assert_match {*calls=10,*} [cmdstat get] - # assert one of the non benchmarked commands is not present - assert_match {} [cmdstat lrange] + common_bench_setup $cmd + default_set_get_checks + } + + test {benchmark: connecting using URI set,get} { + set cmd [redisbenchmarkuri $master_host $master_port "-c 5 -n 10 -t set,get"] + common_bench_setup $cmd + default_set_get_checks + } + + test {benchmark: connecting using URI with authentication set,get} { + r config set masterauth pass + set cmd [redisbenchmarkuriuserpass $master_host $master_port "default" pass "-c 5 -n 10 -t set,get"] + common_bench_setup $cmd + default_set_get_checks } test {benchmark: full test suite} { - r config resetstat set cmd [redisbenchmark $master_host $master_port "-c 10 -n 100"] - if {[catch { exec {*}$cmd } error]} { - set first_line [lindex [split $error "\n"] 0] - puts [colorstr red "redis-benchmark non zero code. first line: $first_line"] - fail "redis-benchmark non zero code. first line: $first_line" - } + common_bench_setup $cmd + # ping total calls are 2*issued commands per test due to PING_INLINE and PING_MBULK assert_match {*calls=200,*} [cmdstat ping] assert_match {*calls=100,*} [cmdstat set] @@ -55,32 +75,17 @@ start_server {tags {"benchmark network external:skip"}} { } test {benchmark: multi-thread set,get} { - r config resetstat - r flushall set cmd [redisbenchmark $master_host $master_port "--threads 10 -c 5 -n 10 -t set,get"] - if {[catch { exec {*}$cmd } error]} { - set first_line [lindex [split $error "\n"] 0] - puts [colorstr red "redis-benchmark non zero code. first line: $first_line"] - fail "redis-benchmark non zero code. first line: $first_line" - } - assert_match {*calls=10,*} [cmdstat set] - assert_match {*calls=10,*} [cmdstat get] - # assert one of the non benchmarked commands is not present - assert_match {} [cmdstat lrange] + common_bench_setup $cmd + default_set_get_checks # ensure only one key was populated assert_match {1} [scan [regexp -inline {keys\=([\d]*)} [r info keyspace]] keys=%d] } test {benchmark: pipelined full set,get} { - r config resetstat - r flushall set cmd [redisbenchmark $master_host $master_port "-P 5 -c 10 -n 10010 -t set,get"] - if {[catch { exec {*}$cmd } error]} { - set first_line [lindex [split $error "\n"] 0] - puts [colorstr red "redis-benchmark non zero code. first line: $first_line"] - fail "redis-benchmark non zero code. first line: $first_line" - } + common_bench_setup $cmd assert_match {*calls=10010,*} [cmdstat set] assert_match {*calls=10010,*} [cmdstat get] # assert one of the non benchmarked commands is not present @@ -91,14 +96,8 @@ start_server {tags {"benchmark network external:skip"}} { } test {benchmark: arbitrary command} { - r config resetstat - r flushall set cmd [redisbenchmark $master_host $master_port "-c 5 -n 150 INCRBYFLOAT mykey 10.0"] - if {[catch { exec {*}$cmd } error]} { - set first_line [lindex [split $error "\n"] 0] - puts [colorstr red "redis-benchmark non zero code. first line: $first_line"] - fail "redis-benchmark non zero code. first line: $first_line" - } + common_bench_setup $cmd assert_match {*calls=150,*} [cmdstat incrbyfloat] # assert one of the non benchmarked commands is not present assert_match {} [cmdstat get] @@ -108,14 +107,8 @@ start_server {tags {"benchmark network external:skip"}} { } test {benchmark: keyspace length} { - r flushall - r config resetstat set cmd [redisbenchmark $master_host $master_port "-r 50 -t set -n 1000"] - if {[catch { exec {*}$cmd } error]} { - set first_line [lindex [split $error "\n"] 0] - puts [colorstr red "redis-benchmark non zero code. first line: $first_line"] - fail "redis-benchmark non zero code. first line: $first_line" - } + common_bench_setup $cmd assert_match {*calls=1000,*} [cmdstat set] # assert one of the non benchmarked commands is not present assert_match {} [cmdstat get] @@ -127,19 +120,20 @@ start_server {tags {"benchmark network external:skip"}} { # tls specific tests if {$::tls} { test {benchmark: specific tls-ciphers} { - r flushall - r config resetstat set cmd [redisbenchmark $master_host $master_port "-r 50 -t set -n 1000 --tls-ciphers \"DEFAULT:-AES128-SHA256\""] - if {[catch { exec {*}$cmd } error]} { - set first_line [lindex [split $error "\n"] 0] - puts [colorstr red "redis-benchmark non zero code. first line: $first_line"] - fail "redis-benchmark non zero code. first line: $first_line" - } + common_bench_setup $cmd assert_match {*calls=1000,*} [cmdstat set] # assert one of the non benchmarked commands is not present assert_match {} [cmdstat get] } + test {benchmark: tls connecting using URI with authentication set,get} { + r config set masterauth pass + set cmd [redisbenchmarkuriuserpass $master_host $master_port "default" pass "-c 5 -n 10 -t set,get"] + common_bench_setup $cmd + default_set_get_checks + } + test {benchmark: specific tls-ciphersuites} { r flushall r config resetstat diff --git a/tests/support/benchmark.tcl b/tests/support/benchmark.tcl index 3d08b76f5..156b20556 100644 --- a/tests/support/benchmark.tcl +++ b/tests/support/benchmark.tcl @@ -17,3 +17,17 @@ proc redisbenchmark {host port {opts {}}} { lappend cmd {*}$opts return $cmd } + +proc redisbenchmarkuri {host port {opts {}}} { + set cmd [list src/redis-benchmark -u redis://$host:$port] + lappend cmd {*}[redisbenchmark_tls_config "tests"] + lappend cmd {*}$opts + return $cmd +} + +proc redisbenchmarkuriuserpass {host port user pass {opts {}}} { + set cmd [list src/redis-benchmark -u redis://$user:$pass@$host:$port] + lappend cmd {*}[redisbenchmark_tls_config "tests"] + lappend cmd {*}$opts + return $cmd +}