diff -Nru repmgr-4.0.2/configfile.c repmgr-4.0.3/configfile.c --- repmgr-4.0.2/configfile.c 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/configfile.c 2018-02-14 02:28:38.000000000 +0000 @@ -671,7 +671,7 @@ * Raise an error if a known parameter is provided with an empty * value. Currently there's no reason why empty parameters are needed; * if we want to accept those, we'd need to add stricter default - * checking, as currently e.g. an empty `node` value will be converted + * checking, as currently e.g. an empty `node_id` value will be converted * to '0'. */ if (known_parameter == true && !strlen(value)) @@ -1600,31 +1600,109 @@ } -bool -parse_pg_basebackup_options(const char *pg_basebackup_options, t_basebackup_options *backup_options, int server_version_num, ItemList *error_list) +int +parse_output_to_argv(const char *string, char ***argv_array) { int options_len = 0; char *options_string = NULL; char *options_string_ptr = NULL; + int c = 1, + argc_item = 1; + char *argv_item = NULL; + char **local_argv_array = NULL; + ItemListCell *cell; /* * Add parsed options to this list, then copy to an array to pass to * getopt */ - static ItemList option_argv = {NULL, NULL}; + ItemList option_argv = {NULL, NULL}; + + options_len = strlen(string) + 1; + options_string = pg_malloc0(options_len); + options_string_ptr = options_string; + + /* Copy the string before operating on it with strtok() */ + strncpy(options_string, string, options_len); + + /* Extract arguments into a list and keep a count of the total */ + while ((argv_item = strtok(options_string_ptr, " ")) != NULL) + { + item_list_append(&option_argv, trim(argv_item)); + + argc_item++; + + if (options_string_ptr != NULL) + options_string_ptr = NULL; + } + + pfree(options_string); + + /* + * Array of argument values to pass to getopt_long - this will need to + * include an empty string as the first value (normally this would be the + * program name) + */ + local_argv_array = pg_malloc0(sizeof(char *) * (argc_item + 2)); + + /* Insert a blank dummy program name at the start of the array */ + local_argv_array[0] = pg_malloc0(1); + + /* + * Copy the previously extracted arguments from our list to the array + */ + for (cell = option_argv.head; cell; cell = cell->next) + { + int argv_len = strlen(cell->string) + 1; + + local_argv_array[c] = (char *)pg_malloc0(argv_len); + + strncpy(local_argv_array[c], cell->string, argv_len); + + c++; + } + + local_argv_array[c] = NULL; + + item_list_free(&option_argv); + + *argv_array = local_argv_array; + + return argc_item; +} + + +void +free_parsed_argv(char ***argv_array) +{ + char **local_argv_array = *argv_array; + int i = 0; + + while (local_argv_array[i] != NULL) + { + pfree((char *)local_argv_array[i]); + i++; + } + + pfree((char **)local_argv_array); + *argv_array = NULL; +} - char *argv_item = NULL; - int c, - argc_item = 1; + +bool +parse_pg_basebackup_options(const char *pg_basebackup_options, t_basebackup_options *backup_options, int server_version_num, ItemList *error_list) +{ + bool backup_options_ok = true; + + int c = 0, + argc_item = 0; char **argv_array = NULL; - ItemListCell *cell = NULL; int optindex = 0; struct option *long_options = NULL; - bool backup_options_ok = true; /* We're only interested in these options */ static struct option long_options_9[] = @@ -1650,56 +1728,12 @@ if (!strlen(pg_basebackup_options)) return backup_options_ok; - options_len = strlen(pg_basebackup_options) + 1; - options_string = pg_malloc(options_len); - options_string_ptr = options_string; - if (server_version_num >= 100000) long_options = long_options_10; else long_options = long_options_9; - /* Copy the string before operating on it with strtok() */ - strncpy(options_string, pg_basebackup_options, options_len); - - /* Extract arguments into a list and keep a count of the total */ - while ((argv_item = strtok(options_string_ptr, " ")) != NULL) - { - item_list_append(&option_argv, argv_item); - - argc_item++; - - if (options_string_ptr != NULL) - options_string_ptr = NULL; - } - - /* - * Array of argument values to pass to getopt_long - this will need to - * include an empty string as the first value (normally this would be the - * program name) - */ - argv_array = pg_malloc0(sizeof(char *) * (argc_item + 2)); - - /* Insert a blank dummy program name at the start of the array */ - argv_array[0] = pg_malloc0(1); - - c = 1; - - /* - * Copy the previously extracted arguments from our list to the array - */ - for (cell = option_argv.head; cell; cell = cell->next) - { - int argv_len = strlen(cell->string) + 1; - - argv_array[c] = pg_malloc0(argv_len); - - strncpy(argv_array[c], cell->string, argv_len); - - c++; - } - - argv_array[c] = NULL; + argc_item = parse_output_to_argv(pg_basebackup_options, &argv_array); /* Reset getopt's optind variable */ optind = 0; @@ -1743,15 +1777,7 @@ backup_options_ok = false; } - pfree(options_string); - - { - int i; - - for (i = 0; i < argc_item + 2; i++) - pfree(argv_array[i]); - } - pfree(argv_array); + free_parsed_argv(&argv_array); return backup_options_ok; } diff -Nru repmgr-4.0.2/configfile.h repmgr-4.0.3/configfile.h --- repmgr-4.0.2/configfile.h 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/configfile.h 2018-02-14 02:28:38.000000000 +0000 @@ -248,7 +248,6 @@ } - void set_progname(const char *argv0); const char *progname(void); @@ -263,12 +262,15 @@ ItemList *error_list, int minval); - bool parse_pg_basebackup_options(const char *pg_basebackup_options, t_basebackup_options *backup_options, int server_version_num, ItemList *error_list); +int parse_output_to_argv(const char *string, char ***argv_array); +void free_parsed_argv(char ***argv_array); + + /* called by repmgr-client and repmgrd */ void exit_with_cli_errors(ItemList *error_list); void print_item_list(ItemList *item_list); diff -Nru repmgr-4.0.2/config.h.in repmgr-4.0.3/config.h.in --- repmgr-4.0.2/config.h.in 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/config.h.in 2018-02-14 02:28:38.000000000 +0000 @@ -1,4 +1,2 @@ /* config.h.in. Generated from configure.in by autoheader. */ -/* Only build repmgr for BDR */ -#undef BDR_ONLY diff -Nru repmgr-4.0.2/configure repmgr-4.0.3/configure --- repmgr-4.0.2/configure 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/configure 2018-02-14 02:28:38.000000000 +0000 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for repmgr 4.0.2. +# Generated by GNU Autoconf 2.69 for repmgr 4.0.3. # # Report bugs to . # @@ -582,8 +582,8 @@ # Identity of this package. PACKAGE_NAME='repmgr' PACKAGE_TARNAME='repmgr' -PACKAGE_VERSION='4.0.2' -PACKAGE_STRING='repmgr 4.0.2' +PACKAGE_VERSION='4.0.3' +PACKAGE_STRING='repmgr 4.0.3' PACKAGE_BUGREPORT='pgsql-bugs@postgresql.org' PACKAGE_URL='https://2ndquadrant.com/en/resources/repmgr/' @@ -633,7 +633,6 @@ ac_subst_files='' ac_user_opts=' enable_option_checking -with_bdr_only ' ac_precious_vars='build_alias host_alias @@ -1179,7 +1178,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures repmgr 4.0.2 to adapt to many kinds of systems. +\`configure' configures repmgr 4.0.3 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1240,15 +1239,10 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of repmgr 4.0.2:";; + short | recursive ) echo "Configuration of repmgr 4.0.3:";; esac cat <<\_ACEOF -Optional Packages: - --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] - --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) - --with-bdr-only BDR-only build - Some influential environment variables: PG_CONFIG Location to find pg_config for target PostgreSQL (default PATH) @@ -1319,7 +1313,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -repmgr configure 4.0.2 +repmgr configure 4.0.3 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -1338,7 +1332,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by repmgr $as_me 4.0.2, which was +It was created by repmgr $as_me 4.0.3, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -1694,20 +1688,6 @@ - -# Check whether --with-bdr_only was given. -if test "${with_bdr_only+set}" = set; then : - withval=$with_bdr_only; -fi - -if test "x$with_bdr_only" != "x"; then : - -$as_echo "#define BDR_ONLY \"1\"" >>confdefs.h - - -fi - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5 $as_echo_n "checking for a sed that does not truncate output... " >&6; } if ${ac_cv_path_SED+:} false; then : @@ -2379,7 +2359,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by repmgr $as_me 4.0.2, which was +This file was extended by repmgr $as_me 4.0.3, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -2442,7 +2422,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -repmgr config.status 4.0.2 +repmgr config.status 4.0.3 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff -Nru repmgr-4.0.2/configure.in repmgr-4.0.3/configure.in --- repmgr-4.0.2/configure.in 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/configure.in 2018-02-14 02:28:38.000000000 +0000 @@ -1,4 +1,4 @@ -AC_INIT([repmgr], [4.0.2], [pgsql-bugs@postgresql.org], [repmgr], [https://2ndquadrant.com/en/resources/repmgr/]) +AC_INIT([repmgr], [4.0.3], [pgsql-bugs@postgresql.org], [repmgr], [https://2ndquadrant.com/en/resources/repmgr/]) AC_COPYRIGHT([Copyright (c) 2010-2018, 2ndQuadrant Ltd.]) @@ -6,12 +6,6 @@ AC_ARG_VAR([PG_CONFIG], [Location to find pg_config for target PostgreSQL (default PATH)]) -AC_ARG_WITH([bdr_only], [AS_HELP_STRING([--with-bdr-only], [BDR-only build])]) -AS_IF([test "x$with_bdr_only" != "x"], - [AC_DEFINE([BDR_ONLY], ["1"], [Only build repmgr for BDR])] -) - - AC_PROG_SED if test -z "$PG_CONFIG"; then diff -Nru repmgr-4.0.2/dbutils.c repmgr-4.0.3/dbutils.c --- repmgr-4.0.2/dbutils.c 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/dbutils.c 2018-02-14 02:28:38.000000000 +0000 @@ -437,15 +437,18 @@ for (c = 0; c < param_list->size; c++) { - if (param_list->keywords[c] != NULL) + if (param_list->keywords != NULL && param_list->keywords[c] != NULL) pfree(param_list->keywords[c]); - if (param_list->values[c] != NULL) + if (param_list->values != NULL && param_list->values[c] != NULL) pfree(param_list->values[c]); } - pfree(param_list->keywords); - pfree(param_list->values); + if (param_list->keywords != NULL) + pfree(param_list->keywords); + + if (param_list->values != NULL) + pfree(param_list->values); } @@ -1255,7 +1258,7 @@ initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT node_id " - " FROM repmgr.nodes " + " FROM repmgr.nodes " " WHERE type = 'primary' " " AND active IS TRUE "); @@ -1866,8 +1869,8 @@ initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT " REPMGR_NODES_COLUMNS - " FROM repmgr.nodes " - " WHERE node_id = %i", + " FROM repmgr.nodes n " + " WHERE n.node_id = %i", node_id); log_verbose(LOG_DEBUG, "get_node_record():\n %s", query.data); @@ -1894,8 +1897,8 @@ appendPQExpBuffer(&query, "SELECT " REPMGR_NODES_COLUMNS - " FROM repmgr.nodes " - " WHERE node_name = '%s' ", + " FROM repmgr.nodes n " + " WHERE n.node_name = '%s' ", node_name); log_verbose(LOG_DEBUG, "get_node_record_by_name():\n %s", query.data); @@ -2020,8 +2023,8 @@ appendPQExpBuffer(&query, " SELECT " REPMGR_NODES_COLUMNS - " FROM repmgr.nodes " - "ORDER BY node_id "); + " FROM repmgr.nodes n " + "ORDER BY n.node_id "); log_verbose(LOG_DEBUG, "get_all_node_records():\n%s", query.data); @@ -2046,9 +2049,9 @@ appendPQExpBuffer(&query, " SELECT " REPMGR_NODES_COLUMNS - " FROM repmgr.nodes " - " WHERE upstream_node_id = %i " - "ORDER BY node_id ", + " FROM repmgr.nodes n " + " WHERE n.upstream_node_id = %i " + "ORDER BY n.node_id ", node_id); log_verbose(LOG_DEBUG, "get_downstream_node_records():\n%s", query.data); @@ -2075,11 +2078,11 @@ appendPQExpBuffer(&query, " SELECT " REPMGR_NODES_COLUMNS - " FROM repmgr.nodes " - " WHERE upstream_node_id = %i " - " AND node_id != %i " - " AND active IS TRUE " - "ORDER BY node_id ", + " FROM repmgr.nodes n " + " WHERE n.upstream_node_id = %i " + " AND n.node_id != %i " + " AND n.active IS TRUE " + "ORDER BY n.node_id ", upstream_node_id, node_id); @@ -2107,8 +2110,8 @@ appendPQExpBuffer(&query, " SELECT " REPMGR_NODES_COLUMNS - " FROM repmgr.nodes " - "ORDER BY priority DESC, node_name "); + " FROM repmgr.nodes n " + "ORDER BY n.priority DESC, n.node_name "); log_verbose(LOG_DEBUG, "get_node_records_by_priority():\n%s", query.data); @@ -2123,7 +2126,11 @@ return; } -void +/* + * return all node records together with their upstream's node name, + * if available. + */ +bool get_all_node_records_with_upstream(PGconn *conn, NodeInfoList *node_list) { PQExpBufferData query; @@ -2145,16 +2152,62 @@ termPQExpBuffer(&query); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + log_error(_("unable to retrieve node records")); + log_detail("%s", PQerrorMessage(conn)); + PQclear(res); + return false; + } + _populate_node_records(res, node_list); PQclear(res); - return; + return true; } bool +get_downsteam_nodes_with_missing_slot(PGconn *conn, int this_node_id, NodeInfoList *node_list) +{ + PQExpBufferData query; + PGresult *res = NULL; + + initPQExpBuffer(&query); + + appendPQExpBuffer(&query, + " SELECT " REPMGR_NODES_COLUMNS + " FROM repmgr.nodes n " + "LEFT JOIN pg_catalog.pg_replication_slots rs " + " ON rs.slot_name = n.node_name " + " WHERE rs.slot_name IS NULL " + " AND n.node_id != %i ", + this_node_id); + + log_verbose(LOG_DEBUG, "get_all_node_records_with_missing_slot():\n%s", query.data); + + res = PQexec(conn, query.data); + + termPQExpBuffer(&query); + + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + log_error(_("unable to retrieve node records")); + log_detail("%s", PQerrorMessage(conn)); + PQclear(res); + return false; + } + + _populate_node_records(res, node_list); + + PQclear(res); + + return true; +} + +bool create_node_record(PGconn *conn, char *repmgr_action, t_node_info *node_info) { if (repmgr_action != NULL) @@ -2271,9 +2324,11 @@ if (PQresultStatus(res) != PGRES_COMMAND_OK) { - log_error(_("unable to %s node record:\n %s"), + log_error(_("unable to %s node record for node \"%s\" (ID: %i)"), action, - PQerrorMessage(conn)); + node_info->node_name, + node_info->node_id); + log_detail("%s", PQerrorMessage(conn)); PQclear(res); return false; } @@ -2633,6 +2688,11 @@ PQExpBufferData query; PGresult *res = NULL; + if (server_version_num == UNKNOWN_SERVER_VERSION_NUM) + server_version_num = get_server_version(conn, NULL); + + Assert(server_version_num != UNKNOWN_SERVER_VERSION_NUM); + initPQExpBuffer(&query); appendPQExpBuffer(&query, @@ -2653,8 +2713,8 @@ appendPQExpBuffer(&query, " current_setting('max_replication_slots')::INT AS max_replication_slots, " " (SELECT COUNT(*) FROM pg_catalog.pg_replication_slots) AS total_replication_slots, " - " (SELECT COUNT(*) FROM pg_catalog.pg_replication_slots WHERE active = TRUE) AS active_replication_slots, " - " (SELECT COUNT(*) FROM pg_catalog.pg_replication_slots WHERE active = FALSE) AS inactive_replication_slots, "); + " (SELECT COUNT(*) FROM pg_catalog.pg_replication_slots WHERE active IS TRUE) AS active_replication_slots, " + " (SELECT COUNT(*) FROM pg_catalog.pg_replication_slots WHERE active IS FALSE) AS inactive_replication_slots, "); } @@ -3251,14 +3311,14 @@ } break; case 'p': - /* %p: former primary id ("repmgr standby switchover") */ + /* %p: primary id ("standby_switchover": former primary id) */ src_ptr++; - if (event_info->former_primary_id != UNKNOWN_NODE_ID) + if (event_info->node_id != UNKNOWN_NODE_ID) { PQExpBufferData node_id; initPQExpBuffer(&node_id); appendPQExpBuffer(&node_id, - "%i", event_info->former_primary_id); + "%i", event_info->node_id); strlcpy(dst_ptr, node_id.data, end_ptr - dst_ptr); dst_ptr += strlen(dst_ptr); termPQExpBuffer(&node_id); @@ -3550,6 +3610,45 @@ return RECORD_FOUND; } + +int +get_free_replication_slots(PGconn *conn) +{ + PQExpBufferData query; + PGresult *res = NULL; + int free_slots = 0; + + initPQExpBuffer(&query); + + appendPQExpBuffer(&query, + " SELECT pg_catalog.current_setting('max_replication_slots')::INT - " + " COUNT(*) AS free_slots" + " FROM pg_catalog.pg_replication_slots"); + + res = PQexec(conn, query.data); + termPQExpBuffer(&query); + + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + log_error(_("unable to execute replication slot query")); + log_detail("%s", PQerrorMessage(conn)); + PQclear(res); + return -1; + } + + if (PQntuples(res) == 0) + { + PQclear(res); + return -1; + } + + free_slots = atoi(PQgetvalue(res, 0, 0)); + + PQclear(res); + return free_slots; +} + + /* ==================== */ /* tablespace functions */ /* ==================== */ @@ -4255,8 +4354,8 @@ appendPQExpBuffer(&query, "SELECT COUNT(*)" - " FROM repmgr.nodes" - " WHERE type != 'bdr' "); + " FROM repmgr.nodes n" + " WHERE n.type != 'bdr' "); res = PQexec(conn, query.data); termPQExpBuffer(&query); @@ -4425,9 +4524,9 @@ initPQExpBuffer(&query); appendPQExpBuffer(&query, - " SELECT node_name " - " FROM repmgr.nodes " - " WHERE node_id != %i", + " SELECT n.node_name " + " FROM repmgr.nodes n " + " WHERE n.node_id != %i", node_id); log_verbose(LOG_DEBUG, "get_bdr_other_node_name():\n %s", query.data); diff -Nru repmgr-4.0.2/dbutils.h repmgr-4.0.3/dbutils.h --- repmgr-4.0.2/dbutils.h 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/dbutils.h 2018-02-14 02:28:38.000000000 +0000 @@ -28,7 +28,7 @@ #include "strutil.h" #include "voting.h" -#define REPMGR_NODES_COLUMNS "node_id, type, upstream_node_id, node_name, conninfo, repluser, slot_name, location, priority, active, config_file, '' AS upstream_node_name " +#define REPMGR_NODES_COLUMNS "n.node_id, n.type, n.upstream_node_id, n.node_name, n.conninfo, n.repluser, n.slot_name, n.location, n.priority, n.active, n.config_file, '' AS upstream_node_name " #define BDR_NODES_COLUMNS "node_sysid, node_timeline, node_dboid, node_status, node_name, node_local_dsn, node_init_from_dsn, node_read_only, node_seq_id" #define ERRBUFF_SIZE 512 @@ -81,6 +81,14 @@ typedef enum { + CONN_UNKNOWN = -1, + CONN_OK, + CONN_BAD, + CONN_ERROR +} ConnectionStatus; + +typedef enum +{ SLOT_UNKNOWN = -1, SLOT_INACTIVE, SLOT_ACTIVE @@ -175,7 +183,7 @@ { char *node_name; char *conninfo_str; - int former_primary_id; + int node_id; } t_event_info; #define T_EVENT_INFO_INITIALIZER { \ @@ -410,7 +418,8 @@ void get_downstream_node_records(PGconn *conn, int node_id, NodeInfoList *nodes); void get_active_sibling_node_records(PGconn *conn, int node_id, int upstream_node_id, NodeInfoList *node_list); void get_node_records_by_priority(PGconn *conn, NodeInfoList *node_list); -void get_all_node_records_with_upstream(PGconn *conn, NodeInfoList *node_list); +bool get_all_node_records_with_upstream(PGconn *conn, NodeInfoList *node_list); +bool get_downsteam_nodes_with_missing_slot(PGconn *conn, int this_node_id, NodeInfoList *noede_list); bool create_node_record(PGconn *conn, char *repmgr_action, t_node_info *node_info); bool update_node_record(PGconn *conn, char *repmgr_action, t_node_info *node_info); @@ -445,6 +454,7 @@ bool create_replication_slot(PGconn *conn, char *slot_name, int server_version_num, PQExpBufferData *error_msg); bool drop_replication_slot(PGconn *conn, char *slot_name); RecordStatus get_slot_record(PGconn *conn, char *slot_name, t_replication_slot *record); +int get_free_replication_slots(PGconn *conn); /* tablespace functions */ bool get_tablespace_name_by_location(PGconn *conn, const char *location, char *name); diff -Nru repmgr-4.0.2/debian/changelog repmgr-4.0.3/debian/changelog --- repmgr-4.0.2/debian/changelog 2018-01-17 10:11:14.000000000 +0000 +++ repmgr-4.0.3/debian/changelog 2018-02-15 08:50:39.000000000 +0000 @@ -1,3 +1,9 @@ +repmgr (4.0.3-1) unstable; urgency=medium + + * New upstream version 4.0.3 + + -- Marco Nenciarini Thu, 15 Feb 2018 09:50:39 +0100 + repmgr (4.0.2-1) unstable; urgency=medium [ Christoph Berg ] diff -Nru repmgr-4.0.2/dirutil.c repmgr-4.0.3/dirutil.c --- repmgr-4.0.2/dirutil.c 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/dirutil.c 2018-02-14 02:28:38.000000000 +0000 @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -34,34 +35,33 @@ #include "dirutil.h" #include "strutil.h" #include "log.h" +#include "controldata.h" static int unlink_dir_callback(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf); +/* PID can be negative if backend is standalone */ +typedef long pgpid_t; /* - * make sure the directory either doesn't exist or is empty - * we use this function to check the new data directory and - * the directories for tablespaces + * Check if a directory exists, and if so whether it is empty. * - * This is the same check initdb does on the new PGDATA dir - * - * Returns 0 if nonexistent, 1 if exists and empty, 2 if not empty, - * or -1 if trouble accessing directory + * This function is used for checking both the data directory + * and tablespace directories. */ -int +DataDirState check_dir(char *path) { - DIR *chkdir; - struct dirent *file; - int result = 1; + DIR *chkdir = NULL; + struct dirent *file = NULL; + int result = DIR_EMPTY; errno = 0; chkdir = opendir(path); if (!chkdir) - return (errno == ENOENT) ? 0 : -1; + return (errno == ENOENT) ? DIR_NOENT : DIR_ERROR; while ((file = readdir(chkdir)) != NULL) { @@ -73,25 +73,15 @@ } else { - result = 2; /* not empty */ + result = DIR_NOT_EMPTY; break; } } -#ifdef WIN32 - - /* - * This fix is in mingw cvs (runtime/mingwex/dirent.c rev 1.4), but not in - * released version - */ - if (GetLastError() == ERROR_NO_MORE_FILES) - errno = 0; -#endif - closedir(chkdir); if (errno != 0) - return -1; /* some kind of I/O error? */ + return DIR_ERROR; /* some kind of I/O error? */ return result; } @@ -106,12 +96,13 @@ if (mkdir_p(path, 0700) == 0) return true; - log_error(_("unable to create directory \"%s\": %s"), - path, strerror(errno)); + log_error(_("unable to create directory \"%s\""), path); + log_detail("%s", strerror(errno)); return false; } + bool set_dir_permissions(char *path) { @@ -146,26 +137,6 @@ oumask = 0; retval = 0; -#ifdef WIN32 - /* skip network and drive specifiers for win32 */ - if (strlen(p) >= 2) - { - if (p[0] == '/' && p[1] == '/') - { - /* network drive */ - p = strstr(p + 2, "/"); - if (p == NULL) - return 1; - } - else if (p[1] == ':' && - ((p[0] >= 'a' && p[0] <= 'z') || - (p[0] >= 'A' && p[0] <= 'Z'))) - { - /* local drive */ - p += 2; - } - } -#endif if (p[0] == '/') /* Skip leading '/'. */ ++p; @@ -242,17 +213,91 @@ return false; } +/* + * Attempt to determine if a PostgreSQL data directory is in use + * by reading the pidfile. This is the same mechanism used by + * "pg_ctl". + * + * This function will abort with appropriate log messages if a file error + * is encountered, as the user will need to address the situation before + * any further useful progress can be made. + */ +PgDirState +is_pg_running(char *path) +{ + long pid; + FILE *pidf; + + char pid_file[MAXPGPATH]; + + /* it's reasonable to assume the pidfile name will not change */ + snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", path); + + pidf = fopen(pid_file, "r"); + if (pidf == NULL) + { + /* + * No PID file - PostgreSQL shouldn't be running. From 9.3 (the + * earliesty version we care about) removal of the PID file will + * cause the postmaster to shut down, so it's highly unlikely + * that PostgreSQL will still be running. + */ + if (errno == ENOENT) + { + return PG_DIR_NOT_RUNNING; + } + else + { + log_error(_("unable to open PostgreSQL PID file \"%s\""), pid_file); + log_detail("%s", strerror(errno)); + exit(ERR_BAD_CONFIG); + } + } + + + /* + * In the unlikely event we're unable to extract a PID from the PID file, + * log a warning but assume we're not dealing with a running instance + * as PostgreSQL should have shut itself down in these cases anyway. + */ + if (fscanf(pidf, "%ld", &pid) != 1) + { + /* Is the file empty? */ + if (ftell(pidf) == 0 && feof(pidf)) + { + log_warning(_("PostgreSQL PID file \"%s\" is empty"), path); + } + else + { + log_warning(_("invalid data in PostgreSQL PID file \"%s\""), path); + } + + return PG_DIR_NOT_RUNNING; + } + + fclose(pidf); + + if (pid == getpid()) + return PG_DIR_NOT_RUNNING; + + if (pid == getppid()) + return PG_DIR_NOT_RUNNING; + + if (kill(pid, 0) == 0) + return PG_DIR_RUNNING; + + return PG_DIR_NOT_RUNNING; +} + bool create_pg_dir(char *path, bool force) { - bool pg_dir = false; - - /* Check this directory could be used as a PGDATA dir */ + /* Check this directory can be used as a PGDATA dir */ switch (check_dir(path)) { - case 0: - /* dir not there, must create it */ + case DIR_NOENT: + /* directory does not exist, attempt to create it */ log_info(_("creating directory \"%s\"..."), path); if (!create_dir(path)) @@ -262,52 +307,51 @@ return false; } break; - case 1: - /* Present but empty, fix permissions and use it */ - log_info(_("checking and correcting permissions on existing directory %s"), + case DIR_EMPTY: + /* exists but empty, fix permissions and use it */ + log_info(_("checking and correcting permissions on existing directory \"%s\""), path); if (!set_dir_permissions(path)) { - log_error(_("unable to change permissions of directory \"%s\":\n %s"), - path, strerror(errno)); + log_error(_("unable to change permissions of directory \"%s\""), path); + log_detail("%s", strerror(errno)); return false; } break; - case 2: - /* Present and not empty */ + case DIR_NOT_EMPTY: + /* exists but is not empty */ log_warning(_("directory \"%s\" exists but is not empty"), path); - pg_dir = is_pg_dir(path); - - if (pg_dir && force) + if (is_pg_dir(path)) { - /* TODO: check DB state, if not running overwrite */ - - if (false) + if (force == true) { - log_notice(_("deleting existing data directory \"%s\""), path); + log_notice(_("-F/--force provided - deleting existing data directory \"%s\""), path); nftw(path, unlink_dir_callback, 64, FTW_DEPTH | FTW_PHYS); + return true; } - /* Let it continue */ - break; + + return false; } - else if (pg_dir && !force) + else { - log_hint(_("This looks like a PostgreSQL directory.\n" - "If you are sure you want to clone here, " - "please check there is no PostgreSQL server " - "running and use the -F/--force option")); + if (force == true) + { + log_notice(_("deleting existing directory \"%s\""), path); + nftw(path, unlink_dir_callback, 64, FTW_DEPTH | FTW_PHYS); + return true; + } return false; } - - return false; - default: + break; + case DIR_ERROR: log_error(_("could not access directory \"%s\": %s"), path, strerror(errno)); return false; } + return true; } diff -Nru repmgr-4.0.2/dirutil.h repmgr-4.0.3/dirutil.h --- repmgr-4.0.2/dirutil.h 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/dirutil.h 2018-02-14 02:28:38.000000000 +0000 @@ -19,12 +19,29 @@ #ifndef _DIRUTIL_H_ #define _DIRUTIL_H_ +typedef enum +{ + DIR_ERROR = -1, + DIR_NOENT, + DIR_EMPTY, + DIR_NOT_EMPTY +} DataDirState; + +typedef enum +{ + PG_DIR_ERROR = -1, + PG_DIR_NOT_RUNNING, + PG_DIR_RUNNING +} PgDirState; + extern int mkdir_p(char *path, mode_t omode); extern bool set_dir_permissions(char *path); -extern int check_dir(char *path); +extern DataDirState check_dir(char *path); extern bool create_dir(char *path); extern bool is_pg_dir(char *path); +extern PgDirState is_pg_running(char *path); extern bool create_pg_dir(char *path, bool force); extern int rmdir_recursive(char *path); + #endif diff -Nru repmgr-4.0.2/doc/appendix-release-notes.sgml repmgr-4.0.3/doc/appendix-release-notes.sgml --- repmgr-4.0.2/doc/appendix-release-notes.sgml 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/doc/appendix-release-notes.sgml 2018-02-14 02:28:38.000000000 +0000 @@ -11,18 +11,150 @@ before performing an upgrade, as there may be version-specific upgrade steps. - See also: + + Release 4.0.3 + ??? Feb ??, 2018 + + + &repmgr; 4.0.3 contains some bug fixes and and a number of + usability enhancements related to logging/diagnostics, + event notifications and pre-action checks. + + + + Usability enhancements + + + + + + + improve repmgr standby switchover + behaviour when pg_ctl is used to control the server and logging output is + not explicitly redirected + + + + + + improve repmgr standby switchover + log messages and provide new exit code ERR_SWITCHOVER_INCOMPLETE when old primary could + not be shut down cleanly + + + + + + add check to verify the demotion candidate can make a replication connection to the + promotion candidate before executing a switchover (GitHub #370) + + + + + + add check for sufficient walsenders and replication slots on the promotion candidate before executing + repmgr standby switchover + (GitHub #371) + + + + + + add --dry-run mode to repmgr standby follow + (GitHub #369) + + + + + + add standby_register_sync event notification, which is fired when + repmgr standby register + is run with the option and the new or updated standby node + record has synchronised to the standy (GitHub #374) + + + + + + when running repmgr cluster show, + if any node is unreachable, output the error message encountered in the list of warnings + (GitHub #369) + + + + + + + + + Bug fixes + + + + + + ensure an inactive data directory can be overwritten when + cloning a standby (GitHub #366) + + + + + + repmgr node status + upstream node display fixed (GitHub #363) + + + + + + repmgr primary unregister: + clarify usage and fix --help output (GitHub #373) + + + + + + parsing of pg_basebackup_options fixed (GitHub #376) + + + + + + ensure the pg_subtrans directory is created when cloning a + standby in Barman mode + + + + + + repmgr witness register: + fix primary node check (GitHub #377). + + + + + + + + + + Release 4.0.2 Thu Jan 18, 2018 - repmgr 4.0.2 contains some bug fixes and minor usability enhancements. + &repmgr; 4.0.2 contains some bug fixes and small usability enhancements. + + This release can be installed as a simple package upgrade from &repmgr; 4.0.1 or 4.0; + repmgrd (if running) should be restarted. + + Usability enhancements @@ -121,7 +253,7 @@ Wed Dec 13, 2017 - repmgr 4.0.1 is a bugfix release. + &repmgr; 4.0.1 is a bugfix release. Bug fixes diff -Nru repmgr-4.0.2/doc/event-notifications.sgml repmgr-4.0.3/doc/event-notifications.sgml --- repmgr-4.0.2/doc/event-notifications.sgml 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/doc/event-notifications.sgml 2018-02-14 02:28:38.000000000 +0000 @@ -37,7 +37,7 @@ repmgr.conf. - This parameter accepts the following format placeholders: + The following format placeholders are provided for all event notifications: @@ -84,18 +84,8 @@ - - - - - - - node ID of the demoted standby ( only) - - - - + The values provided for %t and %d will probably contain spaces, so should be quoted in the provided command @@ -104,34 +94,60 @@ event_notification_command='/path/to/some/script %n %e %s "%t" "%d"' + - Additionally the following format placeholders are available for the event - type bdr_failover and optionally bdr_recovery: + The following parameters are provided for a subset of event notifications: + + + + + node ID of the current primary ( and ) + + + node ID of the demoted primary ( only) + + + + - conninfo string of the next available node + conninfo string of the primary node + ( and ) + + + conninfo string of the next available node + (bdr_failover and bdr_recovery) + - name of the next available node + name of the current primary node ( and ) + + + name of the next available node (bdr_failover and bdr_recovery) + + - These should always be quoted. + The values provided for %c and %a + will probably contain spaces, so should always be quoted. + By default, all notification types will be passed to the designated script; - the notification types can be filtered to explicitly named ones: + the notification types can be filtered to explicitly named ones using the + event_notifications parameter: @@ -145,6 +161,9 @@ standby_register + standby_register_sync + + standby_unregister @@ -187,6 +206,18 @@ repmgrd_failover_follow + repmgrd_upstream_disconnect + + + repmgrd_upstream_reconnect + + + repmgrd_promote_error + + + repmgrd_failover_promote + + bdr_failover @@ -204,6 +235,7 @@ + Note that under some circumstances (e.g. when no replication cluster primary could be located), it will not be possible to write an entry into the diff -Nru repmgr-4.0.2/doc/overview.sgml repmgr-4.0.3/doc/overview.sgml --- repmgr-4.0.2/doc/overview.sgml 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/doc/overview.sgml 2018-02-14 02:28:38.000000000 +0000 @@ -178,8 +178,8 @@ In order to effectively manage a replication cluster, &repmgr; needs to store information about the servers in the cluster in a dedicated database schema. - This schema is automatically by the &repmgr; extension, which is installed - during the first step in initialising a &repmgr;-administered cluster + This schema is automatically created by the &repmgr; extension, which is installed + during the first step in initializing a &repmgr;-administered cluster (repmgr primary register) and contains the following objects: diff -Nru repmgr-4.0.2/doc/repmgr-bdr.sgml repmgr-4.0.3/doc/repmgr-bdr.sgml --- repmgr-4.0.2/doc/repmgr-bdr.sgml 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/doc/repmgr-bdr.sgml 1970-01-01 00:00:00.000000000 +0000 @@ -1,37 +0,0 @@ - - - repmgrd - BDR - - - - BDR - - - BDR failover with repmgrd - - &repmgr; 4.x provides support for monitoring BDR nodes and taking action in - case one of the nodes fails. - - - - Due to the nature of BDR, it's only safe to use this solution for - a two-node scenario. Introducing additional nodes will create an inherent - risk of node desynchronisation if a node goes down without being cleanly - removed from the cluster. - - - - In contrast to streaming replication, there's no concept of "promoting" a new - primary node with BDR. Instead, "failover" involves monitoring both nodes - with `repmgrd` and redirecting queries from the failed node to the remaining - active node. This can be done by using an - event notification script - which is called by repmgrd to dynamically - reconfigure a proxy server/connection pooler such as PgBouncer. - - - - - - diff -Nru repmgr-4.0.2/doc/repmgrd-bdr.sgml repmgr-4.0.3/doc/repmgrd-bdr.sgml --- repmgr-4.0.2/doc/repmgrd-bdr.sgml 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/doc/repmgrd-bdr.sgml 2018-02-14 02:28:38.000000000 +0000 @@ -24,7 +24,7 @@ In contrast to streaming replication, there's no concept of "promoting" a new primary node with BDR. Instead, "failover" involves monitoring both nodes - with `repmgrd` and redirecting queries from the failed node to the remaining + with repmgrd and redirecting queries from the failed node to the remaining active node. This can be done by using an event notification script which is called by repmgrd to dynamically @@ -174,17 +174,13 @@ Key to "failover" execution is the event_notification_command, which is a user-definable script specified in repmpgr.conf - and which should reconfigure the proxy server/ connection pooler to point - to the other, still-active node. + and which can use a &repmgr; event notification + to reconfigure the proxy server / connection pooler so it points to the other, still-active node. + Details of the event will be passed as parameters to the script. - Each time &repmgr; (or repmgrd) records an event, - it can optionally execute the script defined in - event_notification_command to take further action; - details of the event will be passed as parameters. - - - Following placeholders are available to the script: + Following parameter placeholders are available for the script definition in repmpgr.conf; + these will be replaced with the appropriate value when the script is executed: @@ -231,20 +227,37 @@ + + + + + conninfo string of the next available node (bdr_failover and bdr_recovery) + + + + + + + + name of the next available node (bdr_failover and bdr_recovery) + + + - Note that %c and %a will only be provided during - bdr_failover events, which is what is of interest here. + Note that %c and %a are only provided with + particular failover events, in this case bdr_failover. - The provided sample script (`scripts/bdr-pgbouncer.sh`) is configured like - this: + The provided sample script + (scripts/bdr-pgbouncer.sh) + is configured as follows: event_notification_command='/path/to/bdr-pgbouncer.sh %n %e %s "%c" "%a"' - and parses the configures parameters like this: + and parses the placeholder parameters like this: NODE_ID=$1 EVENT_TYPE=$2 @@ -252,12 +265,14 @@ NEXT_CONNINFO=$4 NEXT_NODE_NAME=$5 - - The script also contains some hard-coded values about the PgBouncer - configuration for both nodes; these will need to be adjusted for your local environment - (ideally the scripts would be maintained as templates and generated by some - kind of provisioning system). - + + + The sample script also contains some hard-coded values for the PgBouncer + configuration for both nodes; these will need to be adjusted for your local environment + (ideally the scripts would be maintained as templates and generated by some + kind of provisioning system). + + The script performs following steps: diff -Nru repmgr-4.0.2/doc/repmgr-primary-unregister.sgml repmgr-4.0.3/doc/repmgr-primary-unregister.sgml --- repmgr-4.0.2/doc/repmgr-primary-unregister.sgml 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/doc/repmgr-primary-unregister.sgml 2018-02-14 02:28:38.000000000 +0000 @@ -22,6 +22,10 @@ Execution + repmgr primary unregister should be run on the current primary, + with the ID of the node to unregister passed as . + + Execute with the --dry-run option to check what would happen without actually unregistering the node. @@ -33,6 +37,34 @@ + + Options + + + + + + + + Check prerequisites but don't actually unregister the primary. + + + + + + + + + ID of the inactive primary to be unregistered. + + + + + + + + + Event notifications A primary_unregister event notification will be generated. diff -Nru repmgr-4.0.2/doc/repmgr-standby-follow.sgml repmgr-4.0.3/doc/repmgr-standby-follow.sgml --- repmgr-4.0.2/doc/repmgr-standby-follow.sgml 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/doc/repmgr-standby-follow.sgml 2018-02-14 02:28:38.000000000 +0000 @@ -49,13 +49,52 @@ + Options + + + + + + + Check prerequisites but don't actually follow a new standby. + + + + This does not guarantee the standby can follow the primary; in + particular, whether the primary and standby timelines have diverged, + can currently only be determined by actually attempting to + attach the standby to the primary. + + + + + + + + + + + Wait for a primary to appear. + + + + + + + + Event notifications A standby_follow event notification will be generated. + + If provided, &repmgr; will subsitute the placeholders %p with the node ID of the primary + being followed, %c with its conninfo string, and + %a with its node name. + - + See also diff -Nru repmgr-4.0.2/doc/repmgr-standby-register.sgml repmgr-4.0.3/doc/repmgr-standby-register.sgml --- repmgr-4.0.2/doc/repmgr-standby-register.sgml 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/doc/repmgr-standby-register.sgml 2018-02-14 02:28:38.000000000 +0000 @@ -57,16 +57,16 @@ Waiting for the registration to propagate to the standby - Depending on your environment and workload, it may take some time for - the standby's node record to propagate from the primary to the standby. Some - actions (such as starting repmgrd) require that the standby's node record + Depending on your environment and workload, it may take some time for the standby's node record + to propagate from the primary to the standby. Some actions (such as starting + repmgrd) require that the standby's node record is present and up-to-date to function correctly. - By providing the option --wait-sync to the + By providing the option to the repmgr standby register command, &repmgr; will wait until the record is synchronised before exiting. An optional timeout (in - seconds) can be added to this option (e.g. --wait-sync=60). + seconds) can be added to this option (e.g. ). @@ -75,20 +75,20 @@ Under some circumstances you may wish to register a standby which is not yet running; this can be the case when using provisioning tools to create - a complex replication cluster. In this case, by using the -F/--force + a complex replication cluster. In this case, by using the option and providing the connection parameters to the primary server, the standby can be registered. Similarly, with cascading replication it may be necessary to register a standby whose upstream node has not yet been registered - in this case, - using -F/--force will result in the creation of an inactive placeholder + using will result in the creation of an inactive placeholder record for the upstream node, which will however later need to be registered - with the -F/--force option too. + with the option too. When used with repmgr standby register, care should be taken that use of the - -F/--force option does not result in an incorrectly configured cluster. + option does not result in an incorrectly configured cluster. @@ -96,8 +96,21 @@ Event notifications A standby_register event notification - will be generated. + will be generated immediately after the node record is updated on the primary. + + + If the option is provided, a standby_register_sync + event notification will be generated immediately after the node record has synchronised to the + standby. + + + + If provided, &repmgr; will subsitute the placeholders %p with the node ID of the + primary node, %c with its conninfo string, and + %a with its node name. + + diff -Nru repmgr-4.0.2/doc/repmgr-standby-switchover.sgml repmgr-4.0.3/doc/repmgr-standby-switchover.sgml --- repmgr-4.0.2/doc/repmgr-standby-switchover.sgml 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/doc/repmgr-standby-switchover.sgml 2018-02-14 02:28:38.000000000 +0000 @@ -22,9 +22,19 @@ If other standbys are connected to the demotion candidate, &repmgr; can instruct - these to follow the new primary if the option --siblings-follow - is specified. - + these to follow the new primary if the option --siblings-follow + is specified. This requires a passwordless SSH connection between the promotion + candidate (new primary) and the standbys attached to the demotion candidate + (existing primary). + + + + Performing a switchover is a non-trivial operation. In particular it + relies on the current primary being able to shut down cleanly and quickly. + &repmgr; will attempt to check for potential issues but cannot guarantee + a successful switchover. + + @@ -47,6 +57,13 @@ Check prerequisites but don't actually execute a switchover. + + + Success of does not imply the switchover will + complete successfully, only that + the prerequisites for performing the operation are met. + + @@ -57,6 +74,12 @@ Ignore warnings and continue anyway. + + Specifically, if a problem is encountered when shutting down the current primary, + using will cause &repmgr; to continue by promoting + the standby to be the new primary, and if is + specified, attach any other standbys to the new primary. + @@ -103,6 +126,11 @@ repmgrd should not be active on any nodes while a switchover is being executed. This restriction may be lifted in a later version. + + External database connections, e.g. from an application, should not be permitted while + the switchover is taking place. In particular, active transactions on the primary + can potentially disrupt the shutdown process. + @@ -115,10 +143,48 @@ If using an event notification script, standby_switchover will populate the placeholder parameter %p with the node ID of - the former standby. + the former primary. + + Exit codes + + Following exit codes can be emitted by repmgr standby switchover: + + + + + + + + The switchover completed successfully. + + + + + + + + + The switchover could not be executed. + + + + + + + + + The switchover was executed but a problem was encountered. + Typically this means the former primary could not be reattached + as a standby. + + + + + + See also diff -Nru repmgr-4.0.2/doc/switchover.sgml repmgr-4.0.3/doc/switchover.sgml --- repmgr-4.0.2/doc/switchover.sgml 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/doc/switchover.sgml 2018-02-14 02:28:38.000000000 +0000 @@ -61,6 +61,13 @@ + Ensure that a passwordless SSH connection is possible from the promotion candidate + (standby) to the demotion candidate (current primary). If --siblings-follow + will be used, ensure that passwordless SSH connections are possible from the + promotion candidate to all standbys attached to the demotion candidate. + + + Double-check which commands will be used to stop/start/restart the current primary; on the primary execute: diff -Nru repmgr-4.0.2/doc/upgrading-repmgr.sgml repmgr-4.0.3/doc/upgrading-repmgr.sgml --- repmgr-4.0.2/doc/upgrading-repmgr.sgml 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/doc/upgrading-repmgr.sgml 2018-02-14 02:28:38.000000000 +0000 @@ -11,22 +11,86 @@ containing bugfixes and other minor improvements. Any substantial new functionality will be included in a feature release (e.g. 4.0.x to 4.1.x). - - &repmgr; is implemented as a PostgreSQL extension; to upgrade it, first - install the updated package (or compile the updated source), then in the - database where the &repmgr; extension is installed, execute - ALTER EXTENSION repmgr UPDATE. - - - If repmgrd is running, it may be necessary to restart - the PostgreSQL server if the upgrade contains changes to the shared object - file used by repmgrd; check the release notes for details. - - - Please check the release notes for every - release as they may contain upgrade instructions particular to individual versions. - + + + upgrading + repmgr 4.x and later + + Upgrading repmgr 4.x and later + + &repmgr; 4.x is implemented as a PostgreSQL extension; normally the upgrade consists + of the two following steps: + + + + Install the updated package (or compile the updated source) + + + + + In the database where the &repmgr; extension is installed, execute + ALTER EXTENSION repmgr UPDATE. + + + + + + + Always check the release notes for every + release as they may contain upgrade instructions particular to individual versions. + + + + If the repmgrd daemon is in use, we recommend stopping it + before upgrading &repmgr;. + + + Note that it may be necessary to restart the PostgreSQL server if the upgrade contains + changes to the shared object file used by repmgrd; check the + release notes for details. + + + + + + upgrading + pg_upgrade + + + pg_upgrade + + pg_upgrade and repmgr + + + pg_upgrade requires that if any functions are + dependent on a shared library, this library must be present in both + the old and new installations before pg_upgrade + can be executed. + + + To minimize the risk of any upgrade issues (particularly if an upgrade to + a new major &repmgr; version is involved), we recommend upgrading + &repmgr; on the old server before running + pg_upgrade to ensure that old and new + versions are the same. + + + + This issue applies to any PostgreSQL extension which has + dependencies on a shared library. + + + + For further details please see the pg_upgrade documentation. + + + If replication slots are in use, bear in mind these will not + be recreated by pg_upgrade. These will need to + be recreated manually. + + + diff -Nru repmgr-4.0.2/doc/version.sgml repmgr-4.0.3/doc/version.sgml --- repmgr-4.0.2/doc/version.sgml 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/doc/version.sgml 2018-02-14 02:28:38.000000000 +0000 @@ -1 +1 @@ - + diff -Nru repmgr-4.0.2/errcode.h repmgr-4.0.3/errcode.h --- repmgr-4.0.2/errcode.h 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/errcode.h 2018-02-14 02:28:38.000000000 +0000 @@ -43,5 +43,6 @@ #define ERR_BARMAN 19 #define ERR_REGISTRATION_SYNC 20 #define ERR_OUT_OF_MEMORY 21 +#define ERR_SWITCHOVER_INCOMPLETE 22 #endif /* _ERRCODE_H_ */ diff -Nru repmgr-4.0.2/HISTORY repmgr-4.0.3/HISTORY --- repmgr-4.0.2/HISTORY 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/HISTORY 2018-02-14 02:28:38.000000000 +0000 @@ -1,3 +1,27 @@ +4.0.3 2018-02- + repmgr: improve switchover handling when "pg_ctl" used to control the + server and logging output is not explicitly redirected (Ian) + repmgr: improve switchover log messages and exit code when old primary could + not be shut down cleanly (Ian) + repmgr: check demotion candidate can make a replication connection to the + promotion candidate before executing a switchover; GitHub #370 (Ian) + repmgr: add check for sufficient walsenders/replication slots before executing + a switchover; GitHub #371 (Ian) + repmgr: add --dry-run mode to "repmgr standby follow"; GitHub #368 (Ian) + repmgr: provide information about the primary node for "standby_register" and + "standby_follow" event notifications; GitHub #375 (Ian) + repmgr: add "standby_register_sync" event notification; GitHub #374 (Ian) + repmgr: output any connection error messages in "cluster show"'s list of + warnings; GitHub #369 (Ian) + repmgr: ensure an inactive data directory can be deleted; GitHub #366 (Ian) + repmgr: fix upstream node display in "repmgr node status"; GitHub #363 (fanf2) + repmgr: improve/clarify documentation and update --help output for + "primary unregister"; GitHub #373 (Ian) + repmgr: fix parsing of "pg_basebackup_options"; GitHub #376 (Ian) + repmgr: ensure "pg_subtrans" directory is created when cloning a standby in + Barman mode (Ian) + repmgr: fix primary node check in "witness register"; GitHub #377 (Ian) + 4.0.2 2018-01-18 repmgr: add missing -W option to getopt_long() invocation; GitHub #350 (Ian) repmgr: automatically create slot name if missing; GitHub #343 (Ian) @@ -21,7 +45,6 @@ GitHub #344 (Ian) repmgr: delete any replication slots copied by pg_rewind; GitHub #334 (Ian) repmgr: fix configuration file sanity check; GitHub #342 (Ian) - Improve event notification documentation (Ian) 4.0.0 2017-11-21 Complete rewrite with many changes; for details see the repmgr 4.0.0 release diff -Nru repmgr-4.0.2/repmgr-action-cluster.c repmgr-4.0.3/repmgr-action-cluster.c --- repmgr-4.0.2/repmgr-action-cluster.c 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/repmgr-action-cluster.c 2018-02-14 02:28:38.000000000 +0000 @@ -82,6 +82,7 @@ NodeInfoListCell *cell = NULL; int i = 0; ItemList warnings = {NULL, NULL}; + bool success = false; /* Connect to local database to obtain cluster connection data */ log_verbose(LOG_INFO, _("connecting to database")); @@ -91,11 +92,19 @@ else conn = establish_db_connection_by_params(&source_conninfo, true); - get_all_node_records_with_upstream(conn, &nodes); + success = get_all_node_records_with_upstream(conn, &nodes); + + if (success == false) + { + /* get_all_node_records_with_upstream() will print error message */ + PQfinish(conn); + exit(ERR_BAD_CONFIG); + } if (nodes.node_count == 0) { - log_error(_("unable to retrieve any node records")); + log_error(_("no node records were found")); + log_hint(_("ensure at least one node is registered")); PQfinish(conn); exit(ERR_BAD_CONFIG); } @@ -131,8 +140,14 @@ } else { + char error[MAXLEN]; + + strncpy(error, PQerrorMessage(cell->node_info->conn), MAXLEN); cell->node_info->node_status = NODE_STATUS_DOWN; cell->node_info->recovery_type = RECTYPE_UNKNOWN; + item_list_append_format(&warnings, + "when attempting to connect to node \"%s\" (ID: %i), following error encountered :\n\"%s\"", + cell->node_info->node_name, cell->node_info->node_id, trim(error)); } initPQExpBuffer(&details); @@ -158,15 +173,13 @@ break; case RECTYPE_STANDBY: appendPQExpBuffer(&details, "! running as standby"); - item_list_append_format( - &warnings, + item_list_append_format(&warnings, "node \"%s\" (ID: %i) is registered as primary but running as standby", cell->node_info->node_name, cell->node_info->node_id); break; case RECTYPE_UNKNOWN: appendPQExpBuffer(&details, "! unknown"); - item_list_append_format( - &warnings, + item_list_append_format(&warnings, "node \"%s\" (ID: %i) has unknown replication status", cell->node_info->node_name, cell->node_info->node_id); break; @@ -177,16 +190,14 @@ if (cell->node_info->recovery_type == RECTYPE_PRIMARY) { appendPQExpBuffer(&details, "! running"); - item_list_append_format( - &warnings, + item_list_append_format(&warnings, "node \"%s\" (ID: %i) is running but the repmgr node record is inactive", cell->node_info->node_name, cell->node_info->node_id); } else { appendPQExpBuffer(&details, "! running as standby"); - item_list_append_format( - &warnings, + item_list_append_format(&warnings, "node \"%s\" (ID: %i) is registered as an inactive primary but running as standby", cell->node_info->node_name, cell->node_info->node_id); } @@ -199,8 +210,7 @@ if (cell->node_info->active == true) { appendPQExpBuffer(&details, "? unreachable"); - item_list_append_format( - &warnings, + item_list_append_format(&warnings, "node \"%s\" (ID: %i) is registered as an active primary but is unreachable", cell->node_info->node_name, cell->node_info->node_id); } @@ -226,8 +236,7 @@ break; case RECTYPE_PRIMARY: appendPQExpBuffer(&details, "! running as primary"); - item_list_append_format( - &warnings, + item_list_append_format(&warnings, "node \"%s\" (ID: %i) is registered as standby but running as primary", cell->node_info->node_name, cell->node_info->node_id); break; @@ -245,16 +254,14 @@ if (cell->node_info->recovery_type == RECTYPE_STANDBY) { appendPQExpBuffer(&details, "! running"); - item_list_append_format( - &warnings, + item_list_append_format(&warnings, "node \"%s\" (ID: %i) is running but the repmgr node record is inactive", cell->node_info->node_name, cell->node_info->node_id); } else { appendPQExpBuffer(&details, "! running as primary"); - item_list_append_format( - &warnings, + item_list_append_format(&warnings, "node \"%s\" (ID: %i) is running as primary but the repmgr node record is inactive", cell->node_info->node_name, cell->node_info->node_id); } @@ -267,8 +274,7 @@ if (cell->node_info->active == true) { appendPQExpBuffer(&details, "? unreachable"); - item_list_append_format( - &warnings, + item_list_append_format(&warnings, "node \"%s\" (ID: %i) is registered as an active standby but is unreachable", cell->node_info->node_name, cell->node_info->node_id); } @@ -416,7 +422,7 @@ printf(_("\nWARNING: following issues were detected\n")); for (cell = warnings.head; cell; cell = cell->next) { - printf(_(" %s\n"), cell->string); + printf(_(" - %s\n"), cell->string); } } } @@ -1144,7 +1150,7 @@ } else { - t_conninfo_param_list remote_conninfo; + t_conninfo_param_list remote_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; char *host = NULL; PQExpBufferData quoted_command; diff -Nru repmgr-4.0.2/repmgr-action-node.c repmgr-4.0.3/repmgr-action-node.c --- repmgr-4.0.2/repmgr-action-node.c 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/repmgr-action-node.c 2018-02-14 02:28:38.000000000 +0000 @@ -41,6 +41,7 @@ static void _do_node_archive_config(void); static void _do_node_restore_config(void); +static void do_node_check_replication_connection(void); static CheckStatus do_node_check_archive_ready(PGconn *conn, OutputMode mode, CheckStatusList *list_output); static CheckStatus do_node_check_downstream(PGconn *conn, OutputMode mode, CheckStatusList *list_output); static CheckStatus do_node_check_replication_lag(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output); @@ -249,8 +250,7 @@ if (node_info.max_wal_senders >= 0) { /* In CSV mode, raw values supplied as well */ - key_value_list_set_format( - &node_status, + key_value_list_set_format(&node_status, "Replication connections", "%i (of maximal %i)", node_info.attached_wal_receivers, @@ -258,8 +258,7 @@ } else if (node_info.max_wal_senders == 0) { - key_value_list_set_format( - &node_status, + key_value_list_set_format(&node_status, "Replication connections", "disabled"); } @@ -276,8 +275,7 @@ initPQExpBuffer(&slotinfo); - appendPQExpBuffer( - &slotinfo, + appendPQExpBuffer(&slotinfo, "%i (of maximal %i)", node_info.active_replication_slots + node_info.inactive_replication_slots, node_info.max_replication_slots); @@ -289,8 +287,7 @@ "; %i inactive", node_info.inactive_replication_slots); - item_list_append_format( - &warnings, + item_list_append_format(&warnings, _("- node has %i inactive replication slots"), node_info.inactive_replication_slots); } @@ -309,13 +306,44 @@ } + /* + * check for missing replication slots - we do this regardless of + * what "max_replication_slots" is set to + */ + + { + NodeInfoList missing_slots = T_NODE_INFO_LIST_INITIALIZER; + get_downsteam_nodes_with_missing_slot(conn, + config_file_options.node_id, + &missing_slots); + + if (missing_slots.node_count > 0) + { + NodeInfoListCell *missing_slot_cell = NULL; + + item_list_append_format(&warnings, + _("- replication slots missing for following %i node(s):"), + missing_slots.node_count); + + for (missing_slot_cell = missing_slots.head; missing_slot_cell; missing_slot_cell = missing_slot_cell->next) + { + item_list_append_format(&warnings, + _(" - %s (ID: %i, slot name: \"%s\")"), + missing_slot_cell->node_info->node_name, + missing_slot_cell->node_info->node_id, + missing_slot_cell->node_info->slot_name); + } + } + } + + if (node_info.type == STANDBY) { key_value_list_set_format(&node_status, "Upstream node", "%s (ID: %i)", - node_info.node_name, - node_info.node_id); + node_info.upstream_node_name, + node_info.upstream_node_id); get_replication_info(conn, &replication_info); @@ -463,8 +491,7 @@ initPQExpBuffer(&output); - appendPQExpBuffer( - &output, + appendPQExpBuffer(&output, "--state="); /* sanity-check we're dealing with a PostgreSQL directory */ @@ -580,6 +607,11 @@ exit(return_code); } + if (runtime_options.replication_connection == true) + { + do_node_check_replication_connection(); + exit(SUCCESS); + } if (strlen(config_file_options.conninfo)) conn = establish_db_connection(config_file_options.conninfo, true); @@ -883,6 +915,67 @@ } +static void +do_node_check_replication_connection(void) +{ + PGconn *local_conn = NULL; + PGconn *repl_conn = NULL; + t_node_info node_record = T_NODE_INFO_INITIALIZER; + RecordStatus record_status = RECORD_NOT_FOUND; + t_conninfo_param_list remote_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; + PQExpBufferData output; + + + initPQExpBuffer(&output); + appendPQExpBuffer(&output, + "--connection="); + + if (runtime_options.remote_node_id == UNKNOWN_NODE_ID) + { + appendPQExpBuffer(&output, "UNKNOWN"); + printf("%s\n", output.data); + termPQExpBuffer(&output); + return; + } + + local_conn = establish_db_connection(config_file_options.conninfo, true); + + record_status = get_node_record(local_conn, runtime_options.remote_node_id, &node_record); + PQfinish(local_conn); + + if (record_status != RECORD_FOUND) + { + appendPQExpBuffer(&output, "UNKNOWN"); + printf("%s\n", output.data); + termPQExpBuffer(&output); + return; + } + + initialize_conninfo_params(&remote_conninfo, false); + parse_conninfo_string(node_record.conninfo, &remote_conninfo, NULL, false); + + param_set(&remote_conninfo, "replication", "1"); + param_set(&remote_conninfo, "user", node_record.repluser); + + repl_conn = establish_db_connection_by_params(&remote_conninfo, false); + + if (PQstatus(repl_conn) != CONNECTION_OK) + { + appendPQExpBuffer(&output, "BAD"); + printf("%s\n", output.data); + termPQExpBuffer(&output); + return; + } + + PQfinish(repl_conn); + + appendPQExpBuffer(&output, "OK"); + printf("%s\n", output.data); + termPQExpBuffer(&output); + + return; +} + static CheckStatus do_node_check_archive_ready(PGconn *conn, OutputMode mode, CheckStatusList *list_output) { @@ -1590,6 +1683,7 @@ bool success = true; int server_version_num = UNKNOWN_SERVER_VERSION_NUM; + int follow_error_code = SUCCESS; /* check node is not actually running */ @@ -1859,7 +1953,31 @@ success = do_standby_follow_internal(upstream_conn, &primary_node_record, - &follow_output); + &follow_output, + &follow_error_code); + + if (success == false) + { + log_notice(_("NODE REJOIN failed")); + log_detail("%s", follow_output.data); + + create_event_notification(upstream_conn, + &config_file_options, + config_file_options.node_id, + "node_rejoin", + success, + follow_output.data); + + PQfinish(upstream_conn); + + termPQExpBuffer(&follow_output); + exit(follow_error_code); + } + + /* + * XXX add checks that node actually started and connected to primary, + * if not exit with ERR_REJOIN_FAIL + */ create_event_notification(upstream_conn, &config_file_options, @@ -1870,19 +1988,12 @@ PQfinish(upstream_conn); - if (success == false) - { - log_notice(_("NODE REJOIN failed")); - log_detail("%s", follow_output.data); - - termPQExpBuffer(&follow_output); - exit(ERR_DB_QUERY); - } - log_notice(_("NODE REJOIN successful")); log_detail("%s", follow_output.data); termPQExpBuffer(&follow_output); + + return; } diff -Nru repmgr-4.0.2/repmgr-action-primary.c repmgr-4.0.3/repmgr-action-primary.c --- repmgr-4.0.2/repmgr-action-primary.c 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/repmgr-action-primary.c 2018-02-14 02:28:38.000000000 +0000 @@ -548,7 +548,8 @@ printf(_(" \"primary unregister\" unregisters an inactive primary node.\n")); puts(""); printf(_(" --dry-run check what would happen, but don't actually unregister the primary\n")); - printf(_(" -F, --force force removal of the record\n")); + printf(_(" --node-id ID of the inactive primary node to unregister.\n")); + printf(_(" -F, --force force removal of an active record\n")); puts(""); diff -Nru repmgr-4.0.2/repmgr-action-standby.c repmgr-4.0.3/repmgr-action-standby.c --- repmgr-4.0.2/repmgr-action-standby.c 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/repmgr-action-standby.c 2018-02-14 02:28:38.000000000 +0000 @@ -57,7 +57,7 @@ static int upstream_node_id = UNKNOWN_NODE_ID; static char upstream_data_directory[MAXPGPATH]; -static t_conninfo_param_list recovery_conninfo; +static t_conninfo_param_list recovery_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; static char recovery_conninfo_str[MAXLEN] = ""; static char upstream_repluser[NAMEDATALEN] = ""; @@ -99,10 +99,9 @@ static void write_primary_conninfo(char *line, t_conninfo_param_list *param_list); static bool write_recovery_file_line(FILE *recovery_file, char *recovery_file_path, char *line); -static int parse_output_to_argv(const char *string, char ***argv_array); -static void free_parsed_argv(char ***argv_array); static NodeStatus parse_node_status_is_shutdown_cleanly(const char *node_status_output, XLogRecPtr *checkPoint); static CheckStatus parse_node_check_archiver(const char *node_check_output, int *files, int *threshold); +static ConnectionStatus parse_remote_node_replication_connection(const char *node_check_output); /* * STANDBY CLONE @@ -222,6 +221,49 @@ param_set(&recovery_conninfo, "application_name", "repmgr"); } + + + /* + * Do some sanity checks on the proposed data directory; if it exists: + * - check it's openable + * - check if there's an instance running + * + * We do this here so the check can be part of a --dry-run. + */ + switch (check_dir(local_data_directory)) + { + case DIR_ERROR: + log_error(_("unable to access specified data directory \"%s\""), local_data_directory); + log_detail("%s", strerror(errno)); + exit(ERR_BAD_CONFIG); + break; + case DIR_NOENT: + /* + * directory doesn't exist + * TODO: in --dry-run mode, attempt to create and delete? + */ + break; + case DIR_EMPTY: + /* Present but empty */ + break; + case DIR_NOT_EMPTY: + /* Present but not empty */ + if (is_pg_dir(local_data_directory)) + { + /* even -F/--force is not enough to overwrite an active directory... */ + if (is_pg_running(local_data_directory)) + { + log_error(_("specified data directory \"%s\" appears to contain a running PostgreSQL instance"), + local_data_directory); + log_hint(_("ensure the target data directory does not contain a running PostgreSQL instance")); + exit(ERR_BAD_CONFIG); + } + } + break; + default: + break; + } + /* * By default attempt to connect to the source node. This will fail if no * connection is possible, unless in Barman mode, in which case we can @@ -239,6 +281,9 @@ */ check_source_server(); } + else { + upstream_node_id = runtime_options.upstream_node_id; + } /* * if --upstream-conninfo was supplied, use that (will overwrite value set @@ -278,21 +323,6 @@ exit(ERR_BAD_CONFIG); } - /* - * by this point we should know the target data directory - check there's - * no running Pg instance - */ - if (is_pg_dir(local_data_directory)) - { - DBState state = get_db_state(local_data_directory); - - if (state != DB_SHUTDOWNED && state != DB_SHUTDOWNED_IN_RECOVERY) - { - log_error(_("target data directory appears to contain an active PostgreSQL instance")); - log_detail(_("instance state is %s"), describe_db_state(state)); - exit(ERR_BAD_CONFIG); - } - } if (upstream_conninfo_found == true) { @@ -466,6 +496,7 @@ PQfinish(superuser_conn); } + if (runtime_options.dry_run == true) { if (upstream_node_id != UNKNOWN_NODE_ID) @@ -763,6 +794,7 @@ * * Event(s): * - standby_register + * - standby_register_sync */ /* XXX check --upstream-node-id works when re-registering */ @@ -778,6 +810,11 @@ PQExpBufferData details; + /* so we can pass info about the primary to event notification scripts */ + t_event_info event_info = T_EVENT_INFO_INITIALIZER; + t_node_info primary_node_record = T_NODE_INFO_INITIALIZER; + int primary_node_id = UNKNOWN_NODE_ID; + log_info(_("connecting to local node \"%s\" (ID: %i)"), config_file_options.node_name, config_file_options.node_id); @@ -843,9 +880,10 @@ if (runtime_options.connection_param_provided == false) { - log_error(_("unable to connect to local node \"%s\" (ID: %i) and no primary connection parameters provided"), + log_error(_("unable to connect to local node \"%s\" (ID: %i)"), config_file_options.node_name, config_file_options.node_id); + log_hint(_("to register an inactive standby, additionally provide the primary connection parameters")); exit(ERR_BAD_CONFIG); } } @@ -861,7 +899,7 @@ /* Normal case - we can connect to the local node */ if (PQstatus(conn) == CONNECTION_OK) { - primary_conn = get_primary_connection(conn, NULL, NULL); + primary_conn = get_primary_connection(conn, &primary_node_id, NULL); } /* @@ -886,6 +924,16 @@ } /* + * Populate "event_info" with info about the primary for event notifications + */ + record_status = get_node_record(primary_conn, + primary_node_id, + &primary_node_record); + event_info.node_id = primary_node_id; + event_info.node_name = primary_node_record.node_name; + event_info.conninfo_str = primary_node_record.conninfo; + + /* * Verify that standby and primary are supported and compatible server * versions * @@ -1139,12 +1187,14 @@ &details, " (-F/--force option was used)"); - create_event_notification(primary_conn, - &config_file_options, - config_file_options.node_id, - "standby_register", - false, - details.data); + create_event_notification_extended( + primary_conn, + &config_file_options, + config_file_options.node_id, + "standby_register", + false, + details.data, + &event_info); termPQExpBuffer(&details); PQfinish(primary_conn); @@ -1155,23 +1205,23 @@ exit(ERR_BAD_CONFIG); } - appendPQExpBuffer( - &details, + appendPQExpBuffer(&details, "standby registration succeeded"); if (runtime_options.force == true) - appendPQExpBuffer( - &details, + appendPQExpBuffer(&details, " (-F/--force option was used)"); /* Log the event */ - create_event_notification(primary_conn, - &config_file_options, - config_file_options.node_id, - "standby_register", - true, - details.data); + create_event_notification_extended( + primary_conn, + &config_file_options, + config_file_options.node_id, + "standby_register", + true, + details.data, + &event_info); termPQExpBuffer(&details); @@ -1250,16 +1300,43 @@ timer++; } + /* Log the event */ + initPQExpBuffer(&details); + + if (sync_ok == false) + { + appendPQExpBuffer(&details, + _("node record was not synchronised after %i seconds"), + runtime_options.wait_register_sync_seconds); + } + else + { + appendPQExpBuffer(&details, + _("node record synchronised after %i seconds"), + timer); + } + + create_event_notification_extended( + primary_conn, + &config_file_options, + config_file_options.node_id, + "standby_register_sync", + sync_ok, + details.data, + &event_info); + if (sync_ok == false) { - log_error(_("node record was not synchronised after %i seconds"), - runtime_options.wait_register_sync_seconds); + log_error("%s", details.data); + termPQExpBuffer(&details); PQfinish(primary_conn); PQfinish(conn); exit(ERR_REGISTRATION_SYNC); } log_info(_("node record on standby synchronised from primary")); + log_detail("%s", details.data); + termPQExpBuffer(&details); } @@ -1519,6 +1596,13 @@ } + /* + * Execute a CHECKPOINT as soon as possible after promotion. The primary + * reason for this is to ensure that "pg_control" has the latest timeline + * before it's read by "pg_rewind", typically during a switchover operation. + */ + checkpoint(conn); + /* update node information to reflect new status */ if (update_node_record_set_primary(conn, config_file_options.node_id) == false) { @@ -1580,18 +1664,21 @@ PGconn *local_conn = NULL; PGconn *primary_conn = NULL; - int primary_id = UNKNOWN_NODE_ID; + int primary_node_id = UNKNOWN_NODE_ID; t_node_info primary_node_record = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; + /* so we can pass info about the primary to event notification scripts */ + t_event_info event_info = T_EVENT_INFO_INITIALIZER; int timer = 0; int server_version_num = UNKNOWN_SERVER_VERSION_NUM; PQExpBufferData follow_output; bool success = false; + int follow_error_code = SUCCESS; uint64 local_system_identifier = UNKNOWN_SYSTEM_IDENTIFIER; - t_conninfo_param_list repl_conninfo; + t_conninfo_param_list repl_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; PGconn *repl_conn = NULL; t_system_identification primary_identification = T_SYSTEM_IDENTIFICATION_INITIALIZER; @@ -1620,7 +1707,7 @@ for (timer = 0; timer < config_file_options.primary_follow_timeout; timer++) { primary_conn = get_primary_connection_quiet(local_conn, - &primary_id, + &primary_node_id, NULL); if (PQstatus(primary_conn) == CONNECTION_OK || runtime_options.wait == false) { @@ -1645,7 +1732,16 @@ exit(ERR_BAD_CONFIG); } - record_status = get_node_record(primary_conn, primary_id, &primary_node_record); + if (runtime_options.dry_run == true) + { + log_info(_("connected to node %i, checking for current primary"), primary_node_id); + } + else + { + log_verbose(LOG_INFO, _("connected to node %i, checking for current primary"), primary_node_id); + } + + record_status = get_node_record(primary_conn, primary_node_id, &primary_node_record); if (record_status != RECORD_FOUND) { @@ -1655,6 +1751,54 @@ exit(ERR_BAD_CONFIG); } + /* + * Populate "event_info" with info about the primary for event notifications + */ + event_info.node_id = primary_node_id; + event_info.node_name = primary_node_record.node_name; + event_info.conninfo_str = primary_node_record.conninfo; + + + if (runtime_options.dry_run == true) + { + log_info(_("primary node is \"%s\" (ID: %i)"), + primary_node_record.node_name, + primary_node_id); + } + else + { + log_verbose(LOG_INFO, ("primary node is \"%s\" (ID: %i)"), + primary_node_record.node_name, + primary_node_id); + } + + /* if replication slots in use, check at least one free slot is available */ + + if (config_file_options.use_replication_slots) + { + int free_slots = get_free_replication_slots(primary_conn); + if (free_slots < 0) + { + log_error(_("unable to determine number of free replication slots on the primary")); + PQfinish(primary_conn); + exit(ERR_BAD_CONFIG); + } + + if (free_slots == 0) + { + log_error(_("no free replication slots available on the primary")); + log_hint(_("consider increasing \"max_replication_slots\"")); + PQfinish(primary_conn); + exit(ERR_BAD_CONFIG); + } + else if (runtime_options.dry_run == true) + { + log_info(_("replication slots in use, %i free slots on the primary"), + free_slots); + } + + } + /* XXX check this is not current upstream anyway */ /* check replication connection */ initialize_conninfo_params(&repl_conninfo, false); @@ -1671,6 +1815,11 @@ PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } + else if (runtime_options.dry_run == true) + { + log_info(_("replication connection to primary node was successful")); + } + /* check system_identifiers match */ local_system_identifier = get_system_identifier(config_file_options.data_directory); @@ -1694,22 +1843,38 @@ PQfinish(repl_conn); exit(ERR_BAD_CONFIG); } + else if (runtime_options.dry_run == true) + { + log_info(_("local and primary system identifiers match")); + log_detail(_("system identifier is %lu"), local_system_identifier); + } + + /* TODO: check timelines */ PQfinish(repl_conn); free_conninfo_params(&repl_conninfo); + if (runtime_options.dry_run == true) + { + log_info(_("prerequisites for executing STANDBY FOLLOW are met")); + exit(SUCCESS); + } + initPQExpBuffer(&follow_output); success = do_standby_follow_internal(primary_conn, &primary_node_record, - &follow_output); + &follow_output, + &follow_error_code); - create_event_notification(primary_conn, - &config_file_options, - config_file_options.node_id, - "standby_follow", - success, - follow_output.data); + create_event_notification_extended( + primary_conn, + &config_file_options, + config_file_options.node_id, + "standby_follow", + success, + follow_output.data, + &event_info); PQfinish(primary_conn); @@ -1719,7 +1884,7 @@ log_detail("%s", follow_output.data); termPQExpBuffer(&follow_output); - exit(ERR_DB_QUERY); + exit(follow_error_code); } log_notice(_("STANDBY FOLLOW successful")); @@ -1739,7 +1904,7 @@ * this function. */ bool -do_standby_follow_internal(PGconn *primary_conn, t_node_info *primary_node_record, PQExpBufferData *output) +do_standby_follow_internal(PGconn *primary_conn, t_node_info *primary_node_record, PQExpBufferData *output, int *error_code) { t_node_info local_node_record = T_NODE_INFO_INITIALIZER; int original_upstream_node_id = UNKNOWN_NODE_ID; @@ -1762,8 +1927,9 @@ { log_error(_("unable to retrieve record for node %i"), config_file_options.node_id); - PQfinish(primary_conn); - exit(ERR_BAD_CONFIG); + + *error_code = ERR_BAD_CONFIG; + return false; } /* @@ -1813,7 +1979,7 @@ parse_conninfo_string(primary_node_record->conninfo, &recovery_conninfo, errmsg, true); { - t_conninfo_param_list local_node_conninfo; + t_conninfo_param_list local_node_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; bool parse_success; initialize_conninfo_params(&local_node_conninfo, false); @@ -1892,8 +2058,9 @@ if (!create_recovery_file(&local_node_record, &recovery_conninfo, config_file_options.data_directory)) { - PQfinish(primary_conn); - exit(ERR_BAD_CONFIG); + /* XXX ERR_RECOVERY_FILE ??? */ + *error_code = ERR_BAD_CONFIG; + return false; } /* @@ -1904,30 +2071,95 @@ char server_command[MAXLEN] = ""; bool server_up = is_server_available(config_file_options.conninfo); char *action = NULL; - int r; + bool success; + + PQExpBufferData output_buf; + initPQExpBuffer(&output_buf); if (server_up == true) { - action = "restart"; - get_server_action(ACTION_RESTART, server_command, config_file_options.data_directory); + /* no "service_restart_command" defined - stop and start using pg_ctl*/ + if (config_file_options.service_restart_command[0] == '\0') + { + action = "stopp"; /* sic */ + get_server_action(ACTION_STOP_WAIT, server_command, config_file_options.data_directory); + + /* if translation needed, generate messages in the preceding if/else */ + log_notice(_("%sing server using \"%s\""), + action, + server_command); + + success = local_command(server_command, &output_buf); + + if (success == false) + { + log_error(_("unable to %s server"), action); + + *error_code = ERR_NO_RESTART; + return false; + } + + action = "start"; + get_server_action(ACTION_START, server_command, config_file_options.data_directory); + + /* if translation needed, generate messages in the preceding if/else */ + log_notice(_("%sing server using \"%s\""), + action, + server_command); + + success = local_command(server_command, &output_buf); + + if (success == false) + { + log_error(_("unable to %s server"), action); + + *error_code = ERR_NO_RESTART; + return false; + } + + } + else + { + action = "restart"; + get_server_action(ACTION_RESTART, server_command, config_file_options.data_directory); + + /* if translation needed, generate messages in the preceding if/else */ + log_notice(_("%sing server using \"%s\""), + action, + server_command); + + success = local_command(server_command, &output_buf); + + if (success == false) + { + log_error(_("unable to %s server"), action); + + *error_code = ERR_NO_RESTART; + return false; + } + + } } else { + action = "start"; get_server_action(ACTION_START, server_command, config_file_options.data_directory); - } - /* if translation needed, generate messages in the preceding if/else */ - log_notice(_("%sing server using \"%s\""), - action, - server_command); + /* if translation needed, generate messages in the preceding if/else */ + log_notice(_("%sing server using \"%s\""), + action, + server_command); - r = system(server_command); - if (r != 0) - { - log_error(_("unable to %s server"), action); - PQfinish(primary_conn); - exit(ERR_NO_RESTART); + success = local_command(server_command, &output_buf); + + if (success == false) + { + log_error(_("unable to %s server"), action); + + *error_code = ERR_NO_RESTART; + return false; + } } } @@ -2026,6 +2258,10 @@ i; bool command_success = false; bool shutdown_success = false; + + /* this flag will use to generate the final message generated */ + bool switchover_success = true; + XLogRecPtr remote_last_checkpoint_lsn = InvalidXLogRecPtr; ReplInfo replication_info = T_REPLINFO_INTIALIZER; @@ -2034,8 +2270,13 @@ /* store list of sibling nodes if --siblings-follow specified */ NodeInfoList sibling_nodes = T_NODE_INFO_LIST_INITIALIZER; + int reachable_sibling_node_count = 0; + int reachable_sibling_nodes_with_slot_count = 0; int unreachable_sibling_node_count = 0; + /* number of free replication slots required on promotion candidate */ + int min_required_free_slots = 0; + t_event_info event_info = T_EVENT_INFO_INITIALIZER; /* @@ -2054,6 +2295,7 @@ config_file_options.node_id); PQfinish(local_conn); + exit(ERR_DB_QUERY); } @@ -2117,9 +2359,9 @@ } /* - * Check this standby is attached to the demotion candidate TODO: - check - * standby is attached to demotion candidate - check application_name in - * pg_stat_replication + * Check this standby is attached to the demotion candidate + * TODO: + * - check application_name in pg_stat_replication */ if (local_node_record.upstream_node_id != remote_node_record.node_id) @@ -2137,8 +2379,13 @@ log_verbose(LOG_DEBUG, "remote node name is \"%s\"", remote_node_record.node_name); /* this will fill the %p event notification parameter */ - event_info.former_primary_id = remote_node_record.node_id; + event_info.node_id = remote_node_record.node_id; + /* keep a running total of how many nodes will require a replication slot */ + if (remote_node_record.slot_name[0] != '\0') + { + min_required_free_slots++; + } /* * If --force-rewind specified, check pg_rewind can be used, and * pre-emptively fetch the list of configuration files which should be @@ -2226,8 +2473,7 @@ appendPQExpBuffer(&remote_command_str, "--version 2>/dev/null && echo \"1\" || echo \"0\""); initPQExpBuffer(&command_output); - command_success = remote_command( - remote_host, + command_success = remote_command(remote_host, runtime_options.remote_user, remote_command_str.data, &command_output); @@ -2247,20 +2493,17 @@ termPQExpBuffer(&command_output); initPQExpBuffer(&hint); - appendPQExpBuffer( - &hint, + appendPQExpBuffer(&hint, _("check \"pg_bindir\" is set to the correct path in \"repmgr.conf\"; current value: ")); if (strlen(config_file_options.pg_bindir)) { - appendPQExpBuffer( - &hint, + appendPQExpBuffer(&hint, "\"%s\"", config_file_options.pg_bindir); } else { - appendPQExpBuffer( - &hint, + appendPQExpBuffer(&hint, "(not set)"); } @@ -2281,6 +2524,49 @@ } termPQExpBuffer(&command_output); + /* check demotion candidate can make replication connection to promotion candidate */ + { + initPQExpBuffer(&remote_command_str); + make_remote_repmgr_path(&remote_command_str, &remote_node_record); + appendPQExpBuffer(&remote_command_str, + "node check --remote-node-id=%i --replication-connection", + local_node_record.node_id); + + initPQExpBuffer(&command_output); + + command_success = remote_command(remote_host, + runtime_options.remote_user, + remote_command_str.data, + &command_output); + + termPQExpBuffer(&remote_command_str); + + if (command_success == true) + { + ConnectionStatus conn_status = parse_remote_node_replication_connection(command_output.data); + + switch(conn_status) + { + case CONN_OK: + if (runtime_options.dry_run == true) + { + log_info(_("demotion candidate is able to make replication connection to promotion candidate")); + } + break; + case CONN_BAD: + log_error(_("demotion candidate is unable to make replication connection to promotion candidate")); + exit(ERR_BAD_CONFIG); + break; + default: + log_error(_("unable to deterimine whether candidate is able to make replication connection to promotion candidate")); + exit(ERR_BAD_CONFIG); + break; + } + + termPQExpBuffer(&command_output); + } + } + /* check archive/replication status */ { int lag_seconds = 0; @@ -2481,9 +2767,15 @@ PQfinish(remote_conn); + /* + * populate local node record with current state of various replication-related + * values, so we can check for sufficient walsenders and replication slots + */ + get_node_replication_stats(local_conn, server_version_num, &local_node_record); /* * If --siblings-follow specified, get list and check they're reachable + * (if not just issue a warning) */ get_active_sibling_node_records(local_conn, local_node_record.node_id, @@ -2513,6 +2805,11 @@ } else { + /* include walsender for promotion candidate in total */ + int min_required_wal_senders = 1; + int available_wal_senders = local_node_record.max_wal_senders - + local_node_record.attached_wal_receivers; + for (cell = sibling_nodes.head; cell; cell = cell->next) { /* get host from node record */ @@ -2527,6 +2824,14 @@ else { cell->node_info->reachable = true; + reachable_sibling_node_count++; + min_required_wal_senders++; + + if (cell->node_info->slot_name[0] != '\0') + { + reachable_sibling_nodes_with_slot_count++; + min_required_free_slots++; + } } } @@ -2545,6 +2850,7 @@ sibling_nodes.node_count); } + /* display list of unreachable sibling nodes */ for (cell = sibling_nodes.head; cell; cell = cell->next) { if (cell->node_info->reachable == true) @@ -2561,7 +2867,14 @@ exit(ERR_BAD_CONFIG); } - log_detail(_("F/--force specified, proceeding anyway")); + if (runtime_options.dry_run == true) + { + log_detail(_("F/--force specified, would proceed anyway")); + } + else + { + log_detail(_("F/--force specified, proceeding anyway")); + } } else { @@ -2576,23 +2889,119 @@ log_verbose(LOG_INFO, "%s", msg); } } + + + + /* + * check there are sufficient free walsenders - obviously there's potential + * for a later race condition if some walsenders come into use before the + * switchover operation gets around to attaching the sibling nodes, but + * this should catch any actual existing configuration issue. + */ + if (available_wal_senders < min_required_wal_senders) + { + if (runtime_options.force == false || runtime_options.dry_run == true) + { + log_error(_("insufficient free walsenders to attach all sibling nodes")); + log_detail(_("at least %i walsenders required but only %i free walsenders on promotion candidate"), + min_required_wal_senders, + available_wal_senders); + log_hint(_("increase parameter \"max_wal_senders\" or use -F/--force to proceed in any case")); + + if (runtime_options.dry_run == false) + { + PQfinish(local_conn); + exit(ERR_BAD_CONFIG); + } + } + else + { + log_warning(_("insufficient free walsenders to attach all sibling nodes")); + log_detail(_("at least %i walsenders required but only %i free walsender(s) on promotion candidate"), + min_required_wal_senders, + available_wal_senders); + } + } + else + { + if (runtime_options.dry_run == true) + { + log_info(_("%i walsenders required, %i available"), + min_required_wal_senders, + available_wal_senders); + } + } } } - /* PQfinish(local_conn); */ /* - * Sanity checks completed - prepare for the switchover + * if replication slots are required by demotion candidate and/or siblings, + * check the promotion candidate has sufficient free slots */ - log_notice(_("local node \"%s\" (ID: %i) will be promoted to primary; " - "current primary \"%s\" (ID: %i) will be demoted to standby"), - local_node_record.node_name, - local_node_record.node_id, - remote_node_record.node_name, - remote_node_record.node_id); + if (min_required_free_slots > 0 ) + { + int available_slots = local_node_record.max_replication_slots - + local_node_record.active_replication_slots; + + log_debug("minimum of %i free slots (%i for siblings) required; %i available", + min_required_free_slots, + reachable_sibling_nodes_with_slot_count + , available_slots); + + if (available_slots < min_required_free_slots) + { + if (runtime_options.force == false || runtime_options.dry_run == true) + { + log_error(_("insufficient free replication slots to attach all nodes")); + log_detail(_("at least %i additional replication slots required but only %i free slots available on promotion candidate"), + min_required_free_slots, + available_slots); + log_hint(_("increase parameter \"max_replication_slots\" or use -F/--force to proceed in any case")); + + if (runtime_options.dry_run == false) + { + PQfinish(local_conn); + exit(ERR_BAD_CONFIG); + } + } + } + else + { + if (runtime_options.dry_run == true) + { + log_info(_("%i replication slots required, %i available"), + min_required_free_slots, + available_slots); + } + } + } + /* + * Sanity checks completed - prepare for the switchover + */ + + if (runtime_options.dry_run == true) + { + log_notice(_("local node \"%s\" (ID: %i) would be promoted to primary; " + "current primary \"%s\" (ID: %i) would be demoted to standby"), + local_node_record.node_name, + local_node_record.node_id, + remote_node_record.node_name, + remote_node_record.node_id); + } + else + { + log_notice(_("local node \"%s\" (ID: %i) will be promoted to primary; " + "current primary \"%s\" (ID: %i) will be demoted to standby"), + local_node_record.node_name, + local_node_record.node_id, + remote_node_record.node_name, + remote_node_record.node_id); + } + /* * Stop the remote primary * * We'll issue the pg_ctl command but not force it not to wait; we'll @@ -2622,8 +3031,7 @@ /* XXX handle failure */ - (void) remote_command( - remote_host, + (void) remote_command(remote_host, runtime_options.remote_user, remote_command_str.data, &command_output); @@ -2734,7 +3142,8 @@ termPQExpBuffer(&command_output); } - log_debug("sleeping %i seconds until next check", config_file_options.reconnect_interval); + log_debug("sleeping %i seconds (\"reconnect_interval\") until next check", + config_file_options.reconnect_interval); sleep(config_file_options.reconnect_interval); } @@ -2844,13 +3253,10 @@ /* TODO: verify this node's record was updated correctly */ - if (command_success == false || command_output.data[0] == '0') + if (command_success == false) { log_error(_("rejoin failed %i"), r); - if (strlen(command_output.data) > 2) - log_detail("%s", command_output.data); - create_event_notification_extended(local_conn, &config_file_options, config_file_options.node_id, @@ -2885,12 +3291,17 @@ /* clean up remote node */ remote_conn = establish_db_connection(remote_node_record.conninfo, false); - /* check replication status */ + /* check new standby (old primary) is reachable */ if (PQstatus(remote_conn) != CONNECTION_OK) { - log_error(_("unable to reestablish connection to remote node \"%s\""), - remote_node_record.node_name); - /* log_hint(_("")); // depends on replication status */ + switchover_success = false; + + /* TODO: double-check whether new standby has attached */ + + log_warning(_("switchover did not fully complete")); + log_detail(_("node \"%s\" is now primary but node \"%s\" is not reachable"), + local_node_record.node_name, + remote_node_record.node_name); } else { @@ -2901,17 +3312,20 @@ local_node_record.slot_name); } /* TODO warn about any inactive replication slots */ + + log_notice(_("switchover was successful")); + log_detail(_("node \"%s\" is now primary and node \"%s\" is attached as standby"), + local_node_record.node_name, + remote_node_record.node_name); + } PQfinish(remote_conn); - log_notice(_("switchover was successful")); - log_detail(_("node \"%s\" is now primary"), - local_node_record.node_name); /* * If --siblings-follow specified, attempt to make them follow the new - * standby + * primary */ if (runtime_options.siblings_follow == true && sibling_nodes.node_count > 0) @@ -2984,7 +3398,17 @@ PQfinish(local_conn); - log_notice(_("STANDBY SWITCHOVER is complete")); + if (switchover_success == true) + { + log_notice(_("STANDBY SWITCHOVER has completed successfully")); + } + else + { + log_notice(_("STANDBY SWITCHOVER has completed with issues")); + log_hint(_("see preceding log message(s) for details")); + exit(ERR_SWITCHOVER_INCOMPLETE); + } + return; } @@ -3066,8 +3490,11 @@ { if (!runtime_options.force) { + /* this is unlikely to happen */ if (extension_status == REPMGR_UNKNOWN) { + log_error(_("unable to determine status of \"repmgr\" extension")); + log_detail("%s", PQerrorMessage(primary_conn)); PQfinish(source_conn); exit(ERR_DB_QUERY); } @@ -3082,10 +3509,10 @@ } else if (extension_status == REPMGR_UNAVAILABLE) { - log_detail(_("repmgr extension is not available on the upstream server")); + log_detail(_("repmgr extension is not available on the upstream node")); } - log_hint(_("check that the upstream server is part of a repmgr cluster")); + log_hint(_("check that the upstream node is part of a repmgr cluster")); PQfinish(source_conn); exit(ERR_BAD_CONFIG); } @@ -3136,7 +3563,7 @@ { log_error(_("target data directory appears to be a PostgreSQL data directory")); log_detail(_("target data directory is \"%s\""), local_data_directory); - log_hint(_("ensure the target data directory is empty before running \"STANDBY CLONE\" in pg_basebackup mode")); + log_hint(_("use -F/--force to overwrite the existing data directory")); PQfinish(source_conn); exit(ERR_BAD_CONFIG); } @@ -3239,8 +3666,14 @@ "repmgr database conninfo string on barman server: %s", repmgr_conninfo_buf.data); - /* XXX check this works in all cases */ - maxlen_snprintf(where_condition, "node_id=%i", upstream_node_id); + if (upstream_node_id == UNKNOWN_NODE_ID) + { + maxlen_snprintf(where_condition, "type='primary' AND active IS TRUE"); + } + else + { + maxlen_snprintf(where_condition, "node_id=%i", upstream_node_id); + } initPQExpBuffer(&command_output); maxlen_snprintf(buf, @@ -3559,7 +3992,7 @@ int min_replication_connections = 1, possible_replication_connections = 0; - t_conninfo_param_list repl_conninfo; + t_conninfo_param_list repl_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; /* * Make a copy of the connection parameter arrays, and append @@ -4328,8 +4761,8 @@ /* Only from 9.4 */ "pg_dynshmem", "pg_logical", "pg_logical/snapshots", "pg_logical/mappings", "pg_replslot", /* Already in 9.3 */ - "pg_notify", "pg_serial", "pg_snapshots", "pg_stat", "pg_stat_tmp", "pg_tblspc", - "pg_twophase", "pg_xlog", 0 + "pg_notify", "pg_serial", "pg_snapshots", "pg_stat", "pg_stat_tmp", + "pg_subtrans", "pg_tblspc", "pg_twophase", "pg_xlog", 0 }; const int vers[] = { 100000, @@ -5001,7 +5434,7 @@ bool password_provided = false; int c; char *escaped = NULL; - t_conninfo_param_list env_conninfo; + t_conninfo_param_list env_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; initialize_conninfo_params(&env_conninfo, true); @@ -5156,6 +5589,69 @@ } +static ConnectionStatus +parse_remote_node_replication_connection(const char *node_check_output) +{ + ConnectionStatus conn_status = CONN_UNKNOWN; + + int c = 0, + argc_item = 0; + char **argv_array = NULL; + int optindex = 0; + + /* We're only interested in these options */ + struct option node_check_options[] = + { + {"connection", required_argument, NULL, 'c'}, + {NULL, 0, NULL, 0} + }; + + /* Don't attempt to tokenise an empty string */ + if (!strlen(node_check_output)) + { + return CONN_UNKNOWN; + } + + argc_item = parse_output_to_argv(node_check_output, &argv_array); + + /* Reset getopt's optind variable */ + optind = 0; + + /* Prevent getopt from emitting errors */ + opterr = 0; + + while ((c = getopt_long(argc_item, argv_array, "L:S:", node_check_options, + &optindex)) != -1) + { + switch (c) + { + + /* --connection */ + case 'c': + { + if (strncmp(optarg, "OK", MAXLEN) == 0) + { + conn_status = CONN_OK; + } + else if (strncmp(optarg, "BAD", MAXLEN) == 0) + { + conn_status = CONN_BAD; + } + else if (strncmp(optarg, "UNKNOWN", MAXLEN) == 0) + { + conn_status = CONN_UNKNOWN; + } + } + break; + } + } + + free_parsed_argv(&argv_array); + + return conn_status; +} + + static CheckStatus parse_node_check_archiver(const char *node_check_output, int *files, int *threshold) { @@ -5241,93 +5737,6 @@ } -static int -parse_output_to_argv(const char *string, char ***argv_array) -{ - int options_len = 0; - char *options_string = NULL; - char *options_string_ptr = NULL; - int c = 1, - argc_item = 1; - char *argv_item = NULL; - char **local_argv_array = NULL; - ItemListCell *cell; - - /* - * Add parsed options to this list, then copy to an array to pass to - * getopt - */ - ItemList option_argv = {NULL, NULL}; - - options_len = strlen(string) + 1; - options_string = pg_malloc(options_len); - options_string_ptr = options_string; - - /* Copy the string before operating on it with strtok() */ - strncpy(options_string, string, options_len); - - /* Extract arguments into a list and keep a count of the total */ - while ((argv_item = strtok(options_string_ptr, " ")) != NULL) - { - item_list_append(&option_argv, trim(argv_item)); - - argc_item++; - - if (options_string_ptr != NULL) - options_string_ptr = NULL; - } - - pfree(options_string); - - /* - * Array of argument values to pass to getopt_long - this will need to - * include an empty string as the first value (normally this would be the - * program name) - */ - local_argv_array = pg_malloc0(sizeof(char *) * (argc_item + 2)); - - /* Insert a blank dummy program name at the start of the array */ - local_argv_array[0] = pg_malloc0(1); - - /* - * Copy the previously extracted arguments from our list to the array - */ - for (cell = option_argv.head; cell; cell = cell->next) - { - int argv_len = strlen(cell->string) + 1; - - local_argv_array[c] = (char *)pg_malloc0(argv_len); - - strncpy(local_argv_array[c], cell->string, argv_len); - - c++; - } - - local_argv_array[c] = NULL; - - item_list_free(&option_argv); - - *argv_array = local_argv_array; - - return argc_item; -} - - -static void -free_parsed_argv(char ***argv_array) -{ - char **local_argv_array = *argv_array; - int i = 0; - - while (local_argv_array[i] != NULL) - { - pfree((char *)local_argv_array[i]); - i++; - } - - pfree((char **)local_argv_array); - *argv_array = NULL; -} void @@ -5395,6 +5804,7 @@ puts(""); printf(_(" \"standby follow\" instructs a standby node to follow a new primary.\n")); puts(""); + printf(_(" --dry-run perform checks but don't actually follow the new primary\n")); printf(_(" -W, --wait wait for a primary to appear\n")); puts(""); diff -Nru repmgr-4.0.2/repmgr-action-standby.h repmgr-4.0.3/repmgr-action-standby.h --- repmgr-4.0.2/repmgr-action-standby.h 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/repmgr-action-standby.h 2018-02-14 02:28:38.000000000 +0000 @@ -28,7 +28,7 @@ extern void do_standby_help(void); -extern bool do_standby_follow_internal(PGconn *primary_conn, t_node_info *primary_node_record, PQExpBufferData *output); +extern bool do_standby_follow_internal(PGconn *primary_conn, t_node_info *primary_node_record, PQExpBufferData *output, int *error_code); diff -Nru repmgr-4.0.2/repmgr-action-witness.c repmgr-4.0.3/repmgr-action-witness.c --- repmgr-4.0.2/repmgr-action-witness.c 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/repmgr-action-witness.c 2018-02-14 02:28:38.000000000 +0000 @@ -110,12 +110,12 @@ } /* check primary node's recovery type */ - recovery_type = get_recovery_type(witness_conn); + recovery_type = get_recovery_type(primary_conn); if (recovery_type == RECTYPE_STANDBY) { log_error(_("provided primary node is a standby")); - log_error(_("provide the connection details of the cluster's primary server")); + log_hint(_("provide the connection details of the cluster's primary server")); PQfinish(witness_conn); PQfinish(primary_conn); diff -Nru repmgr-4.0.2/repmgr.c repmgr-4.0.3/repmgr.c --- repmgr-4.0.2/repmgr.c 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/repmgr.c 2018-02-14 02:28:38.000000000 +0000 @@ -288,7 +288,6 @@ Datum notify_follow_primary(PG_FUNCTION_ARGS) { -#ifndef BDR_ONLY int primary_node_id = UNKNOWN_NODE_ID; if (!shared_state) @@ -316,7 +315,7 @@ } LWLockRelease(shared_state->lock); -#endif + PG_RETURN_VOID(); } @@ -329,14 +328,12 @@ if (!shared_state) PG_RETURN_NULL(); -#ifndef BDR_ONLY LWLockAcquire(shared_state->lock, LW_SHARED); if (shared_state->follow_new_primary == true) new_primary_node_id = shared_state->candidate_node_id; LWLockRelease(shared_state->lock); -#endif if (new_primary_node_id == UNKNOWN_NODE_ID) PG_RETURN_NULL(); @@ -348,7 +345,6 @@ Datum reset_voting_status(PG_FUNCTION_ARGS) { -#ifndef BDR_ONLY if (!shared_state) PG_RETURN_NULL(); @@ -366,7 +362,7 @@ } LWLockRelease(shared_state->lock); -#endif + PG_RETURN_VOID(); } diff -Nru repmgr-4.0.2/repmgr-client.c repmgr-4.0.3/repmgr-client.c --- repmgr-4.0.2/repmgr-client.c 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/repmgr-client.c 2018-02-14 02:28:38.000000000 +0000 @@ -60,7 +60,6 @@ #include "repmgr-action-witness.h" #include "repmgr-action-bdr.h" #include "repmgr-action-node.h" - #include "repmgr-action-cluster.h" #include /* for PG_TEMP_FILE_PREFIX */ @@ -73,7 +72,7 @@ t_configuration_options config_file_options = T_CONFIGURATION_OPTIONS_INITIALIZER; /* conninfo params for the node we're operating on */ -t_conninfo_param_list source_conninfo; +t_conninfo_param_list source_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; bool config_file_required = true; char pg_bindir[MAXLEN] = ""; @@ -95,7 +94,7 @@ int main(int argc, char **argv) { - t_conninfo_param_list default_conninfo; + t_conninfo_param_list default_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; int optindex; int c; @@ -329,6 +328,11 @@ strncpy(runtime_options.node_name, optarg, MAXLEN); break; + /* --remote-node-id */ + case OPT_REMOTE_NODE_ID: + runtime_options.remote_node_id = repmgr_atoi(optarg, "--remote-node-id", &cli_errors, false); + break; + /* * standby options * --------------- */ @@ -455,6 +459,10 @@ runtime_options.has_passfile = true; break; + case OPT_REPL_CONN: + runtime_options.replication_connection = true; + break; + /*-------------------- * "node rejoin" options *-------------------- @@ -737,7 +745,6 @@ if (repmgr_command != NULL) { -#ifndef BDR_ONLY if (strcasecmp(repmgr_command, "PRIMARY") == 0 || strcasecmp(repmgr_command, "MASTER") == 0) { if (help_option == true) @@ -794,9 +801,6 @@ action = WITNESS_UNREGISTER; } else if (strcasecmp(repmgr_command, "BDR") == 0) -#else - if (strcasecmp(repmgr_command, "BDR") == 0) -#endif { if (help_option == true) { @@ -1001,7 +1005,7 @@ && config_file_options.use_replication_slots == true) { log_error(_("STANDBY CLONE in Barman mode is incompatible with configuration option \"use_replication_slots\"")); - log_hint(_("set \"use_replication_slots\" to \"no\" in repmgr.conf, or use --without-barman fo clone directly from the upstream server")); + log_hint(_("set \"use_replication_slots\" to \"no\" in repmgr.conf, or use --without-barman to clone directly from the upstream server")); exit(ERR_BAD_CONFIG); } } @@ -1157,7 +1161,6 @@ switch (action) { -#ifndef BDR_ONLY /* PRIMARY */ case PRIMARY_REGISTER: do_primary_register(); @@ -1193,21 +1196,6 @@ case WITNESS_UNREGISTER: do_witness_unregister(); break; -#else - /* we won't ever reach here, but stop the compiler complaining */ - case PRIMARY_REGISTER: - case PRIMARY_UNREGISTER: - case STANDBY_CLONE: - case STANDBY_REGISTER: - case STANDBY_UNREGISTER: - case STANDBY_PROMOTE: - case STANDBY_FOLLOW: - case STANDBY_SWITCHOVER: - case WITNESS_REGISTER: - case WITNESS_UNREGISTER: - break; - -#endif /* BDR */ case BDR_REGISTER: do_bdr_register(); @@ -1599,8 +1587,7 @@ case NODE_STATUS: break; default: - item_list_append_format( - &cli_warnings, + item_list_append_format(&cli_warnings, _("--is-shutdown-cleanly will be ignored when executing %s"), action_name(action)); } @@ -1613,8 +1600,7 @@ case STANDBY_SWITCHOVER: break; default: - item_list_append_format( - &cli_warnings, + item_list_append_format(&cli_warnings, _("--always-promote will be ignored when executing %s"), action_name(action)); } @@ -1628,8 +1614,7 @@ case NODE_REJOIN: break; default: - item_list_append_format( - &cli_warnings, + item_list_append_format(&cli_warnings, _("--force-rewind will be ignored when executing %s"), action_name(action)); } @@ -1643,8 +1628,7 @@ case NODE_REJOIN: break; default: - item_list_append_format( - &cli_warnings, + item_list_append_format(&cli_warnings, _("--config-files will be ignored when executing %s"), action_name(action)); } @@ -1658,6 +1642,7 @@ case PRIMARY_UNREGISTER: case STANDBY_CLONE: case STANDBY_REGISTER: + case STANDBY_FOLLOW: case STANDBY_SWITCHOVER: case WITNESS_REGISTER: case WITNESS_UNREGISTER: @@ -1665,8 +1650,7 @@ case NODE_SERVICE: break; default: - item_list_append_format( - &cli_warnings, + item_list_append_format(&cli_warnings, _("--dry-run is not effective when executing %s"), action_name(action)); } @@ -1688,8 +1672,7 @@ if (used_options > 1) { /* TODO: list which options were used */ - item_list_append( - &cli_errors, + item_list_append(&cli_errors, "only one of --csv, --nagios and --optformat can be used"); } } @@ -1793,10 +1776,8 @@ print_help_header(); printf(_("Usage:\n")); -#ifndef BDR_ONLY printf(_(" %s [OPTIONS] primary {register|unregister}\n"), progname()); printf(_(" %s [OPTIONS] standby {register|unregister|clone|promote|follow}\n"), progname()); -#endif printf(_(" %s [OPTIONS] bdr {register|unregister}\n"), progname()); printf(_(" %s [OPTIONS] node status\n"), progname()); printf(_(" %s [OPTIONS] cluster {show|event|matrix|crosscheck}\n"), progname()); @@ -2123,9 +2104,12 @@ bool local_command(const char *command, PQExpBufferData *outputbuf) { - FILE *fp; + FILE *fp = NULL; char output[MAXLEN]; int retval = 0; + bool success; + + log_verbose(LOG_DEBUG, "executing:\n %s", command); if (outputbuf == NULL) { @@ -2141,20 +2125,29 @@ return false; } - /* TODO: better error handling */ + while (fgets(output, MAXLEN, fp) != NULL) { appendPQExpBuffer(outputbuf, "%s", output); + if (!feof(fp)) + { + break; + } } - pclose(fp); + retval = pclose(fp); + + /* */ + success = (WEXITSTATUS(retval) == 0 || WEXITSTATUS(retval) == 141) ? true : false; + + log_verbose(LOG_DEBUG, "result of command was %i (%i)", WEXITSTATUS(retval), retval); if (outputbuf->data != NULL) log_verbose(LOG_DEBUG, "local_command(): output returned was:\n%s", outputbuf->data); else log_verbose(LOG_DEBUG, "local_command(): no output returned"); - return true; + return success; } @@ -2416,7 +2409,12 @@ pclose(fp); if (outputbuf != NULL) - log_verbose(LOG_DEBUG, "remote_command(): output returned was:\n %s", outputbuf->data); + { + if (strlen(outputbuf->data)) + log_verbose(LOG_DEBUG, "remote_command(): output returned was:\n %s", outputbuf->data); + else + log_verbose(LOG_DEBUG, "remote_command(): no output returned"); + } return true; } @@ -2462,18 +2460,15 @@ { initPQExpBuffer(&command); - appendPQExpBuffer( - &command, + appendPQExpBuffer(&command, "%s %s -w -D ", make_pg_path("pg_ctl"), config_file_options.pg_ctl_options); - appendShellString( - &command, + appendShellString(&command, data_dir); - appendPQExpBuffer( - &command, + appendPQExpBuffer(&command, " start"); strncpy(script, command.data, MAXLEN); @@ -2485,6 +2480,7 @@ } case ACTION_STOP: + case ACTION_STOP_WAIT: { if (config_file_options.service_stop_command[0] != '\0') { @@ -2494,19 +2490,23 @@ else { initPQExpBuffer(&command); - appendPQExpBuffer( - &command, + appendPQExpBuffer(&command, "%s %s -D ", make_pg_path("pg_ctl"), config_file_options.pg_ctl_options); - appendShellString( - &command, + appendShellString(&command, data_dir); - appendPQExpBuffer( - &command, - " -m fast -W stop"); + if (action == ACTION_STOP_WAIT) + appendPQExpBuffer(&command, + " -w"); + else + appendPQExpBuffer(&command, + " -W"); + + appendPQExpBuffer(&command, + " -m fast stop"); strncpy(script, command.data, MAXLEN); @@ -2525,18 +2525,15 @@ else { initPQExpBuffer(&command); - appendPQExpBuffer( - &command, + appendPQExpBuffer(&command, "%s %s -w -D ", make_pg_path("pg_ctl"), config_file_options.pg_ctl_options); - appendShellString( - &command, + appendShellString(&command, data_dir); - appendPQExpBuffer( - &command, + appendPQExpBuffer(&command, " restart"); strncpy(script, command.data, MAXLEN); @@ -2556,18 +2553,15 @@ else { initPQExpBuffer(&command); - appendPQExpBuffer( - &command, + appendPQExpBuffer(&command, "%s %s -w -D ", make_pg_path("pg_ctl"), config_file_options.pg_ctl_options); - appendShellString( - &command, + appendShellString(&command, data_dir); - appendPQExpBuffer( - &command, + appendPQExpBuffer(&command, " reload"); strncpy(script, command.data, MAXLEN); @@ -2588,18 +2582,15 @@ else { initPQExpBuffer(&command); - appendPQExpBuffer( - &command, + appendPQExpBuffer(&command, "%s %s -w -D ", make_pg_path("pg_ctl"), config_file_options.pg_ctl_options); - appendShellString( - &command, + appendShellString(&command, data_dir); - appendPQExpBuffer( - &command, + appendPQExpBuffer(&command, " promote"); strncpy(script, command.data, MAXLEN); @@ -2633,6 +2624,7 @@ return true; case ACTION_STOP: + case ACTION_STOP_WAIT: if (config_file_options.service_stop_command[0] != '\0') { return false; diff -Nru repmgr-4.0.2/repmgr-client-global.h repmgr-4.0.3/repmgr-client-global.h --- repmgr-4.0.2/repmgr-client-global.h 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/repmgr-client-global.h 2018-02-14 02:28:38.000000000 +0000 @@ -68,6 +68,7 @@ int node_id; char node_name[MAXLEN]; char data_dir[MAXPGPATH]; + int remote_node_id; /* "standby clone" options */ bool copy_external_config_files; @@ -103,6 +104,7 @@ bool role; bool slots; bool has_passfile; + bool replication_connection; /* "node join" options */ char config_files[MAXLEN]; @@ -139,8 +141,8 @@ "", "", "", "", \ /* other connection options */ \ "", "", \ - /* node options */ \ - UNKNOWN_NODE_ID, "", "", \ + /* general node options */ \ + UNKNOWN_NODE_ID, "", "", UNKNOWN_NODE_ID, \ /* "standby clone" options */ \ false, CONFIG_FILE_SAMEPATH, false, false, false, "", "", "", \ false, \ @@ -153,7 +155,7 @@ /* "node status" options */ \ false, \ /* "node check" options */ \ - false, false, false, false, false, false, \ + false, false, false, false, false, false, false, \ /* "node join" options */ \ "", \ /* "node service" options */ \ @@ -179,6 +181,7 @@ ACTION_NONE, ACTION_START, ACTION_STOP, + ACTION_STOP_WAIT, ACTION_RESTART, ACTION_RELOAD, ACTION_PROMOTE diff -Nru repmgr-4.0.2/repmgr-client.h repmgr-4.0.3/repmgr-client.h --- repmgr-4.0.2/repmgr-client.h 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/repmgr-client.h 2018-02-14 02:28:38.000000000 +0000 @@ -83,6 +83,8 @@ #define OPT_CONFIG_ARCHIVE_DIR 1034 #define OPT_HAS_PASSFILE 1035 #define OPT_WAIT_START 1036 +#define OPT_REPL_CONN 1037 +#define OPT_REMOTE_NODE_ID 1038 /* deprecated since 3.3 */ #define OPT_DATA_DIR 999 @@ -115,6 +117,7 @@ {"pgdata", required_argument, NULL, 'D'}, {"node-id", required_argument, NULL, OPT_NODE_ID}, {"node-name", required_argument, NULL, OPT_NODE_NAME}, + {"remote-node-id", required_argument, NULL, OPT_REMOTE_NODE_ID}, /* logging options */ {"log-level", required_argument, NULL, 'L'}, @@ -158,6 +161,7 @@ {"role", no_argument, NULL, OPT_ROLE}, {"slots", no_argument, NULL, OPT_SLOTS}, {"has-passfile", no_argument, NULL, OPT_HAS_PASSFILE}, + {"replication-connection", no_argument, NULL, OPT_REPL_CONN}, /* "node rejoin" options */ {"config-files", required_argument, NULL, OPT_CONFIG_FILES}, diff -Nru repmgr-4.0.2/repmgr.conf.sample repmgr-4.0.3/repmgr.conf.sample --- repmgr-4.0.2/repmgr.conf.sample 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/repmgr.conf.sample 2018-02-14 02:28:38.000000000 +0000 @@ -235,8 +235,9 @@ #primary_notification_timeout=60 # Interval (in seconds) which repmgrd on a standby # will wait for a notification from the new primary, # before falling back to degraded monitoring -#monitoring_history=no +#monitoring_history=no # Whether to write monitoring data to the "montoring_history" table +#monitor_interval_secs=2 # Interval (in seconds) at which to write monitoring data #degraded_monitoring_timeout=-1 # Interval (in seconds) after which repmgrd will terminate if the # server being monitored is no longer available. -1 (default) # disables the timeout completely. diff -Nru repmgr-4.0.2/repmgrd.c repmgr-4.0.3/repmgrd.c --- repmgr-4.0.2/repmgrd.c 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/repmgrd.c 2018-02-14 02:28:38.000000000 +0000 @@ -89,6 +89,7 @@ bool cli_monitoring_history = false; RecordStatus record_status; + ExtensionStatus extension_status = REPMGR_UNKNOWN; FILE *fd; @@ -318,6 +319,37 @@ * repmgr has not been properly configured. */ + /* Check "repmgr" the extension is installed */ + extension_status = get_repmgr_extension_status(local_conn); + + if (extension_status != REPMGR_INSTALLED) + { + /* this is unlikely to happen */ + if (extension_status == REPMGR_UNKNOWN) + { + log_error(_("unable to determine status of \"repmgr\" extension")); + log_detail("%s", PQerrorMessage(local_conn)); + PQfinish(local_conn); + exit(ERR_DB_QUERY); + } + + log_error(_("repmgr extension not found on this node")); + + if (extension_status == REPMGR_AVAILABLE) + { + log_detail(_("repmgr extension is available but not installed in database \"%s\""), + PQdb(local_conn)); + } + else if (extension_status == REPMGR_UNAVAILABLE) + { + log_detail(_("repmgr extension is not available on this node")); + } + + log_hint(_("check that this node is part of a repmgr cluster")); + PQfinish(local_conn); + exit(ERR_BAD_CONFIG); + } + /* Retrieve record for this node from the local database */ record_status = get_node_record(local_conn, config_file_options.node_id, &local_node_info); @@ -400,7 +432,6 @@ { switch (local_node_info.type) { -#ifndef BDR_ONLY case PRIMARY: monitor_streaming_primary(); break; @@ -409,12 +440,6 @@ break; case WITNESS: monitor_streaming_witness(); - break; -#else - case PRIMARY: - case STANDBY: - return; -#endif case BDR: monitor_bdr(); return; diff -Nru repmgr-4.0.2/repmgrd-physical.c repmgr-4.0.3/repmgrd-physical.c --- repmgr-4.0.2/repmgrd-physical.c 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/repmgrd-physical.c 2018-02-14 02:28:38.000000000 +0000 @@ -54,7 +54,6 @@ static PGconn *upstream_conn = NULL; static PGconn *primary_conn = NULL; -#ifndef BDR_ONLY static FailoverState failover_state = FAILOVER_STATE_UNKNOWN; static int primary_node_id = UNKNOWN_NODE_ID; @@ -85,15 +84,12 @@ static const char * format_failover_state(FailoverState failover_state); -#endif - /* perform some sanity checks on the node's configuration */ void do_physical_node_check(void) { -#ifndef BDR_ONLY /* * Check if node record is active - if not, and `failover=automatic`, the * node won't be considered as a promotion candidate; this often happens @@ -163,7 +159,6 @@ exit(ERR_BAD_CONFIG); } } -#endif } @@ -174,7 +169,6 @@ void monitor_streaming_primary(void) { -#ifndef BDR_ONLY instr_time log_status_interval_start; PQExpBufferData event_details; @@ -485,14 +479,12 @@ sleep(config_file_options.monitor_interval_secs); } -#endif } void monitor_streaming_standby(void) { -#ifndef BDR_ONLY RecordStatus record_status; instr_time log_status_interval_start; PQExpBufferData event_details; @@ -1019,14 +1011,12 @@ sleep(config_file_options.monitor_interval_secs); } -#endif } void monitor_streaming_witness(void) { -#ifndef BDR_ONLY instr_time log_status_interval_start; instr_time witness_sync_interval_start; @@ -1351,13 +1341,12 @@ sleep(config_file_options.monitor_interval_secs); } -#endif + return; } -#ifndef BDR_ONLY static bool do_primary_failover(void) { @@ -2722,7 +2711,6 @@ return "UNKNOWN_FAILOVER_STATE"; } -#endif /* #ifndef BDR_ONLY */ void close_connections_physical() diff -Nru repmgr-4.0.2/repmgr_version.h.in repmgr-4.0.3/repmgr_version.h.in --- repmgr-4.0.2/repmgr_version.h.in 2018-01-16 04:55:58.000000000 +0000 +++ repmgr-4.0.3/repmgr_version.h.in 2018-02-14 02:28:38.000000000 +0000 @@ -1,3 +1,3 @@ #define REPMGR_VERSION_DATE "" -#define REPMGR_VERSION "4.0.2" +#define REPMGR_VERSION "4.0.3"