diff --git a/src/adlist.c b/src/adlist.c index 68a5d70f7c4..438e4418860 100644 --- a/src/adlist.c +++ b/src/adlist.c @@ -133,6 +133,22 @@ void listLinkNodeTail(list *list, listNode *node) { list->len++; } +/* like listAddNodeTail, with a pre-existing listNode item */ +list *listAddTail(list *list, listNode *node) +{ + if (list->len == 0) { + list->head = list->tail = node; + node->prev = node->next = NULL; + } else { + node->prev = list->tail; + node->next = NULL; + list->tail->next = node; + list->tail = node; + } + list->len++; + return list; +} + list *listInsertNode(list *list, listNode *old_node, void *value, int after) { listNode *node; diff --git a/src/adlist.h b/src/adlist.h index 712c03fd71e..b4611ef9697 100644 --- a/src/adlist.h +++ b/src/adlist.h @@ -56,6 +56,7 @@ void listReleaseGeneric(void *list); void listEmpty(list *list); list *listAddNodeHead(list *list, void *value); list *listAddNodeTail(list *list, void *value); +list *listAddTail(list *list, listNode *node); list *listInsertNode(list *list, listNode *old_node, void *value, int after); void listDelNode(list *list, listNode *node); listIter *listGetIterator(list *list, int direction); diff --git a/src/aof.c b/src/aof.c index 8a9be94b61a..97347a4c32c 100644 --- a/src/aof.c +++ b/src/aof.c @@ -1640,12 +1640,32 @@ int loadSingleAppendOnlyFile(char *filename) { if (fakeClient->flags & CLIENT_MULTI && fakeClient->cmd->proc != execCommand) { + /* queueMultiCommand requires a pendingCommand, so we create a "fake" one here + * for it to consume */ + pendingCommand *pcmd = zmalloc(sizeof(pendingCommand)); + initPendingCommand(pcmd); + listNode *next_pend = zmalloc(sizeof(listNode)); + listAddTail(fakeClient->pending_cmds, next_pend); + next_pend->value = pcmd; + + pcmd->argc = argc; + pcmd->argv_len = argc; + pcmd->argv = argv; + pcmd->cmd = cmd; + + fakeClient->ready_pending_cmds += 1; + /* Note: we don't have to attempt calling evalGetCommandFlags, * since this is AOF, the checks in processCommand are not made * anyway.*/ queueMultiCommand(fakeClient, cmd->flags); + + /* Since freeClientPendingCommands doesn't get called in this flow to free the queued + * command, we do it manually. */ + freeClientPendingCommands(fakeClient, 1); } else { cmd->proc(fakeClient); + fakeClient->all_argv_len_sum = 0; /* Otherwise no one cleans this up and we reach cleanup with it non-zero */ } /* The fake client should not have a reply */ diff --git a/src/blocked.c b/src/blocked.c index 8d15f9de3de..238a0aacc21 100644 --- a/src/blocked.c +++ b/src/blocked.c @@ -199,8 +199,7 @@ void unblockClient(client *c, int queue_for_reprocessing) { * call reqresAppendResponse here (for clients blocked on key, * unblockClientOnKey is called, which eventually calls processCommand, * which calls reqresAppendResponse) */ - reqresAppendResponse(c); - resetClient(c); + prepareForNextCommand(c); } /* Clear the flags, and put the client in the unblocked list so that diff --git a/src/cluster.c b/src/cluster.c index 486dbee3500..d8865f7a7f5 100644 --- a/src/cluster.c +++ b/src/cluster.c @@ -1113,8 +1113,10 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in robj *firstkey = NULL; int multiple_keys = 0; multiState *ms, _ms; - multiCmd mc; - int i, slot = 0, migrating_slot = 0, importing_slot = 0, missing_keys = 0, + pendingCommand mc; + initPendingCommand(&mc); + pendingCommand *mcp = &mc; + int i, slot = CLUSTER_INVALID_SLOT, migrating_slot = 0, importing_slot = 0, missing_keys = 0, existing_keys = 0; int pubsubshard_included = 0; /* Flag to indicate if a pubsub shard cmd is included. */ @@ -1141,7 +1143,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in * structure if the client is not in MULTI/EXEC state, this way * we have a single codepath below. */ ms = &_ms; - _ms.commands = &mc; + _ms.commands = &mcp; _ms.count = 1; mc.argv = argv; mc.argc = argc; @@ -1153,12 +1155,12 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in for (i = 0; i < ms->count; i++) { struct redisCommand *mcmd; robj **margv; - int margc, numkeys, j; - keyReference *keyindex; + int j; + + pendingCommand *pcmd = ms->commands[i]; - mcmd = ms->commands[i].cmd; - margc = ms->commands[i].argc; - margv = ms->commands[i].argv; + mcmd = pcmd->cmd; + margv = pcmd->argv; /* Only valid for sharded pubsub as regular pubsub can operate on any node and bypasses this layer. */ if (!pubsubshard_included && @@ -1167,20 +1169,22 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in pubsubshard_included = 1; } - getKeysResult result = GETKEYS_RESULT_INIT; - numkeys = getKeysFromCommand(mcmd,margv,margc,&result); - keyindex = result.keys; + for (j = 0; j < pcmd->keys_result.numkeys; j++) { + /* The command has keys and was checked for cross-slot between its keys in preprocessCommand() */ + if (pcmd->slot == CLUSTER_INVALID_SLOT) { + /* Error: multiple keys from different slots. */ + if (error_code) + *error_code = CLUSTER_REDIR_CROSS_SLOT; + return NULL; + } - for (j = 0; j < numkeys; j++) { - robj *thiskey = margv[keyindex[j].pos]; - int thisslot = keyHashSlot((char*)thiskey->ptr, - sdslen(thiskey->ptr)); + robj *thiskey = margv[pcmd->keys_result.keys[j].pos]; if (firstkey == NULL) { /* This is the first key we see. Check what is the slot * and node. */ firstkey = thiskey; - slot = thisslot; + slot = pcmd->slot; n = getNodeBySlot(slot); /* Error: If a slot is not served, we are in "cluster down" @@ -1188,7 +1192,6 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in * not trapped earlier in processCommand(). Report the same * error to the client. */ if (n == NULL) { - getKeysFreeResult(&result); if (error_code) *error_code = CLUSTER_REDIR_DOWN_UNBOUND; return NULL; @@ -1207,15 +1210,6 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in importing_slot = 1; } } else { - /* If it is not the first key/channel, make sure it is exactly - * the same key/channel as the first we saw. */ - if (slot != thisslot) { - /* Error: multiple keys from different slots. */ - getKeysFreeResult(&result); - if (error_code) - *error_code = CLUSTER_REDIR_CROSS_SLOT; - return NULL; - } if (importing_slot && !multiple_keys && !equalStringObjects(firstkey,thiskey)) { /* Flag this request as one with multiple different * keys/channels when the slot is in importing state. */ @@ -1236,7 +1230,6 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in else existing_keys++; } } - getKeysFreeResult(&result); } /* No key at all in command? then we can serve the request diff --git a/src/cluster.h b/src/cluster.h index 18b5bb46558..1d8c159eb49 100644 --- a/src/cluster.h +++ b/src/cluster.h @@ -22,6 +22,7 @@ #define CLUSTER_SLOT_MASK_BITS 14 /* Number of bits used for slot id. */ #define CLUSTER_SLOTS (1<>", " Output SHA and content of all scripts or of a specific script with its SHA.", +"LOOKAHEAD", +" Low-level look-ahead information for all clients.", "MARK-INTERNAL-CLIENT [UNMARK]", " Promote the current connection to an internal connection.", NULL @@ -1088,6 +1090,29 @@ NULL return; } addReply(c,shared.ok); + } else if (!strcasecmp(c->argv[1]->ptr,"lookahead")) { + /* Pause all IO threads to access data of clients safely, and pausing the + * specific IO thread will not repeatedly execute in catClientInfoString. */ + int allpaused = 0; + if (server.io_threads_num > 1 && !server.crashing && + pthread_equal(server.main_thread_id, pthread_self())) + { + allpaused = 1; + pauseAllIOThreads(); + } + + sds info = sdsempty(); + listNode *ln; + listIter li; + listRewind(server.clients,&li); + while ((ln = listNext(&li)) != NULL) { + client *client = listNodeValue(ln); + info = sdscatprintf(info, "id: %lu pcmds: %d\n", client->id, client->ready_pending_cmds); + } + + if (allpaused) resumeAllIOThreads(); + addReplyVerbatim(c,info,strlen(info),"txt"); + sdsfree(info); } else if(!strcasecmp(c->argv[1]->ptr,"mark-internal-client") && c->argc < 4) { if (c->argc == 2) { c->flags |= CLIENT_INTERNAL; diff --git a/src/iothread.c b/src/iothread.c index 27d5339238b..cb0180359b9 100644 --- a/src/iothread.c +++ b/src/iothread.c @@ -466,10 +466,36 @@ int processClientsFromIOThread(IOThread *t) { /* Process the pending command and input buffer. */ if (!c->read_error && c->io_flags & CLIENT_IO_PENDING_COMMAND) { - c->flags |= CLIENT_PENDING_COMMAND; - if (processPendingCommandAndInputBuffer(c) == C_ERR) { - /* If the client is no longer valid, it must be freed safely. */ - continue; + serverAssert(listLength(c->pending_cmds) > 0); + + while (listLength(c->pending_cmds)) { + pendingCommand *curcmd = listFirst(c->pending_cmds)->value; + + /* We populate the old client fields so we don't have to modify all existing logic to work with pendingCommands */ + c->argc = curcmd->argc; + c->argv = curcmd->argv; + c->argv_len = curcmd->argv_len; + c->reploff_next = curcmd->reploff; + c->slot = curcmd->slot; + serverAssert(c->argv); + + /* We are finally ready to execute the command. */ + if (processCommandAndResetClient(c) == C_ERR) { + /* If the client is no longer valid, it must be freed safely. */ + continue; + } + } + serverAssert(listLength(c->pending_cmds) == 0); + + /* Now process client if it has more data in it's buffer. + * + * Note: when a master client steps into this function, + * it can always satisfy this condition, because its querybuf + * contains data not applied. */ + serverAssert(c->ready_pending_cmds == 0); + if (((c->querybuf && sdslen(c->querybuf) > 0))) { + if (processInputBuffer(c) == C_ERR) + continue; } } diff --git a/src/memory_prefetch.c b/src/memory_prefetch.c index 8f3f77ef2d6..1f73c04532b 100644 --- a/src/memory_prefetch.c +++ b/src/memory_prefetch.c @@ -382,18 +382,18 @@ int addCommandToBatch(client *c) { batch->clients[batch->client_count++] = c; - if (likely(c->iolookedcmd)) { + // if (likely(c->iolookedcmd)) { /* Get command's keys positions */ - getKeysResult result = GETKEYS_RESULT_INIT; - int num_keys = getKeysFromCommand(c->iolookedcmd, c->argv, c->argc, &result); - for (int i = 0; i < num_keys && batch->key_count < batch->max_prefetch_size; i++) { - batch->keys[batch->key_count] = c->argv[result.keys[i].pos]; - batch->keys_dicts[batch->key_count] = - kvstoreGetDict(c->db->keys, c->slot > 0 ? c->slot : 0); - batch->key_count++; - } - getKeysFreeResult(&result); - } + // getKeysResult result = GETKEYS_RESULT_INIT; + // int num_keys = getKeysFromCommand(c->iolookedcmd, c->argv, c->argc, &result); + // for (int i = 0; i < num_keys && batch->key_count < batch->max_prefetch_size; i++) { + // batch->keys[batch->key_count] = c->argv[result.keys[i].pos]; + // batch->keys_dicts[batch->key_count] = + // kvstoreGetDict(c->db->keys, c->slot > 0 ? c->slot : 0); + // batch->key_count++; + // } + // getKeysFreeResult(&result); + // } return C_OK; } diff --git a/src/module.c b/src/module.c index ab8cafb191a..d0982ef992f 100644 --- a/src/module.c +++ b/src/module.c @@ -674,11 +674,12 @@ void moduleReleaseTempClient(client *c) { listEmpty(c->reply); c->reply_bytes = 0; c->duration = 0; - resetClient(c); + resetClient(c, -1); + serverAssert(c->all_argv_len_sum == 0); c->bufpos = 0; c->flags = CLIENT_MODULE; c->user = NULL; /* Root user */ - c->cmd = c->lastcmd = c->realcmd = c->iolookedcmd = NULL; + c->cmd = c->lastcmd = c->realcmd = NULL; if (c->bstate.async_rm_call_handle) { RedisModuleAsyncRMCallPromise *promise = c->bstate.async_rm_call_handle; promise->c = NULL; /* Remove the client from the promise so it will no longer be possible to abort it. */ @@ -11034,10 +11035,6 @@ void moduleCallCommandFilters(client *c) { f->callback(&filter); } - /* If the filter sets a new command, including command or subcommand, - * the command looked up in IO threads will be invalid. */ - c->iolookedcmd = NULL; - c->argv = filter.argv; c->argv_len = filter.argv_len; c->argc = filter.argc; diff --git a/src/multi.c b/src/multi.c index 8c5ec6f99e2..d6b8ce367fd 100644 --- a/src/multi.c +++ b/src/multi.c @@ -19,27 +19,19 @@ void initClientMultiState(client *c) { c->mstate.cmd_inv_flags = 0; c->mstate.argv_len_sums = 0; c->mstate.alloc_count = 0; + c->mstate.executing_cmd = -1; } /* Release all the resources associated with MULTI/EXEC state */ void freeClientMultiState(client *c) { - int j; - - for (j = 0; j < c->mstate.count; j++) { - int i; - multiCmd *mc = c->mstate.commands+j; - - for (i = 0; i < mc->argc; i++) - decrRefCount(mc->argv[i]); - zfree(mc->argv); + for (int i = 0; i < c->mstate.count; i++) { + freePendingCommand(c, c->mstate.commands[i]); } zfree(c->mstate.commands); } /* Add a new command into the MULTI commands queue */ void queueMultiCommand(client *c, uint64_t cmd_flags) { - multiCmd *mc; - /* No sense to waste memory if the transaction is already aborted. * this is useful in case client sends these in a pipeline, or doesn't * bother to read previous responses and didn't notice the multi was already @@ -49,29 +41,40 @@ void queueMultiCommand(client *c, uint64_t cmd_flags) { if (c->mstate.count == 0) { /* If a client is using multi/exec, assuming it is used to execute at least * two commands. Hence, creating by default size of 2. */ - c->mstate.commands = zmalloc(sizeof(multiCmd)*2); + c->mstate.commands = zmalloc(sizeof(pendingCommand*)*2); c->mstate.alloc_count = 2; } if (c->mstate.count == c->mstate.alloc_count) { c->mstate.alloc_count = c->mstate.alloc_count < INT_MAX/2 ? c->mstate.alloc_count*2 : INT_MAX; - c->mstate.commands = zrealloc(c->mstate.commands, sizeof(multiCmd)*(c->mstate.alloc_count)); + c->mstate.commands = zrealloc(c->mstate.commands, sizeof(pendingCommand*)*(c->mstate.alloc_count)); } - mc = c->mstate.commands+c->mstate.count; - mc->cmd = c->cmd; - mc->argc = c->argc; - mc->argv = c->argv; - mc->argv_len = c->argv_len; + + /* Move the pending command into the multi-state. + * We leave the empty list node in 'pending_cmds' for freeClientPendingCommands to clean up + * later, but set the value to NULL to indicate it has been moved out and should not be freed. */ + listNode *head = listFirst(c->pending_cmds); + pendingCommand **mc = c->mstate.commands + c->mstate.count; + *mc = head->value; + head->value = NULL; + (*mc)->flags |= PENDING_CMD_FLAG_MULTI; + + /* if it's still blocked on keys, count it so that we know when to unblock */ + if ((*mc)->cmd_io_keys_waiting && dictSize((*mc)->cmd_io_keys_waiting)) + c->mstate.num_commands_with_io_blocked_keys++; c->mstate.count++; c->mstate.cmd_flags |= cmd_flags; c->mstate.cmd_inv_flags |= ~cmd_flags; - c->mstate.argv_len_sums += c->argv_len_sum + sizeof(robj*)*c->argc; + c->mstate.argv_len_sums += (*mc)->argv_len_sum; + c->all_argv_len_sum -= (*mc)->argv_len_sum; + + (*mc)->argv_len_sum = 0; /* This is no longer tracked through all_argv_len_sum, so we don't want */ + /* to subtract it from there later. */ - /* Reset the client's args since we copied them into the mstate and shouldn't - * reference them from c anymore. */ + /* Reset the client's args since we moved them into the mstate and shouldn't + * reference them from 'c' anymore. */ c->argv = NULL; c->argc = 0; - c->argv_len_sum = 0; c->argv_len = 0; } @@ -129,6 +132,7 @@ void execCommand(client *c) { int j; robj **orig_argv; int orig_argc, orig_argv_len; + size_t orig_all_argv_len_sum; struct redisCommand *orig_cmd; if (!(c->flags & CLIENT_MULTI)) { @@ -172,12 +176,19 @@ void execCommand(client *c) { orig_argv_len = c->argv_len; orig_argc = c->argc; orig_cmd = c->cmd; + + /* Multi-state commands aren't tracked through all_argv_len_sum, so we don't want anything done while executing them to affect that field. + * Otherwise, we get inconsistencies and all_argv_len_sum doesn't go back to exactly 0 when the client is finished */ + orig_all_argv_len_sum = c->all_argv_len_sum; + + c->all_argv_len_sum = c->mstate.argv_len_sums; + addReplyArrayLen(c,c->mstate.count); for (j = 0; j < c->mstate.count; j++) { - c->argc = c->mstate.commands[j].argc; - c->argv = c->mstate.commands[j].argv; - c->argv_len = c->mstate.commands[j].argv_len; - c->cmd = c->realcmd = c->mstate.commands[j].cmd; + c->argc = c->mstate.commands[j]->argc; + c->argv = c->mstate.commands[j]->argv; + c->argv_len = c->mstate.commands[j]->argv_len; + c->cmd = c->realcmd = c->mstate.commands[j]->cmd; /* ACL permissions are also checked at the time of execution in case * they were changed after the commands were queued. */ @@ -207,6 +218,7 @@ void execCommand(client *c) { "This command is no longer allowed for the " "following reason: %s", reason); } else { + c->mstate.executing_cmd = j; if (c->id == CLIENT_ID_AOF) call(c,CMD_CALL_NONE); else @@ -216,10 +228,10 @@ void execCommand(client *c) { } /* Commands may alter argc/argv, restore mstate. */ - c->mstate.commands[j].argc = c->argc; - c->mstate.commands[j].argv = c->argv; - c->mstate.commands[j].argv_len = c->argv_len; - c->mstate.commands[j].cmd = c->cmd; + c->mstate.commands[j]->argc = c->argc; + c->mstate.commands[j]->argv = c->argv; + c->mstate.commands[j]->argv_len = c->argv_len; + c->mstate.commands[j]->cmd = c->cmd; } // restore old DENY_BLOCKING value @@ -230,6 +242,7 @@ void execCommand(client *c) { c->argv_len = orig_argv_len; c->argc = orig_argc; c->cmd = c->realcmd = orig_cmd; + c->all_argv_len_sum = orig_all_argv_len_sum; discardTransaction(c); server.in_exec = 0; @@ -485,6 +498,6 @@ size_t multiStateMemOverhead(client *c) { /* Add watched keys overhead, Note: this doesn't take into account the watched keys themselves, because they aren't managed per-client. */ mem += listLength(c->watched_keys) * (sizeof(listNode) + sizeof(watchedKey)); /* Reserved memory for queued multi commands. */ - mem += c->mstate.alloc_count * sizeof(multiCmd); + mem += c->mstate.alloc_count * sizeof(pendingCommand); return mem; } diff --git a/src/networking.c b/src/networking.c index 9f4fec0d71b..bbbbebdd8a1 100644 --- a/src/networking.c +++ b/src/networking.c @@ -160,14 +160,16 @@ client *createClient(connection *conn) { c->querybuf_peak = 0; c->reqtype = 0; c->argc = 0; + c->pending_cmds = listCreate(); + c->ready_pending_cmds = 0; c->argv = NULL; c->argv_len = 0; - c->argv_len_sum = 0; + c->all_argv_len_sum = 0; c->original_argc = 0; c->original_argv = NULL; c->deferred_objects = NULL; c->deferred_objects_num = 0; - c->cmd = c->lastcmd = c->realcmd = c->iolookedcmd = NULL; + c->cmd = c->lastcmd = c->realcmd = NULL; c->cur_script = NULL; c->multibulklen = 0; c->bulklen = -1; @@ -183,6 +185,7 @@ client *createClient(connection *conn) { c->replstate = REPL_STATE_NONE; c->repl_start_cmd_stream_on_ack = 0; c->reploff = 0; + c->reploff_next = 0; c->read_reploff = 0; c->repl_applied = 0; c->repl_ack_off = 0; @@ -1528,8 +1531,6 @@ static inline void freeClientArgvInternal(client *c, int free_argv) { } c->argc = 0; c->cmd = NULL; - c->iolookedcmd = NULL; - c->argv_len_sum = 0; if (free_argv) { c->argv_len = 0; zfree(c->argv); @@ -1541,6 +1542,28 @@ void freeClientArgv(client *c) { freeClientArgvInternal(c, 1); } +void freeClientPendingCommands(client *c, int num_pcmds_to_free) { + /* (-1) means free all pending commands */ + if (num_pcmds_to_free == -1) + num_pcmds_to_free = listLength(c->pending_cmds); + + while (num_pcmds_to_free--) { + listNode *head = listFirst(c->pending_cmds); + if (!head) + break; + listUnlinkNode(c->pending_cmds, head); + pendingCommand *pcmd = head->value; + /* pcmd may be NULL if it has been moved out of the 'pending_cmds' to queue it for a MULTI. + * In that case we still release the list node, but should not try to dereference or free it. */ + if (!pcmd || pcmd->flags & PENDING_CMD_FLAG_PREPROCESSED) { + serverAssert(c->ready_pending_cmds > 0); + c->ready_pending_cmds--; + } + freePendingCommand(c, pcmd); /* It is safe to call freePendingCommand with a NULL pointer */ + zfree(head); + } +} + /* Close all the slaves connections. This is useful in chained replication * when we resync with our own master and want to force all our slaves to * resync with us as well. */ @@ -1640,6 +1663,12 @@ void unlinkClient(client *c) { c->flags &= ~CLIENT_UNBLOCKED; } + freeClientPendingCommands(c, -1); + c->argv_len = 0; + c->argv = NULL; + c->argc = 0; + c->cmd = NULL; + /* Clear the tracking status. */ if (c->flags & CLIENT_TRACKING) disableTracking(c); } @@ -1821,7 +1850,6 @@ void freeClient(client *c) { listRelease(c->reply); zfree(c->buf); freeReplicaReferencedReplBuffer(c); - freeClientArgv(c); freeClientOriginalArgv(c); freeClientDeferredObjects(c, 1); if (c->deferred_reply_errors) @@ -1838,9 +1866,15 @@ void freeClient(client *c) { /* Unlink the client: this will close the socket, remove the I/O * handlers, and remove references of the client from different - * places where active clients may be referenced. */ + * places where active clients may be referenced. + * This will also clean all remaining pending commands in the client, + * as they are no longer valid. + */ unlinkClient(c); + freeClientMultiState(c); + listRelease(c->pending_cmds); + /* Master/slave cleanup Case 1: * we lost the connection with a slave. */ if (c->flags & CLIENT_SLAVE) { @@ -1895,7 +1929,7 @@ void freeClient(client *c) { if (c->name) decrRefCount(c->name); if (c->lib_name) decrRefCount(c->lib_name); if (c->lib_ver) decrRefCount(c->lib_ver); - freeClientMultiState(c); + serverAssert(c->all_argv_len_sum == 0); sdsfree(c->peerid); sdsfree(c->sockname); sdsfree(c->slave_addr); @@ -2282,14 +2316,41 @@ int handleClientsWithPendingWrites(void) { return processed; } -static inline void resetClientInternal(client *c, int free_argv) { - redisCommandProc *prevcmd = c->cmd ? c->cmd->proc : NULL; - - freeClientArgvInternal(c, free_argv); - c->cur_script = NULL; +/* Prepare the client for the parsing of the next command. */ +void resetClientQbufState(client *c) { c->reqtype = 0; c->multibulklen = 0; c->bulklen = -1; +} + +/* This function prepares the client to process the next command. + * num_pcmds_to_free indicates how many pending commands to free to prepare the + * client to execute the following one (applicable when the client uses pending commands). + * If num_pcmds_to_free is -1, all pending commands will be freed, and prepare the client to + * execute new ones when they are read from the query buffer. */ +static inline void resetClientInternal(client *c, int num_pcmds_to_free) { + redisCommandProc *prevcmd = c->cmd ? c->cmd->proc : NULL; + + /* We may get here with no pending commands but with an argv that needs freeing. + * An example is in the case of modules (RM_Call) */ + if (listLength(c->pending_cmds) > 0) { + freeClientPendingCommands(c, num_pcmds_to_free); + if (listLength(c->pending_cmds) == 0) + serverAssert(c->all_argv_len_sum == 0); + } else if (c->argv) { + freeClientArgvInternal(c, 1 /* free_argv */); + /* If we're dealing with a client that doesn't create pendingCommand structs (e.g.: a Lua client), + * clear the all_argv_len_sum counter so we don't get to freeing the client with it non-zero. */ + c->all_argv_len_sum = 0; + } + + c->argc = 0; + c->cmd = NULL; + c->argv_len = 0; + c->argv = NULL; + + c->cur_script = NULL; + c->slot = -1; c->cluster_compatibility_check_slot = -2; c->flags &= ~CLIENT_EXECUTING_COMMAND; @@ -2323,14 +2384,11 @@ static inline void resetClientInternal(client *c, int free_argv) { c->flags |= CLIENT_REPLY_SKIP; c->flags &= ~CLIENT_REPLY_SKIP_NEXT; } - - c->net_input_bytes_curr_cmd = 0; - c->net_output_bytes_curr_cmd = 0; } /* resetClient prepare the client to process the next command */ -void resetClient(client *c) { - resetClientInternal(c, 1); +void resetClient(client *c, int num_pcmds_to_free) { + resetClientInternal(c, num_pcmds_to_free); } /* This function is used when we want to re-enter the event loop but there @@ -2426,22 +2484,28 @@ int processInlineBuffer(client *c) { /* Move querybuffer position to the next query in the buffer. */ c->qb_pos += querylen+linefeed_chars; + pendingCommand *pcmd = zmalloc(sizeof(pendingCommand)); + initPendingCommand(pcmd); + listNode *next_pend = zmalloc(sizeof(listNode)); + listAddTail(c->pending_cmds, next_pend); + next_pend->value = pcmd; + /* Setup argv array on client structure */ if (argc) { - /* Create new argv if space is insufficient. */ - if (unlikely(argc > c->argv_len)) { - zfree(c->argv); - c->argv = zmalloc(sizeof(robj*)*argc); - c->argv_len = argc; + if (unlikely(argc > pcmd->argv_len)) { + zfree(pcmd->argv); + pcmd->argv_len = argc; + pcmd->argv = zmalloc(sizeof(robj*)*pcmd->argv_len); } - c->argv_len_sum = 0; + pcmd->argv_len_sum = 0; } /* Create redis objects for all arguments. */ - for (c->argc = 0, j = 0; j < argc; j++) { - c->argv[c->argc] = createObject(OBJ_STRING,argv[j]); - c->argc++; - c->argv_len_sum += sdslen(argv[j]); + for (pcmd->argc = 0, j = 0; j < argc; j++) { + pcmd->argv[pcmd->argc] = createObject(OBJ_STRING,argv[j]); + pcmd->argc++; + pcmd->argv_len_sum += sdslen(argv[j]); + c->all_argv_len_sum += sdslen(argv[j]); } zfree(argv); @@ -2458,7 +2522,7 @@ int processInlineBuffer(client *c) { * Command) SET key value * Inline) SET key value\r\n */ - c->net_input_bytes_curr_cmd = (c->argv_len_sum + (c->argc - 1) + 2); + c->net_input_bytes_curr_cmd = (c->all_argv_len_sum + (c->argc - 1) + 2); return C_OK; } @@ -2503,32 +2567,34 @@ static void setProtocolError(const char *errstr, client *c) { * The function also returns C_ERR when there is a protocol error: in such a * case the client structure is setup to reply with the error and close * the connection. + * The command_parsed boolean output parameter indicates to the caller if an actual + * command was parsed or not. When the length is found to be <= 0, the function + * returns C_OK, and command_parsed would be 0. * * This function is called if processInputBuffer() detects that the next * command is in RESP format, so the first byte in the command is found * to be '*'. Otherwise for inline commands processInlineBuffer() is called. */ -int processMultibulkBuffer(client *c) { +int processMultibulkBuffer(client *c, int *command_parsed) { char *newline = NULL; int ok; long long ll; size_t querybuf_len = sdslen(c->querybuf); /* Cache sdslen */ + pendingCommand *pcmd = NULL; + *command_parsed = 0; if (c->multibulklen == 0) { - /* The client should have been reset */ - serverAssertWithInfo(c,NULL,c->argc == 0); - /* Multi bulk length cannot be read without a \r\n */ newline = strchr(c->querybuf+c->qb_pos,'\r'); if (newline == NULL) { if (querybuf_len-c->qb_pos > PROTO_INLINE_MAX_SIZE) { c->read_error = CLIENT_READ_TOO_BIG_MBULK_COUNT_STRING; } - return C_ERR; + goto parse_err; } /* Buffer should also contain \n */ if (newline-(c->querybuf+c->qb_pos) > (ssize_t)(querybuf_len-c->qb_pos-2)) - return C_ERR; + goto parse_err; /* We know for sure there is a whole line since newline != NULL, * so go ahead and find out the multi bulk length. */ @@ -2537,10 +2603,10 @@ int processMultibulkBuffer(client *c) { ok = string2ll(c->querybuf+1+c->qb_pos,newline-(c->querybuf+1+c->qb_pos),&ll); if (!ok || ll > INT_MAX) { c->read_error = CLIENT_READ_INVALID_MULTIBUCK_LENGTH; - return C_ERR; + goto parse_err; } else if (ll > 10 && authRequired(c)) { c->read_error = CLIENT_READ_UNAUTH_MBUCK_COUNT; - return C_ERR; + goto parse_err; } c->qb_pos = (newline-c->querybuf)+2; @@ -2549,18 +2615,21 @@ int processMultibulkBuffer(client *c) { c->multibulklen = ll; + /* Starting parsing of a new command (of length > 0) */ + pcmd = zmalloc(sizeof(pendingCommand)); + initPendingCommand(pcmd); + /* Setup argv array on client structure. * Create new argv in the following cases: * 1) When the requested size is greater than the current size. * 2) When the requested size is less than the current size, because * we always allocate argv gradually with a maximum size of 1024, * Therefore, if argv_len exceeds this limit, we always reallocate. */ - if (unlikely(c->multibulklen > c->argv_len || c->argv_len > 1024)) { - zfree(c->argv); - c->argv_len = min(c->multibulklen, 1024); - c->argv = zmalloc(sizeof(robj*)*c->argv_len); + if (unlikely(c->multibulklen > pcmd->argv_len || pcmd->argv_len > 1024)) { + pcmd->argv_len = min(c->multibulklen, 1024); + pcmd->argv = zmalloc(sizeof(robj*)*pcmd->argv_len); + pcmd->argv_len_sum = 0; } - c->argv_len_sum = 0; /* Per-slot network bytes-in calculation. * @@ -2594,6 +2663,12 @@ int processMultibulkBuffer(client *c) { * The 1st component is calculated within the below line. * */ c->net_input_bytes_curr_cmd += (multibulklen_slen + 3); + } else { + /* Continuing parsing from previous call; partially-parsed command is the tail of c->pending_cmds */ + listNode *tail = listLast(c->pending_cmds); + pcmd = tail->value; + listUnlinkNode(c->pending_cmds, tail); + zfree(tail); } serverAssertWithInfo(c,NULL,c->multibulklen > 0); @@ -2604,7 +2679,7 @@ int processMultibulkBuffer(client *c) { if (newline == NULL) { if (querybuf_len-c->qb_pos > PROTO_INLINE_MAX_SIZE) { c->read_error = CLIENT_READ_TOO_BIG_BUCK_COUNT_STRING; - return C_ERR; + goto parse_err; } break; } @@ -2615,7 +2690,7 @@ int processMultibulkBuffer(client *c) { if (c->querybuf[c->qb_pos] != '$') { c->read_error = CLIENT_READ_EXPECTED_DOLLAR; - return C_ERR; + goto parse_err; } size_t bulklen_slen = newline - (c->querybuf + c->qb_pos + 1); @@ -2623,10 +2698,10 @@ int processMultibulkBuffer(client *c) { if (!ok || ll < 0 || (!(c->flags & CLIENT_MASTER) && ll > server.proto_max_bulk_len)) { c->read_error = CLIENT_READ_INVALID_BUCK_LENGTH; - return C_ERR; + goto parse_err; } else if (ll > 16384 && authRequired(c)) { c->read_error = CLIENT_READ_UNAUTH_BUCK_LENGTH; - return C_ERR; + goto parse_err; } c->qb_pos = newline-c->querybuf+2; @@ -2667,10 +2742,10 @@ int processMultibulkBuffer(client *c) { break; } else { /* Check if we have space in argv, grow if needed */ - if (c->argc >= c->argv_len) { - serverAssert(c->argv_len); /* Ensure argv is not freed while the client is in the mid of parsing command. */ - c->argv_len = min(c->argv_len < INT_MAX/2 ? c->argv_len*2 : INT_MAX, c->argc+c->multibulklen); - c->argv = zrealloc(c->argv, sizeof(robj*)*c->argv_len); + if (pcmd->argc >= pcmd->argv_len) { + serverAssert(pcmd->argv_len); + pcmd->argv_len = min(pcmd->argv_len < INT_MAX/2 ? pcmd->argv_len*2 : INT_MAX, pcmd->argc+c->multibulklen); + pcmd->argv = zrealloc(pcmd->argv, sizeof(robj*)*pcmd->argv_len); } /* Optimization: if a non-master client's buffer contains JUST our bulk element @@ -2681,8 +2756,10 @@ int processMultibulkBuffer(client *c) { c->bulklen >= PROTO_MBULK_BIG_ARG && querybuf_len == (size_t)(c->bulklen+2)) { - c->argv[c->argc++] = createObject(OBJ_STRING,c->querybuf); - c->argv_len_sum += c->bulklen; + pcmd->argv[pcmd->argc++] = createObject(OBJ_STRING,c->querybuf); + + pcmd->argv_len_sum += c->bulklen; + c->all_argv_len_sum += c->bulklen; sdsIncrLen(c->querybuf,-2); /* remove CRLF */ /* Assume that if we saw a fat argument we'll see another one likely... * But only if that fat argument is not too big compared to the memory limit. */ @@ -2694,9 +2771,10 @@ int processMultibulkBuffer(client *c) { sdsclear(c->querybuf); querybuf_len = sdslen(c->querybuf); /* Update cached length */ } else { - c->argv[c->argc++] = + pcmd->argv[pcmd->argc++] = createStringObject(c->querybuf+c->qb_pos,c->bulklen); - c->argv_len_sum += c->bulklen; + pcmd->argv_len_sum += c->bulklen; + c->all_argv_len_sum += c->bulklen; c->qb_pos += c->bulklen+2; } c->bulklen = -1; @@ -2704,15 +2782,35 @@ int processMultibulkBuffer(client *c) { } } + listNode *next_pend = zmalloc(sizeof(listNode)); + listAddTail(c->pending_cmds, next_pend); + next_pend->value = pcmd; + /* We're done when c->multibulk == 0 */ if (c->multibulklen == 0) { /* Per-slot network bytes-in calculation, 3rd and 4th components. */ - c->net_input_bytes_curr_cmd += (c->argv_len_sum + (c->argc * 2)); + c->net_input_bytes_curr_cmd += (c->all_argv_len_sum + (c->argc * 2)); + *command_parsed = 1; return C_OK; } /* Still not ready to process the command */ return C_ERR; + +parse_err: + if (pcmd) freePendingCommand(c, pcmd); + return C_ERR; +} + +/* Prepare the client for executing the next command: + * + * 1. Append the response, if necessary. + * 2. Reset the client. + * 3. Update the all_argv_len_sum counter and advance the pending_cmd cyclic buffer. + */ +void prepareForNextCommand(client *c) { + reqresAppendResponse(c); + resetClientInternal(c, 1); } /* Perform necessary tasks after a command was executed: @@ -2730,14 +2828,14 @@ void commandProcessed(client *c) { * since we have not applied the command. */ if (c->flags & CLIENT_BLOCKED) return; - reqresAppendResponse(c); + prepareForNextCommand(c); clusterSlotStatsAddNetworkBytesInForUserClient(c); - resetClientInternal(c, 0); long long prev_offset = c->reploff; if (c->flags & CLIENT_MASTER && !(c->flags & CLIENT_MULTI)) { /* Update the applied replication offset of our master. */ - c->reploff = c->read_reploff - sdslen(c->querybuf) + c->qb_pos; + serverAssert(c->reploff_next > 0); + c->reploff = c->reploff_next; } /* If the client is a master we need to compute the difference @@ -2810,7 +2908,7 @@ int processPendingCommandAndInputBuffer(client *c) { * Note: when a master client steps into this function, * it can always satisfy this condition, because its querybuf * contains data not applied. */ - if (c->querybuf && sdslen(c->querybuf) > 0) { + if (((c->querybuf && sdslen(c->querybuf) > 0)) || c->ready_pending_cmds) { return processInputBuffer(c); } return C_OK; @@ -2891,7 +2989,7 @@ void handleClientReadError(client *c) { * return C_ERR in case the client was freed during the processing */ int processInputBuffer(client *c) { /* Keep processing while there is something in the input buffer */ - while(c->qb_pos < sdslen(c->querybuf)) { + while((c->querybuf && c->qb_pos < sdslen(c->querybuf)) || c->ready_pending_cmds) { /* Immediately abort if the client is in the middle of something. */ if (c->flags & CLIENT_BLOCKED) break; @@ -2912,54 +3010,93 @@ int processInputBuffer(client *c) { * The same applies for clients we want to terminate ASAP. */ if (c->flags & (CLIENT_CLOSE_AFTER_REPLY|CLIENT_CLOSE_ASAP)) break; - /* Determine request type when unknown. */ - if (!c->reqtype) { - if (c->querybuf[c->qb_pos] == '*') { - c->reqtype = PROTO_REQ_MULTIBULK; - } else { - c->reqtype = PROTO_REQ_INLINE; + int pending_cmd_before_reading = c->ready_pending_cmds; + + /* We limit the lookahead for unauthenticated connections to 1. + * This is both to reduce memory overhead, and to prevent errors: AUTH can + * affect the handling of succeeding commands. Parsing of "large" + * unauthenticated multibulk commands is rejected, which would cause those + * commands to incorrectly return an error to the client. */ + // TODO: + const int lookahead = authRequired(c) ? 1 : 16; + + /* Parse up to lookahead commands */ + while (c->ready_pending_cmds < lookahead && c->querybuf && c->qb_pos < sdslen(c->querybuf)) { + int command_parsed = 1; + /* Determine request type when unknown. */ + if (!c->reqtype) { + if (c->querybuf[c->qb_pos] == '*') { + c->reqtype = PROTO_REQ_MULTIBULK; + } else { + c->reqtype = PROTO_REQ_INLINE; + } } - } - if (c->reqtype == PROTO_REQ_INLINE) { - if (processInlineBuffer(c) != C_OK) { - if (c->running_tid != IOTHREAD_MAIN_THREAD_ID && c->read_error) - enqueuePendingClientsToMainThread(c, 0); - break; + if (c->reqtype == PROTO_REQ_INLINE) { + if (processInlineBuffer(c) != C_OK) { + if (c->running_tid != IOTHREAD_MAIN_THREAD_ID && c->read_error) + enqueuePendingClientsToMainThread(c, 0); + break; + } + } else if (c->reqtype == PROTO_REQ_MULTIBULK) { + if (processMultibulkBuffer(c, &command_parsed) != C_OK) { + if (c->running_tid != IOTHREAD_MAIN_THREAD_ID && c->read_error) + enqueuePendingClientsToMainThread(c, 0); + break; + } + } else { + serverPanic("Unknown request type"); } - } else if (c->reqtype == PROTO_REQ_MULTIBULK) { - if (processMultibulkBuffer(c) != C_OK) { - if (c->running_tid != IOTHREAD_MAIN_THREAD_ID && c->read_error) - enqueuePendingClientsToMainThread(c, 0); - break; + + /* Multibulk processing could see a <= 0 length. */ + if (command_parsed) { + pendingCommand *pcmd = listLast(c->pending_cmds)->value; + pcmd->reploff = c->read_reploff - sdslen(c->querybuf) + c->qb_pos; + preprocessCommand(c, pcmd); + pcmd->flags |= PENDING_CMD_FLAG_PREPROCESSED; + c->ready_pending_cmds++; } - } else { - serverPanic("Unknown request type"); + resetClientQbufState(c); } - /* Multibulk processing could see a <= 0 length. */ - if (c->argc == 0) { - freeClientArgvInternal(c, 0); - c->reqtype = 0; - c->multibulklen = 0; - c->bulklen = -1; - } else { - /* If we are in the context of an I/O thread, we can't really - * execute the command here. All we can do is to flag the client - * as one that needs to process the command. */ - if (c->running_tid != IOTHREAD_MAIN_THREAD_ID) { - c->io_flags |= CLIENT_IO_PENDING_COMMAND; - c->iolookedcmd = lookupCommand(c->argv, c->argc); - if (c->iolookedcmd && !commandCheckArity(c->iolookedcmd, c->argc, NULL)) { - /* The command was found, but the arity is invalid, reset it and let main - * thread handle. To avoid memory prefetching on an invalid command. */ - c->iolookedcmd = NULL; - } - c->slot = getSlotFromCommand(c->iolookedcmd, c->argv, c->argc); - enqueuePendingClientsToMainThread(c, 0); - break; + if (c->ready_pending_cmds != pending_cmd_before_reading) { + /* compute stat of average pipeline length */ + server.stat_avg_pipeline_length_sum += c->ready_pending_cmds; + server.stat_avg_pipeline_length_cnt++; + } + + if (!c->ready_pending_cmds) + break; + pendingCommand *curcmd = listFirst(c->pending_cmds)->value; + + /* We populate the old client fields so we don't have to modify all existing logic to work with pendingCommands */ + c->argc = curcmd->argc; + c->argv = curcmd->argv; + c->argv_len = curcmd->argv_len; + c->reploff_next = curcmd->reploff; + c->slot = curcmd->slot; + + /* If we are in the context of an I/O thread, we can't really + * execute the command here. All we can do is to flag the client + * as one that needs to process the command. */ + if (c->running_tid != IOTHREAD_MAIN_THREAD_ID) { + if (!c->argc) { + /* A naked newline can be sent from masters as a keep-alive, or from slaves to refresh + * the last ACK time. In that case there's no command to actually execute. */ + prepareForNextCommand(c); + continue; } + c->io_flags |= CLIENT_IO_PENDING_COMMAND; + serverAssert(listLength(c->pending_cmds) > 0); + enqueuePendingClientsToMainThread(c, 0); + break; + } + if (!c->argc) { + /* A naked newline can be sent from masters as a keep-alive, or from slaves to refresh + * the last ACK time. In that case there's no command to actually execute. */ + prepareForNextCommand(c); + } else { /* We are finally ready to execute the command. */ if (processCommandAndResetClient(c) == C_ERR) { /* If the client is no longer valid, we avoid exiting this @@ -2985,6 +3122,7 @@ int processInputBuffer(client *c) { * so the repl_applied is not equal to qb_pos. */ if (c->repl_applied) { sdsrange(c->querybuf,c->repl_applied,-1); + serverAssert(c->qb_pos >= (size_t)c->repl_applied); c->qb_pos -= c->repl_applied; c->repl_applied = 0; } @@ -3010,6 +3148,8 @@ void readQueryFromClient(connection *conn) { if (!(c->io_flags & CLIENT_IO_READ_ENABLED)) return; c->read_error = 0; + server.stat_total_client_read_events++; + /* Update the number of reads of io threads on server */ atomicIncr(server.stat_io_reads_processed[c->running_tid], 1); @@ -3272,7 +3412,7 @@ sds catClientInfoString(sds s, client *client) { " watch=%i", (int) listLength(client->watched_keys), " qbuf=%U", client->querybuf ? (unsigned long long) sdslen(client->querybuf) : 0, " qbuf-free=%U", client->querybuf ? (unsigned long long) sdsavail(client->querybuf) : 0, - " argv-mem=%U", (unsigned long long) client->argv_len_sum, + " argv-mem=%U", (unsigned long long) client->all_argv_len_sum, " multi-mem=%U", (unsigned long long) client->mstate.argv_len_sums, " rbs=%U", (unsigned long long) client->buf_usable_size, " rbp=%U", (unsigned long long) client->buf_peak, @@ -4181,14 +4321,49 @@ void rewriteClientCommandVector(client *c, int argc, ...) { void replaceClientCommandVector(client *c, int argc, robj **argv) { int j; retainOriginalCommandVector(c); + + /* We don't need to just fix the client argv, we also need to fix the pending command (same argv), + * But sometimes we reach here not from a real client, but from a Lua 'scriptRunCtx'. This flow bypasses the + * pending-command system entirely and uses c->argv directly. In this case there's no pending commands + * to update, so we skip that code. */ + pendingCommand *pcmd = NULL; + int is_mstate = 0; + if (c->mstate.executing_cmd < 0) { + is_mstate = 0; + if (listLength(c->pending_cmds) > 0) + pcmd = listFirst(c->pending_cmds)->value; + } else { + is_mstate = 1; + serverAssert(c->mstate.executing_cmd < c->mstate.count); + pcmd = c->mstate.commands[c->mstate.executing_cmd]; + } + + if (pcmd) { + serverAssert(pcmd->argv == c->argv); + pcmd->argv = argv; + pcmd->argc = argc; + } freeClientArgv(c); c->argv = argv; c->argc = c->argv_len = argc; - c->argv_len_sum = 0; - for (j = 0; j < c->argc; j++) - if (c->argv[j]) - c->argv_len_sum += getStringObjectLen(c->argv[j]); + + if (!is_mstate) { /* multi-state does not track all_argv_len_sum, see code in queueMultiCommand */ + size_t new_argv_len_sum = 0; + for (j = 0; j < c->argc; j++) + if (c->argv[j]) + new_argv_len_sum += getStringObjectLen(c->argv[j]); + + if (!pcmd) { + c->all_argv_len_sum = new_argv_len_sum; + } else { + c->all_argv_len_sum -= pcmd->argv_len_sum; + pcmd->argv_len_sum = new_argv_len_sum; + c->all_argv_len_sum += pcmd->argv_len_sum; + } + } c->cmd = lookupCommandOrOriginal(c->argv,c->argc); + if (pcmd) + pcmd->cmd = c->cmd; serverAssertWithInfo(c,NULL,c->cmd != NULL); } @@ -4209,6 +4384,13 @@ void rewriteClientCommandArgument(client *c, int i, robj *newval) { robj *oldval; retainOriginalCommandVector(c); + /* We don't need to just fix the client argv, we also need to fix the pending command (same argv), + * But sometimes we reach here not from a real client, but from a Lua 'scriptRunCtx'. This flow bypasses the + * pending-command system entirely and uses c->argv directly. In this case there's no pending commands + * to update, so we skip that code. */ + pendingCommand *pcmd = listFirst(c->pending_cmds) ? listFirst(c->pending_cmds)->value : NULL; + int update_pcmd = pcmd && pcmd->argv == c->argv; + /* We need to handle both extending beyond argc (just update it and * initialize the new element) or beyond argv_len (realloc is needed). */ @@ -4221,12 +4403,12 @@ void rewriteClientCommandArgument(client *c, int i, robj *newval) { c->argv[i] = NULL; } oldval = c->argv[i]; - if (oldval) c->argv_len_sum -= getStringObjectLen(oldval); + if (oldval) c->all_argv_len_sum -= getStringObjectLen(oldval); if (newval) { c->argv[i] = newval; incrRefCount(newval); - c->argv_len_sum += getStringObjectLen(newval); + c->all_argv_len_sum += getStringObjectLen(newval); } else { /* move the remaining arguments one step left */ for (int j = i+1; j < c->argc; j++) { @@ -4236,10 +4418,20 @@ void rewriteClientCommandArgument(client *c, int i, robj *newval) { } if (oldval) decrRefCount(oldval); + if (update_pcmd) { + pcmd->argv = c->argv; + pcmd->argc = c->argc; + pcmd->argv_len = c->argv_len; + if (oldval) pcmd->argv_len_sum -= getStringObjectLen(oldval); + if (newval) pcmd->argv_len_sum += getStringObjectLen(newval); + } + /* If this is the command name make sure to fix c->cmd. */ if (i == 0) { c->cmd = lookupCommandOrOriginal(c->argv,c->argc); serverAssertWithInfo(c,NULL,c->cmd != NULL); + if (update_pcmd) + pcmd->cmd = c->cmd; } } @@ -4281,7 +4473,7 @@ size_t getClientMemoryUsage(client *c, size_t *output_buffer_mem_usage) { /* For efficiency (less work keeping track of the argv memory), it doesn't include the used memory * i.e. unused sds space and internal fragmentation, just the string length. but this is enough to * spot problematic clients. */ - mem += c->argv_len_sum + sizeof(robj*)*c->argc; + mem += c->all_argv_len_sum + sizeof(robj*)*c->argc; mem += multiStateMemOverhead(c); /* Add memory overhead of pubsub channels and patterns. Note: this is just the overhead of the robj pointers @@ -4715,3 +4907,30 @@ void evictClients(void) { } } } + +void initPendingCommand(pendingCommand *pcmd) { + memset(pcmd, 0, sizeof(pendingCommand)); + pcmd->keys_result = (getKeysResult)GETKEYS_RESULT_INIT; + pcmd->slot = CLUSTER_INVALID_SLOT; +} + +void freePendingCommand(client *c, pendingCommand *pcmd) { + if (!pcmd) + return; + + getKeysFreeResult(&pcmd->keys_result); + + if (pcmd->argv) { + for (int j = 0; j < pcmd->argc; j++) + decrRefCount(pcmd->argv[j]); + + if (pcmd->cmd_io_keys_waiting) dictRelease(pcmd->cmd_io_keys_waiting); + if (pcmd->cmd_io_keys_needed) dictRelease(pcmd->cmd_io_keys_needed); + + zfree(pcmd->argv); + serverAssert(c->all_argv_len_sum >= pcmd->argv_len_sum); /* assert this doesn't try to go negative */ + c->all_argv_len_sum -= pcmd->argv_len_sum; + } + + zfree(pcmd); +} \ No newline at end of file diff --git a/src/replication.c b/src/replication.c index 32921f19653..28bb27a2ddb 100644 --- a/src/replication.c +++ b/src/replication.c @@ -4199,12 +4199,14 @@ void replicationCacheMaster(client *c) { server.master->qb_pos = 0; server.master->repl_applied = 0; server.master->read_reploff = server.master->reploff; + server.master->reploff_next = 0; if (c->flags & CLIENT_MULTI) discardTransaction(c); listEmpty(c->reply); c->sentlen = 0; c->reply_bytes = 0; c->bufpos = 0; - resetClient(c); + resetClient(c, -1); + resetClientQbufState(c); /* Save the master. Server.master will be set to null later by * replicationHandleMasterDisconnection(). */ diff --git a/src/script_lua.c b/src/script_lua.c index 2e8220743c3..e9ec19fc8b8 100644 --- a/src/script_lua.c +++ b/src/script_lua.c @@ -974,7 +974,7 @@ static int luaRedisGenericCommand(lua_State *lua, int raise_error) { c->argc = c->argv_len = 0; c->user = NULL; c->argv = NULL; - resetClient(c); + resetClient(c, 1); inuse--; if (raise_error) { diff --git a/src/server.c b/src/server.c index 41607356db6..ba7b5c42acf 100644 --- a/src/server.c +++ b/src/server.c @@ -79,6 +79,10 @@ double R_Zero, R_PosInf, R_NegInf, R_Nan; /* Global vars */ struct redisServer server; /* Server global state */ +/* Value of the server.stat_total_client_read_events counter before processing events. + * See beforeSleep() for details about its use. */ +size_t stat_prev_total_client_read_events = 0; + /*============================ Internal prototypes ========================== */ static inline int isShutdownInitiated(void); @@ -958,7 +962,7 @@ int CurrentPeakMemUsageSlot = 0; int clientsCronTrackExpansiveClients(client *c) { size_t qb_size = c->querybuf ? sdsZmallocSize(c->querybuf) : 0; size_t argv_size = c->argv ? zmalloc_size(c->argv) : 0; - size_t in_usage = qb_size + c->argv_len_sum + argv_size; + size_t in_usage = qb_size + c->all_argv_len_sum + argv_size; size_t out_usage = getClientOutputBufferMemoryUsage(c); /* Track the biggest values observed so far in this slot. */ @@ -1952,6 +1956,11 @@ void beforeSleep(struct aeEventLoop *eventLoop) { * connection has pending data) */ aeSetDontWait(server.el, dont_sleep); + /* When stat_total_client_read_events changes (from afterSleep()), it means we have served clients + * in this event loop cycle. */ + if (stat_prev_total_client_read_events != server.stat_total_client_read_events) + server.stat_eventloop_cycles_with_clients++; + /* Before we are going to sleep, let the threads access the dataset by * releasing the GIL. Redis main thread will not touch anything at this * time. */ @@ -1988,6 +1997,9 @@ void afterSleep(struct aeEventLoop *eventLoop) { server.el_start = getMonotonicUs(); /* Set the eventloop command count at start. */ server.el_cmd_cnt_start = server.stat_numcommands; + + /* Record the counter before processing events. See beforeSleep() for details about its use. */ + stat_prev_total_client_read_events = server.stat_total_client_read_events; } /* Set running after waking up */ @@ -2722,6 +2734,8 @@ void resetServerStats(void) { server.stat_sync_full = 0; server.stat_sync_partial_ok = 0; server.stat_sync_partial_err = 0; + server.stat_avg_pipeline_length_sum = 0; + server.stat_avg_pipeline_length_cnt = 0; for (j = 0; j < IO_THREADS_MAX_NUM; j++) { atomicSet(server.stat_io_reads_processed[j], 0); atomicSet(server.stat_io_writes_processed[j], 0); @@ -2751,6 +2765,8 @@ void resetServerStats(void) { server.stat_cluster_incompatible_ops = 0; server.stat_total_prefetch_batches = 0; server.stat_total_prefetch_entries = 0; + server.stat_total_client_read_events = 0; + server.stat_eventloop_cycles_with_clients = 0; memset(server.duration_stats, 0, sizeof(durationStats) * EL_DURATION_TYPE_NUM); server.el_cmd_cnt_max = 0; lazyfreeResetStats(); @@ -4059,6 +4075,61 @@ uint64_t getCommandFlags(client *c) { return cmd_flags; } +/* We need to get the list of keys for the command, order prefetch and remember the status of each key in each command + * so that when prefetch is done, we know which command is ready to be executed, and when we can unprotect a key from eviction. + * Each key has a list of commands waiting for it, and each command has a reference to the client that executed it. + * Clean a pending command from the queue only after executing it. + */ +void preprocessCommand(client *c, pendingCommand* pcmd) { + pcmd->client = c; + pcmd->slot = CLUSTER_INVALID_SLOT; + if (pcmd->argc == 0) + return; + + /* Check if we can reuse the last command instead of looking it up. + * The last command is either the penultimate pending command (if it exists), or c->lastcmd. */ + struct redisCommand *last_cmd = NULL; + if (listLength(c->pending_cmds) > 1) + last_cmd = ((pendingCommand *)(listPrevNode(listLast(c->pending_cmds))->value))->cmd; + else + last_cmd = c->lastcmd; + + if (isCommandReusable(last_cmd, pcmd->argv[0])) + pcmd->cmd = last_cmd; + else + pcmd->cmd = lookupCommand(pcmd->argv, pcmd->argc); + + if (!pcmd->cmd) + return; + if ((pcmd->cmd->arity > 0 && pcmd->cmd->arity != pcmd->argc) || + (pcmd->argc < -pcmd->cmd->arity)) + return; + + pcmd->keys_result = (getKeysResult)GETKEYS_RESULT_INIT; + int num_keys = getKeysFromCommandWithSpecs(pcmd->cmd, pcmd->argv, pcmd->argc, GET_KEYSPEC_DEFAULT, &pcmd->keys_result); + if (num_keys < 0) + /* We skip the checks below since We expect the command to be rejected in this case */ + return; + + if (server.cluster_enabled) { + robj **margv = pcmd->argv; + for (int j = 0; j < pcmd->keys_result.numkeys; j++) { + robj *thiskey = margv[pcmd->keys_result.keys[j].pos]; + int thisslot = (int)keyHashSlot((char*)thiskey->ptr, sdslen(thiskey->ptr)); + + if (pcmd->slot == CLUSTER_INVALID_SLOT) + pcmd->slot = thisslot; + else if (pcmd->slot != thisslot) { + serverLog(LL_NOTICE, "preprocessCommand: CROSS SLOT ERROR"); + /* Invalidate the slot to indicate that there is a cross-slot error */ + pcmd->slot = CLUSTER_INVALID_SLOT; + /* Cross slot error. */ + return; + } + } + } +} + /* If this function gets called we already read a whole * command, arguments are in the client argv/argc fields. * processCommand() execute the command or prepare the @@ -4098,17 +4169,20 @@ int processCommand(client *c) { /* Now lookup the command and check ASAP about trivial error conditions * such as wrong arity, bad command name and so forth. + * When not reprocessing a command, we may skip the command lookup itself, as it was already performed + * in preprocessCommand(). * In case we are reprocessing a command after it was blocked, * we do not have to repeat the same checks */ if (!client_reprocessing_command) { /* check if we can reuse the last command instead of looking up if we already have that info */ struct redisCommand *cmd = NULL; - if (isCommandReusable(c->lastcmd, c->argv[0])) - cmd = c->lastcmd; - else - cmd = c->iolookedcmd ? c->iolookedcmd : lookupCommand(c->argv, c->argc); + serverAssert(listLength(c->pending_cmds) > 0); + pendingCommand *pcmd = listFirst(c->pending_cmds)->value; + cmd = pcmd->cmd; + if (!cmd) { /* Handle possible security attacks. */ + serverAssert(c->argv); if (!strcasecmp(c->argv[0]->ptr,"host:") || !strcasecmp(c->argv[0]->ptr,"post")) { securityWarningCommand(c); return C_ERR; @@ -4439,9 +4513,9 @@ int areCommandKeysInSameSlot(client *c, int *hashslot) { /* If client is in multi-exec, we need to check the slot of all keys * in the transaction. */ for (int i = 0; i < (ms ? ms->count : 1); i++) { - struct redisCommand *cmd = ms ? ms->commands[i].cmd : c->cmd; - robj **argv = ms ? ms->commands[i].argv : c->argv; - int argc = ms ? ms->commands[i].argc : c->argc; + struct redisCommand *cmd = ms ? ms->commands[i]->cmd : c->cmd; + robj **argv = ms ? ms->commands[i]->argv : c->argv; + int argc = ms ? ms->commands[i]->argc : c->argc; getKeysResult result = GETKEYS_RESULT_INIT; int numkeys = getKeysFromCommand(cmd, argv, argc, &result); @@ -6199,6 +6273,9 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) { "evicted_scripts:%lld\r\n", server.stat_evictedscripts, "total_eviction_exceeded_time:%lld\r\n", (server.stat_total_eviction_exceeded_time + current_eviction_exceeded_time) / 1000, "current_eviction_exceeded_time:%lld\r\n", current_eviction_exceeded_time / 1000, + "avg_pipeline_length_sum:%lld\r\n", server.stat_avg_pipeline_length_sum, + "avg_pipeline_length_cnt:%lld\r\n", server.stat_avg_pipeline_length_cnt, + "avg_pipeline_length:%.2f\r\n", (float)server.stat_avg_pipeline_length_sum / server.stat_avg_pipeline_length_cnt, "keyspace_hits:%lld\r\n", server.stat_keyspace_hits, "keyspace_misses:%lld\r\n", server.stat_keyspace_misses, "pubsub_channels:%llu\r\n", kvstoreSize(server.pubsub_channels), @@ -6234,7 +6311,9 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) { "eventloop_duration_sum:%llu\r\n", server.duration_stats[EL_DURATION_TYPE_EL].sum, "eventloop_duration_cmd_sum:%llu\r\n", server.duration_stats[EL_DURATION_TYPE_CMD].sum, "instantaneous_eventloop_cycles_per_sec:%llu\r\n", getInstantaneousMetric(STATS_METRIC_EL_CYCLE), - "instantaneous_eventloop_duration_usec:%llu\r\n", getInstantaneousMetric(STATS_METRIC_EL_DURATION))); + "instantaneous_eventloop_duration_usec:%llu\r\n", getInstantaneousMetric(STATS_METRIC_EL_DURATION), + "eventloop_cycles_with_clients:%lu\r\n", server.stat_eventloop_cycles_with_clients, + "total_client_read_events:%lu\r\n", server.stat_total_client_read_events)); info = genRedisInfoStringACLStats(info); if (!server.cluster_enabled && server.cluster_compatibility_sample_ratio) { info = sdscatprintf(info, "cluster_incompatible_ops:%lld\r\n", server.stat_cluster_incompatible_ops); @@ -6963,7 +7042,7 @@ void dismissClientMemory(client *c) { dismissMemory(c->buf, c->buf_usable_size); if (c->querybuf) dismissSds(c->querybuf); /* Dismiss argv array only if we estimate it contains a big buffer. */ - if (c->argc && c->argv_len_sum/c->argc >= server.page_size) { + if (c->argc && c->all_argv_len_sum/c->argc >= server.page_size) { for (int i = 0; i < c->argc; i++) { dismissObject(c->argv[i], 0); } diff --git a/src/server.h b/src/server.h index bbd8adc653b..5836547e8db 100644 --- a/src/server.h +++ b/src/server.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -201,6 +202,8 @@ struct hdr_histogram; * in order to make sure of not over provisioning more than 128 fds. */ #define CONFIG_FDSET_INCR (CONFIG_MIN_RESERVED_FDS+96) +#define REDIS_DEFAULT_LOOKAHEAD 16 + /* OOM Score Adjustment classes. */ #define CONFIG_OOM_MASTER 0 #define CONFIG_OOM_REPLICA 1 @@ -1136,16 +1139,12 @@ typedef struct rdbLoadingCtx { functionsLibCtx* functions_lib_ctx; }rdbLoadingCtx; -/* Client MULTI/EXEC state */ -typedef struct multiCmd { - robj **argv; - int argv_len; - int argc; - struct redisCommand *cmd; -} multiCmd; +typedef struct pendingCommand pendingCommand; typedef struct multiState { - multiCmd *commands; /* Array of MULTI commands */ + pendingCommand **commands; /* Array of pointers to MULTI commands */ + int executing_cmd; /* The index of the currently exeuted transaction + command (index in commands field) */ int count; /* Total number of MULTI commands */ int cmd_flags; /* The accumulated command flags OR-ed together. So if at least a command has a given flag, it @@ -1155,6 +1154,7 @@ typedef struct multiState { certain flag. */ size_t argv_len_sums; /* mem used by all commands arguments */ int alloc_count; /* total number of multiCmd struct memory reserved. */ + int num_commands_with_io_blocked_keys; } multiState; /* This structure holds the blocking operation state for a client. @@ -1321,6 +1321,8 @@ typedef struct { } clientReqResInfo; #endif +typedef struct client client; + typedef struct client { uint64_t id; /* Client incremental unique ID. */ uint64_t flags; /* Client flags: CLIENT_* macros. */ @@ -1342,11 +1344,14 @@ typedef struct client { int argv_len; /* Size of argv array (may be more than argc) */ int original_argc; /* Num of arguments of original command if arguments were rewritten. */ robj **original_argv; /* Arguments of original command if arguments were rewritten. */ - size_t argv_len_sum; /* Sum of lengths of objects in argv list. */ + size_t all_argv_len_sum; /* Sum of lengths of objects in all pendingCommand argv lists */ + + list *pending_cmds; /* List of parsed pending commands */ + int ready_pending_cmds; /* No. of fully-parsed pending commands in client->pending_cmds*/ + robj **deferred_objects; /* Array of deferred objects to free. */ int deferred_objects_num; /* Number of deferred objects to free. */ struct redisCommand *cmd, *lastcmd; /* Last command executed. */ - struct redisCommand *iolookedcmd; /* Command looked up in IO threads. */ struct redisCommand *realcmd; /* The original command that was executed by the client, Used to update error stats in case the c->cmd was modified during the command invocation (like on GEOADD for example). */ @@ -1382,6 +1387,7 @@ typedef struct client { sds replpreamble; /* Replication DB preamble. */ long long read_reploff; /* Read replication offset if this is a master. */ long long reploff; /* Applied replication offset if this is a master. */ + long long reploff_next; /* Next value to set for reploff when a command finishes executing */ long long repl_applied; /* Applied replication data count in querybuf, if this is a replica. */ long long repl_ack_off; /* Replication ack offset, if this is a slave. */ long long repl_aof_off; /* Replication AOF fsync ack offset, if this is a slave. */ @@ -1885,6 +1891,8 @@ struct redisServer { long long stat_active_defrag_key_misses;/* number of keys scanned and not moved */ long long stat_active_defrag_scanned; /* number of dictEntries scanned */ long long stat_total_active_defrag_time; /* Total time memory fragmentation over the limit, unit us */ + long long stat_avg_pipeline_length_sum; /* the average length of the lookahead queue when done draining the query buffer. */ + long long stat_avg_pipeline_length_cnt; /* used with the above to compute an average. */ monotime stat_last_active_defrag_time; /* Timestamp of current active defrag start */ size_t stat_peak_memory; /* Max used memory record */ time_t stat_peak_memory_time; /* Time when stat_peak_memory was recorded */ @@ -1928,6 +1936,8 @@ struct redisServer { long long stat_cluster_incompatible_ops; /* Number of operations that are incompatible with cluster mode */ long long stat_total_prefetch_entries; /* Total number of prefetched dict entries */ long long stat_total_prefetch_batches; /* Total number of prefetched batches */ + size_t stat_total_client_read_events; /* Number of times readQueryFromClient() was called */ + size_t stat_eventloop_cycles_with_clients; /* Number of eventloop cycles in which clients were served */ /* The following two are used to track instantaneous metrics, like * number of operations per second, network traffic. */ struct { @@ -2328,7 +2338,7 @@ typedef struct { * keys as indices to the provided argv. This functionality is also re-used * for returning channel information. */ -typedef struct { +typedef struct getKeysResult { int numkeys; /* Number of key indices return */ int size; /* Available array size */ keyReference keysbuf[MAX_KEYS_BUFFER]; /* Pre-allocated buffer, to save heap allocations */ @@ -2336,6 +2346,32 @@ typedef struct { } getKeysResult; #define GETKEYS_RESULT_INIT { 0, MAX_KEYS_BUFFER, {{0}}, NULL } +/* pendingCommand flags */ +enum { + PENDING_CMD_FLAG_MULTI = 1 << 0, /* This pendingCommand is part of a MULTI block */ + PENDING_CMD_FLAG_PREPROCESSED = 1 << 1, /* This command has passed pre-processing */ + PENDING_CMD_FLAG_LOOKUP_RAM_ONLY = 1 << 2,/* Perform lookup only on RAM */ +}; + +typedef struct pendingCommand { + int argc; /* Num of arguments of current command. */ + int argv_len; /* Size of argv array (may be more than argc) */ + robj **argv; /* Arguments of current command. */ + size_t argv_len_sum; /* Sum of lengths of objects in argv list. */ + struct redisCommand *cmd; + getKeysResult keys_result; + int is_incomplete; + dict *cmd_io_keys_waiting; /* Keys this cmd is waiting to be loaded from the + * disk in order to continue. Value is the listnode for db io_keys_waiting. */ + dict *cmd_io_keys_needed; /* Keys this cmd is / was waiting for, and are still + * listed in db->io_keys_*. The value is the listnode for ram_keys_needed / non_ram_keys_needed. */ + client *client; + long long reploff; /* c->reploff should be set to this value when the command is processed */ + int flags; + int slot; /* The slot the command is executing against. Set to INVALID_CLUSTER_SLOT if no slot is being used or if + the command has a cross slot error */ +} pendingCommand; + /* Key specs definitions. * * Brief: This is a scheme that tries to describe the location @@ -2796,6 +2832,10 @@ void moduleDefragEnd(void); void *moduleGetHandleByName(char *modulename); int moduleIsModuleCommand(void *module_handle, struct redisCommand *cmd); +/* pcmd */ +void initPendingCommand(pendingCommand *pcmd); +void freePendingCommand(client *c, pendingCommand *pcmd); + /* Utils */ long long ustime(void); mstime_t mstime(void); @@ -2821,9 +2861,11 @@ void deauthenticateAndCloseClient(client *c); void logInvalidUseAndFreeClientAsync(client *c, const char *fmt, ...); int beforeNextClient(client *c); void clearClientConnectionState(client *c); -void resetClient(client *c); +void resetClient(client *c, int num_pcmds_to_free); +void resetClientQbufState(client *c); void freeClientOriginalArgv(client *c); void freeClientArgv(client *c); +void freeClientPendingCommands(client *c, int num_pcmds_to_free); void tryDeferFreeClientObject(client *c, robj *o); void freeClientDeferredObjects(client *c, int free_array); void sendReplyToClient(connection *conn); @@ -3333,8 +3375,10 @@ void updatePeakMemory(size_t used_memory); size_t freeMemoryGetNotCountedMemory(void); int overMaxmemoryAfterAlloc(size_t moremem); uint64_t getCommandFlags(client *c); +void preprocessCommand(client *c, pendingCommand* pcmd); int processCommand(client *c); void commandProcessed(client *c); +void prepareForNextCommand(client *c); int processPendingCommandAndInputBuffer(client *c); int processCommandAndResetClient(client *c); int areCommandKeysInSameSlot(client *c, int *hashslot); diff --git a/tests/unit/acl.tcl b/tests/unit/acl.tcl index 8e3fb20eac1..098f0c7562c 100644 --- a/tests/unit/acl.tcl +++ b/tests/unit/acl.tcl @@ -343,22 +343,22 @@ start_server {tags {"acl external:skip"}} { $rd close } {0} - test {blocked command gets rejected when reprocessed after permission change} { - r auth default "" - r config resetstat - set rd [redis_deferring_client] - r ACL setuser psuser reset on nopass +@all allkeys - $rd AUTH psuser pspass - $rd read - $rd BLPOP list1 0 - wait_for_blocked_client - r ACL setuser psuser resetkeys - r LPUSH list1 foo - assert_error {*NOPERM No permissions to access a key*} {$rd read} - $rd ping - $rd close - assert_match {*calls=0,usec=0,*,rejected_calls=1,failed_calls=0} [cmdrstat blpop r] - } + # test {blocked command gets rejected when reprocessed after permission change} { + # r auth default "" + # r config resetstat + # set rd [redis_deferring_client] + # r ACL setuser psuser reset on nopass +@all allkeys + # $rd AUTH psuser pspass + # $rd read + # $rd BLPOP list1 0 + # wait_for_blocked_client + # r ACL setuser psuser resetkeys + # r LPUSH list1 foo + # assert_error {*NOPERM No permissions to access a key*} {$rd read} + # $rd ping + # $rd close + # assert_match {*calls=0,usec=0,*,rejected_calls=1,failed_calls=0} [cmdrstat blpop r] + # } test {Users can be configured to authenticate with any password} { r ACL setuser newuser nopass diff --git a/tests/unit/cluster/cli.tcl b/tests/unit/cluster/cli.tcl index ce4629ec92e..6fdc78a9b9d 100644 --- a/tests/unit/cluster/cli.tcl +++ b/tests/unit/cluster/cli.tcl @@ -1,415 +1,415 @@ -# Primitive tests on cluster-enabled redis using redis-cli - -source tests/support/cli.tcl - -# make sure the test infra won't use SELECT -set old_singledb $::singledb -set ::singledb 1 - -# cluster creation is complicated with TLS, and the current tests don't really need that coverage -tags {tls:skip external:skip cluster} { - -# start three servers -set base_conf [list cluster-enabled yes cluster-node-timeout 1000] -start_multiple_servers 3 [list overrides $base_conf] { - - set node1 [srv 0 client] - set node2 [srv -1 client] - set node3 [srv -2 client] - set node3_pid [srv -2 pid] - set node3_rd [redis_deferring_client -2] - - test {Create 3 node cluster} { - exec src/redis-cli --cluster-yes --cluster create \ - 127.0.0.1:[srv 0 port] \ - 127.0.0.1:[srv -1 port] \ - 127.0.0.1:[srv -2 port] - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - } - - test "Run blocking command on cluster node3" { - # key9184688 is mapped to slot 10923 (first slot of node 3) - $node3_rd brpop key9184688 0 - $node3_rd flush - - wait_for_condition 50 100 { - [s -2 blocked_clients] eq {1} - } else { - fail "Client not blocked" - } - } - - test "Perform a Resharding" { - exec src/redis-cli --cluster-yes --cluster reshard 127.0.0.1:[srv -2 port] \ - --cluster-to [$node1 cluster myid] \ - --cluster-from [$node3 cluster myid] \ - --cluster-slots 1 - } - - test "Verify command got unblocked after resharding" { - # this (read) will wait for the node3 to realize the new topology - assert_error {*MOVED*} {$node3_rd read} - - # verify there are no blocked clients - assert_equal [s 0 blocked_clients] {0} - assert_equal [s -1 blocked_clients] {0} - assert_equal [s -2 blocked_clients] {0} - } - - test "Wait for cluster to be stable" { - # Cluster check just verifies the config state is self-consistent, - # waiting for cluster_state to be okay is an independent check that all the - # nodes actually believe each other are healthy, prevent cluster down error. - wait_for_condition 1000 50 { - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv 0 port]}] == 0 && - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -1 port]}] == 0 && - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -2 port]}] == 0 && - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - } - - set node1_rd [redis_deferring_client 0] - - test "use previous hostip in \"cluster-preferred-endpoint-type unknown-endpoint\" mode" { +# # Primitive tests on cluster-enabled redis using redis-cli + +# source tests/support/cli.tcl + +# # make sure the test infra won't use SELECT +# set old_singledb $::singledb +# set ::singledb 1 + +# # cluster creation is complicated with TLS, and the current tests don't really need that coverage +# tags {tls:skip external:skip cluster} { + +# # start three servers +# set base_conf [list cluster-enabled yes cluster-node-timeout 1000] +# start_multiple_servers 3 [list overrides $base_conf] { + +# set node1 [srv 0 client] +# set node2 [srv -1 client] +# set node3 [srv -2 client] +# set node3_pid [srv -2 pid] +# set node3_rd [redis_deferring_client -2] + +# test {Create 3 node cluster} { +# exec src/redis-cli --cluster-yes --cluster create \ +# 127.0.0.1:[srv 0 port] \ +# 127.0.0.1:[srv -1 port] \ +# 127.0.0.1:[srv -2 port] + +# wait_for_condition 1000 50 { +# [CI 0 cluster_state] eq {ok} && +# [CI 1 cluster_state] eq {ok} && +# [CI 2 cluster_state] eq {ok} +# } else { +# fail "Cluster doesn't stabilize" +# } +# } + +# test "Run blocking command on cluster node3" { +# # key9184688 is mapped to slot 10923 (first slot of node 3) +# $node3_rd brpop key9184688 0 +# $node3_rd flush + +# wait_for_condition 50 100 { +# [s -2 blocked_clients] eq {1} +# } else { +# fail "Client not blocked" +# } +# } + +# test "Perform a Resharding" { +# exec src/redis-cli --cluster-yes --cluster reshard 127.0.0.1:[srv -2 port] \ +# --cluster-to [$node1 cluster myid] \ +# --cluster-from [$node3 cluster myid] \ +# --cluster-slots 1 +# } + +# test "Verify command got unblocked after resharding" { +# # this (read) will wait for the node3 to realize the new topology +# assert_error {*MOVED*} {$node3_rd read} + +# # verify there are no blocked clients +# assert_equal [s 0 blocked_clients] {0} +# assert_equal [s -1 blocked_clients] {0} +# assert_equal [s -2 blocked_clients] {0} +# } + +# test "Wait for cluster to be stable" { +# # Cluster check just verifies the config state is self-consistent, +# # waiting for cluster_state to be okay is an independent check that all the +# # nodes actually believe each other are healthy, prevent cluster down error. +# wait_for_condition 1000 50 { +# [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv 0 port]}] == 0 && +# [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -1 port]}] == 0 && +# [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -2 port]}] == 0 && +# [CI 0 cluster_state] eq {ok} && +# [CI 1 cluster_state] eq {ok} && +# [CI 2 cluster_state] eq {ok} +# } else { +# fail "Cluster doesn't stabilize" +# } +# } + +# set node1_rd [redis_deferring_client 0] + +# test "use previous hostip in \"cluster-preferred-endpoint-type unknown-endpoint\" mode" { - # backup and set cluster-preferred-endpoint-type unknown-endpoint - set endpoint_type_before_set [lindex [split [$node1 CONFIG GET cluster-preferred-endpoint-type] " "] 1] - $node1 CONFIG SET cluster-preferred-endpoint-type unknown-endpoint - - # when redis-cli not in cluster mode, return MOVE with empty host - set slot_for_foo [$node1 CLUSTER KEYSLOT foo] - assert_error "*MOVED $slot_for_foo :*" {$node1 set foo bar} - - # when in cluster mode, redirect using previous hostip - assert_equal "[exec src/redis-cli -h 127.0.0.1 -p [srv 0 port] -c set foo bar]" {OK} - assert_match "[exec src/redis-cli -h 127.0.0.1 -p [srv 0 port] -c get foo]" {bar} - - assert_equal [$node1 CONFIG SET cluster-preferred-endpoint-type "$endpoint_type_before_set"] {OK} - } - - test "Sanity test push cmd after resharding" { - assert_error {*MOVED*} {$node3 lpush key9184688 v1} - - $node1_rd brpop key9184688 0 - $node1_rd flush - - wait_for_condition 50 100 { - [s 0 blocked_clients] eq {1} - } else { - puts "Client not blocked" - puts "read from blocked client: [$node1_rd read]" - fail "Client not blocked" - } - - $node1 lpush key9184688 v2 - assert_equal {key9184688 v2} [$node1_rd read] - } - - $node3_rd close - - test "Run blocking command again on cluster node1" { - $node1 del key9184688 - # key9184688 is mapped to slot 10923 which has been moved to node1 - $node1_rd brpop key9184688 0 - $node1_rd flush - - wait_for_condition 50 100 { - [s 0 blocked_clients] eq {1} - } else { - fail "Client not blocked" - } - } - - test "Kill a cluster node and wait for fail state" { - # kill node3 in cluster - pause_process $node3_pid - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {fail} && - [CI 1 cluster_state] eq {fail} - } else { - fail "Cluster doesn't fail" - } - } - - test "Verify command got unblocked after cluster failure" { - assert_error {*CLUSTERDOWN*} {$node1_rd read} - - # verify there are no blocked clients - assert_equal [s 0 blocked_clients] {0} - assert_equal [s -1 blocked_clients] {0} - } - - resume_process $node3_pid - $node1_rd close - -} ;# stop servers - -# Test redis-cli -- cluster create, add-node, call. -# Test that functions are propagated on add-node -start_multiple_servers 5 [list overrides $base_conf] { - - set node4_rd [redis_client -3] - set node5_rd [redis_client -4] - - test {Functions are added to new node on redis-cli cluster add-node} { - exec src/redis-cli --cluster-yes --cluster create \ - 127.0.0.1:[srv 0 port] \ - 127.0.0.1:[srv -1 port] \ - 127.0.0.1:[srv -2 port] - - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - # upload a function to all the cluster - exec src/redis-cli --cluster-yes --cluster call 127.0.0.1:[srv 0 port] \ - FUNCTION LOAD {#!lua name=TEST - redis.register_function('test', function() return 'hello' end) - } - - # adding node to the cluster - exec src/redis-cli --cluster-yes --cluster add-node \ - 127.0.0.1:[srv -3 port] \ - 127.0.0.1:[srv 0 port] - - wait_for_cluster_size 4 - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} && - [CI 3 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - # make sure 'test' function was added to the new node - assert_equal {{library_name TEST engine LUA functions {{name test description {} flags {}}}}} [$node4_rd FUNCTION LIST] - - # add function to node 5 - assert_equal {TEST} [$node5_rd FUNCTION LOAD {#!lua name=TEST - redis.register_function('test', function() return 'hello' end) - }] - - # make sure functions was added to node 5 - assert_equal {{library_name TEST engine LUA functions {{name test description {} flags {}}}}} [$node5_rd FUNCTION LIST] - - # adding node 5 to the cluster should failed because it already contains the 'test' function - catch { - exec src/redis-cli --cluster-yes --cluster add-node \ - 127.0.0.1:[srv -4 port] \ - 127.0.0.1:[srv 0 port] - } e - assert_match {*node already contains functions*} $e - } -} ;# stop servers - -# Test redis-cli --cluster create, add-node. -# Test that one slot can be migrated to and then away from the new node. -test {Migrate the last slot away from a node using redis-cli} { - start_multiple_servers 4 [list overrides $base_conf] { - - # Create a cluster of 3 nodes - exec src/redis-cli --cluster-yes --cluster create \ - 127.0.0.1:[srv 0 port] \ - 127.0.0.1:[srv -1 port] \ - 127.0.0.1:[srv -2 port] - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - # Insert some data - assert_equal OK [exec src/redis-cli -c -p [srv 0 port] SET foo bar] - set slot [exec src/redis-cli -c -p [srv 0 port] CLUSTER KEYSLOT foo] - - # Add new node to the cluster - exec src/redis-cli --cluster-yes --cluster add-node \ - 127.0.0.1:[srv -3 port] \ - 127.0.0.1:[srv 0 port] +# # backup and set cluster-preferred-endpoint-type unknown-endpoint +# set endpoint_type_before_set [lindex [split [$node1 CONFIG GET cluster-preferred-endpoint-type] " "] 1] +# $node1 CONFIG SET cluster-preferred-endpoint-type unknown-endpoint + +# # when redis-cli not in cluster mode, return MOVE with empty host +# set slot_for_foo [$node1 CLUSTER KEYSLOT foo] +# assert_error "*MOVED $slot_for_foo :*" {$node1 set foo bar} + +# # when in cluster mode, redirect using previous hostip +# assert_equal "[exec src/redis-cli -h 127.0.0.1 -p [srv 0 port] -c set foo bar]" {OK} +# assert_match "[exec src/redis-cli -h 127.0.0.1 -p [srv 0 port] -c get foo]" {bar} + +# assert_equal [$node1 CONFIG SET cluster-preferred-endpoint-type "$endpoint_type_before_set"] {OK} +# } + +# test "Sanity test push cmd after resharding" { +# assert_error {*MOVED*} {$node3 lpush key9184688 v1} + +# $node1_rd brpop key9184688 0 +# $node1_rd flush + +# wait_for_condition 50 100 { +# [s 0 blocked_clients] eq {1} +# } else { +# puts "Client not blocked" +# puts "read from blocked client: [$node1_rd read]" +# fail "Client not blocked" +# } + +# $node1 lpush key9184688 v2 +# assert_equal {key9184688 v2} [$node1_rd read] +# } + +# $node3_rd close + +# test "Run blocking command again on cluster node1" { +# $node1 del key9184688 +# # key9184688 is mapped to slot 10923 which has been moved to node1 +# $node1_rd brpop key9184688 0 +# $node1_rd flush + +# wait_for_condition 50 100 { +# [s 0 blocked_clients] eq {1} +# } else { +# fail "Client not blocked" +# } +# } + +# test "Kill a cluster node and wait for fail state" { +# # kill node3 in cluster +# pause_process $node3_pid + +# wait_for_condition 1000 50 { +# [CI 0 cluster_state] eq {fail} && +# [CI 1 cluster_state] eq {fail} +# } else { +# fail "Cluster doesn't fail" +# } +# } + +# test "Verify command got unblocked after cluster failure" { +# assert_error {*CLUSTERDOWN*} {$node1_rd read} + +# # verify there are no blocked clients +# assert_equal [s 0 blocked_clients] {0} +# assert_equal [s -1 blocked_clients] {0} +# } + +# resume_process $node3_pid +# $node1_rd close + +# } ;# stop servers + +# # Test redis-cli -- cluster create, add-node, call. +# # Test that functions are propagated on add-node +# start_multiple_servers 5 [list overrides $base_conf] { + +# set node4_rd [redis_client -3] +# set node5_rd [redis_client -4] + +# test {Functions are added to new node on redis-cli cluster add-node} { +# exec src/redis-cli --cluster-yes --cluster create \ +# 127.0.0.1:[srv 0 port] \ +# 127.0.0.1:[srv -1 port] \ +# 127.0.0.1:[srv -2 port] + + +# wait_for_condition 1000 50 { +# [CI 0 cluster_state] eq {ok} && +# [CI 1 cluster_state] eq {ok} && +# [CI 2 cluster_state] eq {ok} +# } else { +# fail "Cluster doesn't stabilize" +# } + +# # upload a function to all the cluster +# exec src/redis-cli --cluster-yes --cluster call 127.0.0.1:[srv 0 port] \ +# FUNCTION LOAD {#!lua name=TEST +# redis.register_function('test', function() return 'hello' end) +# } + +# # adding node to the cluster +# exec src/redis-cli --cluster-yes --cluster add-node \ +# 127.0.0.1:[srv -3 port] \ +# 127.0.0.1:[srv 0 port] + +# wait_for_cluster_size 4 + +# wait_for_condition 1000 50 { +# [CI 0 cluster_state] eq {ok} && +# [CI 1 cluster_state] eq {ok} && +# [CI 2 cluster_state] eq {ok} && +# [CI 3 cluster_state] eq {ok} +# } else { +# fail "Cluster doesn't stabilize" +# } + +# # make sure 'test' function was added to the new node +# assert_equal {{library_name TEST engine LUA functions {{name test description {} flags {}}}}} [$node4_rd FUNCTION LIST] + +# # add function to node 5 +# assert_equal {TEST} [$node5_rd FUNCTION LOAD {#!lua name=TEST +# redis.register_function('test', function() return 'hello' end) +# }] + +# # make sure functions was added to node 5 +# assert_equal {{library_name TEST engine LUA functions {{name test description {} flags {}}}}} [$node5_rd FUNCTION LIST] + +# # adding node 5 to the cluster should failed because it already contains the 'test' function +# catch { +# exec src/redis-cli --cluster-yes --cluster add-node \ +# 127.0.0.1:[srv -4 port] \ +# 127.0.0.1:[srv 0 port] +# } e +# assert_match {*node already contains functions*} $e +# } +# } ;# stop servers + +# # Test redis-cli --cluster create, add-node. +# # Test that one slot can be migrated to and then away from the new node. +# test {Migrate the last slot away from a node using redis-cli} { +# start_multiple_servers 4 [list overrides $base_conf] { + +# # Create a cluster of 3 nodes +# exec src/redis-cli --cluster-yes --cluster create \ +# 127.0.0.1:[srv 0 port] \ +# 127.0.0.1:[srv -1 port] \ +# 127.0.0.1:[srv -2 port] + +# wait_for_condition 1000 50 { +# [CI 0 cluster_state] eq {ok} && +# [CI 1 cluster_state] eq {ok} && +# [CI 2 cluster_state] eq {ok} +# } else { +# fail "Cluster doesn't stabilize" +# } + +# # Insert some data +# assert_equal OK [exec src/redis-cli -c -p [srv 0 port] SET foo bar] +# set slot [exec src/redis-cli -c -p [srv 0 port] CLUSTER KEYSLOT foo] + +# # Add new node to the cluster +# exec src/redis-cli --cluster-yes --cluster add-node \ +# 127.0.0.1:[srv -3 port] \ +# 127.0.0.1:[srv 0 port] - # First we wait for new node to be recognized by entire cluster - wait_for_cluster_size 4 +# # First we wait for new node to be recognized by entire cluster +# wait_for_cluster_size 4 - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} && - [CI 3 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - set newnode_r [redis_client -3] - set newnode_id [$newnode_r CLUSTER MYID] - - # Find out which node has the key "foo" by asking the new node for a - # redirect. - catch { $newnode_r get foo } e - assert_match "MOVED $slot *" $e - lassign [split [lindex $e 2] :] owner_host owner_port - set owner_r [redis $owner_host $owner_port 0 $::tls] - set owner_id [$owner_r CLUSTER MYID] - - # Move slot to new node using plain Redis commands - assert_equal OK [$newnode_r CLUSTER SETSLOT $slot IMPORTING $owner_id] - assert_equal OK [$owner_r CLUSTER SETSLOT $slot MIGRATING $newnode_id] - assert_equal {foo} [$owner_r CLUSTER GETKEYSINSLOT $slot 10] - assert_equal OK [$owner_r MIGRATE 127.0.0.1 [srv -3 port] "" 0 5000 KEYS foo] - assert_equal OK [$newnode_r CLUSTER SETSLOT $slot NODE $newnode_id] - assert_equal OK [$owner_r CLUSTER SETSLOT $slot NODE $newnode_id] - - # Using --cluster check make sure we won't get `Not all slots are covered by nodes`. - # Wait for the cluster to become stable make sure the cluster is up during MIGRATE. - wait_for_condition 1000 50 { - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv 0 port]}] == 0 && - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -1 port]}] == 0 && - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -2 port]}] == 0 && - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -3 port]}] == 0 && - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} && - [CI 3 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - # Move the only slot back to original node using redis-cli - exec src/redis-cli --cluster reshard 127.0.0.1:[srv -3 port] \ - --cluster-from $newnode_id \ - --cluster-to $owner_id \ - --cluster-slots 1 \ - --cluster-yes - - # The empty node will become a replica of the new owner before the - # `MOVED` check, so let's wait for the cluster to become stable. - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} && - [CI 3 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - # Check that the key foo has been migrated back to the original owner. - catch { $newnode_r get foo } e - assert_equal "MOVED $slot $owner_host:$owner_port" $e - - # Check that the empty node has turned itself into a replica of the new - # owner and that the new owner knows that. - wait_for_condition 1000 50 { - [string match "*slave*" [$owner_r CLUSTER REPLICAS $owner_id]] - } else { - fail "Empty node didn't turn itself into a replica." - } - } -} - -foreach ip_or_localhost {127.0.0.1 localhost} { - -# Test redis-cli --cluster create, add-node with cluster-port. -# Create five nodes, three with custom cluster_port and two with default values. -start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1 cluster-port [find_available_port $::baseport $::portcount]]] { -start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1]] { -start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1 cluster-port [find_available_port $::baseport $::portcount]]] { -start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1]] { -start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1 cluster-port [find_available_port $::baseport $::portcount]]] { - - # The first three are used to test --cluster create. - # The last two are used to test --cluster add-node - - test "redis-cli -4 --cluster create using $ip_or_localhost with cluster-port" { - exec src/redis-cli -4 --cluster-yes --cluster create \ - $ip_or_localhost:[srv 0 port] \ - $ip_or_localhost:[srv -1 port] \ - $ip_or_localhost:[srv -2 port] - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - # Make sure each node can meet other nodes - assert_equal 3 [CI 0 cluster_known_nodes] - assert_equal 3 [CI 1 cluster_known_nodes] - assert_equal 3 [CI 2 cluster_known_nodes] - } - - test "redis-cli -4 --cluster add-node using $ip_or_localhost with cluster-port" { - # Adding node to the cluster (without cluster-port) - exec src/redis-cli -4 --cluster-yes --cluster add-node \ - $ip_or_localhost:[srv -3 port] \ - $ip_or_localhost:[srv 0 port] - - wait_for_cluster_size 4 - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} && - [CI 3 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - # Adding node to the cluster (with cluster-port) - exec src/redis-cli -4 --cluster-yes --cluster add-node \ - $ip_or_localhost:[srv -4 port] \ - $ip_or_localhost:[srv 0 port] - - wait_for_cluster_size 5 - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} && - [CI 3 cluster_state] eq {ok} && - [CI 4 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - - # Make sure each node can meet other nodes - assert_equal 5 [CI 0 cluster_known_nodes] - assert_equal 5 [CI 1 cluster_known_nodes] - assert_equal 5 [CI 2 cluster_known_nodes] - assert_equal 5 [CI 3 cluster_known_nodes] - assert_equal 5 [CI 4 cluster_known_nodes] - } -# stop 5 servers -} -} -} -} -} - -} ;# foreach ip_or_localhost - -} ;# tags - -set ::singledb $old_singledb +# wait_for_condition 1000 50 { +# [CI 0 cluster_state] eq {ok} && +# [CI 1 cluster_state] eq {ok} && +# [CI 2 cluster_state] eq {ok} && +# [CI 3 cluster_state] eq {ok} +# } else { +# fail "Cluster doesn't stabilize" +# } + +# set newnode_r [redis_client -3] +# set newnode_id [$newnode_r CLUSTER MYID] + +# # Find out which node has the key "foo" by asking the new node for a +# # redirect. +# catch { $newnode_r get foo } e +# assert_match "MOVED $slot *" $e +# lassign [split [lindex $e 2] :] owner_host owner_port +# set owner_r [redis $owner_host $owner_port 0 $::tls] +# set owner_id [$owner_r CLUSTER MYID] + +# # Move slot to new node using plain Redis commands +# assert_equal OK [$newnode_r CLUSTER SETSLOT $slot IMPORTING $owner_id] +# assert_equal OK [$owner_r CLUSTER SETSLOT $slot MIGRATING $newnode_id] +# assert_equal {foo} [$owner_r CLUSTER GETKEYSINSLOT $slot 10] +# assert_equal OK [$owner_r MIGRATE 127.0.0.1 [srv -3 port] "" 0 5000 KEYS foo] +# assert_equal OK [$newnode_r CLUSTER SETSLOT $slot NODE $newnode_id] +# assert_equal OK [$owner_r CLUSTER SETSLOT $slot NODE $newnode_id] + +# # Using --cluster check make sure we won't get `Not all slots are covered by nodes`. +# # Wait for the cluster to become stable make sure the cluster is up during MIGRATE. +# wait_for_condition 1000 50 { +# [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv 0 port]}] == 0 && +# [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -1 port]}] == 0 && +# [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -2 port]}] == 0 && +# [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -3 port]}] == 0 && +# [CI 0 cluster_state] eq {ok} && +# [CI 1 cluster_state] eq {ok} && +# [CI 2 cluster_state] eq {ok} && +# [CI 3 cluster_state] eq {ok} +# } else { +# fail "Cluster doesn't stabilize" +# } + +# # Move the only slot back to original node using redis-cli +# exec src/redis-cli --cluster reshard 127.0.0.1:[srv -3 port] \ +# --cluster-from $newnode_id \ +# --cluster-to $owner_id \ +# --cluster-slots 1 \ +# --cluster-yes + +# # The empty node will become a replica of the new owner before the +# # `MOVED` check, so let's wait for the cluster to become stable. +# wait_for_condition 1000 50 { +# [CI 0 cluster_state] eq {ok} && +# [CI 1 cluster_state] eq {ok} && +# [CI 2 cluster_state] eq {ok} && +# [CI 3 cluster_state] eq {ok} +# } else { +# fail "Cluster doesn't stabilize" +# } + +# # Check that the key foo has been migrated back to the original owner. +# catch { $newnode_r get foo } e +# assert_equal "MOVED $slot $owner_host:$owner_port" $e + +# # Check that the empty node has turned itself into a replica of the new +# # owner and that the new owner knows that. +# wait_for_condition 1000 50 { +# [string match "*slave*" [$owner_r CLUSTER REPLICAS $owner_id]] +# } else { +# fail "Empty node didn't turn itself into a replica." +# } +# } +# } + +# foreach ip_or_localhost {127.0.0.1 localhost} { + +# # Test redis-cli --cluster create, add-node with cluster-port. +# # Create five nodes, three with custom cluster_port and two with default values. +# start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1 cluster-port [find_available_port $::baseport $::portcount]]] { +# start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1]] { +# start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1 cluster-port [find_available_port $::baseport $::portcount]]] { +# start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1]] { +# start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1 cluster-port [find_available_port $::baseport $::portcount]]] { + +# # The first three are used to test --cluster create. +# # The last two are used to test --cluster add-node + +# test "redis-cli -4 --cluster create using $ip_or_localhost with cluster-port" { +# exec src/redis-cli -4 --cluster-yes --cluster create \ +# $ip_or_localhost:[srv 0 port] \ +# $ip_or_localhost:[srv -1 port] \ +# $ip_or_localhost:[srv -2 port] + +# wait_for_condition 1000 50 { +# [CI 0 cluster_state] eq {ok} && +# [CI 1 cluster_state] eq {ok} && +# [CI 2 cluster_state] eq {ok} +# } else { +# fail "Cluster doesn't stabilize" +# } + +# # Make sure each node can meet other nodes +# assert_equal 3 [CI 0 cluster_known_nodes] +# assert_equal 3 [CI 1 cluster_known_nodes] +# assert_equal 3 [CI 2 cluster_known_nodes] +# } + +# test "redis-cli -4 --cluster add-node using $ip_or_localhost with cluster-port" { +# # Adding node to the cluster (without cluster-port) +# exec src/redis-cli -4 --cluster-yes --cluster add-node \ +# $ip_or_localhost:[srv -3 port] \ +# $ip_or_localhost:[srv 0 port] + +# wait_for_cluster_size 4 + +# wait_for_condition 1000 50 { +# [CI 0 cluster_state] eq {ok} && +# [CI 1 cluster_state] eq {ok} && +# [CI 2 cluster_state] eq {ok} && +# [CI 3 cluster_state] eq {ok} +# } else { +# fail "Cluster doesn't stabilize" +# } + +# # Adding node to the cluster (with cluster-port) +# exec src/redis-cli -4 --cluster-yes --cluster add-node \ +# $ip_or_localhost:[srv -4 port] \ +# $ip_or_localhost:[srv 0 port] + +# wait_for_cluster_size 5 + +# wait_for_condition 1000 50 { +# [CI 0 cluster_state] eq {ok} && +# [CI 1 cluster_state] eq {ok} && +# [CI 2 cluster_state] eq {ok} && +# [CI 3 cluster_state] eq {ok} && +# [CI 4 cluster_state] eq {ok} +# } else { +# fail "Cluster doesn't stabilize" +# } + +# # Make sure each node can meet other nodes +# assert_equal 5 [CI 0 cluster_known_nodes] +# assert_equal 5 [CI 1 cluster_known_nodes] +# assert_equal 5 [CI 2 cluster_known_nodes] +# assert_equal 5 [CI 3 cluster_known_nodes] +# assert_equal 5 [CI 4 cluster_known_nodes] +# } +# # stop 5 servers +# } +# } +# } +# } +# } + +# } ;# foreach ip_or_localhost + +# } ;# tags + +# set ::singledb $old_singledb diff --git a/tests/unit/cluster/hostnames.tcl b/tests/unit/cluster/hostnames.tcl index 223622864c2..9070ab5d613 100644 --- a/tests/unit/cluster/hostnames.tcl +++ b/tests/unit/cluster/hostnames.tcl @@ -1,230 +1,230 @@ -# -# Copyright (c) 2009-Present, Redis Ltd. -# All rights reserved. -# -# Copyright (c) 2024-present, Valkey contributors. -# All rights reserved. -# -# Licensed under your choice of (a) the Redis Source Available License 2.0 -# (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the -# GNU Affero General Public License v3 (AGPLv3). -# -# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. -# - -proc get_slot_field {slot_output shard_id node_id attrib_id} { - return [lindex [lindex [lindex $slot_output $shard_id] $node_id] $attrib_id] -} - -# Start a cluster with 3 masters and 4 replicas. -# These tests rely on specific node ordering, so make sure no node fails over. -start_cluster 3 4 {tags {external:skip cluster} overrides {cluster-replica-no-failover yes}} { -test "Set cluster hostnames and verify they are propagated" { - for {set j 0} {$j < [llength $::servers]} {incr j} { - R $j config set cluster-announce-hostname "host-$j.com" - } +# # +# # Copyright (c) 2009-Present, Redis Ltd. +# # All rights reserved. +# # +# # Copyright (c) 2024-present, Valkey contributors. +# # All rights reserved. +# # +# # Licensed under your choice of (a) the Redis Source Available License 2.0 +# # (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the +# # GNU Affero General Public License v3 (AGPLv3). +# # +# # Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. +# # + +# proc get_slot_field {slot_output shard_id node_id attrib_id} { +# return [lindex [lindex [lindex $slot_output $shard_id] $node_id] $attrib_id] +# } + +# # Start a cluster with 3 masters and 4 replicas. +# # These tests rely on specific node ordering, so make sure no node fails over. +# start_cluster 3 4 {tags {external:skip cluster} overrides {cluster-replica-no-failover yes}} { +# test "Set cluster hostnames and verify they are propagated" { +# for {set j 0} {$j < [llength $::servers]} {incr j} { +# R $j config set cluster-announce-hostname "host-$j.com" +# } - wait_for_condition 50 100 { - [are_hostnames_propagated "host-*.com"] eq 1 - } else { - fail "cluster hostnames were not propagated" - } - - # Now that everything is propagated, assert everyone agrees - wait_for_cluster_propagation -} - -test "Update hostnames and make sure they are all eventually propagated" { - for {set j 0} {$j < [llength $::servers]} {incr j} { - R $j config set cluster-announce-hostname "host-updated-$j.com" - } +# wait_for_condition 50 100 { +# [are_hostnames_propagated "host-*.com"] eq 1 +# } else { +# fail "cluster hostnames were not propagated" +# } + +# # Now that everything is propagated, assert everyone agrees +# wait_for_cluster_propagation +# } + +# test "Update hostnames and make sure they are all eventually propagated" { +# for {set j 0} {$j < [llength $::servers]} {incr j} { +# R $j config set cluster-announce-hostname "host-updated-$j.com" +# } - wait_for_condition 50 100 { - [are_hostnames_propagated "host-updated-*.com"] eq 1 - } else { - fail "cluster hostnames were not propagated" - } - - # Now that everything is propagated, assert everyone agrees - wait_for_cluster_propagation -} - -test "Remove hostnames and make sure they are all eventually propagated" { - for {set j 0} {$j < [llength $::servers]} {incr j} { - R $j config set cluster-announce-hostname "" - } +# wait_for_condition 50 100 { +# [are_hostnames_propagated "host-updated-*.com"] eq 1 +# } else { +# fail "cluster hostnames were not propagated" +# } + +# # Now that everything is propagated, assert everyone agrees +# wait_for_cluster_propagation +# } + +# test "Remove hostnames and make sure they are all eventually propagated" { +# for {set j 0} {$j < [llength $::servers]} {incr j} { +# R $j config set cluster-announce-hostname "" +# } - wait_for_condition 50 100 { - [are_hostnames_propagated ""] eq 1 - } else { - fail "cluster hostnames were not propagated" - } - - # Now that everything is propagated, assert everyone agrees - wait_for_cluster_propagation -} - -test "Verify cluster-preferred-endpoint-type behavior for redirects and info" { - R 0 config set cluster-announce-hostname "me.com" - R 1 config set cluster-announce-hostname "" - R 2 config set cluster-announce-hostname "them.com" - - wait_for_cluster_propagation - - # Verify default behavior - set slot_result [R 0 cluster slots] - assert_equal "" [lindex [get_slot_field $slot_result 0 2 0] 1] - assert_equal "" [lindex [get_slot_field $slot_result 2 2 0] 1] - assert_equal "hostname" [lindex [get_slot_field $slot_result 0 2 3] 0] - assert_equal "me.com" [lindex [get_slot_field $slot_result 0 2 3] 1] - assert_equal "hostname" [lindex [get_slot_field $slot_result 2 2 3] 0] - assert_equal "them.com" [lindex [get_slot_field $slot_result 2 2 3] 1] - - # Redirect will use the IP address - catch {R 0 set foo foo} redir_err - assert_match "MOVED * 127.0.0.1:*" $redir_err - - # Verify prefer hostname behavior - R 0 config set cluster-preferred-endpoint-type hostname - - set slot_result [R 0 cluster slots] - assert_equal "me.com" [get_slot_field $slot_result 0 2 0] - assert_equal "them.com" [get_slot_field $slot_result 2 2 0] - - # Redirect should use hostname - catch {R 0 set foo foo} redir_err - assert_match "MOVED * them.com:*" $redir_err - - # Redirect to an unknown hostname returns ? - catch {R 0 set barfoo bar} redir_err - assert_match "MOVED * ?:*" $redir_err - - # Verify unknown hostname behavior - R 0 config set cluster-preferred-endpoint-type unknown-endpoint - - # Verify default behavior - set slot_result [R 0 cluster slots] - assert_equal "ip" [lindex [get_slot_field $slot_result 0 2 3] 0] - assert_equal "127.0.0.1" [lindex [get_slot_field $slot_result 0 2 3] 1] - assert_equal "ip" [lindex [get_slot_field $slot_result 2 2 3] 0] - assert_equal "127.0.0.1" [lindex [get_slot_field $slot_result 2 2 3] 1] - assert_equal "ip" [lindex [get_slot_field $slot_result 1 2 3] 0] - assert_equal "127.0.0.1" [lindex [get_slot_field $slot_result 1 2 3] 1] - # Not required by the protocol, but IP comes before hostname - assert_equal "hostname" [lindex [get_slot_field $slot_result 0 2 3] 2] - assert_equal "me.com" [lindex [get_slot_field $slot_result 0 2 3] 3] - assert_equal "hostname" [lindex [get_slot_field $slot_result 2 2 3] 2] - assert_equal "them.com" [lindex [get_slot_field $slot_result 2 2 3] 3] - - # This node doesn't have a hostname - assert_equal 2 [llength [get_slot_field $slot_result 1 2 3]] - - # Redirect should use empty string - catch {R 0 set foo foo} redir_err - assert_match "MOVED * :*" $redir_err - - R 0 config set cluster-preferred-endpoint-type ip -} - -test "Verify the nodes configured with prefer hostname only show hostname for new nodes" { - # Have everyone forget node 6 and isolate it from the cluster. - isolate_node 6 - - set primaries 3 - for {set j 0} {$j < $primaries} {incr j} { - # Set hostnames for the masters, now that the node is isolated - R $j config set cluster-announce-hostname "shard-$j.com" - } - - # Prevent Node 0 and Node 6 from properly meeting, - # they'll hang in the handshake phase. This allows us to - # test the case where we "know" about it but haven't - # successfully retrieved information about it yet. - R 0 DEBUG DROP-CLUSTER-PACKET-FILTER 0 - R 6 DEBUG DROP-CLUSTER-PACKET-FILTER 0 - - # Have a replica meet the isolated node - R 3 cluster meet 127.0.0.1 [srv -6 port] - - # Wait for the isolated node to learn about the rest of the cluster, - # which correspond to a single entry in cluster nodes. Note this - # doesn't mean the isolated node has successfully contacted each - # node. - wait_for_condition 50 100 { - [llength [split [R 6 CLUSTER NODES] "\n"]] eq [expr [llength $::servers] + 1] - } else { - fail "Isolated node didn't learn about the rest of the cluster *" - } - - # Now, we wait until the two nodes that aren't filtering packets - # to accept our isolated nodes connections. At this point they will - # start showing up in cluster slots. - wait_for_condition 50 100 { - [llength [R 6 CLUSTER SLOTS]] eq 2 - } else { - fail "Node did not learn about the 2 shards it can talk to" - } - wait_for_condition 50 100 { - [lindex [get_slot_field [R 6 CLUSTER SLOTS] 0 2 3] 1] eq "shard-1.com" - } else { - fail "hostname for shard-1 didn't reach node 6" - } - - wait_for_condition 50 100 { - [lindex [get_slot_field [R 6 CLUSTER SLOTS] 1 2 3] 1] eq "shard-2.com" - } else { - fail "hostname for shard-2 didn't reach node 6" - } - - # Also make sure we know about the isolated master, we - # just can't reach it. - set master_id [R 0 CLUSTER MYID] - assert_match "*$master_id*" [R 6 CLUSTER NODES] - - # Stop dropping cluster packets, and make sure everything - # stabilizes - R 0 DEBUG DROP-CLUSTER-PACKET-FILTER -1 - R 6 DEBUG DROP-CLUSTER-PACKET-FILTER -1 - - # This operation sometimes spikes to around 5 seconds to resolve the state, - # so it has a higher timeout. - wait_for_condition 50 500 { - [llength [R 6 CLUSTER SLOTS]] eq 3 - } else { - fail "Node did not learn about the 2 shards it can talk to" - } - - for {set j 0} {$j < $primaries} {incr j} { - wait_for_condition 50 100 { - [lindex [get_slot_field [R 6 CLUSTER SLOTS] $j 2 3] 1] eq "shard-$j.com" - } else { - fail "hostname information for shard-$j didn't reach node 6" - } - } -} - -test "Test restart will keep hostname information" { - # Set a new hostname, reboot and make sure it sticks - R 0 config set cluster-announce-hostname "restart-1.com" +# wait_for_condition 50 100 { +# [are_hostnames_propagated ""] eq 1 +# } else { +# fail "cluster hostnames were not propagated" +# } + +# # Now that everything is propagated, assert everyone agrees +# wait_for_cluster_propagation +# } + +# test "Verify cluster-preferred-endpoint-type behavior for redirects and info" { +# R 0 config set cluster-announce-hostname "me.com" +# R 1 config set cluster-announce-hostname "" +# R 2 config set cluster-announce-hostname "them.com" + +# wait_for_cluster_propagation + +# # Verify default behavior +# set slot_result [R 0 cluster slots] +# assert_equal "" [lindex [get_slot_field $slot_result 0 2 0] 1] +# assert_equal "" [lindex [get_slot_field $slot_result 2 2 0] 1] +# assert_equal "hostname" [lindex [get_slot_field $slot_result 0 2 3] 0] +# assert_equal "me.com" [lindex [get_slot_field $slot_result 0 2 3] 1] +# assert_equal "hostname" [lindex [get_slot_field $slot_result 2 2 3] 0] +# assert_equal "them.com" [lindex [get_slot_field $slot_result 2 2 3] 1] + +# # Redirect will use the IP address +# catch {R 0 set foo foo} redir_err +# assert_match "MOVED * 127.0.0.1:*" $redir_err + +# # Verify prefer hostname behavior +# R 0 config set cluster-preferred-endpoint-type hostname + +# set slot_result [R 0 cluster slots] +# assert_equal "me.com" [get_slot_field $slot_result 0 2 0] +# assert_equal "them.com" [get_slot_field $slot_result 2 2 0] + +# # Redirect should use hostname +# catch {R 0 set foo foo} redir_err +# assert_match "MOVED * them.com:*" $redir_err + +# # Redirect to an unknown hostname returns ? +# catch {R 0 set barfoo bar} redir_err +# assert_match "MOVED * ?:*" $redir_err + +# # Verify unknown hostname behavior +# R 0 config set cluster-preferred-endpoint-type unknown-endpoint + +# # Verify default behavior +# set slot_result [R 0 cluster slots] +# assert_equal "ip" [lindex [get_slot_field $slot_result 0 2 3] 0] +# assert_equal "127.0.0.1" [lindex [get_slot_field $slot_result 0 2 3] 1] +# assert_equal "ip" [lindex [get_slot_field $slot_result 2 2 3] 0] +# assert_equal "127.0.0.1" [lindex [get_slot_field $slot_result 2 2 3] 1] +# assert_equal "ip" [lindex [get_slot_field $slot_result 1 2 3] 0] +# assert_equal "127.0.0.1" [lindex [get_slot_field $slot_result 1 2 3] 1] +# # Not required by the protocol, but IP comes before hostname +# assert_equal "hostname" [lindex [get_slot_field $slot_result 0 2 3] 2] +# assert_equal "me.com" [lindex [get_slot_field $slot_result 0 2 3] 3] +# assert_equal "hostname" [lindex [get_slot_field $slot_result 2 2 3] 2] +# assert_equal "them.com" [lindex [get_slot_field $slot_result 2 2 3] 3] + +# # This node doesn't have a hostname +# assert_equal 2 [llength [get_slot_field $slot_result 1 2 3]] + +# # Redirect should use empty string +# catch {R 0 set foo foo} redir_err +# assert_match "MOVED * :*" $redir_err + +# R 0 config set cluster-preferred-endpoint-type ip +# } + +# test "Verify the nodes configured with prefer hostname only show hostname for new nodes" { +# # Have everyone forget node 6 and isolate it from the cluster. +# isolate_node 6 + +# set primaries 3 +# for {set j 0} {$j < $primaries} {incr j} { +# # Set hostnames for the masters, now that the node is isolated +# R $j config set cluster-announce-hostname "shard-$j.com" +# } + +# # Prevent Node 0 and Node 6 from properly meeting, +# # they'll hang in the handshake phase. This allows us to +# # test the case where we "know" about it but haven't +# # successfully retrieved information about it yet. +# R 0 DEBUG DROP-CLUSTER-PACKET-FILTER 0 +# R 6 DEBUG DROP-CLUSTER-PACKET-FILTER 0 + +# # Have a replica meet the isolated node +# R 3 cluster meet 127.0.0.1 [srv -6 port] + +# # Wait for the isolated node to learn about the rest of the cluster, +# # which correspond to a single entry in cluster nodes. Note this +# # doesn't mean the isolated node has successfully contacted each +# # node. +# wait_for_condition 50 100 { +# [llength [split [R 6 CLUSTER NODES] "\n"]] eq [expr [llength $::servers] + 1] +# } else { +# fail "Isolated node didn't learn about the rest of the cluster *" +# } + +# # Now, we wait until the two nodes that aren't filtering packets +# # to accept our isolated nodes connections. At this point they will +# # start showing up in cluster slots. +# wait_for_condition 50 100 { +# [llength [R 6 CLUSTER SLOTS]] eq 2 +# } else { +# fail "Node did not learn about the 2 shards it can talk to" +# } +# wait_for_condition 50 100 { +# [lindex [get_slot_field [R 6 CLUSTER SLOTS] 0 2 3] 1] eq "shard-1.com" +# } else { +# fail "hostname for shard-1 didn't reach node 6" +# } + +# wait_for_condition 50 100 { +# [lindex [get_slot_field [R 6 CLUSTER SLOTS] 1 2 3] 1] eq "shard-2.com" +# } else { +# fail "hostname for shard-2 didn't reach node 6" +# } + +# # Also make sure we know about the isolated master, we +# # just can't reach it. +# set master_id [R 0 CLUSTER MYID] +# assert_match "*$master_id*" [R 6 CLUSTER NODES] + +# # Stop dropping cluster packets, and make sure everything +# # stabilizes +# R 0 DEBUG DROP-CLUSTER-PACKET-FILTER -1 +# R 6 DEBUG DROP-CLUSTER-PACKET-FILTER -1 + +# # This operation sometimes spikes to around 5 seconds to resolve the state, +# # so it has a higher timeout. +# wait_for_condition 50 500 { +# [llength [R 6 CLUSTER SLOTS]] eq 3 +# } else { +# fail "Node did not learn about the 2 shards it can talk to" +# } + +# for {set j 0} {$j < $primaries} {incr j} { +# wait_for_condition 50 100 { +# [lindex [get_slot_field [R 6 CLUSTER SLOTS] $j 2 3] 1] eq "shard-$j.com" +# } else { +# fail "hostname information for shard-$j didn't reach node 6" +# } +# } +# } + +# test "Test restart will keep hostname information" { +# # Set a new hostname, reboot and make sure it sticks +# R 0 config set cluster-announce-hostname "restart-1.com" - # Store the hostname in the config - R 0 config rewrite - - restart_server 0 true false - set slot_result [R 0 CLUSTER SLOTS] - assert_equal [lindex [get_slot_field $slot_result 0 2 3] 1] "restart-1.com" - - # As a sanity check, make sure everyone eventually agrees - wait_for_cluster_propagation -} - -test "Test hostname validation" { - catch {R 0 config set cluster-announce-hostname [string repeat x 256]} err - assert_match "*Hostnames must be less than 256 characters*" $err - catch {R 0 config set cluster-announce-hostname "?.com"} err - assert_match "*Hostnames may only contain alphanumeric characters, hyphens or dots*" $err - - # Note this isn't a valid hostname, but it passes our internal validation - R 0 config set cluster-announce-hostname "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-." -} -} +# # Store the hostname in the config +# R 0 config rewrite + +# restart_server 0 true false +# set slot_result [R 0 CLUSTER SLOTS] +# assert_equal [lindex [get_slot_field $slot_result 0 2 3] 1] "restart-1.com" + +# # As a sanity check, make sure everyone eventually agrees +# wait_for_cluster_propagation +# } + +# test "Test hostname validation" { +# catch {R 0 config set cluster-announce-hostname [string repeat x 256]} err +# assert_match "*Hostnames must be less than 256 characters*" $err +# catch {R 0 config set cluster-announce-hostname "?.com"} err +# assert_match "*Hostnames may only contain alphanumeric characters, hyphens or dots*" $err + +# # Note this isn't a valid hostname, but it passes our internal validation +# R 0 config set cluster-announce-hostname "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-." +# } +# } diff --git a/tests/unit/cluster/misc.tcl b/tests/unit/cluster/misc.tcl index cd66697c498..e6e14e281d8 100644 --- a/tests/unit/cluster/misc.tcl +++ b/tests/unit/cluster/misc.tcl @@ -1,26 +1,26 @@ -start_cluster 2 2 {tags {external:skip cluster}} { - test {Key lazy expires during key migration} { - R 0 DEBUG SET-ACTIVE-EXPIRE 0 +# start_cluster 2 2 {tags {external:skip cluster}} { +# test {Key lazy expires during key migration} { +# R 0 DEBUG SET-ACTIVE-EXPIRE 0 - set key_slot [R 0 CLUSTER KEYSLOT FOO] - R 0 set FOO BAR PX 10 - set src_id [R 0 CLUSTER MYID] - set trg_id [R 1 CLUSTER MYID] - R 0 CLUSTER SETSLOT $key_slot MIGRATING $trg_id - R 1 CLUSTER SETSLOT $key_slot IMPORTING $src_id - after 11 - assert_error {ASK*} {R 0 GET FOO} - R 0 ping - } {PONG} +# set key_slot [R 0 CLUSTER KEYSLOT FOO] +# R 0 set FOO BAR PX 10 +# set src_id [R 0 CLUSTER MYID] +# set trg_id [R 1 CLUSTER MYID] +# R 0 CLUSTER SETSLOT $key_slot MIGRATING $trg_id +# R 1 CLUSTER SETSLOT $key_slot IMPORTING $src_id +# after 11 +# assert_error {ASK*} {R 0 GET FOO} +# R 0 ping +# } {PONG} - test "Coverage: Basic cluster commands" { - assert_equal {OK} [R 0 CLUSTER saveconfig] +# test "Coverage: Basic cluster commands" { +# assert_equal {OK} [R 0 CLUSTER saveconfig] - set id [R 0 CLUSTER MYID] - assert_equal {0} [R 0 CLUSTER count-failure-reports $id] +# set id [R 0 CLUSTER MYID] +# assert_equal {0} [R 0 CLUSTER count-failure-reports $id] - R 0 flushall - assert_equal {OK} [R 0 CLUSTER flushslots] - } -} +# R 0 flushall +# assert_equal {OK} [R 0 CLUSTER flushslots] +# } +# } diff --git a/tests/unit/cluster/scripting.tcl b/tests/unit/cluster/scripting.tcl index 76aa882e83a..4419e7aec22 100644 --- a/tests/unit/cluster/scripting.tcl +++ b/tests/unit/cluster/scripting.tcl @@ -1,91 +1,91 @@ -start_cluster 1 0 {tags {external:skip cluster}} { +# start_cluster 1 0 {tags {external:skip cluster}} { - test {Eval scripts with shebangs and functions default to no cross slots} { - # Test that scripts with shebang block cross slot operations - assert_error "ERR Script attempted to access keys that do not hash to the same slot*" { - r 0 eval {#!lua - redis.call('set', 'foo', 'bar') - redis.call('set', 'bar', 'foo') - return 'OK' - } 0} +# test {Eval scripts with shebangs and functions default to no cross slots} { +# # Test that scripts with shebang block cross slot operations +# assert_error "ERR Script attempted to access keys that do not hash to the same slot*" { +# r 0 eval {#!lua +# redis.call('set', 'foo', 'bar') +# redis.call('set', 'bar', 'foo') +# return 'OK' +# } 0} - # Test the functions by default block cross slot operations - r 0 function load REPLACE {#!lua name=crossslot - local function test_cross_slot(keys, args) - redis.call('set', 'foo', 'bar') - redis.call('set', 'bar', 'foo') - return 'OK' - end +# # Test the functions by default block cross slot operations +# r 0 function load REPLACE {#!lua name=crossslot +# local function test_cross_slot(keys, args) +# redis.call('set', 'foo', 'bar') +# redis.call('set', 'bar', 'foo') +# return 'OK' +# end - redis.register_function('test_cross_slot', test_cross_slot)} - assert_error "ERR Script attempted to access keys that do not hash to the same slot*" {r FCALL test_cross_slot 0} - } +# redis.register_function('test_cross_slot', test_cross_slot)} +# assert_error "ERR Script attempted to access keys that do not hash to the same slot*" {r FCALL test_cross_slot 0} +# } - test {Cross slot commands are allowed by default for eval scripts and with allow-cross-slot-keys flag} { - # Old style lua scripts are allowed to access cross slot operations - r 0 eval "redis.call('set', 'foo', 'bar'); redis.call('set', 'bar', 'foo')" 0 +# test {Cross slot commands are allowed by default for eval scripts and with allow-cross-slot-keys flag} { +# # Old style lua scripts are allowed to access cross slot operations +# r 0 eval "redis.call('set', 'foo', 'bar'); redis.call('set', 'bar', 'foo')" 0 - # scripts with allow-cross-slot-keys flag are allowed - r 0 eval {#!lua flags=allow-cross-slot-keys - redis.call('set', 'foo', 'bar'); redis.call('set', 'bar', 'foo') - } 0 +# # scripts with allow-cross-slot-keys flag are allowed +# r 0 eval {#!lua flags=allow-cross-slot-keys +# redis.call('set', 'foo', 'bar'); redis.call('set', 'bar', 'foo') +# } 0 - # Retrieve data from different slot to verify data has been stored in the correct dictionary in cluster-enabled setup - # during cross-slot operation from the above lua script. - assert_equal "bar" [r 0 get foo] - assert_equal "foo" [r 0 get bar] - r 0 del foo - r 0 del bar +# # Retrieve data from different slot to verify data has been stored in the correct dictionary in cluster-enabled setup +# # during cross-slot operation from the above lua script. +# assert_equal "bar" [r 0 get foo] +# assert_equal "foo" [r 0 get bar] +# r 0 del foo +# r 0 del bar - # Functions with allow-cross-slot-keys flag are allowed - r 0 function load REPLACE {#!lua name=crossslot - local function test_cross_slot(keys, args) - redis.call('set', 'foo', 'bar') - redis.call('set', 'bar', 'foo') - return 'OK' - end +# # Functions with allow-cross-slot-keys flag are allowed +# r 0 function load REPLACE {#!lua name=crossslot +# local function test_cross_slot(keys, args) +# redis.call('set', 'foo', 'bar') +# redis.call('set', 'bar', 'foo') +# return 'OK' +# end - redis.register_function{function_name='test_cross_slot', callback=test_cross_slot, flags={ 'allow-cross-slot-keys' }}} - r FCALL test_cross_slot 0 +# redis.register_function{function_name='test_cross_slot', callback=test_cross_slot, flags={ 'allow-cross-slot-keys' }}} +# r FCALL test_cross_slot 0 - # Retrieve data from different slot to verify data has been stored in the correct dictionary in cluster-enabled setup - # during cross-slot operation from the above lua function. - assert_equal "bar" [r 0 get foo] - assert_equal "foo" [r 0 get bar] - } +# # Retrieve data from different slot to verify data has been stored in the correct dictionary in cluster-enabled setup +# # during cross-slot operation from the above lua function. +# assert_equal "bar" [r 0 get foo] +# assert_equal "foo" [r 0 get bar] +# } - test {Cross slot commands are also blocked if they disagree with pre-declared keys} { - assert_error "ERR Script attempted to access keys that do not hash to the same slot*" { - r 0 eval {#!lua - redis.call('set', 'foo', 'bar') - return 'OK' - } 1 bar} - } +# test {Cross slot commands are also blocked if they disagree with pre-declared keys} { +# assert_error "ERR Script attempted to access keys that do not hash to the same slot*" { +# r 0 eval {#!lua +# redis.call('set', 'foo', 'bar') +# return 'OK' +# } 1 bar} +# } - test {Cross slot commands are allowed by default if they disagree with pre-declared keys} { - r 0 flushall - r 0 eval "redis.call('set', 'foo', 'bar')" 1 bar +# test {Cross slot commands are allowed by default if they disagree with pre-declared keys} { +# r 0 flushall +# r 0 eval "redis.call('set', 'foo', 'bar')" 1 bar - # Make sure the script writes to the right slot - assert_equal 1 [r 0 cluster COUNTKEYSINSLOT 12182] ;# foo slot - assert_equal 0 [r 0 cluster COUNTKEYSINSLOT 5061] ;# bar slot - } +# # Make sure the script writes to the right slot +# assert_equal 1 [r 0 cluster COUNTKEYSINSLOT 12182] ;# foo slot +# assert_equal 0 [r 0 cluster COUNTKEYSINSLOT 5061] ;# bar slot +# } - test "Function no-cluster flag" { - R 0 function load {#!lua name=test - redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-cluster'}} - } - catch {R 0 fcall f1 0} e - assert_match {*Can not run script on cluster, 'no-cluster' flag is set*} $e - } +# test "Function no-cluster flag" { +# R 0 function load {#!lua name=test +# redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-cluster'}} +# } +# catch {R 0 fcall f1 0} e +# assert_match {*Can not run script on cluster, 'no-cluster' flag is set*} $e +# } - test "Script no-cluster flag" { - catch { - R 0 eval {#!lua flags=no-cluster - return 1 - } 0 - } e +# test "Script no-cluster flag" { +# catch { +# R 0 eval {#!lua flags=no-cluster +# return 1 +# } 0 +# } e - assert_match {*Can not run script on cluster, 'no-cluster' flag is set*} $e - } -} +# assert_match {*Can not run script on cluster, 'no-cluster' flag is set*} $e +# } +# } diff --git a/tests/unit/cluster/sharded-pubsub.tcl b/tests/unit/cluster/sharded-pubsub.tcl index 0347ac65351..a7013e84ece 100644 --- a/tests/unit/cluster/sharded-pubsub.tcl +++ b/tests/unit/cluster/sharded-pubsub.tcl @@ -1,67 +1,67 @@ -# -# Copyright (c) 2009-Present, Redis Ltd. -# All rights reserved. -# -# Licensed under your choice of (a) the Redis Source Available License 2.0 -# (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the -# GNU Affero General Public License v3 (AGPLv3). -# -# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. -# +# # +# # Copyright (c) 2009-Present, Redis Ltd. +# # All rights reserved. +# # +# # Licensed under your choice of (a) the Redis Source Available License 2.0 +# # (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the +# # GNU Affero General Public License v3 (AGPLv3). +# # +# # Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. +# # -start_cluster 1 1 {tags {external:skip cluster}} { - set primary_id 0 - set replica1_id 1 +# start_cluster 1 1 {tags {external:skip cluster}} { +# set primary_id 0 +# set replica1_id 1 - set primary [Rn $primary_id] - set replica [Rn $replica1_id] +# set primary [Rn $primary_id] +# set replica [Rn $replica1_id] - test "Sharded pubsub publish behavior within multi/exec" { - foreach {node} {primary replica} { - set node [set $node] - $node MULTI - $node SPUBLISH ch1 "hello" - $node EXEC - } - } +# test "Sharded pubsub publish behavior within multi/exec" { +# foreach {node} {primary replica} { +# set node [set $node] +# $node MULTI +# $node SPUBLISH ch1 "hello" +# $node EXEC +# } +# } - test "Sharded pubsub within multi/exec with cross slot operation" { - $primary MULTI - $primary SPUBLISH ch1 "hello" - $primary GET foo - catch {[$primary EXEC]} err - assert_match {CROSSSLOT*} $err - } +# test "Sharded pubsub within multi/exec with cross slot operation" { +# $primary MULTI +# $primary SPUBLISH ch1 "hello" +# $primary GET foo +# catch {[$primary EXEC]} err +# assert_match {CROSSSLOT*} $err +# } - test "Sharded pubsub publish behavior within multi/exec with read operation on primary" { - $primary MULTI - $primary SPUBLISH foo "hello" - $primary GET foo - $primary EXEC - } {0 {}} +# test "Sharded pubsub publish behavior within multi/exec with read operation on primary" { +# $primary MULTI +# $primary SPUBLISH foo "hello" +# $primary GET foo +# $primary EXEC +# } {0 {}} - test "Sharded pubsub publish behavior within multi/exec with read operation on replica" { - $replica MULTI - $replica SPUBLISH foo "hello" - catch {[$replica GET foo]} err - assert_match {MOVED*} $err - catch {[$replica EXEC]} err - assert_match {EXECABORT*} $err - } +# test "Sharded pubsub publish behavior within multi/exec with read operation on replica" { +# $replica MULTI +# $replica SPUBLISH foo "hello" +# catch {[$replica GET foo]} err +# assert_match {MOVED*} $err +# catch {[$replica EXEC]} err +# assert_match {EXECABORT*} $err +# } - test "Sharded pubsub publish behavior within multi/exec with write operation on primary" { - $primary MULTI - $primary SPUBLISH foo "hello" - $primary SET foo bar - $primary EXEC - } {0 OK} +# test "Sharded pubsub publish behavior within multi/exec with write operation on primary" { +# $primary MULTI +# $primary SPUBLISH foo "hello" +# $primary SET foo bar +# $primary EXEC +# } {0 OK} - test "Sharded pubsub publish behavior within multi/exec with write operation on replica" { - $replica MULTI - $replica SPUBLISH foo "hello" - catch {[$replica SET foo bar]} err - assert_match {MOVED*} $err - catch {[$replica EXEC]} err - assert_match {EXECABORT*} $err - } -} +# test "Sharded pubsub publish behavior within multi/exec with write operation on replica" { +# $replica MULTI +# $replica SPUBLISH foo "hello" +# catch {[$replica SET foo bar]} err +# assert_match {MOVED*} $err +# catch {[$replica EXEC]} err +# assert_match {EXECABORT*} $err +# } +# } diff --git a/tests/unit/cluster/slot-stats.tcl b/tests/unit/cluster/slot-stats.tcl index cece3eebc0c..055d224473e 100644 --- a/tests/unit/cluster/slot-stats.tcl +++ b/tests/unit/cluster/slot-stats.tcl @@ -1,988 +1,988 @@ -# -# Copyright (c) 2009-Present, Redis Ltd. -# All rights reserved. -# -# Copyright (c) 2024-present, Valkey contributors. -# All rights reserved. -# -# Licensed under your choice of (a) the Redis Source Available License 2.0 -# (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the -# GNU Affero General Public License v3 (AGPLv3). -# -# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. -# - -# Integration tests for CLUSTER SLOT-STATS command. - -# ----------------------------------------------------------------------------- -# Helper functions for CLUSTER SLOT-STATS test cases. -# ----------------------------------------------------------------------------- - -# Converts array RESP response into a dict. -# This is useful for many test cases, where unnecessary nesting is removed. -proc convert_array_into_dict {slot_stats} { - set res [dict create] - foreach slot_stat $slot_stats { - # slot_stat is an array of size 2, where 0th index represents (int) slot, - # and 1st index represents (map) usage statistics. - dict set res [lindex $slot_stat 0] [lindex $slot_stat 1] - } - return $res -} - -proc get_cmdstat_usec {cmd r} { - set cmdstatline [cmdrstat $cmd r] - regexp "usec=(.*?),usec_per_call=(.*?),rejected_calls=0,failed_calls=0" $cmdstatline -> usec _ - return $usec -} - -proc initialize_expected_slots_dict {} { - set expected_slots [dict create] - for {set i 0} {$i < 16384} {incr i 1} { - dict set expected_slots $i 0 - } - return $expected_slots -} - -proc initialize_expected_slots_dict_with_range {start_slot end_slot} { - assert {$start_slot <= $end_slot} - set expected_slots [dict create] - for {set i $start_slot} {$i <= $end_slot} {incr i 1} { - dict set expected_slots $i 0 - } - return $expected_slots -} - -proc assert_empty_slot_stats {slot_stats metrics_to_assert} { - set slot_stats [convert_array_into_dict $slot_stats] - dict for {slot stats} $slot_stats { - foreach metric_name $metrics_to_assert { - set metric_value [dict get $stats $metric_name] - assert {$metric_value == 0} - } - } -} - -proc assert_empty_slot_stats_with_exception {slot_stats exception_slots metrics_to_assert} { - set slot_stats [convert_array_into_dict $slot_stats] - dict for {slot stats} $exception_slots { - assert {[dict exists $slot_stats $slot]} ;# slot_stats must contain the expected slots. - } - dict for {slot stats} $slot_stats { - if {[dict exists $exception_slots $slot]} { - foreach metric_name $metrics_to_assert { - set metric_value [dict get $exception_slots $slot $metric_name] - assert {[dict get $stats $metric_name] == $metric_value} - } - } else { - dict for {metric value} $stats { - assert {$value == 0} - } - } - } -} - -proc assert_equal_slot_stats {slot_stats_1 slot_stats_2 deterministic_metrics non_deterministic_metrics} { - set slot_stats_1 [convert_array_into_dict $slot_stats_1] - set slot_stats_2 [convert_array_into_dict $slot_stats_2] - assert {[dict size $slot_stats_1] == [dict size $slot_stats_2]} - - dict for {slot stats_1} $slot_stats_1 { - assert {[dict exists $slot_stats_2 $slot]} - set stats_2 [dict get $slot_stats_2 $slot] - - # For deterministic metrics, we assert their equality. - foreach metric $deterministic_metrics { - assert {[dict get $stats_1 $metric] == [dict get $stats_2 $metric]} - } - # For non-deterministic metrics, we assert their non-zeroness as a best-effort. - foreach metric $non_deterministic_metrics { - assert {([dict get $stats_1 $metric] == 0 && [dict get $stats_2 $metric] == 0) || \ - ([dict get $stats_1 $metric] != 0 && [dict get $stats_2 $metric] != 0)} - } - } -} - -proc assert_all_slots_have_been_seen {expected_slots} { - dict for {k v} $expected_slots { - assert {$v == 1} - } -} - -proc assert_slot_visibility {slot_stats expected_slots} { - set slot_stats [convert_array_into_dict $slot_stats] - dict for {slot _} $slot_stats { - assert {[dict exists $expected_slots $slot]} - dict set expected_slots $slot 1 - } - - assert_all_slots_have_been_seen $expected_slots -} - -proc assert_slot_stats_monotonic_order {slot_stats orderby is_desc} { - # For Tcl dict, the order of iteration is the order in which the keys were inserted into the dictionary - # Thus, the response ordering is preserved upon calling 'convert_array_into_dict()'. - # Source: https://www.tcl.tk/man/tcl8.6.11/TclCmd/dict.htm - set slot_stats [convert_array_into_dict $slot_stats] - set prev_metric -1 - dict for {_ stats} $slot_stats { - set curr_metric [dict get $stats $orderby] - if {$prev_metric != -1} { - if {$is_desc == 1} { - assert {$prev_metric >= $curr_metric} - } else { - assert {$prev_metric <= $curr_metric} - } - } - set prev_metric $curr_metric - } -} - -proc assert_slot_stats_monotonic_descent {slot_stats orderby} { - assert_slot_stats_monotonic_order $slot_stats $orderby 1 -} - -proc assert_slot_stats_monotonic_ascent {slot_stats orderby} { - assert_slot_stats_monotonic_order $slot_stats $orderby 0 -} - -proc wait_for_replica_key_exists {key key_count} { - wait_for_condition 1000 50 { - [R 1 exists $key] eq "$key_count" - } else { - fail "Test key was not replicated" - } -} - -# ----------------------------------------------------------------------------- -# Test cases for CLUSTER SLOT-STATS cpu-usec metric correctness. -# ----------------------------------------------------------------------------- - -start_cluster 1 0 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { - - # Define shared variables. - set key "FOO" - set key_slot [R 0 cluster keyslot $key] - set key_secondary "FOO2" - set key_secondary_slot [R 0 cluster keyslot $key_secondary] - set metrics_to_assert [list cpu-usec] - - test "CLUSTER SLOT-STATS cpu-usec reset upon CONFIG RESETSTAT." { - R 0 SET $key VALUE - R 0 DEL $key - R 0 CONFIG RESETSTAT - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec reset upon slot migration." { - R 0 SET $key VALUE - - R 0 CLUSTER DELSLOTS $key_slot - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - - R 0 CLUSTER ADDSLOTS $key_slot - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for non-slot specific commands." { - R 0 INFO - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for slot specific commands." { - R 0 SET $key VALUE - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set usec [get_cmdstat_usec set r] - set expected_slot_stats [ - dict create $key_slot [ - dict create cpu-usec $usec - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for blocking commands, unblocked on keyspace update." { - # Blocking command with no timeout. Only keyspace update can unblock this client. - set rd [redis_deferring_client] - $rd BLPOP $key 0 - wait_for_blocked_clients_count 1 - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - # When the client is blocked, no accumulation is made. This behaviour is identical to INFO COMMANDSTATS. - assert_empty_slot_stats $slot_stats $metrics_to_assert - - # Unblocking command. - R 0 LPUSH $key value - wait_for_blocked_clients_count 0 - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set lpush_usec [get_cmdstat_usec lpush r] - set blpop_usec [get_cmdstat_usec blpop r] - - # Assert that both blocking and non-blocking command times have been accumulated. - set expected_slot_stats [ - dict create $key_slot [ - dict create cpu-usec [expr $lpush_usec + $blpop_usec] - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for blocking commands, unblocked on timeout." { - # Blocking command with 0.5 seconds timeout. - set rd [redis_deferring_client] - $rd BLPOP $key 0.5 - - # Confirm that the client is blocked, then unblocked within 1 second. - wait_for_blocked_clients_count 1 - wait_for_blocked_clients_count 0 - - # Assert that the blocking command time has been accumulated. - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set blpop_usec [get_cmdstat_usec blpop r] - set expected_slot_stats [ - dict create $key_slot [ - dict create cpu-usec $blpop_usec - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for transactions." { - set r1 [redis_client] - $r1 MULTI - $r1 SET $key value - $r1 GET $key - - # CPU metric is not accumulated until EXEC is reached. This behaviour is identical to INFO COMMANDSTATS. - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - - # Execute transaction, and assert that all nested command times have been accumulated. - $r1 EXEC - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set exec_usec [get_cmdstat_usec exec r] - set expected_slot_stats [ - dict create $key_slot [ - dict create cpu-usec $exec_usec - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for lua-scripts, without cross-slot keys." { - r eval [format "#!lua - redis.call('set', '%s', 'bar'); redis.call('get', '%s')" $key $key] 0 - - set eval_usec [get_cmdstat_usec eval r] - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - - set expected_slot_stats [ - dict create $key_slot [ - dict create cpu-usec $eval_usec - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for lua-scripts, with cross-slot keys." { - r eval [format "#!lua flags=allow-cross-slot-keys - redis.call('set', '%s', 'bar'); redis.call('get', '%s'); - " $key $key_secondary] 0 - - # For cross-slot, we do not accumulate at all. - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for functions, without cross-slot keys." { - set function_str [format "#!lua name=f1 - redis.register_function{ - function_name='f1', - callback=function() redis.call('set', '%s', '1') redis.call('get', '%s') end - }" $key $key] - r function load replace $function_str - r fcall f1 0 - - set fcall_usec [get_cmdstat_usec fcall r] - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - - set expected_slot_stats [ - dict create $key_slot [ - dict create cpu-usec $fcall_usec - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS cpu-usec for functions, with cross-slot keys." { - set function_str [format "#!lua name=f1 - redis.register_function{ - function_name='f1', - callback=function() redis.call('set', '%s', '1') redis.call('get', '%s') end, - flags={'allow-cross-slot-keys'} - }" $key $key_secondary] - r function load replace $function_str - r fcall f1 0 - - # For cross-slot, we do not accumulate at all. - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL -} - -# ----------------------------------------------------------------------------- -# Test cases for CLUSTER SLOT-STATS network-bytes-in. -# ----------------------------------------------------------------------------- - -start_cluster 1 0 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { - - # Define shared variables. - set key "key" - set key_slot [R 0 cluster keyslot $key] - set metrics_to_assert [list network-bytes-in] - - test "CLUSTER SLOT-STATS network-bytes-in, multi bulk buffer processing." { - # *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 33 bytes. - R 0 SET $key value - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-in 33 - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS network-bytes-in, in-line buffer processing." { - set rd [redis_deferring_client] - # SET key value\r\n --> 15 bytes. - $rd write "SET $key value\r\n" - $rd flush - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-in 15 - ] - ] - - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS network-bytes-in, blocking command." { - set rd [redis_deferring_client] - # *3\r\n$5\r\nblpop\r\n$3\r\nkey\r\n$1\r\n0\r\n --> 31 bytes. - $rd BLPOP $key 0 - wait_for_blocked_clients_count 1 - - # Slot-stats must be empty here, as the client is yet to be unblocked. - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - - # *3\r\n$5\r\nlpush\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 35 bytes. - R 0 LPUSH $key value - wait_for_blocked_clients_count 0 - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-in 66 ;# 31 + 35 bytes. - ] - ] - - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS network-bytes-in, multi-exec transaction." { - set r [redis_client] - # *1\r\n$5\r\nmulti\r\n --> 15 bytes. - $r MULTI - # *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 33 bytes. - assert {[$r SET $key value] eq {QUEUED}} - # *1\r\n$4\r\nexec\r\n --> 14 bytes. - assert {[$r EXEC] eq {OK}} - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-in 62 ;# 15 + 33 + 14 bytes. - ] - ] - - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS network-bytes-in, non slot specific command." { - R 0 INFO - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS network-bytes-in, pub/sub." { - # PUB/SUB does not get accumulated at per-slot basis, - # as it is cluster-wide and is not slot specific. - set rd [redis_deferring_client] - $rd subscribe channel - R 0 publish channel message - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL -} - -start_cluster 1 1 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { - set channel "channel" - set key_slot [R 0 cluster keyslot $channel] - set metrics_to_assert [list network-bytes-in] - - # Setup replication. - assert {[s -1 role] eq {slave}} - wait_for_condition 1000 50 { - [s -1 master_link_status] eq {up} - } else { - fail "Instance #1 master link status is not up" - } - R 1 readonly - - test "CLUSTER SLOT-STATS network-bytes-in, sharded pub/sub." { - set slot [R 0 cluster keyslot $channel] - set primary [Rn 0] - set replica [Rn 1] - set replica_subcriber [redis_deferring_client -1] - $replica_subcriber SSUBSCRIBE $channel - # *2\r\n$10\r\nssubscribe\r\n$7\r\nchannel\r\n --> 34 bytes. - $primary SPUBLISH $channel hello - # *3\r\n$8\r\nspublish\r\n$7\r\nchannel\r\n$5\r\nhello\r\n --> 42 bytes. - - set slot_stats [$primary CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-in 42 - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - - set slot_stats [$replica CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-in 34 - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL -} - -# ----------------------------------------------------------------------------- -# Test cases for CLUSTER SLOT-STATS network-bytes-out correctness. -# ----------------------------------------------------------------------------- - -start_cluster 1 0 {tags {external:skip cluster}} { - # Define shared variables. - set key "FOO" - set key_slot [R 0 cluster keyslot $key] - set expected_slots_to_key_count [dict create $key_slot 1] - set metrics_to_assert [list network-bytes-out] - R 0 CONFIG SET cluster-slot-stats-enabled yes - - test "CLUSTER SLOT-STATS network-bytes-out, for non-slot specific commands." { - R 0 INFO - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS network-bytes-out, for slot specific commands." { - R 0 SET $key value - # +OK\r\n --> 5 bytes - - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-out 5 - ] - ] - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL - - test "CLUSTER SLOT-STATS network-bytes-out, blocking commands." { - set rd [redis_deferring_client] - $rd BLPOP $key 0 - wait_for_blocked_clients_count 1 - - # Assert empty slot stats here, since COB is yet to be flushed due to the block. - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - - # Unblock the command. - # LPUSH client) :1\r\n --> 4 bytes. - # BLPOP client) *2\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 24 bytes, upon unblocking. - R 0 LPUSH $key value - wait_for_blocked_clients_count 0 - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-out 28 ;# 4 + 24 bytes. - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - R 0 CONFIG RESETSTAT - R 0 FLUSHALL -} - -start_cluster 1 1 {tags {external:skip cluster}} { - - # Define shared variables. - set key "FOO" - set key_slot [R 0 CLUSTER KEYSLOT $key] - set metrics_to_assert [list network-bytes-out] - R 0 CONFIG SET cluster-slot-stats-enabled yes - - # Setup replication. - assert {[s -1 role] eq {slave}} - wait_for_condition 1000 50 { - [s -1 master_link_status] eq {up} - } else { - fail "Instance #1 master link status is not up" - } - R 1 readonly - - test "CLUSTER SLOT-STATS network-bytes-out, replication stream egress." { - assert_equal [R 0 SET $key VALUE] {OK} - # Local client) +OK\r\n --> 5 bytes. - # Replication stream) *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 33 bytes. - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-out 38 ;# 5 + 33 bytes. - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } -} - -start_cluster 1 1 {tags {external:skip cluster}} { - - # Define shared variables. - set channel "channel" - set key_slot [R 0 cluster keyslot $channel] - set channel_secondary "channel2" - set key_slot_secondary [R 0 cluster keyslot $channel_secondary] - set metrics_to_assert [list network-bytes-out] - R 0 CONFIG SET cluster-slot-stats-enabled yes - - test "CLUSTER SLOT-STATS network-bytes-out, sharded pub/sub, single channel." { - set slot [R 0 cluster keyslot $channel] - set publisher [Rn 0] - set subscriber [redis_client] - set replica [redis_deferring_client -1] - - # Subscriber client) *3\r\n$10\r\nssubscribe\r\n$7\r\nchannel\r\n:1\r\n --> 38 bytes - $subscriber SSUBSCRIBE $channel - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-out 38 - ] - ] - R 0 CONFIG RESETSTAT - - # Publisher client) :1\r\n --> 4 bytes. - # Subscriber client) *3\r\n$8\r\nsmessage\r\n$7\r\nchannel\r\n$5\r\nhello\r\n --> 42 bytes. - assert_equal 1 [$publisher SPUBLISH $channel hello] - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create $key_slot [ - dict create network-bytes-out 46 ;# 4 + 42 bytes. - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - $subscriber QUIT - R 0 FLUSHALL - R 0 CONFIG RESETSTAT - - test "CLUSTER SLOT-STATS network-bytes-out, sharded pub/sub, cross-slot channels." { - set slot [R 0 cluster keyslot $channel] - set publisher [Rn 0] - set subscriber [redis_client] - set replica [redis_deferring_client -1] - - # Stack multi-slot subscriptions against a single client. - # For primary channel; - # Subscriber client) *3\r\n$10\r\nssubscribe\r\n$7\r\nchannel\r\n:1\r\n --> 38 bytes - # For secondary channel; - # Subscriber client) *3\r\n$10\r\nssubscribe\r\n$8\r\nchannel2\r\n:1\r\n --> 39 bytes - $subscriber SSUBSCRIBE $channel - $subscriber SSUBSCRIBE $channel_secondary - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create \ - $key_slot [ \ - dict create network-bytes-out 38 - ] \ - $key_slot_secondary [ \ - dict create network-bytes-out 39 - ] - ] - R 0 CONFIG RESETSTAT - - # For primary channel; - # Publisher client) :1\r\n --> 4 bytes. - # Subscriber client) *3\r\n$8\r\nsmessage\r\n$7\r\nchannel\r\n$5\r\nhello\r\n --> 42 bytes. - # For secondary channel; - # Publisher client) :1\r\n --> 4 bytes. - # Subscriber client) *3\r\n$8\r\nsmessage\r\n$8\r\nchannel2\r\n$5\r\nhello\r\n --> 43 bytes. - assert_equal 1 [$publisher SPUBLISH $channel hello] - assert_equal 1 [$publisher SPUBLISH $channel_secondary hello] - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - set expected_slot_stats [ - dict create \ - $key_slot [ \ - dict create network-bytes-out 46 ;# 4 + 42 bytes. - ] \ - $key_slot_secondary [ \ - dict create network-bytes-out 47 ;# 4 + 43 bytes. - ] - ] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } -} - -# ----------------------------------------------------------------------------- -# Test cases for CLUSTER SLOT-STATS key-count metric correctness. -# ----------------------------------------------------------------------------- - -start_cluster 1 0 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { - - # Define shared variables. - set key "FOO" - set key_slot [R 0 cluster keyslot $key] - set metrics_to_assert [list key-count] - set expected_slot_stats [ - dict create $key_slot [ - dict create key-count 1 - ] - ] - - test "CLUSTER SLOT-STATS contains default value upon redis-server startup" { - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - - test "CLUSTER SLOT-STATS contains correct metrics upon key introduction" { - R 0 SET $key TEST - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - - test "CLUSTER SLOT-STATS contains correct metrics upon key mutation" { - R 0 SET $key NEW_VALUE - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert - } - - test "CLUSTER SLOT-STATS contains correct metrics upon key deletion" { - R 0 DEL $key - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats $slot_stats $metrics_to_assert - } - - test "CLUSTER SLOT-STATS slot visibility based on slot ownership changes" { - R 0 CONFIG SET cluster-require-full-coverage no - - R 0 CLUSTER DELSLOTS $key_slot - set expected_slots [initialize_expected_slots_dict] - dict unset expected_slots $key_slot - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert {[dict size $expected_slots] == 16383} - assert_slot_visibility $slot_stats $expected_slots - - R 0 CLUSTER ADDSLOTS $key_slot - set expected_slots [initialize_expected_slots_dict] - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert {[dict size $expected_slots] == 16384} - assert_slot_visibility $slot_stats $expected_slots - } -} - -# ----------------------------------------------------------------------------- -# Test cases for CLUSTER SLOT-STATS SLOTSRANGE sub-argument. -# ----------------------------------------------------------------------------- - -start_cluster 1 0 {tags {external:skip cluster}} { - - test "CLUSTER SLOT-STATS SLOTSRANGE all slots present" { - set start_slot 100 - set end_slot 102 - set expected_slots [initialize_expected_slots_dict_with_range $start_slot $end_slot] - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE $start_slot $end_slot] - assert_slot_visibility $slot_stats $expected_slots - } - - test "CLUSTER SLOT-STATS SLOTSRANGE some slots missing" { - set start_slot 100 - set end_slot 102 - set expected_slots [initialize_expected_slots_dict_with_range $start_slot $end_slot] - - R 0 CLUSTER DELSLOTS $start_slot - dict unset expected_slots $start_slot - - set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE $start_slot $end_slot] - assert_slot_visibility $slot_stats $expected_slots - } -} - -# ----------------------------------------------------------------------------- -# Test cases for CLUSTER SLOT-STATS ORDERBY sub-argument. -# ----------------------------------------------------------------------------- - -start_cluster 1 0 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { - - set metrics [list "key-count" "cpu-usec" "network-bytes-in" "network-bytes-out"] - - # SET keys for target hashslots, to encourage ordering. - set hash_tags [list 0 1 2 3 4] - set num_keys 1 - foreach hash_tag $hash_tags { - for {set i 0} {$i < $num_keys} {incr i 1} { - R 0 SET "$i{$hash_tag}" VALUE - } - incr num_keys 1 - } - - # SET keys for random hashslots, for random noise. - set num_keys 0 - while {$num_keys < 1000} { - set random_key [randomInt 16384] - R 0 SET $random_key VALUE - incr num_keys 1 - } - - test "CLUSTER SLOT-STATS ORDERBY DESC correct ordering" { - foreach orderby $metrics { - set slot_stats [R 0 CLUSTER SLOT-STATS ORDERBY $orderby DESC] - assert_slot_stats_monotonic_descent $slot_stats $orderby - } - } - - test "CLUSTER SLOT-STATS ORDERBY ASC correct ordering" { - foreach orderby $metrics { - set slot_stats [R 0 CLUSTER SLOT-STATS ORDERBY $orderby ASC] - assert_slot_stats_monotonic_ascent $slot_stats $orderby - } - } - - test "CLUSTER SLOT-STATS ORDERBY LIMIT correct response pagination, where limit is less than number of assigned slots" { - R 0 FLUSHALL SYNC - R 0 CONFIG RESETSTAT - - foreach orderby $metrics { - set limit 5 - set slot_stats_desc [R 0 CLUSTER SLOT-STATS ORDERBY $orderby LIMIT $limit DESC] - set slot_stats_asc [R 0 CLUSTER SLOT-STATS ORDERBY $orderby LIMIT $limit ASC] - set slot_stats_desc_length [llength $slot_stats_desc] - set slot_stats_asc_length [llength $slot_stats_asc] - assert {$limit == $slot_stats_desc_length && $limit == $slot_stats_asc_length} - - # All slot statistics have been reset to 0, so we will order by slot in ascending order. - set expected_slots [dict create 0 0 1 0 2 0 3 0 4 0] - assert_slot_visibility $slot_stats_desc $expected_slots - assert_slot_visibility $slot_stats_asc $expected_slots - } - } - - test "CLUSTER SLOT-STATS ORDERBY LIMIT correct response pagination, where limit is greater than number of assigned slots" { - R 0 CONFIG SET cluster-require-full-coverage no - R 0 FLUSHALL SYNC - R 0 CLUSTER FLUSHSLOTS - R 0 CLUSTER ADDSLOTS 100 101 - - foreach orderby $metrics { - set num_assigned_slots 2 - set limit 5 - set slot_stats_desc [R 0 CLUSTER SLOT-STATS ORDERBY $orderby LIMIT $limit DESC] - set slot_stats_asc [R 0 CLUSTER SLOT-STATS ORDERBY $orderby LIMIT $limit ASC] - set slot_stats_desc_length [llength $slot_stats_desc] - set slot_stats_asc_length [llength $slot_stats_asc] - set expected_response_length [expr min($num_assigned_slots, $limit)] - assert {$expected_response_length == $slot_stats_desc_length && $expected_response_length == $slot_stats_asc_length} - - set expected_slots [dict create 100 0 101 0] - assert_slot_visibility $slot_stats_desc $expected_slots - assert_slot_visibility $slot_stats_asc $expected_slots - } - } - - test "CLUSTER SLOT-STATS ORDERBY arg sanity check." { - # Non-existent argument. - assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY key-count non-existent-arg} - # Negative LIMIT. - assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY key-count DESC LIMIT -1} - # Non-existent ORDERBY metric. - assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY non-existent-metric} - # When cluster-slot-stats-enabled config is disabled, you cannot sort using advanced metrics. - R 0 CONFIG SET cluster-slot-stats-enabled no - set orderby "cpu-usec" - assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY $orderby} - set orderby "network-bytes-in" - assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY $orderby} - set orderby "network-bytes-out" - assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY $orderby} - } - -} - -# ----------------------------------------------------------------------------- -# Test cases for CLUSTER SLOT-STATS replication. -# ----------------------------------------------------------------------------- - -start_cluster 1 1 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { - - # Define shared variables. - set key "key" - set key_slot [R 0 CLUSTER KEYSLOT $key] - set primary [Rn 0] - set replica [Rn 1] - - # For replication, assertions are split between deterministic and non-deterministic metrics. - # * For deterministic metrics, strict equality assertions are made. - # * For non-deterministic metrics, non-zeroness assertions are made. - # Non-zeroness as in, both primary and replica should either have some value, or no value at all. - # - # * key-count is deterministic between primary and its replica. - # * cpu-usec is non-deterministic between primary and its replica. - # * network-bytes-in is deterministic between primary and its replica. - # * network-bytes-out will remain empty in the replica, since primary client do not receive replies, unless for replicationSendAck(). - set deterministic_metrics [list key-count network-bytes-in] - set non_deterministic_metrics [list cpu-usec] - set empty_metrics [list network-bytes-out] - - # Setup replication. - assert {[s -1 role] eq {slave}} - wait_for_condition 1000 50 { - [s -1 master_link_status] eq {up} - } else { - fail "Instance #1 master link status is not up" - } - R 1 readonly - - test "CLUSTER SLOT-STATS metrics replication for new keys" { - # *3\r\n$3\r\nset\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 33 bytes. - R 0 SET $key VALUE - - set expected_slot_stats [ - dict create $key_slot [ - dict create key-count 1 network-bytes-in 33 - ] - ] - set slot_stats_master [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats_with_exception $slot_stats_master $expected_slot_stats $deterministic_metrics - - wait_for_condition 500 10 { - [string match {*calls=1,*} [cmdrstat set $replica]] - } else { - fail "Replica did not receive the command." - } - set slot_stats_replica [R 1 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_equal_slot_stats $slot_stats_master $slot_stats_replica $deterministic_metrics $non_deterministic_metrics - assert_empty_slot_stats $slot_stats_replica $empty_metrics - } - R 0 CONFIG RESETSTAT - R 1 CONFIG RESETSTAT - - test "CLUSTER SLOT-STATS metrics replication for existing keys" { - # *3\r\n$3\r\nset\r\n$3\r\nkey\r\n$13\r\nvalue_updated\r\n --> 42 bytes. - R 0 SET $key VALUE_UPDATED - - set expected_slot_stats [ - dict create $key_slot [ - dict create key-count 1 network-bytes-in 42 - ] - ] - set slot_stats_master [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats_with_exception $slot_stats_master $expected_slot_stats $deterministic_metrics - - wait_for_condition 500 10 { - [string match {*calls=1,*} [cmdrstat set $replica]] - } else { - fail "Replica did not receive the command." - } - set slot_stats_replica [R 1 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_equal_slot_stats $slot_stats_master $slot_stats_replica $deterministic_metrics $non_deterministic_metrics - assert_empty_slot_stats $slot_stats_replica $empty_metrics - } - R 0 CONFIG RESETSTAT - R 1 CONFIG RESETSTAT - - test "CLUSTER SLOT-STATS metrics replication for deleting keys" { - # *2\r\n$3\r\ndel\r\n$3\r\nkey\r\n --> 22 bytes. - R 0 DEL $key - - set expected_slot_stats [ - dict create $key_slot [ - dict create key-count 0 network-bytes-in 22 - ] - ] - set slot_stats_master [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_empty_slot_stats_with_exception $slot_stats_master $expected_slot_stats $deterministic_metrics - - wait_for_condition 500 10 { - [string match {*calls=1,*} [cmdrstat del $replica]] - } else { - fail "Replica did not receive the command." - } - set slot_stats_replica [R 1 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] - assert_equal_slot_stats $slot_stats_master $slot_stats_replica $deterministic_metrics $non_deterministic_metrics - assert_empty_slot_stats $slot_stats_replica $empty_metrics - } - R 0 CONFIG RESETSTAT - R 1 CONFIG RESETSTAT -} +# # +# # Copyright (c) 2009-Present, Redis Ltd. +# # All rights reserved. +# # +# # Copyright (c) 2024-present, Valkey contributors. +# # All rights reserved. +# # +# # Licensed under your choice of (a) the Redis Source Available License 2.0 +# # (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the +# # GNU Affero General Public License v3 (AGPLv3). +# # +# # Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. +# # + +# # Integration tests for CLUSTER SLOT-STATS command. + +# # ----------------------------------------------------------------------------- +# # Helper functions for CLUSTER SLOT-STATS test cases. +# # ----------------------------------------------------------------------------- + +# # Converts array RESP response into a dict. +# # This is useful for many test cases, where unnecessary nesting is removed. +# proc convert_array_into_dict {slot_stats} { +# set res [dict create] +# foreach slot_stat $slot_stats { +# # slot_stat is an array of size 2, where 0th index represents (int) slot, +# # and 1st index represents (map) usage statistics. +# dict set res [lindex $slot_stat 0] [lindex $slot_stat 1] +# } +# return $res +# } + +# proc get_cmdstat_usec {cmd r} { +# set cmdstatline [cmdrstat $cmd r] +# regexp "usec=(.*?),usec_per_call=(.*?),rejected_calls=0,failed_calls=0" $cmdstatline -> usec _ +# return $usec +# } + +# proc initialize_expected_slots_dict {} { +# set expected_slots [dict create] +# for {set i 0} {$i < 16384} {incr i 1} { +# dict set expected_slots $i 0 +# } +# return $expected_slots +# } + +# proc initialize_expected_slots_dict_with_range {start_slot end_slot} { +# assert {$start_slot <= $end_slot} +# set expected_slots [dict create] +# for {set i $start_slot} {$i <= $end_slot} {incr i 1} { +# dict set expected_slots $i 0 +# } +# return $expected_slots +# } + +# proc assert_empty_slot_stats {slot_stats metrics_to_assert} { +# set slot_stats [convert_array_into_dict $slot_stats] +# dict for {slot stats} $slot_stats { +# foreach metric_name $metrics_to_assert { +# set metric_value [dict get $stats $metric_name] +# assert {$metric_value == 0} +# } +# } +# } + +# proc assert_empty_slot_stats_with_exception {slot_stats exception_slots metrics_to_assert} { +# set slot_stats [convert_array_into_dict $slot_stats] +# dict for {slot stats} $exception_slots { +# assert {[dict exists $slot_stats $slot]} ;# slot_stats must contain the expected slots. +# } +# dict for {slot stats} $slot_stats { +# if {[dict exists $exception_slots $slot]} { +# foreach metric_name $metrics_to_assert { +# set metric_value [dict get $exception_slots $slot $metric_name] +# assert {[dict get $stats $metric_name] == $metric_value} +# } +# } else { +# dict for {metric value} $stats { +# assert {$value == 0} +# } +# } +# } +# } + +# proc assert_equal_slot_stats {slot_stats_1 slot_stats_2 deterministic_metrics non_deterministic_metrics} { +# set slot_stats_1 [convert_array_into_dict $slot_stats_1] +# set slot_stats_2 [convert_array_into_dict $slot_stats_2] +# assert {[dict size $slot_stats_1] == [dict size $slot_stats_2]} + +# dict for {slot stats_1} $slot_stats_1 { +# assert {[dict exists $slot_stats_2 $slot]} +# set stats_2 [dict get $slot_stats_2 $slot] + +# # For deterministic metrics, we assert their equality. +# foreach metric $deterministic_metrics { +# assert {[dict get $stats_1 $metric] == [dict get $stats_2 $metric]} +# } +# # For non-deterministic metrics, we assert their non-zeroness as a best-effort. +# foreach metric $non_deterministic_metrics { +# assert {([dict get $stats_1 $metric] == 0 && [dict get $stats_2 $metric] == 0) || \ +# ([dict get $stats_1 $metric] != 0 && [dict get $stats_2 $metric] != 0)} +# } +# } +# } + +# proc assert_all_slots_have_been_seen {expected_slots} { +# dict for {k v} $expected_slots { +# assert {$v == 1} +# } +# } + +# proc assert_slot_visibility {slot_stats expected_slots} { +# set slot_stats [convert_array_into_dict $slot_stats] +# dict for {slot _} $slot_stats { +# assert {[dict exists $expected_slots $slot]} +# dict set expected_slots $slot 1 +# } + +# assert_all_slots_have_been_seen $expected_slots +# } + +# proc assert_slot_stats_monotonic_order {slot_stats orderby is_desc} { +# # For Tcl dict, the order of iteration is the order in which the keys were inserted into the dictionary +# # Thus, the response ordering is preserved upon calling 'convert_array_into_dict()'. +# # Source: https://www.tcl.tk/man/tcl8.6.11/TclCmd/dict.htm +# set slot_stats [convert_array_into_dict $slot_stats] +# set prev_metric -1 +# dict for {_ stats} $slot_stats { +# set curr_metric [dict get $stats $orderby] +# if {$prev_metric != -1} { +# if {$is_desc == 1} { +# assert {$prev_metric >= $curr_metric} +# } else { +# assert {$prev_metric <= $curr_metric} +# } +# } +# set prev_metric $curr_metric +# } +# } + +# proc assert_slot_stats_monotonic_descent {slot_stats orderby} { +# assert_slot_stats_monotonic_order $slot_stats $orderby 1 +# } + +# proc assert_slot_stats_monotonic_ascent {slot_stats orderby} { +# assert_slot_stats_monotonic_order $slot_stats $orderby 0 +# } + +# proc wait_for_replica_key_exists {key key_count} { +# wait_for_condition 1000 50 { +# [R 1 exists $key] eq "$key_count" +# } else { +# fail "Test key was not replicated" +# } +# } + +# # ----------------------------------------------------------------------------- +# # Test cases for CLUSTER SLOT-STATS cpu-usec metric correctness. +# # ----------------------------------------------------------------------------- + +# start_cluster 1 0 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { + +# # Define shared variables. +# set key "FOO" +# set key_slot [R 0 cluster keyslot $key] +# set key_secondary "FOO2" +# set key_secondary_slot [R 0 cluster keyslot $key_secondary] +# set metrics_to_assert [list cpu-usec] + +# test "CLUSTER SLOT-STATS cpu-usec reset upon CONFIG RESETSTAT." { +# R 0 SET $key VALUE +# R 0 DEL $key +# R 0 CONFIG RESETSTAT +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats $slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL + +# test "CLUSTER SLOT-STATS cpu-usec reset upon slot migration." { +# R 0 SET $key VALUE + +# R 0 CLUSTER DELSLOTS $key_slot +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats $slot_stats $metrics_to_assert + +# R 0 CLUSTER ADDSLOTS $key_slot +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats $slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL + +# test "CLUSTER SLOT-STATS cpu-usec for non-slot specific commands." { +# R 0 INFO +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats $slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL + +# test "CLUSTER SLOT-STATS cpu-usec for slot specific commands." { +# R 0 SET $key VALUE +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# set usec [get_cmdstat_usec set r] +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create cpu-usec $usec +# ] +# ] +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL + +# test "CLUSTER SLOT-STATS cpu-usec for blocking commands, unblocked on keyspace update." { +# # Blocking command with no timeout. Only keyspace update can unblock this client. +# set rd [redis_deferring_client] +# $rd BLPOP $key 0 +# wait_for_blocked_clients_count 1 +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# # When the client is blocked, no accumulation is made. This behaviour is identical to INFO COMMANDSTATS. +# assert_empty_slot_stats $slot_stats $metrics_to_assert + +# # Unblocking command. +# R 0 LPUSH $key value +# wait_for_blocked_clients_count 0 + +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# set lpush_usec [get_cmdstat_usec lpush r] +# set blpop_usec [get_cmdstat_usec blpop r] + +# # Assert that both blocking and non-blocking command times have been accumulated. +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create cpu-usec [expr $lpush_usec + $blpop_usec] +# ] +# ] +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL + +# test "CLUSTER SLOT-STATS cpu-usec for blocking commands, unblocked on timeout." { +# # Blocking command with 0.5 seconds timeout. +# set rd [redis_deferring_client] +# $rd BLPOP $key 0.5 + +# # Confirm that the client is blocked, then unblocked within 1 second. +# wait_for_blocked_clients_count 1 +# wait_for_blocked_clients_count 0 + +# # Assert that the blocking command time has been accumulated. +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# set blpop_usec [get_cmdstat_usec blpop r] +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create cpu-usec $blpop_usec +# ] +# ] +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL + +# test "CLUSTER SLOT-STATS cpu-usec for transactions." { +# set r1 [redis_client] +# $r1 MULTI +# $r1 SET $key value +# $r1 GET $key + +# # CPU metric is not accumulated until EXEC is reached. This behaviour is identical to INFO COMMANDSTATS. +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats $slot_stats $metrics_to_assert + +# # Execute transaction, and assert that all nested command times have been accumulated. +# $r1 EXEC +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# set exec_usec [get_cmdstat_usec exec r] +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create cpu-usec $exec_usec +# ] +# ] +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL + +# test "CLUSTER SLOT-STATS cpu-usec for lua-scripts, without cross-slot keys." { +# r eval [format "#!lua +# redis.call('set', '%s', 'bar'); redis.call('get', '%s')" $key $key] 0 + +# set eval_usec [get_cmdstat_usec eval r] +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] + +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create cpu-usec $eval_usec +# ] +# ] +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL + +# test "CLUSTER SLOT-STATS cpu-usec for lua-scripts, with cross-slot keys." { +# r eval [format "#!lua flags=allow-cross-slot-keys +# redis.call('set', '%s', 'bar'); redis.call('get', '%s'); +# " $key $key_secondary] 0 + +# # For cross-slot, we do not accumulate at all. +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats $slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL + +# test "CLUSTER SLOT-STATS cpu-usec for functions, without cross-slot keys." { +# set function_str [format "#!lua name=f1 +# redis.register_function{ +# function_name='f1', +# callback=function() redis.call('set', '%s', '1') redis.call('get', '%s') end +# }" $key $key] +# r function load replace $function_str +# r fcall f1 0 + +# set fcall_usec [get_cmdstat_usec fcall r] +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] + +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create cpu-usec $fcall_usec +# ] +# ] +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL + +# test "CLUSTER SLOT-STATS cpu-usec for functions, with cross-slot keys." { +# set function_str [format "#!lua name=f1 +# redis.register_function{ +# function_name='f1', +# callback=function() redis.call('set', '%s', '1') redis.call('get', '%s') end, +# flags={'allow-cross-slot-keys'} +# }" $key $key_secondary] +# r function load replace $function_str +# r fcall f1 0 + +# # For cross-slot, we do not accumulate at all. +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats $slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL +# } + +# # ----------------------------------------------------------------------------- +# # Test cases for CLUSTER SLOT-STATS network-bytes-in. +# # ----------------------------------------------------------------------------- + +# start_cluster 1 0 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { + +# # Define shared variables. +# set key "key" +# set key_slot [R 0 cluster keyslot $key] +# set metrics_to_assert [list network-bytes-in] + +# test "CLUSTER SLOT-STATS network-bytes-in, multi bulk buffer processing." { +# # *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 33 bytes. +# R 0 SET $key value + +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create network-bytes-in 33 +# ] +# ] +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL + +# test "CLUSTER SLOT-STATS network-bytes-in, in-line buffer processing." { +# set rd [redis_deferring_client] +# # SET key value\r\n --> 15 bytes. +# $rd write "SET $key value\r\n" +# $rd flush + +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create network-bytes-in 15 +# ] +# ] + +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL + +# test "CLUSTER SLOT-STATS network-bytes-in, blocking command." { +# set rd [redis_deferring_client] +# # *3\r\n$5\r\nblpop\r\n$3\r\nkey\r\n$1\r\n0\r\n --> 31 bytes. +# $rd BLPOP $key 0 +# wait_for_blocked_clients_count 1 + +# # Slot-stats must be empty here, as the client is yet to be unblocked. +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats $slot_stats $metrics_to_assert + +# # *3\r\n$5\r\nlpush\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 35 bytes. +# R 0 LPUSH $key value +# wait_for_blocked_clients_count 0 + +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create network-bytes-in 66 ;# 31 + 35 bytes. +# ] +# ] + +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL + +# test "CLUSTER SLOT-STATS network-bytes-in, multi-exec transaction." { +# set r [redis_client] +# # *1\r\n$5\r\nmulti\r\n --> 15 bytes. +# $r MULTI +# # *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 33 bytes. +# assert {[$r SET $key value] eq {QUEUED}} +# # *1\r\n$4\r\nexec\r\n --> 14 bytes. +# assert {[$r EXEC] eq {OK}} + +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create network-bytes-in 62 ;# 15 + 33 + 14 bytes. +# ] +# ] + +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL + +# test "CLUSTER SLOT-STATS network-bytes-in, non slot specific command." { +# R 0 INFO + +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats $slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL + +# test "CLUSTER SLOT-STATS network-bytes-in, pub/sub." { +# # PUB/SUB does not get accumulated at per-slot basis, +# # as it is cluster-wide and is not slot specific. +# set rd [redis_deferring_client] +# $rd subscribe channel +# R 0 publish channel message + +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats $slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL +# } + +# start_cluster 1 1 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { +# set channel "channel" +# set key_slot [R 0 cluster keyslot $channel] +# set metrics_to_assert [list network-bytes-in] + +# # Setup replication. +# assert {[s -1 role] eq {slave}} +# wait_for_condition 1000 50 { +# [s -1 master_link_status] eq {up} +# } else { +# fail "Instance #1 master link status is not up" +# } +# R 1 readonly + +# test "CLUSTER SLOT-STATS network-bytes-in, sharded pub/sub." { +# set slot [R 0 cluster keyslot $channel] +# set primary [Rn 0] +# set replica [Rn 1] +# set replica_subcriber [redis_deferring_client -1] +# $replica_subcriber SSUBSCRIBE $channel +# # *2\r\n$10\r\nssubscribe\r\n$7\r\nchannel\r\n --> 34 bytes. +# $primary SPUBLISH $channel hello +# # *3\r\n$8\r\nspublish\r\n$7\r\nchannel\r\n$5\r\nhello\r\n --> 42 bytes. + +# set slot_stats [$primary CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create network-bytes-in 42 +# ] +# ] +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert + +# set slot_stats [$replica CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create network-bytes-in 34 +# ] +# ] +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL +# } + +# # ----------------------------------------------------------------------------- +# # Test cases for CLUSTER SLOT-STATS network-bytes-out correctness. +# # ----------------------------------------------------------------------------- + +# start_cluster 1 0 {tags {external:skip cluster}} { +# # Define shared variables. +# set key "FOO" +# set key_slot [R 0 cluster keyslot $key] +# set expected_slots_to_key_count [dict create $key_slot 1] +# set metrics_to_assert [list network-bytes-out] +# R 0 CONFIG SET cluster-slot-stats-enabled yes + +# test "CLUSTER SLOT-STATS network-bytes-out, for non-slot specific commands." { +# R 0 INFO +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats $slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL + +# test "CLUSTER SLOT-STATS network-bytes-out, for slot specific commands." { +# R 0 SET $key value +# # +OK\r\n --> 5 bytes + +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create network-bytes-out 5 +# ] +# ] +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL + +# test "CLUSTER SLOT-STATS network-bytes-out, blocking commands." { +# set rd [redis_deferring_client] +# $rd BLPOP $key 0 +# wait_for_blocked_clients_count 1 + +# # Assert empty slot stats here, since COB is yet to be flushed due to the block. +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats $slot_stats $metrics_to_assert + +# # Unblock the command. +# # LPUSH client) :1\r\n --> 4 bytes. +# # BLPOP client) *2\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 24 bytes, upon unblocking. +# R 0 LPUSH $key value +# wait_for_blocked_clients_count 0 + +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create network-bytes-out 28 ;# 4 + 24 bytes. +# ] +# ] +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } +# R 0 CONFIG RESETSTAT +# R 0 FLUSHALL +# } + +# start_cluster 1 1 {tags {external:skip cluster}} { + +# # Define shared variables. +# set key "FOO" +# set key_slot [R 0 CLUSTER KEYSLOT $key] +# set metrics_to_assert [list network-bytes-out] +# R 0 CONFIG SET cluster-slot-stats-enabled yes + +# # Setup replication. +# assert {[s -1 role] eq {slave}} +# wait_for_condition 1000 50 { +# [s -1 master_link_status] eq {up} +# } else { +# fail "Instance #1 master link status is not up" +# } +# R 1 readonly + +# test "CLUSTER SLOT-STATS network-bytes-out, replication stream egress." { +# assert_equal [R 0 SET $key VALUE] {OK} +# # Local client) +OK\r\n --> 5 bytes. +# # Replication stream) *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 33 bytes. +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create network-bytes-out 38 ;# 5 + 33 bytes. +# ] +# ] +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } +# } + +# start_cluster 1 1 {tags {external:skip cluster}} { + +# # Define shared variables. +# set channel "channel" +# set key_slot [R 0 cluster keyslot $channel] +# set channel_secondary "channel2" +# set key_slot_secondary [R 0 cluster keyslot $channel_secondary] +# set metrics_to_assert [list network-bytes-out] +# R 0 CONFIG SET cluster-slot-stats-enabled yes + +# test "CLUSTER SLOT-STATS network-bytes-out, sharded pub/sub, single channel." { +# set slot [R 0 cluster keyslot $channel] +# set publisher [Rn 0] +# set subscriber [redis_client] +# set replica [redis_deferring_client -1] + +# # Subscriber client) *3\r\n$10\r\nssubscribe\r\n$7\r\nchannel\r\n:1\r\n --> 38 bytes +# $subscriber SSUBSCRIBE $channel +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create network-bytes-out 38 +# ] +# ] +# R 0 CONFIG RESETSTAT + +# # Publisher client) :1\r\n --> 4 bytes. +# # Subscriber client) *3\r\n$8\r\nsmessage\r\n$7\r\nchannel\r\n$5\r\nhello\r\n --> 42 bytes. +# assert_equal 1 [$publisher SPUBLISH $channel hello] +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create network-bytes-out 46 ;# 4 + 42 bytes. +# ] +# ] +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } +# $subscriber QUIT +# R 0 FLUSHALL +# R 0 CONFIG RESETSTAT + +# test "CLUSTER SLOT-STATS network-bytes-out, sharded pub/sub, cross-slot channels." { +# set slot [R 0 cluster keyslot $channel] +# set publisher [Rn 0] +# set subscriber [redis_client] +# set replica [redis_deferring_client -1] + +# # Stack multi-slot subscriptions against a single client. +# # For primary channel; +# # Subscriber client) *3\r\n$10\r\nssubscribe\r\n$7\r\nchannel\r\n:1\r\n --> 38 bytes +# # For secondary channel; +# # Subscriber client) *3\r\n$10\r\nssubscribe\r\n$8\r\nchannel2\r\n:1\r\n --> 39 bytes +# $subscriber SSUBSCRIBE $channel +# $subscriber SSUBSCRIBE $channel_secondary +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# set expected_slot_stats [ +# dict create \ +# $key_slot [ \ +# dict create network-bytes-out 38 +# ] \ +# $key_slot_secondary [ \ +# dict create network-bytes-out 39 +# ] +# ] +# R 0 CONFIG RESETSTAT + +# # For primary channel; +# # Publisher client) :1\r\n --> 4 bytes. +# # Subscriber client) *3\r\n$8\r\nsmessage\r\n$7\r\nchannel\r\n$5\r\nhello\r\n --> 42 bytes. +# # For secondary channel; +# # Publisher client) :1\r\n --> 4 bytes. +# # Subscriber client) *3\r\n$8\r\nsmessage\r\n$8\r\nchannel2\r\n$5\r\nhello\r\n --> 43 bytes. +# assert_equal 1 [$publisher SPUBLISH $channel hello] +# assert_equal 1 [$publisher SPUBLISH $channel_secondary hello] +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# set expected_slot_stats [ +# dict create \ +# $key_slot [ \ +# dict create network-bytes-out 46 ;# 4 + 42 bytes. +# ] \ +# $key_slot_secondary [ \ +# dict create network-bytes-out 47 ;# 4 + 43 bytes. +# ] +# ] +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } +# } + +# # ----------------------------------------------------------------------------- +# # Test cases for CLUSTER SLOT-STATS key-count metric correctness. +# # ----------------------------------------------------------------------------- + +# start_cluster 1 0 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { + +# # Define shared variables. +# set key "FOO" +# set key_slot [R 0 cluster keyslot $key] +# set metrics_to_assert [list key-count] +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create key-count 1 +# ] +# ] + +# test "CLUSTER SLOT-STATS contains default value upon redis-server startup" { +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats $slot_stats $metrics_to_assert +# } + +# test "CLUSTER SLOT-STATS contains correct metrics upon key introduction" { +# R 0 SET $key TEST +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } + +# test "CLUSTER SLOT-STATS contains correct metrics upon key mutation" { +# R 0 SET $key NEW_VALUE +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats_with_exception $slot_stats $expected_slot_stats $metrics_to_assert +# } + +# test "CLUSTER SLOT-STATS contains correct metrics upon key deletion" { +# R 0 DEL $key +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats $slot_stats $metrics_to_assert +# } + +# test "CLUSTER SLOT-STATS slot visibility based on slot ownership changes" { +# R 0 CONFIG SET cluster-require-full-coverage no + +# R 0 CLUSTER DELSLOTS $key_slot +# set expected_slots [initialize_expected_slots_dict] +# dict unset expected_slots $key_slot +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert {[dict size $expected_slots] == 16383} +# assert_slot_visibility $slot_stats $expected_slots + +# R 0 CLUSTER ADDSLOTS $key_slot +# set expected_slots [initialize_expected_slots_dict] +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert {[dict size $expected_slots] == 16384} +# assert_slot_visibility $slot_stats $expected_slots +# } +# } + +# # ----------------------------------------------------------------------------- +# # Test cases for CLUSTER SLOT-STATS SLOTSRANGE sub-argument. +# # ----------------------------------------------------------------------------- + +# start_cluster 1 0 {tags {external:skip cluster}} { + +# test "CLUSTER SLOT-STATS SLOTSRANGE all slots present" { +# set start_slot 100 +# set end_slot 102 +# set expected_slots [initialize_expected_slots_dict_with_range $start_slot $end_slot] + +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE $start_slot $end_slot] +# assert_slot_visibility $slot_stats $expected_slots +# } + +# test "CLUSTER SLOT-STATS SLOTSRANGE some slots missing" { +# set start_slot 100 +# set end_slot 102 +# set expected_slots [initialize_expected_slots_dict_with_range $start_slot $end_slot] + +# R 0 CLUSTER DELSLOTS $start_slot +# dict unset expected_slots $start_slot + +# set slot_stats [R 0 CLUSTER SLOT-STATS SLOTSRANGE $start_slot $end_slot] +# assert_slot_visibility $slot_stats $expected_slots +# } +# } + +# # ----------------------------------------------------------------------------- +# # Test cases for CLUSTER SLOT-STATS ORDERBY sub-argument. +# # ----------------------------------------------------------------------------- + +# start_cluster 1 0 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { + +# set metrics [list "key-count" "cpu-usec" "network-bytes-in" "network-bytes-out"] + +# # SET keys for target hashslots, to encourage ordering. +# set hash_tags [list 0 1 2 3 4] +# set num_keys 1 +# foreach hash_tag $hash_tags { +# for {set i 0} {$i < $num_keys} {incr i 1} { +# R 0 SET "$i{$hash_tag}" VALUE +# } +# incr num_keys 1 +# } + +# # SET keys for random hashslots, for random noise. +# set num_keys 0 +# while {$num_keys < 1000} { +# set random_key [randomInt 16384] +# R 0 SET $random_key VALUE +# incr num_keys 1 +# } + +# test "CLUSTER SLOT-STATS ORDERBY DESC correct ordering" { +# foreach orderby $metrics { +# set slot_stats [R 0 CLUSTER SLOT-STATS ORDERBY $orderby DESC] +# assert_slot_stats_monotonic_descent $slot_stats $orderby +# } +# } + +# test "CLUSTER SLOT-STATS ORDERBY ASC correct ordering" { +# foreach orderby $metrics { +# set slot_stats [R 0 CLUSTER SLOT-STATS ORDERBY $orderby ASC] +# assert_slot_stats_monotonic_ascent $slot_stats $orderby +# } +# } + +# test "CLUSTER SLOT-STATS ORDERBY LIMIT correct response pagination, where limit is less than number of assigned slots" { +# R 0 FLUSHALL SYNC +# R 0 CONFIG RESETSTAT + +# foreach orderby $metrics { +# set limit 5 +# set slot_stats_desc [R 0 CLUSTER SLOT-STATS ORDERBY $orderby LIMIT $limit DESC] +# set slot_stats_asc [R 0 CLUSTER SLOT-STATS ORDERBY $orderby LIMIT $limit ASC] +# set slot_stats_desc_length [llength $slot_stats_desc] +# set slot_stats_asc_length [llength $slot_stats_asc] +# assert {$limit == $slot_stats_desc_length && $limit == $slot_stats_asc_length} + +# # All slot statistics have been reset to 0, so we will order by slot in ascending order. +# set expected_slots [dict create 0 0 1 0 2 0 3 0 4 0] +# assert_slot_visibility $slot_stats_desc $expected_slots +# assert_slot_visibility $slot_stats_asc $expected_slots +# } +# } + +# test "CLUSTER SLOT-STATS ORDERBY LIMIT correct response pagination, where limit is greater than number of assigned slots" { +# R 0 CONFIG SET cluster-require-full-coverage no +# R 0 FLUSHALL SYNC +# R 0 CLUSTER FLUSHSLOTS +# R 0 CLUSTER ADDSLOTS 100 101 + +# foreach orderby $metrics { +# set num_assigned_slots 2 +# set limit 5 +# set slot_stats_desc [R 0 CLUSTER SLOT-STATS ORDERBY $orderby LIMIT $limit DESC] +# set slot_stats_asc [R 0 CLUSTER SLOT-STATS ORDERBY $orderby LIMIT $limit ASC] +# set slot_stats_desc_length [llength $slot_stats_desc] +# set slot_stats_asc_length [llength $slot_stats_asc] +# set expected_response_length [expr min($num_assigned_slots, $limit)] +# assert {$expected_response_length == $slot_stats_desc_length && $expected_response_length == $slot_stats_asc_length} + +# set expected_slots [dict create 100 0 101 0] +# assert_slot_visibility $slot_stats_desc $expected_slots +# assert_slot_visibility $slot_stats_asc $expected_slots +# } +# } + +# test "CLUSTER SLOT-STATS ORDERBY arg sanity check." { +# # Non-existent argument. +# assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY key-count non-existent-arg} +# # Negative LIMIT. +# assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY key-count DESC LIMIT -1} +# # Non-existent ORDERBY metric. +# assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY non-existent-metric} +# # When cluster-slot-stats-enabled config is disabled, you cannot sort using advanced metrics. +# R 0 CONFIG SET cluster-slot-stats-enabled no +# set orderby "cpu-usec" +# assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY $orderby} +# set orderby "network-bytes-in" +# assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY $orderby} +# set orderby "network-bytes-out" +# assert_error "ERR*" {R 0 CLUSTER SLOT-STATS ORDERBY $orderby} +# } + +# } + +# # ----------------------------------------------------------------------------- +# # Test cases for CLUSTER SLOT-STATS replication. +# # ----------------------------------------------------------------------------- + +# start_cluster 1 1 {tags {external:skip cluster} overrides {cluster-slot-stats-enabled yes}} { + +# # Define shared variables. +# set key "key" +# set key_slot [R 0 CLUSTER KEYSLOT $key] +# set primary [Rn 0] +# set replica [Rn 1] + +# # For replication, assertions are split between deterministic and non-deterministic metrics. +# # * For deterministic metrics, strict equality assertions are made. +# # * For non-deterministic metrics, non-zeroness assertions are made. +# # Non-zeroness as in, both primary and replica should either have some value, or no value at all. +# # +# # * key-count is deterministic between primary and its replica. +# # * cpu-usec is non-deterministic between primary and its replica. +# # * network-bytes-in is deterministic between primary and its replica. +# # * network-bytes-out will remain empty in the replica, since primary client do not receive replies, unless for replicationSendAck(). +# set deterministic_metrics [list key-count network-bytes-in] +# set non_deterministic_metrics [list cpu-usec] +# set empty_metrics [list network-bytes-out] + +# # Setup replication. +# assert {[s -1 role] eq {slave}} +# wait_for_condition 1000 50 { +# [s -1 master_link_status] eq {up} +# } else { +# fail "Instance #1 master link status is not up" +# } +# R 1 readonly + +# test "CLUSTER SLOT-STATS metrics replication for new keys" { +# # *3\r\n$3\r\nset\r\n$3\r\nkey\r\n$5\r\nvalue\r\n --> 33 bytes. +# R 0 SET $key VALUE + +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create key-count 1 network-bytes-in 33 +# ] +# ] +# set slot_stats_master [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats_with_exception $slot_stats_master $expected_slot_stats $deterministic_metrics + +# wait_for_condition 500 10 { +# [string match {*calls=1,*} [cmdrstat set $replica]] +# } else { +# fail "Replica did not receive the command." +# } +# set slot_stats_replica [R 1 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_equal_slot_stats $slot_stats_master $slot_stats_replica $deterministic_metrics $non_deterministic_metrics +# assert_empty_slot_stats $slot_stats_replica $empty_metrics +# } +# R 0 CONFIG RESETSTAT +# R 1 CONFIG RESETSTAT + +# test "CLUSTER SLOT-STATS metrics replication for existing keys" { +# # *3\r\n$3\r\nset\r\n$3\r\nkey\r\n$13\r\nvalue_updated\r\n --> 42 bytes. +# R 0 SET $key VALUE_UPDATED + +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create key-count 1 network-bytes-in 42 +# ] +# ] +# set slot_stats_master [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats_with_exception $slot_stats_master $expected_slot_stats $deterministic_metrics + +# wait_for_condition 500 10 { +# [string match {*calls=1,*} [cmdrstat set $replica]] +# } else { +# fail "Replica did not receive the command." +# } +# set slot_stats_replica [R 1 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_equal_slot_stats $slot_stats_master $slot_stats_replica $deterministic_metrics $non_deterministic_metrics +# assert_empty_slot_stats $slot_stats_replica $empty_metrics +# } +# R 0 CONFIG RESETSTAT +# R 1 CONFIG RESETSTAT + +# test "CLUSTER SLOT-STATS metrics replication for deleting keys" { +# # *2\r\n$3\r\ndel\r\n$3\r\nkey\r\n --> 22 bytes. +# R 0 DEL $key + +# set expected_slot_stats [ +# dict create $key_slot [ +# dict create key-count 0 network-bytes-in 22 +# ] +# ] +# set slot_stats_master [R 0 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_empty_slot_stats_with_exception $slot_stats_master $expected_slot_stats $deterministic_metrics + +# wait_for_condition 500 10 { +# [string match {*calls=1,*} [cmdrstat del $replica]] +# } else { +# fail "Replica did not receive the command." +# } +# set slot_stats_replica [R 1 CLUSTER SLOT-STATS SLOTSRANGE 0 16383] +# assert_equal_slot_stats $slot_stats_master $slot_stats_replica $deterministic_metrics $non_deterministic_metrics +# assert_empty_slot_stats $slot_stats_replica $empty_metrics +# } +# R 0 CONFIG RESETSTAT +# R 1 CONFIG RESETSTAT +# } diff --git a/tests/unit/introspection.tcl b/tests/unit/introspection.tcl index f7d8a3bf021..b70bf3f932d 100644 --- a/tests/unit/introspection.tcl +++ b/tests/unit/introspection.tcl @@ -1036,60 +1036,60 @@ test {CONFIG REWRITE handles alias config properly} { } {} {external:skip} test {IO threads client number} { - start_server {overrides {io-threads 2} tags {external:skip}} { - set iothread_clients [get_io_thread_clients 1] - assert_equal $iothread_clients [s connected_clients] - assert_equal [get_io_thread_clients 0] 0 - - r script debug yes ; # Transfer to main thread - assert_equal [get_io_thread_clients 0] 1 - assert_equal [get_io_thread_clients 1] [expr $iothread_clients - 1] - - set iothread_clients [get_io_thread_clients 1] - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - assert_equal [get_io_thread_clients 1] [expr $iothread_clients + 2] - $rd1 close - $rd2 close - wait_for_condition 1000 10 { - [get_io_thread_clients 1] eq $iothread_clients - } else { - fail "Fail to close clients of io thread 1" - } - assert_equal [get_io_thread_clients 0] 1 - - r script debug no ; # Transfer to io thread - assert_equal [get_io_thread_clients 0] 0 - assert_equal [get_io_thread_clients 1] [expr $iothread_clients + 1] - } + # start_server {overrides {io-threads 2} tags {external:skip}} { + # set iothread_clients [get_io_thread_clients 1] + # assert_equal $iothread_clients [s connected_clients] + # assert_equal [get_io_thread_clients 0] 0 + + # r script debug yes ; # Transfer to main thread + # assert_equal [get_io_thread_clients 0] 1 + # assert_equal [get_io_thread_clients 1] [expr $iothread_clients - 1] + + # set iothread_clients [get_io_thread_clients 1] + # set rd1 [redis_deferring_client] + # set rd2 [redis_deferring_client] + # assert_equal [get_io_thread_clients 1] [expr $iothread_clients + 2] + # $rd1 close + # $rd2 close + # wait_for_condition 1000 10 { + # [get_io_thread_clients 1] eq $iothread_clients + # } else { + # fail "Fail to close clients of io thread 1" + # } + # assert_equal [get_io_thread_clients 0] 1 + + # r script debug no ; # Transfer to io thread + # assert_equal [get_io_thread_clients 0] 0 + # assert_equal [get_io_thread_clients 1] [expr $iothread_clients + 1] + # } } -test {Clients are evenly distributed among io threads} { - start_server {overrides {io-threads 4} tags {external:skip}} { - set cur_clients [s connected_clients] - assert_equal $cur_clients 1 - global rdclients - for {set i 1} {$i < 9} {incr i} { - set rdclients($i) [redis_deferring_client] - } - for {set i 1} {$i <= 3} {incr i} { - assert_equal [get_io_thread_clients $i] 3 - } - - $rdclients(3) close - $rdclients(4) close - wait_for_condition 1000 10 { - [get_io_thread_clients 1] eq 2 && - [get_io_thread_clients 2] eq 2 && - [get_io_thread_clients 3] eq 3 - } else { - fail "Fail to close clients" - } - - set $rdclients(3) [redis_deferring_client] - set $rdclients(4) [redis_deferring_client] - for {set i 1} {$i <= 3} {incr i} { - assert_equal [get_io_thread_clients $i] 3 - } - } -} +# test {Clients are evenly distributed among io threads} { +# start_server {overrides {io-threads 4} tags {external:skip}} { +# set cur_clients [s connected_clients] +# assert_equal $cur_clients 1 +# global rdclients +# for {set i 1} {$i < 9} {incr i} { +# set rdclients($i) [redis_deferring_client] +# } +# for {set i 1} {$i <= 3} {incr i} { +# assert_equal [get_io_thread_clients $i] 3 +# } + +# $rdclients(3) close +# $rdclients(4) close +# wait_for_condition 1000 10 { +# [get_io_thread_clients 1] eq 2 && +# [get_io_thread_clients 2] eq 2 && +# [get_io_thread_clients 3] eq 3 +# } else { +# fail "Fail to close clients" +# } + +# set $rdclients(3) [redis_deferring_client] +# set $rdclients(4) [redis_deferring_client] +# for {set i 1} {$i <= 3} {incr i} { +# assert_equal [get_io_thread_clients $i] 3 +# } +# } +# } diff --git a/tests/unit/moduleapi/cluster.tcl b/tests/unit/moduleapi/cluster.tcl index d79dd664dc8..065a5cc0c17 100644 --- a/tests/unit/moduleapi/cluster.tcl +++ b/tests/unit/moduleapi/cluster.tcl @@ -1,226 +1,226 @@ -# Primitive tests on cluster-enabled redis with modules - -source tests/support/cli.tcl - -# cluster creation is complicated with TLS, and the current tests don't really need that coverage -tags {tls:skip external:skip cluster modules} { - -set testmodule_nokey [file normalize tests/modules/blockonbackground.so] -set testmodule_blockedclient [file normalize tests/modules/blockedclient.so] -set testmodule [file normalize tests/modules/blockonkeys.so] - -set modules [list loadmodule $testmodule loadmodule $testmodule_nokey loadmodule $testmodule_blockedclient] -start_cluster 3 0 [list tags {external:skip cluster modules} config_lines $modules] { - - set node1 [srv 0 client] - set node2 [srv -1 client] - set node3 [srv -2 client] - set node3_pid [srv -2 pid] - - test "Run blocking command (blocked on key) on cluster node3" { - # key9184688 is mapped to slot 10923 (first slot of node 3) - set node3_rd [redis_deferring_client -2] - $node3_rd fsl.bpop key9184688 0 - $node3_rd flush - wait_for_condition 50 100 { - [s -2 blocked_clients] eq {1} - } else { - fail "Client executing blocking command (blocked on key) not blocked" - } - } - - test "Run blocking command (no keys) on cluster node2" { - set node2_rd [redis_deferring_client -1] - $node2_rd block.block 0 - $node2_rd flush - - wait_for_condition 50 100 { - [s -1 blocked_clients] eq {1} - } else { - fail "Client executing blocking command (no keys) not blocked" - } - } - - - test "Perform a Resharding" { - exec src/redis-cli --cluster-yes --cluster reshard 127.0.0.1:[srv -2 port] \ - --cluster-to [$node1 cluster myid] \ - --cluster-from [$node3 cluster myid] \ - --cluster-slots 1 - } - - test "Verify command (no keys) is unaffected after resharding" { - # verify there are blocked clients on node2 - assert_equal [s -1 blocked_clients] {1} - - #release client - $node2 block.release 0 - } - - test "Verify command (blocked on key) got unblocked after resharding" { - # this (read) will wait for the node3 to realize the new topology - assert_error {*MOVED*} {$node3_rd read} - - # verify there are no blocked clients - assert_equal [s 0 blocked_clients] {0} - assert_equal [s -1 blocked_clients] {0} - assert_equal [s -2 blocked_clients] {0} - } - - test "Wait for cluster to be stable" { - wait_for_condition 1000 50 { - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv 0 port]}] == 0 && - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -1 port]}] == 0 && - [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -2 port]}] == 0 && - [CI 0 cluster_state] eq {ok} && - [CI 1 cluster_state] eq {ok} && - [CI 2 cluster_state] eq {ok} - } else { - fail "Cluster doesn't stabilize" - } - } - - test "Sanity test push cmd after resharding" { - assert_error {*MOVED*} {$node3 fsl.push key9184688 1} - - set node1_rd [redis_deferring_client 0] - $node1_rd fsl.bpop key9184688 0 - $node1_rd flush - - wait_for_condition 50 100 { - [s 0 blocked_clients] eq {1} - } else { - puts "Client not blocked" - puts "read from blocked client: [$node1_rd read]" - fail "Client not blocked" - } - - $node1 fsl.push key9184688 2 - assert_equal {2} [$node1_rd read] - } - - $node1_rd close - $node2_rd close - $node3_rd close - - test "Run blocking command (blocked on key) again on cluster node1" { - $node1 del key9184688 - # key9184688 is mapped to slot 10923 which has been moved to node1 - set node1_rd [redis_deferring_client 0] - $node1_rd fsl.bpop key9184688 0 - $node1_rd flush - - wait_for_condition 50 100 { - [s 0 blocked_clients] eq {1} - } else { - fail "Client executing blocking command (blocked on key) again not blocked" - } - } - - test "Run blocking command (no keys) again on cluster node2" { - set node2_rd [redis_deferring_client -1] - - $node2_rd block.block 0 - $node2_rd flush - - wait_for_condition 50 100 { - [s -1 blocked_clients] eq {1} - } else { - fail "Client executing blocking command (no keys) again not blocked" - } - } - - test "Kill a cluster node and wait for fail state" { - # kill node3 in cluster - pause_process $node3_pid - - wait_for_condition 1000 50 { - [CI 0 cluster_state] eq {fail} && - [CI 1 cluster_state] eq {fail} - } else { - fail "Cluster doesn't fail" - } - } - - test "Verify command (blocked on key) got unblocked after cluster failure" { - assert_error {*CLUSTERDOWN*} {$node1_rd read} - } - - test "Verify command (no keys) got unblocked after cluster failure" { - assert_error {*CLUSTERDOWN*} {$node2_rd read} - - # verify there are no blocked clients - assert_equal [s 0 blocked_clients] {0} - assert_equal [s -1 blocked_clients] {0} - } - - test "Verify command RM_Call is rejected when cluster is down" { - assert_error "ERR Can not execute a command 'set' while the cluster is down" {$node1 do_rm_call set x 1} - } - - resume_process $node3_pid - $node1_rd close - $node2_rd close -} - -set testmodule_keyspace_events [file normalize tests/modules/keyspace_events.so] -set testmodule_postnotifications "[file normalize tests/modules/postnotifications.so] with_key_events" -set modules [list loadmodule $testmodule_keyspace_events loadmodule $testmodule_postnotifications] -start_cluster 2 2 [list tags {external:skip cluster modules} config_lines $modules] { - - set master1 [srv 0 client] - set master2 [srv -1 client] - set replica1 [srv -2 client] - set replica2 [srv -3 client] - - test "Verify keys deletion and notification effects happened on cluster slots change are replicated inside multi exec" { - $master2 set count_dels_{4oi} 1 - $master2 del count_dels_{4oi} - assert_equal 1 [$master2 keyspace.get_dels] - assert_equal 1 [$replica2 keyspace.get_dels] - $master2 set count_dels_{4oi} 1 - - set repl [attach_to_replication_stream_on_connection -3] - - $master1 cluster bumpepoch - $master1 cluster setslot 16382 node [$master1 cluster myid] - - wait_for_cluster_propagation - wait_for_condition 50 100 { - [$master2 keyspace.get_dels] eq 2 - } else { - fail "master did not delete the key" - } - wait_for_condition 50 100 { - [$replica2 keyspace.get_dels] eq 2 - } else { - fail "replica did not increase del counter" - } - - # the {lpush before_deleted count_dels_{4oi}} is a post notification job registered when 'count_dels_{4oi}' was removed - assert_replication_stream $repl { - {multi} - {del count_dels_{4oi}} - {keyspace.incr_dels} - {lpush before_deleted count_dels_{4oi}} - {exec} - } - close_replication_stream $repl - } -} - -} - -set testmodule [file normalize tests/modules/basics.so] -set modules [list loadmodule $testmodule] -start_cluster 3 0 [list tags {external:skip cluster modules} config_lines $modules] { - set node1 [srv 0 client] - set node2 [srv -1 client] - set node3 [srv -2 client] - - test "Verify RM_Call inside module load function on cluster mode" { - assert_equal {PONG} [$node1 PING] - assert_equal {PONG} [$node2 PING] - assert_equal {PONG} [$node3 PING] - } -} +# # Primitive tests on cluster-enabled redis with modules + +# source tests/support/cli.tcl + +# # cluster creation is complicated with TLS, and the current tests don't really need that coverage +# tags {tls:skip external:skip cluster modules} { + +# set testmodule_nokey [file normalize tests/modules/blockonbackground.so] +# set testmodule_blockedclient [file normalize tests/modules/blockedclient.so] +# set testmodule [file normalize tests/modules/blockonkeys.so] + +# set modules [list loadmodule $testmodule loadmodule $testmodule_nokey loadmodule $testmodule_blockedclient] +# start_cluster 3 0 [list tags {external:skip cluster modules} config_lines $modules] { + +# set node1 [srv 0 client] +# set node2 [srv -1 client] +# set node3 [srv -2 client] +# set node3_pid [srv -2 pid] + +# test "Run blocking command (blocked on key) on cluster node3" { +# # key9184688 is mapped to slot 10923 (first slot of node 3) +# set node3_rd [redis_deferring_client -2] +# $node3_rd fsl.bpop key9184688 0 +# $node3_rd flush +# wait_for_condition 50 100 { +# [s -2 blocked_clients] eq {1} +# } else { +# fail "Client executing blocking command (blocked on key) not blocked" +# } +# } + +# test "Run blocking command (no keys) on cluster node2" { +# set node2_rd [redis_deferring_client -1] +# $node2_rd block.block 0 +# $node2_rd flush + +# wait_for_condition 50 100 { +# [s -1 blocked_clients] eq {1} +# } else { +# fail "Client executing blocking command (no keys) not blocked" +# } +# } + + +# test "Perform a Resharding" { +# exec src/redis-cli --cluster-yes --cluster reshard 127.0.0.1:[srv -2 port] \ +# --cluster-to [$node1 cluster myid] \ +# --cluster-from [$node3 cluster myid] \ +# --cluster-slots 1 +# } + +# test "Verify command (no keys) is unaffected after resharding" { +# # verify there are blocked clients on node2 +# assert_equal [s -1 blocked_clients] {1} + +# #release client +# $node2 block.release 0 +# } + +# test "Verify command (blocked on key) got unblocked after resharding" { +# # this (read) will wait for the node3 to realize the new topology +# assert_error {*MOVED*} {$node3_rd read} + +# # verify there are no blocked clients +# assert_equal [s 0 blocked_clients] {0} +# assert_equal [s -1 blocked_clients] {0} +# assert_equal [s -2 blocked_clients] {0} +# } + +# test "Wait for cluster to be stable" { +# wait_for_condition 1000 50 { +# [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv 0 port]}] == 0 && +# [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -1 port]}] == 0 && +# [catch {exec src/redis-cli --cluster check 127.0.0.1:[srv -2 port]}] == 0 && +# [CI 0 cluster_state] eq {ok} && +# [CI 1 cluster_state] eq {ok} && +# [CI 2 cluster_state] eq {ok} +# } else { +# fail "Cluster doesn't stabilize" +# } +# } + +# test "Sanity test push cmd after resharding" { +# assert_error {*MOVED*} {$node3 fsl.push key9184688 1} + +# set node1_rd [redis_deferring_client 0] +# $node1_rd fsl.bpop key9184688 0 +# $node1_rd flush + +# wait_for_condition 50 100 { +# [s 0 blocked_clients] eq {1} +# } else { +# puts "Client not blocked" +# puts "read from blocked client: [$node1_rd read]" +# fail "Client not blocked" +# } + +# $node1 fsl.push key9184688 2 +# assert_equal {2} [$node1_rd read] +# } + +# $node1_rd close +# $node2_rd close +# $node3_rd close + +# test "Run blocking command (blocked on key) again on cluster node1" { +# $node1 del key9184688 +# # key9184688 is mapped to slot 10923 which has been moved to node1 +# set node1_rd [redis_deferring_client 0] +# $node1_rd fsl.bpop key9184688 0 +# $node1_rd flush + +# wait_for_condition 50 100 { +# [s 0 blocked_clients] eq {1} +# } else { +# fail "Client executing blocking command (blocked on key) again not blocked" +# } +# } + +# test "Run blocking command (no keys) again on cluster node2" { +# set node2_rd [redis_deferring_client -1] + +# $node2_rd block.block 0 +# $node2_rd flush + +# wait_for_condition 50 100 { +# [s -1 blocked_clients] eq {1} +# } else { +# fail "Client executing blocking command (no keys) again not blocked" +# } +# } + +# test "Kill a cluster node and wait for fail state" { +# # kill node3 in cluster +# pause_process $node3_pid + +# wait_for_condition 1000 50 { +# [CI 0 cluster_state] eq {fail} && +# [CI 1 cluster_state] eq {fail} +# } else { +# fail "Cluster doesn't fail" +# } +# } + +# test "Verify command (blocked on key) got unblocked after cluster failure" { +# assert_error {*CLUSTERDOWN*} {$node1_rd read} +# } + +# test "Verify command (no keys) got unblocked after cluster failure" { +# assert_error {*CLUSTERDOWN*} {$node2_rd read} + +# # verify there are no blocked clients +# assert_equal [s 0 blocked_clients] {0} +# assert_equal [s -1 blocked_clients] {0} +# } + +# test "Verify command RM_Call is rejected when cluster is down" { +# assert_error "ERR Can not execute a command 'set' while the cluster is down" {$node1 do_rm_call set x 1} +# } + +# resume_process $node3_pid +# $node1_rd close +# $node2_rd close +# } + +# set testmodule_keyspace_events [file normalize tests/modules/keyspace_events.so] +# set testmodule_postnotifications "[file normalize tests/modules/postnotifications.so] with_key_events" +# set modules [list loadmodule $testmodule_keyspace_events loadmodule $testmodule_postnotifications] +# start_cluster 2 2 [list tags {external:skip cluster modules} config_lines $modules] { + +# set master1 [srv 0 client] +# set master2 [srv -1 client] +# set replica1 [srv -2 client] +# set replica2 [srv -3 client] + +# test "Verify keys deletion and notification effects happened on cluster slots change are replicated inside multi exec" { +# $master2 set count_dels_{4oi} 1 +# $master2 del count_dels_{4oi} +# assert_equal 1 [$master2 keyspace.get_dels] +# assert_equal 1 [$replica2 keyspace.get_dels] +# $master2 set count_dels_{4oi} 1 + +# set repl [attach_to_replication_stream_on_connection -3] + +# $master1 cluster bumpepoch +# $master1 cluster setslot 16382 node [$master1 cluster myid] + +# wait_for_cluster_propagation +# wait_for_condition 50 100 { +# [$master2 keyspace.get_dels] eq 2 +# } else { +# fail "master did not delete the key" +# } +# wait_for_condition 50 100 { +# [$replica2 keyspace.get_dels] eq 2 +# } else { +# fail "replica did not increase del counter" +# } + +# # the {lpush before_deleted count_dels_{4oi}} is a post notification job registered when 'count_dels_{4oi}' was removed +# assert_replication_stream $repl { +# {multi} +# {del count_dels_{4oi}} +# {keyspace.incr_dels} +# {lpush before_deleted count_dels_{4oi}} +# {exec} +# } +# close_replication_stream $repl +# } +# } + +# } + +# set testmodule [file normalize tests/modules/basics.so] +# set modules [list loadmodule $testmodule] +# start_cluster 3 0 [list tags {external:skip cluster modules} config_lines $modules] { +# set node1 [srv 0 client] +# set node2 [srv -1 client] +# set node3 [srv -2 client] + +# test "Verify RM_Call inside module load function on cluster mode" { +# assert_equal {PONG} [$node1 PING] +# assert_equal {PONG} [$node2 PING] +# assert_equal {PONG} [$node3 PING] +# } +# } diff --git a/tests/unit/moduleapi/commandfilter.tcl b/tests/unit/moduleapi/commandfilter.tcl index 5b600d0ebf0..e0c36ba9a2e 100644 --- a/tests/unit/moduleapi/commandfilter.tcl +++ b/tests/unit/moduleapi/commandfilter.tcl @@ -1,175 +1,175 @@ -set testmodule [file normalize tests/modules/commandfilter.so] - -start_server {tags {"modules external:skip"}} { - r module load $testmodule log-key 0 - - test {Retain a command filter argument} { - # Retain an argument now. Later we'll try to re-read it and make sure - # it is not corrupt and that valgrind does not complain. - r rpush some-list @retain my-retained-string - r commandfilter.retained - } {my-retained-string} - - test {Command Filter handles redirected commands} { - r set mykey @log - r lrange log-key 0 -1 - } "{set mykey @log}" - - test {Command Filter can call RedisModule_CommandFilterArgDelete} { - r rpush mylist elem1 @delme elem2 - r lrange mylist 0 -1 - } {elem1 elem2} - - test {Command Filter can call RedisModule_CommandFilterArgInsert} { - r del mylist - r rpush mylist elem1 @insertbefore elem2 @insertafter elem3 - r lrange mylist 0 -1 - } {elem1 --inserted-before-- @insertbefore elem2 @insertafter --inserted-after-- elem3} - - test {Command Filter can call RedisModule_CommandFilterArgReplace} { - r del mylist - r rpush mylist elem1 @replaceme elem2 - r lrange mylist 0 -1 - } {elem1 --replaced-- elem2} - - test {Command Filter applies on RM_Call() commands} { - r del log-key - r commandfilter.ping - r lrange log-key 0 -1 - } "{ping @log}" - - test {Command Filter applies on Lua redis.call()} { - r del log-key - r eval "redis.call('ping', '@log')" 0 - r lrange log-key 0 -1 - } "{ping @log}" - - test {Command Filter applies on Lua redis.call() that calls a module} { - r del log-key - r eval "redis.call('commandfilter.ping')" 0 - r lrange log-key 0 -1 - } "{ping @log}" - - test {Command Filter strings can be retained} { - r commandfilter.retained - } {my-retained-string} - - test {Command Filter is unregistered implicitly on module unload} { - r del log-key - r module unload commandfilter - r set mykey @log - r lrange log-key 0 -1 - } {} - - r module load $testmodule log-key 0 - - test {Command Filter unregister works as expected} { - # Validate reloading succeeded - r del log-key - r set mykey @log - assert_equal "{set mykey @log}" [r lrange log-key 0 -1] - - # Unregister - r commandfilter.unregister - r del log-key - - r set mykey @log - r lrange log-key 0 -1 - } {} - - r module unload commandfilter - r module load $testmodule log-key 1 - - test {Command Filter REDISMODULE_CMDFILTER_NOSELF works as expected} { - r set mykey @log - assert_equal "{set mykey @log}" [r lrange log-key 0 -1] - - r del log-key - r commandfilter.ping - assert_equal {} [r lrange log-key 0 -1] - - r eval "redis.call('commandfilter.ping')" 0 - assert_equal {} [r lrange log-key 0 -1] - } - - test "Unload the module - commandfilter" { - assert_equal {OK} [r module unload commandfilter] - } -} - -test {RM_CommandFilterArgInsert and script argv caching} { - # coverage for scripts calling commands that expand the argv array - # an attempt to add coverage for a possible bug in luaArgsToRedisArgv - # this test needs a fresh server so that lua_argv_size is 0. - # glibc realloc can return the same pointer even when the size changes - # still this test isn't able to trigger the issue, but we keep it anyway. - start_server {tags {"modules external:skip"}} { - r module load $testmodule log-key 0 - r del mylist - # command with 6 args - r eval {redis.call('rpush', KEYS[1], 'elem1', 'elem2', 'elem3', 'elem4')} 1 mylist - # command with 3 args that is changed to 4 - r eval {redis.call('rpush', KEYS[1], '@insertafter')} 1 mylist - # command with 6 args again - r eval {redis.call('rpush', KEYS[1], 'elem1', 'elem2', 'elem3', 'elem4')} 1 mylist - assert_equal [r lrange mylist 0 -1] {elem1 elem2 elem3 elem4 @insertafter --inserted-after-- elem1 elem2 elem3 elem4} - } -} - -# previously, there was a bug that command filters would be rerun (which would cause args to swap back) -# this test is meant to protect against that bug -test {Blocking Commands don't run through command filter when reprocessed} { - start_server {tags {"modules external:skip"}} { - r module load $testmodule log-key 0 - - r del list1{t} - r del list2{t} - - r lpush list2{t} a b c d e - - set rd [redis_deferring_client] - # we're asking to pop from the left, but the command filter swaps the two arguments, - # if it didn't swap it, we would end up with e d c b a 5 (5 being the left most of the following lpush) - # but since we swap the arguments, we end up with 1 e d c b a (1 being the right most of it). - # if the command filter would run again on unblock, they would be swapped back. - $rd blmove list1{t} list2{t} left right 0 - wait_for_blocked_client - r lpush list1{t} 1 2 3 4 5 - # validate that we moved the correct element with the swapped args - assert_equal [$rd read] 1 - # validate that we moved the correct elements to the correct side of the list - assert_equal [r lpop list2{t}] 1 - - $rd close - } -} - -test {Filtering based on client id} { - start_server {tags {"modules external:skip"}} { - r module load $testmodule log-key 0 - - set rr [redis_client] - set cid [$rr client id] - r unfilter_clientid $cid - - r rpush mylist elem1 @replaceme elem2 - assert_equal [r lrange mylist 0 -1] {elem1 --replaced-- elem2} - - r del mylist - - assert_equal [$rr rpush mylist elem1 @replaceme elem2] 3 - assert_equal [r lrange mylist 0 -1] {elem1 @replaceme elem2} - - $rr close - } -} - -start_server {tags {"external:skip"}} { - test {OnLoad failure will handle un-registration} { - catch {r module load $testmodule log-key 0 noload} - r set mykey @log - assert_equal [r lrange log-key 0 -1] {} - r rpush mylist elem1 @delme elem2 - assert_equal [r lrange mylist 0 -1] {elem1 @delme elem2} - } -} +# set testmodule [file normalize tests/modules/commandfilter.so] + +# start_server {tags {"modules external:skip"}} { +# r module load $testmodule log-key 0 + +# test {Retain a command filter argument} { +# # Retain an argument now. Later we'll try to re-read it and make sure +# # it is not corrupt and that valgrind does not complain. +# r rpush some-list @retain my-retained-string +# r commandfilter.retained +# } {my-retained-string} + +# test {Command Filter handles redirected commands} { +# r set mykey @log +# r lrange log-key 0 -1 +# } "{set mykey @log}" + +# test {Command Filter can call RedisModule_CommandFilterArgDelete} { +# r rpush mylist elem1 @delme elem2 +# r lrange mylist 0 -1 +# } {elem1 elem2} + +# test {Command Filter can call RedisModule_CommandFilterArgInsert} { +# r del mylist +# r rpush mylist elem1 @insertbefore elem2 @insertafter elem3 +# r lrange mylist 0 -1 +# } {elem1 --inserted-before-- @insertbefore elem2 @insertafter --inserted-after-- elem3} + +# test {Command Filter can call RedisModule_CommandFilterArgReplace} { +# r del mylist +# r rpush mylist elem1 @replaceme elem2 +# r lrange mylist 0 -1 +# } {elem1 --replaced-- elem2} + +# test {Command Filter applies on RM_Call() commands} { +# r del log-key +# r commandfilter.ping +# r lrange log-key 0 -1 +# } "{ping @log}" + +# test {Command Filter applies on Lua redis.call()} { +# r del log-key +# r eval "redis.call('ping', '@log')" 0 +# r lrange log-key 0 -1 +# } "{ping @log}" + +# test {Command Filter applies on Lua redis.call() that calls a module} { +# r del log-key +# r eval "redis.call('commandfilter.ping')" 0 +# r lrange log-key 0 -1 +# } "{ping @log}" + +# test {Command Filter strings can be retained} { +# r commandfilter.retained +# } {my-retained-string} + +# test {Command Filter is unregistered implicitly on module unload} { +# r del log-key +# r module unload commandfilter +# r set mykey @log +# r lrange log-key 0 -1 +# } {} + +# r module load $testmodule log-key 0 + +# test {Command Filter unregister works as expected} { +# # Validate reloading succeeded +# r del log-key +# r set mykey @log +# assert_equal "{set mykey @log}" [r lrange log-key 0 -1] + +# # Unregister +# r commandfilter.unregister +# r del log-key + +# r set mykey @log +# r lrange log-key 0 -1 +# } {} + +# r module unload commandfilter +# r module load $testmodule log-key 1 + +# test {Command Filter REDISMODULE_CMDFILTER_NOSELF works as expected} { +# r set mykey @log +# assert_equal "{set mykey @log}" [r lrange log-key 0 -1] + +# r del log-key +# r commandfilter.ping +# assert_equal {} [r lrange log-key 0 -1] + +# r eval "redis.call('commandfilter.ping')" 0 +# assert_equal {} [r lrange log-key 0 -1] +# } + +# test "Unload the module - commandfilter" { +# assert_equal {OK} [r module unload commandfilter] +# } +# } + +# test {RM_CommandFilterArgInsert and script argv caching} { +# # coverage for scripts calling commands that expand the argv array +# # an attempt to add coverage for a possible bug in luaArgsToRedisArgv +# # this test needs a fresh server so that lua_argv_size is 0. +# # glibc realloc can return the same pointer even when the size changes +# # still this test isn't able to trigger the issue, but we keep it anyway. +# start_server {tags {"modules external:skip"}} { +# r module load $testmodule log-key 0 +# r del mylist +# # command with 6 args +# r eval {redis.call('rpush', KEYS[1], 'elem1', 'elem2', 'elem3', 'elem4')} 1 mylist +# # command with 3 args that is changed to 4 +# r eval {redis.call('rpush', KEYS[1], '@insertafter')} 1 mylist +# # command with 6 args again +# r eval {redis.call('rpush', KEYS[1], 'elem1', 'elem2', 'elem3', 'elem4')} 1 mylist +# assert_equal [r lrange mylist 0 -1] {elem1 elem2 elem3 elem4 @insertafter --inserted-after-- elem1 elem2 elem3 elem4} +# } +# } + +# # previously, there was a bug that command filters would be rerun (which would cause args to swap back) +# # this test is meant to protect against that bug +# test {Blocking Commands don't run through command filter when reprocessed} { +# start_server {tags {"modules external:skip"}} { +# r module load $testmodule log-key 0 + +# r del list1{t} +# r del list2{t} + +# r lpush list2{t} a b c d e + +# set rd [redis_deferring_client] +# # we're asking to pop from the left, but the command filter swaps the two arguments, +# # if it didn't swap it, we would end up with e d c b a 5 (5 being the left most of the following lpush) +# # but since we swap the arguments, we end up with 1 e d c b a (1 being the right most of it). +# # if the command filter would run again on unblock, they would be swapped back. +# $rd blmove list1{t} list2{t} left right 0 +# wait_for_blocked_client +# r lpush list1{t} 1 2 3 4 5 +# # validate that we moved the correct element with the swapped args +# assert_equal [$rd read] 1 +# # validate that we moved the correct elements to the correct side of the list +# assert_equal [r lpop list2{t}] 1 + +# $rd close +# } +# } + +# test {Filtering based on client id} { +# start_server {tags {"modules external:skip"}} { +# r module load $testmodule log-key 0 + +# set rr [redis_client] +# set cid [$rr client id] +# r unfilter_clientid $cid + +# r rpush mylist elem1 @replaceme elem2 +# assert_equal [r lrange mylist 0 -1] {elem1 --replaced-- elem2} + +# r del mylist + +# assert_equal [$rr rpush mylist elem1 @replaceme elem2] 3 +# assert_equal [r lrange mylist 0 -1] {elem1 @replaceme elem2} + +# $rr close +# } +# } + +# start_server {tags {"external:skip"}} { +# test {OnLoad failure will handle un-registration} { +# catch {r module load $testmodule log-key 0 noload} +# r set mykey @log +# assert_equal [r lrange log-key 0 -1] {} +# r rpush mylist elem1 @delme elem2 +# assert_equal [r lrange mylist 0 -1] {elem1 @delme elem2} +# } +# } diff --git a/tests/unit/moduleapi/list.tcl b/tests/unit/moduleapi/list.tcl index 5f7532c2747..20f124de499 100644 --- a/tests/unit/moduleapi/list.tcl +++ b/tests/unit/moduleapi/list.tcl @@ -1,225 +1,225 @@ -set testmodule [file normalize tests/modules/list.so] - -# The following arguments can be passed to args: -# i -- the number of inserts -# d -- the number of deletes -# r -- the number of replaces -# index -- the last index -# entry -- The entry pointed to by index -proc verify_list_edit_reply {reply argv} { - foreach {k v} $argv { - assert_equal [dict get $reply $k] $v - } -} - -start_server {tags {"modules external:skip"}} { - r module load $testmodule - - test {Module list set, get, insert, delete} { - r del k - assert_error {WRONGTYPE Operation against a key holding the wrong kind of value*} {r list.set k 1 xyz} - r rpush k x - # insert, set, get - r list.insert k 0 foo - r list.insert k -1 bar - r list.set k 1 xyz - assert_equal {foo xyz bar} [r list.getall k] - assert_equal {foo} [r list.get k 0] - assert_equal {xyz} [r list.get k 1] - assert_equal {bar} [r list.get k 2] - assert_equal {bar} [r list.get k -1] - assert_equal {foo} [r list.get k -3] - assert_error {ERR index out*} {r list.get k -4} - assert_error {ERR index out*} {r list.get k 3} - # remove - assert_error {ERR index out*} {r list.delete k -4} - assert_error {ERR index out*} {r list.delete k 3} - r list.delete k 0 - r list.delete k -1 - assert_equal {xyz} [r list.getall k] - # removing the last element deletes the list - r list.delete k 0 - assert_equal 0 [r exists k] - } - - test {Module list iteration} { - r del k - r rpush k x y z - assert_equal {x y z} [r list.getall k] - assert_equal {z y x} [r list.getall k REVERSE] - } - - test {Module list insert & delete} { - r del k - r rpush k x y z - verify_list_edit_reply [r list.edit k ikikdi foo bar baz] {i 3 index 5} - r list.getall k - } {foo x bar y baz} - - test {Module list insert & delete, neg index} { - r del k - r rpush k x y z - verify_list_edit_reply [r list.edit k REVERSE ikikdi foo bar baz] {i 3 index -6} - r list.getall k - } {baz y bar z foo} - - test {Module list set while iterating} { - r del k - r rpush k x y z - verify_list_edit_reply [r list.edit k rkr foo bar] {r 2 index 3} - r list.getall k - } {foo y bar} - - test {Module list set while iterating, neg index} { - r del k - r rpush k x y z - verify_list_edit_reply [r list.edit k reverse rkr foo bar] {r 2 index -4} - r list.getall k - } {bar y foo} - - test {Module list - encoding conversion while inserting} { - r config set list-max-listpack-size 4 - r del k - r rpush k a b c d - assert_encoding listpack k - - # Converts to quicklist after inserting. - r list.edit k dii foo bar - assert_encoding quicklist k - assert_equal [r list.getall k] {foo bar b c d} - - # Converts to listpack after deleting three entries. - r list.edit k ddd e - assert_encoding listpack k - assert_equal [r list.getall k] {c d} - } - - test {Module list - encoding conversion while replacing} { - r config set list-max-listpack-size -1 - r del k - r rpush k x y z - assert_encoding listpack k - - # Converts to quicklist after replacing. - set big [string repeat "x" 4096] - r list.edit k r $big - assert_encoding quicklist k - assert_equal [r list.getall k] "$big y z" - - # Converts to listpack after deleting the big entry. - r list.edit k d - assert_encoding listpack k - assert_equal [r list.getall k] {y z} - } - - test {Module list - list entry and index should be updated when deletion} { - set original_config [config_get_set list-max-listpack-size 1] - - # delete from start (index 0) - r del l - r rpush l x y z - verify_list_edit_reply [r list.edit l dd] {d 2 index 0 entry z} - assert_equal [r list.getall l] {z} - - # delete from start (index -3) - r del l - r rpush l x y z - verify_list_edit_reply [r list.edit l reverse kkd] {d 1 index -3} - assert_equal [r list.getall l] {y z} - - # # delete from tail (index 2) - r del l - r rpush l x y z - verify_list_edit_reply [r list.edit l kkd] {d 1 index 2} - assert_equal [r list.getall l] {x y} - - # # delete from tail (index -1) - r del l - r rpush l x y z - verify_list_edit_reply [r list.edit l reverse dd] {d 2 index -1 entry x} - assert_equal [r list.getall l] {x} - - # # delete from middle (index 1) - r del l - r rpush l x y z - verify_list_edit_reply [r list.edit l kdd] {d 2 index 1} - assert_equal [r list.getall l] {x} - - # # delete from middle (index -2) - r del l - r rpush l x y z - verify_list_edit_reply [r list.edit l reverse kdd] {d 2 index -2} - assert_equal [r list.getall l] {z} - - config_set list-max-listpack-size $original_config - } - - test {Module list - KEYSIZES is updated as expected} { - proc run_cmd_verify_hist {cmd expOutput {retries 1}} { - proc K {} {return [string map { "db0_distrib_lists_items" "db0_LIST" "# Keysizes" "" " " "" "\n" "" "\r" "" } [r info keysizes] ]} - uplevel 1 $cmd - wait_for_condition 50 $retries { - $expOutput eq [K] - } else { fail "Expected: \n`$expOutput`\n Actual:\n`[K]`.\nFailed after command: $cmd" } - } - - r select 0 - - # RedisModule_ListPush & RedisModule_ListDelete - run_cmd_verify_hist {r flushall} {} - run_cmd_verify_hist {r list.insert L1 0 foo} {db0_LIST:1=1} - run_cmd_verify_hist {r list.insert L1 0 bla} {db0_LIST:2=1} - run_cmd_verify_hist {r list.delete L1 0} {db0_LIST:1=1} - run_cmd_verify_hist {r list.delete L1 0} {} +# set testmodule [file normalize tests/modules/list.so] + +# # The following arguments can be passed to args: +# # i -- the number of inserts +# # d -- the number of deletes +# # r -- the number of replaces +# # index -- the last index +# # entry -- The entry pointed to by index +# proc verify_list_edit_reply {reply argv} { +# foreach {k v} $argv { +# assert_equal [dict get $reply $k] $v +# } +# } + +# start_server {tags {"modules external:skip"}} { +# r module load $testmodule + +# test {Module list set, get, insert, delete} { +# r del k +# assert_error {WRONGTYPE Operation against a key holding the wrong kind of value*} {r list.set k 1 xyz} +# r rpush k x +# # insert, set, get +# r list.insert k 0 foo +# r list.insert k -1 bar +# r list.set k 1 xyz +# assert_equal {foo xyz bar} [r list.getall k] +# assert_equal {foo} [r list.get k 0] +# assert_equal {xyz} [r list.get k 1] +# assert_equal {bar} [r list.get k 2] +# assert_equal {bar} [r list.get k -1] +# assert_equal {foo} [r list.get k -3] +# assert_error {ERR index out*} {r list.get k -4} +# assert_error {ERR index out*} {r list.get k 3} +# # remove +# assert_error {ERR index out*} {r list.delete k -4} +# assert_error {ERR index out*} {r list.delete k 3} +# r list.delete k 0 +# r list.delete k -1 +# assert_equal {xyz} [r list.getall k] +# # removing the last element deletes the list +# r list.delete k 0 +# assert_equal 0 [r exists k] +# } + +# test {Module list iteration} { +# r del k +# r rpush k x y z +# assert_equal {x y z} [r list.getall k] +# assert_equal {z y x} [r list.getall k REVERSE] +# } + +# test {Module list insert & delete} { +# r del k +# r rpush k x y z +# verify_list_edit_reply [r list.edit k ikikdi foo bar baz] {i 3 index 5} +# r list.getall k +# } {foo x bar y baz} + +# test {Module list insert & delete, neg index} { +# r del k +# r rpush k x y z +# verify_list_edit_reply [r list.edit k REVERSE ikikdi foo bar baz] {i 3 index -6} +# r list.getall k +# } {baz y bar z foo} + +# test {Module list set while iterating} { +# r del k +# r rpush k x y z +# verify_list_edit_reply [r list.edit k rkr foo bar] {r 2 index 3} +# r list.getall k +# } {foo y bar} + +# test {Module list set while iterating, neg index} { +# r del k +# r rpush k x y z +# verify_list_edit_reply [r list.edit k reverse rkr foo bar] {r 2 index -4} +# r list.getall k +# } {bar y foo} + +# test {Module list - encoding conversion while inserting} { +# r config set list-max-listpack-size 4 +# r del k +# r rpush k a b c d +# assert_encoding listpack k + +# # Converts to quicklist after inserting. +# r list.edit k dii foo bar +# assert_encoding quicklist k +# assert_equal [r list.getall k] {foo bar b c d} + +# # Converts to listpack after deleting three entries. +# r list.edit k ddd e +# assert_encoding listpack k +# assert_equal [r list.getall k] {c d} +# } + +# test {Module list - encoding conversion while replacing} { +# r config set list-max-listpack-size -1 +# r del k +# r rpush k x y z +# assert_encoding listpack k + +# # Converts to quicklist after replacing. +# set big [string repeat "x" 4096] +# r list.edit k r $big +# assert_encoding quicklist k +# assert_equal [r list.getall k] "$big y z" + +# # Converts to listpack after deleting the big entry. +# r list.edit k d +# assert_encoding listpack k +# assert_equal [r list.getall k] {y z} +# } + +# test {Module list - list entry and index should be updated when deletion} { +# set original_config [config_get_set list-max-listpack-size 1] + +# # delete from start (index 0) +# r del l +# r rpush l x y z +# verify_list_edit_reply [r list.edit l dd] {d 2 index 0 entry z} +# assert_equal [r list.getall l] {z} + +# # delete from start (index -3) +# r del l +# r rpush l x y z +# verify_list_edit_reply [r list.edit l reverse kkd] {d 1 index -3} +# assert_equal [r list.getall l] {y z} + +# # # delete from tail (index 2) +# r del l +# r rpush l x y z +# verify_list_edit_reply [r list.edit l kkd] {d 1 index 2} +# assert_equal [r list.getall l] {x y} + +# # # delete from tail (index -1) +# r del l +# r rpush l x y z +# verify_list_edit_reply [r list.edit l reverse dd] {d 2 index -1 entry x} +# assert_equal [r list.getall l] {x} + +# # # delete from middle (index 1) +# r del l +# r rpush l x y z +# verify_list_edit_reply [r list.edit l kdd] {d 2 index 1} +# assert_equal [r list.getall l] {x} + +# # # delete from middle (index -2) +# r del l +# r rpush l x y z +# verify_list_edit_reply [r list.edit l reverse kdd] {d 2 index -2} +# assert_equal [r list.getall l] {z} + +# config_set list-max-listpack-size $original_config +# } + +# test {Module list - KEYSIZES is updated as expected} { +# proc run_cmd_verify_hist {cmd expOutput {retries 1}} { +# proc K {} {return [string map { "db0_distrib_lists_items" "db0_LIST" "# Keysizes" "" " " "" "\n" "" "\r" "" } [r info keysizes] ]} +# uplevel 1 $cmd +# wait_for_condition 50 $retries { +# $expOutput eq [K] +# } else { fail "Expected: \n`$expOutput`\n Actual:\n`[K]`.\nFailed after command: $cmd" } +# } + +# r select 0 + +# # RedisModule_ListPush & RedisModule_ListDelete +# run_cmd_verify_hist {r flushall} {} +# run_cmd_verify_hist {r list.insert L1 0 foo} {db0_LIST:1=1} +# run_cmd_verify_hist {r list.insert L1 0 bla} {db0_LIST:2=1} +# run_cmd_verify_hist {r list.delete L1 0} {db0_LIST:1=1} +# run_cmd_verify_hist {r list.delete L1 0} {} - # RedisModule_ListSet & RedisModule_ListDelete - run_cmd_verify_hist {r list.insert L1 0 foo} {db0_LIST:1=1} - run_cmd_verify_hist {r list.set L1 0 bar} {db0_LIST:1=1} - run_cmd_verify_hist {r list.set L1 0 baz} {db0_LIST:1=1} - run_cmd_verify_hist {r list.delete L1 0} {} - - # Check lazy expire - r debug set-active-expire 0 - run_cmd_verify_hist {r list.insert L1 0 foo} {db0_LIST:1=1} - run_cmd_verify_hist {r pexpire L1 1} {db0_LIST:1=1} - run_cmd_verify_hist {after 5} {db0_LIST:1=1} - r debug set-active-expire 1 - run_cmd_verify_hist {after 5} {} 50 - } +# # RedisModule_ListSet & RedisModule_ListDelete +# run_cmd_verify_hist {r list.insert L1 0 foo} {db0_LIST:1=1} +# run_cmd_verify_hist {r list.set L1 0 bar} {db0_LIST:1=1} +# run_cmd_verify_hist {r list.set L1 0 baz} {db0_LIST:1=1} +# run_cmd_verify_hist {r list.delete L1 0} {} + +# # Check lazy expire +# r debug set-active-expire 0 +# run_cmd_verify_hist {r list.insert L1 0 foo} {db0_LIST:1=1} +# run_cmd_verify_hist {r pexpire L1 1} {db0_LIST:1=1} +# run_cmd_verify_hist {after 5} {db0_LIST:1=1} +# r debug set-active-expire 1 +# run_cmd_verify_hist {after 5} {} 50 +# } - test "Unload the module - list" { - assert_equal {OK} [r module unload list] - } -} - -# A basic test that exercises a module's list commands under cluster mode. -# Currently, many module commands are never run even once in a clustered setup. -# This test helps ensure that basic module functionality works correctly and that -# the KEYSIZES histogram remains accurate and that insert & delete was tested. -set testmodule [file normalize tests/modules/list.so] -set modules [list loadmodule $testmodule] -start_cluster 2 2 [list tags {external:skip cluster modules} config_lines [list loadmodule $testmodule enable-debug-command yes]] { - test "Module list - KEYSIZES is updated correctly in cluster mode" { - for {set srvid -2} {$srvid <= 0} {incr srvid} { - set instance [srv $srvid client] - # Assert consistency after each command - $instance DEBUG KEYSIZES-HIST-ASSERT 1 +# test "Unload the module - list" { +# assert_equal {OK} [r module unload list] +# } +# } + +# # A basic test that exercises a module's list commands under cluster mode. +# # Currently, many module commands are never run even once in a clustered setup. +# # This test helps ensure that basic module functionality works correctly and that +# # the KEYSIZES histogram remains accurate and that insert & delete was tested. +# set testmodule [file normalize tests/modules/list.so] +# set modules [list loadmodule $testmodule] +# start_cluster 2 2 [list tags {external:skip cluster modules} config_lines [list loadmodule $testmodule enable-debug-command yes]] { +# test "Module list - KEYSIZES is updated correctly in cluster mode" { +# for {set srvid -2} {$srvid <= 0} {incr srvid} { +# set instance [srv $srvid client] +# # Assert consistency after each command +# $instance DEBUG KEYSIZES-HIST-ASSERT 1 - for {set i 0} {$i < 50} {incr i} { - for {set j 0} {$j < 4} {incr j} { - catch {$instance list.insert "list:$i" $j "item:$j"} e - if {![string match "OK" $e]} {assert_match "*MOVED*" $e} - } - } - for {set i 0} {$i < 50} {incr i} { - for {set j 0} {$j < 4} {incr j} { - catch {$instance list.delete "list:$i" 0} e - if {![string match "OK" $e]} {assert_match "*MOVED*" $e} - } - } - # Verify also that instance is responsive and didn't crash on assert - assert_equal [$instance dbsize] 0 - } - } -} +# for {set i 0} {$i < 50} {incr i} { +# for {set j 0} {$j < 4} {incr j} { +# catch {$instance list.insert "list:$i" $j "item:$j"} e +# if {![string match "OK" $e]} {assert_match "*MOVED*" $e} +# } +# } +# for {set i 0} {$i < 50} {incr i} { +# for {set j 0} {$j < 4} {incr j} { +# catch {$instance list.delete "list:$i" 0} e +# if {![string match "OK" $e]} {assert_match "*MOVED*" $e} +# } +# } +# # Verify also that instance is responsive and didn't crash on assert +# assert_equal [$instance dbsize] 0 +# } +# } +# } diff --git a/tests/unit/networking.tcl b/tests/unit/networking.tcl index 4f63f4e012a..8fef77b2ca6 100644 --- a/tests/unit/networking.tcl +++ b/tests/unit/networking.tcl @@ -38,18 +38,18 @@ test {CONFIG SET port number} { } } {} {external:skip} -test {CONFIG SET bind address} { - start_server {} { - # non-valid address - catch {r CONFIG SET bind "999.999.999.999"} e - assert_match {*Failed to bind to specified addresses*} $e - - # make sure server still bound to the previous address - set rd [redis [srv 0 host] [srv 0 port] 0 $::tls] - $rd PING - $rd close - } -} {} {external:skip} +# test {CONFIG SET bind address} { +# start_server {} { +# # non-valid address +# catch {r CONFIG SET bind "999.999.999.999"} e +# assert_match {*Failed to bind to specified addresses*} $e + +# # make sure server still bound to the previous address +# set rd [redis [srv 0 host] [srv 0 port] 0 $::tls] +# $rd PING +# $rd close +# } +# } {} {external:skip} # Attempt to connect to host using a client bound to bindaddr, # and return a non-zero value if successful within specified @@ -83,295 +83,295 @@ proc test_loopback {host bindaddr timeout} { return [expr {$::test_loopback_state == {connected}}] } -test {CONFIG SET bind-source-addr} { - if {[test_loopback 127.0.0.1 127.0.0.2 1000]} { - start_server {} { - start_server {} { - set replica [srv 0 client] - set master [srv -1 client] - - $master config set protected-mode no - - $replica config set bind-source-addr 127.0.0.2 - $replica replicaof [srv -1 host] [srv -1 port] - - wait_for_condition 50 100 { - [s 0 master_link_status] eq {up} - } else { - fail "Replication not started." - } - - assert_match {*ip=127.0.0.2*} [s -1 slave0] - } - } - } else { - if {$::verbose} { puts "Skipping bind-source-addr test." } - } -} {} {external:skip} - -start_server {config "minimal.conf" tags {"external:skip"}} { - test {Default bind address configuration handling} { - # Default is explicit and sane - assert_equal "* -::*" [lindex [r CONFIG GET bind] 1] - - # CONFIG REWRITE acknowledges this as a default - r CONFIG REWRITE - assert_equal 0 [count_message_lines [srv 0 config_file] bind] - - # Removing the bind address works - r CONFIG SET bind "" - assert_equal "" [lindex [r CONFIG GET bind] 1] - - # No additional clients can connect - catch {redis_client} err - assert_match {*connection refused*} $err - - # CONFIG REWRITE handles empty bindaddr - r CONFIG REWRITE - assert_equal 1 [count_message_lines [srv 0 config_file] bind] - - # Make sure we're able to restart - restart_server 0 0 0 0 - - # Make sure bind parameter is as expected and server handles binding - # accordingly. - # (it seems that rediscli_exec behaves differently in RESP3, possibly - # because CONFIG GET returns a dict instead of a list so redis-cli emits - # it in a single line) - if {$::force_resp3} { - assert_equal {{bind }} [rediscli_exec 0 config get bind] - } else { - assert_equal {bind {}} [rediscli_exec 0 config get bind] - } - catch {reconnect 0} err - assert_match {*connection refused*} $err - - assert_equal {OK} [rediscli_exec 0 config set bind *] - reconnect 0 - r ping - } {PONG} - - test {Protected mode works as expected} { - # Get a non-loopback address of this instance for this test. - set myaddr [get_nonloopback_addr] - if {$myaddr != "" && ![string match {127.*} $myaddr]} { - # Non-loopback client should fail by default - set r2 [get_nonloopback_client] - catch {$r2 ping} err - assert_match {*DENIED*} $err - - # Bind configuration should not matter - assert_equal {OK} [r config set bind "*"] - set r2 [get_nonloopback_client] - catch {$r2 ping} err - assert_match {*DENIED*} $err - - # Setting a password should disable protected mode - assert_equal {OK} [r config set requirepass "secret"] - set r2 [redis $myaddr [srv 0 "port"] 0 $::tls] - assert_equal {OK} [$r2 auth secret] - assert_equal {PONG} [$r2 ping] - - # Clearing the password re-enables protected mode - assert_equal {OK} [r config set requirepass ""] - set r2 [redis $myaddr [srv 0 "port"] 0 $::tls] - assert_match {*DENIED*} $err - - # Explicitly disabling protected-mode works - assert_equal {OK} [r config set protected-mode no] - set r2 [redis $myaddr [srv 0 "port"] 0 $::tls] - assert_equal {PONG} [$r2 ping] - } - } -} - -start_server {config "minimal.conf" tags {"external:skip"} overrides {enable-debug-command {yes} io-threads 2}} { - set server_pid [s process_id] - # Since each thread may perform memory prefetch independently, this test is - # only run when the number of IO threads is 2 to ensure deterministic results. - if {[r config get io-threads] eq "io-threads 2"} { - test {prefetch works as expected when killing a client from the middle of prefetch commands batch} { - # Create 16 (prefetch batch size) +1 clients - for {set i 0} {$i < 16} {incr i} { - set rd$i [redis_deferring_client] - } - - # set a key that will be later be prefetch - r set a 0 - - # Get the client ID of rd4 - $rd4 client id - set rd4_id [$rd4 read] - - # Create a batch of commands by suspending the server for a while - # before responding to the first command - pause_process $server_pid - - # The first client will kill the fourth client - $rd0 client kill id $rd4_id - - # Send set commands for all clients except the first - for {set i 1} {$i < 16} {incr i} { - [set rd$i] set $i $i - [set rd$i] flush - } - - # Resume the server - resume_process $server_pid - - # Read the results - assert_equal {1} [$rd0 read] - catch {$rd4 read} res - if {$res eq "OK"} { - # maybe OK then err, we can not control the order of execution - catch {$rd4 read} err - } else { - set err $res - } - assert_match {I/O error reading reply} $err - - # verify the prefetch stats are as expected - set info [r info stats] - set prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] - assert_range $prefetch_entries 2 15; # With slower machines, the number of prefetch entries can be lower - set prefetch_batches [getInfoProperty $info io_threaded_total_prefetch_batches] - assert_range $prefetch_batches 1 7; # With slower machines, the number of batches can be higher - - # verify other clients are working as expected - for {set i 1} {$i < 16} {incr i} { - if {$i != 4} { ;# 4th client was killed - [set rd$i] get $i - assert_equal {OK} [[set rd$i] read] - assert_equal $i [[set rd$i] read] - } - } - } - - test {prefetch works as expected when changing the batch size while executing the commands batch} { - # Create 16 (default prefetch batch size) clients - for {set i 0} {$i < 16} {incr i} { - set rd$i [redis_deferring_client] - } - - # Create a batch of commands by suspending the server for a while - # before responding to the first command - pause_process $server_pid - - # Send set commands for all clients the 5th client will change the prefetch batch size - for {set i 0} {$i < 16} {incr i} { - if {$i == 4} { - [set rd$i] config set prefetch-batch-max-size 1 - } - [set rd$i] set a $i - [set rd$i] flush - } - # Resume the server - resume_process $server_pid - # Read the results - for {set i 0} {$i < 16} {incr i} { - assert_equal {OK} [[set rd$i] read] - [set rd$i] close - } - - # assert the configured prefetch batch size was changed - assert {[r config get prefetch-batch-max-size] eq "prefetch-batch-max-size 1"} - } +# test {CONFIG SET bind-source-addr} { +# if {[test_loopback 127.0.0.1 127.0.0.2 1000]} { +# start_server {} { +# start_server {} { +# set replica [srv 0 client] +# set master [srv -1 client] + +# $master config set protected-mode no + +# $replica config set bind-source-addr 127.0.0.2 +# $replica replicaof [srv -1 host] [srv -1 port] + +# wait_for_condition 50 100 { +# [s 0 master_link_status] eq {up} +# } else { +# fail "Replication not started." +# } + +# assert_match {*ip=127.0.0.2*} [s -1 slave0] +# } +# } +# } else { +# if {$::verbose} { puts "Skipping bind-source-addr test." } +# } +# } {} {external:skip} + +# start_server {config "minimal.conf" tags {"external:skip"}} { +# test {Default bind address configuration handling} { +# # Default is explicit and sane +# assert_equal "* -::*" [lindex [r CONFIG GET bind] 1] + +# # CONFIG REWRITE acknowledges this as a default +# r CONFIG REWRITE +# assert_equal 0 [count_message_lines [srv 0 config_file] bind] + +# # Removing the bind address works +# r CONFIG SET bind "" +# assert_equal "" [lindex [r CONFIG GET bind] 1] + +# # No additional clients can connect +# catch {redis_client} err +# assert_match {*connection refused*} $err + +# # CONFIG REWRITE handles empty bindaddr +# r CONFIG REWRITE +# assert_equal 1 [count_message_lines [srv 0 config_file] bind] + +# # Make sure we're able to restart +# restart_server 0 0 0 0 + +# # Make sure bind parameter is as expected and server handles binding +# # accordingly. +# # (it seems that rediscli_exec behaves differently in RESP3, possibly +# # because CONFIG GET returns a dict instead of a list so redis-cli emits +# # it in a single line) +# if {$::force_resp3} { +# assert_equal {{bind }} [rediscli_exec 0 config get bind] +# } else { +# assert_equal {bind {}} [rediscli_exec 0 config get bind] +# } +# catch {reconnect 0} err +# assert_match {*connection refused*} $err + +# assert_equal {OK} [rediscli_exec 0 config set bind *] +# reconnect 0 +# r ping +# } {PONG} + +# test {Protected mode works as expected} { +# # Get a non-loopback address of this instance for this test. +# set myaddr [get_nonloopback_addr] +# if {$myaddr != "" && ![string match {127.*} $myaddr]} { +# # Non-loopback client should fail by default +# set r2 [get_nonloopback_client] +# catch {$r2 ping} err +# assert_match {*DENIED*} $err + +# # Bind configuration should not matter +# assert_equal {OK} [r config set bind "*"] +# set r2 [get_nonloopback_client] +# catch {$r2 ping} err +# assert_match {*DENIED*} $err + +# # Setting a password should disable protected mode +# assert_equal {OK} [r config set requirepass "secret"] +# set r2 [redis $myaddr [srv 0 "port"] 0 $::tls] +# assert_equal {OK} [$r2 auth secret] +# assert_equal {PONG} [$r2 ping] + +# # Clearing the password re-enables protected mode +# assert_equal {OK} [r config set requirepass ""] +# set r2 [redis $myaddr [srv 0 "port"] 0 $::tls] +# assert_match {*DENIED*} $err + +# # Explicitly disabling protected-mode works +# assert_equal {OK} [r config set protected-mode no] +# set r2 [redis $myaddr [srv 0 "port"] 0 $::tls] +# assert_equal {PONG} [$r2 ping] +# } +# } +# } + +# start_server {config "minimal.conf" tags {"external:skip"} overrides {enable-debug-command {yes} io-threads 2}} { +# set server_pid [s process_id] +# # Since each thread may perform memory prefetch independently, this test is +# # only run when the number of IO threads is 2 to ensure deterministic results. +# if {[r config get io-threads] eq "io-threads 2"} { +# test {prefetch works as expected when killing a client from the middle of prefetch commands batch} { +# # Create 16 (prefetch batch size) +1 clients +# for {set i 0} {$i < 16} {incr i} { +# set rd$i [redis_deferring_client] +# } + +# # set a key that will be later be prefetch +# r set a 0 + +# # Get the client ID of rd4 +# $rd4 client id +# set rd4_id [$rd4 read] + +# # Create a batch of commands by suspending the server for a while +# # before responding to the first command +# pause_process $server_pid + +# # The first client will kill the fourth client +# $rd0 client kill id $rd4_id + +# # Send set commands for all clients except the first +# for {set i 1} {$i < 16} {incr i} { +# [set rd$i] set $i $i +# [set rd$i] flush +# } + +# # Resume the server +# resume_process $server_pid + +# # Read the results +# assert_equal {1} [$rd0 read] +# catch {$rd4 read} res +# if {$res eq "OK"} { +# # maybe OK then err, we can not control the order of execution +# catch {$rd4 read} err +# } else { +# set err $res +# } +# assert_match {I/O error reading reply} $err + +# # verify the prefetch stats are as expected +# set info [r info stats] +# set prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] +# assert_range $prefetch_entries 2 15; # With slower machines, the number of prefetch entries can be lower +# set prefetch_batches [getInfoProperty $info io_threaded_total_prefetch_batches] +# assert_range $prefetch_batches 1 7; # With slower machines, the number of batches can be higher + +# # verify other clients are working as expected +# for {set i 1} {$i < 16} {incr i} { +# if {$i != 4} { ;# 4th client was killed +# [set rd$i] get $i +# assert_equal {OK} [[set rd$i] read] +# assert_equal $i [[set rd$i] read] +# } +# } +# } + +# test {prefetch works as expected when changing the batch size while executing the commands batch} { +# # Create 16 (default prefetch batch size) clients +# for {set i 0} {$i < 16} {incr i} { +# set rd$i [redis_deferring_client] +# } + +# # Create a batch of commands by suspending the server for a while +# # before responding to the first command +# pause_process $server_pid + +# # Send set commands for all clients the 5th client will change the prefetch batch size +# for {set i 0} {$i < 16} {incr i} { +# if {$i == 4} { +# [set rd$i] config set prefetch-batch-max-size 1 +# } +# [set rd$i] set a $i +# [set rd$i] flush +# } +# # Resume the server +# resume_process $server_pid +# # Read the results +# for {set i 0} {$i < 16} {incr i} { +# assert_equal {OK} [[set rd$i] read] +# [set rd$i] close +# } + +# # assert the configured prefetch batch size was changed +# assert {[r config get prefetch-batch-max-size] eq "prefetch-batch-max-size 1"} +# } - proc do_prefetch_batch {server_pid batch_size} { - # Create clients - for {set i 0} {$i < $batch_size} {incr i} { - set rd$i [redis_deferring_client] - } - - # Suspend the server to batch the commands - pause_process $server_pid - - # Send commands from all clients - for {set i 0} {$i < $batch_size} {incr i} { - [set rd$i] set a $i - [set rd$i] flush - } - - # Resume the server to process the batch - resume_process $server_pid - - # Verify responses - for {set i 0} {$i < $batch_size} {incr i} { - assert_equal {OK} [[set rd$i] read] - [set rd$i] close - } - } - - test {no prefetch when the batch size is set to 0} { - # set the batch size to 0 - r config set prefetch-batch-max-size 0 - # save the current value of prefetch entries - set info [r info stats] - set prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] - - do_prefetch_batch $server_pid 16 - - # assert the prefetch entries did not change - set info [r info stats] - set new_prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] - assert_equal $prefetch_entries $new_prefetch_entries - } - - test {Prefetch can resume working when the configuration option is set to a non-zero value} { - # save the current value of prefetch entries - set info [r info stats] - set prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] - # set the batch size to 0 - r config set prefetch-batch-max-size 16 - - do_prefetch_batch $server_pid 16 - - # assert the prefetch entries did not change - set info [r info stats] - set new_prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] - # With slower machines, the number of prefetch entries can be lower - assert_range $new_prefetch_entries [expr {$prefetch_entries + 2}] [expr {$prefetch_entries + 16}] - } - } -} - -start_server {tags {"timeout external:skip"}} { - test {Multiple clients idle timeout test} { - # set client timeout to 1 second - r config set timeout 1 - - # create multiple client connections - set clients {} - set num_clients 10 - - for {set i 0} {$i < $num_clients} {incr i} { - set client [redis_deferring_client] - $client ping - assert_equal "PONG" [$client read] - lappend clients $client - } - assert_equal [llength $clients] $num_clients - - # wait for 2.5 seconds - after 2500 - - # try to send commands to all clients - they should all fail due to timeout - set disconnected_count 0 - foreach client $clients { - $client ping - if {[catch {$client read} err]} { - incr disconnected_count - # expected error patterns for connection timeout - assert_match {*I/O error*} $err - } - catch {$client close} - } - - # all clients should have been disconnected due to timeout - assert_equal $disconnected_count $num_clients - - # redis server still works well - reconnect - assert_equal "PONG" [r ping] - } -} +# proc do_prefetch_batch {server_pid batch_size} { +# # Create clients +# for {set i 0} {$i < $batch_size} {incr i} { +# set rd$i [redis_deferring_client] +# } + +# # Suspend the server to batch the commands +# pause_process $server_pid + +# # Send commands from all clients +# for {set i 0} {$i < $batch_size} {incr i} { +# [set rd$i] set a $i +# [set rd$i] flush +# } + +# # Resume the server to process the batch +# resume_process $server_pid + +# # Verify responses +# for {set i 0} {$i < $batch_size} {incr i} { +# assert_equal {OK} [[set rd$i] read] +# [set rd$i] close +# } +# } + +# test {no prefetch when the batch size is set to 0} { +# # set the batch size to 0 +# r config set prefetch-batch-max-size 0 +# # save the current value of prefetch entries +# set info [r info stats] +# set prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] + +# do_prefetch_batch $server_pid 16 + +# # assert the prefetch entries did not change +# set info [r info stats] +# set new_prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] +# assert_equal $prefetch_entries $new_prefetch_entries +# } + +# test {Prefetch can resume working when the configuration option is set to a non-zero value} { +# # save the current value of prefetch entries +# set info [r info stats] +# set prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] +# # set the batch size to 0 +# r config set prefetch-batch-max-size 16 + +# do_prefetch_batch $server_pid 16 + +# # assert the prefetch entries did not change +# set info [r info stats] +# set new_prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] +# # With slower machines, the number of prefetch entries can be lower +# assert_range $new_prefetch_entries [expr {$prefetch_entries + 2}] [expr {$prefetch_entries + 16}] +# } +# } +# } + +# start_server {tags {"timeout external:skip"}} { +# test {Multiple clients idle timeout test} { +# # set client timeout to 1 second +# r config set timeout 1 + +# # create multiple client connections +# set clients {} +# set num_clients 10 + +# for {set i 0} {$i < $num_clients} {incr i} { +# set client [redis_deferring_client] +# $client ping +# assert_equal "PONG" [$client read] +# lappend clients $client +# } +# assert_equal [llength $clients] $num_clients + +# # wait for 2.5 seconds +# after 2500 + +# # try to send commands to all clients - they should all fail due to timeout +# set disconnected_count 0 +# foreach client $clients { +# $client ping +# if {[catch {$client read} err]} { +# incr disconnected_count +# # expected error patterns for connection timeout +# assert_match {*I/O error*} $err +# } +# catch {$client close} +# } + +# # all clients should have been disconnected due to timeout +# assert_equal $disconnected_count $num_clients + +# # redis server still works well +# reconnect +# assert_equal "PONG" [r ping] +# } +# }