class Redis::Client::Connector::Sentinel
def check(client)
def check(client) # Check the instance is really of the role we are looking for. # We can't assume the command is supported since it was introduced # recently and this client should work with old stuff. begin role = client.call([:role])[0] rescue Redis::CommandError # Assume the test is passed if we can't get a reply from ROLE... role = @role end if role != @role client.disconnect raise ConnectionError, "Instance role mismatch. Expected #{@role}, got #{role}." end end
def initialize(options)
def initialize(options) super(options) @options[:password] = DEFAULTS.fetch(:password) @options[:db] = DEFAULTS.fetch(:db) @sentinels = @options.delete(:sentinels).dup @role = @options.fetch(:role, "master").to_s @master = @options[:host] end
def resolve
def resolve result = case @role when "master" resolve_master when "slave" resolve_slave else raise ArgumentError, "Unknown instance role #{@role}" end result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.") end
def resolve_master
def resolve_master sentinel_detect do |client| if reply = client.call(["sentinel", "get-master-addr-by-name", @master]) {:host => reply[0], :port => reply[1]} end end end
def resolve_slave
def resolve_slave sentinel_detect do |client| if reply = client.call(["sentinel", "slaves", @master]) slave = Hash[*reply.sample] {:host => slave.fetch("ip"), :port => slave.fetch("port")} end end end
def sentinel_detect
def sentinel_detect @sentinels.each do |sentinel| client = Client.new(@options.merge({ :host => sentinel[:host], :port => sentinel[:port], :reconnect_attempts => 0, })) begin if result = yield(client) # This sentinel responded. Make sure we ask it first next time. @sentinels.delete(sentinel) @sentinels.unshift(sentinel) return result end rescue BaseConnectionError ensure client.disconnect end end raise CannotConnectError, "No sentinels available." end