Commit 819d291b authored by Oran Agra's avatar Oran Agra
Browse files

Merge remote-tracking branch 'origin/unstable' into 7.2

parents a51eb05b 936cfa46
......@@ -19,8 +19,7 @@ proc get_cluster_nodes {id {status "*"}} {
set args [split $l]
set node [dict create \
id [lindex $args 0] \
addr [lindex [split [lindex $args 1] ,] 0] \
shard-id [lindex [split [lindex [split [lindex $args 1] ,] 2] = ] 1]\
addr [lindex $args 1] \
flags [split [lindex $args 2] ,] \
slaveof [lindex $args 3] \
ping_sent [lindex $args 4] \
......
......@@ -82,3 +82,8 @@ test "Coverage: CLUSTER HELP" {
test "Coverage: ASKING" {
assert_equal {OK} [R 0 ASKING]
}
test "CLUSTER SLAVES and CLUSTER REPLICAS with zero replicas" {
assert_equal {} [R 0 cluster slaves [R 0 CLUSTER MYID]]
assert_equal {} [R 0 cluster replicas [R 0 CLUSTER MYID]]
}
......@@ -16,11 +16,20 @@ test "Cluster is up" {
test "The first master has actually two slaves" {
wait_for_condition 1000 50 {
[llength [lindex [R 0 role] 2]] == 2
&& [llength [R 0 cluster replicas [R 0 CLUSTER MYID]]] == 2
} else {
fail "replicas didn't connect"
}
}
test "CLUSTER SLAVES and CLUSTER REPLICAS output is consistent" {
# Because we already have command output that cover CLUSTER REPLICAS elsewhere,
# here we simply judge whether their output is consistent to cover CLUSTER SLAVES.
set res [R 0 cluster slaves [R 0 CLUSTER MYID]]
set res2 [R 0 cluster replicas [R 0 CLUSTER MYID]]
assert_equal $res $res2
}
test {Slaves of #0 are instance #5 and #10 as expected} {
set port0 [get_instance_attrib redis 0 port]
assert {[lindex [R 5 role] 2] == $port0}
......
......@@ -77,6 +77,43 @@ test "slot migration is invalid from primary to replica" {
assert_match "*Target node is not a master" $err
}
proc count_bound_slots {n} {
set slot_count 0
foreach slot_range_mapping [$n cluster slots] {
set start_slot [lindex $slot_range_mapping 0]
set end_slot [lindex $slot_range_mapping 1]
incr slot_count [expr $end_slot - $start_slot + 1]
}
return $slot_count
}
test "slot must be unbound on the owner when it is deleted" {
set node0 [Rn 0]
set node1 [Rn 1]
assert {[count_bound_slots $node0] eq 16384}
assert {[count_bound_slots $node1] eq 16384}
set slot_to_delete 0
# Delete
$node0 CLUSTER DELSLOTS $slot_to_delete
# Verify
# The node that owns the slot must unbind the slot that was deleted
wait_for_condition 1000 50 {
[count_bound_slots $node0] == 16383
} else {
fail "Cluster slot deletion was not recorded on the node that owns the slot"
}
# We don't propagate slot deletion across all nodes in the cluster.
# This can lead to extra redirect before the clients find out that the slot is unbound.
wait_for_condition 1000 50 {
[count_bound_slots $node1] == 16384
} else {
fail "Cluster slot deletion should not be propagated to all nodes in the cluster"
}
}
if {$::tls} {
test {CLUSTER SLOTS from non-TLS client in TLS cluster} {
set slots_tls [R 0 cluster slots]
......
......@@ -182,6 +182,10 @@ test "Test the replica reports a loading state while it's loading" {
fail "Replica never transitioned to loading"
}
# Verify cluster shards and cluster slots (deprecated) API responds while the node is loading data.
R $replica_id CLUSTER SHARDS
R $replica_id CLUSTER SLOTS
# Speed up the key loading and verify everything resumes
R $replica_id config set key-load-delay 0
......@@ -227,26 +231,6 @@ test "CLUSTER MYSHARDID reports same id for both primary and replica" {
}
}
test "CLUSTER NODES reports correct shard id" {
for {set i 0} {$i < 8} {incr i} {
set nodes [get_cluster_nodes $i]
set node_id_to_shardid_mapping []
foreach n $nodes {
set node_shard_id [dict get $n shard-id]
set node_id [dict get $n id]
assert_equal [string length $node_shard_id] 40
if {[dict exists $node_id_to_shardid_mapping $node_id]} {
assert_equal [dict get $node_id_to_shardid_mapping $node_id] $node_shard_id
} else {
dict set node_id_to_shardid_mapping $node_id $node_shard_id
}
if {[lindex [dict get $n flags] 0] eq "myself"} {
assert_equal [R $i cluster myshardid] [dict get $n shard-id]
}
}
}
}
test "New replica receives primary's shard id" {
#find a primary
set id 0
......
......@@ -204,6 +204,30 @@ start_server {tags {"repl external:skip"}} {
assert {[$master dbsize] > 0}
}
test {spopwithcount rewrite srem command} {
$master del myset
set content {}
for {set j 0} {$j < 4000} {} {
lappend content [incr j]
}
$master sadd myset {*}$content
$master spop myset 1023
$master spop myset 1024
$master spop myset 1025
assert_match 928 [$master scard myset]
assert_match {*calls=3,*} [cmdrstat spop $master]
wait_for_condition 50 100 {
[status $slave master_repl_offset] == [status $master master_repl_offset]
} else {
fail "SREM replication inconsistency."
}
assert_match {*calls=4,*} [cmdrstat srem $slave]
assert_match 928 [$slave scard myset]
}
test {Replication of SPOP command -- alsoPropagate() API} {
$master del myset
set size [expr 1+[randomInt 100]]
......
......@@ -248,6 +248,7 @@ test {Replica client-output-buffer size is limited to backlog_limit/16 when no r
set master [srv 0 client]
set master_host [srv 0 host]
set master_port [srv 0 port]
$master config set maxmemory-policy allkeys-lru
$master config set repl-backlog-size 16384
$master config set client-output-buffer-limit "replica 32768 32768 60"
......@@ -262,13 +263,21 @@ test {Replica client-output-buffer size is limited to backlog_limit/16 when no r
fail "Can't turn the instance into a replica"
}
# Write a big key that is gonna breach the obuf limit and cause the replica to disconnect,
# then in the same event loop, add at least 16 more keys, and enable eviction, so that the
# eviction code has a chance to call flushSlavesOutputBuffers, and then run PING to trigger the eviction code
set _v [prepare_value $keysize]
$master set key $_v
$master write "[format_command mset key $_v k1 1 k2 2 k3 3 k4 4 k5 5 k6 6 k7 7 k8 8 k9 9 ka a kb b kc c kd d ke e kf f kg g kh h]config set maxmemory 1\r\nping\r\n"
$master flush
$master read
$master read
$master read
wait_for_ofs_sync $master $replica
# Write another key to force the test to wait for another event loop iteration
# to give the serverCron a chance to disconnect replicas with COB size exceeding the limits
$master set key1 "1"
# Write another key to force the test to wait for another event loop iteration so that we
# give the serverCron a chance to disconnect replicas with COB size exceeding the limits
$master config set maxmemory 0
$master set key1 1
wait_for_ofs_sync $master $replica
assert {[status $master connected_slaves] == 1}
......@@ -279,6 +288,8 @@ test {Replica client-output-buffer size is limited to backlog_limit/16 when no r
fail "replica client-output-buffer usage is higher than expected."
}
# now we expect the replica to re-connect but fail partial sync (it doesn't have large
# enough COB limit and must result in a full-sync)
assert {[status $master sync_partial_ok] == 0}
# Before this fix (#11905), the test would trigger an assertion in 'o->used >= c->ref_block_pos'
......
......@@ -321,6 +321,62 @@ int do_rm_call_async(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
return REDISMODULE_OK;
}
typedef struct ThreadedAsyncRMCallCtx{
RedisModuleBlockedClient *bc;
RedisModuleCallReply *reply;
} ThreadedAsyncRMCallCtx;
void *send_async_reply(void *arg) {
ThreadedAsyncRMCallCtx *ta_rm_call_ctx = arg;
rm_call_async_on_unblocked(NULL, ta_rm_call_ctx->reply, ta_rm_call_ctx->bc);
RedisModule_Free(ta_rm_call_ctx);
return NULL;
}
/* Called when the command that was blocked on 'RM_Call' gets unblocked
* and schedule a thread to send the reply to the blocked client. */
static void rm_call_async_reply_on_thread(RedisModuleCtx *ctx, RedisModuleCallReply *reply, void *private_data) {
UNUSED(ctx);
ThreadedAsyncRMCallCtx *ta_rm_call_ctx = RedisModule_Alloc(sizeof(*ta_rm_call_ctx));
ta_rm_call_ctx->bc = private_data;
ta_rm_call_ctx->reply = reply;
pthread_t tid;
int res = pthread_create(&tid, NULL, send_async_reply, ta_rm_call_ctx);
assert(res == 0);
}
/*
* Callback for do_rm_call_async_on_thread.
* Gets the command to invoke as the first argument to the command and runs it,
* passing the rest of the arguments to the command invocation.
* If the command got blocked, blocks the client and unblock on a background thread.
* this allows check the K (allow blocking) argument to RM_Call, and make sure that the reply
* that passes to unblock handler is owned by the handler and are not attached to any
* context that might be freed after the callback ends.
*/
int do_rm_call_async_on_thread(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
UNUSED(argv);
UNUSED(argc);
if(argc < 2){
return RedisModule_WrongArity(ctx);
}
const char* cmd = RedisModule_StringPtrLen(argv[1], NULL);
RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "KEv", argv + 2, argc - 2);
if(RedisModule_CallReplyType(rep) != REDISMODULE_REPLY_PROMISE) {
rm_call_async_send_reply(ctx, rep);
} else {
RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, NULL, NULL, NULL, 0);
RedisModule_CallReplyPromiseSetUnblockHandler(rep, rm_call_async_reply_on_thread, bc);
RedisModule_FreeCallReply(rep);
}
return REDISMODULE_OK;
}
/* Private data for wait_and_do_rm_call_async that holds information about:
* 1. the block client, to unblock when done.
* 2. the arguments, contains the command to run using RM_Call */
......@@ -553,6 +609,39 @@ static int is_in_slow_bg_operation(RedisModuleCtx *ctx, RedisModuleString **argv
return REDISMODULE_OK;
}
static void timer_callback(RedisModuleCtx *ctx, void *data)
{
UNUSED(ctx);
RedisModuleBlockedClient *bc = data;
// Get Redis module context
RedisModuleCtx *reply_ctx = RedisModule_GetThreadSafeContext(bc);
// Reply to client
RedisModule_ReplyWithSimpleString(reply_ctx, "OK");
// Unblock client
RedisModule_UnblockClient(bc, NULL);
// Free the Redis module context
RedisModule_FreeThreadSafeContext(reply_ctx);
}
int unblock_by_timer(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
if (argc != 2)
return RedisModule_WrongArity(ctx);
long long period;
if (RedisModule_StringToLongLong(argv[1],&period) != REDISMODULE_OK)
return RedisModule_ReplyWithError(ctx,"ERR invalid period");
RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, NULL, NULL, NULL, 0);
RedisModule_CreateTimer(ctx, period, timer_callback, bc);
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
......@@ -571,6 +660,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
"write", 0, 0, 0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx, "do_rm_call_async_on_thread", do_rm_call_async_on_thread,
"write", 0, 0, 0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx, "do_rm_call_async_script_mode", do_rm_call_async,
"write", 0, 0, 0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
......@@ -612,5 +705,8 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
if (RedisModule_CreateCommand(ctx, "is_in_slow_bg_operation", is_in_slow_bg_operation, "allow-busy", 0, 0, 0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx, "unblock_by_timer", unblock_by_timer, "", 0, 0, 0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}
......@@ -131,9 +131,72 @@ int fsl_push(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
fsl->list[fsl->length++] = ele;
RedisModule_SignalKeyAsReady(ctx, argv[1]);
RedisModule_ReplicateVerbatim(ctx);
return RedisModule_ReplyWithSimpleString(ctx, "OK");
}
typedef struct {
RedisModuleString *keyname;
long long ele;
} timer_data_t;
static void timer_callback(RedisModuleCtx *ctx, void *data)
{
timer_data_t *td = data;
fsl_t *fsl;
if (!get_fsl(ctx, td->keyname, REDISMODULE_WRITE, 1, &fsl, 1))
return;
if (fsl->length == LIST_SIZE)
return; /* list is full */
if (fsl->length != 0 && fsl->list[fsl->length-1] >= td->ele)
return; /* new element has to be greater than the head element */
fsl->list[fsl->length++] = td->ele;
RedisModule_SignalKeyAsReady(ctx, td->keyname);
RedisModule_Replicate(ctx, "FSL.PUSH", "sl", td->keyname, td->ele);
RedisModule_FreeString(ctx, td->keyname);
RedisModule_Free(td);
}
/* FSL.PUSHTIMER <key> <int> <period-in-ms> - Push the number 9000 to the fixed-size list (to the right).
* It must be greater than the element in the head of the list. */
int fsl_pushtimer(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
if (argc != 4)
return RedisModule_WrongArity(ctx);
long long ele;
if (RedisModule_StringToLongLong(argv[2],&ele) != REDISMODULE_OK)
return RedisModule_ReplyWithError(ctx,"ERR invalid integer");
long long period;
if (RedisModule_StringToLongLong(argv[3],&period) != REDISMODULE_OK)
return RedisModule_ReplyWithError(ctx,"ERR invalid period");
fsl_t *fsl;
if (!get_fsl(ctx, argv[1], REDISMODULE_WRITE, 1, &fsl, 1))
return REDISMODULE_OK;
if (fsl->length == LIST_SIZE)
return RedisModule_ReplyWithError(ctx,"ERR list is full");
timer_data_t *td = RedisModule_Alloc(sizeof(*td));
td->keyname = argv[1];
RedisModule_RetainString(ctx, td->keyname);
td->ele = ele;
RedisModuleTimerID id = RedisModule_CreateTimer(ctx, period, timer_callback, td);
RedisModule_ReplyWithLongLong(ctx, id);
return REDISMODULE_OK;
}
int bpop_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
......@@ -145,6 +208,9 @@ int bpop_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
RedisModule_Assert(fsl->length);
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
/* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
RedisModule_ReplicateVerbatim(ctx);
return REDISMODULE_OK;
}
......@@ -181,6 +247,8 @@ int fsl_bpop(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
} else {
RedisModule_Assert(fsl->length);
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
/* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
RedisModule_ReplicateVerbatim(ctx);
}
return REDISMODULE_OK;
......@@ -201,6 +269,8 @@ int bpopgt_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int arg
RedisModule_Assert(fsl->length);
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
/* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
RedisModule_ReplicateVerbatim(ctx);
return REDISMODULE_OK;
}
......@@ -247,6 +317,8 @@ int fsl_bpopgt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
} else {
RedisModule_Assert(fsl->length);
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
/* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
RedisModule_ReplicateVerbatim(ctx);
}
return REDISMODULE_OK;
......@@ -270,6 +342,8 @@ int bpoppush_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int a
long long ele = src->list[--src->length];
dst->list[dst->length++] = ele;
RedisModule_SignalKeyAsReady(ctx, dst_keyname);
/* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
RedisModule_ReplicateVerbatim(ctx);
return RedisModule_ReplyWithLongLong(ctx, ele);
}
......@@ -314,6 +388,8 @@ int fsl_bpoppush(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
dst->list[dst->length++] = ele;
RedisModule_SignalKeyAsReady(ctx, argv[2]);
RedisModule_ReplyWithLongLong(ctx, ele);
/* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
RedisModule_ReplicateVerbatim(ctx);
}
return REDISMODULE_OK;
......@@ -350,6 +426,8 @@ int blockonkeys_popall_reply_callback(RedisModuleCtx *ctx, RedisModuleString **a
RedisModule_ReplyWithString(ctx, elem);
RedisModule_FreeString(ctx, elem);
}
/* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
RedisModule_ReplicateVerbatim(ctx);
RedisModule_ReplySetArrayLength(ctx, len);
} else {
RedisModule_ReplyWithError(ctx, "ERR Not a list");
......@@ -415,6 +493,7 @@ int blockonkeys_lpush(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (!strncasecmp(str, "blockonkeys.lpush_unblock", len)) {
RedisModule_SignalKeyAsReady(ctx, argv[1]);
}
RedisModule_ReplicateVerbatim(ctx);
return RedisModule_ReplyWithSimpleString(ctx, "OK");
}
......@@ -433,6 +512,8 @@ int blockonkeys_blpopn_reply_callback(RedisModuleCtx *ctx, RedisModuleString **a
RedisModule_ReplyWithString(ctx, elem);
RedisModule_FreeString(ctx, elem);
}
/* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
RedisModule_ReplicateVerbatim(ctx);
result = REDISMODULE_OK;
} else if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_LIST ||
RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) {
......@@ -491,6 +572,8 @@ int blockonkeys_blpopn(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
RedisModule_ReplyWithString(ctx, elem);
RedisModule_FreeString(ctx, elem);
}
/* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
RedisModule_ReplicateVerbatim(ctx);
} else {
RedisModule_BlockClientOnKeys(ctx, blockonkeys_blpopn_reply_callback,
timeout ? blockonkeys_blpopn_timeout_callback : blockonkeys_blpopn_abort_callback,
......@@ -524,6 +607,9 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
if (RedisModule_CreateCommand(ctx,"fsl.push",fsl_push,"write",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"fsl.pushtimer",fsl_pushtimer,"write",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"fsl.bpop",fsl_bpop,"write",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
......
......@@ -8,8 +8,11 @@ static const char log_command_name[] = "commandfilter.log";
static const char ping_command_name[] = "commandfilter.ping";
static const char retained_command_name[] = "commandfilter.retained";
static const char unregister_command_name[] = "commandfilter.unregister";
static const char unfiltered_clientid_name[] = "unfilter_clientid";
static int in_log_command = 0;
unsigned long long unfiltered_clientid = 0;
static RedisModuleCommandFilter *filter, *filter1;
static RedisModuleString *retained;
......@@ -89,6 +92,26 @@ int CommandFilter_LogCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int
return REDISMODULE_OK;
}
int CommandFilter_UnfilteredClientdId(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
if (argc < 2)
return RedisModule_WrongArity(ctx);
long long id;
if (RedisModule_StringToLongLong(argv[1], &id) != REDISMODULE_OK) {
RedisModule_ReplyWithError(ctx, "invalid client id");
return REDISMODULE_OK;
}
if (id < 0) {
RedisModule_ReplyWithError(ctx, "invalid client id");
return REDISMODULE_OK;
}
unfiltered_clientid = id;
RedisModule_ReplyWithSimpleString(ctx, "OK");
return REDISMODULE_OK;
}
/* Filter to protect against Bug #11894 reappearing
*
* ensures that the filter is only run the first time through, and not on reprocessing
......@@ -117,6 +140,9 @@ void CommandFilter_BlmoveSwap(RedisModuleCommandFilterCtx *filter)
void CommandFilter_CommandFilter(RedisModuleCommandFilterCtx *filter)
{
unsigned long long id = RedisModule_CommandFilterGetClientId(filter);
if (id == unfiltered_clientid) return;
if (in_log_command) return; /* don't process our own RM_Call() from CommandFilter_LogCommand() */
/* Fun manipulations:
......@@ -192,6 +218,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
CommandFilter_UnregisterCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx, unfiltered_clientid_name,
CommandFilter_UnfilteredClientdId, "admin", 1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if ((filter = RedisModule_RegisterCommandFilter(ctx, CommandFilter_CommandFilter,
noself ? REDISMODULE_CMDFILTER_NOSELF : 0))
== NULL) return REDISMODULE_ERR;
......
......@@ -5,6 +5,18 @@
#define UNUSED(V) ((void) V)
int cmd_publish_classic_multi(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
if (argc < 3)
return RedisModule_WrongArity(ctx);
RedisModule_ReplyWithArray(ctx, argc-2);
for (int i = 2; i < argc; i++) {
int receivers = RedisModule_PublishMessage(ctx, argv[1], argv[i]);
RedisModule_ReplyWithLongLong(ctx, receivers);
}
return REDISMODULE_OK;
}
int cmd_publish_classic(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
if (argc != 3)
......@@ -35,6 +47,9 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
if (RedisModule_CreateCommand(ctx,"publish.classic",cmd_publish_classic,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"publish.classic_multi",cmd_publish_classic_multi,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"publish.shard",cmd_publish_shard,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
......
......@@ -41,7 +41,10 @@ test "New master [join $addr {:}] role matches" {
test "Update log level" {
set current_loglevel [S 0 SENTINEL CONFIG GET loglevel]
assert {[lindex $current_loglevel 1] == {notice}}
S 0 SENTINEL CONFIG SET loglevel warning
foreach {loglevel} {debug verbose notice warning nothing} {
S 0 SENTINEL CONFIG SET loglevel $loglevel
set updated_loglevel [S 0 SENTINEL CONFIG GET loglevel]
assert {[lindex $updated_loglevel 1] == {warning}}
assert {[lindex $updated_loglevel 1] == $loglevel}
}
}
......@@ -63,15 +63,21 @@ test "The old master eventually gets reconfigured as a slave" {
}
foreach flag {crash-after-election crash-after-promotion} {
# Before each SIMULATE-FAILURE test, re-source init-tests to get a clean environment
source "../tests/includes/init-tests.tcl"
test "SENTINEL SIMULATE-FAILURE $flag works" {
assert_equal {OK} [S 0 SENTINEL SIMULATE-FAILURE $flag]
# Trigger a failover, failover will trigger leader election, replica promotion
# Sentinel may enter failover and exit before the command, catch it and allow it
wait_for_condition 300 50 {
[catch {S 0 SENTINEL FAILOVER mymaster}] == 0
||
([catch {S 0 SENTINEL FAILOVER mymaster} reply] == 1 &&
[string match {*couldn't open socket: connection refused*} $reply])
} else {
catch {S 0 SENTINEL FAILOVER mymaster} reply
puts [S 0 SENTINEL REPLICAS mymaster]
fail "Sentinel manual failover did not work, got: $reply"
}
......
source "../tests/includes/init-tests.tcl"
test "SENTINEL CONFIG SET and SENTINEL CONFIG GET handles multiple variables" {
foreach_sentinel_id id {
assert_equal {OK} [S $id SENTINEL CONFIG SET resolve-hostnames yes announce-port 1234]
}
assert_match {*yes*1234*} [S 1 SENTINEL CONFIG GET resolve-hostnames announce-port]
assert_match {announce-port 1234} [S 1 SENTINEL CONFIG GET announce-port]
}
test "SENTINEL CONFIG GET for duplicate and unknown variables" {
assert_equal {OK} [S 1 SENTINEL CONFIG SET resolve-hostnames yes announce-port 1234]
assert_match {resolve-hostnames yes} [S 1 SENTINEL CONFIG GET resolve-hostnames resolve-hostnames does-not-exist]
}
test "SENTINEL CONFIG GET for patterns" {
assert_equal {OK} [S 1 SENTINEL CONFIG SET loglevel notice announce-port 1234 announce-hostnames yes ]
assert_match {loglevel notice} [S 1 SENTINEL CONFIG GET log* *level loglevel]
assert_match {announce-hostnames yes announce-ip*announce-port 1234} [S 1 SENTINEL CONFIG GET announce*]
}
test "SENTINEL CONFIG SET duplicate variables" {
catch {[S 1 SENTINEL CONFIG SET resolve-hostnames yes announce-port 1234 announce-port 100]} e
if {![string match "*Duplicate argument*" $e]} {
fail "Should give wrong arity error"
}
}
test "SENTINEL CONFIG SET, one option does not exist" {
foreach_sentinel_id id {
assert_equal {OK} [S $id SENTINEL CONFIG SET announce-port 111]
catch {[S $id SENTINEL CONFIG SET does-not-exist yes announce-port 1234]} e
if {![string match "*Invalid argument*" $e]} {
fail "Should give Invalid argument error"
}
}
# The announce-port should not be set to 1234 as it was called with a wrong argument
assert_match {*111*} [S 1 SENTINEL CONFIG GET announce-port]
}
test "SENTINEL CONFIG SET, one option with wrong value" {
foreach_sentinel_id id {
assert_equal {OK} [S $id SENTINEL CONFIG SET resolve-hostnames no]
catch {[S $id SENTINEL CONFIG SET announce-port -1234 resolve-hostnames yes]} e
if {![string match "*Invalid value*" $e]} {
fail "Expected to return Invalid value error"
}
}
# The resolve-hostnames should not be set to yes as it was called after an argument with an invalid value
assert_match {*no*} [S 1 SENTINEL CONFIG GET resolve-hostnames]
}
test "SENTINEL CONFIG SET, wrong number of arguments" {
catch {[S 1 SENTINEL CONFIG SET resolve-hostnames yes announce-port 1234 announce-ip]} e
if {![string match "*Missing argument*" $e]} {
fail "Expected to return Missing argument error"
}
}
......@@ -70,9 +70,18 @@ proc continuous_slot_allocation {masters} {
# tests run.
proc cluster_setup {masters node_count slot_allocator code} {
# Have all nodes meet
if {$::tls} {
set tls_cluster [lindex [R 0 CONFIG GET tls-cluster] 1]
}
if {$::tls && !$tls_cluster} {
for {set i 1} {$i < $node_count} {incr i} {
R 0 CLUSTER MEET [srv -$i host] [srv -$i pport]
}
} else {
for {set i 1} {$i < $node_count} {incr i} {
R 0 CLUSTER MEET [srv -$i host] [srv -$i port]
}
}
$slot_allocator $masters
......@@ -150,3 +159,43 @@ proc get_cluster_nodes id {
}
return $nodes
}
# Returns 1 if no node knows node_id, 0 if any node knows it.
proc node_is_forgotten {node_id} {
for {set j 0} {$j < [llength $::servers]} {incr j} {
set cluster_nodes [R $j CLUSTER NODES]
if { [string match "*$node_id*" $cluster_nodes] } {
return 0
}
}
return 1
}
# Isolate a node from the cluster and give it a new nodeid
proc isolate_node {id} {
set node_id [R $id CLUSTER MYID]
R $id CLUSTER RESET HARD
# Here we additionally test that CLUSTER FORGET propagates to all nodes.
set other_id [expr $id == 0 ? 1 : 0]
R $other_id CLUSTER FORGET $node_id
wait_for_condition 50 100 {
[node_is_forgotten $node_id]
} else {
fail "CLUSTER FORGET was not propagated to all nodes"
}
}
# Check if cluster's view of hostnames is consistent
proc are_hostnames_propagated {match_string} {
for {set j 0} {$j < [llength $::servers]} {incr j} {
set cfg [R $j cluster slots]
foreach node $cfg {
for {set i 2} {$i < [llength $node]} {incr i} {
if {! [string match $match_string [lindex [lindex [lindex $node $i] 3] 1]] } {
return 0
}
}
}
}
return 1
}
......@@ -496,7 +496,8 @@ proc start_server {options {code undefined}} {
# start every server on a different port
set port [find_available_port $::baseport $::portcount]
if {$::tls} {
dict set config "port" 0
set pport [find_available_port $::baseport $::portcount]
dict set config "port" $pport
dict set config "tls-port" $port
dict set config "tls-cluster" "yes"
dict set config "tls-replication" "yes"
......@@ -567,6 +568,8 @@ proc start_server {options {code undefined}} {
puts "Port $port was already busy, trying another port..."
set port [find_available_port $::baseport $::portcount]
if {$::tls} {
set pport [find_available_port $::baseport $::portcount]
dict set config port $pport
dict set config "tls-port" $port
} else {
dict set config port $port
......@@ -615,6 +618,9 @@ proc start_server {options {code undefined}} {
dict set srv "stdout" $stdout
dict set srv "stderr" $stderr
dict set srv "unixsocket" $unixsocket
if {$::tls} {
dict set srv "pport" $pport
}
# if a block of code is supplied, we wait for the server to become
# available, create a client object and kill the server afterwards
......
......@@ -1106,3 +1106,12 @@ proc lmap args {
}
set temp
}
proc format_command {args} {
set cmd "*[llength $args]\r\n"
foreach a $args {
append cmd "$[string length $a]\r\n$a\r\n"
}
set _ $cmd
}
......@@ -98,9 +98,11 @@ set ::all_tests {
unit/cluster/cli
unit/cluster/scripting
unit/cluster/hostnames
unit/cluster/human-announced-nodename
unit/cluster/multi-slot-operations
unit/cluster/slot-ownership
unit/cluster/links
unit/cluster/cluster-response-tls
}
# Index to the next test to run in the ::all_tests list.
set ::next_test 0
......
......@@ -323,6 +323,23 @@ 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 {Users can be configured to authenticate with any password} {
r ACL setuser newuser nopass
r AUTH newuser zipzapblabla
......
......@@ -214,6 +214,16 @@ start_server {tags {"bitops"}} {
}
r del mystring
}
test {BITFIELD_RO with only key as argument} {
set res [r bitfield_ro bits]
assert {$res eq {}}
}
test {BITFIELD_RO fails when write option is used} {
catch {r bitfield_ro bits set u8 0 100 get u8 0} err
assert_match {*ERR BITFIELD_RO only supports the GET subcommand*} $err
}
}
start_server {tags {"repl external:skip"}} {
......@@ -240,12 +250,12 @@ start_server {tags {"repl external:skip"}} {
assert_equal 100 [$slave bitfield_ro bits get u8 0]
}
test {BITFIELD_RO with only key as argument} {
test {BITFIELD_RO with only key as argument on read-only replica} {
set res [$slave bitfield_ro bits]
assert {$res eq {}}
}
test {BITFIELD_RO fails when write option is used} {
test {BITFIELD_RO fails when write option is used on read-only replica} {
catch {$slave bitfield_ro bits set u8 0 100 get u8 0} err
assert_match {*ERR BITFIELD_RO only supports the GET subcommand*} $err
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment