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# Get a specific node by ID by parsing the CLUSTER NODES output
46# of the instance Number 'instance_id'
47proc get_node_by_id {instance_id node_id} {
48    set nodes [get_cluster_nodes $instance_id]
49    foreach n $nodes {
50        if {[dict get $n id] eq $node_id} {return $n}
51    }
52    return {}
53}
54
55# Return the value of the specified CLUSTER INFO field.
56proc CI {n field} {
57    get_info_field [R $n cluster info] $field
58}
59
60# Assuming nodes are reest, this function performs slots allocation.
61# Only the first 'n' nodes are used.
62proc cluster_allocate_slots {n} {
63    set slot 16383
64    while {$slot >= 0} {
65        # Allocate successive slots to random nodes.
66        set node [randomInt $n]
67        lappend slots_$node $slot
68        incr slot -1
69    }
70    for {set j 0} {$j < $n} {incr j} {
71        R $j cluster addslots {*}[set slots_${j}]
72    }
73}
74
75# Check that cluster nodes agree about "state", or raise an error.
76proc 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
87# Search the first node starting from ID $first that is not
88# already configured as a slave.
89proc cluster_find_available_slave {first} {
90    foreach_redis_id id {
91        if {$id < $first} continue
92        if {[instance_is_killed redis $id]} continue
93        set me [get_myself $id]
94        if {[dict get $me slaveof] eq {-}} {return $id}
95    }
96    fail "No available slaves"
97}
98
99# Add 'slaves' slaves to a cluster composed of 'masters' masters.
100# It assumes that masters are allocated sequentially from instance ID 0
101# to N-1.
102proc cluster_allocate_slaves {masters slaves} {
103    for {set j 0} {$j < $slaves} {incr j} {
104        set master_id [expr {$j % $masters}]
105        set slave_id [cluster_find_available_slave $masters]
106        set master_myself [get_myself $master_id]
107        R $slave_id cluster replicate [dict get $master_myself id]
108    }
109}
110
111# Create a cluster composed of the specified number of masters and slaves.
112proc create_cluster {masters slaves} {
113    cluster_allocate_slots $masters
114    if {$slaves} {
115        cluster_allocate_slaves $masters $slaves
116    }
117    assert_cluster_state ok
118}
119
120# Set the cluster node-timeout to all the reachalbe nodes.
121proc set_cluster_node_timeout {to} {
122    foreach_redis_id id {
123        catch {R $id CONFIG SET cluster-node-timeout $to}
124    }
125}
126
127# Check if the cluster is writable and readable. Use node "id"
128# as a starting point to talk with the cluster.
129proc cluster_write_test {id} {
130    set prefix [randstring 20 20 alpha]
131    set port [get_instance_attrib redis $id port]
132    set cluster [redis_cluster 127.0.0.1:$port]
133    for {set j 0} {$j < 100} {incr j} {
134        $cluster set key.$j $prefix.$j
135    }
136    for {set j 0} {$j < 100} {incr j} {
137        assert {[$cluster get key.$j] eq "$prefix.$j"}
138    }
139    $cluster close
140}
141