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 )