Blame SOURCES/bz1872754-pgsqlms-new-ra.patch

0c4b27
diff --color -uNr a/doc/man/Makefile.am b/doc/man/Makefile.am
0c4b27
--- a/doc/man/Makefile.am	2021-04-12 12:51:56.831835953 +0200
0c4b27
+++ b/doc/man/Makefile.am	2021-04-13 13:38:14.198361848 +0200
0c4b27
@@ -154,6 +154,7 @@
0c4b27
                           ocf_heartbeat_ovsmonitor.7 \
0c4b27
                           ocf_heartbeat_pgagent.7 \
0c4b27
                           ocf_heartbeat_pgsql.7 \
0c4b27
+                          ocf_heartbeat_pgsqlms.7 \
0c4b27
                           ocf_heartbeat_pingd.7 \
0c4b27
                           ocf_heartbeat_podman.7 \
0c4b27
                           ocf_heartbeat_portblock.7 \
0c4b27
diff --color -uNr a/heartbeat/Makefile.am b/heartbeat/Makefile.am
0c4b27
--- a/heartbeat/Makefile.am	2021-04-12 12:51:56.831835953 +0200
0c4b27
+++ b/heartbeat/Makefile.am	2021-04-13 13:37:45.741292178 +0200
0c4b27
@@ -149,6 +149,7 @@
0c4b27
 			ovsmonitor		\
0c4b27
 			pgagent			\
0c4b27
 			pgsql			\
0c4b27
+			pgsqlms			\
0c4b27
 			pingd			\
0c4b27
 			podman			\
0c4b27
 			portblock		\
0c4b27
@@ -209,7 +210,10 @@
0c4b27
 			  mysql-common.sh	\
0c4b27
 			  nfsserver-redhat.sh	\
0c4b27
 			  findif.sh		\
0c4b27
-			  ocf.py
0c4b27
+			  ocf.py		\
0c4b27
+			  OCF_Directories.pm	\
0c4b27
+			  OCF_Functions.pm	\
0c4b27
+			  OCF_ReturnCodes.pm
0c4b27
 
0c4b27
 # Legacy locations
0c4b27
 hbdir			= $(sysconfdir)/ha.d
