Compare commits

...

211 Commits

Author SHA1 Message Date
Ian Barwick
b6cf22ac90 Update HISTORY 2016-12-27 14:49:50 +09:00
Ian Barwick
d89a73cbf4 repmgrd: fix log messages and code comments to reflect what is actually happening
Sometimes we're setting node status to active.
2016-12-27 14:48:34 +09:00
Ian Barwick
1f09e92e3f Typo fix 2016-12-26 15:23:04 +09:00
Ian Barwick
1bdc72a07b Updates to README and --help output 2016-12-26 15:23:00 +09:00
Ian Barwick
a6f1c6e483 repmgr: use provided --replication-user in pg_basebackup mode 2016-12-26 15:22:56 +09:00
Ian Barwick
6e14f0bc5d repmgr: in rsync mode don't delete backup_history file
Make behaviour consistent with pg_basebackup.
2016-12-26 10:39:05 +09:00
Ian Barwick
a336d22bd9 repmgr: miscelleanous code cleanup 2016-12-26 10:39:00 +09:00
Ian Barwick
e88a8a9708 repmgr: standby register --wait-sync=0 can loop infinitely 2016-12-23 11:29:19 +09:00
Ian Barwick
8f3f4eb4a3 Update version number for 3.3 branch 2016-12-22 16:53:39 +09:00
Ian Barwick
dc18e5b791 repmgr: escape conninfo parameters when cloning from Barman 2016-12-22 12:21:44 +09:00
Ian Barwick
9da0914976 Consolidate various backported functions 2016-12-22 12:11:54 +09:00
Ian Barwick
666e71a589 repmgr: escape parameter values in primary_conninfo, if required
Addresses GitHub #253.
2016-12-22 11:40:46 +09:00
Ian Barwick
062af91d36 Update HISTORY 2016-12-21 17:06:58 +09:00
Ian Barwick
571ad698db Add documentation for standby register --force and --upstream-conninfo 2016-12-21 17:06:07 +09:00
Ian Barwick
742f7e167f repmgr: support option --replication-user in STANDBY CLONE too 2016-12-14 17:00:03 +09:00
Ian Barwick
1fb2801639 repmgr: add option --replication-user 2016-12-14 15:58:11 +09:00
Ian Barwick
e3031f0204 repmgr: fix log message 2016-12-14 12:39:47 +09:00
Ian Barwick
79748f28f1 repmgr: improve STANDBY REGISTER sanity checks and log messages 2016-12-14 12:38:42 +09:00
Ian Barwick
46740b64a9 repmgr: enable forced registration of a node with a downstream cascaded standby 2016-12-14 12:22:41 +09:00
Ian Barwick
6557099832 repmgr: initial support for standby register --force
This functionality is intended for those cases where a cascading replication
cluster is being automatically provisioned and it might be necessary to
clone multiple levels in parallel.

