diff --git a/dbutils.c b/dbutils.c index bef993f7..ef21f06c 100644 --- a/dbutils.c +++ b/dbutils.c @@ -1928,6 +1928,51 @@ vacuum_table(PGconn *primary_conn, const char *table) return success; } +/* + * For use in PostgreSQL 12 and later + */ +bool +promote_standby(PGconn *conn, bool wait, int wait_seconds) +{ + PQExpBufferData query; + bool success = true; + PGresult *res = NULL; + + initPQExpBuffer(&query); + + appendPQExpBuffer(&query, + "SELECT pg_catalog.pg_promote(wait := %s", + wait ? "TRUE" : "FALSE"); + + if (wait_seconds > 0) + { + appendPQExpBuffer(&query, + ", wait_seconds := %i", + wait_seconds); + } + + appendPQExpBufferStr(&query, ")"); + + res = PQexec(conn, query.data); + + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + log_db_error(conn, query.data, _("unable to execute pg_promote()")); + success = false; + } + else + { + /* NOTE: if "wait" is false, pg_promote() will always return true */ + success = atobool(PQgetvalue(res, 0, 0)); + } + + termPQExpBuffer(&query); + PQclear(res); + + return success; +} + + /* ===================== */ /* Node record functions */ /* ===================== */ diff --git a/dbutils.h b/dbutils.h index 72dcea69..1976512e 100644 --- a/dbutils.h +++ b/dbutils.h @@ -424,7 +424,7 @@ ExtensionStatus get_repmgr_extension_status(PGconn *conn); /* node management functions */ void checkpoint(PGconn *conn); bool vacuum_table(PGconn *conn, const char *table); - +bool promote_standby(PGconn *conn, bool wait, int wait_seconds); /* node record functions */ t_server_type parse_node_type(const char *type); diff --git a/repmgr-action-standby.c b/repmgr-action-standby.c index ff8acf51..68c5fee0 100644 --- a/repmgr-action-standby.c +++ b/repmgr-action-standby.c @@ -73,7 +73,7 @@ static char local_repmgr_tmp_directory[MAXPGPATH]; static char datadir_list_filename[MAXLEN]; static char barman_command_buf[MAXLEN] = ""; -static void _do_standby_promote_internal(PGconn *conn); +static void _do_standby_promote_internal(PGconn *conn, int server_version_num); static void _do_create_recovery_conf(void); static void check_barman_config(void); @@ -1956,13 +1956,14 @@ do_standby_promote(void) RecoveryType recovery_type = RECTYPE_UNKNOWN; int existing_primary_id = UNKNOWN_NODE_ID; + int server_version_num = UNKNOWN_SERVER_VERSION_NUM; conn = establish_db_connection(config_file_options.conninfo, true); log_verbose(LOG_INFO, _("connected to standby, checking its state")); /* Verify that standby is a supported server version */ - check_server_version(conn, "standby", true, NULL); + server_version_num = check_server_version(conn, "standby", true, NULL); /* Check we are in a standby node */ recovery_type = get_recovery_type(conn); @@ -2008,16 +2009,14 @@ do_standby_promote(void) PQfinish(current_primary_conn); - _do_standby_promote_internal(conn); + _do_standby_promote_internal(conn, server_version_num); } static void -_do_standby_promote_internal(PGconn *conn) +_do_standby_promote_internal(PGconn *conn, int server_version_num) { - char script[MAXLEN]; - int r, - i; + int i; bool promote_success = false; PQExpBufferData details; @@ -2049,24 +2048,51 @@ _do_standby_promote_internal(PGconn *conn) * `pg_ctl promote` returns immediately and (prior to 10.0) has no -w * option so we can't be sure when or if the promotion completes. For now * we'll poll the server until the default timeout (60 seconds) + * + * For PostgreSQL 12+, use the pg_promote() function - note this is + * experimental */ - - get_server_action(ACTION_PROMOTE, script, (char *) data_dir); - log_notice(_("promoting standby to primary")); - log_detail(_("promoting server \"%s\" (ID: %i) using \"%s\""), - local_node_record.node_name, - local_node_record.node_id, - script); - log_detail(_("waiting up to %i seconds (parameter \"promote_check_timeout\") for promotion to complete"), - config_file_options.promote_check_timeout); - r = system(script); - if (r != 0) + if (server_version_num >= 120000) { - log_error(_("unable to promote server from standby to primary")); - exit(ERR_PROMOTION_FAIL); + log_detail(_("promoting server \"%s\" (ID: %i) using pg_promote()"), + local_node_record.node_name, + local_node_record.node_id); + + /* + * We'll check for promotion success ourselves, but will abort + * if some unrecoverable error prevented the function from being + * executed. + */ + if (!promote_standby(conn, false, 0)) + { + log_error(_("unable to promote server from standby to primary")); + exit(ERR_PROMOTION_FAIL); + } } + else + { + char script[MAXLEN]; + int r; + + get_server_action(ACTION_PROMOTE, script, (char *) data_dir); + + log_detail(_("promoting server \"%s\" (ID: %i) using \"%s\""), + local_node_record.node_name, + local_node_record.node_id, + script); + + r = system(script); + if (r != 0) + { + log_error(_("unable to promote server from standby to primary")); + exit(ERR_PROMOTION_FAIL); + } + } + + log_notice(_("waiting up to %i seconds (parameter \"promote_check_timeout\") for promotion to complete"), + config_file_options.promote_check_timeout); for (i = 0; i < config_file_options.promote_check_timeout; i += config_file_options.promote_check_interval) { @@ -2807,6 +2833,7 @@ do_standby_switchover(void) PGconn *local_conn = NULL; PGconn *remote_conn = NULL; + int server_version_num = UNKNOWN_SERVER_VERSION_NUM; t_node_info local_node_record = T_NODE_INFO_INITIALIZER; /* the remote server is the primary to be demoted */ @@ -2866,6 +2893,9 @@ do_standby_switchover(void) local_conn = establish_db_connection(config_file_options.conninfo, true); + /* Verify that standby is a supported server version */ + server_version_num = check_server_version(local_conn, "standby", true, NULL); + record_status = get_node_record(local_conn, config_file_options.node_id, &local_node_record); if (record_status != RECORD_FOUND) { @@ -3944,7 +3974,7 @@ do_standby_switchover(void) } /* promote standby (local node) */ - _do_standby_promote_internal(local_conn); + _do_standby_promote_internal(local_conn, server_version_num); /*