0c4b27
diff --color -uNr a/heartbeat/OCF_Directories.pm b/heartbeat/OCF_Directories.pm
0c4b27
--- a/heartbeat/OCF_Directories.pm	1970-01-01 01:00:00.000000000 +0100
0c4b27
+++ b/heartbeat/OCF_Directories.pm	2021-04-13 13:37:35.621267404 +0200
0c4b27
@@ -0,0 +1,139 @@
0c4b27
+#!/usr/bin/perl
0c4b27
+# This program is open source, licensed under the PostgreSQL License.
0c4b27
+# For license terms, see the LICENSE file.
0c4b27
+#
0c4b27
+# Copyright (C) 2016-2020: Jehan-Guillaume de Rorthais and Mael Rimbault
0c4b27
+
0c4b27
+=head1 NAME
0c4b27
+
0c4b27
+OCF_Directories - Binaries and binary options for use in Resource Agents
0c4b27
+
0c4b27
+=head1 SYNOPSIS
0c4b27
+
0c4b27
+  use FindBin;
0c4b27
+  use lib "$FindBin::RealBin/../../lib/heartbeat/";
0c4b27
+  
0c4b27
+  use OCF_Directories;
0c4b27
+
0c4b27
+=head1 DESCRIPTION
0c4b27
+
0c4b27
+This module has been ported from the ocf-directories shell script of the
0c4b27
+resource-agents project. See L<https://github.com/ClusterLabs/resource-agents/>.
0c4b27
+
0c4b27
+=head1 VARIABLES
0c4b27
+
0c4b27
+Here are the variables exported by this module:
0c4b27
+
0c4b27
+=over
0c4b27
+
0c4b27
+=item $INITDIR
0c4b27
+
0c4b27
+=item $HA_DIR
0c4b27
+
0c4b27
+=item $HA_RCDIR
0c4b27
+
0c4b27
+=item $HA_CONFDIR
0c4b27
+
0c4b27
+=item $HA_CF
0c4b27
+
0c4b27
+=item $HA_VARLIB
0c4b27
+
0c4b27
+=item $HA_RSCTMP
0c4b27
+
0c4b27
+=item $HA_RSCTMP_OLD
0c4b27
+
0c4b27
+=item $HA_FIFO
0c4b27
+
0c4b27
+=item $HA_BIN
0c4b27
+
0c4b27
+=item $HA_SBIN_DIR
0c4b27
+
0c4b27
+=item $HA_DATEFMT
0c4b27
+
0c4b27
+=item $HA_DEBUGLOG
0c4b27
+
0c4b27
+=item $HA_RESOURCEDIR
0c4b27
+
0c4b27
+=item $HA_DOCDIR
0c4b27
+
0c4b27
+=item $__SCRIPT_NAME
0c4b27
+
0c4b27
+=item $HA_VARRUN
0c4b27
+
0c4b27
+=item $HA_VARLOCK
0c4b27
+
0c4b27
+=item $ocf_prefix
0c4b27
+
0c4b27
+=item $ocf_exec_prefix
0c4b27
+
0c4b27
+=back
0c4b27
+
0c4b27
+=cut
0c4b27
+
0c4b27
+package OCF_Directories;
0c4b27
+
0c4b27
+use strict;
0c4b27
+use warnings;
0c4b27
+use 5.008;
0c4b27
+use File::Basename;
0c4b27
+
0c4b27
+BEGIN {
0c4b27
+    use Exporter;
0c4b27
+
0c4b27
+
0c4b27
+    our $VERSION   = 'v2.3.0';
0c4b27
+    our @ISA       = ('Exporter');
0c4b27
+    our @EXPORT    = qw(
0c4b27
+        $INITDIR
0c4b27
+        $HA_DIR
0c4b27
+        $HA_RCDIR
0c4b27
+        $HA_CONFDIR
0c4b27
+        $HA_CF
0c4b27
+        $HA_VARLIB
0c4b27
+        $HA_RSCTMP
0c4b27
+        $HA_RSCTMP_OLD
0c4b27
+        $HA_FIFO
0c4b27
+        $HA_BIN
0c4b27
+        $HA_SBIN_DIR
0c4b27
+        $HA_DATEFMT
0c4b27
+        $HA_DEBUGLOG
0c4b27
+        $HA_RESOURCEDIR
0c4b27
+        $HA_DOCDIR
0c4b27
+        $__SCRIPT_NAME
0c4b27
+        $HA_VARRUN
0c4b27
+        $HA_VARLOCK
0c4b27
+        $ocf_prefix
0c4b27
+        $ocf_exec_prefix
0c4b27
+    );
0c4b27
+    our @EXPORT_OK = ( @EXPORT );
0c4b27
+}
0c4b27
+
0c4b27
+our $INITDIR         = ( $ENV{'INITDIR'}       || '/etc/init.d' );
0c4b27
+our $HA_DIR          = ( $ENV{'HA_DIR'}        || '/etc/ha.d' );
0c4b27
+our $HA_RCDIR        = ( $ENV{'HA_RCDIR'}      || '/etc/ha.d/rc.d' );
0c4b27
+our $HA_CONFDIR      = ( $ENV{'HA_CONFDIR'}    || '/etc/ha.d/conf' );
0c4b27
+our $HA_CF           = ( $ENV{'HA_CF'}         || '/etc/ha.d/ha.cf' );
0c4b27
+our $HA_VARLIB       = ( $ENV{'HA_VARLIB'}     || '/var/lib/heartbeat' );
0c4b27
+our $HA_RSCTMP       = ( $ENV{'HA_RSCTMP'}     || '/run/resource-agents' );
0c4b27
+our $HA_RSCTMP_OLD   = ( $ENV{'HA_RSCTMP_OLD'} || '/var/run/heartbeat/rsctmp' );
0c4b27
+our $HA_FIFO         = ( $ENV{'HA_FIFO'}       || '/var/lib/heartbeat/fifo' );
0c4b27
+our $HA_BIN          = ( $ENV{'HA_BIN'}        || '/usr/libexec/heartbeat' );
0c4b27
+our $HA_SBIN_DIR     = ( $ENV{'HA_SBIN_DIR'}   || '/usr/sbin' );
0c4b27
+our $HA_DATEFMT      = ( $ENV{'HA_DATEFMT'}    || '%b %d %T ' );
0c4b27
+our $HA_DEBUGLOG     = ( $ENV{'HA_DEBUGLOG'}   || '/dev/null' );
0c4b27
+our $HA_RESOURCEDIR  = ( $ENV{'HA_RESOURCEDIR'}|| '/etc/ha.d/resource.d' );
0c4b27
+our $HA_DOCDIR       = ( $ENV{'HA_DOCDIR'}     || '/usr/share/doc/heartbeat' );
0c4b27
+our $__SCRIPT_NAME   = ( $ENV{'__SCRIPT_NAME'} || fileparse($0) );
0c4b27
+our $HA_VARRUN       = ( $ENV{'HA_VARRUN'}     || '/var/run' );
0c4b27
+our $HA_VARLOCK      = ( $ENV{'HA_VARLOCK'}    || '/var/lock/subsys' );
0c4b27
+our $ocf_prefix      = '/usr';
0c4b27
+our $ocf_exec_prefix = '/usr';
0c4b27
+
0c4b27
+1;
0c4b27
+
0c4b27
+=head1 COPYRIGHT AND LICENSE
0c4b27
+
0c4b27
+Copyright (C) 2016: Jehan-Guillaume de Rorthais and Mael Rimbault.
0c4b27
+
0c4b27
+Licensed under the PostgreSQL License.
0c4b27
+
0c4b27
diff --color -uNr a/heartbeat/OCF_Functions.pm b/heartbeat/OCF_Functions.pm
0c4b27
--- a/heartbeat/OCF_Functions.pm	1970-01-01 01:00:00.000000000 +0100
0c4b27
+++ b/heartbeat/OCF_Functions.pm	2021-04-13 13:37:35.621267404 +0200
0c4b27
@@ -0,0 +1,631 @@
0c4b27
+#!/usr/bin/perl
0c4b27
+# This program is open source, licensed under the PostgreSQL License.
0c4b27
+# For license terms, see the LICENSE file.
0c4b27
+#
0c4b27
+# Copyright (C) 2016-2020: Jehan-Guillaume de Rorthais and Mael Rimbault
0c4b27
+
0c4b27
+=head1 NAME
0c4b27
+
0c4b27
+OCF_Functions - helper subroutines for OCF agent
0c4b27
+
0c4b27
+=head1 SYNOPSIS
0c4b27
+
0c4b27
+  use FindBin;
0c4b27
+  use lib "$FindBin::RealBin/../../lib/heartbeat/";
0c4b27
+  
0c4b27
+  use OCF_Functions;
0c4b27
+
0c4b27
+=head1 DESCRIPTION
0c4b27
+
0c4b27
+This module has been ported from the ocf-shellfuncs shell script of the
0c4b27
+resource-agents project. See L<https://github.com/ClusterLabs/resource-agents/>.
0c4b27
+
0c4b27
+=head1 VARIABLE
0c4b27
+
0c4b27
+The only variable exported by this module is C<__OCF_ACTION>.
0c4b27
+
0c4b27
+=head1 SUBROUTINES
0c4b27
+
0c4b27
+Here are the subroutines ported from ocf-shellfuncs and exported by this module:
0c4b27
+
0c4b27
+=over
0c4b27
+
0c4b27
+=item ha_debug
0c4b27
+
0c4b27
+=item ha_log
0c4b27
+
0c4b27
+=item hadate
0c4b27
+
0c4b27
+=item ocf_is_clone
0c4b27
+
0c4b27
+=item ocf_is_ms
0c4b27
+
0c4b27
+=item ocf_is_probe
0c4b27
+
0c4b27
+=item ocf_is_root
0c4b27
+
0c4b27
+=item ocf_is_true
0c4b27
+
0c4b27
+=item ocf_is_ver
0c4b27
+
0c4b27
+=item ocf_local_nodename
0c4b27
+
0c4b27
+=item ocf_log
0c4b27
+
0c4b27
+=item ocf_exit_reason
0c4b27
+
0c4b27
+=item ocf_maybe_random
0c4b27
+
0c4b27
+=item ocf_ver2num
0c4b27
+
0c4b27
+=item ocf_ver_complete_level
0c4b27
+
0c4b27
+=item ocf_ver_level
0c4b27
+
0c4b27
+=item ocf_version_cmp
0c4b27
+
0c4b27
+=item set_logtag
0c4b27
+
0c4b27
+=back
0c4b27
+
0c4b27
+Here are the subroutines only existing in the perl module but not in the
0c4b27
+ocf-shellfuncs script:
0c4b27
+
0c4b27
+=over
0c4b27
+
0c4b27
+=item ocf_notify_env
0c4b27
+
0c4b27
+=back
0c4b27
+
0c4b27
+=cut
0c4b27
+
0c4b27
+package OCF_Functions;
0c4b27
+
0c4b27
+use strict;
0c4b27
+use warnings;
0c4b27
+use 5.008;
0c4b27
+use POSIX qw( strftime setlocale LC_ALL );
0c4b27
+use English;
0c4b27
+
0c4b27
+use FindBin;
0c4b27
+use lib "$FindBin::RealBin/../../lib/heartbeat/";
0c4b27
+
0c4b27
+use OCF_ReturnCodes;
0c4b27
+use OCF_Directories;
0c4b27
+
0c4b27
+BEGIN {
0c4b27
+    use Exporter;
0c4b27
+
0c4b27
+    our $VERSION   = 'v2.3.0';
0c4b27
+    our @ISA       = ('Exporter');
0c4b27
+    our @EXPORT    = qw(
0c4b27
+        $__OCF_ACTION
0c4b27
+        ocf_is_root
0c4b27
+        ocf_maybe_random
0c4b27
+        ocf_is_true
0c4b27
+        hadate
0c4b27
+        set_logtag
0c4b27
+        ha_log
0c4b27
+        ha_debug
0c4b27
+        ocf_log
0c4b27
+        ocf_exit_reason
0c4b27
+        ocf_is_probe
0c4b27
+        ocf_is_clone
0c4b27
+        ocf_is_ms
0c4b27
+        ocf_is_ver
0c4b27
+        ocf_ver2num
0c4b27
+        ocf_ver_level
0c4b27
+        ocf_ver_complete_level
0c4b27
+        ocf_version_cmp
0c4b27
+        ocf_local_nodename
0c4b27
+        ocf_notify_env
0c4b27
+    );
0c4b27
+    our @EXPORT_OK = ( @EXPORT );
0c4b27
+}
0c4b27
+
0c4b27
+our $__OCF_ACTION;
0c4b27
+
0c4b27
+sub ocf_is_root {
0c4b27
+    return $EUID == 0;
0c4b27
+}
0c4b27
+
0c4b27
+sub ocf_maybe_random {
0c4b27
+    return int( rand( 32767 ) );
0c4b27
+}
0c4b27
+
0c4b27
+sub ocf_is_true {
0c4b27
+    my $v = shift;
0c4b27
+    return ( defined $v and $v =~ /^(?:yes|true|1|YES|TRUE|ja|on|ON)$/ );
0c4b27
+}
0c4b27
+
0c4b27
+sub hadate {
0c4b27
+  return strftime( $HA_DATEFMT, localtime );
0c4b27
+}
0c4b27
+
0c4b27
+sub set_logtag {
0c4b27
+
0c4b27
+    return if defined $ENV{'HA_LOGTAG'} and $ENV{'HA_LOGTAG'} ne '';
0c4b27
+
0c4b27
+    if ( defined $ENV{'OCF_RESOURCE_INSTANCE'} and $ENV{'OCF_RESOURCE_INSTANCE'} ne '' ) {
0c4b27
+        $ENV{'HA_LOGTAG'} = "$__SCRIPT_NAME($ENV{'OCF_RESOURCE_INSTANCE'})[$PID]";
0c4b27
+    }
0c4b27
+    else {
0c4b27
+        $ENV{'HA_LOGTAG'}="${__SCRIPT_NAME}[$PID]";
0c4b27
+    }
0c4b27
+}
0c4b27
+
0c4b27
+sub __ha_log {
0c4b27
+    my $ignore_stderr = 0;
0c4b27
+    my $loglevel      = '';
0c4b27
+
0c4b27
+    if ( $_[0] eq '--ignore-stderr' ) {
0c4b27
+        $ignore_stderr = 1;
0c4b27
+        shift;
0c4b27
+    }
0c4b27
+
0c4b27
+    $ENV{'HA_LOGFACILITY'} = '' if not defined $ENV{'HA_LOGFACILITY'}
0c4b27
+        or $ENV{'HA_LOGFACILITY'} eq 'none';
0c4b27
+
0c4b27
+    # if we're connected to a tty, then output to stderr
0c4b27
+    if ( -t STDERR ) {
0c4b27
+        # FIXME
0c4b27
+        # T.N.: this was ported with the bug on $loglevel being empty
0c4b27
+        # and never set before the test here...
0c4b27
+        if ( defined $ENV{'HA_debug'}
0c4b27
+             and $ENV{'HA_debug'} == 0
0c4b27
+             and $loglevel eq 'debug'
0c4b27
+        ) {
0c4b27
+            return 0;
0c4b27
+        }
0c4b27
+        elsif ( $ignore_stderr ) {
0c4b27
+            # something already printed this error to stderr, so ignore
0c4b27
+            return 0;
0c4b27
+        }
0c4b27
+        if ( defined $ENV{'HA_LOGTAG'} and $ENV{'HA_LOGTAG'} ne '' ) {
0c4b27
+            printf STDERR "%s: %s\n", $ENV{'HA_LOGTAG'}, join ' ', @ARG;
0c4b27
+        }
0c4b27
+        else {
0c4b27
+            printf STDERR "%s\n", join ' ', @ARG;
0c4b27
+        }
0c4b27
+        return 0;
0c4b27
+    }
0c4b27
+
0c4b27
+    set_logtag();
0c4b27
+
0c4b27
+    if ( defined $ENV{'HA_LOGD'} and $ENV{'HA_LOGD'} eq 'yes' ) {
0c4b27
+        system 'ha_logger', '-t', $ENV{'HA_LOGTAG'}, @ARG;
0c4b27
+        return 0 if ( $? >> 8 ) == 0;
0c4b27
+    }
0c4b27
+
0c4b27
+    unless ( $ENV{'HA_LOGFACILITY'} eq '' ) {
0c4b27
+        # logging through syslog
0c4b27
+        # loglevel is unknown, use 'notice' for now
0c4b27
+        $loglevel = 'notice';
0c4b27
+        for ( "@ARG" ) {
0c4b27
+            if ( /ERROR/ ) {
0c4b27
+                $loglevel = 'err';
0c4b27
+            }
0c4b27
+            elsif ( /WARN/ ) {
0c4b27
+                $loglevel = 'warning';
0c4b27
+            }
0c4b27
+            elsif (/INFO|info/ ) {
0c4b27
+                $loglevel = 'info';
0c4b27
+            }
0c4b27
+        }
0c4b27
+
0c4b27
+        system 'logger', '-t', $ENV{'HA_LOGTAG'}, '-p',
0c4b27
+            "$ENV{'HA_LOGFACILITY'}.$loglevel", @ARG;
0c4b27
+    }
0c4b27
+
0c4b27
+    if ( defined $ENV{'HA_LOGFILE'} and $ENV{'HA_LOGFILE'} ne '' ) {
0c4b27
+        # appending to $HA_LOGFILE
0c4b27
+        open my $logfile, '>>', $ENV{'HA_LOGFILE'};
0c4b27
+        printf $logfile "%s:	%s %s\n", $ENV{'HA_LOGTAG'}, hadate(),
0c4b27
+            join (' ', @ARG);
0c4b27
+        close $logfile;
0c4b27
+    }
0c4b27
+
0c4b27
+    # appending to stderr
0c4b27
+    printf STDERR "%s %s\n", hadate(), join ' ', @ARG
0c4b27
+        if (not defined $ENV{'HA_LOGFACILITY'} or $ENV{'HA_LOGFACILITY'} eq '')
0c4b27
+            and (not defined $ENV{'HA_LOGFILE'} or $ENV{'HA_LOGFILE'} eq '' )
0c4b27
+            and not $ignore_stderr;
0c4b27
+
0c4b27
+    if ( defined $ENV{'HA_DEBUGLOG'} and $ENV{'HA_DEBUGLOG'} ne ''
0c4b27
+        and $ENV{'HA_LOGFILE'} ne $ENV{'HA_DEBUGLOG'}
0c4b27
+    ) {
0c4b27
+        # appending to $HA_DEBUGLOG
0c4b27
+        open my $logfile, '>>', $ENV{'HA_DEBUGLOG'};
0c4b27
+        printf $logfile "%s:	%s %s\n", $ENV{'HA_LOGTAG'}, hadate(),
0c4b27
+            join (' ', @ARG);
0c4b27
+        close $logfile;
0c4b27
+    }
0c4b27
+}
0c4b27
+
0c4b27
+sub ha_log {
0c4b27
+    return __ha_log( @ARG );
0c4b27
+}
0c4b27
+
0c4b27
+sub ha_debug {
0c4b27
+
0c4b27
+    return 0 if defined $ENV{'HA_debug'} and $ENV{'HA_debug'} == 0;
0c4b27
+
0c4b27
+    if ( -t STDERR ) {
0c4b27
+        if ( defined $ENV{'HA_LOGTAG'} and $ENV{'HA_LOGTAG'} ne '' ) {
0c4b27
+            printf STDERR "%s: %s\n", $ENV{'HA_LOGTAG'}, join ' ', @ARG;
0c4b27
+        }
0c4b27
+        else {
0c4b27
+            printf STDERR "%s\n", join ' ', @ARG;
0c4b27
+        }
0c4b27
+        
0c4b27
+        return 0;
0c4b27
+    }
0c4b27
+
0c4b27
+    set_logtag();
0c4b27
+
0c4b27
+    if ( defined $ENV{'HA_LOGD'} and $ENV{'HA_LOGD'} eq 'yes' ) {
0c4b27
+        system 'ha_logger', '-t', $ENV{'HA_LOGTAG'}, '-D', 'ha-debug', @ARG;
0c4b27
+        return 0 if ( $? >> 8 ) == 0;
0c4b27
+    }
0c4b27
+
0c4b27
+    $ENV{'HA_LOGFACILITY'} = '' if not defined $ENV{'HA_LOGFACILITY'}
0c4b27
+        or $ENV{'HA_LOGFACILITY'} eq 'none';
0c4b27
+
0c4b27
+    unless ( $ENV{'HA_LOGFACILITY'} eq '' ) {
0c4b27
+        # logging through syslog
0c4b27
+
0c4b27
+        system 'logger', '-t', $ENV{'HA_LOGTAG'}, '-p',
0c4b27
+            "$ENV{'HA_LOGFACILITY'}.debug", @ARG;
0c4b27
+    }
0c4b27
+
0c4b27
+    if ( defined $ENV{'HA_DEBUGLOG'} and -f $ENV{'HA_DEBUGLOG'} ) {
0c4b27
+        my $logfile;
0c4b27
+        # appending to $HA_DEBUGLOG
0c4b27
+        open $logfile, '>>', $ENV{'HA_DEBUGLOG'};
0c4b27
+        printf $logfile "%s:	%s %s\n", $ENV{'HA_LOGTAG'}, hadate(),
0c4b27
+            join (' ', @ARG);
0c4b27
+        close $logfile;
0c4b27
+    }
0c4b27
+
0c4b27
+    # appending to stderr
0c4b27
+    printf STDERR "%s: %s %s\n", $ENV{'HA_LOGTAG'}, hadate(), join ' ', @ARG
0c4b27
+        if (not defined $ENV{'HA_LOGFACILITY'} or $ENV{'HA_LOGFACILITY'} eq '')
0c4b27
+            and (not defined $ENV{'HA_DEBUGLOG'} or $ENV{'HA_DEBUGLOG'} eq '' );
0c4b27
+}
0c4b27
+
0c4b27
+#
0c4b27
+# ocf_log: log messages from the resource agent
0c4b27
+# This function is slightly different from its equivalent in ocf-shellfuncs.in
0c4b27
+# as it behaves like printf.
0c4b27
+# Arguments:
0c4b27
+#   * __OCF_PRIO: log level
0c4b27
+#   * __OCF_MSG:  printf-like format string
0c4b27
+#   * all other arguments are values for the printf-like format string
0c4b27
+#
0c4b27
+sub ocf_log {
0c4b27
+    my $__OCF_PRIO;
0c4b27
+    my $__OCF_MSG;
0c4b27
+
0c4b27
+    # TODO: Revisit and implement internally.
0c4b27
+    if ( scalar @ARG < 2 ) {
0c4b27
+        ocf_log ( 'err', "Not enough arguments [%d] to ocf_log", scalar @ARG );
0c4b27
+    }
0c4b27
+
0c4b27
+    $__OCF_PRIO = shift;
0c4b27
+    $__OCF_MSG  = shift;
0c4b27
+    $__OCF_MSG  = sprintf $__OCF_MSG, @ARG;
0c4b27
+
0c4b27
+    for ( $__OCF_PRIO ) {
0c4b27
+        if    ( /crit/  ) { $__OCF_PRIO = 'CRIT'    }
0c4b27
+        elsif ( /err/   ) { $__OCF_PRIO = 'ERROR'   }
0c4b27
+        elsif ( /warn/  ) { $__OCF_PRIO = 'WARNING' }
0c4b27
+        elsif ( /info/  ) { $__OCF_PRIO = 'INFO'    }
0c4b27
+        elsif ( /debug/ ) { $__OCF_PRIO = 'DEBUG'   }
0c4b27
+        else  { $__OCF_PRIO =~ tr/[a-z]/[A-Z]/ }
0c4b27
+    }
0c4b27
+
0c4b27
+    if ( $__OCF_PRIO eq 'DEBUG' ) {
0c4b27
+        ha_debug( "$__OCF_PRIO: $__OCF_MSG");
0c4b27
+    }
0c4b27
+    else {
0c4b27
+        ha_log( "$__OCF_PRIO: $__OCF_MSG");
0c4b27
+    }
0c4b27
+}
0c4b27
+
0c4b27
+
0c4b27
+#
0c4b27
+# ocf_exit_reason: print exit error string to stderr and log
0c4b27
+# Usage:           Allows the OCF script to provide a string
0c4b27
+#                  describing why the exit code was returned.
0c4b27
+# Arguments:       reason - required, The string that represents
0c4b27
+#                  why the error occured.
0c4b27
+#
0c4b27
+sub ocf_exit_reason {
0c4b27
+    my $cookie = $ENV{'OCF_EXIT_REASON_PREFIX'} || 'ocf-exit-reason:';
0c4b27
+    my $fmt;
0c4b27
+    my $msg;
0c4b27
+
0c4b27
+    # No argument is likely not intentional.
0c4b27
+    # Just one argument implies a printf format string of just "%s".
0c4b27
+    # "Least surprise" in case some interpolated string from variable
0c4b27
+    # expansion or other contains a percent sign.
0c4b27
+    # More than one argument: first argument is going to be the format string.
0c4b27
+    ocf_log ( 'err', 'Not enough arguments [%d] to ocf_exit_reason',
0c4b27
+        scalar @ARG ) if scalar @ARG < 1;
0c4b27
+
0c4b27
+    $fmt = shift;
0c4b27
+    $msg = sprintf $fmt, @ARG;
0c4b27
+
0c4b27
+    print STDERR "$cookie$msg\n";
0c4b27
+    __ha_log( '--ignore-stderr', "ERROR: $msg" );
0c4b27
+}
0c4b27
+
0c4b27
+# returns true if the CRM is currently running a probe. A probe is
0c4b27
+# defined as a monitor operation with a monitoring interval of zero.
0c4b27
+sub ocf_is_probe {
0c4b27
+    return ( $__OCF_ACTION eq 'monitor'
0c4b27
+        and $ENV{'OCF_RESKEY_CRM_meta_interval'} == 0 );
0c4b27
+}
0c4b27
+
0c4b27
+# returns true if the resource is configured as a clone. This is
0c4b27
+# defined as a resource where the clone-max meta attribute is present,
0c4b27
+# and set to greater than zero.
0c4b27
+sub ocf_is_clone {
0c4b27
+    return ( defined $ENV{'OCF_RESKEY_CRM_meta_clone_max'}
0c4b27
+        and $ENV{'OCF_RESKEY_CRM_meta_clone_max'} > 0 );
0c4b27
+}
0c4b27
+
0c4b27
+# returns true if the resource is configured as a multistate
0c4b27
+# (master/slave) resource. This is defined as a resource where the
0c4b27
+# master-max meta attribute is present, and set to greater than zero.
0c4b27
+sub ocf_is_ms {
0c4b27
+    return ( defined $ENV{'OCF_RESKEY_CRM_meta_master_max'}
0c4b27
+        and  $ENV{'OCF_RESKEY_CRM_meta_master_max'} > 0 );
0c4b27
+}
0c4b27
+
0c4b27
+# version check functions
0c4b27
+# allow . and - to delimit version numbers
0c4b27
+# max version number is 999
0c4b27
+# letters and such are effectively ignored
0c4b27
+#
0c4b27
+sub ocf_is_ver {
0c4b27
+    return $ARG[0] =~ /^[0-9][0-9.-]*[0-9]$/;
0c4b27
+}
0c4b27
+
0c4b27
+sub ocf_ver2num {
0c4b27
+    my $v = 0;
0c4b27
+    
0c4b27
+    $v = $v * 1000 + $1 while $ARG[0] =~ /(\d+)/g;
0c4b27
+
0c4b27
+    return $v;
0c4b27
+}
0c4b27
+
0c4b27
+sub ocf_ver_level {
0c4b27
+    my $v = () = $ARG[0] =~ /(\d+)/g;
0c4b27
+    return $v;
0c4b27
+}
0c4b27
+
0c4b27
+sub ocf_ver_complete_level {
0c4b27
+    my $ver   = shift;
0c4b27
+    my $level = shift;
0c4b27
+    my $i     = 0;
0c4b27
+
0c4b27
+    for ( my $i = 0; $i < $level; $i++ ) {
0c4b27
+        $ver .= "$ver.0";
0c4b27
+    }
0c4b27
+
0c4b27
+    return $ver;
0c4b27
+}
0c4b27
+
0c4b27
+# usage: ocf_version_cmp VER1 VER2
0c4b27
+#     version strings can contain digits, dots, and dashes
0c4b27
+#     must start and end with a digit
0c4b27
+# returns:
0c4b27
+#     0: VER1 smaller (older) than VER2
0c4b27
+#     1: versions equal
0c4b27
+#     2: VER1 greater (newer) than VER2
0c4b27
+#     3: bad format
0c4b27
+sub ocf_version_cmp {
0c4b27
+    my $v1 = shift;
0c4b27
+    my $v2 = shift;
0c4b27
+    my $v1_level;
0c4b27
+    my $v2_level;
0c4b27
+    my $level_diff;
0c4b27
+    
0c4b27
+    return 3 unless ocf_is_ver( $v1 );
0c4b27
+    return 3 unless ocf_is_ver( $v2 );
0c4b27
+
0c4b27
+    $v1_level = ocf_ver_level( $v1 );
0c4b27
+    $v2_level = ocf_ver_level( $v2 );
0c4b27
+
0c4b27
+    if ( $v1_level < $v2_level ) {
0c4b27
+        $level_diff = $v2_level - $v1_level;
0c4b27
+        $v1 = ocf_ver_complete_level( $v1, $level_diff );
0c4b27
+    }
0c4b27
+    elsif ( $v1_level > $v2_level ) {
0c4b27
+        $level_diff = $v1_level - $v2_level;
0c4b27
+        $v2 = ocf_ver_complete_level( $v2, $level_diff );
0c4b27
+    }
0c4b27
+
0c4b27
+    $v1 = ocf_ver2num( $v1 );
0c4b27
+    $v2 = ocf_ver2num( $v2 );
0c4b27
+
0c4b27
+    if    ( $v1 == $v2 ) { return 1; }
0c4b27
+    elsif ( $v1 < $v2  ) { return 0; }
0c4b27
+
0c4b27
+    return 2; # -1 would look funny in shell ;-) ( T.N. not in perl ;) )
0c4b27
+}
0c4b27
+
0c4b27
+sub ocf_local_nodename {
0c4b27
+    # use crm_node -n for pacemaker > 1.1.8
0c4b27
+    my $nodename;
0c4b27
+
0c4b27
+    qx{ which pacemakerd > /dev/null 2>&1 };
0c4b27
+    if ( $? == 0 ) {
0c4b27
+        my $version;
0c4b27
+        my $ret = qx{ pacemakerd -\$ };
0c4b27
+
0c4b27
+        $ret =~ /Pacemaker ([\d.]+)/;
0c4b27
+        $version = $1;
0c4b27
+
0c4b27
+        if ( ocf_version_cmp( $version, '1.1.8' ) == 2 ) {
0c4b27
+            qx{ which crm_node > /dev/null 2>&1 };
0c4b27
+            $nodename = qx{ crm_node -n } if $? == 0;
0c4b27
+        }
0c4b27
+    }
0c4b27
+    else {
0c4b27
+        # otherwise use uname -n
0c4b27
+        $nodename = qx { uname -n };
0c4b27
+    }
0c4b27
+
0c4b27
+    chomp $nodename;
0c4b27
+    return $nodename;
0c4b27
+}
0c4b27
+
0c4b27
+# Parse and returns the notify environment variables in a convenient structure
0c4b27
+# Returns undef if the action is not a notify
0c4b27
+# Returns undef if the resource is neither a clone or a multistate one
0c4b27
+sub ocf_notify_env {
0c4b27
+    my $i;
0c4b27
+    my %notify_env;
0c4b27
+
0c4b27
+    return undef unless $__OCF_ACTION eq 'notify';
0c4b27
+
0c4b27
+    return undef unless ocf_is_clone() or ocf_is_ms();
0c4b27
+
0c4b27
+    %notify_env = (
0c4b27
+        'type'       => $ENV{'OCF_RESKEY_CRM_meta_notify_type'}      || '',
0c4b27
+        'operation'  => $ENV{'OCF_RESKEY_CRM_meta_notify_operation'} || '',
0c4b27
+        'active'     => [ ],
0c4b27
+        'inactive'   => [ ],
0c4b27
+        'start'      => [ ],
0c4b27
+        'stop'       => [ ],
0c4b27
+    );
0c4b27
+
0c4b27
+    for my $action ( qw{ active start stop } ) {
0c4b27
+        next unless
0c4b27
+                defined $ENV{"OCF_RESKEY_CRM_meta_notify_${action}_resource"}
0c4b27
+            and defined $ENV{"OCF_RESKEY_CRM_meta_notify_${action}_uname"};
0c4b27
+
0c4b27
+        $i = 0;
0c4b27
+        $notify_env{ $action }[$i++]{'rsc'} = $_ foreach split /\s+/ =>
0c4b27
+            $ENV{"OCF_RESKEY_CRM_meta_notify_${action}_resource"};
0c4b27
+
0c4b27
+        $i = 0;
0c4b27
+        $notify_env{ $action }[$i++]{'uname'} = $_ foreach split /\s+/ =>
0c4b27
+            $ENV{"OCF_RESKEY_CRM_meta_notify_${action}_uname"};
0c4b27
+    }
0c4b27
+
0c4b27
+    # notify_nactive_uname doesn't exists. See:
0c4b27
+    # http://lists.clusterlabs.org/pipermail/developers/2017-January/000406.html
0c4b27
+    if ( defined $ENV{"OCF_RESKEY_CRM_meta_notify_inactive_resource"} ) {
0c4b27
+        $i = 0;
0c4b27
+        $notify_env{'inactive'}[$i++]{'rsc'} = $_ foreach split /\s+/ =>
0c4b27
+            $ENV{"OCF_RESKEY_CRM_meta_notify_inactive_resource"};
0c4b27
+    }
0c4b27
+
0c4b27
+    # exit if the resource is not a mutistate one
0c4b27
+    return %notify_env unless ocf_is_ms();
0c4b27
+
0c4b27
+    for my $action ( qw{ master slave promote demote } ) {
0c4b27
+        $notify_env{ $action } = [ ];
0c4b27
+
0c4b27
+        next unless
0c4b27
+                defined $ENV{"OCF_RESKEY_CRM_meta_notify_${action}_resource"}
0c4b27
+            and defined $ENV{"OCF_RESKEY_CRM_meta_notify_${action}_uname"};
0c4b27
+
0c4b27
+        $i = 0;
0c4b27
+        $notify_env{ $action }[$i++]{'rsc'} = $_ foreach split /\s+/ =>
0c4b27
+            $ENV{"OCF_RESKEY_CRM_meta_notify_${action}_resource"};
0c4b27
+
0c4b27
+        $i = 0;
0c4b27
+        $notify_env{ $action }[$i++]{'uname'} = $_ foreach split /\s+/ =>
0c4b27
+            $ENV{"OCF_RESKEY_CRM_meta_notify_${action}_uname"};
0c4b27
+    }
0c4b27
+
0c4b27
+    # Fix active and inactive fields for Pacemaker version < 1.1.16
0c4b27
+    # ie. crm_feature_set < 3.0.11
0c4b27
+    # See http://lists.clusterlabs.org/pipermail/developers/2016-August/000265.html
0c4b27
+    # and git commit a6713c5d40327eff8549e7f596501ab1785b8765
0c4b27
+    if (
0c4b27
+        ocf_version_cmp( $ENV{"OCF_RESKEY_crm_feature_set"}, '3.0.11' ) == 0
0c4b27
+    ) {
0c4b27
+        $notify_env{ 'active' } = [
0c4b27
+            @{ $notify_env{ 'master' } },
0c4b27
+            @{ $notify_env{ 'slave' } }
0c4b27
+        ];
0c4b27
+    }
0c4b27
+
0c4b27
+    return %notify_env;
0c4b27
+}
0c4b27
+
0c4b27
+$__OCF_ACTION = $ARGV[0];
0c4b27
+
0c4b27
+# Return to sanity for the agents...
0c4b27
+
0c4b27
+undef $ENV{'LC_ALL'};
0c4b27
+$ENV{'LC_ALL'} = 'C';
0c4b27
+setlocale( LC_ALL, 'C' );
0c4b27
+undef $ENV{'LANG'};
0c4b27
+undef $ENV{'LANGUAGE'};
0c4b27
+
0c4b27
+$ENV{'OCF_ROOT'} = '/usr/lib/ocf'
0c4b27
+    unless defined $ENV{'OCF_ROOT'} and $ENV{'OCF_ROOT'} ne '';
0c4b27
+
0c4b27
+# old
0c4b27
+undef $ENV{'OCF_FUNCTIONS_DIR'}
0c4b27
+    if defined $ENV{'OCF_FUNCTIONS_DIR'}
0c4b27
+    and $ENV{'OCF_FUNCTIONS_DIR'} eq "$ENV{'OCF_ROOT'}/resource.d/heartbeat";
0c4b27
+
0c4b27
+# Define OCF_RESKEY_CRM_meta_interval in case it isn't already set,
0c4b27
+# to make sure that ocf_is_probe() always works
0c4b27
+$ENV{'OCF_RESKEY_CRM_meta_interval'} = 0
0c4b27
+    unless defined $ENV{'OCF_RESKEY_CRM_meta_interval'};
0c4b27
+
0c4b27
+# Strip the OCF_RESKEY_ prefix from this particular parameter
0c4b27
+unless ( defined $ENV{'$OCF_RESKEY_OCF_CHECK_LEVEL'}
0c4b27
+    and $ENV{'$OCF_RESKEY_OCF_CHECK_LEVEL'} ne ''
0c4b27
+) {
0c4b27
+    $ENV{'OCF_CHECK_LEVEL'} = $ENV{'$OCF_RESKEY_OCF_CHECK_LEVEL'};
0c4b27
+}
0c4b27
+else {
0c4b27
+    ENV{'OCF_CHECK_LEVEL'} = 0;
0c4b27
+}
0c4b27
+
0c4b27
+unless ( -d $ENV{'OCF_ROOT'} ) {
0c4b27
+    ha_log( "ERROR: OCF_ROOT points to non-directory $ENV{'OCF_ROOT'}." );
0c4b27
+    $! = $OCF_ERR_GENERIC;
0c4b27
+    die;
0c4b27
+}
0c4b27
+
0c4b27
+$ENV{'OCF_RESOURCE_TYPE'} = $__SCRIPT_NAME
0c4b27
+    unless defined $ENV{'OCF_RESOURCE_TYPE'}
0c4b27
+    and $ENV{'OCF_RESOURCE_TYPE'} ne '';
0c4b27
+
0c4b27
+unless ( defined $ENV{'OCF_RA_VERSION_MAJOR'}
0c4b27
+    and $ENV{'OCF_RA_VERSION_MAJOR'} ne ''
0c4b27
+) {
0c4b27
+    # We are being invoked as an init script.
0c4b27
+    # Fill in some things with reasonable values.
0c4b27
+    $ENV{'OCF_RESOURCE_INSTANCE'} = 'default';
0c4b27
+    return 1;
0c4b27
+}
0c4b27
+
0c4b27
+$ENV{'OCF_RESOURCE_INSTANCE'} = "undef" if $__OCF_ACTION eq 'meta-data';
0c4b27
+
0c4b27
+unless ( defined $ENV{'OCF_RESOURCE_INSTANCE'}
0c4b27
+    and $ENV{'OCF_RESOURCE_INSTANCE'} ne ''
0c4b27
+) {
0c4b27
+    ha_log( "ERROR: Need to tell us our resource instance name." );
0c4b27
+    $! = $OCF_ERR_ARGS;
0c4b27
+    die;
0c4b27
+}
0c4b27
+
0c4b27
+1;
0c4b27
+
0c4b27
+
0c4b27
+=head1 COPYRIGHT AND LICENSE
0c4b27
+
0c4b27
+Copyright (C) 2016: Jehan-Guillaume de Rorthais and Mael Rimbault.
0c4b27
+
0c4b27
+Licensed under the PostgreSQL License.
0c4b27
diff --color -uNr a/heartbeat/OCF_ReturnCodes.pm b/heartbeat/OCF_ReturnCodes.pm
0c4b27
--- a/heartbeat/OCF_ReturnCodes.pm	1970-01-01 01:00:00.000000000 +0100
0c4b27
+++ b/heartbeat/OCF_ReturnCodes.pm	2021-04-13 13:37:35.621267404 +0200
0c4b27
@@ -0,0 +1,97 @@
0c4b27
+#!/usr/bin/perl
0c4b27
+# This program is open source, licensed under the PostgreSQL License.
0c4b27
+# For license terms, see the LICENSE file.
0c4b27
+#
0c4b27
+# Copyright (C) 2016-2020: Jehan-Guillaume de Rorthais and Mael Rimbault
0c4b27
+
0c4b27
+=head1 NAME
0c4b27
+
0c4b27
+OCF_ReturnCodes - Common varibales for the OCF Resource Agents supplied by
0c4b27
+heartbeat.
0c4b27
+
0c4b27
+=head1 SYNOPSIS
0c4b27
+
0c4b27
+  use FindBin;
0c4b27
+  use lib "$FindBin::RealBin/../../lib/heartbeat/";
0c4b27
+  
0c4b27
+  use OCF_ReturnCodes;
0c4b27
+
0c4b27
+=head1 DESCRIPTION
0c4b27
+
0c4b27
+This module has been ported from the ocf-retrurncodes shell script of the
0c4b27
+resource-agents project. See L<https://github.com/ClusterLabs/resource-agents/>.
0c4b27
+
0c4b27
+=head1 VARIABLES
0c4b27
+
0c4b27
+Here are the variables exported by this module:
0c4b27
+
0c4b27
+=over
0c4b27
+
0c4b27
+=item $OCF_SUCCESS
0c4b27
+
0c4b27
+=item $OCF_ERR_GENERIC
0c4b27
+
0c4b27
+=item $OCF_ERR_ARGS
0c4b27
+
0c4b27
+=item $OCF_ERR_UNIMPLEMENTED
0c4b27
+
0c4b27
+=item $OCF_ERR_PERM
0c4b27
+
0c4b27
+=item $OCF_ERR_INSTALLED
0c4b27
+
0c4b27
+=item $OCF_ERR_CONFIGURED
0c4b27
+
0c4b27
+=item $OCF_NOT_RUNNING
0c4b27
+
0c4b27
+=item $OCF_RUNNING_MASTER
0c4b27
+
0c4b27
+=item $OCF_FAILED_MASTER
0c4b27
+
0c4b27
+=back
0c4b27
+
0c4b27
+=cut
0c4b27
+
0c4b27
+package OCF_ReturnCodes;
0c4b27
+
0c4b27
+use strict;
0c4b27
+use warnings;
0c4b27
+use 5.008;
0c4b27
+
0c4b27
+BEGIN {
0c4b27
+    use Exporter;
0c4b27
+
0c4b27
+    our $VERSION   = 'v2.3.0';
0c4b27
+    our @ISA       = ('Exporter');
0c4b27
+    our @EXPORT    = qw(
0c4b27
+        $OCF_SUCCESS
0c4b27
+        $OCF_ERR_GENERIC
0c4b27
+        $OCF_ERR_ARGS
0c4b27
+        $OCF_ERR_UNIMPLEMENTED
0c4b27
+        $OCF_ERR_PERM
0c4b27
+        $OCF_ERR_INSTALLED
0c4b27
+        $OCF_ERR_CONFIGURED
0c4b27
+        $OCF_NOT_RUNNING
0c4b27
+        $OCF_RUNNING_MASTER
0c4b27
+        $OCF_FAILED_MASTER
0c4b27
+    );
0c4b27
+    our @EXPORT_OK = ( @EXPORT );
0c4b27
+}
0c4b27
+
0c4b27
+our $OCF_SUCCESS           = 0;
0c4b27
+our $OCF_ERR_GENERIC       = 1;
0c4b27
+our $OCF_ERR_ARGS          = 2;
0c4b27
+our $OCF_ERR_UNIMPLEMENTED = 3;
0c4b27
+our $OCF_ERR_PERM          = 4;
0c4b27
+our $OCF_ERR_INSTALLED     = 5;
0c4b27
+our $OCF_ERR_CONFIGURED    = 6;
0c4b27
+our $OCF_NOT_RUNNING       = 7;
0c4b27
+our $OCF_RUNNING_MASTER    = 8;
0c4b27
+our $OCF_FAILED_MASTER     = 9;
0c4b27
+
0c4b27
+1;
0c4b27
+
0c4b27
+=head1 COPYRIGHT AND LICENSE
0c4b27
+
0c4b27
+Copyright (C) 2016: Jehan-Guillaume de Rorthais and Mael Rimbault.
0c4b27
+
0c4b27
+Licensed under the PostgreSQL License.
0c4b27
diff --color -uNr a/heartbeat/pgsqlms b/heartbeat/pgsqlms
0c4b27
--- a/heartbeat/pgsqlms	1970-01-01 01:00:00.000000000 +0100
0c4b27
+++ b/heartbeat/pgsqlms	2021-04-13 13:37:40.934280411 +0200
0c4b27
@@ -0,0 +1,2308 @@
0c4b27
+#!/usr/bin/perl
0c4b27
+# This program is open source, licensed under the PostgreSQL License.
0c4b27
+# For license terms, see the LICENSE file.
0c4b27
+#
0c4b27
+# Copyright (C) 2016-2020: Jehan-Guillaume de Rorthais and Mael Rimbault
0c4b27
+
0c4b27
+=head1 NAME
0c4b27
+
0c4b27
+ocf_heartbeat_pgsqlms - A PostgreSQL multi-state resource agent for Pacemaker
0c4b27
+
0c4b27
+=head1 SYNOPSIS
0c4b27
+
0c4b27
+B<pgsqlms> [start | stop | monitor | promote | demote | notify | reload | methods | meta-data | validate-all]
0c4b27
+
0c4b27
+=head1 DESCRIPTION
0c4b27
+
0c4b27
+Resource script for PostgreSQL in replication. It manages PostgreSQL servers using streaming replication as an HA resource.
0c4b27
+
0c4b27
+=cut
0c4b27
+
0c4b27
+use strict;
0c4b27
+use warnings;
0c4b27
+use 5.008;
0c4b27
+
0c4b27
+use POSIX qw(locale_h);
0c4b27
+use Scalar::Util qw(looks_like_number);
0c4b27
+use File::Spec;
0c4b27
+use File::Temp;
0c4b27
+use Data::Dumper;
0c4b27
+
0c4b27
+my $OCF_FUNCTIONS_DIR;
0c4b27
+BEGIN {
0c4b27
+	$OCF_FUNCTIONS_DIR = defined $ENV{'OCF_FUNCTIONS_DIR'} ? "$ENV{'OCF_FUNCTIONS_DIR'}" : "$ENV{'OCF_ROOT'}/lib/heartbeat";
0c4b27
+}
0c4b27
+use lib "$OCF_FUNCTIONS_DIR";
0c4b27
+
0c4b27
+use OCF_ReturnCodes;
0c4b27
+use OCF_Directories;
0c4b27
+use OCF_Functions;
0c4b27
+
0c4b27
+our $VERSION = 'v2.3.0';
0c4b27
+our $PROGRAM = 'pgsqlms';
0c4b27
+
0c4b27
+# OCF environment
0c4b27
+my $OCF_RESOURCE_INSTANCE = $ENV{'OCF_RESOURCE_INSTANCE'};
0c4b27
+my $OCF_RUNNING_SLAVE     = $OCF_SUCCESS;
0c4b27
+my %OCF_NOTIFY_ENV        = ocf_notify_env() if $__OCF_ACTION eq 'notify';
0c4b27
+
0c4b27
+# Default parameters values
0c4b27
+my $system_user_default = "postgres";
0c4b27
+my $bindir_default      = "/usr/bin";
0c4b27
+my $pgdata_default      = "/var/lib/pgsql/data";
0c4b27
+my $pghost_default      = "/tmp";
0c4b27
+my $pgport_default      = 5432;
0c4b27
+my $start_opts_default  = "";
0c4b27
+my $maxlag_default      = "0";
0c4b27
+
0c4b27
+# Set default values if not found in environment
0c4b27
+my $system_user  = $ENV{'OCF_RESKEY_system_user'} || $system_user_default;
0c4b27
+my $bindir       = $ENV{'OCF_RESKEY_bindir'} || $bindir_default;
0c4b27
+my $pgdata       = $ENV{'OCF_RESKEY_pgdata'} || $pgdata_default;
0c4b27
+my $datadir      = $ENV{'OCF_RESKEY_datadir'} || $pgdata;
0c4b27
+my $pghost       = $ENV{'OCF_RESKEY_pghost'} || $pghost_default;
0c4b27
+my $pgport       = $ENV{'OCF_RESKEY_pgport'} || $pgport_default;
0c4b27
+my $start_opts   = $ENV{'OCF_RESKEY_start_opts'} || $start_opts_default;
0c4b27
+my $maxlag       = $ENV{'OCF_RESKEY_maxlag'} || $maxlag_default;
0c4b27
+my $recovery_tpl = $ENV{'OCF_RESKEY_recovery_template'}
0c4b27
+    || "$pgdata/recovery.conf.pcmk";
0c4b27
+
0c4b27
+
0c4b27
+# PostgreSQL commands path
0c4b27
+my $POSTGRES   = "$bindir/postgres";
0c4b27
+my $PGCTL      = "$bindir/pg_ctl";
0c4b27
+my $PGPSQL     = "$bindir/psql";
0c4b27
+my $PGCTRLDATA = "$bindir/pg_controldata";
0c4b27
+my $PGISREADY  = "$bindir/pg_isready";
0c4b27
+my $PGWALDUMP  = "$bindir/pg_waldump";
0c4b27
+
0c4b27
+# pacemaker commands path
0c4b27
+my $CRM_MASTER    = "$HA_SBIN_DIR/crm_master --lifetime forever";
0c4b27
+my $CRM_NODE      = "$HA_SBIN_DIR/crm_node";
0c4b27
+my $CRM_RESOURCE  = "$HA_SBIN_DIR/crm_resource";
0c4b27
+my $ATTRD_PRIV    = "$HA_SBIN_DIR/attrd_updater --private --lifetime reboot";
0c4b27
+
0c4b27
+# Global vars
0c4b27
+my $nodename;
0c4b27
+my $exit_code = 0;
0c4b27
+# numeric pgsql versions
0c4b27
+my $PGVERNUM;
0c4b27
+my $PGVER_93 = 90300;
0c4b27
+my $PGVER_10 = 100000;
0c4b27
+my $PGVER_12 = 120000;
0c4b27
+
0c4b27
+# Run a query using psql.
0c4b27
+#
0c4b27
+# This function returns an array with psql return code as first element and
0c4b27
+# the result as second one.
0c4b27
+#
0c4b27
+sub _query {
0c4b27
+    my $query        = shift;
0c4b27
+    my $res          = shift;
0c4b27
+    my $connstr      = "dbname=postgres";
0c4b27
+    my $RS           = chr(30); # ASCII RS  (record separator)
0c4b27
+    my $FS           = chr(3);  # ASCII ETX (end of text)
0c4b27
+    my $postgres_uid = getpwnam( $system_user );
0c4b27
+    my $oldeuid      = $>;
0c4b27
+    my $tmpfile;
0c4b27
+    my @res;
0c4b27
+    my $ans;
0c4b27
+    my $pid;
0c4b27
+    my $rc;
0c4b27
+
0c4b27
+    unless ( defined $res and defined $query and $query ne '' ) {
0c4b27
+        ocf_log( 'debug', '_query: wrong parameters!' );
0c4b27
+        return -1;
0c4b27
+    }
0c4b27
+
0c4b27
+    unless ( $tmpfile = File::Temp->new(
0c4b27
+            TEMPLATE => 'pgsqlms-XXXXXXXX',
0c4b27
+            DIR      => $HA_RSCTMP
0c4b27
+        ) )
0c4b27
+    {
0c4b27
+        ocf_exit_reason( 'Could not create or write in a temp file' );
0c4b27
+        exit $OCF_ERR_INSTALLED;
0c4b27
+    }
0c4b27
+
0c4b27
+    print $tmpfile $query;
0c4b27
+    chmod 0644, $tmpfile;
0c4b27
+
0c4b27
+    ocf_log( 'debug', '_query: %s', $query );
0c4b27
+
0c4b27
+    # Change the effective user to the given system_user so after forking
0c4b27
+    # the given uid to the process should allow psql to connect w/o password
0c4b27
+    $> = $postgres_uid;
0c4b27
+
0c4b27
+    # Forking + piping
0c4b27
+    $pid = open(my $KID, "-|");
0c4b27
+
0c4b27
+    if ( $pid == 0 ) { # child
0c4b27
+        exec $PGPSQL, '--set', 'ON_ERROR_STOP=1', '-qXAtf', $tmpfile,
0c4b27
+            '-R', $RS, '-F', $FS, '--port', $pgport, '--host', $pghost,
0c4b27
+            $connstr;
0c4b27
+    }
0c4b27
+
0c4b27
+    # parent
0c4b27
+    $> = $oldeuid;
0c4b27
+
0c4b27
+    {
0c4b27
+        local $/;
0c4b27
+        $ans = <$KID>;
0c4b27
+    }
0c4b27
+
0c4b27
+    close $KID;
0c4b27
+    $rc = $? >> 8;
0c4b27
+
0c4b27
+    ocf_log( 'debug', '_query: psql return code: %d', $rc );
0c4b27
+
0c4b27
+    if ( defined $ans ) {
0c4b27
+        chop $ans;
0c4b27
+
0c4b27
+        push @{ $res }, [ split(chr(3) => $_, -1) ]
0c4b27
+            foreach split (chr(30) => $ans, -1);
0c4b27
+
0c4b27
+        ocf_log( 'debug', '_query: @res: %s',
0c4b27
+            Data::Dumper->new( [ $res ] )->Terse(1)->Dump );
0c4b27
+    }
0c4b27
+
0c4b27
+    # Possible return codes:
0c4b27
+    #  -1: wrong parameters
0c4b27
+    #   0: OK
0c4b27
+    #   1: failed to get resources (memory, missing file, ...)
0c4b27
+    #   2: unable to connect
0c4b27
+    #   3: query failed
0c4b27
+    return $rc;
0c4b27
+}
0c4b27
+
0c4b27
+# Get the last received location on a standby
0c4b27
+# if the first argument is true, returns the value as decimal
0c4b27
+# if the first argument is false, returns the value as LSN
0c4b27
+# Returns undef if query failed
0c4b27
+sub _get_last_received_lsn {
0c4b27
+    my ( $dec ) = @_;
0c4b27
+    my $pg_last_wal_receive_lsn = 'pg_last_wal_receive_lsn()';
0c4b27
+    my $pg_wal_lsn_diff         = 'pg_wal_lsn_diff';
0c4b27
+    my $query;
0c4b27
+    my $rc;
0c4b27
+    my @rs;
0c4b27
+
0c4b27
+    if ( $PGVERNUM < $PGVER_10  ) {
0c4b27
+        $pg_last_wal_receive_lsn = 'pg_last_xlog_receive_location()';
0c4b27
+        $pg_wal_lsn_diff         = 'pg_xlog_location_diff';
0c4b27
+    }
0c4b27
+
0c4b27
+    if ( $dec ) {
0c4b27
+        $query = "SELECT $pg_wal_lsn_diff( $pg_last_wal_receive_lsn, '0/0' )";
0c4b27
+    }
0c4b27
+    else {
0c4b27
+        $query = "SELECT $pg_last_wal_receive_lsn";
0c4b27
+    }
0c4b27
+
0c4b27
+    $rc = _query( $query, \@rs );
0c4b27
+
0c4b27
+    return $rs[0][0] if $rc == 0 and $rs[0][0];
0c4b27
+
0c4b27
+    ocf_log( 'err', 'Could not query last received LSN (%s)', $rc ) if $rc != 0;
0c4b27
+    ocf_log( 'err', 'No values for last received LSN' )
0c4b27
+        if $rc == 0 and not $rs[0][0];
0c4b27
+
0c4b27
+    return undef;
0c4b27
+}
0c4b27
+
0c4b27
+# Get the master score for each connected standby
0c4b27
+# Returns directly the result set of the query or exit with an error.
0c4b27
+# Exits with OCF_ERR_GENERIC if the query failed
0c4b27
+sub _get_lag_scores {
0c4b27
+    my $pg_current_wal_lsn = 'pg_current_wal_lsn()';
0c4b27
+    my $pg_wal_lsn_diff    = 'pg_wal_lsn_diff';
0c4b27
+    my $write_lsn          = 'write_lsn';
0c4b27
+    my $query;
0c4b27
+    my $rc;
0c4b27
+    my @rs;
0c4b27
+
0c4b27
+    if ( $PGVERNUM < $PGVER_10  ) {
0c4b27
+        $pg_current_wal_lsn = 'pg_current_xlog_location()';
0c4b27
+        $pg_wal_lsn_diff    = 'pg_xlog_location_diff';
0c4b27
+        $write_lsn          = 'write_location';
0c4b27
+    }
0c4b27
+
0c4b27
+    # We check locations of connected standbies by querying the
0c4b27
+    # "pg_stat_replication" view.
0c4b27
+    # The row_number applies on the result set ordered on write_location ASC so
0c4b27
+    # the highest row_number should be given to the closest node from the
0c4b27
+    # master, then the lowest node name (alphanumeric sort) in case of equality.
0c4b27
+    # The result set itself is order by priority DESC to process best known
0c4b27
+    # candidate first.
0c4b27
+    $query = qq{
0c4b27
+      SELECT application_name, priority, location, state, current_lag
0c4b27
+      FROM (
0c4b27
+        SELECT application_name,
0c4b27
+          (1000 - (
0c4b27
+            row_number() OVER (
0c4b27
+              PARTITION BY state IN ('startup', 'backup')
0c4b27
+              ORDER BY location ASC, application_name ASC
0c4b27
+            ) - 1
0c4b27
+           ) * 10
0c4b27
+          ) * CASE WHEN ( $maxlag > 0
0c4b27
+                     AND current_lag > $maxlag)
0c4b27
+                        THEN -1
0c4b27
+                   ELSE 1
0c4b27
+              END AS priority,
0c4b27
+          location, state, current_lag
0c4b27
+        FROM (
0c4b27
+          SELECT application_name, $write_lsn AS location, state,
0c4b27
+            $pg_wal_lsn_diff($pg_current_wal_lsn, $write_lsn) AS current_lag
0c4b27
+          FROM pg_stat_replication
0c4b27
+        ) AS s2
0c4b27
+      ) AS s1
0c4b27
+      ORDER BY priority DESC
0c4b27
+    };
0c4b27
+
0c4b27
+    $rc = _query( $query, \@rs );
0c4b27
+
0c4b27
+    if ( $rc != 0 ) {
0c4b27
+        ocf_exit_reason( 'Query to get standby locations failed (%d)', $rc );
0c4b27
+        exit $OCF_ERR_GENERIC;
0c4b27
+    }
0c4b27
+
0c4b27
+    return \@rs;
0c4b27
+}
0c4b27
+
0c4b27
+# get the timeout for the current action given from environment var
0c4b27
+# Returns   timeout as integer
0c4b27
+#           undef if unknown
0c4b27
+sub _get_action_timeout {
0c4b27
+    my $timeout = $ENV{'OCF_RESKEY_CRM_meta_timeout'} / 1000;
0c4b27
+
0c4b27
+    ocf_log( 'debug', '_get_action_timeout: known timeout: %s',
0c4b27
+        defined $timeout ? $timeout : 'undef' );
0c4b27
+
0c4b27
+    return $timeout if defined $timeout and $timeout =~ /^\d+$/;
0c4b27
+
0c4b27
+    return undef;
0c4b27
+}
0c4b27
+
0c4b27
+# Get, parse and return the value of the given private attribute name
0c4b27
+# Returns an empty string if not found.
0c4b27
+sub _get_priv_attr {
0c4b27
+    my ( $name, $node ) = @_;
0c4b27
+    my $val             = '';
0c4b27
+    my $node_arg        = '';
0c4b27
+    my $ans;
0c4b27
+
0c4b27
+    $node = '' unless defined $node;
0c4b27
+    $name = "$name-$OCF_RESOURCE_INSTANCE";
0c4b27
+
0c4b27
+    $node_arg= "--node $node" if $node ne '';
0c4b27
+
0c4b27
+    $ans = qx{ $ATTRD_PRIV --name "$name" --query $node_arg };
0c4b27
+
0c4b27
+    $ans =~ m/^name=".*" host=".*" value="(.*)"$/;
0c4b27
+
0c4b27
+    $val = $1 if defined $1;
0c4b27
+
0c4b27
+    ocf_log( 'debug', '_get_priv_attr: value of "%s"%s is "%s"', $name,
0c4b27
+        ( $node ? " on \"$node\"": ""),
0c4b27
+        $val );
0c4b27
+
0c4b27
+    return $val;
0c4b27
+}
0c4b27
+
0c4b27
+# Set the given private attribute name to the given value
0c4b27
+# As setting an attribute is asynchronous, this will return as soon as the
0c4b27
+# attribute is really set by attrd and available.
0c4b27
+sub _set_priv_attr {
0c4b27
+    my ( $name, $val ) = @_;
0c4b27
+    my $name_instance  = "$name-$OCF_RESOURCE_INSTANCE";
0c4b27
+
0c4b27
+    ocf_log( 'debug', '_set_priv_attr: set "%s=%s"...', $name_instance, $val );
0c4b27
+
0c4b27
+    qx{ $ATTRD_PRIV --name "$name_instance" --update "$val" };
0c4b27
+
0c4b27
+    # give attr name without the resource instance name as _get_priv_attr adds
0c4b27
+    # it as well
0c4b27
+    while ( _get_priv_attr( $name ) ne $val ) {
0c4b27
+        ocf_log( 'debug', '_set_priv_attr: waiting attrd ack for "%s"...', $name_instance );
0c4b27
+        select( undef, undef, undef, 0.1 );
0c4b27
+    }
0c4b27
+
0c4b27
+    return;
0c4b27
+}
0c4b27
+
0c4b27
+# Delete the given private attribute.
0c4b27
+# As setting an attribute is asynchronous, this will return as soon as the
0c4b27
+# attribute is really deleted by attrd.
0c4b27
+sub _delete_priv_attr {
0c4b27
+    my ( $name ) = @_;
0c4b27
+    my $name_instance  = "$name-$OCF_RESOURCE_INSTANCE";
0c4b27
+
0c4b27
+    ocf_log( 'debug', '_delete_priv_attr: delete "%s"...', $name_instance );
0c4b27
+
0c4b27
+    qx{ $ATTRD_PRIV --name "$name_instance" --delete };
0c4b27
+
0c4b27
+    # give attr name without the resource instance name as _get_priv_attr adds
0c4b27
+    # it as well
0c4b27
+    while ( _get_priv_attr( $name ) ne '' ) {
0c4b27
+        ocf_log( 'debug', '_delete_priv_attr: waiting attrd ack for "%s"...',
0c4b27
+            $name_instance );
0c4b27
+        select( undef, undef, undef, 0.1 );
0c4b27
+    }
0c4b27
+
0c4b27
+    return;
0c4b27
+}
0c4b27
+
0c4b27
+# Get, parse and return the resource master score on given node.
0c4b27
+# Returns an empty string if not found.
0c4b27
+# Returns undef on crm_master call on error
0c4b27
+sub _get_master_score {
0c4b27
+    my ( $node ) = @_;
0c4b27
+    my $node_arg = '';
0c4b27
+    my $score;
0c4b27
+
0c4b27
+    $node_arg = sprintf '--node "%s"', $node if defined $node and $node ne '';
0c4b27
+
0c4b27
+    $score = qx{ $CRM_MASTER --quiet --get-value $node_arg 2> /dev/null };
0c4b27
+
0c4b27
+    return '' unless $? == 0 and defined $score;
0c4b27
+
0c4b27
+    chomp $score;
0c4b27
+
0c4b27
+    return $score;
0c4b27
+}
0c4b27
+
0c4b27
+# Set the master score of the local node or the optionally given node.
0c4b27
+# As setting an attribute is asynchronous, this will return as soon as the
0c4b27
+# attribute is really set by attrd and available everywhere.
0c4b27
+sub _set_master_score {
0c4b27
+    my ( $score, $node ) = @_;
0c4b27
+    my $node_arg = '';
0c4b27
+    my $tmp;
0c4b27
+
0c4b27
+    $node_arg = sprintf '--node "%s"', $node if defined $node and $node ne '';
0c4b27
+
0c4b27
+    qx{ $CRM_MASTER $node_arg --quiet --update "$score" };
0c4b27
+
0c4b27
+    while ( ( $tmp = _get_master_score( $node ) ) ne $score ) {
0c4b27
+        ocf_log( 'debug',
0c4b27
+            '_set_master_score: waiting to set score to "%s" (currently "%s")...',
0c4b27
+            $score, $tmp );
0c4b27
+        select(undef, undef, undef, 0.1);
0c4b27
+    }
0c4b27
+
0c4b27
+    return;
0c4b27
+}
0c4b27
+
0c4b27
+# _master_score_exists
0c4b27
+# This subroutine checks if a master score is set for one of the relative clones
0c4b27
+# in the cluster and the score is greater or equal of 0.
0c4b27
+# Returns 1 if at least one master score >= 0 is found.
0c4b27
+# Returns 0 otherwise
0c4b27
+sub _master_score_exists {
0c4b27
+    my @partition_nodes = split /\s+/ => qx{ $CRM_NODE --partition };
0c4b27
+
0c4b27
+    foreach my $node ( @partition_nodes ) {
0c4b27
+        my $score = _get_master_score( $node );
0c4b27
+
0c4b27
+        return 1 if defined $score and $score ne '' and $score > -1;
0c4b27
+    }
0c4b27
+
0c4b27
+    return 0;
0c4b27
+}
0c4b27
+
0c4b27
+# Check if the current transiation is a recover of a master clone on given node.
0c4b27
+sub _is_master_recover {
0c4b27
+    my ( $n ) = @_;
0c4b27
+
0c4b27
+    return (
0c4b27
+            scalar grep { $_->{'uname'} eq $n } @{ $OCF_NOTIFY_ENV{'master'} }
0c4b27
+        and scalar grep { $_->{'uname'} eq $n } @{ $OCF_NOTIFY_ENV{'promote'} }
0c4b27
+    );
0c4b27
+}
0c4b27
+
0c4b27
+# Check if the current transition is a recover of a slave clone on given node.
0c4b27
+sub _is_slave_recover {
0c4b27
+    my ( $n ) = @_;
0c4b27
+
0c4b27
+    return (
0c4b27
+            scalar grep { $_->{'uname'} eq $n } @{ $OCF_NOTIFY_ENV{'slave'} }
0c4b27
+        and scalar grep { $_->{'uname'} eq $n } @{ $OCF_NOTIFY_ENV{'start'} }
0c4b27
+    );
0c4b27
+}
0c4b27
+
0c4b27
+# check if th current transition is a switchover to the given node.
0c4b27
+sub _is_switchover {
0c4b27
+    my ( $n ) = @_;
0c4b27
+    my $old = $OCF_NOTIFY_ENV{'master'}[0]{'uname'};
0c4b27
+
0c4b27
+    return 0 if scalar @{ $OCF_NOTIFY_ENV{'master'} }  != 1
0c4b27
+             or scalar @{ $OCF_NOTIFY_ENV{'demote'} }  != 1
0c4b27
+             or scalar @{ $OCF_NOTIFY_ENV{'promote'} } != 1;
0c4b27
+
0c4b27
+    return (
0c4b27
+           scalar grep { $_->{'uname'} eq $old } @{ $OCF_NOTIFY_ENV{'demote'} }
0c4b27
+       and scalar grep { $_->{'uname'} eq $n } @{ $OCF_NOTIFY_ENV{'slave'} }
0c4b27
+       and scalar grep { $_->{'uname'} eq $n } @{ $OCF_NOTIFY_ENV{'promote'} }
0c4b27
+       and not scalar grep { $_->{'uname'} eq $old } @{ $OCF_NOTIFY_ENV{'stop'} }
0c4b27
+    );
0c4b27
+}
0c4b27
+
0c4b27
+# Run the given command as the "system_user" given as parameter.
0c4b27
+# It basically forks and seteuid/setuid away from root.
0c4b27
+#
0c4b27
+sub _runas {
0c4b27
+    my $rc;
0c4b27
+    my $pid;
0c4b27
+    my @cmd = @_;
0c4b27
+    my (undef, undef, $postgres_uid, $postgres_gid ) = getpwnam( $system_user );
0c4b27
+
0c4b27
+    $pid = fork;
0c4b27
+
0c4b27
+    if ( $pid == 0 ) { # in child
0c4b27
+        $) = "$postgres_gid $postgres_gid";
0c4b27
+        while ( my ( undef, undef, $gid, $members ) = getgrent ) {
0c4b27
+            $) .= " $gid" if grep { $system_user eq $_ } split /\s+/, $members
0c4b27
+        }
0c4b27
+        $( = $postgres_gid;
0c4b27
+
0c4b27
+        $< = $> = $postgres_uid;
0c4b27
+
0c4b27
+        exec @cmd;
0c4b27
+    }
0c4b27
+
0c4b27
+    ocf_log( 'debug', '_runas: launching as "%s" command "%s"', $system_user,
0c4b27
+        join(' ', @cmd) );
0c4b27
+
0c4b27
+    waitpid $pid, 0;
0c4b27
+    $rc = $? >> 8;
0c4b27
+
0c4b27
+    return $rc;
0c4b27
+}
0c4b27
+
0c4b27
+# Check if instance is listening on the given host/port.
0c4b27
+#
0c4b27
+sub _pg_isready {
0c4b27
+    # Add 60s to the timeout or use a 24h timeout fallback to make sure
0c4b27
+    # Pacemaker will give up before us and take decisions
0c4b27
+    my $timeout = ( _get_action_timeout() || 60*60*24 )  + 60;
0c4b27
+    my $rc = _runas( $PGISREADY, '-h', $pghost, '-p', $pgport, '-d', 'postgres', '-t', $timeout );
0c4b27
+
0c4b27
+    # Possible error codes:
0c4b27
+    #   1: ping rejected (usually when instance is in startup, in crash
0c4b27
+    #      recovery, in warm standby, or when a shutdown is in progress)
0c4b27
+    #   2: no response, usually means the instance is down
0c4b27
+    #   3: no attempt, probably a syntax error, should not happen
0c4b27
+    return $rc;
0c4b27
+}
0c4b27
+
0c4b27
+# Check the postmaster.pid file and the postmaster process.
0c4b27
+# WARNING: we do not distinguish the scenario where postmaster.pid does not
0c4b27
+# exist from the scenario where the process is still alive. It should be ok
0c4b27
+# though, as this is considered a hard error from monitor.
0c4b27
+#
0c4b27
+sub _pg_ctl_status {
0c4b27
+    my $rc = _runas( $PGCTL, '--pgdata', $pgdata, 'status' );
0c4b27
+
0c4b27
+    # Possible error codes:
0c4b27
+    #   3: postmaster.pid file does not exist OR it does but the process
0c4b27
+    #      with the PID found in the file is not alive
0c4b27
+    return $rc;
0c4b27
+}
0c4b27
+
0c4b27
+# Start the local instance using pg_ctl
0c4b27
+#
0c4b27
+sub _pg_ctl_start {
0c4b27
+    # Add 60s to the timeout or use a 24h timeout fallback to make sure
0c4b27
+    # Pacemaker will give up before us and take decisions
0c4b27
+    my $timeout = ( _get_action_timeout() || 60*60*24 ) + 60;
0c4b27
+
0c4b27
+    my @cmd = ( $PGCTL, '--pgdata', $pgdata, '-w', '--timeout', $timeout, 'start' );
0c4b27
+
0c4b27
+    push @cmd => ( '-o', $start_opts ) if $start_opts ne '';
0c4b27
+
0c4b27
+    return _runas( @cmd );
0c4b27
+}
0c4b27
+
0c4b27
+# Enable the Standby mode.
0c4b27
+#
0c4b27
+# Up to v11, creates the recovery.conf file based on the given template.
0c4b27
+# Since v12, creates standby.signal.
0c4b27
+sub _enable_recovery {
0c4b27
+    my $fh;
0c4b27
+    my $content      = '';
0c4b27
+    my $standby_file = "$datadir/standby.signal";
0c4b27
+    my (undef, undef, $uid, $gid) = getpwnam($system_user);
0c4b27
+
0c4b27
+    if ( $PGVERNUM < $PGVER_12 ) {
0c4b27
+        $standby_file = "$datadir/recovery.conf";
0c4b27
+
0c4b27
+        ocf_log( 'debug',
0c4b27
+            '_enable_recovery: get replication configuration from the template file "%s"',
0c4b27
+            $recovery_tpl );
0c4b27
+
0c4b27
+        # Create the recovery.conf file to start the instance as a secondary.
0c4b27
+        # NOTE: the recovery.conf is supposed to be set up so the secondary can
0c4b27
+        # connect to the primary instance, eg. using a virtual IP address.
0c4b27
+        # As there is no primary instance available at startup, secondaries will
0c4b27
+        # complain about failing to connect.
0c4b27
+        # As we can not reload a recovery.conf file on a standby without restarting
0c4b27
+        # it, we will leave with this.
0c4b27
+        # FIXME how would the reload help us in this case ?
0c4b27
+        unless ( defined open( $fh, '<', $recovery_tpl ) ) {
0c4b27
+            ocf_exit_reason( 'Could not open file "%s": %s', $recovery_tpl, $! );
0c4b27
+            exit $OCF_ERR_CONFIGURED;
0c4b27
+        }
0c4b27
+
0c4b27
+        # Copy all parameters from the template file
0c4b27
+        while (my $line = <$fh>) {
0c4b27
+            chomp $line;
0c4b27
+            $content .= "$line\n";
0c4b27
+        }
0c4b27
+        close $fh;
0c4b27
+    }
0c4b27
+
0c4b27
+    ocf_log( 'debug', '_enable_recovery: write the standby file "%s"', $standby_file );
0c4b27
+
0c4b27
+    unless ( open( $fh, '>', $standby_file ) ) {
0c4b27
+        ocf_exit_reason( 'Could not open file "%s": %s', $standby_file, $! );
0c4b27
+        exit $OCF_ERR_CONFIGURED;
0c4b27
+    }
0c4b27
+
0c4b27
+    # Write the recovery.conf file using configuration from the template file
0c4b27
+    print $fh $content;
0c4b27
+
0c4b27
+    close $fh;
0c4b27
+
0c4b27
+    unless ( chown $uid, $gid, $standby_file ) {
0c4b27
+        ocf_exit_reason( 'Could not set owner of "%s"', $standby_file );
0c4b27
+        exit $OCF_ERR_CONFIGURED;
0c4b27
+    };
0c4b27
+}
0c4b27
+
0c4b27
+# Parse and return various informations about the local PostgreSQL instance as
0c4b27
+# reported by its controldata file.
0c4b27
+#
0c4b27
+# WARNING: the status is NOT updated in case of crash.
0c4b27
+#
0c4b27
+# This sub exit the script with an error on failure
0c4b27
+sub _get_controldata {
0c4b27
+    my %controldata;
0c4b27
+    my $ans;
0c4b27
+
0c4b27
+    $ans = qx{ $PGCTRLDATA "$datadir" 2>/dev/null };
0c4b27
+
0c4b27
+    # Parse the output of pg_controldata.
0c4b27
+    # This output is quite stable between pg versions, but we might need to sort
0c4b27
+    # it at some point if things are moving in there...
0c4b27
+    $ans =~ m{
0c4b27
+        # get the current state
0c4b27
+        ^\QDatabase cluster state\E:\s+(.*?)\s*$
0c4b27
+        .*
0c4b27
+        # Get the latest known REDO location
0c4b27
+        ^\QLatest checkpoint's REDO location\E:\s+([/0-9A-F]+)\s*$
0c4b27
+        .*
0c4b27
+        # Get the latest known TL
0c4b27
+        ^\QLatest checkpoint's TimeLineID\E:\s+(\d+)\s*$
0c4b27
+        .*
0c4b27
+        # Get the wal level
0c4b27
+        # NOTE: pg_controldata output changed with PostgreSQL 9.5, so we need to
0c4b27
+        # account for both syntaxes
0c4b27
+        ^(?:\QCurrent \E)?\Qwal_level setting\E:\s+(.*?)\s*$
0c4b27
+    }smx;
0c4b27
+
0c4b27
+    $controldata{'state'}     = $1 if defined $1;
0c4b27
+    $controldata{'redo'}      = $2 if defined $2;
0c4b27
+    $controldata{'tl'}        = $3 if defined $3;
0c4b27
+    $controldata{'wal_level'} = $4 if defined $4;
0c4b27
+
0c4b27
+    ocf_log( 'debug',
0c4b27
+        "_get_controldata: found: %s",
0c4b27
+        Data::Dumper->new( [ \%controldata ] )->Terse(1)->Dump );
0c4b27
+
0c4b27
+    return %controldata if defined $controldata{'state'}
0c4b27
+                        and defined $controldata{'tl'}
0c4b27
+                        and defined $controldata{'redo'}
0c4b27
+                        and defined $controldata{'wal_level'};
0c4b27
+
0c4b27
+    ocf_exit_reason( 'Could not read all datas from controldata file for "%s"',
0c4b27
+        $datadir );
0c4b27
+
0c4b27
+    ocf_log( 'debug',
0c4b27
+        "_get_controldata: controldata file: %s",
0c4b27
+        Data::Dumper->new( [ \%controldata ] )->Terse(1)->Dump, $ans );
0c4b27
+
0c4b27
+    exit $OCF_ERR_ARGS;
0c4b27
+}
0c4b27
+
0c4b27
+# Pead major version from datadir/PG_VERSION and return it as numeric version
0c4b27
+sub _get_pg_version {
0c4b27
+    my $fh;
0c4b27
+    my $PGVERSION;
0c4b27
+    my $PGVERNUM;
0c4b27
+
0c4b27
+    # check PG_VERSION
0c4b27
+    if ( ! -s "$datadir/PG_VERSION" ) {
0c4b27
+        ocf_exit_reason( 'PG_VERSION does not exist in "%s"', $datadir );
0c4b27
+        exit $OCF_ERR_ARGS;
0c4b27
+    }
0c4b27
+
0c4b27
+    unless ( open( $fh, '<', "$datadir/PG_VERSION" ) ) {
0c4b27
+        ocf_exit_reason( "Could not open file \"$datadir/PG_VERSION\": $!" );
0c4b27
+        exit $OCF_ERR_ARGS;
0c4b27
+    }
0c4b27
+
0c4b27
+    read( $fh, $PGVERSION, 32 );
0c4b27
+    close $fh;
0c4b27
+
0c4b27
+    chomp $PGVERSION;
0c4b27
+
0c4b27
+    $PGVERSION =~ /^(\d+)(?:\.(\d+))?$/;
0c4b27
+    $PGVERNUM  = $1 * 10000;
0c4b27
+    $PGVERNUM += $2 * 100 if $1 < 10; # no 2nd num in the major version from v10
0c4b27
+
0c4b27
+    return $PGVERNUM;
0c4b27
+}
0c4b27
+
0c4b27
+# Use pg_controldata to check the state of the PostgreSQL server. This
0c4b27
+# function returns codes depending on this state, so we can find whether the
0c4b27
+# instance is a primary or a secondary, or use it to detect any inconsistency
0c4b27
+# that could indicate the instance has crashed.
0c4b27
+#
0c4b27
+sub _controldata_to_ocf {
0c4b27
+    my %cdata = _get_controldata();
0c4b27
+
0c4b27
+    while ( 1 ) {
0c4b27
+        ocf_log( 'debug', '_controldata: instance "%s" state is "%s"',
0c4b27
+            $OCF_RESOURCE_INSTANCE, $cdata{'state'} );
0c4b27
+
0c4b27
+        # Instance should be running as a primary.
0c4b27
+        return $OCF_RUNNING_MASTER if $cdata{'state'} eq "in production";
0c4b27
+
0c4b27
+        # Instance should be running as a secondary.
0c4b27
+        # This state includes warm standby (rejects connections attempts,
0c4b27
+        # including pg_isready)
0c4b27
+        return $OCF_SUCCESS if $cdata{'state'} eq "in archive recovery";
0c4b27
+
0c4b27
+
0c4b27
+        # The instance should be stopped.
0c4b27
+        # We don't care if it was a primary or secondary before, because we
0c4b27
+        # always start instances as secondaries, and then promote if necessary.
0c4b27
+        return $OCF_NOT_RUNNING if $cdata{'state'} eq "shut down"
0c4b27
+            or $cdata{'state'} eq "shut down in recovery";
0c4b27
+
0c4b27
+        # The state is "in crash recovery", "starting up" or "shutting down".
0c4b27
+        # This state should be transitional, so we wait and loop to check if
0c4b27
+        # it changes.
0c4b27
+        # If it does not, pacemaker will eventually abort with a timeout.
0c4b27
+        ocf_log( 'debug',
0c4b27
+            '_controldata: waiting for transitionnal state "%s" to finish',
0c4b27
+            $cdata{'state'} );
0c4b27
+        sleep 1;
0c4b27
+        %cdata = _get_controldata();
0c4b27
+    }
0c4b27
+
0c4b27
+    # If we reach this point, something went really wrong with this code or
0c4b27
+    # pg_controldata.
0c4b27
+    ocf_exit_reason( 'Unable get instance "%s" state using pg_controldata',
0c4b27
+        $OCF_RESOURCE_INSTANCE );
0c4b27
+
0c4b27
+    return $OCF_ERR_INSTALLED ;
0c4b27
+}
0c4b27
+
0c4b27
+# Check the write_location of all secondaries, and adapt their master score so
0c4b27
+# that the instance closest to the master will be the selected candidate should
0c4b27
+# a promotion be triggered.
0c4b27
+# NOTE: This is only a hint to pacemaker! The selected candidate to promotion
0c4b27
+# actually re-check it is the best candidate and force a re-election by failing
0c4b27
+# if a better one exists. This avoid a race condition between the call of the
0c4b27
+# monitor action and the promotion where another slave might have catchup faster
0c4b27
+# with the master.
0c4b27
+# NOTE: we cannot directly use the write_location, neither a lsn_diff value as
0c4b27
+# promotion score as Pacemaker considers any value greater than 1,000,000 as
0c4b27
+# INFINITY.
0c4b27
+#
0c4b27
+# This sub must be executed from a master monitor action.
0c4b27
+#
0c4b27
+sub _check_locations {
0c4b27
+    my $partition_nodes;
0c4b27
+    my $node_score;
0c4b27
+    my $row_num;
0c4b27
+    my $row;
0c4b27
+    my @rs;
0c4b27
+
0c4b27
+    # Set the master score if not already done
0c4b27
+    $node_score = _get_master_score();
0c4b27
+    _set_master_score( '1001' ) unless $node_score eq '1001';
0c4b27
+
0c4b27
+    # Ask crm_node what nodes are present in our current cluster partition
0c4b27
+    $partition_nodes = qx{ $CRM_NODE --partition };
0c4b27
+
0c4b27
+    @rs = @{ _get_lag_scores() };
0c4b27
+
0c4b27
+    $row_num = scalar @rs;
0c4b27
+
0c4b27
+    # If no lag are reported at this point, it means that there is no
0c4b27
+    # secondary instance connected.
0c4b27
+    ocf_log( 'warning', 'No secondary connected to the master' )
0c4b27
+        if $row_num == 0;
0c4b27
+
0c4b27
+    # For each standby connected, set their master score based on the following
0c4b27
+    # rule: the first known node/application, with the highest priority and
0c4b27
+    # an acceptable state.
0c4b27
+    while ( $row = shift @rs ) {
0c4b27
+
0c4b27
+        if ( $partition_nodes !~ /$row->[0]/ ) {
0c4b27
+            ocf_log( 'info', 'Ignoring unknown application_name/node "%s"',
0c4b27
+                $row->[0] );
0c4b27
+            next;
0c4b27
+        }
0c4b27
+
0c4b27
+        if ( $row->[0] eq $nodename ) {
0c4b27
+            ocf_log( 'warning', 'Streaming replication with myself!' );
0c4b27
+            next;
0c4b27
+        }
0c4b27
+
0c4b27
+        $node_score = _get_master_score( $row->[0] );
0c4b27
+
0c4b27
+        if ( $row->[3] =~ /^\s*(?:startup|backup)\s*$/ ) {
0c4b27
+            # We exclude any standby being in state backup (pg_basebackup) or
0c4b27
+            # startup (new standby or failing standby)
0c4b27
+            ocf_log( 'info', 'Forbidding promotion on "%s" in state "%s"',
0c4b27
+                $row->[0], $row->[3] );
0c4b27
+
0c4b27
+            _set_master_score( '-1', $row->[0] ) unless $node_score eq '-1';
0c4b27
+        }
0c4b27
+        else {
0c4b27
+            ocf_log( 'debug',
0c4b27
+                '_check_locations: checking "%s" promotion ability (current_score: %s, priority: %s, location: %s, lag: %s)',
0c4b27
+                $row->[0], $node_score, $row->[1], $row->[2], $row->[4] );
0c4b27
+
0c4b27
+            if ( $node_score ne $row->[1] ) {
0c4b27
+                if ( $row->[1] < -1 ) {
0c4b27
+                    ocf_log( 'info', 'Update score of "%s" from %s to %s because replication lag (%s) is higher than given maxlag (%s).',
0c4b27
+                        $row->[0], $node_score, $row->[1], $row->[4], $maxlag );
0c4b27
+                }
0c4b27
+                else {
0c4b27
+                    ocf_log( 'info', 'Update score of "%s" from %s to %s because of a change in the replication lag (%s).',
0c4b27
+                        $row->[0], $node_score, $row->[1], $row->[4] );
0c4b27
+                }
0c4b27
+                _set_master_score( $row->[1], $row->[0] );
0c4b27
+            }
0c4b27
+            else {
0c4b27
+                ocf_log( 'debug',
0c4b27
+                    '_check_locations: "%s" keeps its current score of %s',
0c4b27
+                    $row->[0], $row->[1] );
0c4b27
+            }
0c4b27
+        }
0c4b27
+
0c4b27
+        # Remove this node from the known nodes list.
0c4b27
+        $partition_nodes =~ s/(?:^|\s)$row->[0](?:\s|$)/ /g;
0c4b27
+    }
0c4b27
+
0c4b27
+    $partition_nodes =~ s/(?:^\s+)|(?:\s+$)//g;
0c4b27
+
0c4b27
+    # If there are still nodes in "partition_nodes", it means there is no
0c4b27
+    # corresponding line in "pg_stat_replication".
0c4b27
+    # Exclude these nodes that are not part of the cluster at this
0c4b27
+    # point.
0c4b27
+    foreach my $node (split /\s+/ => $partition_nodes) {
0c4b27
+        # Exclude the current node.
0c4b27
+        next if $node eq $nodename;
0c4b27
+
0c4b27
+        # do not warn if the master score is already set to -1000.
0c4b27
+        # this avoid log flooding (gh #138)
0c4b27
+        $node_score = _get_master_score( $node );
0c4b27
+        next if $node_score eq '-1000';
0c4b27
+
0c4b27
+        ocf_log( 'warning', '"%s" is not connected to the primary', $node );
0c4b27
+        _set_master_score( '-1000', $node );
0c4b27
+    }
0c4b27
+
0c4b27
+    return $OCF_SUCCESS;
0c4b27
+}
0c4b27
+
0c4b27
+# _check_switchover
0c4b27
+# check if the pgsql switchover to the localnode is safe.
0c4b27
+# This is supposed to be called **after** the master has been stopped or demoted.
0c4b27
+# This sub checks if the local standby received the shutdown checkpoint from the
0c4b27
+# old master to make sure it can take over the master role and the old master
0c4b27
+# will be able to catchup as a standby after.
0c4b27
+#
0c4b27
+# Returns 0 if switchover is safe
0c4b27
+# Returns 1 if swithcover is not safe
0c4b27
+# Returns 2 for internal error
0c4b27
+sub _check_switchover {
0c4b27
+    my $has_sht_chk = 0;
0c4b27
+    my $last_redo;
0c4b27
+    my $last_lsn;
0c4b27
+    my $ans;
0c4b27
+    my $rc;
0c4b27
+    my $tl;
0c4b27
+    my %cdata;
0c4b27
+
0c4b27
+    $PGWALDUMP = "$bindir/pg_xlogdump" if $PGVERNUM < $PGVER_10;
0c4b27
+
0c4b27
+    ocf_log( 'info', 'Switchover in progress from "%s" to "%s".'
0c4b27
+        .' Need to check the last record in WAL',
0c4b27
+        $OCF_NOTIFY_ENV{'demote'}[0]{'uname'}, $nodename );
0c4b27
+
0c4b27
+    # check if we received the shutdown checkpoint of the master during its
0c4b27
+    # demote process.
0c4b27
+    # We need the last local checkpoint LSN and the last received LSN from
0c4b27
+    # master to check in the WAL between these adresses if we have a
0c4b27
+    # "checkpoint shutdown" using pg_xlogdump/pg_waldump.
0c4b27
+    #
0c4b27
+    # Force a checkpoint to make sure the controldata shows the very last TL
0c4b27
+    # and the master's shutdown checkpoint
0c4b27
+    _query( q{ CHECKPOINT }, {} );
0c4b27
+    %cdata     = _get_controldata();
0c4b27
+    $tl        = $cdata{'tl'};
0c4b27
+    $last_redo = $cdata{'redo'};
0c4b27
+
0c4b27
+    # Get the last received LSN from master
0c4b27
+    $last_lsn = _get_last_received_lsn();
0c4b27
+
0c4b27
+    unless ( defined $last_lsn ) {
0c4b27
+        ocf_exit_reason( 'Could not fetch last received LSN!' );
0c4b27
+
0c4b27
+        return 2;
0c4b27
+    }
0c4b27
+
0c4b27
+    $ans = qx{ $PGWALDUMP --path "$datadir" --timeline "$tl" \\
0c4b27
+               --start "$last_redo" --end "$last_lsn" 2>&1 };
0c4b27
+    $rc = $?;
0c4b27
+
0c4b27
+    ocf_log( 'debug',
0c4b27
+        '_check_switchover: %s rc: "%s", tl: "%s", last_chk: %s, last_lsn: %s, output: "%s"',
0c4b27
+        $PGWALDUMP, $rc, $tl, $last_redo, $last_lsn, $ans
0c4b27
+    );
0c4b27
+
0c4b27
+    if ( $rc == 0 and
0c4b27
+         $ans =~ m{^rmgr: XLOG.*desc: (?i:checkpoint)(?::|_SHUTDOWN) redo [0-9A-F/]+; tli $tl;.*; shutdown$}m
0c4b27
+    ) {
0c4b27
+        ocf_log( 'info', 'Slave received the shutdown checkpoint' );
0c4b27
+        return 0;
0c4b27
+    }
0c4b27
+
0c4b27
+    ocf_exit_reason(
0c4b27
+        'Did not receive the shutdown checkpoint from the old master!' );
0c4b27
+
0c4b27
+    return 1;
0c4b27
+}
0c4b27
+
0c4b27
+# Check to confirm if the instance is really started as _pg_isready stated and
0c4b27
+# check if the instance is primary or secondary.
0c4b27
+#
0c4b27
+sub _confirm_role {
0c4b27
+    my $is_in_recovery;
0c4b27
+    my $rc;
0c4b27
+    my @rs;
0c4b27
+
0c4b27
+    $rc = _query( "SELECT pg_is_in_recovery()", \@rs );
0c4b27
+
0c4b27
+    $is_in_recovery = $rs[0][0];
0c4b27
+
0c4b27
+    if ( $rc == 0 ) {
0c4b27
+        # The query was executed, check the result.
0c4b27
+        if ( $is_in_recovery eq 't' ) {
0c4b27
+            # The instance is a secondary.
0c4b27
+            ocf_log( 'debug', "_confirm_role: instance $OCF_RESOURCE_INSTANCE is a secondary");
0c4b27
+            return $OCF_SUCCESS;
0c4b27
+        }
0c4b27
+        elsif ( $is_in_recovery eq 'f' ) {
0c4b27
+            # The instance is a primary.
0c4b27
+            ocf_log( 'debug', "_confirm_role: instance $OCF_RESOURCE_INSTANCE is a primary");
0c4b27
+            # Check lsn diff with current slaves if any
0c4b27
+            _check_locations() if $__OCF_ACTION eq 'monitor';
0c4b27
+            return $OCF_RUNNING_MASTER;
0c4b27
+        }
0c4b27
+
0c4b27
+        # This should not happen, raise a hard configuration error.
0c4b27
+        ocf_exit_reason(
0c4b27
+            'Unexpected result from query to check if "%s" is a primary or a secondary: "%s"',
0c4b27
+            $OCF_RESOURCE_INSTANCE, $is_in_recovery );
0c4b27
+
0c4b27
+        return $OCF_ERR_CONFIGURED;
0c4b27
+    }
0c4b27
+    elsif ( $rc == 1 or $rc == 2 ) {
0c4b27
+        # psql cound not connect to the instance.
0c4b27
+        # As pg_isready reported the instance was listening, this error
0c4b27
+        # could be a max_connection saturation. Just report a soft error.
0c4b27
+        ocf_exit_reason( 'psql could not connect to instance "%s"',
0c4b27
+            $OCF_RESOURCE_INSTANCE );
0c4b27
+        return $OCF_ERR_GENERIC;
0c4b27
+    }
0c4b27
+
0c4b27
+    # The query failed (rc: 3) or bad parameters (rc: -1).
0c4b27
+    # This should not happen, raise a hard configuration error.
0c4b27
+    ocf_exit_reason(
0c4b27
+        'The query to check if instance "%s" is a primary or a secondary failed (rc: %d)',
0c4b27
+        $OCF_RESOURCE_INSTANCE, $rc );
0c4b27
+
0c4b27
+    return $OCF_ERR_CONFIGURED;
0c4b27
+}
0c4b27
+
0c4b27
+
0c4b27
+# Check to confirm if the instance is really stopped as _pg_isready stated
0c4b27
+# and if it was propertly shut down.
0c4b27
+#
0c4b27
+sub _confirm_stopped {
0c4b27
+    my $pgctlstatus_rc;
0c4b27
+    my $controldata_rc;
0c4b27
+
0c4b27
+    # Check the postmaster process status.
0c4b27
+    $pgctlstatus_rc = _pg_ctl_status();
0c4b27
+
0c4b27
+    if ( $pgctlstatus_rc == 0 ) {
0c4b27
+        # The PID file exists and the process is available.
0c4b27
+        # That should not be the case, return an error.
0c4b27
+        ocf_exit_reason(
0c4b27
+            'Instance "%s" is not listening, but the process referenced in postmaster.pid exists',
0c4b27
+            $OCF_RESOURCE_INSTANCE );
0c4b27
+        return $OCF_ERR_GENERIC;
0c4b27
+    }
0c4b27
+
0c4b27
+    # The PID file does not exist or the process is not available.
0c4b27
+    ocf_log( 'debug',
0c4b27
+        '_confirm_stopped: no postmaster process found for instance "%s"',
0c4b27
+        $OCF_RESOURCE_INSTANCE );
0c4b27
+
0c4b27
+    if ( -f "$datadir/backup_label" ) {
0c4b27
+        # We are probably on a freshly built secondary that was not started yet.
0c4b27
+        ocf_log( 'debug',
0c4b27
+            '_confirm_stopped: backup_label file exists: probably on a never started secondary',
0c4b27
+        );
0c4b27
+        return $OCF_NOT_RUNNING;
0c4b27
+    }
0c4b27
+
0c4b27
+    # Continue the check with pg_controldata.
0c4b27
+    $controldata_rc = _controldata_to_ocf();
0c4b27
+    if ( $controldata_rc == $OCF_RUNNING_MASTER ) {
0c4b27
+        # The controldata has not been updated to "shutdown".
0c4b27
+        # It should mean we had a crash on a primary instance.
0c4b27
+        ocf_exit_reason(
0c4b27
+            'Instance "%s" controldata indicates a running primary instance, the instance has probably crashed',
0c4b27
+            $OCF_RESOURCE_INSTANCE );
0c4b27
+        return $OCF_FAILED_MASTER;
0c4b27
+    }
0c4b27
+    elsif ( $controldata_rc == $OCF_SUCCESS ) {
0c4b27
+        # The controldata has not been updated to "shutdown in recovery".
0c4b27
+        # It should mean we had a crash on a secondary instance.
0c4b27
+        # There is no "FAILED_SLAVE" return code, so we return a generic error.
0c4b27
+        ocf_exit_reason(
0c4b27
+            'Instance "%s" controldata indicates a running secondary instance, the instance has probably crashed',
0c4b27
+            $OCF_RESOURCE_INSTANCE );
0c4b27
+        return $OCF_ERR_GENERIC;
0c4b27
+    }
0c4b27
+    elsif ( $controldata_rc == $OCF_NOT_RUNNING ) {
0c4b27
+        # The controldata state is consistent, the instance was probably
0c4b27
+        # propertly shut down.
0c4b27
+        ocf_log( 'debug',
0c4b27
+            '_confirm_stopped: instance "%s" controldata indicates that the instance was propertly shut down',
0c4b27
+            $OCF_RESOURCE_INSTANCE );
0c4b27
+        return $OCF_NOT_RUNNING;
0c4b27
+    }
0c4b27
+
0c4b27
+    # Something went wrong with the controldata check.
0c4b27
+    ocf_exit_reason(
0c4b27
+        'Could not get instance "%s" status from controldata (returned: %d)',
0c4b27
+        $OCF_RESOURCE_INSTANCE, $controldata_rc );
0c4b27
+
0c4b27
+    return $OCF_ERR_GENERIC;
0c4b27
+}
0c4b27
+
0c4b27
+############################################################
0c4b27
+#### OCF FUNCS
0c4b27
+
0c4b27
+
0c4b27
+
0c4b27
+=head1 SUPPORTED PARAMETERS
0c4b27
+
0c4b27
+=over
0c4b27
+
0c4b27
+=item B<pgdata>
0c4b27
+
0c4b27
+Location of the PGDATA of your instance
0c4b27
+
0c4b27
+(optional, string, default "/var/lib/pgsql/data")
0c4b27
+
0c4b27
+=item B<pghost>
0c4b27
+
0c4b27
+The socket directory or IP address to use to connect to the local instance
0c4b27
+
0c4b27
+(optional, string, default "/tmp")
0c4b27
+
0c4b27
+=item B<pgport>
0c4b27
+
0c4b27
+The port to connect to the local instance
0c4b27
+
0c4b27
+(optional, integer, default "5432")
0c4b27
+
0c4b27
+=item B<bindir>
0c4b27
+
0c4b27
+Location of the PostgreSQL binaries.
0c4b27
+
0c4b27
+(optional, string, default "/usr/bin")
0c4b27
+
0c4b27
+=item B<system_user>
0c4b27
+
0c4b27
+The system owner of your instance's process
0c4b27
+
0c4b27
+(optional, string, default "postgres")
0c4b27
+
0c4b27
+=item B<recovery_template>
0c4b27
+
0c4b27
+B<ONLY> for PostgreSQL 11 and bellow.
0c4b27
+
0c4b27
+The local template that will be copied as the C<PGDATA/recovery.conf> file.
0c4b27
+This template file must exists on all node.
0c4b27
+
0c4b27
+With PostgreSQL 12 and higher, the cluster will refuse to start if this
0c4b27
+parameter is set or a template file is found.
0c4b27
+
0c4b27
+(optional, string, default "$PGDATA/recovery.conf.pcmk")
0c4b27
+
0c4b27
+=item B<maxlag>
0c4b27
+
0c4b27
+Maximum lag allowed on a standby before we set a negative master score on it.
0c4b27
+The calculation is based on the difference between the current xlog location on
0c4b27
+the master and the write location on the standby.
0c4b27
+
0c4b27
+(optional, integer, default "0" disables this feature)
0c4b27
+
0c4b27
+=item B<datadir>
0c4b27
+
0c4b27
+Path to the directory set in C<data_directory> from your postgresql.conf file.
0c4b27
+This parameter has same default than PostgreSQL itself: the C<pgdata> parameter
0c4b27
+value.
0c4b27
+
0c4b27
+Unless you have a special PostgreSQL setup and you understand this parameter,
0c4b27
+B<ignore it>
0c4b27
+
0c4b27
+(optional, string, default to the value of C<pgdata>)
0c4b27
+
0c4b27
+=item B<start_opts>
0c4b27
+
0c4b27
+Additional arguments given to the postgres process on startup. See
0c4b27
+"postgres --help" for available options. Useful when the postgresql.conf file
0c4b27
+is not in the data directory (PGDATA), eg.:
0c4b27
+
0c4b27
+  -c config_file=/etc/postgresql/9.3/main/postgresql.conf
0c4b27
+
0c4b27
+(optinal, string, default "")
0c4b27
+
0c4b27
+=back
0c4b27
+
0c4b27
+=cut
0c4b27
+
0c4b27
+sub ocf_meta_data {
0c4b27
+    print qq{
0c4b27
+        
0c4b27
+        <resource-agent name="pgsqlms">
0c4b27
+          <version>1.0</version>
0c4b27
+
0c4b27
+          <longdesc lang="en">
0c4b27
+            Resource script for PostgreSQL in replication. It manages PostgreSQL servers using streaming replication as an HA resource.
0c4b27
+          </longdesc>
0c4b27
+          <shortdesc lang="en">Manages PostgreSQL servers in replication</shortdesc>
0c4b27
+          <parameters>
0c4b27
+            <parameter name="system_user" unique="0" required="0">
0c4b27
+              <longdesc lang="en">
0c4b27
+                System user account used to run the PostgreSQL server
0c4b27
+              </longdesc>
0c4b27
+              <shortdesc lang="en">PostgreSQL system User</shortdesc>
0c4b27
+              <content type="string" default="$system_user_default" />
0c4b27
+            </parameter>
0c4b27
+
0c4b27
+            <parameter name="bindir" unique="0" required="0">
0c4b27
+              <longdesc lang="en">
0c4b27
+                Path to the directory storing the PostgreSQL binaries. The agent uses psql, pg_isready, pg_controldata and pg_ctl.
0c4b27
+              </longdesc>
0c4b27
+              <shortdesc lang="en">Path to the PostgreSQL binaries</shortdesc>
0c4b27
+              <content type="string" default="$bindir_default" />
0c4b27
+            </parameter>
0c4b27
+
0c4b27
+            <parameter name="pgdata" unique="1" required="0">
0c4b27
+              <longdesc lang="en">
0c4b27
+                Path to the data directory, e.g. PGDATA
0c4b27
+              </longdesc>
0c4b27
+              <shortdesc lang="en">Path to the data directory</shortdesc>
0c4b27
+              <content type="string" default="$pgdata_default" />
0c4b27
+            </parameter>
0c4b27
+
0c4b27
+            <parameter name="datadir" unique="1" required="0">
0c4b27
+              <longdesc lang="en">
0c4b27
+                Path to the directory set in data_directory from your postgresql.conf file. This parameter
0c4b27
+                has the same default than PostgreSQL itself: the pgdata parameter value. Unless you have a
0c4b27
+                special PostgreSQL setup and you understand this parameter, ignore it.
0c4b27
+              </longdesc>
0c4b27
+              <shortdesc lang="en">Path to the directory set in data_directory from your postgresql.conf file</shortdesc>
0c4b27
+              <content type="string" default="PGDATA" />
0c4b27
+            </parameter>
0c4b27
+
0c4b27
+            <parameter name="pghost" unique="0" required="0">
0c4b27
+              <longdesc lang="en">
0c4b27
+                Host IP address or unix socket folder the instance is listening on.
0c4b27
+              </longdesc>
0c4b27
+              <shortdesc lang="en">Instance IP or unix socket folder</shortdesc>
0c4b27
+              <content type="string" default="$pghost_default" />
0c4b27
+            </parameter>
0c4b27
+
0c4b27
+            <parameter name="pgport" unique="0" required="0">
0c4b27
+              <longdesc lang="en">
0c4b27
+                Port the instance is listening on.
0c4b27
+              </longdesc>
0c4b27
+              <shortdesc lang="en">Instance port</shortdesc>
0c4b27
+              <content type="integer" default="$pgport_default" />
0c4b27
+            </parameter>
0c4b27
+
0c4b27
+           <parameter name="maxlag" unique="0" required="0">
0c4b27
+              <longdesc lang="en">
0c4b27
+                Maximum lag allowed on a standby before we set a negative master score on it. The calculation
0c4b27
+                is based on the difference between the current LSN on the master and the LSN
0c4b27
+                written on the standby.
0c4b27
+                This parameter must be a valid positive number as described in PostgreSQL documentation.
0c4b27
+                See: https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS-NUMERIC
0c4b27
+              </longdesc>
0c4b27
+              <shortdesc lang="en">Maximum write lag before we mark a standby as inappropriate to promote</shortdesc>
0c4b27
+              <content type="integer" default="$maxlag_default" />
0c4b27
+            </parameter>
0c4b27
+
0c4b27
+            <parameter name="recovery_template" unique="1" required="0">
0c4b27
+              <longdesc lang="en">
0c4b27
+                Path to the recovery.conf template. This file is simply copied to \$PGDATA
0c4b27
+                before starting the instance as slave.
0c4b27
+                ONLY for PostgreSQL 11 and bellow. This parameter is IGNORED for
0c4b27
+                PostgreSQL 12 and higher. The cluster will refuse to start if a template
0c4b27
+                file is found.
0c4b27
+              </longdesc>
0c4b27
+              <shortdesc lang="en">Path to the recovery.conf template for PostgreSQL 11 and older.</shortdesc>
0c4b27
+              <content type="string" default="PGDATA/recovery.conf.pcmk" />
0c4b27
+            </parameter>
0c4b27
+
0c4b27
+            <parameter name="start_opts" unique="0" required="0">
0c4b27
+              <longdesc lang="en">
0c4b27
+                Additionnal arguments given to the postgres process on startup.
0c4b27
+                See "postgres --help" for available options. Usefull when the
0c4b27
+                postgresql.conf file is not in the data directory (PGDATA), eg.:
0c4b27
+                "-c config_file=/etc/postgresql/9.3/main/postgresql.conf".
0c4b27
+              </longdesc>
0c4b27
+              <shortdesc lang="en">Additionnal arguments given to the postgres process on startup.</shortdesc>
0c4b27
+              <content type="string" default="$start_opts_default" />
0c4b27
+            </parameter>
0c4b27
+
0c4b27
+          </parameters>
0c4b27
+          <actions>
0c4b27
+            <action name="start" timeout="60" />
0c4b27
+            <action name="stop" timeout="60" />
0c4b27
+            <action name="reload" timeout="20" />
0c4b27
+            <action name="promote" timeout="30" />
0c4b27
+            <action name="demote" timeout="120" />
0c4b27
+            <action name="monitor" depth="0" timeout="10" interval="15"/>
0c4b27
+            <action name="monitor" depth="0" timeout="10" interval="15" role="Master"/>
0c4b27
+            <action name="monitor" depth="0" timeout="10" interval="16" role="Slave"/>
0c4b27
+            <action name="notify" timeout="60" />
0c4b27
+            <action name="meta-data" timeout="5" />
0c4b27
+            <action name="validate-all" timeout="5" />
0c4b27
+            <action name="methods" timeout="5" />
0c4b27
+          </actions>
0c4b27
+        </resource-agent>
0c4b27
+    };
0c4b27
+    return $OCF_SUCCESS;
0c4b27
+}
0c4b27
+
0c4b27
+
0c4b27
+=head1 SUPPORTED ACTIONS
0c4b27
+
0c4b27
+This resource agent supports the following actions (operations):
0c4b27
+
0c4b27
+=over
0c4b27
+
0c4b27
+=item B<start>
0c4b27
+
0c4b27
+Starts the resource. Suggested minimum timeout: 60.
0c4b27
+
0c4b27
+=item B<stop>
0c4b27
+
0c4b27
+Stops the resource. Suggested minimum timeout: 60.
0c4b27
+
0c4b27
+=item B<reload>
0c4b27
+
0c4b27
+Suggested minimum timeout: 20.
0c4b27
+
0c4b27
+=item B<promote>
0c4b27
+
0c4b27
+Promotes the resource to the Master role. Suggested minimum timeout: 30.
0c4b27
+
0c4b27
+=item B<demote>
0c4b27
+
0c4b27
+Demotes the resource to the Slave role. Suggested minimum timeout: 120.
0c4b27
+
0c4b27
+=item B<monitor (Master role)>
0c4b27
+
0c4b27
+Performs a detailed status check. Suggested minimum timeout: 10.
0c4b27
+Suggested interval: 15.
0c4b27
+
0c4b27
+=item B<monitor (Slave role)>
0c4b27
+
0c4b27
+Performs a detailed status check. Suggested minimum timeout: 10.
0c4b27
+Suggested interval: 16.
0c4b27
+
0c4b27
+=item B<notify>
0c4b27
+
0c4b27
+Suggested minimum timeout: 60
0c4b27
+
0c4b27
+=item B<meta-data>
0c4b27
+
0c4b27
+Retrieves resource agent metadata (internal use only).
0c4b27
+Suggested minimum timeout: 5.
0c4b27
+
0c4b27
+=item B<methods>
0c4b27
+
0c4b27
+Suggested minimum timeout: 5.
0c4b27
+
0c4b27
+=item B<validate-all>
0c4b27
+
0c4b27
+Performs a validation of the resource configuration.
0c4b27
+Suggested minimum timeout: 5.
0c4b27
+
0c4b27
+=back
0c4b27
+
0c4b27
+=cut
0c4b27
+
0c4b27
+sub ocf_methods {
0c4b27
+    print q{
0c4b27
+        start
0c4b27
+        stop
0c4b27
+        reload
0c4b27
+        promote
0c4b27
+        demote
0c4b27
+        monitor
0c4b27
+        notify
0c4b27
+        methods
0c4b27
+        meta-data
0c4b27
+        validate-all
0c4b27
+    };
0c4b27
+
0c4b27
+    return $OCF_SUCCESS;
0c4b27
+}
0c4b27
+
0c4b27
+############################################################
0c4b27
+#### RA FUNCS
0c4b27
+
0c4b27
+sub pgsql_validate_all {
0c4b27
+    my $fh;
0c4b27
+    my $ans = '';
0c4b27
+    my %cdata;
0c4b27
+
0c4b27
+    unless (
0c4b27
+        ocf_version_cmp( $ENV{"OCF_RESKEY_crm_feature_set"}, '3.0.9' ) == 2
0c4b27
+    ) {
0c4b27
+        ocf_exit_reason(
0c4b27
+            'PAF %s is compatible with Pacemaker 1.1.13 and greater',
0c4b27
+            $VERSION
0c4b27
+        );
0c4b27
+        return $OCF_ERR_INSTALLED;
0c4b27
+    }
0c4b27
+
0c4b27
+    # check notify=true
0c4b27
+    $ans = qx{ $CRM_RESOURCE --resource "$OCF_RESOURCE_INSTANCE" \\
0c4b27
+                 --meta --get-parameter notify 2>/dev/null };
0c4b27
+    chomp $ans;
0c4b27
+    unless ( lc($ans) =~ /^true$|^on$|^yes$|^y$|^1$/ ) {
0c4b27
+        ocf_exit_reason(
0c4b27
+            'You must set meta parameter notify=true for your master resource'
0c4b27
+        );
0c4b27
+        return $OCF_ERR_INSTALLED;
0c4b27
+    }
0c4b27
+
0c4b27
+    # check master-max=1
0c4b27
+    unless (
0c4b27
+        defined $ENV{'OCF_RESKEY_CRM_meta_master_max'}
0c4b27
+            and $ENV{'OCF_RESKEY_CRM_meta_master_max'} eq '1'
0c4b27
+    ) {
0c4b27
+        ocf_exit_reason(
0c4b27
+            'You must set meta parameter master-max=1 for your master resource'
0c4b27
+        );
0c4b27
+        return $OCF_ERR_INSTALLED;
0c4b27
+    }
0c4b27
+
0c4b27
+    if ( $PGVERNUM >= $PGVER_12 ) {
0c4b27
+        # check PostgreSQL setup: checks related to v12 and after
0c4b27
+        my $guc;
0c4b27
+
0c4b27
+        # recovery.conf template must not exists
0c4b27
+        if ( -f $recovery_tpl ) {
0c4b27
+            ocf_exit_reason(
0c4b27
+                'Recovery template file "%s" is forbidden for PostgreSQL 12 and above',
0c4b27
+                $recovery_tpl );
0c4b27
+            exit $OCF_ERR_ARGS;
0c4b27
+        }
0c4b27
+
0c4b27
+        # WARNING: you MUST put -C as first argument to bypass the root check
0c4b27
+        $guc = qx{ $POSTGRES -C recovery_target_timeline -D "$pgdata" $start_opts};
0c4b27
+        chomp $guc;
0c4b27
+        unless ( $guc eq 'latest' ) {
0c4b27
+            ocf_exit_reason(
0c4b27
+                q{Parameter "recovery_target_timeline" MUST be set to 'latest'. } .
0c4b27
+                q{It is currently set to '%s'}, $guc );
0c4b27
+            return $OCF_ERR_ARGS;
0c4b27
+        }
0c4b27
+
0c4b27
+        $guc = qx{ $POSTGRES -C primary_conninfo -D "$pgdata" $start_opts};
0c4b27
+        unless ($guc =~ /\bapplication_name='?$nodename'?\b/) {
0c4b27
+            ocf_exit_reason(
0c4b27
+                q{Parameter "primary_conninfo" MUST contain 'application_name=%s'. }.
0c4b27
+                q{It is currently set to '%s'}, $nodename, $guc );
0c4b27
+            return $OCF_ERR_ARGS;
0c4b27
+        }
0c4b27
+    }
0c4b27
+    else {
0c4b27
+        my @content;
0c4b27
+
0c4b27
+        # check recovery template
0c4b27
+        if ( ! -f $recovery_tpl ) {
0c4b27
+            ocf_exit_reason( 'Recovery template file "%s" does not exist',
0c4b27
+                $recovery_tpl );
0c4b27
+            return $OCF_ERR_ARGS;
0c4b27
+        }
0c4b27
+
0c4b27
+        # check content of the recovery template file
0c4b27
+        unless ( open( $fh, '<', $recovery_tpl ) ) {
0c4b27
+            ocf_exit_reason( 'Could not open file "%s": %s', $recovery_tpl, $! );
0c4b27
+            return $OCF_ERR_ARGS;
0c4b27
+        }
0c4b27
+        @content = <$fh>;
0c4b27
+        close $fh;
0c4b27
+
0c4b27
+
0c4b27
+        unless ( grep /^\s*standby_mode\s*=\s*'?on'?\s*$/, @content ) {
0c4b27
+            ocf_exit_reason(
0c4b27
+                'Recovery template file must contain "standby_mode = on"' );
0c4b27
+            return $OCF_ERR_ARGS;
0c4b27
+        }
0c4b27
+
0c4b27
+        unless ( grep /^\s*recovery_target_timeline\s*=\s*'?latest'?\s*$/, @content ) {
0c4b27
+            ocf_exit_reason(
0c4b27
+                "Recovery template file must contain \"recovery_target_timeline = 'latest'\""
0c4b27
+            );
0c4b27
+            return $OCF_ERR_ARGS;
0c4b27
+        }
0c4b27
+
0c4b27
+        unless (
0c4b27
+            grep /^\s*primary_conninfo\s*=.*['\s]application_name=$nodename['\s]/,
0c4b27
+            @content
0c4b27
+        ) {
0c4b27
+            ocf_exit_reason(
0c4b27
+                'Recovery template file must contain in primary_conninfo parameter "application_name=%s"',
0c4b27
+                $nodename );
0c4b27
+            return $OCF_ERR_ARGS;
0c4b27
+        }
0c4b27
+    }
0c4b27
+
0c4b27
+    unless ( looks_like_number($maxlag) ) {
0c4b27
+        ocf_exit_reason( 'maxlag is not a number: "%s"', $maxlag );
0c4b27
+        return $OCF_ERR_INSTALLED;
0c4b27
+    }
0c4b27
+
0c4b27
+    # check system user
0c4b27
+    unless ( defined getpwnam $system_user ) {
0c4b27
+        ocf_exit_reason( 'System user "%s" does not exist', $system_user );
0c4b27
+        return $OCF_ERR_ARGS;
0c4b27
+    }
0c4b27
+
0c4b27
+    # require 9.3 minimum
0c4b27
+    if ( $PGVERNUM < $PGVER_93 ) {
0c4b27
+        ocf_exit_reason( "Require 9.3 and more" );
0c4b27
+        return $OCF_ERR_INSTALLED;
0c4b27
+    }
0c4b27
+
0c4b27
+    # check binaries
0c4b27
+    unless ( -x $PGCTL and -x $PGPSQL and -x $PGCTRLDATA and -x $PGISREADY
0c4b27
+         and ( -x $PGWALDUMP or -x "$bindir/pg_xlogdump")
0c4b27
+     ) {
0c4b27
+        ocf_exit_reason(
0c4b27
+            "Missing one or more binary. Check following path: %s, %s, %s, %s, %s or %s",
0c4b27
+            $PGCTL, $PGPSQL, $PGCTRLDATA, $PGISREADY, $PGWALDUMP, "$bindir/pg_xlogdump" );
0c4b27
+        return $OCF_ERR_ARGS;
0c4b27
+    }
0c4b27
+
0c4b27
+    # require wal_level >= hot_standby
0c4b27
+    %cdata = _get_controldata();
0c4b27
+    unless ( $cdata{'wal_level'} =~ m{hot_standby|logical|replica} ) {
0c4b27
+        ocf_exit_reason(
0c4b27
+            'wal_level must be one of "hot_standby", "logical" or "replica"' );
0c4b27
+        return $OCF_ERR_ARGS;
0c4b27
+    }
0c4b27
+
0c4b27
+    return $OCF_SUCCESS;
0c4b27
+}
0c4b27
+
0c4b27
+
0c4b27
+# Start the PostgreSQL instance as a *secondary*
0c4b27
+#
0c4b27
+sub pgsql_start {
0c4b27
+    my $rc         = pgsql_monitor();
0c4b27
+    my %cdata      = _get_controldata();
0c4b27
+    my $prev_state = $cdata{'state'};
0c4b27
+
0c4b27
+    # Instance must be running as secondary or being stopped.
0c4b27
+    # Anything else is an error.
0c4b27
+    if ( $rc == $OCF_SUCCESS ) {
0c4b27
+        ocf_log( 'info', 'Instance "%s" already started',
0c4b27
+            $OCF_RESOURCE_INSTANCE );
0c4b27
+        return $OCF_SUCCESS;
0c4b27
+    }
0c4b27
+    elsif ( $rc != $OCF_NOT_RUNNING ) {
0c4b27
+        ocf_exit_reason( 'Unexpected state for instance "%s" (returned %d)',
0c4b27
+            $OCF_RESOURCE_INSTANCE, $rc );
0c4b27
+        return $OCF_ERR_GENERIC;
0c4b27
+    }
0c4b27
+
0c4b27
+    #
0c4b27
+    # From here, the instance is NOT running for sure.
0c4b27
+    #
0c4b27
+
0c4b27
+    ocf_log( 'debug',
0c4b27
+        'pgsql_start: instance "%s" is not running, starting it as a secondary',
0c4b27
+        $OCF_RESOURCE_INSTANCE );
0c4b27
+
0c4b27
+    # Must start as a standby, so enable recovery.
0c4b27
+    _enable_recovery();
0c4b27
+
0c4b27
+    # Start the instance as a secondary.
0c4b27
+    $rc = _pg_ctl_start();
0c4b27
+
0c4b27
+    if ( $rc == 0 ) {
0c4b27
+
0c4b27
+        # Wait for the start to finish.
0c4b27
+        sleep 1 while ( $rc = pgsql_monitor() ) == $OCF_NOT_RUNNING;
0c4b27
+
0c4b27
+        if ( $rc == $OCF_SUCCESS ) {
0c4b27
+            ocf_log( 'info', 'Instance "%s" started', $OCF_RESOURCE_INSTANCE );
0c4b27
+
0c4b27
+            # Check if a master score exists in the cluster.
0c4b27
+            # During the very first start of the cluster, no master score will
0c4b27
+            # exists on any of the existing slaves, unless an admin designated
0c4b27
+            # one of them using crm_master. If no master exists the cluster will
0c4b27
+            # not promote a master among the slaves.
0c4b27
+            # To solve this situation, we check if there is at least one master
0c4b27
+            # score existing on one node in the cluster. Do nothing if at least
0c4b27
+            # one master score is found among the clones of the resource. If no
0c4b27
+            # master score exists, set a score of 1 only if the resource was a
0c4b27
+            # shut downed master before the start.
0c4b27
+            if ( $prev_state eq "shut down" and not _master_score_exists() ) {
0c4b27
+                ocf_log( 'info', 'No master score around. Set mine to 1' );
0c4b27
+
0c4b27
+                _set_master_score( '1' );
0c4b27
+            }
0c4b27
+
0c4b27
+            return $OCF_SUCCESS;
0c4b27
+        }
0c4b27
+
0c4b27
+        ocf_exit_reason(
0c4b27
+            'Instance "%s" is not running as a slave (returned %d)',
0c4b27
+             $OCF_RESOURCE_INSTANCE, $rc );
0c4b27
+
0c4b27
+        return $OCF_ERR_GENERIC;
0c4b27
+    }
0c4b27
+
0c4b27
+    ocf_exit_reason( 'Instance "%s" failed to start (rc: %d)',
0c4b27
+        $OCF_RESOURCE_INSTANCE, $rc );
0c4b27
+
0c4b27
+    return $OCF_ERR_GENERIC;
0c4b27
+}
0c4b27
+
0c4b27
+# Stop the PostgreSQL instance
0c4b27
+#
0c4b27
+sub pgsql_stop {
0c4b27
+    my $rc;
0c4b27
+    my $state;
0c4b27
+    my $pidfile = "$datadir/postmaster.pid";
0c4b27
+    # Add 60s to the timeout or use a 24h timeout fallback to make sure
0c4b27
+    # Pacemaker will give up before us and take decisions
0c4b27
+    my $timeout = ( _get_action_timeout() || 60*60*24 ) + 60;
0c4b27
+
0c4b27
+    # Instance must be running as secondary or primary or being stopped.
0c4b27
+    # Anything else is an error.
0c4b27
+    $rc = pgsql_monitor();
0c4b27
+    if ( $rc == $OCF_NOT_RUNNING ) {
0c4b27
+        ocf_log( 'info', 'Instance "%s" already stopped',
0c4b27
+            $OCF_RESOURCE_INSTANCE );
0c4b27
+        return $OCF_SUCCESS;
0c4b27
+    }
0c4b27
+    elsif ( $rc != $OCF_SUCCESS and $rc != $OCF_RUNNING_MASTER ) {
0c4b27
+        ocf_exit_reason( 'Unexpected state for instance "%s" (returned %d)',
0c4b27
+            $OCF_RESOURCE_INSTANCE, $rc );
0c4b27
+        return $OCF_ERR_GENERIC;
0c4b27
+    }
0c4b27
+
0c4b27
+    #
0c4b27
+    # From here, the instance is running for sure.
0c4b27
+    #
0c4b27
+
0c4b27
+    ocf_log( 'debug', 'pgsql_stop: instance "%s" is running, stopping it',
0c4b27
+        $OCF_RESOURCE_INSTANCE );
0c4b27
+
0c4b27
+    # Try to quit with proper shutdown.
0c4b27
+
0c4b27
+
0c4b27
+    $rc = _runas( $PGCTL, '--pgdata', $pgdata, '-w', '--timeout', $timeout,
0c4b27
+        '-m', 'fast', 'stop' );
0c4b27
+
0c4b27
+    if ( $rc == 0 ) {
0c4b27
+        # Wait for the stop to finish.
0c4b27
+        sleep 1 while ( $rc = pgsql_monitor() ) != $OCF_NOT_RUNNING ;
0c4b27
+
0c4b27
+        ocf_log( 'info', 'Instance "%s" stopped', $OCF_RESOURCE_INSTANCE );
0c4b27
+
0c4b27
+        return $OCF_SUCCESS;
0c4b27
+    }
0c4b27
+
0c4b27
+    ocf_exit_reason( 'Instance "%s" failed to stop', $OCF_RESOURCE_INSTANCE );
0c4b27
+    return $OCF_ERR_GENERIC;
0c4b27
+}
0c4b27
+
0c4b27
+# Monitor the PostgreSQL instance
0c4b27
+#
0c4b27
+sub pgsql_monitor {
0c4b27
+    my $pgisready_rc;
0c4b27
+    my $controldata_rc;
0c4b27
+
0c4b27
+    ocf_log( 'debug', 'pgsql_monitor: monitor is a probe' ) if ocf_is_probe();
0c4b27
+
0c4b27
+    # First check, verify if the instance is listening.
0c4b27
+    $pgisready_rc = _pg_isready();
0c4b27
+
0c4b27
+    if ( $pgisready_rc == 0 ) {
0c4b27
+        # The instance is listening.
0c4b27
+        # We confirm that the instance is up and return if it is a primary or a
0c4b27
+        # secondary
0c4b27
+        ocf_log( 'debug', 'pgsql_monitor: instance "%s" is listening',
0c4b27
+            $OCF_RESOURCE_INSTANCE );
0c4b27
+        return _confirm_role();
0c4b27
+    }
0c4b27
+
0c4b27
+    if ( $pgisready_rc == 1 ) {
0c4b27
+        # The attempt was rejected.
0c4b27
+        # This could happen in several cases:
0c4b27
+        #   - at startup
0c4b27
+        #   - during shutdown
0c4b27
+        #   - during crash recovery
0c4b27
+        #   - if instance is a warm standby
0c4b27
+        # Except for the warm standby case, this should be a transitional state.
0c4b27
+        # We try to confirm using pg_controldata.
0c4b27
+        ocf_log( 'debug',
0c4b27
+            'pgsql_monitor: instance "%s" rejects connections - checking again...',
0c4b27
+            $OCF_RESOURCE_INSTANCE );
0c4b27
+        $controldata_rc = _controldata_to_ocf();
0c4b27
+
0c4b27
+        if ( $controldata_rc == $OCF_RUNNING_MASTER
0c4b27
+            or $controldata_rc == $OCF_SUCCESS
0c4b27
+        ) {
0c4b27
+            # This state indicates that pg_isready check should succeed.
0c4b27
+            # We check again.
0c4b27
+            ocf_log( 'debug',
0c4b27
+                'pgsql_monitor: instance "%s" controldata shows a running status',
0c4b27
+                $OCF_RESOURCE_INSTANCE );
0c4b27
+
0c4b27
+            $pgisready_rc = _pg_isready();
0c4b27
+            if ( $pgisready_rc == 0 ) {
0c4b27
+                # Consistent with pg_controdata output.
0c4b27
+                # We can check if the instance is primary or secondary
0c4b27
+                ocf_log( 'debug', 'pgsql_monitor: instance "%s" is listening',
0c4b27
+                    $OCF_RESOURCE_INSTANCE );
0c4b27
+                return _confirm_role();
0c4b27
+            }
0c4b27
+
0c4b27
+            # Still not consistent, raise an error.
0c4b27
+            # NOTE: if the instance is a warm standby, we end here.
0c4b27
+            # TODO raise an hard error here ?
0c4b27
+            ocf_exit_reason(
0c4b27
+                'Instance "%s" controldata is not consistent with pg_isready (returned: %d)',
0c4b27
+                $OCF_RESOURCE_INSTANCE, $pgisready_rc );
0c4b27
+            ocf_log( 'info',
0c4b27
+                'If this instance is in warm standby, this resource agent only supports hot standby',
0c4b27
+                $OCF_RESOURCE_INSTANCE, $pgisready_rc );
0c4b27
+
0c4b27
+            return $OCF_ERR_GENERIC;
0c4b27
+        }
0c4b27
+
0c4b27
+        if ( $controldata_rc == $OCF_NOT_RUNNING ) {
0c4b27
+            # This state indicates that pg_isready check should fail with rc 2.
0c4b27
+            # We check again.
0c4b27
+            $pgisready_rc = _pg_isready();
0c4b27
+            if ( $pgisready_rc == 2 ) {
0c4b27
+                # Consistent with pg_controdata output.
0c4b27
+                # We check the process status using pg_ctl status and check
0c4b27
+                # if it was propertly shut down using pg_controldata.
0c4b27
+                ocf_log( 'debug',
0c4b27
+                    'pgsql_monitor: instance "%s" is not listening',
0c4b27
+                    $OCF_RESOURCE_INSTANCE );
0c4b27
+                return _confirm_stopped();
0c4b27
+            }
0c4b27
+            # Still not consistent, raise an error.
0c4b27
+            # TODO raise an hard error here ?
0c4b27
+            ocf_exit_reason(
0c4b27
+                'Instance "%s" controldata is not consistent with pg_isready (returned: %d)',
0c4b27
+                $OCF_RESOURCE_INSTANCE, $pgisready_rc );
0c4b27
+
0c4b27
+            return $OCF_ERR_GENERIC;
0c4b27
+        }
0c4b27
+
0c4b27
+        # Something went wrong with the controldata check, hard fail.
0c4b27
+        ocf_exit_reason(
0c4b27
+            'Could not get instance "%s" status from controldata (returned: %d)',
0c4b27
+            $OCF_RESOURCE_INSTANCE, $controldata_rc );
0c4b27
+
0c4b27
+        return $OCF_ERR_INSTALLED;
0c4b27
+    }
0c4b27
+
0c4b27
+    elsif ( $pgisready_rc == 2 ) {
0c4b27
+        # The instance is not listening.
0c4b27
+        # We check the process status using pg_ctl status and check
0c4b27
+        # if it was propertly shut down using pg_controldata.
0c4b27
+        ocf_log( 'debug', 'pgsql_monitor: instance "%s" is not listening',
0c4b27
+            $OCF_RESOURCE_INSTANCE );
0c4b27
+        return _confirm_stopped();
0c4b27
+    }
0c4b27
+
0c4b27
+    elsif ( $pgisready_rc == 3 ) {
0c4b27
+        # No attempt was done, probably a syntax error.
0c4b27
+        # Hard configuration error, we don't want to retry or failover here.
0c4b27
+        ocf_exit_reason(
0c4b27
+            'Unknown error while checking if instance "%s" is listening (returned %d)',
0c4b27
+            $OCF_RESOURCE_INSTANCE, $pgisready_rc );
0c4b27
+
0c4b27
+        return $OCF_ERR_CONFIGURED;
0c4b27
+    }
0c4b27
+
0c4b27
+    ocf_exit_reason( 'Unexpected result when checking instance "%s" status',
0c4b27
+        $OCF_RESOURCE_INSTANCE );
0c4b27
+
0c4b27
+    return $OCF_ERR_GENERIC;
0c4b27
+}
0c4b27
+
0c4b27
+
0c4b27
+# Demote the PostgreSQL instance from primary to secondary
0c4b27
+# To demote a PostgreSQL instance, we must:
0c4b27
+#   * stop it gracefully
0c4b27
+#   * create recovery.conf with standby_mode = on
0c4b27
+#   * start it
0c4b27
+#
0c4b27
+sub pgsql_demote {
0c4b27
+    my $rc;
0c4b27
+
0c4b27
+    $rc = pgsql_monitor();
0c4b27
+
0c4b27
+    # Running as primary. Normal, expected behavior.
0c4b27
+    if ( $rc == $OCF_RUNNING_MASTER ) {
0c4b27
+        ocf_log( 'debug', 'pgsql_demote: "%s" currently running as a primary',
0c4b27
+            $OCF_RESOURCE_INSTANCE )  ;
0c4b27
+    }
0c4b27
+    elsif ( $rc == $OCF_SUCCESS ) {
0c4b27
+        # Already running as secondary. Nothing to do.
0c4b27
+        ocf_log( 'debug',
0c4b27
+            'pgsql_demote: "%s" currently running as a secondary',
0c4b27
+            $OCF_RESOURCE_INSTANCE );
0c4b27
+            return $OCF_SUCCESS;
0c4b27
+    }
0c4b27
+    elsif ( $rc == $OCF_NOT_RUNNING ) {
0c4b27
+        # Instance is stopped. Nothing to do.
0c4b27
+        ocf_log( 'debug', 'pgsql_demote: "%s" currently shut down',
0c4b27
+            $OCF_RESOURCE_INSTANCE );
0c4b27
+    }
0c4b27
+    elsif ( $rc == $OCF_ERR_CONFIGURED ) {
0c4b27
+        # We actually prefer raising a hard or fatal error instead of leaving
0c4b27
+        # the CRM abording its transition for a new one because of a soft error.
0c4b27
+        # The hard error will force the CRM to move the resource immediately.
0c4b27
+        return $OCF_ERR_CONFIGURED;
0c4b27
+    }
0c4b27
+    else {
0c4b27
+        return $OCF_ERR_GENERIC;
0c4b27
+    }
0c4b27
+
0c4b27
+    # TODO we need to make sure at least one slave is connected!!
0c4b27
+
0c4b27
+    # WARNING if the resource state is stopped instead of master, the ocf ra dev
0c4b27
+    # rsc advises to return OCF_ERR_GENERIC, misleading the CRM in a loop where
0c4b27
+    # it computes transitions of demote(failing)->stop->start->promote actions
0c4b27
+    # until failcount == migration-threshold.
0c4b27
+    # This is a really ugly trick to keep going with the demode action if the
0c4b27
+    # rsc is already stopped gracefully.
0c4b27
+    # See discussion "CRM trying to demote a stopped resource" on
0c4b27
+    # developers@clusterlabs.org
0c4b27
+    unless ( $rc == $OCF_NOT_RUNNING ) {
0c4b27
+        # Add 60s to the timeout or use a 24h timeout fallback to make sure
0c4b27
+        # Pacemaker will give up before us and take decisions
0c4b27
+        my $timeout = ( _get_action_timeout() || 60*60*24 )  + 60;
0c4b27
+
0c4b27
+        # WARNING the instance **MUST** be stopped gracefully.
0c4b27
+        # Do **not** use pg_stop() or service or systemctl here as these
0c4b27
+        # commands might force-stop the PostgreSQL instance using immediate
0c4b27
+        # after some timeout and return success, which is misleading.
0c4b27
+
0c4b27
+        $rc = _runas( $PGCTL, '--pgdata', $pgdata, '--mode', 'fast', '-w',
0c4b27
+            '--timeout', $timeout , 'stop' );
0c4b27
+
0c4b27
+        # No need to wait for stop to complete, this is handled in pg_ctl
0c4b27
+        # using -w option.
0c4b27
+        unless ( $rc == 0 ) {
0c4b27
+            ocf_exit_reason( 'Failed to stop "%s" using pg_ctl (returned %d)',
0c4b27
+                $OCF_RESOURCE_INSTANCE, $rc );
0c4b27
+            return $OCF_ERR_GENERIC;
0c4b27
+        }
0c4b27
+
0c4b27
+        # Double check that the instance is stopped correctly.
0c4b27
+        $rc = pgsql_monitor();
0c4b27
+        unless ( $rc == $OCF_NOT_RUNNING ) {
0c4b27
+            ocf_exit_reason(
0c4b27
+                'Unexpected "%s" state: monitor status (%d) disagree with pg_ctl return code',
0c4b27
+                $OCF_RESOURCE_INSTANCE, $rc );
0c4b27
+            return $OCF_ERR_GENERIC;
0c4b27
+        }
0c4b27
+    }
0c4b27
+
0c4b27
+    #
0c4b27
+    # At this point, the instance **MUST** be stopped gracefully.
0c4b27
+    #
0c4b27
+
0c4b27
+    # Note: We do not need to handle the recovery.conf file here as pgsql_start
0c4b27
+    # deal with that itself. Equally, no need to wait for the start to complete
0c4b27
+    # here, handled in pgsql_start.
0c4b27
+    $rc = pgsql_start();
0c4b27
+    if ( $rc == $OCF_SUCCESS ) {
0c4b27
+        ocf_log( 'info', 'pgsql_demote: "%s" started as a secondary',
0c4b27
+            $OCF_RESOURCE_INSTANCE );
0c4b27
+        return $OCF_SUCCESS;
0c4b27
+    }
0c4b27
+
0c4b27
+    # NOTE: No need to double check the instance state as pgsql_start already use
0c4b27
+    # pgsql_monitor to check the state before returning.
0c4b27
+
0c4b27
+    ocf_exit_reason( 'Starting "%s" as a standby failed (returned %d)',
0c4b27
+        $OCF_RESOURCE_INSTANCE, $rc );
0c4b27
+    return $OCF_ERR_GENERIC;
0c4b27
+}
0c4b27
+
0c4b27
+
0c4b27
+# Promote the secondary instance to primary
0c4b27
+#
0c4b27
+sub pgsql_promote {
0c4b27
+    my $rc;
0c4b27
+    my $cancel_switchover;
0c4b27
+
0c4b27
+    $rc = pgsql_monitor();
0c4b27
+
0c4b27
+    if ( $rc == $OCF_SUCCESS ) {
0c4b27
+        # Running as slave. Normal, expected behavior.
0c4b27
+        ocf_log( 'debug', 'pgsql_promote: "%s" currently running as a standby',
0c4b27
+            $OCF_RESOURCE_INSTANCE );
0c4b27
+    }
0c4b27
+    elsif ( $rc == $OCF_RUNNING_MASTER ) {
0c4b27
+        # Already a master. Unexpected, but not a problem.
0c4b27
+        ocf_log( 'info', '"%s" already running as a primary',
0c4b27
+            $OCF_RESOURCE_INSTANCE );
0c4b27
+        return $OCF_SUCCESS;
0c4b27
+    }
0c4b27
+    elsif ( $rc == $OCF_NOT_RUNNING ) { # INFO this is not supposed to happen.
0c4b27
+        # Currently not running. Need to start before promoting.
0c4b27
+        ocf_log( 'info', '"%s" currently not running, starting it',
0c4b27
+            $OCF_RESOURCE_INSTANCE );
0c4b27
+
0c4b27
+        $rc = pgsql_start();
0c4b27
+        if ( $rc != $OCF_SUCCESS ) {
0c4b27
+            ocf_exit_reason( 'Failed to start the instance "%s"',
0c4b27
+                $OCF_RESOURCE_INSTANCE );
0c4b27
+            return $OCF_ERR_GENERIC;
0c4b27
+        }
0c4b27
+    }
0c4b27
+    else {
0c4b27
+        ocf_exit_reason( 'Unexpected error, cannot promote "%s"',
0c4b27
+            $OCF_RESOURCE_INSTANCE );
0c4b27
+        return $OCF_ERR_GENERIC;
0c4b27
+    }
0c4b27
+
0c4b27
+    #
0c4b27
+    # At this point, the instance **MUST** be started as a secondary.
0c4b27
+    #
0c4b27
+
0c4b27
+    # Cancel the switchover if it has been considered not safe during the
0c4b27
+    # pre-promote action
0c4b27
+    $cancel_switchover = _get_priv_attr('cancel_switchover');
0c4b27
+    if ( $cancel_switchover ) { # if not empty or not 0
0c4b27
+        ocf_exit_reason( 'Switchover has been canceled from pre-promote action' );
0c4b27
+
0c4b27
+        _delete_priv_attr( 'cancel_switchover' );
0c4b27
+
0c4b27
+        return $OCF_ERR_GENERIC if $cancel_switchover eq '1';
0c4b27
+        return $OCF_ERR_ARGS; # ban the resource from the node if we have an
0c4b27
+                              # internal error during _check_switchover
0c4b27
+    }
0c4b27
+
0c4b27
+    # Do not check for a better candidate if we try to recover the master
0c4b27
+    # Recover of a master is detected during the pre-promote action. It sets the
0c4b27
+    # private attribute 'recover_master' to '1' if this is a master recover.
0c4b27
+    if ( _get_priv_attr( 'recover_master' ) eq '1' ) {
0c4b27
+        ocf_log( 'info', 'Recovering old master, no election needed');
0c4b27
+    }
0c4b27
+    else {
0c4b27
+
0c4b27
+        # The promotion is occurring on the best known candidate (highest
0c4b27
+        # master score), as chosen by pacemaker during the last working monitor
0c4b27
+        # on previous master (see pgsql_monitor/_check_locations subs).
0c4b27
+        # To avoid any race condition between the last monitor action on the
0c4b27
+        # previous master and the **real** most up-to-date standby, we
0c4b27
+        # set each standby location during the "pre-promote" action, and stored
0c4b27
+        # them using the "lsn_location" resource attribute.
0c4b27
+        #
0c4b27
+        # The best standby to promote would have the highest known LSN. If the
0c4b27
+        # current resource is not the best one, we need to modify the master
0c4b27
+        # scores accordingly, and abort the current promotion.
0c4b27
+        ocf_log( 'debug',
0c4b27
+            'pgsql_promote: checking if current node is the best candidate for promotion' );
0c4b27
+
0c4b27
+        # Exclude nodes that are known to be unavailable (not in the current
0c4b27
+        # partition) using the "crm_node" command
0c4b27
+        my @active_nodes    = split /\s+/ => _get_priv_attr( 'nodes' );
0c4b27
+        my $node_to_promote = '';
0c4b27
+        my $ans;
0c4b27
+        my $max_tl;
0c4b27
+        my $max_lsn;
0c4b27
+        my $node_tl;
0c4b27
+        my $node_lsn;
0c4b27
+        my $wal_num;
0c4b27
+        my $wal_off;
0c4b27
+
0c4b27
+        # Get the "lsn_location" attribute value for the current node, as set
0c4b27
+        # during the "pre-promote" action.
0c4b27
+        # It should be the greatest among the secondary instances.
0c4b27
+        $ans = _get_priv_attr( 'lsn_location' );
0c4b27
+
0c4b27
+        if ( $ans eq '' ) {
0c4b27
+            # This should not happen as the "lsn_location" attribute should have
0c4b27
+            # been updated during the "pre-promote" action.
0c4b27
+            ocf_exit_reason( 'Can not get current node LSN location' );
0c4b27
+            return $OCF_ERR_GENERIC;
0c4b27
+        }
0c4b27
+
0c4b27
+        chomp $ans;
0c4b27
+        ( $max_tl, $max_lsn ) = split /#/, $ans;
0c4b27
+
0c4b27
+        ocf_log( 'debug', 'pgsql_promote: current node TL#LSN location: %s#%s',
0c4b27
+            $max_tl, $max_lsn );
0c4b27
+
0c4b27
+        # Now we compare with the other available nodes.
0c4b27
+        foreach my $node ( @active_nodes ) {
0c4b27
+            # We exclude the current node from the check.
0c4b27
+            next if $node eq $nodename;
0c4b27
+
0c4b27
+            # Get the "lsn_location" attribute value for the node, as set during
0c4b27
+            # the "pre-promote" action.
0c4b27
+            # This is implemented as a loop as private attributes are asynchronously
0c4b27
+            # available from other nodes.
0c4b27
+            # see: https://github.com/ClusterLabs/PAF/issues/131
0c4b27
+            # NOTE: if a node did not set its lsn_location for some reason, this will end
0c4b27
+            # with a timeout and the whole promotion will start again.
0c4b27
+            WAIT_FOR_LSN: {
0c4b27
+                $ans = _get_priv_attr( 'lsn_location', $node );
0c4b27
+                if ( $ans eq '' ) {
0c4b27
+                    ocf_log( 'info', 'pgsql_promote: waiting for LSN from %s', $node );
0c4b27
+                    select( undef, undef, undef, 0.1 );
0c4b27
+                    redo WAIT_FOR_LSN;
0c4b27
+                }
0c4b27
+            }
0c4b27
+
0c4b27
+            chomp $ans;
0c4b27
+            ( $node_tl, $node_lsn ) = split /#/, $ans;
0c4b27
+
0c4b27
+            ocf_log( 'debug',
0c4b27
+                'pgsql_promote: comparing with "%s": TL#LSN is %s#%s',
0c4b27
+                $node, $node_tl, $node_lsn );
0c4b27
+
0c4b27
+            # If the node has a higher LSN, select it as a best candidate to
0c4b27
+            # promotion and keep looping to check the TL/LSN of other nodes.
0c4b27
+            if ( $node_tl > $max_tl
0c4b27
+                or ( $node_tl == $max_tl and $node_lsn > $max_lsn )
0c4b27
+            ) {
0c4b27
+                ocf_log( 'debug',
0c4b27
+                    'pgsql_promote: "%s" is a better candidate to promote (%s#%s > %s#%s)',
0c4b27
+                    $node, $node_tl, $node_lsn, $max_tl, $max_lsn );
0c4b27
+                $node_to_promote = $node;
0c4b27
+                $max_tl          = $node_tl;
0c4b27
+                $max_lsn         = $node_lsn;
0c4b27
+            }
0c4b27
+        }
0c4b27
+
0c4b27
+        # If any node has been selected, we adapt the master scores accordingly
0c4b27
+        # and break the current promotion.
0c4b27
+        if ( $node_to_promote ne '' ) {
0c4b27
+            ocf_exit_reason(
0c4b27
+                '%s is the best candidate to promote, aborting current promotion',
0c4b27
+                $node_to_promote );
0c4b27
+
0c4b27
+            # Reset current node master score.
0c4b27
+            _set_master_score( '1' );
0c4b27
+
0c4b27
+            # Set promotion candidate master score.
0c4b27
+            _set_master_score( '1000', $node_to_promote );
0c4b27
+
0c4b27
+            # We fail the promotion to trigger another promotion transition
0c4b27
+            # with the new scores.
0c4b27
+            return $OCF_ERR_GENERIC;
0c4b27
+        }
0c4b27
+
0c4b27
+        # Else, we will keep on promoting the current node.
0c4b27
+    }
0c4b27
+
0c4b27
+    unless (
0c4b27
+        # Promote the instance on the current node.
0c4b27
+        _runas( $PGCTL, '--pgdata', $pgdata, '-w', 'promote' ) == 0 )
0c4b27
+    {
0c4b27
+        ocf_exit_reason( 'Error during promotion command' );
0c4b27
+        return $OCF_ERR_GENERIC;
0c4b27
+    }
0c4b27
+
0c4b27
+    # The instance promotion is asynchronous, so we need to wait for this
0c4b27
+    # process to complete.
0c4b27
+    while ( pgsql_monitor() != $OCF_RUNNING_MASTER ) {
0c4b27
+        ocf_log( 'info', 'Waiting for the promote to complete' );
0c4b27
+        sleep 1;
0c4b27
+    }
0c4b27
+
0c4b27
+    ocf_log( 'info', 'Promote complete' );
0c4b27
+
0c4b27
+    return $OCF_SUCCESS;
0c4b27
+}
0c4b27
+
0c4b27
+# This action is called **before** the actual promotion when a failing master is
0c4b27
+# considered unreclaimable, recoverable or a new master must be promoted
0c4b27
+# (switchover or first start).
0c4b27
+# As every "notify" action, it is executed almost simultaneously on all
0c4b27
+# available nodes.
0c4b27
+sub pgsql_notify_pre_promote {
0c4b27
+    my $rc;
0c4b27
+    my $node_tl;
0c4b27
+    my $node_lsn;
0c4b27
+    my %cdata;
0c4b27
+    my %active_nodes;
0c4b27
+    my $attr_nodes;
0c4b27
+
0c4b27
+    ocf_log( 'info', 'Promoting instance on node "%s"',
0c4b27
+        $OCF_NOTIFY_ENV{'promote'}[0]{'uname'} );
0c4b27
+
0c4b27
+    # No need to do an election between slaves if this is recovery of the master
0c4b27
+    if ( _is_master_recover( $OCF_NOTIFY_ENV{'promote'}[0]{'uname'} ) ) {
0c4b27
+        ocf_log( 'warning', 'This is a master recovery!' );
0c4b27
+
0c4b27
+        _set_priv_attr( 'recover_master', '1' )
0c4b27
+            if $OCF_NOTIFY_ENV{'promote'}[0]{'uname'} eq $nodename;
0c4b27
+
0c4b27
+        return $OCF_SUCCESS;
0c4b27
+    }
0c4b27
+
0c4b27
+    # Environment cleanup!
0c4b27
+    _delete_priv_attr( 'lsn_location'      );
0c4b27
+    _delete_priv_attr( 'recover_master'    );
0c4b27
+    _delete_priv_attr( 'nodes'             );
0c4b27
+    _delete_priv_attr( 'cancel_switchover' );
0c4b27
+
0c4b27
+    # check for the last received entry of WAL from the master if we are
0c4b27
+    # the designated slave to promote
0c4b27
+    if ( _is_switchover( $nodename ) and scalar
0c4b27
+         grep { $_->{'uname'} eq $nodename } @{ $OCF_NOTIFY_ENV{'promote'} }
0c4b27
+    ) {
0c4b27
+        $rc = _check_switchover();
0c4b27
+
0c4b27
+        unless ( $rc == 0 ) {
0c4b27
+            # Shortcut the election process as the switchover will be
0c4b27
+            # canceled
0c4b27
+            _set_priv_attr( 'cancel_switchover', $rc );
0c4b27
+            return $OCF_SUCCESS; # return code is ignored during notify
0c4b27
+        }
0c4b27
+
0c4b27
+        # If the sub keeps going, that means the switchover is safe.
0c4b27
+        # Keep going with the election process in case the switchover was
0c4b27
+        # instruct to the wrong node.
0c4b27
+        # FIXME: should we allow a switchover to a lagging slave?
0c4b27
+    }
0c4b27
+
0c4b27
+    # We need to trigger an election between existing slaves to promote the best
0c4b27
+    # one based on its current LSN location. Each node set a private attribute
0c4b27
+    # "lsn_location" with its TL and LSN location.
0c4b27
+    #
0c4b27
+    # During the following promote action, The designated standby for
0c4b27
+    # promotion use these attributes to check if the instance to be promoted
0c4b27
+    # is the best one, so we can avoid a race condition between the last
0c4b27
+    # successful monitor on the previous master and the current promotion.
0c4b27
+
0c4b27
+    # As we can not break the transition from a notification action, we check
0c4b27
+    # during the promotion if each node TL and LSN are valid.
0c4b27
+
0c4b27
+    # Force a checpoint to make sure the controldata shows the very last TL
0c4b27
+    _query( q{ CHECKPOINT }, {} );
0c4b27
+    %cdata    = _get_controldata();
0c4b27
+    $node_lsn = _get_last_received_lsn( 'in decimal' );
0c4b27
+
0c4b27
+    unless ( defined $node_lsn ) {
0c4b27
+        ocf_log( 'warning', 'Unknown current node LSN' );
0c4b27
+        # Return code are ignored during notifications...
0c4b27
+        return $OCF_SUCCESS;
0c4b27
+    }
0c4b27
+
0c4b27
+    $node_lsn = "$cdata{'tl'}#$node_lsn";
0c4b27
+
0c4b27
+    ocf_log( 'info', 'Current node TL#LSN: %s', $node_lsn );
0c4b27
+
0c4b27
+    # Set the "lsn_location" attribute value for this node so we can use it
0c4b27
+    # during the following "promote" action.
0c4b27
+    _set_priv_attr( 'lsn_location', $node_lsn );
0c4b27
+
0c4b27
+    ocf_log( 'warning', 'Could not set the current node LSN' )
0c4b27
+        if $? != 0 ;
0c4b27
+
0c4b27
+    # If this node is the future master, keep track of the slaves that
0c4b27
+    # received the same notification to compare our LSN with them during
0c4b27
+    # promotion
0c4b27
+    if ( $OCF_NOTIFY_ENV{'promote'}[0]{'uname'} eq $nodename ) {
0c4b27
+        # Build the list of active nodes:
0c4b27
+        #   master + slave + start - stop
0c4b27
+        # FIXME: Deal with rsc started during the same transaction but **after**
0c4b27
+        #        the promotion ?
0c4b27
+        $active_nodes{ $_->{'uname'} }++ foreach @{ $OCF_NOTIFY_ENV{'active'} },
0c4b27
+                                                 @{ $OCF_NOTIFY_ENV{'start'} };
0c4b27
+        $active_nodes{ $_->{'uname'} }-- foreach @{ $OCF_NOTIFY_ENV{'stop'} };
0c4b27
+
0c4b27
+        $attr_nodes = join " "
0c4b27
+            => grep { $active_nodes{$_} > 0 } keys %active_nodes;
0c4b27
+
0c4b27
+        _set_priv_attr( 'nodes', $attr_nodes );
0c4b27
+    }
0c4b27
+
0c4b27
+    return $OCF_SUCCESS;
0c4b27
+}
0c4b27
+
0c4b27
+# This action is called after a promote action.
0c4b27
+sub pgsql_notify_post_promote {
0c4b27
+
0c4b27
+    # We have a new master (or the previous one recovered).
0c4b27
+    # Environment cleanup!
0c4b27
+    _delete_priv_attr( 'lsn_location'      );
0c4b27
+    _delete_priv_attr( 'recover_master'    );
0c4b27
+    _delete_priv_attr( 'nodes'             );
0c4b27
+    _delete_priv_attr( 'cancel_switchover' );
0c4b27
+
0c4b27
+    return $OCF_SUCCESS;
0c4b27
+}
0c4b27
+
0c4b27
+# This is called before a demote occurs.
0c4b27
+sub pgsql_notify_pre_demote {
0c4b27
+    my $rc;
0c4b27
+    my %cdata;
0c4b27
+
0c4b27
+    # do nothing if the local node will not be demoted
0c4b27
+    return $OCF_SUCCESS unless scalar
0c4b27
+        grep { $_->{'uname'} eq $nodename } @{ $OCF_NOTIFY_ENV{'demote'} };
0c4b27
+
0c4b27
+    $rc = pgsql_monitor();
0c4b27
+
0c4b27
+    # do nothing if this is not a master recovery
0c4b27
+    return $OCF_SUCCESS unless _is_master_recover( $nodename )
0c4b27
+                           and $rc == $OCF_FAILED_MASTER;
0c4b27
+
0c4b27
+    # in case of master crash, we need to detect if the CRM tries to recover
0c4b27
+    # the master clone. The usual transition is to do:
0c4b27
+    #   demote->stop->start->promote
0c4b27
+    #
0c4b27
+    # There are multiple flaws with this transition:
0c4b27
+    #  * the 1st and 2nd actions will fail because the instance is in
0c4b27
+    #    OCF_FAILED_MASTER step
0c4b27
+    #  * the usual start action is dangerous as the instance will start with
0c4b27
+    #    a recovery.conf instead of entering a normal recovery process
0c4b27
+    #
0c4b27
+    # To avoid this, we try to start the instance in recovery from here.
0c4b27
+    # If it success, at least it will be demoted correctly with a normal
0c4b27
+    # status. If it fails, it will be catched up in next steps.
0c4b27
+
0c4b27
+    ocf_log( 'info', 'Trying to start failing master "%s"...',
0c4b27
+        $OCF_RESOURCE_INSTANCE );
0c4b27
+
0c4b27
+    # Either the instance managed to start or it couldn't.
0c4b27
+    # We rely on the pg_ctk '-w' switch to take care of this. If it couldn't
0c4b27
+    # start, this error will be catched up later during the various checks
0c4b27
+    _pg_ctl_start();
0c4b27
+
0c4b27
+    %cdata = _get_controldata();
0c4b27
+
0c4b27
+    ocf_log( 'info', 'State is "%s" after recovery attempt', $cdata{'state'} );
0c4b27
+
0c4b27
+    return $OCF_SUCCESS;
0c4b27
+}
0c4b27
+
0c4b27
+# This is called before a stop occurs.
0c4b27
+sub pgsql_notify_pre_stop {
0c4b27
+    my $rc;
0c4b27
+    my %cdata;
0c4b27
+
0c4b27
+    # do nothing if the local node will not be stopped
0c4b27
+    return $OCF_SUCCESS unless scalar
0c4b27
+        grep { $_->{'uname'} eq $nodename } @{ $OCF_NOTIFY_ENV{'stop'} };
0c4b27
+
0c4b27
+    $rc = _controldata_to_ocf();
0c4b27
+
0c4b27
+    # do nothing if this is not a slave recovery
0c4b27
+    return $OCF_SUCCESS unless _is_slave_recover( $nodename )
0c4b27
+                           and $rc == $OCF_RUNNING_SLAVE;
0c4b27
+
0c4b27
+    # in case of slave crash, we need to detect if the CRM tries to recover
0c4b27
+    # the slaveclone. The usual transition is to do: stop->start
0c4b27
+    #
0c4b27
+    # This transition can no twork because the instance is in
0c4b27
+    # OCF_ERR_GENERIC step. So the stop action will fail, leading most
0c4b27
+    # probably to fencing action.
0c4b27
+    #
0c4b27
+    # To avoid this, we try to start the instance in recovery from here.
0c4b27
+    # If it success, at least it will be stopped correctly with a normal
0c4b27
+    # status. If it fails, it will be catched up in next steps.
0c4b27
+
0c4b27
+    ocf_log( 'info', 'Trying to start failing slave "%s"...',
0c4b27
+        $OCF_RESOURCE_INSTANCE );
0c4b27
+
0c4b27
+    # Either the instance managed to start or it couldn't.
0c4b27
+    # We rely on the pg_ctk '-w' switch to take care of this. If it couldn't
0c4b27
+    # start, this error will be catched up later during the various checks
0c4b27
+    _pg_ctl_start();
0c4b27
+
0c4b27
+    %cdata = _get_controldata();
0c4b27
+
0c4b27
+    ocf_log( 'info', 'State is "%s" after recovery attempt', $cdata{'state'} );
0c4b27
+
0c4b27
+    return $OCF_SUCCESS;
0c4b27
+}
0c4b27
+
0c4b27
+# Notify type actions, called on all available nodes before (pre) and after
0c4b27
+# (post) other actions, like promote, start, ...
0c4b27
+#
0c4b27
+sub pgsql_notify {
0c4b27
+    my $type_op;
0c4b27
+
0c4b27
+    ocf_log( 'debug', "pgsql_notify: environment variables: %s",
0c4b27
+        Data::Dumper->new( [ \%OCF_NOTIFY_ENV ] )->Sortkeys(1)->Terse(1)->Dump );
0c4b27
+
0c4b27
+    return unless %OCF_NOTIFY_ENV;
0c4b27
+
0c4b27
+    $type_op = "$OCF_NOTIFY_ENV{'type'}-$OCF_NOTIFY_ENV{'operation'}";
0c4b27
+
0c4b27
+    for ( $type_op ) {
0c4b27
+        if    ( /^pre-promote$/  ) { return pgsql_notify_pre_promote()  }
0c4b27
+        elsif ( /^post-promote$/ ) { return pgsql_notify_post_promote() }
0c4b27
+        elsif ( /^pre-demote$/   ) { return pgsql_notify_pre_demote()   }
0c4b27
+        elsif ( /^pre-stop$/     ) { return pgsql_notify_pre_stop()     }
0c4b27
+    }
0c4b27
+
0c4b27
+    return $OCF_SUCCESS;
0c4b27
+}
0c4b27
+
0c4b27
+# Action used to allow for online modification of resource parameters value.
0c4b27
+#
0c4b27
+sub pgsql_reload {
0c4b27
+
0c4b27
+    # No action necessary, the action declaration is enough to inform pacemaker
0c4b27
+    # that the modification of any non-unique parameter can be applied without
0c4b27
+    # having to restart the resource.
0c4b27
+    ocf_log( 'info', 'Instance "%s" reloaded', $OCF_RESOURCE_INSTANCE );
0c4b27
+    return $OCF_SUCCESS;
0c4b27
+
0c4b27
+}
0c4b27
+
0c4b27
+############################################################
0c4b27
+#### MAIN
0c4b27
+
0c4b27
+exit ocf_meta_data() if $__OCF_ACTION eq 'meta-data';
0c4b27
+exit ocf_methods()   if $__OCF_ACTION eq 'methods';
0c4b27
+
0c4b27
+# Avoid "could not change directory" when executing commands as "system-user".
0c4b27
+chdir File::Spec->tmpdir();
0c4b27
+
0c4b27
+# mandatory sanity checks
0c4b27
+# check pgdata
0c4b27
+if ( ! -d $pgdata ) {
0c4b27
+    ocf_exit_reason( 'PGDATA "%s" does not exist', $pgdata );
0c4b27
+    exit $OCF_ERR_ARGS;
0c4b27
+}
0c4b27
+
0c4b27
+# check datadir
0c4b27
+if ( ! -d $datadir ) {
0c4b27
+    ocf_exit_reason( 'data_directory "%s" does not exist', $datadir );
0c4b27
+    exit $OCF_ERR_ARGS;
0c4b27
+}
0c4b27
+
0c4b27
+# Set PostgreSQL version
0c4b27
+$PGVERNUM = _get_pg_version();
0c4b27
+
0c4b27
+# Set current node name.
0c4b27
+$nodename = ocf_local_nodename();
0c4b27
+
0c4b27
+$exit_code = pgsql_validate_all();
0c4b27
+
0c4b27
+exit $exit_code if $exit_code != $OCF_SUCCESS or $__OCF_ACTION eq 'validate-all';
0c4b27
+
0c4b27
+# Run action
0c4b27
+for ( $__OCF_ACTION ) {
0c4b27
+    if    ( /^start$/     ) { $exit_code = pgsql_start()   }
0c4b27
+    elsif ( /^stop$/      ) { $exit_code = pgsql_stop()    }
0c4b27
+    elsif ( /^monitor$/   ) { $exit_code = pgsql_monitor() }
0c4b27
+    elsif ( /^promote$/   ) { $exit_code = pgsql_promote() }
0c4b27
+    elsif ( /^demote$/    ) { $exit_code = pgsql_demote()  }
0c4b27
+    elsif ( /^notify$/    ) { $exit_code = pgsql_notify()  }
0c4b27
+    elsif ( /^reload$/    ) { $exit_code = pgsql_reload()  }
0c4b27
+    else  { $exit_code = $OCF_ERR_UNIMPLEMENTED }
0c4b27
+}
0c4b27
+
0c4b27
+exit $exit_code;
0c4b27
+
0c4b27
+
0c4b27
+=head1 EXAMPLE CRM SHELL
0c4b27
+
0c4b27
+The following is an example configuration for a pgsqlms resource using the
0c4b27
+crm(8) shell:
0c4b27
+
0c4b27
+  primitive pgsqld pgsqlms                                                 \
0c4b27
+    params pgdata="/var/lib/postgresql/9.6/main"                           \
0c4b27
+      bindir="/usr/lib/postgresql/9.6/bin"                                 \
0c4b27
+      pghost="/var/run/postgresql"                                         \
0c4b27
+      recovery_template="/etc/postgresql/9.6/main/recovery.conf.pcmk"      \
0c4b27
+      start_opts="-c config_file=/etc/postgresql/9.6/main/postgresql.conf" \
0c4b27
+    op start timeout=60s                                                   \
0c4b27
+    op stop timeout=60s                                                    \
0c4b27
+    op promote timeout=30s                                                 \
0c4b27
+    op demote timeout=120s                                                 \
0c4b27
+    op monitor interval=15s timeout=10s role="Master"                      \
0c4b27
+    op monitor interval=16s timeout=10s role="Slave"                       \
0c4b27
+    op notify timeout=60s
0c4b27
+
0c4b27
+  ms pgsql-ha pgsqld meta notify=true
0c4b27
+
0c4b27
+
0c4b27
+=head1 EXAMPLE PCS
0c4b27
+
0c4b27
+The following is an example configuration for a pgsqlms resource using pcs(8):
0c4b27
+
0c4b27
+  pcs resource create pgsqld ocf:heartbeat:pgsqlms            \
0c4b27
+    bindir=/usr/pgsql-9.6/bin pgdata=/var/lib/pgsql/9.6/data  \
0c4b27
+    op start timeout=60s                                      \
0c4b27
+    op stop timeout=60s                                       \
0c4b27
+    op promote timeout=30s                                    \
0c4b27
+    op demote timeout=120s                                    \
0c4b27
+    op monitor interval=15s timeout=10s role="Master"         \
0c4b27
+    op monitor interval=16s timeout=10s role="Slave"          \
0c4b27
+    op notify timeout=60s --master notify=true
0c4b27
+
0c4b27
+=head1 SEE ALSO
0c4b27
+
0c4b27
+http://clusterlabs.org/
0c4b27
+
0c4b27
+=head1 AUTHOR
0c4b27
+
0c4b27
+Jehan-Guillaume de Rorthais and Mael Rimbault.
0c4b27
+
0c4b27
+=cut
0c4b27
diff --color -uNr a/paf_LICENSE b/paf_LICENSE
0c4b27
--- a/paf_LICENSE	1970-01-01 01:00:00.000000000 +0100
0c4b27
+++ b/paf_LICENSE	2021-04-14 09:16:39.083555835 +0200
0c4b27
@@ -0,0 +1,19 @@
0c4b27
+Copyright (c) 2016-2020, Jehan-Guillaume de Rorthais, Mael Rimbault.
0c4b27
+
0c4b27
+Permission to use, copy, modify, and distribute this software and its
0c4b27
+documentation for any purpose, without fee, and without a written agreement
0c4b27
+is hereby granted, provided that the above copyright notice and this
0c4b27
+paragraph and the following two paragraphs appear in all copies.
0c4b27
+
0c4b27
+IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
0c4b27
+DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
0c4b27
+LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
0c4b27
+DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
0c4b27
+POSSIBILITY OF SUCH DAMAGE.
0c4b27
+
0c4b27
+THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIMS ANY WARRANTIES,
0c4b27
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
0c4b27
+AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
0c4b27
+ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
0c4b27
+PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
0c4b27
+
0c4b27
diff --color -uNr a/paf_README.md b/paf_README.md
0c4b27
--- a/paf_README.md	1970-01-01 01:00:00.000000000 +0100
0c4b27
+++ b/paf_README.md	2021-04-14 09:18:57.450968048 +0200
0c4b27
@@ -0,0 +1,86 @@
0c4b27
+# PostgreSQL Automatic Failover
0c4b27
+
0c4b27
+High-Availibility for Postgres, based on industry references Pacemaker and
0c4b27
+Corosync.
0c4b27
+
0c4b27
+## Description
0c4b27
+
0c4b27
+Pacemaker is nowadays the industry reference for High Availability. In the same
0c4b27
+fashion than for Systemd, all Linux distributions moved (or are moving) to this
0c4b27
+unique Pacemaker+Corosync stack, removing all other existing high availability
0c4b27
+stacks (CMAN, RGManager, OpenAIS, ...). It is able to detect failure on various
0c4b27
+services and automatically decide to failover the failing resource to another
0c4b27
+node when possible.
0c4b27
+
0c4b27
+To be able to manage a specific service resource, Pacemaker interact with it
0c4b27
+through a so-called "Resource Agent". Resource agents must comply to the OCF
0c4b27
+specification which define what they must implement (start, stop, promote,
0c4b27
+etc), how they should behave and inform Pacemaker of their results.
0c4b27
+
0c4b27
+PostgreSQL Automatic Failover is a new OCF resource Agent dedicated to
0c4b27
+PostgreSQL. Its original wish is to keep a clear limit between the Pacemaker
0c4b27
+administration and the PostgreSQL one, to keep things simple, documented and
0c4b27
+yet powerful.
0c4b27
+
0c4b27
+Once your PostgreSQL cluster built using internal streaming replication, PAF is
0c4b27
+able to expose to Pacemaker what is the current status of the PostgreSQL
0c4b27
+instance on each node: master, slave, stopped, catching up, etc. Should a
0c4b27
+failure occurs on the master, Pacemaker will try to recover it by default.
0c4b27
+Should the failure be non-recoverable, PAF allows the slaves to be able to
0c4b27
+elect the best of them (the closest one to the old master) and promote it as
0c4b27
+the new master. All of this thanks to the robust, feature-full and most
0c4b27
+importantly experienced project: Pacemaker.
0c4b27
+
0c4b27
+For information about how to install this agent, see `INSTALL.md`.
0c4b27
+
0c4b27
+## Setup and requirements
0c4b27
+
0c4b27
+PAF supports PostgreSQL 9.3 and higher. It has been extensively tested under
0c4b27
+CentOS 6 and 7 in various scenario.
0c4b27
+
0c4b27
+PAF has been written to give to the administrator the maximum control
0c4b27
+over their PostgreSQL configuration and architecture. Thus, you are 100%
0c4b27
+responsible for the master/slave creations and their setup. The agent
0c4b27
+will NOT edit your setup. It only requires you to follow these pre-requisites:
0c4b27
+
0c4b27
+  * slave __must__ be in hot_standby (accept read-only connections) ;
0c4b27
+  * the following parameters __must__ be configured in the appropriate place :
0c4b27
+    * `standby_mode = on` (for PostgreSQL 11 and before)
0c4b27
+    * `recovery_target_timeline = 'latest'`
0c4b27
+    * `primary_conninfo` wih `application_name` set to the node name as seen
0c4b27
+      in Pacemaker.
0c4b27
+  * these last parameters has been merged inside the instance configuration
0c4b27
+    file with PostgreSQL 12. For PostgreSQL 11 and before, you __must__
0c4b27
+    provide a `recovery.conf` template file.
0c4b27
+
0c4b27
+When setting up the resource in Pacemaker, here are the available parameters you
0c4b27
+can set:
0c4b27
+
0c4b27
+  * `bindir`: location of the PostgreSQL binaries (default: `/usr/bin`)
0c4b27
+  * `pgdata`: location of the PGDATA of your instance (default:
0c4b27
+    `/var/lib/pgsql/data`)
0c4b27
+  * `datadir`: path to the directory set in `data_directory` from your
0c4b27
+    postgresql.conf file. This parameter has same default than PostgreSQL
0c4b27
+    itself: the `pgdata` parameter value. Unless you have a special PostgreSQL
0c4b27
+    setup and you understand this parameter, __ignore it__
0c4b27
+  * `pghost`: the socket directory or IP address to use to connect to the
0c4b27
+    local instance (default: `/tmp` or `/var/run/postgresql` for DEBIAN)
0c4b27
+  * `pgport`:  the port to connect to the local instance (default: `5432`)
0c4b27
+  * `recovery_template`: __only__ for PostgreSQL 11 and before. The local 
0c4b27
+    template that will be copied as the `PGDATA/recovery.conf` file. This
0c4b27
+    file must not exist on any node for PostgreSQL 12 and after.
0c4b27
+    (default: `$PGDATA/recovery.conf.pcmk`)
0c4b27
+  * `start_opts`: Additional arguments given to the postgres process on startup.
0c4b27
+    See "postgres --help" for available options. Useful when the postgresql.conf
0c4b27
+    file is not in the data directory (PGDATA), eg.:
0c4b27
+    `-c config_file=/etc/postgresql/9.3/main/postgresql.conf`
0c4b27
+  * `system_user`: the system owner of your instance's process (default:
0c4b27
+    `postgres`)
0c4b27
+  * `maxlag`: maximum lag allowed on a standby before we set a negative master
0c4b27
+    score on it. The calculation is based on the difference between the current
0c4b27
+    xlog location on the master and the write location on the standby.
0c4b27
+    (default: 0, which disables this feature)
0c4b27
+
0c4b27
+For a demonstration about how to setup a cluster, see
0c4b27
+[http://clusterlabs.github.io/PAF/documentation.html](http://clusterlabs.github.io/PAF/documentation.html).
0c4b27
+