summaryrefslogtreecommitdiffstats
path: root/tests/unit/moduleapi/hooks.tcl
blob: 94b0f6f31256d55c6f95c1fc765d91defe98f6dd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
set testmodule [file normalize tests/modules/hooks.so]

tags "modules" {
    start_server [list overrides [list loadmodule "$testmodule" appendonly yes]] {
        test {Test module aof save on server start from empty} {
            assert {[r hooks.event_count persistence-syncaof-start] == 1}
        }

        test {Test clients connection / disconnection hooks} {
            for {set j 0} {$j < 2} {incr j} {
                set rd1 [redis_deferring_client]
                $rd1 close
            }
            assert {[r hooks.event_count client-connected] > 1}
            assert {[r hooks.event_count client-disconnected] > 1}
        }

        test {Test module client change event for blocked client} {
            set rd [redis_deferring_client]
            # select db other than 0
            $rd select 1
            # block on key
            $rd brpop foo 0
            # kill blocked client
            r client kill skipme yes
            # assert server is still up
            assert_equal [r ping] PONG
            $rd close
        } 

        test {Test module cron hook} {
            after 100
            assert {[r hooks.event_count cron-loop] > 0}
            set hz [r hooks.event_last cron-loop]
            assert_equal $hz 10
        }

        test {Test module loaded / unloaded hooks} {
            set othermodule [file normalize tests/modules/infotest.so]
            r module load $othermodule
            r module unload infotest
            assert_equal [r hooks.event_last module-loaded] "infotest"
            assert_equal [r hooks.event_last module-unloaded] "infotest"
        }

        test {Test module aofrw hook} {
            r debug populate 1000 foo 10000 ;# 10mb worth of data
            r config set rdbcompression no ;# rdb progress is only checked once in 2mb
            r BGREWRITEAOF
            waitForBgrewriteaof r
            assert_equal [string match {*module-event-persistence-aof-start*} [exec tail -20 < [srv 0 stdout]]] 1
            assert_equal [string match {*module-event-persistence-end*} [exec tail -20 < [srv 0 stdout]]] 1
        }

        test {Test module aof load and rdb/aof progress hooks} {
            # create some aof tail (progress is checked only once in 1000 commands)
            for {set j 0} {$j < 4000} {incr j} {
                r set "bar$j" x
            }
            # set some configs that will cause many loading progress events during aof loading
            r config set key-load-delay 500
            r config set dynamic-hz no
            r config set hz 500
            r DEBUG LOADAOF
            assert_equal [r hooks.event_last loading-aof-start] 0
            assert_equal [r hooks.event_last loading-end] 0
            assert {[r hooks.event_count loading-rdb-start] == 0}
            assert_lessthan 2 [r hooks.event_count loading-progress-rdb] ;# comes from the preamble section
            assert_lessthan 2 [r hooks.event_count loading-progress-aof]
            if {$::verbose} {
                puts "rdb progress events [r hooks.event_count loading-progress-rdb]"
                puts "aof progress events [r hooks.event_count loading-progress-aof]"
            }
        }
        # undo configs before next test
        r config set dynamic-hz yes
        r config set key-load-delay 0

        test {Test module rdb save hook} {
            # debug reload does: save, flush, load:
            assert {[r hooks.event_count persistence-syncrdb-start] == 0}
            assert {[r hooks.event_count loading-rdb-start] == 0}
            r debug reload
            assert {[r hooks.event_count persistence-syncrdb-start] == 1}
            assert {[r hooks.event_count loading-rdb-start] == 1}
        }

        test {Test key unlink hook} {
            r set testkey1 hello
            r del testkey1
            assert {[r hooks.event_count key-info-testkey1] == 1}
            assert_equal [r hooks.event_last key-info-testkey1] testkey1
            r lpush testkey1 hello
            r lpop testkey1
            assert {[r hooks.event_count key-info-testkey1] == 2}
            assert_equal [r hooks.event_last key-info-testkey1] testkey1
            r set testkey2 world
            r unlink testkey2
            assert {[r hooks.event_count key-info-testkey2] == 1}
            assert_equal [r hooks.event_last key-info-testkey2] testkey2
        }

        test {Test removed key event} {
            r set str abcd
            r set str abcde
            # For String Type value is returned
            assert_equal {abcd overwritten} [r hooks.is_key_removed str]
            assert_equal -1 [r hooks.pexpireat str]

            r del str
            assert_equal {abcde deleted} [r hooks.is_key_removed str]
            assert_equal -1 [r hooks.pexpireat str]

            # test int encoded string
            r set intstr 12345678
            # incr doesn't fire event
            r incr intstr
            catch {[r hooks.is_key_removed intstr]} output
            assert_match {ERR * removed} $output
            r del intstr
            assert_equal {12345679 deleted} [r hooks.is_key_removed intstr]

            catch {[r hooks.is_key_removed not-exists]} output
            assert_match {ERR * removed} $output

            r hset hash f v
            r hdel hash f
            assert_equal {0 deleted} [r hooks.is_key_removed hash]

            r hset hash f v a b
            r del hash
            assert_equal {2 deleted} [r hooks.is_key_removed hash]

            r lpush list 1
            r lpop list
            assert_equal {0 deleted} [r hooks.is_key_removed list]

            r lpush list 1 2 3
            r del list
            assert_equal {3 deleted} [r hooks.is_key_removed list]

            r sadd set 1
            r spop set
            assert_equal {0 deleted} [r hooks.is_key_removed set]

            r sadd set 1 2 3 4
            r del set
            assert_equal {4 deleted} [r hooks.is_key_removed set]

            r zadd zset 1 f
            r zpopmin zset
            assert_equal {0 deleted} [r hooks.is_key_removed zset]

            r zadd zset 1 f 2 d
            r del zset
            assert_equal {2 deleted} [r hooks.is_key_removed zset]

            r xadd stream 1-1 f v
            r xdel stream 1-1
            # Stream does not delete object when del entry
            catch {[r hooks.is_key_removed stream]} output
            assert_match {ERR * removed} $output
            r del stream
            assert_equal {0 deleted} [r hooks.is_key_removed stream]

            r xadd stream 2-1 f v
            r del stream
            assert_equal {1 deleted} [r hooks.is_key_removed stream]

            # delete key because of active expire
            set size [r dbsize]
            r set active-expire abcd px 1
            #ensure active expire
            wait_for_condition 50 100 {
                [r dbsize] == $size
            } else {
                fail "Active expire not trigger"
            }
            assert_equal {abcd expired} [r hooks.is_key_removed active-expire]
            # current time is greater than pexpireat
            set now [r time]
            set mill [expr ([lindex $now 0]*1000)+([lindex $now 1]/1000)]
            assert {$mill >= [r hooks.pexpireat active-expire]}

            # delete key because of lazy expire
            r debug set-active-expire 0
            r set lazy-expire abcd px 1
            after 10
            r get lazy-expire
            assert_equal {abcd expired} [r hooks.is_key_removed lazy-expire]
            set now [r time]
            set mill [expr ([lindex $now 0]*1000)+([lindex $now 1]/1000)]
            assert {$mill >= [r hooks.pexpireat lazy-expire]}
            r debug set-active-expire 1

            # delete key not yet expired
            set now [r time]
            set expireat [expr ([lindex $now 0]*1000)+([lindex $now 1]/1000)+1000000]
            r set not-expire abcd pxat $expireat
            r del not-expire
            assert_equal {abcd deleted} [r hooks.is_key_removed not-expire]
            assert_equal $expireat [r hooks.pexpireat not-expire]

            # Test key evict
            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
            r setex volatile-key 10000 x
            # 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
            r getbit big-key 0
            assert_equal {x evicted} [r hooks.is_key_removed volatile-key]
            r config set maxmemory-policy $old_policy
            r config set maxmemory 0
        } {OK} {needs:debug}

        test {Test flushdb hooks} {
            r flushdb
            assert_equal [r hooks.event_last flush-start] 9
            assert_equal [r hooks.event_last flush-end] 9
            r flushall
            assert_equal [r hooks.event_last flush-start] -1
            assert_equal [r hooks.event_last flush-end] -1
        }

        # replication related tests
        set master [srv 0 client]
        set master_host [srv 0 host]
        set master_port [srv 0 port]
        start_server {} {
            r module load $testmodule
            set replica [srv 0 client]
            set replica_host [srv 0 host]
            set replica_port [srv 0 port]
            $replica replicaof $master_host $master_port

            wait_replica_online $master

            test {Test master link up hook} {
                assert_equal [r hooks.event_count masterlink-up] 1
                assert_equal [r hooks.event_count masterlink-down] 0
            }

            test {Test role-replica hook} {
                assert_equal [r hooks.event_count role-replica] 1
                assert_equal [r hooks.event_count role-master] 0
                assert_equal [r hooks.event_last role-replica] [s 0 master_host]
            }

            test {Test replica-online hook} {
                assert_equal [r -1 hooks.event_count replica-online] 1
                assert_equal [r -1 hooks.event_count replica-offline] 0
            }

            test {Test master link down hook} {
                r client kill type master
                assert_equal [r hooks.event_count masterlink-down] 1

                wait_for_condition 50 100 {
                    [string match {*master_link_status:up*} [r info replication]]
                } else {
                    fail "Replica didn't reconnect"
                }

                assert_equal [r hooks.event_count masterlink-down] 1
                assert_equal [r hooks.event_count masterlink-up] 2
            }

            wait_for_condition 50 10 {
                [string match {*master_link_status:up*} [r info replication]]
            } else {
                fail "Can't turn the instance into a replica"
            }

            $replica replicaof no one

            test {Test role-master hook} {
                assert_equal [r hooks.event_count role-replica] 1
                assert_equal [r hooks.event_count role-master] 1
                assert_equal [r hooks.event_last role-master] {}
            }

            test {Test replica-offline hook} {
                assert_equal [r -1 hooks.event_count replica-online] 2
                assert_equal [r -1 hooks.event_count replica-offline] 2
            }
            # get the replica stdout, to be used by the next test
            set replica_stdout [srv 0 stdout]
        }

        test {Test swapdb hooks} {
            r swapdb 0 10
            assert_equal [r hooks.event_last swapdb-first] 0
            assert_equal [r hooks.event_last swapdb-second] 10
        }

        test {Test configchange hooks} {
            r config set rdbcompression no 
            assert_equal [r hooks.event_last config-change-count] 1
            assert_equal [r hooks.event_last config-change-first] rdbcompression
        }

        # look into the log file of the server that just exited
        test {Test shutdown hook} {
            assert_equal [string match {*module-event-shutdown*} [exec tail -5 < $replica_stdout]] 1
        }
    }

    start_server {} {
        test {OnLoad failure will handle un-registration} {
            catch {r module load $testmodule noload}
            r flushall
            r ping
        }
    }
}