mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-22 16:18:28 -05:00
38028dab8d
RM_Call is designed to let modules call redis commands disregarding the OOM state (the module is responsible to declare its command flags to redis, or perform the necessary checks). The other (new) alternative is to pass the "M" flag to RM_Call so that redis can OOM reject commands implicitly. However, Currently, RM_Call enforces OOM on scripts (excluding scripts that declared `allow-oom`) in all cases, regardless of the RM_Call "M" flag being present. This PR fixes scripts to be consistent with other commands being executed by RM_Call. It modifies the flow in effect treats scripts as if they if they have the ALLOW_OOM script flag, if the "M" flag is not passed (i.e. no OOM checking is being performed by RM_Call, so no OOM checking should be done on script). Co-authored-by: Oran Agra <oran@redislabs.com>
492 lines
14 KiB
Tcl
492 lines
14 KiB
Tcl
set testmodule [file normalize tests/modules/misc.so]
|
|
|
|
start_server {tags {"modules"}} {
|
|
r module load $testmodule
|
|
|
|
test {test RM_Call} {
|
|
set info [r test.call_info commandstats]
|
|
# cmdstat is not in a default section, so we also test an argument was passed
|
|
assert { [string match "*cmdstat_module*" $info] }
|
|
}
|
|
|
|
test {test RM_Call args array} {
|
|
set info [r test.call_generic info commandstats]
|
|
# cmdstat is not in a default section, so we also test an argument was passed
|
|
assert { [string match "*cmdstat_module*" $info] }
|
|
}
|
|
|
|
test {test RM_Call recursive} {
|
|
set info [r test.call_generic test.call_generic info commandstats]
|
|
assert { [string match "*cmdstat_module*" $info] }
|
|
}
|
|
|
|
test {test redis version} {
|
|
set version [s redis_version]
|
|
assert_equal $version [r test.redisversion]
|
|
}
|
|
|
|
test {test long double conversions} {
|
|
set ld [r test.ld_conversion]
|
|
assert {[string match $ld "0.00000000000000001"]}
|
|
}
|
|
|
|
test {test unsigned long long conversions} {
|
|
set ret [r test.ull_conversion]
|
|
assert {[string match $ret "ok"]}
|
|
}
|
|
|
|
test {test module db commands} {
|
|
r set x foo
|
|
set key [r test.randomkey]
|
|
assert_equal $key "x"
|
|
assert_equal [r test.dbsize] 1
|
|
r test.flushall
|
|
assert_equal [r test.dbsize] 0
|
|
}
|
|
|
|
test {test RedisModule_ResetDataset do not reset functions} {
|
|
r function load {#!lua name=lib
|
|
redis.register_function('test', function() return 1 end)
|
|
}
|
|
assert_equal [r function list] {{library_name lib engine LUA functions {{name test description {} flags {}}}}}
|
|
r test.flushall
|
|
assert_equal [r function list] {{library_name lib engine LUA functions {{name test description {} flags {}}}}}
|
|
r function flush
|
|
}
|
|
|
|
test {test module keyexists} {
|
|
r set x foo
|
|
assert_equal 1 [r test.keyexists x]
|
|
r del x
|
|
assert_equal 0 [r test.keyexists x]
|
|
}
|
|
|
|
test {test module lru api} {
|
|
r config set maxmemory-policy allkeys-lru
|
|
r set x foo
|
|
set lru [r test.getlru x]
|
|
assert { $lru <= 1000 }
|
|
set was_set [r test.setlru x 100000]
|
|
assert { $was_set == 1 }
|
|
set idle [r object idletime x]
|
|
assert { $idle >= 100 }
|
|
set lru [r test.getlru x]
|
|
assert { $lru >= 100000 }
|
|
r config set maxmemory-policy allkeys-lfu
|
|
set lru [r test.getlru x]
|
|
assert { $lru == -1 }
|
|
set was_set [r test.setlru x 100000]
|
|
assert { $was_set == 0 }
|
|
}
|
|
r config set maxmemory-policy allkeys-lru
|
|
|
|
test {test module lfu api} {
|
|
r config set maxmemory-policy allkeys-lfu
|
|
r set x foo
|
|
set lfu [r test.getlfu x]
|
|
assert { $lfu >= 1 }
|
|
set was_set [r test.setlfu x 100]
|
|
assert { $was_set == 1 }
|
|
set freq [r object freq x]
|
|
assert { $freq <= 100 }
|
|
set lfu [r test.getlfu x]
|
|
assert { $lfu <= 100 }
|
|
r config set maxmemory-policy allkeys-lru
|
|
set lfu [r test.getlfu x]
|
|
assert { $lfu == -1 }
|
|
set was_set [r test.setlfu x 100]
|
|
assert { $was_set == 0 }
|
|
}
|
|
|
|
test {test module clientinfo api} {
|
|
# Test basic sanity and SSL flag
|
|
set info [r test.clientinfo]
|
|
set ssl_flag [expr $::tls ? {"ssl:"} : {":"}]
|
|
|
|
assert { [dict get $info db] == 9 }
|
|
assert { [dict get $info flags] == "${ssl_flag}::::" }
|
|
|
|
# Test MULTI flag
|
|
r multi
|
|
r test.clientinfo
|
|
set info [lindex [r exec] 0]
|
|
assert { [dict get $info flags] == "${ssl_flag}::::multi" }
|
|
|
|
# Test TRACKING flag
|
|
r client tracking on
|
|
set info [r test.clientinfo]
|
|
assert { [dict get $info flags] == "${ssl_flag}::tracking::" }
|
|
}
|
|
|
|
test {test module get/set client name by id api} {
|
|
catch { r test.getname } e
|
|
assert_equal "-ERR No name" $e
|
|
r client setname nobody
|
|
catch { r test.setname "name with spaces" } e
|
|
assert_match "*Invalid argument*" $e
|
|
assert_equal nobody [r client getname]
|
|
assert_equal nobody [r test.getname]
|
|
r test.setname somebody
|
|
assert_equal somebody [r client getname]
|
|
}
|
|
|
|
test {test module getclientcert api} {
|
|
set cert [r test.getclientcert]
|
|
|
|
if {$::tls} {
|
|
assert {$cert != ""}
|
|
} else {
|
|
assert {$cert == ""}
|
|
}
|
|
}
|
|
|
|
test {test detached thread safe cnotext} {
|
|
r test.log_tsctx "info" "Test message"
|
|
verify_log_message 0 "*<misc> Test message*" 0
|
|
}
|
|
|
|
test {test RM_Call CLIENT INFO} {
|
|
assert_match "*fd=-1*" [r test.call_generic client info]
|
|
}
|
|
|
|
test {Unsafe command names are sanitized in INFO output} {
|
|
r test.weird:cmd
|
|
set info [r info commandstats]
|
|
assert_match {*cmdstat_test.weird_cmd:calls=1*} $info
|
|
}
|
|
|
|
test {test monotonic time} {
|
|
set x [r test.monotonic_time]
|
|
assert { [r test.monotonic_time] >= $x }
|
|
}
|
|
|
|
test {rm_call OOM} {
|
|
r config set maxmemory 1
|
|
r config set maxmemory-policy volatile-lru
|
|
|
|
# sanity test plain call
|
|
assert_equal {OK} [
|
|
r test.rm_call set x 1
|
|
]
|
|
|
|
# add the M flag
|
|
assert_error {OOM *} {
|
|
r test.rm_call_flags M set x 1
|
|
|
|
}
|
|
|
|
# test a non deny-oom command
|
|
assert_equal {1} [
|
|
r test.rm_call_flags M get x
|
|
]
|
|
|
|
r config set maxmemory 0
|
|
} {OK} {needs:config-maxmemory}
|
|
|
|
test {rm_call clear OOM} {
|
|
r config set maxmemory 1
|
|
|
|
# verify rm_call fails with OOM
|
|
assert_error {OOM *} {
|
|
r test.rm_call_flags M set x 1
|
|
}
|
|
|
|
# clear OOM state
|
|
r config set maxmemory 0
|
|
|
|
# test set command is allowed
|
|
r test.rm_call_flags M set x 1
|
|
} {OK} {needs:config-maxmemory}
|
|
|
|
test {rm_call OOM Eval} {
|
|
r config set maxmemory 1
|
|
r config set maxmemory-policy volatile-lru
|
|
|
|
# use the M flag without allow-oom shebang flag
|
|
assert_error {OOM *} {
|
|
r test.rm_call_flags M eval {#!lua
|
|
redis.call('set','x',1)
|
|
return 1
|
|
} 1 x
|
|
}
|
|
|
|
# add the M flag with allow-oom shebang flag
|
|
assert_equal {1} [
|
|
r test.rm_call_flags M eval {#!lua flags=allow-oom
|
|
redis.call('set','x',1)
|
|
return 1
|
|
} 1 x
|
|
]
|
|
|
|
r config set maxmemory 0
|
|
} {OK} {needs:config-maxmemory}
|
|
|
|
test {rm_call write flag} {
|
|
# add the W flag
|
|
assert_error {ERR Write command 'set' was called while write is not allowed.} {
|
|
r test.rm_call_flags W set x 1
|
|
}
|
|
|
|
# test a non deny-oom command
|
|
r test.rm_call_flags W get x
|
|
} {1}
|
|
|
|
test {rm_call EVAL} {
|
|
r test.rm_call eval {
|
|
redis.call('set','x',1)
|
|
return 1
|
|
} 1 x
|
|
|
|
assert_error {ERR Write commands are not allowed from read-only scripts.*} {
|
|
r test.rm_call eval {#!lua flags=no-writes
|
|
redis.call('set','x',1)
|
|
return 1
|
|
} 1 x
|
|
}
|
|
}
|
|
|
|
# Note: each script is unique, to check that flags are extracted correctly
|
|
test {rm_call EVAL - OOM - with M flag} {
|
|
r config set maxmemory 1
|
|
|
|
# script without shebang, but uses SET, so fails
|
|
assert_error {*OOM command not allowed when used memory > 'maxmemory'*} {
|
|
r test.rm_call_flags M eval {
|
|
redis.call('set','x',1)
|
|
return 1
|
|
} 1 x
|
|
}
|
|
|
|
# script with an allow-oom flag, succeeds despite using SET
|
|
r test.rm_call_flags M eval {#!lua flags=allow-oom
|
|
redis.call('set','x', 1)
|
|
return 2
|
|
} 1 x
|
|
|
|
# script with no-writes flag, implies allow-oom, succeeds
|
|
r test.rm_call_flags M eval {#!lua flags=no-writes
|
|
redis.call('get','x')
|
|
return 2
|
|
} 1 x
|
|
|
|
# script with shebang using default flags, so fails regardless of using only GET
|
|
assert_error {*OOM command not allowed when used memory > 'maxmemory'*} {
|
|
r test.rm_call_flags M eval {#!lua
|
|
redis.call('get','x')
|
|
return 3
|
|
} 1 x
|
|
}
|
|
|
|
# script without shebang, but uses GET, so succeeds
|
|
r test.rm_call_flags M eval {
|
|
redis.call('get','x')
|
|
return 4
|
|
} 1 x
|
|
|
|
r config set maxmemory 0
|
|
} {OK} {needs:config-maxmemory}
|
|
|
|
# All RM_Call for script succeeds in OOM state without using the M flag
|
|
test {rm_call EVAL - OOM - without M flag} {
|
|
r config set maxmemory 1
|
|
|
|
# no shebang at all
|
|
r test.rm_call eval {
|
|
redis.call('set','x',1)
|
|
return 6
|
|
} 1 x
|
|
|
|
# Shebang without flags
|
|
r test.rm_call eval {#!lua
|
|
redis.call('set','x', 1)
|
|
return 7
|
|
} 1 x
|
|
|
|
# with allow-oom flag
|
|
r test.rm_call eval {#!lua flags=allow-oom
|
|
redis.call('set','x', 1)
|
|
return 8
|
|
} 1 x
|
|
|
|
r config set maxmemory 0
|
|
} {OK} {needs:config-maxmemory}
|
|
|
|
test "not enough good replicas" {
|
|
r set x "some value"
|
|
r config set min-replicas-to-write 1
|
|
|
|
# rm_call in script mode
|
|
assert_error {NOREPLICAS *} {r test.rm_call_flags S set x s}
|
|
|
|
assert_equal [
|
|
r test.rm_call eval {#!lua flags=no-writes
|
|
return redis.call('get','x')
|
|
} 1 x
|
|
] "some value"
|
|
|
|
assert_equal [
|
|
r test.rm_call eval {
|
|
return redis.call('get','x')
|
|
} 1 x
|
|
] "some value"
|
|
|
|
assert_error {NOREPLICAS *} {
|
|
r test.rm_call eval {#!lua
|
|
return redis.call('get','x')
|
|
} 1 x
|
|
}
|
|
|
|
assert_error {NOREPLICAS *} {
|
|
r test.rm_call eval {
|
|
return redis.call('set','x', 1)
|
|
} 1 x
|
|
}
|
|
|
|
r config set min-replicas-to-write 0
|
|
}
|
|
|
|
test {rm_call EVAL - read-only replica} {
|
|
r replicaof 127.0.0.1 1
|
|
|
|
# rm_call in script mode
|
|
assert_error {READONLY *} {r test.rm_call_flags S set x 1}
|
|
|
|
assert_error {READONLY You can't write against a read only replica. script*} {
|
|
r test.rm_call eval {
|
|
redis.call('set','x',1)
|
|
return 1
|
|
} 1 x
|
|
}
|
|
|
|
r test.rm_call eval {#!lua flags=no-writes
|
|
redis.call('get','x')
|
|
return 2
|
|
} 1 x
|
|
|
|
assert_error {READONLY Can not run script with write flag on readonly replica*} {
|
|
r test.rm_call eval {#!lua
|
|
redis.call('get','x')
|
|
return 3
|
|
} 1 x
|
|
}
|
|
|
|
r test.rm_call eval {
|
|
redis.call('get','x')
|
|
return 4
|
|
} 1 x
|
|
|
|
r replicaof no one
|
|
} {OK} {needs:config-maxmemory}
|
|
|
|
test {rm_call EVAL - stale replica} {
|
|
r replicaof 127.0.0.1 1
|
|
r config set replica-serve-stale-data no
|
|
|
|
# rm_call in script mode
|
|
assert_error {MASTERDOWN *} {
|
|
r test.rm_call_flags S get x
|
|
}
|
|
|
|
assert_error {MASTERDOWN *} {
|
|
r test.rm_call eval {#!lua flags=no-writes
|
|
redis.call('get','x')
|
|
return 2
|
|
} 1 x
|
|
}
|
|
|
|
assert_error {MASTERDOWN *} {
|
|
r test.rm_call eval {
|
|
redis.call('get','x')
|
|
return 4
|
|
} 1 x
|
|
}
|
|
|
|
r replicaof no one
|
|
r config set replica-serve-stale-data yes
|
|
} {OK} {needs:config-maxmemory}
|
|
|
|
test "rm_call EVAL - failed bgsave prevents writes" {
|
|
r config set rdb-key-save-delay 10000000
|
|
populate 1000
|
|
r set x x
|
|
r bgsave
|
|
set pid1 [get_child_pid 0]
|
|
catch {exec kill -9 $pid1}
|
|
waitForBgsave r
|
|
|
|
# make sure a read command succeeds
|
|
assert_equal [r get x] x
|
|
|
|
# make sure a write command fails
|
|
assert_error {MISCONF *} {r set x y}
|
|
|
|
# rm_call in script mode
|
|
assert_error {MISCONF *} {r test.rm_call_flags S set x 1}
|
|
|
|
# repeate with script
|
|
assert_error {MISCONF *} {r test.rm_call eval {
|
|
return redis.call('set','x',1)
|
|
} 1 x
|
|
}
|
|
assert_equal {x} [r test.rm_call eval {
|
|
return redis.call('get','x')
|
|
} 1 x
|
|
]
|
|
|
|
# again with script using shebang
|
|
assert_error {MISCONF *} {r test.rm_call eval {#!lua
|
|
return redis.call('set','x',1)
|
|
} 1 x
|
|
}
|
|
assert_equal {x} [r test.rm_call eval {#!lua flags=no-writes
|
|
return redis.call('get','x')
|
|
} 1 x
|
|
]
|
|
|
|
r config set rdb-key-save-delay 0
|
|
r bgsave
|
|
waitForBgsave r
|
|
|
|
# server is writable again
|
|
r set x y
|
|
} {OK}
|
|
}
|
|
|
|
start_server {tags {"modules"}} {
|
|
r module load $testmodule
|
|
|
|
test {test Dry Run - OK OOM/ACL} {
|
|
set x 5
|
|
r set x $x
|
|
catch {r test.rm_call_flags DMC set x 10} e
|
|
assert_match {*NULL reply returned*} $e
|
|
assert_equal [r get x] 5
|
|
}
|
|
|
|
test {test Dry Run - Fail OOM} {
|
|
set x 5
|
|
r set x $x
|
|
r config set maxmemory 1
|
|
catch {r test.rm_call_flags DM set x 10} e
|
|
assert_match {*OOM*} $e
|
|
assert_equal [r get x] $x
|
|
r config set maxmemory 0
|
|
} {OK} {needs:config-maxmemory}
|
|
|
|
test {test Dry Run - Fail ACL} {
|
|
set x 5
|
|
r set x $x
|
|
# deny all permissions besides the dryrun command
|
|
r acl setuser default resetkeys
|
|
|
|
catch {r test.rm_call_flags DC set x 10} e
|
|
assert_match {*NOPERM No permissions to access a key*} $e
|
|
r acl setuser default +@all ~*
|
|
assert_equal [r get x] $x
|
|
}
|
|
|
|
test "Unload the module - misc" {
|
|
assert_equal {OK} [r module unload misc]
|
|
}
|
|
}
|