Commit f5ca1f9e authored by Oran Agra's avatar Oran Agra
Browse files

Merge unstable into 6.2

parents 92bde124 61d3fdb4
source "../../../tests/support/cli.tcl"
proc config_set_all_nodes {keyword value} {
foreach_redis_id id {
R $id config set $keyword $value
}
}
proc fix_cluster {addr} {
set code [catch {
exec ../../../src/redis-cli {*}[rediscli_tls_config "../../../tests"] --cluster fix $addr << yes
} result]
if {$code != 0} {
puts "redis-cli --cluster fix returns non-zero exit code, output below:\n$result"
}
# Note: redis-cli --cluster fix may return a non-zero exit code if nodes don't agree,
# but we can ignore that and rely on the check below.
assert_cluster_state ok
wait_for_condition 100 100 {
[catch {exec ../../../src/redis-cli {*}[rediscli_tls_config "../../../tests"] --cluster check $addr} result] == 0
} else {
puts "redis-cli --cluster check returns non-zero exit code, output below:\n$result"
fail "Cluster could not settle with configuration"
}
}
...@@ -64,6 +64,8 @@ proc exec_instance {type dirname cfgfile} { ...@@ -64,6 +64,8 @@ proc exec_instance {type dirname cfgfile} {
proc spawn_instance {type base_port count {conf {}} {base_conf_file ""}} { proc spawn_instance {type base_port count {conf {}} {base_conf_file ""}} {
for {set j 0} {$j < $count} {incr j} { for {set j 0} {$j < $count} {incr j} {
set port [find_available_port $base_port $::redis_port_count] set port [find_available_port $base_port $::redis_port_count]
# plaintext port (only used for TLS cluster)
set pport 0
# Create a directory for this instance. # Create a directory for this instance.
set dirname "${type}_${j}" set dirname "${type}_${j}"
lappend ::dirs $dirname lappend ::dirs $dirname
...@@ -83,7 +85,9 @@ proc spawn_instance {type base_port count {conf {}} {base_conf_file ""}} { ...@@ -83,7 +85,9 @@ proc spawn_instance {type base_port count {conf {}} {base_conf_file ""}} {
puts $cfg "tls-port $port" puts $cfg "tls-port $port"
puts $cfg "tls-replication yes" puts $cfg "tls-replication yes"
puts $cfg "tls-cluster yes" puts $cfg "tls-cluster yes"
puts $cfg "port 0" # plaintext port, only used by plaintext clients in a TLS cluster
set pport [find_available_port $base_port $::redis_port_count]
puts $cfg "port $pport"
puts $cfg [format "tls-cert-file %s/../../tls/server.crt" [pwd]] puts $cfg [format "tls-cert-file %s/../../tls/server.crt" [pwd]]
puts $cfg [format "tls-key-file %s/../../tls/server.key" [pwd]] puts $cfg [format "tls-key-file %s/../../tls/server.key" [pwd]]
puts $cfg [format "tls-client-cert-file %s/../../tls/client.crt" [pwd]] puts $cfg [format "tls-client-cert-file %s/../../tls/client.crt" [pwd]]
...@@ -118,6 +122,8 @@ proc spawn_instance {type base_port count {conf {}} {base_conf_file ""}} { ...@@ -118,6 +122,8 @@ proc spawn_instance {type base_port count {conf {}} {base_conf_file ""}} {
set cfg [open $cfgfile a+] set cfg [open $cfgfile a+]
if {$::tls} { if {$::tls} {
puts $cfg "tls-port $port" puts $cfg "tls-port $port"
set pport [find_available_port $base_port $::redis_port_count]
puts $cfg "port $pport"
} else { } else {
puts $cfg "port $port" puts $cfg "port $port"
} }
...@@ -143,6 +149,7 @@ proc spawn_instance {type base_port count {conf {}} {base_conf_file ""}} { ...@@ -143,6 +149,7 @@ proc spawn_instance {type base_port count {conf {}} {base_conf_file ""}} {
pid $pid \ pid $pid \
host $::host \ host $::host \
port $port \ port $port \
plaintext-port $pport \
link $link \ link $link \
] ]
} }
...@@ -492,6 +499,14 @@ proc RI {n field} { ...@@ -492,6 +499,14 @@ proc RI {n field} {
get_info_field [R $n info] $field get_info_field [R $n info] $field
} }
proc RPort {n} {
if {$::tls} {
return [lindex [R $n config get tls-port] 1]
} else {
return [lindex [R $n config get port] 1]
}
}
# Iterate over IDs of sentinel or redis instances. # Iterate over IDs of sentinel or redis instances.
proc foreach_instance_id {instances idvar code} { proc foreach_instance_id {instances idvar code} {
upvar 1 $idvar id upvar 1 $idvar id
......
...@@ -518,5 +518,16 @@ test {corrupt payload: fuzzer findings - HRANDFIELD on bad ziplist} { ...@@ -518,5 +518,16 @@ test {corrupt payload: fuzzer findings - HRANDFIELD on bad ziplist} {
} }
} }
test {corrupt payload: fuzzer findings - stream with no records} {
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
r config set sanitize-dump-payload no
r debug set-skip-checksum-validation 1
r restore _stream 0 "\x0F\x01\x10\x00\x00\x01\x78\x4D\x55\x68\x09\x00\x00\x00\x00\x00\x00\x00\x00\x40\x42\x42\x00\x00\x00\x18\x00\x02\x01\x01\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x00\x01\x02\x01\x00\x01\x00\x01\x01\x01\x00\x01\x05\x01\x03\x01\x3E\x01\x00\x01\x01\x01\x82\x5F\x31\x03\x05\x01\x02\x01\x50\x01\x00\x01\x01\x01\x02\x01\x05\x23\xFF\x02\x81\x00\x00\x01\x78\x4D\x55\x68\x59\x00\x01\x07\x6D\x79\x67\x72\x6F\x75\x70\x81\x00\x00\x01\x78\x4D\x55\x68\x47\x00\x01\x00\x00\x01\x78\x4D\x55\x68\x47\x00\x00\x00\x00\x00\x00\x00\x00\x9F\x68\x55\x4D\x78\x01\x00\x00\x01\x01\x05\x41\x6C\x69\x63\x65\x85\x68\x55\x4D\x78\x01\x00\x00\x01\x00\x00\x01\x78\x4D\x55\x68\x47\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\xF1\xC0\x72\x70\x39\x40\x1E\xA9" replace
catch {r XREAD STREAMS _stream $}
assert_equal [count_log_message 0 "crashed by signal"] 0
assert_equal [count_log_message 0 "Guru Meditation"] 1
}
}
} ;# tags } ;# tags
...@@ -111,7 +111,13 @@ start_server {} { ...@@ -111,7 +111,13 @@ start_server {} {
$replica1 replicaof no one $replica1 replicaof no one
$replica2 replicaof 127.0.0.1 1 ;# we can't promote it to master since that will cycle the replication id $replica2 replicaof 127.0.0.1 1 ;# we can't promote it to master since that will cycle the replication id
$master config set repl-ping-replica-period 1 $master config set repl-ping-replica-period 1
after 1500 set replofs [status $master master_repl_offset]
wait_for_condition 50 100 {
[status $replica3 master_repl_offset] > $replofs &&
[status $replica4 master_repl_offset] > $replofs
} else {
fail "replica didn't sync in time"
}
# make everyone sync from the replica1 that didn't get the last ping from the old master # make everyone sync from the replica1 that didn't get the last ping from the old master
# replica4 will keep syncing from the old master which now syncs from replica1 # replica4 will keep syncing from the old master which now syncs from replica1
...@@ -195,10 +201,16 @@ start_server {} { ...@@ -195,10 +201,16 @@ start_server {} {
fail "Chained replica not replicating from its master" fail "Chained replica not replicating from its master"
} }
# Do a write on the master, and wait for 3 seconds for the master to # Do a write on the master, and wait for the master to
# send some PINGs to its replica # send some PINGs to its replica
$R(0) INCR counter2 $R(0) INCR counter2
after 2000 set replofs [status $R(0) master_repl_offset]
wait_for_condition 50 100 {
[status $R(1) master_repl_offset] > $replofs &&
[status $R(2) master_repl_offset] > $replofs
} else {
fail "replica didn't sync in time"
}
set sync_partial_master [status $R(0) sync_partial_ok] set sync_partial_master [status $R(0) sync_partial_ok]
set sync_partial_replica [status $R(1) sync_partial_ok] set sync_partial_replica [status $R(1) sync_partial_ok]
$R(0) CONFIG SET repl-ping-replica-period 100 $R(0) CONFIG SET repl-ping-replica-period 100
......
...@@ -207,6 +207,28 @@ start_server {tags {"cli"}} { ...@@ -207,6 +207,28 @@ start_server {tags {"cli"}} {
assert_equal "foo\nbar" [run_cli lrange list 0 -1] assert_equal "foo\nbar" [run_cli lrange list 0 -1]
} }
test_nontty_cli "Quoted input arguments" {
r set "\x00\x00" "value"
assert_equal "value" [run_cli --quoted-input get {"\x00\x00"}]
}
test_nontty_cli "No accidental unquoting of input arguments" {
run_cli --quoted-input set {"\x41\x41"} quoted-val
run_cli set {"\x41\x41"} unquoted-val
assert_equal "quoted-val" [r get AA]
assert_equal "unquoted-val" [r get {"\x41\x41"}]
}
test_nontty_cli "Invalid quoted input arguments" {
catch {run_cli --quoted-input set {"Unterminated}} err
assert_match {*exited abnormally*} $err
# A single arg that unquotes to two arguments is also not expected
catch {run_cli --quoted-input set {"arg1" "arg2"}} err
assert_match {*exited abnormally*} $err
}
test_nontty_cli "Read last argument from pipe" { test_nontty_cli "Read last argument from pipe" {
assert_equal "OK" [run_cli_with_input_pipe "echo foo" set key] assert_equal "OK" [run_cli_with_input_pipe "echo foo" set key]
assert_equal "foo\n" [r get key] assert_equal "foo\n" [r get key]
...@@ -247,6 +269,20 @@ start_server {tags {"cli"}} { ...@@ -247,6 +269,20 @@ start_server {tags {"cli"}} {
test_redis_cli_rdb_dump test_redis_cli_rdb_dump
} }
test "Scan mode" {
r flushdb
populate 1000 key: 1
# basic use
assert_equal 1000 [llength [split [run_cli --scan]]]
# pattern
assert_equal {key:2} [run_cli --scan --pattern "*:2"]
# pattern matching with a quoted string
assert_equal {key:2} [run_cli --scan --quoted-pattern {"*:\x32"}]
}
test "Connecting as a replica" { test "Connecting as a replica" {
set fd [open_cli "--replica"] set fd [open_cli "--replica"]
wait_for_condition 500 500 { wait_for_condition 500 500 {
......
...@@ -79,12 +79,16 @@ start_server {tags {"repl"}} { ...@@ -79,12 +79,16 @@ start_server {tags {"repl"}} {
$master config set min-slaves-max-lag 2 $master config set min-slaves-max-lag 2
$master config set min-slaves-to-write 1 $master config set min-slaves-to-write 1
assert {[$master set foo bar] eq {OK}} assert {[$master set foo bar] eq {OK}}
$slave deferred 1 exec kill -SIGSTOP [srv 0 pid]
$slave debug sleep 6 wait_for_condition 100 100 {
after 4000 [catch {$master set foo bar}] != 0
catch {$master set foo bar} e } else {
set e fail "Master didn't become readonly"
} {NOREPLICAS*} }
catch {$master set foo bar} err
assert_match {NOREPLICAS*} $err
exec kill -SIGCONT [srv 0 pid]
}
} }
} }
......
...@@ -196,9 +196,11 @@ start_server {tags {"repl"}} { ...@@ -196,9 +196,11 @@ start_server {tags {"repl"}} {
} {master} } {master}
test {SLAVEOF should start with link status "down"} { test {SLAVEOF should start with link status "down"} {
r multi
r slaveof [srv -1 host] [srv -1 port] r slaveof [srv -1 host] [srv -1 port]
s master_link_status r info replication
} {down} r exec
} {*master_link_status:down*}
test {The role should immediately be changed to "replica"} { test {The role should immediately be changed to "replica"} {
s role s role
...@@ -595,9 +597,9 @@ start_server {tags {"repl"}} { ...@@ -595,9 +597,9 @@ start_server {tags {"repl"}} {
$master debug populate 20000 test 10000 $master debug populate 20000 test 10000
$master config set rdbcompression no $master config set rdbcompression no
# If running on Linux, we also measure utime/stime to detect possible I/O handling issues # If running on Linux, we also measure utime/stime to detect possible I/O handling issues
set os [catch {exec unamee}] set os [catch {exec uname}]
set measure_time [expr {$os == "Linux"} ? 1 : 0] set measure_time [expr {$os == "Linux"} ? 1 : 0]
foreach all_drop {no slow fast all} { foreach all_drop {no slow fast all timeout} {
test "diskless $all_drop replicas drop during rdb pipe" { test "diskless $all_drop replicas drop during rdb pipe" {
set replicas {} set replicas {}
set replicas_alive {} set replicas_alive {}
...@@ -614,7 +616,7 @@ start_server {tags {"repl"}} { ...@@ -614,7 +616,7 @@ start_server {tags {"repl"}} {
# so that the whole rdb generation process is bound to that # so that the whole rdb generation process is bound to that
set loglines [count_log_lines -1] set loglines [count_log_lines -1]
[lindex $replicas 0] config set repl-diskless-load swapdb [lindex $replicas 0] config set repl-diskless-load swapdb
[lindex $replicas 0] config set key-load-delay 100 [lindex $replicas 0] config set key-load-delay 100 ;# 20k keys and 100 microseconds sleep means at least 2 seconds
[lindex $replicas 0] replicaof $master_host $master_port [lindex $replicas 0] replicaof $master_host $master_port
[lindex $replicas 1] replicaof $master_host $master_port [lindex $replicas 1] replicaof $master_host $master_port
...@@ -645,6 +647,12 @@ start_server {tags {"repl"}} { ...@@ -645,6 +647,12 @@ start_server {tags {"repl"}} {
exec kill [srv -1 pid] exec kill [srv -1 pid]
set replicas_alive [lreplace $replicas_alive 0 0] set replicas_alive [lreplace $replicas_alive 0 0]
} }
if {$all_drop == "timeout"} {
$master config set repl-timeout 2
# we want the slow replica to hang on a key for very long so it'll reach repl-timeout
exec kill -SIGSTOP [srv -1 pid]
after 2000
}
# wait for rdb child to exit # wait for rdb child to exit
wait_for_condition 500 100 { wait_for_condition 500 100 {
...@@ -663,6 +671,14 @@ start_server {tags {"repl"}} { ...@@ -663,6 +671,14 @@ start_server {tags {"repl"}} {
if {$all_drop == "slow" || $all_drop == "fast"} { if {$all_drop == "slow" || $all_drop == "fast"} {
wait_for_log_messages -2 {"*Diskless rdb transfer, done reading from pipe, 1 replicas still up*"} $loglines 1 1 wait_for_log_messages -2 {"*Diskless rdb transfer, done reading from pipe, 1 replicas still up*"} $loglines 1 1
} }
if {$all_drop == "timeout"} {
wait_for_log_messages -2 {"*Disconnecting timedout replica (full sync)*"} $loglines 1 1
wait_for_log_messages -2 {"*Diskless rdb transfer, done reading from pipe, 1 replicas still up*"} $loglines 1 1
# master disconnected the slow replica, remove from array
set replicas_alive [lreplace $replicas_alive 0 0]
# release it
exec kill -SIGCONT [srv -1 pid]
}
# make sure we don't have a busy loop going thought epoll_wait # make sure we don't have a busy loop going thought epoll_wait
if {$measure_time} { if {$measure_time} {
...@@ -676,7 +692,7 @@ start_server {tags {"repl"}} { ...@@ -676,7 +692,7 @@ start_server {tags {"repl"}} {
puts "master utime: $master_utime" puts "master utime: $master_utime"
puts "master stime: $master_stime" puts "master stime: $master_stime"
} }
if {!$::no_latency && ($all_drop == "all" || $all_drop == "slow")} { if {!$::no_latency && ($all_drop == "all" || $all_drop == "slow" || $all_drop == "timeout")} {
assert {$master_utime < 70} assert {$master_utime < 70}
assert {$master_stime < 70} assert {$master_stime < 70}
} }
...@@ -720,7 +736,7 @@ start_server {tags {"repl"}} { ...@@ -720,7 +736,7 @@ start_server {tags {"repl"}} {
test "diskless replication child being killed is collected" { test "diskless replication child being killed is collected" {
# when diskless master is waiting for the replica to become writable # when diskless master is waiting for the replica to become writable
# it removes the read event from the rdb pipe so if the child gets killed # it removes the read event from the rdb pipe so if the child gets killed
# the replica will hung. and the master may not collect the pid with wait3 # the replica will hung. and the master may not collect the pid with waitpid
start_server {tags {"repl"}} { start_server {tags {"repl"}} {
set master [srv 0 client] set master [srv 0 client]
set master_host [srv 0 host] set master_host [srv 0 host]
......
...@@ -38,6 +38,8 @@ ...@@ -38,6 +38,8 @@
/** strores all the keys on which we got 'loaded' keyspace notification **/ /** strores all the keys on which we got 'loaded' keyspace notification **/
RedisModuleDict *loaded_event_log = NULL; RedisModuleDict *loaded_event_log = NULL;
/** stores all the keys on which we got 'module' keyspace notification **/
RedisModuleDict *module_event_log = NULL;
static int KeySpace_NotificationLoaded(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key){ static int KeySpace_NotificationLoaded(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key){
REDISMODULE_NOT_USED(ctx); REDISMODULE_NOT_USED(ctx);
...@@ -78,6 +80,50 @@ static int KeySpace_NotificationGeneric(RedisModuleCtx *ctx, int type, const cha ...@@ -78,6 +80,50 @@ static int KeySpace_NotificationGeneric(RedisModuleCtx *ctx, int type, const cha
return REDISMODULE_OK; return REDISMODULE_OK;
} }
static int KeySpace_NotificationModule(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) {
REDISMODULE_NOT_USED(ctx);
REDISMODULE_NOT_USED(type);
REDISMODULE_NOT_USED(event);
const char* keyName = RedisModule_StringPtrLen(key, NULL);
int nokey;
RedisModule_DictGetC(module_event_log, (void*)keyName, strlen(keyName), &nokey);
if(nokey){
RedisModule_DictSetC(module_event_log, (void*)keyName, strlen(keyName), RedisModule_HoldString(ctx, key));
}
return REDISMODULE_OK;
}
static int cmdNotify(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
if(argc != 2){
return RedisModule_WrongArity(ctx);
}
RedisModule_NotifyKeyspaceEvent(ctx, REDISMODULE_NOTIFY_MODULE, "notify", argv[1]);
RedisModule_ReplyWithNull(ctx);
return REDISMODULE_OK;
}
static int cmdIsModuleKeyNotified(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
if(argc != 2){
return RedisModule_WrongArity(ctx);
}
const char* key = RedisModule_StringPtrLen(argv[1], NULL);
int nokey;
RedisModuleString* keyStr = RedisModule_DictGetC(module_event_log, (void*)key, strlen(key), &nokey);
RedisModule_ReplyWithArray(ctx, 2);
RedisModule_ReplyWithLongLong(ctx, !nokey);
if(nokey){
RedisModule_ReplyWithNull(ctx);
}else{
RedisModule_ReplyWithString(ctx, keyStr);
}
return REDISMODULE_OK;
}
static int cmdIsKeyLoaded(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){ static int cmdIsKeyLoaded(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
if(argc != 2){ if(argc != 2){
return RedisModule_WrongArity(ctx); return RedisModule_WrongArity(ctx);
...@@ -171,6 +217,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -171,6 +217,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
} }
loaded_event_log = RedisModule_CreateDict(ctx); loaded_event_log = RedisModule_CreateDict(ctx);
module_event_log = RedisModule_CreateDict(ctx);
int keySpaceAll = RedisModule_GetKeyspaceNotificationFlagsAll(); int keySpaceAll = RedisModule_GetKeyspaceNotificationFlagsAll();
...@@ -187,6 +234,18 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -187,6 +234,18 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
return REDISMODULE_ERR; return REDISMODULE_ERR;
} }
if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_MODULE, KeySpace_NotificationModule) != REDISMODULE_OK){
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx,"keyspace.notify", cmdNotify,"",0,0,0) == REDISMODULE_ERR){
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx,"keyspace.is_module_key_notified", cmdIsModuleKeyNotified,"",0,0,0) == REDISMODULE_ERR){
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx,"keyspace.is_key_loaded", cmdIsKeyLoaded,"",0,0,0) == REDISMODULE_ERR){ if (RedisModule_CreateCommand(ctx,"keyspace.is_key_loaded", cmdIsKeyLoaded,"",0,0,0) == REDISMODULE_ERR){
return REDISMODULE_ERR; return REDISMODULE_ERR;
} }
...@@ -219,6 +278,16 @@ int RedisModule_OnUnload(RedisModuleCtx *ctx) { ...@@ -219,6 +278,16 @@ int RedisModule_OnUnload(RedisModuleCtx *ctx) {
RedisModule_FreeString(ctx, val); RedisModule_FreeString(ctx, val);
} }
RedisModule_FreeDict(ctx, loaded_event_log); RedisModule_FreeDict(ctx, loaded_event_log);
RedisModule_DictIteratorStop(iter);
loaded_event_log = NULL; loaded_event_log = NULL;
iter = RedisModule_DictIteratorStartC(module_event_log, "^", NULL, 0);
while((key = RedisModule_DictNextC(iter, &keyLen, (void**)&val))){
RedisModule_FreeString(ctx, val);
}
RedisModule_FreeDict(ctx, module_event_log);
RedisModule_DictIteratorStop(iter);
module_event_log = NULL;
return REDISMODULE_OK; return REDISMODULE_OK;
} }
...@@ -70,6 +70,44 @@ int propagateTestTimerCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int ...@@ -70,6 +70,44 @@ int propagateTestTimerCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int
return REDISMODULE_OK; return REDISMODULE_OK;
} }
/* Timer callback. */
void timerNestedHandler(RedisModuleCtx *ctx, void *data) {
int repl = (long long)data;
/* The goal is the trigger a module command that calls RM_Replicate
* in order to test MULTI/EXEC structre */
RedisModule_Replicate(ctx,"INCRBY","cc","timer-nested-start","1");
RedisModuleCallReply *reply = RedisModule_Call(ctx,"propagate-test.nested", repl? "!" : "");
RedisModule_FreeCallReply(reply);
RedisModule_Replicate(ctx,"INCRBY","cc","timer-nested-end","1");
}
int propagateTestTimerNestedCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
RedisModuleTimerID timer_id =
RedisModule_CreateTimer(ctx,100,timerNestedHandler,(void*)0);
REDISMODULE_NOT_USED(timer_id);
RedisModule_ReplyWithSimpleString(ctx,"OK");
return REDISMODULE_OK;
}
int propagateTestTimerNestedReplCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
RedisModuleTimerID timer_id =
RedisModule_CreateTimer(ctx,100,timerNestedHandler,(void*)1);
REDISMODULE_NOT_USED(timer_id);
RedisModule_ReplyWithSimpleString(ctx,"OK");
return REDISMODULE_OK;
}
/* The thread entry point. */ /* The thread entry point. */
void *threadMain(void *arg) { void *threadMain(void *arg) {
REDISMODULE_NOT_USED(arg); REDISMODULE_NOT_USED(arg);
...@@ -131,6 +169,42 @@ int propagateTestMixedCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int ...@@ -131,6 +169,42 @@ int propagateTestMixedCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int
return REDISMODULE_OK; return REDISMODULE_OK;
} }
int propagateTestNestedCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
RedisModuleCallReply *reply;
/* This test mixes multiple propagation systems. */
reply = RedisModule_Call(ctx, "INCR", "c!", "using-call");
RedisModule_FreeCallReply(reply);
reply = RedisModule_Call(ctx,"propagate-test.simple", "!");
RedisModule_FreeCallReply(reply);
RedisModule_Replicate(ctx,"INCR","c","counter-3");
RedisModule_Replicate(ctx,"INCR","c","counter-4");
reply = RedisModule_Call(ctx, "INCR", "c!", "after-call");
RedisModule_FreeCallReply(reply);
RedisModule_ReplyWithSimpleString(ctx,"OK");
return REDISMODULE_OK;
}
int propagateTestIncr(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
REDISMODULE_NOT_USED(argc);
RedisModuleCallReply *reply;
/* This test propagates the module command, not the INCR it executes. */
reply = RedisModule_Call(ctx, "INCR", "s", argv[1]);
RedisModule_ReplyWithCallReply(ctx,reply);
RedisModule_FreeCallReply(reply);
RedisModule_ReplicateVerbatim(ctx);
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv); REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc); REDISMODULE_NOT_USED(argc);
...@@ -143,6 +217,16 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -143,6 +217,16 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
"",1,1,1) == REDISMODULE_ERR) "",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test.timer-nested",
propagateTestTimerNestedCommand,
"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test.timer-nested-repl",
propagateTestTimerNestedReplCommand,
"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test.thread", if (RedisModule_CreateCommand(ctx,"propagate-test.thread",
propagateTestThreadCommand, propagateTestThreadCommand,
"",1,1,1) == REDISMODULE_ERR) "",1,1,1) == REDISMODULE_ERR)
...@@ -158,5 +242,15 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -158,5 +242,15 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
"",1,1,1) == REDISMODULE_ERR) "",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test.nested",
propagateTestNestedCommand,
"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test.incr",
propagateTestIncr,
"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK; return REDISMODULE_OK;
} }
...@@ -9,7 +9,7 @@ if {$::simulate_error} { ...@@ -9,7 +9,7 @@ if {$::simulate_error} {
} }
test "Basic failover works if the master is down" { test "Basic failover works if the master is down" {
set old_port [RI $master_id tcp_port] set old_port [RPort $master_id]
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
assert {[lindex $addr 1] == $old_port} assert {[lindex $addr 1] == $old_port}
kill_instance redis $master_id kill_instance redis $master_id
...@@ -53,7 +53,7 @@ test "ODOWN is not possible without N (quorum) Sentinels reports" { ...@@ -53,7 +53,7 @@ test "ODOWN is not possible without N (quorum) Sentinels reports" {
foreach_sentinel_id id { foreach_sentinel_id id {
S $id SENTINEL SET mymaster quorum [expr $sentinels+1] S $id SENTINEL SET mymaster quorum [expr $sentinels+1]
} }
set old_port [RI $master_id tcp_port] set old_port [RPort $master_id]
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
assert {[lindex $addr 1] == $old_port} assert {[lindex $addr 1] == $old_port}
kill_instance redis $master_id kill_instance redis $master_id
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
source "../tests/includes/init-tests.tcl" source "../tests/includes/init-tests.tcl"
test "We can failover with Sentinel 1 crashed" { test "We can failover with Sentinel 1 crashed" {
set old_port [RI $master_id tcp_port] set old_port [RPort $master_id]
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
assert {[lindex $addr 1] == $old_port} assert {[lindex $addr 1] == $old_port}
......
...@@ -10,7 +10,7 @@ source "../tests/includes/init-tests.tcl" ...@@ -10,7 +10,7 @@ source "../tests/includes/init-tests.tcl"
proc 02_test_slaves_replication {} { proc 02_test_slaves_replication {} {
uplevel 1 { uplevel 1 {
test "Check that slaves replicate from current master" { test "Check that slaves replicate from current master" {
set master_port [RI $master_id tcp_port] set master_port [RPort $master_id]
foreach_redis_id id { foreach_redis_id id {
if {$id == $master_id} continue if {$id == $master_id} continue
if {[instance_is_killed redis $id]} continue if {[instance_is_killed redis $id]} continue
...@@ -28,7 +28,7 @@ proc 02_test_slaves_replication {} { ...@@ -28,7 +28,7 @@ proc 02_test_slaves_replication {} {
proc 02_crash_and_failover {} { proc 02_crash_and_failover {} {
uplevel 1 { uplevel 1 {
test "Crash the master and force a failover" { test "Crash the master and force a failover" {
set old_port [RI $master_id tcp_port] set old_port [RPort $master_id]
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
assert {[lindex $addr 1] == $old_port} assert {[lindex $addr 1] == $old_port}
kill_instance redis $master_id kill_instance redis $master_id
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
source "../tests/includes/init-tests.tcl" source "../tests/includes/init-tests.tcl"
test "Manual failover works" { test "Manual failover works" {
set old_port [RI $master_id tcp_port] set old_port [RPort $master_id]
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
assert {[lindex $addr 1] == $old_port} assert {[lindex $addr 1] == $old_port}
catch {S 0 SENTINEL FAILOVER mymaster} reply catch {S 0 SENTINEL FAILOVER mymaster} reply
......
source "../tests/includes/init-tests.tcl"
test "Check acceptable replica-priority values" {
foreach_redis_id id {
if {$id == $master_id} continue
# ensure replica-announced accepts yes and no
catch {R $id CONFIG SET replica-announced no} e
if {$e ne "OK"} {
fail "Unable to set replica-announced to no"
}
catch {R $id CONFIG SET replica-announced yes} e
if {$e ne "OK"} {
fail "Unable to set replica-announced to yes"
}
# ensure a random value throw error
catch {R $id CONFIG SET replica-announced 321} e
if {$e eq "OK"} {
fail "Able to set replica-announced with something else than yes or no (321) whereas it should not be possible"
}
catch {R $id CONFIG SET replica-announced a3b2c1} e
if {$e eq "OK"} {
fail "Able to set replica-announced with something else than yes or no (a3b2c1) whereas it should not be possible"
}
# test only the first redis replica, no need to double test
break
}
}
proc 10_test_number_of_replicas {n_replicas_expected} {
test "Check sentinel replies with $n_replicas_expected replicas" {
# ensure sentinels replies with the right number of replicas
foreach_sentinel_id id {
# retries 40 x 500ms = 20s as SENTINEL_INFO_PERIOD = 10s
set len [llength [S $id SENTINEL REPLICAS mymaster]]
wait_for_condition 40 500 {
[llength [S $id SENTINEL REPLICAS mymaster]] == $n_replicas_expected
} else {
fail "Sentinel replies with a wrong number of replicas with replica-announced=yes (expected $n_replicas_expected but got $len) on sentinel $id"
}
}
}
}
proc 10_set_replica_announced {master_id announced n_replicas} {
test "Set replica-announced=$announced on $n_replicas replicas" {
set i 0
foreach_redis_id id {
if {$id == $master_id} continue
#puts "set replica-announce=$announced on redis #$id"
R $id CONFIG SET replica-announced "$announced"
incr i
if { $n_replicas!="all" && $i >= $n_replicas } { break }
}
}
}
# ensure all replicas are announced
10_set_replica_announced $master_id "yes" "all"
# ensure all replicas are announced by sentinels
10_test_number_of_replicas 4
# ensure the first 2 replicas are not announced
10_set_replica_announced $master_id "no" 2
# ensure sentinels are not announcing the first 2 replicas that have been set unannounced
10_test_number_of_replicas 2
# ensure all replicas are announced
10_set_replica_announced $master_id "yes" "all"
# ensure all replicas are not announced by sentinels
10_test_number_of_replicas 4
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
# #
# Example usage: # Example usage:
# #
# set c [redis_cluster 127.0.0.1 6379 127.0.0.1 6380] # set c [redis_cluster {127.0.0.1:6379 127.0.0.1:6380}]
# $c set foo # $c set foo
# $c get foo # $c get foo
# $c close # $c close
...@@ -17,6 +17,7 @@ set ::redis_cluster::id 0 ...@@ -17,6 +17,7 @@ set ::redis_cluster::id 0
array set ::redis_cluster::startup_nodes {} array set ::redis_cluster::startup_nodes {}
array set ::redis_cluster::nodes {} array set ::redis_cluster::nodes {}
array set ::redis_cluster::slots {} array set ::redis_cluster::slots {}
array set ::redis_cluster::tls {}
# List of "plain" commands, which are commands where the sole key is always # List of "plain" commands, which are commands where the sole key is always
# the first argument. # the first argument.
...@@ -34,11 +35,14 @@ set ::redis_cluster::plain_commands { ...@@ -34,11 +35,14 @@ set ::redis_cluster::plain_commands {
dump bitcount bitpos pfadd pfcount dump bitcount bitpos pfadd pfcount
} }
proc redis_cluster {nodes} { # Create a cluster client. The nodes are given as a list of host:port. The TLS
# parameter (1 or 0) is optional and defaults to the global $::tls.
proc redis_cluster {nodes {tls -1}} {
set id [incr ::redis_cluster::id] set id [incr ::redis_cluster::id]
set ::redis_cluster::startup_nodes($id) $nodes set ::redis_cluster::startup_nodes($id) $nodes
set ::redis_cluster::nodes($id) {} set ::redis_cluster::nodes($id) {}
set ::redis_cluster::slots($id) {} set ::redis_cluster::slots($id) {}
set ::redis_cluster::tls($id) [expr $tls == -1 ? $::tls : $tls]
set handle [interp alias {} ::redis_cluster::instance$id {} ::redis_cluster::__dispatch__ $id] set handle [interp alias {} ::redis_cluster::instance$id {} ::redis_cluster::__dispatch__ $id]
$handle refresh_nodes_map $handle refresh_nodes_map
return $handle return $handle
...@@ -60,9 +64,10 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} { ...@@ -60,9 +64,10 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} {
foreach start_node $::redis_cluster::startup_nodes($id) { foreach start_node $::redis_cluster::startup_nodes($id) {
set ip_port [lindex [split $start_node @] 0] set ip_port [lindex [split $start_node @] 0]
lassign [split $ip_port :] start_host start_port lassign [split $ip_port :] start_host start_port
set tls $::redis_cluster::tls($id)
if {[catch { if {[catch {
set r {} set r {}
set r [redis $start_host $start_port 0 $::tls] set r [redis $start_host $start_port 0 $tls]
set nodes_descr [$r cluster nodes] set nodes_descr [$r cluster nodes]
$r close $r close
} e]} { } e]} {
...@@ -107,7 +112,8 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} { ...@@ -107,7 +112,8 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} {
# Connect to the node # Connect to the node
set link {} set link {}
catch {set link [redis $host $port 0 $::tls]} set tls $::redis_cluster::tls($id)
catch {set link [redis $host $port 0 $tls]}
# Build this node description as an hash. # Build this node description as an hash.
set node [dict create \ set node [dict create \
...@@ -161,9 +167,32 @@ proc ::redis_cluster::__method__close {id} { ...@@ -161,9 +167,32 @@ proc ::redis_cluster::__method__close {id} {
catch {unset ::redis_cluster::startup_nodes($id)} catch {unset ::redis_cluster::startup_nodes($id)}
catch {unset ::redis_cluster::nodes($id)} catch {unset ::redis_cluster::nodes($id)}
catch {unset ::redis_cluster::slots($id)} catch {unset ::redis_cluster::slots($id)}
catch {unset ::redis_cluster::tls($id)}
catch {interp alias {} ::redis_cluster::instance$id {}} catch {interp alias {} ::redis_cluster::instance$id {}}
} }
proc ::redis_cluster::__method__masternode_for_slot {id slot} {
# Get the node mapped to this slot.
set node_addr [dict get $::redis_cluster::slots($id) $slot]
if {$node_addr eq {}} {
error "No mapped node for slot $slot."
}
return [dict get $::redis_cluster::nodes($id) $node_addr]
}
proc ::redis_cluster::__method__masternode_notfor_slot {id slot} {
# Get a node that is not mapped to this slot.
set node_addr [dict get $::redis_cluster::slots($id) $slot]
set addrs [dict keys $::redis_cluster::nodes($id)]
foreach addr [lshuffle $addrs] {
set node [dict get $::redis_cluster::nodes($id) $addr]
if {$node_addr ne $addr && [dict get $node slaveof] eq "-"} {
return $node
}
}
error "Slot $slot is everywhere"
}
proc ::redis_cluster::__dispatch__ {id method args} { proc ::redis_cluster::__dispatch__ {id method args} {
if {[info command ::redis_cluster::__method__$method] eq {}} { if {[info command ::redis_cluster::__method__$method] eq {}} {
# Get the keys from the command. # Get the keys from the command.
...@@ -186,10 +215,15 @@ proc ::redis_cluster::__dispatch__ {id method args} { ...@@ -186,10 +215,15 @@ proc ::redis_cluster::__dispatch__ {id method args} {
# Execute the command in the node we think is the slot owner. # Execute the command in the node we think is the slot owner.
set retry 100 set retry 100
set asking 0
while {[incr retry -1]} { while {[incr retry -1]} {
if {$retry < 5} {after 100} if {$retry < 5} {after 100}
set node [dict get $::redis_cluster::nodes($id) $node_addr] set node [dict get $::redis_cluster::nodes($id) $node_addr]
set link [dict get $node link] set link [dict get $node link]
if {$asking} {
$link ASKING
set asking 0
}
if {[catch {$link $method {*}$args} e]} { if {[catch {$link $method {*}$args} e]} {
if {$link eq {} || \ if {$link eq {} || \
[string range $e 0 4] eq {MOVED} || \ [string range $e 0 4] eq {MOVED} || \
...@@ -202,6 +236,7 @@ proc ::redis_cluster::__dispatch__ {id method args} { ...@@ -202,6 +236,7 @@ proc ::redis_cluster::__dispatch__ {id method args} {
} elseif {[string range $e 0 2] eq {ASK}} { } elseif {[string range $e 0 2] eq {ASK}} {
# ASK redirection. # ASK redirection.
set node_addr [lindex $e 2] set node_addr [lindex $e 2]
set asking 1
continue continue
} else { } else {
# Non redirecting error. # Non redirecting error.
......
...@@ -35,6 +35,7 @@ array set ::redis::addr {} ...@@ -35,6 +35,7 @@ array set ::redis::addr {}
array set ::redis::blocking {} array set ::redis::blocking {}
array set ::redis::deferred {} array set ::redis::deferred {}
array set ::redis::reconnect {} array set ::redis::reconnect {}
array set ::redis::tls {}
array set ::redis::callback {} array set ::redis::callback {}
array set ::redis::state {} ;# State in non-blocking reply reading array set ::redis::state {} ;# State in non-blocking reply reading
array set ::redis::statestack {} ;# Stack of states, for nested mbulks array set ::redis::statestack {} ;# Stack of states, for nested mbulks
...@@ -58,7 +59,7 @@ proc redis {{server 127.0.0.1} {port 6379} {defer 0} {tls 0} {tlsoptions {}}} { ...@@ -58,7 +59,7 @@ proc redis {{server 127.0.0.1} {port 6379} {defer 0} {tls 0} {tlsoptions {}}} {
set ::redis::blocking($id) 1 set ::redis::blocking($id) 1
set ::redis::deferred($id) $defer set ::redis::deferred($id) $defer
set ::redis::reconnect($id) 0 set ::redis::reconnect($id) 0
set ::redis::tls $tls set ::redis::tls($id) $tls
::redis::redis_reset_state $id ::redis::redis_reset_state $id
interp alias {} ::redis::redisHandle$id {} ::redis::__dispatch__ $id interp alias {} ::redis::redisHandle$id {} ::redis::__dispatch__ $id
} }
...@@ -83,7 +84,7 @@ proc ::redis::__dispatch__raw__ {id method argv} { ...@@ -83,7 +84,7 @@ proc ::redis::__dispatch__raw__ {id method argv} {
# Reconnect the link if needed. # Reconnect the link if needed.
if {$fd eq {}} { if {$fd eq {}} {
lassign $::redis::addr($id) host port lassign $::redis::addr($id) host port
if {$::redis::tls} { if {$::redis::tls($id)} {
set ::redis::fd($id) [::tls::socket $host $port] set ::redis::fd($id) [::tls::socket $host $port]
} else { } else {
set ::redis::fd($id) [socket $host $port] set ::redis::fd($id) [socket $host $port]
...@@ -158,6 +159,7 @@ proc ::redis::__method__close {id fd} { ...@@ -158,6 +159,7 @@ proc ::redis::__method__close {id fd} {
catch {unset ::redis::blocking($id)} catch {unset ::redis::blocking($id)}
catch {unset ::redis::deferred($id)} catch {unset ::redis::deferred($id)}
catch {unset ::redis::reconnect($id)} catch {unset ::redis::reconnect($id)}
catch {unset ::redis::tls($id)}
catch {unset ::redis::state($id)} catch {unset ::redis::state($id)}
catch {unset ::redis::statestack($id)} catch {unset ::redis::statestack($id)}
catch {unset ::redis::callback($id)} catch {unset ::redis::callback($id)}
......
...@@ -253,7 +253,7 @@ proc wait_server_started {config_file stdout pid} { ...@@ -253,7 +253,7 @@ proc wait_server_started {config_file stdout pid} {
# Check if the port is actually busy and the server failed # Check if the port is actually busy and the server failed
# for this reason. # for this reason.
if {[regexp {Could not create server TCP} [exec cat $stdout]]} { if {[regexp {Failed listening on port} [exec cat $stdout]]} {
set port_busy 1 set port_busy 1
break break
} }
...@@ -508,6 +508,7 @@ proc start_server {options {code undefined}} { ...@@ -508,6 +508,7 @@ proc start_server {options {code undefined}} {
set num_tests $::num_tests set num_tests $::num_tests
if {[catch { uplevel 1 $code } error]} { if {[catch { uplevel 1 $code } error]} {
set backtrace $::errorInfo set backtrace $::errorInfo
set assertion [string match "assertion:*" $error]
# fetch srv back from the server list, in case it was restarted by restart_server (new PID) # fetch srv back from the server list, in case it was restarted by restart_server (new PID)
set srv [lindex $::servers end] set srv [lindex $::servers end]
...@@ -519,17 +520,23 @@ proc start_server {options {code undefined}} { ...@@ -519,17 +520,23 @@ proc start_server {options {code undefined}} {
dict set srv "skipleaks" 1 dict set srv "skipleaks" 1
kill_server $srv kill_server $srv
# Print warnings from log if {$::dump_logs && $assertion} {
puts [format "\nLogged warnings (pid %d):" [dict get $srv "pid"]] # if we caught an assertion ($::num_failed isn't incremented yet)
set warnings [warnings_from_file [dict get $srv "stdout"]] # this happens when the test spawns a server and not the other way around
if {[string length $warnings] > 0} { dump_server_log $srv
puts "$warnings"
} else { } else {
puts "(none)" # Print crash report from log
} set crashlog [crashlog_from_file [dict get $srv "stdout"]]
if {[string length $crashlog] > 0} {
puts [format "\nLogged crash report (pid %d):" [dict get $srv "pid"]]
puts "$crashlog"
puts "" puts ""
}
}
if {$::durable} { if {!$assertion && $::durable} {
# durable is meant to prevent the whole tcl test from exiting on
# an exception. an assertion will be caught by the test proc.
set msg [string range $error 10 end] set msg [string range $error 10 end]
lappend details $msg lappend details $msg
lappend details $backtrace lappend details $backtrace
......
...@@ -165,6 +165,8 @@ proc test {name code {okpattern undefined} {options undefined}} { ...@@ -165,6 +165,8 @@ proc test {name code {okpattern undefined} {options undefined}} {
if {[catch {set retval [uplevel 1 $code]} error]} { if {[catch {set retval [uplevel 1 $code]} error]} {
set assertion [string match "assertion:*" $error] set assertion [string match "assertion:*" $error]
if {$assertion || $::durable} { if {$assertion || $::durable} {
# durable prevents the whole tcl test from exiting on an exception.
# an assertion is handled gracefully anyway.
set msg [string range $error 10 end] set msg [string range $error 10 end]
lappend details $msg lappend details $msg
if {!$assertion} { if {!$assertion} {
......
...@@ -31,7 +31,7 @@ proc zlistAlikeSort {a b} { ...@@ -31,7 +31,7 @@ proc zlistAlikeSort {a b} {
# Return all log lines starting with the first line that contains a warning. # Return all log lines starting with the first line that contains a warning.
# Generally, this will be an assertion error with a stack trace. # Generally, this will be an assertion error with a stack trace.
proc warnings_from_file {filename} { proc crashlog_from_file {filename} {
set lines [split [exec cat $filename] "\n"] set lines [split [exec cat $filename] "\n"]
set matched 0 set matched 0
set logall 0 set logall 0
...@@ -506,18 +506,18 @@ proc stop_write_load {handle} { ...@@ -506,18 +506,18 @@ proc stop_write_load {handle} {
proc K { x y } { set x } proc K { x y } { set x }
# Shuffle a list. From Tcl wiki. Originally from Steve Cohen that improved # Shuffle a list with Fisher-Yates algorithm.
# other versions. Code should be under public domain.
proc lshuffle {list} { proc lshuffle {list} {
set n [llength $list] set n [llength $list]
while {$n>0} { while {$n>1} {
set j [expr {int(rand()*$n)}] set j [expr {int(rand()*$n)}]
lappend slist [lindex $list $j]
incr n -1 incr n -1
set temp [lindex $list $n] if {$n==$j} continue
set list [lreplace [K $list [set list {}]] $j $j $temp] set v [lindex $list $j]
lset list $j [lindex $list $n]
lset list $n $v
} }
return $slist return $list
} }
# Execute a background process writing complex data for the specified number # Execute a background process writing complex data for the specified number
...@@ -682,20 +682,83 @@ proc string2printable s { ...@@ -682,20 +682,83 @@ proc string2printable s {
return $res return $res
} }
# Check that probability of each element are between {min_prop} and {max_prop}. # Calculation value of Chi-Square Distribution. By this value
proc check_histogram_distribution {res min_prop max_prop} { # we can verify the random distribution sample confidence.
# Based on the following wiki:
# https://en.wikipedia.org/wiki/Chi-square_distribution
#
# param res Random sample list
# return Value of Chi-Square Distribution
#
# x2_value: return of chi_square_value function
# df: Degrees of freedom, Number of independent values minus 1
#
# By using x2_value and df to back check the cardinality table,
# we can know the confidence of the random sample.
proc chi_square_value {res} {
unset -nocomplain mydict unset -nocomplain mydict
foreach key $res { foreach key $res {
dict incr mydict $key 1 dict incr mydict $key 1
} }
set x2_value 0
set p [expr [llength $res] / [dict size $mydict]]
foreach key [dict keys $mydict] { foreach key [dict keys $mydict] {
set value [dict get $mydict $key] set value [dict get $mydict $key]
set probability [expr {double($value) / [llength $res]}]
if {$probability < $min_prop || $probability > $max_prop} { # Aggregate the chi-square value of each element
return false set v [expr {pow($value - $p, 2) / $p}]
set x2_value [expr {$x2_value + $v}]
} }
return $x2_value
}
#subscribe to Pub/Sub channels
proc consume_subscribe_messages {client type channels} {
set numsub -1
set counts {}
for {set i [llength $channels]} {$i > 0} {incr i -1} {
set msg [$client read]
assert_equal $type [lindex $msg 0]
# when receiving subscribe messages the channels names
# are ordered. when receiving unsubscribe messages
# they are unordered
set idx [lsearch -exact $channels [lindex $msg 1]]
if {[string match "*unsubscribe" $type]} {
assert {$idx >= 0}
} else {
assert {$idx == 0}
}
set channels [lreplace $channels $idx $idx]
# aggregate the subscription count to return to the caller
lappend counts [lindex $msg 2]
} }
return true # we should have received messages for channels
assert {[llength $channels] == 0}
return $counts
}
proc subscribe {client channels} {
$client subscribe {*}$channels
consume_subscribe_messages $client subscribe $channels
}
proc unsubscribe {client {channels {}}} {
$client unsubscribe {*}$channels
consume_subscribe_messages $client unsubscribe $channels
}
proc psubscribe {client channels} {
$client psubscribe {*}$channels
consume_subscribe_messages $client psubscribe $channels
}
proc punsubscribe {client {channels {}}} {
$client punsubscribe {*}$channels
consume_subscribe_messages $client punsubscribe $channels
} }
\ No newline at end of file
...@@ -113,6 +113,46 @@ start_server {tags {"acl"}} { ...@@ -113,6 +113,46 @@ start_server {tags {"acl"}} {
set e set e
} {*NOPERM*channel*} } {*NOPERM*channel*}
test {Validate subset of channels is prefixed with resetchannels flag} {
r ACL setuser hpuser on nopass resetchannels &foo +@all
# Verify resetchannels flag is prefixed before the channel name(s)
set users [r ACL LIST]
set curruser "hpuser"
foreach user [lshuffle $users] {
if {[string first $curruser $user] != -1} {
assert_equal {user hpuser on nopass resetchannels &foo +@all} $user
}
}
# authenticate as hpuser
r AUTH hpuser pass
assert_equal {0} [r PUBLISH foo bar]
catch {r PUBLISH bar game} e
# Falling back to psuser for the below tests
r AUTH psuser pspass
r ACL deluser hpuser
set e
} {*NOPERM*channel*}
test {In transaction queue publish/subscribe/psubscribe to unauthorized channel will fail} {
r ACL setuser psuser +multi +discard
r MULTI
catch {r PUBLISH notexits helloworld} e
r DISCARD
assert_match {*NOPERM*} $e
r MULTI
catch {r SUBSCRIBE notexits foo:1} e
r DISCARD
assert_match {*NOPERM*} $e
r MULTI
catch {r PSUBSCRIBE notexits:* bar:*} e
r DISCARD
assert_match {*NOPERM*} $e
}
test {It's possible to allow subscribing to a subset of channels} { test {It's possible to allow subscribing to a subset of channels} {
set rd [redis_deferring_client] set rd [redis_deferring_client]
$rd AUTH psuser pspass $rd AUTH psuser pspass
...@@ -409,6 +449,14 @@ start_server {tags {"acl"}} { ...@@ -409,6 +449,14 @@ start_server {tags {"acl"}} {
set e set e
} {*NOAUTH*} } {*NOAUTH*}
test {When default user has no command permission, hello command still works for other users} {
r ACL setuser secure-user >supass on +@all
r ACL setuser default -@all
r HELLO 2 AUTH secure-user supass
r ACL setuser default nopass +@all
r AUTH default ""
}
test {ACL HELP should not have unexpected options} { test {ACL HELP should not have unexpected options} {
catch {r ACL help xxx} e catch {r ACL help xxx} e
assert_match "*Unknown subcommand or wrong number of arguments*" $e assert_match "*Unknown subcommand or wrong number of arguments*" $e
...@@ -437,14 +485,44 @@ exec cp -f tests/assets/user.acl $server_path ...@@ -437,14 +485,44 @@ exec cp -f tests/assets/user.acl $server_path
start_server [list overrides [list "dir" $server_path "aclfile" "user.acl"]] { start_server [list overrides [list "dir" $server_path "aclfile" "user.acl"]] {
# user alice on allcommands allkeys >alice # user alice on allcommands allkeys >alice
# user bob on -@all +@set +acl ~set* >bob # user bob on -@all +@set +acl ~set* >bob
# user default on nopass ~* +@all
test {default: load from include file, can access any channels} {
r SUBSCRIBE foo
r PSUBSCRIBE bar*
r UNSUBSCRIBE
r PUNSUBSCRIBE
r PUBLISH hello world
}
test {default: with config acl-pubsub-default allchannels after reset, can access any channels} {
r ACL setuser default reset on nopass ~* +@all
r SUBSCRIBE foo
r PSUBSCRIBE bar*
r UNSUBSCRIBE
r PUNSUBSCRIBE
r PUBLISH hello world
}
test "Alice: can excute all command" { test {default: with config acl-pubsub-default resetchannels after reset, can not access any channels} {
r CONFIG SET acl-pubsub-default resetchannels
r ACL setuser default reset on nopass ~* +@all
catch {r SUBSCRIBE foo} e
assert_match {*NOPERM*} $e
catch {r PSUBSCRIBE bar*} e
assert_match {*NOPERM*} $e
catch {r PUBLISH hello world} e
assert_match {*NOPERM*} $e
r CONFIG SET acl-pubsub-default resetchannels
}
test {Alice: can execute all command} {
r AUTH alice alice r AUTH alice alice
assert_equal "alice" [r acl whoami] assert_equal "alice" [r acl whoami]
r SET key value r SET key value
} }
test "Bob: just excute @set and acl command" { test {Bob: just execute @set and acl command} {
r AUTH bob bob r AUTH bob bob
assert_equal "bob" [r acl whoami] assert_equal "bob" [r acl whoami]
assert_equal "3" [r sadd set 1 2 3] assert_equal "3" [r sadd set 1 2 3]
...@@ -452,7 +530,7 @@ start_server [list overrides [list "dir" $server_path "aclfile" "user.acl"]] { ...@@ -452,7 +530,7 @@ start_server [list overrides [list "dir" $server_path "aclfile" "user.acl"]] {
set e set e
} {*NOPERM*} } {*NOPERM*}
test "ACL load and save" { test {ACL load and save} {
r ACL setuser eve +get allkeys >eve on r ACL setuser eve +get allkeys >eve on
r ACL save r ACL save
...@@ -469,4 +547,85 @@ start_server [list overrides [list "dir" $server_path "aclfile" "user.acl"]] { ...@@ -469,4 +547,85 @@ start_server [list overrides [list "dir" $server_path "aclfile" "user.acl"]] {
catch {r SET key value} e catch {r SET key value} e
set e set e
} {*NOPERM*} } {*NOPERM*}
test {ACL load and save with restricted channels} {
r AUTH alice alice
r ACL setuser harry on nopass resetchannels &test +@all ~*
r ACL save
# ACL load will free user and kill clients
r ACL load
catch {r ACL LIST} e
assert_match {*I/O error*} $e
reconnect
r AUTH harry anything
r publish test bar
catch {r publish test1 bar} e
r ACL deluser harry
set e
} {*NOPERM*}
}
set server_path [tmpdir "resetchannels.acl"]
exec cp -f tests/assets/nodefaultuser.acl $server_path
exec cp -f tests/assets/default.conf $server_path
start_server [list overrides [list "dir" $server_path "acl-pubsub-default" "resetchannels" "aclfile" "nodefaultuser.acl"]] {
test {Default user has access to all channels irrespective of flag} {
set channelinfo [dict get [r ACL getuser default] channels]
assert_equal "*" $channelinfo
set channelinfo [dict get [r ACL getuser alice] channels]
assert_equal "" $channelinfo
}
test {Update acl-pubsub-default, existing users shouldn't get affected} {
set channelinfo [dict get [r ACL getuser default] channels]
assert_equal "*" $channelinfo
r CONFIG set acl-pubsub-default allchannels
r ACL setuser mydefault
set channelinfo [dict get [r ACL getuser mydefault] channels]
assert_equal "*" $channelinfo
r CONFIG set acl-pubsub-default resetchannels
set channelinfo [dict get [r ACL getuser mydefault] channels]
assert_equal "*" $channelinfo
}
test {Single channel is valid} {
r ACL setuser onechannel &test
set channelinfo [dict get [r ACL getuser onechannel] channels]
assert_equal test $channelinfo
r ACL deluser onechannel
}
test {Single channel is not valid with allchannels} {
r CONFIG set acl-pubsub-default allchannels
catch {r ACL setuser onechannel &test} err
r CONFIG set acl-pubsub-default resetchannels
set err
} {*start with an empty list of channels*}
}
set server_path [tmpdir "resetchannels.acl"]
exec cp -f tests/assets/nodefaultuser.acl $server_path
exec cp -f tests/assets/default.conf $server_path
start_server [list overrides [list "dir" $server_path "acl-pubsub-default" "resetchannels" "aclfile" "nodefaultuser.acl"]] {
test {Only default user has access to all channels irrespective of flag} {
set channelinfo [dict get [r ACL getuser default] channels]
assert_equal "*" $channelinfo
set channelinfo [dict get [r ACL getuser alice] channels]
assert_equal "" $channelinfo
}
}
start_server {overrides {user "default on nopass ~* +@all"}} {
test {default: load from config file, can access any channels} {
r SUBSCRIBE foo
r PSUBSCRIBE bar*
r UNSUBSCRIBE
r PUNSUBSCRIBE
r PUBLISH hello world
}
} }
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