mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-23 00:28:26 -05:00
365cbf46a7
Follow the conclusions to support Functions in redis cluster (#9899) Added 2 new FUNCTION sub-commands: 1. `FUNCTION DUMP` - dump a binary payload representation of all the functions. 2. `FUNCTION RESTORE <PAYLOAD> [FLUSH|APPEND|REPLACE]` - give the binary payload extracted using `FUNCTION DUMP`, restore all the functions on the given payload. Restore policy can be given to control how to handle existing functions (default is APPEND): * FLUSH: delete all existing functions. * APPEND: appends the restored functions to the existing functions. On collision, abort. * REPLACE: appends the restored functions to the existing functions. On collision, replace the old function with the new function. Modify `redis-cli --cluster add-node` to use `FUNCTION DUMP` to get existing functions from one of the nodes in the cluster, and `FUNCTION RESTORE` to load the same set of functions to the new node. `redis-cli` will execute this step before sending the `CLUSTER MEET` command to the new node. If `FUNCTION DUMP` returns an error, assume the current Redis version do not support functions and skip `FUNCTION RESTORE`. If `FUNCTION RESTORE` fails, abort and do not send the `CLUSTER MEET` command. If the new node already contains functions (before the `FUNCTION RESTORE` is sent), abort and do not add the node to the cluster. Test was added to verify `redis-cli --cluster add-node` works as expected.
469 lines
16 KiB
Tcl
469 lines
16 KiB
Tcl
start_server {tags {"scripting"}} {
|
|
test {FUNCTION - Basic usage} {
|
|
r function create LUA test {return 'hello'}
|
|
r fcall test 0
|
|
} {hello}
|
|
|
|
test {FUNCTION - Create an already exiting function raise error} {
|
|
catch {
|
|
r function create LUA test {return 'hello1'}
|
|
} e
|
|
set _ $e
|
|
} {*Function already exists*}
|
|
|
|
test {FUNCTION - Create an already exiting function raise error (case insensitive)} {
|
|
catch {
|
|
r function create LUA TEST {return 'hello1'}
|
|
} e
|
|
set _ $e
|
|
} {*Function already exists*}
|
|
|
|
test {FUNCTION - Create a function with wrong name format} {
|
|
catch {
|
|
r function create LUA {bad\0foramat} {return 'hello1'}
|
|
} e
|
|
set _ $e
|
|
} {*Function names can only contain letters and numbers*}
|
|
|
|
test {FUNCTION - Create function with unexisting engine} {
|
|
catch {
|
|
r function create bad_engine test {return 'hello1'}
|
|
} e
|
|
set _ $e
|
|
} {*Engine not found*}
|
|
|
|
test {FUNCTION - Test uncompiled script} {
|
|
catch {
|
|
r function create LUA test1 {bad script}
|
|
} e
|
|
set _ $e
|
|
} {*Error compiling function*}
|
|
|
|
test {FUNCTION - test replace argument} {
|
|
r function create LUA test REPLACE {return 'hello1'}
|
|
r fcall test 0
|
|
} {hello1}
|
|
|
|
test {FUNCTION - test function case insensitive} {
|
|
r fcall TEST 0
|
|
} {hello1}
|
|
|
|
test {FUNCTION - test replace argument with function creation failure keeps old function} {
|
|
catch {r function create LUA test REPLACE {error}}
|
|
r fcall test 0
|
|
} {hello1}
|
|
|
|
test {FUNCTION - test function delete} {
|
|
r function delete test
|
|
catch {
|
|
r fcall test 0
|
|
} e
|
|
set _ $e
|
|
} {*Function not found*}
|
|
|
|
test {FUNCTION - test description argument} {
|
|
r function create LUA test DESCRIPTION {some description} {return 'hello'}
|
|
r function list
|
|
} {{name test engine LUA description {some description}}}
|
|
|
|
test {FUNCTION - test info specific function} {
|
|
r function info test WITHCODE
|
|
} {name test engine LUA description {some description} code {return 'hello'}}
|
|
|
|
test {FUNCTION - test info without code} {
|
|
r function info test
|
|
} {name test engine LUA description {some description}}
|
|
|
|
test {FUNCTION - test info on function that does not exists} {
|
|
catch {
|
|
r function info bad_function_name
|
|
} e
|
|
set _ $e
|
|
} {*Function does not exists*}
|
|
|
|
test {FUNCTION - test info with bad number of arguments} {
|
|
catch {
|
|
r function info test WITHCODE bad_arg
|
|
} e
|
|
set _ $e
|
|
} {*wrong number of arguments*}
|
|
|
|
test {FUNCTION - test fcall bad arguments} {
|
|
catch {
|
|
r fcall test bad_arg
|
|
} e
|
|
set _ $e
|
|
} {*Bad number of keys provided*}
|
|
|
|
test {FUNCTION - test fcall bad number of keys arguments} {
|
|
catch {
|
|
r fcall test 10 key1
|
|
} e
|
|
set _ $e
|
|
} {*Number of keys can't be greater than number of args*}
|
|
|
|
test {FUNCTION - test fcall negative number of keys} {
|
|
catch {
|
|
r fcall test -1 key1
|
|
} e
|
|
set _ $e
|
|
} {*Number of keys can't be negative*}
|
|
|
|
test {FUNCTION - test function delete on not exiting function} {
|
|
catch {
|
|
r function delete test1
|
|
} e
|
|
set _ $e
|
|
} {*Function not found*}
|
|
|
|
test {FUNCTION - test function kill when function is not running} {
|
|
catch {
|
|
r function kill
|
|
} e
|
|
set _ $e
|
|
} {*No scripts in execution*}
|
|
|
|
test {FUNCTION - test wrong subcommand} {
|
|
catch {
|
|
r function bad_subcommand
|
|
} e
|
|
set _ $e
|
|
} {*Unknown subcommand*}
|
|
|
|
test {FUNCTION - test loading from rdb} {
|
|
r debug reload
|
|
r fcall test 0
|
|
} {hello} {needs:debug}
|
|
|
|
test {FUNCTION - test debug reload different options} {
|
|
catch {r debug reload noflush} e
|
|
assert_match "*Error trying to load the RDB*" $e
|
|
r debug reload noflush merge
|
|
r function list
|
|
} {{name test engine LUA description {some description}}} {needs:debug}
|
|
|
|
test {FUNCTION - test debug reload with nosave and noflush} {
|
|
r function delete test
|
|
r set x 1
|
|
r function create LUA test1 DESCRIPTION {some description} {return 'hello'}
|
|
r debug reload
|
|
r function create LUA test2 DESCRIPTION {some description} {return 'hello'}
|
|
r debug reload nosave noflush merge
|
|
assert_equal [r fcall test1 0] {hello}
|
|
assert_equal [r fcall test2 0] {hello}
|
|
} {} {needs:debug}
|
|
|
|
test {FUNCTION - test flushall and flushdb do not clean functions} {
|
|
r function flush
|
|
r function create lua test REPLACE {return redis.call('set', 'x', '1')}
|
|
r flushall
|
|
r flushdb
|
|
r function list
|
|
} {{name test engine LUA description {}}}
|
|
|
|
test {FUNCTION - test function dump and restore} {
|
|
r function flush
|
|
r function create lua test description {some description} {return 'hello'}
|
|
set e [r function dump]
|
|
r function delete test
|
|
assert_match {} [r function list]
|
|
r function restore $e
|
|
r function list
|
|
} {{name test engine LUA description {some description}}}
|
|
|
|
test {FUNCTION - test function dump and restore with flush argument} {
|
|
set e [r function dump]
|
|
r function flush
|
|
assert_match {} [r function list]
|
|
r function restore $e FLUSH
|
|
r function list
|
|
} {{name test engine LUA description {some description}}}
|
|
|
|
test {FUNCTION - test function dump and restore with append argument} {
|
|
set e [r function dump]
|
|
r function flush
|
|
assert_match {} [r function list]
|
|
r function create lua test {return 'hello1'}
|
|
catch {r function restore $e APPEND} err
|
|
assert_match {*already exists*} $err
|
|
r function flush
|
|
r function create lua test1 {return 'hello1'}
|
|
r function restore $e APPEND
|
|
assert_match {hello} [r fcall test 0]
|
|
assert_match {hello1} [r fcall test1 0]
|
|
}
|
|
|
|
test {FUNCTION - test function dump and restore with replace argument} {
|
|
r function flush
|
|
r function create LUA test DESCRIPTION {some description} {return 'hello'}
|
|
set e [r function dump]
|
|
r function flush
|
|
assert_match {} [r function list]
|
|
r function create lua test {return 'hello1'}
|
|
assert_match {hello1} [r fcall test 0]
|
|
r function restore $e REPLACE
|
|
assert_match {hello} [r fcall test 0]
|
|
}
|
|
|
|
test {FUNCTION - test function restore with bad payload do not drop existing functions} {
|
|
r function flush
|
|
r function create LUA test DESCRIPTION {some description} {return 'hello'}
|
|
catch {r function restore bad_payload} e
|
|
assert_match {*payload version or checksum are wrong*} $e
|
|
r function list
|
|
} {{name test engine LUA description {some description}}}
|
|
|
|
test {FUNCTION - test function restore with wrong number of arguments} {
|
|
catch {r function restore arg1 args2 arg3} e
|
|
set _ $e
|
|
} {*wrong number of arguments*}
|
|
|
|
test {FUNCTION - test fcall_ro with write command} {
|
|
r function create lua test REPLACE {return redis.call('set', 'x', '1')}
|
|
catch { r fcall_ro test 0 } e
|
|
set _ $e
|
|
} {*Write commands are not allowed from read-only scripts*}
|
|
|
|
test {FUNCTION - test fcall_ro with read only commands} {
|
|
r function create lua test REPLACE {return redis.call('get', 'x')}
|
|
r set x 1
|
|
r fcall_ro test 0
|
|
} {1}
|
|
|
|
test {FUNCTION - test keys and argv} {
|
|
r function create lua test REPLACE {return redis.call('set', KEYS[1], ARGV[1])}
|
|
r fcall test 1 x foo
|
|
r get x
|
|
} {foo}
|
|
|
|
test {FUNCTION - test command get keys on fcall} {
|
|
r COMMAND GETKEYS fcall test 1 x foo
|
|
} {x}
|
|
|
|
test {FUNCTION - test command get keys on fcall_ro} {
|
|
r COMMAND GETKEYS fcall_ro test 1 x foo
|
|
} {x}
|
|
|
|
test {FUNCTION - test function kill} {
|
|
set rd [redis_deferring_client]
|
|
r config set script-time-limit 10
|
|
r function create lua test REPLACE {local a = 1 while true do a = a + 1 end}
|
|
$rd fcall test 0
|
|
after 200
|
|
catch {r ping} e
|
|
assert_match {BUSY*} $e
|
|
assert_match {running_script {name test command {fcall test 0} duration_ms *} engines LUA} [r FUNCTION STATS]
|
|
r function kill
|
|
after 200 ; # Give some time to Lua to call the hook again...
|
|
assert_equal [r ping] "PONG"
|
|
}
|
|
|
|
test {FUNCTION - test script kill not working on function} {
|
|
set rd [redis_deferring_client]
|
|
r config set script-time-limit 10
|
|
r function create lua test REPLACE {local a = 1 while true do a = a + 1 end}
|
|
$rd fcall test 0
|
|
after 200
|
|
catch {r ping} e
|
|
assert_match {BUSY*} $e
|
|
catch {r script kill} e
|
|
assert_match {BUSY*} $e
|
|
r function kill
|
|
after 200 ; # Give some time to Lua to call the hook again...
|
|
assert_equal [r ping] "PONG"
|
|
}
|
|
|
|
test {FUNCTION - test function kill not working on eval} {
|
|
set rd [redis_deferring_client]
|
|
r config set script-time-limit 10
|
|
$rd eval {local a = 1 while true do a = a + 1 end} 0
|
|
after 200
|
|
catch {r ping} e
|
|
assert_match {BUSY*} $e
|
|
catch {r function kill} e
|
|
assert_match {BUSY*} $e
|
|
r script kill
|
|
after 200 ; # Give some time to Lua to call the hook again...
|
|
assert_equal [r ping] "PONG"
|
|
}
|
|
|
|
test {FUNCTION - test function flush} {
|
|
r function create lua test REPLACE {local a = 1 while true do a = a + 1 end}
|
|
assert_match {{name test engine LUA description {}}} [r function list]
|
|
r function flush
|
|
assert_match {} [r function list]
|
|
|
|
r function create lua test REPLACE {local a = 1 while true do a = a + 1 end}
|
|
assert_match {{name test engine LUA description {}}} [r function list]
|
|
r function flush async
|
|
assert_match {} [r function list]
|
|
|
|
r function create lua test REPLACE {local a = 1 while true do a = a + 1 end}
|
|
assert_match {{name test engine LUA description {}}} [r function list]
|
|
r function flush sync
|
|
assert_match {} [r function list]
|
|
}
|
|
|
|
test {FUNCTION - test function wrong argument} {
|
|
catch {r function flush bad_arg} e
|
|
assert_match {*only supports SYNC|ASYNC*} $e
|
|
|
|
catch {r function flush sync extra_arg} e
|
|
assert_match {*wrong number of arguments*} $e
|
|
}
|
|
}
|
|
|
|
start_server {tags {"scripting repl external:skip"}} {
|
|
start_server {} {
|
|
test "Connect a replica to the master instance" {
|
|
r -1 slaveof [srv 0 host] [srv 0 port]
|
|
wait_for_condition 50 100 {
|
|
[s -1 role] eq {slave} &&
|
|
[string match {*master_link_status:up*} [r -1 info replication]]
|
|
} else {
|
|
fail "Can't turn the instance into a replica"
|
|
}
|
|
}
|
|
|
|
test {FUNCTION - creation is replicated to replica} {
|
|
r function create LUA test DESCRIPTION {some description} {return 'hello'}
|
|
wait_for_condition 50 100 {
|
|
[r -1 function list] eq {{name test engine LUA description {some description}}}
|
|
} else {
|
|
fail "Failed waiting for function to replicate to replica"
|
|
}
|
|
}
|
|
|
|
test {FUNCTION - call on replica} {
|
|
r -1 fcall test 0
|
|
} {hello}
|
|
|
|
test {FUNCTION - restore is replicated to replica} {
|
|
set e [r function dump]
|
|
|
|
r function delete test
|
|
wait_for_condition 50 100 {
|
|
[r -1 function list] eq {}
|
|
} else {
|
|
fail "Failed waiting for function to replicate to replica"
|
|
}
|
|
|
|
r function restore $e
|
|
|
|
wait_for_condition 50 100 {
|
|
[r -1 function list] eq {{name test engine LUA description {some description}}}
|
|
} else {
|
|
fail "Failed waiting for function to replicate to replica"
|
|
}
|
|
}
|
|
|
|
test {FUNCTION - delete is replicated to replica} {
|
|
r function delete test
|
|
wait_for_condition 50 100 {
|
|
[r -1 function list] eq {}
|
|
} else {
|
|
fail "Failed waiting for function to replicate to replica"
|
|
}
|
|
}
|
|
|
|
test {FUNCTION - flush is replicated to replica} {
|
|
r function create LUA test DESCRIPTION {some description} {return 'hello'}
|
|
wait_for_condition 50 100 {
|
|
[r -1 function list] eq {{name test engine LUA description {some description}}}
|
|
} else {
|
|
fail "Failed waiting for function to replicate to replica"
|
|
}
|
|
r function flush
|
|
wait_for_condition 50 100 {
|
|
[r -1 function list] eq {}
|
|
} else {
|
|
fail "Failed waiting for function to replicate to replica"
|
|
}
|
|
}
|
|
|
|
test "Disconnecting the replica from master instance" {
|
|
r -1 slaveof no one
|
|
# creating a function after disconnect to make sure function
|
|
# is replicated on rdb phase
|
|
r function create LUA test DESCRIPTION {some description} {return 'hello'}
|
|
|
|
# reconnect the replica
|
|
r -1 slaveof [srv 0 host] [srv 0 port]
|
|
wait_for_condition 50 100 {
|
|
[s -1 role] eq {slave} &&
|
|
[string match {*master_link_status:up*} [r -1 info replication]]
|
|
} else {
|
|
fail "Can't turn the instance into a replica"
|
|
}
|
|
}
|
|
|
|
test "FUNCTION - test replication to replica on rdb phase" {
|
|
r -1 fcall test 0
|
|
} {hello}
|
|
|
|
test "FUNCTION - test replication to replica on rdb phase info command" {
|
|
r -1 function info test WITHCODE
|
|
} {name test engine LUA description {some description} code {return 'hello'}}
|
|
|
|
test "FUNCTION - create on read only replica" {
|
|
catch {
|
|
r -1 function create LUA test DESCRIPTION {some description} {return 'hello'}
|
|
} e
|
|
set _ $e
|
|
} {*can't write against a read only replica*}
|
|
|
|
test "FUNCTION - delete on read only replica" {
|
|
catch {
|
|
r -1 function delete test
|
|
} e
|
|
set _ $e
|
|
} {*can't write against a read only replica*}
|
|
|
|
test "FUNCTION - function effect is replicated to replica" {
|
|
r function create LUA test REPLACE {return redis.call('set', 'x', '1')}
|
|
r fcall test 0
|
|
assert {[r get x] eq {1}}
|
|
wait_for_condition 50 100 {
|
|
[r -1 get x] eq {1}
|
|
} else {
|
|
fail "Failed waiting function effect to be replicated to replica"
|
|
}
|
|
}
|
|
|
|
test "FUNCTION - modify key space of read only replica" {
|
|
catch {
|
|
r -1 fcall test 0
|
|
} e
|
|
set _ $e
|
|
} {*can't write against a read only replica*}
|
|
}
|
|
}
|
|
|
|
test {FUNCTION can processes create, delete and flush commands in AOF when doing "debug loadaof" in read-only slaves} {
|
|
start_server {} {
|
|
r config set appendonly yes
|
|
waitForBgrewriteaof r
|
|
r FUNCTION CREATE lua test "return 'hello'"
|
|
r config set slave-read-only yes
|
|
r slaveof 127.0.0.1 0
|
|
r debug loadaof
|
|
r slaveof no one
|
|
assert_equal [r function list] {{name test engine LUA description {}}}
|
|
|
|
r FUNCTION DELETE test
|
|
|
|
r slaveof 127.0.0.1 0
|
|
r debug loadaof
|
|
r slaveof no one
|
|
assert_equal [r function list] {}
|
|
|
|
r FUNCTION CREATE lua test "return 'hello'"
|
|
r FUNCTION FLUSH
|
|
|
|
r slaveof 127.0.0.1 0
|
|
r debug loadaof
|
|
r slaveof no one
|
|
assert_equal [r function list] {}
|
|
}
|
|
} {} {needs:debug external:skip}
|