xref: /redis-3.2.3/src/redis-trib.rb (revision 79d2f70f)
1#!/usr/bin/env ruby
2
3# TODO (temporary here, we'll move this into the Github issues once
4#       redis-trib initial implementation is completed).
5#
6# - Make sure that if the rehashing fails in the middle redis-trib will try
7#   to recover.
8# - When redis-trib performs a cluster check, if it detects a slot move in
9#   progress it should prompt the user to continue the move from where it
10#   stopped.
11# - Gracefully handle Ctrl+C in move_slot to prompt the user if really stop
12#   while rehashing, and performing the best cleanup possible if the user
13#   forces the quit.
14# - When doing "fix" set a global Fix to true, and prompt the user to
15#   fix the problem if automatically fixable every time there is something
16#   to fix. For instance:
17#   1) If there is a node that pretend to receive a slot, or to migrate a
18#      slot, but has no entries in that slot, fix it.
19#   2) If there is a node having keys in slots that are not owned by it
20#      fix this condition moving the entries in the same node.
21#   3) Perform more possibly slow tests about the state of the cluster.
22#   4) When aborted slot migration is detected, fix it.
23
24require 'rubygems'
25require 'redis'
26
27ClusterHashSlots = 16384
28
29def xputs(s)
30    case s[0..2]
31    when ">>>"
32        color="29;1"
33    when "[ER"
34        color="31;1"
35    when "[OK"
36        color="32"
37    when "[FA","***"
38        color="33"
39    else
40        color=nil
41    end
42
43    color = nil if ENV['TERM'] != "xterm"
44    print "\033[#{color}m" if color
45    print s
46    print "\033[0m" if color
47    print "\n"
48end
49
50class ClusterNode
51    def initialize(addr)
52        s = addr.split(":")
53        if s.length != 2
54            puts "Invalid node name #{addr}"
55            exit 1
56        end
57        @r = nil
58        @info = {}
59        @info[:host] = s[0]
60        @info[:port] = s[1]
61        @info[:slots] = {}
62        @info[:migrating] = {}
63        @info[:importing] = {}
64        @info[:replicate] = false
65        @dirty = false # True if we need to flush slots info into node.
66        @friends = []
67    end
68
69    def friends
70        @friends
71    end
72
73    def slots
74        @info[:slots]
75    end
76
77    def has_flag?(flag)
78        @info[:flags].index(flag)
79    end
80
81    def to_s
82        "#{@info[:host]}:#{@info[:port]}"
83    end
84
85    def connect(o={})
86        return if @r
87        print "Connecting to node #{self}: "
88        STDOUT.flush
89        begin
90            @r = Redis.new(:host => @info[:host], :port => @info[:port], :timeout => 60)
91            @r.ping
92        rescue
93            xputs "[ERR] Sorry, can't connect to node #{self}"
94            exit 1 if o[:abort]
95            @r = nil
96        end
97        xputs "OK"
98    end
99
100    def assert_cluster
101        info = @r.info
102        if !info["cluster_enabled"] || info["cluster_enabled"].to_i == 0
103            xputs "[ERR] Node #{self} is not configured as a cluster node."
104            exit 1
105        end
106    end
107
108    def assert_empty
109        if !(@r.cluster("info").split("\r\n").index("cluster_known_nodes:1")) ||
110            (@r.info['db0'])
111            xputs "[ERR] Node #{self} is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0."
112            exit 1
113        end
114    end
115
116    def load_info(o={})
117        self.connect
118        nodes = @r.cluster("nodes").split("\n")
119        nodes.each{|n|
120            # name addr flags role ping_sent ping_recv link_status slots
121            split = n.split
122            name,addr,flags,master_id,ping_sent,ping_recv,config_epoch,link_status = split[0..6]
123            slots = split[8..-1]
124            info = {
125                :name => name,
126                :addr => addr,
127                :flags => flags.split(","),
128                :replicate => master_id,
129                :ping_sent => ping_sent.to_i,
130                :ping_recv => ping_recv.to_i,
131                :link_status => link_status
132            }
133            info[:replicate] = false if master_id == "-"
134
135            if info[:flags].index("myself")
136                @info = @info.merge(info)
137                @info[:slots] = {}
138                slots.each{|s|
139                    if s[0..0] == '['
140                        if s.index("->-") # Migrating
141                            slot,dst = s[1..-1].split("->-")
142                            @info[:migrating][slot.to_i] = dst
143                        elsif s.index("-<-") # Importing
144                            slot,src = s[1..-1].split("-<-")
145                            @info[:importing][slot.to_i] = src
146                        end
147                    elsif s.index("-")
148                        start,stop = s.split("-")
149                        self.add_slots((start.to_i)..(stop.to_i))
150                    else
151                        self.add_slots((s.to_i)..(s.to_i))
152                    end
153                } if slots
154                @dirty = false
155                @r.cluster("info").split("\n").each{|e|
156                    k,v=e.split(":")
157                    k = k.to_sym
158                    v.chop!
159                    if k != :cluster_state
160                        @info[k] = v.to_i
161                    else
162                        @info[k] = v
163                    end
164                }
165            elsif o[:getfriends]
166                @friends << info
167            end
168        }
169    end
170
171    def add_slots(slots)
172        slots.each{|s|
173            @info[:slots][s] = :new
174        }
175        @dirty = true
176    end
177
178    def set_as_replica(node_id)
179        @info[:replicate] = node_id
180        @dirty = true
181    end
182
183    def flush_node_config
184        return if !@dirty
185        if @info[:replicate]
186            begin
187                @r.cluster("replicate",@info[:replicate])
188            rescue
189                # If the cluster did not already joined it is possible that
190                # the slave does not know the master node yet. So on errors
191                # we return ASAP leaving the dirty flag set, to flush the
192                # config later.
193                return
194            end
195        else
196            new = []
197            @info[:slots].each{|s,val|
198                if val == :new
199                    new << s
200                    @info[:slots][s] = true
201                end
202            }
203            @r.cluster("addslots",*new)
204        end
205        @dirty = false
206    end
207
208    def info_string
209        # We want to display the hash slots assigned to this node
210        # as ranges, like in: "1-5,8-9,20-25,30"
211        #
212        # Note: this could be easily written without side effects,
213        # we use 'slots' just to split the computation into steps.
214
215        # First step: we want an increasing array of integers
216        # for instance: [1,2,3,4,5,8,9,20,21,22,23,24,25,30]
217        slots = @info[:slots].keys.sort
218
219        # As we want to aggregate adjacent slots we convert all the
220        # slot integers into ranges (with just one element)
221        # So we have something like [1..1,2..2, ... and so forth.
222        slots.map!{|x| x..x}
223
224        # Finally we group ranges with adjacent elements.
225        slots = slots.reduce([]) {|a,b|
226            if !a.empty? && b.first == (a[-1].last)+1
227                a[0..-2] + [(a[-1].first)..(b.last)]
228            else
229                a + [b]
230            end
231        }
232
233        # Now our task is easy, we just convert ranges with just one
234        # element into a number, and a real range into a start-end format.
235        # Finally we join the array using the comma as separator.
236        slots = slots.map{|x|
237            x.count == 1 ? x.first.to_s : "#{x.first}-#{x.last}"
238        }.join(",")
239
240        role = self.has_flag?("master") ? "M" : "S"
241
242        if self.info[:replicate] and @dirty
243            is = "S: #{self.info[:name]} #{self.to_s}"
244        else
245            is = "#{role}: #{self.info[:name]} #{self.to_s}\n"+
246            "   slots:#{slots} (#{self.slots.length} slots) "+
247            "#{(self.info[:flags]-["myself"]).join(",")}"
248        end
249        if self.info[:replicate]
250            is += "\n   replicates #{info[:replicate]}"
251        elsif self.has_flag?("master") && self.info[:replicas]
252            is += "\n   #{info[:replicas].length} additional replica(s)"
253        end
254        is
255    end
256
257    # Return a single string representing nodes and associated slots.
258    # TODO: remove slaves from config when slaves will be handled
259    # by Redis Cluster.
260    def get_config_signature
261        config = []
262        @r.cluster("nodes").each_line{|l|
263            s = l.split
264            slots = s[8..-1].select {|x| x[0..0] != "["}
265            next if slots.length == 0
266            config << s[0]+":"+(slots.sort.join(","))
267        }
268        config.sort.join("|")
269    end
270
271    def info
272        @info
273    end
274
275    def is_dirty?
276        @dirty
277    end
278
279    def r
280        @r
281    end
282end
283
284class RedisTrib
285    def initialize
286        @nodes = []
287        @fix = false
288        @errors = []
289    end
290
291    def check_arity(req_args, num_args)
292        if ((req_args > 0 and num_args != req_args) ||
293           (req_args < 0 and num_args < req_args.abs))
294           xputs "[ERR] Wrong number of arguments for specified sub command"
295           exit 1
296        end
297    end
298
299    def add_node(node)
300        @nodes << node
301    end
302
303    def cluster_error(msg)
304        @errors << msg
305        xputs msg
306    end
307
308    def get_node_by_name(name)
309        @nodes.each{|n|
310            return n if n.info[:name] == name.downcase
311        }
312        return nil
313    end
314
315    # This function returns the master that has the least number of replicas
316    # in the cluster. If there are multiple masters with the same smaller
317    # number of replicas, one at random is returned.
318    def get_master_with_least_replicas
319        masters = @nodes.select{|n| n.has_flag? "master"}
320        sorted = masters.sort{|a,b|
321            a.info[:replicas].length <=> b.info[:replicas].length
322        }
323        sorted[0]
324    end
325
326    def check_cluster
327        xputs ">>> Performing Cluster Check (using node #{@nodes[0]})"
328        show_nodes
329        check_config_consistency
330        check_open_slots
331        check_slots_coverage
332    end
333
334    # Merge slots of every known node. If the resulting slots are equal
335    # to ClusterHashSlots, then all slots are served.
336    def covered_slots
337        slots = {}
338        @nodes.each{|n|
339            slots = slots.merge(n.slots)
340        }
341        slots
342    end
343
344    def check_slots_coverage
345        xputs ">>> Check slots coverage..."
346        slots = covered_slots
347        if slots.length == ClusterHashSlots
348            xputs "[OK] All #{ClusterHashSlots} slots covered."
349        else
350            cluster_error \
351                "[ERR] Not all #{ClusterHashSlots} slots are covered by nodes."
352            fix_slots_coverage if @fix
353        end
354    end
355
356    def check_open_slots
357        xputs ">>> Check for open slots..."
358        open_slots = []
359        @nodes.each{|n|
360            if n.info[:migrating].size > 0
361                cluster_error \
362                    "[WARNING] Node #{n} has slots in migrating state (#{n.info[:migrating].keys.join(",")})."
363                open_slots += n.info[:migrating].keys
364            elsif n.info[:importing].size > 0
365                cluster_error \
366                    "[WARNING] Node #{n} has slots in importing state (#{n.info[:importing].keys.join(",")})."
367                open_slots += n.info[:importing].keys
368            end
369        }
370        open_slots.uniq!
371        if open_slots.length > 0
372            xputs "[WARNING] The following slots are open: #{open_slots.join(",")}"
373        end
374        if @fix
375            open_slots.each{|slot| fix_open_slot slot}
376        end
377    end
378
379    def nodes_with_keys_in_slot(slot)
380        nodes = []
381        @nodes.each{|n|
382            nodes << n if n.r.cluster("getkeysinslot",slot,1).length > 0
383        }
384        nodes
385    end
386
387    def fix_slots_coverage
388        not_covered = (0...ClusterHashSlots).to_a - covered_slots.keys
389        xputs ">>> Fixing slots coverage..."
390        xputs "List of not covered slots: " + not_covered.join(",")
391
392        # For every slot, take action depending on the actual condition:
393        # 1) No node has keys for this slot.
394        # 2) A single node has keys for this slot.
395        # 3) Multiple nodes have keys for this slot.
396        slots = {}
397        not_covered.each{|slot|
398            nodes = nodes_with_keys_in_slot(slot)
399            slots[slot] = nodes
400            xputs "Slot #{slot} has keys in #{nodes.length} nodes: #{nodes.join}"
401        }
402
403        none = slots.select {|k,v| v.length == 0}
404        single = slots.select {|k,v| v.length == 1}
405        multi = slots.select {|k,v| v.length > 1}
406
407        # Handle case "1": keys in no node.
408        if none.length > 0
409            xputs "The folowing uncovered slots have no keys across the cluster:"
410            xputs none.keys.join(",")
411            yes_or_die "Fix these slots by covering with a random node?"
412            none.each{|slot,nodes|
413                node = @nodes.sample
414                xputs ">>> Covering slot #{slot} with #{node}"
415                node.r.cluster("addslots",slot)
416            }
417        end
418
419        # Handle case "2": keys only in one node.
420        if single.length > 0
421            xputs "The folowing uncovered slots have keys in just one node:"
422            puts single.keys.join(",")
423            yes_or_die "Fix these slots by covering with those nodes?"
424            single.each{|slot,nodes|
425                xputs ">>> Covering slot #{slot} with #{nodes[0]}"
426                nodes[0].r.cluster("addslots",slot)
427            }
428        end
429
430        # Handle case "3": keys in multiple nodes.
431        if multi.length > 0
432            xputs "The folowing uncovered slots have keys in multiple nodes:"
433            xputs multi.keys.join(",")
434            yes_or_die "Fix these slots by moving keys into a single node?"
435            multi.each{|slot,nodes|
436                xputs ">>> Covering slot #{slot} moving keys to #{nodes[0]}"
437                # TODO
438                # 1) Set all nodes as "MIGRATING" for this slot, so that we
439                # can access keys in the hash slot using ASKING.
440                # 2) Move everything to node[0]
441                # 3) Clear MIGRATING from nodes, and ADDSLOTS the slot to
442                # node[0].
443                raise "TODO: Work in progress"
444            }
445        end
446    end
447
448    # Return the owner of the specified slot
449    def get_slot_owner(slot)
450        @nodes.each{|n|
451            n.slots.each{|s,_|
452                return n if s == slot
453            }
454        }
455        nil
456    end
457
458    # Slot 'slot' was found to be in importing or migrating state in one or
459    # more nodes. This function fixes this condition by migrating keys where
460    # it seems more sensible.
461    def fix_open_slot(slot)
462        puts ">>> Fixing open slot #{slot}"
463
464        # Try to obtain the current slot owner, according to the current
465        # nodes configuration.
466        owner = get_slot_owner(slot)
467
468        # If there is no slot owner, set as owner the slot with the biggest
469        # number of keys, among the set of migrating / importing nodes.
470        if !owner
471            xputs "*** Fix me, some work to do here."
472            # Select owner...
473            # Use ADDSLOTS to assign the slot.
474            exit 1
475        end
476
477        migrating = []
478        importing = []
479        @nodes.each{|n|
480            next if n.has_flag? "slave"
481            if n.info[:migrating][slot]
482                migrating << n
483            elsif n.info[:importing][slot]
484                importing << n
485            elsif n.r.cluster("countkeysinslot",slot) > 0 && n != owner
486                xputs "*** Found keys about slot #{slot} in node #{n}!"
487                importing << n
488            end
489        }
490        puts "Set as migrating in: #{migrating.join(",")}"
491        puts "Set as importing in: #{importing.join(",")}"
492
493        # Case 1: The slot is in migrating state in one slot, and in
494        #         importing state in 1 slot. That's trivial to address.
495        if migrating.length == 1 && importing.length == 1
496            move_slot(migrating[0],importing[0],slot,:verbose=>true,:fix=>true)
497        elsif migrating.length == 0 && importing.length > 0
498            xputs ">>> Moving all the #{slot} slot keys to its owner #{owner}"
499            importing.each {|node|
500                next if node == owner
501                move_slot(node,owner,slot,:verbose=>true,:fix=>true,:cold=>true)
502                xputs ">>> Setting #{slot} as STABLE in #{node}"
503                node.r.cluster("setslot",slot,"stable")
504            }
505        else
506            xputs "[ERR] Sorry, Redis-trib can't fix this slot yet (work in progress)"
507        end
508    end
509
510    # Check if all the nodes agree about the cluster configuration
511    def check_config_consistency
512        if !is_config_consistent?
513            cluster_error "[ERR] Nodes don't agree about configuration!"
514        else
515            xputs "[OK] All nodes agree about slots configuration."
516        end
517    end
518
519    def is_config_consistent?
520        signatures=[]
521        @nodes.each{|n|
522            signatures << n.get_config_signature
523        }
524        return signatures.uniq.length == 1
525    end
526
527    def wait_cluster_join
528        print "Waiting for the cluster to join"
529        while !is_config_consistent?
530            print "."
531            STDOUT.flush
532            sleep 1
533        end
534        print "\n"
535    end
536
537    def alloc_slots
538        nodes_count = @nodes.length
539        masters_count = @nodes.length / (@replicas+1)
540        masters = []
541        slaves = []
542
543        # The first step is to split instances by IP. This is useful as
544        # we'll try to allocate master nodes in different physical machines
545        # (as much as possible) and to allocate slaves of a given master in
546        # different physical machines as well.
547        #
548        # This code assumes just that if the IP is different, than it is more
549        # likely that the instance is running in a different physical host
550        # or at least a different virtual machine.
551        ips = {}
552        @nodes.each{|n|
553            ips[n.info[:host]] = [] if !ips[n.info[:host]]
554            ips[n.info[:host]] << n
555        }
556
557        # Select master instances
558        puts "Using #{masters_count} masters:"
559        while masters.length < masters_count
560            ips.each{|ip,nodes_list|
561                next if nodes_list.length == 0
562                masters << nodes_list.shift
563                puts masters[-1]
564                nodes_count -= 1
565                break if masters.length == masters_count
566            }
567        end
568
569        # Alloc slots on masters
570        slots_per_node = ClusterHashSlots.to_f / masters_count
571        first = 0
572        cursor = 0.0
573        masters.each_with_index{|n,masternum|
574            last = (cursor+slots_per_node-1).round
575            if last > ClusterHashSlots || masternum == masters.length-1
576                last = ClusterHashSlots-1
577            end
578            last = first if last < first # Min step is 1.
579            n.add_slots first..last
580            first = last+1
581            cursor += slots_per_node
582        }
583
584        # Select N replicas for every master.
585        # We try to split the replicas among all the IPs with spare nodes
586        # trying to avoid the host where the master is running, if possible.
587        #
588        # Note we loop two times.  The first loop assigns the requested
589        # number of replicas to each master.  The second loop assigns any
590        # remaining instances as extra replicas to masters.  Some masters
591        # may end up with more than their requested number of replicas, but
592        # all nodes will be used.
593        assignment_verbose = false
594
595        [:requested,:unused].each{|assign|
596            masters.each{|m|
597                assigned_replicas = 0
598                while assigned_replicas < @replicas
599                    break if nodes_count == 0
600                    if assignment_verbose
601                        if assign == :requested
602                            puts "Requesting total of #{@replicas} replicas " \
603                                 "(#{assigned_replicas} replicas assigned " \
604                                 "so far with #{nodes_count} total remaining)."
605                        elsif assign == :unused
606                            puts "Assigning extra instance to replication " \
607                                 "role too (#{nodes_count} remaining)."
608                        end
609                    end
610                    ips.each{|ip,nodes_list|
611                        next if nodes_list.length == 0
612                        # Skip instances with the same IP as the master if we
613                        # have some more IPs available.
614                        next if ip == m.info[:host] && nodes_count > nodes_list.length
615                        slave = nodes_list.shift
616                        slave.set_as_replica(m.info[:name])
617                        nodes_count -= 1
618                        assigned_replicas += 1
619                        puts "Adding replica #{slave} to #{m}"
620                        break
621                    }
622                end
623            }
624        }
625    end
626
627    def flush_nodes_config
628        @nodes.each{|n|
629            n.flush_node_config
630        }
631    end
632
633    def show_nodes
634        @nodes.each{|n|
635            xputs n.info_string
636        }
637    end
638
639    # Redis Cluster config epoch collision resolution code is able to eventually
640    # set a different epoch to each node after a new cluster is created, but
641    # it is slow compared to assign a progressive config epoch to each node
642    # before joining the cluster. However we do just a best-effort try here
643    # since if we fail is not a problem.
644    def assign_config_epoch
645        config_epoch = 1
646        @nodes.each{|n|
647            begin
648                n.r.cluster("set-config-epoch",config_epoch)
649            rescue
650            end
651            config_epoch += 1
652        }
653    end
654
655    def join_cluster
656        # We use a brute force approach to make sure the node will meet
657        # each other, that is, sending CLUSTER MEET messages to all the nodes
658        # about the very same node.
659        # Thanks to gossip this information should propagate across all the
660        # cluster in a matter of seconds.
661        first = false
662        @nodes.each{|n|
663            if !first then first = n.info; next; end # Skip the first node
664            n.r.cluster("meet",first[:host],first[:port])
665        }
666    end
667
668    def yes_or_die(msg)
669        print "#{msg} (type 'yes' to accept): "
670        STDOUT.flush
671        if !(STDIN.gets.chomp.downcase == "yes")
672            xputs "*** Aborting..."
673            exit 1
674        end
675    end
676
677    def load_cluster_info_from_node(nodeaddr)
678        node = ClusterNode.new(nodeaddr)
679        node.connect(:abort => true)
680        node.assert_cluster
681        node.load_info(:getfriends => true)
682        add_node(node)
683        node.friends.each{|f|
684            next if f[:flags].index("noaddr") ||
685                    f[:flags].index("disconnected") ||
686                    f[:flags].index("fail")
687            fnode = ClusterNode.new(f[:addr])
688            fnode.connect()
689            fnode.load_info()
690            add_node(fnode)
691        }
692        populate_nodes_replicas_info
693    end
694
695    # This function is called by load_cluster_info_from_node in order to
696    # add additional information to every node as a list of replicas.
697    def populate_nodes_replicas_info
698        # Start adding the new field to every node.
699        @nodes.each{|n|
700            n.info[:replicas] = []
701        }
702
703        # Populate the replicas field using the replicate field of slave
704        # nodes.
705        @nodes.each{|n|
706            if n.info[:replicate]
707                master = get_node_by_name(n.info[:replicate])
708                if !master
709                    xputs "*** WARNING: #{n} claims to be slave of unknown node ID #{n.info[:replicate]}."
710                else
711                    master.info[:replicas] << n
712                end
713            end
714        }
715    end
716
717    # Given a list of source nodes return a "resharding plan"
718    # with what slots to move in order to move "numslots" slots to another
719    # instance.
720    def compute_reshard_table(sources,numslots)
721        moved = []
722        # Sort from bigger to smaller instance, for two reasons:
723        # 1) If we take less slots than instances it is better to start
724        #    getting from the biggest instances.
725        # 2) We take one slot more from the first instance in the case of not
726        #    perfect divisibility. Like we have 3 nodes and need to get 10
727        #    slots, we take 4 from the first, and 3 from the rest. So the
728        #    biggest is always the first.
729        sources = sources.sort{|a,b| b.slots.length <=> a.slots.length}
730        source_tot_slots = sources.inject(0) {|sum,source|
731            sum+source.slots.length
732        }
733        sources.each_with_index{|s,i|
734            # Every node will provide a number of slots proportional to the
735            # slots it has assigned.
736            n = (numslots.to_f/source_tot_slots*s.slots.length)
737            if i == 0
738                n = n.ceil
739            else
740                n = n.floor
741            end
742            s.slots.keys.sort[(0...n)].each{|slot|
743                if moved.length < numslots
744                    moved << {:source => s, :slot => slot}
745                end
746            }
747        }
748        return moved
749    end
750
751    def show_reshard_table(table)
752        table.each{|e|
753            puts "    Moving slot #{e[:slot]} from #{e[:source].info[:name]}"
754        }
755    end
756
757    # Move slots between source and target nodes using MIGRATE.
758    #
759    # Options:
760    # :verbose -- Print a dot for every moved key.
761    # :fix     -- We are moving in the context of a fix. Use REPLACE.
762    # :cold    -- Move keys without opening / reconfiguring the nodes.
763    def move_slot(source,target,slot,o={})
764        # We start marking the slot as importing in the destination node,
765        # and the slot as migrating in the target host. Note that the order of
766        # the operations is important, as otherwise a client may be redirected
767        # to the target node that does not yet know it is importing this slot.
768        print "Moving slot #{slot} from #{source} to #{target}: "; STDOUT.flush
769        if !o[:cold]
770            target.r.cluster("setslot",slot,"importing",source.info[:name])
771            source.r.cluster("setslot",slot,"migrating",target.info[:name])
772        end
773        # Migrate all the keys from source to target using the MIGRATE command
774        while true
775            keys = source.r.cluster("getkeysinslot",slot,10)
776            break if keys.length == 0
777            keys.each{|key|
778                begin
779                    source.r.client.call(["migrate",target.info[:host],target.info[:port],key,0,15000])
780                rescue => e
781                    if o[:fix] && e.to_s =~ /BUSYKEY/
782                        xputs "*** Target key #{key} exists. Replace it for FIX."
783                        source.r.client.call(["migrate",target.info[:host],target.info[:port],key,0,15000,:replace])
784                    else
785                        puts ""
786                        xputs "[ERR] #{e}"
787                        exit 1
788                    end
789                end
790                print "." if o[:verbose]
791                STDOUT.flush
792            }
793        end
794
795        puts
796        # Set the new node as the owner of the slot in all the known nodes.
797        if !o[:cold]
798            @nodes.each{|n|
799                n.r.cluster("setslot",slot,"node",target.info[:name])
800            }
801        end
802    end
803
804    # redis-trib subcommands implementations
805
806    def check_cluster_cmd(argv,opt)
807        load_cluster_info_from_node(argv[0])
808        check_cluster
809    end
810
811    def fix_cluster_cmd(argv,opt)
812        @fix = true
813        load_cluster_info_from_node(argv[0])
814        check_cluster
815    end
816
817    def reshard_cluster_cmd(argv,opt)
818        load_cluster_info_from_node(argv[0])
819        check_cluster
820        if @errors.length != 0
821            puts "*** Please fix your cluster problems before resharding"
822            exit 1
823        end
824        numslots = 0
825        while numslots <= 0 or numslots > ClusterHashSlots
826            print "How many slots do you want to move (from 1 to #{ClusterHashSlots})? "
827            numslots = STDIN.gets.to_i
828        end
829        target = nil
830        while not target
831            print "What is the receiving node ID? "
832            target = get_node_by_name(STDIN.gets.chop)
833            if !target || target.has_flag?("slave")
834                xputs "*** The specified node is not known or not a master, please retry."
835                target = nil
836            end
837        end
838        sources = []
839        xputs "Please enter all the source node IDs."
840        xputs "  Type 'all' to use all the nodes as source nodes for the hash slots."
841        xputs "  Type 'done' once you entered all the source nodes IDs."
842        while true
843            print "Source node ##{sources.length+1}:"
844            line = STDIN.gets.chop
845            src = get_node_by_name(line)
846            if line == "done"
847                if sources.length == 0
848                    puts "No source nodes given, operation aborted"
849                    exit 1
850                else
851                    break
852                end
853            elsif line == "all"
854                @nodes.each{|n|
855                    next if n.info[:name] == target.info[:name]
856                    next if n.has_flag?("slave")
857                    sources << n
858                }
859                break
860            elsif !src || src.has_flag?("slave")
861                xputs "*** The specified node is not known or is not a master, please retry."
862            elsif src.info[:name] == target.info[:name]
863                xputs "*** It is not possible to use the target node as source node."
864            else
865                sources << src
866            end
867        end
868        puts "\nReady to move #{numslots} slots."
869        puts "  Source nodes:"
870        sources.each{|s| puts "    "+s.info_string}
871        puts "  Destination node:"
872        puts "    #{target.info_string}"
873        reshard_table = compute_reshard_table(sources,numslots)
874        puts "  Resharding plan:"
875        show_reshard_table(reshard_table)
876        print "Do you want to proceed with the proposed reshard plan (yes/no)? "
877        yesno = STDIN.gets.chop
878        exit(1) if (yesno != "yes")
879        reshard_table.each{|e|
880            move_slot(e[:source],target,e[:slot],:verbose=>true)
881        }
882    end
883
884    # This is an helper function for create_cluster_cmd that verifies if
885    # the number of nodes and the specified replicas have a valid configuration
886    # where there are at least three master nodes and enough replicas per node.
887    def check_create_parameters
888        masters = @nodes.length/(@replicas+1)
889        if masters < 3
890            puts "*** ERROR: Invalid configuration for cluster creation."
891            puts "*** Redis Cluster requires at least 3 master nodes."
892            puts "*** This is not possible with #{@nodes.length} nodes and #{@replicas} replicas per node."
893            puts "*** At least #{3*(@replicas+1)} nodes are required."
894            exit 1
895        end
896    end
897
898    def create_cluster_cmd(argv,opt)
899        opt = {'replicas' => 0}.merge(opt)
900        @replicas = opt['replicas'].to_i
901
902        xputs ">>> Creating cluster"
903        argv[0..-1].each{|n|
904            node = ClusterNode.new(n)
905            node.connect(:abort => true)
906            node.assert_cluster
907            node.load_info
908            node.assert_empty
909            add_node(node)
910        }
911        check_create_parameters
912        xputs ">>> Performing hash slots allocation on #{@nodes.length} nodes..."
913        alloc_slots
914        show_nodes
915        yes_or_die "Can I set the above configuration?"
916        flush_nodes_config
917        xputs ">>> Nodes configuration updated"
918        xputs ">>> Assign a different config epoch to each node"
919        assign_config_epoch
920        xputs ">>> Sending CLUSTER MEET messages to join the cluster"
921        join_cluster
922        # Give one second for the join to start, in order to avoid that
923        # wait_cluster_join will find all the nodes agree about the config as
924        # they are still empty with unassigned slots.
925        sleep 1
926        wait_cluster_join
927        flush_nodes_config # Useful for the replicas
928        check_cluster
929    end
930
931    def addnode_cluster_cmd(argv,opt)
932        xputs ">>> Adding node #{argv[0]} to cluster #{argv[1]}"
933
934        # Check the existing cluster
935        load_cluster_info_from_node(argv[1])
936        check_cluster
937
938        # If --master-id was specified, try to resolve it now so that we
939        # abort before starting with the node configuration.
940        if opt['slave']
941            if opt['master-id']
942                master = get_node_by_name(opt['master-id'])
943                if !master
944                    xputs "[ERR] No such master ID #{opt['master-id']}"
945                end
946            else
947                master = get_master_with_least_replicas
948                xputs "Automatically selected master #{master}"
949            end
950        end
951
952        # Add the new node
953        new = ClusterNode.new(argv[0])
954        new.connect(:abort => true)
955        new.assert_cluster
956        new.load_info
957        new.assert_empty
958        first = @nodes.first.info
959        add_node(new)
960
961        # Send CLUSTER MEET command to the new node
962        xputs ">>> Send CLUSTER MEET to node #{new} to make it join the cluster."
963        new.r.cluster("meet",first[:host],first[:port])
964
965        # Additional configuration is needed if the node is added as
966        # a slave.
967        if opt['slave']
968            wait_cluster_join
969            xputs ">>> Configure node as replica of #{master}."
970            new.r.cluster("replicate",master.info[:name])
971        end
972        xputs "[OK] New node added correctly."
973    end
974
975    def delnode_cluster_cmd(argv,opt)
976        id = argv[1].downcase
977        xputs ">>> Removing node #{id} from cluster #{argv[0]}"
978
979        # Load cluster information
980        load_cluster_info_from_node(argv[0])
981
982        # Check if the node exists and is not empty
983        node = get_node_by_name(id)
984
985        if !node
986            xputs "[ERR] No such node ID #{id}"
987            exit 1
988        end
989
990        if node.slots.length != 0
991            xputs "[ERR] Node #{node} is not empty! Reshard data away and try again."
992            exit 1
993        end
994
995        # Send CLUSTER FORGET to all the nodes but the node to remove
996        xputs ">>> Sending CLUSTER FORGET messages to the cluster..."
997        @nodes.each{|n|
998            next if n == node
999            if n.info[:replicate] && n.info[:replicate].downcase == id
1000                # Reconfigure the slave to replicate with some other node
1001                master = get_master_with_least_replicas
1002                xputs ">>> #{n} as replica of #{master}"
1003                n.r.cluster("replicate",master.info[:name])
1004            end
1005            n.r.cluster("forget",argv[1])
1006        }
1007
1008        # Finally shutdown the node
1009        xputs ">>> SHUTDOWN the node."
1010        node.r.shutdown
1011    end
1012
1013    def set_timeout_cluster_cmd(argv,opt)
1014        timeout = argv[1].to_i
1015        if timeout < 100
1016            puts "Setting a node timeout of less than 100 milliseconds is a bad idea."
1017            exit 1
1018        end
1019
1020        # Load cluster information
1021        load_cluster_info_from_node(argv[0])
1022        ok_count = 0
1023        err_count = 0
1024
1025        # Send CLUSTER FORGET to all the nodes but the node to remove
1026        xputs ">>> Reconfiguring node timeout in every cluster node..."
1027        @nodes.each{|n|
1028            begin
1029                n.r.config("set","cluster-node-timeout",timeout)
1030                n.r.config("rewrite")
1031                ok_count += 1
1032                xputs "*** New timeout set for #{n}"
1033            rescue => e
1034                puts "ERR setting node-timeot for #{n}: #{e}"
1035                err_count += 1
1036            end
1037        }
1038        xputs ">>> New node timeout set. #{ok_count} OK, #{err_count} ERR."
1039    end
1040
1041    def call_cluster_cmd(argv,opt)
1042        cmd = argv[1..-1]
1043        cmd[0] = cmd[0].upcase
1044
1045        # Load cluster information
1046        load_cluster_info_from_node(argv[0])
1047        xputs ">>> Calling #{cmd.join(" ")}"
1048        @nodes.each{|n|
1049            begin
1050                res = n.r.send(*cmd)
1051                puts "#{n}: #{res}"
1052            rescue => e
1053                puts "#{n}: #{e}"
1054            end
1055        }
1056    end
1057
1058    def import_cluster_cmd(argv,opt)
1059        source_addr = opt['from']
1060        xputs ">>> Importing data from #{source_addr} to cluster #{argv[1]}"
1061
1062        # Check the existing cluster.
1063        load_cluster_info_from_node(argv[0])
1064        check_cluster
1065
1066        # Connect to the source node.
1067        xputs ">>> Connecting to the source Redis instance"
1068        src_host,src_port = source_addr.split(":")
1069        source = Redis.new(:host =>src_host, :port =>src_port)
1070        if source.info['cluster_enabled'].to_i == 1
1071            xputs "[ERR] The source node should not be a cluster node."
1072        end
1073        xputs "*** Importing #{source.dbsize} keys from DB 0"
1074
1075        # Build a slot -> node map
1076        slots = {}
1077        @nodes.each{|n|
1078            n.slots.each{|s,_|
1079                slots[s] = n
1080            }
1081        }
1082
1083        # Use SCAN to iterate over the keys, migrating to the
1084        # right node as needed.
1085        cursor = nil
1086        while cursor != 0
1087            cursor,keys = source.scan(cursor,:count,1000)
1088            cursor = cursor.to_i
1089            keys.each{|k|
1090                # Migrate keys using the MIGRATE command.
1091                slot = key_to_slot(k)
1092                target = slots[slot]
1093                print "Migrating #{k} to #{target}: "
1094                STDOUT.flush
1095                begin
1096                    source.client.call(["migrate",target.info[:host],target.info[:port],k,0,15000])
1097                rescue => e
1098                    puts e
1099                else
1100                    puts "OK"
1101                end
1102            }
1103        end
1104    end
1105
1106    def help_cluster_cmd(argv,opt)
1107        show_help
1108        exit 0
1109    end
1110
1111    # Parse the options for the specific command "cmd".
1112    # Returns an hash populate with option => value pairs, and the index of
1113    # the first non-option argument in ARGV.
1114    def parse_options(cmd)
1115        idx = 1 ; # Current index into ARGV
1116        options={}
1117        while idx < ARGV.length && ARGV[idx][0..1] == '--'
1118            if ARGV[idx][0..1] == "--"
1119                option = ARGV[idx][2..-1]
1120                idx += 1
1121                if ALLOWED_OPTIONS[cmd] == nil || ALLOWED_OPTIONS[cmd][option] == nil
1122                    puts "Unknown option '#{option}' for command '#{cmd}'"
1123                    exit 1
1124                end
1125                if ALLOWED_OPTIONS[cmd][option]
1126                    value = ARGV[idx]
1127                    idx += 1
1128                else
1129                    value = true
1130                end
1131                options[option] = value
1132            else
1133                # Remaining arguments are not options.
1134                break
1135            end
1136        end
1137
1138        # Enforce mandatory options
1139        if ALLOWED_OPTIONS[cmd]
1140            ALLOWED_OPTIONS[cmd].each {|option,val|
1141                if !options[option] && val == :required
1142                    puts "Option '--#{option}' is required "+ \
1143                         "for subcommand '#{cmd}'"
1144                    exit 1
1145                end
1146            }
1147        end
1148        return options,idx
1149    end
1150end
1151
1152#################################################################################
1153# Libraries
1154#
1155# We try to don't depend on external libs since this is a critical part
1156# of Redis Cluster.
1157#################################################################################
1158
1159# This is the CRC16 algorithm used by Redis Cluster to hash keys.
1160# Implementation according to CCITT standards.
1161#
1162# This is actually the XMODEM CRC 16 algorithm, using the
1163# following parameters:
1164#
1165# Name                       : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN"
1166# Width                      : 16 bit
1167# Poly                       : 1021 (That is actually x^16 + x^12 + x^5 + 1)
1168# Initialization             : 0000
1169# Reflect Input byte         : False
1170# Reflect Output CRC         : False
1171# Xor constant to output CRC : 0000
1172# Output for "123456789"     : 31C3
1173
1174module RedisClusterCRC16
1175    def RedisClusterCRC16.crc16(bytes)
1176        crc = 0
1177        bytes.each_byte{|b|
1178            crc = ((crc<<8) & 0xffff) ^ XMODEMCRC16Lookup[((crc>>8)^b) & 0xff]
1179        }
1180        crc
1181    end
1182
1183private
1184    XMODEMCRC16Lookup = [
1185        0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
1186        0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
1187        0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
1188        0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
1189        0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
1190        0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
1191        0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
1192        0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
1193        0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
1194        0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
1195        0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
1196        0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
1197        0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
1198        0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
1199        0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
1200        0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
1201        0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
1202        0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
1203        0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
1204        0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
1205        0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
1206        0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
1207        0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
1208        0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
1209        0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
1210        0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
1211        0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
1212        0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
1213        0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
1214        0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
1215        0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
1216        0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
1217    ]
1218end
1219
1220# Turn a key name into the corrisponding Redis Cluster slot.
1221def key_to_slot(key)
1222    # Only hash what is inside {...} if there is such a pattern in the key.
1223    # Note that the specification requires the content that is between
1224    # the first { and the first } after the first {. If we found {} without
1225    # nothing in the middle, the whole key is hashed as usually.
1226    s = key.index "{"
1227    if s
1228        e = key.index "}",s+1
1229        if e && e != s+1
1230            key = key[s+1..e-1]
1231        end
1232    end
1233    RedisClusterCRC16.crc16(key) % 16384
1234end
1235
1236#################################################################################
1237# Definition of commands
1238#################################################################################
1239
1240COMMANDS={
1241    "create"  => ["create_cluster_cmd", -2, "host1:port1 ... hostN:portN"],
1242    "check"   => ["check_cluster_cmd", 2, "host:port"],
1243    "fix"     => ["fix_cluster_cmd", 2, "host:port"],
1244    "reshard" => ["reshard_cluster_cmd", 2, "host:port"],
1245    "add-node" => ["addnode_cluster_cmd", 3, "new_host:new_port existing_host:existing_port"],
1246    "del-node" => ["delnode_cluster_cmd", 3, "host:port node_id"],
1247    "set-timeout" => ["set_timeout_cluster_cmd", 3, "host:port milliseconds"],
1248    "call" =>    ["call_cluster_cmd", -3, "host:port command arg arg .. arg"],
1249    "import" =>  ["import_cluster_cmd", 2, "host:port"],
1250    "help"    => ["help_cluster_cmd", 1, "(show this help)"]
1251}
1252
1253ALLOWED_OPTIONS={
1254    "create" => {"replicas" => true},
1255    "add-node" => {"slave" => false, "master-id" => true},
1256    "import" => {"from" => :required}
1257}
1258
1259def show_help
1260    puts "Usage: redis-trib <command> <options> <arguments ...>\n\n"
1261    COMMANDS.each{|k,v|
1262        o = ""
1263        puts "  #{k.ljust(15)} #{v[2]}"
1264        if ALLOWED_OPTIONS[k]
1265            ALLOWED_OPTIONS[k].each{|optname,has_arg|
1266                puts "                  --#{optname}" + (has_arg ? " <arg>" : "")
1267            }
1268        end
1269    }
1270    puts "\nFor check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.\n"
1271end
1272
1273# Sanity check
1274if ARGV.length == 0
1275    show_help
1276    exit 1
1277end
1278
1279rt = RedisTrib.new
1280cmd_spec = COMMANDS[ARGV[0].downcase]
1281if !cmd_spec
1282    puts "Unknown redis-trib subcommand '#{ARGV[0]}'"
1283    exit 1
1284end
1285
1286# Parse options
1287cmd_options,first_non_option = rt.parse_options(ARGV[0].downcase)
1288rt.check_arity(cmd_spec[1],ARGV.length-(first_non_option-1))
1289
1290# Dispatch
1291rt.send(cmd_spec[0],ARGV[first_non_option..-1],cmd_options)
1292