Unverified Commit d2b5a579 authored by Oran Agra's avatar Oran Agra Committed by GitHub
Browse files

Merge pull request #10355 from oranagra/release-7.0-rc2

Release 7.0 RC2
parents d5915a16 10dc57ab
...@@ -4,6 +4,7 @@ set server_path [tmpdir server.aof] ...@@ -4,6 +4,7 @@ set server_path [tmpdir server.aof]
set aof_dirname "appendonlydir" set aof_dirname "appendonlydir"
set aof_basename "appendonly.aof" set aof_basename "appendonly.aof"
set aof_dirpath "$server_path/$aof_dirname" set aof_dirpath "$server_path/$aof_dirname"
set aof_base_file "$server_path/$aof_dirname/${aof_basename}.1$::base_aof_sufix$::aof_format_suffix"
set aof_file "$server_path/$aof_dirname/${aof_basename}.1$::incr_aof_sufix$::aof_format_suffix" set aof_file "$server_path/$aof_dirname/${aof_basename}.1$::incr_aof_sufix$::aof_format_suffix"
set aof_manifest_file "$server_path/$aof_dirname/$aof_basename$::manifest_suffix" set aof_manifest_file "$server_path/$aof_dirname/$aof_basename$::manifest_suffix"
...@@ -142,7 +143,7 @@ tags {"aof external:skip"} { ...@@ -142,7 +143,7 @@ tags {"aof external:skip"} {
## Test that redis-check-aof indeed sees this AOF is not valid ## Test that redis-check-aof indeed sees this AOF is not valid
test "Short read: Utility should confirm the AOF is not valid" { test "Short read: Utility should confirm the AOF is not valid" {
catch { catch {
exec src/redis-check-aof $aof_file exec src/redis-check-aof $aof_manifest_file
} result } result
assert_match "*not valid*" $result assert_match "*not valid*" $result
} }
...@@ -154,13 +155,13 @@ tags {"aof external:skip"} { ...@@ -154,13 +155,13 @@ tags {"aof external:skip"} {
} }
catch { catch {
exec src/redis-check-aof $aof_file exec src/redis-check-aof $aof_manifest_file
} result } result
assert_match "*ok_up_to_line=8*" $result assert_match "*ok_up_to_line=8*" $result
} }
test "Short read: Utility should be able to fix the AOF" { test "Short read: Utility should be able to fix the AOF" {
set result [exec src/redis-check-aof --fix $aof_file << "y\n"] set result [exec src/redis-check-aof --fix $aof_manifest_file << "y\n"]
assert_match "*Successfully truncated AOF*" $result assert_match "*Successfully truncated AOF*" $result
} }
...@@ -444,7 +445,7 @@ tags {"aof external:skip"} { ...@@ -444,7 +445,7 @@ tags {"aof external:skip"} {
test {Truncate AOF to specific timestamp} { test {Truncate AOF to specific timestamp} {
# truncate to timestamp 1628217473 # truncate to timestamp 1628217473
exec src/redis-check-aof --truncate-to-timestamp 1628217473 $aof_file exec src/redis-check-aof --truncate-to-timestamp 1628217473 $aof_manifest_file
start_server_aof [list dir $server_path] { start_server_aof [list dir $server_path] {
set c [redis [dict get $srv host] [dict get $srv port] 0 $::tls] set c [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_done_loading $c wait_done_loading $c
...@@ -454,7 +455,7 @@ tags {"aof external:skip"} { ...@@ -454,7 +455,7 @@ tags {"aof external:skip"} {
} }
# truncate to timestamp 1628217471 # truncate to timestamp 1628217471
exec src/redis-check-aof --truncate-to-timestamp 1628217471 $aof_file exec src/redis-check-aof --truncate-to-timestamp 1628217471 $aof_manifest_file
start_server_aof [list dir $server_path] { start_server_aof [list dir $server_path] {
set c [redis [dict get $srv host] [dict get $srv port] 0 $::tls] set c [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_done_loading $c wait_done_loading $c
...@@ -464,7 +465,7 @@ tags {"aof external:skip"} { ...@@ -464,7 +465,7 @@ tags {"aof external:skip"} {
} }
# truncate to timestamp 1628217470 # truncate to timestamp 1628217470
exec src/redis-check-aof --truncate-to-timestamp 1628217470 $aof_file exec src/redis-check-aof --truncate-to-timestamp 1628217470 $aof_manifest_file
start_server_aof [list dir $server_path] { start_server_aof [list dir $server_path] {
set c [redis [dict get $srv host] [dict get $srv port] 0 $::tls] set c [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_done_loading $c wait_done_loading $c
...@@ -473,7 +474,7 @@ tags {"aof external:skip"} { ...@@ -473,7 +474,7 @@ tags {"aof external:skip"} {
} }
# truncate to timestamp 1628217469 # truncate to timestamp 1628217469
catch {exec src/redis-check-aof --truncate-to-timestamp 1628217469 $aof_file} e catch {exec src/redis-check-aof --truncate-to-timestamp 1628217469 $aof_manifest_file} e
assert_match {*aborting*} $e assert_match {*aborting*} $e
} }
...@@ -515,4 +516,120 @@ tags {"aof external:skip"} { ...@@ -515,4 +516,120 @@ tags {"aof external:skip"} {
assert_equal [r get foo] 102 assert_equal [r get foo] 102
} }
} }
test {Test redis-check-aof for old style resp AOF} {
create_aof $aof_dirpath $aof_file {
append_to_aof [formatCommand set foo hello]
append_to_aof [formatCommand set bar world]
}
catch {
exec src/redis-check-aof $aof_file
} result
assert_match "*Start checking Old-Style AOF*is valid*" $result
}
test {Test redis-check-aof for old style rdb-preamble AOF} {
catch {
exec src/redis-check-aof tests/assets/rdb-preamble.aof
} result
assert_match "*Start checking Old-Style AOF*RDB preamble is OK, proceeding with AOF tail*is valid*" $result
}
test {Test redis-check-aof for Multi Part AOF with resp AOF base} {
create_aof $aof_dirpath $aof_base_file {
append_to_aof [formatCommand set foo hello]
append_to_aof [formatCommand set bar world]
}
create_aof $aof_dirpath $aof_file {
append_to_aof [formatCommand set foo hello]
append_to_aof [formatCommand set bar world]
}
create_aof_manifest $aof_dirpath $aof_manifest_file {
append_to_manifest "file appendonly.aof.1.base.aof seq 1 type b\n"
append_to_manifest "file appendonly.aof.1.incr.aof seq 1 type i\n"
}
catch {
exec src/redis-check-aof $aof_manifest_file
} result
assert_match "*Start checking Multi Part AOF*Start to check BASE AOF (RESP format)*BASE AOF*is valid*Start to check INCR files*INCR AOF*is valid*All AOF files and manifest are valid*" $result
}
test {Test redis-check-aof for Multi Part AOF with rdb-preamble AOF base} {
exec cp tests/assets/rdb-preamble.aof $aof_base_file
create_aof $aof_dirpath $aof_file {
append_to_aof [formatCommand set foo hello]
append_to_aof [formatCommand set bar world]
}
create_aof_manifest $aof_dirpath $aof_manifest_file {
append_to_manifest "file appendonly.aof.1.base.aof seq 1 type b\n"
append_to_manifest "file appendonly.aof.1.incr.aof seq 1 type i\n"
}
catch {
exec src/redis-check-aof $aof_manifest_file
} result
assert_match "*Start checking Multi Part AOF*Start to check BASE AOF (RDB format)*DB preamble is OK, proceeding with AOF tail*BASE AOF*is valid*Start to check INCR files*INCR AOF*is valid*All AOF files and manifest are valid*" $result
}
test {Test redis-check-aof only truncates the last file for Multi Part AOF in fix mode} {
create_aof $aof_dirpath $aof_base_file {
append_to_aof [formatCommand set foo hello]
append_to_aof [formatCommand multi]
append_to_aof [formatCommand set bar world]
}
create_aof $aof_dirpath $aof_file {
append_to_aof [formatCommand set foo hello]
append_to_aof [formatCommand set bar world]
}
create_aof_manifest $aof_dirpath $aof_manifest_file {
append_to_manifest "file appendonly.aof.1.base.aof seq 1 type b\n"
append_to_manifest "file appendonly.aof.1.incr.aof seq 1 type i\n"
}
catch {
exec src/redis-check-aof $aof_manifest_file
} result
assert_match "*not valid*" $result
catch {
exec src/redis-check-aof --fix $aof_manifest_file
} result
assert_match "*Failed to truncate AOF*because it is not the last file*" $result
}
test {Test redis-check-aof only truncates the last file for Multi Part AOF in truncate-to-timestamp mode} {
create_aof $aof_dirpath $aof_base_file {
append_to_aof "#TS:1628217470\r\n"
append_to_aof [formatCommand set foo1 bar1]
append_to_aof "#TS:1628217471\r\n"
append_to_aof [formatCommand set foo2 bar2]
append_to_aof "#TS:1628217472\r\n"
append_to_aof "#TS:1628217473\r\n"
append_to_aof [formatCommand set foo3 bar3]
append_to_aof "#TS:1628217474\r\n"
}
create_aof $aof_dirpath $aof_file {
append_to_aof [formatCommand set foo hello]
append_to_aof [formatCommand set bar world]
}
create_aof_manifest $aof_dirpath $aof_manifest_file {
append_to_manifest "file appendonly.aof.1.base.aof seq 1 type b\n"
append_to_manifest "file appendonly.aof.1.incr.aof seq 1 type i\n"
}
catch {
exec src/redis-check-aof --truncate-to-timestamp 1628217473 $aof_manifest_file
} result
assert_match "*Failed to truncate AOF*to timestamp*because it is not the last file*" $result
}
} }
...@@ -193,9 +193,8 @@ test {corrupt payload: listpack invalid size header} { ...@@ -193,9 +193,8 @@ test {corrupt payload: listpack invalid size header} {
test {corrupt payload: listpack too long entry len} { test {corrupt payload: listpack too long entry len} {
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] { start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
r config set sanitize-dump-payload no r config set sanitize-dump-payload no
r restore key 0 "\x0F\x01\x10\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x02\x40\x55\x55\x00\x00\x00\x0F\x00\x01\x01\x00\x01\x02\x01\x88\x31\x00\x00\x00\x00\x00\x00\x00\x09\x88\x32\x00\x00\x00\x00\x00\x00\x00\x09\x00\x01\x00\x01\x00\x01\x00\x01\x02\x02\x89\x31\x00\x00\x00\x00\x00\x00\x00\x09\x88\x61\x00\x00\x00\x00\x00\x00\x00\x09\x88\x32\x00\x00\x00\x00\x00\x00\x00\x09\x88\x62\x00\x00\x00\x00\x00\x00\x00\x09\x08\x01\xFF\x0A\x01\x00\x00\x09\x00\x40\x63\xC9\x37\x03\xA2\xE5\x68"
catch { catch {
r xinfo stream key full r restore key 0 "\x0F\x01\x10\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x02\x40\x55\x55\x00\x00\x00\x0F\x00\x01\x01\x00\x01\x02\x01\x88\x31\x00\x00\x00\x00\x00\x00\x00\x09\x88\x32\x00\x00\x00\x00\x00\x00\x00\x09\x00\x01\x00\x01\x00\x01\x00\x01\x02\x02\x89\x31\x00\x00\x00\x00\x00\x00\x00\x09\x88\x61\x00\x00\x00\x00\x00\x00\x00\x09\x88\x32\x00\x00\x00\x00\x00\x00\x00\x09\x88\x62\x00\x00\x00\x00\x00\x00\x00\x09\x08\x01\xFF\x0A\x01\x00\x00\x09\x00\x40\x63\xC9\x37\x03\xA2\xE5\x68"
} err } err
assert_equal [count_log_message 0 "crashed by signal"] 0 assert_equal [count_log_message 0 "crashed by signal"] 0
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1 assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
...@@ -205,9 +204,9 @@ test {corrupt payload: listpack too long entry len} { ...@@ -205,9 +204,9 @@ test {corrupt payload: listpack too long entry len} {
test {corrupt payload: listpack very long entry len} { test {corrupt payload: listpack very long entry len} {
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] { start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
r config set sanitize-dump-payload no r config set sanitize-dump-payload no
r restore key 0 "\x0F\x01\x10\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x02\x40\x55\x55\x00\x00\x00\x0F\x00\x01\x01\x00\x01\x02\x01\x88\x31\x00\x00\x00\x00\x00\x00\x00\x09\x88\x32\x00\x00\x00\x00\x00\x00\x00\x09\x00\x01\x00\x01\x00\x01\x00\x01\x02\x02\x88\x31\x00\x00\x00\x00\x00\x00\x00\x09\x88\x61\x00\x00\x00\x00\x00\x00\x00\x09\x88\x32\x00\x00\x00\x00\x00\x00\x00\x09\x9C\x62\x00\x00\x00\x00\x00\x00\x00\x09\x08\x01\xFF\x0A\x01\x00\x00\x09\x00\x63\x6F\x42\x8E\x7C\xB5\xA2\x9D"
catch { catch {
r xinfo stream key full # This will catch migrated payloads from v6.2.x
r restore key 0 "\x0F\x01\x10\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x02\x40\x55\x55\x00\x00\x00\x0F\x00\x01\x01\x00\x01\x02\x01\x88\x31\x00\x00\x00\x00\x00\x00\x00\x09\x88\x32\x00\x00\x00\x00\x00\x00\x00\x09\x00\x01\x00\x01\x00\x01\x00\x01\x02\x02\x88\x31\x00\x00\x00\x00\x00\x00\x00\x09\x88\x61\x00\x00\x00\x00\x00\x00\x00\x09\x88\x32\x00\x00\x00\x00\x00\x00\x00\x09\x9C\x62\x00\x00\x00\x00\x00\x00\x00\x09\x08\x01\xFF\x0A\x01\x00\x00\x09\x00\x63\x6F\x42\x8E\x7C\xB5\xA2\x9D"
} err } err
assert_equal [count_log_message 0 "crashed by signal"] 0 assert_equal [count_log_message 0 "crashed by signal"] 0
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1 assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
......
...@@ -46,6 +46,12 @@ start_server {tags {"repl external:skip"}} { ...@@ -46,6 +46,12 @@ start_server {tags {"repl external:skip"}} {
set master_port [srv -1 port] set master_port [srv -1 port]
set slave [srv 0 client] set slave [srv 0 client]
# Load some functions to be used later
$master FUNCTION load lua test replace {
redis.register_function{function_name='f_default_flags', callback=function(keys, args) return redis.call('get',keys[1]) end, flags={}}
redis.register_function{function_name='f_no_writes', callback=function(keys, args) return redis.call('get',keys[1]) end, flags={'no-writes'}}
}
test {First server should have role slave after SLAVEOF} { test {First server should have role slave after SLAVEOF} {
$slave slaveof $master_host $master_port $slave slaveof $master_host $master_port
wait_replica_online $master wait_replica_online $master
...@@ -54,28 +60,46 @@ start_server {tags {"repl external:skip"}} { ...@@ -54,28 +60,46 @@ start_server {tags {"repl external:skip"}} {
test {With min-slaves-to-write (1,3): master should be writable} { test {With min-slaves-to-write (1,3): master should be writable} {
$master config set min-slaves-max-lag 3 $master config set min-slaves-max-lag 3
$master config set min-slaves-to-write 1 $master config set min-slaves-to-write 1
$master set foo bar assert_equal OK [$master set foo 123]
} {OK} assert_equal OK [$master eval "return redis.call('set','foo',12345)" 0]
}
test {With min-slaves-to-write (2,3): master should not be writable} { test {With min-slaves-to-write (2,3): master should not be writable} {
$master config set min-slaves-max-lag 3 $master config set min-slaves-max-lag 3
$master config set min-slaves-to-write 2 $master config set min-slaves-to-write 2
catch {$master set foo bar} e assert_error "*NOREPLICAS*" {$master set foo bar}
set e assert_error "*NOREPLICAS*" {$master eval "redis.call('set','foo','bar')" 0}
} {NOREPLICAS*} }
test {With min-slaves-to-write function without no-write flag} {
assert_error "*NOREPLICAS*" {$master fcall f_default_flags 1 foo}
assert_equal "12345" [$master fcall f_no_writes 1 foo]
}
test {With not enough good slaves, read in Lua script is still accepted} {
$master config set min-slaves-max-lag 3
$master config set min-slaves-to-write 1
$master eval "redis.call('set','foo','bar')" 0
$master config set min-slaves-to-write 2
$master eval "return redis.call('get','foo')" 0
} {bar}
test {With min-slaves-to-write: master not writable with lagged slave} { test {With min-slaves-to-write: master not writable with lagged slave} {
$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_equal OK [$master set foo 123]
assert_equal OK [$master eval "return redis.call('set','foo',12345)" 0]
# Killing a slave to make it become a lagged slave.
exec kill -SIGSTOP [srv 0 pid] exec kill -SIGSTOP [srv 0 pid]
# Waiting for slave kill.
wait_for_condition 100 100 { wait_for_condition 100 100 {
[catch {$master set foo bar}] != 0 [catch {$master set foo 123}] != 0
} else { } else {
fail "Master didn't become readonly" fail "Master didn't become readonly"
} }
catch {$master set foo bar} err assert_error "*NOREPLICAS*" {$master set foo 123}
assert_match {NOREPLICAS*} $err assert_error "*NOREPLICAS*" {$master eval "return redis.call('set','foo',12345)" 0}
exec kill -SIGCONT [srv 0 pid] exec kill -SIGCONT [srv 0 pid]
} }
} }
......
...@@ -318,7 +318,7 @@ foreach mdl {no yes} { ...@@ -318,7 +318,7 @@ foreach mdl {no yes} {
stop_write_load $load_handle4 stop_write_load $load_handle4
# Make sure no more commands processed # Make sure no more commands processed
wait_load_handlers_disconnected wait_load_handlers_disconnected -3
wait_for_ofs_sync $master [lindex $slaves 0] wait_for_ofs_sync $master [lindex $slaves 0]
wait_for_ofs_sync $master [lindex $slaves 1] wait_for_ofs_sync $master [lindex $slaves 1]
...@@ -428,7 +428,7 @@ foreach testType {Successful Aborted} { ...@@ -428,7 +428,7 @@ foreach testType {Successful Aborted} {
} else { } else {
fail "Replica didn't get into loading mode" fail "Replica didn't get into loading mode"
} }
assert_equal [s -1 async_loading] 0 assert_equal [s -1 async_loading] 0
} }
...@@ -444,7 +444,7 @@ foreach testType {Successful Aborted} { ...@@ -444,7 +444,7 @@ foreach testType {Successful Aborted} {
} else { } else {
fail "Replica didn't disconnect" fail "Replica didn't disconnect"
} }
test {Diskless load swapdb (different replid): old database is exposed after replication fails} { test {Diskless load swapdb (different replid): old database is exposed after replication fails} {
# Ensure we see old values from replica # Ensure we see old values from replica
assert_equal [$replica get mykey] "myvalue" assert_equal [$replica get mykey] "myvalue"
...@@ -551,10 +551,10 @@ foreach testType {Successful Aborted} { ...@@ -551,10 +551,10 @@ foreach testType {Successful Aborted} {
} else { } else {
fail "Replica didn't get into async_loading mode" fail "Replica didn't get into async_loading mode"
} }
assert_equal [s -1 loading] 0 assert_equal [s -1 loading] 0
} }
test {Diskless load swapdb (async_loading): old database is exposed while async replication is in progress} { test {Diskless load swapdb (async_loading): old database is exposed while async replication is in progress} {
# Ensure we still see old values while async_loading is in progress and also not LOADING status # Ensure we still see old values while async_loading is in progress and also not LOADING status
assert_equal [$replica get mykey] "myvalue" assert_equal [$replica get mykey] "myvalue"
...@@ -598,7 +598,7 @@ foreach testType {Successful Aborted} { ...@@ -598,7 +598,7 @@ foreach testType {Successful Aborted} {
} else { } else {
fail "Replica didn't disconnect" fail "Replica didn't disconnect"
} }
test {Diskless load swapdb (async_loading): old database is exposed after async replication fails} { test {Diskless load swapdb (async_loading): old database is exposed after async replication fails} {
# Ensure we see old values from replica # Ensure we see old values from replica
assert_equal [$replica get mykey] "myvalue" assert_equal [$replica get mykey] "myvalue"
...@@ -1222,14 +1222,100 @@ test {replica can handle EINTR if use diskless load} { ...@@ -1222,14 +1222,100 @@ test {replica can handle EINTR if use diskless load} {
# Wait for the replica to start reading the rdb # Wait for the replica to start reading the rdb
set res [wait_for_log_messages -1 {"*Loading DB in memory*"} 0 200 10] set res [wait_for_log_messages -1 {"*Loading DB in memory*"} 0 200 10]
set loglines [lindex $res 1] set loglines [lindex $res 1]
# Wait till we see the watchgod log line AFTER the loading started # Wait till we see the watchgod log line AFTER the loading started
wait_for_log_messages -1 {"*WATCHDOG TIMER EXPIRED*"} $loglines 200 10 wait_for_log_messages -1 {"*WATCHDOG TIMER EXPIRED*"} $loglines 200 10
# Make sure we're still loading, and that there was just one full sync attempt # Make sure we're still loading, and that there was just one full sync attempt
assert ![log_file_matches [srv -1 stdout] "*Reconnecting to MASTER*"] assert ![log_file_matches [srv -1 stdout] "*Reconnecting to MASTER*"]
assert_equal 1 [s 0 sync_full] assert_equal 1 [s 0 sync_full]
assert_equal 1 [s -1 loading] assert_equal 1 [s -1 loading]
} }
} }
} {} {external:skip} } {} {external:skip}
start_server {tags {"repl" "external:skip"}} {
test "replica do not write the reply to the replication link - SYNC (_addReplyToBufferOrList)" {
set rd [redis_deferring_client]
set lines [count_log_lines 0]
$rd sync
$rd ping
catch {$rd read} e
if {$::verbose} { puts "SYNC _addReplyToBufferOrList: $e" }
assert_equal "PONG" [r ping]
# Check we got the warning logs about the PING command.
verify_log_message 0 "*Replica generated a reply to command 'ping', disconnecting it: *" $lines
$rd close
catch {exec kill -9 [get_child_pid 0]}
waitForBgsave r
}
test "replica do not write the reply to the replication link - SYNC (addReplyDeferredLen)" {
set rd [redis_deferring_client]
set lines [count_log_lines 0]
$rd sync
$rd xinfo help
catch {$rd read} e
if {$::verbose} { puts "SYNC addReplyDeferredLen: $e" }
assert_equal "PONG" [r ping]
# Check we got the warning logs about the XINFO HELP command.
verify_log_message 0 "*Replica generated a reply to command 'xinfo|help', disconnecting it: *" $lines
$rd close
catch {exec kill -9 [get_child_pid 0]}
waitForBgsave r
}
test "replica do not write the reply to the replication link - PSYNC (_addReplyToBufferOrList)" {
set rd [redis_deferring_client]
set lines [count_log_lines 0]
$rd psync replicationid -1
assert_match {FULLRESYNC * 0} [$rd read]
$rd get foo
catch {$rd read} e
if {$::verbose} { puts "PSYNC _addReplyToBufferOrList: $e" }
assert_equal "PONG" [r ping]
# Check we got the warning logs about the GET command.
verify_log_message 0 "*Replica generated a reply to command 'get', disconnecting it: *" $lines
verify_log_message 0 "*== CRITICAL == This master is sending an error to its replica: *" $lines
verify_log_message 0 "*Replica can't interact with the keyspace*" $lines
$rd close
catch {exec kill -9 [get_child_pid 0]}
waitForBgsave r
}
test "replica do not write the reply to the replication link - PSYNC (addReplyDeferredLen)" {
set rd [redis_deferring_client]
set lines [count_log_lines 0]
$rd psync replicationid -1
assert_match {FULLRESYNC * 0} [$rd read]
$rd slowlog get
catch {$rd read} e
if {$::verbose} { puts "PSYNC addReplyDeferredLen: $e" }
assert_equal "PONG" [r ping]
# Check we got the warning logs about the SLOWLOG GET command.
verify_log_message 0 "*Replica generated a reply to command 'slowlog|get', disconnecting it: *" $lines
$rd close
catch {exec kill -9 [get_child_pid 0]}
waitForBgsave r
}
test "PSYNC with wrong offset should throw error" {
# It used to accept the FULL SYNC, but also replied with an error.
assert_error {ERR value is not an integer or out of range} {r psync replicationid offset_str}
set logs [exec tail -n 100 < [srv 0 stdout]]
assert_match {*Replica * asks for synchronization but with a wrong offset} $logs
assert_equal "PONG" [r ping]
}
}
...@@ -2,11 +2,12 @@ ...@@ -2,11 +2,12 @@
# find the OS # find the OS
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
warning_cflags = -W -Wall -Wno-missing-field-initializers
ifeq ($(uname_S),Darwin) ifeq ($(uname_S),Darwin)
SHOBJ_CFLAGS ?= -W -Wall -dynamic -fno-common -g -ggdb -std=c99 -O2 SHOBJ_CFLAGS ?= $(warning_cflags) -dynamic -fno-common -g -ggdb -std=c99 -O2
SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup
else # Linux, others else # Linux, others
SHOBJ_CFLAGS ?= -W -Wall -fno-common -g -ggdb -std=c99 -O2 SHOBJ_CFLAGS ?= $(warning_cflags) -fno-common -g -ggdb -std=c99 -O2
SHOBJ_LDFLAGS ?= -shared SHOBJ_LDFLAGS ?= -shared
endif endif
...@@ -40,6 +41,7 @@ TEST_MODULES = \ ...@@ -40,6 +41,7 @@ TEST_MODULES = \
keyspace_events.so \ keyspace_events.so \
blockedclient.so \ blockedclient.so \
getkeys.so \ getkeys.so \
getchannels.so \
test_lazyfree.so \ test_lazyfree.so \
timer.so \ timer.so \
defragtest.so \ defragtest.so \
...@@ -51,6 +53,7 @@ TEST_MODULES = \ ...@@ -51,6 +53,7 @@ TEST_MODULES = \
list.so \ list.so \
subcommands.so \ subcommands.so \
reply.so \ reply.so \
cmdintrospection.so \
eventloop.so eventloop.so
.PHONY: all .PHONY: all
......
...@@ -15,11 +15,13 @@ int set_aclcheck_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { ...@@ -15,11 +15,13 @@ int set_aclcheck_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
const char *flags = RedisModule_StringPtrLen(argv[1], NULL); const char *flags = RedisModule_StringPtrLen(argv[1], NULL);
if (!strcasecmp(flags, "W")) { if (!strcasecmp(flags, "W")) {
permissions = REDISMODULE_KEY_PERMISSION_WRITE; permissions = REDISMODULE_CMD_KEY_UPDATE;
} else if (!strcasecmp(flags, "R")) { } else if (!strcasecmp(flags, "R")) {
permissions = REDISMODULE_KEY_PERMISSION_READ; permissions = REDISMODULE_CMD_KEY_ACCESS;
} else if (!strcasecmp(flags, "*")) { } else if (!strcasecmp(flags, "*")) {
permissions = REDISMODULE_KEY_PERMISSION_ALL; permissions = REDISMODULE_CMD_KEY_UPDATE | REDISMODULE_CMD_KEY_ACCESS;
} else if (!strcasecmp(flags, "~")) {
permissions = 0; /* Requires either read or write */
} else { } else {
RedisModule_ReplyWithError(ctx, "INVALID FLAGS"); RedisModule_ReplyWithError(ctx, "INVALID FLAGS");
return REDISMODULE_OK; return REDISMODULE_OK;
...@@ -58,7 +60,7 @@ int publish_aclcheck_channel(RedisModuleCtx *ctx, RedisModuleString **argv, int ...@@ -58,7 +60,7 @@ int publish_aclcheck_channel(RedisModuleCtx *ctx, RedisModuleString **argv, int
/* Check that the pubsub channel can be accessed */ /* Check that the pubsub channel can be accessed */
RedisModuleString *user_name = RedisModule_GetCurrentUserName(ctx); RedisModuleString *user_name = RedisModule_GetCurrentUserName(ctx);
RedisModuleUser *user = RedisModule_GetModuleUserFromUserName(user_name); RedisModuleUser *user = RedisModule_GetModuleUserFromUserName(user_name);
int ret = RedisModule_ACLCheckChannelPermissions(user, argv[1], 1); int ret = RedisModule_ACLCheckChannelPermissions(user, argv[1], REDISMODULE_CMD_CHANNEL_SUBSCRIBE);
if (ret != 0) { if (ret != 0) {
RedisModule_ReplyWithError(ctx, "DENIED CHANNEL"); RedisModule_ReplyWithError(ctx, "DENIED CHANNEL");
RedisModule_FreeModuleUser(user); RedisModule_FreeModuleUser(user);
......
#include "redismodule.h"
#define UNUSED(V) ((void) V)
int cmd_xadd(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
UNUSED(argv);
UNUSED(argc);
RedisModule_ReplyWithSimpleString(ctx, "OK");
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
if (RedisModule_Init(ctx, "cmdintrospection", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"cmdintrospection.xadd",cmd_xadd,"write deny-oom random fast",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
RedisModuleCommand *xadd = RedisModule_GetCommand(ctx,"cmdintrospection.xadd");
RedisModuleCommandInfo info = {
.version = REDISMODULE_COMMAND_INFO_VERSION,
.arity = -5,
.summary = "Appends a new entry to a stream",
.since = "5.0.0",
.complexity = "O(1) when adding a new entry, O(N) when trimming where N being the number of entries evicted.",
.tips = "nondeterministic_output",
.history = (RedisModuleCommandHistoryEntry[]){
/* NOTE: All versions specified should be the module's versions, not
* Redis'! We use Redis versions in this example for the purpose of
* testing (comparing the output with the output of the vanilla
* XADD). */
{"6.2.0", "Added the `NOMKSTREAM` option, `MINID` trimming strategy and the `LIMIT` option."},
{"7.0.0", "Added support for the `<ms>-*` explicit ID form."},
{0}
},
.key_specs = (RedisModuleCommandKeySpec[]){
{
.notes = "UPDATE instead of INSERT because of the optional trimming feature",
.flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
.begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
.bs.index.pos = 1,
.find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
.fk.range = {0,1,0}
},
{0}
},
.args = (RedisModuleCommandArg[]){
{
.name = "key",
.type = REDISMODULE_ARG_TYPE_KEY,
.key_spec_index = 0
},
{
.name = "nomkstream",
.type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
.token = "NOMKSTREAM",
.since = "6.2.0",
.flags = REDISMODULE_CMD_ARG_OPTIONAL
},
{
.name = "trim",
.type = REDISMODULE_ARG_TYPE_BLOCK,
.flags = REDISMODULE_CMD_ARG_OPTIONAL,
.subargs = (RedisModuleCommandArg[]){
{
.name = "strategy",
.type = REDISMODULE_ARG_TYPE_ONEOF,
.subargs = (RedisModuleCommandArg[]){
{
.name = "maxlen",
.type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
.token = "MAXLEN",
},
{
.name = "minid",
.type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
.token = "MINID",
.since = "6.2.0",
},
{0}
}
},
{
.name = "operator",
.type = REDISMODULE_ARG_TYPE_ONEOF,
.flags = REDISMODULE_CMD_ARG_OPTIONAL,
.subargs = (RedisModuleCommandArg[]){
{
.name = "equal",
.type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
.token = "="
},
{
.name = "approximately",
.type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
.token = "~"
},
{0}
}
},
{
.name = "threshold",
.type = REDISMODULE_ARG_TYPE_STRING,
},
{
.name = "count",
.type = REDISMODULE_ARG_TYPE_INTEGER,
.token = "LIMIT",
.since = "6.2.0",
.flags = REDISMODULE_CMD_ARG_OPTIONAL
},
{0}
}
},
{
.name = "id_or_auto",
.type = REDISMODULE_ARG_TYPE_ONEOF,
.subargs = (RedisModuleCommandArg[]){
{
.name = "auto_id",
.type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
.token = "*"
},
{
.name = "id",
.type = REDISMODULE_ARG_TYPE_STRING,
},
{0}
}
},
{
.name = "field_value",
.type = REDISMODULE_ARG_TYPE_BLOCK,
.flags = REDISMODULE_CMD_ARG_MULTIPLE,
.subargs = (RedisModuleCommandArg[]){
{
.name = "field",
.type = REDISMODULE_ARG_TYPE_STRING,
},
{
.name = "value",
.type = REDISMODULE_ARG_TYPE_STRING,
},
{0}
}
},
{0}
}
};
if (RedisModule_SetCommandInfo(xadd, &info) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}
#include "redismodule.h"
#include <strings.h>
#include <assert.h>
#include <unistd.h>
#include <errno.h>
/* A sample with declarable channels, that are used to validate against ACLs */
int getChannels_subscribe(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if ((argc - 1) % 3 != 0) {
RedisModule_WrongArity(ctx);
return REDISMODULE_OK;
}
char *err = NULL;
/* getchannels.command [[subscribe|unsubscribe|publish] [pattern|literal] <channel> ...]
* This command marks the given channel is accessed based on the
* provided modifiers. */
for (int i = 1; i < argc; i += 3) {
const char *operation = RedisModule_StringPtrLen(argv[i], NULL);
const char *type = RedisModule_StringPtrLen(argv[i+1], NULL);
int flags = 0;
if (!strcasecmp(operation, "subscribe")) {
flags |= REDISMODULE_CMD_CHANNEL_SUBSCRIBE;
} else if (!strcasecmp(operation, "unsubscribe")) {
flags |= REDISMODULE_CMD_CHANNEL_UNSUBSCRIBE;
} else if (!strcasecmp(operation, "publish")) {
flags |= REDISMODULE_CMD_CHANNEL_PUBLISH;
} else {
err = "Invalid channel operation";
break;
}
if (!strcasecmp(type, "literal")) {
/* No op */
} else if (!strcasecmp(type, "pattern")) {
flags |= REDISMODULE_CMD_CHANNEL_PATTERN;
} else {
err = "Invalid channel type";
break;
}
if (RedisModule_IsChannelsPositionRequest(ctx)) {
RedisModule_ChannelAtPosWithFlags(ctx, i+2, flags);
}
}
if (!RedisModule_IsChannelsPositionRequest(ctx)) {
if (err) {
RedisModule_ReplyWithError(ctx, err);
} else {
/* Normal implementation would go here, but for tests just return okay */
RedisModule_ReplyWithSimpleString(ctx, "OK");
}
}
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
if (RedisModule_Init(ctx, "getchannels", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx, "getchannels.command", getChannels_subscribe, "getchannels-api", 0, 0, 0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}
...@@ -44,6 +44,40 @@ int getkeys_command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -44,6 +44,40 @@ int getkeys_command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
return REDISMODULE_OK; return REDISMODULE_OK;
} }
int getkeys_command_with_flags(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
int i;
int count = 0;
/* Handle getkeys-api introspection */
if (RedisModule_IsKeysPositionRequest(ctx)) {
for (i = 0; i < argc; i++) {
size_t len;
const char *str = RedisModule_StringPtrLen(argv[i], &len);
if (len == 3 && !strncasecmp(str, "key", 3) && i + 1 < argc)
RedisModule_KeyAtPosWithFlags(ctx, i + 1, REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS);
}
return REDISMODULE_OK;
}
/* Handle real command invocation */
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_LEN);
for (i = 0; i < argc; i++) {
size_t len;
const char *str = RedisModule_StringPtrLen(argv[i], &len);
if (len == 3 && !strncasecmp(str, "key", 3) && i + 1 < argc) {
RedisModule_ReplyWithString(ctx, argv[i+1]);
count++;
}
}
RedisModule_ReplySetArrayLength(ctx, count);
return REDISMODULE_OK;
}
int getkeys_fixed(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) int getkeys_fixed(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{ {
int i; int i;
...@@ -57,19 +91,22 @@ int getkeys_fixed(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -57,19 +91,22 @@ int getkeys_fixed(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
/* Introspect a command using RM_GetCommandKeys() and returns the list /* Introspect a command using RM_GetCommandKeys() and returns the list
* of keys. Essentially this is COMMAND GETKEYS implemented in a module. * of keys. Essentially this is COMMAND GETKEYS implemented in a module.
* INTROSPECT <with-flags> <cmd> <args>
*/ */
int getkeys_introspect(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) int getkeys_introspect(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{ {
UNUSED(argv); long long with_flags = 0;
UNUSED(argc);
if (argc < 3) { if (argc < 4) {
RedisModule_WrongArity(ctx); RedisModule_WrongArity(ctx);
return REDISMODULE_OK; return REDISMODULE_OK;
} }
int num_keys; if (RedisModule_StringToLongLong(argv[1],&with_flags) != REDISMODULE_OK)
int *keyidx = RedisModule_GetCommandKeys(ctx, &argv[1], argc - 1, &num_keys); return RedisModule_ReplyWithError(ctx,"ERR invalid integer");
int num_keys, *keyflags = NULL;
int *keyidx = RedisModule_GetCommandKeysWithFlags(ctx, &argv[2], argc - 2, &num_keys, with_flags ? &keyflags : NULL);
if (!keyidx) { if (!keyidx) {
if (!errno) if (!errno)
...@@ -93,10 +130,27 @@ int getkeys_introspect(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -93,10 +130,27 @@ int getkeys_introspect(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
int i; int i;
RedisModule_ReplyWithArray(ctx, num_keys); RedisModule_ReplyWithArray(ctx, num_keys);
for (i = 0; i < num_keys; i++) for (i = 0; i < num_keys; i++) {
RedisModule_ReplyWithString(ctx, argv[1 + keyidx[i]]); if (!with_flags) {
RedisModule_ReplyWithString(ctx, argv[2 + keyidx[i]]);
continue;
}
RedisModule_ReplyWithArray(ctx, 2);
RedisModule_ReplyWithString(ctx, argv[2 + keyidx[i]]);
char* sflags = "";
if (keyflags[i] & REDISMODULE_CMD_KEY_RO)
sflags = "RO";
else if (keyflags[i] & REDISMODULE_CMD_KEY_RW)
sflags = "RW";
else if (keyflags[i] & REDISMODULE_CMD_KEY_OW)
sflags = "OW";
else if (keyflags[i] & REDISMODULE_CMD_KEY_RM)
sflags = "RM";
RedisModule_ReplyWithCString(ctx, sflags);
}
RedisModule_Free(keyidx); RedisModule_Free(keyidx);
RedisModule_Free(keyflags);
} }
return REDISMODULE_OK; return REDISMODULE_OK;
...@@ -111,6 +165,9 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -111,6 +165,9 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
if (RedisModule_CreateCommand(ctx,"getkeys.command", getkeys_command,"getkeys-api",0,0,0) == REDISMODULE_ERR) if (RedisModule_CreateCommand(ctx,"getkeys.command", getkeys_command,"getkeys-api",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"getkeys.command_with_flags", getkeys_command_with_flags,"getkeys-api",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"getkeys.fixed", getkeys_fixed,"",2,4,1) == REDISMODULE_ERR) if (RedisModule_CreateCommand(ctx,"getkeys.fixed", getkeys_fixed,"",2,4,1) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
......
#include "redismodule.h" #include "redismodule.h"
#ifdef INCLUDE_UNRELEASED_KEYSPEC_API
#define UNUSED(V) ((void) V) #define UNUSED(V) ((void) V)
int kspec_legacy(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { /* This function implements all commands in this module. All we care about is
* the COMMAND metadata anyway. */
int kspec_impl(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
UNUSED(argv); UNUSED(argv);
UNUSED(argc); UNUSED(argc);
RedisModule_ReplyWithSimpleString(ctx, "OK"); RedisModule_ReplyWithSimpleString(ctx, "OK");
return REDISMODULE_OK; return REDISMODULE_OK;
} }
int kspec_complex1(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { int createKspecNone(RedisModuleCtx *ctx) {
UNUSED(argv); /* A command without keyspecs; only the legacy (first,last,step) triple (MSET like spec). */
UNUSED(argc); if (RedisModule_CreateCommand(ctx,"kspec.none",kspec_impl,"",1,-1,2) == REDISMODULE_ERR)
RedisModule_ReplyWithSimpleString(ctx, "OK"); return REDISMODULE_ERR;
return REDISMODULE_OK;
}
int kspec_complex2(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
UNUSED(argv);
UNUSED(argc);
RedisModule_ReplyWithSimpleString(ctx, "OK");
return REDISMODULE_OK; return REDISMODULE_OK;
} }
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { int createKspecTwoRanges(RedisModuleCtx *ctx) {
REDISMODULE_NOT_USED(argv); /* Test that two position/range-based key specs are combined to produce the
REDISMODULE_NOT_USED(argc); * legacy (first,last,step) values representing both keys. */
if (RedisModule_CreateCommand(ctx,"kspec.tworanges",kspec_impl,"",0,0,0) == REDISMODULE_ERR)
int spec_id; return REDISMODULE_ERR;
if (RedisModule_Init(ctx, "keyspecs", 1, REDISMODULE_APIVER_1)== REDISMODULE_ERR) RedisModuleCommand *command = RedisModule_GetCommand(ctx,"kspec.tworanges");
RedisModuleCommandInfo info = {
.version = REDISMODULE_COMMAND_INFO_VERSION,
.arity = -2,
.key_specs = (RedisModuleCommandKeySpec[]){
{
.flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
.begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
.bs.index.pos = 1,
.find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
.fk.range = {0,1,0}
},
{
.flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
.begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
.bs.index.pos = 2,
/* Omitted find_keys_type is shorthand for RANGE {0,1,0} */
},
{0}
}
};
if (RedisModule_SetCommandInfo(command, &info) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
/* Test legacy range "gluing" */ return REDISMODULE_OK;
if (RedisModule_CreateCommand(ctx,"kspec.legacy",kspec_legacy,"",0,0,0) == REDISMODULE_ERR) }
return REDISMODULE_ERR;
RedisModuleCommand *legacy = RedisModule_GetCommand(ctx,"kspec.legacy");
if (RedisModule_AddCommandKeySpec(legacy,"RO ACCESS",&spec_id) == REDISMODULE_ERR) int createKspecKeyword(RedisModuleCtx *ctx) {
return REDISMODULE_ERR; /* Only keyword-based specs. The legacy triple is wiped and set to (0,0,0). */
if (RedisModule_SetCommandKeySpecBeginSearchIndex(legacy,spec_id,1) == REDISMODULE_ERR) if (RedisModule_CreateCommand(ctx,"kspec.keyword",kspec_impl,"",3,-1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(legacy,spec_id,0,1,0) == REDISMODULE_ERR)
RedisModuleCommand *command = RedisModule_GetCommand(ctx,"kspec.keyword");
RedisModuleCommandInfo info = {
.version = REDISMODULE_COMMAND_INFO_VERSION,
.key_specs = (RedisModuleCommandKeySpec[]){
{
.flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
.begin_search_type = REDISMODULE_KSPEC_BS_KEYWORD,
.bs.keyword.keyword = "KEYS",
.bs.keyword.startfrom = 1,
.find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
.fk.range = {-1,1,0}
},
{0}
}
};
if (RedisModule_SetCommandInfo(command, &info) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(legacy,"RW UPDATE",&spec_id) == REDISMODULE_ERR) return REDISMODULE_OK;
return REDISMODULE_ERR; }
if (RedisModule_SetCommandKeySpecBeginSearchIndex(legacy,spec_id,2) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(legacy,spec_id,0,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
/* First is legacy, rest are new specs */ int createKspecComplex1(RedisModuleCtx *ctx) {
if (RedisModule_CreateCommand(ctx,"kspec.complex1",kspec_complex1,"",1,1,1) == REDISMODULE_ERR) /* First is a range a single key. The rest are keyword-based specs. */
if (RedisModule_CreateCommand(ctx,"kspec.complex1",kspec_impl,"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
RedisModuleCommand *command = RedisModule_GetCommand(ctx,"kspec.complex1");
RedisModuleCommandInfo info = {
.version = REDISMODULE_COMMAND_INFO_VERSION,
.key_specs = (RedisModuleCommandKeySpec[]){
{
.flags = REDISMODULE_CMD_KEY_RO,
.begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
.bs.index.pos = 1,
},
{
.flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
.begin_search_type = REDISMODULE_KSPEC_BS_KEYWORD,
.bs.keyword.keyword = "STORE",
.bs.keyword.startfrom = 2,
},
{
.flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
.begin_search_type = REDISMODULE_KSPEC_BS_KEYWORD,
.bs.keyword.keyword = "KEYS",
.bs.keyword.startfrom = 2,
.find_keys_type = REDISMODULE_KSPEC_FK_KEYNUM,
.fk.keynum = {0,1,1}
},
{0}
}
};
if (RedisModule_SetCommandInfo(command, &info) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
RedisModuleCommand *complex1 = RedisModule_GetCommand(ctx,"kspec.complex1");
if (RedisModule_AddCommandKeySpec(complex1,"RW UPDATE",&spec_id) == REDISMODULE_ERR) return REDISMODULE_OK;
return REDISMODULE_ERR; }
if (RedisModule_SetCommandKeySpecBeginSearchKeyword(complex1,spec_id,"STORE",2) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(complex1,spec_id,0,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(complex1,"RO ACCESS",&spec_id) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecBeginSearchKeyword(complex1,spec_id,"KEYS",2) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysKeynum(complex1,spec_id,0,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
int createKspecComplex2(RedisModuleCtx *ctx) {
/* First is not legacy, more than STATIC_KEYS_SPECS_NUM specs */ /* First is not legacy, more than STATIC_KEYS_SPECS_NUM specs */
if (RedisModule_CreateCommand(ctx,"kspec.complex2",kspec_complex2,"",0,0,0) == REDISMODULE_ERR) if (RedisModule_CreateCommand(ctx,"kspec.complex2",kspec_impl,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
RedisModuleCommand *complex2 = RedisModule_GetCommand(ctx,"kspec.complex2");
RedisModuleCommand *command = RedisModule_GetCommand(ctx,"kspec.complex2");
if (RedisModule_AddCommandKeySpec(complex2,"RW UPDATE",&spec_id) == REDISMODULE_ERR) RedisModuleCommandInfo info = {
return REDISMODULE_ERR; .version = REDISMODULE_COMMAND_INFO_VERSION,
if (RedisModule_SetCommandKeySpecBeginSearchKeyword(complex2,spec_id,"STORE",5) == REDISMODULE_ERR) .key_specs = (RedisModuleCommandKeySpec[]){
return REDISMODULE_ERR; {
if (RedisModule_SetCommandKeySpecFindKeysRange(complex2,spec_id,0,1,0) == REDISMODULE_ERR) .flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
return REDISMODULE_ERR; .begin_search_type = REDISMODULE_KSPEC_BS_KEYWORD,
.bs.keyword.keyword = "STORE",
if (RedisModule_AddCommandKeySpec(complex2,"RO ACCESS",&spec_id) == REDISMODULE_ERR) .bs.keyword.startfrom = 5,
return REDISMODULE_ERR; .find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
if (RedisModule_SetCommandKeySpecBeginSearchIndex(complex2,spec_id,1) == REDISMODULE_ERR) .fk.range = {0,1,0}
return REDISMODULE_ERR; },
if (RedisModule_SetCommandKeySpecFindKeysRange(complex2,spec_id,0,1,0) == REDISMODULE_ERR) {
.flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
.begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
.bs.index.pos = 1,
.find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
.fk.range = {0,1,0}
},
{
.flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
.begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
.bs.index.pos = 2,
.find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
.fk.range = {0,1,0}
},
{
.flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
.begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
.bs.index.pos = 3,
.find_keys_type = REDISMODULE_KSPEC_FK_KEYNUM,
.fk.keynum = {0,1,1}
},
{
.flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
.begin_search_type = REDISMODULE_KSPEC_BS_KEYWORD,
.bs.keyword.keyword = "MOREKEYS",
.bs.keyword.startfrom = 5,
.find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
.fk.range = {-1,1,0}
},
{0}
}
};
if (RedisModule_SetCommandInfo(command, &info) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(complex2,"RO ACCESS",&spec_id) == REDISMODULE_ERR) return REDISMODULE_OK;
return REDISMODULE_ERR; }
if (RedisModule_SetCommandKeySpecBeginSearchIndex(complex2,spec_id,2) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(complex2,spec_id,0,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(complex2,"RW UPDATE",&spec_id) == REDISMODULE_ERR) int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return REDISMODULE_ERR; REDISMODULE_NOT_USED(argv);
if (RedisModule_SetCommandKeySpecBeginSearchIndex(complex2,spec_id,3) == REDISMODULE_ERR) REDISMODULE_NOT_USED(argc);
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysKeynum(complex2,spec_id,0,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(complex2,"RW UPDATE",&spec_id) == REDISMODULE_ERR) if (RedisModule_Init(ctx, "keyspecs", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecBeginSearchKeyword(complex2,spec_id,"MOREKEYS",5) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(complex2,spec_id,-1,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (createKspecNone(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
if (createKspecTwoRanges(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
if (createKspecKeyword(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
if (createKspecComplex1(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
if (createKspecComplex2(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
return REDISMODULE_OK; return REDISMODULE_OK;
} }
#endif
...@@ -29,11 +29,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -29,11 +29,7 @@ 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);
#ifdef INCLUDE_UNRELEASED_KEYSPEC_API if (RedisModule_Init(ctx, "subcommands", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
int spec_id;
#endif
if (RedisModule_Init(ctx, "subcommands", 1, REDISMODULE_APIVER_1)== REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"subcommands.bitarray",NULL,"",0,0,0) == REDISMODULE_ERR) if (RedisModule_CreateCommand(ctx,"subcommands.bitarray",NULL,"",0,0,0) == REDISMODULE_ERR)
...@@ -43,28 +39,40 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -43,28 +39,40 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
if (RedisModule_CreateSubcommand(parent,"set",cmd_set,"",0,0,0) == REDISMODULE_ERR) if (RedisModule_CreateSubcommand(parent,"set",cmd_set,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
RedisModuleCommand *subcmd = RedisModule_GetCommand(ctx,"subcommands.bitarray|set"); RedisModuleCommand *subcmd = RedisModule_GetCommand(ctx,"subcommands.bitarray|set");
RedisModuleCommandInfo cmd_set_info = {
#ifdef INCLUDE_UNRELEASED_KEYSPEC_API .version = REDISMODULE_COMMAND_INFO_VERSION,
if (RedisModule_AddCommandKeySpec(subcmd,"RW UPDATE",&spec_id) == REDISMODULE_ERR) .key_specs = (RedisModuleCommandKeySpec[]){
return REDISMODULE_ERR; {
if (RedisModule_SetCommandKeySpecBeginSearchIndex(subcmd,spec_id,1) == REDISMODULE_ERR) .flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
return REDISMODULE_ERR; .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
if (RedisModule_SetCommandKeySpecFindKeysRange(subcmd,spec_id,0,1,0) == REDISMODULE_ERR) .bs.index.pos = 1,
.find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
.fk.range = {0,1,0}
},
{0}
}
};
if (RedisModule_SetCommandInfo(subcmd, &cmd_set_info) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
#endif
if (RedisModule_CreateSubcommand(parent,"get",cmd_get,"",0,0,0) == REDISMODULE_ERR) if (RedisModule_CreateSubcommand(parent,"get",cmd_get,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
subcmd = RedisModule_GetCommand(ctx,"subcommands.bitarray|get"); subcmd = RedisModule_GetCommand(ctx,"subcommands.bitarray|get");
RedisModuleCommandInfo cmd_get_info = {
#ifdef INCLUDE_UNRELEASED_KEYSPEC_API .version = REDISMODULE_COMMAND_INFO_VERSION,
if (RedisModule_AddCommandKeySpec(subcmd,"RO ACCESS",&spec_id) == REDISMODULE_ERR) .key_specs = (RedisModuleCommandKeySpec[]){
return REDISMODULE_ERR; {
if (RedisModule_SetCommandKeySpecBeginSearchIndex(subcmd,spec_id,1) == REDISMODULE_ERR) .flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
return REDISMODULE_ERR; .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
if (RedisModule_SetCommandKeySpecFindKeysRange(subcmd,spec_id,0,1,0) == REDISMODULE_ERR) .bs.index.pos = 1,
.find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
.fk.range = {0,1,0}
},
{0}
}
};
if (RedisModule_SetCommandInfo(subcmd, &cmd_get_info) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
#endif
/* Get the name of the command currently running. */ /* Get the name of the command currently running. */
if (RedisModule_CreateCommand(ctx,"subcommands.parent_get_fullname",cmd_get_fullname,"",0,0,0) == REDISMODULE_ERR) if (RedisModule_CreateCommand(ctx,"subcommands.parent_get_fullname",cmd_get_fullname,"",0,0,0) == REDISMODULE_ERR)
......
...@@ -44,5 +44,22 @@ test "Sentinel Set with other error situations" { ...@@ -44,5 +44,22 @@ test "Sentinel Set with other error situations" {
# unknown parameter option # unknown parameter option
assert_error "ERR Unknown option or number of arguments for SENTINEL SET 'fakeoption'" {S 0 SENTINEL SET mymaster fakeoption fakevalue} assert_error "ERR Unknown option or number of arguments for SENTINEL SET 'fakeoption'" {S 0 SENTINEL SET mymaster fakeoption fakevalue}
}
# save new config to disk failed
set info [S 0 SENTINEL master mymaster]
set origin_quorum [dict get $info quorum]
set update_quorum [expr $origin_quorum+1]
set sentinel_id 0
set configfilename [file join "sentinel_$sentinel_id" "sentinel.conf"]
set configfilename_bak [file join "sentinel_$sentinel_id" "sentinel.conf.bak"]
file rename $configfilename $configfilename_bak
file mkdir $configfilename
catch {[S 0 SENTINEL SET mymaster quorum $update_quorum]} err
file delete $configfilename
file rename $configfilename_bak $configfilename
assert_match "ERR Failed to save config file*" $err
}
source "../tests/includes/init-tests.tcl"
test "info command with at most one argument" {
set subCommandList {}
foreach arg {"" "all" "default" "everything"} {
if {$arg == ""} {
set info [S 0 info]
} else {
set info [S 0 info $arg]
}
assert { [string match "*redis_version*" $info] }
assert { [string match "*maxclients*" $info] }
assert { [string match "*used_cpu_user*" $info] }
assert { [string match "*sentinel_tilt*" $info] }
assert { ![string match "*used_memory*" $info] }
assert { ![string match "*rdb_last_bgsave*" $info] }
assert { ![string match "*master_repl_offset*" $info] }
assert { ![string match "*cluster_enabled*" $info] }
}
}
test "info command with one sub-section" {
set info [S 0 info cpu]
assert { [string match "*used_cpu_user*" $info] }
assert { ![string match "*sentinel_tilt*" $info] }
assert { ![string match "*redis_version*" $info] }
set info [S 0 info sentinel]
assert { [string match "*sentinel_tilt*" $info] }
assert { ![string match "*used_cpu_user*" $info] }
assert { ![string match "*redis_version*" $info] }
}
test "info command with multiple sub-sections" {
set info [S 0 info server sentinel replication]
assert { [string match "*redis_version*" $info] }
assert { [string match "*sentinel_tilt*" $info] }
assert { ![string match "*used_memory*" $info] }
assert { ![string match "*used_cpu_user*" $info] }
set info [S 0 info cpu all]
assert { [string match "*used_cpu_user*" $info] }
assert { [string match "*sentinel_tilt*" $info] }
assert { [string match "*redis_version*" $info] }
assert { ![string match "*used_memory*" $info] }
assert { ![string match "*master_repl_offset*" $info] }
}
...@@ -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::readraw {} array set ::redis::readraw {}
array set ::redis::attributes {} ;# Holds the RESP3 attributes from the last call
array set ::redis::reconnect {} array set ::redis::reconnect {}
array set ::redis::tls {} array set ::redis::tls {}
array set ::redis::callback {} array set ::redis::callback {}
...@@ -105,6 +106,7 @@ proc ::redis::__dispatch__raw__ {id method argv} { ...@@ -105,6 +106,7 @@ proc ::redis::__dispatch__raw__ {id method argv} {
set argv [lrange $argv 0 end-1] set argv [lrange $argv 0 end-1]
} }
if {[info command ::redis::__method__$method] eq {}} { if {[info command ::redis::__method__$method] eq {}} {
catch {unset ::redis::attributes($id)}
set cmd "*[expr {[llength $argv]+1}]\r\n" set cmd "*[expr {[llength $argv]+1}]\r\n"
append cmd "$[string length $method]\r\n$method\r\n" append cmd "$[string length $method]\r\n$method\r\n"
foreach a $argv { foreach a $argv {
...@@ -165,6 +167,7 @@ proc ::redis::__method__close {id fd} { ...@@ -165,6 +167,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::readraw($id)} catch {unset ::redis::readraw($id)}
catch {unset ::redis::attributes($id)}
catch {unset ::redis::reconnect($id)} catch {unset ::redis::reconnect($id)}
catch {unset ::redis::tls($id)} catch {unset ::redis::tls($id)}
catch {unset ::redis::state($id)} catch {unset ::redis::state($id)}
...@@ -185,6 +188,10 @@ proc ::redis::__method__readraw {id fd val} { ...@@ -185,6 +188,10 @@ proc ::redis::__method__readraw {id fd val} {
set ::redis::readraw($id) $val set ::redis::readraw($id) $val
} }
proc ::redis::__method__attributes {id fd} {
set _ $::redis::attributes($id)
}
proc ::redis::redis_write {fd buf} { proc ::redis::redis_write {fd buf} {
puts -nonewline $fd $buf puts -nonewline $fd $buf
} }
...@@ -286,8 +293,8 @@ proc ::redis::redis_read_reply {id fd} { ...@@ -286,8 +293,8 @@ proc ::redis::redis_read_reply {id fd} {
* {return [redis_multi_bulk_read $id $fd]} * {return [redis_multi_bulk_read $id $fd]}
% {return [redis_read_map $id $fd]} % {return [redis_read_map $id $fd]}
| { | {
# ignore attributes for now (nowhere to store them) set attrib [redis_read_map $id $fd]
redis_read_map $id $fd set ::redis::attributes($id) $attrib
continue continue
} }
default { default {
......
...@@ -24,10 +24,10 @@ proc assert_no_match {pattern value} { ...@@ -24,10 +24,10 @@ proc assert_no_match {pattern value} {
} }
} }
proc assert_match {pattern value} { proc assert_match {pattern value {detail ""}} {
if {![string match $pattern $value]} { if {![string match $pattern $value]} {
set context "(context: [info frame -1])" set context "(context: [info frame -1])"
error "assertion:Expected '$value' to match '$pattern' $context" error "assertion:Expected '$value' to match '$pattern' $context $detail"
} }
} }
...@@ -84,9 +84,9 @@ proc assert_range {value min max {detail ""}} { ...@@ -84,9 +84,9 @@ proc assert_range {value min max {detail ""}} {
proc assert_error {pattern code {detail ""}} { proc assert_error {pattern code {detail ""}} {
if {[catch {uplevel 1 $code} error]} { if {[catch {uplevel 1 $code} error]} {
assert_match $pattern $error assert_match $pattern $error $detail
} else { } else {
assert_failed "assertion:Expected an error but nothing was caught" $detail assert_failed "Expected an error matching '$pattern' but got '$error'" $detail
} }
} }
......
...@@ -89,7 +89,7 @@ proc waitForBgsave r { ...@@ -89,7 +89,7 @@ proc waitForBgsave r {
puts -nonewline "\nWaiting for background save to finish... " puts -nonewline "\nWaiting for background save to finish... "
flush stdout flush stdout
} }
after 1000 after 50
} else { } else {
break break
} }
...@@ -103,7 +103,7 @@ proc waitForBgrewriteaof r { ...@@ -103,7 +103,7 @@ proc waitForBgrewriteaof r {
puts -nonewline "\nWaiting for background AOF rewrite to finish... " puts -nonewline "\nWaiting for background AOF rewrite to finish... "
flush stdout flush stdout
} }
after 1000 after 50
} else { } else {
break break
} }
...@@ -647,7 +647,7 @@ proc latencyrstat_percentiles {cmd r} { ...@@ -647,7 +647,7 @@ proc latencyrstat_percentiles {cmd r} {
proc generate_fuzzy_traffic_on_key {key duration} { proc generate_fuzzy_traffic_on_key {key duration} {
# Commands per type, blocking commands removed # Commands per type, blocking commands removed
# TODO: extract these from help.h or elsewhere, and improve to include other types # TODO: extract these from COMMAND DOCS, and improve to include other types
set string_commands {APPEND BITCOUNT BITFIELD BITOP BITPOS DECR DECRBY GET GETBIT GETRANGE GETSET INCR INCRBY INCRBYFLOAT MGET MSET MSETNX PSETEX SET SETBIT SETEX SETNX SETRANGE LCS STRLEN} set string_commands {APPEND BITCOUNT BITFIELD BITOP BITPOS DECR DECRBY GET GETBIT GETRANGE GETSET INCR INCRBY INCRBYFLOAT MGET MSET MSETNX PSETEX SET SETBIT SETEX SETNX SETRANGE LCS STRLEN}
set hash_commands {HDEL HEXISTS HGET HGETALL HINCRBY HINCRBYFLOAT HKEYS HLEN HMGET HMSET HSCAN HSET HSETNX HSTRLEN HVALS HRANDFIELD} set hash_commands {HDEL HEXISTS HGET HGETALL HINCRBY HINCRBYFLOAT HKEYS HLEN HMGET HMSET HSCAN HSET HSETNX HSTRLEN HVALS HRANDFIELD}
set zset_commands {ZADD ZCARD ZCOUNT ZINCRBY ZINTERSTORE ZLEXCOUNT ZPOPMAX ZPOPMIN ZRANGE ZRANGEBYLEX ZRANGEBYSCORE ZRANK ZREM ZREMRANGEBYLEX ZREMRANGEBYRANK ZREMRANGEBYSCORE ZREVRANGE ZREVRANGEBYLEX ZREVRANGEBYSCORE ZREVRANK ZSCAN ZSCORE ZUNIONSTORE ZRANDMEMBER} set zset_commands {ZADD ZCARD ZCOUNT ZINCRBY ZINTERSTORE ZLEXCOUNT ZPOPMAX ZPOPMIN ZRANGE ZRANGEBYLEX ZRANGEBYSCORE ZRANK ZREM ZREMRANGEBYLEX ZREMRANGEBYRANK ZREMRANGEBYSCORE ZREVRANGE ZREVRANGEBYLEX ZREVRANGEBYSCORE ZREVRANK ZSCAN ZSCORE ZUNIONSTORE ZRANDMEMBER}
......
...@@ -20,6 +20,7 @@ set ::all_tests { ...@@ -20,6 +20,7 @@ set ::all_tests {
unit/keyspace unit/keyspace
unit/scan unit/scan
unit/info unit/info
unit/info-command
unit/type/string unit/type/string
unit/type/incr unit/type/incr
unit/type/list unit/type/list
...@@ -92,6 +93,7 @@ set ::all_tests { ...@@ -92,6 +93,7 @@ set ::all_tests {
unit/cluster unit/cluster
unit/client-eviction unit/client-eviction
unit/violations unit/violations
unit/replybufsize
} }
# Index to the next test to run in the ::all_tests list. # Index to the next test to run in the ::all_tests list.
set ::next_test 0 set ::next_test 0
......
...@@ -7,6 +7,11 @@ start_server {tags {"acl external:skip"}} { ...@@ -7,6 +7,11 @@ start_server {tags {"acl external:skip"}} {
r ACL setuser newuser r ACL setuser newuser
} }
test {Usernames can not contain spaces or null characters} {
catch {r ACL setuser "a a"} err
set err
} {*Usernames can't contain spaces or null characters*}
test {New users start disabled} { test {New users start disabled} {
r ACL setuser newuser >passwd1 r ACL setuser newuser >passwd1
catch {r AUTH newuser passwd1} err catch {r AUTH newuser passwd1} err
...@@ -699,6 +704,23 @@ start_server {tags {"acl external:skip"}} { ...@@ -699,6 +704,23 @@ start_server {tags {"acl external:skip"}} {
catch {[r ping]} e catch {[r ping]} e
assert_match "*I/O error*" $e assert_match "*I/O error*" $e
} }
test {ACL GENPASS command failed test} {
catch {r ACL genpass -236} err1
catch {r ACL genpass 5000} err2
assert_match "*ACL GENPASS argument must be the number*" $err1
assert_match "*ACL GENPASS argument must be the number*" $err2
}
test {Default user can not be removed} {
catch {r ACL deluser default} err
set err
} {ERR The 'default' user cannot be removed}
test {ACL load non-existing configured ACL file} {
catch {r ACL load} err
set err
} {*Redis instance is not configured to use an ACL file*}
} }
set server_path [tmpdir "server.acl"] set server_path [tmpdir "server.acl"]
......
...@@ -3,6 +3,11 @@ start_server {tags {"auth external:skip"}} { ...@@ -3,6 +3,11 @@ start_server {tags {"auth external:skip"}} {
catch {r auth foo} err catch {r auth foo} err
set _ $err set _ $err
} {ERR*any password*} } {ERR*any password*}
test {Arity check for auth command} {
catch {r auth a b c} err
set _ $err
} {*syntax error*}
} }
start_server {tags {"auth external:skip"} overrides {requirepass foobar}} { start_server {tags {"auth external:skip"} overrides {requirepass foobar}} {
......
...@@ -45,6 +45,10 @@ proc mb {v} { ...@@ -45,6 +45,10 @@ proc mb {v} {
return [expr $v * 1024 * 1024] return [expr $v * 1024 * 1024]
} }
proc kb {v} {
return [expr $v * 1024]
}
start_server {} { start_server {} {
set maxmemory_clients 3000000 set maxmemory_clients 3000000
r config set maxmemory-clients $maxmemory_clients r config set maxmemory-clients $maxmemory_clients
...@@ -213,7 +217,7 @@ start_server {} { ...@@ -213,7 +217,7 @@ start_server {} {
r debug pause-cron 0 r debug pause-cron 0
$rr close $rr close
$redirected_c close $redirected_c close
} } {0} {needs:debug}
test "client evicted due to client tracking prefixes" { test "client evicted due to client tracking prefixes" {
r flushdb r flushdb
...@@ -391,6 +395,7 @@ start_server {} { ...@@ -391,6 +395,7 @@ start_server {} {
test "evict clients only until below limit" { test "evict clients only until below limit" {
set client_count 10 set client_count 10
set client_mem [mb 1] set client_mem [mb 1]
r debug replybuffer-peak-reset-time never
r config set maxmemory-clients 0 r config set maxmemory-clients 0
r client setname control r client setname control
r client no-evict on r client no-evict on
...@@ -433,19 +438,23 @@ start_server {} { ...@@ -433,19 +438,23 @@ start_server {} {
set connected_clients [llength [lsearch -all [split [string trim [r client list]] "\r\n"] *name=client*]] set connected_clients [llength [lsearch -all [split [string trim [r client list]] "\r\n"] *name=client*]]
assert {$connected_clients == [expr $client_count / 2]} assert {$connected_clients == [expr $client_count / 2]}
# Restore the peak reset time to default
r debug replybuffer-peak-reset-time reset
foreach rr $rrs {$rr close} foreach rr $rrs {$rr close}
} } {} {needs:debug}
} }
start_server {} { start_server {} {
test "evict clients in right order (large to small)" { test "evict clients in right order (large to small)" {
# Note that each size step needs to be at least x2 larger than previous step # Note that each size step needs to be at least x2 larger than previous step
# because of how the client-eviction size bucktting works # because of how the client-eviction size bucktting works
set sizes [list 100000 [mb 1] [mb 3]] set sizes [list [kb 128] [mb 1] [mb 3]]
set clients_per_size 3 set clients_per_size 3
r client setname control r client setname control
r client no-evict on r client no-evict on
r config set maxmemory-clients 0 r config set maxmemory-clients 0
r debug replybuffer-peak-reset-time never
# Run over all sizes and create some clients using up that size # Run over all sizes and create some clients using up that size
set total_client_mem 0 set total_client_mem 0
...@@ -470,7 +479,6 @@ start_server {} { ...@@ -470,7 +479,6 @@ start_server {} {
# Account total client memory usage # Account total client memory usage
incr total_mem [expr $clients_per_size * $client_mem] incr total_mem [expr $clients_per_size * $client_mem]
} }
incr total_mem [client_field control tot-mem]
# Make sure all clients are connected # Make sure all clients are connected
set clients [split [string trim [r client list]] "\r\n"] set clients [split [string trim [r client list]] "\r\n"]
...@@ -481,8 +489,9 @@ start_server {} { ...@@ -481,8 +489,9 @@ start_server {} {
# For each size reduce maxmemory-clients so relevant clients should be evicted # For each size reduce maxmemory-clients so relevant clients should be evicted
# do this from largest to smallest # do this from largest to smallest
foreach size [lreverse $sizes] { foreach size [lreverse $sizes] {
set control_mem [client_field control tot-mem]
set total_mem [expr $total_mem - $clients_per_size * $size] set total_mem [expr $total_mem - $clients_per_size * $size]
r config set maxmemory-clients $total_mem r config set maxmemory-clients [expr $total_mem + $control_mem]
set clients [split [string trim [r client list]] "\r\n"] set clients [split [string trim [r client list]] "\r\n"]
# Verify only relevant clients were evicted # Verify only relevant clients were evicted
for {set i 0} {$i < [llength $sizes]} {incr i} { for {set i 0} {$i < [llength $sizes]} {incr i} {
...@@ -495,8 +504,12 @@ start_server {} { ...@@ -495,8 +504,12 @@ start_server {} {
} }
} }
} }
# Restore the peak reset time to default
r debug replybuffer-peak-reset-time reset
foreach rr $rrs {$rr close} foreach rr $rrs {$rr close}
} } {} {needs:debug}
} }
} }
......
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