+ node.friends.each{|f|
+ fnode = ClusterNode.new(f[:addr])
+ fnode.connect()
+ fnode.load_info()
+ add_node(fnode)
+ }
+ end
+
+ # Given a list of source nodes return a "resharding plan"
+ # with what slots to move in order to move "numslots" slots to another
+ # instance.
+ def compute_reshard_table(sources,numslots)
+ moved = []
+ # Sort from bigger to smaller instance, for two reasons:
+ # 1) If we take less slots than instanes it is better to start getting from
+ # the biggest instances.
+ # 2) We take one slot more from the first instance in the case of not perfect
+ # divisibility. Like we have 3 nodes and need to get 10 slots, we take
+ # 4 from the first, and 3 from the rest. So the biggest is always the first.
+ sources = sources.sort{|a,b| b.slots.length <=> a.slots.length}
+ sources.each_with_index{|s,i|
+ # Every node will provide a number of slots proportional to the
+ # slots it has assigned.
+ n = (numslots.to_f/4096*s.slots.length)
+ if i == 0
+ n = n.ceil
+ else
+ n = n.floor
+ end
+ s.slots.keys.sort[(0...n)].each{|slot|
+ if moved.length < numslots
+ moved << {:source => s, :slot => slot}
+ end
+ }
+ }
+ return moved
+ end
+
+ def show_reshard_table(table)
+ table.each{|e|
+ puts " Moving slot #{e[:slot]} from #{e[:source].info[:name]}"
+ }
+ end
+
+ def move_slot(source,target,slot,o={})
+ # We start marking the slot as importing in the destination node,
+ # and the slot as migrating in the target host. Note that the order of
+ # the operations is important, as otherwise a client may be redirected to
+ # the target node that does not yet know it is importing this slot.
+ print "Moving slot #{slot}: "; STDOUT.flush
+ target.r.cluster("setslot",slot,"importing",source.info[:name])
+ source.r.cluster("setslot",slot,"migrating",source.info[:name])
+ # Migrate all the keys from source to target using the MIGRATE command
+ while true
+ keys = source.r.cluster("getkeysinslot",slot,10)
+ break if keys.length == 0
+ keys.each{|key|
+ source.r.migrate(target.info[:host],target.info[:port],key,0,1)
+ print "." if o[:verbose]
+ STDOUT.flush
+ }
+ end
+ puts
+ # Set the new node as the owner of the slot in all the known nodes.
+ @nodes.each{|n|
+ n.r.cluster("setslot",slot,"node",target.info[:name])
+ }
+ end
+
+ # redis-trib subcommands implementations
+
+ def check_cluster_cmd
+ load_cluster_info_from_node(ARGV[1])