Meir Shpilraien (Spielrein) 5c3938d5cc
Match REDISMODULE_OPEN_KEY_* flags to LOOKUP_* flags (#11772)
The PR adds support for the following flags on RedisModule_OpenKey:

* REDISMODULE_OPEN_KEY_NONOTIFY - Don't trigger keyspace event on key misses.
* REDISMODULE_OPEN_KEY_NOSTATS - Don't update keyspace hits/misses counters.
* REDISMODULE_OPEN_KEY_NOEXPIRE - Avoid deleting lazy expired keys.
* REDISMODULE_OPEN_KEY_NOEFFECTS - Avoid any effects from fetching the key

In addition, added `RM_GetOpenKeyModesAll`, which returns the mask of all
supported OpenKey modes. This allows the module to check, in runtime, which
OpenKey modes are supported by the current Redis instance.
2023-02-09 14:59:05 +02:00

502 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 {test silent open key} {
r debug set-active-expire 0
r test.clear_n_events
r set x 1 PX 10
after 1000
# now the key has been expired, open it silently and make sure not event were fired.
assert_error {key not found} {r test.silent_open_key x}
assert_equal {0} [r test.get_n_events]
}
test "Unload the module - misc" {
assert_equal {OK} [r module unload misc]
}
}