The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package RedisDB::Sentinel;

use strict;
use warnings;
our $VERSION = "2.52";
$VERSION = eval $VERSION;

use Carp;
use RedisDB;
use Try::Tiny;

=head1 NAME

RedisDB::Sentinel - interface to redis servers managed by sentinel

=head1 SYNOPSIS

    use RedisDB::Sentinel;

    my $redis = RedisDB::Sentinel->connect_to_master(
        service => $service_name,
        sentinels => [
            {
                host => 'host1',
                port => 26379
            },
            {
                host => 'host2',
                port => 26379
            },
        ],
    );
    $redis->set( $key, $value );
    my $value = $redis->get($key);

=head1 DESCRIPTION

This module provides interface to access redis servers managed by sentinels, it
handles communication with sentinels and dispatches commands to the master
redis.

=head1 METHODS

=cut

=head2 $class->connect_to_master(%params)

retrieve information about master from sentinel and return RedisDB object
connected to master. Additionally sets on_conect_error handler to retrieve
information about new master in case of reconnect. Requires two parameters:

=over 4

=item B<service>

service name to which you want to connect

=item B<sentinels>

list of sentinels. Each element is a hash with "host" and "port" elements.

=back

Other parameters are passed as is to constructor of RedisDB object. Note, that
host, port, and on_connect_error parameters will be overwritten by
RedisDB::Sentinel

=cut

sub connect_to_master {
    my ( $class, %args ) = @_;

    my $service = delete $args{service}
      or croak '"service" parameter is required';
    my @sentinels = @{ delete $args{sentinels} }
      or croak '"sentinels" parameter is required';

    my ( $host, $port ) = _get_master_from_sentinel( $service, \@sentinels );
    return RedisDB->new(
        %args,
        host             => $host,
        port             => $port,
        on_connect_error => sub {
            my ( $redis, $error ) = @_;
            my ( $host, $port ) = _get_master_from_sentinel( $service, \@sentinels );
            $redis->{host} = $host;
            $redis->{port} = $port;
            return;
        },
    );
}

sub _get_master_from_sentinel {
    my ( $service, $sentinels ) = @_;

    for ( 1 .. @$sentinels ) {
        my $master = try {
            my $sentinel = RedisDB->new( %{ $sentinels->[0] } );
            $sentinel->execute( 'sentinel', 'get-master-addr-by-name', $service );
        };
        return @$master if $master;
        push @$sentinels, shift @$sentinels;
    }
}

1;

__END__

=head1 AUTHOR

Pavel Shaydo, C<< <zwon at cpan.org> >>

=head1 LICENSE AND COPYRIGHT

Copyright 2011-2016 Pavel Shaydo.

This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.

=cut