As always, use of `--force` implies you know what you are doing.
2016-12-12 15:51:19 +09:00
Ian Barwick
083e288ac3 repmgr: add usage warnings for --no-conninfo-password 2016-12-08 23:01:46 +09:00
Ian Barwick
f5e3d7c041 Merge branch 'no-passwords'
GitHub pull request #255
2016-12-08 22:56:04 +09:00
Abhijit Menon-Sen
402e02f4b7 Add an option to suppress passwords in recovery.conf/primary_conninfo 2016-12-08 22:36:18 +09:00
Ian Barwick
a21b16f960 Update HISTORY 2016-12-07 17:12:21 +09:00
Ian Barwick
be58af701b repmgr: add option --upstream-conninfo
When executing `repmgr standby clone`, this enables the primary_conninfo
string set in recovery.conf to be explictly defined (rather than generated
from the upstream node's conninfo string and connection parameters).

This is primarily intended for those cases when an L2 cascaded standby
is being cloned from the cluster primary, and its intended upstream
might not yet be available.
2016-12-07 17:05:57 +09:00
Ian Barwick
eb2cdf8a98 repmgr: improve handling of command line parameter errors
Previously providing a parameter which requires a value (e.g. -f/--config-file)
would result in a misleading error like "Unknown option -f".

Rather than suppress getopt's error messages, we'll rely on these to inform
about missing required values or unknown options (as other PostgreSQL tools
do), though we will still provide our own list of command line errors and
warnings countered above and beyond getopt's sanity checks.
2016-12-05 14:40:32 +09:00
Ian Barwick
7cc0400c03 repmgr: add option --log-to-file; remove timestamp from log output by default
Log lines will still be prefixed with timestamp if `-log-to-file` used.
2016-12-05 14:09:41 +09:00
Ian Barwick
9788b2bd29 repmgr: don't display timestamp in log output
Differentiate between repmgr and repmgrd output
2016-12-05 10:44:30 +09:00
Ian Barwick
227f0190f7 repmgr: initial support for PostgreSQL 10
Handle directory name change from pg_xlog to pg_wal.

Note that some functions with "xlog" in their name may also change.
2016-12-02 12:06:08 +09:00
Ian Barwick
d6dbc70916 Update HISTORY 2016-11-25 08:17:01 +09:00
Ian Barwick
d2f4eda224 Note use of phbouncer %include directive 2016-11-23 09:33:30 +09:00
Ian Barwick
2588853e83 Refactor sample script in repmgrd-node-fencing.md 2016-11-23 09:21:14 +09:00
Ian Barwick
b54f98ed8a repmgr: always log to STDERR even if log facility defined 2016-11-23 08:55:55 +09:00
Ian Barwick
26f73686e5 repmgrd: fixes to configuration reload mechanism 2016-11-02 23:23:26 +09:00
Ian Barwick
e274a2cbcb repmgrd: clean up some redundant code 2016-11-02 16:52:44 +09:00
Ian Barwick
d502bbe614 repmgrd: enable logging configuration to be changed 2016-11-01 20:34:50 +09:00
Ian Barwick
2594411820 reload_config(): document items which can change 2016-11-01 20:08:57 +09:00
Ian Barwick
d22535de00 repmgr: pointless parsing the configuration here 2016-11-01 19:28:03 +09:00
Ian Barwick
fce1f0cd4a Refactor reload_config()
Remove any non-repmgrd specific items.

parse_config() already sanity-checks the values so no need to
recheck. Refactor parse_config() so when called by reload_config()
it won't exit if errors are encountered.
2016-11-01 19:05:21 +09:00
Ian Barwick
bb842c3989 repmgr: improve replication status checking during switchover
When checking the new standby's record in pg_stat_replication, keep
polling until the expected status is reported, and only give up
after a timeout was exceeded.

Previously repmgr would report an error if status was "startup",
even though this is not a problem.
2016-11-01 17:42:35 +09:00
Ian Barwick
556ff3c311 repmgrd: clarify master_response_timeout 2016-11-01 15:56:57 +09:00
Ian Barwick
251486546d repmgr: when unregistering a witness, remove record on that witness to
Fixes GitHub #250.
2016-10-30 16:28:12 +09:00
Ian Barwick
53d3e71cd3 README: formatting 2016-10-27 10:42:42 +09:00
Ian Barwick
b986ce81b2 README: link to latest release notes 2016-10-27 10:40:31 +09:00
Ian Barwick
7ddb060bdc README: clarify that the repmgr metadatabase must be part of the replication cluster 2016-10-26 20:29:30 +09:00
Ian Barwick
6b02faf37c Update HISTORY 2016-10-24 09:04:04 +09:00
Ian Barwick
0cde0068dd repmgrd: improve witness failover logging
Log the new master node as INFO rather than DEBUG.
2016-10-20 10:56:16 +09:00
Ian Barwick
20d66df0ef repmgr: prevent involuntary cloning where no repmgr schema present on master 2016-10-19 20:26:55 +09:00
Ian Barwick
3f7c30b84d repmgr: require a valid repmgr cluster name unless -F/--force supplied
Addresses issue mentioned in GitHub #242.
2016-10-19 16:16:12 +09:00
Ian Barwick
a63baf7fcb repmgr: rename local variable config_file_found to remote_config_file_found
We also have a global `config_file_found` - avoid confusion with that.
2016-10-19 15:30:02 +09:00
Ian Barwick
e19c643389 repmgr: refactor check_upstream_config() for clarity
Also ensure the check for sufficient walsenders is only carried out
when using pg_basebackup.
2016-10-19 15:06:21 +09:00
Ian Barwick
f058833451 repmgr: in Barman clone mode, don't try and create the data directory twice
In Barman mode the data directory is created early containing a temporary
directory needed to hold temporary files while cloning from the Barman
server. In other modes we might not know the data directory location until
connecting to the source server, so its creation happens later. In Barman
mode ensure that step is skipped.
2016-10-19 10:00:50 +09:00
Ian Barwick
96c14adfdb repmgr: ensure data directory defaults to that of the source node
As long as -D/--pgdata is not supplied, and barman mode is not used
for cloning.
2016-10-17 16:34:49 +09:00
Ian Barwick
50119056a5 repmgr: clean up runtime options structure
Place elements in a sensible order and split the associated initializer
macro over multiple lines for easier editing.

Also move a few related global variables into to the structure to keep
everything in the same place.
2016-10-11 12:54:56 +09:00
Ian Barwick
a279c42df9 "pg_logical/snapshot" -> "pg_logical/snapshots" 2016-10-11 09:32:55 +09:00
Ian Barwick
f70b6ea136 Merge branch 'fix-barman-mode' of git://github.com/gciolli/repmgr
Fixes GitHub #245.
2016-10-11 09:21:31 +09:00
Ian Barwick
e4cb6d7130 repmgr: simplify LSN parsing
Also silences a compiler warning.
2016-10-10 22:56:49 +09:00
Gianni Ciolli
502c056753 When restoring from Barman, create pg_logical subdirectories
Nodes cloned from Barman backups were missing these subdirectories,
and so they were unable to start when promoted.
2016-10-10 10:18:33 +02:00
Ian Barwick
871ec47ff5 Fix repmgr cluster crosscheck output
Show actual node ID rather than incremental number.

Fixes GitHub #244.
2016-10-10 16:20:17 +09:00
Ian Barwick
f435abb3ec Update README
Also reorder HISTORY entries.
2016-10-10 15:10:06 +09:00
Ian Barwick
a217b4d0a9 repmgr: standardize SSH-related error messages 2016-10-07 07:42:15 +09:00
Ian Barwick
2dcb75f889 Add 'cluster crosscheck' to help output detail
Per GitHub #243.
2016-10-06 07:38:45 +09:00
Ian Barwick
b509ce6382 Minor README fix 2016-10-05 16:47:37 +09:00
Ian Barwick
1150bf272a Update README
`--ignore-external-config-files` deprecated
2016-10-05 15:09:07 +09:00
Ian Barwick
09ac6cd145 Update history 2016-10-05 13:57:10 +09:00
Ian Barwick
2fae788bc4 Add documentation for repmgrd failover process and failed node fencing
Addresses GitHub #200.
2016-10-05 11:25:36 +09:00
Ian Barwick
eb90f864c9 repmgr: consistent error message style 2016-10-05 10:31:25 +09:00
Ian Barwick
ba89758366 Update barman-wal-restore documentation
Barman 2.0 provides this in a separate, more convenient `barman-cli` package;
document this and add note about previous `barman-wal-restore.py` script.
2016-10-03 15:59:03 +09:00
Ian Barwick
84595fe711 Tweak repmgr.conf.sample
Put `monitor_interval_secs` at the start of the `repmgrd` section, as it's
a very fundamental configuration item.
2016-10-03 15:57:33 +09:00
Ian Barwick
9523894808 Bump dev version number
3.3dev
2016-09-30 15:14:35 +09:00
Ian Barwick
df09af4d57 Update README
`repmgr cluster show --csv` now only reflects node availability,
and no longer overloads this with node type (master/standby) information.
2016-09-30 15:07:34 +09:00
Ian Barwick
2c1cbc6bf9 Fix witness server initialisation 2016-09-30 13:43:47 +09:00
Ian Barwick
ed22fe326e Document and expand pg_ctl override configuration options
These are now prefixed with "service_" to emphasize that they're
OS-level commands, not repmgr ones; also added reload and promote
commands:

    service_start_command
    service_stop_command
    service_restart_command
    service_reload_command
    service_promote_command

GitHub #169
2016-09-30 11:58:45 +09:00
Ian Barwick
46500e1408 Documentation update and miscellaneous code cleanup 2016-09-30 09:30:22 +09:00
Ian Barwick
c3971513b6 Refactor show diagnose to handle node IDs correctly.
See previous comments for `show matrix`.
2016-09-30 01:46:01 +09:00
Ian Barwick
a2910eded9 Refactor show matrix to handle node IDs correctly.
Previously the code assumed repmgr node IDs to be sequential,
which is not guaranteed to be the case. With a non-sequential
list of node IDs, an incorrect node id would be displayed,
and memory accessed beyond the bounds of the matrix array.

The refactored code is considerably less elegant than the original
but will correctly handle a non-sequential sequence of node IDs.
2016-09-29 18:51:55 +09:00
Ian Barwick
dc70e2d804 Remove superfluous PQfinish() call
Connection was previously closed, if this error condition is triggered
a segfault will occur.
2016-09-29 11:53:13 +09:00
Ian Barwick
ea45158f50 Refactor cluster diagnose
- use the remote user setting, like other SSH-based remote operations
  (avoid hardcoding the user name)
- enable `repmgr cluster matrix'  to accept the cluster name, node id
  and the database connection information instead of requiring repmgr.conf;
  this means we don't have to assume that repmgr.conf is in one
  of the default locations
2016-09-29 11:41:14 +09:00
Gianni Ciolli
84d1e16edd Factor out "build_cluster_diagnose" from "do_cluster_diagnose"
We separate the code that builds the cube from the code that displays
it, in preparation for reusing the cube somewhere else, e.g. for
automatic failover detection.
2016-09-29 01:23:09 +09:00
Gianni Ciolli
57815af3ac Factor out "build_cluster_matrix" from "do_cluster_matrix"
We separate the code that builds the matrix from the code that
displays it, in preparation for reusing the matrix somewhere else,
e.g. for automatic failover detection.
2016-09-29 01:22:54 +09:00
Gianni Ciolli
a4a2e48ab4 README rewording 2016-09-29 01:21:06 +09:00
Gianni Ciolli
5189488b92 Add "cluster diagnose" mode
This mode merges the output of "cluster matrix" from each node to
improve node state knowledge.
2016-09-29 01:19:36 +09:00
Gianni Ciolli
263128a740 Bug fix 2016-09-29 00:59:26 +09:00
Ian Barwick
f775750334 cluster matrix: make remote execution of cluster show more configurable
- use the remote user setting, like other SSH-based remote operations
  (avoid hardcoding the user name)
- enable `repmgr cluster show` to accept the cluster name and the
  database connection information instead of requiring repmgr.conf;
  this means we don't have to assume that repmgr.conf is in one
  of the default locations
2016-09-29 00:54:33 +09:00
Ian Barwick
41ec45a4cc Remove ssh_hostname support
Currently repmgr assumes the SSH hostname will be the same as the
database hostname, and it's easy enough now to extract this
from the node's conninfo string.

We can consider re-adding this in the next release if required.
2016-09-29 00:24:04 +09:00
Gianni Ciolli
9b5b9acb82 Add "cluster matrix" mode and "ssh_hostname" parameter
- The "cluster matrix" command supports CSV mode via the --csv
  switch.
- Add the optional ssh_hostname configuration parameter, which is
  required by "cluster matrix".
- A corresponding ssh_hostname column has been added to the repl_nodes
  table and to the repl_show_nodes view.
2016-09-28 23:40:35 +09:00
Ian Barwick
77de5dbeeb Update HISTORY 2016-09-28 23:37:42 +09:00
Gunnlaugur Thor Briem
465f1a73a5 Fix inconsistent repmgr.conf path in failover cfg
The examples of `promote_command` and `follow_command` reference the
`repmgr.conf` file under a different path from the rest of the README.
This makes them consistent with the rest of the README.
2016-09-28 23:14:49 +09:00
Ian Barwick
c4f84bd777 Update README
Document new `--copy-external-config-files` option.
2016-09-28 16:40:13 +09:00
Ian Barwick
da4dc26505 repmgr: various minor logging fixes 2016-09-28 15:51:26 +09:00
Ian Barwick
19670db1d4 repmgr: add missing 'break' statements in switch structure 2016-09-28 15:41:29 +09:00
Ian Barwick
b9f52e74eb repmgr: add --help output and documentation for --wait-sync 2016-09-28 14:22:26 +09:00
Ian Barwick
fa10fd8493 repmgr: add option --wait-sync for standby register
Causes repmgr to wait for the updated node record to propagate
to the standby before exiting. This can be used to ensure that
actions which depend on the standby's node record being synchronised
(such as starting repmgrd) are not carried out prematurely.

Addresses GitHub #103
2016-09-28 14:08:42 +09:00
Ian Barwick
b7f20ee1f7 repmgrd: don't start if node is inactive and failover=automatic
If failover=automatic, it would be reasonable to expect repmgrd to
consider this node as a promotion candidate, however this will not
happen if it is marked inactive. This often happens when a failed
primary is recloned as a standby but not re-registered, and if
repmgrd would run it would give the incorrect impression that
failover capability is available.

Addresses GitHub #153.
2016-09-28 10:59:20 +09:00
Ian Barwick
bbb2e2f017 Initial implementation of improved configuration file copying
GitHub #210.
2016-09-27 22:19:45 +09:00
Ian Barwick
52328b8f33 repmgr: update README 2016-09-27 10:27:27 +09:00
Ian Barwick
65c2be3441 Remove redundant code 2016-09-26 10:44:34 +09:00
Ian Barwick
b17593ff4d repmgr: improve handling application name during standby clone
Addresses issues noticed while investigating GitHub #238
2016-09-26 10:41:01 +09:00
Ian Barwick
7c1776655b repmgr: correctly set application name during standby follow
Addresses issue mentioned in GitHub #238
2016-09-25 21:33:11 +09:00
Ian Barwick
789470b227 repmgr: rename 'upstream_conninfo' etc. to 'recovery_conninfo' etc.
These variables are used only for generating `primary_conninfo` in recovery.conf,
rename to make their purpose clearer.
2016-09-25 19:18:54 +09:00
Martin
5d3c0d6163 Make it clear in the error message that the type of connection that
failed was an SSH connection, not a libpq one.
2016-09-23 09:15:09 -03:00
Ian Barwick
44d4ca46b0 repmgr: place write_primary_conninfo() next to the other recovery.conf functions
Also terminate the buffer created, for tidiness.
2016-09-22 10:10:26 +09:00
Ian Barwick
114c1bddcb repmgr: only require 'wal_keep_segments' to be set in certain corner cases
Now that repmgr uses pg_basebackup's `--xlog-method=stream` setting by
default, and enables provision of `restore_command`, there's no reason
to require `wal_keep_segments` to be set in the default use-case.

`repmgr standby clone` will now only fail with an error if `wal_keep_segments`
is zero and one of the following cases applies:

* `--rsync-only` clone with no `restore_command` set
* clone with pg_basebackup and `--xlog-method=fetch`
* -w/--wal-keep-segments specified on the command line

If, for whatever reason, it's necessary to perform a standby clone
with `wal_keep_segments=0` in one of the above cases, specifying
`-w/--wal-keep-segments=0` on the command line will effectively
override the check.

GitHub #204
2016-09-21 16:42:14 +09:00
Ian Barwick
5090b8cab1 Update HISTORY 2016-09-21 13:39:42 +09:00
Ian Barwick
5e338473f7 repmgr: before cloning a standby, verify sufficient free walsenders available
Previously repmgr only checked that 'max_wal_senders' is a positive value.
It will now additionally verify that the requisite number of replication
connections can actually be made before commencing with a cloning operation
using pg_basebackup.

GitHub #214
2016-09-21 12:36:17 +09:00
Ian Barwick
e043d5c9a9 Clarify requirements for passwordless SSH access
This is already effectively optional; in 3.2 we will ensure it becomes
fully optional (mainly by deprecating --ignore-external-config-files
and replacing it with --copy-external-config-files).
2016-09-21 08:43:45 +09:00
Ian Barwick
03911488aa repmgr: in standby switchover, quote file paths in remotely executed commands
Per suggestion from GitHub user sebasmannem (#229)
2016-09-21 08:09:44 +09:00
Ian Barwick
3e51a85e07 repmgr: consolidate error messages during replication slot generation
Return error messages to the called so they can be logged as events;
prevent log message duplication in one case.
2016-09-20 09:16:33 +09:00
Ian Barwick
036c59526a repmgr: in switchover mode, prevent checks for local config file if not provided
In switchover mode, if no remote repmgr config file is provided with `-C`,
repmgr attempts to look for a file with the same path as the local
file (provided with `-f/--config-file`). However if this was not specified,
repmgr would execute `ls` with an empty filepath on the remote host, which
appeared to succeed, causing subsequent remote repmgr command executions
to fail as a blank value was provided for `-f/--config-file`.

Fixes GitHub #229.
2016-09-19 21:24:49 +09:00
Ian Barwick
2c55accbdd README: clarify supported versions 2016-09-19 20:59:35 +09:00
Ian Barwick
3ce231a571 Ensure get_pg_settings() returns false if parameter not found
Previously, if e.g. a non-superuser connection is used to get a value
like `data_directory`, which is available to superusers only, it
would return true.
2016-09-19 14:08:27 +09:00
Ian Barwick
178b380f34 Explictly specify pg_catalog path in all system queries 2016-09-19 14:08:20 +09:00
Abhijit Menon-Sen
4d36712901 Another typo fix 2016-09-17 14:15:50 +05:30
Abhijit Menon-Sen
7c3c30ae4a More typo fixes from Andreas 2016-09-17 14:02:26 +05:30
Abhijit Menon-Sen
7e6491a6d6 Fix typo, thanks to Andreas Kretschmer 2016-09-16 21:05:09 +05:30
Ian Barwick
ac8910000f repmgr: Explicitly set permissions on recovery.conf to 0600
Per GitHub #236 (dmarck)
2016-09-09 11:39:55 +09:00
Ian Barwick
cc3c2f5073 Merge branch 'conninfo-fixes'
Refactor recovery.conf generation to take into account the node being
cloned from might not be the intended upstream node, e.g. "grandchild"
node being cloned direct from the master ("grandparent") rather than
the intended parent node.

This extends functionality introduced with Barman support and ensures
that behaviour of cascaded standby cloning is consistent, regardless
of cloning method.
2016-09-05 13:16:31 +09:00
Ian Barwick
171df20386 repmgr: avoid (null) in local_command debug output 2016-09-01 12:14:36 +09:00
Ian Barwick
2105837ef4 repmgr: in standby clone, always add user-supplied connection information to upstream default parameters
Also misc other cleanup and documentation.
2016-09-01 12:06:19 +09:00
Ian Barwick
d12ecba63c repmgr: fix Barman error message in standy clone
Report the Barman catalogue name for the managed server, not the
hostname of the Barman server itself.
2016-08-31 13:50:16 +09:00
Ian Barwick
5276cb279c repmgr: add correct return codes for get_tablespace_data()
Remove unused variable retval.
2016-08-31 13:46:44 +09:00
Ian Barwick
719ad3cf95 repmgr: properly remove temporary directory created when cloning from Barman 2016-08-31 13:37:17 +09:00
Ian Barwick
e87399afc1 Clean up create_recovery_file() function
Caller is responsible for providing a t_conninfo_param_list from which
the value for "primary_conninfo" is created.
2016-08-31 13:03:59 +09:00
Ian Barwick
1d05345aa3 Add error code ERR_BARMAN
Indicates unrecoverable error condition when accessing the barman server
2016-08-31 11:41:55 +09:00
Ian Barwick
a8afa843ee Add parameter checks and help output for --no-upstream-connection 2016-08-31 11:35:23 +09:00
Ian Barwick
5c4b477d84 Consolidate various checks in do_standby_clone() 2016-08-31 11:03:44 +09:00
Ian Barwick
f8fe801225 use repmgr db connection with barman 2016-08-31 09:53:18 +09:00
Ian Barwick
d7456d879d Add option --no-upstream-connection and parse Barman conninfo string 2016-08-31 09:30:03 +09:00
Ian Barwick
751469a08d Check SSH and create data dir early
so we can init the barman stuff, which we want to verify the
upstream conninfo
2016-08-31 09:28:50 +09:00
Ian Barwick
afa5c1469b Actually differentiate between clone source node and defined upstream node 2016-08-31 09:28:36 +09:00
Ian Barwick
1778eeab9c Initial change for differentiating between host we're cloning from and the defined upstream 2016-08-31 09:25:11 +09:00
Gianni Ciolli
95de5ef976 Bug fix 2016-08-31 09:23:36 +09:00
Gianni Ciolli
c0eea90402 Cascading replication support for Barman mode
If upstream_node is specified, we point the standby to that node;
otherwise we point it to the current primary node.
2016-08-31 09:23:25 +09:00
Gianni Ciolli
135fa2e1b9 Fixing primary_conninfo generation (#232)
After introducing Barman mode, it is no longer true that STANDBY CLONE
can derive primary_conninfo from the connection to the master. Now we
ask Barman how to connect to a valid cluster node, and then we fetch
the conninfo for the current master from repmgr metadata.
2016-08-30 10:45:22 +09:00
Gianni Ciolli
2a8861be8b Fixing inaccurate option checking (#230)
The incompatibility between Barman mode and use_replication_slots was
not checked properly, and did not cause repmgr to exit.
2016-08-30 08:33:41 +09:00
Ian Barwick
a55c224510 README: add note about cloned standby configuration. 2016-08-25 09:33:52 +09:00
Ian Barwick
844b9f54e4 Merge branch 'github-234-repmgrd-no-schema' 2016-08-25 09:21:03 +09:00
Ian Barwick
8de84707d9 Always use PQstatus to check connection status
This addresses GitHib #234.
2016-08-25 08:35:47 +09:00
Ian Barwick
3ea61689eb repmgr.conf.sample: change db and user names in conninfo string
They now match the examples given in README.md
2016-08-23 14:11:04 +09:00
Ian Barwick
efb106f8a0 Pass ssh_options when executing a remote command.
This resolves GitHub #233.
2016-08-22 14:34:48 +09:00
Ian Barwick
5baec14a1e Add repmgr.conf setting 'barman_config'
Enables provision of a non-default Barman configuration file.
2016-08-18 15:06:06 +09:00
Ian Barwick
fe469fe188 Merge pull request #191 from gciolli/feature-barman-support
Add Barman support to repmgr standby clone
2016-08-18 15:04:46 +09:00
Ian Barwick
5a7ce552f0 "renumber" deprecated command line options without a short version
This will keep them out of the main list.
2016-08-17 14:51:32 +09:00
Ian Barwick
ef7bed1b3d repmgrd: refactor standby monitoring status query and code
This had grown somewhat complex with addition of handling for
various corner cases. Much of the work has now been delegated
to the query itself.
2016-08-16 19:15:58 +09:00
Ian Barwick
6bd1c6a36d Skip largely pointless master reconnection attempt.
Experimental - see notes in code.
2016-08-16 13:25:39 +09:00
Ian Barwick
9831cabd4d Minor refactoring of do_master_failover()
- rename some variables for clarity
- ensure all structures are initialised correctly
- update code comments
2016-08-16 11:23:59 +09:00
Ian Barwick
d244fb29d7 Update HISTORY 2016-08-15 13:15:55 +09:00
Ian Barwick
4a349f7224 repmgr: emit warning if a deprecated command line option is used 2016-08-15 12:27:55 +09:00
Ian Barwick
fb6109b3e6 Update README.md
Note default usage of `pg_basebackup --xlog-method=stream`.
2016-08-15 10:16:22 +09:00
Martín Marqués
b314f5aaf4 Merge pull request #225 from ryno83/patch-1
Update README.md
2016-08-12 19:52:25 +02:00
Renaud Fortier
7fc340a8e2 Update README.md
I think this will improve the readme.
2016-08-12 10:28:31 -04:00
Ian Barwick
e4c8bd981b Update HISTORY 2016-08-12 09:59:10 +09:00
Ian Barwick
a310417a49 Refactor standby monitoring query
Addresses GitHub #224
2016-08-11 17:28:16 +09:00
Ian Barwick
9a07686ceb When the output of a remote command isn't required, ensure it's consumed anyway
This fixes a regression introduced with commit 85f68e9f77

Also clean up some code made redundant by same.
2016-08-11 08:46:55 +09:00
Ian Barwick
45aa0724c4 Update HISTORY
Also remove code comment obsoleted by previous commit
2016-08-09 15:32:56 +09:00
Ian Barwick
a558e9379e Merge branch 'gciolli-fix_219'
PR #220 resolving GitHub #219
2016-08-09 15:29:59 +09:00
Gianni Ciolli
85f68e9f77 Only collect remote command output if the caller requires it
This addresses GitHub #216 and #167.
2016-08-09 14:14:52 +09:00
Ian Barwick
00e55c0672 Update HISTORY 2016-08-09 12:29:19 +09:00
Ian Barwick
84ab37c600 Improve handling of failover events when failover is set to manual
- prevent repmgrd from repeatedly executing the failover code
- add event notification 'standby_disconnect_manual'
- update documentation

This addresses GitHub #221.
2016-08-09 12:09:09 +09:00
Ian Barwick
6a198401db Fix repmgrd's command line help option parsing
As in commit d0c05e6f46, properly distinguish between
the command line option -? and getopt's unknown option marker '?'
2016-08-08 21:17:56 +09:00
Ian Barwick
cb78802027 repmgrd: prevent endless loops in failover with manual node
The LSN reported by the shared memory function defaults to "0/0"
(InvalidXLogRecPtr) - this indicates that the repmgrd on that node
hasn't been able to update it yet. However during failover several
places in the code assumed this is an error, which would cause
an endless loop waiting for updates which would never come.

To get around this without changing function definitions, we can
store an explicit message in the shared memory location field so the
caller can tell whether the other node hasn't yet updated the field,
or encountered situation which means it should not be considered
as a promotion candidate (which in most cases will be because
`failover` is set to `manual`.

Resolves GitHub #222.
2016-08-08 14:29:24 +09:00
Gianni Ciolli
48f637486d Now STANDBY SWITCHOVER and STANDBY FOLLOW log an event notification on
success and also on some failures, precisely those when it makes sense
or it is reasonably possible to do so.
2016-08-05 14:19:04 +02:00
Ian Barwick
73280a426b Update HISTORY 2016-08-05 16:43:52 +09:00
Ian Barwick
b8ee321d5f Merge branch 'gciolli-feature-better-auto-deb'
GitHub pull request #218
2016-08-05 16:42:27 +09:00
Gianni Ciolli
ccdc0f9871 Improved "repmgr-auto" Debian package
* Version set to 3.2dev

* Binaries are placed in PGBINDIR and then linked from /usr/bin,
  instead of being placed into /usr/bin directly. This is necessary
  for the switchover command, because it requires pg_rewind, which is
  placed in PGBINDIR too.
2016-08-05 09:34:32 +02:00
Gianni Ciolli
3bccd79510 Two bugfixes to Barman mode
- we forgot to recreate an empty pg_replslot;
- we parse tablespace info more safely.
2016-08-04 14:40:24 +02:00
Gianni Ciolli
a4ee10ca22 Rewording sentence in README 2016-08-04 14:40:24 +02:00
Gianni Ciolli
7ca9ff6d54 Reindenting do_standby_clone (no code change). 2016-08-04 14:40:24 +02:00
Gianni Ciolli
b660eb7988 Rename from pg_restore_command to restore_command
This follows the recent upstream change.
2016-08-04 14:40:23 +02:00
Gianni Ciolli
6a4546a7b3 Updated README to reflect the new barman-wal-restore.py script 2016-08-04 14:40:23 +02:00
Gianni Ciolli
2f529e20c1 Barman support, draft #1
TODO: we need to check what happens with configuration files placed in
non-standard locations.
2016-08-04 14:40:23 +02:00
Gianni Ciolli
9853581d12 Introduce a Cell List to store tablespace data 2016-08-04 14:40:23 +02:00
Gianni Ciolli
ecdae9671f Factor out tablespace metadata retrieval
The purpose of this commit is to prepare the terrain for non-default
tablespace support in the forthcoming Barman support.
2016-08-04 14:40:23 +02:00
Gianni Ciolli
1f3e937bbe Add local_command function to run commands locally 2016-08-04 14:40:23 +02:00
Ian Barwick
89aeccedc2 Various bugfixes and code documentation improvements 2016-08-04 12:31:24 +09:00
Ian Barwick
d9bda915bb Update documentation and --help output for witness register
This completes the implementation of GitHub #186
2016-08-04 10:36:26 +09:00
Ian Barwick
c565be4ab6 Improve database connection status checking 2016-08-04 10:36:21 +09:00
Ian Barwick
c26fd21351 Implement repmgr standby register command 2016-08-04 10:36:16 +09:00
Ian Barwick
6b57d0e680 Separate witness registration into do_witness_register() 2016-08-04 10:36:12 +09:00
Ian Barwick
6faf029c93 Add witness unregister command info in help output 2016-08-02 18:39:04 +09:00
Ian Barwick
c42437a4f2 standby/witness unregister - enable even if node isn't running
If the `--node` option is provided with the id of the node to unregister,
the action can be executed on any node.

This addresses GitHub #211.
2016-08-02 17:09:27 +09:00
Ian Barwick
d0c05e6f46 Clean up command line option handling and help output
- properly distinguish between the command line option -? and getopt's
  unknown option marker '?'
- remove deprecated command line options --initdb-no-pwprompt and
  -l/--local-port
- add witness command summary in help output
2016-08-02 14:40:13 +09:00
Ian Barwick
050f007cc2 Initial implementation of witness unregister 2016-08-02 12:22:54 +09:00
Ian Barwick
371d80ff35 Document repmgr cluster show --csv 2016-08-01 16:10:10 +09:00
Ian Barwick
e0a61afb7d Suppress connection error display in repmgr cluster show
This prevents connection error messages being mixed in
with `repmgr cluster show` output. Error message output can
still be enabled with the --verbose flag.

Fixes GitHub #215
2016-08-01 14:57:40 +09:00
Ian Barwick
bbc88ce05c Miscellaneous code cleanup and typo fixes 2016-07-28 16:32:07 +09:00
Ian Barwick
61e907cf70 Update README
Default log level is NOTICE, not INFO.
2016-07-27 12:11:00 +09:00
Ian Barwick
02668ee045 Parse the contents of the "pg_basebackup_options" parameter in repmgr.conf
This is to ensure that when repmgr executes pg_basebackup it doesn't
add any options which would conflict with user-supplied options.

This is related to GitHub #206, where the -S/--slot option has been
added for 9.6 - it's important to check this doesn't conflict with
-X/--xlog-method.

While we're at it, rename the ErrorList handling code to ItemList
etc. so we can use it for generic non-error-related lists.
2016-07-26 16:12:43 +09:00
Ian Barwick
36eb26f86d Mark some variables as static. 2016-07-26 11:40:26 +09:00
Ian Barwick
cbc2c7b3e6 From PostgreSQL 9.6, use pg_basebackup's -S/--slot option if appropriate
GitHub #206
2016-07-26 10:35:30 +09:00
Ian Barwick
8a28dadde4 Rename RECOVERY_FILE to RECOVERY_COMMAND_FILE
This is for consistency with the PostgreSQL source code (see:
src/backend/access/transam/xlog.c ), but as it's not exported
we need to define it ourselves anyway.
2016-07-26 09:19:52 +09:00
Ian Barwick
3eda7373ad Prevent duplicated parameters being passed to pg_basebackup
This was not a problem, but ugly.
2016-07-25 20:44:36 +09:00
Ian Barwick
34e574ac66 Update README
Add caveats for pg_rewind with 9.4 and 9.3 in particular, also fix typo.
2016-07-21 08:58:44 +09:00
Ian Barwick
e8fcc3d7a6 repmgr: set default user for -R/--remote-user 2016-07-18 14:30:30 +09:00
Ian Barwick
eba0f1d7ae Update README with note about using screen et al 2016-07-18 11:01:42 +09:00
Ian Barwick
db32565b36 Add notes about setting pg_bindir for Debian/Ubuntu-based distributions.
repmgr doesn't know about pg_ctlcluster.

Per GitHub query #196.
2016-07-15 16:00:31 +09:00
Ian Barwick
94befc3230 repmgr standby clone historically accepts a hostname as third parameter 2016-07-15 11:17:05 +09:00
Ian Barwick
340899f082 Update README
Add note about 2ndQuadrant RPM repository.
2016-07-13 09:55:01 +09:00
Ian Barwick
76681c0850 Update code comments 2016-07-12 10:56:31 +09:00
Ian Barwick
eebaef59a3 Remove unused error code ERR_BAD_PASSWORD 2016-07-12 09:36:50 +09:00
Ian Barwick
ddaaa28449 README: update error code list 2016-07-12 09:35:52 +09:00
Ian Barwick
e81bf869ec Update README with details about conninfo parameter handling
From 3.1.4 `repmgr` will behave like other PostgreSQL utilities
when handling database connection parameters, in particular
accepting a conninfo string and honouring libpq connection defaults.
2016-07-12 09:27:22 +09:00
Ian Barwick
fa62d715c2 Make more consistent use of conninfo parameters
Removed the existing keyword array which has a fixed, limited number
of parameters and replace it with a dynamic array which can be
used to store as many parameters as reported by libpq.
2016-07-11 15:24:53 +09:00
Ian Barwick
72af24e1d6 Add missing space when setting "application_name" 2016-07-08 09:46:59 +09:00
Ian Barwick
61d617ae93 Improve default host/dbname handling
repmgr disallows socket connections anyway (the whole point of providing
the host is to connect to a remote machine) so don't show that as
a fallback default in the -?/--help output.
2016-07-08 08:34:15 +09:00
Ian Barwick
319fba8b1f Enable a conninfo string to be passed to repmgr in the -d/--dbname parameter
This matches the behaviour of other PostgreSQL utilities such as pg_basebackup,
psql et al.

Note that unlike psql, but like pg_basebackup, repmgr does not accept a
"left-over" parameter as a conninfo string; this could be added later.

Parameters specified in the conninfo string will override any parameters
supplied correcly (e.g. `-d "host=foo"` will override `-h bar`).
2016-07-08 08:31:14 +09:00
Ian Barwick
c92ea1d057 Generate "primary_conninfo" using the primary connection's parameters
Having successfully connected to the primary, we can use the actual parameters
reported by libpq to create "primary_conninfo", rather than the limited
subset previously defined by repmgr. Assuming that the user can
pass a conninfo string to repmgr (see following commit), this makes it
possible to provide other connection parameters, e.g. related to
SSL usage.
2016-07-08 08:11:11 +09:00
Ian Barwick
b2ca6fd35e Remove now-superfluous wildcard in rmtree() call 2016-07-07 09:51:55 +09:00
Martin
c880187e89 Fix alignment and syntax 2016-07-06 15:52:58 -03:00
Jarkko Oranen
4724da41ad Add sample configuration for systemd support 2016-07-06 15:52:58 -03:00
Jarkko Oranen
d44885b330 Allow overriding start, stop and restart commands issued by repmgr
This commit introduces three new options:
  - start_command
  - stop_command
  - restart_command

If these are set, repmgr will issue the specified command instead
of the default pg_ctl commands
2016-07-06 15:52:58 -03:00
24 changed files with 6161 additions and 1626 deletions

12
FAQ.md
View File

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

66
HISTORY
View File

@@ -1,4 +1,68 @@
3.1.4 2016-07- 3.3 2016-12-27
repmgr: always log to STDERR even if log facility defined (Ian)
repmgr: add --log-to-file to log repmgr output to the defined
log facility (Ian)
repmgr: improve handling of command line parameter errors (Ian)
repmgr: add option --upstream-conninfo to explicitly set
'primary_conninfo' in recovery.conf (Ian)
repmgr: enable a standby to be registered which isn't running (Ian)
repmgr: enable `standby register --force` to update a node record
with cascaded downstream node records (Ian)
repmgr: add option `--no-conninfo-password` (Abhijit, Ian)
repmgr: add initial support for PostgreSQL 10.0 (Ian)
repmgr: escape values in primary_conninfo if needed (Ian)
3.2.1 2016-10-24
repmgr: require a valid repmgr cluster name unless -F/--force
supplied (Ian)
repmgr: check master server is registered with repmgr before
cloning (Ian)
repmgr: ensure data directory defaults to that of the source node (Ian)
repmgr: various fixes to Barman cloning mode (Gianni, Ian)
repmgr: fix `repmgr cluster crosscheck` output (Ian)
3.2 2016-10-05
repmgr: add support for cloning from a Barman backup (Gianni)
repmgr: add commands `standby matrix` and `standby crosscheck` (Gianni)
repmgr: suppress connection error display in `repmgr cluster show`
unless `--verbose` supplied (Ian)
repmgr: add commands `witness register` and `witness unregister` (Ian)
repmgr: enable `standby unregister` / `witness unregister` to be
executed for a node which is not running (Ian)
repmgr: remove deprecated command line options --initdb-no-pwprompt and
-l/--local-port (Ian)
repmgr: before cloning with pg_basebackup, check that sufficient free
walsenders are available (Ian)
repmgr: add option `--wait-sync` for `standby register` which causes
repmgr to wait for the registered node record to synchronise to
the standby (Ian)
repmgr: add option `--copy-external-config-files` for files outside
of the data directory (Ian)
repmgr: only require `wal_keep_segments` to be set in certain corner
cases (Ian)
repmgr: better support cloning from a node other than the one to
stream from (Ian)
repmgrd: add configuration options to override the default pg_ctl
commands (Jarkko Oranen, Ian)
repmgrd: don't start if node is inactive and failover=automatic (Ian)
packaging: improve "repmgr-auto" Debian package (Gianni)
3.1.5 2016-08-15
repmgrd: in a failover situation, prevent endless looping when
attempting to establish the status of a node with
`failover=manual` (Ian)
repmgrd: improve handling of failover events on standbys with
`failover=manual`, and create a new event notification
for this, `standby_disconnect_manual` (Ian)
repmgr: add further event notifications (Gianni)
repmgr: when executing `standby switchover`, don't collect remote
command output unless required (Gianni, Ian)
repmgrd: improve standby monitoring query (Ian, based on suggestion
from Álvaro)
repmgr: various command line handling improvements (Ian)
3.1.4 2016-07-12
repmgr: new configuration option for setting "restore_command" repmgr: new configuration option for setting "restore_command"
in the recovery.conf file generated by repmgr (Martín) in the recovery.conf file generated by repmgr (Martín)
repmgr: add --csv option to "repmgr cluster show" (Gianni) repmgr: add --csv option to "repmgr cluster show" (Gianni)

View File

@@ -5,7 +5,7 @@
HEADERS = $(wildcard *.h) HEADERS = $(wildcard *.h)
repmgrd_OBJS = dbutils.o config.o repmgrd.o log.o strutil.o repmgrd_OBJS = dbutils.o config.o repmgrd.o log.o strutil.o
repmgr_OBJS = dbutils.o check_dir.o config.o repmgr.o log.o strutil.o dirmod.o repmgr_OBJS = dbutils.o check_dir.o config.o repmgr.o log.o strutil.o dirmod.o compat.o
DATA = repmgr.sql uninstall_repmgr.sql DATA = repmgr.sql uninstall_repmgr.sql
@@ -87,10 +87,12 @@ PG_VERSION = $(shell pg_config --version | cut -d ' ' -f 2 | cut -d '.' -f 1,2)
REPMGR_VERSION = $(shell grep REPMGR_VERSION version.h | cut -d ' ' -f 3 | cut -d '"' -f 2) REPMGR_VERSION = $(shell grep REPMGR_VERSION version.h | cut -d ' ' -f 3 | cut -d '"' -f 2)
PKGLIBDIR = $(shell pg_config --pkglibdir) PKGLIBDIR = $(shell pg_config --pkglibdir)
SHAREDIR = $(shell pg_config --sharedir) SHAREDIR = $(shell pg_config --sharedir)
PGBINDIR = /usr/lib/postgresql/$(PG_VERSION)/bin
deb: repmgrd repmgr deb: repmgrd repmgr
mkdir -p ./debian/usr/bin mkdir -p ./debian/usr/bin ./debian$(PGBINDIR)
cp repmgrd repmgr ./debian/usr/bin/ cp repmgrd repmgr ./debian$(PGBINDIR)
ln -s ../..$(PGBINDIR)/repmgr ./debian/usr/bin/repmgr
mkdir -p ./debian$(SHAREDIR)/contrib/ mkdir -p ./debian$(SHAREDIR)/contrib/
cp sql/repmgr_funcs.sql ./debian$(SHAREDIR)/contrib/ cp sql/repmgr_funcs.sql ./debian$(SHAREDIR)/contrib/
cp sql/uninstall_repmgr_funcs.sql ./debian$(SHAREDIR)/contrib/ cp sql/uninstall_repmgr_funcs.sql ./debian$(SHAREDIR)/contrib/

729
README.md

File diff suppressed because it is too large Load Diff

111
compat.c Normal file
View File

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

29
compat.h Normal file
View File

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

368
config.c
View File

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

View File

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

194
dbutils.c
View File

@@ -1,5 +1,6 @@
/* /*
* dbutils.c - Database connection/management functions * dbutils.c - Database connection/management functions
*
* Copyright (C) 2ndQuadrant, 2010-2016 * Copyright (C) 2ndQuadrant, 2010-2016
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@@ -34,7 +35,7 @@ char repmgr_schema_quoted[MAXLEN] = "";
static int _get_node_record(PGconn *conn, char *cluster, char *sqlquery, t_node_info *node_info); static int _get_node_record(PGconn *conn, char *cluster, char *sqlquery, t_node_info *node_info);
PGconn * PGconn *
_establish_db_connection(const char *conninfo, const bool exit_on_error, const bool log_notice) _establish_db_connection(const char *conninfo, const bool exit_on_error, const bool log_notice, const bool verbose_only)
{ {
/* Make a connection to the database */ /* Make a connection to the database */
PGconn *conn = NULL; PGconn *conn = NULL;
@@ -50,15 +51,23 @@ _establish_db_connection(const char *conninfo, const bool exit_on_error, const b
/* Check to see that the backend connection was successfully made */ /* Check to see that the backend connection was successfully made */
if ((PQstatus(conn) != CONNECTION_OK)) if ((PQstatus(conn) != CONNECTION_OK))
{ {
if (log_notice) bool emit_log = true;
if (verbose_only == true && verbose_logging == false)
emit_log = false;
if (emit_log)
{ {
log_notice(_("connection to database failed: %s\n"), if (log_notice)
PQerrorMessage(conn)); {
} log_notice(_("connection to database failed: %s\n"),
else PQerrorMessage(conn));
{ }
log_err(_("connection to database failed: %s\n"), else
PQerrorMessage(conn)); {
log_err(_("connection to database failed: %s\n"),
PQerrorMessage(conn));
}
} }
if (exit_on_error) if (exit_on_error)
@@ -71,16 +80,35 @@ _establish_db_connection(const char *conninfo, const bool exit_on_error, const b
return conn; return conn;
} }
/*
* Establish a database connection, optionally exit on error
*/
PGconn * PGconn *
establish_db_connection(const char *conninfo, const bool exit_on_error) establish_db_connection(const char *conninfo, const bool exit_on_error)
{ {
return _establish_db_connection(conninfo, exit_on_error, false); return _establish_db_connection(conninfo, exit_on_error, false, false);
} }
/*
* Attempt to establish a database connection, never exit on error, only
* output error messages if --verbose option used
*/
PGconn * PGconn *
test_db_connection(const char *conninfo, const bool exit_on_error) establish_db_connection_quiet(const char *conninfo)
{ {
return _establish_db_connection(conninfo, exit_on_error, true); return _establish_db_connection(conninfo, false, false, true);
}
/*
* Attempt to establish a database connection, never exit on error,
* output connection error messages as NOTICE (useful when connection
* failure is expected)
*/
PGconn *
test_db_connection(const char *conninfo)
{
return _establish_db_connection(conninfo, false, true, false);
} }
@@ -186,7 +214,7 @@ check_cluster_schema(PGconn *conn)
char sqlquery[QUERY_STR_LEN]; char sqlquery[QUERY_STR_LEN];
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT 1 FROM pg_namespace WHERE nspname = '%s'", "SELECT 1 FROM pg_catalog.pg_namespace WHERE nspname = '%s'",
get_repmgr_schema()); get_repmgr_schema());
log_verbose(LOG_DEBUG, "check_cluster_schema(): %s\n", sqlquery); log_verbose(LOG_DEBUG, "check_cluster_schema(): %s\n", sqlquery);
@@ -251,7 +279,6 @@ is_pgup(PGconn *conn, int timeout)
/* Check the connection status twice in case it changes after reset */ /* Check the connection status twice in case it changes after reset */
bool twice = false; bool twice = false;
/* Check the connection status twice in case it changes after reset */
for (;;) for (;;)
{ {
if (PQstatus(conn) != CONNECTION_OK) if (PQstatus(conn) != CONNECTION_OK)
@@ -381,7 +408,7 @@ guc_set(PGconn *conn, const char *parameter, const char *op,
int retval = 1; int retval = 1;
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT true FROM pg_settings " "SELECT true FROM pg_catalog.pg_settings "
" WHERE name = '%s' AND setting %s '%s'", " WHERE name = '%s' AND setting %s '%s'",
parameter, op, value); parameter, op, value);
@@ -417,7 +444,7 @@ guc_set_typed(PGconn *conn, const char *parameter, const char *op,
int retval = 1; int retval = 1;
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT true FROM pg_settings " "SELECT true FROM pg_catalog.pg_settings "
" WHERE name = '%s' AND setting::%s %s '%s'::%s", " WHERE name = '%s' AND setting::%s %s '%s'::%s",
parameter, datatype, op, value, datatype); parameter, datatype, op, value, datatype);
@@ -449,7 +476,7 @@ get_cluster_size(PGconn *conn, char *size)
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT pg_catalog.pg_size_pretty(SUM(pg_catalog.pg_database_size(oid))::bigint) " "SELECT pg_catalog.pg_size_pretty(SUM(pg_catalog.pg_database_size(oid))::bigint) "
" FROM pg_database "); " FROM pg_catalog.pg_database ");
log_verbose(LOG_DEBUG, "get_cluster_size():\n%s\n", sqlquery); log_verbose(LOG_DEBUG, "get_cluster_size():\n%s\n", sqlquery);
@@ -476,11 +503,11 @@ get_pg_setting(PGconn *conn, const char *setting, char *output)
char sqlquery[QUERY_STR_LEN]; char sqlquery[QUERY_STR_LEN];
PGresult *res; PGresult *res;
int i; int i;
bool success = true; bool success = false;
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT name, setting " "SELECT name, setting "
" FROM pg_settings WHERE name = '%s'", " FROM pg_catalog.pg_settings WHERE name = '%s'",
setting); setting);
log_verbose(LOG_DEBUG, "get_pg_setting(): %s\n", sqlquery); log_verbose(LOG_DEBUG, "get_pg_setting(): %s\n", sqlquery);
@@ -917,7 +944,7 @@ get_repmgr_schema_quoted(PGconn *conn)
bool bool
create_replication_slot(PGconn *conn, char *slot_name, int server_version_num) create_replication_slot(PGconn *conn, char *slot_name, int server_version_num, PQExpBufferData *error_msg)
{ {
char sqlquery[QUERY_STR_LEN]; char sqlquery[QUERY_STR_LEN];
int query_res; int query_res;
@@ -936,8 +963,9 @@ create_replication_slot(PGconn *conn, char *slot_name, int server_version_num)
{ {
if (strcmp(slot_info.slot_type, "physical") != 0) if (strcmp(slot_info.slot_type, "physical") != 0)
{ {
log_err(_("Slot '%s' exists and is not a physical slot\n"), appendPQExpBuffer(error_msg,
slot_name); _("Slot '%s' exists and is not a physical slot\n"),
slot_name);
return false; return false;
} }
@@ -949,8 +977,9 @@ create_replication_slot(PGconn *conn, char *slot_name, int server_version_num)
return true; return true;
} }
log_err(_("Slot '%s' already exists as an active slot\n"), appendPQExpBuffer(error_msg,
slot_name); _("Slot '%s' already exists as an active slot\n"),
slot_name);
return false; return false;
} }
@@ -958,25 +987,26 @@ create_replication_slot(PGconn *conn, char *slot_name, int server_version_num)
if (server_version_num >= 90600) if (server_version_num >= 90600)
{ {
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT * FROM pg_create_physical_replication_slot('%s', TRUE)", "SELECT * FROM pg_catalog.pg_create_physical_replication_slot('%s', TRUE)",
slot_name); slot_name);
} }
else else
{ {
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT * FROM pg_create_physical_replication_slot('%s')", "SELECT * FROM pg_catalog.pg_create_physical_replication_slot('%s')",
slot_name); slot_name);
} }
log_debug(_("create_replication_slot(): Creating slot '%s' on primary\n"), slot_name); log_debug(_("create_replication_slot(): Creating slot '%s' on master\n"), slot_name);
log_verbose(LOG_DEBUG, "create_replication_slot():\n%s\n", sqlquery); log_verbose(LOG_DEBUG, "create_replication_slot():\n%s\n", sqlquery);
res = PQexec(conn, sqlquery); res = PQexec(conn, sqlquery);
if (!res || PQresultStatus(res) != PGRES_TUPLES_OK) if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
{ {
log_err(_("unable to create slot '%s' on the primary node: %s\n"), appendPQExpBuffer(error_msg,
slot_name, _("unable to create slot '%s' on the master node: %s\n"),
PQerrorMessage(conn)); slot_name,
PQerrorMessage(conn));
PQclear(res); PQclear(res);
return false; return false;
} }
@@ -994,7 +1024,7 @@ get_slot_record(PGconn *conn, char *slot_name, t_replication_slot *record)
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT slot_name, slot_type, active " "SELECT slot_name, slot_type, active "
" FROM pg_replication_slots " " FROM pg_catalog.pg_replication_slots "
" WHERE slot_name = '%s' ", " WHERE slot_name = '%s' ",
slot_name); slot_name);
@@ -1196,7 +1226,8 @@ witness_copy_node_records(PGconn *masterconn, PGconn *witnessconn, char *cluster
/* Get current records from primary */ /* Get current records from primary */
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT id, type, upstream_node_id, name, conninfo, priority, slot_name, active FROM %s.repl_nodes", "SELECT id, type, upstream_node_id, name, conninfo, priority, slot_name, active "
" FROM %s.repl_nodes",
get_repmgr_schema_quoted(masterconn)); get_repmgr_schema_quoted(masterconn));
log_verbose(LOG_DEBUG, "witness_copy_node_records():\n%s\n", sqlquery); log_verbose(LOG_DEBUG, "witness_copy_node_records():\n%s\n", sqlquery);
@@ -1310,7 +1341,8 @@ create_node_record(PGconn *conn, char *action, int node, char *type, int upstrea
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"INSERT INTO %s.repl_nodes " "INSERT INTO %s.repl_nodes "
" (id, type, upstream_node_id, cluster, " " (id, type, upstream_node_id, cluster, "
" name, conninfo, slot_name, priority, active) " " name, conninfo, slot_name, "
" priority, active) "
"VALUES (%i, '%s', %s, '%s', '%s', '%s', %s, %i, %s) ", "VALUES (%i, '%s', %s, '%s', '%s', '%s', %s, %i, %s) ",
get_repmgr_schema_quoted(conn), get_repmgr_schema_quoted(conn),
node, node,
@@ -1404,10 +1436,11 @@ create_event_record(PGconn *conn, t_configuration_options *options, int node_id,
bool success = true; bool success = true;
struct tm ts; struct tm ts;
/* Only attempt to write a record if a connection handle was provided. /*
Also check that the repmgr schema has been properly intialised - if * Only attempt to write a record if a connection handle was provided.
not it means no configuration file was provided, which can happen with * Also check that the repmgr schema has been properly initialised - if
e.g. `repmgr standby clone`, and we won't know which schema to write to. * not it means no configuration file was provided, which can happen with
* e.g. `repmgr standby clone`, and we won't know which schema to write to.
*/ */
if (conn != NULL && strcmp(repmgr_schema, DEFAULT_REPMGR_SCHEMA_PREFIX) != 0) if (conn != NULL && strcmp(repmgr_schema, DEFAULT_REPMGR_SCHEMA_PREFIX) != 0)
{ {
@@ -1456,7 +1489,6 @@ create_event_record(PGconn *conn, t_configuration_options *options, int node_id,
PQerrorMessage(conn)); PQerrorMessage(conn));
success = false; success = false;
} }
else else
{ {
@@ -1597,6 +1629,89 @@ create_event_record(PGconn *conn, t_configuration_options *options, int node_id,
} }
bool
update_node_record(PGconn *conn, char *action, int node, char *type, int upstream_node, char *cluster_name, char *node_name, char *conninfo, int priority, char *slot_name, bool active)
{
char sqlquery[QUERY_STR_LEN];
char upstream_node_id[MAXLEN];
char slot_name_buf[MAXLEN];
PGresult *res;
/* XXX this segment copied from create_node_record() */
if (upstream_node == NO_UPSTREAM_NODE)
{
/*
* No explicit upstream node id provided for standby - attempt to
* get primary node id
*/
if (strcmp(type, "standby") == 0)
{
int primary_node_id = get_master_node_id(conn, cluster_name);
maxlen_snprintf(upstream_node_id, "%i", primary_node_id);
}
else
{
maxlen_snprintf(upstream_node_id, "%s", "NULL");
}
}
else
{
maxlen_snprintf(upstream_node_id, "%i", upstream_node);
}
if (slot_name != NULL && slot_name[0])
{
maxlen_snprintf(slot_name_buf, "'%s'", slot_name);
}
else
{
maxlen_snprintf(slot_name_buf, "%s", "NULL");
}
/* XXX convert to placeholder query */
sqlquery_snprintf(sqlquery,
"UPDATE %s.repl_nodes SET "
" type = '%s', "
" upstream_node_id = %s, "
" cluster = '%s', "
" name = '%s', "
" conninfo = '%s', "
" slot_name = %s, "
" priority = %i, "
" active = %s "
" WHERE id = %i ",
get_repmgr_schema_quoted(conn),
type,
upstream_node_id,
cluster_name,
node_name,
conninfo,
slot_name_buf,
priority,
active == true ? "TRUE" : "FALSE",
node);
log_verbose(LOG_DEBUG, "update_node_record(): %s\n", sqlquery);
if (action != NULL)
{
log_verbose(LOG_DEBUG, "update_node_record(): action is \"%s\"\n", action);
}
res = PQexec(conn, sqlquery);
if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
{
log_err(_("Unable to update node record\n%s\n"),
PQerrorMessage(conn));
PQclear(res);
return false;
}
PQclear(res);
return true;
}
/* /*
* Update node record following change of status * Update node record following change of status
* (e.g. inactive primary converted to standby) * (e.g. inactive primary converted to standby)
@@ -1686,7 +1801,8 @@ get_node_record(PGconn *conn, char *cluster, int node_id, t_node_info *node_info
sqlquery_snprintf( sqlquery_snprintf(
sqlquery, sqlquery,
"SELECT id, type, upstream_node_id, name, conninfo, slot_name, priority, active" "SELECT id, type, upstream_node_id, name, conninfo, "
" slot_name, priority, active"
" FROM %s.repl_nodes " " FROM %s.repl_nodes "
" WHERE cluster = '%s' " " WHERE cluster = '%s' "
" AND id = %i", " AND id = %i",

View File

@@ -1,5 +1,6 @@
/* /*
* dbutils.h * dbutils.h
*
* Copyright (c) 2ndQuadrant, 2010-2016 * Copyright (c) 2ndQuadrant, 2010-2016
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@@ -21,6 +22,7 @@
#define _REPMGR_DBUTILS_H_ #define _REPMGR_DBUTILS_H_
#include "access/xlogdefs.h" #include "access/xlogdefs.h"
#include "pqexpbuffer.h"
#include "config.h" #include "config.h"
#include "strutil.h" #include "strutil.h"
@@ -77,15 +79,16 @@ typedef struct s_replication_slot
bool active; bool active;
} t_replication_slot; } t_replication_slot;
extern char repmgr_schema[MAXLEN];
PGconn *_establish_db_connection(const char *conninfo, PGconn *_establish_db_connection(const char *conninfo,
const bool exit_on_error, const bool exit_on_error,
const bool log_notice); const bool log_notice,
const bool verbose_only);
PGconn *establish_db_connection(const char *conninfo, PGconn *establish_db_connection(const char *conninfo,
const bool exit_on_error); const bool exit_on_error);
PGconn *test_db_connection(const char *conninfo, PGconn *establish_db_connection_quiet(const char *conninfo);
const bool exit_on_error); PGconn *test_db_connection(const char *conninfo);
PGconn *establish_db_connection_by_params(const char *keywords[], PGconn *establish_db_connection_by_params(const char *keywords[],
const char *values[], const char *values[],
const bool exit_on_error); const bool exit_on_error);
@@ -116,7 +119,7 @@ int wait_connection_availability(PGconn *conn, long long timeout);
bool cancel_query(PGconn *conn, int timeout); bool cancel_query(PGconn *conn, int timeout);
char *get_repmgr_schema(void); char *get_repmgr_schema(void);
char *get_repmgr_schema_quoted(PGconn *conn); char *get_repmgr_schema_quoted(PGconn *conn);
bool create_replication_slot(PGconn *conn, char *slot_name, int server_version_num); bool create_replication_slot(PGconn *conn, char *slot_name, int server_version_num, PQExpBufferData *error_msg);
int get_slot_record(PGconn *conn, char *slot_name, t_replication_slot *record); int get_slot_record(PGconn *conn, char *slot_name, t_replication_slot *record);
bool drop_replication_slot(PGconn *conn, char *slot_name); bool drop_replication_slot(PGconn *conn, char *slot_name);
bool start_backup(PGconn *conn, char *first_wal_segment, bool fast_checkpoint); bool start_backup(PGconn *conn, char *first_wal_segment, bool fast_checkpoint);
@@ -127,6 +130,7 @@ bool create_node_record(PGconn *conn, char *action, int node, char *type, int u
bool delete_node_record(PGconn *conn, int node, char *action); bool delete_node_record(PGconn *conn, int node, char *action);
int get_node_record(PGconn *conn, char *cluster, int node_id, t_node_info *node_info); int get_node_record(PGconn *conn, char *cluster, int node_id, t_node_info *node_info);
int get_node_record_by_name(PGconn *conn, char *cluster, const char *node_name, t_node_info *node_info); int get_node_record_by_name(PGconn *conn, char *cluster, const char *node_name, t_node_info *node_info);
bool update_node_record(PGconn *conn, char *action, int node, char *type, int upstream_node, char *cluster_name, char *node_name, char *conninfo, int priority, char *slot_name, bool active);
bool update_node_record_status(PGconn *conn, char *cluster_name, int this_node_id, char *type, int upstream_node_id, bool active); bool update_node_record_status(PGconn *conn, char *cluster_name, int this_node_id, char *type, int upstream_node_id, bool active);
bool update_node_record_set_upstream(PGconn *conn, char *cluster_name, int this_node_id, int new_upstream_node_id); bool update_node_record_set_upstream(PGconn *conn, char *cluster_name, int this_node_id, int new_upstream_node_id);
bool create_event_record(PGconn *conn, t_configuration_options *options, int node_id, char *event, bool successful, char *details); bool create_event_record(PGconn *conn, t_configuration_options *options, int node_id, char *event, bool successful, char *details);

View File

@@ -1,5 +1,5 @@
Package: repmgr-auto Package: repmgr-auto
Version: 3.1.3 Version: 3.2dev
Section: database Section: database
Priority: optional Priority: optional
Architecture: all Architecture: all

View File

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

View File

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

View File

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

44
log.c
View File

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

8
log.h
View File

@@ -25,6 +25,9 @@
#define REPMGR_SYSLOG 1 #define REPMGR_SYSLOG 1
#define REPMGR_STDERR 2 #define REPMGR_STDERR 2
#define OM_COMMAND_LINE 1
#define OM_DAEMON 2
extern void extern void
stderr_log_with_level(const char *level_name, int level, const char *fmt,...) stderr_log_with_level(const char *level_name, int level, const char *fmt,...)
__attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 4))); __attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 4)));
@@ -130,5 +133,8 @@ __attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
extern int log_type; extern int log_type;
extern int log_level; extern int log_level;
extern int verbose_logging;
extern int terse_logging;
extern int logger_output_mode;
#endif #endif /* _REPMGR_LOG_H_ */

5147
repmgr.c

File diff suppressed because it is too large Load Diff

View File

@@ -31,7 +31,7 @@
# #
# https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING # https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
# #
#conninfo='host=192.168.204.104 dbname=repmgr_db user=repmgr_usr' #conninfo='host=192.168.204.104 dbname=repmgr user=repmgr'
# #
# If repmgrd is in use, consider explicitly setting `connect_timeout` in the # If repmgrd is in use, consider explicitly setting `connect_timeout` in the
# conninfo string to determine the length of time which elapses before # conninfo string to determine the length of time which elapses before
@@ -66,6 +66,12 @@
# (default: NOTICE) # (default: NOTICE)
#loglevel=NOTICE #loglevel=NOTICE
# Note that logging facility settings will only apply to `repmgrd` by default;
# `repmgr` will always write to STDERR unless the switch `--log-to-file` is
# supplied, in which case it will log to the same destination as `repmgrd`.
# This is mainly intended for those cases when `repmgr` is executed directly
# by `repmgrd`.
# Logging facility: possible values are STDERR or - for Syslog integration - one of LOCAL0, LOCAL1, ..., LOCAL7, USER # Logging facility: possible values are STDERR or - for Syslog integration - one of LOCAL0, LOCAL1, ..., LOCAL7, USER
# (default: STDERR) # (default: STDERR)
#logfacility=STDERR #logfacility=STDERR
@@ -74,6 +80,12 @@
# #
#logfile='/var/log/repmgr/repmgr.log' #logfile='/var/log/repmgr/repmgr.log'
# By default only repmgrd log output will be written to a file,
# if defined in "logfile"
# enable this to restore old behaviour where output from the repmgr
# client will be written to the logfile too
#log_repmgr_to_file = 0
# event notifications can be passed to an arbitrary external program # event notifications can be passed to an arbitrary external program
# together with the following parameters: # together with the following parameters:
# #
@@ -100,6 +112,34 @@
# path to PostgreSQL binary directory (location of pg_ctl, pg_basebackup etc.) # path to PostgreSQL binary directory (location of pg_ctl, pg_basebackup etc.)
# (if not provided, defaults to system $PATH) # (if not provided, defaults to system $PATH)
#pg_bindir=/usr/bin/ #pg_bindir=/usr/bin/
#
# Debian/Ubuntu users: you will probably need to set this to the directory
# where `pg_ctl` is located, e.g. /usr/lib/postgresql/9.5/bin/
# service control commands
#
# repmgr provides options to override the default pg_ctl commands
# used to stop, start, restart, reload and promote the PostgreSQL cluster
#
# NOTE: These commands must be runnable on remote nodes as well for switchover
# to function correctly.
#
# If you use sudo, the user repmgr runs as (usually 'postgres') must have
# passwordless sudo access to execute the command
#
# For example, to use systemd, you may use the following configuration:
#
# # this is required when running sudo over ssh without -t:
# Defaults:postgres !requiretty
# postgres ALL = NOPASSWD: /usr/bin/systemctl stop postgresql-9.5, \
# /usr/bin/systemctl start postgresql-9.5, \
# /usr/bin/systemctl restart postgresql-9.5
#
# service_start_command = systemctl start postgresql-9.5
# service_stop_command = systemctl stop postgresql-9.5
# service_restart_command = systemctl restart postgresql-9.5
# service_reload_command = pg_ctlcluster 9.5 main reload
# service_promote_command = pg_ctlcluster 9.5 main promote
# external command options # external command options
@@ -132,9 +172,11 @@
# These settings are only applied when repmgrd is running. Values shown # These settings are only applied when repmgrd is running. Values shown
# are defaults. # are defaults.
# Number of seconds to wait for a response from the primary server before # monitoring interval in seconds; default is 2
# deciding it has failed. #monitor_interval_secs=2
# Maximum number of seconds to wait for a response from the primary server
# before deciding it has failed.
#master_response_timeout=60 #master_response_timeout=60
# Number of attempts at what interval (in seconds) to try and # Number of attempts at what interval (in seconds) to try and
@@ -144,16 +186,21 @@
#reconnect_interval=10 #reconnect_interval=10
# Autofailover options # Autofailover options
#failover=manual # one of 'automatic', 'manual' #failover=manual # one of 'automatic', 'manual' (default: manual)
# (default: manual) # defines the action to take in the event of upstream failure
#priority=100 # a value of zero or less prevents the node being promoted to primary #
# 'automatic': repmgrd will automatically attempt to promote the
# node or follow the new upstream node
# 'manual': repmgrd will take no action and the mode will require
# manual attention to reattach it to replication
#priority=100 # indicate a preferred priorty for promoting nodes
# a value of zero or less prevents the node being promoted to primary
# (default: 100) # (default: 100)
#promote_command='repmgr standby promote -f /path/to/repmgr.conf' #promote_command='repmgr standby promote -f /path/to/repmgr.conf'
#follow_command='repmgr standby follow -f /path/to/repmgr.conf -W' #follow_command='repmgr standby follow -f /path/to/repmgr.conf -W'
# monitoring interval in seconds; default is 2
#monitor_interval_secs=2
# change wait time for primary; before we bail out and exit when the primary # change wait time for primary; before we bail out and exit when the primary
# disappears, we wait 'reconnect_attempts' * 'retry_promote_interval_secs' # disappears, we wait 'reconnect_attempts' * 'retry_promote_interval_secs'
# seconds; by default this would be half an hour, as 'retry_promote_interval_secs' # seconds; by default this would be half an hour, as 'retry_promote_interval_secs'

195
repmgr.h
View File

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

View File

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

484
repmgrd.c
View File

@@ -1,5 +1,6 @@
/* /*
* repmgrd.c - Replication manager daemon * repmgrd.c - Replication manager daemon
*
* Copyright (C) 2ndQuadrant, 2010-2016 * 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
@@ -41,7 +42,10 @@
#include "access/xlogdefs.h" #include "access/xlogdefs.h"
#include "pqexpbuffer.h" #include "pqexpbuffer.h"
/* Message strings passed in repmgrSharedState->location */
#define PASSIVE_NODE "PASSIVE_NODE"
#define LSN_QUERY_ERROR "LSN_QUERY_ERROR"
/* Local info */ /* Local info */
t_configuration_options local_options = T_CONFIGURATION_OPTIONS_INITIALIZER; t_configuration_options local_options = T_CONFIGURATION_OPTIONS_INITIALIZER;
@@ -59,6 +63,13 @@ t_node_info node_info;
bool failover_done = false; bool failover_done = false;
/*
* when `failover=manual`, and the upstream server has gone away,
* this flag is set to indicate we should connect to whatever the
* current master is to update monitoring information
*/
bool manual_mode_upstream_disconnected = false;
char *pid_file = NULL; char *pid_file = NULL;
static void help(void); static void help(void);
@@ -100,17 +111,15 @@ static void check_and_create_pid_file(const char *pid_file);
static void static void
close_connections() close_connections()
{ {
if (master_conn != NULL && PQisBusy(master_conn) == 1) if (PQstatus(master_conn) == CONNECTION_OK && PQisBusy(master_conn) == 1)
cancel_query(master_conn, local_options.master_response_timeout); cancel_query(master_conn, local_options.master_response_timeout);
if (my_local_conn != NULL)
if (PQstatus(my_local_conn) == CONNECTION_OK)
PQfinish(my_local_conn); PQfinish(my_local_conn);
if (master_conn != NULL && master_conn != my_local_conn) if (PQstatus(master_conn) == CONNECTION_OK)
PQfinish(master_conn); PQfinish(master_conn);
master_conn = NULL;
my_local_conn = NULL;
} }
@@ -124,7 +133,7 @@ main(int argc, char **argv)
{"monitoring-history", no_argument, NULL, 'm'}, {"monitoring-history", no_argument, NULL, 'm'},
{"daemonize", no_argument, NULL, 'd'}, {"daemonize", no_argument, NULL, 'd'},
{"pid-file", required_argument, NULL, 'p'}, {"pid-file", required_argument, NULL, 'p'},
{"help", no_argument, NULL, '?'}, {"help", no_argument, NULL, OPT_HELP},
{"version", no_argument, NULL, 'V'}, {"version", no_argument, NULL, 'V'},
{NULL, 0, NULL, 0} {NULL, 0, NULL, 0}
}; };
@@ -158,6 +167,23 @@ main(int argc, char **argv)
{ {
switch (c) switch (c)
{ {
case '?':
/* Actual help option given */
if (strcmp(argv[optind - 1], "-?") == 0)
{
help();
exit(SUCCESS);
}
/* unknown option reported by getopt */
else
goto unknown_option;
break;
case OPT_HELP:
help();
exit(SUCCESS);
case 'V':
printf("%s %s (PostgreSQL %s)\n", progname(), REPMGR_VERSION, PG_VERSION);
exit(SUCCESS);
case 'f': case 'f':
config_file = optarg; config_file = optarg;
break; break;
@@ -173,18 +199,21 @@ main(int argc, char **argv)
case 'p': case 'p':
pid_file = optarg; pid_file = optarg;
break; break;
case '?':
help();
exit(SUCCESS);
case 'V':
printf("%s %s (PostgreSQL %s)\n", progname(), REPMGR_VERSION, PG_VERSION);
exit(SUCCESS);
default: default:
unknown_option:
usage(); usage();
exit(ERR_BAD_CONFIG); exit(ERR_BAD_CONFIG);
} }
} }
/*
* Tell the logger we're a daemon - this will ensure any output logged
* before the logger is initialized will be formatted correctly
*/
logger_output_mode = OM_DAEMON;
/* /*
* Parse the configuration file, if provided. If no configuration file * Parse the configuration file, if provided. If no configuration file
* was provided, or one was but was incomplete, parse_config() will * was provided, or one was but was incomplete, parse_config() will
@@ -225,6 +254,7 @@ main(int argc, char **argv)
} }
logger_init(&local_options, progname()); logger_init(&local_options, progname());
if (verbose) if (verbose)
logger_set_verbose(); logger_set_verbose();
@@ -289,10 +319,46 @@ main(int argc, char **argv)
log_debug("node id is %i, upstream is %i\n", node_info.node_id, node_info.upstream_node_id); log_debug("node id is %i, upstream is %i\n", node_info.node_id, node_info.upstream_node_id);
/*
* Check if node record is active - if not, and `failover=automatic`, the node
* won't be considered as a promotion candidate; this often happens when
* a failed primary is recloned and the node was not re-registered, giving
* the impression failover capability is there when it's not. In this case
* abort with an error and a hint about registering.
*
* If `failover=manual`, repmgrd can continue to passively monitor the node, but
* we should nevertheless issue a warning and the same hint.
*/
if (node_info.active == false)
{
char *hint = "Check that 'repmgr (master|standby) register' was executed for this node";
switch (local_options.failover)
{
case AUTOMATIC_FAILOVER:
log_err(_("This node is marked as inactive and cannot be used for failover\n"));
log_hint(_("%s\n"), hint);
terminate(ERR_BAD_CONFIG);
case MANUAL_FAILOVER:
log_warning(_("This node is marked as inactive and will be passively monitored only\n"));
log_hint(_("%s\n"), hint);
break;
default:
/* This should never happen */
log_err(_("Unknown failover mode %i\n"), local_options.failover);
terminate(ERR_BAD_CONFIG);
}
}
/* /*
* MAIN LOOP This loops cycles at startup and once per failover and * MAIN LOOP This loops cycles at startup and once per failover and
* Requisites: - my_local_conn needs to be already setted with an active * Requisites:
* connection - no master connection * - my_local_conn must have an active connection to the monitored node
* - master_conn must not be open
*/ */
do do
{ {
@@ -404,7 +470,7 @@ main(int argc, char **argv)
local_options.cluster_name, local_options.cluster_name,
&master_options.node, NULL); &master_options.node, NULL);
if (master_conn == NULL) if (PQstatus(master_conn) != CONNECTION_OK)
{ {
PQExpBufferData errmsg; PQExpBufferData errmsg;
initPQExpBuffer(&errmsg); initPQExpBuffer(&errmsg);
@@ -433,6 +499,7 @@ main(int argc, char **argv)
my_local_conn = establish_db_connection(local_options.conninfo, true); my_local_conn = establish_db_connection(local_options.conninfo, true);
update_registration(); update_registration();
} }
/* Log startup event */ /* Log startup event */
if (startup_event_logged == false) if (startup_event_logged == false)
{ {
@@ -588,15 +655,15 @@ witness_monitor(void)
} }
else else
{ {
log_debug(_("new master found with node ID: %i\n"), master_options.node); log_info(_("new master found with node ID: %i\n"), master_options.node);
connection_ok = true; connection_ok = true;
/* /*
* Update the repl_nodes table from the new master to reflect the changed * Update the repl_nodes table from the new master to reflect the changed
* node configuration * node configuration
* *
* XXX it would be neat to be able to handle this with e.g. table-based * It would be neat to be able to handle this with e.g. table-based
* logical replication * logical replication if available in core
*/ */
witness_copy_node_records(master_conn, my_local_conn, local_options.cluster_name); witness_copy_node_records(master_conn, my_local_conn, local_options.cluster_name);
@@ -639,7 +706,7 @@ witness_monitor(void)
local_options.master_response_timeout) != 1) local_options.master_response_timeout) != 1)
return; return;
/* Get local xlog info */ /* Get timestamp for monitoring update */
sqlquery_snprintf(sqlquery, "SELECT CURRENT_TIMESTAMP"); sqlquery_snprintf(sqlquery, "SELECT CURRENT_TIMESTAMP");
res = PQexec(my_local_conn, sqlquery); res = PQexec(my_local_conn, sqlquery);
@@ -692,13 +759,14 @@ static void
standby_monitor(void) standby_monitor(void)
{ {
PGresult *res; PGresult *res;
char sqlquery[QUERY_STR_LEN];
char monitor_standby_timestamp[MAXLEN]; char monitor_standby_timestamp[MAXLEN];
char last_wal_primary_location[MAXLEN]; char last_wal_primary_location[MAXLEN];
char last_xlog_receive_location[MAXLEN]; char last_xlog_receive_location[MAXLEN];
char last_xlog_replay_location[MAXLEN]; char last_xlog_replay_location[MAXLEN];
char last_xact_replay_timestamp[MAXLEN]; char last_xact_replay_timestamp[MAXLEN];
bool last_xlog_receive_location_gte_replayed; bool receiving_streamed_wal = true;
char sqlquery[QUERY_STR_LEN];
XLogRecPtr lsn_master_current_xlog_location; XLogRecPtr lsn_master_current_xlog_location;
XLogRecPtr lsn_last_xlog_receive_location; XLogRecPtr lsn_last_xlog_receive_location;
@@ -714,12 +782,12 @@ standby_monitor(void)
PGconn *upstream_conn; PGconn *upstream_conn;
char upstream_conninfo[MAXCONNINFO]; char upstream_conninfo[MAXCONNINFO];
int upstream_node_id; int upstream_node_id;
t_node_info upstream_node;
int active_master_id; int active_master_id;
const char *upstream_node_type = NULL; const char *upstream_node_type = NULL;
bool receiving_streamed_wal = true;
/* /*
* Verify that the local node is still available - if not there's * Verify that the local node is still available - if not there's
* no point in doing much else anyway * no point in doing much else anyway
@@ -741,52 +809,91 @@ standby_monitor(void)
goto continue_monitoring_standby; goto continue_monitoring_standby;
} }
upstream_conn = get_upstream_connection(my_local_conn, /*
local_options.cluster_name, * Standby has `failover` set to manual and is disconnected from
local_options.node, * replication following a prior upstream node failure - we'll
&upstream_node_id, * find the master to be able to write monitoring information, if
upstream_conninfo); * required
*/
if (manual_mode_upstream_disconnected == true)
{
upstream_conn = get_master_connection(my_local_conn,
local_options.cluster_name,
&upstream_node_id,
upstream_conninfo);
upstream_node_type = "master";
}
else
{
upstream_conn = get_upstream_connection(my_local_conn,
local_options.cluster_name,
local_options.node,
&upstream_node_id,
upstream_conninfo);
upstream_node_type = (upstream_node_id == master_options.node) upstream_node_type = (upstream_node_id == master_options.node)
? "master" ? "master"
: "upstream"; : "upstream";
}
/* /*
* Check that the upstream node is still available * Check that the upstream node is still available
* If not, initiate failover process * If not, initiate failover process
*/ *
check_connection(&upstream_conn, upstream_node_type, upstream_conninfo);
/*
* This takes up to local_options.reconnect_attempts * * This takes up to local_options.reconnect_attempts *
* local_options.reconnect_interval seconds * local_options.reconnect_interval seconds
*/ */
check_connection(&upstream_conn, upstream_node_type, upstream_conninfo);
if (PQstatus(upstream_conn) != CONNECTION_OK) if (PQstatus(upstream_conn) != CONNECTION_OK)
{ {
int previous_master_node_id = master_options.node;
PQfinish(upstream_conn); PQfinish(upstream_conn);
upstream_conn = NULL; upstream_conn = NULL;
/*
* When `failover=manual`, no actual failover will be performed, instead
* the following happens:
* - find the new master
* - create an event notification `standby_disconnect_manual`
* - set a flag to indicate we're disconnected from replication,
*/
if (local_options.failover == MANUAL_FAILOVER) if (local_options.failover == MANUAL_FAILOVER)
{ {
log_err(_("Unable to reconnect to %s. Now checking if another node has been promoted.\n"), upstream_node_type); log_err(_("Unable to reconnect to %s. Now checking if another node has been promoted.\n"), upstream_node_type);
/*
* Set the location string in shared memory to indicate to other
* repmgrd instances that we're *not* a promotion candidate and
* that other repmgrd instance should not expect location updates
* from us
*/
update_shared_memory(PASSIVE_NODE);
for (connection_retries = 0; connection_retries < local_options.reconnect_attempts; connection_retries++) for (connection_retries = 0; connection_retries < local_options.reconnect_attempts; connection_retries++)
{ {
master_conn = get_master_connection(my_local_conn, master_conn = get_master_connection(my_local_conn,
local_options.cluster_name, &master_options.node, NULL); local_options.cluster_name, &master_options.node, NULL);
if (PQstatus(master_conn) == CONNECTION_OK) if (PQstatus(master_conn) == CONNECTION_OK)
{ {
/* /*
* Connected, we can continue the process so break the * Connected, we can continue the process so break the
* loop * loop
*/ */
log_err(_("connected to node %d, continuing monitoring.\n"), log_notice(_("connected to node %d, continuing monitoring.\n"),
master_options.node); master_options.node);
break; break;
} }
else else
{ {
/*
* XXX this is the only place where `retry_promote_interval_secs`
* is used - this parameter should be renamed or possibly be replaced
*/
log_err( log_err(
_("no new master found, waiting %i seconds before retry...\n"), _("no new master found, waiting %i seconds before retry...\n"),
local_options.retry_promote_interval_secs local_options.retry_promote_interval_secs
@@ -816,6 +923,36 @@ standby_monitor(void)
terminate(ERR_DB_CON); terminate(ERR_DB_CON);
} }
/*
* connected to a master - is it the same as the former upstream?
* if not:
* - create event standby_disconnect
* - set global "disconnected_manual_standby"
*/
if (previous_master_node_id != master_options.node)
{
PQExpBufferData errmsg;
initPQExpBuffer(&errmsg);
appendPQExpBuffer(&errmsg,
_("node %i is in manual failover mode and is now disconnected from replication"),
local_options.node);
log_verbose(LOG_DEBUG, "old master: %i; current: %i\n", previous_master_node_id, master_options.node);
manual_mode_upstream_disconnected = true;
create_event_record(master_conn,
&local_options,
local_options.node,
"standby_disconnect_manual",
/* here "true" indicates the action has occurred as expected */
true,
errmsg.data);
}
} }
else if (local_options.failover == AUTOMATIC_FAILOVER) else if (local_options.failover == AUTOMATIC_FAILOVER)
{ {
@@ -826,6 +963,8 @@ standby_monitor(void)
* Failover handling is handled differently depending on whether * Failover handling is handled differently depending on whether
* the failed node is the master or a cascading standby * the failed node is the master or a cascading standby
*/ */
t_node_info upstream_node;
upstream_node = get_node_info(my_local_conn, local_options.cluster_name, upstream_node_id); upstream_node = get_node_info(my_local_conn, local_options.cluster_name, upstream_node_id);
if (upstream_node.type == MASTER) if (upstream_node.type == MASTER)
@@ -883,8 +1022,8 @@ standby_monitor(void)
* *
* We should log a message so the user knows of the situation at hand. * 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 * XXX add event notification
*/ */
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(_("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")); log_err(_("Check your cluster and manually fix any anomaly.\n"));
@@ -916,8 +1055,8 @@ standby_monitor(void)
* the stream. If we set the local standby node as failed and it's now running * 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. * and receiving replication data, we should activate it again.
*/ */
set_local_node_status(); set_local_node_status();
log_info(_("standby connection recovered!\n")); 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 */
@@ -929,8 +1068,6 @@ standby_monitor(void)
* from the upstream node to write monitoring information * from the upstream node to write monitoring information
*/ */
upstream_node = get_node_info(my_local_conn, local_options.cluster_name, upstream_node_id);
sprintf(sqlquery, sprintf(sqlquery,
"SELECT id " "SELECT id "
" FROM %s.repl_nodes " " FROM %s.repl_nodes "
@@ -982,13 +1119,28 @@ standby_monitor(void)
if (wait_connection_availability(master_conn, local_options.master_response_timeout) != 1) if (wait_connection_availability(master_conn, local_options.master_response_timeout) != 1)
return; return;
/* Get local xlog info */ /* Get local xlog info
*
* If receive_location is NULL, we're in archive recovery and not streaming WAL
* If receive_location is less than replay location, we were streaming WAL but are
* somehow disconnected and evidently in archive recovery
*/
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT CURRENT_TIMESTAMP, " " SELECT ts, "
"pg_catalog.pg_last_xlog_receive_location(), " " CASE WHEN (receive_location IS NULL OR receive_location < replay_location) "
"pg_catalog.pg_last_xlog_replay_location(), " " THEN replay_location "
"pg_catalog.pg_last_xact_replay_timestamp(), " " ELSE receive_location"
"pg_catalog.pg_last_xlog_receive_location() >= pg_catalog.pg_last_xlog_replay_location()"); " END AS receive_location,"
" replay_location, "
" replay_timestamp, "
" COALESCE(receive_location, '0/0') >= replay_location AS receiving_streamed_wal "
" FROM (SELECT CURRENT_TIMESTAMP AS ts, "
" pg_catalog.pg_last_xlog_receive_location() AS receive_location, "
" pg_catalog.pg_last_xlog_replay_location() AS replay_location, "
" pg_catalog.pg_last_xact_replay_timestamp() AS replay_timestamp "
" ) q ");
res = PQexec(my_local_conn, sqlquery); res = PQexec(my_local_conn, sqlquery);
if (PQresultStatus(res) != PGRES_TUPLES_OK) if (PQresultStatus(res) != PGRES_TUPLES_OK)
@@ -1004,40 +1156,17 @@ standby_monitor(void)
strncpy(last_xlog_replay_location, PQgetvalue(res, 0, 2), MAXLEN); strncpy(last_xlog_replay_location, PQgetvalue(res, 0, 2), MAXLEN);
strncpy(last_xact_replay_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) receiving_streamed_wal = (strcmp(PQgetvalue(res, 0, 4), "t") == 0)
? true ? true
: false; : false;
/* if (receiving_streamed_wal == false)
* If pg_last_xlog_receive_location is NULL, this means we're in archive
* recovery and will need to calculate lag based on pg_last_xlog_replay_location
*/
/*
* Replayed WAL is greater than received streamed WAL
*/
if (PQgetisnull(res, 0, 1))
{ {
receiving_streamed_wal = false; log_verbose(LOG_DEBUG, _("standby %i not connected to streaming replication"), local_options.node);
} }
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 (receiving_streamed_wal == true && last_xlog_receive_location_gte_replayed == false)
{
log_verbose(LOG_WARNING,
"Replayed WAL newer than received WAL - is this standby connected to its upstream?\n");
}
/* /*
* Get master xlog position * Get master xlog position
* *
@@ -1059,23 +1188,9 @@ standby_monitor(void)
lsn_master_current_xlog_location = lsn_to_xlogrecptr(last_wal_primary_location, NULL); lsn_master_current_xlog_location = lsn_to_xlogrecptr(last_wal_primary_location, NULL);
lsn_last_xlog_replay_location = lsn_to_xlogrecptr(last_xlog_replay_location, NULL); lsn_last_xlog_replay_location = lsn_to_xlogrecptr(last_xlog_replay_location, NULL);
lsn_last_xlog_receive_location = lsn_to_xlogrecptr(last_xlog_receive_location, NULL);
/* Calculate apply lag */ apply_lag = (long long unsigned int)lsn_last_xlog_receive_location - lsn_last_xlog_replay_location;
if (last_xlog_receive_location_gte_replayed == false)
{
/*
* We're not receiving streaming WAL - in this case the receive location
* equals the last replayed location
*/
apply_lag = 0;
strncpy(last_xlog_receive_location, last_xlog_replay_location, MAXLEN);
lsn_last_xlog_receive_location = lsn_to_xlogrecptr(last_xlog_replay_location, NULL);
}
else
{
apply_lag = (long long unsigned int)lsn_last_xlog_receive_location - lsn_last_xlog_replay_location;
lsn_last_xlog_receive_location = lsn_to_xlogrecptr(last_xlog_receive_location, NULL);
}
/* Calculate replication lag */ /* Calculate replication lag */
if (lsn_master_current_xlog_location >= lsn_last_xlog_receive_location) if (lsn_master_current_xlog_location >= lsn_last_xlog_receive_location)
@@ -1146,7 +1261,7 @@ do_master_failover(void)
PGresult *res; PGresult *res;
char sqlquery[QUERY_STR_LEN]; char sqlquery[QUERY_STR_LEN];
int total_nodes = 0; int total_active_nodes = 0;
int visible_nodes = 0; int visible_nodes = 0;
int ready_nodes = 0; int ready_nodes = 0;
@@ -1158,8 +1273,6 @@ do_master_failover(void)
XLogRecPtr xlog_recptr; XLogRecPtr xlog_recptr;
bool lsn_format_ok; bool lsn_format_ok;
char last_xlog_replay_location[MAXLEN];
PGconn *node_conn = NULL; PGconn *node_conn = NULL;
/* /*
@@ -1179,7 +1292,7 @@ do_master_failover(void)
"SELECT id, conninfo, type, upstream_node_id " "SELECT id, conninfo, type, upstream_node_id "
" FROM %s.repl_nodes " " FROM %s.repl_nodes "
" WHERE cluster = '%s' " " WHERE cluster = '%s' "
" AND active IS TRUE " " AND active IS TRUE "
" AND priority > 0 " " AND priority > 0 "
" ORDER BY priority DESC, id " " ORDER BY priority DESC, id "
" LIMIT %i ", " LIMIT %i ",
@@ -1195,32 +1308,25 @@ do_master_failover(void)
terminate(ERR_DB_QUERY); terminate(ERR_DB_QUERY);
} }
/* total_active_nodes = PQntuples(res);
* total nodes that are registered log_debug(_("%d active nodes registered\n"), total_active_nodes);
*/
total_nodes = PQntuples(res);
log_debug(_("%d active nodes registered\n"), total_nodes);
/* /*
* Build an array with the nodes and indicate which ones are visible and * Build an array with the nodes and indicate which ones are visible and
* ready * ready
*/ */
for (i = 0; i < total_nodes; i++) for (i = 0; i < total_active_nodes; i++)
{ {
char node_type[MAXLEN];
nodes[i] = (t_node_info) T_NODE_INFO_INITIALIZER;
nodes[i].node_id = atoi(PQgetvalue(res, i, 0)); nodes[i].node_id = atoi(PQgetvalue(res, i, 0));
strncpy(nodes[i].conninfo_str, PQgetvalue(res, i, 1), MAXCONNINFO); strncpy(nodes[i].conninfo_str, PQgetvalue(res, i, 1), MAXCONNINFO);
strncpy(node_type, PQgetvalue(res, i, 2), MAXLEN);
nodes[i].type = parse_node_type(PQgetvalue(res, i, 2)); nodes[i].type = parse_node_type(node_type);
/* Copy details of the failed node */
/* XXX only node_id is actually used later */
if (nodes[i].type == MASTER)
{
failed_master.node_id = nodes[i].node_id;
failed_master.xlog_location = nodes[i].xlog_location;
failed_master.is_ready = nodes[i].is_ready;
}
nodes[i].upstream_node_id = atoi(PQgetvalue(res, i, 3)); nodes[i].upstream_node_id = atoi(PQgetvalue(res, i, 3));
@@ -1231,11 +1337,42 @@ do_master_failover(void)
nodes[i].is_visible = false; nodes[i].is_visible = false;
nodes[i].is_ready = false; nodes[i].is_ready = false;
nodes[i].xlog_location = InvalidXLogRecPtr; log_debug(_("node=%i conninfo=\"%s\" type=%s\n"),
nodes[i].node_id,
nodes[i].conninfo_str,
node_type);
log_debug(_("node=%d conninfo=\"%s\" type=%s\n"), /* Copy details of the failed master node */
nodes[i].node_id, nodes[i].conninfo_str, if (nodes[i].type == MASTER)
PQgetvalue(res, i, 2)); {
/* XXX only node_id is currently used */
failed_master.node_id = nodes[i].node_id;
/*
* XXX experimental
*
* Currently an attempt is made to connect to the master,
* which is very likely to be a waste of time at this point, as we'll
* have spent the last however many seconds trying to do just that
* in check_connection() before deciding it's gone away.
*
* If the master did come back at this point, the voting algorithm should decide
* it's the "best candidate" anyway and no standby will promote itself or
* attempt to follow* another server.
*
* If we don't try and connect to the master here (and the code generally
* assumes it's failed anyway) but it does come back any time from here
* onwards, promotion will fail and the promotion candidate will
* notice the reappearance.
*
* TLDR version: by skipping the master connection attempt (and the chances
* the master would reappear between the last attempt in check_connection()
* and now are minimal) we can remove useless cycles during the failover process;
* if the master does reappear it will be caught before later anyway.
*/
continue;
}
node_conn = establish_db_connection(nodes[i].conninfo_str, false); node_conn = establish_db_connection(nodes[i].conninfo_str, false);
@@ -1256,13 +1393,13 @@ do_master_failover(void)
PQclear(res); PQclear(res);
log_debug(_("total nodes counted: registered=%d, visible=%d\n"), log_debug(_("total nodes counted: registered=%d, visible=%d\n"),
total_nodes, visible_nodes); total_active_nodes, visible_nodes);
/* /*
* Am I on the group that should keep alive? If I see less than half of * Am I on the group that should keep alive? If I see less than half of
* total_nodes then I should do nothing * total_active_nodes then I should do nothing
*/ */
if (visible_nodes < (total_nodes / 2.0)) if (visible_nodes < (total_active_nodes / 2.0))
{ {
log_err(_("Unable to reach most of the nodes.\n" log_err(_("Unable to reach most of the nodes.\n"
"Let the other standby servers decide which one will be the master.\n" "Let the other standby servers decide which one will be the master.\n"
@@ -1271,7 +1408,7 @@ do_master_failover(void)
} }
/* Query all available nodes to determine readiness and LSN */ /* Query all available nodes to determine readiness and LSN */
for (i = 0; i < total_nodes; i++) for (i = 0; i < total_active_nodes; i++)
{ {
log_debug("checking node %i...\n", nodes[i].node_id); log_debug("checking node %i...\n", nodes[i].node_id);
@@ -1340,8 +1477,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_xlog_replay_location, "'%X/%X'", 0, 0);
update_shared_memory(last_xlog_replay_location); update_shared_memory(LSN_QUERY_ERROR);
terminate(ERR_DB_QUERY); terminate(ERR_DB_QUERY);
} }
/* write last location in shared memory */ /* write last location in shared memory */
@@ -1349,7 +1486,7 @@ do_master_failover(void)
PQclear(res); PQclear(res);
/* Wait for each node to come up and report a valid LSN */ /* Wait for each node to come up and report a valid LSN */
for (i = 0; i < total_nodes; i++) for (i = 0; i < total_active_nodes; i++)
{ {
/* /*
* ensure witness server is marked as ready, and skip * ensure witness server is marked as ready, and skip
@@ -1382,7 +1519,6 @@ do_master_failover(void)
*/ */
if (PQstatus(node_conn) != CONNECTION_OK) if (PQstatus(node_conn) != CONNECTION_OK)
{ {
/* XXX */
log_info(_("At this point, it could be some race conditions " log_info(_("At this point, it could be some race conditions "
"that are acceptable, assume the node is restarting " "that are acceptable, assume the node is restarting "
"and starting failover procedure\n")); "and starting failover procedure\n"));
@@ -1391,6 +1527,7 @@ do_master_failover(void)
while (!nodes[i].is_ready) while (!nodes[i].is_ready)
{ {
char location_value[MAXLEN];
sqlquery_snprintf(sqlquery, sqlquery_snprintf(sqlquery,
"SELECT %s.repmgr_get_last_standby_location()", "SELECT %s.repmgr_get_last_standby_location()",
@@ -1406,7 +1543,11 @@ do_master_failover(void)
terminate(ERR_DB_QUERY); terminate(ERR_DB_QUERY);
} }
xlog_recptr = lsn_to_xlogrecptr(PQgetvalue(res, 0, 0), &lsn_format_ok); /* Copy the returned value as we'll need to reference it a few times */
strncpy(location_value, PQgetvalue(res, 0, 0), MAXLEN);
PQclear(res);
xlog_recptr = lsn_to_xlogrecptr(location_value, &lsn_format_ok);
/* If position reported as "invalid", check for format error or /* If position reported as "invalid", check for format error or
* empty string; otherwise position is 0/0 and we need to continue * empty string; otherwise position is 0/0 and we need to continue
@@ -1414,10 +1555,36 @@ do_master_failover(void)
*/ */
if (xlog_recptr == InvalidXLogRecPtr) if (xlog_recptr == InvalidXLogRecPtr)
{ {
bool continue_loop = true;
if (lsn_format_ok == false) if (lsn_format_ok == false)
{ {
/*
* The node is indicating it is not a promotion candidate -
* in this case we can store its invalid LSN to ensure it
* can't be a promotion candidate when comparing locations
*/
if (strcmp(location_value, PASSIVE_NODE) == 0)
{
log_debug("node %i is passive mode\n", nodes[i].node_id);
log_info(_("node %i will not be considered for promotion\n"), nodes[i].node_id);
nodes[i].xlog_location = InvalidXLogRecPtr;
continue_loop = false;
}
/*
* This should probably never happen but if it does, rule the
* node out as a promotion candidate
*/
else if (strcmp(location_value, LSN_QUERY_ERROR) == 0)
{
log_warning(_("node %i is unable to update its shared memory and will not be considered for promotion\n"), nodes[i].node_id);
nodes[i].xlog_location = InvalidXLogRecPtr;
continue_loop = false;
}
/* Unable to parse value returned by `repmgr_get_last_standby_location()` */ /* Unable to parse value returned by `repmgr_get_last_standby_location()` */
if (*PQgetvalue(res, 0, 0) == '\0') else if (*location_value == '\0')
{ {
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
@@ -1426,8 +1593,8 @@ do_master_failover(void)
_("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")
); );
PQclear(res);
PQfinish(node_conn); PQfinish(node_conn);
/* XXX shouldn't we just ignore this node? */
exit(ERR_BAD_CONFIG); exit(ERR_BAD_CONFIG);
} }
@@ -1435,25 +1602,29 @@ do_master_failover(void)
* Very unlikely to happen; in the absence of any better * Very unlikely to happen; in the absence of any better
* strategy keep checking * strategy keep checking
*/ */
log_warning(_("unable to parse LSN \"%s\"\n"), else {
PQgetvalue(res, 0, 0)); log_warning(_("unable to parse LSN \"%s\"\n"),
location_value);
}
} }
else else
{ {
log_debug( log_debug(
_("invalid LSN returned from node %i: '%s'\n"), _("invalid LSN returned from node %i: '%s'\n"),
nodes[i].node_id, nodes[i].node_id,
PQgetvalue(res, 0, 0) location_value);
);
} }
PQclear(res); /*
* If the node is still reporting an InvalidXLogRecPtr, it means
/* If position is 0/0, keep checking */ * its repmgrd hasn't yet had time to update it (either with a valid
/* XXX we should add a timeout here to prevent infinite looping * XLogRecPtr or a message) so we continue looping.
*
* XXX we should add a timeout here to prevent infinite looping
* if the other node's repmgrd is not up * if the other node's repmgrd is not up
*/ */
continue; if (continue_loop == true)
continue;
} }
if (nodes[i].xlog_location < xlog_recptr) if (nodes[i].xlog_location < xlog_recptr)
@@ -1461,8 +1632,7 @@ do_master_failover(void)
nodes[i].xlog_location = xlog_recptr; nodes[i].xlog_location = xlog_recptr;
} }
log_debug(_("LSN of node %i is: %s\n"), nodes[i].node_id, PQgetvalue(res, 0, 0)); log_debug(_("LSN of node %i is: %s\n"), nodes[i].node_id, location_value);
PQclear(res);
ready_nodes++; ready_nodes++;
nodes[i].is_ready = true; nodes[i].is_ready = true;
@@ -1475,7 +1645,7 @@ do_master_failover(void)
/* /*
* determine which one is the best candidate to promote to master * determine which one is the best candidate to promote to master
*/ */
for (i = 0; i < total_nodes; i++) for (i = 0; i < total_active_nodes; i++)
{ {
/* witness server can never be a candidate */ /* witness server can never be a candidate */
if (nodes[i].type == WITNESS) if (nodes[i].type == WITNESS)
@@ -1564,6 +1734,8 @@ do_master_failover(void)
{ {
log_notice(_("Original master reappeared before this standby was promoted - no action taken\n")); log_notice(_("Original master reappeared before this standby was promoted - no action taken\n"));
/* XXX log an event here? */
PQfinish(master_conn); PQfinish(master_conn);
master_conn = NULL; master_conn = NULL;
@@ -1700,8 +1872,10 @@ do_master_failover(void)
termPQExpBuffer(&event_details); termPQExpBuffer(&event_details);
} }
/* to force it to re-calculate mode and master node */ /*
// ^ ZZZ check that behaviour ^ * setting "failover_done" to true will cause the node's monitoring loop
* to restart in the appropriate mode for the node's (possibly new) role
*/
failover_done = true; failover_done = true;
} }
@@ -1877,7 +2051,7 @@ check_connection(PGconn **conn, const char *type, const char *conninfo)
{ {
if (conninfo == NULL) if (conninfo == NULL)
{ {
log_err("INTERNAL ERROR: *conn == NULL && conninfo == NULL"); log_err("INTERNAL ERROR: *conn == NULL && conninfo == NULL\n");
terminate(ERR_INTERNAL); terminate(ERR_INTERNAL);
} }
*conn = establish_db_connection(conninfo, false); *conn = establish_db_connection(conninfo, false);
@@ -1917,18 +2091,21 @@ check_connection(PGconn **conn, const char *type, const char *conninfo)
/* /*
* set_local_node_status() * set_local_node_status()
* *
* If failure of the local node is detected, attempt to connect * Attempt to connect to the current master server (as stored in the global
* to the current master server (as stored in the global variable * variable `master_conn`) and set the local node's status to the result
* `master_conn`) and update its record to failed. * of `is_standby(my_local_conn)`. Normally this will be used to mark
* a node as failed, but in some circumstances we may be marking it
* as recovered.
*/ */
static bool static bool
set_local_node_status(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];
bool local_node_status;
if (!check_connection(&master_conn, "master", NULL)) if (!check_connection(&master_conn, "master", NULL))
{ {
@@ -1987,24 +2164,29 @@ set_local_node_status(void)
/* /*
* Attempt to set the active record to the correct value. * Attempt to set the active record to the correct value.
* First
*/ */
local_node_status = (is_standby(my_local_conn) == 1);
if (!update_node_record_status(master_conn, if (!update_node_record_status(master_conn,
local_options.cluster_name, local_options.cluster_name,
node_info.node_id, node_info.node_id,
"standby", "standby",
node_info.upstream_node_id, node_info.upstream_node_id,
is_standby(my_local_conn)==1)) local_node_status))
{ {
log_err(_("unable to set local node %i as inactive on master: %s\n"), log_err(_("unable to set local node %i as %s on master: %s\n"),
node_info.node_id, node_info.node_id,
local_node_status == false ? "inactive" : "active",
PQerrorMessage(master_conn)); PQerrorMessage(master_conn));
return false; return false;
} }
log_notice(_("marking this node (%i) as inactive on master\n"), node_info.node_id); log_notice(_("marking this node (%i) as %s on master\n"),
node_info.node_id,
local_node_status == false ? "inactive" : "active");
return true; return true;
} }
@@ -2138,7 +2320,7 @@ lsn_to_xlogrecptr(char *lsn, bool *format_ok)
{ {
if (format_ok != NULL) if (format_ok != NULL)
*format_ok = false; *format_ok = false;
log_err(_("incorrect log location format: %s\n"), lsn); log_warning(_("incorrect log location format: %s\n"), lsn);
return 0; return 0;
} }

View File

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

View File

@@ -22,6 +22,7 @@
#define _STRUTIL_H_ #define _STRUTIL_H_
#include <stdlib.h> #include <stdlib.h>
#include "pqexpbuffer.h"
#include "errcode.h" #include "errcode.h"
@@ -48,4 +49,6 @@ extern int
maxlen_snprintf(char *str, const char *format,...) maxlen_snprintf(char *str, const char *format,...)
__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3))); __attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
extern char *
escape_recovery_conf_value(const char *src);
#endif /* _STRUTIL_H_ */ #endif /* _STRUTIL_H_ */

View File

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