Compare commits

..

9 Commits
v3.3 ... v3.1.1

Author SHA1 Message Date
Ian Barwick
5ad674edff Bump version
3.1.1
2016-02-23 15:56:24 +09:00
Ian Barwick
ac09bad89c Minor fixes to README.md 2016-02-23 14:37:59 +09:00
Ian Barwick
009d92fec8 Ensure witness node is registered before the repl_nodes table is copied
This fixes a bug introduced into the previous commit, where the
witness node was registered last to prevent a spurious node record
being created even if witness server creation failed.
2016-02-23 14:37:54 +09:00
Martin
b3d8a68a1d Fix a few paragraphs from the README.md. 2016-02-23 14:37:48 +09:00
Ian Barwick
05b47cb2a8 Prevent repmgr/repmgrd running as root 2016-02-23 14:37:44 +09:00
Ian Barwick
dc542a1b7d Better handling of errors during witness creation
Ensure witness is only registered after all steps for creation
have been successfully completed.

Also write an event record if connection could not be made to
the witness server after initial creation.

This addresses GitHub issue #146.
2016-02-23 14:37:39 +09:00
Ian Barwick
6ce8058749 witness creation: extract database and user names from the local conninfo string
99.9% of the time they'll be the same as the primary connection, but
it's more consistent to use the provided local conninfo string
(from which the port is already extracted).
2016-02-23 14:37:31 +09:00
Ian Barwick
2edcac77f0 README.md: update witness server section 2016-02-23 14:37:27 +09:00
Ian Barwick
f740374392 Add '-P/--pwprompt' option for "repmgr create witness"
Optionally prompt for superuser and repmgr user when creating a witness.
This ensures a password can be provided if the primary's pg_hba.conf
mandates it.

This deprecates '--initdb-no-pwprompt'; and changes the default behaviour of
"repmgr create witness", which previously required a superuser password
unless '--initdb-no-pwprompt' was supplied.

This behaviour is more consistent with other PostgreSQL utilities such
as createuser.

Partial fix for GitHub issue #145.
2016-02-23 14:37:23 +09:00
33 changed files with 2084 additions and 7479 deletions

14
FAQ.md
View File

@@ -38,7 +38,7 @@ General
No. Hash indexes and replication do not mix well and their use is No. Hash indexes and replication do not mix well and their use is
explicitly discouraged; see: explicitly discouraged; see:
https://www.postgresql.org/docs/current/interactive/sql-createindex.html#AEN74175 http://www.postgresql.org/docs/current/interactive/sql-createindex.html#AEN74175
`repmgr` `repmgr`
-------- --------
@@ -137,7 +137,6 @@ General
of events which includes servers removed from the replication cluster of events which includes servers removed from the replication cluster
which no longer have an entry in the `repl_nodes` table. which no longer have an entry in the `repl_nodes` table.
`repmgrd` `repmgrd`
--------- ---------
@@ -152,9 +151,6 @@ General
In `repmgr.conf`, set its priority to a value of 0 or less. In `repmgr.conf`, set its priority to a value of 0 or less.
Additionally, if `failover` is set to `manual`, the node will never
be considered as a promotion candidate.
- Does `repmgrd` support delayed standbys? - Does `repmgrd` support delayed standbys?
`repmgrd` can monitor delayed standbys - those set up with `repmgrd` can monitor delayed standbys - those set up with
@@ -173,11 +169,3 @@ General
Configure your system's `logrotate` service to do this; see example Configure your system's `logrotate` service to do this; see example
in README.md in README.md
- I've recloned a failed master as a standby, but `repmgrd` refuses to start?
Check you registered the standby after recloning. If unregistered the standby
cannot be considered as a promotion candidate even if `failover` is set to
`automatic`, which is probably not what you want. `repmgrd` will start if
`failover` is set to `manual` so the node's replication status can still
be monitored, if desired.

94
HISTORY
View File

@@ -1,96 +1,4 @@
3.3 2016-12-27 3.1.1 2016-02-
repmgr: always log to STDERR even if log facility defined (Ian)
repmgr: add --log-to-file to log repmgr output to the defined
log facility (Ian)
repmgr: improve handling of command line parameter errors (Ian)
repmgr: add option --upstream-conninfo to explicitly set
'primary_conninfo' in recovery.conf (Ian)
repmgr: enable a standby to be registered which isn't running (Ian)
repmgr: enable `standby register --force` to update a node record
with cascaded downstream node records (Ian)
repmgr: add option `--no-conninfo-password` (Abhijit, Ian)
repmgr: add initial support for PostgreSQL 10.0 (Ian)
repmgr: escape values in primary_conninfo if needed (Ian)
3.2.1 2016-10-24
repmgr: require a valid repmgr cluster name unless -F/--force
supplied (Ian)
repmgr: check master server is registered with repmgr before
cloning (Ian)
repmgr: ensure data directory defaults to that of the source node (Ian)
repmgr: various fixes to Barman cloning mode (Gianni, Ian)
repmgr: fix `repmgr cluster crosscheck` output (Ian)
3.2 2016-10-05
repmgr: add support for cloning from a Barman backup (Gianni)
repmgr: add commands `standby matrix` and `standby crosscheck` (Gianni)
repmgr: suppress connection error display in `repmgr cluster show`
unless `--verbose` supplied (Ian)
repmgr: add commands `witness register` and `witness unregister` (Ian)
repmgr: enable `standby unregister` / `witness unregister` to be
executed for a node which is not running (Ian)
repmgr: remove deprecated command line options --initdb-no-pwprompt and
-l/--local-port (Ian)
repmgr: before cloning with pg_basebackup, check that sufficient free
walsenders are available (Ian)
repmgr: add option `--wait-sync` for `standby register` which causes
repmgr to wait for the registered node record to synchronise to
the standby (Ian)
repmgr: add option `--copy-external-config-files` for files outside
of the data directory (Ian)
repmgr: only require `wal_keep_segments` to be set in certain corner
cases (Ian)
repmgr: better support cloning from a node other than the one to
stream from (Ian)
repmgrd: add configuration options to override the default pg_ctl
commands (Jarkko Oranen, Ian)
repmgrd: don't start if node is inactive and failover=automatic (Ian)
packaging: improve "repmgr-auto" Debian package (Gianni)
3.1.5 2016-08-15
repmgrd: in a failover situation, prevent endless looping when
attempting to establish the status of a node with
`failover=manual` (Ian)
repmgrd: improve handling of failover events on standbys with
`failover=manual`, and create a new event notification
for this, `standby_disconnect_manual` (Ian)
repmgr: add further event notifications (Gianni)
repmgr: when executing `standby switchover`, don't collect remote
command output unless required (Gianni, Ian)
repmgrd: improve standby monitoring query (Ian, based on suggestion
from Álvaro)
repmgr: various command line handling improvements (Ian)
3.1.4 2016-07-12
repmgr: new configuration option for setting "restore_command"
in the recovery.conf file generated by repmgr (Martín)
repmgr: add --csv option to "repmgr cluster show" (Gianni)
repmgr: enable provision of a conninfo string as the -d/--dbname
parameter, similar to other PostgreSQL utilities (Ian)
repmgr: during switchover operations improve detection of
demotion candidate shutdown (Ian)
various bugfixes and documentation updates (Ian, Martín)
3.1.3 2016-05-17
repmgrd: enable monitoring when a standby is catching up by
replaying archived WAL (Ian)
repmgrd: when upstream_node_id is NULL, assume upstream node
to be current master (Ian)
repmgrd: check for reappearance of the master node if standby
promotion fails (Ian)
improve handling of rsync failure conditions (Martín)
3.1.2 2016-04-12
Fix pg_ctl path generation in do_standby_switchover() (Ian)
Regularly sync witness server repl_nodes table (Ian)
Documentation improvements (Gianni, dhyannataraj)
(Experimental) ensure repmgr handles failover slots when copying
in rsync mode (Craig, Ian)
rsync mode handling fixes (Martín)
Enable repmgr to compile against 9.6devel (Ian)
3.1.1 2016-02-24
Add '-P/--pwprompt' option for "repmgr create witness" (Ian) Add '-P/--pwprompt' option for "repmgr create witness" (Ian)
Prevent repmgr/repmgrd running as root (Ian) Prevent repmgr/repmgrd running as root (Ian)

View File

@@ -2,32 +2,23 @@
# Makefile # Makefile
# Copyright (c) 2ndQuadrant, 2010-2016 # Copyright (c) 2ndQuadrant, 2010-2016
HEADERS = $(wildcard *.h)
repmgrd_OBJS = dbutils.o config.o repmgrd.o log.o strutil.o repmgrd_OBJS = dbutils.o config.o repmgrd.o log.o strutil.o
repmgr_OBJS = dbutils.o check_dir.o config.o repmgr.o log.o strutil.o dirmod.o compat.o repmgr_OBJS = dbutils.o check_dir.o config.o repmgr.o log.o strutil.o
DATA = repmgr.sql uninstall_repmgr.sql DATA = repmgr.sql uninstall_repmgr.sql
PG_CPPFLAGS = -I$(libpq_srcdir) PG_CPPFLAGS = -I$(libpq_srcdir)
PG_LIBS = $(libpq_pgport) PG_LIBS = $(libpq_pgport)
all: repmgrd repmgr
all: repmgrd repmgr
$(MAKE) -C sql $(MAKE) -C sql
repmgrd: $(repmgrd_OBJS) repmgrd: $(repmgrd_OBJS)
$(CC) -o repmgrd $(CFLAGS) $(repmgrd_OBJS) $(PG_LIBS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) $(CC) $(CFLAGS) $(repmgrd_OBJS) $(PG_LIBS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o repmgrd
$(MAKE) -C sql $(MAKE) -C sql
repmgr: $(repmgr_OBJS) repmgr: $(repmgr_OBJS)
$(CC) -o repmgr $(CFLAGS) $(repmgr_OBJS) $(PG_LIBS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) $(CC) $(CFLAGS) $(repmgr_OBJS) $(PG_LIBS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o repmgr
# Make all objects depend on all include files. This is a bit of a
# shotgun approach, but the codebase is small enough that a complete rebuild
# is very fast anyway.
$(repmgr_OBJS): $(HEADERS)
$(repmgrd_OBJS): $(HEADERS)
ifdef USE_PGXS ifdef USE_PGXS
PG_CONFIG = pg_config PG_CONFIG = pg_config
@@ -40,8 +31,8 @@ include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk include $(top_srcdir)/contrib/contrib-global.mk
endif endif
# XXX: This overrides the pgxs install target - we're building two binaries, # XXX: Try to use PROGRAM construct (see pgxs.mk) someday. Right now
# which is not supported by pgxs.mk's PROGRAM construct. # is overriding pgxs install.
install: install_prog install_ext install: install_prog install_ext
install_prog: install_prog:
@@ -52,12 +43,6 @@ install_prog:
install_ext: install_ext:
$(MAKE) -C sql install $(MAKE) -C sql install
# Distribution-specific package building targets
# ----------------------------------------------
#
# XXX we recommend using the PGDG-supplied packages where possible;
# see README.md for details.
install_rhel: install_rhel:
mkdir -p '$(DESTDIR)/etc/init.d/' mkdir -p '$(DESTDIR)/etc/init.d/'
$(INSTALL_PROGRAM) RHEL/repmgrd.init '$(DESTDIR)/etc/init.d/repmgrd' $(INSTALL_PROGRAM) RHEL/repmgrd.init '$(DESTDIR)/etc/init.d/repmgrd'
@@ -87,12 +72,10 @@ PG_VERSION = $(shell pg_config --version | cut -d ' ' -f 2 | cut -d '.' -f 1,2)
REPMGR_VERSION = $(shell grep REPMGR_VERSION version.h | cut -d ' ' -f 3 | cut -d '"' -f 2) REPMGR_VERSION = $(shell grep REPMGR_VERSION version.h | cut -d ' ' -f 3 | cut -d '"' -f 2)
PKGLIBDIR = $(shell pg_config --pkglibdir) PKGLIBDIR = $(shell pg_config --pkglibdir)
SHAREDIR = $(shell pg_config --sharedir) SHAREDIR = $(shell pg_config --sharedir)
PGBINDIR = /usr/lib/postgresql/$(PG_VERSION)/bin
deb: repmgrd repmgr deb: repmgrd repmgr
mkdir -p ./debian/usr/bin ./debian$(PGBINDIR) mkdir -p ./debian/usr/bin
cp repmgrd repmgr ./debian$(PGBINDIR) cp repmgrd repmgr ./debian/usr/bin/
ln -s ../..$(PGBINDIR)/repmgr ./debian/usr/bin/repmgr
mkdir -p ./debian$(SHAREDIR)/contrib/ mkdir -p ./debian$(SHAREDIR)/contrib/
cp sql/repmgr_funcs.sql ./debian$(SHAREDIR)/contrib/ cp sql/repmgr_funcs.sql ./debian$(SHAREDIR)/contrib/
cp sql/uninstall_repmgr_funcs.sql ./debian$(SHAREDIR)/contrib/ cp sql/uninstall_repmgr_funcs.sql ./debian$(SHAREDIR)/contrib/

805
README.md

File diff suppressed because it is too large Load Diff

61
RHEL/repmgr3-93.spec Normal file
View File

@@ -0,0 +1,61 @@
Summary: repmgr
Name: repmgr
Version: 3.0
Release: 1
License: GPLv3
Group: System Environment/Daemons
URL: http://repmgr.org
Packager: Ian Barwick <ian@2ndquadrant.com>
Vendor: 2ndQuadrant Limited
Distribution: centos
Source0: %{name}-%{version}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
%description
repmgr is a utility suite which greatly simplifies
the process of setting up and managing replication
using streaming replication within a cluster of
PostgreSQL servers.
%prep
%setup
%build
export PATH=$PATH:/usr/pgsql-9.3/bin/
%{__make} USE_PGXS=1
%install
[ "%{buildroot}" != "/" ] && %{__rm} -rf %{buildroot}
export PATH=$PATH:/usr/pgsql-9.3/bin/
%{__make} USE_PGXS=1 install DESTDIR=%{buildroot} INSTALL="install -p"
%{__make} USE_PGXS=1 install_prog DESTDIR=%{buildroot} INSTALL="install -p"
%{__make} USE_PGXS=1 install_rhel DESTDIR=%{buildroot} INSTALL="install -p"
%clean
[ "%{buildroot}" != "/" ] && %{__rm} -rf %{buildroot}
%files
%defattr(-,root,root)
/usr/bin/repmgr
/usr/bin/repmgrd
/usr/pgsql-9.3/bin/repmgr
/usr/pgsql-9.3/bin/repmgrd
/usr/pgsql-9.3/lib/repmgr_funcs.so
/usr/pgsql-9.3/share/contrib/repmgr.sql
/usr/pgsql-9.3/share/contrib/repmgr_funcs.sql
/usr/pgsql-9.3/share/contrib/uninstall_repmgr.sql
/usr/pgsql-9.3/share/contrib/uninstall_repmgr_funcs.sql
%attr(0755,root,root)/etc/init.d/repmgrd
%attr(0644,root,root)/etc/sysconfig/repmgrd
%attr(0644,root,root)/etc/repmgr/repmgr.conf.sample
%changelog
* Tue Mar 10 2015 Ian Barwick ian@2ndquadrant.com>
- build for repmgr 3.0
* Thu Jun 05 2014 Nathan Van Overloop <nathan.van.overloop@nexperteam.be> 2.0.2
- fix witness creation to create db and user if needed
* Fri Apr 04 2014 Nathan Van Overloop <nathan.van.overloop@nexperteam.be> 2.0.1
- initial build for RHEL6

133
RHEL/repmgrd.init Executable file
View File

@@ -0,0 +1,133 @@
#!/bin/sh
#
# chkconfig: - 75 16
# description: Enable repmgrd replication management and monitoring daemon for PostgreSQL
# processname: repmgrd
# pidfile="/var/run/${NAME}.pid"
# Source function library.
INITD=/etc/rc.d/init.d
. $INITD/functions
# Get function listing for cross-distribution logic.
TYPESET=`typeset -f|grep "declare"`
# Get network config.
. /etc/sysconfig/network
DESC="PostgreSQL replication management and monitoring daemon"
NAME=repmgrd
REPMGRD_ENABLED=no
REPMGRD_OPTS=
REPMGRD_USER=postgres
REPMGRD_BIN=/usr/pgsql-9.3/bin/repmgrd
REPMGRD_PIDFILE=/var/run/repmgrd.pid
REPMGRD_LOCK=/var/lock/subsys/${NAME}
REPMGRD_LOG=/var/lib/pgsql/9.3/data/pg_log/repmgrd.log
# Read configuration variable file if it is present
[ -r /etc/sysconfig/$NAME ] && . /etc/sysconfig/$NAME
# For SELinux we need to use 'runuser' not 'su'
if [ -x /sbin/runuser ]
then
SU=runuser
else
SU=su
fi
test -x $REPMGRD_BIN || exit 0
case "$REPMGRD_ENABLED" in
[Yy]*)
break
;;
*)
exit 0
;;
esac
if [ -z "${REPMGRD_OPTS}" ]
then
echo "Not starting ${NAME}, REPMGRD_OPTS not set in /etc/sysconfig/${NAME}"
exit 0
fi
start()
{
REPMGRD_START=$"Starting ${NAME} service: "
# Make sure startup-time log file is valid
if [ ! -e "${REPMGRD_LOG}" -a ! -h "${REPMGRD_LOG}" ]
then
touch "${REPMGRD_LOG}" || exit 1
chown ${REPMGRD_USER}:postgres "${REPMGRD_LOG}"
chmod go-rwx "${REPMGRD_LOG}"
[ -x /sbin/restorecon ] && /sbin/restorecon "${REPMGRD_LOG}"
fi
echo -n "${REPMGRD_START}"
$SU -l $REPMGRD_USER -c "${REPMGRD_BIN} ${REPMGRD_OPTS} -p ${REPMGRD_PIDFILE} &" >> "${REPMGRD_LOG}" 2>&1 < /dev/null
sleep 2
pid=`head -n 1 "${REPMGRD_PIDFILE}" 2>/dev/null`
if [ "x${pid}" != "x" ]
then
success "${REPMGRD_START}"
touch "${REPMGRD_LOCK}"
echo $pid > "${REPMGRD_PIDFILE}"
echo
else
failure "${REPMGRD_START}"
echo
script_result=1
fi
}
stop()
{
echo -n $"Stopping ${NAME} service: "
if [ -e "${REPMGRD_LOCK}" ]
then
killproc ${NAME}
ret=$?
if [ $ret -eq 0 ]
then
echo_success
rm -f "${REPMGRD_PIDFILE}"
rm -f "${REPMGRD_LOCK}"
else
echo_failure
script_result=1
fi
else
# not running; per LSB standards this is "ok"
echo_success
fi
echo
}
# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status -p $REPMGRD_PIDFILE $NAME
script_result=$?
;;
restart)
stop
start
;;
*)
echo $"Usage: $0 {start|stop|status|restart}"
exit 2
esac
exit $script_result

