cluster.tcl 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. # Cluster-specific test functions.
  2. #
  3. # Copyright (C) 2014 Salvatore Sanfilippo antirez@gmail.com
  4. # This software is released under the BSD License. See the COPYING file for
  5. # more information.
  6. # Track cluster configuration as created by create_cluster below
  7. set ::cluster_master_nodes 0
  8. set ::cluster_replica_nodes 0
  9. # Returns a parsed CLUSTER NODES output as a list of dictionaries.
  10. proc get_cluster_nodes id {
  11. set lines [split [R $id cluster nodes] "\r\n"]
  12. set nodes {}
  13. foreach l $lines {
  14. set l [string trim $l]
  15. if {$l eq {}} continue
  16. set args [split $l]
  17. set node [dict create \
  18. id [lindex $args 0] \
  19. addr [lindex $args 1] \
  20. flags [split [lindex $args 2] ,] \
  21. slaveof [lindex $args 3] \
  22. ping_sent [lindex $args 4] \
  23. pong_recv [lindex $args 5] \
  24. config_epoch [lindex $args 6] \
  25. linkstate [lindex $args 7] \
  26. slots [lrange $args 8 end] \
  27. ]
  28. lappend nodes $node
  29. }
  30. return $nodes
  31. }
  32. # Test node for flag.
  33. proc has_flag {node flag} {
  34. expr {[lsearch -exact [dict get $node flags] $flag] != -1}
  35. }
  36. # Returns the parsed myself node entry as a dictionary.
  37. proc get_myself id {
  38. set nodes [get_cluster_nodes $id]
  39. foreach n $nodes {
  40. if {[has_flag $n myself]} {return $n}
  41. }
  42. return {}
  43. }
  44. # Get a specific node by ID by parsing the CLUSTER NODES output
  45. # of the instance Number 'instance_id'
  46. proc get_node_by_id {instance_id node_id} {
  47. set nodes [get_cluster_nodes $instance_id]
  48. foreach n $nodes {
  49. if {[dict get $n id] eq $node_id} {return $n}
  50. }
  51. return {}
  52. }
  53. # Return the value of the specified CLUSTER INFO field.
  54. proc CI {n field} {
  55. get_info_field [R $n cluster info] $field
  56. }
  57. # Return the value of the specified INFO field.
  58. proc s {n field} {
  59. get_info_field [R $n info] $field
  60. }
  61. # Assuming nodes are reest, this function performs slots allocation.
  62. # Only the first 'n' nodes are used.
  63. proc cluster_allocate_slots {n} {
  64. set slot 16383
  65. while {$slot >= 0} {
  66. # Allocate successive slots to random nodes.
  67. set node [randomInt $n]
  68. lappend slots_$node $slot
  69. incr slot -1
  70. }
  71. for {set j 0} {$j < $n} {incr j} {
  72. R $j cluster addslots {*}[set slots_${j}]
  73. }
  74. }
  75. # Check that cluster nodes agree about "state", or raise an error.
  76. proc assert_cluster_state {state} {
  77. foreach_redis_id id {
  78. if {[instance_is_killed redis $id]} continue
  79. wait_for_condition 1000 50 {
  80. [CI $id cluster_state] eq $state
  81. } else {
  82. fail "Cluster node $id cluster_state:[CI $id cluster_state]"
  83. }
  84. }
  85. }
  86. # Search the first node starting from ID $first that is not
  87. # already configured as a slave.
  88. proc cluster_find_available_slave {first} {
  89. foreach_redis_id id {
  90. if {$id < $first} continue
  91. if {[instance_is_killed redis $id]} continue
  92. set me [get_myself $id]
  93. if {[dict get $me slaveof] eq {-}} {return $id}
  94. }
  95. fail "No available slaves"
  96. }
  97. # Add 'slaves' slaves to a cluster composed of 'masters' masters.
  98. # It assumes that masters are allocated sequentially from instance ID 0
  99. # to N-1.
  100. proc cluster_allocate_slaves {masters slaves} {
  101. for {set j 0} {$j < $slaves} {incr j} {
  102. set master_id [expr {$j % $masters}]
  103. set slave_id [cluster_find_available_slave $masters]
  104. set master_myself [get_myself $master_id]
  105. R $slave_id cluster replicate [dict get $master_myself id]
  106. }
  107. }
  108. # Create a cluster composed of the specified number of masters and slaves.
  109. proc create_cluster {masters slaves} {
  110. cluster_allocate_slots $masters
  111. if {$slaves} {
  112. cluster_allocate_slaves $masters $slaves
  113. }
  114. assert_cluster_state ok
  115. set ::cluster_master_nodes $masters
  116. set ::cluster_replica_nodes $slaves
  117. }
  118. # Set the cluster node-timeout to all the reachalbe nodes.
  119. proc set_cluster_node_timeout {to} {
  120. foreach_redis_id id {
  121. catch {R $id CONFIG SET cluster-node-timeout $to}
  122. }
  123. }
  124. # Check if the cluster is writable and readable. Use node "id"
  125. # as a starting point to talk with the cluster.
  126. proc cluster_write_test {id} {
  127. set prefix [randstring 20 20 alpha]
  128. set port [get_instance_attrib redis $id port]
  129. set cluster [redis_cluster 127.0.0.1:$port]
  130. for {set j 0} {$j < 100} {incr j} {
  131. $cluster set key.$j $prefix.$j
  132. }
  133. for {set j 0} {$j < 100} {incr j} {
  134. assert {[$cluster get key.$j] eq "$prefix.$j"}
  135. }
  136. $cluster close
  137. }
  138. # Check if cluster configuration is consistent.
  139. proc cluster_config_consistent {} {
  140. for {set j 0} {$j < $::cluster_master_nodes + $::cluster_replica_nodes} {incr j} {
  141. if {$j == 0} {
  142. set base_cfg [R $j cluster slots]
  143. } else {
  144. set cfg [R $j cluster slots]
  145. if {$cfg != $base_cfg} {
  146. return 0
  147. }
  148. }
  149. }
  150. return 1
  151. }
  152. # Wait for cluster configuration to propagate and be consistent across nodes.
  153. proc wait_for_cluster_propagation {} {
  154. wait_for_condition 50 100 {
  155. [cluster_config_consistent] eq 1
  156. } else {
  157. fail "cluster config did not reach a consistent state"
  158. }
  159. }