Compare commits

...

70 Commits

Author SHA1 Message Date
Ian Barwick
2615cffecc v3.0.2
Not yet tagged, pending a couple of tests
2015-10-02 11:46:13 +09:00
Ian Barwick
1f838f99c2 Update TODO 2015-10-02 11:46:13 +09:00
Ian Barwick
d3f119005b Update version string to 3.0.1 2015-10-02 11:45:57 +09:00
Ian Barwick
db6d4d8820 Update version string 2015-10-02 11:45:57 +09:00
Ian Barwick
7a8a50e229 Add CONTRIBUTING.md 2015-10-02 10:18:06 +09:00
Ian Barwick
e188044593 Clean up markup 2015-10-02 10:07:36 +09:00
Ian Barwick
636f4b03c6 Spelling and markup fixes 2015-10-02 09:55:34 +09:00
Ian Barwick
bf96b383a3 Merge branch 'eternaltyro-master' 2015-10-02 09:49:55 +09:00
Ian Barwick
3a2e40f381 Merge branch 'master' of https://github.com/eternaltyro/repmgr into eternaltyro-master 2015-10-02 09:49:39 +09:00
Minh Danh
c608bb28ee Fix some typos 2015-10-02 08:03:55 +09:00
Ian Barwick
ca9c2e1143 Fix -D/--data-dir's long option
This was erroneously rendered as --dest-dir. This bugfeature seems
to have been around for a long time; however as the only way anyone
could know of the existence of --dest-dir is by reading the source
code, we can safely remove it.

Pointed out by Github user Jehan-Guillaume (ioguix) de Rorthais.
2015-10-02 07:50:19 +09:00
Ian Barwick
3a6d6b8899 Fix typo in README.md
Reported by Github user knopwob.
2015-10-02 07:17:39 +09:00
Ian Barwick
4091cb7f18 Update TODO 2015-10-01 16:09:37 +09:00
Ian Barwick
870b0a53b6 Allow 'primary' as synonym for 'master'.
"Primary" is the term preferred in the PostgreSQL documentation, so
we should at least support it.

Practically this means it's possible to write "rempgr primary register"
in place of "repmgr master register".

The next feature-release should replace "master" with "primary" in
the documentation and log messages.

Per gripe in Github #112.
2015-10-01 16:01:12 +09:00
Ian Barwick
6184cc57be Bump version in HEAD 2015-10-01 13:37:23 +09:00
Ian Barwick
e1254b6773 Update HISTORY 2015-10-01 13:24:02 +09:00
Ian Barwick
1c9121c2d8 Witness server - extract explicit port setting from conninfo setting
This makes the '-l/--local-port' option redundant, which is now
marked as deprecated.
2015-10-01 09:42:44 +09:00
Ian Barwick
6da03a6157 Free connection options array after use. 2015-10-01 09:16:36 +09:00
Ian Barwick
9bb6befa25 Sanity check 'conninfo' parameter
Catch errors early, it makes everyone's life easier.
2015-10-01 09:06:27 +09:00
Ian Barwick
a8e5c68d03 Better document '-l/--local-port' option.
Per Github #59.
2015-10-01 08:40:27 +09:00
Ian Barwick
b83e18c503 Fix '-l, --local-port' help output.
Is for witness server only.

Use constants for default port definitions while we're at it.

Github #123.
2015-10-01 08:24:14 +09:00
Ian Barwick
d4b845d213 'repmgr witness create --force': overwrite any existing node record
Consistent with 'repmgr standby register --force'.

Fixes Github #122.
2015-10-01 08:02:06 +09:00
Ian Barwick
75aad9a85e repmgr witness create --force: overwrite existing data directory
Per Github #82 (dimitri).
2015-09-30 17:05:54 +09:00
Ian Barwick
e115825cd6 Fix comment capitalization 2015-09-30 14:58:43 +09:00
Ian Barwick
6cf5ab2e53 Update HISTORY 2015-09-30 12:48:10 +09:00
Ian Barwick
f8119d20ea Handle tablespace mapping in 9.5 and later in --rsync-only mode
9.5 introduces the tablespace_map file, which is created on the upstream
node while a backup is running. We need to overwrite this with the
provided values.

Note that we only write explicitly-provided values to the tablespace_map
file, however the existing symlinks for non-specified tablespaces
will have been copied anyway.

Fixes Github #119.
2015-09-30 11:48:09 +09:00
Ian Barwick
0caddf2d2c Fix check when tablespace mapping option used with 9.3
As pointed out by EvilElk (Github issue #91).
2015-09-29 14:30:37 +09:00
Ian Barwick
a4abbc6f0c Minor formatting fixes 2015-09-29 14:18:48 +09:00
Ian Barwick
d7e489ea0a Update repmgr --help output
(per Github report #113 from renard)
2015-09-25 11:26:12 +09:00
Ian Barwick
2bcacff3b3 Update TODO 2015-09-25 11:19:21 +09:00
Ian Barwick
45eb0ea5d3 Miscelleanous comment fixes 2015-09-25 11:17:26 +09:00
Ian Barwick
c3bd02b83d Standardize if-statement formatting
"if(" -> "if ("
2015-09-24 17:45:08 +09:00
Ian Barwick
8e7d110a22 Check for existing master record before deleting it
Otherwise repmgr implies it's deleting a record which isn't actually
there.
2015-09-24 17:39:39 +09:00
Ian Barwick
43874d5576 Close database connection as soon as it is no longer required 2015-09-24 16:55:59 +09:00
Ian Barwick
87ff9d09ba Ensure --force option works with "master register"
This was failing previously if repmgr was able to connect to the
master database
2015-09-24 16:55:59 +09:00
Ian Barwick
c429b0b186 Don't fail with error when registering master if schema already defined
Registering a master creates the schema, but it may be desirable
to forcibly reregister a master without deleting the schema, so
uncouple the dependency.

Also ensure schema creation is atomic by wrapping it in a transaction.

Per GitHub issue #49.
2015-09-24 16:55:43 +09:00
Martín Marqués
03b88178c1 Minor change in an error log message 2015-09-23 10:44:07 -03:00
Martín Marqués
5f33f4286f Merge pull request #115 from xocolatl/unregister
Add a STANDBY UNREGISTER command.
2015-09-23 10:17:22 -03:00
Vik Fearing
932f84910b Add a STANDBY UNREGISTER command.
In some cases it is desirable to remove repmgr's handling of a standby, perhaps
because that standby is to be removed altogether.  With no UNREGISTER command,
one had to manually delete the corresponding row from repl_nodes.
2015-09-23 12:37:42 +02:00
Ian Barwick
1ef7f1368d Update TODO 2015-09-23 14:05:09 +09:00
Ian Barwick
640abed18f Don't close connection to master until upstream node updated 2015-09-23 13:11:51 +09:00
Tomas Vondra
ef6b24551a call update_node_record_set_upstream() for STANDBY FOLLOW
repmgrd correctly updates ID of the upstream node after automatic
failover, but repmgr was not doing that for manual failvers.

This moves the existing function to dbutils and modifies it so that
it does not rely on global variables with configuration (available
just in repmgrd).

This should fix issue #67 (hopefully, haven't done much testing).
2015-09-23 12:32:47 +09:00
Ian Barwick
42847e44d2 Update HISTORY file 2015-09-23 10:29:40 +09:00
Ian Barwick
dd7cfce3d3 Add TODO note about default configuration file.
Per suggestion in #108.
2015-09-22 19:26:19 +09:00
Ian Barwick
30fd111cba Rework config file handling
If no configuration file provided, also check default Postgres
sysconfig dir.

It would also be useful to check the configuration directory
provided by the RPM/DEB packages, not sure if that's programmatically
feasible.
2015-09-21 15:55:29 +09:00
Ian Barwick
65e63b062e Generally tidy up help output 2015-09-21 11:49:06 +09:00
Ian Barwick
053f672caa Treat -?/--help and -V/--version as normal options
Currently repmgr/repmgrd will only accept these as valid when
provided as the first command line option, however it's possible
a user will want to get the output of those options by adding
them to the end of a previously inputted command.

Note that after the first of these options is encountered, the
program will terminate and not process any other options. This
is consistent with psql's behaviour

Per GitHub issue #107 from Sébastien Gross.
2015-09-21 09:53:51 +09:00
Ian Barwick
f6d02b85d8 Better handling of situation where logfile can't be opened
If freopen() fails, stderr is diverted to an undisclosed location
and it's not clear what is going on.

Also add an explicit notice announcing our intention to divert
logging output to a file.

Per #105.

Note that it might make sense to disable logfile output when
running the repmgr command line client as normally you'd expect
immediate feedback.
2015-09-15 13:37:40 +09:00
Ian Barwick
6ebf3a7319 Make example output host match input configuration 2015-09-14 14:32:34 +09:00
Ian Barwick
7345ddcf00 Whitespace tweak 2015-09-10 14:27:21 +09:00
Ian Barwick
eb0af7ca23 Always pass -D/--pgdata option to pg_basebackup
repmgr does not require explicit provision of the target data
directory when cloning a standby (it defaults to the same directory
as on the master). However this is a required option for pg_basebackup
which was only being provided if repmgr's -D/--data-dir option was
set, so ensure we always provide whatever repmgr is using.

Per report from Martín.
2015-08-25 14:36:51 +09:00
Ian Barwick
ae47e5f413 Fix example command syntax 2015-08-12 12:32:41 +09:00
Ian Barwick
46100a9549 "Supports 9.3 and 9.4" -> "Supports 9.3 and later"
In case anyone thinks it might not run on 9.5.
2015-08-12 12:26:27 +09:00
Gianni Ciolli
9bd95cabdf Merge pull request #98 from gciolli/master
Bug #97 fix (standby clone --force does not empty pg_xlog).
2015-08-11 15:10:37 +02:00
Gianni Ciolli
f1584469bf Bug #97 fix (standby clone --force does not empty pg_xlog). 2015-08-11 14:59:23 +02:00
Abhijit Menon-Sen
a7f46d24de Merge pull request #96 from gciolli/master
Bug #90 fix (autofailover with reconnect_attemps > 1).
2015-08-11 06:35:31 +05:30
Gianni Ciolli
462d446477 Bug #90 fix (autofailover with reconnect_attemps > 1).
The main change is that now check_connection requires a conninfo
parameter, and the connection object has type (PGconn **) so it can be
replaced by check_connection if needed.

The bug was caused by the fact that the first failure resulted in
*conn == NULL, so that subsequent checks of the upstream connection
were failing irrespectively of the actual state of the upstream node.

Now, when *conn == NULL, check_connection will use conninfo to
establish a new connection and place it into *conn. We introduce a new
INTERNAL_ERROR code for the case when they are both NULL.

In passing, we also reworded a confusing error message, distinguishing
a timeout from the actual elapsed time.
2015-08-10 20:58:43 +02:00
Ian Barwick
23a72f489c Add note about configuring 'shared_preload_libraries' for repmgr
Useful to put all the possible postgresql.conf changes in one place.
2015-08-07 14:37:57 +09:00
Christoph Monech-Tegeder
f3f56b0cd6 fix repmgrd spelling in default config files
make sure the daemon has it's trailing _d_ everywhere
2015-08-06 17:20:56 +02:00
Abhijit Menon-Sen
00146b7fbd Merge pull request #88 from soxwellfb/fix-config-reader
Ignore comments after values
2015-08-04 13:28:04 +05:30
Simon Oxwell
faf72a2514 Ignore comments after values 2015-07-15 14:53:35 +10:00
Ian Barwick
7010b636e0 Reword error message for consistency
Github issue #87
2015-07-10 10:11:23 +09:00
Gregory Duchatelet
00deff9069 [Fix] if 'node' config parameter is set as a string, nothing complain and all nodes are set to id '0'. 2015-07-06 11:20:31 +02:00
Ian Barwick
5240a5723a Clarify WAL file retention issue during standby cloning.
Per GitHub issue #86.
2015-07-02 09:32:30 +09:00
Martín Marqués
45e29c5b28 Typo in a comment 2015-06-04 18:12:35 +09:00
Ian Barwick
5def293ed6 Configuration file: ignore whitespace following the '=' sign 2015-06-04 11:58:34 +09:00
Ian Barwick
ff7b4d3f02 Improve whitespace handling
Ignore blank lines which consist of whitespace. Per issue #71 in
GitHub.

This fix also improves comment handling and will treat lines with
whitespace before the '#' character as whitespace.
2015-06-04 11:44:38 +09:00
Ian Barwick
a54478a045 Documentation tweaks 2015-05-12 10:16:52 +09:00
Marco Nenciarini
7ad9a2c28a Allow repmgr to be compiled with a libpq newer than PostgreSQL
Fixes #44
2015-04-29 18:00:54 +02:00
Yogesh Girikumar
0037e66034 fix messy formatting 2015-03-31 22:58:50 +05:30
22 changed files with 1146 additions and 554 deletions

29
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,29 @@
License and Contributions
=========================
`repmgr` is licensed under the GPL v3. All of its code and documentation is
Copyright 2010-2015, 2ndQuadrant Limited. See the files COPYRIGHT and LICENSE for
details.
The development of repmgr has primarily been sponsored by 2ndQuadrant customers.
Additional work has been sponsored by the 4CaaST project for cloud computing,
which has received funding from the European Union's Seventh Framework Programme
(FP7/2007-2013) under grant agreement 258862.
Contributions to `repmgr` are welcome, and will be listed in the file `CREDITS`.
2ndQuadrant Limited requires that any contributions provide a copyright
assignment and a disclaimer of any work-for-hire ownership claims from the
employer of the developer. This lets us make sure that all of the repmgr
distribution remains free code. Please contact info@2ndQuadrant.com for a
copy of the relevant Copyright Assignment Form.
Code style
----------
Code in repmgr is formatted to a consistent style using the following command:
astyle --style=ansi --indent=tab --suffix=none *.c *.h
Contributors should reformat their code similarly before submitting code to
the project, in order to minimize merge conflicts with other work.

View File

@@ -203,6 +203,12 @@ repmgr will also ask for the superuser password on the witness database so
it can reconnect when needed (the command line option --initdb-no-pwprompt it can reconnect when needed (the command line option --initdb-no-pwprompt
will set up a password-less superuser). will set up a password-less superuser).
By default the witness server will listen on port 5499; this value can be
overridden by explicitly providing the port number in the conninfo string
in repmgr.conf. (Note that it is also possible to specify the port number
with the -l/--local-port option, however this option is now deprecated and
will be overridden by a port setting in the conninfo string).
Start the repmgrd daemons Start the repmgrd daemons
------------------------- -------------------------

16
HISTORY
View File

@@ -1,3 +1,19 @@
3.0.2 2015-09-
Improve handling of --help/--version options; and improve help output (Ian)
Improve handling of situation where logfile can't be opened (Ian)
Always pass -D/--pgdata option to pg_basebackup (Ian)
Bugfix: standby clone --force does not empty pg_xlog (Gianni)
Bugfix: autofailover with reconnect_attempts > 1 (Gianni)
Bugfix: ignore comments after values (soxwellfb)
Bugfix: handle string values in 'node' parameter correctly (Gregory Duchatelet)
Allow repmgr to be compiled with a newer libpq (Marco)
Bugfix: call update_node_record_set_upstream() for STANDBY FOLLOW (Tomas)
Update `repmgr --help` output (per Github report from renard)
Update tablespace remapping in --rsync-only mode for 9.5 and later (Ian)
Deprecate `-l/--local-port` option - the port can be extracted
from the conninfo string in repmgr.conf (Ian)
Add STANDBY UNREGISTE (Vik Fearing)
3.0.1 2015-04-16 3.0.1 2015-04-16
Prevent repmgrd from looping infinitely if node was not registered (Ian) Prevent repmgrd from looping infinitely if node was not registered (Ian)
When promoting a standby, have repmgr (not repmgrd) handle metadata updates (Ian) When promoting a standby, have repmgr (not repmgrd) handle metadata updates (Ian)

View File

@@ -4,10 +4,10 @@ Packaging
Notes on RedHat Linux, Fedora, and CentOS Builds Notes on RedHat Linux, Fedora, and CentOS Builds
------------------------------------------------ ------------------------------------------------
The RPM packages of PostgreSQL put ``pg_config`` into the ``postgresql-devel`` The RPM packages of PostgreSQL put `pg_config` into the `postgresql-devel`
package, not the main server one. And if you have a RPM install of PostgreSQL package, not the main server one. And if you have a RPM install of PostgreSQL
9.0, the entire PostgreSQL binary directory will not be in your PATH by default 9.0, the entire PostgreSQL binary directory will not be in your PATH by default
either. Individual utilities are made available via the ``alternatives`` either. Individual utilities are made available via the `alternatives`
mechanism, but not all commands will be wrapped that way. The files installed mechanism, but not all commands will be wrapped that way. The files installed
by repmgr will certainly not be in the default PATH for the postgres user by repmgr will certainly not be in the default PATH for the postgres user
on such a system. They will instead be in /usr/pgsql-9.0/bin/ on this on such a system. They will instead be in /usr/pgsql-9.0/bin/ on this
@@ -15,31 +15,33 @@ type of system.
When building repmgr against a RPM packaged build, you may discover that some When building repmgr against a RPM packaged build, you may discover that some
development packages are needed as well. The following build errors can development packages are needed as well. The following build errors can
occur:: occur:
/usr/bin/ld: cannot find -lxslt /usr/bin/ld: cannot find -lxslt
/usr/bin/ld: cannot find -lpam /usr/bin/ld: cannot find -lpam
Install the following packages to correct those:: Install the following packages to correct those:
yum install libxslt-devel yum install libxslt-devel
yum install pam-devel yum install pam-devel
If building repmgr as a regular user, then doing the install into the system If building repmgr as a regular user, then doing the install into the system
directories using sudo, the syntax is hard. ``pg_config`` won't be in root's directories using sudo, the syntax is hard. `pg_config` won't be in root's
path either. The following recipe should work:: path either. The following recipe should work:
sudo PATH="/usr/pgsql-9.0/bin:$PATH" make USE_PGXS=1 install sudo PATH="/usr/pgsql-9.0/bin:$PATH" make USE_PGXS=1 install
Issues with 32 and 64 bit RPMs Issues with 32 and 64 bit RPMs
------------------------------ ------------------------------
If when building, you receive a series of errors of this form:: If when building, you receive a series of errors of this form:
/usr/bin/ld: skipping incompatible /usr/pgsql-9.0/lib/libpq.so when searching for -lpq /usr/bin/ld: skipping incompatible /usr/pgsql-9.0/lib/libpq.so when searching for -lpq
This is likely because you have both the 32 and 64 bit versions of the This is likely because you have both the 32 and 64 bit versions of the
``postgresql90-devel`` package installed. You can check that like this:: `postgresql90-devel` package installed. You can check that like this:
rpm -qa --queryformat '%{NAME}\t%{ARCH}\n' | grep postgresql90-devel rpm -qa --queryformat '%{NAME}\t%{ARCH}\n' | grep postgresql90-devel
@@ -47,7 +49,8 @@ And if two packages appear, one for i386 and one for x86_64, that's not supposed
to be allowed. to be allowed.
This can happen when using the PGDG repo to install that package; This can happen when using the PGDG repo to install that package;
here is an example sessions demonstrating the problem case appearing:: here is an example sessions demonstrating the problem case appearing:
# yum install postgresql-devel # yum install postgresql-devel
.. ..
@@ -67,20 +70,21 @@ here is an example sessions demonstrating the problem case appearing::
postgresql90-devel i386 9.0.2-2PGDG.rhel5 pgdg90 1.5 M postgresql90-devel i386 9.0.2-2PGDG.rhel5 pgdg90 1.5 M
postgresql90-devel x86_64 9.0.2-2PGDG.rhel5 pgdg90 1.6 M postgresql90-devel x86_64 9.0.2-2PGDG.rhel5 pgdg90 1.6 M
Note how both the i386 and x86_64 platform architectures are selected for Note how both the i386 and x86_64 platform architectures are selected for
installation. Your main PostgreSQL package will only be compatible with one of installation. Your main PostgreSQL package will only be compatible with one of
those, and if the repmgr build finds the wrong postgresql90-devel these those, and if the repmgr build finds the wrong postgresql90-devel these
"skipping incompatible" messages appear. "skipping incompatible" messages appear.
In this case, you can temporarily remove both packages, then just install the In this case, you can temporarily remove both packages, then just install the
correct one for your architecture. Example:: correct one for your architecture. Example:
rpm -e postgresql90-devel --allmatches rpm -e postgresql90-devel --allmatches
yum install postgresql90-devel-9.0.2-2PGDG.rhel5.x86_64 yum install postgresql90-devel-9.0.2-2PGDG.rhel5.x86_64
Instead just deleting the package from the wrong platform might not leave behind Instead just deleting the package from the wrong platform might not leave behind
the correct files, due to the way in which these accidentally happen to interact. the correct files, due to the way in which these accidentally happen to interact.
If you already tried to build repmgr before doing this, you'll need to do:: If you already tried to build repmgr before doing this, you'll need to do:
make USE_PGXS=1 clean make USE_PGXS=1 clean
@@ -89,17 +93,17 @@ to get rid of leftover files from the wrong architecture.
Notes on Ubuntu, Debian or other Debian-based Builds Notes on Ubuntu, Debian or other Debian-based Builds
---------------------------------------------------- ----------------------------------------------------
The Debian packages of PostgreSQL put ``pg_config`` into the development package The Debian packages of PostgreSQL put `pg_config` into the development package
called ``postgresql-server-dev-$version``. called `postgresql-server-dev-$version`.
When building repmgr against a Debian packages build, you may discover that some When building repmgr against a Debian packages build, you may discover that some
development packages are needed as well. You will need the following development development packages are needed as well. You will need the following development
packages installed:: packages installed:
sudo apt-get install libxslt-dev libxml2-dev libpam-dev libedit-dev sudo apt-get install libxslt-dev libxml2-dev libpam-dev libedit-dev
If you're using Debian packages for PostgreSQL and are building repmgr with the If you're using Debian packages for PostgreSQL and are building repmgr with the
USE_PGXS option you also need to install the corresponding development package:: USE_PGXS option you also need to install the corresponding development package:
sudo apt-get install postgresql-server-dev-9.0 sudo apt-get install postgresql-server-dev-9.0
@@ -110,12 +114,12 @@ multiple installed versions of PostgreSQL on the same system through a wrapper
called pg_wrapper and repmgr is not (yet) known to this wrapper. called pg_wrapper and repmgr is not (yet) known to this wrapper.
You can solve this in many different ways, the most Debian like is to make an You can solve this in many different ways, the most Debian like is to make an
alternate for repmgr and repmgrd:: alternate for repmgr and repmgrd:
sudo update-alternatives --install /usr/bin/repmgr repmgr /usr/lib/postgresql/9.0/bin/repmgr 10 sudo update-alternatives --install /usr/bin/repmgr repmgr /usr/lib/postgresql/9.0/bin/repmgr 10
sudo update-alternatives --install /usr/bin/repmgrd repmgrd /usr/lib/postgresql/9.0/bin/repmgrd 10 sudo update-alternatives --install /usr/bin/repmgrd repmgrd /usr/lib/postgresql/9.0/bin/repmgrd 10
You can also make a deb package of repmgr using:: You can also make a deb package of repmgr using:
make USE_PGXS=1 deb make USE_PGXS=1 deb

View File

@@ -21,7 +21,8 @@ Master setup
CREATE DATABASE repmgr_db OWNER repmgr_usr; CREATE DATABASE repmgr_db OWNER repmgr_usr;
``` ```
- configure `postgresql.conf` for replication (see above) - configure `postgresql.conf` for replication (see README.md for sample
settings)
- update `pg_hba.conf`, e.g.: - update `pg_hba.conf`, e.g.:
@@ -111,7 +112,7 @@ created in the `repl_nodes` table should look something like this:
repmgr_db=# SELECT * from repmgr_test.repl_nodes; repmgr_db=# SELECT * from repmgr_test.repl_nodes;
id | type | upstream_node_id | cluster | name | conninfo | slot_name | priority | active id | type | upstream_node_id | cluster | name | conninfo | slot_name | priority | active
----+---------+------------------+---------+-------+-------------------------------------------------+-----------+----------+-------- ----+---------+------------------+---------+-------+----------------------------------------------------+-----------+----------+--------
1 | primary | | test | node1 | host=localhost user=repmgr_usr dbname=repmgr_db | | 0 | t 1 | primary | | test | node1 | host=repmgr_node1 user=repmgr_usr dbname=repmgr_db | | 0 | t
2 | standby | 1 | test | node2 | host=localhost user=repmgr_usr dbname=repmgr_db | | 0 | t 2 | standby | 1 | test | node2 | host=repmgr_node2 user=repmgr_usr dbname=repmgr_db | | 0 | t
(2 rows) (2 rows)

View File

@@ -7,7 +7,7 @@ hot-standby capabilities with tools to set up standby servers, monitor
replication, and perform administrative tasks such as failover or manual replication, and perform administrative tasks such as failover or manual
switchover operations. switchover operations.
This document covers `repmgr 3`, which supports PostgreSQL 9.4 and 9.3. This document covers `repmgr 3`, which supports PostgreSQL 9.3 and later.
This version can use `pg_basebackup` to clone standby servers, supports This version can use `pg_basebackup` to clone standby servers, supports
replication slots and cascading replication, doesn't require a restart replication slots and cascading replication, doesn't require a restart
after promotion, and has many usability improvements. after promotion, and has many usability improvements.
@@ -53,7 +53,7 @@ on any UNIX-like system which PostgreSQL itself supports.
All nodes must be running the same major version of PostgreSQL, and we All nodes must be running the same major version of PostgreSQL, and we
recommend that they also run the same minor version. This version of recommend that they also run the same minor version. This version of
`repmgr` (v3) supports PostgreSQL 9.3 and 9.4. `repmgr` (v3) supports PostgreSQL 9.3 and later.
Earlier versions of `repmgr` needed password-less SSH access between Earlier versions of `repmgr` needed password-less SSH access between
nodes in order to clone standby servers using `rsync`. `repmgr 3` can nodes in order to clone standby servers using `rsync`. `repmgr 3` can
@@ -98,8 +98,8 @@ for details.
### PostgreSQL configuration ### PostgreSQL configuration
The primary server needs to be configured for replication with the The primary server needs to be configured for replication with settings
following settings in `postgresql.conf`: like the following in `postgresql.conf`:
# Allow read-only queries on standby servers. The number of WAL # Allow read-only queries on standby servers. The number of WAL
# senders should be larger than the number of standby servers. # senders should be larger than the number of standby servers.
@@ -121,13 +121,18 @@ following settings in `postgresql.conf`:
archive_mode = on archive_mode = on
archive_command = 'cd .' archive_command = 'cd .'
# You can also set additional replication parameters here, such as # If you plan to use repmgrd, ensure that shared_preload_libraries
# hot_standby_feedback or synchronous_standby_names. # is configured to load 'repmgr_funcs'
shared_preload_libraries = 'repmgr_funcs'
PostgreSQL 9.4 makes it possible to use replication slots, which means PostgreSQL 9.4 makes it possible to use replication slots, which means
the value of wal_keep_segments need no longer be set. With 9.3, `repmgr` the value of `wal_keep_segments` need no longer be set. See section
expects it to be set to at least 5000 (= 80GB of WAL) by default, though "Replication slots" below for more details.
this can be overriden with the `-w N` argument.
With PostgreSQL 9.3, `repmgr` expects `wal_keep_segments` to be set to
at least 5000 (= 80GB of WAL) by default, though this can be overriden
with the `-w N` argument.
A dedicated PostgreSQL superuser account and a database in which to A dedicated PostgreSQL superuser account and a database in which to
store monitoring and replication data are required. Create them by store monitoring and replication data are required. Create them by
@@ -223,7 +228,7 @@ The node can then be restarted.
The node will then need to be re-registered with `repmgr`; again The node will then need to be re-registered with `repmgr`; again
the `--force` option is required to update the existing record: the `--force` option is required to update the existing record:
repmgr -f /etc/repmgr/repmgr.conf repmgr -f /etc/repmgr/repmgr.conf \
--force \ --force \
standby register standby register
@@ -345,6 +350,7 @@ Following event types currently exist:
master_register master_register
standby_register standby_register
standby_unregister
standby_clone standby_clone
standby_promote standby_promote
witness_create witness_create
@@ -398,6 +404,18 @@ stored in the `repl_nodes` table.
Note that `repmgr` will fail with an error if this option is specified when Note that `repmgr` will fail with an error if this option is specified when
working with PostgreSQL 9.3. working with PostgreSQL 9.3.
Be aware that when initially cloning a standby, you will need to ensure
that all required WAL files remain available while the cloning is taking
place. If using the default `pg_basebackup` method, we recommend setting
`pg_basebackup`'s `--xlog-method` parameter to `stream` like this:
pg_basebackup_options='--xlog-method=stream'
See the `pg_basebackup` documentation [*] for details. Otherwise you'll need
to set `wal_keep_segments` to an appropriately high value.
[*] http://www.postgresql.org/docs/current/static/app-pgbasebackup.html
Further reading: Further reading:
* http://www.postgresql.org/docs/current/interactive/warm-standby.html#STREAMING-REPLICATION-SLOTS * http://www.postgresql.org/docs/current/interactive/warm-standby.html#STREAMING-REPLICATION-SLOTS
* http://blog.2ndquadrant.com/postgresql-9-4-slots/ * http://blog.2ndquadrant.com/postgresql-9-4-slots/
@@ -435,12 +453,19 @@ its port if is different from the default one.
Registers a master in a cluster. This command needs to be executed before any Registers a master in a cluster. This command needs to be executed before any
standby nodes are registered. standby nodes are registered.
`primary register` can be used as an alias for `master register`.
* `standby register` * `standby register`
Registers a standby with `repmgr`. This command needs to be executed to enable Registers a standby with `repmgr`. This command needs to be executed to enable
promote/follow operations and to allow `repmgrd` to work with the node. promote/follow operations and to allow `repmgrd` to work with the node.
An existing standby can be registered using this command. An existing standby can be registered using this command.
* `standby unregister`
Unregisters a standby with `repmgr`. This command does not affect the actual
replication.
* `standby clone [node to be cloned]` * `standby clone [node to be cloned]`
Clones a new standby node from the data directory of the master (or Clones a new standby node from the data directory of the master (or

View File

@@ -12,7 +12,7 @@ REPMGRD_ENABLED=no
#REPMGRD_USER=postgres #REPMGRD_USER=postgres
# repmgrd binary # repmgrd binary
#REPMGRD_BIN=/usr/bin/repmgr #REPMGRD_BIN=/usr/bin/repmgrd
# pid file # pid file
#REPMGRD_PIDFILE=/var/lib/pgsql/repmgr/repmgrd.pid #REPMGRD_PIDFILE=/var/lib/pgsql/repmgr/repmgrd.pid

View File

@@ -1,12 +1,13 @@
Set up trusted copy between postgres accounts Set up trusted copy between postgres accounts
--------------------------------------------- ---------------------------------------------
If you need to use rsync to clone standby servers, the postgres account If you need to use `rsync` to clone standby servers, the `postgres` account
on your master and standby servers must be each able to access the other on your primary and standby servers must be each able to access the other
using SSH without a password. using SSH without a password.
First generate a ssh key, using an empty passphrase, and copy the resulting First generate an ssh key, using an empty passphrase, and copy the resulting
keys and a maching authorization file to a privledged user on the other system:: keys and a matching authorization file to a privileged user account on the other
system:
[postgres@node1]$ ssh-keygen -t rsa [postgres@node1]$ ssh-keygen -t rsa
Generating public/private rsa key pair. Generating public/private rsa key pair.
@@ -22,8 +23,8 @@ keys and a maching authorization file to a privledged user on the other system::
[postgres@node1]$ cd ~/.ssh [postgres@node1]$ cd ~/.ssh
[postgres@node1]$ scp id_rsa.pub id_rsa authorized_keys user@node2: [postgres@node1]$ scp id_rsa.pub id_rsa authorized_keys user@node2:
Login as a user on the other system, and install the files into the postgres Login as a user on the other system, and install the files into the `postgres`
user's account:: user's account:
[user@node2 ~]$ sudo chown postgres.postgres authorized_keys id_rsa.pub id_rsa [user@node2 ~]$ sudo chown postgres.postgres authorized_keys id_rsa.pub id_rsa
[user@node2 ~]$ sudo mkdir -p ~postgres/.ssh [user@node2 ~]$ sudo mkdir -p ~postgres/.ssh

23
TODO
View File

@@ -10,6 +10,10 @@ Known issues in repmgr
Planned feature improvements Planned feature improvements
============================ ============================
* Use 'primary' instead of 'master' in documentation and log output
for consistency with PostgreSQL documentation. See also commit
870b0a53b627eeb9aca1fc14cbafe25b5beafe12.
* A better check which standby did receive most of the data * A better check which standby did receive most of the data
* Make the fact that a standby may be delayed a factor in the voting * Make the fact that a standby may be delayed a factor in the voting
@@ -22,6 +26,19 @@ Planned feature improvements
* Use pg_basebackup for the data directory, and ALSO rsync for the * Use pg_basebackup for the data directory, and ALSO rsync for the
configuration files. configuration files.
* Use pg_basebackup -X s * If no configuration file supplied, search in sensible default locations
NOTE: this can be used by including `-X s` in the configuration parameter (currently: current directory and `pg_config --sysconfdir`); if
`pg_basebackup_options` possible this should include the location provided by the package,
if installed.
* repmgrd: if connection to the upstream node fails on startup, optionally
retry for a certain period before giving up; this will cover cases when
e.g. primary and standby are both starting up, and the standby comes up
before the primary. See github issue #80.
* make old master node ID available for event notification commands
(See github issue #80).
* Have pg_basebackup use replication slots, if and when support for
this is added; see:
http://www.postgresql.org/message-id/555DD2B2.7020000@gmx.net

View File

@@ -23,14 +23,19 @@
#include <errno.h> #include <errno.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <ftw.h>
/* NB: postgres_fe must be included BEFORE check_dir */ /* NB: postgres_fe must be included BEFORE check_dir */
#include "postgres_fe.h" #include <libpq-fe.h>
#include "check_dir.h" #include <postgres_fe.h>
#include "check_dir.h"
#include "strutil.h" #include "strutil.h"
#include "log.h" #include "log.h"
static bool _create_pg_dir(char *dir, bool force, bool for_witness);
static int unlink_dir_callback(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf);
/* /*
* make sure the directory either doesn't exist or is empty * make sure the directory either doesn't exist or is empty
* we use this function to check the new data directory and * we use this function to check the new data directory and
@@ -243,6 +248,19 @@ is_pg_dir(char *dir)
bool bool
create_pg_dir(char *dir, bool force) create_pg_dir(char *dir, bool force)
{
return _create_pg_dir(dir, force, false);
}
bool
create_witness_pg_dir(char *dir, bool force)
{
return _create_pg_dir(dir, force, true);
}
static bool
_create_pg_dir(char *dir, bool force, bool for_witness)
{ {
bool pg_dir = false; bool pg_dir = false;
@@ -279,12 +297,24 @@ create_pg_dir(char *dir, bool force)
pg_dir = is_pg_dir(dir); pg_dir = is_pg_dir(dir);
/*
* we use force to reduce the time needed to restore a node which
* turn async after a failover or anything else
*/
if (pg_dir && force) if (pg_dir && force)
{ {
/*
* The witness server does not store any data other than a copy of the
* repmgr metadata, so in --force mode we can simply overwrite the
* directory.
*
* For non-witness servers, we'll leave the data in place, both to reduce
* the risk of unintentional data loss and to make it possible for the
* data directory to be brought up-to-date with rsync.
*/
if (for_witness)
{
log_notice(_("deleting existing data directory \"%s\"\n"), dir);
nftw(dir, unlink_dir_callback, 64, FTW_DEPTH | FTW_PHYS);
}
/* Let it continue */ /* Let it continue */
break; break;
} }
@@ -306,3 +336,14 @@ create_pg_dir(char *dir, bool force)
} }
return true; return true;
} }
static int
unlink_dir_callback(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
{
int rv = remove(fpath);
if (rv)
perror(fpath);
return rv;
}

View File

@@ -26,5 +26,6 @@ bool create_dir(char *dir);
bool set_dir_permissions(char *dir); bool set_dir_permissions(char *dir);
bool is_pg_dir(char *dir); bool is_pg_dir(char *dir);
bool create_pg_dir(char *dir, bool force); bool create_pg_dir(char *dir, bool force);
bool create_witness_pg_dir(char *dir, bool force);
#endif #endif

145
config.c
View File

@@ -27,9 +27,11 @@
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 char config_file_path[MAXPGPATH];
static bool config_file_provided = false;
/* /*
* parse_config() * load_config()
* *
* Set default options and overwrite with values from provided configuration * Set default options and overwrite with values from provided configuration
* file. * file.
@@ -40,30 +42,21 @@ static void tablespace_list_append(t_configuration_options *options, const char
* reload_config() * reload_config()
*/ */
bool bool
parse_config(const char *config_file, t_configuration_options *options) load_config(const char *config_file, t_configuration_options *options, char *argv0)
{ {
char *s, struct stat config;
buff[MAXLINELENGTH];
char config_file_buf[MAXLEN];
char name[MAXLEN];
char value[MAXLEN];
bool config_file_provided = false;
FILE *fp;
/* Sanity checks */ /* Sanity checks */
/* /*
* If a configuration file was provided, check it exists, otherwise * If a configuration file was provided, check it exists, otherwise
* emit an error * emit an error and terminate
*/ */
if (config_file[0]) if (config_file[0])
{ {
struct stat config; strncpy(config_file_path, config_file, MAXPGPATH);
canonicalize_path(config_file_path);
strncpy(config_file_buf, config_file, MAXLEN); if (stat(config_file_path, &config) != 0)
canonicalize_path(config_file_buf);
if(stat(config_file_buf, &config) != 0)
{ {
log_err(_("provided configuration file '%s' not found: %s\n"), log_err(_("provided configuration file '%s' not found: %s\n"),
config_file, config_file,
@@ -76,16 +69,53 @@ parse_config(const char *config_file, t_configuration_options *options)
} }
/* /*
* If no configuration file was provided, set to a default file * If no configuration file was provided, attempt to find a default file
* which `parse_config()` will attempt to read if it exists
*/ */
else if (config_file_provided == false)
{ {
strncpy(config_file_buf, DEFAULT_CONFIG_FILE, MAXLEN); char my_exec_path[MAXPGPATH];
char etc_path[MAXPGPATH];
/* First check if one is in the default sysconfdir */
if (find_my_exec(argv0, my_exec_path) < 0)
{
fprintf(stderr, _("%s: could not find own program executable\n"), argv0);
exit(EXIT_FAILURE);
}
get_etc_path(my_exec_path, etc_path);
snprintf(config_file_path, MAXPGPATH, "%s/repmgr.conf", etc_path);
log_debug(_("Looking for configuration file in %s\n"), etc_path);
if (stat(config_file_path, &config) != 0)
{
/* Not found - default to ./repmgr.conf */
strncpy(config_file_path, DEFAULT_CONFIG_FILE, MAXPGPATH);
canonicalize_path(config_file_path);
log_debug(_("Looking for configuration file in %s\n"), config_file_path);
}
}
return parse_config(options);
} }
fp = fopen(config_file_buf, "r"); bool
parse_config(t_configuration_options *options)
{
FILE *fp;
char *s,
buff[MAXLINELENGTH];
char name[MAXLEN];
char value[MAXLEN];
/* For sanity-checking provided conninfo string */
PQconninfoOption *conninfo_options;
char *conninfo_errmsg = NULL;
fp = fopen(config_file_path, "r");
/* /*
* Since some commands don't require a config file at all, not having one * Since some commands don't require a config file at all, not having one
@@ -101,7 +131,7 @@ parse_config(const char *config_file, t_configuration_options *options)
{ {
if (config_file_provided) if (config_file_provided)
{ {
log_err(_("unable to open provided configuration file '%s'; terminating\n"), config_file_buf); log_err(_("unable to open provided configuration file '%s'; terminating\n"), config_file_path);
exit(ERR_BAD_CONFIG); exit(ERR_BAD_CONFIG);
} }
@@ -149,13 +179,17 @@ parse_config(const char *config_file, t_configuration_options *options)
{ {
bool known_parameter = true; bool known_parameter = true;
/* Skip blank lines and comments */
if (buff[0] == '\n' || buff[0] == '#')
continue;
/* Parse name/value pair from line */ /* Parse name/value pair from line */
parse_line(buff, name, value); parse_line(buff, name, value);
/* Skip blank lines */
if (!strlen(name))
continue;
/* Skip comments */
if (name[0] == '#')
continue;
/* Copy into correct entry in parameters struct */ /* Copy into correct entry in parameters struct */
if (strcmp(name, "cluster") == 0) if (strcmp(name, "cluster") == 0)
strncpy(options->cluster_name, value, MAXLEN); strncpy(options->cluster_name, value, MAXLEN);
@@ -262,6 +296,12 @@ parse_config(const char *config_file, t_configuration_options *options)
exit(ERR_BAD_CONFIG); exit(ERR_BAD_CONFIG);
} }
if (options->node == 0)
{
log_err(_("'node' must be an integer greater than zero\n"));
exit(ERR_BAD_CONFIG);
}
if (*options->node_name == '\0') if (*options->node_name == '\0')
{ {
log_err(_("required parameter 'node_name' was not found\n")); log_err(_("required parameter 'node_name' was not found\n"));
@@ -274,6 +314,19 @@ parse_config(const char *config_file, t_configuration_options *options)
exit(ERR_BAD_CONFIG); exit(ERR_BAD_CONFIG);
} }
/* Sanity check the provided conninfo string
*
* NOTE: this verifies the string format and checks for valid options
* but does not sanity check values
*/
conninfo_options = PQconninfoParse(options->conninfo, &conninfo_errmsg);
if (conninfo_options == NULL)
{
log_err(_("Parameter 'conninfo' is invalid: %s"), conninfo_errmsg);
exit(ERR_BAD_CONFIG);
}
PQconninfoFree(conninfo_options);
/* The following checks are for valid parameter values */ /* The following checks are for valid parameter values */
if (options->master_response_timeout <= 0) if (options->master_response_timeout <= 0)
{ {
@@ -331,24 +384,50 @@ parse_line(char *buff, char *name, char *value)
int j = 0; int j = 0;
/* /*
* first we find the name of the parameter * Extract parameter name, if present
*/ */
for (; i < MAXLEN; ++i) for (; i < MAXLEN; ++i)
{ {
if (buff[i] != '=')
name[j++] = buff[i]; if (buff[i] == '=')
else
break; break;
switch(buff[i])
{
/* Ignore whitespace */
case ' ':
case '\n':
case '\r':
case '\t':
continue;
default:
name[j++] = buff[i];
}
} }
name[j] = '\0'; name[j] = '\0';
/* /*
* Now the value * Ignore any whitespace following the '=' sign
*/
for (; i < MAXLEN; ++i)
{
if (buff[i+1] == ' ')
continue;
if (buff[i+1] == '\t')
continue;
break;
}
/*
* Extract parameter value
*/ */
j = 0; j = 0;
for (++i; i < MAXLEN; ++i) for (++i; i < MAXLEN; ++i)
if (buff[i] == '\'') if (buff[i] == '\'')
continue; continue;
else if (buff[i] == '#')
break;
else if (buff[i] != '\n') else if (buff[i] != '\n')
value[j++] = buff[i]; value[j++] = buff[i];
else else
@@ -358,7 +437,7 @@ parse_line(char *buff, char *name, char *value)
} }
bool bool
reload_config(char *config_file, t_configuration_options * orig_options) reload_config(t_configuration_options *orig_options)
{ {
PGconn *conn; PGconn *conn;
t_configuration_options new_options; t_configuration_options new_options;
@@ -369,7 +448,7 @@ reload_config(char *config_file, t_configuration_options * orig_options)
*/ */
log_info(_("reloading configuration file and updating repmgr tables\n")); log_info(_("reloading configuration file and updating repmgr tables\n"));
parse_config(config_file, &new_options); parse_config(&new_options);
if (new_options.node == -1) if (new_options.node == -1)
{ {
log_warning(_("unable to parse new configuration, retaining current configuration\n")); log_warning(_("unable to parse new configuration, retaining current configuration\n"));

View File

@@ -83,9 +83,10 @@ typedef struct
#define T_CONFIGURATION_OPTIONS_INITIALIZER { "", -1, NO_UPSTREAM_NODE, "", MANUAL_FAILOVER, -1, "", "", "", "", "", "", "", -1, -1, -1, "", "", "", "", 0, 0, 0, "", { NULL, NULL }, {NULL, NULL} } #define T_CONFIGURATION_OPTIONS_INITIALIZER { "", -1, NO_UPSTREAM_NODE, "", MANUAL_FAILOVER, -1, "", "", "", "", "", "", "", -1, -1, -1, "", "", "", "", 0, 0, 0, "", { NULL, NULL }, {NULL, NULL} }
bool parse_config(const char *config_file, t_configuration_options *options); bool load_config(const char *config_file, t_configuration_options *options, char *argv0);
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);
bool reload_config(char *config_file, t_configuration_options *orig_options);
#endif #endif

125
dbutils.c
View File

@@ -82,6 +82,72 @@ establish_db_connection_by_params(const char *keywords[], const char *values[],
} }
bool
begin_transaction(PGconn *conn)
{
PGresult *res;
res = PQexec(conn, "BEGIN");
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
log_err(_("Unable to begin transaction: %s\n"),
PQerrorMessage(conn));
PQclear(res);
return false;
}
PQclear(res);
return true;
}
bool
commit_transaction(PGconn *conn)
{
PGresult *res;
res = PQexec(conn, "COMMIT");
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
log_err(_("Unable to commit transaction: %s\n"),
PQerrorMessage(conn));
PQclear(res);
return false;
}
PQclear(res);
return true;
}
bool
rollback_transaction(PGconn *conn)
{
PGresult *res;
res = PQexec(conn, "ROLLBACK");
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
log_err(_("Unable to rollback transaction: %s\n"),
PQerrorMessage(conn));
PQclear(res);
return false;
}
PQclear(res);
return true;
}
bool bool
check_cluster_schema(PGconn *conn) check_cluster_schema(PGconn *conn)
{ {
@@ -1080,8 +1146,8 @@ delete_node_record(PGconn *conn, int node, char *action)
* *
* Note this function may be called with `conn` set to NULL in cases where * Note this function may be called with `conn` set to NULL in cases where
* the master node is not available and it's therefore not possible to write * the master node is not available and it's therefore not possible to write
* an event record. In this case, if `event_notification_command` is set a user- * an event record. In this case, if `event_notification_command` is set, a
* defined notification to be generated; if not, this function will have * user-defined notification to be generated; if not, this function will have
* no effect. * no effect.
*/ */
@@ -1094,7 +1160,7 @@ 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 intialised - 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.
@@ -1283,3 +1349,56 @@ create_event_record(PGconn *conn, t_configuration_options *options, int node_id,
return success; return success;
} }
bool
update_node_record_set_upstream(PGconn *conn, char *cluster_name, int this_node_id, int new_upstream_node_id)
{
PGresult *res;
char sqlquery[QUERY_STR_LEN];
log_debug(_("update_node_record_set_upstream(): Updating node %i's upstream node to %i\n"), this_node_id, new_upstream_node_id);
sqlquery_snprintf(sqlquery,
" UPDATE %s.repl_nodes "
" SET upstream_node_id = %i "
" WHERE cluster = '%s' "
" AND id = %i ",
get_repmgr_schema_quoted(conn),
new_upstream_node_id,
cluster_name,
this_node_id);
res = PQexec(conn, sqlquery);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
log_err(_("Unable to set new upstream node id: %s\n"),
PQerrorMessage(conn));
PQclear(res);
return false;
}
PQclear(res);
return true;
}
PGresult *
get_node_record(PGconn *conn, char *cluster, int node_id)
{
char sqlquery[QUERY_STR_LEN];
sprintf(sqlquery,
"SELECT id, upstream_node_id, conninfo, type, slot_name, active "
" FROM %s.repl_nodes "
" WHERE cluster = '%s' "
" AND id = %i",
get_repmgr_schema_quoted(conn),
cluster,
node_id);
log_debug("get_node_record(): %s\n", sqlquery);
return PQexec(conn, sqlquery);
}

View File

@@ -30,6 +30,9 @@ PGconn *establish_db_connection(const char *conninfo,
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);
bool begin_transaction(PGconn *conn);
bool commit_transaction(PGconn *conn);
bool rollback_transaction(PGconn *conn);
bool check_cluster_schema(PGconn *conn); bool check_cluster_schema(PGconn *conn);
int is_standby(PGconn *conn); int is_standby(PGconn *conn);
bool is_pgup(PGconn *conn, int timeout); bool is_pgup(PGconn *conn, int timeout);
@@ -63,6 +66,7 @@ bool copy_configuration(PGconn *masterconn, PGconn *witnessconn, char *cluster_
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 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);
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);
bool update_node_record_set_upstream(PGconn *conn, char *cluster_name, int this_node_id, int new_upstream_node_id);
PGresult * get_node_record(PGconn *conn, char *cluster, int node_id);
#endif #endif

View File

@@ -12,7 +12,7 @@ REPMGRD_ENABLED=no
#REPMGRD_USER=postgres #REPMGRD_USER=postgres
# repmgrd binary # repmgrd binary
#REPMGR_BIN=/usr/bin/repmgr #REPMGRD_BIN=/usr/bin/repmgrd
# pid file # pid file
#REPMGR_PIDFILE=/var/run/repmgrd.pid #REPMGRD_PIDFILE=/var/run/repmgrd.pid

View File

@@ -35,5 +35,6 @@
#define ERR_BAD_SSH 12 #define ERR_BAD_SSH 12
#define ERR_SYS_FAILURE 13 #define ERR_SYS_FAILURE 13
#define ERR_BAD_BASEBACKUP 14 #define ERR_BAD_BASEBACKUP 14
#define ERR_INTERNAL 15
#endif /* _ERRCODE_H_ */ #endif /* _ERRCODE_H_ */

26
log.c
View File

@@ -144,12 +144,32 @@ logger_init(t_configuration_options * opts, const char *ident, const char *level
{ {
FILE *fd; FILE *fd;
fd = freopen(opts->logfile, "a", stderr); /* Check if we can write to the specified file before redirecting
* stderr - if freopen() fails, stderr output will vanish into
* the ether and the user won't know what's going on.
*/
fd = fopen(opts->logfile, "a");
if (fd == NULL) if (fd == NULL)
{ {
fprintf(stderr, "error reopening stderr to '%s': %s", stderr_log_err(_("Unable to open specified logfile '%s' for writing: %s\n"), opts->logfile, strerror(errno));
opts->logfile, strerror(errno)); stderr_log_err(_("Terminating\n"));
exit(ERR_BAD_CONFIG);
}
fclose(fd);
stderr_log_notice(_("Redirecting logging output to '%s'\n"), opts->logfile);
fd = freopen(opts->logfile, "a", stderr);
/* 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(),
we'll write to stdout as a last resort.
*/
if (fd == NULL)
{
printf(_("Unable to open specified logfile %s for writing: %s\n"), opts->logfile, strerror(errno));
printf(_("Terminating\n"));
exit(ERR_BAD_CONFIG);
} }
} }

495
repmgr.c
View File

@@ -7,18 +7,19 @@
* *
* Commands implemented are: * Commands implemented are:
* *
* MASTER REGISTER * [ MASTER | PRIMARY ] REGISTER
* *
* STANDBY REGISTER * STANDBY REGISTER
* STANDBY UNREGISTER
* STANDBY CLONE * STANDBY CLONE
* STANDBY FOLLOW * STANDBY FOLLOW
* STANDBY PROMOTE * STANDBY PROMOTE
* *
* WITNESS CREATE
*
* CLUSTER SHOW * CLUSTER SHOW
* CLUSTER CLEANUP * CLUSTER CLEANUP
* *
* WITNESS CREATE
*
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
@@ -53,15 +54,23 @@
#define RECOVERY_FILE "recovery.conf" #define RECOVERY_FILE "recovery.conf"
#ifndef TABLESPACE_MAP
#define TABLESPACE_MAP "tablespace_map"
#endif
#define WITNESS_DEFAULT_PORT "5499" /* If this value is ever changed, remember
* to update comments and documentation */
#define NO_ACTION 0 /* Dummy default action */ #define NO_ACTION 0 /* Dummy default action */
#define MASTER_REGISTER 1 #define MASTER_REGISTER 1
#define STANDBY_REGISTER 2 #define STANDBY_REGISTER 2
#define STANDBY_CLONE 3 #define STANDBY_UNREGISTER 3
#define STANDBY_PROMOTE 4 #define STANDBY_CLONE 4
#define STANDBY_FOLLOW 5 #define STANDBY_PROMOTE 5
#define WITNESS_CREATE 6 #define STANDBY_FOLLOW 6
#define CLUSTER_SHOW 7 #define WITNESS_CREATE 7
#define CLUSTER_CLEANUP 8 #define CLUSTER_SHOW 8
#define CLUSTER_CLEANUP 9
@@ -69,7 +78,7 @@ static bool create_recovery_file(const char *data_dir);
static int test_ssh_connection(char *host, char *remote_user); static int test_ssh_connection(char *host, char *remote_user);
static int copy_remote_files(char *host, char *remote_user, char *remote_path, static int copy_remote_files(char *host, char *remote_user, char *remote_path,
char *local_path, bool is_directory, int server_version_num); char *local_path, bool is_directory, int server_version_num);
static int run_basebackup(void); static int run_basebackup(const char *data_dir);
static void check_parameters_for_action(const int action); static void check_parameters_for_action(const int action);
static bool create_schema(PGconn *conn); static bool create_schema(PGconn *conn);
static void write_primary_conninfo(char *line); static void write_primary_conninfo(char *line);
@@ -83,6 +92,7 @@ static char *make_pg_path(char *file);
static void do_master_register(void); static void do_master_register(void);
static void do_standby_register(void); static void do_standby_register(void);
static void do_standby_unregister(void);
static void do_standby_clone(void); static void do_standby_clone(void);
static void do_standby_promote(void); static void do_standby_promote(void);
static void do_standby_follow(void); static void do_standby_follow(void);
@@ -129,7 +139,7 @@ main(int argc, char **argv)
{"port", required_argument, NULL, 'p'}, {"port", required_argument, NULL, 'p'},
{"username", required_argument, NULL, 'U'}, {"username", required_argument, NULL, 'U'},
{"superuser", required_argument, NULL, 'S'}, {"superuser", required_argument, NULL, 'S'},
{"dest-dir", required_argument, NULL, 'D'}, {"data-dir", required_argument, NULL, 'D'},
{"local-port", required_argument, NULL, 'l'}, {"local-port", required_argument, NULL, 'l'},
{"config-file", required_argument, NULL, 'f'}, {"config-file", required_argument, NULL, 'f'},
{"remote-user", required_argument, NULL, 'R'}, {"remote-user", required_argument, NULL, 'R'},
@@ -145,6 +155,8 @@ main(int argc, char **argv)
{"check-upstream-config", no_argument, NULL, 2}, {"check-upstream-config", no_argument, NULL, 2},
{"recovery-min-apply-delay", required_argument, NULL, 3}, {"recovery-min-apply-delay", required_argument, NULL, 3},
{"ignore-external-config-files", no_argument, NULL, 4}, {"ignore-external-config-files", no_argument, NULL, 4},
{"help", no_argument, NULL, '?'},
{"version", no_argument, NULL, 'V'},
{NULL, 0, NULL, 0} {NULL, 0, NULL, 0}
}; };
@@ -158,28 +170,20 @@ main(int argc, char **argv)
progname = get_progname(argv[0]); progname = get_progname(argv[0]);
if (argc > 1)
{
if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
{
help(progname);
exit(SUCCESS);
}
if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
{
printf("%s %s (PostgreSQL %s)\n", progname, REPMGR_VERSION, PG_VERSION);
exit(SUCCESS);
}
}
/* Prevent getopt_long() from printing an error message */ /* Prevent getopt_long() from printing an error message */
opterr = 0; opterr = 0;
while ((c = getopt_long(argc, argv, "d:h:p:U:S:D:l:f:R:w:k:FWIvb:r:c", long_options, while ((c = getopt_long(argc, argv, "?Vd:h:p:U:S:D:l:f:R:w:k:FWIvb:r:c", long_options,
&optindex)) != -1) &optindex)) != -1)
{ {
switch (c) switch (c)
{ {
case '?':
help(progname);
exit(SUCCESS);
case 'V':
printf("%s %s (PostgreSQL %s)\n", progname, REPMGR_VERSION, PG_VERSION);
exit(SUCCESS);
case 'd': case 'd':
strncpy(runtime_options.dbname, optarg, MAXLEN); strncpy(runtime_options.dbname, optarg, MAXLEN);
break; break;
@@ -299,8 +303,10 @@ main(int argc, char **argv)
/* /*
* Now we need to obtain the action, this comes in one of these forms: * Now we need to obtain the action, this comes in one of these forms:
* MASTER REGISTER | STANDBY {REGISTER | CLONE [node] | PROMOTE | FOLLOW * MASTER REGISTER |
* [node]} | WITNESS CREATE CLUSTER {SHOW | CLEANUP} * STANDBY {REGISTER | UNREGISTER | CLONE [node] | PROMOTE | FOLLOW [node]} |
* WITNESS CREATE |
* CLUSTER {SHOW | CLEANUP}
* *
* the node part is optional, if we receive it then we shouldn't have * the node part is optional, if we receive it then we shouldn't have
* received a -h option * received a -h option
@@ -310,6 +316,8 @@ main(int argc, char **argv)
server_mode = argv[optind++]; server_mode = argv[optind++];
if (strcasecmp(server_mode, "STANDBY") != 0 && if (strcasecmp(server_mode, "STANDBY") != 0 &&
strcasecmp(server_mode, "MASTER") != 0 && strcasecmp(server_mode, "MASTER") != 0 &&
/* allow PRIMARY as synonym for MASTER */
strcasecmp(server_mode, "PRIMARY") != 0 &&
strcasecmp(server_mode, "WITNESS") != 0 && strcasecmp(server_mode, "WITNESS") != 0 &&
strcasecmp(server_mode, "CLUSTER") != 0) strcasecmp(server_mode, "CLUSTER") != 0)
{ {
@@ -324,7 +332,7 @@ main(int argc, char **argv)
{ {
server_cmd = argv[optind++]; server_cmd = argv[optind++];
/* check posibilities for all server modes */ /* check posibilities for all server modes */
if (strcasecmp(server_mode, "MASTER") == 0) if (strcasecmp(server_mode, "MASTER") == 0 || strcasecmp(server_mode, "PRIMARY") == 0 )
{ {
if (strcasecmp(server_cmd, "REGISTER") == 0) if (strcasecmp(server_cmd, "REGISTER") == 0)
action = MASTER_REGISTER; action = MASTER_REGISTER;
@@ -333,6 +341,8 @@ main(int argc, char **argv)
{ {
if (strcasecmp(server_cmd, "REGISTER") == 0) if (strcasecmp(server_cmd, "REGISTER") == 0)
action = STANDBY_REGISTER; action = STANDBY_REGISTER;
if (strcasecmp(server_cmd, "UNREGISTER") == 0)
action = STANDBY_UNREGISTER;
else if (strcasecmp(server_cmd, "CLONE") == 0) else if (strcasecmp(server_cmd, "CLONE") == 0)
action = STANDBY_CLONE; action = STANDBY_CLONE;
else if (strcasecmp(server_cmd, "PROMOTE") == 0) else if (strcasecmp(server_cmd, "PROMOTE") == 0)
@@ -425,7 +435,6 @@ main(int argc, char **argv)
if (runtime_options.verbose && runtime_options.config_file[0]) if (runtime_options.verbose && runtime_options.config_file[0])
{ {
log_notice(_("opening configuration file: %s\n"), log_notice(_("opening configuration file: %s\n"),
runtime_options.config_file); runtime_options.config_file);
} }
@@ -435,7 +444,7 @@ main(int argc, char **argv)
* however if available we'll parse it anyway for options like 'log_level', * however if available we'll parse it anyway for options like 'log_level',
* 'use_replication_slots' etc. * 'use_replication_slots' etc.
*/ */
config_file_parsed = parse_config(runtime_options.config_file, &options); config_file_parsed = load_config(runtime_options.config_file, &options, argv[0]);
/* /*
* Initialise pg_bindir - command line parameter will override * Initialise pg_bindir - command line parameter will override
@@ -542,6 +551,9 @@ main(int argc, char **argv)
case STANDBY_REGISTER: case STANDBY_REGISTER:
do_standby_register(); do_standby_register();
break; break;
case STANDBY_UNREGISTER:
do_standby_unregister();
break;
case STANDBY_CLONE: case STANDBY_CLONE:
do_standby_clone(); do_standby_clone();
break; break;
@@ -691,6 +703,7 @@ static void
do_master_register(void) do_master_register(void)
{ {
PGconn *conn; PGconn *conn;
PGconn *master_conn;
bool schema_exists = false; bool schema_exists = false;
int ret; int ret;
@@ -716,56 +729,74 @@ do_master_register(void)
exit(ERR_BAD_CONFIG); exit(ERR_BAD_CONFIG);
} }
/* Check if there is a schema for this cluster */ /* Create schema and associated database objects, if it does not exist */
schema_exists = check_cluster_schema(conn); schema_exists = check_cluster_schema(conn);
/* If schema exists and force option not selected, raise an error */
if(schema_exists && !runtime_options.force)
{
log_notice(_("schema '%s' already exists.\n"), get_repmgr_schema());
PQfinish(conn);
exit(ERR_BAD_CONFIG);
}
if (!schema_exists) if (!schema_exists)
{ {
log_info(_("master register: creating database objects inside the %s schema\n"), log_info(_("master register: creating database objects inside the %s schema\n"),
get_repmgr_schema()); get_repmgr_schema());
/* ok, create the schema */ begin_transaction(conn);
if (!create_schema(conn)) if (!create_schema(conn))
return;
}
else
{ {
PGconn *master_conn; log_err(_("Unable to create repmgr schema - see preceding error message(s); aborting\n"));
rollback_transaction(conn);
PQfinish(conn);
exit(ERR_BAD_CONFIG);
}
commit_transaction(conn);
}
/* Ensure there isn't any other master already registered */ /* Ensure there isn't any other master already registered */
master_conn = get_master_connection(conn, master_conn = get_master_connection(conn,
options.cluster_name, NULL, NULL); options.cluster_name, NULL, NULL);
if (master_conn != NULL)
if (master_conn != NULL && !runtime_options.force)
{ {
PQfinish(master_conn); PQfinish(master_conn);
log_warning(_("there is a master already in cluster %s\n"), log_err(_("there is a master already in cluster %s\n"),
options.cluster_name); options.cluster_name);
exit(ERR_BAD_CONFIG); exit(ERR_BAD_CONFIG);
} }
PQfinish(master_conn);
/* XXX we should check if a node with a different ID is registered as
master, otherwise it would be possible to insert a duplicate record
with --force, which would result in an unwelcome "multi-master" situation
*/
/* Delete any existing record for this node if --force set */
if (runtime_options.force) if (runtime_options.force)
{ {
bool node_record_deleted = delete_node_record(conn, PGresult *res;
bool node_record_deleted;
begin_transaction(conn);
res = get_node_record(conn, options.cluster_name, options.node);
if (PQntuples(res))
{
log_notice(_("deleting existing master record with id %i\n"), options.node);
node_record_deleted = delete_node_record(conn,
options.node, options.node,
"master register"); "master register");
if (node_record_deleted == false) if (node_record_deleted == false)
{ {
PQfinish(master_conn); rollback_transaction(conn);
PQfinish(conn); PQfinish(conn);
exit(ERR_BAD_CONFIG); exit(ERR_BAD_CONFIG);
} }
} }
commit_transaction(conn);
} }
/* Now register the master */ /* Now register the master */
record_created = create_node_record(conn, record_created = create_node_record(conn,
"master register", "master register",
@@ -908,6 +939,85 @@ do_standby_register(void)
} }
static void
do_standby_unregister(void)
{
PGconn *conn;
PGconn *master_conn;
int ret;
bool node_record_deleted;
log_info(_("connecting to standby database\n"));
conn = establish_db_connection(options.conninfo, true);
/* Check we are a standby */
ret = is_standby(conn);
if (ret == 0 || ret == -1)
{
log_err(_(ret == 0 ? "this node should be a standby (%s)\n" :
"connection to node (%s) lost\n"), options.conninfo);
PQfinish(conn);
exit(ERR_BAD_CONFIG);
}
/* Check if there is a schema for this cluster */
if (check_cluster_schema(conn) == false)
{
/* schema doesn't exist */
log_err(_("schema '%s' doesn't exist.\n"), get_repmgr_schema());
PQfinish(conn);
exit(ERR_BAD_CONFIG);
}
/* check if there is a master in this cluster */
log_info(_("connecting to master database\n"));
master_conn = get_master_connection(conn, options.cluster_name,
NULL, NULL);
if (!master_conn)
{
log_err(_("a master must be defined before unregistering a slave\n"));
exit(ERR_BAD_CONFIG);
}
/*
* Verify that standby and master are supported and compatible server
* versions
*/
check_master_standby_version_match(conn, master_conn);
/* Now unregister the standby */
log_info(_("unregistering the standby\n"));
node_record_deleted = delete_node_record(master_conn,
options.node,
"standby unregister");
if (node_record_deleted == false)
{
PQfinish(master_conn);
PQfinish(conn);
exit(ERR_BAD_CONFIG);
}
/* Log the event */
create_event_record(master_conn,
&options,
options.node,
"standby_unregister",
true,
NULL);
PQfinish(master_conn);
PQfinish(conn);
log_info(_("standby unregistration complete\n"));
log_notice(_("standby node correctly unregistered for cluster %s with id %d (conninfo: %s)\n"),
options.cluster_name, options.node, options.conninfo);
return;
}
static void static void
do_standby_clone(void) do_standby_clone(void)
{ {
@@ -1009,14 +1119,14 @@ do_standby_clone(void)
* *
* -T/--tablespace-mapping is not available as a pg_basebackup option for * -T/--tablespace-mapping is not available as a pg_basebackup option for
* PostgreSQL 9.3 - we can only handle that with rsync, so if `--rsync-only` * PostgreSQL 9.3 - we can only handle that with rsync, so if `--rsync-only`
# not set, fail with an error * not set, fail with an error
*/ */
if (options.tablespace_mapping.head != NULL) if (options.tablespace_mapping.head != NULL)
{ {
TablespaceListCell *cell; TablespaceListCell *cell;
if(get_server_version(upstream_conn, NULL) < 90400) if (get_server_version(upstream_conn, NULL) < 90400 && !runtime_options.rsync_only)
{ {
log_err(_("in PostgreSQL 9.3, tablespace mapping can only be used in conjunction with --rsync-only\n")); log_err(_("in PostgreSQL 9.3, tablespace mapping can only be used in conjunction with --rsync-only\n"));
PQfinish(upstream_conn); PQfinish(upstream_conn);
@@ -1184,6 +1294,15 @@ do_standby_clone(void)
if (runtime_options.rsync_only) if (runtime_options.rsync_only)
{ {
PQExpBufferData tablespace_map;
bool tablespace_map_rewrite = false;
/* For 9.5 and greater, create our own tablespace_map file */
if (server_version_num >= 90500)
{
initPQExpBuffer(&tablespace_map);
}
/* /*
* From pg 9.1 default is to wait for a sync standby to ack, avoid that by * From pg 9.1 default is to wait for a sync standby to ack, avoid that by
* turning off sync rep for this session * turning off sync rep for this session
@@ -1258,6 +1377,7 @@ do_standby_clone(void)
initPQExpBuffer(&tblspc_dir_dst); initPQExpBuffer(&tblspc_dir_dst);
initPQExpBuffer(&tblspc_oid); initPQExpBuffer(&tblspc_oid);
appendPQExpBuffer(&tblspc_oid, "%s", PQgetvalue(res, i, 0)); appendPQExpBuffer(&tblspc_oid, "%s", PQgetvalue(res, i, 0));
appendPQExpBuffer(&tblspc_dir_src, "%s", PQgetvalue(res, i, 1)); appendPQExpBuffer(&tblspc_dir_src, "%s", PQgetvalue(res, i, 1));
@@ -1292,8 +1412,21 @@ do_standby_clone(void)
tblspc_dir_src.data, tblspc_dir_dst.data, tblspc_dir_src.data, tblspc_dir_dst.data,
true, server_version_num); true, server_version_num);
/* Update symlink in pg_tblspc */
/* Update symlinks in pg_tblspc */
if (mapping_found == true) if (mapping_found == true)
{
/* 9.5 and later - create a tablespace_map file */
if (server_version_num >= 90500)
{
tablespace_map_rewrite = true;
appendPQExpBuffer(&tablespace_map,
"%s %s\n",
tblspc_oid.data,
tblspc_dir_dst.data);
}
/* Pre-9.5, we have to manipulate the symlinks in pg_tblspc/ ourselves */
else
{ {
PQExpBufferData tblspc_symlink; PQExpBufferData tblspc_symlink;
@@ -1315,13 +1448,49 @@ do_standby_clone(void)
} }
}
}
if(server_version_num >= 90500 && tablespace_map_rewrite == true)
{
PQExpBufferData tablespace_map_filename;
FILE *tablespace_map_file;
initPQExpBuffer(&tablespace_map_filename);
appendPQExpBuffer(&tablespace_map_filename, "%s/%s",
local_data_directory,
TABLESPACE_MAP);
/* Unlink any existing file (it should be there, but we don't care if it isn't) */
if (unlink(tablespace_map_filename.data) < 0 && errno != ENOENT)
{
log_err(_("unable to remove tablespace_map file %s\n"), tablespace_map_filename.data);
exit(ERR_BAD_CONFIG);
}
tablespace_map_file = fopen(tablespace_map_filename.data, "w");
if (tablespace_map_file == NULL)
{
log_err(_("unable to create tablespace_map file '%s'\n"), tablespace_map_filename.data);
exit(ERR_BAD_CONFIG);
}
if (fputs(tablespace_map.data, tablespace_map_file) == EOF)
{
log_err(_("unable to write to tablespace_map file '%s'\n"), tablespace_map_filename.data);
fclose(tablespace_map_file);
exit(ERR_BAD_CONFIG);
}
fclose(tablespace_map_file);
} }
PQclear(res); PQclear(res);
} }
else else
{ {
r = run_basebackup(); r = run_basebackup(local_data_directory);
if (r != 0) if (r != 0)
{ {
log_warning(_("standby clone: base backup failed\n")); log_warning(_("standby clone: base backup failed\n"));
@@ -1449,6 +1618,24 @@ stop_backup:
exit(retval); exit(retval);
} }
/*
* Remove existing WAL from the target directory, since
* rsync's --exclude option doesn't do it.
*/
if (runtime_options.force)
{
char script[MAXLEN];
maxlen_snprintf(script, "rm -rf %s/pg_xlog/*",
local_data_directory);
r = system(script);
if (r != 0)
{
log_err(_("unable to empty local WAL directory %s/pg_xlog/\n"),
local_data_directory);
exit(ERR_BAD_RSYNC);
}
}
/* Finally, write the recovery.conf file */ /* Finally, write the recovery.conf file */
create_recovery_file(local_data_directory); create_recovery_file(local_data_directory);
@@ -1678,6 +1865,7 @@ do_standby_follow(void)
char script[MAXLEN]; char script[MAXLEN];
char master_conninfo[MAXLEN]; char master_conninfo[MAXLEN];
PGconn *master_conn; PGconn *master_conn;
int master_id;
int r, int r,
retval; retval;
@@ -1716,7 +1904,7 @@ do_standby_follow(void)
} }
master_conn = get_master_connection(conn, master_conn = get_master_connection(conn,
options.cluster_name, NULL, (char *) &master_conninfo); options.cluster_name, &master_id, (char *) &master_conninfo);
} }
while (master_conn == NULL && runtime_options.wait_for_master); while (master_conn == NULL && runtime_options.wait_for_master);
@@ -1753,7 +1941,6 @@ do_standby_follow(void)
strncpy(runtime_options.host, PQhost(master_conn), MAXLEN); strncpy(runtime_options.host, PQhost(master_conn), MAXLEN);
strncpy(runtime_options.masterport, PQport(master_conn), MAXLEN); strncpy(runtime_options.masterport, PQport(master_conn), MAXLEN);
strncpy(runtime_options.username, PQuser(master_conn), MAXLEN); strncpy(runtime_options.username, PQuser(master_conn), MAXLEN);
PQfinish(master_conn);
log_info(_("changing standby's master\n")); log_info(_("changing standby's master\n"));
@@ -1785,6 +1972,16 @@ do_standby_follow(void)
exit(ERR_NO_RESTART); exit(ERR_NO_RESTART);
} }
if (update_node_record_set_upstream(master_conn, options.cluster_name,
options.node, master_id) == false)
{
log_err(_("unable to update upstream node"));
PQfinish(master_conn);
exit(ERR_BAD_CONFIG);
}
PQfinish(master_conn);
return; return;
} }
@@ -1808,6 +2005,9 @@ do_witness_create(void)
bool success; bool success;
bool record_created; bool record_created;
PQconninfoOption *conninfo_options;
PQconninfoOption *conninfo_option;
/* Connection parameters for master only */ /* Connection parameters for master only */
keywords[0] = "host"; keywords[0] = "host";
values[0] = runtime_options.host; values[0] = runtime_options.host;
@@ -1818,7 +2018,7 @@ do_witness_create(void)
masterconn = establish_db_connection_by_params(keywords, values, true); masterconn = establish_db_connection_by_params(keywords, values, true);
if (!masterconn) if (!masterconn)
{ {
/* No event logging possible as we can't connect to the master */ /* No event logging possible here as we can't connect to the master */
log_err(_("unable to connect to master\n")); log_err(_("unable to connect to master\n"));
exit(ERR_DB_CON); exit(ERR_DB_CON);
} }
@@ -1873,7 +2073,7 @@ do_witness_create(void)
} }
/* Check this directory could be used as a PGDATA dir */ /* Check this directory could be used as a PGDATA dir */
if (!create_pg_dir(runtime_options.dest_dir, runtime_options.force)) if (!create_witness_pg_dir(runtime_options.dest_dir, runtime_options.force))
{ {
PQExpBufferData errmsg; PQExpBufferData errmsg;
initPQExpBuffer(&errmsg); initPQExpBuffer(&errmsg);
@@ -1949,6 +2149,27 @@ do_witness_create(void)
xsnprintf(buf, sizeof(buf), "\n#Configuration added by %s\n", progname); xsnprintf(buf, sizeof(buf), "\n#Configuration added by %s\n", progname);
fputs(buf, pg_conf); fputs(buf, pg_conf);
/* Attempt to extract a port number from the provided conninfo string
* This will override any value provided with '-l/--local-port', as it's
* what we'll later try and connect to anyway. '-l/--local-port' should
* be deprecated.
*/
conninfo_options = PQconninfoParse(options.conninfo, NULL);
for (conninfo_option = conninfo_options; conninfo_option->keyword != NULL; conninfo_option++)
{
if (strcmp(conninfo_option->keyword, "port") == 0)
{
if (conninfo_option->val != NULL && conninfo_option->val[0] != '\0')
{
strncpy(runtime_options.localport, conninfo_option->val, MAXLEN);
break;
}
}
}
PQconninfoFree(conninfo_options);
/* /*
* If not specified by the user, the default port for the witness server * If not specified by the user, the default port for the witness server
* is 5499; this is intended to support running the witness server as * is 5499; this is intended to support running the witness server as
@@ -1956,7 +2177,7 @@ do_witness_create(void)
* dedicated server. * dedicated server.
*/ */
if (!runtime_options.localport[0]) if (!runtime_options.localport[0])
strncpy(runtime_options.localport, "5499", MAXLEN); strncpy(runtime_options.localport, WITNESS_DEFAULT_PORT, MAXLEN);
xsnprintf(buf, sizeof(buf), "port = %s\n", runtime_options.localport); xsnprintf(buf, sizeof(buf), "port = %s\n", runtime_options.localport);
fputs(buf, pg_conf); fputs(buf, pg_conf);
@@ -2105,6 +2326,19 @@ do_witness_create(void)
/* register ourselves in the master */ /* register ourselves in the master */
if (runtime_options.force)
{
bool node_record_deleted = delete_node_record(masterconn,
options.node,
"witness create");
if (node_record_deleted == false)
{
PQfinish(masterconn);
exit(ERR_BAD_CONFIG);
}
}
record_created = create_node_record(masterconn, record_created = create_node_record(masterconn,
"witness create", "witness create",
options.node, options.node,
@@ -2134,8 +2368,12 @@ do_witness_create(void)
log_info(_("starting copy of configuration from master...\n")); log_info(_("starting copy of configuration from master...\n"));
begin_transaction(witnessconn);
if (!create_schema(witnessconn)) if (!create_schema(witnessconn))
{ {
rollback_transaction(witnessconn);
create_event_record(masterconn, create_event_record(masterconn,
&options, &options,
options.node, options.node,
@@ -2147,6 +2385,8 @@ do_witness_create(void)
exit(ERR_BAD_CONFIG); exit(ERR_BAD_CONFIG);
} }
commit_transaction(witnessconn);
/* copy configuration from master, only repl_nodes is needed */ /* copy configuration from master, only repl_nodes is needed */
if (!copy_configuration(masterconn, witnessconn, options.cluster_name)) if (!copy_configuration(masterconn, witnessconn, options.cluster_name))
{ {
@@ -2199,16 +2439,18 @@ do_witness_create(void)
static void static void
help(const char *progname) help(const char *progname)
{ {
printf(_("\n%s: Replicator manager \n"), progname); printf(_("%s: replication management tool for PostgreSQL\n"), progname);
printf(_("\n"));
printf(_("Usage:\n")); printf(_("Usage:\n"));
printf(_(" %s [OPTIONS] master {register}\n"), progname); printf(_(" %s [OPTIONS] master {register}\n"), progname);
printf(_(" %s [OPTIONS] standby {register|clone|promote|follow}\n"), printf(_(" %s [OPTIONS] standby {register|unregister|clone|promote|follow}\n"),
progname); progname);
printf(_(" %s [OPTIONS] cluster {show|cleanup}\n"), progname); printf(_(" %s [OPTIONS] cluster {show|cleanup}\n"), progname);
printf(_("\nGeneral options:\n")); printf(_("\n"));
printf(_(" --help show this help, then exit\n")); printf(_("General options:\n"));
printf(_(" --version output version information, then exit\n")); printf(_(" -?, --help show this help, then exit\n"));
printf(_(" --verbose output verbose activity information\n")); printf(_(" -V, --version output version information, then exit\n"));
printf(_(" -v, --verbose output verbose activity information\n"));
printf(_("\nConnection options:\n")); printf(_("\nConnection options:\n"));
printf(_(" -d, --dbname=DBNAME database to connect to\n")); printf(_(" -d, --dbname=DBNAME database to connect to\n"));
printf(_(" -h, --host=HOSTNAME database server host or socket directory\n")); printf(_(" -h, --host=HOSTNAME database server host or socket directory\n"));
@@ -2218,11 +2460,12 @@ help(const char *progname)
printf(_(" -b, --pg_bindir=PATH path to PostgreSQL binaries (optional)\n")); printf(_(" -b, --pg_bindir=PATH path to PostgreSQL binaries (optional)\n"));
printf(_(" -D, --data-dir=DIR local directory where the files will be\n" \ printf(_(" -D, --data-dir=DIR local directory where the files will be\n" \
" copied to\n")); " copied to\n"));
printf(_(" -l, --local-port=PORT standby or witness server local port\n"));
printf(_(" -f, --config-file=PATH path to the configuration file\n")); printf(_(" -f, --config-file=PATH path to the configuration file\n"));
printf(_(" -R, --remote-user=USERNAME database server username for rsync\n")); printf(_(" -R, --remote-user=USERNAME database server username for rsync\n"));
printf(_(" -S, --superuser=USERNAME superuser username for witness database\n" \ printf(_(" -S, --superuser=USERNAME superuser username for witness database\n" \
" (default: postgres)\n")); " (default: postgres)\n"));
/* remove this line in the next significant release */
printf(_(" -l, --local-port=PORT (DEPRECATED) witness server local port (default: %s)\n"), WITNESS_DEFAULT_PORT);
printf(_(" -w, --wal-keep-segments=VALUE minimum value for the GUC\n" \ printf(_(" -w, --wal-keep-segments=VALUE minimum value for the GUC\n" \
" wal_keep_segments (default: %s)\n"), DEFAULT_WAL_KEEP_SEGMENTS); " wal_keep_segments (default: %s)\n"), DEFAULT_WAL_KEEP_SEGMENTS);
printf(_(" -k, --keep-history=VALUE keeps indicated number of days of history\n")); printf(_(" -k, --keep-history=VALUE keeps indicated number of days of history\n"));
@@ -2241,8 +2484,10 @@ help(const char *progname)
printf(_(" master register - registers the master in a cluster\n")); printf(_(" master register - registers the master in a cluster\n"));
printf(_(" standby clone [node] - creates a new standby\n")); printf(_(" standby clone [node] - creates a new standby\n"));
printf(_(" standby register - registers a standby in a cluster\n")); printf(_(" standby register - registers a standby in a cluster\n"));
printf(_(" standby unregister - unregisters a standby in a cluster\n"));
printf(_(" standby promote - promotes a specific standby to master\n")); printf(_(" standby promote - promotes a specific standby to master\n"));
printf(_(" standby follow - makes standby follow a new master\n")); printf(_(" standby follow - makes standby follow a new master\n"));
printf(_(" witness create - creates a new witness server\n"));
printf(_(" cluster show - displays information about cluster nodes\n")); printf(_(" cluster show - displays information about cluster nodes\n"));
printf(_(" cluster cleanup - prunes or truncates monitoring history\n" \ printf(_(" cluster cleanup - prunes or truncates monitoring history\n" \
" (monitoring history creation requires repmgrd\n" \ " (monitoring history creation requires repmgrd\n" \
@@ -2471,17 +2716,19 @@ copy_remote_files(char *host, char *remote_user, char *remote_path,
static int static int
run_basebackup() run_basebackup(const char *data_dir)
{ {
char script[MAXLEN]; char script[MAXLEN];
int r = 0; int r = 0;
PQExpBufferData params; PQExpBufferData params;
TablespaceListCell *cell; TablespaceListCell *cell;
/* Creare pg_basebackup command line options */ /* Create pg_basebackup command line options */
initPQExpBuffer(&params); initPQExpBuffer(&params);
appendPQExpBuffer(&params, " -D %s", data_dir);
if (strlen(runtime_options.host)) if (strlen(runtime_options.host))
{ {
appendPQExpBuffer(&params, " -h %s", runtime_options.host); appendPQExpBuffer(&params, " -h %s", runtime_options.host);
@@ -2497,11 +2744,6 @@ run_basebackup()
appendPQExpBuffer(&params, " -U %s", runtime_options.username); appendPQExpBuffer(&params, " -U %s", runtime_options.username);
} }
if(strlen(runtime_options.dest_dir))
{
appendPQExpBuffer(&params, " -D %s", runtime_options.dest_dir);
}
if (runtime_options.fast_checkpoint) { if (runtime_options.fast_checkpoint) {
appendPQExpBuffer(&params, " -c fast"); appendPQExpBuffer(&params, " -c fast");
} }
@@ -2577,6 +2819,23 @@ check_parameters_for_action(const int action)
error_list_append(_("destination directory not required when executing STANDBY REGISTER")); error_list_append(_("destination directory not required when executing STANDBY REGISTER"));
} }
break; break;
case STANDBY_UNREGISTER:
/*
* To unregister a standby we only need the repmgr.conf we don't
* need connection parameters to the master because we can detect
* the master in repl_nodes
*/
if (runtime_options.host[0] || runtime_options.masterport[0] ||
runtime_options.username[0] || runtime_options.dbname[0])
{
error_list_append(_("master connection parameters not required when executing STANDBY UNREGISTER"));
}
if (runtime_options.dest_dir[0])
{
error_list_append(_("destination directory not required when executing STANDBY UNREGISTER"));
}
break;
case STANDBY_PROMOTE: case STANDBY_PROMOTE:
/* /*
@@ -2676,6 +2935,7 @@ check_parameters_for_action(const int action)
} }
/* The caller should wrap this function in a transaction */
static bool static bool
create_schema(PGconn *conn) create_schema(PGconn *conn)
{ {
@@ -2690,8 +2950,11 @@ create_schema(PGconn *conn)
{ {
log_err(_("unable to create the schema %s: %s\n"), log_err(_("unable to create the schema %s: %s\n"),
get_repmgr_schema(), PQerrorMessage(conn)); get_repmgr_schema(), PQerrorMessage(conn));
PQfinish(conn);
exit(ERR_BAD_CONFIG); if (res != NULL)
PQclear(res);
return false;
} }
PQclear(res); PQclear(res);
@@ -2714,6 +2977,10 @@ create_schema(PGconn *conn)
{ {
log_err(_("unable to create the function repmgr_update_last_updated: %s\n"), log_err(_("unable to create the function repmgr_update_last_updated: %s\n"),
PQerrorMessage(conn)); PQerrorMessage(conn));
if (res != NULL)
PQclear(res);
return false; return false;
} }
PQclear(res); PQclear(res);
@@ -2731,6 +2998,10 @@ create_schema(PGconn *conn)
{ {
log_err(_("unable to create the function repmgr_get_last_updated: %s\n"), log_err(_("unable to create the function repmgr_get_last_updated: %s\n"),
PQerrorMessage(conn)); PQerrorMessage(conn));
if (res != NULL)
PQclear(res);
return false; return false;
} }
PQclear(res); PQclear(res);
@@ -2759,8 +3030,11 @@ create_schema(PGconn *conn)
{ {
log_err(_("unable to create table '%s.repl_nodes': %s\n"), log_err(_("unable to create table '%s.repl_nodes': %s\n"),
get_repmgr_schema_quoted(conn), PQerrorMessage(conn)); get_repmgr_schema_quoted(conn), PQerrorMessage(conn));
PQfinish(conn);
exit(ERR_BAD_CONFIG); if (res != NULL)
PQclear(res);
return false;
} }
PQclear(res); PQclear(res);
@@ -2783,8 +3057,11 @@ create_schema(PGconn *conn)
{ {
log_err(_("unable to create table '%s.repl_monitor': %s\n"), log_err(_("unable to create table '%s.repl_monitor': %s\n"),
get_repmgr_schema_quoted(conn), PQerrorMessage(conn)); get_repmgr_schema_quoted(conn), PQerrorMessage(conn));
PQfinish(conn);
exit(ERR_BAD_CONFIG); if (res != NULL)
PQclear(res);
return false;
} }
PQclear(res); PQclear(res);
@@ -2806,8 +3083,11 @@ create_schema(PGconn *conn)
{ {
log_err(_("unable to create table '%s.repl_events': %s\n"), log_err(_("unable to create table '%s.repl_events': %s\n"),
get_repmgr_schema_quoted(conn), PQerrorMessage(conn)); get_repmgr_schema_quoted(conn), PQerrorMessage(conn));
PQfinish(conn);
exit(ERR_BAD_CONFIG); if (res != NULL)
PQclear(res);
return false;
} }
PQclear(res); PQclear(res);
@@ -2840,8 +3120,11 @@ create_schema(PGconn *conn)
{ {
log_err(_("unable to create view %s.repl_status: %s\n"), log_err(_("unable to create view %s.repl_status: %s\n"),
get_repmgr_schema_quoted(conn), PQerrorMessage(conn)); get_repmgr_schema_quoted(conn), PQerrorMessage(conn));
PQfinish(conn);
exit(ERR_BAD_CONFIG); if (res != NULL)
PQclear(res);
return false;
} }
PQclear(res); PQclear(res);
@@ -2857,8 +3140,11 @@ create_schema(PGconn *conn)
{ {
log_err(_("unable to create index 'idx_repl_status_sort' on '%s.repl_monitor': %s\n"), log_err(_("unable to create index 'idx_repl_status_sort' on '%s.repl_monitor': %s\n"),
get_repmgr_schema_quoted(conn), PQerrorMessage(conn)); get_repmgr_schema_quoted(conn), PQerrorMessage(conn));
PQfinish(conn);
exit(ERR_BAD_CONFIG); if (res != NULL)
PQclear(res);
return false;
} }
PQclear(res); PQclear(res);
@@ -2878,6 +3164,8 @@ create_schema(PGconn *conn)
{ {
fprintf(stderr, "Cannot create the function repmgr_update_standby_location: %s\n", fprintf(stderr, "Cannot create the function repmgr_update_standby_location: %s\n",
PQerrorMessage(conn)); PQerrorMessage(conn));
if (res != NULL)
PQclear(res); PQclear(res);
return false; return false;
@@ -2896,7 +3184,10 @@ create_schema(PGconn *conn)
{ {
fprintf(stderr, "Cannot create the function repmgr_get_last_standby_location: %s\n", fprintf(stderr, "Cannot create the function repmgr_get_last_standby_location: %s\n",
PQerrorMessage(conn)); PQerrorMessage(conn));
if (res != NULL)
PQclear(res); PQclear(res);
return false; return false;
} }
PQclear(res); PQclear(res);
@@ -2945,7 +3236,7 @@ write_primary_conninfo(char *line)
} }
maxlen_snprintf(conn_buf, "port=%s%s%s%s%s", maxlen_snprintf(conn_buf, "port=%s%s%s%s%s",
(runtime_options.masterport[0]) ? runtime_options.masterport : "5432", (runtime_options.masterport[0]) ? runtime_options.masterport : DEF_PGPORT_STR,
host_buf, user_buf, password_buf, host_buf, user_buf, password_buf,
appname_buf); appname_buf);
@@ -3259,18 +3550,7 @@ update_node_record_set_master(PGconn *conn, int this_node_id)
log_debug(_("Setting %i as master and marking existing master as failed\n"), this_node_id); log_debug(_("Setting %i as master and marking existing master as failed\n"), this_node_id);
res = PQexec(conn, "BEGIN"); begin_transaction(conn);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
log_err(_("Unable to begin transaction: %s\n"),
PQerrorMessage(conn));
PQclear(res);
return false;
}
PQclear(res);
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
" UPDATE %s.repl_nodes " " UPDATE %s.repl_nodes "
@@ -3289,7 +3569,7 @@ update_node_record_set_master(PGconn *conn, int this_node_id)
PQerrorMessage(conn)); PQerrorMessage(conn));
PQclear(res); PQclear(res);
PQexec(conn, "ROLLBACK"); rollback_transaction(conn);
return false; return false;
} }
@@ -3320,20 +3600,7 @@ update_node_record_set_master(PGconn *conn, int this_node_id)
PQclear(res); PQclear(res);
res = PQexec(conn, "COMMIT"); return commit_transaction(conn);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
log_err(_("Unable to set commit transaction: %s\n"),
PQerrorMessage(conn));
PQclear(res);
return false;
}
PQclear(res);
return true;
} }
@@ -3344,7 +3611,7 @@ do_check_upstream_config(void)
bool config_ok; bool config_ok;
int server_version_num; int server_version_num;
parse_config(runtime_options.config_file, &options); parse_config(&options);
/* Connection parameters for upstream server only */ /* Connection parameters for upstream server only */
keywords[0] = "host"; keywords[0] = "host";

View File

@@ -20,11 +20,9 @@
#ifndef _REPMGR_H_ #ifndef _REPMGR_H_
#define _REPMGR_H_ #define _REPMGR_H_
#include "postgres_fe.h" #include <libpq-fe.h>
#include "libpq-fe.h" #include <postgres_fe.h>
#include <getopt_long.h>
#include "getopt_long.h"
#include "strutil.h" #include "strutil.h"
#include "dbutils.h" #include "dbutils.h"

152
repmgrd.c
View File

@@ -88,11 +88,9 @@ static void check_node_configuration(void);
static void standby_monitor(void); static void standby_monitor(void);
static void witness_monitor(void); static void witness_monitor(void);
static bool check_connection(PGconn *conn, const char *type); static bool check_connection(PGconn **conn, const char *type, const char *conninfo);
static bool set_local_node_failed(void); static bool set_local_node_failed(void);
static bool update_node_record_set_upstream(PGconn *conn, int this_node_id, int new_upstream_node_id);
static void update_shared_memory(char *last_wal_standby_applied); static void update_shared_memory(char *last_wal_standby_applied);
static void update_registration(void); static void update_registration(void);
static void do_master_failover(void); static void do_master_failover(void);
@@ -147,6 +145,8 @@ main(int argc, char **argv)
{"monitoring-history", no_argument, NULL, 'm'}, {"monitoring-history", no_argument, NULL, 'm'},
{"daemonize", no_argument, NULL, 'd'}, {"daemonize", no_argument, NULL, 'd'},
{"pid-file", required_argument, NULL, 'p'}, {"pid-file", required_argument, NULL, 'p'},
{"help", no_argument, NULL, '?'},
{"version", no_argument, NULL, 'V'},
{NULL, 0, NULL, 0} {NULL, 0, NULL, 0}
}; };
@@ -160,21 +160,7 @@ main(int argc, char **argv)
int server_version_num = 0; int server_version_num = 0;
progname = get_progname(argv[0]); progname = get_progname(argv[0]);
if (argc > 1) while ((c = getopt_long(argc, argv, "?Vf:v:mdp:", long_options, &optindex)) != -1)
{
if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
{
help(progname);
exit(SUCCESS);
}
if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
{
printf("%s %s (PostgreSQL %s)\n", progname, REPMGR_VERSION, PG_VERSION);
exit(SUCCESS);
}
}
while ((c = getopt_long(argc, argv, "f:v:mdp:", long_options, &optindex)) != -1)
{ {
switch (c) switch (c)
{ {
@@ -193,6 +179,12 @@ main(int argc, char **argv)
case 'p': case 'p':
pid_file = optarg; pid_file = optarg;
break; break;
case '?':
help(progname);
exit(SUCCESS);
case 'V':
printf("%s %s (PostgreSQL %s)\n", progname, REPMGR_VERSION, PG_VERSION);
exit(SUCCESS);
default: default:
usage(); usage();
exit(ERR_BAD_CONFIG); exit(ERR_BAD_CONFIG);
@@ -208,7 +200,7 @@ main(int argc, char **argv)
* which case we'll need to refactor parse_config() not to abort, * which case we'll need to refactor parse_config() not to abort,
* and return the error message. * and return the error message.
*/ */
parse_config(config_file, &local_options); load_config(config_file, &local_options, argv[0]);
if (daemonize) if (daemonize)
{ {
@@ -319,7 +311,7 @@ main(int argc, char **argv)
check_cluster_configuration(my_local_conn); check_cluster_configuration(my_local_conn);
check_node_configuration(); check_node_configuration();
if (reload_config(config_file, &local_options)) if (reload_config(&local_options))
{ {
PQfinish(my_local_conn); PQfinish(my_local_conn);
my_local_conn = establish_db_connection(local_options.conninfo, true); my_local_conn = establish_db_connection(local_options.conninfo, true);
@@ -353,7 +345,7 @@ main(int argc, char **argv)
*/ */
do do
{ {
if (check_connection(master_conn, "master")) if (check_connection(&master_conn, "master", NULL))
{ {
sleep(local_options.monitor_interval_secs); sleep(local_options.monitor_interval_secs);
} }
@@ -368,10 +360,10 @@ main(int argc, char **argv)
if (got_SIGHUP) if (got_SIGHUP)
{ {
/* /*
* if we can reload, then could need to change * if we can reload the configuration file, then could need to change
* my_local_conn * my_local_conn
*/ */
if (reload_config(config_file, &local_options)) if (reload_config(&local_options))
{ {
PQfinish(my_local_conn); PQfinish(my_local_conn);
my_local_conn = establish_db_connection(local_options.conninfo, true); my_local_conn = establish_db_connection(local_options.conninfo, true);
@@ -432,7 +424,7 @@ main(int argc, char **argv)
check_cluster_configuration(my_local_conn); check_cluster_configuration(my_local_conn);
check_node_configuration(); check_node_configuration();
if (reload_config(config_file, &local_options)) if (reload_config(&local_options))
{ {
PQfinish(my_local_conn); PQfinish(my_local_conn);
my_local_conn = establish_db_connection(local_options.conninfo, true); my_local_conn = establish_db_connection(local_options.conninfo, true);
@@ -483,7 +475,7 @@ main(int argc, char **argv)
* if we can reload, then could need to change * if we can reload, then could need to change
* my_local_conn * my_local_conn
*/ */
if (reload_config(config_file, &local_options)) if (reload_config(&local_options))
{ {
PQfinish(my_local_conn); PQfinish(my_local_conn);
my_local_conn = establish_db_connection(local_options.conninfo, true); my_local_conn = establish_db_connection(local_options.conninfo, true);
@@ -536,7 +528,7 @@ witness_monitor(void)
* of a missing master and promotion of a standby by that standby's * of a missing master and promotion of a standby by that standby's
* repmgrd, so we'll loop for a while before giving up. * repmgrd, so we'll loop for a while before giving up.
*/ */
connection_ok = check_connection(master_conn, "master"); connection_ok = check_connection(&master_conn, "master", NULL);
if (connection_ok == false) if (connection_ok == false)
{ {
@@ -693,6 +685,7 @@ standby_monitor(void)
bool did_retry = false; bool did_retry = false;
PGconn *upstream_conn; PGconn *upstream_conn;
char upstream_conninfo[MAXCONNINFO];
int upstream_node_id; int upstream_node_id;
t_node_info upstream_node; t_node_info upstream_node;
@@ -704,7 +697,7 @@ standby_monitor(void)
* no point in doing much else anyway * no point in doing much else anyway
*/ */
if (!check_connection(my_local_conn, "standby")) if (!check_connection(&my_local_conn, "standby", NULL))
{ {
PQExpBufferData errmsg; PQExpBufferData errmsg;
@@ -730,7 +723,7 @@ standby_monitor(void)
upstream_conn = get_upstream_connection(my_local_conn, upstream_conn = get_upstream_connection(my_local_conn,
local_options.cluster_name, local_options.cluster_name,
local_options.node, local_options.node,
&upstream_node_id, NULL); &upstream_node_id, upstream_conninfo);
type = upstream_node_id == master_options.node type = upstream_node_id == master_options.node
? "master" ? "master"
@@ -742,12 +735,12 @@ standby_monitor(void)
* we cannot reconnect, try to get a new upstream node. * we cannot reconnect, try to get a new upstream node.
*/ */
check_connection(upstream_conn, type); /* this takes up to check_connection(&upstream_conn, type, upstream_conninfo);
* local_options.reconnect_attempts /*
* This takes up to local_options.reconnect_attempts *
* local_options.reconnect_intvl seconds * local_options.reconnect_intvl seconds
*/ */
if (PQstatus(upstream_conn) != CONNECTION_OK) if (PQstatus(upstream_conn) != CONNECTION_OK)
{ {
PQfinish(upstream_conn); PQfinish(upstream_conn);
@@ -879,7 +872,7 @@ standby_monitor(void)
log_err(_("standby node has disappeared, trying to reconnect...\n")); log_err(_("standby node has disappeared, trying to reconnect...\n"));
did_retry = true; did_retry = true;
if (!check_connection(my_local_conn, "standby")) if (!check_connection(&my_local_conn, "standby", NULL))
{ {
set_local_node_failed(); set_local_node_failed();
terminate(0); terminate(0);
@@ -944,8 +937,9 @@ standby_monitor(void)
master_conn = get_master_connection(my_local_conn, master_conn = get_master_connection(my_local_conn,
local_options.cluster_name, local_options.cluster_name,
&master_options.node, NULL); &master_options.node, NULL);
} }
if (PQstatus(master_conn) != CONNECTION_OK)
PQreset(master_conn);
/* /*
* Cancel any query that is still being executed, so i can insert the * Cancel any query that is still being executed, so i can insert the
@@ -1152,8 +1146,8 @@ do_master_failover(void)
total_nodes, visible_nodes); total_nodes, visible_nodes);
/* /*
* am i on the group that should keep alive? if i see less than half of * Am I on the group that should keep alive? If I see less than half of
* total_nodes then i should do nothing * total_nodes then I should do nothing
*/ */
if (visible_nodes < (total_nodes / 2.0)) if (visible_nodes < (total_nodes / 2.0))
{ {
@@ -1524,7 +1518,7 @@ do_master_failover(void)
my_local_conn = establish_db_connection(local_options.conninfo, true); my_local_conn = establish_db_connection(local_options.conninfo, true);
/* update node information to reflect new status */ /* update node information to reflect new status */
if(update_node_record_set_upstream(new_master_conn, node_info.node_id, best_candidate.node_id) == false) if (update_node_record_set_upstream(new_master_conn, local_options.cluster_name, node_info.node_id, best_candidate.node_id) == false)
{ {
appendPQExpBuffer(&event_details, appendPQExpBuffer(&event_details,
_("Unable to update node record for node %i (following new upstream node %i)"), _("Unable to update node record for node %i (following new upstream node %i)"),
@@ -1592,7 +1586,7 @@ do_upstream_standby_failover(t_node_info upstream_node)
* Verify that we can still talk to the cluster master even though * Verify that we can still talk to the cluster master even though
* node upstream is not available * node upstream is not available
*/ */
if (!check_connection(master_conn, "master")) if (!check_connection(&master_conn, "master", NULL))
{ {
log_err(_("do_upstream_standby_failover(): Unable to connect to last known master node\n")); log_err(_("do_upstream_standby_failover(): Unable to connect to last known master node\n"));
return false; return false;
@@ -1668,7 +1662,7 @@ do_upstream_standby_failover(t_node_info upstream_node)
terminate(ERR_BAD_CONFIG); terminate(ERR_BAD_CONFIG);
} }
if(update_node_record_set_upstream(master_conn, node_info.node_id, upstream_node_id) == false) if (update_node_record_set_upstream(master_conn, local_options.cluster_name, node_info.node_id, upstream_node_id) == false)
{ {
terminate(ERR_BAD_CONFIG); terminate(ERR_BAD_CONFIG);
} }
@@ -1681,7 +1675,7 @@ do_upstream_standby_failover(t_node_info upstream_node)
static bool static bool
check_connection(PGconn *conn, const char *type) check_connection(PGconn **conn, const char *type, const char *conninfo)
{ {
int connection_retries; int connection_retries;
@@ -1692,7 +1686,16 @@ check_connection(PGconn *conn, const char *type)
*/ */
for (connection_retries = 0; connection_retries < local_options.reconnect_attempts; connection_retries++) for (connection_retries = 0; connection_retries < local_options.reconnect_attempts; connection_retries++)
{ {
if (!is_pgup(conn, local_options.master_response_timeout)) if (*conn == NULL)
{
if (conninfo == NULL)
{
log_err("INTERNAL ERROR: *conn == NULL && conninfo == NULL");
terminate(ERR_INTERNAL);
}
*conn = establish_db_connection(conninfo, false);
}
if (!is_pgup(*conn, local_options.master_response_timeout))
{ {
log_warning(_("connection to %s has been lost, trying to recover... %i seconds before failover decision\n"), log_warning(_("connection to %s has been lost, trying to recover... %i seconds before failover decision\n"),
type, type,
@@ -1710,9 +1713,9 @@ check_connection(PGconn *conn, const char *type)
} }
} }
if (!is_pgup(conn, local_options.master_response_timeout)) if (!is_pgup(*conn, local_options.master_response_timeout))
{ {
log_err(_("unable to reconnect to %s after %i seconds...\n"), log_err(_("unable to reconnect to %s (timeout %i seconds)...\n"),
type, type,
local_options.master_response_timeout local_options.master_response_timeout
); );
@@ -1740,7 +1743,7 @@ set_local_node_failed(void)
int active_master_node_id = NODE_NOT_FOUND; int active_master_node_id = NODE_NOT_FOUND;
char master_conninfo[MAXLEN]; char master_conninfo[MAXLEN];
if (!check_connection(master_conn, "master")) if (!check_connection(&master_conn, "master", NULL))
{ {
log_err(_("set_local_node_failed(): Unable to connect to last known master node\n")); log_err(_("set_local_node_failed(): Unable to connect to last known master node\n"));
return false; return false;
@@ -1966,17 +1969,21 @@ usage(void)
void void
help(const char *progname) help(const char *progname)
{ {
printf(_("Usage: %s [OPTIONS]\n"), progname); printf(_("%s: replication management daemon for PostgreSQL\n"), progname);
printf(_("Replicator manager daemon for PostgreSQL.\n")); printf(_("\n"));
printf(_("\nOptions:\n")); printf(_("Usage:\n"));
printf(_(" --help show this help, then exit\n")); printf(_(" %s [OPTIONS]\n"), progname);
printf(_(" --version output version information, then exit\n")); printf(_("\n"));
printf(_("Options:\n"));
printf(_(" -?, --help show this help, then exit\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" -v, --verbose output verbose activity information\n")); printf(_(" -v, --verbose output verbose activity information\n"));
printf(_(" -m, --monitoring-history track advance or lag of the replication in every standby in repl_monitor\n")); printf(_(" -m, --monitoring-history track advance or lag of the replication in every standby in repl_monitor\n"));
printf(_(" -f, --config-file=PATH path to the configuration file\n")); printf(_(" -f, --config-file=PATH path to the configuration file\n"));
printf(_(" -d, --daemonize detach process from foreground\n")); printf(_(" -d, --daemonize detach process from foreground\n"));
printf(_(" -p, --pid-file=PATH write a PID file\n")); printf(_(" -p, --pid-file=PATH write a PID file\n"));
printf(_("\n%s monitors a cluster of servers.\n"), progname); printf(_("\n"));
printf(_("%s monitors a cluster of servers and optionally performs failover.\n"), progname);
} }
@@ -2219,23 +2226,12 @@ check_and_create_pid_file(const char *pid_file)
t_node_info t_node_info
get_node_info(PGconn *conn, char *cluster, int node_id) get_node_info(PGconn *conn, char *cluster, int node_id)
{ {
char sqlquery[QUERY_STR_LEN];
PGresult *res; PGresult *res;
t_node_info node_info = { NODE_NOT_FOUND, NO_UPSTREAM_NODE, "", InvalidXLogRecPtr, UNKNOWN, false, false}; t_node_info node_info = { NODE_NOT_FOUND, NO_UPSTREAM_NODE, "", InvalidXLogRecPtr, UNKNOWN, false, false};
sprintf(sqlquery, res = get_node_record(conn, cluster, node_id);
"SELECT id, upstream_node_id, conninfo, type, slot_name, active "
" FROM %s.repl_nodes "
" WHERE cluster = '%s' "
" AND id = %i",
get_repmgr_schema_quoted(conn),
local_options.cluster_name,
node_id);
log_debug("get_node_info(): %s\n", sqlquery);
res = PQexec(my_local_conn, sqlquery);
if (PQresultStatus(res) != PGRES_TUPLES_OK) if (PQresultStatus(res) != PGRES_TUPLES_OK)
{ {
PQExpBufferData errmsg; PQExpBufferData errmsg;
@@ -2299,37 +2295,3 @@ parse_node_type(const char *type)
return UNKNOWN; return UNKNOWN;
} }
static bool
update_node_record_set_upstream(PGconn *conn, int this_node_id, int new_upstream_node_id)
{
PGresult *res;
char sqlquery[QUERY_STR_LEN];
log_debug(_("update_node_record_set_upstream(): Updating node %i's upstream node to %i\n"), this_node_id, new_upstream_node_id);
sqlquery_snprintf(sqlquery,
" UPDATE %s.repl_nodes "
" SET upstream_node_id = %i "
" WHERE cluster = '%s' "
" AND id = %i ",
get_repmgr_schema_quoted(conn),
new_upstream_node_id,
local_options.cluster_name,
this_node_id);
res = PQexec(conn, sqlquery);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
log_err(_("Unable to set new upstream node id: %s\n"),
PQerrorMessage(conn));
PQclear(res);
return false;
}
PQclear(res);
return true;
}

View File

@@ -1,6 +1,6 @@
#ifndef _VERSION_H_ #ifndef _VERSION_H_
#define _VERSION_H_ #define _VERSION_H_
#define REPMGR_VERSION "3.0dev" #define REPMGR_VERSION "3.0.2"
#endif #endif