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