21
RHEL/repmgrd.sysconfig Normal file
View File

@@ -0,0 +1,21 @@
# default settings for repmgrd. This file is source by /bin/sh from
# /etc/init.d/repmgrd
# disable repmgrd by default so it won't get started upon installation
# valid values: yes/no
REPMGRD_ENABLED=no
# Options for repmgrd (required)
#REPMGRD_OPTS="--verbose -d -f /var/lib/pgsql/repmgr/repmgr.conf"
# User to run repmgrd as
#REPMGRD_USER=postgres
# repmgrd binary
#REPMGRD_BIN=/usr/bin/repmgrd
# pid file
#REPMGRD_PIDFILE=/var/lib/pgsql/repmgr/repmgrd.pid
# log file
#REPMGRD_LOG=/var/lib/pgsql/repmgr/repmgrd.log

5
TODO
View File

@@ -53,9 +53,8 @@ Planned feature improvements
requested, activate the replication slot using pg_receivexlog to negate the requested, activate the replication slot using pg_receivexlog to negate the
need to set `wal_keep_segments` just for the initial clone (9.4 and 9.5). need to set `wal_keep_segments` just for the initial clone (9.4 and 9.5).
* repmgr: enable "standby follow" to point a standby at another standby, not * Take into account the fact that a standby can obtain WAL from an archive,
just the replication cluster master (see GitHub #130) so even if direct streaming replication is interrupted, it may be up-to-date
Usability improvements Usability improvements
====================== ======================

111
compat.c
View File

@@ -1,111 +0,0 @@
/*
*
* compat.c
* Provide backports of various functions not publicly
* exposed before PostgreSQL 9.6
*
* Copyright (C) 2ndQuadrant, 2010-2016
*
* Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#if (PG_VERSION_NUM < 90600)
#include "repmgr.h"
#include "compat.h"
/*
* Append the given string to the buffer, with suitable quoting for passing
* the string as a value, in a keyword/pair value in a libpq connection
* string
*
* This function is copied from src/bin/pg_dump/dumputils.c
* as it is only publicly exposed from 9.6
*/
void
appendConnStrVal(PQExpBuffer buf, const char *str)
{
const char *s;
bool needquotes;
/*
* If the string is one or more plain ASCII characters, no need to quote
* it. This is quite conservative, but better safe than sorry.
*/
needquotes = true;
for (s = str; *s; s++)
{
if (!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') ||
(*s >= '0' && *s <= '9') || *s == '_' || *s == '.'))
{
needquotes = true;
break;
}
needquotes = false;
}
if (needquotes)
{
appendPQExpBufferChar(buf, '\'');
while (*str)
{
/* ' and \ must be escaped by to \' and \\ */
if (*str == '\'' || *str == '\\')
appendPQExpBufferChar(buf, '\\');
appendPQExpBufferChar(buf, *str);
str++;
}
appendPQExpBufferChar(buf, '\'');
}
else
appendPQExpBufferStr(buf, str);
}
/*
* Adapted from: src/fe_utils/string_utils.c
*
* Function not publicly available before PostgreSQL 9.6.
*/
void
appendShellString(PQExpBuffer buf, const char *str)
{
const char *p;
appendPQExpBufferChar(buf, '\'');
for (p = str; *p; p++)
{
if (*p == '\n' || *p == '\r')
{
fprintf(stderr,
_("shell command argument contains a newline or carriage return: \"%s\"\n"),
str);
exit(ERR_BAD_CONFIG);
}
if (*p == '\'')
appendPQExpBufferStr(buf, "'\"'\"'");
else
appendPQExpBufferChar(buf, *p);
}
appendPQExpBufferChar(buf, '\'');
}
#endif

View File

