1
0

server.tcl 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. set ::global_overrides {}
  2. set ::tags {}
  3. set ::valgrind_errors {}
  4. proc start_server_error {config_file error} {
  5. set err {}
  6. append err "Can't start the Redis server\n"
  7. append err "CONFIGURATION:"
  8. append err [exec cat $config_file]
  9. append err "\nERROR:"
  10. append err [string trim $error]
  11. send_data_packet $::test_server_fd err $err
  12. }
  13. proc check_valgrind_errors stderr {
  14. set res [find_valgrind_errors $stderr true]
  15. if {$res != ""} {
  16. send_data_packet $::test_server_fd err "Valgrind error: $res\n"
  17. }
  18. }
  19. proc clean_persistence config {
  20. # we may wanna keep the logs for later, but let's clean the persistence
  21. # files right away, since they can accumulate and take up a lot of space
  22. set config [dict get $config "config"]
  23. set rdb [format "%s/%s" [dict get $config "dir"] "dump.rdb"]
  24. set aof [format "%s/%s" [dict get $config "dir"] "appendonly.aof"]
  25. catch {exec rm -rf $rdb}
  26. catch {exec rm -rf $aof}
  27. }
  28. proc kill_server config {
  29. # nothing to kill when running against external server
  30. if {$::external} return
  31. # nevermind if its already dead
  32. if {![is_alive $config]} {
  33. # Check valgrind errors if needed
  34. if {$::valgrind} {
  35. check_valgrind_errors [dict get $config stderr]
  36. }
  37. return
  38. }
  39. set pid [dict get $config pid]
  40. # check for leaks
  41. if {![dict exists $config "skipleaks"]} {
  42. catch {
  43. if {[string match {*Darwin*} [exec uname -a]]} {
  44. tags {"leaks"} {
  45. test "Check for memory leaks (pid $pid)" {
  46. set output {0 leaks}
  47. catch {exec leaks $pid} output option
  48. # In a few tests we kill the server process, so leaks will not find it.
  49. # It'll exits with exit code >1 on error, so we ignore these.
  50. if {[dict exists $option -errorcode]} {
  51. set details [dict get $option -errorcode]
  52. if {[lindex $details 0] eq "CHILDSTATUS"} {
  53. set status [lindex $details 2]
  54. if {$status > 1} {
  55. set output "0 leaks"
  56. }
  57. }
  58. }
  59. set output
  60. } {*0 leaks*}
  61. }
  62. }
  63. }
  64. }
  65. # kill server and wait for the process to be totally exited
  66. send_data_packet $::test_server_fd server-killing $pid
  67. catch {exec kill $pid}
  68. # Node might have been stopped in the test
  69. catch {exec kill -SIGCONT $pid}
  70. if {$::valgrind} {
  71. set max_wait 60000
  72. } else {
  73. set max_wait 10000
  74. }
  75. while {[is_alive $config]} {
  76. incr wait 10
  77. if {$wait >= $max_wait} {
  78. puts "Forcing process $pid to exit..."
  79. catch {exec kill -KILL $pid}
  80. } elseif {$wait % 1000 == 0} {
  81. puts "Waiting for process $pid to exit..."
  82. }
  83. after 10
  84. }
  85. # Check valgrind errors if needed
  86. if {$::valgrind} {
  87. check_valgrind_errors [dict get $config stderr]
  88. }
  89. # Remove this pid from the set of active pids in the test server.
  90. send_data_packet $::test_server_fd server-killed $pid
  91. }
  92. proc is_alive config {
  93. set pid [dict get $config pid]
  94. if {[catch {exec kill -0 $pid} err]} {
  95. return 0
  96. } else {
  97. return 1
  98. }
  99. }
  100. proc ping_server {host port} {
  101. set retval 0
  102. if {[catch {
  103. if {$::tls} {
  104. set fd [::tls::socket $host $port]
  105. } else {
  106. set fd [socket $host $port]
  107. }
  108. fconfigure $fd -translation binary
  109. puts $fd "PING\r\n"
  110. flush $fd
  111. set reply [gets $fd]
  112. if {[string range $reply 0 0] eq {+} ||
  113. [string range $reply 0 0] eq {-}} {
  114. set retval 1
  115. }
  116. close $fd
  117. } e]} {
  118. if {$::verbose} {
  119. puts -nonewline "."
  120. }
  121. } else {
  122. if {$::verbose} {
  123. puts -nonewline "ok"
  124. }
  125. }
  126. return $retval
  127. }
  128. # Return 1 if the server at the specified addr is reachable by PING, otherwise
  129. # returns 0. Performs a try every 50 milliseconds for the specified number
  130. # of retries.
  131. proc server_is_up {host port retrynum} {
  132. after 10 ;# Use a small delay to make likely a first-try success.
  133. set retval 0
  134. while {[incr retrynum -1]} {
  135. if {[catch {ping_server $host $port} ping]} {
  136. set ping 0
  137. }
  138. if {$ping} {return 1}
  139. after 50
  140. }
  141. return 0
  142. }
  143. # Check if current ::tags match requested tags. If ::allowtags are used,
  144. # there must be some intersection. If ::denytags are used, no intersection
  145. # is allowed. Returns 1 if tags are acceptable or 0 otherwise, in which
  146. # case err_return names a return variable for the message to be logged.
  147. proc tags_acceptable {tags err_return} {
  148. upvar $err_return err
  149. # If tags are whitelisted, make sure there's match
  150. if {[llength $::allowtags] > 0} {
  151. set matched 0
  152. foreach tag $::allowtags {
  153. if {[lsearch $tags $tag] >= 0} {
  154. incr matched
  155. }
  156. }
  157. if {$matched < 1} {
  158. set err "Tag: none of the tags allowed"
  159. return 0
  160. }
  161. }
  162. foreach tag $::denytags {
  163. if {[lsearch $tags $tag] >= 0} {
  164. set err "Tag: $tag denied"
  165. return 0
  166. }
  167. }
  168. if {$::external && [lsearch $tags "external:skip"] >= 0} {
  169. set err "Not supported on external server"
  170. return 0
  171. }
  172. if {$::singledb && [lsearch $tags "singledb:skip"] >= 0} {
  173. set err "Not supported on singledb"
  174. return 0
  175. }
  176. if {$::cluster_mode && [lsearch $tags "cluster:skip"] >= 0} {
  177. set err "Not supported in cluster mode"
  178. return 0
  179. }
  180. return 1
  181. }
  182. # doesn't really belong here, but highly coupled to code in start_server
  183. proc tags {tags code} {
  184. # If we 'tags' contain multiple tags, quoted and separated by spaces,
  185. # we want to get rid of the quotes in order to have a proper list
  186. set tags [string map { \" "" } $tags]
  187. set ::tags [concat $::tags $tags]
  188. if {![tags_acceptable $::tags err]} {
  189. incr ::num_aborted
  190. send_data_packet $::test_server_fd ignore $err
  191. set ::tags [lrange $::tags 0 end-[llength $tags]]
  192. return
  193. }
  194. uplevel 1 $code
  195. set ::tags [lrange $::tags 0 end-[llength $tags]]
  196. }
  197. # Write the configuration in the dictionary 'config' in the specified
  198. # file name.
  199. proc create_server_config_file {filename config} {
  200. set fp [open $filename w+]
  201. foreach directive [dict keys $config] {
  202. puts -nonewline $fp "$directive "
  203. puts $fp [dict get $config $directive]
  204. }
  205. close $fp
  206. }
  207. proc spawn_server {config_file stdout stderr} {
  208. set pid [exec ./target/release/microredis $config_file >> $stdout 2>> $stderr &]
  209. if {$::wait_server} {
  210. set msg "server started PID: $pid. press any key to continue..."
  211. puts $msg
  212. read stdin 1
  213. }
  214. # Tell the test server about this new instance.
  215. send_data_packet $::test_server_fd server-spawned $pid
  216. return $pid
  217. }
  218. # Wait for actual startup, return 1 if port is busy, 0 otherwise
  219. proc wait_server_started {config_file stdout pid} {
  220. set checkperiod 100; # Milliseconds
  221. set maxiter [expr {120*1000/$checkperiod}] ; # Wait up to 2 minutes.
  222. set port_busy 0
  223. while 1 {
  224. if {[regexp -- " PID: $pid" [exec cat $stdout]]} {
  225. break
  226. }
  227. after $checkperiod
  228. incr maxiter -1
  229. if {$maxiter == 0} {
  230. start_server_error $config_file "No PID detected in log $stdout"
  231. puts "--- LOG CONTENT ---"
  232. puts [exec cat $stdout]
  233. puts "-------------------"
  234. break
  235. }
  236. # Check if the port is actually busy and the server failed
  237. # for this reason.
  238. if {[regexp {Failed listening on port} [exec cat $stdout]]} {
  239. set port_busy 1
  240. break
  241. }
  242. }
  243. return $port_busy
  244. }
  245. proc dump_server_log {srv} {
  246. set pid [dict get $srv "pid"]
  247. puts "\n===== Start of server log (pid $pid) =====\n"
  248. puts [exec cat [dict get $srv "stdout"]]
  249. puts "===== End of server log (pid $pid) =====\n"
  250. }
  251. proc run_external_server_test {code overrides} {
  252. set srv {}
  253. dict set srv "host" $::host
  254. dict set srv "port" $::port
  255. set client [redis $::host $::port 0 $::tls]
  256. dict set srv "client" $client
  257. if {!$::singledb} {
  258. $client select 9
  259. }
  260. set config {}
  261. dict set config "port" $::port
  262. dict set srv "config" $config
  263. # append the server to the stack
  264. lappend ::servers $srv
  265. if {[llength $::servers] > 1} {
  266. if {$::verbose} {
  267. puts "Notice: nested start_server statements in external server mode, test must be aware of that!"
  268. }
  269. }
  270. r flushall
  271. # store overrides
  272. set saved_config {}
  273. foreach {param val} $overrides {
  274. dict set saved_config $param [lindex [r config get $param] 1]
  275. r config set $param $val
  276. # If we enable appendonly, wait for for rewrite to complete. This is
  277. # required for tests that begin with a bg* command which will fail if
  278. # the rewriteaof operation is not completed at this point.
  279. if {$param == "appendonly" && $val == "yes"} {
  280. waitForBgrewriteaof r
  281. }
  282. }
  283. if {[catch {set retval [uplevel 2 $code]} error]} {
  284. if {$::durable} {
  285. set msg [string range $error 10 end]
  286. lappend details $msg
  287. lappend details $::errorInfo
  288. lappend ::tests_failed $details
  289. incr ::num_failed
  290. send_data_packet $::test_server_fd err [join $details "\n"]
  291. } else {
  292. # Re-raise, let handler up the stack take care of this.
  293. error $error $::errorInfo
  294. }
  295. }
  296. # restore overrides
  297. dict for {param val} $saved_config {
  298. r config set $param $val
  299. }
  300. lpop ::servers
  301. }
  302. proc start_server {options {code undefined}} {
  303. # setup defaults
  304. set baseconfig "default.conf"
  305. set overrides {}
  306. set omit {}
  307. set tags {}
  308. set keep_persistence false
  309. # parse options
  310. foreach {option value} $options {
  311. switch $option {
  312. "config" {
  313. set baseconfig $value
  314. }
  315. "overrides" {
  316. set overrides $value
  317. }
  318. "omit" {
  319. set omit $value
  320. }
  321. "tags" {
  322. # If we 'tags' contain multiple tags, quoted and separated by spaces,
  323. # we want to get rid of the quotes in order to have a proper list
  324. set tags [string map { \" "" } $value]
  325. set ::tags [concat $::tags $tags]
  326. }
  327. "keep_persistence" {
  328. set keep_persistence $value
  329. }
  330. default {
  331. error "Unknown option $option"
  332. }
  333. }
  334. }
  335. # We skip unwanted tags
  336. if {![tags_acceptable $::tags err]} {
  337. incr ::num_aborted
  338. send_data_packet $::test_server_fd ignore $err
  339. set ::tags [lrange $::tags 0 end-[llength $tags]]
  340. return
  341. }
  342. # If we are running against an external server, we just push the
  343. # host/port pair in the stack the first time
  344. if {$::external} {
  345. run_external_server_test $code $overrides
  346. set ::tags [lrange $::tags 0 end-[llength $tags]]
  347. return
  348. }
  349. set data [split [exec cat "tests/assets/$baseconfig"] "\n"]
  350. set config {}
  351. if {$::tls} {
  352. dict set config "tls-cert-file" [format "%s/tests/tls/server.crt" [pwd]]
  353. dict set config "tls-key-file" [format "%s/tests/tls/server.key" [pwd]]
  354. dict set config "tls-client-cert-file" [format "%s/tests/tls/client.crt" [pwd]]
  355. dict set config "tls-client-key-file" [format "%s/tests/tls/client.key" [pwd]]
  356. dict set config "tls-dh-params-file" [format "%s/tests/tls/redis.dh" [pwd]]
  357. dict set config "tls-ca-cert-file" [format "%s/tests/tls/ca.crt" [pwd]]
  358. dict set config "loglevel" "debug"
  359. }
  360. foreach line $data {
  361. if {[string length $line] > 0 && [string index $line 0] ne "#"} {
  362. set elements [split $line " "]
  363. set directive [lrange $elements 0 0]
  364. set arguments [lrange $elements 1 end]
  365. dict set config $directive $arguments
  366. }
  367. }
  368. # use a different directory every time a server is started
  369. dict set config dir [tmpdir server]
  370. # start every server on a different port
  371. set port [find_available_port $::baseport $::portcount]
  372. if {$::tls} {
  373. dict set config "port" 0
  374. dict set config "tls-port" $port
  375. dict set config "tls-cluster" "yes"
  376. dict set config "tls-replication" "yes"
  377. } else {
  378. dict set config port $port
  379. }
  380. set unixsocket [file normalize [format "%s/%s" [dict get $config "dir"] "socket"]]
  381. dict set config "unixsocket" $unixsocket
  382. # apply overrides from global space and arguments
  383. foreach {directive arguments} [concat $::global_overrides $overrides] {
  384. dict set config $directive $arguments
  385. }
  386. # remove directives that are marked to be omitted
  387. foreach directive $omit {
  388. dict unset config $directive
  389. }
  390. # write new configuration to temporary file
  391. set config_file [tmpfile redis.conf]
  392. create_server_config_file $config_file $config
  393. set stdout [format "%s/%s" [dict get $config "dir"] "stdout"]
  394. set stderr [format "%s/%s" [dict get $config "dir"] "stderr"]
  395. # if we're inside a test, write the test name to the server log file
  396. if {[info exists ::cur_test]} {
  397. set fd [open $stdout "a+"]
  398. puts $fd "### Starting server for test $::cur_test"
  399. close $fd
  400. }
  401. # We need a loop here to retry with different ports.
  402. set server_started 0
  403. while {$server_started == 0} {
  404. if {$::verbose} {
  405. puts -nonewline "=== ($tags) Starting server ${::host}:${port} "
  406. }
  407. send_data_packet $::test_server_fd "server-spawning" "port $port"
  408. set pid [spawn_server $config_file $stdout $stderr]
  409. # check that the server actually started
  410. set port_busy [wait_server_started $config_file $stdout $pid]
  411. # Sometimes we have to try a different port, even if we checked
  412. # for availability. Other test clients may grab the port before we
  413. # are able to do it for example.
  414. if {$port_busy} {
  415. puts "Port $port was already busy, trying another port..."
  416. set port [find_available_port $::baseport $::portcount]
  417. if {$::tls} {
  418. dict set config "tls-port" $port
  419. } else {
  420. dict set config port $port
  421. }
  422. create_server_config_file $config_file $config
  423. # Truncate log so wait_server_started will not be looking at
  424. # output of the failed server.
  425. close [open $stdout "w"]
  426. continue; # Try again
  427. }
  428. if {$::valgrind} {set retrynum 1000} else {set retrynum 100}
  429. if {$code ne "undefined"} {
  430. set serverisup [server_is_up $::host $port $retrynum]
  431. } else {
  432. set serverisup 1
  433. }
  434. if {$::verbose} {
  435. puts ""
  436. }
  437. if {!$serverisup} {
  438. set err {}
  439. append err [exec cat $stdout] "\n" [exec cat $stderr]
  440. start_server_error $config_file $err
  441. return
  442. }
  443. set server_started 1
  444. }
  445. # setup properties to be able to initialize a client object
  446. set port_param [expr $::tls ? {"tls-port"} : {"port"}]
  447. set host $::host
  448. if {[dict exists $config bind]} { set host [dict get $config bind] }
  449. if {[dict exists $config $port_param]} { set port [dict get $config $port_param] }
  450. # setup config dict
  451. dict set srv "config_file" $config_file
  452. dict set srv "config" $config
  453. dict set srv "pid" $pid
  454. dict set srv "host" $host
  455. dict set srv "port" $port
  456. dict set srv "stdout" $stdout
  457. dict set srv "stderr" $stderr
  458. dict set srv "unixsocket" $unixsocket
  459. # if a block of code is supplied, we wait for the server to become
  460. # available, create a client object and kill the server afterwards
  461. if {$code ne "undefined"} {
  462. set line [exec head -n1 $stdout]
  463. if {[string match {*already in use*} $line]} {
  464. error_and_quit $config_file $line
  465. }
  466. while 1 {
  467. # check that the server actually started and is ready for connections
  468. if {[count_message_lines $stdout "Ready to accept"] > 0} {
  469. break
  470. }
  471. after 10
  472. }
  473. # append the server to the stack
  474. lappend ::servers $srv
  475. # connect client (after server dict is put on the stack)
  476. reconnect
  477. # remember previous num_failed to catch new errors
  478. set prev_num_failed $::num_failed
  479. # execute provided block
  480. set num_tests $::num_tests
  481. if {[catch { uplevel 1 $code } error]} {
  482. set backtrace $::errorInfo
  483. set assertion [string match "assertion:*" $error]
  484. # fetch srv back from the server list, in case it was restarted by restart_server (new PID)
  485. set srv [lindex $::servers end]
  486. # pop the server object
  487. set ::servers [lrange $::servers 0 end-1]
  488. # Kill the server without checking for leaks
  489. dict set srv "skipleaks" 1
  490. kill_server $srv
  491. if {$::dump_logs && $assertion} {
  492. # if we caught an assertion ($::num_failed isn't incremented yet)
  493. # this happens when the test spawns a server and not the other way around
  494. dump_server_log $srv
  495. } else {
  496. # Print crash report from log
  497. set crashlog [crashlog_from_file [dict get $srv "stdout"]]
  498. if {[string length $crashlog] > 0} {
  499. puts [format "\nLogged crash report (pid %d):" [dict get $srv "pid"]]
  500. puts "$crashlog"
  501. puts ""
  502. }
  503. }
  504. if {!$assertion && $::durable} {
  505. # durable is meant to prevent the whole tcl test from exiting on
  506. # an exception. an assertion will be caught by the test proc.
  507. set msg [string range $error 10 end]
  508. lappend details $msg
  509. lappend details $backtrace
  510. lappend ::tests_failed $details
  511. incr ::num_failed
  512. send_data_packet $::test_server_fd err [join $details "\n"]
  513. } else {
  514. # Re-raise, let handler up the stack take care of this.
  515. error $error $backtrace
  516. }
  517. } else {
  518. if {$::dump_logs && $prev_num_failed != $::num_failed} {
  519. dump_server_log $srv
  520. }
  521. }
  522. # fetch srv back from the server list, in case it was restarted by restart_server (new PID)
  523. set srv [lindex $::servers end]
  524. # Don't do the leak check when no tests were run
  525. if {$num_tests == $::num_tests} {
  526. dict set srv "skipleaks" 1
  527. }
  528. # pop the server object
  529. set ::servers [lrange $::servers 0 end-1]
  530. set ::tags [lrange $::tags 0 end-[llength $tags]]
  531. kill_server $srv
  532. if {!$keep_persistence} {
  533. clean_persistence $srv
  534. }
  535. set _ ""
  536. } else {
  537. set ::tags [lrange $::tags 0 end-[llength $tags]]
  538. set _ $srv
  539. }
  540. }
  541. proc restart_server {level wait_ready rotate_logs {reconnect 1}} {
  542. set srv [lindex $::servers end+$level]
  543. kill_server $srv
  544. set pid [dict get $srv "pid"]
  545. set stdout [dict get $srv "stdout"]
  546. set stderr [dict get $srv "stderr"]
  547. if {$rotate_logs} {
  548. set ts [clock format [clock seconds] -format %y%m%d%H%M%S]
  549. file rename $stdout $stdout.$ts.$pid
  550. file rename $stderr $stderr.$ts.$pid
  551. }
  552. set prev_ready_count [count_message_lines $stdout "Ready to accept"]
  553. # if we're inside a test, write the test name to the server log file
  554. if {[info exists ::cur_test]} {
  555. set fd [open $stdout "a+"]
  556. puts $fd "### Restarting server for test $::cur_test"
  557. close $fd
  558. }
  559. set config_file [dict get $srv "config_file"]
  560. set pid [spawn_server $config_file $stdout $stderr]
  561. # check that the server actually started
  562. wait_server_started $config_file $stdout $pid
  563. # update the pid in the servers list
  564. dict set srv "pid" $pid
  565. # re-set $srv in the servers list
  566. lset ::servers end+$level $srv
  567. if {$wait_ready} {
  568. while 1 {
  569. # check that the server actually started and is ready for connections
  570. if {[count_message_lines $stdout "Ready to accept"] > $prev_ready_count} {
  571. break
  572. }
  573. after 10
  574. }
  575. }
  576. if {$reconnect} {
  577. reconnect $level
  578. }
  579. }