summaryrefslogtreecommitdiffstats
path: root/tests/unit/tracking.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit/tracking.tcl')
-rw-r--r--tests/unit/tracking.tcl902
1 files changed, 902 insertions, 0 deletions
diff --git a/tests/unit/tracking.tcl b/tests/unit/tracking.tcl
new file mode 100644
index 0000000..bea8508
--- /dev/null
+++ b/tests/unit/tracking.tcl
@@ -0,0 +1,902 @@
+# logreqres:skip because it seems many of these tests rely heavily on RESP2
+start_server {tags {"tracking network logreqres:skip"}} {
+ # Create a deferred client we'll use to redirect invalidation
+ # messages to.
+ set rd_redirection [redis_deferring_client]
+ $rd_redirection client id
+ set redir_id [$rd_redirection read]
+ $rd_redirection subscribe __redis__:invalidate
+ $rd_redirection read ; # Consume the SUBSCRIBE reply.
+
+ # Create another client that's not used as a redirection client
+ # We should always keep this client's buffer clean
+ set rd [redis_deferring_client]
+
+ # Client to be used for SET and GET commands
+ # We don't read this client's buffer
+ set rd_sg [redis_client]
+
+ proc clean_all {} {
+ uplevel {
+ # We should make r TRACKING off first. If r is in RESP3,
+ # r FLUSH ALL will send us tracking-redir-broken or other
+ # info which will not be consumed.
+ r CLIENT TRACKING off
+ $rd QUIT
+ $rd_redirection QUIT
+ set rd [redis_deferring_client]
+ set rd_redirection [redis_deferring_client]
+ $rd_redirection client id
+ set redir_id [$rd_redirection read]
+ $rd_redirection subscribe __redis__:invalidate
+ $rd_redirection read ; # Consume the SUBSCRIBE reply.
+ r FLUSHALL
+ r HELLO 2
+ r config set tracking-table-max-keys 1000000
+ }
+ }
+
+ test {Clients are able to enable tracking and redirect it} {
+ r CLIENT TRACKING on REDIRECT $redir_id
+ } {*OK}
+
+ test {The other connection is able to get invalidations} {
+ r SET a{t} 1
+ r SET b{t} 1
+ r GET a{t}
+ r INCR b{t} ; # This key should not be notified, since it wasn't fetched.
+ r INCR a{t}
+ set keys [lindex [$rd_redirection read] 2]
+ assert {[llength $keys] == 1}
+ assert {[lindex $keys 0] eq {a{t}}}
+ }
+
+ test {The client is now able to disable tracking} {
+ # Make sure to add a few more keys in the tracking list
+ # so that we can check for leaks, as a side effect.
+ r MGET a{t} b{t} c{t} d{t} e{t} f{t} g{t}
+ r CLIENT TRACKING off
+ } {*OK}
+
+ test {Clients can enable the BCAST mode with the empty prefix} {
+ r CLIENT TRACKING on BCAST REDIRECT $redir_id
+ } {*OK*}
+
+ test {The connection gets invalidation messages about all the keys} {
+ r MSET a{t} 1 b{t} 2 c{t} 3
+ set keys [lsort [lindex [$rd_redirection read] 2]]
+ assert {$keys eq {a{t} b{t} c{t}}}
+ }
+
+ test {Clients can enable the BCAST mode with prefixes} {
+ r CLIENT TRACKING off
+ r CLIENT TRACKING on BCAST REDIRECT $redir_id PREFIX a: PREFIX b:
+ r MULTI
+ r INCR a:1{t}
+ r INCR a:2{t}
+ r INCR b:1{t}
+ r INCR b:2{t}
+ # we should not get this key
+ r INCR c:1{t}
+ r EXEC
+ # Because of the internals, we know we are going to receive
+ # two separated notifications for the two different prefixes.
+ set keys1 [lsort [lindex [$rd_redirection read] 2]]
+ set keys2 [lsort [lindex [$rd_redirection read] 2]]
+ set keys [lsort [list {*}$keys1 {*}$keys2]]
+ assert {$keys eq {a:1{t} a:2{t} b:1{t} b:2{t}}}
+ }
+
+ test {Adding prefixes to BCAST mode works} {
+ r CLIENT TRACKING on BCAST REDIRECT $redir_id PREFIX c:
+ r INCR c:1234
+ set keys [lsort [lindex [$rd_redirection read] 2]]
+ assert {$keys eq {c:1234}}
+ }
+
+ test {Tracking NOLOOP mode in standard mode works} {
+ r CLIENT TRACKING off
+ r CLIENT TRACKING on REDIRECT $redir_id NOLOOP
+ r MGET otherkey1{t} loopkey{t} otherkey2{t}
+ $rd_sg SET otherkey1{t} 1; # We should get this
+ r SET loopkey{t} 1 ; # We should not get this
+ $rd_sg SET otherkey2{t} 1; # We should get this
+ # Because of the internals, we know we are going to receive
+ # two separated notifications for the two different keys.
+ set keys1 [lsort [lindex [$rd_redirection read] 2]]
+ set keys2 [lsort [lindex [$rd_redirection read] 2]]
+ set keys [lsort [list {*}$keys1 {*}$keys2]]
+ assert {$keys eq {otherkey1{t} otherkey2{t}}}
+ }
+
+ test {Tracking NOLOOP mode in BCAST mode works} {
+ r CLIENT TRACKING off
+ r CLIENT TRACKING on BCAST REDIRECT $redir_id NOLOOP
+ $rd_sg SET otherkey1 1; # We should get this
+ r SET loopkey 1 ; # We should not get this
+ $rd_sg SET otherkey2 1; # We should get this
+ # Because $rd_sg send command synchronously, we know we are
+ # going to receive two separated notifications.
+ set keys1 [lsort [lindex [$rd_redirection read] 2]]
+ set keys2 [lsort [lindex [$rd_redirection read] 2]]
+ set keys [lsort [list {*}$keys1 {*}$keys2]]
+ assert {$keys eq {otherkey1 otherkey2}}
+ }
+
+ test {Tracking gets notification of expired keys} {
+ r CLIENT TRACKING off
+ r CLIENT TRACKING on BCAST REDIRECT $redir_id NOLOOP
+ r SET mykey myval px 1
+ r SET mykeyotherkey myval ; # We should not get it
+ after 1000
+ set keys [lsort [lindex [$rd_redirection read] 2]]
+ assert {$keys eq {mykey}}
+ }
+
+ test {Tracking gets notification of lazy expired keys} {
+ r CLIENT TRACKING off
+ r CLIENT TRACKING on BCAST REDIRECT $redir_id NOLOOP
+ # Use multi-exec to expose a race where the key gets an two invalidations
+ # in the same event loop, once by the client so filtered by NOLOOP, and
+ # the second one by the lazy expire
+ r MULTI
+ r SET mykey{t} myval px 1
+ r SET mykeyotherkey{t} myval ; # We should not get it
+ r DEBUG SLEEP 0.1
+ r GET mykey{t}
+ r EXEC
+ set keys [lsort [lindex [$rd_redirection read] 2]]
+ assert {$keys eq {mykey{t}}}
+ } {} {needs:debug}
+
+ test {HELLO 3 reply is correct} {
+ set reply [r HELLO 3]
+ assert_equal [dict get $reply proto] 3
+ }
+
+ test {HELLO without protover} {
+ set reply [r HELLO 3]
+ assert_equal [dict get $reply proto] 3
+
+ set reply [r HELLO]
+ assert_equal [dict get $reply proto] 3
+
+ set reply [r HELLO 2]
+ assert_equal [dict get $reply proto] 2
+
+ set reply [r HELLO]
+ assert_equal [dict get $reply proto] 2
+
+ # restore RESP3 for next test
+ r HELLO 3
+ }
+
+ test {RESP3 based basic invalidation} {
+ r CLIENT TRACKING off
+ r CLIENT TRACKING on
+ $rd_sg SET key1 1
+ r GET key1
+ $rd_sg SET key1 2
+ r read
+ } {invalidate key1}
+
+ test {RESP3 tracking redirection} {
+ r CLIENT TRACKING off
+ r CLIENT TRACKING on REDIRECT $redir_id
+ $rd_sg SET key1 1
+ r GET key1
+ $rd_sg SET key1 2
+ set res [lindex [$rd_redirection read] 2]
+ assert {$res eq {key1}}
+ }
+
+ test {Invalidations of previous keys can be redirected after switching to RESP3} {
+ r HELLO 2
+ $rd_sg SET key1 1
+ r GET key1
+ r HELLO 3
+ $rd_sg SET key1 2
+ set res [lindex [$rd_redirection read] 2]
+ assert {$res eq {key1}}
+ }
+
+ test {Invalidations of new keys can be redirected after switching to RESP3} {
+ r HELLO 3
+ $rd_sg SET key1 1
+ r GET key1
+ $rd_sg SET key1 2
+ set res [lindex [$rd_redirection read] 2]
+ assert {$res eq {key1}}
+ }
+
+ test {Invalid keys should not be tracked for scripts in NOLOOP mode} {
+ $rd_sg CLIENT TRACKING off
+ $rd_sg CLIENT TRACKING on NOLOOP
+ $rd_sg HELLO 3
+ $rd_sg SET key1 1
+ assert_equal "1" [$rd_sg GET key1]
+
+ # For write command in script, invalid key should not be tracked with NOLOOP flag
+ $rd_sg eval "return redis.call('set', 'key1', '2')" 1 key1
+ assert_equal "2" [$rd_sg GET key1]
+ $rd_sg CLIENT TRACKING off
+ }
+
+ test {Tracking only occurs for scripts when a command calls a read-only command} {
+ r CLIENT TRACKING off
+ r CLIENT TRACKING on
+ $rd_sg MSET key2{t} 1 key2{t} 1
+
+ # If a script doesn't call any read command, don't track any keys
+ r EVAL "redis.call('set', 'key3{t}', 'bar')" 2 key1{t} key2{t}
+ $rd_sg MSET key2{t} 2 key1{t} 2
+ assert_equal "PONG" [r ping]
+
+ # If a script calls a read command, just the read keys
+ r EVAL "redis.call('get', 'key2{t}')" 2 key1{t} key2{t}
+ $rd_sg MSET key2{t} 2 key3{t} 2
+ assert_equal {invalidate key2{t}} [r read]
+ assert_equal "PONG" [r ping]
+
+ # RO variants work like the normal variants
+
+ # If a RO script doesn't call any read command, don't track any keys
+ r EVAL_RO "redis.call('ping')" 2 key1{t} key2{t}
+ $rd_sg MSET key2{t} 2 key1{t} 2
+ assert_equal "PONG" [r ping]
+
+ # If a RO script calls a read command, just the read keys
+ r EVAL_RO "redis.call('get', 'key2{t}')" 2 key1{t} key2{t}
+ $rd_sg MSET key2{t} 2 key3{t} 2
+ assert_equal {invalidate key2{t}} [r read]
+ assert_equal "PONG" [r ping]
+ }
+
+ test {RESP3 Client gets tracking-redir-broken push message after cached key changed when rediretion client is terminated} {
+ r CLIENT TRACKING on REDIRECT $redir_id
+ $rd_sg SET key1 1
+ r GET key1
+ $rd_redirection QUIT
+ assert_equal OK [$rd_redirection read]
+ $rd_sg SET key1 2
+ set MAX_TRIES 100
+ set res -1
+ for {set i 0} {$i <= $MAX_TRIES && $res < 0} {incr i} {
+ set res [lsearch -exact [r PING] "tracking-redir-broken"]
+ }
+ assert {$res >= 0}
+ # Consume PING reply
+ assert_equal PONG [r read]
+
+ # Reinstantiating after QUIT
+ set rd_redirection [redis_deferring_client]
+ $rd_redirection CLIENT ID
+ set redir_id [$rd_redirection read]
+ $rd_redirection SUBSCRIBE __redis__:invalidate
+ $rd_redirection read ; # Consume the SUBSCRIBE reply
+ }
+
+ test {Different clients can redirect to the same connection} {
+ r CLIENT TRACKING on REDIRECT $redir_id
+ $rd CLIENT TRACKING on REDIRECT $redir_id
+ assert_equal OK [$rd read] ; # Consume the TRACKING reply
+ $rd_sg MSET key1{t} 1 key2{t} 1
+ r GET key1{t}
+ $rd GET key2{t}
+ assert_equal 1 [$rd read] ; # Consume the GET reply
+ $rd_sg INCR key1{t}
+ $rd_sg INCR key2{t}
+ set res1 [lindex [$rd_redirection read] 2]
+ set res2 [lindex [$rd_redirection read] 2]
+ assert {$res1 eq {key1{t}}}
+ assert {$res2 eq {key2{t}}}
+ }
+
+ test {Different clients using different protocols can track the same key} {
+ $rd HELLO 3
+ set reply [$rd read] ; # Consume the HELLO reply
+ assert_equal 3 [dict get $reply proto]
+ $rd CLIENT TRACKING on
+ assert_equal OK [$rd read] ; # Consume the TRACKING reply
+ $rd_sg set key1 1
+ r GET key1
+ $rd GET key1
+ assert_equal 1 [$rd read] ; # Consume the GET reply
+ $rd_sg INCR key1
+ set res1 [lindex [$rd_redirection read] 2]
+ $rd PING ; # Non redirecting client has to talk to the server in order to get invalidation message
+ set res2 [lindex [split [$rd read] " "] 1]
+ assert_equal PONG [$rd read] ; # Consume the PING reply, which comes together with the invalidation message
+ assert {$res1 eq {key1}}
+ assert {$res2 eq {key1}}
+ }
+
+ test {No invalidation message when using OPTIN option} {
+ r CLIENT TRACKING on OPTIN REDIRECT $redir_id
+ $rd_sg SET key1 1
+ r GET key1 ; # This key should not be notified, since OPTIN is on and CLIENT CACHING yes wasn't called
+ $rd_sg SET key1 2
+ # Preparing some message to consume on $rd_redirection so we don't get blocked
+ r CLIENT TRACKING off
+ r CLIENT TRACKING on REDIRECT $redir_id
+ $rd_sg SET key2 1
+ r GET key2 ; # This key should be notified
+ $rd_sg SET key2 2
+ set res [lindex [$rd_redirection read] 2]
+ assert {$res eq {key2}}
+ }
+
+ test {Invalidation message sent when using OPTIN option with CLIENT CACHING yes} {
+ r CLIENT TRACKING on OPTIN REDIRECT $redir_id
+ $rd_sg SET key1 3
+ r CLIENT CACHING yes
+ r GET key1
+ $rd_sg SET key1 4
+ set res [lindex [$rd_redirection read] 2]
+ assert {$res eq {key1}}
+ }
+
+ test {Invalidation message sent when using OPTOUT option} {
+ r CLIENT TRACKING off
+ r CLIENT TRACKING on OPTOUT REDIRECT $redir_id
+ $rd_sg SET key1 1
+ r GET key1
+ $rd_sg SET key1 2
+ set res [lindex [$rd_redirection read] 2]
+ assert {$res eq {key1}}
+ }
+
+ test {No invalidation message when using OPTOUT option with CLIENT CACHING no} {
+ $rd_sg SET key1 1
+ r CLIENT CACHING no
+ r GET key1 ; # This key should not be notified, since OPTOUT is on and CLIENT CACHING no was called
+ $rd_sg SET key1 2
+ # Preparing some message to consume on $rd_redirection so we don't get blocked
+ $rd_sg SET key2 1
+ r GET key2 ; # This key should be notified
+ $rd_sg SET key2 2
+ set res [lindex [$rd_redirection read] 2]
+ assert {$res eq {key2}}
+ }
+
+ test {Able to redirect to a RESP3 client} {
+ $rd_redirection UNSUBSCRIBE __redis__:invalidate ; # Need to unsub first before we can do HELLO 3
+ set res [$rd_redirection read] ; # Consume the UNSUBSCRIBE reply
+ assert_equal {__redis__:invalidate} [lindex $res 1]
+ $rd_redirection HELLO 3
+ set res [$rd_redirection read] ; # Consume the HELLO reply
+ assert_equal [dict get $reply proto] 3
+ $rd_redirection SUBSCRIBE __redis__:invalidate
+ set res [$rd_redirection read] ; # Consume the SUBSCRIBE reply
+ assert_equal {__redis__:invalidate} [lindex $res 1]
+ r CLIENT TRACKING on REDIRECT $redir_id
+ $rd_sg SET key1 1
+ r GET key1
+ $rd_sg INCR key1
+ set res [lindex [$rd_redirection read] 1]
+ assert {$res eq {key1}}
+ $rd_redirection HELLO 2
+ set res [$rd_redirection read] ; # Consume the HELLO reply
+ assert_equal [dict get $res proto] 2
+ }
+
+ test {After switching from normal tracking to BCAST mode, no invalidation message is produced for pre-BCAST keys} {
+ r CLIENT TRACKING off
+ r HELLO 3
+ r CLIENT TRACKING on
+ $rd_sg SET key1 1
+ r GET key1
+ r CLIENT TRACKING off
+ r CLIENT TRACKING on BCAST
+ $rd_sg INCR key1
+ set inv_msg [r PING]
+ set ping_reply [r read]
+ assert {$inv_msg eq {invalidate key1}}
+ assert {$ping_reply eq {PONG}}
+ }
+
+ test {BCAST with prefix collisions throw errors} {
+ set r [redis_client]
+ catch {$r CLIENT TRACKING ON BCAST PREFIX FOOBAR PREFIX FOO} output
+ assert_match {ERR Prefix 'FOOBAR'*'FOO'*} $output
+
+ catch {$r CLIENT TRACKING ON BCAST PREFIX FOO PREFIX FOOBAR} output
+ assert_match {ERR Prefix 'FOO'*'FOOBAR'*} $output
+
+ $r CLIENT TRACKING ON BCAST PREFIX FOO PREFIX BAR
+ catch {$r CLIENT TRACKING ON BCAST PREFIX FO} output
+ assert_match {ERR Prefix 'FO'*'FOO'*} $output
+
+ catch {$r CLIENT TRACKING ON BCAST PREFIX BARB} output
+ assert_match {ERR Prefix 'BARB'*'BAR'*} $output
+
+ $r CLIENT TRACKING OFF
+ }
+
+ test {hdel deliver invalidate message after response in the same connection} {
+ r CLIENT TRACKING off
+ r HELLO 3
+ r CLIENT TRACKING on
+ r HSET myhash f 1
+ r HGET myhash f
+ set res [r HDEL myhash f]
+ assert_equal $res 1
+ set res [r read]
+ assert_equal $res {invalidate myhash}
+ }
+
+ test {Tracking invalidation message is not interleaved with multiple keys response} {
+ r CLIENT TRACKING off
+ r HELLO 3
+ r CLIENT TRACKING on
+ # We need disable active expire, so we can trigger lazy expire
+ r DEBUG SET-ACTIVE-EXPIRE 0
+ r MULTI
+ r MSET x{t} 1 y{t} 2
+ r PEXPIRE y{t} 100
+ r GET y{t}
+ r EXEC
+ after 110
+ # Read expired key y{t}, generate invalidate message about this key
+ set res [r MGET x{t} y{t}]
+ assert_equal $res {1 {}}
+ # Consume the invalidate message which is after command response
+ set res [r read]
+ assert_equal $res {invalidate y{t}}
+ r DEBUG SET-ACTIVE-EXPIRE 1
+ } {OK} {needs:debug}
+
+ test {Tracking invalidation message is not interleaved with transaction response} {
+ r CLIENT TRACKING off
+ r HELLO 3
+ r CLIENT TRACKING on
+ r MSET a{t} 1 b{t} 2
+ r GET a{t}
+ # Start a transaction, make a{t} generate an invalidate message
+ r MULTI
+ r INCR a{t}
+ r GET b{t}
+ set res [r EXEC]
+ assert_equal $res {2 2}
+ set res [r read]
+ # Consume the invalidate message which is after command response
+ assert_equal $res {invalidate a{t}}
+ }
+
+ test {Tracking invalidation message of eviction keys should be before response} {
+ # Get the current memory limit and calculate a new limit.
+ r CLIENT TRACKING off
+ r HELLO 3
+ r CLIENT TRACKING on
+
+ # make the previous test is really done before sampling used_memory
+ wait_lazyfree_done r
+
+ set used [expr {[s used_memory] - [s mem_not_counted_for_evict]}]
+ set limit [expr {$used+100*1024}]
+ set old_policy [lindex [r config get maxmemory-policy] 1]
+ r config set maxmemory $limit
+ # We set policy volatile-random, so only keys with ttl will be evicted
+ r config set maxmemory-policy volatile-random
+ # Add a volatile key and tracking it.
+ r setex volatile-key 10000 x
+ r get volatile-key
+ # We use SETBIT here, so we can set a big key and get the used_memory
+ # bigger than maxmemory. Next command will evict volatile keys. We
+ # can't use SET, as SET uses big input buffer, so it will fail.
+ r setbit big-key 1600000 0 ;# this will consume 200kb
+ # volatile-key is evicted before response.
+ set res [r getbit big-key 0]
+ assert_equal $res {invalidate volatile-key}
+ set res [r read]
+ assert_equal $res 0
+ r config set maxmemory-policy $old_policy
+ r config set maxmemory 0
+ }
+
+ test {Unblocked BLMOVE gets notification after response} {
+ r RPUSH list2{t} a
+ $rd HELLO 3
+ $rd read
+ $rd CLIENT TRACKING on
+ $rd read
+ # Tracking key list2{t}
+ $rd LRANGE list2{t} 0 -1
+ $rd read
+ # We block on list1{t}
+ $rd BLMOVE list1{t} list2{t} left left 0
+ wait_for_blocked_clients_count 1
+ # unblock $rd, list2{t} gets element and generate invalidation message
+ r rpush list1{t} foo
+ assert_equal [$rd read] {foo}
+ assert_equal [$rd read] {invalidate list2{t}}
+ }
+
+ test {Tracking gets notification on tracking table key eviction} {
+ r CLIENT TRACKING off
+ r CLIENT TRACKING on REDIRECT $redir_id NOLOOP
+ r MSET key1{t} 1 key2{t} 2
+ # Let the server track the two keys for us
+ r MGET key1{t} key2{t}
+ # Force the eviction of all the keys but one:
+ r config set tracking-table-max-keys 1
+ # Note that we may have other keys in the table for this client,
+ # since we disabled/enabled tracking multiple time with the same
+ # ID, and tracking does not do ID cleanups for performance reasons.
+ # So we check that eventually we'll receive one or the other key,
+ # otherwise the test will die for timeout.
+ while 1 {
+ set keys [lindex [$rd_redirection read] 2]
+ if {$keys eq {key1{t}} || $keys eq {key2{t}}} break
+ }
+ # We should receive an expire notification for one of
+ # the two keys (only one must remain)
+ assert {$keys eq {key1{t}} || $keys eq {key2{t}}}
+ }
+
+ test {Invalidation message received for flushall} {
+ clean_all
+ r CLIENT TRACKING on REDIRECT $redir_id
+ $rd_sg SET key1 1
+ r GET key1
+ $rd_sg FLUSHALL
+ set msg [$rd_redirection read]
+ assert {[lindex msg 2] eq {} }
+ }
+
+ test {Invalidation message received for flushdb} {
+ clean_all
+ r CLIENT TRACKING on REDIRECT $redir_id
+ $rd_sg SET key1 1
+ r GET key1
+ $rd_sg FLUSHDB
+ set msg [$rd_redirection read]
+ assert {[lindex msg 2] eq {} }
+ }
+
+ test {Test ASYNC flushall} {
+ clean_all
+ r CLIENT TRACKING on REDIRECT $redir_id
+ r GET key1
+ r GET key2
+ assert_equal [s 0 tracking_total_keys] 2
+ $rd_sg FLUSHALL ASYNC
+ assert_equal [s 0 tracking_total_keys] 0
+ assert_equal [lindex [$rd_redirection read] 2] {}
+ }
+
+ test {flushdb tracking invalidation message is not interleaved with transaction response} {
+ clean_all
+ r HELLO 3
+ r CLIENT TRACKING on
+ r SET a{t} 1
+ r GET a{t}
+ r MULTI
+ r FLUSHDB
+ set res [r EXEC]
+ assert_equal $res {OK}
+ # Consume the invalidate message which is after command response
+ r read
+ } {invalidate {}}
+
+ # Keys are defined to be evicted 100 at a time by default.
+ # If after eviction the number of keys still surpasses the limit
+ # defined in tracking-table-max-keys, we increases eviction
+ # effort to 200, and then 300, etc.
+ # This test tests this effort incrementation.
+ test {Server is able to evacuate enough keys when num of keys surpasses limit by more than defined initial effort} {
+ clean_all
+ set NUM_OF_KEYS_TO_TEST 250
+ set TRACKING_TABLE_MAX_KEYS 1
+ r CLIENT TRACKING on REDIRECT $redir_id
+ for {set i 0} {$i < $NUM_OF_KEYS_TO_TEST} {incr i} {
+ $rd_sg SET key$i $i
+ r GET key$i
+ }
+ r config set tracking-table-max-keys $TRACKING_TABLE_MAX_KEYS
+ # If not enough keys are evicted, we won't get enough invalidation
+ # messages, and "$rd_redirection read" will block.
+ # If too many keys are evicted, we will get too many invalidation
+ # messages, and the assert will fail.
+ for {set i 0} {$i < $NUM_OF_KEYS_TO_TEST - $TRACKING_TABLE_MAX_KEYS} {incr i} {
+ $rd_redirection read
+ }
+ $rd_redirection PING
+ assert {[$rd_redirection read] eq {pong {}}}
+ }
+
+ test {Tracking info is correct} {
+ clean_all
+ r CLIENT TRACKING on REDIRECT $redir_id
+ $rd_sg SET key1 1
+ $rd_sg SET key2 2
+ r GET key1
+ r GET key2
+ $rd CLIENT TRACKING on BCAST PREFIX prefix:
+ assert [string match *OK* [$rd read]]
+ $rd_sg SET prefix:key1 1
+ $rd_sg SET prefix:key2 2
+ set info [r info]
+ regexp "\r\ntracking_total_items:(.*?)\r\n" $info _ total_items
+ regexp "\r\ntracking_total_keys:(.*?)\r\n" $info _ total_keys
+ regexp "\r\ntracking_total_prefixes:(.*?)\r\n" $info _ total_prefixes
+ regexp "\r\ntracking_clients:(.*?)\r\n" $info _ tracking_clients
+ assert {$total_items == 2}
+ assert {$total_keys == 2}
+ assert {$total_prefixes == 1}
+ assert {$tracking_clients == 2}
+ }
+
+ test {CLIENT GETREDIR provides correct client id} {
+ set res [r CLIENT GETREDIR]
+ assert_equal $redir_id $res
+ r CLIENT TRACKING off
+ set res [r CLIENT GETREDIR]
+ assert_equal -1 $res
+ r CLIENT TRACKING on
+ set res [r CLIENT GETREDIR]
+ assert_equal 0 $res
+ }
+
+ test {CLIENT TRACKINGINFO provides reasonable results when tracking off} {
+ r CLIENT TRACKING off
+ set res [r client trackinginfo]
+ set flags [dict get $res flags]
+ assert_equal {off} $flags
+ set redirect [dict get $res redirect]
+ assert_equal {-1} $redirect
+ set prefixes [dict get $res prefixes]
+ assert_equal {} $prefixes
+ }
+
+ test {CLIENT TRACKINGINFO provides reasonable results when tracking on} {
+ r CLIENT TRACKING on
+ set res [r client trackinginfo]
+ set flags [dict get $res flags]
+ assert_equal {on} $flags
+ set redirect [dict get $res redirect]
+ assert_equal {0} $redirect
+ set prefixes [dict get $res prefixes]
+ assert_equal {} $prefixes
+ }
+
+ test {CLIENT TRACKINGINFO provides reasonable results when tracking on with options} {
+ r CLIENT TRACKING on REDIRECT $redir_id noloop
+ set res [r client trackinginfo]
+ set flags [dict get $res flags]
+ assert_equal {on noloop} $flags
+ set redirect [dict get $res redirect]
+ assert_equal $redir_id $redirect
+ set prefixes [dict get $res prefixes]
+ assert_equal {} $prefixes
+ }
+
+ test {CLIENT TRACKINGINFO provides reasonable results when tracking optin} {
+ r CLIENT TRACKING off
+ r CLIENT TRACKING on optin
+ set res [r client trackinginfo]
+ set flags [dict get $res flags]
+ assert_equal {on optin} $flags
+ set redirect [dict get $res redirect]
+ assert_equal {0} $redirect
+ set prefixes [dict get $res prefixes]
+ assert_equal {} $prefixes
+
+ r CLIENT CACHING yes
+ set res [r client trackinginfo]
+ set flags [dict get $res flags]
+ assert_equal {on optin caching-yes} $flags
+ }
+
+ test {CLIENT TRACKINGINFO provides reasonable results when tracking optout} {
+ r CLIENT TRACKING off
+ r CLIENT TRACKING on optout
+ set res [r client trackinginfo]
+ set flags [dict get $res flags]
+ assert_equal {on optout} $flags
+ set redirect [dict get $res redirect]
+ assert_equal {0} $redirect
+ set prefixes [dict get $res prefixes]
+ assert_equal {} $prefixes
+
+ r CLIENT CACHING no
+ set res [r client trackinginfo]
+ set flags [dict get $res flags]
+ assert_equal {on optout caching-no} $flags
+ }
+
+ test {CLIENT TRACKINGINFO provides reasonable results when tracking bcast mode} {
+ r CLIENT TRACKING off
+ r CLIENT TRACKING on BCAST PREFIX foo PREFIX bar
+ set res [r client trackinginfo]
+ set flags [dict get $res flags]
+ assert_equal {on bcast} $flags
+ set redirect [dict get $res redirect]
+ assert_equal {0} $redirect
+ set prefixes [lsort [dict get $res prefixes]]
+ assert_equal {bar foo} $prefixes
+
+ r CLIENT TRACKING off
+ r CLIENT TRACKING on BCAST
+ set res [r client trackinginfo]
+ set prefixes [dict get $res prefixes]
+ assert_equal {{}} $prefixes
+ }
+
+ test {CLIENT TRACKINGINFO provides reasonable results when tracking redir broken} {
+ clean_all
+ r HELLO 3
+ r CLIENT TRACKING on REDIRECT $redir_id
+ $rd_sg SET key1 1
+ r GET key1
+ $rd_redirection QUIT
+ assert_equal OK [$rd_redirection read]
+ $rd_sg SET key1 2
+ set res [lsearch -exact [r read] "tracking-redir-broken"]
+ assert {$res >= 0}
+ set res [r client trackinginfo]
+ set flags [dict get $res flags]
+ assert_equal {on broken_redirect} $flags
+ set redirect [dict get $res redirect]
+ assert_equal $redir_id $redirect
+ set prefixes [dict get $res prefixes]
+ assert_equal {} $prefixes
+ }
+
+ test {Regression test for #11715} {
+ # This issue manifests when a client invalidates keys through the max key
+ # limit, which invalidates keys to get Redis below the limit, but no command is
+ # then executed. This can occur in several ways but the simplest is through
+ # multi-exec which queues commands.
+ clean_all
+ r config set tracking-table-max-keys 2
+
+ # The cron will invalidate keys if we're above the limit, so disable it.
+ r debug pause-cron 1
+
+ # Set up a client that has listened to 2 keys and start a multi, this
+ # sets up the crash for later.
+ $rd HELLO 3
+ $rd read
+ $rd CLIENT TRACKING on
+ assert_match "OK" [$rd read]
+ $rd mget "1{tag}" "2{tag}"
+ assert_match "{} {}" [$rd read]
+ $rd multi
+ assert_match "OK" [$rd read]
+
+ # Reduce the tracking table keys to 1, this doesn't immediately take affect, but
+ # instead will apply on the next command.
+ r config set tracking-table-max-keys 1
+
+ # This command will get queued, so make sure this command doesn't crash.
+ $rd ping
+ $rd exec
+
+ # Validate we got some invalidation message and then the command was queued.
+ assert_match "invalidate *{tag}" [$rd read]
+ assert_match "QUEUED" [$rd read]
+ assert_match "PONG" [$rd read]
+
+ r debug pause-cron 0
+ } {OK} {needs:debug}
+
+ foreach resp {3 2} {
+ test "RESP$resp based basic invalidation with client reply off" {
+ # This entire test is mostly irrelevant for RESP2, but we run it anyway just for some extra coverage.
+ clean_all
+
+ $rd hello $resp
+ $rd read
+ $rd client tracking on
+ $rd read
+
+ $rd_sg set foo bar
+ $rd get foo
+ $rd read
+
+ $rd client reply off
+
+ $rd_sg set foo bar2
+
+ if {$resp == 3} {
+ assert_equal {invalidate foo} [$rd read]
+ } elseif {$resp == 2} { } ;# Just coverage
+
+ # Verify things didn't get messed up and no unexpected reply was pushed to the client.
+ $rd client reply on
+ assert_equal {OK} [$rd read]
+ $rd ping
+ assert_equal {PONG} [$rd read]
+ }
+ }
+
+ test {RESP3 based basic redirect invalidation with client reply off} {
+ clean_all
+
+ set rd_redir [redis_deferring_client]
+ $rd_redir hello 3
+ $rd_redir read
+
+ $rd_redir client id
+ set rd_redir_id [$rd_redir read]
+
+ $rd client tracking on redirect $rd_redir_id
+ $rd read
+
+ $rd_sg set foo bar
+ $rd get foo
+ $rd read
+
+ $rd_redir client reply off
+
+ $rd_sg set foo bar2
+ assert_equal {invalidate foo} [$rd_redir read]
+
+ # Verify things didn't get messed up and no unexpected reply was pushed to the client.
+ $rd_redir client reply on
+ assert_equal {OK} [$rd_redir read]
+ $rd_redir ping
+ assert_equal {PONG} [$rd_redir read]
+
+ $rd_redir close
+ }
+
+ test {RESP3 based basic tracking-redir-broken with client reply off} {
+ clean_all
+
+ $rd hello 3
+ $rd read
+ $rd client tracking on redirect $redir_id
+ $rd read
+
+ $rd_sg set foo bar
+ $rd get foo
+ $rd read
+
+ $rd client reply off
+
+ $rd_redirection quit
+ $rd_redirection read
+
+ $rd_sg set foo bar2
+
+ set res [lsearch -exact [$rd read] "tracking-redir-broken"]
+ assert_morethan_equal $res 0
+
+ # Verify things didn't get messed up and no unexpected reply was pushed to the client.
+ $rd client reply on
+ assert_equal {OK} [$rd read]
+ $rd ping
+ assert_equal {PONG} [$rd read]
+ }
+
+ $rd_redirection close
+ $rd_sg close
+ $rd close
+}
+
+# Just some extra covergae for --log-req-res, because we do not
+# run the full tracking unit in that mode
+start_server {tags {"tracking network"}} {
+ test {Coverage: Basic CLIENT CACHING} {
+ set rd_redirection [redis_deferring_client]
+ $rd_redirection client id
+ set redir_id [$rd_redirection read]
+ assert_equal {OK} [r CLIENT TRACKING on OPTIN REDIRECT $redir_id]
+ assert_equal {OK} [r CLIENT CACHING yes]
+ r CLIENT TRACKING off
+ } {OK}
+
+ test {Coverage: Basic CLIENT REPLY} {
+ r CLIENT REPLY on
+ } {OK}
+
+ test {Coverage: Basic CLIENT TRACKINGINFO} {
+ r CLIENT TRACKINGINFO
+ } {flags off redirect -1 prefixes {}}
+
+ test {Coverage: Basic CLIENT GETREDIR} {
+ r CLIENT GETREDIR
+ } {-1}
+}