Apache-Session-Browseable
view release on metacpan or search on metacpan
lib/Apache/Session/Browseable/Store/Patroni.pm view on Meta::CPAN
}
sub update {
my $self = shift;
return $self->_try( 'update', @_ );
}
sub materialize {
my $self = shift;
return $self->_try( 'materialize', @_ );
}
sub remove {
my $self = shift;
return $self->_try( 'remove', @_ );
}
sub checkMaster {
my ( $self, $args ) = @_;
delete $self->{failure};
my $originalDataSource =
$self->{_originalDataSource} || $args->{DataSource};
my $cache = $patroniCache{$originalDataSource} ||= {};
# Circuit breaker: avoid hammering Patroni API if it's failing
my $circuitBreakerDelay = $args->{PatroniCircuitBreakerDelay} || 30;
if ( $cache->{lastFailure}
and ( time() - $cache->{lastFailure} ) < $circuitBreakerDelay )
{
# Circuit breaker active, try cached leader as fallback
return $self->_useCachedLeader( $args, $originalDataSource,
"Circuit breaker active" );
}
require JSON;
require LWP::UserAgent;
require IO::Socket::SSL;
# SSL verification: secure by default, can be disabled with PatroniVerifySSL => 0
my $verify_ssl = $args->{PatroniVerifySSL} // 1;
my %ssl_opts;
if ($verify_ssl) {
%ssl_opts = (
verify_hostname => 1,
SSL_verify_mode => &IO::Socket::SSL::SSL_VERIFY_PEER,
( $args->{PatroniSSLCAFile} ? ( SSL_ca_file => $args->{PatroniSSLCAFile} ) : () ),
( $args->{PatroniSSLCAPath} ? ( SSL_ca_path => $args->{PatroniSSLCAPath} ) : () ),
);
}
else {
%ssl_opts = (
verify_hostname => 0,
SSL_verify_mode => &IO::Socket::SSL::SSL_VERIFY_NONE,
);
}
my $ua = LWP::UserAgent->new(
env_proxy => 1,
ssl_opts => \%ssl_opts,
timeout => $args->{PatroniTimeout} || 3,
);
my $res;
foreach my $patroniUrl ( split /[,\s]\s*/,
( $args->{PatroniUrl} || $args->{patroniUrl} ) )
{
my $resp = $ua->get($patroniUrl);
if ( $resp->is_success ) {
my $c = eval { JSON::from_json( $resp->decoded_content ) };
if ( $@ or !$c->{members} or ref( $c->{members} ) ne 'ARRAY' ) {
print STDERR "Bad response from $patroniUrl: "
. $resp->decoded_content . "\n";
next;
}
my @leaders = grep { $_->{role} eq 'leader' } @{ $c->{members} };
# Check for split-brain scenario
if ( @leaders > 1 ) {
my $leadersList =
join( ', ', map { "$_->{host}:$_->{port}" } @leaders );
print STDERR
"Multiple leaders detected (split-brain) from $patroniUrl"
. " - Leaders: $leadersList\n";
next;
}
my ($leader) = @leaders;
unless ($leader) {
print STDERR "No leader found from $patroniUrl: "
. $resp->decoded_content . "\n";
next;
}
# Validate leader has required fields
unless ( defined $leader->{host} && defined $leader->{port} ) {
print STDERR "Leader missing host or port from $patroniUrl: "
. $resp->decoded_content . "\n";
next;
}
# Check leader health state
if ( $leader->{state} && $leader->{state} ne 'running' ) {
print STDERR
"Leader not in running state (state=$leader->{state})"
. " from $patroniUrl\n";
next;
}
# Cache the leader info
$cache->{leader} = {
host => $leader->{host},
port => $leader->{port},
time => time()
};
# Reset circuit breaker on success
delete $cache->{lastFailure};
$args->{DataSource} =
( run in 1.183 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )