Compare commits

...

215 Commits

Author SHA1 Message Date
Ian Barwick
274a30efa5 Fix pre-9.6 wal_level check 2016-04-12 16:17:50 +09:00
Ian Barwick
db63b5bb1c Fix hint message formatting 2016-04-12 16:08:07 +09:00
Ian Barwick
e100728b93 Update HISTORY 2016-04-12 15:51:42 +09:00
Ian Barwick
d104f2a914 Update HISTORY 2016-04-12 15:51:42 +09:00
Ian Barwick
2946c097f0 repmgrd: rename some variables to better match the system functions they're populated from 2016-04-12 15:51:42 +09:00
Ian Barwick
a538ceb0ea Remove duplicate inclusion from header file 2016-04-12 15:51:42 +09:00
Ian Barwick
5a2a8d1c82 Update HISTORY 2016-04-12 15:51:41 +09:00
Ian Barwick
b5a7efa58e Preserve failover slots when cloning a standby, if enabled 2016-04-12 15:51:38 +09:00
Ian Barwick
9f6f58e4ed MAXFILENAME -> MAXPGPATH 2016-04-06 11:19:00 +09:00
Craig Ringer
c22f4eaf6f WIP support for preserving failover slots 2016-04-06 11:18:54 +09:00
Martin
925d82f7a4 Add to the documentation the need to have archive_mode and
archive_command set in postgresql.conf

Fixes #154
2016-04-05 21:37:14 -03:00
Ian Barwick
1db577e294 Update Makefile
Add include file dependencies (see caveat in file).

Also update comments.
2016-04-06 09:20:00 +09:00
Martin
a886fddccc We were not checking the return code after rsyncing the tablespaces.
This fixes #168
2016-04-05 15:30:42 -03:00
Martin
83e5f98171 Ignore rsync error code for vanished files.
It's very common to come over vanish files during a backup or rsync
o the data directory (dropped index, temp tables, etc.)

This fixes #149
2016-04-05 15:22:40 -03:00
Ian Barwick
eb31a56186 Enable long option --pgdata as alias for -D/--data-dir
pg_ctl provides -D/--pgdata; we want to be as close to the core utilities
as possible.
2016-04-05 22:30:19 +09:00
Ian Barwick
8cd2c6fd05 Add comment about wal_level setting interpretation 2016-04-04 14:57:20 +09:00
Ian Barwick
e3e1c5de4e Use "immediately_reserve" parameter in pg_create_physical_replication_slot (9.6) 2016-04-04 12:56:00 +09:00
Ian Barwick
f9a150504a Enable repmgr to be compiled with PostgreSQL 9.6 2016-04-04 12:41:03 +09:00
Ian Barwick
5bc809466c Make self-referencing foreign key on repl_nodes table deferrable 2016-04-01 15:19:22 +09:00
Ian Barwick
5d32026b79 Improve debugging output for node resyncing
We'll need this for testing.
2016-04-01 11:29:35 +09:00
Ian Barwick
2a8d6f72c6 Make witness server node update an atomic operation
If the connection to the primary is lost, roll back to the previously
known state.

TRUNCATE is of course not MVCC-friendly, but that shouldn't matter here
as only one process should ever be looking at this table.
2016-04-01 11:07:17 +09:00
Ian Barwick
190cc7dcb4 Rename copy_configuration () to witness_copy_node_records()
As it's witness-specific. Per suggestion from Martín.
2016-04-01 08:44:23 +09:00
Ian Barwick
819937d4bd Replace MAXFILENAME with MAXPGPATH 2016-03-31 20:10:39 +09:00
Ian Barwick
57299cb978 Comment out configuration items in sample config file
The configured values are either the defaults, or examples which
may not work in a real environment. If this file is being used as
a template, the onus is on the user to uncomment and check all
desired parameters.
2016-03-31 15:14:15 +09:00
Ian Barwick
59f503835b Merge branch 'gciolli-master' 2016-03-31 15:00:29 +09:00
Ian Barwick
33e626cd75 Merge branch 'master' of https://github.com/gciolli/repmgr into gciolli-master 2016-03-31 15:00:10 +09:00
Ian Barwick
491ec37adf Update HISTORY 2016-03-31 14:58:50 +09:00
Ian Barwick
c93790fc96 Check directory entity filetype in a more portable way 2016-03-30 20:20:36 +09:00
Ian Barwick
ecabe2c294 Fix pg_ctl path generation in do_standby_switchover() 2016-03-30 16:45:47 +09:00
Gianni Ciolli
2ba57e5938 Rewording comment for clarity. 2016-03-30 09:27:37 +02:00
Ian Barwick
2eec17e25f Add headers as dependencies in Makefile 2016-03-30 10:28:41 +09:00
Ian Barwick
c48c248c15 Regularly sync witness server repl_nodes table.
Although the witness server will resync the repl_nodes table following
a failover, other operations (e.g. removing or cloning a standby)
were previously not reflected in the witness server's copy of this
table.

As a short-term workaround, automatically resync the table at regular
intervals (defined by the configuration file parameter
"witness_repl_nodes_sync_interval_secs", default 30 seconds).
2016-03-29 16:49:28 +09:00
Martín Marqués
958e45f2b8 Merge pull request #165 from dhyannataraj/master
Better use /24 network mask in this example
2016-03-28 13:32:09 -03:00
Nikolay Shaplov
daafd70383 Better use /24 network mask in this example 2016-03-28 18:56:50 +03:00
Ian Barwick
c828598bfb It's unlikely this situation will occur on a witness server
Which is why the error message is for master/standby only.
2016-03-28 15:53:25 +09:00
Ian Barwick
b55519c4a2 Add hint about registering the server after cloning it.
This step is easy to forget.
2016-03-02 09:41:06 +09:00
Ian Barwick
4cafd443e1 README: Add note about 'repmgr_funcs' 2016-02-25 16:36:19 +09:00
Ian Barwick
d400d7f9ac repmgrd: fix error message 2016-02-24 15:33:36 +09:00
Ian Barwick
62bb3db1f8 Fix code comment 2016-02-24 15:27:08 +09:00
Ian Barwick
d9961bbb17 Minor fixes to README.md 2016-02-23 14:25:26 +09:00
Ian Barwick
e1b8982c14 Ensure witness node is registered before the repl_nodes table is copied
This fixes a bug introduced into the previous commit, where the
witness node was registered last to prevent a spurious node record
being created even if witness server creation failed.
2016-02-23 07:36:41 +09:00
Martin
2fe3b3c2a3 Fix a few paragraphs from the README.md. 2016-02-22 09:20:11 -03:00
Ian Barwick
c6e1bc205a Prevent repmgr/repmgrd running as root 2016-02-22 14:58:17 +09:00
Ian Barwick
7241391ddc Better handling of errors during witness creation
Ensure witness is only registered after all steps for creation
have been successfully completed.

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

This addresses GitHub issue #146.
2016-02-16 14:28:33 +09:00
Ian Barwick
c8f449f178 witness creation: extract database and user names from the local conninfo string
99.9% of the time they'll be the same as the primary connection, but
it's more consistent to use the provided local conninfo string
(from which the port is already extracted).
2016-02-09 15:59:28 +09:00
Ian Barwick
49420c437f README.md: update witness server section 2016-02-09 15:58:51 +09:00
Ian Barwick
827ffef5f9 Add '-P/--pwprompt' option for "repmgr create witness"
Optionally prompt for superuser and repmgr user when creating a witness.
This ensures a password can be provided if the primary's pg_hba.conf
mandates it.

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

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

Partial fix for GitHub issue #145.
2016-02-09 15:11:50 +09:00
Ian Barwick
16296bb1c3 Bump dev version number 2016-02-01 15:10:03 +09:00
Ian Barwick
c9c18d6216 Update comments in SQL upgrade file 2016-02-01 13:49:58 +09:00
Ian Barwick
d21f506614 Add placeholders for old FAILOVER.rst and QUICKSTART.md pointing to new README.md 2016-02-01 08:02:37 +09:00
Ian Barwick
fbad18085e README.md: formatting and spelling fixes 2016-01-29 17:09:12 +09:00
Ian Barwick
ca08b1c3bb REAMDE.md: formatting 2016-01-29 16:55:47 +09:00
Ian Barwick
3d95fab0ac README.md: minor corrections 2016-01-29 16:47:12 +09:00
Ian Barwick
12d6ce4629 Bump version 2016-01-28 16:09:06 +09:00
Ian Barwick
dfb34ae7b6 Update HISTORY 2016-01-28 11:44:28 +09:00
Ian Barwick
98c4eb002a README: add replication slot example 2016-01-28 11:43:42 +09:00
Ian Barwick
faed8a65f7 Add new README file
Replaces existing legacy documentation.
2016-01-28 11:32:19 +09:00
Ian Barwick
a81cf04614 Update various comments 2016-01-28 11:13:38 +09:00
Ian Barwick
ca6cbcf965 Add sanity checks to be sure pg_rewind can be used before executing a switchover 2016-01-28 09:25:00 +09:00
Ian Barwick
16c1e13019 Sanity check for presence of pg_rewind on remote server 2016-01-28 07:25:23 +09:00
Ian Barwick
1375adcac8 Standardize capitalisation in log messages 2016-01-28 07:24:45 +09:00
Ian Barwick
e859a58405 Change some repmgrd log messages to NOTICE
So key events during failover on promoted and following standbys
logged at the same level.
2016-01-27 18:39:27 +09:00
Ian Barwick
1a6d830314 Various logging, help output and comment tweaks 2016-01-27 17:10:54 +09:00
Ian Barwick
a96f478a43 Allow negative values in configuration parameters, where appropriate.
Make the code match the documentation.

As pointed out by GitHub user phyber (#142).

Also various other minor improvements to error reporting during
config file parsing.
2016-01-27 09:10:19 +09:00
Ian Barwick
8f20ab16dd Display default log level in -?/--help output 2016-01-25 14:39:21 +09:00
Ian Barwick
3ec436f30d Better document default values in repmgr.conf.sample 2016-01-22 15:22:22 +09:00
Ian Barwick
61e00bf1c7 Improve handling of default connection parameters 2016-01-22 14:14:14 +09:00
Ian Barwick
5d71869fc1 --help output: put default values in quotation marks
Similar to psql.
2016-01-20 15:56:26 +09:00
Ian Barwick
7598e08b6f Display default connection options in --help output 2016-01-20 15:46:41 +09:00
Ian Barwick
ba71e1eedf Use maxlen_snprintf() in do_witness_create() 2016-01-20 15:13:52 +09:00
Ian Barwick
a4c07b23fb Document --pg_rewind option in help output 2016-01-20 15:09:03 +09:00
Ian Barwick
0c36f921f7 help() -> do_help()
For consistency and easier location of the function body.
2016-01-20 15:02:03 +09:00
Ian Barwick
8ac5a5444e Enable repmgr standby switchover for 9.3/9.4 by recloning
A bit of a hack and unsuited for large databases - install
pg_rewind instead. Or upgrade to 9.5.
2016-01-20 14:51:16 +09:00
Ian Barwick
f60e7346e2 Don't copy 'recovery.done' or 'recovery.conf' when cloning a standby
'recovery.conf' will be overwritten, but we don't want a 'recovery.done'
for another server.
2016-01-20 14:34:13 +09:00
Ian Barwick
855ca8fe1a Support separately-compiled pg_rewind for "repmgr standby switchover" in 9.3/9.4 2016-01-20 14:21:02 +09:00
Ian Barwick
daa79d1a0f Remove any recovery.done file copied in by pg_rewind
It'll be the old standby/new primary's old recovery.conf file,
which we don't want floating about confusing things.
2016-01-20 13:18:52 +09:00
Ian Barwick
211768d911 Support pg_rewind when executing "repmgr standby switchover"
9.5 and later.
2016-01-20 13:05:47 +09:00
Ian Barwick
f982708b35 Add function test_db_connection()
The difference between this and establish_db_connection() is that
it outputs any connection failure as a [NOTICE] rather than an
[ERROR]; it's intended for use in e.g. polling a server to wait
for it to come up/go down, while preventing [ERROR] log lines
which may cause confusion.
2016-01-20 07:56:03 +09:00
Ian Barwick
995083d66c Change event record type for repmgr standby follow to 'standby_follow' for consistency 2016-01-19 14:53:16 +09:00
Ian Barwick
be58d6af96 Update TODO 2016-01-19 13:57:46 +09:00
Ian Barwick
a52e97e622 Update FAQ 2016-01-19 13:53:18 +09:00
Ian Barwick
cc1ea00333 Ensure event logging doesn't generate an error when cloning from a standby 2016-01-19 13:51:49 +09:00
Ian Barwick
ec3596521f Fix comment typo 2016-01-19 11:39:41 +09:00
Ian Barwick
66245ccc03 Add warning if -r used with -c 2016-01-14 23:12:41 +09:00
Ian Barwick
c7542063be Only display -c/--fast-checkpoint hint when using pg_basebackup 2016-01-14 22:51:02 +09:00
Ian Barwick
2633d994ef Add FAQ item 2016-01-14 21:00:30 +09:00
Ian Barwick
5359d45463 Remove deprecated -l/--local-port from --help output
We'll still parse it for backwards compatibility
2016-01-14 20:51:03 +09:00
Ian Barwick
efa60d142c Update HISTORY 2016-01-14 11:49:55 +09:00
Ian Barwick
f3d0ab9ab9 Improve "archive_mode" configuration check
There's no compelling reason to require "archive_mode" to be enabled
for streaming replication. It is of course a good idea to archive WAL
using e.g. barman ( http://www.pgbarman.org/ ) as part of a comprehensive
backup strategy, but repmgr and streaming replication work fine without
it.

Per GitHub #141.

Also revise the configuration check for "archive_command" to be
triggered only when "archive_mode" is not "off", as from PostgreSQL
9.5 onwards "archive_mode" can also be "on" or "always".
2016-01-14 09:33:08 +09:00
Ian Barwick
7e6bac1be6 Display a couple of repetitive log messages in verbose mode only 2016-01-08 11:10:34 +09:00
Ian Barwick
b72058dba8 Update copyright notice to 2016 2016-01-05 15:57:46 +09:00
Ian Barwick
79d1332f9c Update HISTORY 2016-01-05 13:54:17 +09:00
Ian Barwick
cde721e3fc repmgr: -r/--rsync-only does not require a parameter 2016-01-05 11:04:20 +09:00
Ian Barwick
7b2439b824 repmgrd: -v/--verbose option does not require a parameter 2016-01-05 10:45:47 +09:00
Martin
787cd94142 Changing the "Maintainer" field in the Debian control file to a generic
value.
Who ever wants to build a deb from the tar-ball can modify the control file
and add his contact information.
2016-01-04 13:33:25 -03:00
Martin
056e64f635 Bug in the control file: Version should not have a 'v' in front 2016-01-04 13:28:51 -03:00
Martín Marqués
6b5a609d30 Merge pull request #72 from Bengrunt/patch-1
Updated makefile for deb creation

There's still a bug in the Version field of the Control file (it shouldn't have a 'v' in front of the version).

Will fix that immediately after.
2016-01-04 13:26:59 -03:00
Ian Barwick
7a4d84379c Prevent invalid replication_lag values being written to the monitoring table
A fix for this was introduced with commit ee9270fe8d
and removed in 4f1c67a1bf.

Refactor the original fix to simply omit attempting to write an invalid entry
to the monitoring table.
2016-01-04 13:31:50 +09:00
Ian Barwick
490e12b1af Clean up whitespace and comments 2016-01-04 11:58:33 +09:00
Martín Marqués
7b9df3ac8f Merge pull request #133 from martinmarques/fix-standby-follows-other-node-repmgrd-fails
Fix standby follows other node repmgrd fails
2015-12-29 13:25:09 -03:00
Martín Marqués
d6bf870316 Merge pull request #131 from martinmarques/fix-failed-standby
Fix failed standby
2015-12-29 13:24:08 -03:00
Ian Barwick
b15e8debe1 No need to manually create repmgr schema. 2015-12-29 11:56:39 +09:00
Martín Marqués
310faf1bd9 Merge pull request #137 from martinmarques/create_view_repl_show_nodes
We need to update the repmgr.sql file with the new view and create a
2015-12-22 13:29:47 -03:00
Martin
35caeaa66a We need to update the repmgr.sql file with the new view and create a
repmgr3.0_repmgr3.1.sql for the upgrade process when passing to 3.1
2015-12-22 13:27:42 -03:00
Ian Barwick
ba300c58f7 Make "cluster show" output dynamic
Calculate the width of the "Name" and "Upstream" columns dynamically.

Based on pull request #135 by sengaya, edited and modified by myself
to include a psql-like separator line.
2015-12-22 15:03:04 +09:00
Ian Barwick
f2370de2fa Minor fixes related to changes in 56b9ca79 2015-12-22 13:35:52 +09:00
Ian Barwick
3920deb803 Merge branch 'create_view_repl_show_nodes' of https://github.com/martinmarques/repmgr 2015-12-22 13:24:42 +09:00
Ian Barwick
e452bf6601 Short option -c does not take a value 2015-12-22 12:35:42 +09:00
Ian Barwick
167b4efbb3 Add note about why 'hot_standby=on' is currently required 2015-12-22 10:47:44 +09:00
Martin
56b9ca7992 Re-write part of commit from #116 so we have the query as a view to
use from the database.
Use the view instead of the query in cluster_show()
2015-12-18 20:53:52 -03:00
Martín Marqués
9c002c7e38 Merge pull request #116 from renard/follow
Add more information in "cluster show"
2015-12-18 18:31:07 -03:00
Ian Barwick
cfec04d19f Modify log output to hint 2015-12-18 17:24:04 +09:00
Martin
4f1c67a1bf This doesn't really mean the standby s following a new master, so we are
removing it.
Basically, on startup the standby will start receiving again from the
begining of the WAL and so received will be lower then applied.

A proper code is needed to make sure the standby is still following the
correct master (as per node information)
2015-12-17 12:17:03 -03:00
Martín Marqués
2f4fd2b7fa Merge pull request #110 from kjoe/master
Debian init script repmgrd process stop fix

This means rejecting pull request on #121
2015-12-12 08:46:56 -03:00
Martín Marqués
aca2b9547f Change where we activate back the standby node that was failed.
We will do it where we are sending the message that says that the
standby has recovered, eliminating some complexity
2015-12-11 09:36:48 -03:00
Martín Marqués
c9db7f57d2 Fix bug discovered last week which prevents recovered standby from being
used in the cluster.
Main issue was that if the local repmgrd was not able to connect locally,
it would set the local node as failed (active = false). This is fine, because
we actually don't know if the node is active (actually, it's not active ATM)
so it's best to keep it out of the cluster.
The problem is that if the postgres service comes back up, and is able to
recover by it self, then we should ack that fact and set it as active.
There was another issue related with repmgrd being terminated if the postgres
service was downs. This is not the correct thing to do: we should keep
trying to connect to the local standby.
2015-12-07 16:14:19 -03:00
Martín Marqués
96ac39ba0f Fix bug discovered last week which prevents recovered standby from being
used in the cluster.
Main issue was that if the local repmgrd was not able to connect locally,
it would set the local node as failed (active = false). This is fine, because
we actually don't know if the node is active (actually, it's not active ATM)
so it's best to keep it out of the cluster.
The problem is that if the postgres service comes back up, and is able to
recover by it self, then we should ack that fact and set it as active.
There was another issue related with repmgrd being terminated if the postgres
service was downs. This is not the correct thing to do: we should keep
trying to connect to the local standby.
2015-12-07 15:59:28 -03:00
Ian Barwick
88a3378203 Update HISTORY 2015-11-30 16:56:34 +09:00
Ian Barwick
4db0efab47 pg_replslot will only exist in 9.4 and later
We need to clean this up regardless of whether "use_replication_slots"
is set.
2015-11-30 15:40:43 +09:00
Ian Barwick
864d57953a Ensure pg_replslot directory is cleaned up after "standby clone" with rsync
This ensures the directory is in the same state as it would be
after cloning the standby with pg_basebackup, i.e. empty.
2015-11-30 15:31:43 +09:00
Ian Barwick
84d2a292b2 Update TODO 2015-11-30 14:26:00 +09:00
Ian Barwick
62d53b7622 Drop a previously created replication slot if base backup fails for any reason
Per Github #129
2015-11-30 12:39:19 +09:00
Ian Barwick
77d52adb53 Use existing config file options when attempting to connect to server during switchover 2015-11-30 12:20:43 +09:00
Ian Barwick
7a3e2f2a3a Update help output with switchover-related commands 2015-11-30 12:20:37 +09:00
Ian Barwick
120688013e Add "standby switchover" mode
Perform a switchover by:
 - stopping current primary node
 - promoting this standby node to primary
 - forcing previous primary node to follow this node