@@ -1,29 +0,0 @@
/*
* compat.h
* Copyright (c) 2ndQuadrant, 2010-2016
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef _COMPAT_H_
#define _COMPAT_H_
extern void
appendConnStrVal(PQExpBuffer buf, const char *str);
extern void
appendShellString(PQExpBuffer buf, const char *str);
#endif

381
config.c
View File

@@ -1,6 +1,5 @@
/* /*
* config.c - Functions to parse the config file * config.c - Functions to parse the config file
*
* Copyright (C) 2ndQuadrant, 2010-2016 * Copyright (C) 2ndQuadrant, 2010-2016
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@@ -10,11 +9,11 @@
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
@@ -27,9 +26,9 @@
static void parse_event_notifications_list(t_configuration_options *options, const char *arg); static void parse_event_notifications_list(t_configuration_options *options, const char *arg);
static void tablespace_list_append(t_configuration_options *options, const char *arg); static void tablespace_list_append(t_configuration_options *options, const char *arg);
static void exit_with_errors(ItemList *config_errors); static void exit_with_errors(ErrorList *config_errors);
const static char *_progname = NULL; const static char *_progname = '\0';
static char config_file_path[MAXPGPATH]; static char config_file_path[MAXPGPATH];
static bool config_file_provided = false; static bool config_file_provided = false;
bool config_file_found = false; bool config_file_found = false;
@@ -55,8 +54,8 @@ progname(void)
* *
* Returns true if a configuration file could be parsed, otherwise false. * Returns true if a configuration file could be parsed, otherwise false.
* *
* Any *repmgrd-specific* configuration options added/changed in this function must also be * Any configuration options changed in this function must also be changed in
* added/changed in reload_config() * reload_config()
* *
* NOTE: this function is called before the logger is set up, so we need * NOTE: this function is called before the logger is set up, so we need
* to handle the verbose option ourselves; also the default log level is NOTICE, * to handle the verbose option ourselves; also the default log level is NOTICE,
@@ -99,9 +98,9 @@ load_config(const char *config_file, bool verbose, t_configuration_options *opti
/* /*
* If no configuration file was provided, attempt to find a default file * If no configuration file was provided, attempt to find a default file
* in this order: * in this order:
* - current directory * - current directory
* - /etc/repmgr.conf * - /etc/repmgr.conf
* - default sysconfdir * - default sysconfdir
* *
* here we just check for the existence of the file; parse_config() * here we just check for the existence of the file; parse_config()
* will handle read errors etc. * will handle read errors etc.
@@ -181,23 +180,6 @@ load_config(const char *config_file, bool verbose, t_configuration_options *opti
} }
bool
parse_config(t_configuration_options *options)
{
/* Collate configuration file errors here for friendlier reporting */
static ItemList config_errors = { NULL, NULL };
_parse_config(options, &config_errors);
if (config_errors.head != NULL)
{
exit_with_errors(&config_errors);
}
return true;
}
/* /*
* Parse configuration file; if any errors are encountered, * Parse configuration file; if any errors are encountered,
* list them and exit. * list them and exit.
@@ -205,8 +187,8 @@ parse_config(t_configuration_options *options)
* Ensure any default values set here are synced with repmgr.conf.sample * Ensure any default values set here are synced with repmgr.conf.sample
* and any other documentation. * and any other documentation.
*/ */
void bool
_parse_config(t_configuration_options *options, ItemList *error_list) parse_config(t_configuration_options *options)
{ {
FILE *fp; FILE *fp;
char *s, char *s,
@@ -218,6 +200,9 @@ _parse_config(t_configuration_options *options, ItemList *error_list)
PQconninfoOption *conninfo_options; PQconninfoOption *conninfo_options;
char *conninfo_errmsg = NULL; char *conninfo_errmsg = NULL;
/* Collate configuration file errors here for friendlier reporting */
static ErrorList config_errors = { NULL, NULL };
bool node_found = false; bool node_found = false;
/* Initialize configuration options with sensible defaults /* Initialize configuration options with sensible defaults
@@ -225,28 +210,20 @@ _parse_config(t_configuration_options *options, ItemList *error_list)
* to be initialised here * to be initialised here
*/ */
memset(options->cluster_name, 0, sizeof(options->cluster_name)); memset(options->cluster_name, 0, sizeof(options->cluster_name));
options->node = UNKNOWN_NODE_ID; options->node = -1;
options->upstream_node = NO_UPSTREAM_NODE; options->upstream_node = NO_UPSTREAM_NODE;
options->use_replication_slots = 0; options->use_replication_slots = 0;
memset(options->conninfo, 0, sizeof(options->conninfo)); memset(options->conninfo, 0, sizeof(options->conninfo));
memset(options->barman_server, 0, sizeof(options->barman_server));
memset(options->barman_config, 0, sizeof(options->barman_config));
options->failover = MANUAL_FAILOVER; options->failover = MANUAL_FAILOVER;
options->priority = DEFAULT_PRIORITY; options->priority = DEFAULT_PRIORITY;
memset(options->node_name, 0, sizeof(options->node_name)); memset(options->node_name, 0, sizeof(options->node_name));
memset(options->promote_command, 0, sizeof(options->promote_command)); memset(options->promote_command, 0, sizeof(options->promote_command));
memset(options->follow_command, 0, sizeof(options->follow_command)); memset(options->follow_command, 0, sizeof(options->follow_command));
memset(options->service_stop_command, 0, sizeof(options->service_stop_command));
memset(options->service_start_command, 0, sizeof(options->service_start_command));
memset(options->service_restart_command, 0, sizeof(options->service_restart_command));
memset(options->service_reload_command, 0, sizeof(options->service_reload_command));
memset(options->service_promote_command, 0, sizeof(options->service_promote_command));
memset(options->rsync_options, 0, sizeof(options->rsync_options)); memset(options->rsync_options, 0, sizeof(options->rsync_options));
memset(options->ssh_options, 0, sizeof(options->ssh_options)); memset(options->ssh_options, 0, sizeof(options->ssh_options));
memset(options->pg_bindir, 0, sizeof(options->pg_bindir)); memset(options->pg_bindir, 0, sizeof(options->pg_bindir));
memset(options->pg_ctl_options, 0, sizeof(options->pg_ctl_options)); memset(options->pg_ctl_options, 0, sizeof(options->pg_ctl_options));
memset(options->pg_basebackup_options, 0, sizeof(options->pg_basebackup_options)); memset(options->pg_basebackup_options, 0, sizeof(options->pg_basebackup_options));
memset(options->restore_command, 0, sizeof(options->restore_command));
/* default master_response_timeout is 60 seconds */ /* default master_response_timeout is 60 seconds */
options->master_response_timeout = 60; options->master_response_timeout = 60;
@@ -258,12 +235,7 @@ _parse_config(t_configuration_options *options, ItemList *error_list)
options->monitor_interval_secs = 2; options->monitor_interval_secs = 2;
options->retry_promote_interval_secs = 300; options->retry_promote_interval_secs = 300;
/* default to resyncing repl_nodes table every 30 seconds on the witness server */
options->witness_repl_nodes_sync_interval_secs = 30;
memset(options->event_notification_command, 0, sizeof(options->event_notification_command)); memset(options->event_notification_command, 0, sizeof(options->event_notification_command));
options->event_notifications.head = NULL;
options->event_notifications.tail = NULL;
options->tablespace_mapping.head = NULL; options->tablespace_mapping.head = NULL;
options->tablespace_mapping.tail = NULL; options->tablespace_mapping.tail = NULL;
@@ -276,7 +248,7 @@ _parse_config(t_configuration_options *options, ItemList *error_list)
{ {
log_verbose(LOG_NOTICE, _("no configuration file provided and no default file found - " log_verbose(LOG_NOTICE, _("no configuration file provided and no default file found - "
"continuing with default values\n")); "continuing with default values\n"));
return; return true;
} }
fp = fopen(config_file_path, "r"); fp = fopen(config_file_path, "r");
@@ -321,17 +293,13 @@ _parse_config(t_configuration_options *options, ItemList *error_list)
strncpy(options->cluster_name, value, MAXLEN); strncpy(options->cluster_name, value, MAXLEN);
else if (strcmp(name, "node") == 0) else if (strcmp(name, "node") == 0)
{ {
options->node = repmgr_atoi(value, "node", error_list, false); options->node = repmgr_atoi(value, "node", &config_errors, false);
node_found = true; node_found = true;
} }
else if (strcmp(name, "upstream_node") == 0) else if (strcmp(name, "upstream_node") == 0)
options->upstream_node = repmgr_atoi(value, "upstream_node", error_list, false); options->upstream_node = repmgr_atoi(value, "upstream_node", &config_errors, false);
else if (strcmp(name, "conninfo") == 0) else if (strcmp(name, "conninfo") == 0)
strncpy(options->conninfo, value, MAXLEN); strncpy(options->conninfo, value, MAXLEN);
else if (strcmp(name, "barman_server") == 0)
strncpy(options->barman_server, value, MAXLEN);
else if (strcmp(name, "barman_config") == 0)
strncpy(options->barman_config, value, MAXLEN);
else if (strcmp(name, "rsync_options") == 0) else if (strcmp(name, "rsync_options") == 0)
strncpy(options->rsync_options, value, QUERY_STR_LEN); strncpy(options->rsync_options, value, QUERY_STR_LEN);
else if (strcmp(name, "ssh_options") == 0) else if (strcmp(name, "ssh_options") == 0)
@@ -356,39 +324,28 @@ _parse_config(t_configuration_options *options, ItemList *error_list)
} }
else else
{ {
item_list_append(error_list, _("value for 'failover' must be 'automatic' or 'manual'\n")); error_list_append(&config_errors,_("value for 'failover' must be 'automatic' or 'manual'\n"));
} }
} }
else if (strcmp(name, "priority") == 0) else if (strcmp(name, "priority") == 0)
options->priority = repmgr_atoi(value, "priority", error_list, true); options->priority = repmgr_atoi(value, "priority", &config_errors, true);
else if (strcmp(name, "node_name") == 0) else if (strcmp(name, "node_name") == 0)
strncpy(options->node_name, value, MAXLEN); strncpy(options->node_name, value, MAXLEN);
else if (strcmp(name, "promote_command") == 0) else if (strcmp(name, "promote_command") == 0)
strncpy(options->promote_command, value, MAXLEN); strncpy(options->promote_command, value, MAXLEN);
else if (strcmp(name, "follow_command") == 0) else if (strcmp(name, "follow_command") == 0)
strncpy(options->follow_command, value, MAXLEN); strncpy(options->follow_command, value, MAXLEN);
else if (strcmp(name, "service_stop_command") == 0)
strncpy(options->service_stop_command, value, MAXLEN);
else if (strcmp(name, "service_start_command") == 0)
strncpy(options->service_start_command, value, MAXLEN);
else if (strcmp(name, "service_restart_command") == 0)
strncpy(options->service_restart_command, value, MAXLEN);
else if (strcmp(name, "service_reload_command") == 0)
strncpy(options->service_reload_command, value, MAXLEN);
else if (strcmp(name, "service_promote_command") == 0)
strncpy(options->service_promote_command, value, MAXLEN);
else if (strcmp(name, "master_response_timeout") == 0) else if (strcmp(name, "master_response_timeout") == 0)
options->master_response_timeout = repmgr_atoi(value, "master_response_timeout", error_list, false); options->master_response_timeout = repmgr_atoi(value, "master_response_timeout", &config_errors, false);
/* /* 'primary_response_timeout' as synonym for 'master_response_timeout' -
* 'primary_response_timeout' as synonym for 'master_response_timeout' -
* we'll switch terminology in a future release (3.1?) * we'll switch terminology in a future release (3.1?)
*/ */
else if (strcmp(name, "primary_response_timeout") == 0) else if (strcmp(name, "primary_response_timeout") == 0)
options->master_response_timeout = repmgr_atoi(value, "primary_response_timeout", error_list, false); options->master_response_timeout = repmgr_atoi(value, "primary_response_timeout", &config_errors, false);
else if (strcmp(name, "reconnect_attempts") == 0) else if (strcmp(name, "reconnect_attempts") == 0)
options->reconnect_attempts = repmgr_atoi(value, "reconnect_attempts", error_list, false); options->reconnect_attempts = repmgr_atoi(value, "reconnect_attempts", &config_errors, false);
else if (strcmp(name, "reconnect_interval") == 0) else if (strcmp(name, "reconnect_interval") == 0)
options->reconnect_interval = repmgr_atoi(value, "reconnect_interval", error_list, false); options->reconnect_interval = repmgr_atoi(value, "reconnect_interval", &config_errors, false);
else if (strcmp(name, "pg_bindir") == 0) else if (strcmp(name, "pg_bindir") == 0)
strncpy(options->pg_bindir, value, MAXLEN); strncpy(options->pg_bindir, value, MAXLEN);
else if (strcmp(name, "pg_ctl_options") == 0) else if (strcmp(name, "pg_ctl_options") == 0)
@@ -398,22 +355,18 @@ _parse_config(t_configuration_options *options, ItemList *error_list)
else if (strcmp(name, "logfile") == 0) else if (strcmp(name, "logfile") == 0)
strncpy(options->logfile, value, MAXLEN); strncpy(options->logfile, value, MAXLEN);
else if (strcmp(name, "monitor_interval_secs") == 0) else if (strcmp(name, "monitor_interval_secs") == 0)
options->monitor_interval_secs = repmgr_atoi(value, "monitor_interval_secs", error_list, false); options->monitor_interval_secs = repmgr_atoi(value, "monitor_interval_secs", &config_errors, false);
else if (strcmp(name, "retry_promote_interval_secs") == 0) else if (strcmp(name, "retry_promote_interval_secs") == 0)
options->retry_promote_interval_secs = repmgr_atoi(value, "retry_promote_interval_secs", error_list, false); options->retry_promote_interval_secs = repmgr_atoi(value, "retry_promote_interval_secs", &config_errors, false);
else if (strcmp(name, "witness_repl_nodes_sync_interval_secs") == 0)
options->witness_repl_nodes_sync_interval_secs = repmgr_atoi(value, "witness_repl_nodes_sync_interval_secs", error_list, false);
else if (strcmp(name, "use_replication_slots") == 0) else if (strcmp(name, "use_replication_slots") == 0)
/* XXX we should have a dedicated boolean argument format */ /* XXX we should have a dedicated boolean argument format */
options->use_replication_slots = repmgr_atoi(value, "use_replication_slots", error_list, false); options->use_replication_slots = repmgr_atoi(value, "use_replication_slots", &config_errors, false);
else if (strcmp(name, "event_notification_command") == 0) else if (strcmp(name, "event_notification_command") == 0)
strncpy(options->event_notification_command, value, MAXLEN); strncpy(options->event_notification_command, value, MAXLEN);
else if (strcmp(name, "event_notifications") == 0) else if (strcmp(name, "event_notifications") == 0)
parse_event_notifications_list(options, value); parse_event_notifications_list(options, value);
else if (strcmp(name, "tablespace_mapping") == 0) else if (strcmp(name, "tablespace_mapping") == 0)
tablespace_list_append(options, value); tablespace_list_append(options, value);
else if (strcmp(name, "restore_command") == 0)
strncpy(options->restore_command, value, MAXLEN);
else else
{ {
known_parameter = false; known_parameter = false;
@@ -433,7 +386,7 @@ _parse_config(t_configuration_options *options, ItemList *error_list)
_("no value provided for parameter \"%s\""), _("no value provided for parameter \"%s\""),
name); name);
item_list_append(error_list, error_message_buf); error_list_append(&config_errors, error_message_buf);
} }
} }
@@ -442,11 +395,11 @@ _parse_config(t_configuration_options *options, ItemList *error_list)
if (node_found == false) if (node_found == false)
{ {
item_list_append(error_list, _("\"node\": parameter was not found")); error_list_append(&config_errors, _("\"node\": parameter was not found"));
} }
else if (options->node == 0) else if (options->node == 0)
{ {
item_list_append(error_list, _("\"node\": must be greater than zero")); error_list_append(&config_errors, _("\"node\": must be greater than zero"));
} }
if (strlen(options->conninfo)) if (strlen(options->conninfo))
@@ -466,11 +419,18 @@ _parse_config(t_configuration_options *options, ItemList *error_list)
_("\"conninfo\": %s"), _("\"conninfo\": %s"),
conninfo_errmsg); conninfo_errmsg);
item_list_append(error_list, error_message_buf); error_list_append(&config_errors, error_message_buf);
} }
PQconninfoFree(conninfo_options); PQconninfoFree(conninfo_options);
} }
if (config_errors.head != NULL)
{
exit_with_errors(&config_errors);
}
return true;
} }
@@ -560,85 +520,70 @@ parse_line(char *buf, char *name, char *value)
trim(value); trim(value);
} }
/*
* reload_config()
*
* This is only called by repmgrd after receiving a SIGHUP or when a monitoring
* loop is started up; it therefore only needs to reload options required
* by repmgrd, which are as follows:
*
* changeable options:
* - failover
* - follow_command
* - logfacility
* - logfile
* - loglevel
* - master_response_timeout
* - monitor_interval_secs
* - priority
* - promote_command
* - reconnect_attempts
* - reconnect_interval
* - retry_promote_interval_secs
* - witness_repl_nodes_sync_interval_secs
*
* non-changeable options:
* - cluster_name
* - conninfo
* - node
* - node_name
*
* extract with something like:
* grep local_options\\. repmgrd.c | perl -n -e '/local_options\.([\w_]+)/ && print qq|$1\n|;' | sort | uniq
*/
bool bool
reload_config(t_configuration_options *orig_options) reload_config(t_configuration_options *orig_options)
{ {
PGconn *conn; PGconn *conn;
t_configuration_options new_options = T_CONFIGURATION_OPTIONS_INITIALIZER; t_configuration_options new_options;
bool config_changed = false; bool config_changed = false;
bool log_config_changed = false;
static ItemList config_errors = { NULL, NULL };
/* /*
* Re-read the configuration file: repmgr.conf * Re-read the configuration file: repmgr.conf
*/ */
log_info(_("reloading configuration file\n")); log_info(_("reloading configuration file and updating repmgr tables\n"));
_parse_config(&new_options, &config_errors); parse_config(&new_options);
if (new_options.node == -1)
if (config_errors.head != NULL)
{ {
/* XXX dump errors to log */
log_warning(_("unable to parse new configuration, retaining current configuration\n")); log_warning(_("unable to parse new configuration, retaining current configuration\n"));
return false; return false;
} }
/* The following options cannot be changed */
if (strcmp(new_options.cluster_name, orig_options->cluster_name) != 0) if (strcmp(new_options.cluster_name, orig_options->cluster_name) != 0)
{ {
log_warning(_("cluster_name cannot be changed, retaining current configuration\n")); log_warning(_("unable to change cluster name, retaining current configuration\n"));
return false; return false;
} }
if (new_options.node != orig_options->node) if (new_options.node != orig_options->node)
{ {
log_warning(_("node ID cannot be changed, retaining current configuration\n")); log_warning(_("unable to change node ID, retaining current configuration\n"));
return false; return false;
} }
if (strcmp(new_options.node_name, orig_options->node_name) != 0) if (strcmp(new_options.node_name, orig_options->node_name) != 0)
{ {
log_warning(_("node_name cannot be changed, keeping current configuration\n")); log_warning(_("unable to change standby name, keeping current configuration\n"));
return false;
}
if (new_options.failover != MANUAL_FAILOVER && new_options.failover != AUTOMATIC_FAILOVER)
{
log_warning(_("new value for 'failover' must be 'automatic' or 'manual'\n"));
return false;
}
if (new_options.master_response_timeout <= 0)
{
log_warning(_("new value for 'master_response_timeout' must be greater than zero\n"));
return false;
}
if (new_options.reconnect_attempts < 0)
{
log_warning(_("new value for 'reconnect_attempts' must be zero or greater\n"));
return false;
}
if (new_options.reconnect_interval < 0)
{
log_warning(_("new value for 'reconnect_interval' must be zero or greater\n"));
return false; return false;
} }
if (strcmp(orig_options->conninfo, new_options.conninfo) != 0) if (strcmp(orig_options->conninfo, new_options.conninfo) != 0)
{ {
/* Test conninfo string works*/ /* Test conninfo string */
conn = establish_db_connection(new_options.conninfo, false); conn = establish_db_connection(new_options.conninfo, false);
if (!conn || (PQstatus(conn) != CONNECTION_OK)) if (!conn || (PQstatus(conn) != CONNECTION_OK))
{ {
@@ -655,6 +600,27 @@ reload_config(t_configuration_options *orig_options)
* to manage them * to manage them
*/ */
/* cluster_name */
if (strcmp(orig_options->cluster_name, new_options.cluster_name) != 0)
{
strcpy(orig_options->cluster_name, new_options.cluster_name);
config_changed = true;
}
/* conninfo */
if (strcmp(orig_options->conninfo, new_options.conninfo) != 0)
{
strcpy(orig_options->conninfo, new_options.conninfo);
config_changed = true;
}
/* node */
if (orig_options->node != new_options.node)
{
orig_options->node = new_options.node;
config_changed = true;
}
/* failover */ /* failover */
if (orig_options->failover != new_options.failover) if (orig_options->failover != new_options.failover)
{ {
@@ -662,27 +628,6 @@ reload_config(t_configuration_options *orig_options)
config_changed = true; config_changed = true;
} }
/* follow_command */
if (strcmp(orig_options->follow_command, new_options.follow_command) != 0)
{
strcpy(orig_options->follow_command, new_options.follow_command);
config_changed = true;
}
/* master_response_timeout */
if (orig_options->master_response_timeout != new_options.master_response_timeout)
{
orig_options->master_response_timeout = new_options.master_response_timeout;
config_changed = true;
}
/* monitor_interval_secs */
if (orig_options->monitor_interval_secs != new_options.monitor_interval_secs)
{
orig_options->monitor_interval_secs = new_options.monitor_interval_secs;
config_changed = true;
}
/* priority */ /* priority */
if (orig_options->priority != new_options.priority) if (orig_options->priority != new_options.priority)
{ {
@@ -690,6 +635,13 @@ reload_config(t_configuration_options *orig_options)
config_changed = true; config_changed = true;
} }
/* node_name */
if (strcmp(orig_options->node_name, new_options.node_name) != 0)
{
strcpy(orig_options->node_name, new_options.node_name);
config_changed = true;
}
/* promote_command */ /* promote_command */
if (strcmp(orig_options->promote_command, new_options.promote_command) != 0) if (strcmp(orig_options->promote_command, new_options.promote_command) != 0)
{ {
@@ -697,6 +649,44 @@ reload_config(t_configuration_options *orig_options)
config_changed = true; config_changed = true;
} }
/* follow_command */
if (strcmp(orig_options->follow_command, new_options.follow_command) != 0)
{
strcpy(orig_options->follow_command, new_options.follow_command);
config_changed = true;
}
/*
* XXX These ones can change with a simple SIGHUP?
*
* strcpy (orig_options->loglevel, new_options.loglevel); strcpy
* (orig_options->logfacility, new_options.logfacility);
*
* logger_shutdown(); XXX do we have progname here ? logger_init(progname,
* orig_options.loglevel, orig_options.logfacility);
*/
/* rsync_options */
if (strcmp(orig_options->rsync_options, new_options.rsync_options) != 0)
{
strcpy(orig_options->rsync_options, new_options.rsync_options);
config_changed = true;
}
/* ssh_options */
if (strcmp(orig_options->ssh_options, new_options.ssh_options) != 0)
{
strcpy(orig_options->ssh_options, new_options.ssh_options);
config_changed = true;
}
/* master_response_timeout */
if (orig_options->master_response_timeout != new_options.master_response_timeout)
{
orig_options->master_response_timeout = new_options.master_response_timeout;
config_changed = true;
}
/* reconnect_attempts */ /* reconnect_attempts */
if (orig_options->reconnect_attempts != new_options.reconnect_attempts) if (orig_options->reconnect_attempts != new_options.reconnect_attempts)
{ {
@@ -711,6 +701,27 @@ reload_config(t_configuration_options *orig_options)
config_changed = true; config_changed = true;
} }
/* pg_ctl_options */
if (strcmp(orig_options->pg_ctl_options, new_options.pg_ctl_options) != 0)
{
strcpy(orig_options->pg_ctl_options, new_options.pg_ctl_options);
config_changed = true;
}
/* pg_basebackup_options */
if (strcmp(orig_options->pg_basebackup_options, new_options.pg_basebackup_options) != 0)
{
strcpy(orig_options->pg_basebackup_options, new_options.pg_basebackup_options);
config_changed = true;
}
/* monitor_interval_secs */
if (orig_options->monitor_interval_secs != new_options.monitor_interval_secs)
{
orig_options->monitor_interval_secs = new_options.monitor_interval_secs;
config_changed = true;
}
/* retry_promote_interval_secs */ /* retry_promote_interval_secs */
if (orig_options->retry_promote_interval_secs != new_options.retry_promote_interval_secs) if (orig_options->retry_promote_interval_secs != new_options.retry_promote_interval_secs)
{ {
@@ -718,54 +729,20 @@ reload_config(t_configuration_options *orig_options)
config_changed = true; config_changed = true;
} }
/* use_replication_slots */
/* witness_repl_nodes_sync_interval_secs */ if (orig_options->use_replication_slots != new_options.use_replication_slots)
if (orig_options->witness_repl_nodes_sync_interval_secs != new_options.witness_repl_nodes_sync_interval_secs)
{ {
orig_options->witness_repl_nodes_sync_interval_secs = new_options.witness_repl_nodes_sync_interval_secs; orig_options->use_replication_slots = new_options.use_replication_slots;
config_changed = true; config_changed = true;
} }
/*
* Handle changes to logging configuration
*/
if (strcmp(orig_options->logfacility, new_options.logfacility) != 0)
{
strcpy(orig_options->logfacility, new_options.logfacility);
log_config_changed = true;
}
if (strcmp(orig_options->logfile, new_options.logfile) != 0)
{
strcpy(orig_options->logfile, new_options.logfile);
log_config_changed = true;
}
if (strcmp(orig_options->loglevel, new_options.loglevel) != 0)
{
strcpy(orig_options->loglevel, new_options.loglevel);
log_config_changed = true;
}
if (log_config_changed == true)
{
log_notice(_("restarting logging with changed parameters\n"));
logger_shutdown();
logger_init(orig_options, progname());
}
if (config_changed == true) if (config_changed == true)
{ {
log_notice(_("configuration file reloaded with changed parameters\n")); log_debug(_("reload_config(): configuration has changed\n"));
} }
/* else
* if logging configuration changed, don't say the configuration didn't
* change, as it clearly has.
*/
else if (log_config_changed == false)
{ {
log_info(_("configuration has not changed\n")); log_debug(_("reload_config(): configuration has not changed\n"));
} }
return config_changed; return config_changed;
@@ -773,11 +750,11 @@ reload_config(t_configuration_options *orig_options)
void void
item_list_append(ItemList *item_list, char *error_message) error_list_append(ErrorList *error_list, char *error_message)
{ {
ItemListCell *cell; ErrorListCell *cell;
cell = (ItemListCell *) pg_malloc0(sizeof(ItemListCell)); cell = (ErrorListCell *) pg_malloc0(sizeof(ErrorListCell));
if (cell == NULL) if (cell == NULL)
{ {
@@ -785,19 +762,19 @@ item_list_append(ItemList *item_list, char *error_message)
exit(ERR_BAD_CONFIG); exit(ERR_BAD_CONFIG);
} }
cell->string = pg_malloc0(MAXLEN); cell->error_message = pg_malloc0(MAXLEN);
strncpy(cell->string, error_message, MAXLEN); strncpy(cell->error_message, error_message, MAXLEN);
if (item_list->tail) if (error_list->tail)
{ {
item_list->tail->next = cell; error_list->tail->next = cell;
} }
else else
{ {
item_list->head = cell; error_list->head = cell;
} }
item_list->tail = cell; error_list->tail = cell;
} }
@@ -807,7 +784,7 @@ item_list_append(ItemList *item_list, char *error_message)
* otherwise exit * otherwise exit
*/ */
int int
repmgr_atoi(const char *value, const char *config_item, ItemList *error_list, bool allow_negative) repmgr_atoi(const char *value, const char *config_item, ErrorList *error_list, bool allow_negative)
{ {
char *endptr; char *endptr;
long longval = 0; long longval = 0;
@@ -856,7 +833,7 @@ repmgr_atoi(const char *value, const char *config_item, ItemList *error_list, bo
exit(ERR_BAD_CONFIG); exit(ERR_BAD_CONFIG);
} }
item_list_append(error_list, error_message_buf); error_list_append(error_list, error_message_buf);
} }
return (int32) longval; return (int32) longval;
@@ -939,7 +916,7 @@ static void
parse_event_notifications_list(t_configuration_options *options, const char *arg) parse_event_notifications_list(t_configuration_options *options, const char *arg)
{ {
const char *arg_ptr; const char *arg_ptr;
char event_type_buf[MAXLEN] = ""; char event_type_buf[MAXLEN] = "";
char *dst_ptr = event_type_buf; char *dst_ptr = event_type_buf;
@@ -998,15 +975,15 @@ parse_event_notifications_list(t_configuration_options *options, const char *arg
static void static void
exit_with_errors(ItemList *config_errors) exit_with_errors(ErrorList *config_errors)
{ {
ItemListCell *cell; ErrorListCell *cell;
log_err(_("%s: following errors were found in the configuration file.\n"), progname()); log_err(_("%s: following errors were found in the configuration file.\n"), progname());
for (cell = config_errors->head; cell; cell = cell->next) for (cell = config_errors->head; cell; cell = cell->next)
{ {
log_err("%s\n", cell->string); log_err("%s\n", cell->error_message);
} }
exit(ERR_BAD_CONFIG); exit(ERR_BAD_CONFIG);

View File

@@ -1,6 +1,5 @@
/* /*
* config.h * config.h
*
* Copyright (c) 2ndQuadrant, 2010-2016 * Copyright (c) 2ndQuadrant, 2010-2016
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@@ -58,20 +57,11 @@ typedef struct
int node; int node;
int upstream_node; int upstream_node;
char conninfo[MAXLEN]; char conninfo[MAXLEN];
char barman_server[MAXLEN];
char barman_config[MAXLEN];
int failover; int failover;
int priority; int priority;
char node_name[MAXLEN]; char node_name[MAXLEN];
/* commands executed by repmgrd */
char promote_command[MAXLEN]; char promote_command[MAXLEN];
char follow_command[MAXLEN]; char follow_command[MAXLEN];
/* Overrides for pg_ctl commands */
char service_stop_command[MAXLEN];
char service_start_command[MAXLEN];
char service_restart_command[MAXLEN];
char service_reload_command[MAXLEN];
char service_promote_command[MAXLEN];
char loglevel[MAXLEN]; char loglevel[MAXLEN];
char logfacility[MAXLEN]; char logfacility[MAXLEN];
char rsync_options[QUERY_STR_LEN]; char rsync_options[QUERY_STR_LEN];
@@ -82,66 +72,41 @@ typedef struct
char pg_bindir[MAXLEN]; char pg_bindir[MAXLEN];
char pg_ctl_options[MAXLEN]; char pg_ctl_options[MAXLEN];
char pg_basebackup_options[MAXLEN]; char pg_basebackup_options[MAXLEN];
char restore_command[MAXLEN];
char logfile[MAXLEN]; char logfile[MAXLEN];
int monitor_interval_secs; int monitor_interval_secs;
int retry_promote_interval_secs; int retry_promote_interval_secs;
int witness_repl_nodes_sync_interval_secs;
int use_replication_slots; int use_replication_slots;
char event_notification_command[MAXLEN]; char event_notification_command[MAXLEN];
EventNotificationList event_notifications; EventNotificationList event_notifications;
TablespaceList tablespace_mapping; TablespaceList tablespace_mapping;
} t_configuration_options; } t_configuration_options;
/* #define T_CONFIGURATION_OPTIONS_INITIALIZER { "", -1, NO_UPSTREAM_NODE, "", MANUAL_FAILOVER, -1, "", "", "", "", "", "", "", -1, -1, -1, "", "", "", "", 0, 0, 0, "", { NULL, NULL }, {NULL, NULL} }
* The following will initialize the structure with a minimal set of options;
* actual defaults are set in parse_config() before parsing the configuration file
*/
#define T_CONFIGURATION_OPTIONS_INITIALIZER { "", UNKNOWN_NODE_ID, NO_UPSTREAM_NODE, "", "", "", MANUAL_FAILOVER, -1, "", "", "", "", "", "", "", "", "", "", "", "", -1, -1, -1, "", "", "", "", "", 0, 0, 0, 0, "", { NULL, NULL }, { NULL, NULL } }
typedef struct ItemListCell typedef struct ErrorListCell
{ {
struct ItemListCell *next; struct ErrorListCell *next;
char *string; char *error_message;
} ItemListCell; } ErrorListCell;
typedef struct ItemList typedef struct ErrorList
{ {
ItemListCell *head; ErrorListCell *head;
ItemListCell *tail; ErrorListCell *tail;
} ItemList; } ErrorList;
typedef struct TablespaceDataListCell
{
struct TablespaceDataListCell *next;
char *name;
char *oid;
char *location;
/* optional payload */
FILE *f;
} TablespaceDataListCell;
typedef struct TablespaceDataList
{
TablespaceDataListCell *head;
TablespaceDataListCell *tail;
} TablespaceDataList;
void set_progname(const char *argv0); void set_progname(const char *argv0);
const char * progname(void); const char * progname(void);
bool load_config(const char *config_file, bool verbose, t_configuration_options *options, char *argv0); bool load_config(const char *config_file, bool verbose, t_configuration_options *options, char *argv0);
void _parse_config(t_configuration_options *options, ItemList *error_list);
bool parse_config(t_configuration_options *options);
bool reload_config(t_configuration_options *orig_options); bool reload_config(t_configuration_options *orig_options);
bool parse_config(t_configuration_options *options);
void parse_line(char *buff, char *name, char *value); void parse_line(char *buff, char *name, char *value);
char *trim(char *s); char *trim(char *s);
void item_list_append(ItemList *item_list, char *error_message); void error_list_append(ErrorList *error_list, char *error_message);
int repmgr_atoi(const char *s, int repmgr_atoi(const char *s,
const char *config_item, const char *config_item,
ItemList *error_list, ErrorList *error_list,
bool allow_negative); bool allow_negative);
extern bool config_file_found;
#endif #endif

354
dbutils.c
View File

@@ -1,6 +1,5 @@
/* /*
* dbutils.c - Database connection/management functions * dbutils.c - Database connection/management functions
*
* Copyright (C) 2ndQuadrant, 2010-2016 * Copyright (C) 2ndQuadrant, 2010-2016
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@@ -32,10 +31,9 @@
char repmgr_schema[MAXLEN] = ""; char repmgr_schema[MAXLEN] = "";
char repmgr_schema_quoted[MAXLEN] = ""; char repmgr_schema_quoted[MAXLEN] = "";
static int _get_node_record(PGconn *conn, char *cluster, char *sqlquery, t_node_info *node_info);
PGconn * PGconn *
_establish_db_connection(const char *conninfo, const bool exit_on_error, const bool log_notice, const bool verbose_only) _establish_db_connection(const char *conninfo, const bool exit_on_error, const bool log_notice)
{ {
/* Make a connection to the database */ /* Make a connection to the database */
PGconn *conn = NULL; PGconn *conn = NULL;
@@ -51,23 +49,15 @@ _establish_db_connection(const char *conninfo, const bool exit_on_error, const b
/* Check to see that the backend connection was successfully made */ /* Check to see that the backend connection was successfully made */
if ((PQstatus(conn) != CONNECTION_OK)) if ((PQstatus(conn) != CONNECTION_OK))
{ {
bool emit_log = true; if (log_notice)
if (verbose_only == true && verbose_logging == false)
emit_log = false;
if (emit_log)
{ {
if (log_notice) log_notice(_("connection to database failed: %s\n"),
{ PQerrorMessage(conn));
log_notice(_("connection to database failed: %s\n"), }
PQerrorMessage(conn)); else
} {
else log_err(_("connection to database failed: %s\n"),
{ PQerrorMessage(conn));
log_err(_("connection to database failed: %s\n"),
PQerrorMessage(conn));
}
} }
if (exit_on_error) if (exit_on_error)
@@ -80,35 +70,16 @@ _establish_db_connection(const char *conninfo, const bool exit_on_error, const b
return conn; return conn;
} }
/*
* Establish a database connection, optionally exit on error
*/
PGconn * PGconn *
establish_db_connection(const char *conninfo, const bool exit_on_error) establish_db_connection(const char *conninfo, const bool exit_on_error)
{ {
return _establish_db_connection(conninfo, exit_on_error, false, false); return _establish_db_connection(conninfo, exit_on_error, false);
} }
/*
* Attempt to establish a database connection, never exit on error, only
* output error messages if --verbose option used
*/
PGconn * PGconn *
establish_db_connection_quiet(const char *conninfo) test_db_connection(const char *conninfo, const bool exit_on_error)
{ {
return _establish_db_connection(conninfo, false, false, true); return _establish_db_connection(conninfo, exit_on_error, true);
}
/*
* Attempt to establish a database connection, never exit on error,
* output connection error messages as NOTICE (useful when connection
* failure is expected)
*/
PGconn *
test_db_connection(const char *conninfo)
{
return _establish_db_connection(conninfo, false, true, false);
} }
@@ -214,7 +185,7 @@ check_cluster_schema(PGconn *conn)
char sqlquery[QUERY_STR_LEN]; char sqlquery[QUERY_STR_LEN];
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT 1 FROM pg_catalog.pg_namespace WHERE nspname = '%s'", "SELECT 1 FROM pg_namespace WHERE nspname = '%s'",
get_repmgr_schema()); get_repmgr_schema());
log_verbose(LOG_DEBUG, "check_cluster_schema(): %s\n", sqlquery); log_verbose(LOG_DEBUG, "check_cluster_schema(): %s\n", sqlquery);
@@ -279,6 +250,7 @@ is_pgup(PGconn *conn, int timeout)
/* Check the connection status twice in case it changes after reset */ /* Check the connection status twice in case it changes after reset */
bool twice = false; bool twice = false;
/* Check the connection status twice in case it changes after reset */
for (;;) for (;;)
{ {
if (PQstatus(conn) != CONNECTION_OK) if (PQstatus(conn) != CONNECTION_OK)
@@ -408,7 +380,7 @@ guc_set(PGconn *conn, const char *parameter, const char *op,
int retval = 1; int retval = 1;
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT true FROM pg_catalog.pg_settings " "SELECT true FROM pg_settings "
" WHERE name = '%s' AND setting %s '%s'", " WHERE name = '%s' AND setting %s '%s'",
parameter, op, value); parameter, op, value);
@@ -444,11 +416,11 @@ guc_set_typed(PGconn *conn, const char *parameter, const char *op,
int retval = 1; int retval = 1;
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT true FROM pg_catalog.pg_settings " "SELECT true FROM pg_settings "
" WHERE name = '%s' AND setting::%s %s '%s'::%s", " WHERE name = '%s' AND setting::%s %s '%s'::%s",
parameter, datatype, op, value, datatype); parameter, datatype, op, value, datatype);
log_verbose(LOG_DEBUG, "guc_set_typed():\n%s\n", sqlquery); log_verbose(LOG_DEBUG, "guc_set_typed():n%s\n", sqlquery);
res = PQexec(conn, sqlquery); res = PQexec(conn, sqlquery);
if (PQresultStatus(res) != PGRES_TUPLES_OK) if (PQresultStatus(res) != PGRES_TUPLES_OK)
@@ -476,7 +448,7 @@ get_cluster_size(PGconn *conn, char *size)
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT pg_catalog.pg_size_pretty(SUM(pg_catalog.pg_database_size(oid))::bigint) " "SELECT pg_catalog.pg_size_pretty(SUM(pg_catalog.pg_database_size(oid))::bigint) "
" FROM pg_catalog.pg_database "); " FROM pg_database ");
log_verbose(LOG_DEBUG, "get_cluster_size():\n%s\n", sqlquery); log_verbose(LOG_DEBUG, "get_cluster_size():\n%s\n", sqlquery);
@@ -503,11 +475,11 @@ get_pg_setting(PGconn *conn, const char *setting, char *output)
char sqlquery[QUERY_STR_LEN]; char sqlquery[QUERY_STR_LEN];
PGresult *res; PGresult *res;
int i; int i;
bool success = false; bool success = true;
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT name, setting " "SELECT name, setting "
" FROM pg_catalog.pg_settings WHERE name = '%s'", " FROM pg_settings WHERE name = '%s'",
setting); setting);
log_verbose(LOG_DEBUG, "get_pg_setting(): %s\n", sqlquery); log_verbose(LOG_DEBUG, "get_pg_setting(): %s\n", sqlquery);
@@ -566,7 +538,7 @@ get_conninfo_value(const char *conninfo, const char *keyword, char *output)
conninfo_options = PQconninfoParse(conninfo, NULL); conninfo_options = PQconninfoParse(conninfo, NULL);
if (conninfo_options == NULL) if (conninfo_options == false)
{ {
log_err(_("Unable to parse provided conninfo string \"%s\""), conninfo); log_err(_("Unable to parse provided conninfo string \"%s\""), conninfo);
return false; return false;
@@ -615,7 +587,7 @@ get_upstream_connection(PGconn *standby_conn, char *cluster, int node_id,
upstream_conninfo = upstream_conninfo_out; upstream_conninfo = upstream_conninfo_out;
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
" SELECT un.conninfo, un.id " " SELECT un.conninfo, un.name, un.id "
" FROM %s.repl_nodes un " " FROM %s.repl_nodes un "
"INNER JOIN %s.repl_nodes n " "INNER JOIN %s.repl_nodes n "
" ON (un.id = n.upstream_node_id AND un.cluster = n.cluster)" " ON (un.id = n.upstream_node_id AND un.cluster = n.cluster)"
@@ -632,7 +604,7 @@ get_upstream_connection(PGconn *standby_conn, char *cluster, int node_id,
if (PQresultStatus(res) != PGRES_TUPLES_OK) if (PQresultStatus(res) != PGRES_TUPLES_OK)
{ {
log_err(_("error when attempting to find upstream server\n%s\n"), log_err(_("unable to get conninfo for upstream server\n%s\n"),
PQerrorMessage(standby_conn)); PQerrorMessage(standby_conn));
PQclear(res); PQclear(res);
return NULL; return NULL;
@@ -640,36 +612,9 @@ get_upstream_connection(PGconn *standby_conn, char *cluster, int node_id,
if (!PQntuples(res)) if (!PQntuples(res))
{ {
log_notice(_("no record found for upstream server"));
PQclear(res); PQclear(res);
log_debug("no record found for upstream server\n"); return NULL;
sqlquery_snprintf(sqlquery,
" SELECT un.conninfo, un.id "
" FROM %s.repl_nodes un "
" WHERE un.cluster = '%s' "
" AND un.type='master' "
" AND un.active IS TRUE",
get_repmgr_schema_quoted(standby_conn),
cluster);
res = PQexec(standby_conn, sqlquery);
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
log_err(_("error when attempting to find active master server\n%s\n"),
PQerrorMessage(standby_conn));
PQclear(res);
return NULL;
}
if (!PQntuples(res))
{
PQclear(res);
log_notice(_("no record found for active master server\n"));
return NULL;
}
log_debug("record found for active master server\n");
} }
strncpy(upstream_conninfo, PQgetvalue(res, 0, 0), MAXCONNINFO); strncpy(upstream_conninfo, PQgetvalue(res, 0, 0), MAXCONNINFO);
@@ -944,7 +889,7 @@ get_repmgr_schema_quoted(PGconn *conn)
bool bool
create_replication_slot(PGconn *conn, char *slot_name, int server_version_num, PQExpBufferData *error_msg) create_replication_slot(PGconn *conn, char *slot_name)
{ {
char sqlquery[QUERY_STR_LEN]; char sqlquery[QUERY_STR_LEN];
int query_res; int query_res;
@@ -963,9 +908,8 @@ create_replication_slot(PGconn *conn, char *slot_name, int server_version_num, P
{ {
if (strcmp(slot_info.slot_type, "physical") != 0) if (strcmp(slot_info.slot_type, "physical") != 0)
{ {
appendPQExpBuffer(error_msg, log_err(_("Slot '%s' exists and is not a physical slot\n"),
_("Slot '%s' exists and is not a physical slot\n"), slot_name);
slot_name);
return false; return false;
} }
@@ -977,36 +921,24 @@ create_replication_slot(PGconn *conn, char *slot_name, int server_version_num, P
return true; return true;
} }
appendPQExpBuffer(error_msg, log_err(_("Slot '%s' already exists as an active slot\n"),
_("Slot '%s' already exists as an active slot\n"), slot_name);
slot_name);
return false; return false;
} }
/* In 9.6 and later, reserve the LSN straight away */ sqlquery_snprintf(sqlquery,
if (server_version_num >= 90600) "SELECT * FROM pg_create_physical_replication_slot('%s')",
{ slot_name);
sqlquery_snprintf(sqlquery,
"SELECT * FROM pg_catalog.pg_create_physical_replication_slot('%s', TRUE)",
slot_name);
}
else
{
sqlquery_snprintf(sqlquery,
"SELECT * FROM pg_catalog.pg_create_physical_replication_slot('%s')",
slot_name);
}
log_debug(_("create_replication_slot(): Creating slot '%s' on master\n"), slot_name); log_debug(_("create_replication_slot(): Creating slot '%s' on primary\n"), slot_name);
log_verbose(LOG_DEBUG, "create_replication_slot():\n%s\n", sqlquery); log_verbose(LOG_DEBUG, "create_replication_slot():\n%s\n", sqlquery);
res = PQexec(conn, sqlquery); res = PQexec(conn, sqlquery);
if (!res || PQresultStatus(res) != PGRES_TUPLES_OK) if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
{ {
appendPQExpBuffer(error_msg, log_err(_("unable to create slot '%s' on the primary node: %s\n"),
_("unable to create slot '%s' on the master node: %s\n"), slot_name,
slot_name, PQerrorMessage(conn));
PQerrorMessage(conn));
PQclear(res); PQclear(res);
return false; return false;
} }
@@ -1024,7 +956,7 @@ get_slot_record(PGconn *conn, char *slot_name, t_replication_slot *record)
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT slot_name, slot_type, active " "SELECT slot_name, slot_type, active "
" FROM pg_catalog.pg_replication_slots " " FROM pg_replication_slots "
" WHERE slot_name = '%s' ", " WHERE slot_name = '%s' ",
slot_name); slot_name);
@@ -1179,7 +1111,7 @@ set_config_bool(PGconn *conn, const char *config_param, bool state)
/* /*
* witness_copy_node_records() * copy_configuration()
* *
* Copy records in master's `repl_nodes` table to witness database * Copy records in master's `repl_nodes` table to witness database
* *
@@ -1187,50 +1119,29 @@ set_config_bool(PGconn *conn, const char *config_param, bool state)
* `repmgrd` after a failover event occurs * `repmgrd` after a failover event occurs
*/ */
bool bool
witness_copy_node_records(PGconn *masterconn, PGconn *witnessconn, char *cluster_name) copy_configuration(PGconn *masterconn, PGconn *witnessconn, char *cluster_name)
{ {
char sqlquery[MAXLEN]; char sqlquery[MAXLEN];
PGresult *res; PGresult *res;
int i; int i;
begin_transaction(witnessconn);
/* Defer constraints */
sqlquery_snprintf(sqlquery, "SET CONSTRAINTS ALL DEFERRED;");
log_verbose(LOG_DEBUG, "witness_copy_node_records():\n%s\n", sqlquery);
res = PQexec(witnessconn, sqlquery);
if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
{
log_err(_("Unable to defer constraints:\n%s\n"),
PQerrorMessage(witnessconn));
rollback_transaction(witnessconn);
return false;
}
/* Truncate existing records */
sqlquery_snprintf(sqlquery, "TRUNCATE TABLE %s.repl_nodes", get_repmgr_schema_quoted(witnessconn)); sqlquery_snprintf(sqlquery, "TRUNCATE TABLE %s.repl_nodes", get_repmgr_schema_quoted(witnessconn));
log_verbose(LOG_DEBUG, "witness_copy_node_records():\n%s\n", sqlquery); log_verbose(LOG_DEBUG, "copy_configuration():\n%s\n", sqlquery);
res = PQexec(witnessconn, sqlquery); res = PQexec(witnessconn, sqlquery);
if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
{ {
log_err(_("Unable to truncate witness servers's repl_nodes table:\n%s\n"), log_err(_("Unable to truncate witness servers's repl_nodes table:\n%s\n"),
PQerrorMessage(witnessconn)); PQerrorMessage(witnessconn));
rollback_transaction(witnessconn);
return false; return false;
} }
/* Get current records from primary */
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT id, type, upstream_node_id, name, conninfo, priority, slot_name, active " "SELECT id, type, upstream_node_id, name, conninfo, priority, slot_name FROM %s.repl_nodes",
" FROM %s.repl_nodes",
get_repmgr_schema_quoted(masterconn)); get_repmgr_schema_quoted(masterconn));
log_verbose(LOG_DEBUG, "witness_copy_node_records():\n%s\n", sqlquery); log_verbose(LOG_DEBUG, "copy_configuration():\n%s\n", sqlquery);
res = PQexec(masterconn, sqlquery); res = PQexec(masterconn, sqlquery);
if (PQresultStatus(res) != PGRES_TUPLES_OK) if (PQresultStatus(res) != PGRES_TUPLES_OK)
@@ -1238,23 +1149,20 @@ witness_copy_node_records(PGconn *masterconn, PGconn *witnessconn, char *cluster
log_err("Unable to retrieve node records from master:\n%s\n", log_err("Unable to retrieve node records from master:\n%s\n",
PQerrorMessage(masterconn)); PQerrorMessage(masterconn));
PQclear(res); PQclear(res);
rollback_transaction(witnessconn);
return false; return false;
} }
/* Insert primary records into witness table */
for (i = 0; i < PQntuples(res); i++) for (i = 0; i < PQntuples(res); i++)
{ {
bool node_record_created; bool node_record_created;
log_verbose(LOG_DEBUG, log_verbose(LOG_DEBUG,
"witness_copy_node_records(): writing node record for node %s (id: %s)\n", "copy_configuration(): writing node record for node %s (id: %s)\n",
PQgetvalue(res, i, 3), PQgetvalue(res, i, 4),
PQgetvalue(res, i, 0)); PQgetvalue(res, i, 0));
node_record_created = create_node_record(witnessconn, node_record_created = create_node_record(witnessconn,
"witness_copy_node_records", "copy_configuration",
atoi(PQgetvalue(res, i, 0)), atoi(PQgetvalue(res, i, 0)),
PQgetvalue(res, i, 1), PQgetvalue(res, i, 1),
strlen(PQgetvalue(res, i, 2)) strlen(PQgetvalue(res, i, 2))
@@ -1266,10 +1174,7 @@ witness_copy_node_records(PGconn *masterconn, PGconn *witnessconn, char *cluster
atoi(PQgetvalue(res, i, 5)), atoi(PQgetvalue(res, i, 5)),
strlen(PQgetvalue(res, i, 6)) strlen(PQgetvalue(res, i, 6))
? PQgetvalue(res, i, 6) ? PQgetvalue(res, i, 6)
: NULL, : NULL
(strcmp(PQgetvalue(res, i, 7), "t") == 0)
? true
: false
); );
if (node_record_created == false) if (node_record_created == false)
@@ -1278,16 +1183,11 @@ witness_copy_node_records(PGconn *masterconn, PGconn *witnessconn, char *cluster
log_err("Unable to copy node record to witness database\n%s\n", log_err("Unable to copy node record to witness database\n%s\n",
PQerrorMessage(witnessconn)); PQerrorMessage(witnessconn));
rollback_transaction(witnessconn);
return false; return false;
} }
} }
PQclear(res); PQclear(res);
/* And finished */
commit_transaction(witnessconn);
return true; return true;
} }
@@ -1300,7 +1200,7 @@ witness_copy_node_records(PGconn *masterconn, PGconn *witnessconn, char *cluster
* XXX we should pass the record parameters as a struct. * XXX we should pass the record parameters as a struct.
*/ */
bool bool
create_node_record(PGconn *conn, char *action, int node, char *type, int upstream_node, char *cluster_name, char *node_name, char *conninfo, int priority, char *slot_name, bool active) create_node_record(PGconn *conn, char *action, int node, char *type, int upstream_node, char *cluster_name, char *node_name, char *conninfo, int priority, char *slot_name)
{ {
char sqlquery[QUERY_STR_LEN]; char sqlquery[QUERY_STR_LEN];
char upstream_node_id[MAXLEN]; char upstream_node_id[MAXLEN];
@@ -1341,9 +1241,8 @@ create_node_record(PGconn *conn, char *action, int node, char *type, int upstrea
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"INSERT INTO %s.repl_nodes " "INSERT INTO %s.repl_nodes "
" (id, type, upstream_node_id, cluster, " " (id, type, upstream_node_id, cluster, "
" name, conninfo, slot_name, " " name, conninfo, slot_name, priority) "
" priority, active) " "VALUES (%i, '%s', %s, '%s', '%s', '%s', %s, %i) ",
"VALUES (%i, '%s', %s, '%s', '%s', '%s', %s, %i, %s) ",
get_repmgr_schema_quoted(conn), get_repmgr_schema_quoted(conn),
node, node,
type, type,
@@ -1352,8 +1251,7 @@ create_node_record(PGconn *conn, char *action, int node, char *type, int upstrea
node_name, node_name,
conninfo, conninfo,
slot_name_buf, slot_name_buf,
priority, priority);
active == true ? "TRUE" : "FALSE");
log_verbose(LOG_DEBUG, "create_node_record(): %s\n", sqlquery); log_verbose(LOG_DEBUG, "create_node_record(): %s\n", sqlquery);
@@ -1393,7 +1291,7 @@ delete_node_record(PGconn *conn, int node, char *action)
if (action != NULL) if (action != NULL)
{ {
log_verbose(LOG_DEBUG, "delete_node_record(): action is \"%s\"\n", action); log_verbose(LOG_DEBUG, "create_node_record(): action is \"%s\"\n", action);
} }
res = PQexec(conn, sqlquery); res = PQexec(conn, sqlquery);
@@ -1436,11 +1334,10 @@ create_event_record(PGconn *conn, t_configuration_options *options, int node_id,
bool success = true; bool success = true;
struct tm ts; struct tm ts;
/* /* Only attempt to write a record if a connection handle was provided.
* Only attempt to write a record if a connection handle was provided. Also check that the repmgr schema has been properly intialised - if
* Also check that the repmgr schema has been properly initialised - if not it means no configuration file was provided, which can happen with
* not it means no configuration file was provided, which can happen with e.g. `repmgr standby clone`, and we won't know which schema to write to.
* e.g. `repmgr standby clone`, and we won't know which schema to write to.
*/ */
if (conn != NULL && strcmp(repmgr_schema, DEFAULT_REPMGR_SCHEMA_PREFIX) != 0) if (conn != NULL && strcmp(repmgr_schema, DEFAULT_REPMGR_SCHEMA_PREFIX) != 0)
{ {
@@ -1489,6 +1386,7 @@ create_event_record(PGconn *conn, t_configuration_options *options, int node_id,
PQerrorMessage(conn)); PQerrorMessage(conn));
success = false; success = false;
} }
else else
{ {
@@ -1629,89 +1527,6 @@ create_event_record(PGconn *conn, t_configuration_options *options, int node_id,
} }
bool
update_node_record(PGconn *conn, char *action, int node, char *type, int upstream_node, char *cluster_name, char *node_name, char *conninfo, int priority, char *slot_name, bool active)
{
char sqlquery[QUERY_STR_LEN];
char upstream_node_id[MAXLEN];
char slot_name_buf[MAXLEN];
PGresult *res;
/* XXX this segment copied from create_node_record() */
if (upstream_node == NO_UPSTREAM_NODE)
{
/*
* No explicit upstream node id provided for standby - attempt to
* get primary node id
*/
if (strcmp(type, "standby") == 0)
{
int primary_node_id = get_master_node_id(conn, cluster_name);
maxlen_snprintf(upstream_node_id, "%i", primary_node_id);
}
else
{
maxlen_snprintf(upstream_node_id, "%s", "NULL");
}
}
else
{
maxlen_snprintf(upstream_node_id, "%i", upstream_node);
}
if (slot_name != NULL && slot_name[0])
{
maxlen_snprintf(slot_name_buf, "'%s'", slot_name);
}
else
{
maxlen_snprintf(slot_name_buf, "%s", "NULL");
}
/* XXX convert to placeholder query */
sqlquery_snprintf(sqlquery,
"UPDATE %s.repl_nodes SET "
" type = '%s', "
" upstream_node_id = %s, "
" cluster = '%s', "
" name = '%s', "
" conninfo = '%s', "
" slot_name = %s, "
" priority = %i, "
" active = %s "
" WHERE id = %i ",
get_repmgr_schema_quoted(conn),
type,
upstream_node_id,
cluster_name,
node_name,
conninfo,
slot_name_buf,
priority,
active == true ? "TRUE" : "FALSE",
node);
log_verbose(LOG_DEBUG, "update_node_record(): %s\n", sqlquery);
if (action != NULL)
{
log_verbose(LOG_DEBUG, "update_node_record(): action is \"%s\"\n", action);
}
res = PQexec(conn, sqlquery);
if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
{
log_err(_("Unable to update node record\n%s\n"),
PQerrorMessage(conn));
PQclear(res);
return false;
}
PQclear(res);
return true;
}
/* /*
* Update node record following change of status * Update node record following change of status
* (e.g. inactive primary converted to standby) * (e.g. inactive primary converted to standby)
@@ -1797,12 +1612,12 @@ int
get_node_record(PGconn *conn, char *cluster, int node_id, t_node_info *node_info) get_node_record(PGconn *conn, char *cluster, int node_id, t_node_info *node_info)
{ {
char sqlquery[QUERY_STR_LEN]; char sqlquery[QUERY_STR_LEN];
int result; PGresult *res;
int ntuples;
sqlquery_snprintf( sqlquery_snprintf(
sqlquery, sqlquery,
"SELECT id, type, upstream_node_id, name, conninfo, " "SELECT id, type, upstream_node_id, name, conninfo, slot_name, priority, active"
" slot_name, priority, active"
" FROM %s.repl_nodes " " FROM %s.repl_nodes "
" WHERE cluster = '%s' " " WHERE cluster = '%s' "
" AND id = %i", " AND id = %i",
@@ -1812,51 +1627,6 @@ get_node_record(PGconn *conn, char *cluster, int node_id, t_node_info *node_info
log_verbose(LOG_DEBUG, "get_node_record():\n%s\n", sqlquery); log_verbose(LOG_DEBUG, "get_node_record():\n%s\n", sqlquery);
result = _get_node_record(conn, cluster, sqlquery, node_info);
if (result == 0)
{
log_verbose(LOG_DEBUG, "get_node_record(): no record found for node %i\n", node_id);
}
return result;
}
int
get_node_record_by_name(PGconn *conn, char *cluster, const char *node_name, t_node_info *node_info)
{
char sqlquery[QUERY_STR_LEN];
int result;
sqlquery_snprintf(
sqlquery,
"SELECT id, type, upstream_node_id, name, conninfo, slot_name, priority, active"
" FROM %s.repl_nodes "
" WHERE cluster = '%s' "
" AND name = '%s'",
get_repmgr_schema_quoted(conn),
cluster,
node_name);
log_verbose(LOG_DEBUG, "get_node_record_by_name():\n%s\n", sqlquery);
result = _get_node_record(conn, cluster, sqlquery, node_info);
if (result == 0)
{
log_verbose(LOG_DEBUG, "get_node_record(): no record found for node %s\n", node_name);
}
return result;
}
static int
_get_node_record(PGconn *conn, char *cluster, char *sqlquery, t_node_info *node_info)
{
int ntuples;
PGresult *res;
res = PQexec(conn, sqlquery); res = PQexec(conn, sqlquery);
if (PQresultStatus(res) != PGRES_TUPLES_OK) if (PQresultStatus(res) != PGRES_TUPLES_OK)
{ {
@@ -1867,6 +1637,7 @@ _get_node_record(PGconn *conn, char *cluster, char *sqlquery, t_node_info *node_
if (ntuples == 0) if (ntuples == 0)
{ {
log_verbose(LOG_DEBUG, "get_node_record(): no record found for node %i\n", node_id);
return 0; return 0;
} }
@@ -1887,9 +1658,6 @@ _get_node_record(PGconn *conn, char *cluster, char *sqlquery, t_node_info *node_
} }
int int
get_node_replication_state(PGconn *conn, char *node_name, char *output) get_node_replication_state(PGconn *conn, char *node_name, char *output)
{ {

View File

@@ -1,6 +1,5 @@
/* /*
* dbutils.h * dbutils.h
*
* Copyright (c) 2ndQuadrant, 2010-2016 * Copyright (c) 2ndQuadrant, 2010-2016
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@@ -22,7 +21,6 @@
#define _REPMGR_DBUTILS_H_ #define _REPMGR_DBUTILS_H_
#include "access/xlogdefs.h" #include "access/xlogdefs.h"
#include "pqexpbuffer.h"
#include "config.h" #include "config.h"
#include "strutil.h" #include "strutil.h"
@@ -54,6 +52,18 @@ typedef struct s_node_info
} t_node_info; } t_node_info;
/*
* Struct to store replication slot information
*/
typedef struct s_replication_slot
{
char slot_name[MAXLEN];
char slot_type[MAXLEN];
bool active;
} t_replication_slot;
#define T_NODE_INFO_INITIALIZER { \ #define T_NODE_INFO_INITIALIZER { \
NODE_NOT_FOUND, \ NODE_NOT_FOUND, \
NO_UPSTREAM_NODE, \ NO_UPSTREAM_NODE, \
@@ -68,27 +78,13 @@ typedef struct s_node_info
InvalidXLogRecPtr \ InvalidXLogRecPtr \
} }
/*
* Struct to store replication slot information
*/
typedef struct s_replication_slot
{
char slot_name[MAXLEN];
char slot_type[MAXLEN];
bool active;
} t_replication_slot;
extern char repmgr_schema[MAXLEN];
PGconn *_establish_db_connection(const char *conninfo, PGconn *_establish_db_connection(const char *conninfo,
const bool exit_on_error, const bool exit_on_error,
const bool log_notice, const bool log_notice);
const bool verbose_only);
PGconn *establish_db_connection(const char *conninfo, PGconn *establish_db_connection(const char *conninfo,
const bool exit_on_error); const bool exit_on_error);
PGconn *establish_db_connection_quiet(const char *conninfo); PGconn *test_db_connection(const char *conninfo,
PGconn *test_db_connection(const char *conninfo); const bool exit_on_error);
PGconn *establish_db_connection_by_params(const char *keywords[], PGconn *establish_db_connection_by_params(const char *keywords[],
const char *values[], const char *values[],
const bool exit_on_error); const bool exit_on_error);
@@ -119,18 +115,16 @@ int wait_connection_availability(PGconn *conn, long long timeout);
bool cancel_query(PGconn *conn, int timeout); bool cancel_query(PGconn *conn, int timeout);
char *get_repmgr_schema(void); char *get_repmgr_schema(void);
char *get_repmgr_schema_quoted(PGconn *conn); char *get_repmgr_schema_quoted(PGconn *conn);
bool create_replication_slot(PGconn *conn, char *slot_name, int server_version_num, PQExpBufferData *error_msg); bool create_replication_slot(PGconn *conn, char *slot_name);
int get_slot_record(PGconn *conn, char *slot_name, t_replication_slot *record); int get_slot_record(PGconn *conn, char *slot_name, t_replication_slot *record);
bool drop_replication_slot(PGconn *conn, char *slot_name); bool drop_replication_slot(PGconn *conn, char *slot_name);
bool start_backup(PGconn *conn, char *first_wal_segment, bool fast_checkpoint); bool start_backup(PGconn *conn, char *first_wal_segment, bool fast_checkpoint);
bool stop_backup(PGconn *conn, char *last_wal_segment); bool stop_backup(PGconn *conn, char *last_wal_segment);
bool set_config_bool(PGconn *conn, const char *config_param, bool state); bool set_config_bool(PGconn *conn, const char *config_param, bool state);
bool witness_copy_node_records(PGconn *masterconn, PGconn *witnessconn, char *cluster_name); bool copy_configuration(PGconn *masterconn, PGconn *witnessconn, char *cluster_name);
bool create_node_record(PGconn *conn, char *action, int node, char *type, int upstream_node, char *cluster_name, char *node_name, char *conninfo, int priority, char *slot_name, bool active); bool create_node_record(PGconn *conn, char *action, int node, char *type, int upstream_node, char *cluster_name, char *node_name, char *conninfo, int priority, char *slot_name);
bool delete_node_record(PGconn *conn, int node, char *action); bool delete_node_record(PGconn *conn, int node, char *action);
int get_node_record(PGconn *conn, char *cluster, int node_id, t_node_info *node_info); int get_node_record(PGconn *conn, char *cluster, int node_id, t_node_info *node_info);
int get_node_record_by_name(PGconn *conn, char *cluster, const char *node_name, t_node_info *node_info);
bool update_node_record(PGconn *conn, char *action, int node, char *type, int upstream_node, char *cluster_name, char *node_name, char *conninfo, int priority, char *slot_name, bool active);
bool update_node_record_status(PGconn *conn, char *cluster_name, int this_node_id, char *type, int upstream_node_id, bool active); bool update_node_record_status(PGconn *conn, char *cluster_name, int this_node_id, char *type, int upstream_node_id, bool active);
bool update_node_record_set_upstream(PGconn *conn, char *cluster_name, int this_node_id, int new_upstream_node_id); bool update_node_record_set_upstream(PGconn *conn, char *cluster_name, int this_node_id, int new_upstream_node_id);
bool create_event_record(PGconn *conn, t_configuration_options *options, int node_id, char *event, bool successful, char *details); bool create_event_record(PGconn *conn, t_configuration_options *options, int node_id, char *event, bool successful, char *details);
@@ -139,4 +133,3 @@ int get_node_replication_state(PGconn *conn, char *node_name, char *output)
t_server_type parse_node_type(const char *type); t_server_type parse_node_type(const char *type);
int get_data_checksum_version(const char *data_directory); int get_data_checksum_version(const char *data_directory);
#endif #endif

View File

@@ -1,9 +1,9 @@
Package: repmgr-auto Package: repmgr-auto
Version: 3.2dev Version: 3.0.1
Section: database Section: database
Priority: optional Priority: optional
Architecture: all Architecture: all
Depends: rsync, postgresql-9.3 | postgresql-9.4 | postgresql-9.5 Depends: rsync, postgresql-9.3 | postgresql-9.4
Maintainer: Self built package <user@localhost> Maintainer: Self built package <user@localhost>
Description: PostgreSQL replication setup, magament and monitoring Description: PostgreSQL replication setup, magament and monitoring
has two main executables has two main executables

194
dirmod.c
View File

@@ -1,194 +0,0 @@
/*
*
* dirmod.c
* directory handling functions
*
* Copyright (C) 2ndQuadrant, 2010-2016
*
* Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "postgres_fe.h"
/* Don't modify declarations in system headers */
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
/*
* pgfnames
*
* return a list of the names of objects in the argument directory. Caller
* must call pgfnames_cleanup later to free the memory allocated by this
* function.
*/
char **
pgfnames(const char *path)
{
DIR *dir;
struct dirent *file;
char **filenames;
int numnames = 0;
int fnsize = 200; /* enough for many small dbs */
dir = opendir(path);
if (dir == NULL)
{
return NULL;
}
filenames = (char **) palloc(fnsize * sizeof(char *));
while (errno = 0, (file = readdir(dir)) != NULL)
{
if (strcmp(file->d_name, ".") != 0 && strcmp(file->d_name, "..") != 0)
{
if (numnames + 1 >= fnsize)
{
fnsize *= 2;
filenames = (char **) repalloc(filenames,
fnsize * sizeof(char *));
}
filenames[numnames++] = pstrdup(file->d_name);
}
}
if (errno)
{
fprintf(stderr, _("could not read directory \"%s\": %s\n"),
path, strerror(errno));
}
filenames[numnames] = NULL;
if (closedir(dir))
{
fprintf(stderr, _("could not close directory \"%s\": %s\n"),
path, strerror(errno));
}
return filenames;
}
/*
* pgfnames_cleanup
*
* deallocate memory used for filenames
*/
void
pgfnames_cleanup(char **filenames)
{
char **fn;
for (fn = filenames; *fn; fn++)
pfree(*fn);
pfree(filenames);
}
/*
* rmtree
*
* Delete a directory tree recursively.
* Assumes path points to a valid directory.
* Deletes everything under path.
* If rmtopdir is true deletes the directory too.
* Returns true if successful, false if there was any problem.
* (The details of the problem are reported already, so caller
* doesn't really have to say anything more, but most do.)
*/
bool
rmtree(const char *path, bool rmtopdir)
{
bool result = true;
char pathbuf[MAXPGPATH];
char **filenames;
char **filename;
struct stat statbuf;
/*
* we copy all the names out of the directory before we start modifying
* it.
*/
filenames = pgfnames(path);
if (filenames == NULL)
return false;
/* now we have the names we can start removing things */
for (filename = filenames; *filename; filename++)
{
snprintf(pathbuf, MAXPGPATH, "%s/%s", path, *filename);
/*
* It's ok if the file is not there anymore; we were just about to
* delete it anyway.
*
* This is not an academic possibility. One scenario where this
* happens is when bgwriter has a pending unlink request for a file in
* a database that's being dropped. In dropdb(), we call
* ForgetDatabaseFsyncRequests() to flush out any such pending unlink
* requests, but because that's asynchronous, it's not guaranteed that
* the bgwriter receives the message in time.
*/
if (lstat(pathbuf, &statbuf) != 0)
{
if (errno != ENOENT)
{
result = false;
}
continue;
}
if (S_ISDIR(statbuf.st_mode))
{
/* call ourselves recursively for a directory */
if (!rmtree(pathbuf, true))
{
/* we already reported the error */
result = false;
}
}
else
{
if (unlink(pathbuf) != 0)
{
if (errno != ENOENT)
{
result = false;
}
}
}
}
if (rmtopdir)
{
if (rmdir(path) != 0)
{
result = false;
}
}
pgfnames_cleanup(filenames);
return result;
}

View File

@@ -1,23 +0,0 @@
/*
* dirmod.h
* Copyright (c) 2ndQuadrant, 2010-2016
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef _DIRMOD_H_
#define _DIRMOD_H_
#endif

View File

@@ -1,75 +0,0 @@
repmgrd's failover algorithm
============================
When implementing automatic failover, there are two factors which are critical in
ensuring the desired result is achieved:
- has the master node genuinely failed?
- which is the best node to promote to the new master?
This document outlines repmgrd's decision-making process during automatic failover
for standbys directly connected to the master node.
Master node failure detection
-----------------------------
If a `repmgrd` instance running on a PostgreSQL standby node is unable to connect to
the master node, this doesn't neccesarily mean that the master is down and a
failover is required. Factors such as network connectivity issues could mean that
even though the standby node is isolated, the replication cluster as a whole
is functioning correctly, and promoting the standby without further verification
could result in a "split-brain" situation.
In the event that `repmgrd` is unable to connect to the master node, it will attempt
to reconnect to the master server several times (as defined by the `reconnect_attempts`
parameter in `repmgr.conf`), with reconnection attempts occuring at the interval
specified by `reconnect_interval`. This happens to verify that the master is definitively
not accessible (e.g. that connection was not lost due to a brief network glitch).
Appropriate values for these settings will depend very much on the replication
cluster environment. There will necessarily be a trade-off between the time it
takes to assume the master is not reachable, and the reliability of that conclusion.
A standby in a different physical location to the master will probably need a longer
check interval to rule out possible network issues, whereas one located in the same
rack with a direct connection between servers could perform the check very quickly.
Note that it's possible the master comes back online after this point is reached,
but before a new master has been selected; in this case it will be noticed
during the selection of a new master and no actual failover will take place.
Promotion candidate selection
-----------------------------
Once `repmgrd` has decided the master is definitively unreachable, following checks
will be carried out:
* attempts to connect to all other nodes in the cluster (including the witness
node, if defined) to establish the state of the cluster, including their
current LSN
* If less than half of the nodes are visible (from the viewpoint
of this node), `repmgrd` will not take any further action. This is to ensure that
e.g. if a replication cluster is spread over multiple data centres, a split-brain
situation does not occur if there is a network failure between datacentres. Note
that if nodes are split evenly between data centres, a witness server can be
used to establish the "majority" daat centre.
* `repmgrd` polls all visible servers and waits for each node to return a valid LSN;
it updates the LSN previously stored for this node if it has increased since
the initial check
* once all LSNs have been retrieved, `repmgrd` will check for the highest LSN; if
its own node has the highest LSN, it will attempt to promote itself (using the
command defined in `promote_command` in `repmgr.conf`. Note that if using
`repmgr standby promote` as the promotion command, and the original master becomes available
before the promotion takes effect, `repmgr` will return an error and no promotion
will take place, and `repmgrd` will resume monitoring as usual.
* if the node is not the promotion candidate, `repmgrd` will execute the
`follow_command` defined in `repmgr.conf`. If using `repmgr standby follow` here,
`repmgr` will attempt to detect the new master node and attach to that.

View File

@@ -1,152 +0,0 @@
Fencing a failed master node with repmgrd and pgbouncer
=======================================================
With automatic failover, it's essential to ensure that a failed master
remains inaccessible to your application, even if it comes back online
again, to avoid a split-brain situation.
By using `pgbouncer` together with `repmgrd`, it's possible to combine
automatic failover with a process to isolate the failed master from
your application and ensure that all connections which should go to
the master are directed there smoothly without having to reconfigure
your application. (Note that as a connection pooler, `pgbouncer` can
benefit your application in other ways, but those are beyond the scope
of this document).
* * *
> *WARNING*: automatic failover is tricky to get right. This document
> demonstrates one possible implementation method, however you should
> carefully configure and test any setup to suit the needs of your own
> replication cluster/application.
* * *
In a failover situation, `repmgrd` promotes a standby to master by executing
the command defined in `promote_command`. Normally this would be something like:
repmgr standby promote -f /etc/repmgr.conf
By wrapping this in a custom script which adjusts the `pgbouncer` configuration
on all nodes, it's possible to fence the failed master and redirect write
connections to the new master.
The script consists of three sections:
* commands to pause `pgbouncer` on all nodes
* the promotion command itself
* commands to reconfigure and restart `pgbouncer` on all nodes
Note that it requires password-less SSH access between all nodes to be able to
update the `pgbouncer` configuration files.
For the purposes of this demonstration, we'll assume there are 3 nodes (master
and two standbys), with `pgbouncer` listening on port 6432 handling connections
to a database called `appdb`. The `postgres` system user must have write
access to the `pgbouncer` configuration files on all nodes. We'll assume
there's a main `pgbouncer` configuration file, `/etc/pgbouncer.ini`, which uses
the `%include` directive (available from PgBouncer 1.6) to include a separate
configuration file, `/etc/pgbouncer.database.ini`, which will be modified by
`repmgr`.
`/etc/pgbouncer.ini` should look something like this:
[pgbouncer]
logfile = /var/log/pgbouncer/pgbouncer.log
pidfile = /var/run/pgbouncer/pgbouncer.pid
listen_addr = *
listen_port = 6532
unix_socket_dir = /tmp
auth_type = trust
auth_file = /etc/pgbouncer.auth
admin_users = postgres
stats_users = postgres
pool_mode = transaction
max_client_conn = 100
default_pool_size = 20
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3
log_connections = 1
log_disconnections = 1
log_pooler_errors = 1
%include /etc/pgbouncer.database.ini
The actual script is as follows; adjust the configurable items as appropriate:
`/var/lib/postgres/repmgr/promote.sh`
#!/usr/bin/env bash
set -u
set -e
# Configurable items
PGBOUNCER_HOSTS="node1 node2 node3"
PGBOUNCER_DATABASE_INI="/etc/pgbouncer.database.ini"
PGBOUNCER_DATABASE="appdb"
PGBOUNCER_PORT=6432
REPMGR_DB="repmgr"
REPMGR_USER="repmgr"
REPMGR_SCHEMA="repmgr_test"
# 1. Pause running pgbouncer instances
for HOST in $PGBOUNCER_HOSTS
do
psql -t -c "pause" -h $HOST -p $PGBOUNCER_PORT -U postgres pgbouncer
done
# 2. Promote this node from standby to master
repmgr standby promote -f /etc/repmgr.conf
# 3. Reconfigure pgbouncer instances
PGBOUNCER_DATABASE_INI_NEW="/tmp/pgbouncer.database.ini"
for HOST in $PGBOUNCER_HOSTS
do
# Recreate the pgbouncer config file
echo -e "[databases]\n" > $PGBOUNCER_DATABASE_INI_NEW
psql -d $REPMGR_DB -U $REPMGR_USER -t -A \
-c "SELECT '${PGBOUNCER_DATABASE}-rw= ' || conninfo || ' application_name=pgbouncer_${HOST}' \
FROM ${REPMGR_SCHEMA}.repl_nodes \
WHERE active = TRUE AND type='master'" >> $PGBOUNCER_DATABASE_INI_NEW
psql -d $REPMGR_DB -U $REPMGR_USER -t -A \
-c "SELECT '${PGBOUNCER_DATABASE}-ro= ' || conninfo || ' application_name=pgbouncer_${HOST}' \
FROM $REPMGR_SCHEMA.repl_nodes \
WHERE node_name='${HOST}'" >> $PGBOUNCER_DATABASE_INI_NEW
rsync $PGBOUNCER_DATABASE_INI_NEW $HOST:$PGBOUNCER_DATABASE_INI
psql -tc "reload" -h $HOST -p $PGBOUNCER_PORT -U postgres pgbouncer
psql -tc "resume" -h $HOST -p $PGBOUNCER_PORT -U postgres pgbouncer
done
# Clean up generated file
rm $PGBOUNCER_DATABASE_INI_NEW
echo "Reconfiguration of pgbouncer complete"
Script and template file should be installed on each node where
`repmgrd` is running.
Finally, set `promote_command` in `repmgr.conf` on each node to
point to the custom promote script:
promote_command=/var/lib/postgres/repmgr/promote.sh
and reload/restart any running `repmgrd` instances for the changes to take
effect.

View File

@@ -29,6 +29,7 @@
#define ERR_DB_CON 6 #define ERR_DB_CON 6
#define ERR_DB_QUERY 7 #define ERR_DB_QUERY 7
#define ERR_PROMOTED 8 #define ERR_PROMOTED 8
#define ERR_BAD_PASSWORD 9
#define ERR_STR_OVERFLOW 10 #define ERR_STR_OVERFLOW 10
#define ERR_FAILOVER_FAIL 11 #define ERR_FAILOVER_FAIL 11
#define ERR_BAD_SSH 12 #define ERR_BAD_SSH 12
@@ -36,10 +37,5 @@
#define ERR_BAD_BASEBACKUP 14 #define ERR_BAD_BASEBACKUP 14
#define ERR_INTERNAL 15 #define ERR_INTERNAL 15
#define ERR_MONITORING_FAIL 16 #define ERR_MONITORING_FAIL 16
#define ERR_BAD_BACKUP_LABEL 17
#define ERR_SWITCHOVER_FAIL 18
#define ERR_BARMAN 19
#define ERR_REGISTRATION_SYNC 20
#endif /* _ERRCODE_H_ */ #endif /* _ERRCODE_H_ */

49
log.c
View File

@@ -40,21 +40,15 @@
/* #define REPMGR_DEBUG */ /* #define REPMGR_DEBUG */
static int detect_log_facility(const char *facility); static int detect_log_facility(const char *facility);
static void _stderr_log_with_level(const char *level_name, int level, const char *fmt, va_list ap) static void _stderr_log_with_level(const char *level_name, int level, const char *fmt, va_list ap);
__attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 0)));
int log_type = REPMGR_STDERR; int log_type = REPMGR_STDERR;
int log_level = LOG_NOTICE; int log_level = LOG_NOTICE;
int last_log_level = LOG_NOTICE; int last_log_level = LOG_NOTICE;
int verbose_logging = false; int verbose_logging = false;
int terse_logging = false; int terse_logging = false;
/*
* Global variable to be set by the main application to ensure any log output
* emitted before logger_init is called, is output in the correct format
*/
int logger_output_mode = OM_DAEMON;
extern void void
stderr_log_with_level(const char *level_name, int level, const char *fmt, ...) stderr_log_with_level(const char *level_name, int level, const char *fmt, ...)
{ {
va_list arglist; va_list arglist;
@@ -67,7 +61,9 @@ stderr_log_with_level(const char *level_name, int level, const char *fmt, ...)
static void static void
_stderr_log_with_level(const char *level_name, int level, const char *fmt, va_list ap) _stderr_log_with_level(const char *level_name, int level, const char *fmt, va_list ap)
{ {
char buf[100]; time_t t;
struct tm *tm;
char buff[100];
/* /*
* Store the requested level so that if there's a subsequent * Store the requested level so that if there's a subsequent
@@ -77,21 +73,10 @@ _stderr_log_with_level(const char *level_name, int level, const char *fmt, va_li
if (log_level >= level) if (log_level >= level)
{ {
time(&t);
/* Format log line prefix with timestamp if in daemon mode */ tm = localtime(&t);
if (logger_output_mode == OM_DAEMON) strftime(buff, 100, "[%Y-%m-%d %H:%M:%S]", tm);
{ fprintf(stderr, "%s [%s] ", buff, level_name);
time_t t;
struct tm *tm;
time(&t);
tm = localtime(&t);
strftime(buf, 100, "[%Y-%m-%d %H:%M:%S]", tm);
fprintf(stderr, "%s [%s] ", buf, level_name);
}
else
{
fprintf(stderr, "%s: ", level_name);
}
vfprintf(stderr, fmt, ap); vfprintf(stderr, fmt, ap);
@@ -156,7 +141,7 @@ log_verbose(int level, const char *fmt, ...)
bool bool
logger_init(t_configuration_options *opts, const char *ident) logger_init(t_configuration_options * opts, const char *ident)
{ {
char *level = opts->loglevel; char *level = opts->loglevel;
char *facility = opts->logfacility; char *facility = opts->logfacility;
@@ -190,13 +175,6 @@ logger_init(t_configuration_options *opts, const char *ident)
stderr_log_warning(_("Invalid log level \"%s\" (available values: DEBUG, INFO, NOTICE, WARNING, ERR, ALERT, CRIT or EMERG)\n"), level); stderr_log_warning(_("Invalid log level \"%s\" (available values: DEBUG, INFO, NOTICE, WARNING, ERR, ALERT, CRIT or EMERG)\n"), level);
} }
/*
* STDERR only logging requested - finish here without setting up any further
* logging facility.
*/
if (logger_output_mode == OM_COMMAND_LINE)
return true;
if (facility && *facility) if (facility && *facility)
{ {
@@ -257,10 +235,9 @@ logger_init(t_configuration_options *opts, const char *ident)
stderr_log_notice(_("Redirecting logging output to '%s'\n"), opts->logfile); stderr_log_notice(_("Redirecting logging output to '%s'\n"), opts->logfile);
fd = freopen(opts->logfile, "a", stderr); fd = freopen(opts->logfile, "a", stderr);
/* /* It's possible freopen() may still fail due to e.g. a race condition;
* It's possible freopen() may still fail due to e.g. a race condition; as it's not feasible to restore stderr after a failed freopen(),
* as it's not feasible to restore stderr after a failed freopen(), we'll write to stdout as a last resort.
* we'll write to stdout as a last resort.
*/ */
if (fd == NULL) if (fd == NULL)
{ {

16
log.h
View File

@@ -25,10 +25,7 @@
#define REPMGR_SYSLOG 1 #define REPMGR_SYSLOG 1
#define REPMGR_STDERR 2 #define REPMGR_STDERR 2
#define OM_COMMAND_LINE 1 void
#define OM_DAEMON 2
extern void
stderr_log_with_level(const char *level_name, int level, const char *fmt,...) stderr_log_with_level(const char *level_name, int level, const char *fmt,...)
__attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 4))); __attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 4)));
@@ -126,15 +123,10 @@ bool logger_shutdown(void);
void logger_set_verbose(void); void logger_set_verbose(void);
void logger_set_terse(void); void logger_set_terse(void);
void log_hint(const char *fmt, ...) void log_hint(const char *fmt, ...);
__attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2))); void log_verbose(int level, const char *fmt, ...);
void log_verbose(int level, const char *fmt, ...)
__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
extern int log_type; extern int log_type;
extern int log_level; extern int log_level;
extern int verbose_logging;
extern int terse_logging;
extern int logger_output_mode;
#endif /* _REPMGR_LOG_H_ */ #endif

5722
repmgr.c

File diff suppressed because it is too large Load Diff

View File

@@ -15,29 +15,21 @@
# schema (pattern: "repmgr_{cluster}"); while this name will be quoted # schema (pattern: "repmgr_{cluster}"); while this name will be quoted
# to preserve case, we recommend using lower case and avoiding whitespace # to preserve case, we recommend using lower case and avoiding whitespace
# to facilitate easier querying of the repmgr views and tables. # to facilitate easier querying of the repmgr views and tables.
#cluster=example_cluster cluster=example_cluster
# Node ID and name # Node ID and name
# (Note: we recommend to avoid naming nodes after their initial # (Note: we recommend to avoid naming nodes after their initial
# replication function, as this will cause confusion when e.g. # replication funcion, as this will cause confusion when e.g.
# "standby2" is promoted to primary) # "standby2" is promoted to primary)
#node=2 # a unique integer node=2 # a unique integer
#node_name=node2 # an arbitrary (but unique) string; we recommend using node_name=node2 # an arbitrary (but unique) string; we recommend using
# the server's hostname or another identifier unambiguously # the server's hostname or another identifier unambiguously
# associated with the server to avoid confusion # associated with the server to avoid confusion
# Database connection information as a conninfo string # Database connection information as a conninfo string
# This must be accessible to all servers in the cluster; for details see: # This must be accessible to all servers in the cluster; for details see:
# # http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
# https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING conninfo='host=192.168.204.104 dbname=repmgr_db user=repmgr_usr'
#
#conninfo='host=192.168.204.104 dbname=repmgr user=repmgr'
#
# If repmgrd is in use, consider explicitly setting `connect_timeout` in the
# conninfo string to determine the length of time which elapses before
# a network connection attempt is abandoned; for details see:
#
# https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNECT-CONNECT-TIMEOUT
# Optional configuration items # Optional configuration items
# ============================ # ============================
@@ -45,16 +37,15 @@
# Replication settings # Replication settings
# --------------------- # ---------------------
# When using cascading replication, a standby can connect to another # when using cascading replication and a standby is to be connected to an
# upstream standby node which is specified by setting 'upstream_node'. # upstream standby, specify that node's ID with 'upstream_node'. The node
# In that case, the upstream node must exist before the new standby # must exist before the new standby can be registered. If a standby is
# can be registered. If 'upstream_node' is not set, then the standby # to connect directly to a primary node, this parameter is not required.
# will connect directly to the primary node. upstream_node=1
#upstream_node=1
# use physical replication slots - PostgreSQL 9.4 and later only # use physical replication slots - PostgreSQL 9.4 and later only
# (default: 0) # (default: 0)
#use_replication_slots=0 use_replication_slots=0
# NOTE: 'max_replication_slots' should be configured for at least the # NOTE: 'max_replication_slots' should be configured for at least the
# number of standbys which will connect to the primary. # number of standbys which will connect to the primary.
@@ -64,27 +55,15 @@
# Log level: possible values are DEBUG, INFO, NOTICE, WARNING, ERR, ALERT, CRIT or EMERG # Log level: possible values are DEBUG, INFO, NOTICE, WARNING, ERR, ALERT, CRIT or EMERG
# (default: NOTICE) # (default: NOTICE)
#loglevel=NOTICE loglevel=NOTICE
# Note that logging facility settings will only apply to `repmgrd` by default;
# `repmgr` will always write to STDERR unless the switch `--log-to-file` is
# supplied, in which case it will log to the same destination as `repmgrd`.
# This is mainly intended for those cases when `repmgr` is executed directly
# by `repmgrd`.
# Logging facility: possible values are STDERR or - for Syslog integration - one of LOCAL0, LOCAL1, ..., LOCAL7, USER # Logging facility: possible values are STDERR or - for Syslog integration - one of LOCAL0, LOCAL1, ..., LOCAL7, USER
# (default: STDERR) # (default: STDERR)
#logfacility=STDERR logfacility=STDERR
# stderr can be redirected to an arbitrary file: # stderr can be redirected to an arbitrary file:
# #
#logfile='/var/log/repmgr/repmgr.log' logfile='/var/log/repmgr/repmgr.log'
# By default only repmgrd log output will be written to a file,
# if defined in "logfile"
# enable this to restore old behaviour where output from the repmgr
# client will be written to the logfile too
#log_repmgr_to_file = 0
# event notifications can be passed to an arbitrary external program # event notifications can be passed to an arbitrary external program
# together with the following parameters: # together with the following parameters:
@@ -98,12 +77,12 @@
# the values provided for "%t" and "%d" will probably contain spaces, # the values provided for "%t" and "%d" will probably contain spaces,
# so should be quoted in the provided command configuration, e.g.: # so should be quoted in the provided command configuration, e.g.:
# #
#event_notification_command='/path/to/some/script %n %e %s "%t" "%d"' event_notification_command='/path/to/some/script %n %e %s "%t" "%d"'
# By default, all notifications will be passed; the notification types # By default, all notifications will be passed; the notification types
# can be filtered to explicitly named ones: # can be filtered to explicitly named ones:
# #
#event_notifications=master_register,standby_register,witness_create event_notifications=master_register,standby_register,witness_create
# Environment/command settings # Environment/command settings
@@ -111,45 +90,17 @@
# path to PostgreSQL binary directory (location of pg_ctl, pg_basebackup etc.) # path to PostgreSQL binary directory (location of pg_ctl, pg_basebackup etc.)
# (if not provided, defaults to system $PATH) # (if not provided, defaults to system $PATH)
#pg_bindir=/usr/bin/ pg_bindir=/usr/bin/
#
# Debian/Ubuntu users: you will probably need to set this to the directory
# where `pg_ctl` is located, e.g. /usr/lib/postgresql/9.5/bin/
# service control commands
#
# repmgr provides options to override the default pg_ctl commands
# used to stop, start, restart, reload and promote the PostgreSQL cluster
#
# NOTE: These commands must be runnable on remote nodes as well for switchover
# to function correctly.
#
# If you use sudo, the user repmgr runs as (usually 'postgres') must have
# passwordless sudo access to execute the command
#
# For example, to use systemd, you may use the following configuration:
#
# # this is required when running sudo over ssh without -t:
# Defaults:postgres !requiretty
# postgres ALL = NOPASSWD: /usr/bin/systemctl stop postgresql-9.5, \
# /usr/bin/systemctl start postgresql-9.5, \
# /usr/bin/systemctl restart postgresql-9.5
#
# service_start_command = systemctl start postgresql-9.5
# service_stop_command = systemctl stop postgresql-9.5
# service_restart_command = systemctl restart postgresql-9.5
# service_reload_command = pg_ctlcluster 9.5 main reload
# service_promote_command = pg_ctlcluster 9.5 main promote
# external command options # external command options
#rsync_options=--archive --checksum --compress --progress --rsh="ssh -o \"StrictHostKeyChecking no\"" rsync_options=--archive --checksum --compress --progress --rsh="ssh -o \"StrictHostKeyChecking no\""
#ssh_options=-o "StrictHostKeyChecking no" ssh_options=-o "StrictHostKeyChecking no"
# external command arguments. Values shown are examples. # external command arguments. Values shown are examples.
#pg_ctl_options='-s' pg_ctl_options='-s'
#pg_basebackup_options='--xlog-method=s' pg_basebackup_options='--xlog-method=s'
# Standby clone settings # Standby clone settings
@@ -161,10 +112,6 @@
# #
# tablespace_mapping=/path/to/original/tablespace=/path/to/new/tablespace # tablespace_mapping=/path/to/original/tablespace=/path/to/new/tablespace
# You can specify a restore_command to be used in the recovery.conf that
# will be placed in the cloned standby
#
# restore_command = cp /path/to/archived/wals/%f %p
# Failover settings (repmgrd) # Failover settings (repmgrd)
# --------------------------- # ---------------------------
@@ -172,40 +119,30 @@
# These settings are only applied when repmgrd is running. Values shown # These settings are only applied when repmgrd is running. Values shown
# are defaults. # are defaults.
# monitoring interval in seconds; default is 2 # Number of seconds to wait for a response from the primary server before
#monitor_interval_secs=2 # deciding it has failed.
# Maximum number of seconds to wait for a response from the primary server master_response_timeout=60
# before deciding it has failed.
#master_response_timeout=60
# Number of attempts at what interval (in seconds) to try and # Number of attempts at what interval (in seconds) to try and
# connect to a server to establish its status (e.g. master # connect to a server to establish its status (e.g. master
# during failover) # during failover)
#reconnect_attempts=6 reconnect_attempts=6
#reconnect_interval=10 reconnect_interval=10
# Autofailover options # Autofailover options
#failover=manual # one of 'automatic', 'manual' (default: manual) failover=manual # one of 'automatic', 'manual'
# defines the action to take in the event of upstream failure # (default: manual)
# priority=100 # a value of zero or less prevents the node being promoted to primary
# 'automatic': repmgrd will automatically attempt to promote the
# node or follow the new upstream node
# 'manual': repmgrd will take no action and the mode will require
# manual attention to reattach it to replication
#priority=100 # indicate a preferred priorty for promoting nodes
# a value of zero or less prevents the node being promoted to primary
# (default: 100) # (default: 100)
promote_command='repmgr standby promote -f /path/to/repmgr.conf'
follow_command='repmgr standby follow -f /path/to/repmgr.conf -W'
#promote_command='repmgr standby promote -f /path/to/repmgr.conf' # monitoring interval in seconds; default is 2
#follow_command='repmgr standby follow -f /path/to/repmgr.conf -W' monitor_interval_secs=2
# change wait time for primary; before we bail out and exit when the primary # change wait time for primary; before we bail out and exit when the primary
# disappears, we wait 'reconnect_attempts' * 'retry_promote_interval_secs' # disappears, we wait 'reconnect_attempts' * 'retry_promote_interval_secs'
# seconds; by default this would be half an hour, as 'retry_promote_interval_secs' # seconds; by default this would be half an hour, as 'retry_promote_interval_secs'
# default value is 300) # default value is 300)
#retry_promote_interval_secs=300 retry_promote_interval_secs=300
# Number of seconds after which the witness server resyncs the repl_nodes table
#witness_repl_nodes_sync_interval_secs=15

211
repmgr.h
View File

@@ -23,20 +23,20 @@
#include <libpq-fe.h> #include <libpq-fe.h>
#include <postgres_fe.h> #include <postgres_fe.h>
#include <getopt_long.h> #include <getopt_long.h>
#include "pqexpbuffer.h"
#include "strutil.h" #include "strutil.h"
#include "dbutils.h" #include "dbutils.h"
#include "errcode.h" #include "errcode.h"
#include "config.h" #include "config.h"
#include "dirmod.h"
#define MIN_SUPPORTED_VERSION "9.3" #define MIN_SUPPORTED_VERSION "9.3"
#define MIN_SUPPORTED_VERSION_NUM 90300 #define MIN_SUPPORTED_VERSION_NUM 90300
#include "config.h"
#define MAXFILENAME 1024
#define ERRBUFF_SIZE 512 #define ERRBUFF_SIZE 512
#define DEFAULT_WAL_KEEP_SEGMENTS "0" #define DEFAULT_WAL_KEEP_SEGMENTS "5000"
#define DEFAULT_DEST_DIR "." #define DEFAULT_DEST_DIR "."
#define DEFAULT_REPMGR_SCHEMA_PREFIX "repmgr_" #define DEFAULT_REPMGR_SCHEMA_PREFIX "repmgr_"
#define DEFAULT_PRIORITY 100 #define DEFAULT_PRIORITY 100
@@ -48,200 +48,57 @@
#define NO_UPSTREAM_NODE -1 #define NO_UPSTREAM_NODE -1
#define UNKNOWN_NODE_ID -1 #define UNKNOWN_NODE_ID -1
/* command line options without short versions */
#define OPT_HELP 1
#define OPT_CHECK_UPSTREAM_CONFIG 2
#define OPT_RECOVERY_MIN_APPLY_DELAY 3
#define OPT_COPY_EXTERNAL_CONFIG_FILES 4
#define OPT_CONFIG_ARCHIVE_DIR 5
#define OPT_PG_REWIND 6
#define OPT_PWPROMPT 7
#define OPT_CSV 8
#define OPT_NODE 9
#define OPT_WITHOUT_BARMAN 10
#define OPT_NO_UPSTREAM_CONNECTION 11
#define OPT_REGISTER_WAIT 12
#define OPT_CLUSTER 13
#define OPT_LOG_TO_FILE 14
#define OPT_UPSTREAM_CONNINFO 15
#define OPT_NO_CONNINFO_PASSWORD 16
#define OPT_REPLICATION_USER 17
/* deprecated command line options */
#define OPT_INITDB_NO_PWPROMPT 998
#define OPT_IGNORE_EXTERNAL_CONFIG_FILES 999
/* values for --copy-external-config-files */
#define CONFIG_FILE_SAMEPATH 1
#define CONFIG_FILE_PGDATA 2
/* Run time options type */ /* Run time options type */
typedef struct typedef struct
{ {
/* general repmgr options */
char config_file[MAXPGPATH];
bool verbose;
bool terse;
bool force;
char pg_bindir[MAXLEN]; /* overrides setting in repmgr.conf */
/* logging parameters */
char loglevel[MAXLEN]; /* overrides setting in repmgr.conf */
bool log_to_file;
/* connection parameters */
char dbname[MAXLEN]; char dbname[MAXLEN];
char host[MAXLEN]; char host[MAXLEN];
char username[MAXLEN]; char username[MAXLEN];
char dest_dir[MAXPGPATH]; char dest_dir[MAXFILENAME];
char config_file[MAXFILENAME];
char remote_user[MAXLEN]; char remote_user[MAXLEN];
char superuser[MAXLEN]; char superuser[MAXLEN];
char masterport[MAXLEN];
bool conninfo_provided;
bool connection_param_provided;
bool host_param_provided;
/* standby clone parameters */
bool wal_keep_segments_used;
char wal_keep_segments[MAXLEN]; char wal_keep_segments[MAXLEN];
bool verbose;
bool terse;
bool force;
bool wait_for_master;
bool ignore_rsync_warn; bool ignore_rsync_warn;
bool witness_pwprompt;
bool rsync_only; bool rsync_only;
bool fast_checkpoint; bool fast_checkpoint;
bool without_barman; bool ignore_external_config_files;
bool no_upstream_connection; char pg_ctl_mode[MAXLEN];
bool no_conninfo_password; char masterport[MAXLEN];
bool copy_external_config_files; /*
int copy_external_config_files_destination; * configuration file parameters which can be overridden on the
char upstream_conninfo[MAXLEN]; * command line
char replication_user[MAXLEN]; */
char loglevel[MAXLEN];
/* parameter used by STANDBY SWITCHOVER */
char remote_config_file[MAXLEN];
char pg_rewind[MAXFILENAME];
/* parameter used by STANDBY {ARCHIVE_CONFIG | RESTORE_CONFIG} */
char config_archive_dir[MAXLEN];
/* parameter used by CLUSTER CLEANUP */
int keep_history;
char pg_bindir[MAXLEN];
char recovery_min_apply_delay[MAXLEN]; char recovery_min_apply_delay[MAXLEN];
/* standby register paarameters */ /* deprecated command line options */
bool wait_register_sync; char localport[MAXLEN];
int wait_register_sync_seconds; bool initdb_no_pwprompt;
/* witness create parameters */
bool witness_pwprompt;
/* standby follow parameters */
bool wait_for_master;
/* cluster {show|matrix|crosscheck} parameters */
bool csv_mode;
/* cluster cleanup parameters */
int keep_history;
/* standby switchover parameters */
char remote_config_file[MAXLEN];
bool pg_rewind_supplied;
char pg_rewind[MAXPGPATH];
char pg_ctl_mode[MAXLEN];
/* standby {archive_config | restore_config} parameters */
char config_archive_dir[MAXLEN];
/* {standby|witness} unregister parameters */
int node;
} t_runtime_options; } t_runtime_options;
#define T_RUNTIME_OPTIONS_INITIALIZER { \ #define T_RUNTIME_OPTIONS_INITIALIZER { "", "", "", "", "", "", "", DEFAULT_WAL_KEEP_SEGMENTS, false, false, false, false, false, false, false, false, false, "smart", "", "", "", "", "", 0, "", "", "", false }
/* general repmgr options */ \
"", false, false, false, "", \
/* logging parameters */ \
"", false, \
/* connection parameters */ \
"", "", "", "", "", "", "", \
false, false, false, \
/* standby clone parameters */ \
false, DEFAULT_WAL_KEEP_SEGMENTS, false, false, false, false, false, false, \
false, CONFIG_FILE_SAMEPATH, "", "", "", \
/* standby register paarameters */ \
false, 0, \
/* witness create parameters */ \
false, \
/* standby follow parameters */ \
false, \
/* cluster {show|matrix|crosscheck} parameters */ \
false, \
/* cluster cleanup parameters */ \
0, \
/* standby switchover parameters */ \
"", false, "", "fast", \
/* standby {archive_config | restore_config} parameters */ \
"", \
/* {standby|witness} unregister parameters */ \
UNKNOWN_NODE_ID }
struct BackupLabel
{
XLogRecPtr start_wal_location;
char start_wal_file[MAXLEN];
XLogRecPtr checkpoint_location;
char backup_from[MAXLEN];
char backup_method[MAXLEN];
char start_time[MAXLEN];
char label[MAXLEN];
XLogRecPtr min_failover_slot_lsn;
};
typedef struct
{
char slot[MAXLEN];
char xlog_method[MAXLEN];
} t_basebackup_options;
#define T_BASEBACKUP_OPTIONS_INITIALIZER { "", "" }
typedef struct
{
int size;
char **keywords;
char **values;
} t_conninfo_param_list;
typedef struct
{
char filepath[MAXPGPATH];
char filename[MAXPGPATH];
bool in_data_directory;
} t_configfile_info;
typedef struct
{
int size;
int entries;
t_configfile_info **files;
} t_configfile_list;
#define T_CONFIGFILE_LIST_INITIALIZER { 0, 0, NULL }
typedef struct
{
int node_id;
int node_status;
} t_node_status_rec;
typedef struct
{
int node_id;
char node_name[MAXLEN];
t_node_status_rec **node_status_list;
} t_node_matrix_rec;
typedef struct
{
int node_id;
char node_name[MAXLEN];
t_node_matrix_rec **matrix_list_rec;
} t_node_status_cube;
extern char repmgr_schema[MAXLEN];
extern bool config_file_found;
#endif #endif

View File

@@ -64,7 +64,7 @@ CREATE INDEX idx_repl_status_sort ON repl_monitor(last_monitor_time, standby_nod
* This view shows the list of nodes with the information of which one is the upstream * This view shows the list of nodes with the information of which one is the upstream
* in each case (when appliable) * in each case (when appliable)
*/ */
CREATE VIEW repl_show_nodes AS CREATE VIEW repl_show_nodes AS
SELECT rn.id, rn.conninfo, rn.type, rn.name, rn.cluster, SELECT rn.id, rn.conninfo, rn.type, rn.name, rn.cluster,
rn.priority, rn.active, sq.name AS upstream_node_name rn.priority, rn.active, sq.name AS upstream_node_name
FROM repl_nodes as rn LEFT JOIN repl_nodes AS sq ON sq.id=rn.upstream_node_id; FROM repl_nodes as rn LEFT JOIN repl_nodes AS sq ON sq.id=rn.upstream_node_id;

751
repmgrd.c

File diff suppressed because it is too large Load Diff

View File

@@ -63,15 +63,6 @@ UPDATE repl_nodes SET type = 'master' WHERE id = $master_id;
-- UPDATE repl_nodes SET active = FALSE WHERE id IN (...); -- UPDATE repl_nodes SET active = FALSE WHERE id IN (...);
/* There's also an event table which we need to create */
CREATE TABLE repl_events (
node_id INTEGER NOT NULL,
event TEXT NOT NULL,
successful BOOLEAN NOT NULL DEFAULT TRUE,
event_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
details TEXT NULL
);
/* When you're sure of your changes, commit them */ /* When you're sure of your changes, commit them */
-- COMMIT; -- COMMIT;

View File

@@ -1,32 +0,0 @@
/*
* Update a repmgr 3.1.1 installation to repmgr 3.1.2
* --------------------------------------------------
*
* This update is only required if repmgrd is being used in conjunction
* with a witness server.
*
* The new repmgr package should be installed first. Then
* carry out these steps:
*
* 1. (If repmgrd is used) stop any running repmgrd instances
* 2. On the master node, execute the SQL statement listed below
* 3. (If repmgrd is used) restart repmgrd
*/
/*
* If your repmgr installation is not included in your repmgr
* user's search path, please set the search path to the name
* of the repmgr schema to ensure objects are installed in
* the correct location.
*
* The repmgr schema is "repmgr_" + the cluster name defined in
* 'repmgr.conf'.
*/
-- SET search_path TO 'name_of_repmgr_schema';
BEGIN;
ALTER TABLE repl_nodes DROP CONSTRAINT repl_nodes_upstream_node_id_fkey,
ADD CONSTRAINT repl_nodes_upstream_node_id_fkey FOREIGN KEY (upstream_node_id) REFERENCES repl_nodes(id) DEFERRABLE;
COMMIT;

View File

@@ -83,12 +83,7 @@ _PG_init(void)
* resources in repmgr_shmem_startup(). * resources in repmgr_shmem_startup().
*/ */
RequestAddinShmemSpace(repmgr_memsize()); RequestAddinShmemSpace(repmgr_memsize());
#if (PG_VERSION_NUM >= 90600)
RequestNamedLWLockTranche("repmgr", 1);
#else
RequestAddinLWLocks(1); RequestAddinLWLocks(1);
#endif
/* /*
* Install hooks. * Install hooks.
@@ -133,11 +128,7 @@ repmgr_shmem_startup(void)
if (!found) if (!found)
{ {
/* First time through ... */ /* First time through ... */
#if (PG_VERSION_NUM >= 90600)
shared_state->lock = &(GetNamedLWLockTranche("repmgr"))->lock;
#else
shared_state->lock = LWLockAssign(); shared_state->lock = LWLockAssign();
#endif
snprintf(shared_state->location, snprintf(shared_state->location,
sizeof(shared_state->location), "%X/%X", 0, 0); sizeof(shared_state->location), "%X/%X", 0, 0);
} }

View File

@@ -87,21 +87,3 @@ maxlen_snprintf(char *str, const char *format,...)
return retval; return retval;
} }
/*
* Escape a string for use as a parameter in recovery.conf
* Caller must free returned value
*/
char *
escape_recovery_conf_value(const char *src)
{
char *result = escape_single_quotes_ascii(src);
if (!result)
{
fprintf(stderr, _("%s: out of memory\n"), progname());
exit(ERR_INTERNAL);
}
return result;
}

View File

@@ -22,20 +22,14 @@
#define _STRUTIL_H_ #define _STRUTIL_H_
#include <stdlib.h> #include <stdlib.h>
#include "pqexpbuffer.h"
#include "errcode.h" #include "errcode.h"
#define QUERY_STR_LEN 8192 #define QUERY_STR_LEN 8192
#define MAXLEN 1024 #define MAXLEN 1024
#define MAXLINELENGTH 4096 #define MAXLINELENGTH 4096
#define MAXVERSIONSTR 16 #define MAXVERSIONSTR 16
#define MAXCONNINFO 1024 #define MAXCONNINFO 1024
/* Why? http://stackoverflow.com/a/5459929/398670 */
#define STR(x) CppAsString(x)
#define MAXLEN_STR STR(MAXLEN)
extern int extern int
xsnprintf(char *str, size_t size, const char *format,...) xsnprintf(char *str, size_t size, const char *format,...)
@@ -49,6 +43,4 @@ extern int
maxlen_snprintf(char *str, const char *format,...) maxlen_snprintf(char *str, const char *format,...)
__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3))); __attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
extern char *
escape_recovery_conf_value(const char *src);
#endif /* _STRUTIL_H_ */ #endif /* _STRUTIL_H_ */

View File

@@ -1,6 +1,6 @@
#ifndef _VERSION_H_ #ifndef _VERSION_H_
#define _VERSION_H_ #define _VERSION_H_
#define REPMGR_VERSION "3.3" #define REPMGR_VERSION "3.1.1"
#endif #endif