diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:40:54 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:40:54 +0000 |
commit | 317c0644ccf108aa23ef3fd8358bd66c2840bfc0 (patch) | |
tree | c417b3d25c86b775989cb5ac042f37611b626c8a /tests/unit/moduleapi/propagate.tcl | |
parent | Initial commit. (diff) | |
download | redis-317c0644ccf108aa23ef3fd8358bd66c2840bfc0.tar.xz redis-317c0644ccf108aa23ef3fd8358bd66c2840bfc0.zip |
Adding upstream version 5:7.2.4.upstream/5%7.2.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests/unit/moduleapi/propagate.tcl')
-rw-r--r-- | tests/unit/moduleapi/propagate.tcl | 763 |
1 files changed, 763 insertions, 0 deletions
diff --git a/tests/unit/moduleapi/propagate.tcl b/tests/unit/moduleapi/propagate.tcl new file mode 100644 index 0000000..90a369d --- /dev/null +++ b/tests/unit/moduleapi/propagate.tcl @@ -0,0 +1,763 @@ +set testmodule [file normalize tests/modules/propagate.so] +set miscmodule [file normalize tests/modules/misc.so] +set keyspace_events [file normalize tests/modules/keyspace_events.so] + +tags "modules" { + test {Modules can propagate in async and threaded contexts} { + start_server [list overrides [list loadmodule "$testmodule"]] { + set replica [srv 0 client] + set replica_host [srv 0 host] + set replica_port [srv 0 port] + $replica module load $keyspace_events + start_server [list overrides [list loadmodule "$testmodule"]] { + set master [srv 0 client] + set master_host [srv 0 host] + set master_port [srv 0 port] + $master module load $keyspace_events + + # Start the replication process... + $replica replicaof $master_host $master_port + wait_for_sync $replica + after 1000 + + test {module propagates from timer} { + set repl [attach_to_replication_stream] + + $master propagate-test.timer + + wait_for_condition 500 10 { + [$replica get timer] eq "3" + } else { + fail "The two counters don't match the expected value." + } + + assert_replication_stream $repl { + {select *} + {incr timer} + {incr timer} + {incr timer} + } + close_replication_stream $repl + } + + test {module propagation with notifications} { + set repl [attach_to_replication_stream] + + $master set x y + + assert_replication_stream $repl { + {multi} + {select *} + {incr notifications} + {set x y} + {exec} + } + close_replication_stream $repl + } + + test {module propagation with notifications with multi} { + set repl [attach_to_replication_stream] + + $master multi + $master set x1 y1 + $master set x2 y2 + $master exec + + assert_replication_stream $repl { + {multi} + {select *} + {incr notifications} + {set x1 y1} + {incr notifications} + {set x2 y2} + {exec} + } + close_replication_stream $repl + } + + test {module propagation with notifications with active-expire} { + $master debug set-active-expire 1 + set repl [attach_to_replication_stream] + + $master set asdf1 1 PX 300 + $master set asdf2 2 PX 300 + $master set asdf3 3 PX 300 + + wait_for_condition 500 10 { + [$replica keys asdf*] eq {} + } else { + fail "Not all keys have expired" + } + + # Note whenever there's double notification: SET with PX issues two separate + # notifications: one for "set" and one for "expire" + assert_replication_stream $repl { + {multi} + {select *} + {incr notifications} + {incr notifications} + {set asdf1 1 PXAT *} + {exec} + {multi} + {incr notifications} + {incr notifications} + {set asdf2 2 PXAT *} + {exec} + {multi} + {incr notifications} + {incr notifications} + {set asdf3 3 PXAT *} + {exec} + {multi} + {incr notifications} + {incr notifications} + {incr testkeyspace:expired} + {del asdf*} + {exec} + {multi} + {incr notifications} + {incr notifications} + {incr testkeyspace:expired} + {del asdf*} + {exec} + {multi} + {incr notifications} + {incr notifications} + {incr testkeyspace:expired} + {del asdf*} + {exec} + } + close_replication_stream $repl + + $master debug set-active-expire 0 + } + + test {module propagation with notifications with eviction case 1} { + $master flushall + $master set asdf1 1 + $master set asdf2 2 + $master set asdf3 3 + + $master config set maxmemory-policy allkeys-random + $master config set maxmemory 1 + + # Please note the following loop: + # We evict a key and send a notification, which does INCR on the "notifications" key, so + # that every time we evict any key, "notifications" key exist (it happens inside the + # performEvictions loop). So even evicting "notifications" causes INCR on "notifications". + # If maxmemory_eviction_tenacity would have been set to 100 this would be an endless loop, but + # since the default is 10, at some point the performEvictions loop would end. + # Bottom line: "notifications" always exists and we can't really determine the order of evictions + # This test is here only for sanity + + # The replica will get the notification with multi exec and we have a generic notification handler + # that performs `RedisModule_Call(ctx, "INCR", "c", "multi");` if the notification is inside multi exec. + # so we will have 2 keys, "notifications" and "multi". + wait_for_condition 500 10 { + [$replica dbsize] eq 2 + } else { + fail "Not all keys have been evicted" + } + + $master config set maxmemory 0 + $master config set maxmemory-policy noeviction + } + + test {module propagation with notifications with eviction case 2} { + $master flushall + set repl [attach_to_replication_stream] + + $master set asdf1 1 EX 300 + $master set asdf2 2 EX 300 + $master set asdf3 3 EX 300 + + # Please note we use volatile eviction to prevent the loop described in the test above. + # "notifications" is not volatile so it always remains + $master config resetstat + $master config set maxmemory-policy volatile-ttl + $master config set maxmemory 1 + + wait_for_condition 500 10 { + [s evicted_keys] eq 3 + } else { + fail "Not all keys have been evicted" + } + + $master config set maxmemory 0 + $master config set maxmemory-policy noeviction + + $master set asdf4 4 + + # Note whenever there's double notification: SET with EX issues two separate + # notifications: one for "set" and one for "expire" + # Note that although CONFIG SET maxmemory is called in this flow (see issue #10014), + # eviction will happen and will not induce propagation of the CONFIG command (see #10019). + assert_replication_stream $repl { + {multi} + {select *} + {incr notifications} + {incr notifications} + {set asdf1 1 PXAT *} + {exec} + {multi} + {incr notifications} + {incr notifications} + {set asdf2 2 PXAT *} + {exec} + {multi} + {incr notifications} + {incr notifications} + {set asdf3 3 PXAT *} + {exec} + {multi} + {incr notifications} + {del asdf*} + {exec} + {multi} + {incr notifications} + {del asdf*} + {exec} + {multi} + {incr notifications} + {del asdf*} + {exec} + {multi} + {incr notifications} + {set asdf4 4} + {exec} + } + close_replication_stream $repl + } + + test {module propagation with timer and CONFIG SET maxmemory} { + set repl [attach_to_replication_stream] + + $master config resetstat + $master config set maxmemory-policy volatile-random + + $master propagate-test.timer-maxmemory + + # Wait until the volatile keys are evicted + wait_for_condition 500 10 { + [s evicted_keys] eq 2 + } else { + fail "Not all keys have been evicted" + } + + assert_replication_stream $repl { + {multi} + {select *} + {incr notifications} + {incr notifications} + {set timer-maxmemory-volatile-start 1 PXAT *} + {incr timer-maxmemory-middle} + {incr notifications} + {incr notifications} + {set timer-maxmemory-volatile-end 1 PXAT *} + {exec} + {multi} + {incr notifications} + {del timer-maxmemory-volatile-*} + {exec} + {multi} + {incr notifications} + {del timer-maxmemory-volatile-*} + {exec} + } + close_replication_stream $repl + + $master config set maxmemory 0 + $master config set maxmemory-policy noeviction + } + + test {module propagation with timer and EVAL} { + set repl [attach_to_replication_stream] + + $master propagate-test.timer-eval + + assert_replication_stream $repl { + {multi} + {select *} + {incr notifications} + {incrby timer-eval-start 1} + {incr notifications} + {set foo bar} + {incr timer-eval-middle} + {incr notifications} + {incrby timer-eval-end 1} + {exec} + } + close_replication_stream $repl + } + + test {module propagates nested ctx case1} { + set repl [attach_to_replication_stream] + + $master propagate-test.timer-nested + + wait_for_condition 500 10 { + [$replica get timer-nested-end] eq "1" + } else { + fail "The two counters don't match the expected value." + } + + assert_replication_stream $repl { + {multi} + {select *} + {incrby timer-nested-start 1} + {incrby timer-nested-end 1} + {exec} + } + close_replication_stream $repl + + # Note propagate-test.timer-nested just propagates INCRBY, causing an + # inconsistency, so we flush + $master flushall + } + + test {module propagates nested ctx case2} { + set repl [attach_to_replication_stream] + + $master propagate-test.timer-nested-repl + + wait_for_condition 500 10 { + [$replica get timer-nested-end] eq "1" + } else { + fail "The two counters don't match the expected value." + } + + assert_replication_stream $repl { + {multi} + {select *} + {incrby timer-nested-start 1} + {incr notifications} + {incr using-call} + {incr counter-1} + {incr counter-2} + {incr counter-3} + {incr counter-4} + {incr notifications} + {incr after-call} + {incr notifications} + {incr before-call-2} + {incr notifications} + {incr asdf} + {incr notifications} + {del asdf} + {incr notifications} + {incr after-call-2} + {incr notifications} + {incr timer-nested-middle} + {incrby timer-nested-end 1} + {exec} + } + close_replication_stream $repl + + # Note propagate-test.timer-nested-repl just propagates INCRBY, causing an + # inconsistency, so we flush + $master flushall + } + + test {module propagates from thread} { + set repl [attach_to_replication_stream] + + $master propagate-test.thread + + wait_for_condition 500 10 { + [$replica get a-from-thread] eq "3" + } else { + fail "The two counters don't match the expected value." + } + + assert_replication_stream $repl { + {multi} + {select *} + {incr a-from-thread} + {incr notifications} + {incr thread-call} + {incr b-from-thread} + {exec} + {multi} + {incr a-from-thread} + {incr notifications} + {incr thread-call} + {incr b-from-thread} + {exec} + {multi} + {incr a-from-thread} + {incr notifications} + {incr thread-call} + {incr b-from-thread} + {exec} + } + close_replication_stream $repl + } + + test {module propagates from thread with detached ctx} { + set repl [attach_to_replication_stream] + + $master propagate-test.detached-thread + + wait_for_condition 500 10 { + [$replica get thread-detached-after] eq "1" + } else { + fail "The key doesn't match the expected value." + } + + assert_replication_stream $repl { + {multi} + {select *} + {incr thread-detached-before} + {incr notifications} + {incr thread-detached-1} + {incr notifications} + {incr thread-detached-2} + {incr thread-detached-after} + {exec} + } + close_replication_stream $repl + } + + test {module propagates from command} { + set repl [attach_to_replication_stream] + + $master propagate-test.simple + $master propagate-test.mixed + + assert_replication_stream $repl { + {multi} + {select *} + {incr counter-1} + {incr counter-2} + {exec} + {multi} + {incr notifications} + {incr using-call} + {incr counter-1} + {incr counter-2} + {incr notifications} + {incr after-call} + {exec} + } + close_replication_stream $repl + } + + test {module propagates from EVAL} { + set repl [attach_to_replication_stream] + + assert_equal [ $master eval { \ + redis.call("propagate-test.simple"); \ + redis.call("set", "x", "y"); \ + redis.call("propagate-test.mixed"); return "OK" } 0 ] {OK} + + assert_replication_stream $repl { + {multi} + {select *} + {incr counter-1} + {incr counter-2} + {incr notifications} + {set x y} + {incr notifications} + {incr using-call} + {incr counter-1} + {incr counter-2} + {incr notifications} + {incr after-call} + {exec} + } + close_replication_stream $repl + } + + test {module propagates from command after good EVAL} { + set repl [attach_to_replication_stream] + + assert_equal [ $master eval { return "hello" } 0 ] {hello} + $master propagate-test.simple + $master propagate-test.mixed + + assert_replication_stream $repl { + {multi} + {select *} + {incr counter-1} + {incr counter-2} + {exec} + {multi} + {incr notifications} + {incr using-call} + {incr counter-1} + {incr counter-2} + {incr notifications} + {incr after-call} + {exec} + } + close_replication_stream $repl + } + + test {module propagates from command after bad EVAL} { + set repl [attach_to_replication_stream] + + catch { $master eval { return "hello" } -12 } e + assert_equal $e {ERR Number of keys can't be negative} + $master propagate-test.simple + $master propagate-test.mixed + + assert_replication_stream $repl { + {multi} + {select *} + {incr counter-1} + {incr counter-2} + {exec} + {multi} + {incr notifications} + {incr using-call} + {incr counter-1} + {incr counter-2} + {incr notifications} + {incr after-call} + {exec} + } + close_replication_stream $repl + } + + test {module propagates from multi-exec} { + set repl [attach_to_replication_stream] + + $master multi + $master propagate-test.simple + $master propagate-test.mixed + $master propagate-test.timer-nested-repl + $master exec + + wait_for_condition 500 10 { + [$replica get timer-nested-end] eq "1" + } else { + fail "The two counters don't match the expected value." + } + + assert_replication_stream $repl { + {multi} + {select *} + {incr counter-1} + {incr counter-2} + {incr notifications} + {incr using-call} + {incr counter-1} + {incr counter-2} + {incr notifications} + {incr after-call} + {exec} + {multi} + {incrby timer-nested-start 1} + {incr notifications} + {incr using-call} + {incr counter-1} + {incr counter-2} + {incr counter-3} + {incr counter-4} + {incr notifications} + {incr after-call} + {incr notifications} + {incr before-call-2} + {incr notifications} + {incr asdf} + {incr notifications} + {del asdf} + {incr notifications} + {incr after-call-2} + {incr notifications} + {incr timer-nested-middle} + {incrby timer-nested-end 1} + {exec} + } + close_replication_stream $repl + + # Note propagate-test.timer-nested just propagates INCRBY, causing an + # inconsistency, so we flush + $master flushall + } + + test {module RM_Call of expired key propagation} { + $master debug set-active-expire 0 + + $master set k1 900 px 100 + after 110 + + set repl [attach_to_replication_stream] + $master propagate-test.incr k1 + + assert_replication_stream $repl { + {multi} + {select *} + {del k1} + {propagate-test.incr k1} + {exec} + } + close_replication_stream $repl + + assert_equal [$master get k1] 1 + assert_equal [$master ttl k1] -1 + assert_equal [$replica get k1] 1 + assert_equal [$replica ttl k1] -1 + } + + test {module notification on set} { + set repl [attach_to_replication_stream] + + $master SADD s foo + + wait_for_condition 500 10 { + [$replica SCARD s] eq "1" + } else { + fail "Failed to wait for set to be replicated" + } + + $master SPOP s 1 + + wait_for_condition 500 10 { + [$replica SCARD s] eq "0" + } else { + fail "Failed to wait for set to be replicated" + } + + # Currently the `del` command comes after the notification. + # When we fix spop to fire notification at the end (like all other commands), + # the `del` will come first. + assert_replication_stream $repl { + {multi} + {select *} + {incr notifications} + {sadd s foo} + {exec} + {multi} + {incr notifications} + {incr notifications} + {del s} + {exec} + } + close_replication_stream $repl + } + + test {module key miss notification do not cause read command to be replicated} { + set repl [attach_to_replication_stream] + + $master flushall + + $master get unexisting_key + + wait_for_condition 500 10 { + [$replica get missed] eq "1" + } else { + fail "Failed to wait for set to be replicated" + } + + # Test is checking a wrong!!! behavior that causes a read command to be replicated to replica/aof. + # We keep the test to verify that such a wrong behavior does not cause any crashes. + assert_replication_stream $repl { + {select *} + {flushall} + {multi} + {incr notifications} + {incr missed} + {get unexisting_key} + {exec} + } + + close_replication_stream $repl + } + + test "Unload the module - propagate-test/testkeyspace" { + assert_equal {OK} [r module unload propagate-test] + assert_equal {OK} [r module unload testkeyspace] + } + + assert_equal [s -1 unexpected_error_replies] 0 + } + } + } +} + + +tags "modules aof" { + foreach aofload_type {debug_cmd startup} { + test "Modules RM_Replicate replicates MULTI/EXEC correctly: AOF-load type $aofload_type" { + start_server [list overrides [list loadmodule "$testmodule"]] { + # Enable the AOF + r config set appendonly yes + r config set auto-aof-rewrite-percentage 0 ; # Disable auto-rewrite. + waitForBgrewriteaof r + + r propagate-test.simple + r propagate-test.mixed + r multi + r propagate-test.simple + r propagate-test.mixed + r exec + + assert_equal [r get counter-1] {} + assert_equal [r get counter-2] {} + assert_equal [r get using-call] 2 + assert_equal [r get after-call] 2 + assert_equal [r get notifications] 4 + + # Load the AOF + if {$aofload_type == "debug_cmd"} { + r debug loadaof + } else { + r config rewrite + restart_server 0 true false + wait_done_loading r + } + + # This module behaves bad on purpose, it only calls + # RM_Replicate for counter-1 and counter-2 so values + # after AOF-load are different + assert_equal [r get counter-1] 4 + assert_equal [r get counter-2] 4 + assert_equal [r get using-call] 2 + assert_equal [r get after-call] 2 + # 4+4+2+2 commands from AOF (just above) + 4 "INCR notifications" from AOF + 4 notifications for these INCRs + assert_equal [r get notifications] 20 + + assert_equal {OK} [r module unload propagate-test] + assert_equal [s 0 unexpected_error_replies] 0 + } + } + test "Modules RM_Call does not update stats during aof load: AOF-load type $aofload_type" { + start_server [list overrides [list loadmodule "$miscmodule"]] { + # Enable the AOF + r config set appendonly yes + r config set auto-aof-rewrite-percentage 0 ; # Disable auto-rewrite. + waitForBgrewriteaof r + + r config resetstat + r set foo bar + r EVAL {return redis.call('SET', KEYS[1], ARGV[1])} 1 foo bar2 + r test.rm_call_replicate set foo bar3 + r EVAL {return redis.call('test.rm_call_replicate',ARGV[1],KEYS[1],ARGV[2])} 1 foo set bar4 + + r multi + r set foo bar5 + r EVAL {return redis.call('SET', KEYS[1], ARGV[1])} 1 foo bar6 + r test.rm_call_replicate set foo bar7 + r EVAL {return redis.call('test.rm_call_replicate',ARGV[1],KEYS[1],ARGV[2])} 1 foo set bar8 + r exec + + assert_match {*calls=8,*,rejected_calls=0,failed_calls=0} [cmdrstat set r] + + + # Load the AOF + if {$aofload_type == "debug_cmd"} { + r config resetstat + r debug loadaof + } else { + r config rewrite + restart_server 0 true false + wait_done_loading r + } + + assert_no_match {*calls=*} [cmdrstat set r] + + } + } + } +} |