Caveats:
 - repmgrd must not be running, otherwise it may
   attempt a failover
   (TODO: find some way of notifying repmgrd of planned
    activity like this)
 - currently only set up for two-node operation; any other
   standbys will probably become downstream cascaded standbys
   of the old primary once it's restarted
 - as we're executing repmgr remotely (on the old primary),
   we'll need the location of its configuration file; this
   can be provided explicitly with -C/--remote-config-file,
   otherwise repmgr will look in default locations on the
   remote server
 - this does not yet support "rewinding" stopped nodes
   which will be unable to catch up with the primary

TODO:
 - update help, docs
 - make connection test timeouts/intervals configurable
2015-11-30 12:20:24 +09:00
Ian Barwick
f6d1db5edb Ensure all failures encountered during a base backup jump to the stop_backup label 2015-11-30 12:12:59 +09:00
Ian Barwick
02729d299b Put "starting backup" notice after any slot creation 2015-11-30 12:12:49 +09:00
Abhijit Menon-Sen
88a6a1376e If we're using replication slots, we need to create them earlier
Otherwise, if the backup takes a long time, we might lose WAL we need
long before we create the slot.
2015-11-30 12:12:49 +09:00
Ian Barwick
67df082ee9 Ensure 'master register --force' can't create more than one active primary node record 2015-11-26 10:55:16 +09:00
Ian Barwick
9ed71d6317 Remove unusable setting
Not a configuration item or command line option;
variable is always false.
2015-11-26 10:20:54 +09:00
Ian Barwick
933647d6de Make t_node_info generally available
And have it include all the fields from the repl_nodes table.
2015-11-25 12:57:18 +09:00
Ian Barwick
f99018b202 Add item about hash indexes. 2015-11-24 13:26:36 +09:00
Ian Barwick
ced87373cd Remove hint about hash indexes entirely.
Anyone needing them, particularly in a replication context, should
know what they're doing anyway.

See also: http://www.postgresql.org/docs/current/interactive/sql-createindex.html#AEN74175

"Also, changes to hash indexes are not replicated over streaming or file-based
 replication after the initial base backup, so they give wrong answers to
 queries that subsequently use them. For these reasons, hash index use is presently
 discouraged."
2015-11-24 13:16:46 +09:00
Ian Barwick
1db22546a9 Add missing 'break' 2015-11-23 20:16:42 +09:00
Ian Barwick
7ae0df9c85 Remove unused variable 2015-11-23 16:42:53 +09:00
Ian Barwick
7a80f7a096 Shift some common but not terribly informative log messages to verbose mode only 2015-11-23 14:23:40 +09:00
Ian Barwick
8710e067d0 Logging fixes 2015-11-23 14:07:14 +09:00
Ian Barwick
793950eabd Update TODO 2015-11-23 10:53:59 +09:00
Ian Barwick
d1b4280182 Add /etc/repmgr.conf as a default configuration file location
Also refactor configuration file handling while we're at it.

Previously a configuration file would be ignored if it couldn't
be opened, however that is now treated as an error.
2015-11-19 15:16:18 +09:00
Ian Barwick
64d038c823 Simplify logger_init() parameters
We're passing the t_configuration_options structure anyway, no need to
pass items it contains as separate parameters.
2015-11-19 14:05:20 +09:00
Ian Barwick
46dd734b3d Update code comments 2015-11-19 12:55:13 +09:00
Ian Barwick
0a2e4466aa Update TODO 2015-11-19 11:38:44 +09:00
Ian Barwick
17ab86f7ac Don't display warnings about unused command line parameters in --terse mode 2015-11-19 11:09:36 +09:00
Ian Barwick
d433982af7 repmgr: don't error out on superfluous command line options
When parsing command line arguments in check_parameters_for_action(),
create warnings for paramters supplied but not required (e.g. -D/--data-dir
for MASTER REGISTER), rather than fail with error(s), as the
presence of the parameters won't cause any problems.

Errors will still be raised for required-but-missing parameters, of course.
2015-11-19 10:19:15 +09:00
Ian Barwick
869b6a7a06 Remove implemented TODO item 2015-11-19 09:38:20 +09:00
Ian Barwick
9018dc65de Metadata update also handled by repmgr 2015-11-18 13:17:51 +09:00
Ian Barwick
9cbd8df089 When following a new primary, have repmgr (not repmgrd) create the new slot 2015-11-18 13:06:56 +09:00
Ian Barwick
67a81d1d47 Minor log message fixes 2015-11-18 09:10:22 +09:00
Ian Barwick
ab70007b75 Add a TODO item 2015-11-17 17:13:26 +09:00
Ian Barwick
0145aa0fc3 Update TODO 2015-11-17 15:42:36 +09:00
Ian Barwick
493c307b23 Remove implemented items from TODO list
* repmgr: add explicit --log-level flag, repurpose --verbose flag to
  show extra detailed/repetitive output only (see item below too)

  -> e0cbdd5b31

* debug output: show some repetitive output only if --verbose flag set to prevent
  excessive log growth

  -> 8ab1901a93
2015-11-17 15:35:13 +09:00
Ian Barwick
fc6225a511 Refactor get_master_connection() and update description
Use 'remote_conn' instead of 'master_conn', as the connection
handle can potentially be used for any node.
2015-11-17 13:59:28 +09:00
Ian Barwick
e3111d37ba get_master_connection(): order node list by node type and priority
This should make it more likely that the actual primary is first
in the retrieved list, reducing the number of connections to
other nodes in the cluster which need to be made.
2015-11-17 13:59:28 +09:00
Ian Barwick
2a1a9f2e61 Code formatting 2015-11-17 11:01:53 +09:00
Ian Barwick
71a667ecb8 Fix variable argument handling with log_hint()/log_verbose() 2015-11-17 07:34:20 +09:00
Ian Barwick
3ab91730c3 get_master_connection(): possible to use is_standby() now 2015-11-16 17:49:02 +09:00
Ian Barwick
dd7f9b79ae Tidy up logging output in dbutils.c
Log all executed SQL if verbose mode is enabled.
2015-11-16 17:39:42 +09:00
Ian Barwick
8ab1901a93 Repurpose -v/--verbose; add -t/--terse option (repmgr only)
repmgr and particularly repmgrd currently produce substantial
amounts of log output. Much of this is only useful when troubleshooting
or debugging.

Previously the -v/--verbose option just forced the log level to
INFO. With repmgrd this is pretty pointless - just set the log
level in the configuration file. With repmgr the configuration
file can be overriden by the new -L/--log-level option.

-v/--verbose now provides an additional, chattier/pedantic level
of logging ("Opening *this* logfile", "Executing *this* query",
"running in *this* loop") which is helpful for understanding
repmgr/repmgrd's behaviour, particularly for troubleshooting.
What additional verbose logging is generated will of course a
also depends on the log level set, so e.g. someone trying to
work out which configuration file is actually being opened
can use '--log-level=INFO --verbose' without being bothered
by an avalanche of extra verbose debugging output.

-t/--terse option will silence certain non-essential output, at
the moment any HINTs.

Note that -v/--verbose and -t/--terse are not mutually exclusive
(suggestions for better names welcome).
2015-11-16 13:06:32 +09:00
Ian Barwick
e0cbdd5b31 Add -L/--log-level command line option to repmgr
Overrides any setting in the config file. This will replace the
-v/--verbose option.
2015-11-13 20:54:58 +09:00
Ian Barwick
d62aaeedd0 Change directory warning to a hint 2015-11-13 20:28:27 +09:00
Ian Barwick
05cc7091b5 Explicitly mark static functions as static 2015-11-13 19:46:12 +09:00
Ian Barwick
d192d5665c detect_log_level(): return -1 to indicate invalid log level
0 is EMERG, which is not actually used but is valid. Prior to this
change, repmgr would complain about an invalid log level if set to
this.
2015-11-13 19:39:43 +09:00
Ian Barwick
3848b9011b README.md: add note about setting repmgr user search path 2015-11-13 17:05:45 +09:00
Ian Barwick
487aadc4b9 Add TODO items 2015-11-13 14:51:27 +09:00
Ian Barwick
3f5920a395 Add hint about -c/--fast-checkpoint
When cloning a server without this option, and pg_start_backup() takes time
to complete, repmgr appears to hang and give no indication of what may
or may not be happening. The hint provides an explanation for any
delay and possible action which could be taken to mitigate it.
2015-11-13 14:47:10 +09:00
Ian Barwick
617ea8cb78 Add log_hint() function for logging hints
There are a few places where additional hints are written as log
output, usually LOG_NOTICE. Create an explicit function to provide
hints in a standardized manner; by storing the log level of the
previous logger call, we can ensure the hint is only displayed when
the log message itself would be.

Part of an ongoing effort to better control repmgr's logging output.
2015-11-13 14:29:11 +09:00
Ian Barwick
142517fcca Always use catalog path when calling system functions
Removes any risk of issues due to search path mangling etc.
2015-11-11 11:17:47 +09:00
Ian Barwick
d722e2c74b Clean up help output
master/primary register only has one option
2015-11-10 10:49:29 +09:00
Ian Barwick
abb02cab76 Improve configuration file parsing
Related to Github #127.

- use the previously introduced repmgr_atoi() function to parse
  integers better
- collate all detected errors and output as a list, rather than
  failing on the first error.
2015-11-09 14:56:35 +09:00
Ian Barwick
8e66e4811c Rename variable 'reconnect_intvl' to 'reconnect_interval'
For consistency with the configuration file parameter name
2015-11-09 11:04:42 +09:00
Ian Barwick
ce5a541960 Use strtol() to parse config file arguments too 2015-11-09 11:02:04 +09:00
Ian Barwick
e12be52fa8 Use strtol() in place of atoi() to better verify integer parameters
Per GitHub #127
2015-11-09 11:00:53 +09:00
Ian Barwick
c0911d3286 cluster cleanup: standardize error message and return code 2015-11-05 15:54:58 +09:00
Ian Barwick
6e94432282 Fix log wording 2015-11-05 15:42:07 +09:00
Ian Barwick
29d9232e2f Add informtative logging output for 'repmgr cluster cleanup'
Per Github issue #126.
2015-11-05 15:38:40 +09:00
Ian Barwick
8973812144 "How many" -> "Number of" 2015-11-05 13:51:05 +09:00
Ian Barwick
e775a962ad "in 9.4" -> "from 9.4" 2015-11-05 13:49:36 +09:00
Ian Barwick
12204f7e56 Add note about logfile rotation and repmgrd 2015-11-03 15:34:43 +09:00
Ian Barwick
684f7590b7 Point out existence of the FAQ 2015-11-03 15:22:55 +09:00
Ian Barwick
9d589a780d Wording tweak to prevent ambiguity. 2015-11-03 15:18:57 +09:00
Ian Barwick
83e6d15410 Add TODO item 2015-10-30 17:03:01 +09:00
Ian Barwick
6a10fe0cd9 Replace "slave" with "standby" for consistency
"standby" is used everywhere except in these two error messages.
2015-10-30 16:59:39 +09:00
Ian Barwick
c664682c05 Remove duplicated TODO item 2015-10-30 16:58:38 +09:00
Ian Barwick
44acc8d719 Update HISTORY 2015-10-28 16:13:41 +09:00
Ian Barwick
b911483d5e Specify relevant node in error message 2015-10-28 16:10:08 +09:00
Ian Barwick
ee9270fe8d Terminate repmgrd if standby is no longer connected to upstream 2015-10-28 16:05:35 +09:00
Ian Barwick
d0a4eebeec Update FAQ entry about witness port specification 2015-10-27 18:26:38 +09:00
Ian Barwick
0f5e71f029 Add FAQ item about repmgr permissions in pg_hba.conf 2015-10-27 18:24:02 +09:00
Ian Barwick
dbd90d45f5 Update TODO 2015-10-27 14:41:04 +09:00
Ian Barwick
c8d0fb401f Add note about default log level 2015-10-27 14:39:30 +09:00
Ian Barwick
afda3419cc Put declarations at top of file 2015-10-27 13:17:18 +09:00
Ian Barwick
a86fa4ad4a Explicitly set default value for 'use_replication_slots' 2015-10-27 13:01:20 +09:00
Ian Barwick
7e3007f6e8 Add 'primary_response_timeout' as synonym for 'master_response_timeout'
We'll switch terminology in a future release and maintain
'master_response_timeout' for backwards compatibility
2015-10-27 10:23:54 +09:00
Ian Barwick
8c797a8fea Clarify items in the sample repmgr.conf file
Based on customer feedback.
2015-10-26 16:49:45 +09:00
Ian Barwick
56cec22f22 Use pg_malloc0() instead of malloc()
See also d08bd352c1
2015-10-26 15:34:33 +09:00
Ian Barwick
b61649a3e3 Bump dev version number 2015-10-26 15:14:14 +09:00
Ian Barwick
ded716e403 Improve logging and event notifications when following new upstream node 2015-10-23 09:36:42 +09:00
Ian Barwick
d639dc3342 Add note about checking replication slots when following upstream node 2015-10-23 09:21:01 +09:00
Ian Barwick
17ed81ebb7 Improve log messages when following new primary 2015-10-23 09:17:20 +09:00
Ian Barwick
b00c507ee4 Minor formatting tweak 2015-10-23 08:51:26 +09:00
Ian Barwick
55d8b2ad9c Clarify some items in sample config file
Also change "master" to "primary" in the comments for consistency
with main PostgreSQL terminology. We'll need to add aliases
for the configuration parameters at some point...
2015-10-23 08:49:45 +09:00
Ian Barwick
c918aaad4a Update TODO 2015-10-23 08:49:44 +09:00
Ian Barwick
6e7eee4c01 Only log some debug items if verbose flag is set. 2015-10-23 08:29:35 +09:00
Ian Barwick
5c59e8fc5b Add missing space 2015-10-22 13:13:01 +09:00
Ian Barwick
eba0b6bb1e Update TODO 2015-10-14 16:57:24 +09:00
Ian Barwick
3bc0b80a71 Reword log level error message to be more like the Postgres one 2015-10-07 10:54:47 +09:00
Ian Barwick
06b9e0a8ec Clarify purpose of get_repmgr_schema() 2015-10-07 10:54:47 +09:00
Martín Marqués
120be2db1c Fix bug which prevents repmgrd from starting when the cluster name has
upper case letters.
2015-10-06 20:54:28 -03:00
Ian Barwick
12bd7da836 Clean up --help output
There are a confusing number of command line options, some
of which are only valid for particular operations, e.g. "standby clone".
2015-10-05 11:53:45 +09:00
Ian Barwick
2fd905cf9e Update HISTORY 2015-10-02 14:40:52 +09:00
Sébastien Gross
dd7ebdc1c7 Add more information in "cluster show"
- Add display of current node name.
- Add display of upstream node name.
2015-09-23 16:02:10 +02:00
József Kószó
1636805fa1 Debian init script repmgrd process stop fix 2015-09-21 03:13:19 +02:00
Benjamin Guillon
899d789699 Fixed version number and package dependencies in deb control file. 2015-06-04 11:26:36 +02:00
Benjamin Guillon
cd7a3215df Fixed tabs/spaces issues caused by lazy copy/paster in github web UI when creating the PR. 2015-06-04 11:25:45 +02:00
Benjamin Guillon
f8fd344d9f Updated makefile for deb creation
Added the ability to fetch the installed postgresql version when building the deb package so that install paths are correct.
2015-06-04 09:51:03 +02:00
35 changed files with 5118 additions and 1818 deletions

View File

@@ -2,7 +2,7 @@ License and Contributions
========================= =========================
`repmgr` is licensed under the GPL v3. All of its code and documentation is `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 Copyright 2010-2016, 2ndQuadrant Limited. See the files COPYRIGHT and LICENSE for
details. details.
The development of repmgr has primarily been sponsored by 2ndQuadrant customers. The development of repmgr has primarily been sponsored by 2ndQuadrant customers.

View File

@@ -1,4 +1,4 @@
Copyright (c) 2010-2015, 2ndQuadrant Limited Copyright (c) 2010-2016, 2ndQuadrant Limited
All rights reserved. All rights reserved.
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify

View File

@@ -1,237 +1 @@
==================================================== The contents of this file have been incorporated into the main README.md document.
PostgreSQL Automatic Failover - User Documentation
====================================================
Automatic Failover
==================
repmgr allows for automatic failover when it detects the failure of the master node.
Following is a quick setup for this.
Installation
============
For convenience, we define:
**node1**
is the fully qualified domain name of the Master server, IP 192.168.1.10
**node2**
is the fully qualified domain name of the Standby server, IP 192.168.1.11
**witness**
is the fully qualified domain name of the server used as a witness, IP 192.168.1.12
**Note:** We don't recommend using names with the status of a server like «masterserver»,
because it would be confusing once a failover takes place and the Master is
now on the «standbyserver».
Summary
-------
2 PostgreSQL servers are involved in the replication. Automatic failover needs
a vote to decide what server it should promote, so an odd number is required.
A witness-repmgrd is installed in a third server where it uses a PostgreSQL
cluster to communicate with other repmgrd daemons.
1. Install PostgreSQL in all the servers involved (including the witness server)
2. Install repmgr in all the servers involved (including the witness server)
3. Configure the Master PostreSQL
4. Clone the Master to the Standby using "repmgr standby clone" command
5. Configure repmgr in all the servers involved (including the witness server)
6. Register Master and Standby nodes
7. Initiate witness server
8. Start the repmgrd daemons in all nodes
**Note** A complete High-Availability design needs at least 3 servers to still have
a backup node after a first failure.
Install PostgreSQL
------------------
You can install PostgreSQL using any of the recommended methods. You should ensure
it's 9.0 or later.
Install repmgr
--------------
Install repmgr following the steps in the README file.
Configure PostreSQL
-------------------
Log in to node1.
Edit the file postgresql.conf and modify the parameters::
listen_addresses='*'
wal_level = 'hot_standby'
archive_mode = on
archive_command = 'cd .' # we can also use exit 0, anything that
# just does nothing
max_wal_senders = 10
wal_keep_segments = 5000 # 80 GB required on pg_xlog
hot_standby = on
shared_preload_libraries = 'repmgr_funcs'
Edit the file pg_hba.conf and add lines for the replication::
host repmgr repmgr 127.0.0.1/32 trust
host repmgr repmgr 192.168.1.10/30 trust
host replication all 192.168.1.10/30 trust
**Note:** It is also possible to use a password authentication (md5), .pgpass file
should be edited to allow connection between each node.
Create the user and database to manage replication::
su - postgres
createuser -s repmgr
createdb -O repmgr repmgr
psql -f /usr/share/postgresql/9.0/contrib/repmgr_funcs.sql repmgr
Restart the PostgreSQL server::
pg_ctl -D $PGDATA restart
And check everything is fine in the server log.
Create the ssh-key for the postgres user and copy it to other servers::
su - postgres
ssh-keygen # /!\ do not use a passphrase /!\
cat ~/.ssh/id_rsa.pub > ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
exit
rsync -avz ~postgres/.ssh/authorized_keys node2:~postgres/.ssh/
rsync -avz ~postgres/.ssh/authorized_keys witness:~postgres/.ssh/
rsync -avz ~postgres/.ssh/id_rsa* node2:~postgres/.ssh/
rsync -avz ~postgres/.ssh/id_rsa* witness:~postgres/.ssh/
Clone Master
------------
Log in to node2.
Clone node1 (the current Master)::
su - postgres
repmgr -d repmgr -U repmgr -h node1 standby clone
Start the PostgreSQL server::
pg_ctl -D $PGDATA start
And check everything is fine in the server log.
Configure repmgr
----------------
Log in to each server and configure repmgr by editing the file
/etc/repmgr/repmgr.conf::
cluster=my_cluster
node=1
node_name=earth
conninfo='host=192.168.1.10 dbname=repmgr user=repmgr'
master_response_timeout=60
reconnect_attempts=6
reconnect_interval=10
failover=automatic
promote_command='promote_command.sh'
follow_command='repmgr standby follow -f /etc/repmgr/repmgr.conf'
**cluster**
is the name of the current replication.
**node**
is the number of the current node (1, 2 or 3 in the current example).
**node_name**
is an identifier for every node.
**conninfo**
is used to connect to the local PostgreSQL server (where the configuration file is) from any node. In the witness server configuration you need to add a 'port=5499' to the conninfo.
**master_response_timeout**
is the maximum amount of time we are going to wait before deciding the master has died and start the failover procedure.
**reconnect_attempts**
is the number of times we will try to reconnect to master after a failure has been detected and before start the failover procedure.
**reconnect_interval**
is the amount of time between retries to reconnect to master after a failure has been detected and before start the failover procedure.
**failover**
configure behavior: *manual* or *automatic*.
**promote_command**
the command executed to do the failover (including the PostgreSQL failover itself). The command must return 0 on success.
**follow_command**
the command executed to address the current standby to another Master. The command must return 0 on success.
Register Master and Standby
---------------------------
Log in to node1.
Register the node as Master::
su - postgres
repmgr -f /etc/repmgr/repmgr.conf master register
Log in to node2. Register it as a standby::
su - postgres
repmgr -f /etc/repmgr/repmgr.conf standby register
Initialize witness server
-------------------------
Log in to witness.
Initialize the witness server::
su - postgres
repmgr -d repmgr -U repmgr -h 192.168.1.10 -D $WITNESS_PGDATA -f /etc/repmgr/repmgr.conf witness create
The witness server needs the following information from the command
line:
* Connection details for the current master, to copy the cluster
configuration.
* A location for initializing its own $PGDATA.
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
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
-------------------------
Log in to node2 and witness::
su - postgres
repmgrd -f /etc/repmgr/repmgr.conf --daemonize -> /var/log/postgresql/repmgr.log 2>&1
**Note:** The Master does not need a repmgrd daemon.
Suspend Automatic behavior
==========================
Edit the repmgr.conf of the node to remove from automatic processing and change::
failover=manual
Then, signal repmgrd daemon::
su - postgres
kill -HUP $(pidof repmgrd)
Usage
=====
The repmgr documentation is in the README file (how to build, options, etc.)

39
FAQ.md
View File

@@ -34,6 +34,11 @@ General
replication slots, setting a higher figure will make adding new nodes replication slots, setting a higher figure will make adding new nodes
easier. easier.
- Does `repmgr` support hash indexes?
No. Hash indexes and replication do not mix well and their use is
explicitly discouraged; see:
http://www.postgresql.org/docs/current/interactive/sql-createindex.html#AEN74175
`repmgr` `repmgr`
-------- --------
@@ -96,8 +101,9 @@ General
is intended to support running the witness server as a separate is intended to support running the witness server as a separate
instance on a normal node server, rather than on its own dedicated server. instance on a normal node server, rather than on its own dedicated server.
To specify a port for the witness server, supply the port number to To specify different port for the witness server, supply the port number
repmgr with the `-l/--local-port` command line option. in the `conninfo` string in `repmgr.conf`
(repmgr 3.0.1 and earlier: use the `-l/--local-port` option)
- Do I need to include `shared_preload_libraries = 'repmgr_funcs'` - Do I need to include `shared_preload_libraries = 'repmgr_funcs'`
in `postgresql.conf` if I'm not using `repmgrd`? in `postgresql.conf` if I'm not using `repmgrd`?
@@ -106,6 +112,30 @@ General
If you later decide to run `repmgrd`, you just need to add If you later decide to run `repmgrd`, you just need to add
`shared_preload_libraries = 'repmgr_funcs'` and restart PostgreSQL. `shared_preload_libraries = 'repmgr_funcs'` and restart PostgreSQL.
- I've provided replication permission for the `repmgr` user in `pg_hba.conf`
but `repmgr`/`repmgrd` complains it can't connect to the server... Why?
`repmgr`/`repmgrd` need to be able to connect to the repmgr database
with a normal connection to query metadata. The `replication` connection
permission is for PostgreSQL's streaming replication and doesn't
necessarily need to be the `repmgr` user.
- When cloning a standby, why do I need to provide the connection parameters
for the primary server on the command line, not in the configuration file?
Cloning a standby is a one-time action; the role of the server being cloned
from could change, so fixing it in the configuration file would create
confusion. If `repmgr` needs to establish a connection to the primary
server, it can retrieve this from the `repl_nodes` table or if necessary
scan the replication cluster until it locates the active primary.
- Why is there no foreign key on the `node_id` column in the `repl_events`
table?
Under some circumstances event notifications can be generated for servers
which have not yet been registered; it's also useful to retain a record
of events which includes servers removed from the replication cluster
which no longer have an entry in the `repl_nodes` table.
`repmgrd` `repmgrd`
--------- ---------
@@ -134,3 +164,8 @@ General
Note that after registering a delayed standby, `repmgrd` will only start Note that after registering a delayed standby, `repmgrd` will only start
once the metadata added in the master node has been replicated. once the metadata added in the master node has been replicated.
- How can I get `repmgrd` to rotate its logfile?
Configure your system's `logrotate` service to do this; see example
in README.md

43
HISTORY
View File

@@ -1,4 +1,41 @@
3.0.2 2015-09- 3.1.2 2016-04-12
Fix pg_ctl path generation in do_standby_switchover() (Ian)
Regularly sync witness server repl_nodes table (Ian)
Documentation improvements (Gianni, dhyannataraj)
(Experimental) ensure repmgr handles failover slots when copying
in rsync mode (Craig, Ian)
rsync mode handling fixes (Martín)
Enable repmgr to compile against 9.6devel (Ian)
3.1.1 2016-02-24
Add '-P/--pwprompt' option for "repmgr create witness" (Ian)
Prevent repmgr/repmgrd running as root (Ian)
3.1.0 2016-02-01
Add "repmgr standby switchover" command (Ian)
Revised README file (Ian)
Remove requirement for 'archive_mode' to be enabled (Ian)
Improve -?/--help output, showing default values if relevant (Ian)
Various bugfixes to command line/configuration parameter handling (Ian)
3.0.3 2016-01-04
Create replication slot if required before base backup is run (Abhijit)
standy clone: when using rsync, clean up "pg_replslot" directory (Ian)
Improve --help output (Ian)
Improve config file parsing (Ian)
Various logging output improvements, including explicit HINTS (Ian)
Add --log-level to explicitly set log level on command line (Ian)
Repurpose --verbose to display extra log output (Ian)
Add --terse to hide hints and other non-critical output (Ian)
Reference internal functions with explicit catalog path (Ian)
When following a new primary, have repmgr (not repmgrd) create the new slot (Ian)
Add /etc/repmgr.conf as a default configuration file location (Ian)
Prevent repmgrd's -v/--verbose option expecting a parameter (Ian)
Prevent invalid replication_lag values being written to the monitoring table (Ian)
Improve repmgrd behaviour when monitored standby node is temporarily
unavailable (Martín)
3.0.2 2015-10-02
Improve handling of --help/--version options; and improve help output (Ian) Improve handling of --help/--version options; and improve help output (Ian)
Improve handling of situation where logfile can't be opened (Ian) Improve handling of situation where logfile can't be opened (Ian)
Always pass -D/--pgdata option to pg_basebackup (Ian) Always pass -D/--pgdata option to pg_basebackup (Ian)
@@ -12,7 +49,9 @@
Update tablespace remapping in --rsync-only mode for 9.5 and later (Ian) Update tablespace remapping in --rsync-only mode for 9.5 and later (Ian)
Deprecate `-l/--local-port` option - the port can be extracted Deprecate `-l/--local-port` option - the port can be extracted
from the conninfo string in repmgr.conf (Ian) from the conninfo string in repmgr.conf (Ian)
Add STANDBY UNREGISTE (Vik Fearing) Add STANDBY UNREGISTER (Vik Fearing)
Don't fail with error when registering master if schema already defined (Ian)
Fixes to whitespace handling when parsing config file (Ian)
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)

View File

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

View File

@@ -1,118 +1 @@
repmgr quickstart guide The contents of this file have been incorporated into the main README.md document.
=======================
This quickstart guide provides some annotated examples on basic
`repmgr` setup. It assumes you are familiar with PostgreSQL replication
concepts setup and Linux/UNIX system administration.
For the purposes of this guide, we'll assume the database user will be
`repmgr_usr` and the database will be `repmgr_db`.
Master setup
------------
1. Configure PostgreSQL
- create user and database:
```
CREATE ROLE repmgr_usr LOGIN SUPERUSER;
CREATE DATABASE repmgr_db OWNER repmgr_usr;
```
- configure `postgresql.conf` for replication (see README.md for sample
settings)
- update `pg_hba.conf`, e.g.:
```
host repmgr_db repmgr_usr 192.168.1.0/24 trust
host replication repmgr_usr 192.168.1.0/24 trust
```
Restart the PostgreSQL server after making these changes.
2. Create the `repmgr` configuration file:
$ cat /path/to/repmgr/node1/repmgr.conf
cluster=test
node=1
node_name=node1
conninfo='host=repmgr_node1 user=repmgr_usr dbname=repmgr_db'
pg_bindir=/path/to/postgres/bin
(For an annotated `repmgr.conf` file, see `repmgr.conf.sample` in the
repository's root directory).
3. Register the master node with `repmgr`:
$ repmgr -f /path/to/repmgr/node1/repmgr.conf --verbose master register
[2015-03-03 17:45:53] [INFO] repmgr connecting to master database
[2015-03-03 17:45:53] [INFO] repmgr connected to master, checking its state
[2015-03-03 17:45:53] [INFO] master register: creating database objects inside the repmgr_test schema
[2015-03-03 17:45:53] [NOTICE] Master node correctly registered for cluster test with id 1 (conninfo: host=localhost user=repmgr_usr dbname=repmgr_db)
Standby setup
-------------
1. Use `repmgr standby clone` to clone a standby from the master:
repmgr -D /path/to/standby/data -d repmgr_db -U repmgr_usr --verbose standby clone 192.168.1.2
[2015-03-03 18:18:21] [NOTICE] No configuration file provided and default file './repmgr.conf' not found - continuing with default values
[2015-03-03 18:18:21] [NOTICE] repmgr Destination directory ' /path/to/standby/data' provided
[2015-03-03 18:18:21] [INFO] repmgr connecting to upstream node
[2015-03-03 18:18:21] [INFO] repmgr connected to upstream node, checking its state
[2015-03-03 18:18:21] [INFO] Successfully connected to upstream node. Current installation size is 27 MB
[2015-03-03 18:18:21] [NOTICE] Starting backup...
[2015-03-03 18:18:21] [INFO] creating directory " /path/to/standby/data"...
[2015-03-03 18:18:21] [INFO] Executing: 'pg_basebackup -l "repmgr base backup" -h localhost -p 9595 -U repmgr_usr -D /path/to/standby/data '
NOTICE: pg_stop_backup complete, all required WAL segments have been archived
[2015-03-03 18:18:23] [NOTICE] repmgr standby clone (using pg_basebackup) complete
[2015-03-03 18:18:23] [NOTICE] HINT: You can now start your postgresql server
[2015-03-03 18:18:23] [NOTICE] for example : pg_ctl -D /path/to/standby/data start
Note that the `repmgr.conf` file is not required when cloning a standby.
However we recommend providing a valid `repmgr.conf` if you wish to use
replication slots, or want `repmgr` to log the clone event to the
`repl_events` table.
This will clone the PostgreSQL database files from the master, including its
`postgresql.conf` and `pg_hba.conf` files, and additionally automatically create
the `recovery.conf` file containing the correct parameters to start streaming
from the primary node.
2. Start the PostgreSQL server
3. Create the `repmgr` configuration file:
$ cat /path/node2/repmgr/repmgr.conf
cluster=test
node=2
node_name=node2
conninfo='host=repmgr_node2 user=repmgr_usr dbname=repmgr_db'
pg_bindir=/path/to/postgres/bin
4. Register the standby node with `repmgr`:
$ repmgr -f /path/to/repmgr/node2/repmgr.conf --verbose standby register
[2015-03-03 18:24:34] [NOTICE] Opening configuration file: /path/to/repmgr/node2/repmgr.conf
[2015-03-03 18:24:34] [INFO] repmgr connecting to standby database
[2015-03-03 18:24:34] [INFO] repmgr connecting to master database
[2015-03-03 18:24:34] [INFO] finding node list for cluster 'test'
[2015-03-03 18:24:34] [INFO] checking role of cluster node '1'
[2015-03-03 18:24:34] [INFO] repmgr connected to master, checking its state
[2015-03-03 18:24:34] [INFO] repmgr registering the standby
[2015-03-03 18:24:34] [INFO] repmgr registering the standby complete
[2015-03-03 18:24:34] [NOTICE] Standby node correctly registered for cluster test with id 2 (conninfo: host=localhost user=repmgr_usr dbname=repmgr_db)
This concludes the basic `repmgr` setup of master and standby. The records
created in the `repl_nodes` table should look something like this:
repmgr_db=# SELECT * from repmgr_test.repl_nodes;
id | type | upstream_node_id | cluster | name | conninfo | slot_name | priority | active
----+---------+------------------+---------+-------+----------------------------------------------------+-----------+----------+--------
1 | primary | | test | node1 | host=repmgr_node1 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)

1454
README.md

File diff suppressed because it is too large Load Diff

35
TODO
View File

@@ -7,6 +7,7 @@ Known issues in repmgr
* PGPASSFILE may not be passed to pg_basebackup * PGPASSFILE may not be passed to pg_basebackup
Planned feature improvements Planned feature improvements
============================ ============================
@@ -37,4 +38,36 @@ Planned feature improvements
before the primary. See github issue #80. before the primary. See github issue #80.
* make old master node ID available for event notification commands * make old master node ID available for event notification commands
(See github issue #80). (See github issue #80).
* repmgr standby clone: possibility to use barman instead of performing a new base backup
* possibility to transform a failed master into a new standby with pg_rewind
* "repmgr standby switchover" to promote a standby in a controlled manner
and convert the existing primary into a standby
* make repmgrd more robust
* repmgr: when cloning a standby using pg_basebackup and replication slots are
requested, activate the replication slot using pg_receivexlog to negate the
need to set `wal_keep_segments` just for the initial clone (9.4 and 9.5).
* Take into account the fact that a standby can obtain WAL from an archive,
so even if direct streaming replication is interrupted, it may be up-to-date
Usability improvements
======================
* repmgr: add interrupt handler, so that if the program is interrupted
while running a backup, an attempt can be made to execute pg_stop_backup()
on the primary, to prevent an orphaned backup state existing.
* repmgr: when unregistering a node, delete any entries in the repl_monitoring
table.
* repmgr: for "standby unregister", accept connection parameters for the
primary and perform metadata updates (and slot removal) directly on
the primary, to allow a shutdown standby to be unregistered
(currently the standby must still be running, which means the replication
slot can't be dropped).

View File

@@ -1,6 +1,6 @@
/* /*
* check_dir.c - Directories management functions * check_dir.c - Directories management functions
* Copyright (C) 2ndQuadrant, 2010-2015 * Copyright (C) 2ndQuadrant, 2010-2016
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* 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
@@ -320,10 +320,10 @@ _create_pg_dir(char *dir, bool force, bool for_witness)
} }
else if (pg_dir && !force) else if (pg_dir && !force)
{ {
log_warning(_("\nThis looks like a PostgreSQL directory.\n" log_hint(_("This looks like a PostgreSQL directory.\n"
"If you are sure you want to clone here, " "If you are sure you want to clone here, "
"please check there is no PostgreSQL server " "please check there is no PostgreSQL server "
"running and use the --force option\n")); "running and use the -F/--force option\n"));
return false; return false;
} }

View File

@@ -1,6 +1,6 @@
/* /*
* check_dir.h * check_dir.h
* Copyright (c) 2ndQuadrant, 2010-2015 * Copyright (c) 2ndQuadrant, 2010-2016
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* 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

461
config.c
View File

@@ -1,6 +1,6 @@
/* /*
* config.c - Functions to parse the config file * config.c - Functions to parse the config file
* Copyright (C) 2ndQuadrant, 2010-2015 * Copyright (C) 2ndQuadrant, 2010-2016
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* 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
@@ -26,9 +26,25 @@
static void parse_event_notifications_list(t_configuration_options *options, const char *arg); static void parse_event_notifications_list(t_configuration_options *options, const char *arg);
static void tablespace_list_append(t_configuration_options *options, const char *arg); static void tablespace_list_append(t_configuration_options *options, const char *arg);
static void exit_with_errors(ErrorList *config_errors);
const static char *_progname = '\0';
static char config_file_path[MAXPGPATH]; static char config_file_path[MAXPGPATH];
static bool config_file_provided = false; static bool config_file_provided = false;
bool config_file_found = false;
void
set_progname(const char *argv0)
{
_progname = get_progname(argv0);
}
const char *
progname(void)
{
return _progname;
}
/* /*
* load_config() * load_config()
@@ -40,61 +56,123 @@ static bool config_file_provided = false;
* *
* Any configuration options changed in this function must also be changed in * Any configuration options changed in this function must also be changed in
* reload_config() * reload_config()
*
* NOTE: this function is called before the logger is set up, so we need
* to handle the verbose option ourselves; also the default log level is NOTICE,
* so we can't use DEBUG.
*/ */
bool bool
load_config(const char *config_file, t_configuration_options *options, char *argv0) load_config(const char *config_file, bool verbose, t_configuration_options *options, char *argv0)
{ {
struct stat config; struct stat stat_config;
/* 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 and terminate * emit an error and terminate. We assume that if a user explicitly
* provides a configuration file, they'll want to make sure it's
* used and not fall back to any of the defaults.
*/ */
if (config_file[0]) if (config_file[0])
{ {
strncpy(config_file_path, config_file, MAXPGPATH); strncpy(config_file_path, config_file, MAXPGPATH);
canonicalize_path(config_file_path); canonicalize_path(config_file_path);
if (stat(config_file_path, &config) != 0) if (stat(config_file_path, &stat_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,
strerror(errno) strerror(errno)
); );
exit(ERR_BAD_CONFIG); exit(ERR_BAD_CONFIG);
} }
if (verbose == true)
{
log_notice(_("using configuration file \"%s\"\n"), config_file);
}
config_file_provided = true; config_file_provided = true;
config_file_found = true;
} }
/* /*
* If no configuration file was provided, attempt to find a default file * If no configuration file was provided, attempt to find a default file
* in this order:
* - current directory
* - /etc/repmgr.conf
* - default sysconfdir
*
* here we just check for the existence of the file; parse_config()
* will handle read errors etc.
*/ */
if (config_file_provided == false) if (config_file_provided == false)
{ {
char my_exec_path[MAXPGPATH]; char my_exec_path[MAXPGPATH];
char etc_path[MAXPGPATH]; char sysconf_etc_path[MAXPGPATH];
/* First check if one is in the default sysconfdir */ /* 1. "./repmgr.conf" */
if (verbose == true)
{
log_notice(_("looking for configuration file in current directory\n"));
}
snprintf(config_file_path, MAXPGPATH, "./%s", CONFIG_FILE_NAME);
canonicalize_path(config_file_path);
if (stat(config_file_path, &stat_config) == 0)
{
config_file_found = true;
goto end_search;
}
/* 2. "/etc/repmgr.conf" */
if (verbose == true)
{
log_notice(_("looking for configuration file in /etc\n"));
}
snprintf(config_file_path, MAXPGPATH, "/etc/%s", CONFIG_FILE_NAME);
if (stat(config_file_path, &stat_config) == 0)
{
config_file_found = true;
goto end_search;
}
/* 3. default sysconfdir */
if (find_my_exec(argv0, my_exec_path) < 0) if (find_my_exec(argv0, my_exec_path) < 0)
{ {
fprintf(stderr, _("%s: could not find own program executable\n"), argv0); fprintf(stderr, _("%s: could not find own program executable\n"), argv0);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
get_etc_path(my_exec_path, etc_path); get_etc_path(my_exec_path, sysconf_etc_path);
snprintf(config_file_path, MAXPGPATH, "%s/repmgr.conf", etc_path); if (verbose == true)
log_debug(_("Looking for configuration file in %s\n"), etc_path);
if (stat(config_file_path, &config) != 0)
{ {
/* Not found - default to ./repmgr.conf */ log_notice(_("looking for configuration file in %s\n"), sysconf_etc_path);
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); snprintf(config_file_path, MAXPGPATH, "%s/%s", sysconf_etc_path, CONFIG_FILE_NAME);
if (stat(config_file_path, &stat_config) == 0)
{
config_file_found = true;
goto end_search;
}
end_search:
if (config_file_found == true)
{
if (verbose == true)
{
log_notice(_("configuration file found at: %s\n"), config_file_path);
}
}
else
{
if (verbose == true)
{
log_notice(_("no configuration file provided or found\n"));
}
} }
} }
@@ -102,49 +180,39 @@ load_config(const char *config_file, t_configuration_options *options, char *arg
} }
/*
* Parse configuration file; if any errors are encountered,
* list them and exit.
*
* Ensure any default values set here are synced with repmgr.conf.sample
* and any other documentation.
*/
bool bool
parse_config(t_configuration_options *options) parse_config(t_configuration_options *options)
{ {
FILE *fp; FILE *fp;
char *s, char *s,
buff[MAXLINELENGTH]; buf[MAXLINELENGTH];
char name[MAXLEN]; char name[MAXLEN];
char value[MAXLEN]; char value[MAXLEN];
/* For sanity-checking provided conninfo string */ /* For sanity-checking provided conninfo string */
PQconninfoOption *conninfo_options; PQconninfoOption *conninfo_options;
char *conninfo_errmsg = NULL; char *conninfo_errmsg = NULL;
fp = fopen(config_file_path, "r"); /* Collate configuration file errors here for friendlier reporting */
static ErrorList config_errors = { NULL, NULL };
/* bool node_found = false;
* Since some commands don't require a config file at all, not having one
* isn't necessarily a problem. /* Initialize configuration options with sensible defaults
* * note: the default log level is set in log.c and does not need
* If the user explictly provided a configuration file and we can't * to be initialised here
* read it we'll raise an error.
*
* If no configuration file was provided, we'll try and read the default\
* file if it exists and is readable, but won't worry if it's not.
*/ */
if (fp == NULL)
{
if (config_file_provided)
{
log_err(_("unable to open provided configuration file '%s'; terminating\n"), config_file_path);
exit(ERR_BAD_CONFIG);
}
log_notice(_("no configuration file provided and default file '%s' not found - "
"continuing with default values\n"),
DEFAULT_CONFIG_FILE);
return false;
}
/* Initialize configuration options with sensible defaults */
memset(options->cluster_name, 0, sizeof(options->cluster_name)); memset(options->cluster_name, 0, sizeof(options->cluster_name));
options->node = -1; options->node = -1;
options->upstream_node = NO_UPSTREAM_NODE; options->upstream_node = NO_UPSTREAM_NODE;
options->use_replication_slots = 0;
memset(options->conninfo, 0, sizeof(options->conninfo)); memset(options->conninfo, 0, sizeof(options->conninfo));
options->failover = MANUAL_FAILOVER; options->failover = MANUAL_FAILOVER;
options->priority = DEFAULT_PRIORITY; options->priority = DEFAULT_PRIORITY;
@@ -162,25 +230,58 @@ parse_config(t_configuration_options *options)
/* default to 6 reconnection attempts at intervals of 10 seconds */ /* default to 6 reconnection attempts at intervals of 10 seconds */
options->reconnect_attempts = 6; options->reconnect_attempts = 6;
options->reconnect_intvl = 10; options->reconnect_interval = 10;
options->monitor_interval_secs = 2; options->monitor_interval_secs = 2;
options->retry_promote_interval_secs = 300; options->retry_promote_interval_secs = 300;
/* default to resyncing repl_nodes table every 30 seconds on the witness server */
options->witness_repl_nodes_sync_interval_secs = 30;
memset(options->event_notification_command, 0, sizeof(options->event_notification_command)); memset(options->event_notification_command, 0, sizeof(options->event_notification_command));
options->tablespace_mapping.head = NULL; options->tablespace_mapping.head = NULL;
options->tablespace_mapping.tail = NULL; options->tablespace_mapping.tail = NULL;
/*
* If no configuration file available (user didn't specify and none found
* in the default locations), return with default values
*/
if (config_file_found == false)
{
log_verbose(LOG_NOTICE, _("no configuration file provided and no default file found - "
"continuing with default values\n"));
return true;
}
fp = fopen(config_file_path, "r");
/* Read next line */ /*
while ((s = fgets(buff, sizeof buff, fp)) != NULL) * A configuration file has been found, either provided by the user
* or found in one of the default locations. If we can't open it,
* fail with an error.
*/
if (fp == NULL)
{
if (config_file_provided)
{
log_err(_("unable to open provided configuration file \"%s\"; terminating\n"), config_file_path);
}
else
{
log_err(_("unable to open default configuration file \"%s\"; terminating\n"), config_file_path);
}
exit(ERR_BAD_CONFIG);
}
/* Read file */
while ((s = fgets(buf, sizeof buf, fp)) != NULL)
{ {
bool known_parameter = true; bool known_parameter = true;
/* Parse name/value pair from line */ /* Parse name/value pair from line */
parse_line(buff, name, value); parse_line(buf, name, value);
/* Skip blank lines */ /* Skip blank lines */
if (!strlen(name)) if (!strlen(name))
@@ -194,9 +295,12 @@ parse_config(t_configuration_options *options)
if (strcmp(name, "cluster") == 0) if (strcmp(name, "cluster") == 0)
strncpy(options->cluster_name, value, MAXLEN); strncpy(options->cluster_name, value, MAXLEN);
else if (strcmp(name, "node") == 0) else if (strcmp(name, "node") == 0)
options->node = atoi(value); {
options->node = repmgr_atoi(value, "node", &config_errors, false);
node_found = true;
}
else if (strcmp(name, "upstream_node") == 0) else if (strcmp(name, "upstream_node") == 0)
options->upstream_node = atoi(value); options->upstream_node = repmgr_atoi(value, "upstream_node", &config_errors, false);
else if (strcmp(name, "conninfo") == 0) else if (strcmp(name, "conninfo") == 0)
strncpy(options->conninfo, value, MAXLEN); strncpy(options->conninfo, value, MAXLEN);
else if (strcmp(name, "rsync_options") == 0) else if (strcmp(name, "rsync_options") == 0)
@@ -223,12 +327,11 @@ parse_config(t_configuration_options *options)
} }
else else
{ {
log_err(_("value for 'failover' must be 'automatic' or 'manual'\n")); error_list_append(&config_errors,_("value for 'failover' must be 'automatic' or 'manual'\n"));
exit(ERR_BAD_CONFIG);
} }
} }
else if (strcmp(name, "priority") == 0) else if (strcmp(name, "priority") == 0)
options->priority = atoi(value); options->priority = repmgr_atoi(value, "priority", &config_errors, true);
else if (strcmp(name, "node_name") == 0) else if (strcmp(name, "node_name") == 0)
strncpy(options->node_name, value, MAXLEN); strncpy(options->node_name, value, MAXLEN);
else if (strcmp(name, "promote_command") == 0) else if (strcmp(name, "promote_command") == 0)
@@ -236,11 +339,16 @@ parse_config(t_configuration_options *options)
else if (strcmp(name, "follow_command") == 0) else if (strcmp(name, "follow_command") == 0)
strncpy(options->follow_command, value, MAXLEN); strncpy(options->follow_command, value, MAXLEN);
else if (strcmp(name, "master_response_timeout") == 0) else if (strcmp(name, "master_response_timeout") == 0)
options->master_response_timeout = atoi(value); options->master_response_timeout = repmgr_atoi(value, "master_response_timeout", &config_errors, false);
/* 'primary_response_timeout' as synonym for 'master_response_timeout' -
* we'll switch terminology in a future release (3.1?)
*/
else if (strcmp(name, "primary_response_timeout") == 0)
options->master_response_timeout = repmgr_atoi(value, "primary_response_timeout", &config_errors, false);
else if (strcmp(name, "reconnect_attempts") == 0) else if (strcmp(name, "reconnect_attempts") == 0)
options->reconnect_attempts = atoi(value); options->reconnect_attempts = repmgr_atoi(value, "reconnect_attempts", &config_errors, false);
else if (strcmp(name, "reconnect_interval") == 0) else if (strcmp(name, "reconnect_interval") == 0)
options->reconnect_intvl = atoi(value); options->reconnect_interval = repmgr_atoi(value, "reconnect_interval", &config_errors, false);
else if (strcmp(name, "pg_bindir") == 0) else if (strcmp(name, "pg_bindir") == 0)
strncpy(options->pg_bindir, value, MAXLEN); strncpy(options->pg_bindir, value, MAXLEN);
else if (strcmp(name, "pg_ctl_options") == 0) else if (strcmp(name, "pg_ctl_options") == 0)
@@ -250,11 +358,14 @@ parse_config(t_configuration_options *options)
else if (strcmp(name, "logfile") == 0) else if (strcmp(name, "logfile") == 0)
strncpy(options->logfile, value, MAXLEN); strncpy(options->logfile, value, MAXLEN);
else if (strcmp(name, "monitor_interval_secs") == 0) else if (strcmp(name, "monitor_interval_secs") == 0)
options->monitor_interval_secs = atoi(value); options->monitor_interval_secs = repmgr_atoi(value, "monitor_interval_secs", &config_errors, false);
else if (strcmp(name, "retry_promote_interval_secs") == 0) else if (strcmp(name, "retry_promote_interval_secs") == 0)
options->retry_promote_interval_secs = atoi(value); options->retry_promote_interval_secs = repmgr_atoi(value, "retry_promote_interval_secs", &config_errors, false);
else if (strcmp(name, "witness_repl_nodes_sync_interval_secs") == 0)
options->witness_repl_nodes_sync_interval_secs = repmgr_atoi(value, "witness_repl_nodes_sync_interval_secs", &config_errors, false);
else if (strcmp(name, "use_replication_slots") == 0) else if (strcmp(name, "use_replication_slots") == 0)
options->use_replication_slots = atoi(value); /* XXX we should have a dedicated boolean argument format */
options->use_replication_slots = repmgr_atoi(value, "use_replication_slots", &config_errors, false);
else if (strcmp(name, "event_notification_command") == 0) else if (strcmp(name, "event_notification_command") == 0)
strncpy(options->event_notification_command, value, MAXLEN); strncpy(options->event_notification_command, value, MAXLEN);
else if (strcmp(name, "event_notifications") == 0) else if (strcmp(name, "event_notifications") == 0)
@@ -274,76 +385,54 @@ parse_config(t_configuration_options *options)
* as currently e.g. an empty `node` value will be converted to '0'. * as currently e.g. an empty `node` value will be converted to '0'.
*/ */
if (known_parameter == true && !strlen(value)) { if (known_parameter == true && !strlen(value)) {
log_err(_("no value provided for parameter '%s'\n"), name); char error_message_buf[MAXLEN] = "";
exit(ERR_BAD_CONFIG); snprintf(error_message_buf,
MAXLEN,
_("no value provided for parameter \"%s\""),
name);
error_list_append(&config_errors, error_message_buf);
} }
} }
fclose(fp); fclose(fp);
/* Check config settings */
/* The following checks are for the presence of the parameter */ if (node_found == false)
if (*options->cluster_name == '\0')
{ {
log_err(_("required parameter 'cluster' was not found\n")); error_list_append(&config_errors, _("\"node\": parameter was not found"));
exit(ERR_BAD_CONFIG); }
else if (options->node == 0)
{
error_list_append(&config_errors, _("\"node\": must be greater than zero"));
} }
if (options->node == -1) if (strlen(options->conninfo))
{ {
log_err(_("required parameter 'node' was not found\n"));
exit(ERR_BAD_CONFIG); /* Sanity check the provided conninfo string
*
* NOTE: PQconninfoParse() 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)
{
char error_message_buf[MAXLEN] = "";
snprintf(error_message_buf,
MAXLEN,
_("\"conninfo\": %s"),
conninfo_errmsg);
error_list_append(&config_errors, error_message_buf);
}
PQconninfoFree(conninfo_options);
} }
if (options->node == 0) if (config_errors.head != NULL)
{ {
log_err(_("'node' must be an integer greater than zero\n")); exit_with_errors(&config_errors);
exit(ERR_BAD_CONFIG);
}
if (*options->node_name == '\0')
{
log_err(_("required parameter 'node_name' was not found\n"));
exit(ERR_BAD_CONFIG);
}
if (*options->conninfo == '\0')
{
log_err(_("required parameter 'conninfo' was not found\n"));
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 */
if (options->master_response_timeout <= 0)
{
log_err(_("'master_response_timeout' must be greater than zero\n"));
exit(ERR_BAD_CONFIG);
}
if (options->reconnect_attempts < 0)
{
log_err(_("'reconnect_attempts' must be zero or greater\n"));
exit(ERR_BAD_CONFIG);
}
if (options->reconnect_intvl < 0)
{
log_err(_("'reconnect_interval' must be zero or greater\n"));
exit(ERR_BAD_CONFIG);
} }
return true; return true;
@@ -378,7 +467,7 @@ trim(char *s)
} }
void void
parse_line(char *buff, char *name, char *value) parse_line(char *buf, char *name, char *value)
{ {
int i = 0; int i = 0;
int j = 0; int j = 0;
@@ -389,10 +478,10 @@ parse_line(char *buff, char *name, char *value)
for (; i < MAXLEN; ++i) for (; i < MAXLEN; ++i)
{ {
if (buff[i] == '=') if (buf[i] == '=')
break; break;
switch(buff[i]) switch(buf[i])
{ {
/* Ignore whitespace */ /* Ignore whitespace */
case ' ': case ' ':
@@ -401,7 +490,7 @@ parse_line(char *buff, char *name, char *value)
case '\t': case '\t':
continue; continue;
default: default:
name[j++] = buff[i]; name[j++] = buf[i];
} }
} }
name[j] = '\0'; name[j] = '\0';
@@ -411,9 +500,9 @@ parse_line(char *buff, char *name, char *value)
*/ */
for (; i < MAXLEN; ++i) for (; i < MAXLEN; ++i)
{ {
if (buff[i+1] == ' ') if (buf[i+1] == ' ')
continue; continue;
if (buff[i+1] == '\t') if (buf[i+1] == '\t')
continue; continue;
break; break;
@@ -424,12 +513,12 @@ parse_line(char *buff, char *name, char *value)
*/ */
j = 0; j = 0;
for (++i; i < MAXLEN; ++i) for (++i; i < MAXLEN; ++i)
if (buff[i] == '\'') if (buf[i] == '\'')
continue; continue;
else if (buff[i] == '#') else if (buf[i] == '#')
break; break;
else if (buff[i] != '\n') else if (buf[i] != '\n')
value[j++] = buff[i]; value[j++] = buf[i];
else else
break; break;
value[j] = '\0'; value[j] = '\0';
@@ -491,7 +580,7 @@ reload_config(t_configuration_options *orig_options)
return false; return false;
} }
if (new_options.reconnect_intvl < 0) if (new_options.reconnect_interval < 0)
{ {
log_warning(_("new value for 'reconnect_interval' must be zero or greater\n")); log_warning(_("new value for 'reconnect_interval' must be zero or greater\n"));
return false; return false;
@@ -610,10 +699,10 @@ reload_config(t_configuration_options *orig_options)
config_changed = true; config_changed = true;
} }
/* reconnect_intvl */ /* reconnect_interval */
if (orig_options->reconnect_intvl != new_options.reconnect_intvl) if (orig_options->reconnect_interval != new_options.reconnect_interval)
{ {
orig_options->reconnect_intvl = new_options.reconnect_intvl; orig_options->reconnect_interval = new_options.reconnect_interval;
config_changed = true; config_changed = true;
} }
@@ -665,6 +754,96 @@ reload_config(t_configuration_options *orig_options)
} }
void
error_list_append(ErrorList *error_list, char *error_message)
{
ErrorListCell *cell;
cell = (ErrorListCell *) pg_malloc0(sizeof(ErrorListCell));
if (cell == NULL)
{
log_err(_("unable to allocate memory; terminating.\n"));
exit(ERR_BAD_CONFIG);
}
cell->error_message = pg_malloc0(MAXLEN);
strncpy(cell->error_message, error_message, MAXLEN);
if (error_list->tail)
{
error_list->tail->next = cell;
}
else
{
error_list->head = cell;
}
error_list->tail = cell;
}
/*
* Convert provided string to an integer using strtol;
* on error, if a callback is provided, pass the error message to that,
* otherwise exit
*/
int
repmgr_atoi(const char *value, const char *config_item, ErrorList *error_list, bool allow_negative)
{
char *endptr;
long longval = 0;
char error_message_buf[MAXLEN] = "";
/* It's possible that some versions of strtol() don't treat an empty
* string as an error.
*/
if (*value == '\0')
{
snprintf(error_message_buf,
MAXLEN,
_("no value provided for \"%s\""),
config_item);
}
else
{
errno = 0;
longval = strtol(value, &endptr, 10);
if (value == endptr || errno)
{
snprintf(error_message_buf,
MAXLEN,
_("\"%s\": invalid value (provided: \"%s\")"),
config_item, value);
}
}
/* Disallow negative values for most parameters */
if (allow_negative == false && longval < 0)
{
snprintf(error_message_buf,
MAXLEN,
_("\"%s\" must be zero or greater (provided: %s)"),
config_item, value);
}
/* Error message buffer is set */
if (error_message_buf[0] != '\0')
{
if (error_list == NULL)
{
log_err("%s\n", error_message_buf);
exit(ERR_BAD_CONFIG);
}
error_list_append(error_list, error_message_buf);
}
return (int32) longval;
}
/* /*
* Split argument into old_dir and new_dir and append to tablespace mapping * Split argument into old_dir and new_dir and append to tablespace mapping
@@ -797,3 +976,21 @@ parse_event_notifications_list(t_configuration_options *options, const char *arg
} }
} }
} }
static void
exit_with_errors(ErrorList *config_errors)
{
ErrorListCell *cell;
log_err(_("%s: following errors were found in the configuration file.\n"), progname());
for (cell = config_errors->head; cell; cell = cell->next)
{
log_err("%s\n", cell->error_message);
}
exit(ERR_BAD_CONFIG);
}

View File

@@ -1,6 +1,6 @@
/* /*
* config.h * config.h
* Copyright (c) 2ndQuadrant, 2010-2015 * Copyright (c) 2ndQuadrant, 2010-2016
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* 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
@@ -24,6 +24,7 @@
#include "strutil.h" #include "strutil.h"
#define CONFIG_FILE_NAME "repmgr.conf"
typedef struct EventNotificationListCell typedef struct EventNotificationListCell
{ {
@@ -67,26 +68,46 @@ typedef struct
char ssh_options[QUERY_STR_LEN]; char ssh_options[QUERY_STR_LEN];
int master_response_timeout; int master_response_timeout;
int reconnect_attempts; int reconnect_attempts;
int reconnect_intvl; int reconnect_interval;
char pg_bindir[MAXLEN]; char pg_bindir[MAXLEN];
char pg_ctl_options[MAXLEN]; char pg_ctl_options[MAXLEN];
char pg_basebackup_options[MAXLEN]; char pg_basebackup_options[MAXLEN];
char logfile[MAXLEN]; char logfile[MAXLEN];
int monitor_interval_secs; int monitor_interval_secs;
int retry_promote_interval_secs; int retry_promote_interval_secs;
int witness_repl_nodes_sync_interval_secs;
int use_replication_slots; int use_replication_slots;
char event_notification_command[MAXLEN]; char event_notification_command[MAXLEN];
EventNotificationList event_notifications; EventNotificationList event_notifications;
TablespaceList tablespace_mapping; TablespaceList tablespace_mapping;
} t_configuration_options; } t_configuration_options;
#define T_CONFIGURATION_OPTIONS_INITIALIZER { "", -1, NO_UPSTREAM_NODE, "", MANUAL_FAILOVER, -1, "", "", "", "", "", "", "", -1, -1, -1, "", "", "", "", 0, 0, 0, "", { NULL, NULL }, {NULL, NULL} } #define T_CONFIGURATION_OPTIONS_INITIALIZER { "", -1, NO_UPSTREAM_NODE, "", MANUAL_FAILOVER, -1, "", "", "", "", "", "", "", -1, -1, -1, "", "", "", "", 0, 0, 0, 0, "", { NULL, NULL }, {NULL, NULL} }
typedef struct ErrorListCell
{
struct ErrorListCell *next;
char *error_message;
} ErrorListCell;
bool load_config(const char *config_file, t_configuration_options *options, char *argv0); typedef struct ErrorList
{
ErrorListCell *head;
ErrorListCell *tail;
} ErrorList;
void set_progname(const char *argv0);
const char * progname(void);
bool load_config(const char *config_file, bool verbose, t_configuration_options *options, char *argv0);
bool reload_config(t_configuration_options *orig_options); bool reload_config(t_configuration_options *orig_options);
bool parse_config(t_configuration_options *options); bool parse_config(t_configuration_options *options);
void parse_line(char *buff, char *name, char *value); void parse_line(char *buff, char *name, char *value);
char *trim(char *s); char *trim(char *s);
void error_list_append(ErrorList *error_list, char *error_message);
int repmgr_atoi(const char *s,
const char *config_item,
ErrorList *error_list,
bool allow_negative);
#endif #endif

666
dbutils.c

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
/* /*
* dbutils.h * dbutils.h
* Copyright (c) 2ndQuadrant, 2010-2015 * Copyright (c) 2ndQuadrant, 2010-2016
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* 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
@@ -20,13 +20,71 @@
#ifndef _REPMGR_DBUTILS_H_ #ifndef _REPMGR_DBUTILS_H_
#define _REPMGR_DBUTILS_H_ #define _REPMGR_DBUTILS_H_
#include "access/xlogdefs.h"
#include "config.h" #include "config.h"
#include "strutil.h" #include "strutil.h"
typedef enum {
UNKNOWN = 0,
MASTER,
STANDBY,
WITNESS
} t_server_type;
/*
* Struct to store node information
*/
typedef struct s_node_info
{
int node_id;
int upstream_node_id;
t_server_type type;
char name[MAXLEN];
char conninfo_str[MAXLEN];
char slot_name[MAXLEN];
int priority;
bool active;
bool is_ready;
bool is_visible;
XLogRecPtr xlog_location;
} t_node_info;
/*
* Struct to store replication slot information
*/
typedef struct s_replication_slot
{
char slot_name[MAXLEN];
char slot_type[MAXLEN];
bool active;
} t_replication_slot;
#define T_NODE_INFO_INITIALIZER { \
NODE_NOT_FOUND, \
NO_UPSTREAM_NODE, \
UNKNOWN, \
"", \
"", \
"", \
DEFAULT_PRIORITY, \
true, \
false, \
false, \
InvalidXLogRecPtr \
}
PGconn *_establish_db_connection(const char *conninfo,
const bool exit_on_error,
const bool log_notice);
PGconn *establish_db_connection(const char *conninfo, PGconn *establish_db_connection(const char *conninfo,
const bool exit_on_error); const bool exit_on_error);
PGconn *test_db_connection(const char *conninfo,
const bool exit_on_error);
PGconn *establish_db_connection_by_params(const char *keywords[], PGconn *establish_db_connection_by_params(const char *keywords[],
const char *values[], const char *values[],
const bool exit_on_error); const bool exit_on_error);
@@ -45,7 +103,7 @@ int guc_set(PGconn *conn, const char *parameter, const char *op,
const char *value); const char *value);
int guc_set_typed(PGconn *conn, const char *parameter, const char *op, int guc_set_typed(PGconn *conn, const char *parameter, const char *op,
const char *value, const char *datatype); const char *value, const char *datatype);
bool get_conninfo_value(const char *conninfo, const char *keyword, char *output);
PGconn *get_upstream_connection(PGconn *standby_conn, char *cluster, PGconn *get_upstream_connection(PGconn *standby_conn, char *cluster,
int node_id, int node_id,
int *upstream_node_id_ptr, int *upstream_node_id_ptr,
@@ -57,16 +115,22 @@ int wait_connection_availability(PGconn *conn, long long timeout);
bool cancel_query(PGconn *conn, int timeout); bool cancel_query(PGconn *conn, int timeout);
char *get_repmgr_schema(void); char *get_repmgr_schema(void);
char *get_repmgr_schema_quoted(PGconn *conn); char *get_repmgr_schema_quoted(PGconn *conn);
bool create_replication_slot(PGconn *conn, char *slot_name); bool create_replication_slot(PGconn *conn, char *slot_name, int server_version_num);
int get_slot_record(PGconn *conn, char *slot_name, t_replication_slot *record);
bool drop_replication_slot(PGconn *conn, char *slot_name);
bool start_backup(PGconn *conn, char *first_wal_segment, bool fast_checkpoint); bool start_backup(PGconn *conn, char *first_wal_segment, bool fast_checkpoint);
bool stop_backup(PGconn *conn, char *last_wal_segment); bool stop_backup(PGconn *conn, char *last_wal_segment);
bool set_config_bool(PGconn *conn, const char *config_param, bool state); bool set_config_bool(PGconn *conn, const char *config_param, bool state);
bool copy_configuration(PGconn *masterconn, PGconn *witnessconn, char *cluster_name); bool witness_copy_node_records(PGconn *masterconn, PGconn *witnessconn, char *cluster_name);
bool create_node_record(PGconn *conn, char *action, int node, char *type, int upstream_node, char *cluster_name, char *node_name, char *conninfo, int priority, char *slot_name); bool create_node_record(PGconn *conn, char *action, int node, char *type, int upstream_node, char *cluster_name, char *node_name, char *conninfo, int priority, char *slot_name, bool active);
bool 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); int get_node_record(PGconn *conn, char *cluster, int node_id, t_node_info *node_info);
bool update_node_record_status(PGconn *conn, char *cluster_name, int this_node_id, char *type, int upstream_node_id, bool active);
bool update_node_record_set_upstream(PGconn *conn, char *cluster_name, int this_node_id, int new_upstream_node_id); bool update_node_record_set_upstream(PGconn *conn, char *cluster_name, int this_node_id, int new_upstream_node_id);
PGresult * get_node_record(PGconn *conn, char *cluster, int node_id); bool create_event_record(PGconn *conn, t_configuration_options *options, int node_id, char *event, bool successful, char *details);
int get_node_replication_state(PGconn *conn, char *node_name, char *output);
t_server_type parse_node_type(const char *type);
int get_data_checksum_version(const char *data_directory);
#endif #endif

View File

@@ -1,9 +1,9 @@
Package: repmgr-auto Package: repmgr-auto
Version: 2.0beta2 Version: 3.0.1
Section: database Section: database
Priority: optional Priority: optional
Architecture: all Architecture: all
Depends: rsync, postgresql-9.0 | postgresql-9.1 | postgresql-9.2 | postgresql-9.3 | postgresql-9.4 Depends: rsync, postgresql-9.3 | postgresql-9.4
Maintainer: Jaime Casanova <jaime@2ndQuadrant.com> Maintainer: Self built package <user@localhost>
Description: PostgreSQL replication setup, magament and monitoring Description: PostgreSQL replication setup, magament and monitoring
has two main executables has two main executables

View File

@@ -59,7 +59,7 @@ do_stop()
# 0 if daemon has been stopped # 0 if daemon has been stopped
# 1 if daemon was already stopped # 1 if daemon was already stopped
# other if daemon could not be stopped or a failure occurred # other if daemon could not be stopped or a failure occurred
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $REPMGRD_PIDFILE --exec $REPMGRD_BIN start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $REPMGRD_PIDFILE --name "$(basename $REPMGRD_BIN)"
} }
case "$1" in case "$1" in

View File

@@ -1,6 +1,6 @@
/* /*
* errcode.h * errcode.h
* Copyright (C) 2ndQuadrant, 2010-2015 * Copyright (C) 2ndQuadrant, 2010-2016
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* 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
@@ -36,5 +36,7 @@
#define ERR_SYS_FAILURE 13 #define ERR_SYS_FAILURE 13
#define ERR_BAD_BASEBACKUP 14 #define ERR_BAD_BASEBACKUP 14
#define ERR_INTERNAL 15 #define ERR_INTERNAL 15
#define ERR_MONITORING_FAIL 16
#define ERR_BAD_BACKUP_LABEL 17
#endif /* _ERRCODE_H_ */ #endif /* _ERRCODE_H_ */

130
log.c
View File

@@ -1,6 +1,6 @@
/* /*
* log.c - Logging methods * log.c - Logging methods
* Copyright (C) 2ndQuadrant, 2010-2015 * Copyright (C) 2ndQuadrant, 2010-2016
* *
* This module is a set of methods for logging (currently only syslog) * This module is a set of methods for logging (currently only syslog)
* *
@@ -39,13 +39,37 @@
/* #define REPMGR_DEBUG */ /* #define REPMGR_DEBUG */
static int detect_log_facility(const char *facility);
static void _stderr_log_with_level(const char *level_name, int level, const char *fmt, va_list ap);
int log_type = REPMGR_STDERR;
int log_level = LOG_NOTICE;
int last_log_level = LOG_NOTICE;
int verbose_logging = false;
int terse_logging = false;
void void
stderr_log_with_level(const char *level_name, int level, const char *fmt, ...) stderr_log_with_level(const char *level_name, int level, const char *fmt, ...)
{
va_list arglist;
va_start(arglist, fmt);
_stderr_log_with_level(level_name, level, fmt, arglist);
va_end(arglist);
}
static void
_stderr_log_with_level(const char *level_name, int level, const char *fmt, va_list ap)
{ {
time_t t; time_t t;
struct tm *tm; struct tm *tm;
char buff[100]; char buff[100];
va_list ap;
/*
* Store the requested level so that if there's a subsequent
* log_hint(), we can suppress that if appropriate.
*/
last_log_level = level;
if (log_level >= level) if (log_level >= level)
{ {
@@ -54,24 +78,74 @@ stderr_log_with_level(const char *level_name, int level, const char *fmt, ...)
strftime(buff, 100, "[%Y-%m-%d %H:%M:%S]", tm); strftime(buff, 100, "[%Y-%m-%d %H:%M:%S]", tm);
fprintf(stderr, "%s [%s] ", buff, level_name); fprintf(stderr, "%s [%s] ", buff, level_name);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap); vfprintf(stderr, fmt, ap);
va_end(ap);
fflush(stderr); fflush(stderr);
} }
} }
void
log_hint(const char *fmt, ...)
{
va_list ap;
static int detect_log_level(const char *level); if (terse_logging == false)
static int detect_log_facility(const char *facility); {
va_start(ap, fmt);
_stderr_log_with_level("HINT", last_log_level, fmt, ap);
va_end(ap);
}
}
void
log_verbose(int level, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (verbose_logging == true)
{
switch(level)
{
case LOG_EMERG:
_stderr_log_with_level("EMERG", level, fmt, ap);
break;
case LOG_ALERT:
_stderr_log_with_level("ALERT", level, fmt, ap);
break;
case LOG_CRIT:
_stderr_log_with_level("CRIT", level, fmt, ap);
break;
case LOG_ERR:
_stderr_log_with_level("ERR", level, fmt, ap);
break;
case LOG_WARNING:
_stderr_log_with_level("WARNING", level, fmt, ap);
break;
case LOG_NOTICE:
_stderr_log_with_level("NOTICE", level, fmt, ap);
break;
case LOG_INFO:
_stderr_log_with_level("INFO", level, fmt, ap);
break;
case LOG_DEBUG:
_stderr_log_with_level("DEBUG", level, fmt, ap);
break;
}
}
va_end(ap);
}
int log_type = REPMGR_STDERR;
int log_level = LOG_NOTICE;
bool bool
logger_init(t_configuration_options * opts, const char *ident, const char *level, const char *facility) logger_init(t_configuration_options * opts, const char *ident)
{ {
char *level = opts->loglevel;
char *facility = opts->logfacility;
int l; int l;
int f; int f;
@@ -95,10 +169,10 @@ logger_init(t_configuration_options * opts, const char *ident, const char *level
printf("Assigned level for logger: %d\n", l); printf("Assigned level for logger: %d\n", l);
#endif #endif
if (l > 0) if (l >= 0)
log_level = l; log_level = l;
else else
stderr_log_warning(_("Cannot detect log level %s (use any of DEBUG, INFO, NOTICE, WARNING, ERR, ALERT, CRIT or EMERG)\n"), level); stderr_log_warning(_("Invalid log level \"%s\" (available values: DEBUG, INFO, NOTICE, WARNING, ERR, ALERT, CRIT or EMERG)\n"), level);
} }
if (facility && *facility) if (facility && *facility)
@@ -174,9 +248,9 @@ logger_init(t_configuration_options * opts, const char *ident, const char *level
} }
return true; return true;
} }
bool bool
logger_shutdown(void) logger_shutdown(void)
{ {
@@ -189,17 +263,32 @@ logger_shutdown(void)
} }
/* /*
* Set a minimum logging level. Intended for command line verbosity * Indicate whether extra-verbose logging is required. This will
* options, which might increase requested logging over what's specified * generate a lot of output, particularly debug logging, and should
* in the regular configuration file. * not be permanently enabled in production.
*
* NOTE: in previous repmgr versions, this option forced the log
* level to INFO.
*/ */
void void
logger_min_verbose(int minimum) logger_set_verbose(void)
{ {
if (log_level < minimum) verbose_logging = true;
log_level = minimum;
} }
/*
* Indicate whether some non-critical log messages can be omitted.
* Currently this includes warnings about irrelevant command line
* options and hints.
*/
void logger_set_terse(void)
{
terse_logging = true;
}
int int
detect_log_level(const char *level) detect_log_level(const char *level)
{ {
@@ -220,17 +309,16 @@ detect_log_level(const char *level)
if (!strcmp(level, "EMERG")) if (!strcmp(level, "EMERG"))
return LOG_EMERG; return LOG_EMERG;
return 0; return -1;
} }
int static int
detect_log_facility(const char *facility) detect_log_facility(const char *facility)
{ {
int local = 0; int local = 0;
if (!strncmp(facility, "LOCAL", 5) && strlen(facility) == 6) if (!strncmp(facility, "LOCAL", 5) && strlen(facility) == 6)
{ {
local = atoi(&facility[5]); local = atoi(&facility[5]);
switch (local) switch (local)

14
log.h
View File

@@ -1,6 +1,6 @@
/* /*
* log.h * log.h
* Copyright (c) 2ndQuadrant, 2010-2015 * Copyright (c) 2ndQuadrant, 2010-2016
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* 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
@@ -112,13 +112,19 @@ __attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 4)));
#endif #endif
int detect_log_level(const char *level);
/* Logger initialisation and shutdown */ /* Logger initialisation and shutdown */
bool logger_init(t_configuration_options * opts, const char *ident);
bool logger_shutdown(void); bool logger_shutdown(void);
bool logger_init(t_configuration_options * opts, const char *ident, void logger_set_verbose(void);
const char *level, const char *facility); void logger_set_terse(void);
void logger_min_verbose(int minimum); void log_hint(const char *fmt, ...);
void log_verbose(int level, const char *fmt, ...);
extern int log_type; extern int log_type;
extern int log_level; extern int log_level;

2799
repmgr.c

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,10 @@
# Replication Manager sample configuration file # Replication Manager sample configuration file
################################################### ###################################################
# Some configuration items will be set with a default value; this
# is noted for each item. Where no default value is shown, the
# parameter will be treated as empty or false.
# Required configuration items # Required configuration items
# ============================ # ============================
# #
@@ -11,17 +15,21 @@
# schema (pattern: "repmgr_{cluster}"); while this name will be quoted # schema (pattern: "repmgr_{cluster}"); while this name will be quoted
# to preserve case, we recommend using lower case and avoiding whitespace # to preserve case, we recommend using lower case and avoiding whitespace
# to facilitate easier querying of the repmgr views and tables. # to facilitate easier querying of the repmgr views and tables.
cluster=example_cluster #cluster=example_cluster
# Node ID and name # Node ID and name
# (Note: we recommend to avoid naming nodes after their initial # (Note: we recommend to avoid naming nodes after their initial
# replication funcion, as this will cause confusion when e.g. # replication funcion, as this will cause confusion when e.g.
# "standby2" is promoted to master) # "standby2" is promoted to primary)
node=2 #node=2 # a unique integer
node_name=node2 #node_name=node2 # an arbitrary (but unique) string; we recommend using
# the server's hostname or another identifier unambiguously
# associated with the server to avoid confusion
# Database connection information # Database connection information as a conninfo string
conninfo='host=192.168.204.104 dbname=repmgr_db user=repmgr_usr' # This must be accessible to all servers in the cluster; for details see:
# http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
#conninfo='host=192.168.204.104 dbname=repmgr_db user=repmgr_usr'
# Optional configuration items # Optional configuration items
# ============================ # ============================
@@ -29,33 +37,34 @@ conninfo='host=192.168.204.104 dbname=repmgr_db user=repmgr_usr'
# Replication settings # Replication settings
# --------------------- # ---------------------
# when using cascading replication and a standby is to be connected to an # When using cascading replication, a standby can connect to another
# upstream standby, specify that node's ID with 'upstream_node'. The node # upstream standby node which is specified by setting 'upstream_node'.
# must exist before the new standby can be registered. If a standby is # In that case, the upstream node must exist before the new standby
# to connect directly to a master node, this parameter is not required. # can be registered. If 'upstream_node' is not set, then the standby
# # will connect directly to the primary node.
# upstream_node=1 #upstream_node=1
# physical replication slots - PostgreSQL 9.4 and later only # use physical replication slots - PostgreSQL 9.4 and later only
# (default: 0) # (default: 0)
# #use_replication_slots=0
# use_replication_slots=0
# NOTE: 'max_replication_slots' should be configured for at least the
# number of standbys which will connect to the primary.
# Logging and monitoring settings # Logging and monitoring settings
# ------------------------------- # -------------------------------
# Log level: possible values are DEBUG, INFO, NOTICE, WARNING, ERR, ALERT, CRIT or EMERG # Log level: possible values are DEBUG, INFO, NOTICE, WARNING, ERR, ALERT, CRIT or EMERG
# (default: NOTICE) # (default: NOTICE)
loglevel=NOTICE #loglevel=NOTICE
# Logging facility: possible values are STDERR or - for Syslog integration - one of LOCAL0, LOCAL1, ..., LOCAL7, USER # Logging facility: possible values are STDERR or - for Syslog integration - one of LOCAL0, LOCAL1, ..., LOCAL7, USER
# (default: STDERR) # (default: STDERR)
logfacility=STDERR #logfacility=STDERR
# stderr can be redirected to an arbitrary file: # stderr can be redirected to an arbitrary file:
# #
# logfile='/var/log/repmgr.log' #logfile='/var/log/repmgr/repmgr.log'
# event notifications can be passed to an arbitrary external program # event notifications can be passed to an arbitrary external program
# together with the following parameters: # together with the following parameters:
@@ -69,12 +78,12 @@ logfacility=STDERR
# the values provided for "%t" and "%d" will probably contain spaces, # the values provided for "%t" and "%d" will probably contain spaces,
# so should be quoted in the provided command configuration, e.g.: # so should be quoted in the provided command configuration, e.g.:
# #
# event_notification_command='/path/to/some/script %n %e %s "%t" "%d"' #event_notification_command='/path/to/some/script %n %e %s "%t" "%d"'
# By default, all notifications will be passed; the notification types # By default, all notifications will be passed; the notification types
# can be filtered to explicitly named ones: # can be filtered to explicitly named ones:
# #
# event_notifications=master_register,standby_register,witness_create #event_notifications=master_register,standby_register,witness_create
# Environment/command settings # Environment/command settings
@@ -82,17 +91,17 @@ logfacility=STDERR
# path to PostgreSQL binary directory (location of pg_ctl, pg_basebackup etc.) # path to PostgreSQL binary directory (location of pg_ctl, pg_basebackup etc.)
# (if not provided, defaults to system $PATH) # (if not provided, defaults to system $PATH)
# pg_bindir=/usr/bin/ #pg_bindir=/usr/bin/
# external command options # external command options
# rsync_options=--archive --checksum --compress --progress --rsh="ssh -o \"StrictHostKeyChecking no\"" #rsync_options=--archive --checksum --compress --progress --rsh="ssh -o \"StrictHostKeyChecking no\""
# ssh_options=-o "StrictHostKeyChecking no" #ssh_options=-o "StrictHostKeyChecking no"
# external command arguments # external command arguments. Values shown are examples.
# pg_ctl_options='-s' #pg_ctl_options='-s'
# pg_basebackup_options='--xlog-method=s' #pg_basebackup_options='--xlog-method=s'
# Standby clone settings # Standby clone settings
@@ -108,30 +117,36 @@ logfacility=STDERR
# Failover settings (repmgrd) # Failover settings (repmgrd)
# --------------------------- # ---------------------------
# #
# These settings are only applied when repmgrd is running. # These settings are only applied when repmgrd is running. Values shown
# are defaults.
# How many seconds we wait for master response before declaring master failure # Number of seconds to wait for a response from the primary server before
master_response_timeout=60 # deciding it has failed.
# How many time we try to reconnect to master before starting failover procedure #master_response_timeout=60
reconnect_attempts=6
reconnect_interval=10 # Number of attempts at what interval (in seconds) to try and
# connect to a server to establish its status (e.g. master
# during failover)
#reconnect_attempts=6
#reconnect_interval=10
# Autofailover options # Autofailover options
failover=automatic # one of 'automatic', 'manual' #failover=manual # one of 'automatic', 'manual'
priority=100 # a value of zero or less prevents the node being promoted to master # (default: manual)
promote_command='repmgr standby promote -f /path/to/repmgr.conf' #priority=100 # a value of zero or less prevents the node being promoted to primary
follow_command='repmgr standby follow -f /path/to/repmgr.conf -W' # (default: 100)
#promote_command='repmgr standby promote -f /path/to/repmgr.conf'
#follow_command='repmgr standby follow -f /path/to/repmgr.conf -W'
# monitoring interval; default is 2s # monitoring interval in seconds; default is 2
# #monitor_interval_secs=2
# monitor_interval_secs=2
# change wait time for master; before we bail out and exit when the master # change wait time for primary; before we bail out and exit when the primary
# disappears, we wait 'reconnect_attempts' * 'retry_promote_interval_secs' # disappears, we wait 'reconnect_attempts' * 'retry_promote_interval_secs'
# seconds; by default this would be half an hour, as 'retry_promote_interval_secs' # seconds; by default this would be half an hour, as 'retry_promote_interval_secs'
# default value is 300) # default value is 300)
# #retry_promote_interval_secs=300
# retry_promote_interval_secs=300
# Number of seconds after which the witness server resyncs the repl_nodes table
#witness_repl_nodes_sync_interval_secs=15

View File

@@ -1,6 +1,6 @@
/* /*
* repmgr.h * repmgr.h
* Copyright (c) 2ndQuadrant, 2010-2015 * Copyright (c) 2ndQuadrant, 2010-2016
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* 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
@@ -32,15 +32,10 @@
#define MIN_SUPPORTED_VERSION "9.3" #define MIN_SUPPORTED_VERSION "9.3"
#define MIN_SUPPORTED_VERSION_NUM 90300 #define MIN_SUPPORTED_VERSION_NUM 90300
#include "config.h"
#define MAXFILENAME 1024
#define ERRBUFF_SIZE 512 #define ERRBUFF_SIZE 512
#define DEFAULT_CONFIG_FILE "./repmgr.conf"
#define DEFAULT_WAL_KEEP_SEGMENTS "5000" #define DEFAULT_WAL_KEEP_SEGMENTS "5000"
#define DEFAULT_DEST_DIR "." #define DEFAULT_DEST_DIR "."
#define DEFAULT_MASTER_PORT "5432"
#define DEFAULT_DBNAME "postgres"
#define DEFAULT_REPMGR_SCHEMA_PREFIX "repmgr_" #define DEFAULT_REPMGR_SCHEMA_PREFIX "repmgr_"
#define DEFAULT_PRIORITY 100 #define DEFAULT_PRIORITY 100
#define FAILOVER_NODES_MAX_CHECK 50 #define FAILOVER_NODES_MAX_CHECK 50
@@ -49,14 +44,7 @@
#define AUTOMATIC_FAILOVER 1 #define AUTOMATIC_FAILOVER 1
#define NODE_NOT_FOUND -1 #define NODE_NOT_FOUND -1
#define NO_UPSTREAM_NODE -1 #define NO_UPSTREAM_NODE -1
#define UNKNOWN_NODE_ID -1
typedef enum {
UNKNOWN = 0,
MASTER,
STANDBY,
WITNESS
} t_server_type;
@@ -67,44 +55,60 @@ typedef struct
char dbname[MAXLEN]; char dbname[MAXLEN];
char host[MAXLEN]; char host[MAXLEN];
char username[MAXLEN]; char username[MAXLEN];
char dest_dir[MAXFILENAME]; char dest_dir[MAXPGPATH];
char config_file[MAXFILENAME]; char config_file[MAXPGPATH];
char remote_user[MAXLEN]; char remote_user[MAXLEN];
char superuser[MAXLEN]; char superuser[MAXLEN];
char wal_keep_segments[MAXLEN]; char wal_keep_segments[MAXLEN];
bool verbose; bool verbose;
bool terse;
bool force; bool force;
bool wait_for_master; bool wait_for_master;
bool ignore_rsync_warn; bool ignore_rsync_warn;
bool initdb_no_pwprompt; bool witness_pwprompt;
bool rsync_only; bool rsync_only;
bool fast_checkpoint; bool fast_checkpoint;
bool ignore_external_config_files; bool ignore_external_config_files;
char pg_ctl_mode[MAXLEN];
char masterport[MAXLEN]; char masterport[MAXLEN];
char localport[MAXLEN]; /*
* configuration file parameters which can be overridden on the
* command line
*/
char loglevel[MAXLEN];
/* parameter used by STANDBY SWITCHOVER */
char remote_config_file[MAXLEN];
char pg_rewind[MAXPGPATH];
/* parameter used by STANDBY {ARCHIVE_CONFIG | RESTORE_CONFIG} */
char config_archive_dir[MAXLEN];
/* parameter used by CLUSTER CLEANUP */ /* parameter used by CLUSTER CLEANUP */
int keep_history; int keep_history;
char pg_bindir[MAXLEN]; char pg_bindir[MAXLEN];
char recovery_min_apply_delay[MAXLEN]; char recovery_min_apply_delay[MAXLEN];
/* deprecated command line options */
char localport[MAXLEN];
bool initdb_no_pwprompt;
} t_runtime_options; } t_runtime_options;
#define T_RUNTIME_OPTIONS_INITIALIZER { "", "", "", "", "", "", "", DEFAULT_WAL_KEEP_SEGMENTS, false, false, false, false, false, false, false, false, "", "", 0, "", "" } #define T_RUNTIME_OPTIONS_INITIALIZER { "", "", "", "", "", "", "", DEFAULT_WAL_KEEP_SEGMENTS, false, false, false, false, false, false, false, false, false, "smart", "", "", "", "", "", 0, "", "", "", false }
struct BackupLabel
{
XLogRecPtr start_wal_location;
char start_wal_file[MAXLEN];
XLogRecPtr checkpoint_location;
char backup_from[MAXLEN];
char backup_method[MAXLEN];
char start_time[MAXLEN];
char label[MAXLEN];
XLogRecPtr min_failover_slot_lsn;
};
extern char repmgr_schema[MAXLEN]; extern char repmgr_schema[MAXLEN];
extern bool config_file_found;
typedef struct ErrorListCell
{
struct ErrorListCell *next;
char *error_message;
} ErrorListCell;
typedef struct ErrorList
{
ErrorListCell *head;
ErrorListCell *tail;
} ErrorList;
#endif #endif

View File

@@ -1,7 +1,7 @@
/* /*
* repmgr.sql * repmgr.sql
* *
* Copyright (C) 2ndQuadrant, 2010-2015 * Copyright (C) 2ndQuadrant, 2010-2016
* *
*/ */
@@ -59,3 +59,12 @@ WHERE (standby_node, last_monitor_time) IN (SELECT standby_node, MAX(last_monito
ALTER VIEW repl_status OWNER TO repmgr; ALTER VIEW repl_status OWNER TO repmgr;
CREATE INDEX idx_repl_status_sort ON repl_monitor(last_monitor_time, standby_node); CREATE INDEX idx_repl_status_sort ON repl_monitor(last_monitor_time, standby_node);
/*
* This view shows the list of nodes with the information of which one is the upstream
* in each case (when appliable)
*/
CREATE VIEW repl_show_nodes AS
SELECT rn.id, rn.conninfo, rn.type, rn.name, rn.cluster,
rn.priority, rn.active, sq.name AS upstream_node_name
FROM repl_nodes as rn LEFT JOIN repl_nodes AS sq ON sq.id=rn.upstream_node_id;

479
repmgrd.c
View File

@@ -1,6 +1,6 @@
/* /*
* repmgrd.c - Replication manager daemon * repmgrd.c - Replication manager daemon
* Copyright (C) 2ndQuadrant, 2010-2015 * Copyright (C) 2ndQuadrant, 2010-2016
* *
* This module connects to the nodes of a replication cluster and monitors * This module connects to the nodes of a replication cluster and monitors
* how far are they from master * how far are they from master
@@ -41,22 +41,6 @@
#include "access/xlogdefs.h" #include "access/xlogdefs.h"
#include "pqexpbuffer.h" #include "pqexpbuffer.h"
/*
* Struct to store node information
*/
typedef struct s_node_info
{
int node_id;
int upstream_node_id;
char conninfo_str[MAXLEN];
XLogRecPtr xlog_location;
t_server_type type;
bool is_ready;
bool is_visible;
char slot_name[MAXLEN];
bool active;
} t_node_info;
/* Local info */ /* Local info */
@@ -68,9 +52,7 @@ t_configuration_options master_options;
PGconn *master_conn = NULL; PGconn *master_conn = NULL;
const char *progname; char *config_file = "";
char *config_file = DEFAULT_CONFIG_FILE;
bool verbose = false; bool verbose = false;
bool monitoring_history = false; bool monitoring_history = false;
t_node_info node_info; t_node_info node_info;
@@ -81,7 +63,7 @@ char *pid_file = NULL;
t_configuration_options config = T_CONFIGURATION_OPTIONS_INITIALIZER; t_configuration_options config = T_CONFIGURATION_OPTIONS_INITIALIZER;
static void help(const char *progname); static void help(void);
static void usage(void); static void usage(void);
static void check_cluster_configuration(PGconn *conn); static void check_cluster_configuration(PGconn *conn);
static void check_node_configuration(void); static void check_node_configuration(void);
@@ -89,7 +71,7 @@ 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, const char *conninfo); static bool check_connection(PGconn **conn, const char *type, const char *conninfo);
static bool set_local_node_failed(void); static bool set_local_node_status(void);
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);
@@ -97,7 +79,6 @@ static void do_master_failover(void);
static bool do_upstream_standby_failover(t_node_info upstream_node); static bool do_upstream_standby_failover(t_node_info upstream_node);
static t_node_info get_node_info(PGconn *conn, char *cluster, int node_id); static t_node_info get_node_info(PGconn *conn, char *cluster, int node_id);
static t_server_type parse_node_type(const char *type);
static XLogRecPtr lsn_to_xlogrecptr(char *lsn, bool *format_ok); static XLogRecPtr lsn_to_xlogrecptr(char *lsn, bool *format_ok);
/* /*
@@ -158,9 +139,24 @@ main(int argc, char **argv)
FILE *fd; FILE *fd;
int server_version_num = 0; int server_version_num = 0;
progname = get_progname(argv[0]);
while ((c = getopt_long(argc, argv, "?Vf:v:mdp:", long_options, &optindex)) != -1) set_progname(argv[0]);
/* Disallow running as root to prevent directory ownership problems */
if (geteuid() == 0)
{
fprintf(stderr,
_("%s: cannot be run as root\n"
"Please log in (using, e.g., \"su\") as the "
"(unprivileged) user that owns "
"the data directory.\n"
),
progname());
exit(1);
}
while ((c = getopt_long(argc, argv, "?Vf:vmdp:", long_options, &optindex)) != -1)
{ {
switch (c) switch (c)
{ {
@@ -180,10 +176,10 @@ main(int argc, char **argv)
pid_file = optarg; pid_file = optarg;
break; break;
case '?': case '?':
help(progname); help();
exit(SUCCESS); exit(SUCCESS);
case 'V': case 'V':
printf("%s %s (PostgreSQL %s)\n", progname, REPMGR_VERSION, PG_VERSION); printf("%s %s (PostgreSQL %s)\n", progname(), REPMGR_VERSION, PG_VERSION);
exit(SUCCESS); exit(SUCCESS);
default: default:
usage(); usage();
@@ -200,7 +196,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.
*/ */
load_config(config_file, &local_options, argv[0]); load_config(config_file, verbose, &local_options, argv[0]);
if (daemonize) if (daemonize)
{ {
@@ -230,10 +226,9 @@ main(int argc, char **argv)
strerror(errno)); strerror(errno));
} }
logger_init(&local_options, progname, local_options.loglevel, logger_init(&local_options, progname());
local_options.logfacility);
if (verbose) if (verbose)
logger_min_verbose(LOG_INFO); logger_set_verbose();
if (log_type == REPMGR_SYSLOG) if (log_type == REPMGR_SYSLOG)
{ {
@@ -247,6 +242,7 @@ main(int argc, char **argv)
} }
/* Initialise the repmgr schema name */ /* Initialise the repmgr schema name */
/* XXX check this handles quoting properly */
maxlen_snprintf(repmgr_schema, "%s%s", DEFAULT_REPMGR_SCHEMA_PREFIX, maxlen_snprintf(repmgr_schema, "%s%s", DEFAULT_REPMGR_SCHEMA_PREFIX,
local_options.cluster_name); local_options.cluster_name);
@@ -264,7 +260,7 @@ main(int argc, char **argv)
if (server_version_num > 0) if (server_version_num > 0)
{ {
log_err(_("%s requires PostgreSQL %s or later\n"), log_err(_("%s requires PostgreSQL %s or later\n"),
progname, progname(),
MIN_SUPPORTED_VERSION) ; MIN_SUPPORTED_VERSION) ;
} }
else else
@@ -278,11 +274,18 @@ main(int argc, char **argv)
/* Retrieve record for this node from the local database */ /* Retrieve record for this node from the local database */
node_info = get_node_info(my_local_conn, local_options.cluster_name, local_options.node); node_info = get_node_info(my_local_conn, local_options.cluster_name, local_options.node);
/* No node record found - exit gracefully */ /*
* No node record found - exit gracefully
*
* Note: it's highly unlikely this situation will occur when starting
* repmgrd on a witness, unless someone goes to the trouble of
* deleting the node record from the previously copied table.
*/
if (node_info.node_id == NODE_NOT_FOUND) if (node_info.node_id == NODE_NOT_FOUND)
{ {
log_err(_("No metadata record found for this node - terminating\n")); log_err(_("No metadata record found for this node - terminating\n"));
log_notice(_("HINT: was this node registered with 'repmgr (master|standby) register'?\n")); log_hint(_("Check that 'repmgr (master|standby) register' was executed for this node\n"));
terminate(ERR_BAD_CONFIG); terminate(ERR_BAD_CONFIG);
} }
@@ -295,9 +298,12 @@ main(int argc, char **argv)
*/ */
do do
{ {
/* Timer for repl_nodes synchronisation interval */
int sync_repl_nodes_elapsed = 0;
/* /*
* Set my server mode, establish a connection to master and start * Set my server mode, establish a connection to master and start
* monitor * monitoring
*/ */
switch (node_info.type) switch (node_info.type)
@@ -397,8 +403,8 @@ main(int argc, char **argv)
local_options.cluster_name); local_options.cluster_name);
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 (master_conn == NULL) if (master_conn == NULL)
{ {
@@ -406,8 +412,7 @@ main(int argc, char **argv)
initPQExpBuffer(&errmsg); initPQExpBuffer(&errmsg);
appendPQExpBuffer(&errmsg, appendPQExpBuffer(&errmsg,
_("unable to connect to master node '%s'"), _("unable to connect to master node"));
local_options.cluster_name);
log_err("%s\n", errmsg.data); log_err("%s\n", errmsg.data);
@@ -457,7 +462,7 @@ main(int argc, char **argv)
do do
{ {
log_debug("standby check loop...\n"); log_verbose(LOG_DEBUG, "standby check loop...\n");
if (node_info.type == WITNESS) if (node_info.type == WITNESS)
{ {
@@ -467,8 +472,27 @@ main(int argc, char **argv)
{ {
standby_monitor(); standby_monitor();
} }
sleep(local_options.monitor_interval_secs); sleep(local_options.monitor_interval_secs);
/*
* On a witness node, regularly resync the repl_nodes table
* to keep up with any changes on the primary
*
* TODO: only resync the table if changes actually detected
*/
if (node_info.type == WITNESS)
{
sync_repl_nodes_elapsed += local_options.monitor_interval_secs;
log_debug(_("seconds since last node record sync: %i (sync interval: %i)\n"), sync_repl_nodes_elapsed, local_options.witness_repl_nodes_sync_interval_secs);
if(sync_repl_nodes_elapsed >= local_options.witness_repl_nodes_sync_interval_secs)
{
log_debug(_("Resyncing repl_nodes table\n"));
witness_copy_node_records(master_conn, my_local_conn, local_options.cluster_name);
sync_repl_nodes_elapsed = 0;
}
}
if (got_SIGHUP) if (got_SIGHUP)
{ {
/* /*
@@ -483,6 +507,7 @@ main(int argc, char **argv)
} }
got_SIGHUP = false; got_SIGHUP = false;
} }
if (failover_done) if (failover_done)
{ {
log_debug(_("standby check loop will terminate\n")); log_debug(_("standby check loop will terminate\n"));
@@ -558,10 +583,10 @@ witness_monitor(void)
{ {
log_warning( log_warning(
_("unable to determine a valid master server; waiting %i seconds to retry...\n"), _("unable to determine a valid master server; waiting %i seconds to retry...\n"),
local_options.reconnect_intvl local_options.reconnect_interval
); );
PQfinish(master_conn); PQfinish(master_conn);
sleep(local_options.reconnect_intvl); sleep(local_options.reconnect_interval);
} }
else else
{ {
@@ -575,7 +600,7 @@ witness_monitor(void)
* XXX it would be neat to be able to handle this with e.g. table-based * XXX it would be neat to be able to handle this with e.g. table-based
* logical replication * logical replication
*/ */
copy_configuration(master_conn, my_local_conn, local_options.cluster_name); witness_copy_node_records(master_conn, my_local_conn, local_options.cluster_name);
break; break;
} }
@@ -671,14 +696,15 @@ standby_monitor(void)
PGresult *res; PGresult *res;
char monitor_standby_timestamp[MAXLEN]; char monitor_standby_timestamp[MAXLEN];
char last_wal_master_location[MAXLEN]; char last_wal_master_location[MAXLEN];
char last_wal_standby_received[MAXLEN]; char last_xlog_receive_location[MAXLEN];
char last_wal_standby_applied[MAXLEN]; char last_xlog_replay_location[MAXLEN];
char last_wal_standby_applied_timestamp[MAXLEN]; char last_xact_replay_timestamp[MAXLEN];
bool last_xlog_receive_location_gte_replayed;
char sqlquery[QUERY_STR_LEN]; char sqlquery[QUERY_STR_LEN];
XLogRecPtr lsn_master; XLogRecPtr lsn_master_current_xlog_location;
XLogRecPtr lsn_standby_received; XLogRecPtr lsn_last_xlog_receive_location;
XLogRecPtr lsn_standby_applied; XLogRecPtr lsn_last_xlog_replay_location;
int connection_retries, int connection_retries,
ret; ret;
@@ -701,23 +727,16 @@ standby_monitor(void)
{ {
PQExpBufferData errmsg; PQExpBufferData errmsg;
set_local_node_failed(); set_local_node_status();
initPQExpBuffer(&errmsg); initPQExpBuffer(&errmsg);
appendPQExpBuffer(&errmsg, appendPQExpBuffer(&errmsg,
_("failed to connect to local node, node marked as failed and terminating!")); _("failed to connect to local node, node marked as failed!"));
log_err("%s\n", errmsg.data); log_err("%s\n", errmsg.data);
create_event_record(master_conn, goto continue_monitoring_standby;
&local_options,
local_options.node,
"repmgrd_shutdown",
false,
errmsg.data);
terminate(ERR_DB_CON);
} }
upstream_conn = get_upstream_connection(my_local_conn, upstream_conn = get_upstream_connection(my_local_conn,
@@ -738,7 +757,7 @@ standby_monitor(void)
check_connection(&upstream_conn, type, upstream_conninfo); check_connection(&upstream_conn, type, upstream_conninfo);
/* /*
* This takes up to local_options.reconnect_attempts * * This takes up to local_options.reconnect_attempts *
* local_options.reconnect_intvl seconds * local_options.reconnect_interval seconds
*/ */
if (PQstatus(upstream_conn) != CONNECTION_OK) if (PQstatus(upstream_conn) != CONNECTION_OK)
@@ -846,6 +865,7 @@ standby_monitor(void)
PQfinish(upstream_conn); PQfinish(upstream_conn);
continue_monitoring_standby:
/* Check if we still are a standby, we could have been promoted */ /* Check if we still are a standby, we could have been promoted */
do do
{ {
@@ -861,10 +881,13 @@ standby_monitor(void)
* will require manual resolution as there's no way of determing * will require manual resolution as there's no way of determing
* which master is the correct one. * which master is the correct one.
* *
* We should log a message so the user knows of the situation at hand.
*
* XXX check if the original master is still active and display a * XXX check if the original master is still active and display a
* warning * warning
*/ */
log_err(_("It seems like we have been promoted, so exit from monitoring...\n")); log_err(_("It seems this server was promoted manually (not by repmgr) so you might by in the presence of a split-brain.\n"));
log_err(_("Check your cluster and manually fix any anomaly.\n"));
terminate(1); terminate(1);
break; break;
@@ -874,8 +897,11 @@ standby_monitor(void)
if (!check_connection(&my_local_conn, "standby", NULL)) if (!check_connection(&my_local_conn, "standby", NULL))
{ {
set_local_node_failed(); set_local_node_status();
terminate(0); /*
* Let's continue checking, and if the postgres server on the
* standby comes back up, we will activate it again
*/
} }
break; break;
@@ -884,14 +910,20 @@ standby_monitor(void)
if (did_retry) if (did_retry)
{ {
log_info(_("standby connection recovered!\n")); /*
* There's a possible situation where the standby went down for some reason
* (maintenance for example) and is now up and maybe connected once again to
* the stream. If we set the local standby node as failed and it's now running
* and receiving replication data, we should activate it again.
*/
set_local_node_status();
log_info(_("standby connection recovered!\n"));
} }
/* Fast path for the case where no history is requested */ /* Fast path for the case where no history is requested */
if (!monitoring_history) if (!monitoring_history)
return; return;
/* /*
* If original master has gone away we'll need to get the new one * If original master has gone away we'll need to get the new one
* from the upstream node to write monitoring information * from the upstream node to write monitoring information
@@ -953,7 +985,8 @@ standby_monitor(void)
/* Get local xlog info */ /* Get local xlog info */
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT CURRENT_TIMESTAMP, pg_last_xlog_receive_location(), " "SELECT CURRENT_TIMESTAMP, pg_last_xlog_receive_location(), "
"pg_last_xlog_replay_location(), pg_last_xact_replay_timestamp() "); "pg_last_xlog_replay_location(), pg_last_xact_replay_timestamp(), "
"pg_last_xlog_receive_location() >= pg_last_xlog_replay_location()");
res = PQexec(my_local_conn, sqlquery); res = PQexec(my_local_conn, sqlquery);
if (PQresultStatus(res) != PGRES_TUPLES_OK) if (PQresultStatus(res) != PGRES_TUPLES_OK)
@@ -965,13 +998,33 @@ standby_monitor(void)
} }
strncpy(monitor_standby_timestamp, PQgetvalue(res, 0, 0), MAXLEN); strncpy(monitor_standby_timestamp, PQgetvalue(res, 0, 0), MAXLEN);
strncpy(last_wal_standby_received, PQgetvalue(res, 0, 1), MAXLEN); strncpy(last_xlog_receive_location, PQgetvalue(res, 0, 1), MAXLEN);
strncpy(last_wal_standby_applied, PQgetvalue(res, 0, 2), MAXLEN); strncpy(last_xlog_replay_location, PQgetvalue(res, 0, 2), MAXLEN);
strncpy(last_wal_standby_applied_timestamp, PQgetvalue(res, 0, 3), MAXLEN); strncpy(last_xact_replay_timestamp, PQgetvalue(res, 0, 3), MAXLEN);
last_xlog_receive_location_gte_replayed = (strcmp(PQgetvalue(res, 0, 4), "t") == 0)
? true
: false;
PQclear(res); PQclear(res);
/*
* In the unusual event of a standby becoming disconnected from the primary,
* while this repmgrd remains connected to the primary, subtracting
* "last_xlog_replay_location" from "lsn_last_xlog_receive_location" and coercing to
* (long long unsigned int) will result in a meaningless, very large
* value which will overflow a BIGINT column and spew error messages into the
* PostgreSQL log. In the absence of a better strategy, skip attempting
* to insert a monitoring record.
*/
if (last_xlog_receive_location_gte_replayed == false)
{
log_verbose(LOG_WARNING,
"Invalid replication_lag value calculated - is this standby connected to its upstream?\n");
return;
}
/* Get master xlog info */ /* Get master xlog info */
sqlquery_snprintf(sqlquery, "SELECT pg_current_xlog_location()"); sqlquery_snprintf(sqlquery, "SELECT pg_catalog.pg_current_xlog_location()");
res = PQexec(master_conn, sqlquery); res = PQexec(master_conn, sqlquery);
if (PQresultStatus(res) != PGRES_TUPLES_OK) if (PQresultStatus(res) != PGRES_TUPLES_OK)
@@ -985,35 +1038,47 @@ standby_monitor(void)
PQclear(res); PQclear(res);
/* Calculate the lag */ /* Calculate the lag */
lsn_master = lsn_to_xlogrecptr(last_wal_master_location, NULL); lsn_master_current_xlog_location = lsn_to_xlogrecptr(last_wal_master_location, NULL);
lsn_standby_received = lsn_to_xlogrecptr(last_wal_standby_received, NULL); lsn_last_xlog_receive_location = lsn_to_xlogrecptr(last_xlog_receive_location, NULL);
lsn_standby_applied = lsn_to_xlogrecptr(last_wal_standby_applied, NULL); lsn_last_xlog_replay_location = lsn_to_xlogrecptr(last_xlog_replay_location, NULL);
/* /*
* Build the SQL to execute on master * Build the SQL to execute on master
*/ */
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"INSERT INTO %s.repl_monitor " "INSERT INTO %s.repl_monitor "
" (primary_node, standby_node, " " (primary_node, "
" last_monitor_time, last_apply_time, " " standby_node, "
" last_wal_primary_location, last_wal_standby_location, " " last_monitor_time, "
" replication_lag, apply_lag ) " " last_apply_time, "
" VALUES(%d, %d, " " last_wal_primary_location, "
" '%s'::TIMESTAMP WITH TIME ZONE, '%s'::TIMESTAMP WITH TIME ZONE, " " last_wal_standby_location, "
" '%s', '%s', " " replication_lag, "
" %llu, %llu) ", " apply_lag ) "
" VALUES(%d, "
" %d, "
" '%s'::TIMESTAMP WITH TIME ZONE, "
" '%s'::TIMESTAMP WITH TIME ZONE, "
" '%s', "
" '%s', "
" %llu, "
" %llu) ",
get_repmgr_schema_quoted(master_conn), get_repmgr_schema_quoted(master_conn),
master_options.node, local_options.node, master_options.node,
monitor_standby_timestamp, last_wal_standby_applied_timestamp, local_options.node,
last_wal_master_location, last_wal_standby_received, monitor_standby_timestamp,
(long long unsigned int)(lsn_master - lsn_standby_received), last_xact_replay_timestamp,
(long long unsigned int)(lsn_standby_received - lsn_standby_applied)); last_wal_master_location,
last_xlog_receive_location,
(long long unsigned int)(lsn_master_current_xlog_location - lsn_last_xlog_receive_location),
(long long unsigned int)(lsn_last_xlog_receive_location - lsn_last_xlog_replay_location));
/* /*
* Execute the query asynchronously, but don't check for a result. We will * Execute the query asynchronously, but don't check for a result. We will
* check the result next time we pause for a monitor step. * check the result next time we pause for a monitor step.
*/ */
log_debug("standby_monitor: %s\n", sqlquery); log_verbose(LOG_DEBUG, "standby_monitor:() %s\n", sqlquery);
if (PQsendQuery(master_conn, sqlquery) == 0) if (PQsendQuery(master_conn, sqlquery) == 0)
log_warning(_("query could not be sent to master. %s\n"), log_warning(_("query could not be sent to master. %s\n"),
PQerrorMessage(master_conn)); PQerrorMessage(master_conn));
@@ -1044,7 +1109,7 @@ do_master_failover(void)
XLogRecPtr xlog_recptr; XLogRecPtr xlog_recptr;
bool lsn_format_ok; bool lsn_format_ok;
char last_wal_standby_applied[MAXLEN]; char last_xlog_replay_location[MAXLEN];
PGconn *node_conn = NULL; PGconn *node_conn = NULL;
@@ -1055,10 +1120,10 @@ do_master_failover(void)
t_node_info nodes[FAILOVER_NODES_MAX_CHECK]; t_node_info nodes[FAILOVER_NODES_MAX_CHECK];
/* Store details of the failed node here */ /* Store details of the failed node here */
t_node_info failed_master = {-1, NO_UPSTREAM_NODE, "", InvalidXLogRecPtr, UNKNOWN, false, false}; t_node_info failed_master = T_NODE_INFO_INITIALIZER;
/* Store details of the best candidate for promotion to master here */ /* Store details of the best candidate for promotion to master here */
t_node_info best_candidate = {-1, NO_UPSTREAM_NODE, "", InvalidXLogRecPtr, UNKNOWN, false, false}; t_node_info best_candidate = T_NODE_INFO_INITIALIZER;
/* get a list of standby nodes, including myself */ /* get a list of standby nodes, including myself */
sprintf(sqlquery, sprintf(sqlquery,
@@ -1187,12 +1252,13 @@ do_master_failover(void)
terminate(ERR_FAILOVER_FAIL); terminate(ERR_FAILOVER_FAIL);
} }
sqlquery_snprintf(sqlquery, "SELECT pg_last_xlog_receive_location()"); sqlquery_snprintf(sqlquery, "SELECT pg_catalog.pg_last_xlog_receive_location()");
res = PQexec(node_conn, sqlquery); res = PQexec(node_conn, sqlquery);
if (PQresultStatus(res) != PGRES_TUPLES_OK) if (PQresultStatus(res) != PGRES_TUPLES_OK)
{ {
log_info(_("unable to retrieve node's last standby location: %s\n"), log_info(_("unable to retrieve node's last standby location: %s\n"),
PQerrorMessage(node_conn)); PQerrorMessage(node_conn));
log_debug(_("connection details: %s\n"), nodes[i].conninfo_str); log_debug(_("connection details: %s\n"), nodes[i].conninfo_str);
PQclear(res); PQclear(res);
PQfinish(node_conn); PQfinish(node_conn);
@@ -1218,7 +1284,7 @@ do_master_failover(void)
} }
/* last we get info about this node, and update shared memory */ /* last we get info about this node, and update shared memory */
sprintf(sqlquery, "SELECT pg_last_xlog_receive_location()"); sprintf(sqlquery, "SELECT pg_catalog.pg_last_xlog_receive_location()");
res = PQexec(my_local_conn, sqlquery); res = PQexec(my_local_conn, sqlquery);
if (PQresultStatus(res) != PGRES_TUPLES_OK) if (PQresultStatus(res) != PGRES_TUPLES_OK)
{ {
@@ -1226,8 +1292,8 @@ do_master_failover(void)
" considered as new master and exit.\n"), " considered as new master and exit.\n"),
PQerrorMessage(my_local_conn)); PQerrorMessage(my_local_conn));
PQclear(res); PQclear(res);
sprintf(last_wal_standby_applied, "'%X/%X'", 0, 0); sprintf(last_xlog_replay_location, "'%X/%X'", 0, 0);
update_shared_memory(last_wal_standby_applied); update_shared_memory(last_xlog_replay_location);
terminate(ERR_DB_QUERY); terminate(ERR_DB_QUERY);
} }
/* write last location in shared memory */ /* write last location in shared memory */
@@ -1284,7 +1350,7 @@ do_master_failover(void)
res = PQexec(node_conn, sqlquery); res = PQexec(node_conn, sqlquery);
if (PQresultStatus(res) != PGRES_TUPLES_OK) if (PQresultStatus(res) != PGRES_TUPLES_OK)
{ {
log_err(_("PQexec failed: %s.\nReport an invalid value to not" log_err(_("PQexec failed: %s.\nReport an invalid value to not "
"be considered as new master and exit.\n"), "be considered as new master and exit.\n"),
PQerrorMessage(node_conn)); PQerrorMessage(node_conn));
PQclear(res); PQclear(res);
@@ -1308,7 +1374,7 @@ do_master_failover(void)
log_crit( log_crit(
_("unable to obtain LSN from node %i"), nodes[i].node_id _("unable to obtain LSN from node %i"), nodes[i].node_id
); );
log_info( log_hint(
_("please check that 'shared_preload_libraries=repmgr_funcs' is set in postgresql.conf\n") _("please check that 'shared_preload_libraries=repmgr_funcs' is set in postgresql.conf\n")
); );
@@ -1336,6 +1402,9 @@ do_master_failover(void)
PQclear(res); PQclear(res);
/* If position is 0/0, keep checking */ /* If position is 0/0, keep checking */
/* XXX we should add a timeout here to prevent infinite looping
* if the other node's repmgrd is not up
*/
continue; continue;
} }
@@ -1413,8 +1482,7 @@ do_master_failover(void)
/* wait */ /* wait */
sleep(5); sleep(5);
if (verbose) log_notice(_("this node is the best candidate to be the new master, promoting...\n"));
log_info(_("this node is the best candidate to be the new master, promoting...\n"));
log_debug(_("promote command is: \"%s\"\n"), log_debug(_("promote command is: \"%s\"\n"),
local_options.promote_command); local_options.promote_command);
@@ -1463,10 +1531,8 @@ do_master_failover(void)
/* wait */ /* wait */
sleep(10); sleep(10);
if (verbose) log_notice(_("node %d is the best candidate for new master, attempting to follow...\n"),
log_info(_("node %d is the best candidate to be the new master, we should follow it...\n"), best_candidate.node_id);
best_candidate.node_id);
log_debug(_("follow command is: \"%s\"\n"), local_options.follow_command);
/* /*
* The new master may some time to be promoted. The follow command * The new master may some time to be promoted. The follow command
@@ -1477,57 +1543,23 @@ do_master_failover(void)
fflush(stderr); fflush(stderr);
} }
/*
* If 9.4 or later, and replication slots in use, we'll need to create a
* slot on the new master
*/
new_master_conn = establish_db_connection(best_candidate.conninfo_str, true);
if (local_options.use_replication_slots) log_debug(_("executing follow command: \"%s\"\n"), local_options.follow_command);
{
if (create_replication_slot(new_master_conn, node_info.slot_name) == false)
{
appendPQExpBuffer(&event_details,
_("Unable to create slot '%s' on the master node: %s"),
node_info.slot_name,
PQerrorMessage(new_master_conn));
log_err("%s\n", event_details.data);
create_event_record(new_master_conn,
&local_options,
node_info.node_id,
"repmgrd_failover_follow",
false,
event_details.data);
PQfinish(new_master_conn);
terminate(ERR_DB_QUERY);
}
}
r = system(local_options.follow_command); r = system(local_options.follow_command);
if (r != 0) if (r != 0)
{
log_err(_("follow command failed. You could check and try it manually.\n"));
terminate(ERR_BAD_CONFIG);
}
/* and reconnect to the local database */
my_local_conn = establish_db_connection(local_options.conninfo, true);
/* update node information to reflect new status */
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 execute follow command:\n %s"),
node_info.node_id, local_options.follow_command);
best_candidate.node_id);
log_err("%s\n", event_details.data); log_err("%s\n", event_details.data);
create_event_record(new_master_conn, /* It won't be possible to write to the event notification
* table but we should be able to generate an external notification
* if required.
*/
create_event_record(NULL,
&local_options, &local_options,
node_info.node_id, node_info.node_id,
"repmgrd_failover_follow", "repmgrd_failover_follow",
@@ -1537,13 +1569,20 @@ do_master_failover(void)
terminate(ERR_BAD_CONFIG); terminate(ERR_BAD_CONFIG);
} }
/* and reconnect to the local database */
my_local_conn = establish_db_connection(local_options.conninfo, true);
/* update internal record for this node*/ /* update internal record for this node*/
new_master_conn = establish_db_connection(best_candidate.conninfo_str, true);
node_info = get_node_info(new_master_conn, local_options.cluster_name, local_options.node); node_info = get_node_info(new_master_conn, local_options.cluster_name, local_options.node);
appendPQExpBuffer(&event_details, appendPQExpBuffer(&event_details,
_("Node %i now following new upstream node %i"), _("node %i now following new upstream node %i"),
node_info.node_id, node_info.node_id,
best_candidate.node_id); best_candidate.node_id);
log_notice("%s\n", event_details.data);
create_event_record(new_master_conn, create_event_record(new_master_conn,
&local_options, &local_options,
node_info.node_id, node_info.node_id,
@@ -1570,6 +1609,8 @@ do_master_failover(void)
* It might be worth providing a selection of reconnection strategies * It might be worth providing a selection of reconnection strategies
* as different behaviour might be desirable in different situations; * as different behaviour might be desirable in different situations;
* or maybe the option not to reconnect might be required? * or maybe the option not to reconnect might be required?
*
* XXX check this handles replication slots gracefully
*/ */
static bool static bool
do_upstream_standby_failover(t_node_info upstream_node) do_upstream_standby_failover(t_node_info upstream_node)
@@ -1578,6 +1619,7 @@ do_upstream_standby_failover(t_node_info upstream_node)
char sqlquery[QUERY_STR_LEN]; char sqlquery[QUERY_STR_LEN];
int upstream_node_id = node_info.upstream_node_id; int upstream_node_id = node_info.upstream_node_id;
int r; int r;
PQExpBufferData event_details;
log_debug(_("do_upstream_standby_failover(): performing failover for node %i\n"), log_debug(_("do_upstream_standby_failover(): performing failover for node %i\n"),
node_info.node_id); node_info.node_id);
@@ -1647,26 +1689,65 @@ do_upstream_standby_failover(t_node_info upstream_node)
} }
PQclear(res); PQclear(res);
sleep(local_options.reconnect_intvl); sleep(local_options.reconnect_interval);
} }
/* Close the connection to this server */ /* Close the connection to this server */
PQfinish(my_local_conn); PQfinish(my_local_conn);
my_local_conn = NULL; my_local_conn = NULL;
initPQExpBuffer(&event_details);
/* Follow new upstream */ /* Follow new upstream */
r = system(local_options.follow_command); r = system(local_options.follow_command);
if (r != 0) if (r != 0)
{ {
log_err(_("follow command failed. You could check and try it manually.\n")); appendPQExpBuffer(&event_details,
_("Unable to execute follow command:\n %s"),
local_options.follow_command);
log_err("%s\n", event_details.data);
/* It won't be possible to write to the event notification
* table but we should be able to generate an external notification
* if required.
*/
create_event_record(NULL,
&local_options,
node_info.node_id,
"repmgrd_failover_follow",
false,
event_details.data);
terminate(ERR_BAD_CONFIG); terminate(ERR_BAD_CONFIG);
} }
if (update_node_record_set_upstream(master_conn, local_options.cluster_name, 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)
{ {
appendPQExpBuffer(&event_details,
_("Unable to set node %i's new upstream ID to %i"),
node_info.node_id,
upstream_node_id);
create_event_record(NULL,
&local_options,
node_info.node_id,
"repmgrd_failover_follow",
false,
event_details.data);
terminate(ERR_BAD_CONFIG); terminate(ERR_BAD_CONFIG);
} }
appendPQExpBuffer(&event_details,
_("node %i is now following upstream node %i"),
node_info.node_id,
upstream_node_id);
create_event_record(NULL,
&local_options,
node_info.node_id,
"repmgrd_failover_follow",
true,
event_details.data);
my_local_conn = establish_db_connection(local_options.conninfo, true); my_local_conn = establish_db_connection(local_options.conninfo, true);
return true; return true;
@@ -1681,7 +1762,7 @@ check_connection(PGconn **conn, const char *type, const char *conninfo)
/* /*
* Check if the node is still available if after * Check if the node is still available if after
* local_options.reconnect_attempts * local_options.reconnect_intvl * local_options.reconnect_attempts * local_options.reconnect_interval
* seconds of retries we cannot reconnect return false * seconds of retries we cannot reconnect return false
*/ */
for (connection_retries = 0; connection_retries < local_options.reconnect_attempts; connection_retries++) for (connection_retries = 0; connection_retries < local_options.reconnect_attempts; connection_retries++)
@@ -1699,9 +1780,9 @@ check_connection(PGconn **conn, const char *type, const char *conninfo)
{ {
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,
(local_options.reconnect_intvl * (local_options.reconnect_attempts - connection_retries))); (local_options.reconnect_interval * (local_options.reconnect_attempts - connection_retries)));
/* wait local_options.reconnect_intvl seconds between retries */ /* wait local_options.reconnect_interval seconds between retries */
sleep(local_options.reconnect_intvl); sleep(local_options.reconnect_interval);
} }
else else
{ {
@@ -1728,7 +1809,7 @@ check_connection(PGconn **conn, const char *type, const char *conninfo)
/* /*
* set_local_node_failed() * set_local_node_status()
* *
* If failure of the local node is detected, attempt to connect * If failure of the local node is detected, attempt to connect
* to the current master server (as stored in the global variable * to the current master server (as stored in the global variable
@@ -1736,16 +1817,16 @@ check_connection(PGconn **conn, const char *type, const char *conninfo)
*/ */
static bool static bool
set_local_node_failed(void) set_local_node_status(void)
{ {
PGresult *res; PGresult *res;
char sqlquery[QUERY_STR_LEN]; char sqlquery[QUERY_STR_LEN];
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", NULL)) 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_status(): Unable to connect to last known master node\n"));
return false; return false;
} }
@@ -1799,17 +1880,16 @@ set_local_node_failed(void)
/* /*
* Attempt to set own record as inactive * Attempt to set the active record to the correct value.
* First
*/ */
sqlquery_snprintf(sqlquery,
"UPDATE %s.repl_nodes "
" SET active = FALSE "
" WHERE id = %i ",
get_repmgr_schema_quoted(master_conn),
node_info.node_id);
res = PQexec(master_conn, sqlquery); if (!update_node_record_status(master_conn,
if (PQresultStatus(res) != PGRES_COMMAND_OK) local_options.cluster_name,
node_info.node_id,
"standby",
node_info.upstream_node_id,
is_standby(my_local_conn)==1))
{ {
log_err(_("unable to set local node %i as inactive on master: %s\n"), log_err(_("unable to set local node %i as inactive on master: %s\n"),
node_info.node_id, node_info.node_id,
@@ -1834,7 +1914,7 @@ check_cluster_configuration(PGconn *conn)
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT oid FROM pg_class " "SELECT oid FROM pg_class "
" WHERE oid = '%s.repl_nodes'::regclass ", " WHERE oid = '%s.repl_nodes'::regclass ",
get_repmgr_schema()); get_repmgr_schema_quoted(master_conn));
res = PQexec(conn, sqlquery); res = PQexec(conn, sqlquery);
if (PQresultStatus(res) != PGRES_TUPLES_OK) if (PQresultStatus(res) != PGRES_TUPLES_OK)
{ {
@@ -1907,6 +1987,8 @@ check_node_configuration(void)
/* Adding the node */ /* Adding the node */
log_info(_("adding node %d to cluster '%s'\n"), log_info(_("adding node %d to cluster '%s'\n"),
local_options.node, local_options.cluster_name); local_options.node, local_options.cluster_name);
/* XXX use create_node_record() */
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"INSERT INTO %s.repl_nodes" "INSERT INTO %s.repl_nodes"
" (id, cluster, name, conninfo, priority, witness) " " (id, cluster, name, conninfo, priority, witness) "
@@ -1961,18 +2043,18 @@ lsn_to_xlogrecptr(char *lsn, bool *format_ok)
void void
usage(void) usage(void)
{ {
log_err(_("%s: Replicator manager daemon \n"), progname); log_err(_("%s: Replicator manager daemon \n"), progname());
log_err(_("Try \"%s --help\" for more information.\n"), progname); log_err(_("Try \"%s --help\" for more information.\n"), progname());
} }
void void
help(const char *progname) help(void)
{ {
printf(_("%s: replication management daemon for PostgreSQL\n"), progname); printf(_("%s: replication management daemon for PostgreSQL\n"), progname());
printf(_("\n")); printf(_("\n"));
printf(_("Usage:\n")); printf(_("Usage:\n"));
printf(_(" %s [OPTIONS]\n"), progname); printf(_(" %s [OPTIONS]\n"), progname());
printf(_("\n")); printf(_("\n"));
printf(_("Options:\n")); printf(_("Options:\n"));
printf(_(" -?, --help show this help, then exit\n")); printf(_(" -?, --help show this help, then exit\n"));
@@ -1983,7 +2065,7 @@ help(const char *progname)
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")); printf(_("\n"));
printf(_("%s monitors a cluster of servers and optionally performs failover.\n"), progname); printf(_("%s monitors a cluster of servers and optionally performs failover.\n"), progname());
} }
@@ -2021,14 +2103,14 @@ terminate(int retval)
unlink(pid_file); unlink(pid_file);
} }
log_info(_("%s terminating...\n"), progname); log_info(_("%s terminating...\n"), progname());
exit(retval); exit(retval);
} }
static void static void
update_shared_memory(char *last_wal_standby_applied) update_shared_memory(char *last_xlog_replay_location)
{ {
PGresult *res; PGresult *res;
char sqlquery[QUERY_STR_LEN]; char sqlquery[QUERY_STR_LEN];
@@ -2036,7 +2118,7 @@ update_shared_memory(char *last_wal_standby_applied)
sprintf(sqlquery, sprintf(sqlquery,
"SELECT %s.repmgr_update_standby_location('%s')", "SELECT %s.repmgr_update_standby_location('%s')",
get_repmgr_schema_quoted(my_local_conn), get_repmgr_schema_quoted(my_local_conn),
last_wal_standby_applied); last_xlog_replay_location);
/* If an error happens, just inform about that and continue */ /* If an error happens, just inform about that and continue */
res = PQexec(my_local_conn, sqlquery); res = PQexec(my_local_conn, sqlquery);
@@ -2226,13 +2308,13 @@ 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)
{ {
PGresult *res; int res;
t_node_info node_info = { NODE_NOT_FOUND, NO_UPSTREAM_NODE, "", InvalidXLogRecPtr, UNKNOWN, false, false}; t_node_info node_info = T_NODE_INFO_INITIALIZER;
res = get_node_record(conn, cluster, node_id); res = get_node_record(conn, cluster, node_id, &node_info);
if (PQresultStatus(res) != PGRES_TUPLES_OK) if (res == -1)
{ {
PQExpBufferData errmsg; PQExpBufferData errmsg;
initPQExpBuffer(&errmsg); initPQExpBuffer(&errmsg);
@@ -2251,47 +2333,14 @@ get_node_info(PGconn *conn, char *cluster, int node_id)
false, false,
errmsg.data); errmsg.data);
PQclear(res); PQfinish(conn);
terminate(ERR_DB_QUERY); terminate(ERR_DB_QUERY);
} }
if (!PQntuples(res)) { if (res == 0)
{
log_warning(_("No record found record for node %i\n"), node_id); log_warning(_("No record found record for node %i\n"), node_id);
PQclear(res);
node_info.node_id = NODE_NOT_FOUND;
return node_info;
} }
node_info.node_id = atoi(PQgetvalue(res, 0, 0));
node_info.upstream_node_id = atoi(PQgetvalue(res, 0, 1));
strncpy(node_info.conninfo_str, PQgetvalue(res, 0, 2), MAXLEN);
node_info.type = parse_node_type(PQgetvalue(res, 0, 3));
strncpy(node_info.slot_name, PQgetvalue(res, 0, 4), MAXLEN);
node_info.active = (strcmp(PQgetvalue(res, 0, 5), "t") == 0)
? true
: false;
PQclear(res);
return node_info; return node_info;
} }
static t_server_type
parse_node_type(const char *type)
{
if (strcmp(type, "master") == 0)
{
return MASTER;
}
else if (strcmp(type, "standby") == 0)
{
return STANDBY;
}
else if (strcmp(type, "witness") == 0)
{
return WITNESS;
}
return UNKNOWN;
}

View File

@@ -1,7 +1,7 @@
# #
# Makefile # Makefile
# #
# Copyright (c) 2ndQuadrant, 2010-2015 # Copyright (c) 2ndQuadrant, 2010-2016
# #
MODULE_big = repmgr_funcs MODULE_big = repmgr_funcs

View File

@@ -0,0 +1,35 @@
/*
* Update a repmgr 3.0 installation to repmgr 3.1
* ----------------------------------------------
*
* The new repmgr package should be installed first. Then
* carry out these steps:
*
* 1. (If repmgrd is used) stop any running repmgrd instances
* 2. On the master node, execute the SQL statements listed below
* 3. (If repmgrd is used) restart repmgrd
*/
/*
* If your repmgr installation is not included in your repmgr
* user's search path, please set the search path to the name
* of the repmgr schema to ensure objects are installed in
* the correct location.
*
* The repmgr schema is "repmgr_" + the cluster name defined in
* 'repmgr.conf'.
*/
-- SET search_path TO 'name_of_repmgr_schema';
BEGIN;
-- New view "repl_show_nodes" which also displays the server's
-- upstream node
CREATE VIEW repl_show_nodes AS
SELECT rn.id, rn.conninfo, rn.type, rn.name, rn.cluster,
rn.priority, rn.active, sq.name AS upstream_node_name
FROM repl_nodes as rn LEFT JOIN repl_nodes AS sq ON sq.id=rn.upstream_node_id;
COMMIT;

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
/* /*
* repmgr_function.sql * repmgr_function.sql
* Copyright (c) 2ndQuadrant, 2010-2015 * Copyright (c) 2ndQuadrant, 2010-2016
* *
*/ */

View File

@@ -1,6 +1,6 @@
/* /*
* uninstall_repmgr_funcs.sql * uninstall_repmgr_funcs.sql
* Copyright (c) 2ndQuadrant, 2010-2015 * Copyright (c) 2ndQuadrant, 2010-2016
* *
*/ */

View File

@@ -1,7 +1,7 @@
/* /*
* strutil.c * strutil.c
* *
* Copyright (C) 2ndQuadrant, 2010-2015 * Copyright (C) 2ndQuadrant, 2010-2016
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* 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

View File

@@ -1,6 +1,6 @@
/* /*
* strutil.h * strutil.h
* Copyright (C) 2ndQuadrant, 2010-2015 * Copyright (C) 2ndQuadrant, 2010-2016
* *
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@@ -24,12 +24,17 @@
#include <stdlib.h> #include <stdlib.h>
#include "errcode.h" #include "errcode.h"
#define QUERY_STR_LEN 8192 #define QUERY_STR_LEN 8192
#define MAXLEN 1024 #define MAXLEN 1024
#define MAXLINELENGTH 4096 #define MAXLINELENGTH 4096
#define MAXVERSIONSTR 16 #define MAXVERSIONSTR 16
#define MAXCONNINFO 1024 #define MAXCONNINFO 1024
/* Why? http://stackoverflow.com/a/5459929/398670 */
#define STR(x) CppAsString(x)
#define MAXLEN_STR STR(MAXLEN)
extern int extern int
xsnprintf(char *str, size_t size, const char *format,...) xsnprintf(char *str, size_t size, const char *format,...)

View File

@@ -1,7 +1,7 @@
/* /*
* uninstall_repmgr.sql * uninstall_repmgr.sql
* *
* Copyright (C) 2ndQuadrant, 2010-2015 * Copyright (C) 2ndQuadrant, 2010-2016
* *
*/ */

View File

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