pax_global_header00006660000000000000000000000064142026271000014504gustar00rootroot0000000000000052 comment=78cc278639241aa51ba7897ed26e995d0f27abec repmgr-5.3.1/000077500000000000000000000000001420262710000130065ustar00rootroot00000000000000repmgr-5.3.1/.gitignore000066400000000000000000000011551420262710000150000ustar00rootroot00000000000000# Global excludes across all subdirectories - copied from postgres *.o *.so *.so.[0-9] *.so.[0-9].[0-9] *.sl *.sl.[0-9] *.sl.[0-9].[0-9] *.dylib *.dll *.a *.mo *.pot objfiles.txt .deps/ *.gcno *.gcda *.gcov *.gcov.out lcov.info coverage/ *.vcproj *.vcxproj win32ver.rc *.exe lib*dll.def lib*.pc # autoconf output /autom4te.cache/ # configure output /Makefile /Makefile.global /config.log /config.status /config.h /repmgr_version.h # test output /results/ /regression.diffs /regression.out # other /.lineno *.dSYM *.orig *.rej # generated binaries repmgr repmgrd repmgr4 repmgrd4 # generated files configfile-scan.c repmgr-5.3.1/CONTRIBUTING.md000066400000000000000000000024101420262710000152340ustar00rootroot00000000000000License and Contributions ========================= `repmgr` is licensed under the GPL v3. All of its code and documentation is Copyright 2010-2021, EnterpriseDB Corporation. See the files COPYRIGHT and LICENSE for details. The development of repmgr has primarily been sponsored by 2ndQuadrant customers. Additional work has been sponsored by the 4CaaST project for cloud computing, which has received funding from the European Union's Seventh Framework Programme (FP7/2007-2013) under grant agreement 258862. Contributions to `repmgr` are welcome, and will be listed in the file `CREDITS`. EnterpriseDB Corporation requires that any contributions provide a copyright assignment and a disclaimer of any work-for-hire ownership claims from the employer of the developer. This lets us make sure that all of the repmgr distribution remains free code. Please contact info@enterprise.com for a copy of the relevant Copyright Assignment Form. Code style ---------- Code in repmgr should be formatted to the same standards as the main PostgreSQL project. For more details see: https://www.postgresql.org/docs/current/source-format.html Contributors should reformat their code similarly before submitting code to the project, in order to minimize merge conflicts with other work. repmgr-5.3.1/COPYRIGHT000066400000000000000000000012711420262710000143020ustar00rootroot00000000000000Copyright (c) 2010-2021, EnterpriseDB Corporation All rights reserved. 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 https://www.gnu.org/licenses/ to obtain one. repmgr-5.3.1/CREDITS000066400000000000000000000011211420262710000140210ustar00rootroot00000000000000Code and documentation contributors to repmgr include: Jaime Casanova Simon Riggs Greg Smith Robert J. Noles Gabriele Bartolini Bas van Oostveen Hannu Krosing Cédric Villemain Charles Duffy Daniel Farina Shawn Ellis Jay Taylor Christian Kruse Krzysztof Gajdemski repmgr-5.3.1/FAQ.md000066400000000000000000000006041420262710000137370ustar00rootroot00000000000000FAQ - Frequently Asked Questions about repmgr ============================================= The repmgr 4 FAQ is located here: [repmgr FAQ (Frequently Asked Questions)](https://repmgr.org/docs/current/appendix-faq.html "repmgr FAQ") The repmgr 3.x FAQ can be found here: https://github.com/EnterpriseDB/repmgr/blob/REL3_3_STABLE/FAQ.md Note that repmgr 3.x is no longer supported. repmgr-5.3.1/HISTORY000066400000000000000000001020221420262710000140670ustar00rootroot000000000000005.3.1 2022-??-?? repmgrd: fixes for potential connection leaks (hslightdb) 5.3.0 2021-10-12 standby switchover: improve handling of node rejoin failure (Ian) repmgrd: prefix all shared library functions with "repmgr_" to minimize the risk of clashes with other shared libraries (Ian) repmgrd: at startup, if node record is marked as "inactive", attempt to set it to "active" (Ian) standby clone: set "slot_name" in node record if required (Ian) node rejoin: emit rejoin target note information as NOTICE (Ian) repmgrd: ensure short option "-s" is accepted (Ian) 5.2.1 2020-12-07 config: fix parsing of "replication_type"; GitHub #672 (Ian) standby clone: handle missing "postgresql.auto.conf" (Ian) standby clone: add option --recovery-min-apply-delay (Ian) standby clone: fix data directory permissions handling for PostgreSQL 11 and later (Ian) repmgrd: prevent termination when local node not available and standby_disconnect_on_failover; GitHub #675 (Ian) repmgrd: ensure reconnect_interval" is correctly handled; GitHub #673 (Ian) 5.2.0 2020-10-22 general: add support for PostgreSQL 13 (Ian) general: remove support for PostgreSQL 9.3 (Ian) config: add support for file inclusion directives (Ian) repmgr: "primary unregister --force" will unregister an active primary with no registered standby nodes (Ian) repmgr: add option --verify-backup to "standby clone" (Ian) repmgr: "standby clone" honours --waldir option if set in "pg_basebackup_options" (Ian) repmgr: add option --db-connection to "node check" (Ian) repmgr: report database connection error if the --optformat option was provided to "node check" (Ian) repmgr: improve "node rejoin" checks (Ian) repmgr: enable "node rejoin" to join a target with a lower timeline (Ian) repmgr: support pg_rewind's automatic crash recovery in Pg13 and later (Ian) repmgr: improve output formatting for cluster matrix/crosscheck (Ian) repmgr: improve database connection failure error checking on the demotion candidate during "standby switchover" (Ian) repmgr: make repmgr metadata tables dumpable (Ian) repmgr: fix issue with tablespace mapping when cloning from Barman; GitHub #650 (Ian) repmgr: improve handling of pg_control read errors (Ian) repmgrd: add additional optional parameters to "failover_validation command" (spaskalev; GitHub #651) repmgrd: ensure primary connection is reset if same as upstream; GitHub #633 (Ian) 5.1.0 2020-04-13 repmgr: remove BDR 2.x support repmgr: don't query upstream's data directory (Ian) repmgr: rename --recovery-conf-only to --replication-conf-only (Ian) repmgr: ensure postgresql.auto.conf is created with correct permissions (Ian) repmgr: minimize requirement to check upstream data directory location during "standby clone" (Ian) repmgr: warn about missing pg_rewind prerequisites when executing "standby clone" (Ian) repmgr: add --upstream option to "node check" repmgr: report error code on follow/rejoin failure due to non-available 0 replication slot (Ian) repmgr: ensure "node rejoin" checks for available replication slots (Ian) repmgr: improve "standby switchover" completion checks (Ian) repmgr: add replication configuration file ownership check to "standby switchover" (Ian) repmgr: check the demotion candidate's registered repmgr.conf file can be found (laixiong; GitHub 615) repmgr: consolidate replication connection code (Ian) repmgr: check permissions for "pg_promote()" and fall back to pg_ctl if necessary (Ian) repmgr: in --dry-run mode, display promote command which will be used (Ian) repmgr: enable "service_promote_command" in PostgreSQL 12 (Ian) repmgr: accept option -S/--superuser for "node check"; GitHub #612 (Ian) 5.0 2019-10-15 general: add PostgreSQL 12 support (Ian) general: parse configuration file using flex (Ian) repmgr: rename "repmgr daemon ..." commands to "repmgr service ..." (Ian) repmgr: improve data directory check (Ian) repmgr: improve extension check during "standby clone" (Ian) repmgr: pass provided log level when executing repmgr remotely (Ian) repmgrd: fix handling of upstream node change check (Ian) 4.4 2019-06-27 repmgr: improve "daemon status" output (Ian) repmgr: add "--siblings-follow" option to "standby promote" (Ian) repmgr: add "--repmgrd-force-unpause" option to "standby switchover" (Ian) repmgr: fix data directory permissions issue in barman mode where an existing directory is being overwritten (Ian) repmgr: improve "--dry-run" behaviour for "standby promote" and "standby switchover" (Ian) repmgr: when running "standby clone" with the "--upstream-conninfo" option ensure that "application_name" is set correctly in "primary_conninfo" (Ian) repmgr: ensure "--dry-run" together with --force when running "standby clone" in barman mode does not modify an existing data directory (Ian) repmgr: improve "--dry-run" output when running "standby clone" in basebackup mode (Ian) repmgr: improve upstream walsender checks when running "standby clone" (Ian) repmgr: display node timeline ID in "cluster show" output (Ian) repmgr: in "cluster show" and "daemon status", show upstream node name as reported by each individual node (Ian) repmgr: in "cluster show" and "daemon status", check if a node is attached to its advertised upstream node repmgr: use --compact rather than --terse option in "cluster event" (Ian) repmgr: prevent a standby being cloned from a witness server (Ian) repmgr: prevent a witness server being registered on the cluster primary (John) repmgr: ensure BDR2-specific functionality cannot be used on BDR3 and later (Ian) repmgr: canonicalize the data directory path (Ian) repmgr: note that "standby follow" requires a primary to be available (Ian) repmgrd: monitor standbys attached to primary (Ian) repmgrd: add "primary visibility consensus" functionality (Ian) repmgrd: fix memory leak which occurs while the monitored PostgreSQL node is not running (Ian) general: documentation converted to DocBook XML format (Ian) 4.3 2019-04-02 repmgr: add "daemon (start|stop)" command; GitHub #528 (Ian) repmgr: add --version-number command line option (Ian) repmgr: add --compact option to "cluster show"; GitHub #521 (Ian) repmgr: cluster show - differentiate between unreachable nodes and nodes which are running but rejecting connections (Ian) repmgr: add --dry-run option to "standby promote"; GitHub #522 (Ian) repmgr: add "node check --data-directory-config"; GitHub #523 (Ian) repmgr: prevent potential race condition in "standby switchover" when checking received WAL location; GitHub #518 (Ian) repmgr: ensure "standby switchover" verifies repmgr can read the data directory on the demotion candidate; GitHub #523 (Ian) repmgr: ensure "standby switchover" verifies replication connection exists; GitHub #519 (Ian) repmgr: add sanity check for correct extension version (Ian) repmgr: ensure "witness register --dry-run" does not attempt to read node tables if repmgr extension not installed; GitHub #513 (Ian) repmgr: ensure "standby register" fails when --upstream-node-id is the same as the local node ID (Ian) repmgrd: check binary and extension major versions match; GitHub #515 (Ian) repmgrd: on a cascaded standby, don't fail over if "failover=manual"; GitHub #531 (Ian) repmgrd: don't consider nodes where repmgrd is not running as promotion candidates (Ian) repmgrd: add option "connection_check_type" (Ian) repmgrd: improve witness monitoring when primary node not available (Ian) repmgrd: handle situation where a primary has unexpectedly appeared during failover; GitHub #420 (Ian) general: fix Makefile (John) 4.2 2018-10-24 repmgr: add parameter "shutdown_check_timeout" for use by "standby switchover"; GitHub #504 (Ian) repmgr: add "--node-id" option to "repmgr cluster cleanup"; GitHub #493 (Ian) repmgr: report unreachable nodes when running "repmgr cluster (matrix|crosscheck); GitHub #246 (Ian) repmgr: add configuration file parameter "repmgr_bindir"; GitHub #246 (Ian) repmgr: fix "Missing replication slots" label in "node check"; GitHub #507 (Ian) repmgrd: fix parsing of -d/--daemonize option (Ian) repmgrd: support "pausing" of repmgrd (Ian) 4.1.1 2018-09-05 logging: explicitly log the text of failed queries as ERRORs to assist logfile analysis; GitHub #498 repmgr: truncate version string, if necessary; GitHub #490 (Ian) repmgr: improve messages emitted during "standby promote" (Ian) repmgr: "standby clone" - don't copy external config files in --dry-run mode; GitHub #491 (Ian) repmgr: add "cluster_cleanup" event; GitHub #492 (Ian) repmgr: (standby switchover) improve detection of free walsenders; GitHub #495 (Ian) repmgr: (node rejoin) improve replication slot handling; GitHub #499 (Ian) repmgrd: ensure that sending SIGHUP always results in the log file being reopened; GitHub #485 (Ian) repmgrd: report version number *after* logger initialisation; GitHub #487 (Ian) repmgrd: fix startup on witness node when local data is stale; GitHub #488/#489 (Ian) repmgrd: improve cascaded standby failover handling; GitHub #480 (Ian) repmgrd: improve reconnection handling (Ian) 4.1.0 2018-07-31 repmgr: change default log_level to INFO, add documentation; GitHub #470 (Ian) repmgr: add "--missing-slots" check to "repmgr node check" (Ian) repmgr: improve command line error handling; GitHub #464 (Ian) repmgr: fix "standby register --wait-sync" when no timeout provided (Ian) repmgr: "cluster show" returns non-zero value if an issue encountered; GitHub #456 (Ian) repmgr: "node check" and "node status" returns non-zero value if an issue encountered (Ian) repmgr: add CSV output mode to "cluster event"; GitHub #471 (Ian) repmgr: add -q/--quiet option to suppress non-error output; GitHub #468 (Ian) repmgr: "node status" returns non-zero value if an issue encountered (Ian) repmgr: enable "recovery_min_apply_delay" to be 0; GitHub #448 (Ian) repmgr: "cluster cleanup" - add missing help options; GitHub #461/#462 (gclough) repmgr: ensure witness node follows new primary after switchover; GitHub #453 (Ian) repmgr: fix witness node handling in "node check"/"node status"; GitHub #451 (Ian) repmgr: fix "primary_slot_name" when using "standby clone" with --recovery-conf-only; GitHub #474 (Ian) repmgr: don't perform a switchover if an exclusive backup is running; GitHub #476 (Martín) repmgr: enable "witness unregister" to be run on any node; GitHub #472 (Ian) repmgrd: create a PID file by default; GitHub #457 (Ian) repmgrd: daemonize process by default; GitHub #458 (Ian) 4.0.6 2018-06-14 repmgr: (witness register) prevent registration of a witness server with the same name as an existing node (Ian) repmgr: (standby follow) check node has actually connected to new primary before reporting success; GitHub #444 (Ian) repmgr: (standby clone) improve handling of external configuration file copying, including consideration in --dry-run check; GitHub #443 (Ian) repmgr: (standby clone) don't require presence of "user" parameter in conninfo string; GitHub #437 (Ian) repmgr: (standby clone) improve documentation of --recovery-conf-only mode; GitHub #438 (Ian) repmgr: (node rejoin) fix bug when parsing --config-files parameter; GitHub #442 (Ian) repmgr: when using --dry-run, force log level to INFO to ensure output will always be displayed; GitHub #441 (Ian) repmgr: (cluster matrix/crosscheck) return non-zero exit code if node connection issues detected; GitHub #447 (Ian) repmgrd: ensure local node is counted as quorum member; GitHub #439 (Ian) 4.0.5 2018-05-02 repmgr: poll demoted primary after restart as a standby during a switchover operation; GitHub #408 (Ian) repmgr: add configuration parameter "config_directory"; GitHub #424 (Ian) repmgr: add "dbname=replication" to all replication connection strings; GitHub #421 (Ian) repmgr: add sanity check if --upstream-node-id not supplied when executing "standby register"; GitHub #395 (Ian) repmgr: enable provision of "archive_cleanup_command" in recovery.conf; GitHub #416 (Ian) repmgr: actively check for node to rejoin cluster; GitHub #415 (Ian) repmgr: enable pg_rewind to be used with PostgreSQL 9.3/9.4; GitHub #413 (Ian) repmgr: fix minimum accepted value for "degraded_monitoring_timeout"; GitHub #411 (Ian) repmgr: fix superuser password handling; GitHub #400 (Ian) repmgr: fix parsing of "archive_ready_critical" configuration file parameter; GitHub #426 (Ian) repmgr: fix display of conninfo parsing error messages (Ian) repmgr: fix "repmgr cluster crosscheck" output; GitHub #389 (Ian) repmgrd: prevent standby connection handle from going stale (Ian) repmgrd: fix memory leaks in witness code; GitHub #402 (AndrzejNowicki, Martín) repmgrd: handle "pg_ctl promote" timeout; GitHub #425 (Ian) repmgrd: handle failover situation with only two nodes in the primary location, and at least one node in another location; GitHub #407 (Ian) repmgrd: set "connect_timeout=2" when pinging a server (Ian) 4.0.4 2018-03-09 repmgr: add "standby clone --recovery-conf-only" option; GitHub #382 (Ian) repmgr: make "standby promote" timeout values configurable; GitHub #387 (Ian) repmgr: improve replication slot warnings generated by "node status"; GitHub #385 (Ian) repmgr: remove restriction on replication slots when cloning from a Barman server; GitHub #379 (Ian) repmgr: ensure "node rejoin" honours "--dry-run" option; GitHub #383 (Ian) repmgr: fix --superuser handling when cloning a standby; GitHub #380 (Ian) repmgr: update various help options; GitHub #391, #392 (hasegeli) repmgrd: add event "repmgrd_shutdown"; GitHub #393 (Ian) repmgrd: improve detection of status change from primary to standby (Ian) repmgrd: improve log output in various situations (Ian) repmgrd: improve reconnection to the local node after a failover (Ian) repmgrd: ensure witness server connects to new primary after a failover (Ian) 4.0.3 2018-02-15 repmgr: improve switchover handling when "pg_ctl" used to control the server and logging output is not explicitly redirected (Ian) repmgr: improve switchover log messages and exit code when old primary could not be shut down cleanly (Ian) repmgr: check demotion candidate can make a replication connection to the promotion candidate before executing a switchover; GitHub #370 (Ian) repmgr: add check for sufficient walsenders/replication slots before executing a switchover; GitHub #371 (Ian) repmgr: add --dry-run mode to "repmgr standby follow"; GitHub #368 (Ian) repmgr: provide information about the primary node for "standby_register" and "standby_follow" event notifications; GitHub #375 (Ian) repmgr: add "standby_register_sync" event notification; GitHub #374 (Ian) repmgr: output any connection error messages in "cluster show"'s list of warnings; GitHub #369 (Ian) repmgr: ensure an inactive data directory can be deleted; GitHub #366 (Ian) repmgr: fix upstream node display in "repmgr node status"; GitHub #363 (fanf2) repmgr: improve/clarify documentation and update --help output for "primary unregister"; GitHub #373 (Ian) repmgr: allow replication slots when Barman is configured; GitHub #379 (Ian) repmgr: fix parsing of "pg_basebackup_options"; GitHub #376 (Ian) repmgr: ensure "pg_subtrans" directory is created when cloning a standby in Barman mode (Ian) repmgr: fix primary node check in "witness register"; GitHub #377 (Ian) 4.0.2 2018-01-18 repmgr: add missing -W option to getopt_long() invocation; GitHub #350 (Ian) repmgr: automatically create slot name if missing; GitHub #343 (Ian) repmgr: fixes to parsing output of remote repmgr invocations; GitHub #349 (Ian) repmgr: BDR support - create missing connection replication set if required; GitHub #347 (Ian) repmgr: handle missing node record in "repmgr node rejoin"; GitHub #358 (Ian) repmgr: enable documentation to be build as single HTML file; GitHub #353 (fanf2) repmgr: recognize "--terse" option for "repmgr cluster event"; GitHub #360 (Ian) repmgr: add "--wait-start" option for "repmgr standby register"; GitHub #356 (Ian) repmgr: add "%p" event notification parameter for "repmgr standby switchover" containing the node ID of the demoted primary (Ian) docs: various fixes and updates (Ian, Daymel, Martín, ams) 4.0.1 2017-12-13 repmgr: ensure "repmgr node check --action=" returns appropriate return code; GitHub #340 (Ian) repmgr: add missing schema qualification in get_all_node_records_with_upstream() query GitHub #341 (Martín) repmgr: initialise "voting_term" table in application, not extension SQL; GitHub #344 (Ian) repmgr: delete any replication slots copied by pg_rewind; GitHub #334 (Ian) repmgr: fix configuration file sanity check; GitHub #342 (Ian) 4.0.0 2017-11-21 Complete rewrite with many changes; for details see the repmgr 4.0.0 release notes at: https://repmgr.org/docs/4.0/release-4.0.0.html 3.3.2 2017-06-01 Add support for PostgreSQL 10 (Ian) repmgr: ensure --replication-user option is honoured when passing database connection parameters as a conninfo string (Ian) repmgr: improve detection of pg_rewind on remote server (Ian) repmgr: add DETAIL log output for additional clarification of error messages (Ian) repmgr: suppress various spurious error messages in `standby follow` and `standby switchover` (Ian) repmgr: add missing `-P` option (Ian) repmgrd: monitoring statistic reporting fixes (Ian) 3.3.1 2017-03-13 repmgrd: prevent invalid apply lag value being written to the monitoring table (Ian) repmgrd: fix error in XLogRecPtr conversion when calculating monitoring statistics (Ian) repmgr: if replication slots in use, where possible delete slot on old upstream node after following new upstream (Ian) repmgr: improve logging of rsync actions (Ian) repmgr: improve `standby clone` when synchronous replication in use (Ian) repmgr: stricter checking of allowed node id values repmgr: enable `master register --force` when there is a foreign key dependency from a standby node (Ian) 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" in the recovery.conf file generated by repmgr (Martín) repmgr: add --csv option to "repmgr cluster show" (Gianni) repmgr: enable provision of a conninfo string as the -d/--dbname parameter, similar to other PostgreSQL utilities (Ian) repmgr: during switchover operations improve detection of demotion candidate shutdown (Ian) various bugfixes and documentation updates (Ian, Martín) 3.1.3 2016-05-17 repmgrd: enable monitoring when a standby is catching up by replaying archived WAL (Ian) repmgrd: when upstream_node_id is NULL, assume upstream node to be current master (Ian) repmgrd: check for reappearance of the master node if standby promotion fails (Ian) improve handling of rsync failure conditions (Martín) 3.1.2 2016-04-12 Fix pg_ctl path generation in do_standby_switchover() (Ian) Regularly sync witness server repl_nodes table (Ian) Documentation improvements (Gianni, dhyannataraj) (Experimental) ensure repmgr handles failover slots when copying in rsync mode (Craig, Ian) rsync mode handling fixes (Martín) Enable repmgr to compile against 9.6devel (Ian) 3.1.1 2016-02-24 Add '-P/--pwprompt' option for "repmgr create witness" (Ian) Prevent repmgr/repmgrd running as root (Ian) 3.1.0 2016-02-01 Add "repmgr standby switchover" command (Ian) Revised README file (Ian) Remove requirement for 'archive_mode' to be enabled (Ian) Improve -?/--help output, showing default values if relevant (Ian) Various bugfixes to command line/configuration parameter handling (Ian) 3.0.3 2016-01-04 Create replication slot if required before base backup is run (Abhijit) standy clone: when using rsync, clean up "pg_replslot" directory (Ian) Improve --help output (Ian) Improve config file parsing (Ian) Various logging output improvements, including explicit HINTS (Ian) Add --log-level to explicitly set log level on command line (Ian) Repurpose --verbose to display extra log output (Ian) Add --terse to hide hints and other non-critical output (Ian) Reference internal functions with explicit catalog path (Ian) When following a new primary, have repmgr (not repmgrd) create the new slot (Ian) Add /etc/repmgr.conf as a default configuration file location (Ian) Prevent repmgrd's -v/--verbose option expecting a parameter (Ian) Prevent invalid replication_lag values being written to the monitoring table (Ian) Improve repmgrd behaviour when monitored standby node is temporarily unavailable (Martín) 3.0.2 2015-10-02 Improve handling of --help/--version options; and improve help output (Ian) Improve handling of situation where logfile can't be opened (Ian) Always pass -D/--pgdata option to pg_basebackup (Ian) Bugfix: standby clone --force does not empty pg_xlog (Gianni) Bugfix: autofailover with reconnect_attempts > 1 (Gianni) Bugfix: ignore comments after values (soxwellfb) Bugfix: handle string values in 'node' parameter correctly (Gregory Duchatelet) Allow repmgr to be compiled with a newer libpq (Marco) Bugfix: call update_node_record_set_upstream() for STANDBY FOLLOW (Tomas) Update `repmgr --help` output (per Github report from renard) Update tablespace remapping in --rsync-only mode for 9.5 and later (Ian) Deprecate `-l/--local-port` option - the port can be extracted from the conninfo string in repmgr.conf (Ian) Add STANDBY UNREGISTER (Vik Fearing) Don't fail with error when registering master if schema already defined (Ian) Fixes to whitespace handling when parsing config file (Ian) 3.0.1 2015-04-16 Prevent repmgrd from looping infinitely if node was not registered (Ian) When promoting a standby, have repmgr (not repmgrd) handle metadata updates (Ian) Re-use replication slot if it already exists (Ian) Prevent a test SSH connection being made when not needed (Ian) Correct monitoring table column names (Ian) 3.0 2015-03-27 Require PostgreSQL 9.3 or later (Ian) Use `pg_basebackup` by default (instead of `rsync`) to clone standby servers (Ian) Use `pg_ctl promote` to promote a standby to primary Enable tablespace remapping using `pg_basebackup` (in PostgreSQL 9.3 with `rsync`) (Ian) Support cascaded standbys (Ian) "pg_bindir" no longer required as a configuration parameter (Ian) Enable replication slots to be used (PostgreSQL 9.4 and later (Ian) Command line option "--check-upstream-config" (Ian) Add event logging table and option to execute an external program when an event occurs (Ian) General usability and logging message improvements (Ian) Code consolidation and cleanup (Ian) 2.0.3 2015-04-16 Add -S/--superuser option for witness database creation Ian) Add -c/--fast-checkpoint option for cloning (Christoph) Add option "--initdb-no-pwprompt" (Ian) 2.0.2 2015-02-17 Add "--checksum" in rsync when using "--force" (Jaime) Use createdb/createuser instead of psql (Jaime) Fixes to witness creation and monitoring (wamonite) Use default master port if none supplied (Martín) Documentation fixes and improvements (Ian) 2.0.1 2014-07-16 Documentation fixes and new QUICKSTART file (Ian) Explicitly specify directories to ignore when cloning (Ian) Fix log level for some log messages (Ian) RHEL/CentOS specfile, init script and Makefile fixes (Nathan Van Overloop) Debian init script and config file documentation fixes (József Kószó) Typo fixes (Riegie Godwin Jeyaranchen, PriceChild) 2.0stable 2014-01-30 Documentation fixes (Christian) General refactoring, code quality improvements and stabilization work (Christian) Added proper daemonizing (-d/--daemonize) (Christian) Added PID file handling (-p/--pid-file) (Christian) New config option: monitor_interval_secs (Christian) New config option: retry_promote_interval (Christian) New config option: logfile (Christian) New config option: pg_bindir (Christian) New config option: pgctl_options (Christian) 2.0beta2 2013-12-19 Improve autofailover logic and algorithms (Jaime, Andres) Ignore pg_log when cloning (Jaime) Add timestamps to log line in stderr (Christian) Correctly check wal_keep_segments (Jay Taylor) Add a ssh_options parameter (Jay Taylor) 2.0beta1 2012-07-27 Make CLONE command try to make an exact copy including $PGDATA location (Cedric) Add detection of master failure (Jaime) Add the notion of a witness server (Jaime) Add autofailover capabilities (Jaime) Add a configuration parameter to indicate the script to execute on failover or follow (Jaime) Make the monitoring optional and turned off by default, it can be turned on with --monitoring-history switch (Jaime) Add tunables to specify number of retries to reconnect to master and the time between them (Jaime) 1.2.0 2012-07-27 Test ssh connection before trying to rsync (Cédric) Add CLUSTER SHOW command (Carlo) Add CLUSTER CLEANUP command (Jaime) Add function write_primary_conninfo (Marco) Teach repmgr how to get tablespace's location in different pg version (Jaime) Improve version message (Carlo) 1.1.1 2012-04-18 Add --ignore-rsync-warning (Cédric) Add strnlen for compatibility with OS X (Greg) Improve performance of the repl_status view (Jaime) Remove last argument from log_err (Jaime, Reported by Jeroen Dekkers) Complete documentation about possible error conditions (Jaime) Document how to clean history (Jaime) 1.1.0 2011-03-09 Make options -U, -R and -p not mandatory (Jaime) 1.1.0b1 2011-02-24 Fix missing "--force" option in help (Greg Smith) Correct warning message for wal_keep_segments (Bas van Oostveen) Add Debian build/usage docs (Bas, Hannu Krosing, Cedric Villemain) Add Debian .deb packaging (Hannu) Move configuration data into a structure (Bas, Gabriele Bartolini) Make rsync options configurable (Bas) Add syslog as alternate logging destination (Gabriele) Change from using malloc to static memory allocations (Gabriele) Add debugging messages after every query (Gabriele) Parameterize schema name used for repmgr (Gabriele) Avoid buffer overruns by using snprintf etc. (Gabriele) Fix use of database query after close (Gabriele) Add information about progress during "standby clone" (Gabriele) Fix double free errors in repmgrd (Charles Duffy, Greg) Make repmgr exit with an error code when encountering an error (Charles) Standardize on error return codes, use in repmgrd too (Greg) Add [un]install actions/SQL like most contrib modules (Daniel Farina) Wrap all string construction and produce error on overflow (Daniel) Correct freeing of memory from first_wal_segment (Daniel) Allow creating recovery.conf file with a password (Daniel) Inform when STANDBY CLONE sees an unused config file (Daniel) Use 64-bit computation for WAL apply_lag (Greg) Add info messages for database and general work done (Greg) Map old verbose flag into a useful setting for the new logger (Greg) Document repmgrd startup restrictions and log info about them (Greg) 1.0.0 2010-12-05 First public release repmgr-5.3.1/LICENSE000066400000000000000000001045141420262710000140200ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . repmgr-5.3.1/Makefile.global.in000066400000000000000000000016551420262710000163210ustar00rootroot00000000000000# -*-makefile-*- # Makefile.global.in # @configure_input@ # Can only be built using pgxs USE_PGXS=1 repmgr_abs_srcdir := @abs_srcdir@ PG_CONFIG :=@PG_CONFIG@ PGXS := $(shell $(PG_CONFIG) --pgxs) vpath_build=@vpath_build@ ifeq ($(vpath_build),yes) VPATH := $(repmgr_abs_srcdir)/$(repmgr_subdir) USE_VPATH :=$(VPATH) endif SED=@SED@ GIT_WORK_TREE=${repmgr_abs_srcdir} GIT_DIR=${repmgr_abs_srcdir}/.git export GIT_DIR export GIT_WORK_TREE include $(PGXS) -include ${repmgr_abs_srcdir}/Makefile.custom REPMGR_VERSION=$(shell awk '/^\#define REPMGR_VERSION / { print $3; }' ${repmgr_abs_srcdir}/repmgr_version.h.in | cut -d '"' -f 2) REPMGR_RELEASE_DATE=$(shell awk '/^\#define REPMGR_RELEASE_DATE / { print $3; }' ${repmgr_abs_srcdir}/repmgr_version.h.in | cut -d '"' -f 2) FLEX = flex ########################################################################## # # Global targets and rules %.c: %.l $(FLEX) $(FLEXFLAGS) -o'$@' $< repmgr-5.3.1/Makefile.in000066400000000000000000000070001420262710000150500ustar00rootroot00000000000000# -*-makefile-*- # Makefile.in # @configure_input@ repmgr_subdir = . repmgr_top_builddir = . MODULE_big = repmgr EXTENSION = repmgr DATA = \ repmgr--unpackaged--4.0.sql \ repmgr--unpackaged--5.1.sql \ repmgr--unpackaged--5.2.sql \ repmgr--unpackaged--5.3.sql \ repmgr--4.0.sql \ repmgr--4.0--4.1.sql \ repmgr--4.1.sql \ repmgr--4.1--4.2.sql \ repmgr--4.2.sql \ repmgr--4.2--4.3.sql \ repmgr--4.3.sql \ repmgr--4.3--4.4.sql \ repmgr--4.4.sql \ repmgr--4.4--5.0.sql \ repmgr--5.0.sql \ repmgr--5.0--5.1.sql \ repmgr--5.1.sql \ repmgr--5.1--5.2.sql \ repmgr--5.2.sql \ repmgr--5.2--5.3.sql \ repmgr--5.3.sql REGRESS = repmgr_extension # Hacky workaround to install the binaries SCRIPTS_built = repmgr repmgrd all: \ repmgr \ repmgrd # When in development add -Werror PG_CPPFLAGS = -std=gnu89 -I$(includedir_internal) -I$(libpq_srcdir) -Wall -Wmissing-prototypes -Wmissing-declarations $(EXTRA_CFLAGS) SHLIB_LINK = $(libpq) OBJS = \ repmgr.o include Makefile.global ifeq ($(vpath_build),yes) HEADERS = $(wildcard *.h) else HEADERS_built = $(wildcard *.h) endif $(info Building against PostgreSQL $(MAJORVERSION)) REPMGR_CLIENT_OBJS = repmgr-client.o \ repmgr-action-primary.o repmgr-action-standby.o repmgr-action-witness.o \ repmgr-action-cluster.o repmgr-action-node.o repmgr-action-service.o repmgr-action-daemon.o \ configdata.o configfile.o configfile-scan.o log.o strutil.o controldata.o dirutil.o compat.o \ dbutils.o sysutils.o REPMGRD_OBJS = repmgrd.o repmgrd-physical.o configdata.o configfile.o configfile-scan.o log.o \ dbutils.o strutil.o controldata.o compat.o sysutils.o DATE=$(shell date "+%Y-%m-%d") repmgr_version.h: repmgr_version.h.in $(SED) -E 's/REPMGR_VERSION_DATE.*""/REPMGR_VERSION_DATE "$(DATE)"/' $< >$@; \ $(SED) -i -E 's/PG_ACTUAL_VERSION_NUM/PG_ACTUAL_VERSION_NUM $(VERSION_NUM)/' $@ configfile-scan.c: configfile-scan.l $(REPMGR_CLIENT_OBJS): repmgr-client.h repmgr_version.h repmgr: $(REPMGR_CLIENT_OBJS) $(CC) $(CFLAGS) $(REPMGR_CLIENT_OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) -o $@$(X) repmgrd: $(REPMGRD_OBJS) $(CC) $(CFLAGS) $(REPMGRD_OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) -o $@$(X) $(REPMGR_CLIENT_OBJS): $(HEADERS) $(REPMGRD_OBJS): $(HEADERS) # Ensure Makefiles are up-to-date (should we move this to Makefile.global?) Makefile: Makefile.in config.status configure ./config.status $@ Makefile.global: Makefile.global.in config.status configure ./config.status $@ doc: repmgr_version.h $(MAKE) -C doc html doc-repmgr.html: repmgr_version.h $(MAKE) -C doc repmgr.html doc-repmgr-A4.pdf: repmgr_version.h $(MAKE) -C doc repmgr-A4.pdf doc-repmgr-US.pdf: repmgr_version.h $(MAKE) -C doc repmgr-US.pdf install-doc: doc $(MAKE) -C doc install clean: additional-clean maintainer-clean: additional-maintainer-clean additional-clean: rm -f *.o rm -f repmgr_version.h $(MAKE) -C doc clean additional-maintainer-clean: clean $(MAKE) -C doc maintainer-clean rm -f config.status config.log rm -f config.h rm -f repmgr_version.h rm -f Makefile rm -f Makefile.global @rm -rf autom4te.cache/ ifeq ($(MAJORVERSION),$(filter $(MAJORVERSION),9.3 9.4)) # We must emulate SCRIPTS_built for Pg < 9.5 as PGXS doesn't support it install: install-scripts install-scripts: $(INSTALL_SCRIPT) $(SCRIPTS_built) '$(DESTDIR)$(bindir)/' .PHONY: install-scripts installdirs: installdirs-scripts installdirs-scripts: $(MKDIR_P) '$(DESTDIR)$(bindir)' .PHONY: installdirs-scripts endif .PHONY: doc doc-repmgr.html doc-repmgr-A4.pdf doc-repmgr-US.pdf install-doc repmgr-5.3.1/PACKAGES.md000066400000000000000000000120271420262710000145100ustar00rootroot00000000000000Packaging ========= Notes on RedHat Linux, Fedora, and CentOS Builds ------------------------------------------------ The RPM packages of PostgreSQL put `pg_config` into the `postgresql-devel` package, not the main server one. And if you have a RPM install of PostgreSQL 9.0, the entire PostgreSQL binary directory will not be in your PATH by default either. Individual utilities are made available via the `alternatives` mechanism, but not all commands will be wrapped that way. The files installed by repmgr will certainly not be in the default PATH for the postgres user on such a system. They will instead be in /usr/pgsql-9.0/bin/ on this type of system. When building repmgr against a RPM packaged build, you may discover that some development packages are needed as well. The following build errors can occur: /usr/bin/ld: cannot find -lxslt /usr/bin/ld: cannot find -lpam Install the following packages to correct those: yum install libxslt-devel yum install pam-devel If building repmgr as a regular user, then doing the install into the system directories using sudo, the syntax is hard. `pg_config` won't be in root's path either. The following recipe should work: sudo PATH="/usr/pgsql-9.0/bin:$PATH" make USE_PGXS=1 install Issues with 32 and 64 bit RPMs ------------------------------ If when building, you receive a series of errors of this form: /usr/bin/ld: skipping incompatible /usr/pgsql-9.0/lib/libpq.so when searching for -lpq This is likely because you have both the 32 and 64 bit versions of the `postgresql90-devel` package installed. You can check that like this: rpm -qa --queryformat '%{NAME}\t%{ARCH}\n' | grep postgresql90-devel And if two packages appear, one for i386 and one for x86_64, that's not supposed to be allowed. This can happen when using the PGDG repo to install that package; here is an example sessions demonstrating the problem case appearing: # yum install postgresql-devel .. Setting up Install Process Resolving Dependencies --> Running transaction check ---> Package postgresql90-devel.i386 0:9.0.2-2PGDG.rhel5 set to be updated ---> Package postgresql90-devel.x86_64 0:9.0.2-2PGDG.rhel5 set to be updated --> Finished Dependency Resolution Dependencies Resolved ========================================================================= Package Arch Version Repository Size ========================================================================= Installing: postgresql90-devel i386 9.0.2-2PGDG.rhel5 pgdg90 1.5 M postgresql90-devel x86_64 9.0.2-2PGDG.rhel5 pgdg90 1.6 M Note how both the i386 and x86_64 platform architectures are selected for installation. Your main PostgreSQL package will only be compatible with one of those, and if the repmgr build finds the wrong postgresql90-devel these "skipping incompatible" messages appear. In this case, you can temporarily remove both packages, then just install the correct one for your architecture. Example: rpm -e postgresql90-devel --allmatches yum install postgresql90-devel-9.0.2-2PGDG.rhel5.x86_64 Instead just deleting the package from the wrong platform might not leave behind the correct files, due to the way in which these accidentally happen to interact. If you already tried to build repmgr before doing this, you'll need to do: make USE_PGXS=1 clean to get rid of leftover files from the wrong architecture. Notes on Ubuntu, Debian or other Debian-based Builds ---------------------------------------------------- The Debian packages of PostgreSQL put `pg_config` into the development package called `postgresql-server-dev-$version`. When building repmgr against a Debian packages build, you may discover that some development packages are needed as well. You will need the following development packages installed: sudo apt-get install libxslt-dev libxml2-dev libpam-dev libedit-dev If you're using Debian packages for PostgreSQL and are building repmgr with the USE_PGXS option you also need to install the corresponding development package: sudo apt-get install postgresql-server-dev-9.0 If you build and install repmgr manually it will not be on the system path. The binaries will be installed in /usr/lib/postgresql/$version/bin/ which is not on the default path. The reason behind this is that Ubuntu/Debian systems manage multiple installed versions of PostgreSQL on the same system through a wrapper called pg_wrapper and repmgr is not (yet) known to this wrapper. You can solve this in many different ways, the most Debian like is to make an alternate for repmgr and repmgrd: sudo update-alternatives --install /usr/bin/repmgr repmgr /usr/lib/postgresql/9.0/bin/repmgr 10 sudo update-alternatives --install /usr/bin/repmgrd repmgrd /usr/lib/postgresql/9.0/bin/repmgrd 10 You can also make a deb package of repmgr using: make USE_PGXS=1 deb This will build a Debian package one level up from where you build, normally the same directory that you have your repmgr/ directory in. repmgr-5.3.1/README.md000066400000000000000000000063451420262710000142750ustar00rootroot00000000000000repmgr: Replication Manager for PostgreSQL ========================================== `repmgr` is a suite of open-source tools to manage replication and failover within a cluster of PostgreSQL servers. It enhances PostgreSQL's built-in replication capabilities with utilities to set up standby servers, monitor replication, and perform administrative tasks such as failover or switchover operations. The most recent `repmgr` version (5.2.1) supports all PostgreSQL versions from 9.5 to 13. PostgreSQL 9.4 is also supported, with some restrictions. `repmgr` is distributed under the GNU GPL 3 and maintained by EnterpriseDB. Documentation ------------- The full `repmgr` documentation is available here: > [repmgr documentation](https://repmgr.org/docs/current/index.html) The old `README` file for `repmgr` 3.x is available here: > https://github.com/EnterpriseDB/repmgr/blob/REL3_3_STABLE/README.md Note that the `repmgr` 3.x series is no longer supported and contains known bugs; please upgrade to the [current repmgr version](https://repmgr.org/docs/current/appendix-release-notes.html) as soon as possible. Versions -------- For an overview of `repmgr` versions and PostgreSQL compatibility, see the [repmgr compatibility matrix](https://repmgr.org/docs/current/install-requirements.html#INSTALL-COMPATIBILITY-MATRIX). Files ------ - `CONTRIBUTING.md`: details on how to contribute to `repmgr` - `COPYRIGHT`: Copyright information - `HISTORY`: Summary of changes in each `repmgr` release - `LICENSE`: GNU GPL3 details Directories ----------- - `contrib/`: additional utilities - `doc/`: DocBook-based documentation files - `expected/`: expected regression test output - `scripts/`: example scripts - `sql/`: regression test input Support and Assistance ---------------------- EnterpriseDB provides 24x7 production support for `repmgr`, including configuration assistance, installation verification and training for running a robust replication cluster. For further details see: * [EDB Support Services](https://www.enterprisedb.com/support/postgresql-support-overview-get-the-most-out-of-postgresql) There is a mailing list/forum to discuss contributions or issues: * https://groups.google.com/group/repmgr The IRC channel #repmgr is registered with freenode. Please report bugs and other issues to: * https://github.com/EnterpriseDB/repmgr Further information is available at https://repmgr.org/ We'd love to hear from you about how you use repmgr. Case studies and news are always welcome. Thanks from the repmgr core team. * Ian Barwick * Jaime Casanova * Abhijit Menon-Sen * Simon Riggs * Cedric Villemain Further reading --------------- * [repmgr documentation](https://repmgr.org/docs/current/index.html) * [How to Automate PostgreSQL 12 Replication and Failover with repmgr - Part 1](https://www.2ndquadrant.com/en/blog/how-to-automate-postgresql-12-replication-and-failover-with-repmgr-part-1/) * [How to Automate PostgreSQL 12 Replication and Failover with repmgr - Part 2](https://www.2ndquadrant.com/en/blog/how-to-automate-postgresql-12-replication-and-failover-with-repmgr-part-2/) * [How to implement repmgr for PostgreSQL automatic failover](https://www.enterprisedb.com/postgres-tutorials/how-implement-repmgr-postgresql-automatic-failover) repmgr-5.3.1/TODO.md000066400000000000000000000012741420262710000141010ustar00rootroot00000000000000TODO ==== This file contains a list of improvements which are desirable and/or have been requested, and which we aim to address/implement when time and resources permit. It is *not* a roadmap and there's no guarantee of any item being implemented within any given timeframe. Enable suspension of repmgrd failover ------------------------------------- When performing maintenance, e.g. a switchover, it's necessary to stop all repmgrd nodes to prevent unintended failover; this is obviously inconvenient. We'll need to implement some way of notifying each repmgrd to suspend automatic failover until further notice. Requested in GitHub #410 ( https://github.com/EnterpriseDB/repmgr/issues/410 ) repmgr-5.3.1/compat.c000066400000000000000000000065531420262710000144460ustar00rootroot00000000000000/* * * compat.c * Provides a couple of useful string utility functions adapted * from the backend code, which are not publicly exposed in all * supported PostgreSQL versions. They're unlikely to change but * it would be worth keeping an eye on them for any fixes/improvements. * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . * */ #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 adapted from src/fe_utils/string_utils.c (before 9.6 * located in: src/bin/pg_dump/dumputils.c) */ 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 */ 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 if (*p == '&') appendPQExpBufferStr(buf, "\\&"); else appendPQExpBufferChar(buf, *p); } appendPQExpBufferChar(buf, '\''); } /* * Adapted from: src/fe_utils/string_utils.c */ void appendRemoteShellString(PQExpBuffer buf, const char *str) { const char *p; appendPQExpBufferStr(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 if (*p == '&') appendPQExpBufferStr(buf, "\\&"); else appendPQExpBufferChar(buf, *p); } appendPQExpBufferStr(buf, "\\'"); } repmgr-5.3.1/compat.h000066400000000000000000000021421420262710000144410ustar00rootroot00000000000000/* * compat.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . * */ #ifndef _COMPAT_H_ #define _COMPAT_H_ extern void appendConnStrVal(PQExpBuffer buf, const char *str); extern void appendShellString(PQExpBuffer buf, const char *str); extern void appendRemoteShellString(PQExpBuffer buf, const char *str); #endif repmgr-5.3.1/config.h.in000066400000000000000000000001011420262710000150210ustar00rootroot00000000000000/* config.h.in. Generated from configure.in by autoheader. */ repmgr-5.3.1/configdata.c000066400000000000000000000516051420262710000152600ustar00rootroot00000000000000/* * configdata.c - contains structs with parsed configuration data * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #include "repmgr.h" #include "configfile.h" /* * Parsed configuration settings are stored here */ t_configuration_options config_file_options; /* * Configuration settings are defined here */ struct ConfigFileSetting config_file_settings[] = { /* ================ * node information * ================ */ /* node_id */ { "node_id", CONFIG_INT, { .intptr = &config_file_options.node_id }, { .intdefault = UNKNOWN_NODE_ID }, { .intminval = MIN_NODE_ID }, {}, {} }, /* node_name */ { "node_name", CONFIG_STRING, { .strptr = config_file_options.node_name }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.node_name) }, {} }, /* conninfo */ { "conninfo", CONFIG_STRING, { .strptr = config_file_options.conninfo }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.conninfo) }, {} }, /* replication_user */ { "replication_user", CONFIG_STRING, { .strptr = config_file_options.replication_user }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.replication_user) }, {} }, /* data_directory */ { "data_directory", CONFIG_STRING, { .strptr = config_file_options.data_directory }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.data_directory) }, { .postprocess_func = &repmgr_canonicalize_path } }, /* config_directory */ { "config_directory", CONFIG_STRING, { .strptr = config_file_options.config_directory }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.config_directory) }, { .postprocess_func = &repmgr_canonicalize_path } }, /* pg_bindir */ { "pg_bindir", CONFIG_STRING, { .strptr = config_file_options.pg_bindir }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.pg_bindir) }, { .postprocess_func = &repmgr_canonicalize_path } }, /* repmgr_bindir */ { "repmgr_bindir", CONFIG_STRING, { .strptr = config_file_options.repmgr_bindir }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.repmgr_bindir) }, { .postprocess_func = &repmgr_canonicalize_path } }, /* replication_type */ { "replication_type", CONFIG_REPLICATION_TYPE, { .replicationtypeptr = &config_file_options.replication_type }, { .replicationtypedefault = DEFAULT_REPLICATION_TYPE }, {}, {}, {} }, /* ================ * logging settings * ================ */ /* * log_level * NOTE: the default for "log_level" is set in log.c and does not need * to be initialised here */ { "log_level", CONFIG_STRING, { .strptr = config_file_options.log_level }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.log_level) }, {} }, /* log_facility */ { "log_facility", CONFIG_STRING, { .strptr = config_file_options.log_facility }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.log_facility) }, {} }, /* log_file */ { "log_file", CONFIG_STRING, { .strptr = config_file_options.log_file }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.log_file) }, { .postprocess_func = &repmgr_canonicalize_path } }, /* log_status_interval */ { "log_status_interval", CONFIG_INT, { .intptr = &config_file_options.log_status_interval }, { .intdefault = DEFAULT_LOG_STATUS_INTERVAL, }, { .intminval = 0 }, {}, {} }, /* ====================== * standby clone settings * ====================== */ /* use_replication_slots */ { "use_replication_slots", CONFIG_BOOL, { .boolptr = &config_file_options.use_replication_slots }, { .booldefault = DEFAULT_USE_REPLICATION_SLOTS }, {}, {}, {} }, /* pg_basebackup_options */ { "pg_basebackup_options", CONFIG_STRING, { .strptr = config_file_options.pg_basebackup_options }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.pg_basebackup_options) }, {} }, /* restore_command */ { "restore_command", CONFIG_STRING, { .strptr = config_file_options.restore_command }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.restore_command) }, {} }, /* tablespace_mapping */ { "tablespace_mapping", CONFIG_TABLESPACE_MAPPING, { .tablespacemappingptr = &config_file_options.tablespace_mapping }, {}, {}, {}, {} }, /* recovery_min_apply_delay */ { "recovery_min_apply_delay", CONFIG_STRING, { .strptr = config_file_options.recovery_min_apply_delay }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.recovery_min_apply_delay) }, { .process_func = &parse_time_unit_parameter, .providedptr = &config_file_options.recovery_min_apply_delay_provided } }, /* archive_cleanup_command */ { "archive_cleanup_command", CONFIG_STRING, { .strptr = config_file_options.archive_cleanup_command }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.archive_cleanup_command) }, {} }, /* use_primary_conninfo_password */ { "use_primary_conninfo_password", CONFIG_BOOL, { .boolptr = &config_file_options.use_primary_conninfo_password }, { .booldefault = DEFAULT_USE_PRIMARY_CONNINFO_PASSWORD }, {}, {}, {} }, /* passfile */ { "passfile", CONFIG_STRING, { .strptr = config_file_options.passfile }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.passfile) }, {} }, /* ====================== * standby clone settings * ====================== */ /* promote_check_timeout */ { "promote_check_timeout", CONFIG_INT, { .intptr = &config_file_options.promote_check_timeout }, { .intdefault = DEFAULT_PROMOTE_CHECK_TIMEOUT }, { .intminval = 1 }, {}, {} }, /* promote_check_interval */ { "promote_check_interval", CONFIG_INT, { .intptr = &config_file_options.promote_check_interval }, { .intdefault = DEFAULT_PROMOTE_CHECK_INTERVAL }, { .intminval = 1 }, {}, {} }, /* ======================= * standby follow settings * ======================= */ /* primary_follow_timeout */ { "primary_follow_timeout", CONFIG_INT, { .intptr = &config_file_options.primary_follow_timeout }, { .intdefault = DEFAULT_PRIMARY_FOLLOW_TIMEOUT, }, { .intminval = 1 }, {}, {} }, /* standby_follow_timeout */ { "standby_follow_timeout", CONFIG_INT, { .intptr = &config_file_options.standby_follow_timeout }, { .intdefault = DEFAULT_STANDBY_FOLLOW_TIMEOUT }, { .intminval = 1 }, {}, {} }, /* standby_follow_restart */ { "standby_follow_restart", CONFIG_BOOL, { .boolptr = &config_file_options.standby_follow_restart }, { .booldefault = DEFAULT_STANDBY_FOLLOW_RESTART }, {}, {}, {} }, /* =========================== * standby switchover settings * =========================== */ /* shutdown_check_timeout */ { "shutdown_check_timeout", CONFIG_INT, { .intptr = &config_file_options.shutdown_check_timeout }, { .intdefault = DEFAULT_SHUTDOWN_CHECK_TIMEOUT }, { .intminval = 1 }, {}, {} }, /* standby_reconnect_timeout */ { "standby_reconnect_timeout", CONFIG_INT, { .intptr = &config_file_options.standby_reconnect_timeout }, { .intdefault = DEFAULT_STANDBY_RECONNECT_TIMEOUT }, { .intminval = 1 }, {}, {} }, /* wal_receive_check_timeout */ { "wal_receive_check_timeout", CONFIG_INT, { .intptr = &config_file_options.wal_receive_check_timeout }, { .intdefault = DEFAULT_WAL_RECEIVE_CHECK_TIMEOUT }, { .intminval = 1 }, {}, {} }, /* ==================== * node rejoin settings * ==================== */ /* node_rejoin_timeout */ { "node_rejoin_timeout", CONFIG_INT, { .intptr = &config_file_options.node_rejoin_timeout }, { .intdefault = DEFAULT_NODE_REJOIN_TIMEOUT }, { .intminval = 1 }, {}, {} }, /* =================== * node check settings * =================== */ /* archive_ready_warning */ { "archive_ready_warning", CONFIG_INT, { .intptr = &config_file_options.archive_ready_warning }, { .intdefault = DEFAULT_ARCHIVE_READY_WARNING }, { .intminval = 1 }, {}, {} }, /* archive_ready_critical */ { "archive_ready_critical", CONFIG_INT, { .intptr = &config_file_options.archive_ready_critical }, { .intdefault = DEFAULT_ARCHIVE_READY_CRITICAL }, { .intminval = 1 }, {}, {} }, /* replication_lag_warning */ { "replication_lag_warning", CONFIG_INT, { .intptr = &config_file_options.replication_lag_warning }, { .intdefault = DEFAULT_REPLICATION_LAG_WARNING }, { .intminval = 1 }, {}, {} }, /* replication_lag_critical */ { "replication_lag_critical", CONFIG_INT, { .intptr = &config_file_options.replication_lag_critical }, { .intdefault = DEFAULT_REPLICATION_LAG_CRITICAL }, { .intminval = 1 }, {}, {} }, /* ================ * witness settings * ================ */ /* witness_sync_interval */ { "witness_sync_interval", CONFIG_INT, { .intptr = &config_file_options.witness_sync_interval }, { .intdefault = DEFAULT_WITNESS_SYNC_INTERVAL }, { .intminval = 1 }, {}, {} }, /* ================ * repmgrd settings * ================ */ /* failover */ { "failover", CONFIG_FAILOVER_MODE, { .failovermodeptr = &config_file_options.failover }, { .failovermodedefault = FAILOVER_MANUAL }, {}, {}, {} }, /* location */ { "location", CONFIG_STRING, { .strptr = config_file_options.location }, { .strdefault = DEFAULT_LOCATION }, {}, { .strmaxlen = sizeof(config_file_options.location) }, {} }, /* priority */ { "priority", CONFIG_INT, { .intptr = &config_file_options.priority }, { .intdefault = DEFAULT_PRIORITY, }, { .intminval = 0 }, {}, {} }, /* promote_command */ { "promote_command", CONFIG_STRING, { .strptr = config_file_options.promote_command }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.promote_command) }, {} }, /* follow_command */ { "follow_command", CONFIG_STRING, { .strptr = config_file_options.follow_command }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.follow_command) }, {} }, /* monitor_interval_secs */ { "monitor_interval_secs", CONFIG_INT, { .intptr = &config_file_options.monitor_interval_secs }, { .intdefault = DEFAULT_MONITORING_INTERVAL }, { .intminval = 1 }, {}, {} }, /* reconnect_attempts */ { "reconnect_attempts", CONFIG_INT, { .intptr = &config_file_options.reconnect_attempts }, { .intdefault = DEFAULT_RECONNECTION_ATTEMPTS }, { .intminval = 0 }, {}, {} }, /* reconnect_interval */ { "reconnect_interval", CONFIG_INT, { .intptr = &config_file_options.reconnect_interval }, { .intdefault = DEFAULT_RECONNECTION_INTERVAL }, { .intminval = 0 }, {}, {} }, /* monitoring_history */ { "monitoring_history", CONFIG_BOOL, { .boolptr = &config_file_options.monitoring_history }, { .booldefault = DEFAULT_MONITORING_HISTORY }, {}, {}, {} }, /* degraded_monitoring_timeout */ { "degraded_monitoring_timeout", CONFIG_INT, { .intptr = &config_file_options.degraded_monitoring_timeout }, { .intdefault = DEFAULT_DEGRADED_MONITORING_TIMEOUT }, { .intminval = -1 }, {}, {} }, /* async_query_timeout */ { "async_query_timeout", CONFIG_INT, { .intptr = &config_file_options.async_query_timeout }, { .intdefault = DEFAULT_ASYNC_QUERY_TIMEOUT }, { .intminval = 0 }, {}, {} }, /* primary_notification_timeout */ { "primary_notification_timeout", CONFIG_INT, { .intptr = &config_file_options.primary_notification_timeout }, { .intdefault = DEFAULT_PRIMARY_NOTIFICATION_TIMEOUT }, { .intminval = 0 }, {}, {} }, /* repmgrd_standby_startup_timeout */ { "repmgrd_standby_startup_timeout", CONFIG_INT, { .intptr = &config_file_options.repmgrd_standby_startup_timeout }, { .intdefault = DEFAULT_REPMGRD_STANDBY_STARTUP_TIMEOUT }, { .intminval = 0 }, {}, {} }, /* repmgrd_pid_file */ { "repmgrd_pid_file", CONFIG_STRING, { .strptr = config_file_options.repmgrd_pid_file }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.repmgrd_pid_file) }, { .postprocess_func = &repmgr_canonicalize_path } }, /* repmgrd_exit_on_inactive_node */ { "repmgrd_exit_on_inactive_node", CONFIG_BOOL, { .boolptr = &config_file_options.repmgrd_exit_on_inactive_node}, { .booldefault = DEFAULT_REPMGRD_EXIT_ON_INACTIVE_NODE }, {}, {}, {} }, /* standby_disconnect_on_failover */ { "standby_disconnect_on_failover", CONFIG_BOOL, { .boolptr = &config_file_options.standby_disconnect_on_failover }, { .booldefault = DEFAULT_STANDBY_DISCONNECT_ON_FAILOVER }, {}, {}, {} }, /* sibling_nodes_disconnect_timeout */ { "sibling_nodes_disconnect_timeout", CONFIG_INT, { .intptr = &config_file_options.sibling_nodes_disconnect_timeout }, { .intdefault = DEFAULT_SIBLING_NODES_DISCONNECT_TIMEOUT }, { .intminval = 0 }, {}, {} }, /* connection_check_type */ { "connection_check_type", CONFIG_CONNECTION_CHECK_TYPE, { .checktypeptr = &config_file_options.connection_check_type }, { .checktypedefault = DEFAULT_CONNECTION_CHECK_TYPE }, {}, {}, {} }, /* primary_visibility_consensus */ { "primary_visibility_consensus", CONFIG_BOOL, { .boolptr = &config_file_options.primary_visibility_consensus }, { .booldefault = DEFAULT_PRIMARY_VISIBILITY_CONSENSUS }, {}, {}, {} }, /* always_promote */ { "always_promote", CONFIG_BOOL, { .boolptr = &config_file_options.always_promote }, { .booldefault = DEFAULT_ALWAYS_PROMOTE }, {}, {}, {} }, /* failover_validation_command */ { "failover_validation_command", CONFIG_STRING, { .strptr = config_file_options.failover_validation_command }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.failover_validation_command) }, {} }, /* election_rerun_interval */ { "election_rerun_interval", CONFIG_INT, { .intptr = &config_file_options.election_rerun_interval }, { .intdefault = DEFAULT_ELECTION_RERUN_INTERVAL }, { .intminval = 1 }, {}, {} }, /* child_nodes_check_interval */ { "child_nodes_check_interval", CONFIG_INT, { .intptr = &config_file_options.child_nodes_check_interval }, { .intdefault = DEFAULT_CHILD_NODES_CHECK_INTERVAL }, { .intminval = 1 }, {}, {} }, /* child_nodes_disconnect_min_count */ { "child_nodes_disconnect_min_count", CONFIG_INT, { .intptr = &config_file_options.child_nodes_disconnect_min_count }, { .intdefault = DEFAULT_CHILD_NODES_DISCONNECT_MIN_COUNT }, { .intminval = -1 }, {}, {} }, /* child_nodes_connected_min_count */ { "child_nodes_connected_min_count", CONFIG_INT, { .intptr = &config_file_options.child_nodes_connected_min_count }, { .intdefault = DEFAULT_CHILD_NODES_CONNECTED_MIN_COUNT}, { .intminval = -1 }, {}, {} }, /* child_nodes_connected_include_witness */ { "child_nodes_connected_include_witness", CONFIG_BOOL, { .boolptr = &config_file_options.child_nodes_connected_include_witness }, { .booldefault = DEFAULT_CHILD_NODES_CONNECTED_INCLUDE_WITNESS }, {}, {}, {} }, /* child_nodes_disconnect_timeout */ { "child_nodes_disconnect_timeout", CONFIG_INT, { .intptr = &config_file_options.child_nodes_disconnect_timeout }, { .intdefault = DEFAULT_CHILD_NODES_DISCONNECT_TIMEOUT }, { .intminval = 0 }, {}, {} }, /* child_nodes_disconnect_command */ { "child_nodes_disconnect_command", CONFIG_STRING, { .strptr = config_file_options.child_nodes_disconnect_command }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.child_nodes_disconnect_command) }, {} }, /* ================ * service settings * ================ */ /* pg_ctl_options */ { "pg_ctl_options", CONFIG_STRING, { .strptr = config_file_options.pg_ctl_options }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.pg_ctl_options) }, {} }, /* service_start_command */ { "service_start_command", CONFIG_STRING, { .strptr = config_file_options.service_start_command }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.service_start_command) }, {} }, /* service_stop_command */ { "service_stop_command", CONFIG_STRING, { .strptr = config_file_options.service_stop_command }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.service_stop_command) }, {} }, /* service_restart_command */ { "service_restart_command", CONFIG_STRING, { .strptr = config_file_options.service_restart_command }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.service_restart_command) }, {} }, /* service_reload_command */ { "service_reload_command", CONFIG_STRING, { .strptr = config_file_options.service_reload_command }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.service_reload_command) }, {} }, /* service_promote_command */ { "service_promote_command", CONFIG_STRING, { .strptr = config_file_options.service_promote_command }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.service_promote_command) }, {} }, /* ======================== * repmgrd service settings * ======================== */ /* repmgrd_service_start_command */ { "repmgrd_service_start_command", CONFIG_STRING, { .strptr = config_file_options.repmgrd_service_start_command }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.repmgrd_service_start_command) }, {} }, /* repmgrd_service_stop_command */ { "repmgrd_service_stop_command", CONFIG_STRING, { .strptr = config_file_options.repmgrd_service_stop_command }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.repmgrd_service_stop_command) }, {} }, /* =========================== * event notification settings * =========================== */ /* event_notification_command */ { "event_notification_command", CONFIG_STRING, { .strptr = config_file_options.event_notification_command }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.event_notification_command) }, {} }, { "event_notifications", CONFIG_EVENT_NOTIFICATION_LIST, { .notificationlistptr = &config_file_options.event_notifications }, {}, {}, {}, {} }, /* =============== * barman settings * =============== */ /* barman_host */ { "barman_host", CONFIG_STRING, { .strptr = config_file_options.barman_host }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.barman_host) }, {} }, /* barman_server */ { "barman_server", CONFIG_STRING, { .strptr = config_file_options.barman_server }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.barman_server) }, {} }, /* barman_config */ { "barman_config", CONFIG_STRING, { .strptr = config_file_options.barman_config }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.barman_config) }, {} }, /* ================== * rsync/ssh settings * ================== */ /* rsync_options */ { "rsync_options", CONFIG_STRING, { .strptr = config_file_options.rsync_options }, { .strdefault = "" }, {}, { .strmaxlen = sizeof(config_file_options.rsync_options) }, {} }, /* ssh_options */ { "ssh_options", CONFIG_STRING, { .strptr = config_file_options.ssh_options }, { .strdefault = DEFAULT_SSH_OPTIONS }, {}, { .strmaxlen = sizeof(config_file_options.ssh_options) }, {} }, /* ================================== * undocumented experimental settings * ================================== */ /* reconnect_loop_sync */ { "reconnect_loop_sync", CONFIG_BOOL, { .boolptr = &config_file_options.reconnect_loop_sync }, { .booldefault = false }, {}, {}, {} }, /* ========================== * undocumented test settings * ========================== */ /* promote_delay */ { "promote_delay", CONFIG_INT, { .intptr = &config_file_options.promote_delay }, { .intdefault = 0 }, { .intminval = 1 }, {}, {} }, /* failover_delay */ { "failover_delay", CONFIG_INT, { .intptr = &config_file_options.failover_delay }, { .intdefault = 0 }, { .intminval = 1 }, {}, {} }, { "connection_check_query", CONFIG_STRING, { .strptr = config_file_options.connection_check_query }, { .strdefault = "SELECT 1" }, {}, { .strmaxlen = sizeof(config_file_options.connection_check_query) }, {} }, /* End-of-list marker */ { NULL, CONFIG_INT, {}, {}, {}, {}, {} } }; repmgr-5.3.1/configfile-scan.l000066400000000000000000000370721420262710000162230ustar00rootroot00000000000000/* * Scanner for the configuration file */ %{ #include #include #include #include "repmgr.h" #include "configfile.h" /* * flex emits a yy_fatal_error() function that it calls in response to * critical errors like malloc failure, file I/O errors, and detection of * internal inconsistency. That function prints a message and calls exit(). * Mutate it to instead call our handler, which jumps out of the parser. */ #undef fprintf #define fprintf(file, fmt, msg) CONF_flex_fatal(msg) enum { CONF_ID = 1, CONF_STRING = 2, CONF_INTEGER = 3, CONF_REAL = 4, CONF_EQUALS = 5, CONF_UNQUOTED_STRING = 6, CONF_QUALIFIED_ID = 7, CONF_EOL = 99, CONF_ERROR = 100 }; static unsigned int ConfigFileLineno; static const char *CONF_flex_fatal_errmsg; static sigjmp_buf *CONF_flex_fatal_jmp; static char *CONF_scanstr(const char *s); static int CONF_flex_fatal(const char *msg); static bool ProcessConfigFile(const char *base_dir, const char *config_file, const char *calling_file, bool strict, int depth, KeyValueList *contents, ItemList *error_list, ItemList *warning_list); static bool ProcessConfigFp(FILE *fp, const char *config_file, const char *calling_file, int depth, const char *base_dir, KeyValueList *contents, ItemList *error_list, ItemList *warning_list); static bool ProcessConfigDirectory(const char *base_dir, const char *includedir, const char *calling_file, int depth, KeyValueList *contents, ItemList *error_list, ItemList *warning_list); static char *AbsoluteConfigLocation(const char *base_dir, const char *location, const char *calling_file); %} %option 8bit %option never-interactive %option nodefault %option noinput %option nounput %option noyywrap %option warn %option prefix="CONF_yy" SIGN ("-"|"+") DIGIT [0-9] HEXDIGIT [0-9a-fA-F] UNIT_LETTER [a-zA-Z] INTEGER {SIGN}?({DIGIT}+|0x{HEXDIGIT}+){UNIT_LETTER}* EXPONENT [Ee]{SIGN}?{DIGIT}+ REAL {SIGN}?{DIGIT}*"."{DIGIT}*{EXPONENT}? LETTER [A-Za-z_\200-\377] LETTER_OR_DIGIT [A-Za-z_0-9\200-\377] ID {LETTER}{LETTER_OR_DIGIT}* QUALIFIED_ID {ID}"."{ID} UNQUOTED_STRING {LETTER}({LETTER_OR_DIGIT}|[-._:/])* STRING \'([^'\\\n]|\\.|\'\')*\' %% \n ConfigFileLineno++; return CONF_EOL; [ \t\r]+ /* eat whitespace */ #.* /* eat comment (.* matches anything until newline) */ {ID} return CONF_ID; {QUALIFIED_ID} return CONF_QUALIFIED_ID; {STRING} return CONF_STRING; {UNQUOTED_STRING} return CONF_UNQUOTED_STRING; {INTEGER} return CONF_INTEGER; {REAL} return CONF_REAL; = return CONF_EQUALS; . return CONF_ERROR; %% extern bool ProcessRepmgrConfigFile(const char *config_file, const char *base_dir, ItemList *error_list, ItemList *warning_list) { return ProcessConfigFile(base_dir, config_file, NULL, true, 0, NULL, error_list, warning_list); } extern bool ProcessPostgresConfigFile(const char *config_file, const char *base_dir, bool strict, KeyValueList *contents, ItemList *error_list, ItemList *warning_list) { return ProcessConfigFile(base_dir, config_file, NULL, strict, 0, contents, error_list, warning_list); } static bool ProcessConfigFile(const char *base_dir, const char *config_file, const char *calling_file, bool strict, int depth, KeyValueList *contents, ItemList *error_list, ItemList *warning_list) { char *abs_path; bool success = true; FILE *fp; /* * Reject file name that is all-blank (including empty), as that leads to * confusion --- we'd try to read the containing directory as a file. */ if (strspn(config_file, " \t\r\n") == strlen(config_file)) { return false; } /* * Reject too-deep include nesting depth. This is just a safety check to * avoid dumping core due to stack overflow if an include file loops back * to itself. The maximum nesting depth is pretty arbitrary. */ if (depth > 10) { item_list_append_format(error_list, _("could not open configuration file \"%s\": maximum nesting depth exceeded"), config_file); return false; } abs_path = AbsoluteConfigLocation(base_dir, config_file, calling_file); /* Reject direct recursion */ if (calling_file && strcmp(abs_path, calling_file) == 0) { item_list_append_format(error_list, _("configuration file recursion in \"%s\""), calling_file); pfree(abs_path); return false; } fp = fopen(abs_path, "r"); if (!fp) { if (strict == false) { item_list_append_format(error_list, "skipping configuration file \"%s\"", abs_path); } else { item_list_append_format(error_list, "could not open configuration file \"%s\": %s", abs_path, strerror(errno)); success = false; } } else { success = ProcessConfigFp(fp, abs_path, calling_file, depth + 1, base_dir, contents, error_list, warning_list); } free(abs_path); return success; } static bool ProcessConfigFp(FILE *fp, const char *config_file, const char *calling_file, int depth, const char *base_dir, KeyValueList *contents, ItemList *error_list, ItemList *warning_list) { volatile bool OK = true; volatile YY_BUFFER_STATE lex_buffer = NULL; sigjmp_buf flex_fatal_jmp; int errorcount; int token; if (sigsetjmp(flex_fatal_jmp, 1) == 0) { CONF_flex_fatal_jmp = &flex_fatal_jmp; } else { /* * Regain control after a fatal, internal flex error. It may have * corrupted parser state. Consequently, abandon the file, but trust * that the state remains sane enough for yy_delete_buffer(). */ item_list_append_format(error_list, "%s at file \"%s\" line %u", CONF_flex_fatal_errmsg, config_file, ConfigFileLineno); OK = false; goto cleanup; } /* * Parse */ ConfigFileLineno = 1; errorcount = 0; lex_buffer = yy_create_buffer(fp, YY_BUF_SIZE); yy_switch_to_buffer(lex_buffer); /* This loop iterates once per logical line */ while ((token = yylex())) { char *opt_name = NULL; char *opt_value = NULL; if (token == CONF_EOL) /* empty or comment line */ continue; /* first token on line is option name */ if (token != CONF_ID && token != CONF_QUALIFIED_ID) goto parse_error; opt_name = pstrdup(yytext); /* next we have an optional equal sign; discard if present */ token = yylex(); if (token == CONF_EQUALS) token = yylex(); /* now we must have the option value */ if (token != CONF_ID && token != CONF_STRING && token != CONF_INTEGER && token != CONF_REAL && token != CONF_UNQUOTED_STRING) goto parse_error; if (token == CONF_STRING) /* strip quotes and escapes */ opt_value = CONF_scanstr(yytext); else opt_value = pstrdup(yytext); /* now we'd like an end of line, or possibly EOF */ token = yylex(); if (token != CONF_EOL) { if (token != 0) goto parse_error; /* treat EOF like \n for line numbering purposes, cf bug 4752 */ ConfigFileLineno++; } /* Handle include files */ if (base_dir != NULL && strcasecmp(opt_name, "include_dir") == 0) { /* * An include_dir directive isn't a variable and should be * processed immediately. */ if (!ProcessConfigDirectory(base_dir, opt_value, config_file, depth + 1, contents, error_list, warning_list)) OK = false; yy_switch_to_buffer(lex_buffer); pfree(opt_name); pfree(opt_value); } else if (base_dir != NULL && strcasecmp(opt_name, "include_if_exists") == 0) { if (!ProcessConfigFile(base_dir, opt_value, config_file, false, depth + 1, contents, error_list, warning_list)) OK = false; yy_switch_to_buffer(lex_buffer); pfree(opt_name); pfree(opt_value); } else if (base_dir != NULL && strcasecmp(opt_name, "include") == 0) { if (!ProcessConfigFile(base_dir, opt_value, config_file, true, depth + 1, contents, error_list, warning_list)) OK = false; yy_switch_to_buffer(lex_buffer); pfree(opt_name); pfree(opt_value); } else { /* OK, process the option name and value */ if (contents != NULL) { key_value_list_replace_or_set(contents, opt_name, opt_value); } else { parse_configuration_item(error_list, warning_list, opt_name, opt_value); } } /* break out of loop if read EOF, else loop for next line */ if (token == 0) break; continue; parse_error: /* release storage if we allocated any on this line */ if (opt_name) pfree(opt_name); if (opt_value) pfree(opt_value); /* report the error */ if (token == CONF_EOL || token == 0) { item_list_append_format(error_list, _("syntax error in file \"%s\" line %u, near end of line"), config_file, ConfigFileLineno - 1); } else { item_list_append_format(error_list, _("syntax error in file \"%s\" line %u, near token \"%s\""), config_file, ConfigFileLineno, yytext); } OK = false; errorcount++; /* * To avoid producing too much noise when fed a totally bogus file, * give up after 100 syntax errors per file (an arbitrary number). * Also, if we're only logging the errors at DEBUG level anyway, might * as well give up immediately. (This prevents postmaster children * from bloating the logs with duplicate complaints.) */ if (errorcount >= 100) { fprintf(stderr, _("too many syntax errors found, abandoning file \"%s\"\n"), config_file); break; } /* resync to next end-of-line or EOF */ while (token != CONF_EOL && token != 0) token = yylex(); /* break out of loop on EOF */ if (token == 0) break; } cleanup: yy_delete_buffer(lex_buffer); return OK; } /* * Read and parse all config files in a subdirectory in alphabetical order * * includedir is the absolute or relative path to the subdirectory to scan. * * See ProcessConfigFp for further details. */ static bool ProcessConfigDirectory(const char *base_dir, const char *includedir, const char *calling_file, int depth, KeyValueList *contents, ItemList *error_list, ItemList *warning_list) { char *directory; DIR *d; struct dirent *de; char **filenames; int num_filenames; int size_filenames; bool status; /* * Reject directory name that is all-blank (including empty), as that * leads to confusion --- we'd read the containing directory, typically * resulting in recursive inclusion of the same file(s). */ if (strspn(includedir, " \t\r\n") == strlen(includedir)) { item_list_append_format(error_list, _("empty configuration directory name: \"%s\""), includedir); return false; } directory = AbsoluteConfigLocation(base_dir, includedir, calling_file); d = opendir(directory); if (d == NULL) { item_list_append_format(error_list, _("could not open configuration directory \"%s\": %s"), directory, strerror(errno)); status = false; goto cleanup; } /* * Read the directory and put the filenames in an array, so we can sort * them prior to processing the contents. */ size_filenames = 32; filenames = (char **) palloc(size_filenames * sizeof(char *)); num_filenames = 0; while ((de = readdir(d)) != NULL) { struct stat st; char filename[MAXPGPATH]; /* * Only parse files with names ending in ".conf". Explicitly reject * files starting with ".". This excludes things like "." and "..", * as well as typical hidden files, backup files, and editor debris. */ if (strlen(de->d_name) < 6) continue; if (de->d_name[0] == '.') continue; if (strcmp(de->d_name + strlen(de->d_name) - 5, ".conf") != 0) continue; join_path_components(filename, directory, de->d_name); canonicalize_path(filename); if (stat(filename, &st) == 0) { if (!S_ISDIR(st.st_mode)) { /* Add file to array, increasing its size in blocks of 32 */ if (num_filenames >= size_filenames) { size_filenames += 32; filenames = (char **) repalloc(filenames, size_filenames * sizeof(char *)); } filenames[num_filenames] = pstrdup(filename); num_filenames++; } } else { /* * stat does not care about permissions, so the most likely reason * a file can't be accessed now is if it was removed between the * directory listing and now. */ item_list_append_format(error_list, _("could not stat file \"%s\": %s"), filename, strerror(errno)); status = false; goto cleanup; } } if (num_filenames > 0) { int i; qsort(filenames, num_filenames, sizeof(char *), pg_qsort_strcmp); for (i = 0; i < num_filenames; i++) { if (!ProcessConfigFile(base_dir, filenames[i], calling_file, true, depth, contents, error_list, warning_list)) { status = false; goto cleanup; } } } status = true; cleanup: if (d) closedir(d); pfree(directory); return status; } /* * scanstr * * Strip the quotes surrounding the given string, and collapse any embedded * '' sequences and backslash escapes. * * the string returned is palloc'd and should eventually be pfree'd by the * caller. */ static char * CONF_scanstr(const char *s) { char *newStr; int len, i, j; Assert(s != NULL && s[0] == '\''); len = strlen(s); Assert(s != NULL); Assert(len >= 2); Assert(s[len - 1] == '\''); /* Skip the leading quote; we'll handle the trailing quote below */ s++, len--; /* Since len still includes trailing quote, this is enough space */ newStr = palloc(len); for (i = 0, j = 0; i < len; i++) { if (s[i] == '\\') { i++; switch (s[i]) { case 'b': newStr[j] = '\b'; break; case 'f': newStr[j] = '\f'; break; case 'n': newStr[j] = '\n'; break; case 'r': newStr[j] = '\r'; break; case 't': newStr[j] = '\t'; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { int k; long octVal = 0; for (k = 0; s[i + k] >= '0' && s[i + k] <= '7' && k < 3; k++) octVal = (octVal << 3) + (s[i + k] - '0'); i += k - 1; newStr[j] = ((char) octVal); } break; default: newStr[j] = s[i]; break; } /* switch */ } else if (s[i] == '\'' && s[i + 1] == '\'') { /* doubled quote becomes just one quote */ newStr[j] = s[++i]; } else newStr[j] = s[i]; j++; } /* We copied the ending quote to newStr, so replace with \0 */ Assert(j > 0 && j <= len); newStr[--j] = '\0'; return newStr; } /* * Given a configuration file or directory location that may be a relative * path, return an absolute one. We consider the location to be relative to * the directory holding the calling file, or to DataDir if no calling file. */ static char * AbsoluteConfigLocation(const char *base_dir, const char *location, const char *calling_file) { char abs_path[MAXPGPATH]; if (is_absolute_path(location)) return strdup(location); if (calling_file != NULL) { strlcpy(abs_path, calling_file, sizeof(abs_path)); get_parent_directory(abs_path); join_path_components(abs_path, abs_path, location); canonicalize_path(abs_path); } else if (base_dir != NULL) { join_path_components(abs_path, base_dir, location); canonicalize_path(abs_path); } else { strlcpy(abs_path, location, sizeof(abs_path)); } return strdup(abs_path); } /* * Flex fatal errors bring us here. Stash the error message and jump back to * ParseConfigFp(). Assume all msg arguments point to string constants; this * holds for flex 2.5.31 (earliest we support) and flex 2.5.35 (latest as of * this writing). Otherwise, we would need to copy the message. * * We return "int" since this takes the place of calls to fprintf(). */ static int CONF_flex_fatal(const char *msg) { CONF_flex_fatal_errmsg = msg; siglongjmp(*CONF_flex_fatal_jmp, 1); return 0; /* keep compiler quiet */ } repmgr-5.3.1/configfile.c000066400000000000000000001705201420262710000152640ustar00rootroot00000000000000/* * configfile.c - parse repmgr.conf and other configuration-related functionality * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #include /* for stat() */ #include "repmgr.h" #include "configfile.h" #include "log.h" #include #if (PG_ACTUAL_VERSION_NUM >= 100000) #include /* for durable_rename() */ #endif const static char *_progname = NULL; char config_file_path[MAXPGPATH] = ""; static bool config_file_provided = false; bool config_file_found = false; static void parse_config(bool terse); static void _parse_config(ItemList *error_list, ItemList *warning_list); static void _parse_line(char *buf, char *name, char *value); static void parse_event_notifications_list(EventNotificationList *event_notifications, const char *arg); static void clear_event_notification_list(EventNotificationList *event_notifications); static void copy_config_file_options(t_configuration_options *original, t_configuration_options *copy); static void tablespace_list_append(TablespaceList *tablespace_mapping, const char *arg); static void tablespace_list_copy(t_configuration_options *original, t_configuration_options *copy); static void tablespace_list_free(t_configuration_options *options); static void exit_with_config_file_errors(ItemList *config_errors, ItemList *config_warnings, bool terse); void set_progname(const char *argv0) { _progname = get_progname(argv0); } const char * progname(void) { return _progname; } void load_config(const char *config_file, bool verbose, bool terse, char *argv0) { struct stat stat_config; /* * If a configuration file was provided, check it exists, otherwise emit * an error and terminate. We assume that if a user explicitly provides a * configuration file, they'll want to make sure it's used and not fall * back to any of the defaults. */ if (config_file != NULL && config_file[0] != '\0') { strncpy(config_file_path, config_file, MAXPGPATH); canonicalize_path(config_file_path); /* relative path supplied - convert to absolute path */ if (config_file_path[0] != '/') { PQExpBufferData fullpath; char *pwd = NULL; initPQExpBuffer(&fullpath); /* * we'll attempt to use $PWD to derive the effective path; getcwd() * will likely resolve symlinks, which may result in a path which * isn't permanent (e.g. if filesystem mountpoints change). */ pwd = getenv("PWD"); if (pwd != NULL) { appendPQExpBufferStr(&fullpath, pwd); } else { /* $PWD not available - fall back to getcwd() */ char cwd[MAXPGPATH] = ""; if (getcwd(cwd, MAXPGPATH) == NULL) { log_error(_("unable to execute getcwd()")); log_detail("%s", strerror(errno)); termPQExpBuffer(&fullpath); exit(ERR_BAD_CONFIG); } appendPQExpBufferStr(&fullpath, cwd); } appendPQExpBuffer(&fullpath, "/%s", config_file_path); log_debug("relative configuration file converted to:\n \"%s\"", fullpath.data); strncpy(config_file_path, fullpath.data, MAXPGPATH); termPQExpBuffer(&fullpath); canonicalize_path(config_file_path); } if (stat(config_file_path, &stat_config) != 0) { log_error(_("provided configuration file \"%s\" not found"), config_file); log_detail("%s", strerror(errno)); exit(ERR_BAD_CONFIG); } if (verbose == true) { log_notice(_("using provided configuration file \"%s\""), config_file); } config_file_provided = true; config_file_found = true; } /*----------- * If no configuration file was provided, attempt to find a default file * in this order: * - location provided by packager * - current directory * - /etc/repmgr.conf * - default sysconfdir * * here we just check for the existence of the file; parse_config() will * handle read errors etc. * *----------- */ if (config_file_provided == false) { /* packagers: if feasible, patch configuration file path into "package_conf_file" */ char package_conf_file[MAXPGPATH] = ""; char my_exec_path[MAXPGPATH] = ""; char sysconf_etc_path[MAXPGPATH] = ""; /* 1. location provided by packager */ if (package_conf_file[0] != '\0') { if (verbose == true) fprintf(stdout, _("INFO: checking for package configuration file \"%s\"\n"), package_conf_file); if (stat(package_conf_file, &stat_config) == 0) { strncpy(config_file_path, package_conf_file, MAXPGPATH); config_file_found = true; goto end_search; } } /* 2 "./repmgr.conf" */ log_verbose(LOG_INFO, _("looking for configuration file in current directory\n")); maxpath_snprintf(config_file_path, "./%s", CONFIG_FILE_NAME); canonicalize_path(config_file_path); if (stat(config_file_path, &stat_config) == 0) { config_file_found = true; goto end_search; } /* 3. "/etc/repmgr.conf" */ if (verbose == true) fprintf(stdout, _("INFO: looking for configuration file in /etc\n")); maxpath_snprintf(config_file_path, "/etc/%s", CONFIG_FILE_NAME); if (stat(config_file_path, &stat_config) == 0) { config_file_found = true; goto end_search; } /* 4. default sysconfdir */ if (find_my_exec(argv0, my_exec_path) < 0) { fprintf(stderr, _("ERROR: %s: could not find own program executable\n"), argv0); exit(EXIT_FAILURE); } get_etc_path(my_exec_path, sysconf_etc_path); if (verbose == true) fprintf(stdout, _("INFO: looking for configuration file in \"%s\"\n"), sysconf_etc_path); maxpath_snprintf(config_file_path, "%s/%s", sysconf_etc_path, CONFIG_FILE_NAME); if (stat(config_file_path, &stat_config) == 0) { config_file_found = true; goto end_search; } end_search: if (verbose == true) { if (config_file_found == true) { fprintf(stdout, _("INFO: configuration file found at: \"%s\"\n"), config_file_path); } else { fprintf(stdout, _("INFO: no configuration file provided or found\n")); } } } parse_config(terse); return; } static void parse_config(bool terse) { /* Collate configuration file errors here for friendlier reporting */ static ItemList config_errors = {NULL, NULL}; static ItemList config_warnings = {NULL, NULL}; _parse_config(&config_errors, &config_warnings); /* errors found - exit after printing details, and any warnings */ if (config_errors.head != NULL) { exit_with_config_file_errors(&config_errors, &config_warnings, terse); } if (terse == false && config_warnings.head != NULL) { log_warning(_("the following problems were found in the configuration file:")); print_item_list(&config_warnings); } return; } static void _parse_config(ItemList *error_list, ItemList *warning_list) { FILE *fp; char base_directory[MAXPGPATH]; bool config_ok; ConfigFileSetting *setting; int i = 0; /* * Clear lists pointing to allocated memory */ clear_event_notification_list(&config_file_options.event_notifications); tablespace_list_free(&config_file_options); /* * Initialise with default values */ setting = &config_file_settings[0]; do { switch (setting->type) { case CONFIG_INT: *setting->val.intptr = setting->defval.intdefault; break; case CONFIG_BOOL: *setting->val.boolptr = setting->defval.booldefault; break; case CONFIG_STRING: { memset((char *)setting->val.strptr, 0, setting->maxval.strmaxlen); if (setting->defval.strdefault != NULL) strncpy((char *)setting->val.strptr, setting->defval.strdefault, setting->maxval.strmaxlen); break; } case CONFIG_FAILOVER_MODE: *setting->val.failovermodeptr = setting->defval.failovermodedefault; break; case CONFIG_CONNECTION_CHECK_TYPE: *setting->val.checktypeptr = setting->defval.checktypedefault; break; case CONFIG_REPLICATION_TYPE: *setting->val.replicationtypeptr = setting->defval.replicationtypedefault; break; case CONFIG_EVENT_NOTIFICATION_LIST: case CONFIG_TABLESPACE_MAPPING: /* no default for these types; lists cleared above */ break; default: /* this should never happen */ log_error("unhandled setting type %i", (int)setting->type); } i++; setting = &config_file_settings[i]; } while (setting->name != NULL); /* * If no configuration file available (user didn't specify and none found * in the default locations), return with default values */ if (config_file_found == false) { log_verbose(LOG_NOTICE, _("no configuration file provided and no default file found - " "continuing with default values")); return; } /* * A configuration file has been found, either provided by the user or * found in one of the default locations. Sanity check whether we * can open it, and fail with an error about the nature of the file * (provided or default) if not. We do this here rather than having * to teach the configuration file parser the difference. */ fp = fopen(config_file_path, "r"); if (fp == NULL) { if (config_file_provided) { log_error(_("unable to open provided configuration file \"%s\"; terminating"), config_file_path); } else { log_error(_("unable to open default configuration file \"%s\"; terminating"), config_file_path); } exit(ERR_BAD_CONFIG); } fclose(fp); strncpy(base_directory, config_file_path, MAXPGPATH); canonicalize_path(base_directory); get_parent_directory(base_directory); config_ok = ProcessRepmgrConfigFile(config_file_path, base_directory, error_list, warning_list); /* * Perform some more complex checks which the file processing step can't do, * including checking for required parameters and sanity-checking parameters * with dependencies on other parameters. */ if (config_ok == true) { /* check required parameters */ if (config_file_options.node_id == UNKNOWN_NODE_ID) { item_list_append(error_list, _("\"node_id\": required parameter was not found")); } if (!strlen(config_file_options.node_name)) { item_list_append(error_list, _("\"node_name\": required parameter was not found")); } if (!strlen(config_file_options.data_directory)) { item_list_append(error_list, _("\"data_directory\": required parameter was not found")); } if (!strlen(config_file_options.conninfo)) { item_list_append(error_list, _("\"conninfo\": required parameter was not found")); } else { /* * Basic sanity check of provided conninfo string; this will catch any * invalid parameters (but not values). */ char *conninfo_errmsg = NULL; if (validate_conninfo_string(config_file_options.conninfo, &conninfo_errmsg) == false) { PQExpBufferData error_message_buf; initPQExpBuffer(&error_message_buf); appendPQExpBuffer(&error_message_buf, _("\"conninfo\": %s (provided: \"%s\")"), conninfo_errmsg, config_file_options.conninfo); item_list_append(error_list, error_message_buf.data); termPQExpBuffer(&error_message_buf); } } /* set values for parameters which default to other parameters */ /* * From 4.1, "repmgrd_standby_startup_timeout" replaces "standby_reconnect_timeout" * in repmgrd; fall back to "standby_reconnect_timeout" if no value explicitly provided */ if (config_file_options.repmgrd_standby_startup_timeout == -1) { config_file_options.repmgrd_standby_startup_timeout = config_file_options.standby_reconnect_timeout; } /* add warning about changed "barman_" parameter meanings */ if ((config_file_options.barman_host[0] == '\0' && config_file_options.barman_server[0] != '\0') || (config_file_options.barman_host[0] != '\0' && config_file_options.barman_server[0] == '\0')) { item_list_append(error_list, _("use \"barman_host\" for the hostname of the Barman server")); item_list_append(error_list, _("use \"barman_server\" for the name of the [server] section in the Barman configuration file")); } /* other sanity checks */ if (config_file_options.archive_ready_warning >= config_file_options.archive_ready_critical) { item_list_append(error_list, _("\"archive_ready_critical\" must be greater than \"archive_ready_warning\"")); } if (config_file_options.replication_lag_warning >= config_file_options.replication_lag_critical) { item_list_append(error_list, _("\"replication_lag_critical\" must be greater than \"replication_lag_warning\"")); } if (config_file_options.standby_reconnect_timeout < config_file_options.node_rejoin_timeout) { item_list_append(error_list, _("\"standby_reconnect_timeout\" must be equal to or greater than \"node_rejoin_timeout\"")); } } } void parse_configuration_item(ItemList *error_list, ItemList *warning_list, const char *name, const char *value) { ConfigFileSetting *setting = &config_file_settings[0]; int i = 0; do { if (strcmp(name, setting->name) == 0) { switch (setting->type) { /* Generic types */ case CONFIG_BOOL: { *(bool *)setting->val.boolptr = parse_bool(value, name, error_list); break; } case CONFIG_INT: { *(int *)setting->val.intptr = repmgr_atoi(value, name, error_list, setting->minval.intminval); break; } case CONFIG_STRING: { if (strlen(value) > setting->maxval.strmaxlen) { item_list_append_format(error_list, _("value for \"%s\" must contain fewer than %i characters (current length: %i)"), name, setting->maxval.strmaxlen, (int)strlen(value)); } else { /* custom function for processing this string value */ if (setting->process.process_func != NULL) { (*setting->process.process_func)(name, value, (char *)setting->val.strptr, error_list); } /* otherwise copy as-is */ else { strncpy((char *)setting->val.strptr, value, setting->maxval.strmaxlen); } /* post-processing, e.g. path canonicalisation */ if (setting->process.postprocess_func != NULL) { (*setting->process.postprocess_func)(name, value, (char *)setting->val.strptr, error_list); } if (setting->process.providedptr != NULL) { *(bool *)setting->process.providedptr = true; } } break; } /* repmgr types */ case CONFIG_FAILOVER_MODE: { if (strcmp(value, "manual") == 0) { *(failover_mode_opt *)setting->val.failovermodeptr = FAILOVER_MANUAL; } else if (strcmp(value, "automatic") == 0) { *(failover_mode_opt *)setting->val.failovermodeptr = FAILOVER_AUTOMATIC; } else { item_list_append_format(error_list, _("value for \"%s\" must be \"automatic\" or \"manual\"\n"), name); } break; } case CONFIG_CONNECTION_CHECK_TYPE: { if (strcasecmp(value, "ping") == 0) { *(ConnectionCheckType *)setting->val.checktypeptr = CHECK_PING; } else if (strcasecmp(value, "connection") == 0) { *(ConnectionCheckType *)setting->val.checktypeptr = CHECK_CONNECTION; } else if (strcasecmp(value, "query") == 0) { *(ConnectionCheckType *)setting->val.checktypeptr = CHECK_QUERY; } else { item_list_append_format(error_list, _("value for \"%s\" must be \"ping\", \"connection\" or \"query\"\n"), name); } break; } case CONFIG_REPLICATION_TYPE: { if (strcasecmp(value, "physical") == 0) { *(ReplicationType *)setting->val.replicationtypeptr = REPLICATION_TYPE_PHYSICAL; } else { item_list_append_format(error_list, _("value for \"%s\" must be \"physical\"\n"), name); } break; } case CONFIG_EVENT_NOTIFICATION_LIST: { parse_event_notifications_list((EventNotificationList *)setting->val.notificationlistptr, value); break; } case CONFIG_TABLESPACE_MAPPING: { tablespace_list_append((TablespaceList *)setting->val.tablespacemappingptr, value); break; } default: /* this should never happen */ log_error("encountered unknown configuration type %i when processing \"%s\"", (int)setting->type, setting->name); } /* Configuration item found - we can stop processing here */ return; } i++; setting = &config_file_settings[i]; } while (setting->name); /* If we reach here, the configuration item is either deprecated or unknown */ if (strcmp(name, "cluster") == 0) { item_list_append(warning_list, _("parameter \"cluster\" is deprecated and will be ignored")); } else if (strcmp(name, "node") == 0) { item_list_append(warning_list, _("parameter \"node\" has been renamed to \"node_id\"")); } else if (strcmp(name, "upstream_node") == 0) { item_list_append(warning_list, _("parameter \"upstream_node\" has been removed; use \"--upstream-node-id\" when cloning a standby")); } else if (strcmp(name, "loglevel") == 0) { item_list_append(warning_list, _("parameter \"loglevel\" has been renamed to \"log_level\"")); } else if (strcmp(name, "logfacility") == 0) { item_list_append(warning_list, _("parameter \"logfacility\" has been renamed to \"log_facility\"")); } else if (strcmp(name, "logfile") == 0) { item_list_append(warning_list, _("parameter \"logfile\" has been renamed to \"log_file\"")); } else if (strcmp(name, "master_reponse_timeout") == 0) { item_list_append(warning_list, _("parameter \"master_reponse_timeout\" has been removed; use \"async_query_timeout\" instead")); } else if (strcmp(name, "retry_promote_interval_secs") == 0) { item_list_append(warning_list, _("parameter \"retry_promote_interval_secs\" has been removed; use \"primary_notification_timeout\" instead")); } else { item_list_append_format(warning_list, _("%s='%s': unknown name/value pair provided; ignoring"), name, value); } } bool parse_recovery_conf(const char *data_dir, t_recovery_conf *conf) { char recovery_conf_path[MAXPGPATH] = ""; FILE *fp; char *s = NULL, buf[MAXLINELENGTH] = ""; char name[MAXLEN] = ""; char value[MAXLEN] = ""; snprintf(recovery_conf_path, MAXPGPATH, "%s/%s", data_dir, RECOVERY_COMMAND_FILE); fp = fopen(recovery_conf_path, "r"); if (fp == NULL) { return false; } /* Read file */ while ((s = fgets(buf, sizeof buf, fp)) != NULL) { /* Parse name/value pair from line */ _parse_line(buf, name, value); /* Skip blank lines */ if (!strlen(name)) continue; /* Skip comments */ if (name[0] == '#') continue; /* archive recovery settings */ if (strcmp(name, "restore_command") == 0) strncpy(conf->restore_command, value, MAXLEN); else if (strcmp(name, "archive_cleanup_command") == 0) strncpy(conf->archive_cleanup_command, value, MAXLEN); else if (strcmp(name, "recovery_end_command") == 0) strncpy(conf->recovery_end_command, value, MAXLEN); /* recovery target settings */ else if (strcmp(name, "recovery_target_name") == 0) strncpy(conf->recovery_target_name, value, MAXLEN); else if (strcmp(name, "recovery_target_time") == 0) strncpy(conf->recovery_target_time, value, MAXLEN); else if (strcmp(name, "recovery_target_xid") == 0) strncpy(conf->recovery_target_xid, value, MAXLEN); else if (strcmp(name, "recovery_target_inclusive") == 0) conf->recovery_target_inclusive = parse_bool(value, NULL, NULL); else if (strcmp(name, "recovery_target_timeline") == 0) { if (strncmp(value, "latest", MAXLEN) == 0) { conf->recovery_target_timeline = TARGET_TIMELINE_LATEST; } else { conf->recovery_target_timeline = atoi(value); } } else if (strcmp(name, "recovery_target_action") == 0) { if (strcmp(value, "pause") == 0) conf->recovery_target_action = RTA_PAUSE; else if (strcmp(value, "promote") == 0) conf->recovery_target_action = RTA_PROMOTE; else if (strcmp(value, "shutdown") == 0) conf->recovery_target_action = RTA_SHUTDOWN; } /* standby server settings */ else if (strcmp(name, "standby_mode") == 0) conf->standby_mode = parse_bool(value, NULL, NULL); else if (strcmp(name, "primary_conninfo") == 0) strncpy(conf->primary_conninfo, value, MAXLEN); else if (strcmp(name, "primary_slot_name") == 0) strncpy(conf->trigger_file, value, MAXLEN); else if (strcmp(name, "trigger_file") == 0) strncpy(conf->trigger_file, value, MAXLEN); else if (strcmp(name, "recovery_min_apply_delay") == 0) parse_time_unit_parameter(name, value, conf->recovery_min_apply_delay, NULL); } fclose(fp); return true; } void _parse_line(char *buf, char *name, char *value) { int i = 0; int j = 0; /* * Extract parameter name, if present */ for (; i < MAXLEN; ++i) { if (buf[i] == '=') break; switch (buf[i]) { /* Ignore whitespace */ case ' ': case '\n': case '\r': case '\t': continue; default: name[j++] = buf[i]; } } name[j] = '\0'; /* * Ignore any whitespace following the '=' sign */ for (; i < MAXLEN; ++i) { if (buf[i + 1] == ' ') continue; if (buf[i + 1] == '\t') continue; break; } /* * Extract parameter value */ j = 0; for (++i; i < MAXLEN; ++i) if (buf[i] == '\'') continue; else if (buf[i] == '#') break; else if (buf[i] != '\n') value[j++] = buf[i]; else break; value[j] = '\0'; trim(value); } void parse_time_unit_parameter(const char *name, const char *value, char *dest, ItemList *errors) { char *ptr = NULL; int targ = strtol(value, &ptr, 10); if (targ < 0) { if (errors != NULL) { item_list_append_format(errors, _("invalid value \"%s\" provided for \"%s\""), value, name); } return; } if (ptr && *ptr) { if (strcmp(ptr, "ms") != 0 && strcmp(ptr, "s") != 0 && strcmp(ptr, "min") != 0 && strcmp(ptr, "h") != 0 && strcmp(ptr, "d") != 0) { if (errors != NULL) { item_list_append_format( errors, _("value for \"%s\" must be one of ms/s/min/h/d (provided: \"%s\")"), name, value); return; } } } strncpy(dest, value, MAXLEN); } /* * 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 (keep the list in "doc/repmgrd-configuration.xml" in sync * with these): * * - async_query_timeout * - child_nodes_check_interval * - child_nodes_connected_min_count * - child_nodes_connected_include_witness * - child_nodes_disconnect_command * - child_nodes_disconnect_min_count * - child_nodes_disconnect_timeout * - connection_check_type * - conninfo * - degraded_monitoring_timeout * - event_notification_command * - event_notifications * - failover * - failover_validation_command * - follow_command * - log_facility * - log_file * - log_level * - log_status_interval * - monitor_interval_secs * - monitoring_history * - primary_notification_timeout * - primary_visibility_consensus * - always_promote * - promote_command * - reconnect_attempts * - reconnect_interval * - repmgrd_standby_startup_timeout * - retry_promote_interval_secs * - sibling_nodes_disconnect_timeout * - standby_disconnect_on_failover * * * Not publicly documented: * - promote_delay * * non-changeable options (repmgrd references these from the "repmgr.nodes" * table, not the configuration file) * * - node_id * - node_name * - data_directory * - location * - priority * - replication_type * * extract with something like: * grep config_file_options\\. repmgrd*.c | perl -n -e '/config_file_options\.([\w_]+)/ && print qq|$1\n|;' | sort | uniq * * Returns "true" if the configuration was successfully changed, otherwise "false". */ bool reload_config(t_server_type server_type) { bool log_config_changed = false; ItemList config_errors = {NULL, NULL}; ItemList config_warnings = {NULL, NULL}; ItemList config_changes = {NULL, NULL}; t_configuration_options orig_config_file_options; copy_config_file_options(&config_file_options, &orig_config_file_options); log_info(_("reloading configuration file")); log_detail(_("using file \"%s\""), config_file_path); /* * _parse_config() will sanity-check the provided values and put any * errors/warnings in the provided lists; no need to add further sanity * checks here. We do still need to check for repmgrd-specific * requirements. */ _parse_config(&config_errors, &config_warnings); if (config_file_options.failover == FAILOVER_AUTOMATIC && (server_type == PRIMARY || server_type == STANDBY)) { if (config_file_options.promote_command[0] == '\0') { item_list_append(&config_errors, _("\"promote_command\": required parameter was not found")); } if (config_file_options.follow_command[0] == '\0') { item_list_append(&config_errors, _("\"follow_command\": required parameter was not found")); } } /* The following options cannot be changed */ if (config_file_options.node_id != orig_config_file_options.node_id) { item_list_append_format(&config_errors, _("\"node_id\" cannot be changed, retaining current configuration %i %i"), config_file_options.node_id, orig_config_file_options.node_id); } if (strncmp(config_file_options.node_name, orig_config_file_options.node_name, sizeof(config_file_options.node_name)) != 0) { item_list_append(&config_errors, _("\"node_name\" cannot be changed, keeping current configuration")); } /* * conninfo * * _parse_config() will already have sanity-checked the string; we do that here * again so we can avoid trying to connect with a known bad string */ if (strncmp(config_file_options.conninfo, orig_config_file_options.conninfo, sizeof(config_file_options.conninfo)) != 0 && validate_conninfo_string(config_file_options.conninfo, NULL)) { PGconn *conn; /* Test conninfo string works */ conn = establish_db_connection(config_file_options.conninfo, false); if (!conn || (PQstatus(conn) != CONNECTION_OK)) { item_list_append_format(&config_errors, _("provided \"conninfo\" string \"%s\" is not valid"), config_file_options.conninfo); } else { item_list_append_format(&config_changes, _("\"conninfo\" changed from \"%s\" to \"%s\""), orig_config_file_options.conninfo, config_file_options.conninfo); } PQfinish(conn); } /* * If any issues encountered, raise an error and roll back to the original * configuration */ if (config_errors.head != NULL) { ItemListCell *cell = NULL; PQExpBufferData errors; log_error(_("one or more errors encountered while parsing the configuration file")); initPQExpBuffer(&errors); appendPQExpBufferStr(&errors, "following errors were detected:\n"); for (cell = config_errors.head; cell; cell = cell->next) { appendPQExpBuffer(&errors, " %s\n", cell->string); } log_detail("%s", errors.data); termPQExpBuffer(&errors); log_notice(_("the current configuration has been retained unchanged")); copy_config_file_options(&orig_config_file_options, &config_file_options); return false; } /* * No configuration problems detected - log any changed values. * * NB: keep these in the same order as in configfile.h to make it easier * to manage them */ /* async_query_timeout */ if (config_file_options.async_query_timeout != orig_config_file_options.async_query_timeout) { item_list_append_format(&config_changes, _("\"async_query_timeout\" changed from \"%i\" to \"%i\""), orig_config_file_options.async_query_timeout, config_file_options.async_query_timeout); } /* child_nodes_check_interval */ if (config_file_options.child_nodes_check_interval != orig_config_file_options.child_nodes_check_interval) { item_list_append_format(&config_changes, _("\"child_nodes_check_interval\" changed from \"%i\" to \"%i\""), orig_config_file_options.child_nodes_check_interval, config_file_options.child_nodes_check_interval); } /* child_nodes_disconnect_command */ if (strncmp(config_file_options.child_nodes_disconnect_command, orig_config_file_options.child_nodes_disconnect_command, sizeof(config_file_options.child_nodes_disconnect_command)) != 0) { item_list_append_format(&config_changes, _("\"child_nodes_disconnect_command\" changed from \"%s\" to \"%s\""), orig_config_file_options.child_nodes_disconnect_command, config_file_options.child_nodes_disconnect_command); } /* child_nodes_disconnect_min_count */ if (config_file_options.child_nodes_disconnect_min_count != orig_config_file_options.child_nodes_disconnect_min_count) { item_list_append_format(&config_changes, _("\"child_nodes_disconnect_min_count\" changed from \"%i\" to \"%i\""), orig_config_file_options.child_nodes_disconnect_min_count, config_file_options.child_nodes_disconnect_min_count); } /* child_nodes_connected_min_count */ if (config_file_options.child_nodes_connected_min_count != orig_config_file_options.child_nodes_connected_min_count) { item_list_append_format(&config_changes, _("\"child_nodes_connected_min_count\" changed from \"%i\" to \"%i\""), orig_config_file_options.child_nodes_connected_min_count, config_file_options.child_nodes_connected_min_count); } /* child_nodes_connected_include_witness */ if (config_file_options.child_nodes_connected_include_witness != orig_config_file_options.child_nodes_connected_include_witness) { item_list_append_format(&config_changes, _("\"child_nodes_connected_include_witness\" changed from \"%s\" to \"%s\""), format_bool(orig_config_file_options.child_nodes_connected_include_witness), format_bool(config_file_options.child_nodes_connected_include_witness)); } /* child_nodes_disconnect_timeout */ if (config_file_options.child_nodes_disconnect_timeout != orig_config_file_options.child_nodes_disconnect_timeout) { item_list_append_format(&config_changes, _("\"child_nodes_disconnect_timeout\" changed from \"%i\" to \"%i\""), orig_config_file_options.child_nodes_disconnect_timeout, config_file_options.child_nodes_disconnect_timeout); } /* degraded_monitoring_timeout */ if (config_file_options.degraded_monitoring_timeout != orig_config_file_options.degraded_monitoring_timeout) { item_list_append_format(&config_changes, _("\"degraded_monitoring_timeout\" changed from \"%i\" to \"%i\""), orig_config_file_options.degraded_monitoring_timeout, config_file_options.degraded_monitoring_timeout); } /* event_notification_command */ if (strncmp(config_file_options.event_notification_command, orig_config_file_options.event_notification_command, sizeof(config_file_options.event_notification_command)) != 0) { item_list_append_format(&config_changes, _("\"event_notification_command\" changed from \"%s\" to \"%s\""), orig_config_file_options.event_notification_command, config_file_options.event_notification_command); } /* event_notifications */ if (strncmp(config_file_options.event_notifications_orig, orig_config_file_options.event_notifications_orig, sizeof(config_file_options.event_notifications_orig)) != 0) { item_list_append_format(&config_changes, _("\"event_notifications\" changed from \"%s\" to \"%s\""), orig_config_file_options.event_notifications_orig, config_file_options.event_notifications_orig); } /* failover */ if (config_file_options.failover != orig_config_file_options.failover) { item_list_append_format(&config_changes, _("\"failover\" changed from \"%s\" to \"%s\""), format_failover_mode(orig_config_file_options.failover), format_failover_mode(config_file_options.failover)); } /* follow_command */ if (strncmp(config_file_options.follow_command, orig_config_file_options.follow_command, sizeof(config_file_options.follow_command)) != 0) { item_list_append_format(&config_changes, _("\"follow_command\" changed from \"%s\" to \"%s\""), orig_config_file_options.follow_command, config_file_options.follow_command); } /* monitor_interval_secs */ if (config_file_options.monitor_interval_secs != orig_config_file_options.monitor_interval_secs) { item_list_append_format(&config_changes, _("\"monitor_interval_secs\" changed from \"%i\" to \"%i\""), orig_config_file_options.monitor_interval_secs, config_file_options.monitor_interval_secs); } /* monitoring_history */ if (config_file_options.monitoring_history != orig_config_file_options.monitoring_history) { item_list_append_format(&config_changes, _("\"monitoring_history\" changed from \"%s\" to \"%s\""), format_bool(orig_config_file_options.monitoring_history), format_bool(config_file_options.monitoring_history)); } /* primary_notification_timeout */ if (config_file_options.primary_notification_timeout != orig_config_file_options.primary_notification_timeout) { item_list_append_format(&config_changes, _("\"primary_notification_timeout\" changed from \"%i\" to \"%i\""), orig_config_file_options.primary_notification_timeout, config_file_options.primary_notification_timeout); } /* promote_command */ if (strncmp(config_file_options.promote_command, orig_config_file_options.promote_command, sizeof(config_file_options.promote_command)) != 0) { item_list_append_format(&config_changes, _("\"promote_command\" changed from \"%s\" to \"%s\""), orig_config_file_options.promote_command, config_file_options.promote_command); } /* promote_delay (for testing use only; not documented */ if (config_file_options.promote_delay != orig_config_file_options.promote_delay) { item_list_append_format(&config_changes, _("\"promote_delay\" changed from \"%i\" to \"%i\""), orig_config_file_options.promote_delay, config_file_options.promote_delay); } /* reconnect_attempts */ if (config_file_options.reconnect_attempts != orig_config_file_options.reconnect_attempts) { item_list_append_format(&config_changes, _("\"reconnect_attempts\" changed from \"%i\" to \"%i\""), orig_config_file_options.reconnect_attempts, config_file_options.reconnect_attempts); } /* reconnect_interval */ if (config_file_options.reconnect_interval != orig_config_file_options.reconnect_interval) { item_list_append_format(&config_changes, _("\"reconnect_interval\" changed from \"%i\" to \"%i\""), orig_config_file_options.reconnect_interval, config_file_options.reconnect_interval); } /* repmgrd_standby_startup_timeout */ if (config_file_options.repmgrd_standby_startup_timeout != orig_config_file_options.repmgrd_standby_startup_timeout) { item_list_append_format(&config_changes, _("\"repmgrd_standby_startup_timeout\" changed from \"%i\" to \"%i\""), orig_config_file_options.repmgrd_standby_startup_timeout, config_file_options.repmgrd_standby_startup_timeout); } /* standby_disconnect_on_failover */ if (config_file_options.standby_disconnect_on_failover != orig_config_file_options.standby_disconnect_on_failover) { item_list_append_format(&config_changes, _("\"standby_disconnect_on_failover\" changed from \"%s\" to \"%s\""), format_bool(orig_config_file_options.standby_disconnect_on_failover), format_bool(config_file_options.standby_disconnect_on_failover)); } /* sibling_nodes_disconnect_timeout */ if (config_file_options.sibling_nodes_disconnect_timeout != orig_config_file_options.sibling_nodes_disconnect_timeout) { item_list_append_format(&config_changes, _("\"sibling_nodes_disconnect_timeout\" changed from \"%i\" to \"%i\""), orig_config_file_options.sibling_nodes_disconnect_timeout, config_file_options.sibling_nodes_disconnect_timeout); } /* connection_check_type */ if (config_file_options.connection_check_type != orig_config_file_options.connection_check_type) { item_list_append_format(&config_changes, _("\"connection_check_type\" changed from \"%s\" to \"%s\""), print_connection_check_type(orig_config_file_options.connection_check_type), print_connection_check_type(config_file_options.connection_check_type)); } /* primary_visibility_consensus */ if (config_file_options.primary_visibility_consensus != orig_config_file_options.primary_visibility_consensus) { item_list_append_format(&config_changes, _("\"primary_visibility_consensus\" changed from \"%s\" to \"%s\""), format_bool(orig_config_file_options.primary_visibility_consensus), format_bool(config_file_options.primary_visibility_consensus)); } /* always_promote */ if (config_file_options.always_promote != orig_config_file_options.always_promote) { item_list_append_format(&config_changes, _("\"always_promote\" changed from \"%s\" to \"%s\""), format_bool(orig_config_file_options.always_promote), format_bool(config_file_options.always_promote)); } /* failover_validation_command */ if (strncmp(config_file_options.failover_validation_command, orig_config_file_options.failover_validation_command, sizeof(config_file_options.failover_validation_command)) != 0) { item_list_append_format(&config_changes, _("\"failover_validation_command\" changed from \"%s\" to \"%s\""), orig_config_file_options.failover_validation_command, config_file_options.failover_validation_command); } /* * Handle changes to logging configuration */ /* log_facility */ if (strncmp(config_file_options.log_facility, orig_config_file_options.log_facility, sizeof(config_file_options.log_facility)) != 0) { item_list_append_format(&config_changes, _("\"log_facility\" changed from \"%s\" to \"%s\""), orig_config_file_options.log_facility, config_file_options.log_facility); } /* log_file */ if (strncmp(config_file_options.log_file, orig_config_file_options.log_file, sizeof(config_file_options.log_file)) != 0) { item_list_append_format(&config_changes, _("\"log_file\" changed from \"%s\" to \"%s\""), orig_config_file_options.log_file, config_file_options.log_file); } /* log_level */ if (strncmp(config_file_options.log_level, orig_config_file_options.log_level, sizeof(config_file_options.log_level)) != 0) { item_list_append_format(&config_changes, _("\"log_level\" changed from \"%s\" to \"%s\""), orig_config_file_options.log_level, config_file_options.log_level); } /* log_status_interval */ if (config_file_options.log_status_interval != orig_config_file_options.log_status_interval) { item_list_append_format(&config_changes, _("\"log_status_interval\" changed from \"%i\" to \"%i\""), orig_config_file_options.log_status_interval, config_file_options.log_status_interval); } if (log_config_changed == true) { log_notice(_("restarting logging with changed parameters")); logger_shutdown(); logger_init(&config_file_options, progname()); log_notice(_("configuration file reloaded with changed parameters")); } if (config_changes.head != NULL) { ItemListCell *cell = NULL; PQExpBufferData detail; log_notice(_("configuration was successfully changed")); initPQExpBuffer(&detail); appendPQExpBufferStr(&detail, _("following configuration items were changed:\n")); for (cell = config_changes.head; cell; cell = cell->next) { appendPQExpBuffer(&detail, " %s\n", cell->string); } log_detail("%s", detail.data); termPQExpBuffer(&detail); } else { log_info(_("configuration has not changed")); } /* * parse_configuration_item() (called from _parse_config()) will add warnings * about any deprecated configuration parameters; we'll dump these here as a reminder. */ if (config_warnings.head != NULL) { ItemListCell *cell = NULL; PQExpBufferData detail; log_warning(_("configuration file contains deprecated parameters")); initPQExpBuffer(&detail); appendPQExpBufferStr(&detail, _("following parameters are deprecated:\n")); for (cell = config_warnings.head; cell; cell = cell->next) { appendPQExpBuffer(&detail, " %s\n", cell->string); } log_detail("%s", detail.data); termPQExpBuffer(&detail); } return config_changes.head == NULL ? false : true; } /* * Dump the parsed configuration */ void dump_config(void) { ConfigFileSetting *setting; int i = 0; setting = &config_file_settings[0]; do { printf("%s|", setting->name); switch (setting->type) { case CONFIG_INT: printf("%i", *setting->val.intptr); break; case CONFIG_BOOL: printf("%s", format_bool(*setting->val.boolptr)); break; case CONFIG_STRING: printf("%s", setting->val.strptr); break; case CONFIG_FAILOVER_MODE: printf("%s", format_failover_mode(*setting->val.failovermodeptr)); break; case CONFIG_CONNECTION_CHECK_TYPE: printf("%s", print_connection_check_type(*setting->val.checktypeptr)); break; case CONFIG_REPLICATION_TYPE: printf("%s", print_replication_type(*setting->val.replicationtypeptr)); break; case CONFIG_EVENT_NOTIFICATION_LIST: { char *list = print_event_notification_list(setting->val.notificationlistptr); printf("%s", list); pfree(list); } break; case CONFIG_TABLESPACE_MAPPING: { char *list = print_tablespace_mapping(setting->val.tablespacemappingptr); printf("%s", list); pfree(list); } break; default: /* this should never happen */ log_error("unhandled setting type %i", (int)setting->type); } puts(""); i++; setting = &config_file_settings[i]; } while (setting->name != NULL); } static void exit_with_config_file_errors(ItemList *config_errors, ItemList *config_warnings, bool terse) { log_error(_("following errors were found in the configuration file:")); print_item_list(config_errors); item_list_free(config_errors); if (terse == false && config_warnings->head != NULL) { puts(""); log_warning(_("the following problems were also found in the configuration file:")); print_item_list(config_warnings); item_list_free(config_warnings); } if (config_file_provided == false) log_detail(_("configuration file is: \"%s\""), config_file_path); exit(ERR_BAD_CONFIG); } void exit_with_cli_errors(ItemList *error_list, const char *repmgr_command) { fprintf(stderr, _("The following command line errors were encountered:\n")); print_item_list(error_list); if (repmgr_command != NULL) { fprintf(stderr, _("Try \"%s --help\" or \"%s %s --help\" for more information.\n"), progname(), progname(), repmgr_command); } else { fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname()); } exit(ERR_BAD_CONFIG); } void print_item_list(ItemList *item_list) { ItemListCell *cell = NULL; for (cell = item_list->head; cell; cell = cell->next) { fprintf(stderr, " %s\n", cell->string); } } /* * Convert provided string to an integer using strtol; * on error, if a callback is provided, pass the error message to that, * otherwise exit */ int repmgr_atoi(const char *value, const char *config_item, ItemList *error_list, int minval) { char *endptr = NULL; long longval = 0; PQExpBufferData errors; initPQExpBuffer(&errors); /* * It's possible that some versions of strtol() don't treat an empty * string as an error. */ if (*value == '\0') { /* don't log here - empty values will be caught later */ return 0; } else { errno = 0; longval = strtol(value, &endptr, 10); if (value == endptr || errno) { appendPQExpBuffer(&errors, _("\"%s\": invalid value (provided: \"%s\")"), config_item, value); } else if ((int32) longval < longval) { appendPQExpBuffer(&errors, _("\"%s\": must be a positive signed 32 bit integer, i.e. 2147483647 or less (provided: \"%s\")"), config_item, value); } else if ((int32) longval < minval) /* Disallow negative values for most parameters */ { appendPQExpBuffer(&errors, _("\"%s\": must be %i or greater (provided: \"%s\")"), config_item, minval, value); } } /* Error message buffer is set */ if (errors.data[0] != '\0') { if (error_list == NULL) { log_error("%s", errors.data); termPQExpBuffer(&errors); exit(ERR_BAD_CONFIG); } item_list_append(error_list, errors.data); } termPQExpBuffer(&errors); return (int32) longval; } void repmgr_canonicalize_path(const char *name, const char *value, char *config_item, ItemList *errors) { /* NOTE: canonicalize_path does not produce any errors */ canonicalize_path(config_item); } /* * Interpret a parameter value as a boolean. Currently accepts: * * - true/false * - 1/0 * - on/off * - yes/no * Returns 'false' if unable to determine the booleanness of the value * and adds an entry to the error list, which will result in the program * erroring out before it proceeds to do anything. * * TODO: accept "any unambiguous prefix of one of these" as per postgresql.conf: * * https://www.postgresql.org/docs/current/config-setting.html */ bool parse_bool(const char *s, const char *config_item, ItemList *error_list) { PQExpBufferData errors; if (s == NULL) return true; if (strcasecmp(s, "0") == 0) return false; if (strcasecmp(s, "1") == 0) return true; if (strcasecmp(s, "false") == 0) return false; if (strcasecmp(s, "true") == 0) return true; if (strcasecmp(s, "off") == 0) return false; if (strcasecmp(s, "on") == 0) return true; if (strcasecmp(s, "no") == 0) return false; if (strcasecmp(s, "yes") == 0) return true; if (error_list != NULL) { initPQExpBuffer(&errors); appendPQExpBuffer(&errors, "\"%s\": unable to interpret \"%s\" as a boolean value", config_item, s); item_list_append(error_list, errors.data); termPQExpBuffer(&errors); } return false; } /* * Copy a configuration file struct */ void copy_config_file_options(t_configuration_options *original, t_configuration_options *copy) { memcpy(copy, original, (int)sizeof(t_configuration_options)); /* Copy structures which point to allocated memory */ if (original->event_notifications.head != NULL) { /* For the event notifications, we can just reparse the string */ parse_event_notifications_list(©->event_notifications, original->event_notifications_orig); } if (original->tablespace_mapping.head != NULL) { /* * We allow multiple instances of "tablespace_mapping" in the configuration file * which are appended to the list as they're encountered. */ tablespace_list_copy(original, copy); } } /* * Split argument into old_dir and new_dir and append to tablespace mapping * list. * * Adapted from pg_basebackup.c */ static void tablespace_list_append(TablespaceList *tablespace_mapping, const char *arg) { TablespaceListCell *cell = NULL; char *dst = NULL; char *dst_ptr = NULL; const char *arg_ptr = NULL; cell = (TablespaceListCell *) pg_malloc0(sizeof(TablespaceListCell)); if (cell == NULL) { log_error(_("unable to allocate memory; terminating")); exit(ERR_BAD_CONFIG); } dst_ptr = dst = cell->old_dir; for (arg_ptr = arg; *arg_ptr; arg_ptr++) { if (dst_ptr - dst >= MAXPGPATH) { log_error(_("directory name too long")); exit(ERR_BAD_CONFIG); } if (*arg_ptr == '\\' && *(arg_ptr + 1) == '=') ; /* skip backslash escaping = */ else if (*arg_ptr == '=' && (arg_ptr == arg || *(arg_ptr - 1) != '\\')) { if (*cell->new_dir) { log_error(_("multiple \"=\" signs in tablespace mapping")); exit(ERR_BAD_CONFIG); } else { dst = dst_ptr = cell->new_dir; } } else *dst_ptr++ = *arg_ptr; } if (!*cell->old_dir || !*cell->new_dir) { log_error(_("invalid tablespace mapping format \"%s\", must be \"OLDDIR=NEWDIR\""), arg); exit(ERR_BAD_CONFIG); } canonicalize_path(cell->old_dir); canonicalize_path(cell->new_dir); if (tablespace_mapping->tail) tablespace_mapping->tail->next = cell; else tablespace_mapping->head = cell; tablespace_mapping->tail = cell; } static void tablespace_list_copy(t_configuration_options *original, t_configuration_options *copy) { TablespaceListCell *orig_cell = original->tablespace_mapping.head; for (orig_cell = config_file_options.tablespace_mapping.head; orig_cell; orig_cell = orig_cell->next) { TablespaceListCell *copy_cell = (TablespaceListCell *) pg_malloc0(sizeof(TablespaceListCell)); strncpy(copy_cell->old_dir, orig_cell->old_dir, sizeof(copy_cell->old_dir)); strncpy(copy_cell->new_dir, orig_cell->new_dir, sizeof(copy_cell->new_dir)); if (copy->tablespace_mapping.tail) copy->tablespace_mapping.tail->next = copy_cell; else copy->tablespace_mapping.head = copy_cell; copy->tablespace_mapping.tail = copy_cell; } } static void tablespace_list_free(t_configuration_options *options) { TablespaceListCell *cell = NULL; TablespaceListCell *next_cell = NULL; cell = options->tablespace_mapping.head; while (cell != NULL) { next_cell = cell->next; pfree(cell); cell = next_cell; } options->tablespace_mapping.head = NULL; options->tablespace_mapping.tail = NULL; } bool modify_auto_conf(const char *data_dir, KeyValueList *items) { PQExpBufferData auto_conf; PQExpBufferData auto_conf_tmp; PQExpBufferData auto_conf_contents; FILE *fp; mode_t um; struct stat data_dir_st; KeyValueList config = {NULL, NULL}; KeyValueListCell *cell = NULL; bool success = true; initPQExpBuffer(&auto_conf); appendPQExpBuffer(&auto_conf, "%s/%s", data_dir, PG_AUTOCONF_FILENAME); success = ProcessPostgresConfigFile(auto_conf.data, NULL, false, /* we don't care if the file does not exist */ &config, NULL, NULL); if (success == false) { fprintf(stderr, "unable to process \"%s\"\n", auto_conf.data); termPQExpBuffer(&auto_conf); return false; } /* * Append requested items to any items extracted from the existing file. */ for (cell = items->head; cell; cell = cell->next) { key_value_list_replace_or_set(&config, cell->key, cell->value); } initPQExpBuffer(&auto_conf_tmp); appendPQExpBuffer(&auto_conf_tmp, "%s.tmp", auto_conf.data); initPQExpBuffer(&auto_conf_contents); /* * Keep this in sync with src/backend/utils/misc/guc.c:write_auto_conf_file() */ appendPQExpBufferStr(&auto_conf_contents, "# Do not edit this file manually!\n" "# It will be overwritten by the ALTER SYSTEM command.\n"); for (cell = config.head; cell; cell = cell->next) { appendPQExpBuffer(&auto_conf_contents, "%s = '%s'\n", cell->key, cell->value); } /* stat the data directory for the file mode */ if (stat(data_dir, &data_dir_st) != 0) { /* * This is highly unlikely to happen, but if it does (e.g. freak * race condition with some rogue process which is messing about * with the data directory), there's not a lot we can do. */ log_error(_("error encountered when checking \"%s\""), data_dir); log_detail("%s", strerror(errno)); exit(ERR_BAD_CONFIG); } /* * Set umask so the temporary file is created in the same mode as the data * directory. In PostgreSQL 11 and later this can be 0700 or 0750. */ um = umask(~(data_dir_st.st_mode)); fp = fopen(auto_conf_tmp.data, "w"); umask(um); if (fp == NULL) { fprintf(stderr, "unable to open \"%s\" for writing: %s\n", auto_conf_tmp.data, strerror(errno)); success = false; } else { if (fwrite(auto_conf_contents.data, strlen(auto_conf_contents.data), 1, fp) != 1) { fprintf(stderr, "unable to write to \"%s\": %s\n", auto_conf_tmp.data, strerror(errno)); fclose(fp); success = false; } else { fclose(fp); /* * Note: durable_rename() is not exposed to frontend code before Pg 10. * We only really need to be modifying postgresql.auto.conf from Pg 12, * but provide backwards compatibility for Pg 9.6 and earlier for the * (unlikely) event that a repmgr built against one of those versions * is being used against Pg 12 and later. */ #if (PG_ACTUAL_VERSION_NUM >= 100000) if (durable_rename(auto_conf_tmp.data, auto_conf.data, LOG) != 0) { success = false; } #else if (rename(auto_conf_tmp.data, auto_conf.data) < 0) { success = false; } #endif } } termPQExpBuffer(&auto_conf); termPQExpBuffer(&auto_conf_tmp); termPQExpBuffer(&auto_conf_contents); key_value_list_free(&config); return success; } /* * parse_event_notifications_list() * * */ static void parse_event_notifications_list(EventNotificationList *event_notifications, const char *arg) { const char *arg_ptr = NULL; char event_type_buf[MAXLEN] = ""; char *dst_ptr = event_type_buf; for (arg_ptr = arg; arg_ptr <= (arg + strlen(arg)); arg_ptr++) { /* ignore whitespace */ if (*arg_ptr == ' ' || *arg_ptr == '\t') { continue; } /* * comma (or end-of-string) should mark the end of an event type - * just as long as there was something preceding it */ if ((*arg_ptr == ',' || *arg_ptr == '\0') && event_type_buf[0] != '\0') { EventNotificationListCell *cell; cell = (EventNotificationListCell *) pg_malloc0(sizeof(EventNotificationListCell)); if (cell == NULL) { log_error(_("unable to allocate memory; terminating")); exit(ERR_BAD_CONFIG); } strncpy(cell->event_type, event_type_buf, MAXLEN); if (event_notifications->tail) { event_notifications->tail->next = cell; } else { event_notifications->head = cell; } event_notifications->tail = cell; memset(event_type_buf, 0, MAXLEN); dst_ptr = event_type_buf; } /* ignore duplicated commas */ else if (*arg_ptr == ',') { continue; } else { *dst_ptr++ = *arg_ptr; } } } static void clear_event_notification_list(EventNotificationList *event_notifications) { if (event_notifications->head != NULL) { EventNotificationListCell *cell; EventNotificationListCell *next_cell; cell = event_notifications->head; while (cell != NULL) { next_cell = cell->next; pfree(cell); cell = next_cell; } } event_notifications->head = NULL; event_notifications->tail = NULL; } int parse_output_to_argv(const char *string, char ***argv_array) { int options_len = 0; char *options_string = NULL; char *options_string_ptr = NULL; int c = 1, argc_item = 1; char *argv_item = NULL; char **local_argv_array = NULL; ItemListCell *cell; /* * Add parsed options to this list, then copy to an array to pass to * getopt */ ItemList option_argv = {NULL, NULL}; options_len = strlen(string) + 1; options_string = pg_malloc0(options_len); options_string_ptr = options_string; /* Copy the string before operating on it with strtok() */ strncpy(options_string, string, options_len); /* Extract arguments into a list and keep a count of the total */ while ((argv_item = strtok(options_string_ptr, " ")) != NULL) { item_list_append(&option_argv, trim(argv_item)); argc_item++; if (options_string_ptr != NULL) options_string_ptr = NULL; } pfree(options_string); /* * Array of argument values to pass to getopt_long - this will need to * include an empty string as the first value (normally this would be the * program name) */ local_argv_array = pg_malloc0(sizeof(char *) * (argc_item + 2)); /* Insert a blank dummy program name at the start of the array */ local_argv_array[0] = pg_malloc0(1); /* * Copy the previously extracted arguments from our list to the array */ for (cell = option_argv.head; cell; cell = cell->next) { int argv_len = strlen(cell->string) + 1; local_argv_array[c] = (char *)pg_malloc0(argv_len); strncpy(local_argv_array[c], cell->string, argv_len); c++; } local_argv_array[c] = NULL; item_list_free(&option_argv); *argv_array = local_argv_array; return argc_item; } void free_parsed_argv(char ***argv_array) { char **local_argv_array = *argv_array; int i = 0; while (local_argv_array[i] != NULL) { pfree((char *)local_argv_array[i]); i++; } pfree((char **)local_argv_array); *argv_array = NULL; } bool parse_pg_basebackup_options(const char *pg_basebackup_options, t_basebackup_options *backup_options, int server_version_num, ItemList *error_list) { bool backup_options_ok = true; int c = 0, argc_item = 0; char **argv_array = NULL; int optindex = 0; struct option *long_options = NULL; /* * We're only interested in these options. */ static struct option long_options_10[] = { {"slot", required_argument, NULL, 'S'}, {"wal-method", required_argument, NULL, 'X'}, {"waldir", required_argument, NULL, 1}, {"no-slot", no_argument, NULL, 2}, {NULL, 0, NULL, 0} }; /* * Pre-PostgreSQL 10 options */ static struct option long_options_legacy[] = { {"slot", required_argument, NULL, 'S'}, {"xlog-method", required_argument, NULL, 'X'}, {"xlogdir", required_argument, NULL, 1}, {NULL, 0, NULL, 0} }; /* Don't attempt to tokenise an empty string */ if (!strlen(pg_basebackup_options)) return backup_options_ok; if (server_version_num >= 100000) long_options = long_options_10; else long_options = long_options_legacy; argc_item = parse_output_to_argv(pg_basebackup_options, &argv_array); /* Reset getopt's optind variable */ optind = 0; /* Prevent getopt from emitting errors */ opterr = 0; while ((c = getopt_long(argc_item, argv_array, "S:X:", long_options, &optindex)) != -1) { switch (c) { case 'S': strncpy(backup_options->slot, optarg, MAXLEN); break; case 'X': strncpy(backup_options->wal_method, optarg, MAXLEN); break; case 1: strncpy(backup_options->waldir, optarg, MAXPGPATH); break; case 2: backup_options->no_slot = true; break; case '?': if (server_version_num >= 100000 && optopt == 2) { if (error_list != NULL) { item_list_append(error_list, "invalid use of --no-slot"); } backup_options_ok = false; } break; } } if (backup_options->no_slot == true && backup_options->slot[0] != '\0') { if (error_list != NULL) { item_list_append(error_list, "--no-slot cannot be used with -S/--slot"); } backup_options_ok = false; } /* * If --waldir/--xlogdir provided, check it's an absolute path. */ if (backup_options->waldir[0] != '\0') { canonicalize_path(backup_options->waldir); if (!is_absolute_path(backup_options->waldir)) { if (error_list != NULL) { item_list_append_format(error_list, "--%s must be provided with an absolute path", server_version_num >= 100000 ? "waldir" : "xlogdir"); } backup_options_ok = false; } } free_parsed_argv(&argv_array); return backup_options_ok; } const char * print_replication_type(ReplicationType type) { switch (type) { case REPLICATION_TYPE_PHYSICAL: return "physical"; } /* should never reach here */ return "UNKNOWN"; } const char * print_connection_check_type(ConnectionCheckType type) { switch (type) { case CHECK_PING: return "ping"; case CHECK_QUERY: return "query"; case CHECK_CONNECTION: return "connection"; } /* should never reach here */ return "UNKNOWN"; } char * print_event_notification_list(EventNotificationList *list) { PQExpBufferData buf; char *ptr; EventNotificationListCell *cell; int ptr_len; initPQExpBuffer(&buf); cell = list->head; while (cell != NULL) { appendPQExpBufferStr(&buf, cell->event_type); if (cell->next) appendPQExpBufferChar(&buf, ','); cell = cell->next; } ptr_len = strlen(buf.data); ptr = palloc0(ptr_len + 1); strncpy(ptr, buf.data, ptr_len); termPQExpBuffer(&buf); return ptr; } char * print_tablespace_mapping(TablespaceList *tablespace_mapping) { TablespaceListCell *cell; bool first = true; PQExpBufferData buf; char *ptr; initPQExpBuffer(&buf); for (cell = tablespace_mapping->head; cell; cell = cell->next) { if (first == true) first = false; else appendPQExpBufferChar(&buf, ','); appendPQExpBuffer(&buf, "%s=%s", cell->old_dir, cell->new_dir); } ptr = palloc0(strlen(buf.data) + 1); strncpy(ptr, buf.data, strlen(buf.data)); termPQExpBuffer(&buf); return ptr; } const char * format_failover_mode(failover_mode_opt failover) { switch (failover) { case FAILOVER_MANUAL: return "manual"; case FAILOVER_AUTOMATIC: return "automatic"; default: return "unknown failover mode"; } } repmgr-5.3.1/configfile.h000066400000000000000000000242161420262710000152710ustar00rootroot00000000000000/* * configfile.h * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * * 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 . */ #ifndef _REPMGR_CONFIGFILE_H_ #define _REPMGR_CONFIGFILE_H_ #include #define CONFIG_FILE_NAME "repmgr.conf" #define MAXLINELENGTH 4096 /* magic number for use in t_recovery_conf */ #define TARGET_TIMELINE_LATEST 0 /* * This is defined in src/include/utils.h, however it's not practical * to include that from a frontend application. */ #define PG_AUTOCONF_FILENAME "postgresql.auto.conf" extern bool config_file_found; extern char config_file_path[MAXPGPATH]; typedef enum { FAILOVER_MANUAL, FAILOVER_AUTOMATIC } failover_mode_opt; typedef enum { CHECK_PING, CHECK_QUERY, CHECK_CONNECTION } ConnectionCheckType; typedef enum { REPLICATION_TYPE_PHYSICAL } ReplicationType; typedef struct EventNotificationListCell { struct EventNotificationListCell *next; char event_type[MAXLEN]; } EventNotificationListCell; typedef struct EventNotificationList { EventNotificationListCell *head; EventNotificationListCell *tail; } EventNotificationList; typedef struct TablespaceListCell { struct TablespaceListCell *next; char old_dir[MAXPGPATH]; char new_dir[MAXPGPATH]; } TablespaceListCell; typedef struct TablespaceList { TablespaceListCell *head; TablespaceListCell *tail; } TablespaceList; typedef enum { CONFIG_BOOL, CONFIG_INT, CONFIG_STRING, CONFIG_FAILOVER_MODE, CONFIG_CONNECTION_CHECK_TYPE, CONFIG_EVENT_NOTIFICATION_LIST, CONFIG_TABLESPACE_MAPPING, CONFIG_REPLICATION_TYPE } ConfigItemType; typedef struct ConfigFileSetting { const char *name; ConfigItemType type; union { int *intptr; char *strptr; bool *boolptr; failover_mode_opt *failovermodeptr; ConnectionCheckType *checktypeptr; EventNotificationList *notificationlistptr; TablespaceList *tablespacemappingptr; ReplicationType *replicationtypeptr; } val; union { int intdefault; const char *strdefault; bool booldefault; failover_mode_opt failovermodedefault; ConnectionCheckType checktypedefault; ReplicationType replicationtypedefault; } defval; union { int intminval; } minval; union { int strmaxlen; } maxval; struct { void (*process_func)(const char *, const char *, char *, ItemList *errors); void (*postprocess_func)(const char *, const char *, char *, ItemList *errors); bool *providedptr; } process; } ConfigFileSetting; /* Declare the main configfile structure for client applications */ extern ConfigFileSetting config_file_settings[]; typedef struct { /* node information */ int node_id; char node_name[NAMEDATALEN]; char conninfo[MAXLEN]; char replication_user[NAMEDATALEN]; char data_directory[MAXPGPATH]; char config_directory[MAXPGPATH]; char pg_bindir[MAXPGPATH]; char repmgr_bindir[MAXPGPATH]; ReplicationType replication_type; /* log settings */ char log_level[MAXLEN]; char log_facility[MAXLEN]; char log_file[MAXPGPATH]; int log_status_interval; /* standby clone settings */ bool use_replication_slots; char pg_basebackup_options[MAXLEN]; char restore_command[MAXLEN]; TablespaceList tablespace_mapping; char recovery_min_apply_delay[MAXLEN]; bool recovery_min_apply_delay_provided; char archive_cleanup_command[MAXLEN]; bool use_primary_conninfo_password; char passfile[MAXPGPATH]; /* standby promote settings */ int promote_check_timeout; int promote_check_interval; /* standby follow settings */ int primary_follow_timeout; int standby_follow_timeout; bool standby_follow_restart; /* standby switchover settings */ int shutdown_check_timeout; int standby_reconnect_timeout; int wal_receive_check_timeout; /* node rejoin settings */ int node_rejoin_timeout; /* node check settings */ int archive_ready_warning; int archive_ready_critical; int replication_lag_warning; int replication_lag_critical; /* witness settings */ int witness_sync_interval; /* repmgrd settings */ failover_mode_opt failover; char location[MAXLEN]; int priority; char promote_command[MAXLEN]; char follow_command[MAXLEN]; int monitor_interval_secs; int reconnect_attempts; int reconnect_interval; bool monitoring_history; int degraded_monitoring_timeout; int async_query_timeout; int primary_notification_timeout; int repmgrd_standby_startup_timeout; char repmgrd_pid_file[MAXPGPATH]; bool repmgrd_exit_on_inactive_node; bool standby_disconnect_on_failover; int sibling_nodes_disconnect_timeout; ConnectionCheckType connection_check_type; bool primary_visibility_consensus; bool always_promote; char failover_validation_command[MAXPGPATH]; int election_rerun_interval; int child_nodes_check_interval; int child_nodes_disconnect_min_count; int child_nodes_connected_min_count; bool child_nodes_connected_include_witness; int child_nodes_disconnect_timeout; char child_nodes_disconnect_command[MAXPGPATH]; /* service settings */ char pg_ctl_options[MAXLEN]; char service_start_command[MAXPGPATH]; char service_stop_command[MAXPGPATH]; char service_restart_command[MAXPGPATH]; char service_reload_command[MAXPGPATH]; char service_promote_command[MAXPGPATH]; /* repmgrd service settings */ char repmgrd_service_start_command[MAXPGPATH]; char repmgrd_service_stop_command[MAXPGPATH]; /* event notification settings */ char event_notification_command[MAXPGPATH]; char event_notifications_orig[MAXLEN]; EventNotificationList event_notifications; /* barman settings */ char barman_host[MAXLEN]; char barman_server[MAXLEN]; char barman_config[MAXLEN]; /* rsync/ssh settings */ char rsync_options[MAXLEN]; char ssh_options[MAXLEN]; /* * undocumented settings * * These settings are for testing or experimental features * and may be changed without notice. */ /* experimental settings */ bool reconnect_loop_sync; /* test settings */ int promote_delay; int failover_delay; char connection_check_query[MAXLEN]; } t_configuration_options; /* Declare the main configfile structure for client applications */ extern t_configuration_options config_file_options; typedef struct { char slot[MAXLEN]; char wal_method[MAXLEN]; char waldir[MAXPGPATH]; bool no_slot; /* from PostgreSQL 10 */ } t_basebackup_options; #define T_BASEBACKUP_OPTIONS_INITIALIZER { "", "", "", false } typedef enum { RTA_PAUSE, RTA_PROMOTE, RTA_SHUTDOWN } RecoveryTargetAction; /* * Struct to hold the contents of a parsed recovery.conf file. * We're only really interested in those related to streaming * replication (and also "restore_command") but include the * others for completeness. * * NOTE: "recovery_target" not included as it can only have * one value, "immediate". */ typedef struct { /* archive recovery settings */ char restore_command[MAXLEN]; char archive_cleanup_command[MAXLEN]; char recovery_end_command[MAXLEN]; /* recovery target settings */ char recovery_target_name[MAXLEN]; char recovery_target_time[MAXLEN]; char recovery_target_xid[MAXLEN]; bool recovery_target_inclusive; int recovery_target_timeline; RecoveryTargetAction recovery_target_action; /* default: RTA_PAUSE */ /* standby server settings */ bool standby_mode; char primary_conninfo[MAXLEN]; char primary_slot_name[MAXLEN]; char trigger_file[MAXLEN]; char recovery_min_apply_delay[MAXLEN]; } t_recovery_conf; #define T_RECOVERY_CONF_INITIALIZER { \ /* archive recovery settings */ \ "", "", "", \ /* recovery target settings */ \ "", "", "", true, \ TARGET_TIMELINE_LATEST, \ RTA_PAUSE, \ /* standby server settings */ \ true, \ "", "", "", "" \ } #include "dbutils.h" void set_progname(const char *argv0); const char *progname(void); void load_config(const char *config_file, bool verbose, bool terse, char *argv0); bool reload_config(t_server_type server_type); void dump_config(void); void parse_configuration_item(ItemList *error_list, ItemList *warning_list, const char *name, const char *value); bool parse_recovery_conf(const char *data_dir, t_recovery_conf *conf); bool parse_bool(const char *s, const char *config_item, ItemList *error_list); int repmgr_atoi(const char *s, const char *config_item, ItemList *error_list, int minval); void parse_time_unit_parameter(const char *name, const char *value, char *dest, ItemList *errors); void repmgr_canonicalize_path(const char *name, const char *value, char *config_item, ItemList *errors); bool parse_pg_basebackup_options(const char *pg_basebackup_options, t_basebackup_options *backup_options, int server_version_num, ItemList *error_list); int parse_output_to_argv(const char *string, char ***argv_array); void free_parsed_argv(char ***argv_array); const char *format_failover_mode(failover_mode_opt failover); /* called by repmgr-client and repmgrd */ void exit_with_cli_errors(ItemList *error_list, const char *repmgr_command); void print_item_list(ItemList *item_list); const char *print_replication_type(ReplicationType type); const char *print_connection_check_type(ConnectionCheckType type); char *print_event_notification_list(EventNotificationList *list); char *print_tablespace_mapping(TablespaceList *tablespacemappingptr); extern bool modify_auto_conf(const char *data_dir, KeyValueList *items); extern bool ProcessRepmgrConfigFile(const char *config_file, const char *base_dir, ItemList *error_list, ItemList *warning_list); extern bool ProcessPostgresConfigFile(const char *config_file, const char *base_dir, bool strict, KeyValueList *contents, ItemList *error_list, ItemList *warning_list); #endif /* _REPMGR_CONFIGFILE_H_ */ repmgr-5.3.1/configure000077500000000000000000002737371420262710000147400ustar00rootroot00000000000000#! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.69 for repmgr 5.3.0. # # Report bugs to . # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. # # # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. # # Copyright (c) 2010-2021, EnterpriseDB Corporation ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi as_nl=' ' export as_nl # Printing a long string crashes Solaris 7 /usr/bin/printf. as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo # Prefer a ksh shell builtin over an external printf program on Solaris, # but without wasting forks for bash or zsh. if test -z "$BASH_VERSION$ZSH_VERSION" \ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='print -r --' as_echo_n='print -rn --' elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='printf %s\n' as_echo_n='printf %s' else if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' as_echo_n='/usr/ucb/echo -n' else as_echo_body='eval expr "X$1" : "X\\(.*\\)"' as_echo_n_body='eval arg=$1; case $arg in #( *"$as_nl"*) expr "X$arg" : "X\\(.*\\)$as_nl"; arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; esac; expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" ' export as_echo_n_body as_echo_n='sh -c $as_echo_n_body as_echo' fi export as_echo_body as_echo='sh -c $as_echo_body as_echo' fi # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Unset variables that we do not need and which cause bugs (e.g. in # pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" # suppresses any "Segmentation fault" message there. '((' could # trigger a bug in pdksh 5.2.14. for as_var in BASH_ENV ENV MAIL MAILPATH do eval test x\${$as_var+set} = xset \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then _as_can_reexec=no; export _as_can_reexec; # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. $as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 as_fn_exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi " as_required="as_fn_return () { (exit \$1); } as_fn_success () { as_fn_return 0; } as_fn_failure () { as_fn_return 1; } as_fn_ret_success () { return 0; } as_fn_ret_failure () { return 1; } exitcode=0 as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : else exitcode=1; echo positional parameters were not saved. fi test x\$exitcode = x0 || exit 1 test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 test \$(( 1 + 1 )) = 2 || exit 1" if (eval "$as_required") 2>/dev/null; then : as_have_required=yes else as_have_required=no fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. as_shell=$as_dir/$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : CONFIG_SHELL=$as_shell as_have_required=yes if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : break 2 fi fi done;; esac as_found=false done $as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : CONFIG_SHELL=$SHELL as_have_required=yes fi; } IFS=$as_save_IFS if test "x$CONFIG_SHELL" != x; then : export CONFIG_SHELL # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. $as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi if test x$as_have_required = xno; then : $as_echo "$0: This script requires a shell more modern than all" $as_echo "$0: the shells that I found on your system." if test x${ZSH_VERSION+set} = xset ; then $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" $as_echo "$0: be upgraded to zsh 4.3.4 or later." else $as_echo "$0: Please tell bug-autoconf@gnu.org and $0: repmgr@googlegroups.com about your system, including $0: any error possibly output before this message. Then $0: install a modern shell, or manually run the script $0: under such a shell if you do have one." fi exit 1 fi fi fi SHELL=${CONFIG_SHELL-/bin/sh} export SHELL # Unset more variables known to interfere with behavior of common tools. CLICOLOR_FORCE= GREP_OPTIONS= unset CLICOLOR_FORCE GREP_OPTIONS ## --------------------- ## ## M4sh Shell Functions. ## ## --------------------- ## # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' else as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' else as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits as_lineno_1=$LINENO as_lineno_1a=$LINENO as_lineno_2=$LINENO as_lineno_2a=$LINENO eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall # in an infinite loop. This has already happened in practice. _as_can_reexec=no; export _as_can_reexec # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" test -n "$DJDIR" || exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME='repmgr' PACKAGE_TARNAME='repmgr' PACKAGE_VERSION='5.3.0' PACKAGE_STRING='repmgr 5.3.0' PACKAGE_BUGREPORT='repmgr@googlegroups.com' PACKAGE_URL='https://repmgr.org/' ac_subst_vars='LTLIBOBJS LIBOBJS HAVE_SED HAVE_GSED HAVE_GNUSED vpath_build SED PG_CONFIG target_alias host_alias build_alias LIBS ECHO_T ECHO_N ECHO_C DEFS mandir localedir libdir psdir pdfdir dvidir htmldir infodir docdir oldincludedir includedir localstatedir sharedstatedir sysconfdir datadir datarootdir libexecdir sbindir bindir program_transform_name prefix exec_prefix PACKAGE_URL PACKAGE_BUGREPORT PACKAGE_STRING PACKAGE_VERSION PACKAGE_TARNAME PACKAGE_NAME PATH_SEPARATOR SHELL' ac_subst_files='' ac_user_opts=' enable_option_checking ' ac_precious_vars='build_alias host_alias target_alias PG_CONFIG' # Initialize some variables set by options. ac_init_help= ac_init_version=false ac_unrecognized_opts= ac_unrecognized_sep= # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *=) ac_optarg= ;; *) ac_optarg=yes ;; esac # Accept the important Cygnus configure options, so we can diagnose typos. case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=\$ac_optarg ;; -without-* | --without-*) ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) as_fn_error $? "unrecognized option: \`$ac_option' Try \`$0 --help' for more information" ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; esac done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` as_fn_error $? "missing argument to $ac_option" fi if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi # Check all directory arguments for consistency. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir do eval ac_val=\$$ac_var # Remove trailing slashes. case $ac_val in */ ) ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` eval $ac_var=\$ac_val;; esac # Be sure to have absolute directory names. case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done # There might be people who depend on the old broken behavior: `$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || as_fn_error $? "working directory cannot be determined" test "X$ac_ls_di" = "X$ac_pwd_ls_di" || as_fn_error $? "pwd does not report name of working directory" # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$as_myself" || $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF \`configure' configures repmgr 5.3.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print \`checking ...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for \`--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or \`..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, \`make install' will install all the files in \`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify an installation prefix other than \`$ac_default_prefix' using \`--prefix', for instance \`--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/repmgr] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in short | recursive ) echo "Configuration of repmgr 5.3.0:";; esac cat <<\_ACEOF Some influential environment variables: PG_CONFIG Location to find pg_config for target PostgreSQL (default PATH) Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to . repmgr home page: . _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for guested configure. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF repmgr configure 5.3.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. Copyright (c) 2010-2021, EnterpriseDB Corporation _ACEOF exit fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by repmgr $as_me 5.3.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. $as_echo "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; 2) as_fn_append ac_configure_args1 " '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi as_fn_append ac_configure_args " '$ac_arg'" ;; esac done done { ac_configure_args0=; unset ac_configure_args0;} { ac_configure_args1=; unset ac_configure_args1;} # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there # would cause problems or look ugly. # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? # Save into config.log some information that might help in debugging. { echo $as_echo "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo # The following way of writing the cache mishandles newlines in values, ( for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( *${as_nl}ac_space=\ *) sed -n \ "s/'\''/'\''\\\\'\'''\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" ;; #( *) sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) echo $as_echo "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac $as_echo "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then $as_echo "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac $as_echo "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then $as_echo "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo cat confdefs.h echo fi test "$ac_signal" != 0 && $as_echo "$as_me: caught signal $ac_signal" $as_echo "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h $as_echo "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. cat >>confdefs.h <<_ACEOF #define PACKAGE_NAME "$PACKAGE_NAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_TARNAME "$PACKAGE_TARNAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_VERSION "$PACKAGE_VERSION" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_STRING "$PACKAGE_STRING" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_URL "$PACKAGE_URL" _ACEOF # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. ac_site_file1=NONE ac_site_file2=NONE if test -n "$CONFIG_SITE"; then # We do not want a PATH search for config.site. case $CONFIG_SITE in #(( -*) ac_site_file1=./$CONFIG_SITE;; */*) ac_site_file1=$CONFIG_SITE;; *) ac_site_file1=./$CONFIG_SITE;; esac elif test "x$prefix" != xNONE; then ac_site_file1=$prefix/share/config.site ac_site_file2=$prefix/etc/config.site else ac_site_file1=$ac_default_prefix/share/config.site ac_site_file2=$ac_default_prefix/etc/config.site fi for ac_site_file in "$ac_site_file1" "$ac_site_file2" do test "x$ac_site_file" = xNONE && continue if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 $as_echo "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file See \`config.log' for more details" "$LINENO" 5; } fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 $as_echo "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 $as_echo "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 $as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 $as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then # differences in whitespace do not lead to failure. ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 $as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 $as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 $as_echo "$as_me: former value: \`$ac_old_val'" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 $as_echo "$as_me: current value: \`$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) as_fn_append ac_configure_args " '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 $as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## ## -------------------- ## ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_config_headers="$ac_config_headers config.h" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5 $as_echo_n "checking for a sed that does not truncate output... " >&6; } if ${ac_cv_path_SED+:} false; then : $as_echo_n "(cached) " >&6 else ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ for ac_i in 1 2 3 4 5 6 7; do ac_script="$ac_script$as_nl$ac_script" done echo "$ac_script" 2>/dev/null | sed 99q >conftest.sed { ac_script=; unset ac_script;} if test -z "$SED"; then ac_path_SED_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in sed gsed; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_SED="$as_dir/$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_SED" || continue # Check for GNU ac_path_SED and select it if it is found. # Check for GNU $ac_path_SED case `"$ac_path_SED" --version 2>&1` in *GNU*) ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;; *) ac_count=0 $as_echo_n 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" $as_echo '' >> "conftest.nl" "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_SED_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_SED="$ac_path_SED" ac_path_SED_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_SED_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_SED"; then as_fn_error $? "no acceptable sed could be found in \$PATH" "$LINENO" 5 fi else ac_cv_path_SED=$SED fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5 $as_echo "$ac_cv_path_SED" >&6; } SED="$ac_cv_path_SED" rm -f conftest.sed if test -z "$PG_CONFIG"; then # Extract the first word of "pg_config", so it can be a program name with args. set dummy pg_config; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_PG_CONFIG+:} false; then : $as_echo_n "(cached) " >&6 else case $PG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_PG_CONFIG="$PG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_PG_CONFIG="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi PG_CONFIG=$ac_cv_path_PG_CONFIG if test -n "$PG_CONFIG"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PG_CONFIG" >&5 $as_echo "$PG_CONFIG" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$PG_CONFIG"; then as_fn_error $? "could not find pg_config, set PG_CONFIG or PATH" "$LINENO" 5 fi pgac_pg_config_version=$($PG_CONFIG --version 2>/dev/null) major_version_num=$(echo "$pgac_pg_config_version"| $SED -e 's/^[^0-9]\+ \([0-9]\{1,2\}\).*$/\1/') if test "$major_version_num" -lt '10'; then version_num=$(echo "$pgac_pg_config_version"| $SED -e 's/^[^0-9]\+ \([0-9]*\)\.\([0-9]*\)\([a-zA-Z0-9.]*\)$/\1.\2/') if test -z "$version_num"; then as_fn_error $? "could not detect the PostgreSQL version, wrong or broken pg_config?" "$LINENO" 5 fi version_num_int=$(echo "$version_num"| $SED -e 's/^\([0-9]*\)\.\([0-9]*\)$/\1\2/') if test "$version_num_int" -lt '94'; then as_fn_error $? "repmgr is not compatible with detected PostgreSQL version: $version_num" "$LINENO" 5 fi else version_num=$(echo "$pgac_pg_config_version"| $SED -e 's/^[^0-9]\+ \(.\+\)$/\1/') if test -z "$version_num"; then as_fn_error $? "could not detect the PostgreSQL version, wrong or broken pg_config?" "$LINENO" 5 fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: building against PostgreSQL $version_num" >&5 $as_echo "$as_me: building against PostgreSQL $version_num" >&6;} # add includedir to prerequisites, so tests for headers can succeed CPPFLAGS="-I$($PG_CONFIG --includedir-server) $CFLAGS" # check whether we're building inside the source tree. if test "$srcdir" -ef '.' ; then vpath_build=no else vpath_build=yes fi # Extract the first word of "gnused", so it can be a program name with args. set dummy gnused; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_HAVE_GNUSED+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$HAVE_GNUSED"; then ac_cv_prog_HAVE_GNUSED="$HAVE_GNUSED" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_HAVE_GNUSED="yes" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_HAVE_GNUSED" && ac_cv_prog_HAVE_GNUSED="no" fi fi HAVE_GNUSED=$ac_cv_prog_HAVE_GNUSED if test -n "$HAVE_GNUSED"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $HAVE_GNUSED" >&5 $as_echo "$HAVE_GNUSED" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi # Extract the first word of "gsed", so it can be a program name with args. set dummy gsed; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_HAVE_GSED+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$HAVE_GSED"; then ac_cv_prog_HAVE_GSED="$HAVE_GSED" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_HAVE_GSED="yes" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_HAVE_GSED" && ac_cv_prog_HAVE_GSED="no" fi fi HAVE_GSED=$ac_cv_prog_HAVE_GSED if test -n "$HAVE_GSED"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $HAVE_GSED" >&5 $as_echo "$HAVE_GSED" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi # Extract the first word of "sed", so it can be a program name with args. set dummy sed; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_HAVE_SED+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$HAVE_SED"; then ac_cv_prog_HAVE_SED="$HAVE_SED" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_HAVE_SED="yes" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_HAVE_SED" && ac_cv_prog_HAVE_SED="no" fi fi HAVE_SED=$ac_cv_prog_HAVE_SED if test -n "$HAVE_SED"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $HAVE_SED" >&5 $as_echo "$HAVE_SED" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "$HAVE_GNUSED" = yes; then SED=gnused else if test "$HAVE_GSED" = yes; then SED=gsed else SED=sed fi fi ac_config_files="$ac_config_files Makefile" ac_config_files="$ac_config_files Makefile.global" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # `ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* `ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # `set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # `set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 $as_echo "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else case $cache_file in #( */* | ?:*) mv -f confcache "$cache_file"$$ && mv -f "$cache_file"$$ "$cache_file" ;; #( *) mv -f confcache "$cache_file" ;; esac fi fi else { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 $as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' DEFS=-DHAVE_CONFIG_H ac_libobjs= ac_ltlibobjs= U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`$as_echo "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 $as_echo "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi as_nl=' ' export as_nl # Printing a long string crashes Solaris 7 /usr/bin/printf. as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo # Prefer a ksh shell builtin over an external printf program on Solaris, # but without wasting forks for bash or zsh. if test -z "$BASH_VERSION$ZSH_VERSION" \ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='print -r --' as_echo_n='print -rn --' elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='printf %s\n' as_echo_n='printf %s' else if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' as_echo_n='/usr/ucb/echo -n' else as_echo_body='eval expr "X$1" : "X\\(.*\\)"' as_echo_n_body='eval arg=$1; case $arg in #( *"$as_nl"*) expr "X$arg" : "X\\(.*\\)$as_nl"; arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; esac; expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" ' export as_echo_n_body as_echo_n='sh -c $as_echo_n_body as_echo' fi export as_echo_body as_echo='sh -c $as_echo_body as_echo' fi # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Unset variables that we do not need and which cause bugs (e.g. in # pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" # suppresses any "Segmentation fault" message there. '((' could # trigger a bug in pdksh 5.2.14. for as_var in BASH_ENV ENV MAIL MAILPATH do eval test x\${$as_var+set} = xset \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' else as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' else as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 6>&1 ## ----------------------------------- ## ## Main body of $CONFIG_STATUS script. ## ## ----------------------------------- ## _ASEOF test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by repmgr $as_me 5.3.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF case $ac_config_files in *" "*) set x $ac_config_files; shift; ac_config_files=$*;; esac case $ac_config_headers in *" "*) set x $ac_config_headers; shift; ac_config_headers=$*;; esac cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" config_headers="$ac_config_headers" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ \`$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit --config print configuration, then exit -q, --quiet, --silent do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE --header=FILE[:TEMPLATE] instantiate the configuration header FILE Configuration files: $config_files Configuration headers: $config_headers Report bugs to . repmgr home page: ." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ repmgr config.status 5.3.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" Copyright (C) 2012 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' test -n "\$AWK" || AWK=awk _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do case $1 in --*=?*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; --*=) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg= ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) $as_echo "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) $as_echo "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --header | --heade | --head | --hea ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append CONFIG_HEADERS " '$ac_optarg'" ac_need_defaults=false;; --he | --h) # Conflict between --help and --header as_fn_error $? "ambiguous option: \`$1' Try \`$0 --help' for more information.";; --help | --hel | -h ) $as_echo "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) as_fn_error $? "unrecognized option: \`$1' Try \`$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX $as_echo "$ac_log" } >&5 _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "Makefile.global") CONFIG_FILES="$CONFIG_FILES Makefile.global" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to `$tmp'. $debug || { tmp= ac_tmp= trap 'exit_status=$? : "${ac_tmp:=$tmp}" { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. # This happens for instance with `./config.status config.h'. if test -n "$CONFIG_FILES"; then ac_cr=`echo X | tr X '\015'` # On cygwin, bash can eat \r inside `` if the user requested igncr. # But we know of no other shell where ac_cr would be empty at this # point, so we can use a bashism as a fallback. if test "x$ac_cr" = x; then eval ac_cr=\$\'\\r\' fi ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then ac_cs_awk_cr='\\r' else ac_cs_awk_cr=$ac_cr fi echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF { echo "cat >conf$$subs.awk <<_ACEOF" && echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done rm -f conf$$subs.sh cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h s/^/S["/; s/!.*/"]=/ p g s/^[^!]*!// :repl t repl s/'"$ac_delim"'$// t delim :nl h s/\(.\{148\}\)..*/\1/ t more1 s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ p n b repl :more1 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t nl :delim h s/\(.\{148\}\)..*/\1/ t more2 s/["\\]/\\&/g; s/^/"/; s/$/"/ p b :more2 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t delim ' >$CONFIG_STATUS || ac_write_fail=1 rm -f conf$$subs.awk cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" } { line = $ 0 nfields = split(line, field, "@") substed = 0 len = length(field[1]) for (i = 2; i < nfields; i++) { key = field[i] keylen = length(key) if (S_is_set[key]) { value = S[key] line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) len += length(value) + length(field[++i]) substed = 1 } else len += 1 + keylen } print line } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF # VPATH may cause trouble with some makes, so we remove sole $(srcdir), # ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ h s/// s/^/:/ s/[ ]*$/:/ s/:\$(srcdir):/:/g s/:\${srcdir}:/:/g s/:@srcdir@:/:/g s/^:*// s/:*$// x s/\(=[ ]*\).*/\1/ G s/\n// s/^[^=]*=[ ]*$// }' fi cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" # Set up the scripts for CONFIG_HEADERS section. # No need to generate them if there are no CONFIG_HEADERS. # This happens for instance with `./config.status Makefile'. if test -n "$CONFIG_HEADERS"; then cat >"$ac_tmp/defines.awk" <<\_ACAWK || BEGIN { _ACEOF # Transform confdefs.h into an awk script `defines.awk', embedded as # here-document in config.status, that substitutes the proper values into # config.h.in to produce config.h. # Create a delimiter string that does not exist in confdefs.h, to ease # handling of long lines. ac_delim='%!_!# ' for ac_last_try in false false :; do ac_tt=`sed -n "/$ac_delim/p" confdefs.h` if test -z "$ac_tt"; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done # For the awk script, D is an array of macro values keyed by name, # likewise P contains macro parameters if any. Preserve backslash # newline sequences. ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* sed -n ' s/.\{148\}/&'"$ac_delim"'/g t rset :rset s/^[ ]*#[ ]*define[ ][ ]*/ / t def d :def s/\\$// t bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3"/p s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p d :bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3\\\\\\n"\\/p t cont s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p t cont d :cont n s/.\{148\}/&'"$ac_delim"'/g t clear :clear s/\\$// t bsnlc s/["\\]/\\&/g; s/^/"/; s/$/"/p d :bsnlc s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p b cont ' >$CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 for (key in D) D_is_set[key] = 1 FS = "" } /^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { line = \$ 0 split(line, arg, " ") if (arg[1] == "#") { defundef = arg[2] mac1 = arg[3] } else { defundef = substr(arg[1], 2) mac1 = arg[2] } split(mac1, mac2, "(") #) macro = mac2[1] prefix = substr(line, 1, index(line, defundef) - 1) if (D_is_set[macro]) { # Preserve the white space surrounding the "#". print prefix "define", macro P[macro] D[macro] next } else { # Replace #undef with comments. This is necessary, for example, # in the case of _POSIX_SOURCE, which is predefined and required # on some systems where configure will not decide to define it. if (defundef == "undef") { print "/*", prefix defundef, macro, "*/" next } } } { print } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 fi # test -n "$CONFIG_HEADERS" eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS " shift for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain `:'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 $as_echo "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) ac_sed_conf_input=`$as_echo "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac case $ac_tag in *:-:* | *:-) cat >"$ac_tmp/stdin" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= ac_sed_dataroot=' /datarootdir/ { p q } /@datadir@/p /@docdir@/p /@infodir@/p /@localedir@/p /@mandir@/p' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 $as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when `$srcdir' = `.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t $ac_datarootdir_hack " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&5 $as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" case $ac_file in -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; :H) # # CONFIG_HEADER # if test x"$ac_file" != x-; then { $as_echo "/* $configure_input */" \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" } >"$ac_tmp/config.h" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 $as_echo "$as_me: $ac_file is unchanged" >&6;} else rm -f "$ac_file" mv "$ac_tmp/config.h" "$ac_file" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 fi else $as_echo "/* $configure_input */" \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ || as_fn_error $? "could not create -" "$LINENO" 5 fi ;; esac done # for ac_tag as_fn_exit 0 _ACEOF ac_clean_files=$ac_clean_files_save test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi repmgr-5.3.1/configure.in000066400000000000000000000041271420262710000153230ustar00rootroot00000000000000AC_INIT([repmgr], [5.3.1], [repmgr@googlegroups.com], [repmgr], [https://repmgr.org/]) AC_COPYRIGHT([Copyright (c) 2010-2021, EnterpriseDB Corporation]) AC_CONFIG_HEADER(config.h) AC_ARG_VAR([PG_CONFIG], [Location to find pg_config for target PostgreSQL (default PATH)]) AC_PROG_SED if test -z "$PG_CONFIG"; then AC_PATH_PROG(PG_CONFIG, pg_config) fi if test -z "$PG_CONFIG"; then AC_MSG_ERROR([could not find pg_config, set PG_CONFIG or PATH]) fi pgac_pg_config_version=$($PG_CONFIG --version 2>/dev/null) major_version_num=$(echo "$pgac_pg_config_version"| $SED -e 's/^[[^0-9]]\+ \([[0-9]]\{1,2\}\).*$/\1/') if test "$major_version_num" -lt '10'; then version_num=$(echo "$pgac_pg_config_version"| $SED -e 's/^[[^0-9]]\+ \([[0-9]]*\)\.\([[0-9]]*\)\([[a-zA-Z0-9.]]*\)$/\1.\2/') if test -z "$version_num"; then AC_MSG_ERROR([could not detect the PostgreSQL version, wrong or broken pg_config?]) fi version_num_int=$(echo "$version_num"| $SED -e 's/^\([[0-9]]*\)\.\([[0-9]]*\)$/\1\2/') if test "$version_num_int" -lt '94'; then AC_MSG_ERROR([repmgr is not compatible with detected PostgreSQL version: $version_num]) fi else version_num=$(echo "$pgac_pg_config_version"| $SED -e 's/^[[^0-9]]\+ \(.\+\)$/\1/') if test -z "$version_num"; then AC_MSG_ERROR([could not detect the PostgreSQL version, wrong or broken pg_config?]) fi fi AC_MSG_NOTICE([building against PostgreSQL $version_num]) # add includedir to prerequisites, so tests for headers can succeed CPPFLAGS="-I$($PG_CONFIG --includedir-server) $CFLAGS" # check whether we're building inside the source tree. if test "$srcdir" -ef '.' ; then vpath_build=no else vpath_build=yes fi AC_SUBST(vpath_build) AC_CHECK_PROG(HAVE_GNUSED,gnused,yes,no) AC_CHECK_PROG(HAVE_GSED,gsed,yes,no) AC_CHECK_PROG(HAVE_SED,sed,yes,no) if test "$HAVE_GNUSED" = yes; then SED=gnused else if test "$HAVE_GSED" = yes; then SED=gsed else SED=sed fi fi AC_SUBST(SED) AC_CONFIG_FILES([Makefile]) AC_CONFIG_FILES([Makefile.global]) AC_OUTPUT repmgr-5.3.1/contrib/000077500000000000000000000000001420262710000144465ustar00rootroot00000000000000repmgr-5.3.1/contrib/convert-config.pl000077500000000000000000000036641420262710000177420ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; if (scalar @ARGV < 1) { print qq|Please provide path to the repmgr.conf file as first parameter\n|; exit(1); } my $file = $ARGV[0]; if (!-e $file) { print qq|Provide file "$file" does not exist\n|; exit(1); } if (!-f $file) { print qq|Provide path "$file" does not point to a file\n|; exit(1); } my @outp = (); my $fh = undef; if (!open ($fh, $file)){ print qq|Unable to open "$file"\n|; exit(1); } my $data_directory_found = 0; while(<$fh>) { my $line = $_; chomp $line; if ($line =~ m|\s*#|) { push @outp, $line; next; } if ($line !~ m|\s*(\S+?)\s*=(.+?)$|) { push @outp, $line; next; } my $param = $1; my $value = $2; # These parameters can be removed: next if ($param eq 'cluster'); # These parameters can be renamed: if ($param eq 'node') { push @outp, qq|node_id=${value}|; } elsif ($param eq 'loglevel') { push @outp, qq|log_level=${value}|; } elsif ($param eq 'logfacility') { push @outp, qq|log_facility=${value}|; } elsif ($param eq 'logfile') { push @outp, qq|log_file=${value}|; } elsif ($param eq 'master_response_timeout') { push @outp, qq|async_query_timeout=${value}|; } elsif ($param eq 'retry_promote_interval_secs') { push @outp, qq|primary_notification_timeout=${value}|; } else { if ($param eq 'data_directory') { $data_directory_found = 1; } # Don't quote numbers if ($value =~ /^\d+$/) { push @outp, sprintf(q|%s=%s|, $param, $value); } # Quote everything else else { $value =~ s/'/''/g; push @outp, sprintf(q|%s='%s'|, $param, $value); } } } close $fh; print join("\n", @outp); print "\n"; if ($data_directory_found == 0) { print "data_directory=''\n"; } repmgr-5.3.1/controldata.c000066400000000000000000000247661420262710000155030ustar00rootroot00000000000000/* * controldata.c - functions for reading the pg_control file * * The functions provided here enable repmgr to read a pg_control file * in a version-independent way, even if the PostgreSQL instance is not * running. For that reason we can't use on the pg_control_*() functions * provided in PostgreSQL 9.6 and later. * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * Portions Copyright (c) 1996-2016, 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 . */ #include #include #include #include "postgres_fe.h" #include "repmgr.h" #include "controldata.h" static ControlFileInfo *get_controlfile(const char *DataDir); int get_pg_version(const char *data_directory, char *version_string) { char PgVersionPath[MAXPGPATH] = ""; FILE *fp = NULL; char *endptr = NULL; char file_version_string[MAX_VERSION_STRING] = ""; long file_major, file_minor; int ret; snprintf(PgVersionPath, MAXPGPATH, "%s/PG_VERSION", data_directory); fp = fopen(PgVersionPath, "r"); if (fp == NULL) { log_warning(_("could not open file \"%s\" for reading"), PgVersionPath); log_detail("%s", strerror(errno)); return UNKNOWN_SERVER_VERSION_NUM; } file_version_string[0] = '\0'; ret = fscanf(fp, "%23s", file_version_string); fclose(fp); if (ret != 1 || endptr == file_version_string) { log_warning(_("unable to determine major version number from PG_VERSION")); return UNKNOWN_SERVER_VERSION_NUM; } file_major = strtol(file_version_string, &endptr, 10); file_minor = 0; if (*endptr == '.') file_minor = strtol(endptr + 1, NULL, 10); if (version_string != NULL) strncpy(version_string, file_version_string, MAX_VERSION_STRING); return ((int) file_major * 10000) + ((int) file_minor * 100); } uint64 get_system_identifier(const char *data_directory) { ControlFileInfo *control_file_info = NULL; uint64 system_identifier = UNKNOWN_SYSTEM_IDENTIFIER; control_file_info = get_controlfile(data_directory); if (control_file_info->control_file_processed == true) system_identifier = control_file_info->system_identifier; pfree(control_file_info); return system_identifier; } bool get_db_state(const char *data_directory, DBState *state) { ControlFileInfo *control_file_info = NULL; bool control_file_processed; control_file_info = get_controlfile(data_directory); control_file_processed = control_file_info->control_file_processed; if (control_file_processed == true) *state = control_file_info->state; pfree(control_file_info); return control_file_processed; } XLogRecPtr get_latest_checkpoint_location(const char *data_directory) { ControlFileInfo *control_file_info = NULL; XLogRecPtr checkPoint = InvalidXLogRecPtr; control_file_info = get_controlfile(data_directory); if (control_file_info->control_file_processed == true) checkPoint = control_file_info->checkPoint; pfree(control_file_info); return checkPoint; } int get_data_checksum_version(const char *data_directory) { ControlFileInfo *control_file_info = NULL; int data_checksum_version = UNKNOWN_DATA_CHECKSUM_VERSION; control_file_info = get_controlfile(data_directory); if (control_file_info->control_file_processed == true) data_checksum_version = (int) control_file_info->data_checksum_version; pfree(control_file_info); return data_checksum_version; } const char * describe_db_state(DBState state) { switch (state) { case DB_STARTUP: return _("starting up"); case DB_SHUTDOWNED: return _("shut down"); case DB_SHUTDOWNED_IN_RECOVERY: return _("shut down in recovery"); case DB_SHUTDOWNING: return _("shutting down"); case DB_IN_CRASH_RECOVERY: return _("in crash recovery"); case DB_IN_ARCHIVE_RECOVERY: return _("in archive recovery"); case DB_IN_PRODUCTION: return _("in production"); } return _("unrecognized status code"); } TimeLineID get_timeline(const char *data_directory) { ControlFileInfo *control_file_info = NULL; TimeLineID timeline = -1; control_file_info = get_controlfile(data_directory); timeline = (int) control_file_info->timeline; pfree(control_file_info); return timeline; } TimeLineID get_min_recovery_end_timeline(const char *data_directory) { ControlFileInfo *control_file_info = NULL; TimeLineID timeline = -1; control_file_info = get_controlfile(data_directory); timeline = (int) control_file_info->minRecoveryPointTLI; pfree(control_file_info); return timeline; } XLogRecPtr get_min_recovery_location(const char *data_directory) { ControlFileInfo *control_file_info = NULL; XLogRecPtr minRecoveryPoint = InvalidXLogRecPtr; control_file_info = get_controlfile(data_directory); minRecoveryPoint = control_file_info->minRecoveryPoint; pfree(control_file_info); return minRecoveryPoint; } /* * We maintain our own version of get_controlfile() as we need cross-version * compatibility, and also don't care if the file isn't readable. */ static ControlFileInfo * get_controlfile(const char *DataDir) { char file_version_string[MAX_VERSION_STRING] = ""; ControlFileInfo *control_file_info; int fd, version_num; char ControlFilePath[MAXPGPATH] = ""; void *ControlFileDataPtr = NULL; int expected_size = 0; control_file_info = palloc0(sizeof(ControlFileInfo)); /* set default values */ control_file_info->control_file_processed = false; control_file_info->system_identifier = UNKNOWN_SYSTEM_IDENTIFIER; control_file_info->state = DB_SHUTDOWNED; control_file_info->checkPoint = InvalidXLogRecPtr; control_file_info->data_checksum_version = -1; control_file_info->timeline = -1; control_file_info->minRecoveryPointTLI = -1; control_file_info->minRecoveryPoint = InvalidXLogRecPtr; /* * Read PG_VERSION, as we'll need to determine which struct to read * the control file contents into */ version_num = get_pg_version(DataDir, file_version_string); if (version_num == UNKNOWN_SERVER_VERSION_NUM) { log_warning(_("unable to determine server version number from PG_VERSION")); return control_file_info; } if (version_num < MIN_SUPPORTED_VERSION_NUM) { log_warning(_("data directory appears to be initialised for %s"), file_version_string); log_detail(_("minimum supported PostgreSQL version is %s"), MIN_SUPPORTED_VERSION); return control_file_info; } snprintf(ControlFilePath, MAXPGPATH, "%s/global/pg_control", DataDir); if ((fd = open(ControlFilePath, O_RDONLY | PG_BINARY, 0)) == -1) { log_warning(_("could not open file \"%s\" for reading"), ControlFilePath); log_detail("%s", strerror(errno)); return control_file_info; } if (version_num >= 120000) { #if PG_ACTUAL_VERSION_NUM >= 120000 expected_size = sizeof(ControlFileData12); ControlFileDataPtr = palloc0(expected_size); #endif } else if (version_num >= 110000) { expected_size = sizeof(ControlFileData11); ControlFileDataPtr = palloc0(expected_size); } else if (version_num >= 90500) { expected_size = sizeof(ControlFileData95); ControlFileDataPtr = palloc0(expected_size); } else if (version_num >= 90400) { expected_size = sizeof(ControlFileData94); ControlFileDataPtr = palloc0(expected_size); } if (read(fd, ControlFileDataPtr, expected_size) != expected_size) { log_warning(_("could not read file \"%s\""), ControlFilePath); log_detail("%s", strerror(errno)); close(fd); return control_file_info; } close(fd); control_file_info->control_file_processed = true; if (version_num >= 120000) { #if PG_ACTUAL_VERSION_NUM >= 120000 ControlFileData12 *ptr = (struct ControlFileData12 *)ControlFileDataPtr; control_file_info->system_identifier = ptr->system_identifier; control_file_info->state = ptr->state; control_file_info->checkPoint = ptr->checkPoint; control_file_info->data_checksum_version = ptr->data_checksum_version; control_file_info->timeline = ptr->checkPointCopy.ThisTimeLineID; control_file_info->minRecoveryPointTLI = ptr->minRecoveryPointTLI; control_file_info->minRecoveryPoint = ptr->minRecoveryPoint; #else fprintf(stderr, "ERROR: please use a repmgr version built for PostgreSQL 12 or later\n"); exit(ERR_BAD_CONFIG); #endif } else if (version_num >= 110000) { ControlFileData11 *ptr = (struct ControlFileData11 *)ControlFileDataPtr; control_file_info->system_identifier = ptr->system_identifier; control_file_info->state = ptr->state; control_file_info->checkPoint = ptr->checkPoint; control_file_info->data_checksum_version = ptr->data_checksum_version; control_file_info->timeline = ptr->checkPointCopy.ThisTimeLineID; control_file_info->minRecoveryPointTLI = ptr->minRecoveryPointTLI; control_file_info->minRecoveryPoint = ptr->minRecoveryPoint; } else if (version_num >= 90500) { ControlFileData95 *ptr = (struct ControlFileData95 *)ControlFileDataPtr; control_file_info->system_identifier = ptr->system_identifier; control_file_info->state = ptr->state; control_file_info->checkPoint = ptr->checkPoint; control_file_info->data_checksum_version = ptr->data_checksum_version; control_file_info->timeline = ptr->checkPointCopy.ThisTimeLineID; control_file_info->minRecoveryPointTLI = ptr->minRecoveryPointTLI; control_file_info->minRecoveryPoint = ptr->minRecoveryPoint; } else if (version_num >= 90400) { ControlFileData94 *ptr = (struct ControlFileData94 *)ControlFileDataPtr; control_file_info->system_identifier = ptr->system_identifier; control_file_info->state = ptr->state; control_file_info->checkPoint = ptr->checkPoint; control_file_info->data_checksum_version = ptr->data_checksum_version; control_file_info->timeline = ptr->checkPointCopy.ThisTimeLineID; control_file_info->minRecoveryPointTLI = ptr->minRecoveryPointTLI; control_file_info->minRecoveryPoint = ptr->minRecoveryPoint; } pfree(ControlFileDataPtr); /* * We don't check the CRC here as we're potentially checking a pg_control * file from a different PostgreSQL version to the one repmgr was compiled * against. */ return control_file_info; } repmgr-5.3.1/controldata.h000066400000000000000000000301071420262710000154720ustar00rootroot00000000000000/* * controldata.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California */ #ifndef _CONTROLDATA_H_ #define _CONTROLDATA_H_ #include "postgres_fe.h" #include "catalog/pg_control.h" #define MAX_VERSION_STRING 24 /* * A simplified representation of pg_control containing only those fields * required by repmgr. */ typedef struct { bool control_file_processed; uint64 system_identifier; DBState state; XLogRecPtr checkPoint; uint32 data_checksum_version; TimeLineID timeline; TimeLineID minRecoveryPointTLI; XLogRecPtr minRecoveryPoint; } ControlFileInfo; typedef struct CheckPoint94 { XLogRecPtr redo; /* next RecPtr available when we began to * create CheckPoint (i.e. REDO start point) */ TimeLineID ThisTimeLineID; /* current TLI */ TimeLineID PrevTimeLineID; /* previous TLI, if this record begins a new * timeline (equals ThisTimeLineID otherwise) */ bool fullPageWrites; /* current full_page_writes */ uint32 nextXidEpoch; /* higher-order bits of nextXid */ TransactionId nextXid; /* next free XID */ Oid nextOid; /* next free OID */ MultiXactId nextMulti; /* next free MultiXactId */ MultiXactOffset nextMultiOffset; /* next free MultiXact offset */ TransactionId oldestXid; /* cluster-wide minimum datfrozenxid */ Oid oldestXidDB; /* database with minimum datfrozenxid */ MultiXactId oldestMulti; /* cluster-wide minimum datminmxid */ Oid oldestMultiDB; /* database with minimum datminmxid */ pg_time_t time; /* time stamp of checkpoint */ TransactionId oldestActiveXid; } CheckPoint94; /* Same for 9.5, 9.6, 10, 11 */ typedef struct CheckPoint95 { XLogRecPtr redo; /* next RecPtr available when we began to * create CheckPoint (i.e. REDO start point) */ TimeLineID ThisTimeLineID; /* current TLI */ TimeLineID PrevTimeLineID; /* previous TLI, if this record begins a new * timeline (equals ThisTimeLineID otherwise) */ bool fullPageWrites; /* current full_page_writes */ uint32 nextXidEpoch; /* higher-order bits of nextXid */ TransactionId nextXid; /* next free XID */ Oid nextOid; /* next free OID */ MultiXactId nextMulti; /* next free MultiXactId */ MultiXactOffset nextMultiOffset; /* next free MultiXact offset */ TransactionId oldestXid; /* cluster-wide minimum datfrozenxid */ Oid oldestXidDB; /* database with minimum datfrozenxid */ MultiXactId oldestMulti; /* cluster-wide minimum datminmxid */ Oid oldestMultiDB; /* database with minimum datminmxid */ pg_time_t time; /* time stamp of checkpoint */ TransactionId oldestCommitTsXid; /* oldest Xid with valid commit * timestamp */ TransactionId newestCommitTsXid; /* newest Xid with valid commit * timestamp */ TransactionId oldestActiveXid; } CheckPoint95; #if PG_ACTUAL_VERSION_NUM >= 120000 /* * Following fields removed in PostgreSQL 12; * * uint32 nextXidEpoch; * TransactionId nextXid; * * and replaced by: * * FullTransactionId nextFullXid; */ typedef struct CheckPoint12 { XLogRecPtr redo; /* next RecPtr available when we began to * create CheckPoint (i.e. REDO start point) */ TimeLineID ThisTimeLineID; /* current TLI */ TimeLineID PrevTimeLineID; /* previous TLI, if this record begins a new * timeline (equals ThisTimeLineID otherwise) */ bool fullPageWrites; /* current full_page_writes */ FullTransactionId nextFullXid; /* next free full transaction ID */ Oid nextOid; /* next free OID */ MultiXactId nextMulti; /* next free MultiXactId */ MultiXactOffset nextMultiOffset; /* next free MultiXact offset */ TransactionId oldestXid; /* cluster-wide minimum datfrozenxid */ Oid oldestXidDB; /* database with minimum datfrozenxid */ MultiXactId oldestMulti; /* cluster-wide minimum datminmxid */ Oid oldestMultiDB; /* database with minimum datminmxid */ pg_time_t time; /* time stamp of checkpoint */ TransactionId oldestCommitTsXid; /* oldest Xid with valid commit * timestamp */ TransactionId newestCommitTsXid; /* newest Xid with valid commit * timestamp */ /* * Oldest XID still running. This is only needed to initialize hot standby * mode from an online checkpoint, so we only bother calculating this for * online checkpoints and only when wal_level is replica. Otherwise it's * set to InvalidTransactionId. */ TransactionId oldestActiveXid; } CheckPoint12; #endif typedef struct ControlFileData94 { uint64 system_identifier; uint32 pg_control_version; /* PG_CONTROL_VERSION */ uint32 catalog_version_no; /* see catversion.h */ DBState state; /* see enum above */ pg_time_t time; /* time stamp of last pg_control update */ XLogRecPtr checkPoint; /* last check point record ptr */ XLogRecPtr prevCheckPoint; /* previous check point record ptr */ CheckPoint94 checkPointCopy; /* copy of last check point record */ XLogRecPtr unloggedLSN; /* current fake LSN value, for unlogged rels */ XLogRecPtr minRecoveryPoint; TimeLineID minRecoveryPointTLI; XLogRecPtr backupStartPoint; XLogRecPtr backupEndPoint; bool backupEndRequired; int wal_level; bool wal_log_hints; int MaxConnections; int max_worker_processes; int max_prepared_xacts; int max_locks_per_xact; uint32 maxAlign; /* alignment requirement for tuples */ double floatFormat; /* constant 1234567.0 */ uint32 blcksz; /* data block size for this DB */ uint32 relseg_size; /* blocks per segment of large relation */ uint32 xlog_blcksz; /* block size within WAL files */ uint32 xlog_seg_size; /* size of each WAL segment */ uint32 nameDataLen; /* catalog name field width */ uint32 indexMaxKeys; /* max number of columns in an index */ uint32 toast_max_chunk_size; /* chunk size in TOAST tables */ uint32 loblksize; /* chunk size in pg_largeobject */ bool enableIntTimes; /* int64 storage enabled? */ bool float4ByVal; /* float4 pass-by-value? */ bool float8ByVal; /* float8, int8, etc pass-by-value? */ /* Are data pages protected by checksums? Zero if no checksum version */ uint32 data_checksum_version; } ControlFileData94; /* * Following field added since 9.4: * * bool track_commit_timestamp; * * Unchanged in 9.6 * * In 10, following field appended *after* "data_checksum_version": * * char mock_authentication_nonce[MOCK_AUTH_NONCE_LEN]; * * (but we don't care about that) */ typedef struct ControlFileData95 { uint64 system_identifier; uint32 pg_control_version; /* PG_CONTROL_VERSION */ uint32 catalog_version_no; /* see catversion.h */ DBState state; /* see enum above */ pg_time_t time; /* time stamp of last pg_control update */ XLogRecPtr checkPoint; /* last check point record ptr */ XLogRecPtr prevCheckPoint; /* previous check point record ptr */ CheckPoint95 checkPointCopy; /* copy of last check point record */ XLogRecPtr unloggedLSN; /* current fake LSN value, for unlogged rels */ XLogRecPtr minRecoveryPoint; TimeLineID minRecoveryPointTLI; XLogRecPtr backupStartPoint; XLogRecPtr backupEndPoint; bool backupEndRequired; int wal_level; bool wal_log_hints; int MaxConnections; int max_worker_processes; int max_prepared_xacts; int max_locks_per_xact; bool track_commit_timestamp; uint32 maxAlign; /* alignment requirement for tuples */ double floatFormat; /* constant 1234567.0 */ uint32 blcksz; /* data block size for this DB */ uint32 relseg_size; /* blocks per segment of large relation */ uint32 xlog_blcksz; /* block size within WAL files */ uint32 xlog_seg_size; /* size of each WAL segment */ uint32 nameDataLen; /* catalog name field width */ uint32 indexMaxKeys; /* max number of columns in an index */ uint32 toast_max_chunk_size; /* chunk size in TOAST tables */ uint32 loblksize; /* chunk size in pg_largeobject */ bool enableIntTimes; /* int64 storage enabled? */ bool float4ByVal; /* float4 pass-by-value? */ bool float8ByVal; /* float8, int8, etc pass-by-value? */ uint32 data_checksum_version; } ControlFileData95; /* * Following field removed in 11: * * XLogRecPtr prevCheckPoint; * * In 10, following field appended *after* "data_checksum_version": * * char mock_authentication_nonce[MOCK_AUTH_NONCE_LEN]; * * (but we don't care about that) */ typedef struct ControlFileData11 { uint64 system_identifier; uint32 pg_control_version; /* PG_CONTROL_VERSION */ uint32 catalog_version_no; /* see catversion.h */ DBState state; /* see enum above */ pg_time_t time; /* time stamp of last pg_control update */ XLogRecPtr checkPoint; /* last check point record ptr */ CheckPoint95 checkPointCopy; /* copy of last check point record */ XLogRecPtr unloggedLSN; /* current fake LSN value, for unlogged rels */ XLogRecPtr minRecoveryPoint; TimeLineID minRecoveryPointTLI; XLogRecPtr backupStartPoint; XLogRecPtr backupEndPoint; bool backupEndRequired; int wal_level; bool wal_log_hints; int MaxConnections; int max_worker_processes; int max_prepared_xacts; int max_locks_per_xact; bool track_commit_timestamp; uint32 maxAlign; /* alignment requirement for tuples */ double floatFormat; /* constant 1234567.0 */ uint32 blcksz; /* data block size for this DB */ uint32 relseg_size; /* blocks per segment of large relation */ uint32 xlog_blcksz; /* block size within WAL files */ uint32 xlog_seg_size; /* size of each WAL segment */ uint32 nameDataLen; /* catalog name field width */ uint32 indexMaxKeys; /* max number of columns in an index */ uint32 toast_max_chunk_size; /* chunk size in TOAST tables */ uint32 loblksize; /* chunk size in pg_largeobject */ bool enableIntTimes; /* int64 storage enabled? */ bool float4ByVal; /* float4 pass-by-value? */ bool float8ByVal; /* float8, int8, etc pass-by-value? */ uint32 data_checksum_version; } ControlFileData11; #if PG_ACTUAL_VERSION_NUM >= 120000 /* * Following field added in Pg12: * * int max_wal_senders; */ typedef struct ControlFileData12 { uint64 system_identifier; uint32 pg_control_version; /* PG_CONTROL_VERSION */ uint32 catalog_version_no; /* see catversion.h */ DBState state; /* see enum above */ pg_time_t time; /* time stamp of last pg_control update */ XLogRecPtr checkPoint; /* last check point record ptr */ CheckPoint12 checkPointCopy; /* copy of last check point record */ XLogRecPtr unloggedLSN; /* current fake LSN value, for unlogged rels */ XLogRecPtr minRecoveryPoint; TimeLineID minRecoveryPointTLI; XLogRecPtr backupStartPoint; XLogRecPtr backupEndPoint; bool backupEndRequired; int wal_level; bool wal_log_hints; int MaxConnections; int max_worker_processes; int max_wal_senders; int max_prepared_xacts; int max_locks_per_xact; bool track_commit_timestamp; uint32 maxAlign; /* alignment requirement for tuples */ double floatFormat; /* constant 1234567.0 */ uint32 blcksz; /* data block size for this DB */ uint32 relseg_size; /* blocks per segment of large relation */ uint32 xlog_blcksz; /* block size within WAL files */ uint32 xlog_seg_size; /* size of each WAL segment */ uint32 nameDataLen; /* catalog name field width */ uint32 indexMaxKeys; /* max number of columns in an index */ uint32 toast_max_chunk_size; /* chunk size in TOAST tables */ uint32 loblksize; /* chunk size in pg_largeobject */ bool float4ByVal; /* float4 pass-by-value? */ bool float8ByVal; /* float8, int8, etc pass-by-value? */ uint32 data_checksum_version; } ControlFileData12; #endif extern int get_pg_version(const char *data_directory, char *version_string); extern bool get_db_state(const char *data_directory, DBState *state); extern const char *describe_db_state(DBState state); extern int get_data_checksum_version(const char *data_directory); extern uint64 get_system_identifier(const char *data_directory); extern XLogRecPtr get_latest_checkpoint_location(const char *data_directory); extern TimeLineID get_timeline(const char *data_directory); extern TimeLineID get_min_recovery_end_timeline(const char *data_directory); extern XLogRecPtr get_min_recovery_location(const char *data_directory); #endif /* _CONTROLDATA_H_ */ repmgr-5.3.1/dbutils.c000066400000000000000000004231361420262710000146310ustar00rootroot00000000000000/* * dbutils.c - Database connection/management functions * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * * 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 . */ #include #include #include #include #include #include #include "repmgr.h" #include "dbutils.h" #include "controldata.h" #include "dirutil.h" #define NODE_RECORD_PARAM_COUNT 11 static void log_db_error(PGconn *conn, const char *query_text, const char *fmt,...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 4))); static bool _is_server_available(const char *conninfo, bool quiet); static PGconn *_establish_db_connection(const char *conninfo, const bool exit_on_error, const bool log_notice, const bool verbose_only); static PGconn * _establish_replication_connection_from_params(PGconn *conn, const char *conninfo, const char *repluser); static PGconn *_get_primary_connection(PGconn *standby_conn, int *primary_id, char *primary_conninfo_out, bool quiet); static bool _set_config(PGconn *conn, const char *config_param, const char *sqlquery); static bool _get_pg_setting(PGconn *conn, const char *setting, char *str_output, bool *bool_output, int *int_output); static RecordStatus _get_node_record(PGconn *conn, char *sqlquery, t_node_info *node_info, bool init_defaults); static void _populate_node_record(PGresult *res, t_node_info *node_info, int row, bool init_defaults); static void _populate_node_records(PGresult *res, NodeInfoList *node_list); static bool _create_update_node_record(PGconn *conn, char *action, t_node_info *node_info); static ReplSlotStatus _verify_replication_slot(PGconn *conn, char *slot_name, PQExpBufferData *error_msg); static bool _create_event(PGconn *conn, t_configuration_options *options, int node_id, char *event, bool successful, char *details, t_event_info *event_info, bool send_notification); /* * This provides a standardized way of logging database errors. Note * that the provided PGconn can be a normal or a replication connection; * no attempt is made to write to the database, only to report the output * of PQerrorMessage(). */ void log_db_error(PGconn *conn, const char *query_text, const char *fmt,...) { va_list ap; char buf[MAXLEN]; int retval; va_start(ap, fmt); retval = vsnprintf(buf, MAXLEN, fmt, ap); va_end(ap); if (retval < MAXLEN) log_error("%s", buf); if (conn != NULL) { log_detail("\n%s", PQerrorMessage(conn)); } if (query_text != NULL) { log_detail("query text is:\n%s", query_text); } } /* ================= */ /* utility functions */ /* ================= */ XLogRecPtr parse_lsn(const char *str) { XLogRecPtr ptr = InvalidXLogRecPtr; uint32 high, low; if (sscanf(str, "%x/%x", &high, &low) == 2) ptr = (((XLogRecPtr) high) << 32) + (XLogRecPtr) low; return ptr; } /* ==================== */ /* Connection functions */ /* ==================== */ /* * _establish_db_connection() * * Connect to a database using a conninfo string. * * NOTE: *do not* use this for replication connections; instead use: * establish_db_connection_by_params() */ static PGconn * _establish_db_connection(const char *conninfo, const bool exit_on_error, const bool log_notice, const bool verbose_only) { PGconn *conn = NULL; char *connection_string = NULL; char *errmsg = NULL; t_conninfo_param_list conninfo_params = T_CONNINFO_PARAM_LIST_INITIALIZER; bool is_replication_connection = false; bool parse_success = false; initialize_conninfo_params(&conninfo_params, false); parse_success = parse_conninfo_string(conninfo, &conninfo_params, &errmsg, false); if (parse_success == false) { log_error(_("unable to parse provided conninfo string \"%s\""), conninfo); log_detail("%s", errmsg); free_conninfo_params(&conninfo_params); return NULL; } /* set some default values if not explicitly provided */ param_set_ine(&conninfo_params, "connect_timeout", "2"); param_set_ine(&conninfo_params, "fallback_application_name", "repmgr"); if (param_get(&conninfo_params, "replication") != NULL) is_replication_connection = true; /* use a secure search_path */ param_set(&conninfo_params, "options", "-csearch_path="); connection_string = param_list_to_string(&conninfo_params); log_debug(_("connecting to: \"%s\""), connection_string); conn = PQconnectdb(connection_string); /* Check to see that the backend connection was successfully made */ if ((PQstatus(conn) != CONNECTION_OK)) { bool emit_log = true; if (verbose_only == true && verbose_logging == false) emit_log = false; if (emit_log) { if (log_notice) { log_notice(_("connection to database failed")); log_detail("\n%s", PQerrorMessage(conn)); } else { log_error(_("connection to database failed")); log_detail("\n%s", PQerrorMessage(conn)); } log_detail(_("attempted to connect using:\n %s"), connection_string); } if (exit_on_error) { PQfinish(conn); free_conninfo_params(&conninfo_params); exit(ERR_DB_CONN); } } /* * set "synchronous_commit" to "local" in case synchronous replication is * in use * * XXX set this explicitly before any write operations */ else if (is_replication_connection == false && set_config(conn, "synchronous_commit", "local") == false) { if (exit_on_error) { PQfinish(conn); free_conninfo_params(&conninfo_params); exit(ERR_DB_CONN); } } pfree(connection_string); free_conninfo_params(&conninfo_params); return conn; } /* * Establish a database connection, optionally exit on error */ PGconn * establish_db_connection(const char *conninfo, const bool exit_on_error) { 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 * establish_db_connection_quiet(const char *conninfo) { return _establish_db_connection(conninfo, false, false, true); } PGconn * establish_db_connection_with_replacement_param(const char *conninfo, const char *param, const char *value, const bool exit_on_error) { t_conninfo_param_list node_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; char *errmsg = NULL; bool parse_success = false; PGconn *conn = NULL; initialize_conninfo_params(&node_conninfo, false); parse_success = parse_conninfo_string(conninfo, &node_conninfo, &errmsg, false); if (parse_success == false) { log_error(_("unable to parse conninfo string \"%s\" for local node"), conninfo); log_detail("%s", errmsg); if (exit_on_error == true) exit(ERR_BAD_CONFIG); return NULL; } param_set(&node_conninfo, param, value); conn = establish_db_connection_by_params(&node_conninfo, exit_on_error); free_conninfo_params(&node_conninfo); return conn; } PGconn * establish_primary_db_connection(PGconn *conn, const bool exit_on_error) { t_node_info primary_node_info = T_NODE_INFO_INITIALIZER; bool primary_record_found = get_primary_node_record(conn, &primary_node_info); if (primary_record_found == false) { return NULL; } return establish_db_connection(primary_node_info.conninfo, exit_on_error); } PGconn * establish_db_connection_by_params(t_conninfo_param_list *param_list, const bool exit_on_error) { PGconn *conn = NULL; /* set some default values if not explicitly provided */ param_set_ine(param_list, "connect_timeout", "2"); param_set_ine(param_list, "fallback_application_name", "repmgr"); /* use a secure search_path */ param_set(param_list, "options", "-csearch_path="); /* Connect to the database using the provided parameters */ conn = PQconnectdbParams((const char **) param_list->keywords, (const char **) param_list->values, true); /* Check to see that the backend connection was successfully made */ if ((PQstatus(conn) != CONNECTION_OK)) { log_error(_("connection to database failed")); log_detail("\n%s", PQerrorMessage(conn)); if (exit_on_error) { PQfinish(conn); exit(ERR_DB_CONN); } } else { bool is_replication_connection = false; int i; /* * set "synchronous_commit" to "local" in case synchronous replication * is in use (provided this is not a replication connection) */ for (i = 0; param_list->keywords[i]; i++) { if (strcmp(param_list->keywords[i], "replication") == 0) is_replication_connection = true; } if (is_replication_connection == false && set_config(conn, "synchronous_commit", "local") == false) { if (exit_on_error) { PQfinish(conn); exit(ERR_DB_CONN); } } } return conn; } /* * Given an existing active connection and the name of a replication * user, extract the connection parameters from that connection and * attempt to return a replication connection. */ PGconn * establish_replication_connection_from_conn(PGconn *conn, const char *repluser) { return _establish_replication_connection_from_params(conn, NULL, repluser); } PGconn * establish_replication_connection_from_conninfo(const char *conninfo, const char *repluser) { return _establish_replication_connection_from_params(NULL, conninfo, repluser); } static PGconn * _establish_replication_connection_from_params(PGconn *conn, const char *conninfo, const char *repluser) { t_conninfo_param_list repl_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; PGconn *repl_conn = NULL; initialize_conninfo_params(&repl_conninfo, false); if (conn != NULL) conn_to_param_list(conn, &repl_conninfo); else if (conninfo != NULL) parse_conninfo_string(conninfo, &repl_conninfo, NULL, false); /* Set the provided replication user */ param_set(&repl_conninfo, "user", repluser); param_set(&repl_conninfo, "replication", "1"); param_set(&repl_conninfo, "dbname", "replication"); repl_conn = establish_db_connection_by_params(&repl_conninfo, false); free_conninfo_params(&repl_conninfo); return repl_conn; } PGconn * get_primary_connection(PGconn *conn, int *primary_id, char *primary_conninfo_out) { return _get_primary_connection(conn, primary_id, primary_conninfo_out, false); } PGconn * get_primary_connection_quiet(PGconn *conn, int *primary_id, char *primary_conninfo_out) { return _get_primary_connection(conn, primary_id, primary_conninfo_out, true); } PGconn * duplicate_connection(PGconn *conn, const char *user, bool replication) { t_conninfo_param_list conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; PGconn *duplicate_conn = NULL; initialize_conninfo_params(&conninfo, false); conn_to_param_list(conn, &conninfo); if (user != NULL) param_set(&conninfo, "user", user); if (replication == true) param_set(&conninfo, "replication", "1"); duplicate_conn = establish_db_connection_by_params(&conninfo, false); free_conninfo_params(&conninfo); return duplicate_conn; } void close_connection(PGconn **conn) { if (*conn == NULL) return; PQfinish(*conn); *conn = NULL; } /* =============================== */ /* conninfo manipulation functions */ /* =============================== */ /* * get_conninfo_value() * * Extract the value represented by 'keyword' in 'conninfo' and copy * it to the 'output' buffer. * * Returns true on success, or false on failure (conninfo string could * not be parsed, or provided keyword not found). */ bool get_conninfo_value(const char *conninfo, const char *keyword, char *output) { PQconninfoOption *conninfo_options = NULL; PQconninfoOption *conninfo_option = NULL; conninfo_options = PQconninfoParse(conninfo, NULL); if (conninfo_options == NULL) { log_error(_("unable to parse provided conninfo string \"%s\""), conninfo); return false; } for (conninfo_option = conninfo_options; conninfo_option->keyword != NULL; conninfo_option++) { if (strcmp(conninfo_option->keyword, keyword) == 0) { if (conninfo_option->val != NULL && conninfo_option->val[0] != '\0') { strncpy(output, conninfo_option->val, MAXLEN); break; } } } PQconninfoFree(conninfo_options); return true; } /* * Get a default conninfo value for the provided parameter, and copy * it to the 'output' buffer. * * Returns true on success, or false on failure (provided keyword not found). * */ bool get_conninfo_default_value(const char *param, char *output, int maxlen) { PQconninfoOption *defs = NULL; PQconninfoOption *def = NULL; bool found = false; defs = PQconndefaults(); for (def = defs; def->keyword; def++) { if (strncmp(def->keyword, param, maxlen) == 0) { strncpy(output, def->val, maxlen); found = true; } } PQconninfoFree(defs); return found; } void initialize_conninfo_params(t_conninfo_param_list *param_list, bool set_defaults) { PQconninfoOption *defs = NULL; PQconninfoOption *def = NULL; int c; defs = PQconndefaults(); param_list->size = 0; /* Count maximum number of parameters */ for (def = defs; def->keyword; def++) param_list->size++; /* Initialize our internal parameter list */ param_list->keywords = pg_malloc0(sizeof(char *) * (param_list->size + 1)); param_list->values = pg_malloc0(sizeof(char *) * (param_list->size + 1)); for (c = 0; c < param_list->size; c++) { param_list->keywords[c] = NULL; param_list->values[c] = NULL; } if (set_defaults == true) { /* Pre-set any defaults */ for (def = defs; def->keyword; def++) { if (def->val != NULL && def->val[0] != '\0') { param_set(param_list, def->keyword, def->val); } } } PQconninfoFree(defs); } void free_conninfo_params(t_conninfo_param_list *param_list) { int c; for (c = 0; c < param_list->size; c++) { if (param_list->keywords != NULL && param_list->keywords[c] != NULL) pfree(param_list->keywords[c]); if (param_list->values != NULL && param_list->values[c] != NULL) pfree(param_list->values[c]); } if (param_list->keywords != NULL) pfree(param_list->keywords); if (param_list->values != NULL) pfree(param_list->values); } void copy_conninfo_params(t_conninfo_param_list *dest_list, t_conninfo_param_list *source_list) { int c; for (c = 0; c < source_list->size && source_list->keywords[c] != NULL; c++) { if (source_list->values[c] != NULL && source_list->values[c][0] != '\0') { param_set(dest_list, source_list->keywords[c], source_list->values[c]); } } } void param_set(t_conninfo_param_list *param_list, const char *param, const char *value) { int c; int value_len = strlen(value) + 1; int param_len; /* * Scan array to see if the parameter is already set - if not, replace it */ for (c = 0; c < param_list->size && param_list->keywords[c] != NULL; c++) { if (strcmp(param_list->keywords[c], param) == 0) { if (param_list->values[c] != NULL) pfree(param_list->values[c]); param_list->values[c] = pg_malloc0(value_len); strncpy(param_list->values[c], value, value_len); return; } } /* * Sanity-check that the caller is not trying to overflow the array; * in practice this is highly unlikely, and if it ever happens, this means * something is highly wrong. */ Assert(c < param_list->size); /* * Parameter not in array - add it and its associated value */ param_len = strlen(param) + 1; param_list->keywords[c] = pg_malloc0(param_len); param_list->values[c] = pg_malloc0(value_len); strncpy(param_list->keywords[c], param, param_len); strncpy(param_list->values[c], value, value_len); } /* * Like param_set(), but will only set the parameter if it doesn't exist */ void param_set_ine(t_conninfo_param_list *param_list, const char *param, const char *value) { int c; int value_len = strlen(value) + 1; int param_len; /* * Scan array to see if the parameter is already set - if so, do nothing */ for (c = 0; c < param_list->size && param_list->keywords[c] != NULL; c++) { if (strcmp(param_list->keywords[c], param) == 0) { /* parameter exists, do nothing */ return; } } /* * Sanity-check that the caller is not trying to overflow the array; * in practice this is highly unlikely, and if it ever happens, this means * something is highly wrong. */ Assert(c < param_list->size); /* * Parameter not in array - add it and its associated value */ param_len = strlen(param) + 1; param_list->keywords[c] = pg_malloc0(param_len); param_list->values[c] = pg_malloc0(value_len); strncpy(param_list->keywords[c], param, param_len); strncpy(param_list->values[c], value, value_len); } char * param_get(t_conninfo_param_list *param_list, const char *param) { int c; for (c = 0; c < param_list->size && param_list->keywords[c] != NULL; c++) { if (strcmp(param_list->keywords[c], param) == 0) { if (param_list->values[c] != NULL && param_list->values[c][0] != '\0') return param_list->values[c]; else return NULL; } } return NULL; } /* * Validate a conninfo string by attempting to parse it. * * "errmsg": passed to PQconninfoParse(), may be NULL * * NOTE: PQconninfoParse() verifies the string format and checks for * valid options but does not sanity check values. */ bool validate_conninfo_string(const char *conninfo_str, char **errmsg) { PQconninfoOption *connOptions = NULL; connOptions = PQconninfoParse(conninfo_str, errmsg); if (connOptions == NULL) return false; PQconninfoFree(connOptions); return true; } /* * Parse a conninfo string into a t_conninfo_param_list * * See conn_to_param_list() to do the same for a PGconn. * * "errmsg": passed to PQconninfoParse(), may be NULL * * "ignore_local_params": ignores those parameters specific * to a local installation, i.e. when parsing an upstream * node's conninfo string for inclusion into "primary_conninfo", * don't copy that node's values */ bool parse_conninfo_string(const char *conninfo_str, t_conninfo_param_list *param_list, char **errmsg, bool ignore_local_params) { PQconninfoOption *connOptions = NULL; PQconninfoOption *option = NULL; connOptions = PQconninfoParse(conninfo_str, errmsg); if (connOptions == NULL) return false; for (option = connOptions; option && option->keyword; option++) { /* Ignore non-set or blank parameter values */ if (option->val == NULL || option->val[0] == '\0') continue; /* Ignore settings specific to the upstream node */ if (ignore_local_params == true) { if (strcmp(option->keyword, "application_name") == 0) continue; if (strcmp(option->keyword, "passfile") == 0) continue; if (strcmp(option->keyword, "servicefile") == 0) continue; } param_set(param_list, option->keyword, option->val); } PQconninfoFree(connOptions); return true; } /* * Parse a PGconn into a t_conninfo_param_list * * See parse_conninfo_string() to do the same for a conninfo string * * NOTE: the current use case for this is to take an active connection, * replace the existing username (typically replacing it with the superuser * or replication user name), and make a new connection as that user. * If the "password" field is set, it will cause any connection made with * these parameters to fail (unless of course the password happens to be the * same). Therefore we remove the password altogether, and rely on it being * available via .pgpass. */ void conn_to_param_list(PGconn *conn, t_conninfo_param_list *param_list) { PQconninfoOption *connOptions = NULL; PQconninfoOption *option = NULL; connOptions = PQconninfo(conn); for (option = connOptions; option && option->keyword; option++) { /* Ignore non-set or blank parameter values */ if (option->val == NULL || option->val[0] == '\0') continue; /* Ignore "password" */ if (strcmp(option->keyword, "password") == 0) continue; param_set(param_list, option->keyword, option->val); } PQconninfoFree(connOptions); } /* * Converts param list to string; caller must free returned pointer */ char * param_list_to_string(t_conninfo_param_list *param_list) { int c; PQExpBufferData conninfo_buf; char *conninfo_str = NULL; int len = 0; initPQExpBuffer(&conninfo_buf); for (c = 0; c < param_list->size && param_list->keywords[c] != NULL; c++) { if (param_list->values[c] != NULL && param_list->values[c][0] != '\0') { if (c > 0) appendPQExpBufferChar(&conninfo_buf, ' '); /* XXX escape value */ appendPQExpBuffer(&conninfo_buf, "%s=%s", param_list->keywords[c], param_list->values[c]); } } len = strlen(conninfo_buf.data) + 1; conninfo_str = pg_malloc0(len); strncpy(conninfo_str, conninfo_buf.data, len); termPQExpBuffer(&conninfo_buf); return conninfo_str; } /* * Run a conninfo string through the parser, and pass it back as a normal * conninfo string. This is mainly intended for converting connection URIs * to parameter/value conninfo strings. * * Caller must free returned pointer. */ char * normalize_conninfo_string(const char *conninfo_str) { t_conninfo_param_list conninfo_params = T_CONNINFO_PARAM_LIST_INITIALIZER; bool parse_success = false; char *normalized_string = NULL; char *errmsg = NULL; initialize_conninfo_params(&conninfo_params, false); parse_success = parse_conninfo_string(conninfo_str, &conninfo_params, &errmsg, false); if (parse_success == false) { log_error(_("unable to parse provided conninfo string \"%s\""), conninfo_str); log_detail("%s", errmsg); free_conninfo_params(&conninfo_params); return NULL; } normalized_string = param_list_to_string(&conninfo_params); free_conninfo_params(&conninfo_params); return normalized_string; } /* * check whether the libpq version in use recognizes the "passfile" parameter * (should be 9.6 and later) */ bool has_passfile(void) { PQconninfoOption *defs = PQconndefaults(); PQconninfoOption *def = NULL; bool has_passfile = false; for (def = defs; def->keyword; def++) { if (strcmp(def->keyword, "passfile") == 0) { has_passfile = true; break; } } PQconninfoFree(defs); return has_passfile; } /* ===================== */ /* transaction functions */ /* ===================== */ bool begin_transaction(PGconn *conn) { PGresult *res = NULL; log_verbose(LOG_DEBUG, "begin_transaction()"); res = PQexec(conn, "BEGIN"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_error(_("unable to begin transaction")); log_detail("%s", PQerrorMessage(conn)); PQclear(res); return false; } PQclear(res); return true; } bool commit_transaction(PGconn *conn) { PGresult *res = NULL; log_verbose(LOG_DEBUG, "commit_transaction()"); res = PQexec(conn, "COMMIT"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_error(_("unable to commit transaction")); log_detail("%s", PQerrorMessage(conn)); PQclear(res); return false; } PQclear(res); return true; } bool rollback_transaction(PGconn *conn) { PGresult *res = NULL; log_verbose(LOG_DEBUG, "rollback_transaction()"); res = PQexec(conn, "ROLLBACK"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_error(_("unable to rollback transaction")); log_detail("%s", PQerrorMessage(conn)); PQclear(res); return false; } PQclear(res); return true; } /* ========================== */ /* GUC manipulation functions */ /* ========================== */ static bool _set_config(PGconn *conn, const char *config_param, const char *sqlquery) { bool success = true; PGresult *res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(conn, sqlquery, "_set_config(): unable to set \"%s\"", config_param); success = false; } PQclear(res); return success; } bool set_config(PGconn *conn, const char *config_param, const char *config_value) { PQExpBufferData query; bool result = false; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SET %s TO '%s'", config_param, config_value); log_verbose(LOG_DEBUG, "set_config():\n %s", query.data); result = _set_config(conn, config_param, query.data); termPQExpBuffer(&query); return result; } bool set_config_bool(PGconn *conn, const char *config_param, bool state) { PQExpBufferData query; bool result = false; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SET %s TO %s", config_param, state ? "TRUE" : "FALSE"); log_verbose(LOG_DEBUG, "set_config_bool():\n %s", query.data); result = _set_config(conn, config_param, query.data); termPQExpBuffer(&query); return result; } int guc_set(PGconn *conn, const char *parameter, const char *op, const char *value) { PQExpBufferData query; PGresult *res = NULL; int retval = 1; char *escaped_parameter = escape_string(conn, parameter); char *escaped_value = escape_string(conn, value); initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT true FROM pg_catalog.pg_settings " " WHERE name = '%s' AND setting %s '%s'", escaped_parameter, op, escaped_value); log_verbose(LOG_DEBUG, "guc_set():\n%s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("guc_set(): unable to execute query")); retval = -1; } else if (PQntuples(res) == 0) { retval = 0; } pfree(escaped_parameter); pfree(escaped_value); termPQExpBuffer(&query); PQclear(res); return retval; } bool get_pg_setting(PGconn *conn, const char *setting, char *output) { bool success = _get_pg_setting(conn, setting, output, NULL, NULL); if (success == true) { log_verbose(LOG_DEBUG, _("get_pg_setting(): returned value is \"%s\""), output); } return success; } bool get_pg_setting_bool(PGconn *conn, const char *setting, bool *output) { bool success = _get_pg_setting(conn, setting, NULL, output, NULL); if (success == true) { log_verbose(LOG_DEBUG, _("get_pg_setting(): returned value is \"%s\""), *output == true ? "TRUE" : "FALSE"); } return success; } bool get_pg_setting_int(PGconn *conn, const char *setting, int *output) { bool success = _get_pg_setting(conn, setting, NULL, NULL, output); if (success == true) { log_verbose(LOG_DEBUG, _("get_pg_setting_int(): returned value is \"%i\""), *output); } return success; } bool _get_pg_setting(PGconn *conn, const char *setting, char *str_output, bool *bool_output, int *int_output) { PQExpBufferData query; PGresult *res = NULL; int i; bool success = false; char *escaped_setting = escape_string(conn, setting); if (escaped_setting == NULL) { log_error(_("unable to escape setting \"%s\""), setting); return false; } initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT name, setting " " FROM pg_catalog.pg_settings WHERE name = '%s'", escaped_setting); log_verbose(LOG_DEBUG, "get_pg_setting():\n %s", query.data); res = PQexec(conn, query.data); pfree(escaped_setting); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_pg_setting() - unable to execute query")); termPQExpBuffer(&query); PQclear(res); return false; } for (i = 0; i < PQntuples(res); i++) { if (strcmp(PQgetvalue(res, i, 0), setting) == 0) { if (str_output != NULL) { snprintf(str_output, MAXLEN, "%s", PQgetvalue(res, i, 1)); } else if (bool_output != NULL) { /* * Note we assume the caller is sure this is a boolean parameter */ if (strncmp(PQgetvalue(res, i, 1), "on", MAXLEN) == 0) *bool_output = true; else *bool_output = false; } else if (int_output != NULL) { *int_output = atoi(PQgetvalue(res, i, 1)); } success = true; break; } else { /* highly unlikely this would ever happen */ log_error(_("get_pg_setting(): unknown parameter \"%s\""), PQgetvalue(res, i, 0)); } } termPQExpBuffer(&query); PQclear(res); return success; } bool alter_system_int(PGconn *conn, const char *name, int value) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBuffer(&query, "ALTER SYSTEM SET %s = %i", name, value); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(conn, query.data, _("alter_system_int() - unable to execute query")); success = false; } termPQExpBuffer(&query); PQclear(res); return success; } bool pg_reload_conf(PGconn *conn) { PGresult *res = NULL; bool success = true; res = PQexec(conn, "SELECT pg_catalog.pg_reload_conf()"); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, NULL, _("pg_reload_conf() - unable to execute query")); success = false; } PQclear(res); return success; } /* ============================ */ /* Server information functions */ /* ============================ */ bool get_cluster_size(PGconn *conn, char *size) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBufferStr(&query, "SELECT pg_catalog.pg_size_pretty(pg_catalog.sum(pg_catalog.pg_database_size(oid))::bigint) " " FROM pg_catalog.pg_database "); log_verbose(LOG_DEBUG, "get_cluster_size():\n%s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_cluster_size(): unable to execute query")); success = false; } else { snprintf(size, MAXLEN, "%s", PQgetvalue(res, 0, 0)); } termPQExpBuffer(&query); PQclear(res); return success; } /* * Return the server version number for the connection provided */ int get_server_version(PGconn *conn, char *server_version_buf) { PGresult *res = NULL; int _server_version_num = UNKNOWN_SERVER_VERSION_NUM; const char *sqlquery = "SELECT pg_catalog.current_setting('server_version_num'), " " pg_catalog.current_setting('server_version')"; res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, sqlquery, _("unable to determine server version number")); PQclear(res); return UNKNOWN_SERVER_VERSION_NUM; } _server_version_num = atoi(PQgetvalue(res, 0, 0)); if (server_version_buf != NULL) { int i; char _server_version_buf[MAXVERSIONSTR] = ""; memset(_server_version_buf, 0, MAXVERSIONSTR); /* * Some distributions may add extra info after the actual version number, * e.g. "10.4 (Debian 10.4-2.pgdg90+1)", so copy everything up until the * first space. */ snprintf(_server_version_buf, MAXVERSIONSTR, "%s", PQgetvalue(res, 0, 1)); for (i = 0; i < MAXVERSIONSTR; i++) { if (_server_version_buf[i] == ' ') break; *server_version_buf++ = _server_version_buf[i]; } } PQclear(res); return _server_version_num; } RecoveryType get_recovery_type(PGconn *conn) { PGresult *res = NULL; RecoveryType recovery_type = RECTYPE_UNKNOWN; const char *sqlquery = "SELECT pg_catalog.pg_is_in_recovery()"; log_verbose(LOG_DEBUG, "get_recovery_type(): %s", sqlquery); res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, sqlquery, _("unable to determine if server is in recovery")); recovery_type = RECTYPE_UNKNOWN; } else if (PQntuples(res) == 1) { if (strcmp(PQgetvalue(res, 0, 0), "f") == 0) { recovery_type = RECTYPE_PRIMARY; } else { recovery_type = RECTYPE_STANDBY; } } PQclear(res); return recovery_type; } /* * Read the node list from the provided connection and attempt to connect to each node * in turn to definitely establish if it's the cluster primary. * * The node list is returned in the order which makes it likely that the * current primary will be returned first, reducing the number of speculative * connections which need to be made to other nodes. * * If primary_conninfo_out points to allocated memory of MAXCONNINFO in length, * the primary server's conninfo string will be copied there. */ PGconn * _get_primary_connection(PGconn *conn, int *primary_id, char *primary_conninfo_out, bool quiet) { PQExpBufferData query; PGconn *remote_conn = NULL; PGresult *res = NULL; char remote_conninfo_stack[MAXCONNINFO]; char *remote_conninfo = &*remote_conninfo_stack; int i, node_id; /* * If the caller wanted to get a copy of the connection info string, sub * out the local stack pointer for the pointer passed by the caller. */ if (primary_conninfo_out != NULL) remote_conninfo = primary_conninfo_out; if (primary_id != NULL) { *primary_id = NODE_NOT_FOUND; } /* find all registered nodes */ log_verbose(LOG_INFO, _("searching for primary node")); initPQExpBuffer(&query); appendPQExpBufferStr(&query, " SELECT node_id, conninfo, " " CASE WHEN type = 'primary' THEN 1 ELSE 2 END AS type_priority" " FROM repmgr.nodes " " WHERE active IS TRUE " " AND type != 'witness' " "ORDER BY active DESC, type_priority, priority, node_id"); log_verbose(LOG_DEBUG, "get_primary_connection():\n%s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("_get_primary_connection(): unable to retrieve node records")); termPQExpBuffer(&query); PQclear(res); return NULL; } termPQExpBuffer(&query); for (i = 0; i < PQntuples(res); i++) { RecoveryType recovery_type; /* initialize with the values of the current node being processed */ node_id = atoi(PQgetvalue(res, i, 0)); snprintf(remote_conninfo, MAXCONNINFO, "%s", PQgetvalue(res, i, 1)); log_verbose(LOG_INFO, _("checking if node %i is primary"), node_id); if (quiet) { remote_conn = establish_db_connection_quiet(remote_conninfo); } else { remote_conn = establish_db_connection(remote_conninfo, false); } if (PQstatus(remote_conn) != CONNECTION_OK) { PQfinish(remote_conn); remote_conn = NULL; continue; } recovery_type = get_recovery_type(remote_conn); if (recovery_type == RECTYPE_UNKNOWN) { log_warning(_("unable to retrieve recovery state from node %i"), node_id); PQfinish(remote_conn); continue; } if (recovery_type == RECTYPE_PRIMARY) { PQclear(res); log_verbose(LOG_INFO, _("current primary node is %i"), node_id); if (primary_id != NULL) { *primary_id = node_id; } return remote_conn; } PQfinish(remote_conn); } PQclear(res); return NULL; } /* * Return the id of the active primary node, or NODE_NOT_FOUND if no * record available. * * This reports the value stored in the database only and * does not verify whether the node is actually available */ int get_primary_node_id(PGconn *conn) { PQExpBufferData query; PGresult *res = NULL; int retval = NODE_NOT_FOUND; initPQExpBuffer(&query); appendPQExpBufferStr(&query, "SELECT node_id " " FROM repmgr.nodes " " WHERE type = 'primary' " " AND active IS TRUE "); log_verbose(LOG_DEBUG, "get_primary_node_id():\n%s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_primary_node_id(): unable to execute query")); retval = UNKNOWN_NODE_ID; } else if (PQntuples(res) == 0) { log_verbose(LOG_WARNING, _("get_primary_node_id(): no active primary found")); retval = NODE_NOT_FOUND; } else { retval = atoi(PQgetvalue(res, 0, 0)); } termPQExpBuffer(&query); PQclear(res); return retval; } int get_ready_archive_files(PGconn *conn, const char *data_directory) { char archive_status_dir[MAXPGPATH] = ""; struct stat statbuf; struct dirent *arcdir_ent; DIR *arcdir; int ready_count = 0; if (PQserverVersion(conn) >= 100000) { snprintf(archive_status_dir, MAXPGPATH, "%s/pg_wal/archive_status", data_directory); } else { snprintf(archive_status_dir, MAXPGPATH, "%s/pg_xlog/archive_status", data_directory); } /* sanity-check directory path */ if (stat(archive_status_dir, &statbuf) == -1) { log_error(_("unable to access archive_status directory \"%s\""), archive_status_dir); log_detail("%s", strerror(errno)); return ARCHIVE_STATUS_DIR_ERROR; } arcdir = opendir(archive_status_dir); if (arcdir == NULL) { log_error(_("unable to open archive directory \"%s\""), archive_status_dir); log_detail("%s", strerror(errno)); return ARCHIVE_STATUS_DIR_ERROR; } while ((arcdir_ent = readdir(arcdir)) != NULL) { struct stat statbuf; char file_path[MAXPGPATH + sizeof(arcdir_ent->d_name)]; int basenamelen = 0; snprintf(file_path, sizeof(file_path), "%s/%s", archive_status_dir, arcdir_ent->d_name); /* skip non-files */ if (stat(file_path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode)) { continue; } basenamelen = (int) strlen(arcdir_ent->d_name) - 6; /* * count anything ending in ".ready"; for a more precise * implementation see: src/backend/postmaster/pgarch.c */ if (strcmp(arcdir_ent->d_name + basenamelen, ".ready") == 0) ready_count++; } closedir(arcdir); return ready_count; } bool identify_system(PGconn *repl_conn, t_system_identification *identification) { PGresult *res = NULL; /* semicolon required here */ res = PQexec(repl_conn, "IDENTIFY_SYSTEM;"); if (PQresultStatus(res) != PGRES_TUPLES_OK || !PQntuples(res)) { log_db_error(repl_conn, NULL, _("unable to execute IDENTIFY_SYSTEM")); PQclear(res); return false; } #if defined(__i386__) || defined(__i386) identification->system_identifier = atoll(PQgetvalue(res, 0, 0)); #else identification->system_identifier = atol(PQgetvalue(res, 0, 0)); #endif identification->timeline = atoi(PQgetvalue(res, 0, 1)); identification->xlogpos = parse_lsn(PQgetvalue(res, 0, 2)); PQclear(res); return true; } /* * Return the system identifier by querying pg_control_system(). * * Note there is a similar function in controldata.c ("get_system_identifier()") * which reads the control file. */ uint64 system_identifier(PGconn *conn) { uint64 system_identifier = UNKNOWN_SYSTEM_IDENTIFIER; PGresult *res = NULL; /* * pg_control_system() was introduced in PostgreSQL 9.6 */ if (PQserverVersion(conn) < 90600) { return UNKNOWN_SYSTEM_IDENTIFIER; } res = PQexec(conn, "SELECT system_identifier FROM pg_catalog.pg_control_system()"); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, NULL, _("system_identifier(): unable to query pg_control_system()")); } else { #if defined(__i386__) || defined(__i386) system_identifier = atoll(PQgetvalue(res, 0, 0)); #else system_identifier = atol(PQgetvalue(res, 0, 0)); #endif } PQclear(res); return system_identifier; } TimeLineHistoryEntry * get_timeline_history(PGconn *repl_conn, TimeLineID tli) { PQExpBufferData query; PGresult *res = NULL; PQExpBufferData result; char *resptr; TimeLineHistoryEntry *history; TimeLineID file_tli = UNKNOWN_TIMELINE_ID; uint32 switchpoint_hi; uint32 switchpoint_lo; initPQExpBuffer(&query); appendPQExpBuffer(&query, "TIMELINE_HISTORY %i", (int)tli); res = PQexec(repl_conn, query.data); log_verbose(LOG_DEBUG, "get_timeline_history():\n%s", query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(repl_conn, query.data, _("get_timeline_history(): unable to execute query")); termPQExpBuffer(&query); PQclear(res); return NULL; } termPQExpBuffer(&query); if (PQntuples(res) != 1 || PQnfields(res) != 2) { log_error(_("unexpected response to TIMELINE_HISTORY command")); log_detail(_("got %i rows and %i fields, expected %i rows and %i fields"), PQntuples(res), PQnfields(res), 1, 2); PQclear(res); return NULL; } initPQExpBuffer(&result); appendPQExpBufferStr(&result, PQgetvalue(res, 0, 1)); PQclear(res); resptr = result.data; while (*resptr) { char buf[MAXLEN]; char *bufptr = buf; if (*resptr != '\n') { int len = 0; memset(buf, 0, MAXLEN); while (*resptr && *resptr != '\n' && len < MAXLEN) { *bufptr++ = *resptr++; len++; } if (buf[0]) { int nfields = sscanf(buf, "%u\t%X/%X", &file_tli, &switchpoint_hi, &switchpoint_lo); if (nfields == 3 && file_tli == tli - 1) break; } } if (*resptr) resptr++; } termPQExpBuffer(&result); if (file_tli == UNKNOWN_TIMELINE_ID || file_tli != tli - 1) { log_error(_("timeline %i not found in timeline history file content"), tli); log_detail(_("content is: \"%s\""), result.data); return NULL; } history = (TimeLineHistoryEntry *) palloc(sizeof(TimeLineHistoryEntry)); history->tli = file_tli; history->begin = InvalidXLogRecPtr; /* we don't care about this */ history->end = ((uint64) (switchpoint_hi)) << 32 | (uint64) switchpoint_lo; return history; } /* =============================== */ /* user/role information functions */ /* =============================== */ bool can_execute_pg_promote(PGconn *conn) { PQExpBufferData query; PGresult *res; bool has_pg_promote= false; /* pg_promote() available from PostgreSQL 12 */ if (PQserverVersion(conn) < 120000) return false; initPQExpBuffer(&query); appendPQExpBufferStr(&query, " SELECT pg_catalog.has_function_privilege( " " CURRENT_USER, " " 'pg_catalog.pg_promote(bool,int)', " " 'execute' " " )"); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("can_execute_pg_promote(): unable to query user function privilege")); } else { has_pg_promote = atobool(PQgetvalue(res, 0, 0)); } termPQExpBuffer(&query); return has_pg_promote; } /* * Determine if the user associated with the current connection is * a member of the "pg_monitor" default role, or optionally one * of its three constituent "subroles". */ bool connection_has_pg_monitor_role(PGconn *conn, const char *subrole) { PQExpBufferData query; PGresult *res; bool has_pg_monitor_role = false; /* superusers can read anything, no role check needed */ if (is_superuser_connection(conn, NULL) == true) return true; /* pg_monitor and associated "subroles" introduced in PostgreSQL 10 */ if (PQserverVersion(conn) < 100000) return false; initPQExpBuffer(&query); appendPQExpBufferStr(&query, " SELECT CASE " " WHEN pg_catalog.pg_has_role('pg_monitor','MEMBER') " " THEN TRUE "); if (subrole != NULL) { appendPQExpBuffer(&query, " WHEN pg_catalog.pg_has_role('%s','MEMBER') " " THEN TRUE ", subrole); } appendPQExpBufferStr(&query, " ELSE FALSE " " END AS has_pg_monitor"); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("connection_has_pg_monitor_role(): unable to query user roles")); } else { has_pg_monitor_role = atobool(PQgetvalue(res, 0, 0)); } termPQExpBuffer(&query); PQclear(res); return has_pg_monitor_role; } bool is_replication_role(PGconn *conn, char *rolname) { PQExpBufferData query; PGresult *res; bool is_replication_role = false; initPQExpBuffer(&query); appendPQExpBufferStr(&query, " SELECT rolreplication " " FROM pg_catalog.pg_roles " " WHERE rolname = "); if (rolname != NULL) { appendPQExpBuffer(&query, "'%s'", rolname); } else { appendPQExpBufferStr(&query, "CURRENT_USER"); } res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("is_replication_role(): unable to query user roles")); } else { is_replication_role = atobool(PQgetvalue(res, 0, 0)); } termPQExpBuffer(&query); PQclear(res); return is_replication_role; } bool is_superuser_connection(PGconn *conn, t_connection_user *userinfo) { bool is_superuser = false; const char *current_user = PQuser(conn); const char *superuser_status = PQparameterStatus(conn, "is_superuser"); is_superuser = (strcmp(superuser_status, "on") == 0) ? true : false; if (userinfo != NULL) { snprintf(userinfo->username, sizeof(userinfo->username), "%s", current_user); userinfo->is_superuser = is_superuser; } return is_superuser; } /* =============================== */ /* repmgrd shared memory functions */ /* =============================== */ bool repmgrd_set_local_node_id(PGconn *conn, int local_node_id) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT repmgr.set_local_node_id(%i)", local_node_id); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("repmgrd_set_local_node_id(): unable to execute query")); success = false; } termPQExpBuffer(&query); PQclear(res); return success; } int repmgrd_get_local_node_id(PGconn *conn) { PGresult *res = NULL; int local_node_id = UNKNOWN_NODE_ID; const char *sqlquery = "SELECT repmgr.get_local_node_id()"; res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, sqlquery, _("repmgrd_get_local_node_id(): unable to execute query")); } else if (!PQgetisnull(res, 0, 0)) { local_node_id = atoi(PQgetvalue(res, 0, 0)); } PQclear(res); return local_node_id; } bool repmgrd_check_local_node_id(PGconn *conn) { PGresult *res = NULL; bool node_id_settable = true; const char *sqlquery = "SELECT repmgr.get_local_node_id()"; res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, sqlquery, _("repmgrd_get_local_node_id(): unable to execute query")); } if (PQgetisnull(res, 0, 0)) { node_id_settable = false; } PQclear(res); return node_id_settable; } /* * Function that checks if the primary is in exclusive backup mode. * We'll use this when executing an action can conflict with an exclusive * backup. */ BackupState server_in_exclusive_backup_mode(PGconn *conn) { BackupState backup_state = BACKUP_STATE_UNKNOWN; const char *sqlquery = "SELECT pg_catalog.pg_is_in_backup()"; PGresult *res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, sqlquery, _("unable to retrieve information regarding backup mode of node")); backup_state = BACKUP_STATE_UNKNOWN; } else if (atobool(PQgetvalue(res, 0, 0)) == true) { backup_state = BACKUP_STATE_IN_BACKUP; } else { backup_state = BACKUP_STATE_NO_BACKUP; } PQclear(res); return backup_state; } void repmgrd_set_pid(PGconn *conn, pid_t repmgrd_pid, const char *pidfile) { PQExpBufferData query; PGresult *res = NULL; log_verbose(LOG_DEBUG, "repmgrd_set_pid(): pid is %i", (int) repmgrd_pid); initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT repmgr.set_repmgrd_pid(%i, ", (int) repmgrd_pid); if (pidfile != NULL) { appendPQExpBuffer(&query, " '%s')", pidfile); } else { appendPQExpBufferStr(&query, " NULL)"); } res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to execute \"SELECT repmgr.set_repmgrd_pid()\"")); log_detail("%s", PQerrorMessage(conn)); } PQclear(res); return; } pid_t repmgrd_get_pid(PGconn *conn) { PGresult *res = NULL; pid_t repmgrd_pid = UNKNOWN_PID; res = PQexec(conn, "SELECT repmgr.get_repmgrd_pid()"); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to execute \"SELECT repmgr.get_repmgrd_pid()\"")); log_detail("%s", PQerrorMessage(conn)); } else if (!PQgetisnull(res, 0, 0)) { repmgrd_pid = atoi(PQgetvalue(res, 0, 0)); } PQclear(res); return repmgrd_pid; } bool repmgrd_is_running(PGconn *conn) { PGresult *res = NULL; bool is_running = false; res = PQexec(conn, "SELECT repmgr.repmgrd_is_running()"); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to execute \"SELECT repmgr.repmgrd_is_running()\"")); log_detail("%s", PQerrorMessage(conn)); } else if (!PQgetisnull(res, 0, 0)) { is_running = atobool(PQgetvalue(res, 0, 0)); } PQclear(res); return is_running; } bool repmgrd_is_paused(PGconn *conn) { PGresult *res = NULL; bool is_paused = false; res = PQexec(conn, "SELECT repmgr.repmgrd_is_paused()"); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to execute \"SELECT repmgr.repmgrd_is_paused()\"")); log_detail("%s", PQerrorMessage(conn)); } else if (!PQgetisnull(res, 0, 0)) { is_paused = atobool(PQgetvalue(res, 0, 0)); } PQclear(res); return is_paused; } bool repmgrd_pause(PGconn *conn, bool pause) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT repmgr.repmgrd_pause(%s)", pause == true ? "TRUE" : "FALSE"); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to execute \"SELECT repmgr.repmgrd_pause()\"")); log_detail("%s", PQerrorMessage(conn)); success = false; } PQclear(res); return success; } pid_t get_wal_receiver_pid(PGconn *conn) { PGresult *res = NULL; pid_t wal_receiver_pid = UNKNOWN_PID; res = PQexec(conn, "SELECT repmgr.get_wal_receiver_pid()"); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to execute \"SELECT repmgr.get_wal_receiver_pid()\"")); log_detail("%s", PQerrorMessage(conn)); } else if (!PQgetisnull(res, 0, 0)) { wal_receiver_pid = atoi(PQgetvalue(res, 0, 0)); } PQclear(res); return wal_receiver_pid; } int repmgrd_get_upstream_node_id(PGconn *conn) { PGresult *res = NULL; int upstream_node_id = UNKNOWN_NODE_ID; const char *sqlquery = "SELECT repmgr.get_upstream_node_id()"; res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, sqlquery, _("repmgrd_get_upstream_node_id(): unable to execute query")); } else if (!PQgetisnull(res, 0, 0)) { upstream_node_id = atoi(PQgetvalue(res, 0, 0)); } PQclear(res); return upstream_node_id; } bool repmgrd_set_upstream_node_id(PGconn *conn, int node_id) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT repmgr.set_upstream_node_id(%i) ", node_id); log_verbose(LOG_DEBUG, "repmgrd_set_upstream_node_id():\n %s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("repmgrd_set_upstream_node_id(): unable to set upstream node ID (provided value: %i)"), node_id); success = false; } termPQExpBuffer(&query); PQclear(res); return success; } /* ================ */ /* result functions */ /* ================ */ bool atobool(const char *value) { return (strcmp(value, "t") == 0) ? true : false; } /* =================== */ /* extension functions */ /* =================== */ ExtensionStatus get_repmgr_extension_status(PGconn *conn, t_extension_versions *extversions) { PQExpBufferData query; PGresult *res = NULL; ExtensionStatus status = REPMGR_UNKNOWN; /* TODO: check version */ initPQExpBuffer(&query); appendPQExpBufferStr(&query, " SELECT ae.name, e.extname, " " ae.default_version, " " (((FLOOR(ae.default_version::NUMERIC)::INT) * 10000) + (ae.default_version::NUMERIC - FLOOR(ae.default_version::NUMERIC)::INT) * 1000)::INT AS available, " " ae.installed_version, " " (((FLOOR(ae.installed_version::NUMERIC)::INT) * 10000) + (ae.installed_version::NUMERIC - FLOOR(ae.installed_version::NUMERIC)::INT) * 1000)::INT AS installed " " FROM pg_catalog.pg_available_extensions ae " "LEFT JOIN pg_catalog.pg_extension e " " ON e.extname=ae.name " " WHERE ae.name='repmgr' "); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_repmgr_extension_status(): unable to execute extension query")); status = REPMGR_UNKNOWN; } /* 1. Check extension is actually available */ else if (PQntuples(res) == 0) { status = REPMGR_UNAVAILABLE; } /* 2. Check if extension installed */ else if (PQgetisnull(res, 0, 1) == 0) { int available_version = atoi(PQgetvalue(res, 0, 3)); int installed_version = atoi(PQgetvalue(res, 0, 5)); /* caller wants to know which versions are installed/available */ if (extversions != NULL) { snprintf(extversions->default_version, sizeof(extversions->default_version), "%s", PQgetvalue(res, 0, 2)); extversions->default_version_num = available_version; snprintf(extversions->installed_version, sizeof(extversions->installed_version), "%s", PQgetvalue(res, 0, 4)); extversions->installed_version_num = installed_version; } if (available_version > installed_version) { status = REPMGR_OLD_VERSION_INSTALLED; } else { status = REPMGR_INSTALLED; } } else { status = REPMGR_AVAILABLE; } termPQExpBuffer(&query); PQclear(res); return status; } /* ========================= */ /* node management functions */ /* ========================= */ /* assumes superuser connection */ void checkpoint(PGconn *conn) { PGresult *res = NULL; res = PQexec(conn, "CHECKPOINT"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(conn, NULL, _("unable to execute CHECKPOINT")); } PQclear(res); return; } bool vacuum_table(PGconn *primary_conn, const char *table) { PQExpBufferData query; bool success = true; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, "VACUUM %s", table); res = PQexec(primary_conn, query.data); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(primary_conn, NULL, _("unable to vacuum table \"%s\""), table); success = false; } termPQExpBuffer(&query); PQclear(res); return success; } /* * For use in PostgreSQL 12 and later */ bool promote_standby(PGconn *conn, bool wait, int wait_seconds) { PQExpBufferData query; bool success = true; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT pg_catalog.pg_promote(wait := %s", wait ? "TRUE" : "FALSE"); if (wait_seconds > 0) { appendPQExpBuffer(&query, ", wait_seconds := %i", wait_seconds); } appendPQExpBufferStr(&query, ")"); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("unable to execute pg_promote()")); success = false; } else { /* NOTE: if "wait" is false, pg_promote() will always return true */ success = atobool(PQgetvalue(res, 0, 0)); } termPQExpBuffer(&query); PQclear(res); return success; } bool resume_wal_replay(PGconn *conn) { PGresult *res = NULL; PQExpBufferData query; bool success = true; initPQExpBuffer(&query); if (PQserverVersion(conn) >= 100000) { appendPQExpBufferStr(&query, "SELECT pg_catalog.pg_wal_replay_resume()"); } else { appendPQExpBufferStr(&query, "SELECT pg_catalog.pg_xlog_replay_resume()"); } res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("resume_wal_replay(): unable to resume WAL replay")); success = false; } termPQExpBuffer(&query); PQclear(res); return success; } /* ===================== */ /* Node record functions */ /* ===================== */ /* * Note: init_defaults may only be false when the caller is refreshing a previously * populated record. */ static RecordStatus _get_node_record(PGconn *conn, char *sqlquery, t_node_info *node_info, bool init_defaults) { int ntuples = 0; PGresult *res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, sqlquery, _("_get_node_record(): unable to execute query")); PQclear(res); return RECORD_ERROR; } ntuples = PQntuples(res); if (ntuples == 0) { PQclear(res); return RECORD_NOT_FOUND; } _populate_node_record(res, node_info, 0, init_defaults); PQclear(res); return RECORD_FOUND; } /* * Note: init_defaults may only be false when the caller is refreshing a previously * populated record. */ static void _populate_node_record(PGresult *res, t_node_info *node_info, int row, bool init_defaults) { node_info->node_id = atoi(PQgetvalue(res, row, 0)); node_info->type = parse_node_type(PQgetvalue(res, row, 1)); if (PQgetisnull(res, row, 2)) { node_info->upstream_node_id = NO_UPSTREAM_NODE; } else { node_info->upstream_node_id = atoi(PQgetvalue(res, row, 2)); } snprintf(node_info->node_name, sizeof(node_info->node_name), "%s", PQgetvalue(res, row, 3)); snprintf(node_info->conninfo, sizeof(node_info->conninfo), "%s", PQgetvalue(res, row, 4)); snprintf(node_info->repluser, sizeof(node_info->repluser), "%s", PQgetvalue(res, row, 5)); snprintf(node_info->slot_name, sizeof(node_info->slot_name), "%s", PQgetvalue(res, row, 6)); snprintf(node_info->location, sizeof(node_info->location), "%s", PQgetvalue(res, row, 7)); node_info->priority = atoi(PQgetvalue(res, row, 8)); node_info->active = atobool(PQgetvalue(res, row, 9)); snprintf(node_info->config_file, sizeof(node_info->config_file), "%s", PQgetvalue(res, row, 10)); /* These are only set by certain queries */ snprintf(node_info->upstream_node_name, sizeof(node_info->upstream_node_name), "%s", PQgetvalue(res, row, 11)); if (PQgetisnull(res, row, 12)) { node_info->attached = NODE_ATTACHED_UNKNOWN; } else { node_info->attached = atobool(PQgetvalue(res, row, 12)) ? NODE_ATTACHED : NODE_DETACHED; } /* Set remaining struct fields with default values */ if (init_defaults == true) { node_info->node_status = NODE_STATUS_UNKNOWN; node_info->recovery_type = RECTYPE_UNKNOWN; node_info->last_wal_receive_lsn = InvalidXLogRecPtr; node_info->monitoring_state = MS_NORMAL; node_info->conn = NULL; } } t_server_type parse_node_type(const char *type) { if (strcmp(type, "primary") == 0) { return PRIMARY; } else if (strcmp(type, "standby") == 0) { return STANDBY; } else if (strcmp(type, "witness") == 0) { return WITNESS; } return UNKNOWN; } const char * get_node_type_string(t_server_type type) { switch (type) { case PRIMARY: return "primary"; case STANDBY: return "standby"; case WITNESS: return "witness"; /* this should never happen */ case UNKNOWN: default: log_error(_("unknown node type %i"), type); return "unknown"; } } RecordStatus get_node_record(PGconn *conn, int node_id, t_node_info *node_info) { PQExpBufferData query; RecordStatus result; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT " REPMGR_NODES_COLUMNS " FROM repmgr.nodes n " " WHERE n.node_id = %i", node_id); log_verbose(LOG_DEBUG, "get_node_record():\n %s", query.data); result = _get_node_record(conn, query.data, node_info, true); termPQExpBuffer(&query); if (result == RECORD_NOT_FOUND) { log_verbose(LOG_DEBUG, "get_node_record(): no record found for node %i", node_id); } return result; } RecordStatus refresh_node_record(PGconn *conn, int node_id, t_node_info *node_info) { PQExpBufferData query; RecordStatus result; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT " REPMGR_NODES_COLUMNS " FROM repmgr.nodes n " " WHERE n.node_id = %i", node_id); log_verbose(LOG_DEBUG, "get_node_record():\n %s", query.data); result = _get_node_record(conn, query.data, node_info, false); termPQExpBuffer(&query); if (result == RECORD_NOT_FOUND) { log_verbose(LOG_DEBUG, "refresh_node_record(): no record found for node %i", node_id); } return result; } RecordStatus get_node_record_with_upstream(PGconn *conn, int node_id, t_node_info *node_info) { PQExpBufferData query; RecordStatus result; initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT " REPMGR_NODES_COLUMNS_WITH_UPSTREAM " FROM repmgr.nodes n " " LEFT JOIN repmgr.nodes un " " ON un.node_id = n.upstream_node_id" " WHERE n.node_id = %i", node_id); log_verbose(LOG_DEBUG, "get_node_record():\n %s", query.data); result = _get_node_record(conn, query.data, node_info, true); termPQExpBuffer(&query); if (result == RECORD_NOT_FOUND) { log_verbose(LOG_DEBUG, "get_node_record(): no record found for node %i", node_id); } return result; } RecordStatus get_node_record_by_name(PGconn *conn, const char *node_name, t_node_info *node_info) { PQExpBufferData query; RecordStatus record_status = RECORD_NOT_FOUND; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT " REPMGR_NODES_COLUMNS " FROM repmgr.nodes n " " WHERE n.node_name = '%s' ", node_name); log_verbose(LOG_DEBUG, "get_node_record_by_name():\n %s", query.data); record_status = _get_node_record(conn, query.data, node_info, true); termPQExpBuffer(&query); if (record_status == RECORD_NOT_FOUND) { log_verbose(LOG_DEBUG, "get_node_record_by_name(): no record found for node \"%s\"", node_name); } return record_status; } t_node_info * get_node_record_pointer(PGconn *conn, int node_id) { t_node_info *node_info = pg_malloc0(sizeof(t_node_info)); RecordStatus record_status = RECORD_NOT_FOUND; record_status = get_node_record(conn, node_id, node_info); if (record_status != RECORD_FOUND) { pfree(node_info); return NULL; } return node_info; } bool get_primary_node_record(PGconn *conn, t_node_info *node_info) { RecordStatus record_status = RECORD_NOT_FOUND; int primary_node_id = get_primary_node_id(conn); if (primary_node_id == UNKNOWN_NODE_ID) { return false; } record_status = get_node_record(conn, primary_node_id, node_info); return record_status == RECORD_FOUND ? true : false; } /* * Get the local node record; if this fails, exit. Many operations * depend on this being available, so we'll centralize the check * and failure messages here. */ bool get_local_node_record(PGconn *conn, int node_id, t_node_info *node_info) { RecordStatus record_status = get_node_record(conn, node_id, node_info); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve record for local node")); log_detail(_("local node id is %i"), node_id); log_hint(_("check this node was correctly registered")); PQfinish(conn); exit(ERR_BAD_CONFIG); } return true; } static void _populate_node_records(PGresult *res, NodeInfoList *node_list) { int i; clear_node_info_list(node_list); if (PQresultStatus(res) != PGRES_TUPLES_OK) { return; } for (i = 0; i < PQntuples(res); i++) { NodeInfoListCell *cell; cell = (NodeInfoListCell *) pg_malloc0(sizeof(NodeInfoListCell)); cell->node_info = pg_malloc0(sizeof(t_node_info)); _populate_node_record(res, cell->node_info, i, true); if (node_list->tail) node_list->tail->next = cell; else node_list->head = cell; node_list->tail = cell; node_list->node_count++; } return; } bool get_all_node_records(PGconn *conn, NodeInfoList *node_list) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBufferStr(&query, " SELECT " REPMGR_NODES_COLUMNS " FROM repmgr.nodes n " "ORDER BY n.node_id "); log_verbose(LOG_DEBUG, "get_all_node_records():\n%s", query.data); res = PQexec(conn, query.data); /* this will return an empty list if there was an error executing the query */ _populate_node_records(res, node_list); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_all_node_records(): unable to execute query")); success = false; } PQclear(res); termPQExpBuffer(&query); return success; } bool get_all_nodes_count(PGconn *conn, int *count) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBufferStr(&query, " SELECT count(*) " " FROM repmgr.nodes n "); log_verbose(LOG_DEBUG, "get_all_nodes_count():\n%s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_all_nodes_count(): unable to execute query")); success = false; } else { *count = atoi(PQgetvalue(res, 0, 0)); } PQclear(res); termPQExpBuffer(&query); return success; } void get_downstream_node_records(PGconn *conn, int node_id, NodeInfoList *node_list) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT " REPMGR_NODES_COLUMNS " FROM repmgr.nodes n " " WHERE n.upstream_node_id = %i " "ORDER BY n.node_id ", node_id); log_verbose(LOG_DEBUG, "get_downstream_node_records():\n%s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_downstream_node_records(): unable to execute query")); } termPQExpBuffer(&query); /* this will return an empty list if there was an error executing the query */ _populate_node_records(res, node_list); PQclear(res); return; } void get_active_sibling_node_records(PGconn *conn, int node_id, int upstream_node_id, NodeInfoList *node_list) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT " REPMGR_NODES_COLUMNS " FROM repmgr.nodes n " " WHERE n.upstream_node_id = %i " " AND n.node_id != %i " " AND n.active IS TRUE " "ORDER BY n.node_id ", upstream_node_id, node_id); log_verbose(LOG_DEBUG, "get_active_sibling_node_records():\n%s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_active_sibling_records(): unable to execute query")); } termPQExpBuffer(&query); /* this will return an empty list if there was an error executing the query */ _populate_node_records(res, node_list); PQclear(res); return; } bool get_child_nodes(PGconn *conn, int node_id, NodeInfoList *node_list) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT n.node_id, n.type, n.upstream_node_id, n.node_name, n.conninfo, n.repluser, " " n.slot_name, n.location, n.priority, n.active, n.config_file, " " '' AS upstream_node_name, " " CASE WHEN sr.application_name IS NULL THEN FALSE ELSE TRUE END AS attached " " FROM repmgr.nodes n " " LEFT JOIN pg_catalog.pg_stat_replication sr " " ON sr.application_name = n.node_name " " WHERE n.upstream_node_id = %i ", node_id); log_verbose(LOG_DEBUG, "get_child_nodes():\n%s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_active_sibling_records(): unable to execute query")); success = false; } termPQExpBuffer(&query); /* this will return an empty list if there was an error executing the query */ _populate_node_records(res, node_list); PQclear(res); return success; } void get_node_records_by_priority(PGconn *conn, NodeInfoList *node_list) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBufferStr(&query, " SELECT " REPMGR_NODES_COLUMNS " FROM repmgr.nodes n " "ORDER BY n.priority DESC, n.node_name "); log_verbose(LOG_DEBUG, "get_node_records_by_priority():\n%s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_node_records_by_priority(): unable to execute query")); } termPQExpBuffer(&query); /* this will return an empty list if there was an error executing the query */ _populate_node_records(res, node_list); PQclear(res); return; } /* * return all node records together with their upstream's node name, * if available. */ bool get_all_node_records_with_upstream(PGconn *conn, NodeInfoList *node_list) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBufferStr(&query, " SELECT " REPMGR_NODES_COLUMNS_WITH_UPSTREAM " FROM repmgr.nodes n " " LEFT JOIN repmgr.nodes un " " ON un.node_id = n.upstream_node_id" " ORDER BY n.node_id "); log_verbose(LOG_DEBUG, "get_all_node_records_with_upstream():\n%s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_all_node_records_with_upstream(): unable to retrieve node records")); success = false; } /* this will return an empty list if there was an error executing the query */ _populate_node_records(res, node_list); termPQExpBuffer(&query); PQclear(res); return success; } bool get_downstream_nodes_with_missing_slot(PGconn *conn, int this_node_id, NodeInfoList *node_list) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT " REPMGR_NODES_COLUMNS " FROM repmgr.nodes n " "LEFT JOIN pg_catalog.pg_replication_slots rs " " ON rs.slot_name = n.slot_name " " WHERE n.slot_name IS NOT NULL" " AND rs.slot_name IS NULL " " AND n.upstream_node_id = %i " " AND n.type = 'standby'", this_node_id); log_verbose(LOG_DEBUG, "get_all_node_records_with_missing_slot():\n%s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_downstream_nodes_with_missing_slot(): unable to retrieve node records")); success = false; } /* this will return an empty list if there was an error executing the query */ _populate_node_records(res, node_list); termPQExpBuffer(&query); PQclear(res); return success; } bool create_node_record(PGconn *conn, char *repmgr_action, t_node_info *node_info) { if (repmgr_action != NULL) log_verbose(LOG_DEBUG, "create_node_record(): action is \"%s\"", repmgr_action); return _create_update_node_record(conn, "create", node_info); } bool update_node_record(PGconn *conn, char *repmgr_action, t_node_info *node_info) { if (repmgr_action != NULL) log_verbose(LOG_DEBUG, "update_node_record(): action is \"%s\"", repmgr_action); return _create_update_node_record(conn, "update", node_info); } static bool _create_update_node_record(PGconn *conn, char *action, t_node_info *node_info) { PQExpBufferData query; char node_id[MAXLEN] = ""; char priority[MAXLEN] = ""; char upstream_node_id[MAXLEN] = ""; char *upstream_node_id_ptr = NULL; char *slot_name_ptr = NULL; int param_count = NODE_RECORD_PARAM_COUNT; const char *param_values[NODE_RECORD_PARAM_COUNT]; PGresult *res; bool success = true; maxlen_snprintf(node_id, "%i", node_info->node_id); maxlen_snprintf(priority, "%i", node_info->priority); if (node_info->upstream_node_id == NO_UPSTREAM_NODE && node_info->type == STANDBY) { /* * No explicit upstream node id provided for standby - attempt to get * primary node id */ int primary_node_id = get_primary_node_id(conn); maxlen_snprintf(upstream_node_id, "%i", primary_node_id); upstream_node_id_ptr = upstream_node_id; } else if (node_info->upstream_node_id != NO_UPSTREAM_NODE) { maxlen_snprintf(upstream_node_id, "%i", node_info->upstream_node_id); upstream_node_id_ptr = upstream_node_id; } if (node_info->slot_name[0] != '\0') { slot_name_ptr = node_info->slot_name; } param_values[0] = get_node_type_string(node_info->type); param_values[1] = upstream_node_id_ptr; param_values[2] = node_info->node_name; param_values[3] = node_info->conninfo; param_values[4] = node_info->repluser; param_values[5] = slot_name_ptr; param_values[6] = node_info->location; param_values[7] = priority; param_values[8] = node_info->active == true ? "TRUE" : "FALSE"; param_values[9] = node_info->config_file; param_values[10] = node_id; initPQExpBuffer(&query); if (strcmp(action, "create") == 0) { appendPQExpBufferStr(&query, "INSERT INTO repmgr.nodes " " (node_id, type, upstream_node_id, " " node_name, conninfo, repluser, slot_name, " " location, priority, active, config_file) " "VALUES ($11, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10) "); } else { appendPQExpBufferStr(&query, "UPDATE repmgr.nodes SET " " type = $1, " " upstream_node_id = $2, " " node_name = $3, " " conninfo = $4, " " repluser = $5, " " slot_name = $6, " " location = $7, " " priority = $8, " " active = $9, " " config_file = $10 " " WHERE node_id = $11 "); } res = PQexecParams(conn, query.data, param_count, NULL, param_values, NULL, NULL, 0); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(conn, query.data, _("_create_update_node_record(): unable to %s node record for node \"%s\" (ID: %i)"), action, node_info->node_name, node_info->node_id); success = false; } termPQExpBuffer(&query); PQclear(res); return success; } bool update_node_record_set_active(PGconn *conn, int this_node_id, bool active) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBuffer(&query, "UPDATE repmgr.nodes SET active = %s " " WHERE node_id = %i", active == true ? "TRUE" : "FALSE", this_node_id); log_verbose(LOG_DEBUG, "update_node_record_set_active():\n %s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(conn, query.data, _("update_node_record_set_active(): unable to update node record")); success = false; } termPQExpBuffer(&query); PQclear(res); return success; } bool update_node_record_set_active_standby(PGconn *conn, int this_node_id) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBuffer(&query, "UPDATE repmgr.nodes " " SET type = 'standby', " " active = TRUE " " WHERE node_id = %i", this_node_id); log_verbose(LOG_DEBUG, "update_node_record_set_active_standby():\n %s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(conn, query.data, _("update_node_record_set_active_standby(): unable to update node record")); success = false; } termPQExpBuffer(&query); PQclear(res); return success; } bool update_node_record_set_primary(PGconn *conn, int this_node_id) { PQExpBufferData query; PGresult *res = NULL; log_debug(_("setting node %i as primary and marking existing primary as failed"), this_node_id); begin_transaction(conn); initPQExpBuffer(&query); appendPQExpBuffer(&query, " UPDATE repmgr.nodes " " SET active = FALSE " " WHERE type = 'primary' " " AND active IS TRUE " " AND node_id != %i ", this_node_id); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(conn, query.data, _("update_node_record_set_primary(): unable to set old primary node as inactive")); termPQExpBuffer(&query); PQclear(res); rollback_transaction(conn); return false; } termPQExpBuffer(&query); PQclear(res); initPQExpBuffer(&query); appendPQExpBuffer(&query, " UPDATE repmgr.nodes" " SET type = 'primary', " " upstream_node_id = NULL, " " active = TRUE " " WHERE node_id = %i ", this_node_id); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(conn, query.data, _("unable to set current node %i as active primary"), this_node_id); termPQExpBuffer(&query); PQclear(res); rollback_transaction(conn); return false; } termPQExpBuffer(&query); PQclear(res); return commit_transaction(conn); } bool update_node_record_set_upstream(PGconn *conn, int this_node_id, int new_upstream_node_id) { PQExpBufferData query; PGresult *res = NULL; bool success = true; log_debug(_("update_node_record_set_upstream(): Updating node %i's upstream node to %i"), this_node_id, new_upstream_node_id); initPQExpBuffer(&query); appendPQExpBuffer(&query, " UPDATE repmgr.nodes " " SET upstream_node_id = %i " " WHERE node_id = %i ", new_upstream_node_id, this_node_id); log_verbose(LOG_DEBUG, "update_node_record_set_upstream():\n%s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(conn, query.data, _("update_node_record_set_upstream(): unable to set new upstream node id")); success = false; } termPQExpBuffer(&query); PQclear(res); return success; } /* * Update node record following change of status * (e.g. inactive primary converted to standby) */ bool update_node_record_status(PGconn *conn, int this_node_id, char *type, int upstream_node_id, bool active) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBuffer(&query, " UPDATE repmgr.nodes " " SET type = '%s', " " upstream_node_id = %i, " " active = %s " " WHERE node_id = %i ", type, upstream_node_id, active ? "TRUE" : "FALSE", this_node_id); log_verbose(LOG_DEBUG, "update_node_record_status():\n %s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(conn, query.data, _("update_node_record_status(): unable to update node record status for node %i"), this_node_id); success = false; } termPQExpBuffer(&query); PQclear(res); return success; } /* * Update node record's "conninfo" and "priority" fields. Called by repmgrd * following a configuration file reload. */ bool update_node_record_conn_priority(PGconn *conn, t_configuration_options *options) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBuffer(&query, "UPDATE repmgr.nodes " " SET conninfo = '%s', " " priority = %d " " WHERE node_id = %d ", options->conninfo, options->priority, options->node_id); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(conn, query.data, _("update_node_record_conn_priority(): unable to execute query")); success = false; } termPQExpBuffer(&query); PQclear(res); return success; } /* * Copy node records from primary to witness servers. * * This is used when initially registering a witness server, and * by repmgrd to update the node records when required. */ bool witness_copy_node_records(PGconn *primary_conn, PGconn *witness_conn) { PGresult *res = NULL; NodeInfoList nodes = T_NODE_INFO_LIST_INITIALIZER; NodeInfoListCell *cell = NULL; begin_transaction(witness_conn); /* Defer constraints */ res = PQexec(witness_conn, "SET CONSTRAINTS ALL DEFERRED"); if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(witness_conn, NULL, ("witness_copy_node_records(): unable to defer constraints")); rollback_transaction(witness_conn); PQclear(res); return false; } PQclear(res); /* truncate existing records */ if (truncate_node_records(witness_conn) == false) { rollback_transaction(witness_conn); return false; } if (get_all_node_records(primary_conn, &nodes) == false) { rollback_transaction(witness_conn); return false; } for (cell = nodes.head; cell; cell = cell->next) { if (create_node_record(witness_conn, NULL, cell->node_info) == false) { rollback_transaction(witness_conn); return false; } } /* and done */ commit_transaction(witness_conn); clear_node_info_list(&nodes); return true; } bool delete_node_record(PGconn *conn, int node) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBuffer(&query, "DELETE FROM repmgr.nodes " " WHERE node_id = %i", node); log_verbose(LOG_DEBUG, "delete_node_record():\n %s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(conn, query.data, _("delete_node_record(): unable to delete node record")); success = false; } termPQExpBuffer(&query); PQclear(res); return success; } bool truncate_node_records(PGconn *conn) { PGresult *res = NULL; bool success = true; res = PQexec(conn, "TRUNCATE TABLE repmgr.nodes"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(conn, NULL, _("truncate_node_records(): unable to truncate table \"repmgr.nodes\"")); success = false; } PQclear(res); return success; } bool update_node_record_slot_name(PGconn *primary_conn, int node_id, char *slot_name) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBuffer(&query, " UPDATE repmgr.nodes " " SET slot_name = '%s' " " WHERE node_id = %i ", slot_name, node_id); res = PQexec(primary_conn, query.data); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(primary_conn, query.data, _("unable to set node record slot name")); success = false; } termPQExpBuffer(&query); PQclear(res); return success; } void clear_node_info_list(NodeInfoList *nodes) { NodeInfoListCell *cell = NULL; NodeInfoListCell *next_cell = NULL; log_verbose(LOG_DEBUG, "clear_node_info_list() - closing open connections"); /* close any open connections */ for (cell = nodes->head; cell; cell = cell->next) { if (PQstatus(cell->node_info->conn) == CONNECTION_OK) { PQfinish(cell->node_info->conn); cell->node_info->conn = NULL; } } log_verbose(LOG_DEBUG, "clear_node_info_list() - unlinking"); cell = nodes->head; while (cell != NULL) { next_cell = cell->next; if (cell->node_info->replication_info != NULL) pfree(cell->node_info->replication_info); pfree(cell->node_info); pfree(cell); cell = next_cell; } nodes->head = NULL; nodes->tail = NULL; nodes->node_count = 0; } /* ================================================ */ /* PostgreSQL configuration file location functions */ /* ================================================ */ bool get_datadir_configuration_files(PGconn *conn, KeyValueList *list) { PQExpBufferData query; PGresult *res = NULL; int i; bool success = true; initPQExpBuffer(&query); appendPQExpBufferStr(&query, "WITH files AS ( " " WITH dd AS ( " " SELECT setting " " FROM pg_catalog.pg_settings " " WHERE name = 'data_directory') " " SELECT distinct(sourcefile) AS config_file" " FROM dd, pg_catalog.pg_settings ps " " WHERE ps.sourcefile IS NOT NULL " " AND ps.sourcefile ~ ('^' || dd.setting) " " UNION " " SELECT ps.setting AS config_file" " FROM dd, pg_catalog.pg_settings ps " " WHERE ps.name IN ('config_file', 'hba_file', 'ident_file') " " AND ps.setting ~ ('^' || dd.setting) " ") " " SELECT config_file, " " pg_catalog.regexp_replace(config_file, '^.*\\/','') AS filename " " FROM files " "ORDER BY config_file"); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_datadir_configuration_files(): unable to retrieve configuration file information")); success = false; } else { for (i = 0; i < PQntuples(res); i++) { key_value_list_set(list, PQgetvalue(res, i, 1), PQgetvalue(res, i, 0)); } } termPQExpBuffer(&query); PQclear(res); return success; } bool get_configuration_file_locations(PGconn *conn, t_configfile_list *list) { PQExpBufferData query; PGresult *res = NULL; int i; initPQExpBuffer(&query); appendPQExpBufferStr(&query, " WITH dd AS ( " " SELECT setting AS data_directory" " FROM pg_catalog.pg_settings " " WHERE name = 'data_directory' " " ) " " SELECT DISTINCT(sourcefile), " " pg_catalog.regexp_replace(sourcefile, '^.*\\/', '') AS filename, " " sourcefile ~ ('^' || dd.data_directory) AS in_data_dir " " FROM dd, pg_catalog.pg_settings ps " " WHERE sourcefile IS NOT NULL " " ORDER BY 1 "); log_verbose(LOG_DEBUG, "get_configuration_file_locations():\n %s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_configuration_file_locations(): unable to retrieve configuration file locations")); termPQExpBuffer(&query); PQclear(res); return false; } /* * allocate memory for config file array - number of rows returned from * above query + 2 for pg_hba.conf, pg_ident.conf */ config_file_list_init(list, PQntuples(res) + 2); for (i = 0; i < PQntuples(res); i++) { config_file_list_add(list, PQgetvalue(res, i, 0), PQgetvalue(res, i, 1), atobool(PQgetvalue(res, i, 2))); } termPQExpBuffer(&query); PQclear(res); /* Fetch locations of pg_hba.conf and pg_ident.conf */ initPQExpBuffer(&query); appendPQExpBufferStr(&query, " WITH dd AS ( " " SELECT setting AS data_directory" " FROM pg_catalog.pg_settings " " WHERE name = 'data_directory' " " ) " " SELECT ps.setting, " " pg_catalog.regexp_replace(setting, '^.*\\/', '') AS filename, " " ps.setting ~ ('^' || dd.data_directory) AS in_data_dir " " FROM dd, pg_catalog.pg_settings ps " " WHERE ps.name IN ('hba_file', 'ident_file') " " ORDER BY 1 "); log_verbose(LOG_DEBUG, "get_configuration_file_locations():\n %s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_configuration_file_locations(): unable to retrieve configuration file locations")); termPQExpBuffer(&query); PQclear(res); return false; } for (i = 0; i < PQntuples(res); i++) { config_file_list_add(list, PQgetvalue(res, i, 0), PQgetvalue(res, i, 1), atobool(PQgetvalue(res, i, 2))); } termPQExpBuffer(&query); PQclear(res); return true; } void config_file_list_init(t_configfile_list *list, int max_size) { list->size = max_size; list->entries = 0; list->files = pg_malloc0(sizeof(t_configfile_info *) * max_size); if (list->files == NULL) { log_error(_("config_file_list_init(): unable to allocate memory; terminating")); exit(ERR_OUT_OF_MEMORY); } } void config_file_list_add(t_configfile_list *list, const char *file, const char *filename, bool in_data_dir) { /* Failsafe to prevent entries being added beyond the end */ if (list->entries == list->size) return; list->files[list->entries] = pg_malloc0(sizeof(t_configfile_info)); if (list->files[list->entries] == NULL) { log_error(_("config_file_list_add(): unable to allocate memory; terminating")); exit(ERR_OUT_OF_MEMORY); } snprintf(list->files[list->entries]->filepath, sizeof(list->files[list->entries]->filepath), "%s", file); canonicalize_path(list->files[list->entries]->filepath); snprintf(list->files[list->entries]->filename, sizeof(list->files[list->entries]->filename), "%s", filename); list->files[list->entries]->in_data_directory = in_data_dir; list->entries++; } /* ====================== */ /* event record functions */ /* ====================== */ /* * create_event_record() * * Create a record in the "events" table, but don't execute the * "event_notification_command". */ bool create_event_record(PGconn *conn, t_configuration_options *options, int node_id, char *event, bool successful, char *details) { /* create dummy t_event_info */ t_event_info event_info = T_EVENT_INFO_INITIALIZER; return _create_event(conn, options, node_id, event, successful, details, &event_info, false); } /* * create_event_notification() * * If `conn` is not NULL, insert a record into the events table. * * If configuration parameter "event_notification_command" is set, also * attempt to execute that command. * * Returns true if all operations succeeded, false if one or more failed. * * Note this function may be called with "conn" set to NULL in cases where * the primary node is not available and it's therefore not possible to write * an event record. In this case, if `event_notification_command` is set, a * user-defined notification to be generated; if not, this function will have * no effect. */ bool create_event_notification(PGconn *conn, t_configuration_options *options, int node_id, char *event, bool successful, char *details) { /* create dummy t_event_info */ t_event_info event_info = T_EVENT_INFO_INITIALIZER; return _create_event(conn, options, node_id, event, successful, details, &event_info, true); } /* * create_event_notification_extended() * * The caller may need to pass additional parameters to the event notification * command (currently only the conninfo string of another node) */ bool create_event_notification_extended(PGconn *conn, t_configuration_options *options, int node_id, char *event, bool successful, char *details, t_event_info *event_info) { return _create_event(conn, options, node_id, event, successful, details, event_info, true); } static bool _create_event(PGconn *conn, t_configuration_options *options, int node_id, char *event, bool successful, char *details, t_event_info *event_info, bool send_notification) { PQExpBufferData query; PGresult *res = NULL; char event_timestamp[MAXLEN] = ""; bool success = true; log_verbose(LOG_DEBUG, "_create_event(): event is \"%s\" for node %i", event, node_id); /* * Only attempt to write a record if a connection handle was provided, * and the connection handle points to a node which is not in recovery. */ if (conn != NULL && PQstatus(conn) == CONNECTION_OK && get_recovery_type(conn) == RECTYPE_PRIMARY) { int n_node_id = htonl(node_id); char *t_successful = successful ? "TRUE" : "FALSE"; const char *values[4] = {(char *) &n_node_id, event, t_successful, details }; int lengths[4] = {sizeof(n_node_id), 0, 0, 0 }; int binary[4] = {1, 0, 0, 0}; initPQExpBuffer(&query); appendPQExpBufferStr(&query, " INSERT INTO repmgr.events ( " " node_id, " " event, " " successful, " " details " " ) " " VALUES ($1, $2, $3, $4) " " RETURNING event_timestamp "); log_verbose(LOG_DEBUG, "_create_event():\n %s", query.data); res = PQexecParams(conn, query.data, 4, NULL, values, lengths, binary, 0); if (PQresultStatus(res) != PGRES_TUPLES_OK) { /* we don't treat this as a fatal error */ log_warning(_("unable to create event record")); log_detail("%s", PQerrorMessage(conn)); log_detail("%s", query.data); success = false; } else { /* Store timestamp to send to the notification command */ snprintf(event_timestamp, MAXLEN, "%s", PQgetvalue(res, 0, 0)); } termPQExpBuffer(&query); PQclear(res); } /* * If no database connection provided, or the query failed, generate a * current timestamp ourselves. This isn't quite the same format as * PostgreSQL, but is close enough for diagnostic use. */ if (!strlen(event_timestamp)) { time_t now; struct tm ts; time(&now); ts = *localtime(&now); strftime(event_timestamp, MAXLEN, "%Y-%m-%d %H:%M:%S%z", &ts); } log_verbose(LOG_DEBUG, "_create_event(): Event timestamp is \"%s\"", event_timestamp); /* an event notification command was provided - parse and execute it */ if (send_notification == true && strlen(options->event_notification_command)) { char parsed_command[MAXPGPATH] = ""; const char *src_ptr = NULL; char *dst_ptr = NULL; char *end_ptr = NULL; int r = 0; log_verbose(LOG_DEBUG, "_create_event(): command is '%s'", options->event_notification_command); /* * If configuration option 'event_notifications' was provided, check * if this event is one of the ones listed; if not listed, don't * execute the notification script. * * (If 'event_notifications' was not provided, we assume the script * should be executed for all events). */ if (options->event_notifications.head != NULL) { EventNotificationListCell *cell = NULL; bool notify_ok = false; for (cell = options->event_notifications.head; cell; cell = cell->next) { if (strcmp(event, cell->event_type) == 0) { notify_ok = true; break; } } /* * Event type not found in the 'event_notifications' list - return * early */ if (notify_ok == false) { log_debug(_("not executing notification script for event type \"%s\""), event); return success; } } dst_ptr = parsed_command; end_ptr = parsed_command + MAXPGPATH - 1; *end_ptr = '\0'; for (src_ptr = options->event_notification_command; *src_ptr; src_ptr++) { if (*src_ptr == '%') { switch (src_ptr[1]) { case '%': /* %%: replace with % */ if (dst_ptr < end_ptr) { src_ptr++; *dst_ptr++ = *src_ptr; } break; case 'n': /* %n: node id */ src_ptr++; snprintf(dst_ptr, end_ptr - dst_ptr, "%i", node_id); dst_ptr += strlen(dst_ptr); break; case 'a': /* %a: node name */ src_ptr++; if (event_info->node_name != NULL) { log_verbose(LOG_DEBUG, "node_name: %s", event_info->node_name); strlcpy(dst_ptr, event_info->node_name, end_ptr - dst_ptr); dst_ptr += strlen(dst_ptr); } break; case 'e': /* %e: event type */ src_ptr++; strlcpy(dst_ptr, event, end_ptr - dst_ptr); dst_ptr += strlen(dst_ptr); break; case 'd': /* %d: details */ src_ptr++; if (details != NULL) { PQExpBufferData details_escaped; initPQExpBuffer(&details_escaped); escape_double_quotes(details, &details_escaped); strlcpy(dst_ptr, details_escaped.data, end_ptr - dst_ptr); dst_ptr += strlen(dst_ptr); termPQExpBuffer(&details_escaped); } break; case 's': /* %s: successful */ src_ptr++; strlcpy(dst_ptr, successful ? "1" : "0", end_ptr - dst_ptr); dst_ptr += strlen(dst_ptr); break; case 't': /* %t: timestamp */ src_ptr++; strlcpy(dst_ptr, event_timestamp, end_ptr - dst_ptr); dst_ptr += strlen(dst_ptr); break; case 'c': /* %c: conninfo for next available node */ src_ptr++; if (event_info->conninfo_str != NULL) { log_debug("conninfo: %s", event_info->conninfo_str); strlcpy(dst_ptr, event_info->conninfo_str, end_ptr - dst_ptr); dst_ptr += strlen(dst_ptr); } break; case 'p': /* %p: primary id ("standby_switchover"/"repmgrd_failover_promote": former primary id) */ src_ptr++; if (event_info->node_id != UNKNOWN_NODE_ID) { PQExpBufferData node_id; initPQExpBuffer(&node_id); appendPQExpBuffer(&node_id, "%i", event_info->node_id); strlcpy(dst_ptr, node_id.data, end_ptr - dst_ptr); dst_ptr += strlen(dst_ptr); termPQExpBuffer(&node_id); } break; default: /* otherwise treat the % as not special */ if (dst_ptr < end_ptr) *dst_ptr++ = *src_ptr; break; } } else { if (dst_ptr < end_ptr) *dst_ptr++ = *src_ptr; } } *dst_ptr = '\0'; log_info(_("executing notification command for event \"%s\""), event); log_detail(_("command is:\n %s"), parsed_command); r = system(parsed_command); if (r != 0) { log_warning(_("unable to execute event notification command")); log_detail(_("parsed event notification command was:\n %s"), parsed_command); success = false; } } return success; } PGresult * get_event_records(PGconn *conn, int node_id, const char *node_name, const char *event, bool all, int limit) { PGresult *res; PQExpBufferData query; PQExpBufferData where_clause; initPQExpBuffer(&query); initPQExpBuffer(&where_clause); /* LEFT JOIN used here as a node record may have been removed */ appendPQExpBufferStr(&query, " SELECT e.node_id, n.node_name, e.event, e.successful, " " pg_catalog.to_char(e.event_timestamp, 'YYYY-MM-DD HH24:MI:SS') AS timestamp, " " e.details " " FROM repmgr.events e " "LEFT JOIN repmgr.nodes n ON e.node_id = n.node_id "); if (node_id != UNKNOWN_NODE_ID) { append_where_clause(&where_clause, "n.node_id=%i", node_id); } else if (node_name[0] != '\0') { char *escaped = escape_string(conn, node_name); if (escaped == NULL) { log_error(_("unable to escape value provided for node name")); log_detail(_("node name is: \"%s\""), node_name); } else { append_where_clause(&where_clause, "n.node_name='%s'", escaped); pfree(escaped); } } if (event[0] != '\0') { char *escaped = escape_string(conn, event); if (escaped == NULL) { log_error(_("unable to escape value provided for event")); log_detail(_("event is: \"%s\""), event); } else { append_where_clause(&where_clause, "e.event='%s'", escaped); pfree(escaped); } } appendPQExpBuffer(&query, "\n%s\n", where_clause.data); appendPQExpBufferStr(&query, " ORDER BY e.event_timestamp DESC"); if (all == false && limit > 0) { appendPQExpBuffer(&query, " LIMIT %i", limit); } log_debug("do_cluster_event():\n%s", query.data); res = PQexec(conn, query.data); termPQExpBuffer(&query); termPQExpBuffer(&where_clause); return res; } /* ========================== */ /* replication slot functions */ /* ========================== */ void create_slot_name(char *slot_name, int node_id) { maxlen_snprintf(slot_name, "repmgr_slot_%i", node_id); } static ReplSlotStatus _verify_replication_slot(PGconn *conn, char *slot_name, PQExpBufferData *error_msg) { RecordStatus record_status = RECORD_NOT_FOUND; t_replication_slot slot_info = T_REPLICATION_SLOT_INITIALIZER; /* * Check whether slot exists already; if it exists and is active, that * means another active standby is using it, which creates an error * situation; if not we can reuse it as-is. */ record_status = get_slot_record(conn, slot_name, &slot_info); if (record_status == RECORD_FOUND) { if (strcmp(slot_info.slot_type, "physical") != 0) { if (error_msg) appendPQExpBuffer(error_msg, _("slot \"%s\" exists and is not a physical slot\n"), slot_name); return SLOT_NOT_PHYSICAL; } if (slot_info.active == false) { log_debug("replication slot \"%s\" exists but is inactive; reusing", slot_name); return SLOT_INACTIVE; } if (error_msg) appendPQExpBuffer(error_msg, _("slot \"%s\" already exists as an active slot\n"), slot_name); return SLOT_ACTIVE; } return SLOT_NOT_FOUND; } bool create_replication_slot_replprot(PGconn *conn, PGconn *repl_conn, char *slot_name, PQExpBufferData *error_msg) { PQExpBufferData query; PGresult *res = NULL; bool success = true; ReplSlotStatus slot_status = _verify_replication_slot(conn, slot_name, error_msg); /* Replication slot is unusable */ if (slot_status == SLOT_NOT_PHYSICAL || slot_status == SLOT_ACTIVE) return false; /* Replication slot can be reused */ if (slot_status == SLOT_INACTIVE) return true; initPQExpBuffer(&query); appendPQExpBuffer(&query, "CREATE_REPLICATION_SLOT %s PHYSICAL", slot_name); /* In 9.6 and later, reserve the LSN straight away */ if (PQserverVersion(conn) >= 90600) { appendPQExpBufferStr(&query, " RESERVE_WAL"); } appendPQExpBufferChar(&query, ';'); res = PQexec(repl_conn, query.data); if ((PQresultStatus(res) != PGRES_TUPLES_OK || !PQntuples(res)) && error_msg != NULL) { appendPQExpBuffer(error_msg, _("unable to create replication slot \"%s\" on the upstream node: %s\n"), slot_name, PQerrorMessage(conn)); success = false; } PQclear(res); return success; } bool create_replication_slot_sql(PGconn *conn, char *slot_name, PQExpBufferData *error_msg) { PQExpBufferData query; PGresult *res = NULL; bool success = true; ReplSlotStatus slot_status = _verify_replication_slot(conn, slot_name, error_msg); /* Replication slot is unusable */ if (slot_status == SLOT_NOT_PHYSICAL || slot_status == SLOT_ACTIVE) return false; /* Replication slot can be reused */ if (slot_status == SLOT_INACTIVE) return true; initPQExpBuffer(&query); /* In 9.6 and later, reserve the LSN straight away */ if (PQserverVersion(conn) >= 90600) { appendPQExpBuffer(&query, "SELECT * FROM pg_catalog.pg_create_physical_replication_slot('%s', TRUE)", slot_name); } else { appendPQExpBuffer(&query, "SELECT * FROM pg_catalog.pg_create_physical_replication_slot('%s')", slot_name); } log_debug(_("create_replication_slot_sql(): creating slot \"%s\" on upstream"), slot_name); log_verbose(LOG_DEBUG, "create_replication_slot_sql():\n%s", query.data); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK && error_msg != NULL) { appendPQExpBuffer(error_msg, _("unable to create replication slot \"%s\" on the upstream node: %s\n"), slot_name, PQerrorMessage(conn)); success = false; } PQclear(res); return success; } bool drop_replication_slot_sql(PGconn *conn, char *slot_name) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT pg_catalog.pg_drop_replication_slot('%s')", slot_name); log_verbose(LOG_DEBUG, "drop_replication_slot_sql():\n %s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("drop_replication_slot_sql(): unable to drop replication slot \"%s\""), slot_name); success = false; } else { log_verbose(LOG_DEBUG, "replication slot \"%s\" successfully dropped", slot_name); } termPQExpBuffer(&query); PQclear(res); return success; } bool drop_replication_slot_replprot(PGconn *repl_conn, char *slot_name) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBuffer(&query, "DROP_REPLICATION_SLOT %s", slot_name); log_verbose(LOG_DEBUG, "drop_replication_slot_replprot():\n %s", query.data); res = PQexec(repl_conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(repl_conn, query.data, _("drop_replication_slot_sql(): unable to drop replication slot \"%s\""), slot_name); success = false; } else { log_verbose(LOG_DEBUG, "replication slot \"%s\" successfully dropped", slot_name); } termPQExpBuffer(&query); PQclear(res); return success; } RecordStatus get_slot_record(PGconn *conn, char *slot_name, t_replication_slot *record) { PQExpBufferData query; PGresult *res = NULL; RecordStatus record_status = RECORD_FOUND; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT slot_name, slot_type, active " " FROM pg_catalog.pg_replication_slots " " WHERE slot_name = '%s' ", slot_name); log_verbose(LOG_DEBUG, "get_slot_record():\n%s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_slot_record(): unable to query pg_replication_slots")); record_status = RECORD_ERROR; } else if (!PQntuples(res)) { record_status = RECORD_NOT_FOUND; } else { snprintf(record->slot_name, sizeof(record->slot_name), "%s", PQgetvalue(res, 0, 0)); snprintf(record->slot_type, sizeof(record->slot_type), "%s", PQgetvalue(res, 0, 1)); record->active = atobool(PQgetvalue(res, 0, 2)); } termPQExpBuffer(&query); PQclear(res); return record_status; } int get_free_replication_slot_count(PGconn *conn, int *max_replication_slots) { PQExpBufferData query; PGresult *res = NULL; int free_slots = 0; initPQExpBuffer(&query); appendPQExpBufferStr(&query, " SELECT pg_catalog.current_setting('max_replication_slots')::INT - " " pg_catalog.count(*) " " AS free_slots, " " pg_catalog.current_setting('max_replication_slots')::INT " " AS max_replication_slots " " FROM pg_catalog.pg_replication_slots s" " WHERE s.slot_type = 'physical'"); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_free_replication_slot_count(): unable to execute replication slot query")); free_slots = UNKNOWN_VALUE; } else if (PQntuples(res) == 0) { free_slots = UNKNOWN_VALUE; } else { free_slots = atoi(PQgetvalue(res, 0, 0)); if (max_replication_slots != NULL) *max_replication_slots = atoi(PQgetvalue(res, 0, 1)); } termPQExpBuffer(&query); PQclear(res); return free_slots; } int get_inactive_replication_slots(PGconn *conn, KeyValueList *list) { PQExpBufferData query; PGresult *res = NULL; int i, inactive_slots = 0; initPQExpBuffer(&query); appendPQExpBufferStr(&query, " SELECT slot_name, slot_type " " FROM pg_catalog.pg_replication_slots " " WHERE active IS FALSE " " AND slot_type = 'physical' " " ORDER BY slot_name "); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_inactive_replication_slots(): unable to execute replication slot query")); inactive_slots = -1; } else { inactive_slots = PQntuples(res); for (i = 0; i < inactive_slots; i++) { key_value_list_set(list, PQgetvalue(res, i, 0), PQgetvalue(res, i, 1)); } } termPQExpBuffer(&query); PQclear(res); return inactive_slots; } /* ==================== */ /* tablespace functions */ /* ==================== */ bool get_tablespace_name_by_location(PGconn *conn, const char *location, char *name) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT spcname " " FROM pg_catalog.pg_tablespace " " WHERE pg_catalog.pg_tablespace_location(oid) = '%s'", location); log_verbose(LOG_DEBUG, "get_tablespace_name_by_location():\n%s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("get_tablespace_name_by_location(): unable to execute tablespace query")); success = false; } else if (PQntuples(res) == 0) { success = false; } else { snprintf(name, MAXLEN, "%s", PQgetvalue(res, 0, 0)); } termPQExpBuffer(&query); PQclear(res); return success; } /* ============================ */ /* asynchronous query functions */ /* ============================ */ bool cancel_query(PGconn *conn, int timeout) { char errbuf[ERRBUFF_SIZE] = ""; PGcancel *pgcancel = NULL; if (wait_connection_availability(conn, timeout) != 1) return false; pgcancel = PQgetCancel(conn); if (pgcancel == NULL) return false; /* * PQcancel can only return 0 if socket()/connect()/send() fails, in any * of those cases we can assume something bad happened to the connection */ if (PQcancel(pgcancel, errbuf, ERRBUFF_SIZE) == 0) { log_warning(_("unable to cancel current query")); log_detail("\n%s", errbuf); PQfreeCancel(pgcancel); return false; } PQfreeCancel(pgcancel); return true; } /* * Wait until current query finishes, ignoring any results. * Usually this will be an async query or query cancellation. * * Returns 1 for success; 0 if any error occurred; -1 if timeout reached. */ int wait_connection_availability(PGconn *conn, int timeout) { PGresult *res = NULL; fd_set read_set; int sock = PQsocket(conn); struct timeval tmout, before, after; struct timezone tz; long long timeout_ms; /* calculate timeout in microseconds */ timeout_ms = (long long) timeout * 1000000; while (timeout_ms > 0) { if (PQconsumeInput(conn) == 0) { log_warning(_("wait_connection_availability(): unable to receive data from connection")); log_detail("%s", PQerrorMessage(conn)); return 0; } if (PQisBusy(conn) == 0) { do { res = PQgetResult(conn); PQclear(res); } while (res != NULL); break; } tmout.tv_sec = 0; tmout.tv_usec = 250000; FD_ZERO(&read_set); FD_SET(sock, &read_set); gettimeofday(&before, &tz); if (select(sock, &read_set, NULL, NULL, &tmout) == -1) { log_warning(_("wait_connection_availability(): select() returned with error")); log_detail("%s", strerror(errno)); return -1; } gettimeofday(&after, &tz); timeout_ms -= (after.tv_sec * 1000000 + after.tv_usec) - (before.tv_sec * 1000000 + before.tv_usec); } if (timeout_ms >= 0) { return 1; } log_warning(_("wait_connection_availability(): timeout (%i secs) reached"), timeout); return -1; } /* =========================== */ /* node availability functions */ /* =========================== */ bool is_server_available(const char *conninfo) { return _is_server_available(conninfo, false); } bool is_server_available_quiet(const char *conninfo) { return _is_server_available(conninfo, true); } static bool _is_server_available(const char *conninfo, bool quiet) { PGPing status = PQping(conninfo); log_verbose(LOG_DEBUG, "is_server_available(): ping status for \"%s\" is %s", conninfo, print_pqping_status(status)); if (status == PQPING_OK) return true; if (quiet == false) { log_warning(_("unable to ping \"%s\""), conninfo); log_detail(_("PQping() returned \"%s\""), print_pqping_status(status)); } return false; } bool is_server_available_params(t_conninfo_param_list *param_list) { PGPing status = PQpingParams((const char **) param_list->keywords, (const char **) param_list->values, false); /* deparsing the param_list adds overhead, so only do it if needed */ if (log_level == LOG_DEBUG || status != PQPING_OK) { char *conninfo_str = param_list_to_string(param_list); log_verbose(LOG_DEBUG, "is_server_available_params(): ping status for \"%s\" is %s", conninfo_str, print_pqping_status(status)); if (status != PQPING_OK) { log_warning(_("unable to ping \"%s\""), conninfo_str); log_detail(_("PQping() returned \"%s\""), print_pqping_status(status)); } pfree(conninfo_str); } if (status == PQPING_OK) return true; return false; } /* * Simple throw-away query to stop a connection handle going stale. */ ExecStatusType connection_ping(PGconn *conn) { PGresult *res = PQexec(conn, "SELECT TRUE"); ExecStatusType ping_result; log_verbose(LOG_DEBUG, "connection_ping(): result is %s", PQresStatus(PQresultStatus(res))); ping_result = PQresultStatus(res); PQclear(res); return ping_result; } ExecStatusType connection_ping_reconnect(PGconn *conn) { ExecStatusType ping_result = connection_ping(conn); if (PQstatus(conn) != CONNECTION_OK) { log_warning(_("connection error, attempting to reset")); log_detail("\n%s", PQerrorMessage(conn)); PQreset(conn); ping_result = connection_ping(conn); } log_verbose(LOG_DEBUG, "connection_ping_reconnect(): result is %s", PQresStatus(ping_result)); return ping_result; } /* ==================== */ /* monitoring functions */ /* ==================== */ void add_monitoring_record(PGconn *primary_conn, PGconn *local_conn, int primary_node_id, int local_node_id, char *monitor_standby_timestamp, XLogRecPtr primary_last_wal_location, XLogRecPtr last_wal_receive_lsn, char *last_xact_replay_timestamp, long long unsigned int replication_lag_bytes, long long unsigned int apply_lag_bytes ) { PQExpBufferData query; initPQExpBuffer(&query); appendPQExpBuffer(&query, "INSERT INTO repmgr.monitoring_history " " (primary_node_id, " " standby_node_id, " " last_monitor_time, " " last_apply_time, " " last_wal_primary_location, " " last_wal_standby_location, " " replication_lag, " " apply_lag ) " " VALUES(%i, " " %i, " " '%s'::TIMESTAMP WITH TIME ZONE, " " '%s'::TIMESTAMP WITH TIME ZONE, " " '%X/%X', " " '%X/%X', " " %llu, " " %llu) ", primary_node_id, local_node_id, monitor_standby_timestamp, last_xact_replay_timestamp, format_lsn(primary_last_wal_location), format_lsn(last_wal_receive_lsn), replication_lag_bytes, apply_lag_bytes); log_verbose(LOG_DEBUG, "standby_monitor:()\n%s", query.data); if (PQsendQuery(primary_conn, query.data) == 0) { log_warning(_("query could not be sent to primary:\n %s"), PQerrorMessage(primary_conn)); } else { PGresult *res = PQexec(local_conn, "SELECT repmgr.standby_set_last_updated()"); /* not critical if the above query fails */ if (PQresultStatus(res) != PGRES_TUPLES_OK) log_warning(_("add_monitoring_record(): unable to set last_updated:\n %s"), PQerrorMessage(local_conn)); PQclear(res); } termPQExpBuffer(&query); return; } int get_number_of_monitoring_records_to_delete(PGconn *primary_conn, int keep_history, int node_id) { PQExpBufferData query; int record_count = -1; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT pg_catalog.count(*) " " FROM repmgr.monitoring_history " " WHERE pg_catalog.age(pg_catalog.now(), last_monitor_time) >= '%d days'::interval", keep_history); if (node_id != UNKNOWN_NODE_ID) { appendPQExpBuffer(&query, " AND standby_node_id = %i", node_id); } log_verbose(LOG_DEBUG, "get_number_of_monitoring_records_to_delete():\n %s", query.data); res = PQexec(primary_conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(primary_conn, query.data, _("get_number_of_monitoring_records_to_delete(): unable to query number of monitoring records to clean up")); } else { record_count = atoi(PQgetvalue(res, 0, 0)); } termPQExpBuffer(&query); PQclear(res); return record_count; } bool delete_monitoring_records(PGconn *primary_conn, int keep_history, int node_id) { PQExpBufferData query; bool success = true; PGresult *res = NULL; initPQExpBuffer(&query); if (keep_history > 0 || node_id != UNKNOWN_NODE_ID) { appendPQExpBuffer(&query, "DELETE FROM repmgr.monitoring_history " " WHERE pg_catalog.age(pg_catalog.now(), last_monitor_time) >= '%d days'::INTERVAL ", keep_history); if (node_id != UNKNOWN_NODE_ID) { appendPQExpBuffer(&query, " AND standby_node_id = %i", node_id); } } else { appendPQExpBufferStr(&query, "TRUNCATE TABLE repmgr.monitoring_history"); } res = PQexec(primary_conn, query.data); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(primary_conn, query.data, _("delete_monitoring_records(): unable to delete monitoring records")); success = false; } termPQExpBuffer(&query); PQclear(res); return success; } /* * node voting functions * * These are intended to run under repmgrd and mainly rely on shared memory */ int get_current_term(PGconn *conn) { PGresult *res = NULL; int term = VOTING_TERM_NOT_SET; res = PQexec(conn, "SELECT term FROM repmgr.voting_term"); /* it doesn't matter if for whatever reason the table has no rows */ if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, NULL, _("get_current_term(): unable to query \"repmgr.voting_term\"")); } else if (PQntuples(res) > 0) { term = atoi(PQgetvalue(res, 0, 0)); } PQclear(res); return term; } void initialize_voting_term(PGconn *conn) { PGresult *res = NULL; int current_term = get_current_term(conn); if (current_term == VOTING_TERM_NOT_SET) { res = PQexec(conn, "INSERT INTO repmgr.voting_term (term) VALUES (1)"); } else { res = PQexec(conn, "UPDATE repmgr.voting_term SET term = 1"); } if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(conn, NULL, _("unable to initialize repmgr.voting_term")); } PQclear(res); return; } void increment_current_term(PGconn *conn) { PGresult *res = NULL; res = PQexec(conn, "UPDATE repmgr.voting_term SET term = term + 1"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(conn, NULL, _("unable to increment repmgr.voting_term")); } PQclear(res); return; } bool announce_candidature(PGconn *conn, t_node_info *this_node, t_node_info *other_node, int electoral_term) { PQExpBufferData query; PGresult *res = NULL; bool retval = false; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT repmgr.other_node_is_candidate(%i, %i)", this_node->node_id, electoral_term); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_db_error(conn, query.data, _("announce_candidature(): unable to execute repmgr.other_node_is_candidate()")); } else { retval = atobool(PQgetvalue(res, 0, 0)); } termPQExpBuffer(&query); PQclear(res); return retval; } void notify_follow_primary(PGconn *conn, int primary_node_id) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT repmgr.notify_follow_primary(%i)", primary_node_id); log_verbose(LOG_DEBUG, "notify_follow_primary():\n %s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("unable to execute repmgr.notify_follow_primary()")); } termPQExpBuffer(&query); PQclear(res); return; } bool get_new_primary(PGconn *conn, int *primary_node_id) { PGresult *res = NULL; int new_primary_node_id = UNKNOWN_NODE_ID; bool success = true; const char *sqlquery = "SELECT repmgr.get_new_primary()"; res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, sqlquery, _("unable to execute repmgr.get_new_primary()")); success = false; } else if (PQgetisnull(res, 0, 0)) { success = false; } else { new_primary_node_id = atoi(PQgetvalue(res, 0, 0)); } PQclear(res); /* * repmgr.get_new_primary() will return UNKNOWN_NODE_ID if * "follow_new_primary" is false */ if (new_primary_node_id == UNKNOWN_NODE_ID) success = false; *primary_node_id = new_primary_node_id; return success; } void reset_voting_status(PGconn *conn) { PGresult *res = NULL; const char *sqlquery = "SELECT repmgr.reset_voting_status()"; res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, sqlquery, _("unable to execute repmgr.reset_voting_status()")); } PQclear(res); return; } /* ============================ */ /* replication status functions */ /* ============================ */ /* * Returns the current LSN on the primary. * * This just executes "pg_current_wal_lsn()". * * Function "get_node_current_lsn()" below will return the latest * LSN regardless of recovery state. */ XLogRecPtr get_primary_current_lsn(PGconn *conn) { PGresult *res = NULL; XLogRecPtr ptr = InvalidXLogRecPtr; if (PQserverVersion(conn) >= 100000) { res = PQexec(conn, "SELECT pg_catalog.pg_current_wal_lsn()"); } else { res = PQexec(conn, "SELECT pg_catalog.pg_current_xlog_location()"); } if (PQresultStatus(res) == PGRES_TUPLES_OK) { ptr = parse_lsn(PQgetvalue(res, 0, 0)); } else { log_db_error(conn, NULL, _("unable to execute get_primary_current_lsn()")); } PQclear(res); return ptr; } XLogRecPtr get_last_wal_receive_location(PGconn *conn) { PGresult *res = NULL; XLogRecPtr ptr = InvalidXLogRecPtr; if (PQserverVersion(conn) >= 100000) { res = PQexec(conn, "SELECT pg_catalog.pg_last_wal_receive_lsn()"); } else { res = PQexec(conn, "SELECT pg_catalog.pg_last_xlog_receive_location()"); } if (PQresultStatus(res) == PGRES_TUPLES_OK) { ptr = parse_lsn(PQgetvalue(res, 0, 0)); } else { log_db_error(conn, NULL, _("unable to execute get_last_wal_receive_location()")); } PQclear(res); return ptr; } /* * Returns the latest LSN for the node regardless of recovery state. */ XLogRecPtr get_node_current_lsn(PGconn *conn) { PQExpBufferData query; PGresult *res = NULL; XLogRecPtr ptr = InvalidXLogRecPtr; initPQExpBuffer(&query); if (PQserverVersion(conn) >= 100000) { appendPQExpBufferStr(&query, " WITH lsn_states AS ( " " SELECT " " CASE WHEN pg_catalog.pg_is_in_recovery() IS FALSE " " THEN pg_catalog.pg_current_wal_lsn() " " ELSE NULL " " END " " AS current_wal_lsn, " " CASE WHEN pg_catalog.pg_is_in_recovery() IS TRUE " " THEN pg_catalog.pg_last_wal_receive_lsn() " " ELSE NULL " " END " " AS last_wal_receive_lsn, " " CASE WHEN pg_catalog.pg_is_in_recovery() IS TRUE " " THEN pg_catalog.pg_last_wal_replay_lsn() " " ELSE NULL " " END " " AS last_wal_replay_lsn " " ) "); } else { appendPQExpBufferStr(&query, " WITH lsn_states AS ( " " SELECT " " CASE WHEN pg_catalog.pg_is_in_recovery() IS FALSE " " THEN pg_catalog.pg_current_xlog_location() " " ELSE NULL " " END " " AS current_wal_lsn, " " CASE WHEN pg_catalog.pg_is_in_recovery() IS TRUE " " THEN pg_catalog.pg_last_xlog_receive_location() " " ELSE NULL " " END " " AS last_wal_receive_lsn, " " CASE WHEN pg_catalog.pg_is_in_recovery() IS TRUE " " THEN pg_catalog.pg_last_xlog_replay_location() " " ELSE NULL " " END " " AS last_wal_replay_lsn " " ) "); } appendPQExpBufferStr(&query, " SELECT " " CASE WHEN pg_catalog.pg_is_in_recovery() IS FALSE " " THEN current_wal_lsn " " ELSE " " CASE WHEN last_wal_receive_lsn IS NULL " " THEN last_wal_replay_lsn " " ELSE " " CASE WHEN last_wal_replay_lsn > last_wal_receive_lsn " " THEN last_wal_replay_lsn " " ELSE last_wal_receive_lsn " " END " " END " " END " " AS current_lsn " " FROM lsn_states "); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("unable to execute get_node_current_lsn()")); } else if (!PQgetisnull(res, 0, 0)) { ptr = parse_lsn(PQgetvalue(res, 0, 0)); } termPQExpBuffer(&query); PQclear(res); return ptr; } void init_replication_info(ReplInfo *replication_info) { memset(replication_info->current_timestamp, 0, sizeof(replication_info->current_timestamp)); replication_info->in_recovery = false; replication_info->timeline_id = UNKNOWN_TIMELINE_ID; replication_info->last_wal_receive_lsn = InvalidXLogRecPtr; replication_info->last_wal_replay_lsn = InvalidXLogRecPtr; memset(replication_info->last_xact_replay_timestamp, 0, sizeof(replication_info->last_xact_replay_timestamp)); replication_info->replication_lag_time = 0; replication_info->receiving_streamed_wal = true; replication_info->wal_replay_paused = false; replication_info->upstream_last_seen = -1; replication_info->upstream_node_id = UNKNOWN_NODE_ID; } bool get_replication_info(PGconn *conn, t_server_type node_type, ReplInfo *replication_info) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBufferStr(&query, " SELECT ts, " " in_recovery, " " last_wal_receive_lsn, " " last_wal_replay_lsn, " " last_xact_replay_timestamp, " " CASE WHEN (last_wal_receive_lsn = last_wal_replay_lsn) " " THEN 0::INT " " ELSE " " CASE WHEN last_xact_replay_timestamp IS NULL " " THEN 0::INT " " ELSE " " EXTRACT(epoch FROM (pg_catalog.clock_timestamp() - last_xact_replay_timestamp))::INT " " END " " END AS replication_lag_time, " " last_wal_receive_lsn >= last_wal_replay_lsn AS receiving_streamed_wal, " " wal_replay_paused, " " upstream_last_seen, " " upstream_node_id " " FROM ( " " SELECT CURRENT_TIMESTAMP AS ts, " " pg_catalog.pg_is_in_recovery() AS in_recovery, " " pg_catalog.pg_last_xact_replay_timestamp() AS last_xact_replay_timestamp, "); if (PQserverVersion(conn) >= 100000) { appendPQExpBufferStr(&query, " COALESCE(pg_catalog.pg_last_wal_receive_lsn(), '0/0'::PG_LSN) AS last_wal_receive_lsn, " " COALESCE(pg_catalog.pg_last_wal_replay_lsn(), '0/0'::PG_LSN) AS last_wal_replay_lsn, " " CASE WHEN pg_catalog.pg_is_in_recovery() IS FALSE " " THEN FALSE " " ELSE pg_catalog.pg_is_wal_replay_paused() " " END AS wal_replay_paused, "); } else { appendPQExpBufferStr(&query, " COALESCE(pg_catalog.pg_last_xlog_receive_location(), '0/0'::PG_LSN) AS last_wal_receive_lsn, " " COALESCE(pg_catalog.pg_last_xlog_replay_location(), '0/0'::PG_LSN) AS last_wal_replay_lsn, " " CASE WHEN pg_catalog.pg_is_in_recovery() IS FALSE " " THEN FALSE " " ELSE pg_catalog.pg_is_xlog_replay_paused() " " END AS wal_replay_paused, "); } /* Add information about upstream node from shared memory */ if (node_type == WITNESS) { appendPQExpBufferStr(&query, " repmgr.get_upstream_last_seen() AS upstream_last_seen, " " repmgr.get_upstream_node_id() AS upstream_node_id "); } else { appendPQExpBufferStr(&query, " CASE WHEN pg_catalog.pg_is_in_recovery() IS FALSE " " THEN -1 " " ELSE repmgr.get_upstream_last_seen() " " END AS upstream_last_seen, "); appendPQExpBufferStr(&query, " CASE WHEN pg_catalog.pg_is_in_recovery() IS FALSE " " THEN -1 " " ELSE repmgr.get_upstream_node_id() " " END AS upstream_node_id "); } appendPQExpBufferStr(&query, " ) q "); log_verbose(LOG_DEBUG, "get_replication_info():\n%s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK || !PQntuples(res)) { log_db_error(conn, query.data, _("get_replication_info(): unable to execute query")); success = false; } else { snprintf(replication_info->current_timestamp, sizeof(replication_info->current_timestamp), "%s", PQgetvalue(res, 0, 0)); replication_info->in_recovery = atobool(PQgetvalue(res, 0, 1)); replication_info->last_wal_receive_lsn = parse_lsn(PQgetvalue(res, 0, 2)); replication_info->last_wal_replay_lsn = parse_lsn(PQgetvalue(res, 0, 3)); snprintf(replication_info->last_xact_replay_timestamp, sizeof(replication_info->last_xact_replay_timestamp), "%s", PQgetvalue(res, 0, 4)); replication_info->replication_lag_time = atoi(PQgetvalue(res, 0, 5)); replication_info->receiving_streamed_wal = atobool(PQgetvalue(res, 0, 6)); replication_info->wal_replay_paused = atobool(PQgetvalue(res, 0, 7)); replication_info->upstream_last_seen = atoi(PQgetvalue(res, 0, 8)); replication_info->upstream_node_id = atoi(PQgetvalue(res, 0, 9)); } termPQExpBuffer(&query); PQclear(res); return success; } int get_replication_lag_seconds(PGconn *conn) { PQExpBufferData query; PGresult *res = NULL; int lag_seconds = 0; initPQExpBuffer(&query); if (PQserverVersion(conn) >= 100000) { appendPQExpBufferStr(&query, " SELECT CASE WHEN (pg_catalog.pg_last_wal_receive_lsn() = pg_catalog.pg_last_wal_replay_lsn()) "); } else { appendPQExpBufferStr(&query, " SELECT CASE WHEN (pg_catalog.pg_last_xlog_receive_location() = pg_catalog.pg_last_xlog_replay_location()) "); } appendPQExpBufferStr(&query, " THEN 0 " " ELSE EXTRACT(epoch FROM (pg_catalog.clock_timestamp() - pg_catalog.pg_last_xact_replay_timestamp()))::INT " " END " " AS lag_seconds"); res = PQexec(conn, query.data); log_verbose(LOG_DEBUG, "get_replication_lag_seconds():\n%s", query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_warning("%s", PQerrorMessage(conn)); PQclear(res); return UNKNOWN_REPLICATION_LAG; } if (!PQntuples(res)) { return UNKNOWN_REPLICATION_LAG; } lag_seconds = atoi(PQgetvalue(res, 0, 0)); PQclear(res); return lag_seconds; } TimeLineID get_node_timeline(PGconn *conn, char *timeline_id_str) { TimeLineID timeline_id = UNKNOWN_TIMELINE_ID; /* * PG_control_checkpoint() was introduced in PostgreSQL 9.6 */ if (PQserverVersion(conn) >= 90600) { PGresult *res = NULL; res = PQexec(conn, "SELECT timeline_id FROM pg_catalog.pg_control_checkpoint()"); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, NULL, _("get_node_timeline(): unable to query pg_control_system()")); } else { timeline_id = atoi(PQgetvalue(res, 0, 0)); } PQclear(res); } /* If requested, format the timeline ID as a string */ if (timeline_id_str != NULL) { if (timeline_id == UNKNOWN_TIMELINE_ID) { strncpy(timeline_id_str, "?", MAXLEN); } else { snprintf(timeline_id_str, MAXLEN, "%i", timeline_id); } } return timeline_id; } void get_node_replication_stats(PGconn *conn, t_node_info *node_info) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBufferStr(&query, " SELECT pg_catalog.current_setting('max_wal_senders')::INT AS max_wal_senders, " " (SELECT pg_catalog.count(*) FROM pg_catalog.pg_stat_replication) AS attached_wal_receivers, " " current_setting('max_replication_slots')::INT AS max_replication_slots, " " (SELECT pg_catalog.count(*) FROM pg_catalog.pg_replication_slots WHERE slot_type='physical') AS total_replication_slots, " " (SELECT pg_catalog.count(*) FROM pg_catalog.pg_replication_slots WHERE active IS TRUE AND slot_type='physical') AS active_replication_slots, " " (SELECT pg_catalog.count(*) FROM pg_catalog.pg_replication_slots WHERE active IS FALSE AND slot_type='physical') AS inactive_replication_slots, " " pg_catalog.pg_is_in_recovery() AS in_recovery"); log_verbose(LOG_DEBUG, "get_node_replication_stats():\n%s", query.data); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_warning(_("unable to retrieve node replication statistics")); log_detail("%s", PQerrorMessage(conn)); log_detail("%s", query.data); termPQExpBuffer(&query); PQclear(res); return; } node_info->max_wal_senders = atoi(PQgetvalue(res, 0, 0)); node_info->attached_wal_receivers = atoi(PQgetvalue(res, 0, 1)); node_info->max_replication_slots = atoi(PQgetvalue(res, 0, 2)); node_info->total_replication_slots = atoi(PQgetvalue(res, 0, 3)); node_info->active_replication_slots = atoi(PQgetvalue(res, 0, 4)); node_info->inactive_replication_slots = atoi(PQgetvalue(res, 0, 5)); node_info->recovery_type = strcmp(PQgetvalue(res, 0, 6), "f") == 0 ? RECTYPE_PRIMARY : RECTYPE_STANDBY; termPQExpBuffer(&query); PQclear(res); return; } NodeAttached is_downstream_node_attached(PGconn *conn, char *node_name, char **node_state) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT pid, state " " FROM pg_catalog.pg_stat_replication " " WHERE application_name = '%s'", node_name); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_verbose(LOG_WARNING, _("unable to query pg_stat_replication")); log_detail("%s", PQerrorMessage(conn)); log_detail("%s", query.data); termPQExpBuffer(&query); PQclear(res); return NODE_ATTACHED_UNKNOWN; } termPQExpBuffer(&query); /* * If there's more than one entry in pg_stat_application, there's no * way we can reliably determine which one belongs to the node we're * checking, so there's nothing more we can do. */ if (PQntuples(res) > 1) { log_error(_("multiple entries with \"application_name\" set to \"%s\" found in \"pg_stat_replication\""), node_name); log_hint(_("verify that a unique node name is configured for each node")); PQclear(res); return NODE_ATTACHED_UNKNOWN; } if (PQntuples(res) == 0) { log_warning(_("node \"%s\" not found in \"pg_stat_replication\""), node_name); PQclear(res); return NODE_DETACHED; } /* * If the connection is not a superuser or member of pg_read_all_stats, we * won't be able to retrieve the "state" column, so we'll assume * the node is attached. */ if (connection_has_pg_monitor_role(conn, "pg_read_all_stats")) { const char *state = PQgetvalue(res, 0, 1); if (node_state != NULL) { int state_len = strlen(state); *node_state = palloc0(state_len + 1); strncpy(*node_state, state, state_len); } if (strcmp(state, "streaming") != 0) { log_warning(_("node \"%s\" attached in state \"%s\""), node_name, state); PQclear(res); return NODE_NOT_ATTACHED; } } else if (node_state != NULL) { *node_state = palloc0(1); *node_state[0] = '\0'; } PQclear(res); return NODE_ATTACHED; } void set_upstream_last_seen(PGconn *conn, int upstream_node_id) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT repmgr.set_upstream_last_seen(%i)", upstream_node_id); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("unable to execute repmgr.set_upstream_last_seen()")); } termPQExpBuffer(&query); PQclear(res); } int get_upstream_last_seen(PGconn *conn, t_server_type node_type) { PQExpBufferData query; PGresult *res = NULL; int upstream_last_seen = -1; initPQExpBuffer(&query); if (node_type == WITNESS) { appendPQExpBufferStr(&query, "SELECT repmgr.get_upstream_last_seen()"); } else { appendPQExpBufferStr(&query, "SELECT CASE WHEN pg_catalog.pg_is_in_recovery() IS FALSE " " THEN -1 " " ELSE repmgr.get_upstream_last_seen() " " END AS upstream_last_seen "); } res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("unable to execute repmgr.get_upstream_last_seen()")); } else { upstream_last_seen = atoi(PQgetvalue(res, 0, 0)); } termPQExpBuffer(&query); PQclear(res); return upstream_last_seen; } bool is_wal_replay_paused(PGconn *conn, bool check_pending_wal) { PQExpBufferData query; PGresult *res = NULL; bool is_paused = false; initPQExpBuffer(&query); appendPQExpBufferStr(&query, "SELECT paused.wal_replay_paused "); if (PQserverVersion(conn) >= 100000) { if (check_pending_wal == true) { appendPQExpBufferStr(&query, " AND pg_catalog.pg_last_wal_replay_lsn() < pg_catalog.pg_last_wal_receive_lsn() "); } appendPQExpBufferStr(&query, " FROM (SELECT CASE WHEN pg_catalog.pg_is_in_recovery() IS FALSE " " THEN FALSE " " ELSE pg_catalog.pg_is_wal_replay_paused() " " END AS wal_replay_paused) paused "); } else { if (check_pending_wal == true) { appendPQExpBufferStr(&query, " AND pg_catalog.pg_last_xlog_replay_location() < pg_catalog.pg_last_xlog_receive_location() "); } appendPQExpBufferStr(&query, " FROM (SELECT CASE WHEN pg_catalog.pg_is_in_recovery() IS FALSE " " THEN FALSE " " ELSE pg_catalog.pg_is_xlog_replay_paused() " " END AS wal_replay_paused) paused "); } res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("unable to execute WAL replay pause query")); } else { is_paused = atobool(PQgetvalue(res, 0, 0)); } termPQExpBuffer(&query); PQclear(res); return is_paused; } /* repmgrd status functions */ CheckStatus get_repmgrd_status(PGconn *conn) { PQExpBufferData query; PGresult *res = NULL; CheckStatus repmgrd_status = CHECK_STATUS_CRITICAL; initPQExpBuffer(&query); appendPQExpBufferStr(&query, " SELECT " " CASE " " WHEN repmgr.repmgrd_is_running() " " THEN " " CASE " " WHEN repmgr.repmgrd_is_paused() THEN 1 ELSE 0 " " END " " ELSE 2 " " END AS repmgrd_status"); res = PQexec(conn, query.data); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_db_error(conn, query.data, _("unable to execute repmgrd status query")); } else { repmgrd_status = atoi(PQgetvalue(res, 0, 0)); } termPQExpBuffer(&query); PQclear(res); return repmgrd_status; } /* miscellaneous debugging functions */ const char * print_node_status(NodeStatus node_status) { switch (node_status) { case NODE_STATUS_UNKNOWN: return "UNKNOWN"; case NODE_STATUS_UP: return "UP"; case NODE_STATUS_SHUTTING_DOWN: return "SHUTTING_DOWN"; case NODE_STATUS_DOWN: return "SHUTDOWN"; case NODE_STATUS_UNCLEAN_SHUTDOWN: return "UNCLEAN_SHUTDOWN"; case NODE_STATUS_REJECTED: return "REJECTED"; } return "UNIDENTIFIED_STATUS"; } const char * print_pqping_status(PGPing ping_status) { switch (ping_status) { case PQPING_OK: return "PQPING_OK"; case PQPING_REJECT: return "PQPING_REJECT"; case PQPING_NO_RESPONSE: return "PQPING_NO_RESPONSE"; case PQPING_NO_ATTEMPT: return "PQPING_NO_ATTEMPT"; } return "PQPING_UNKNOWN_STATUS"; } repmgr-5.3.1/dbutils.h000066400000000000000000000454321420262710000146350ustar00rootroot00000000000000/* * dbutils.h * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _REPMGR_DBUTILS_H_ #define _REPMGR_DBUTILS_H_ #include "access/timeline.h" #include "access/xlogdefs.h" #include "pqexpbuffer.h" #include "portability/instr_time.h" #include "configfile.h" #include "strutil.h" #include "voting.h" #define REPMGR_NODES_COLUMNS \ "n.node_id, " \ "n.type, " \ "n.upstream_node_id, " \ "n.node_name, " \ "n.conninfo, " \ "n.repluser, " \ "n.slot_name, " \ "n.location, " \ "n.priority, " \ "n.active, " \ "n.config_file, " \ "'' AS upstream_node_name, " \ "NULL AS attached " #define REPMGR_NODES_COLUMNS_WITH_UPSTREAM \ "n.node_id, " \ "n.type, " \ "n.upstream_node_id, " \ "n.node_name, " \ "n.conninfo, " \ "n.repluser, " \ "n.slot_name, " \ "n.location, " \ "n.priority, " \ "n.active, "\ "n.config_file, " \ "un.node_name AS upstream_node_name, " \ "NULL AS attached " #define ERRBUFF_SIZE 512 typedef enum { UNKNOWN = 0, PRIMARY, STANDBY, WITNESS } t_server_type; typedef enum { REPMGR_INSTALLED = 0, REPMGR_OLD_VERSION_INSTALLED, REPMGR_AVAILABLE, REPMGR_UNAVAILABLE, REPMGR_UNKNOWN } ExtensionStatus; typedef enum { RECTYPE_UNKNOWN = -1, RECTYPE_PRIMARY, RECTYPE_STANDBY } RecoveryType; typedef enum { RECORD_ERROR = -1, RECORD_FOUND, RECORD_NOT_FOUND } RecordStatus; typedef enum { MS_NORMAL = 0, MS_DEGRADED = 1 } MonitoringState; typedef enum { NODE_STATUS_UNKNOWN = -1, NODE_STATUS_UP, NODE_STATUS_SHUTTING_DOWN, NODE_STATUS_DOWN, NODE_STATUS_UNCLEAN_SHUTDOWN, NODE_STATUS_REJECTED } NodeStatus; typedef enum { CONN_UNKNOWN = -1, CONN_OK, CONN_BAD, CONN_ERROR } ConnectionStatus; typedef enum { /* unable to query "pg_stat_replication" or other error */ NODE_ATTACHED_UNKNOWN = -1, /* node has record in "pg_stat_replication" and state is not "streaming" */ NODE_ATTACHED, /* node has record in "pg_stat_replication" but state is not "streaming" */ NODE_NOT_ATTACHED, /* node has no record in "pg_stat_replication" */ NODE_DETACHED } NodeAttached; typedef enum { SLOT_UNKNOWN = -1, SLOT_NOT_FOUND, SLOT_NOT_PHYSICAL, SLOT_INACTIVE, SLOT_ACTIVE } ReplSlotStatus; typedef enum { BACKUP_STATE_UNKNOWN = -1, BACKUP_STATE_IN_BACKUP, BACKUP_STATE_NO_BACKUP } BackupState; /* * Struct to store extension version information */ typedef struct s_extension_versions { char default_version[8]; int default_version_num; char installed_version[8]; int installed_version_num; } t_extension_versions; #define T_EXTENSION_VERSIONS_INITIALIZER { \ "", \ UNKNOWN_SERVER_VERSION_NUM, \ "", \ UNKNOWN_SERVER_VERSION_NUM \ } typedef struct { char current_timestamp[MAXLEN]; bool in_recovery; TimeLineID timeline_id; char timeline_id_str[MAXLEN]; XLogRecPtr last_wal_receive_lsn; XLogRecPtr last_wal_replay_lsn; char last_xact_replay_timestamp[MAXLEN]; int replication_lag_time; bool receiving_streamed_wal; bool wal_replay_paused; int upstream_last_seen; int upstream_node_id; } ReplInfo; /* * Struct to store node information. * * The first section represents the contents of the "repmgr.nodes" * table; subsequent section contain information collated in * various contexts. */ typedef struct s_node_info { /* contents of "repmgr.nodes" */ int node_id; int upstream_node_id; t_server_type type; char node_name[NAMEDATALEN]; char upstream_node_name[NAMEDATALEN]; char conninfo[MAXLEN]; char repluser[NAMEDATALEN]; char location[MAXLEN]; int priority; bool active; char slot_name[MAXLEN]; char config_file[MAXPGPATH]; /* used during failover to track node status */ XLogRecPtr last_wal_receive_lsn; NodeStatus node_status; RecoveryType recovery_type; MonitoringState monitoring_state; PGconn *conn; /* for ad-hoc use e.g. when working with a list of nodes */ char details[MAXLEN]; bool reachable; NodeAttached attached; /* various statistics */ int max_wal_senders; int attached_wal_receivers; int max_replication_slots; int total_replication_slots; int active_replication_slots; int inactive_replication_slots; /* replication info */ ReplInfo *replication_info; } t_node_info; #define T_NODE_INFO_INITIALIZER { \ /* contents of "repmgr.nodes" */ \ NODE_NOT_FOUND, \ NO_UPSTREAM_NODE, \ UNKNOWN, \ "", \ "", \ "", \ "", \ DEFAULT_LOCATION, \ DEFAULT_PRIORITY, \ true, \ "", \ "", \ /* used during failover to track node status */ \ InvalidXLogRecPtr, \ NODE_STATUS_UNKNOWN, \ RECTYPE_UNKNOWN, \ MS_NORMAL, \ NULL, \ /* for ad-hoc use e.g. when working with a list of nodes */ \ "", true, true, \ /* various statistics */ \ -1, -1, -1, -1, -1, -1, \ NULL \ } /* structs to store a list of repmgr node records */ typedef struct NodeInfoListCell { struct NodeInfoListCell *next; t_node_info *node_info; } NodeInfoListCell; typedef struct NodeInfoList { NodeInfoListCell *head; NodeInfoListCell *tail; int node_count; } NodeInfoList; #define T_NODE_INFO_LIST_INITIALIZER { \ NULL, \ NULL, \ 0 \ } typedef struct s_event_info { char *node_name; char *conninfo_str; int node_id; } t_event_info; #define T_EVENT_INFO_INITIALIZER { \ NULL, \ NULL, \ UNKNOWN_NODE_ID \ } /* * Struct to store list of conninfo keywords and values */ typedef struct { int size; char **keywords; char **values; } t_conninfo_param_list; #define T_CONNINFO_PARAM_LIST_INITIALIZER { \ 0, \ NULL, \ NULL, \ } /* * Struct to store replication slot information */ typedef struct s_replication_slot { char slot_name[MAXLEN]; char slot_type[MAXLEN]; bool active; } t_replication_slot; #define T_REPLICATION_SLOT_INITIALIZER { "", "", false } typedef struct s_connection_user { char username[MAXLEN]; bool is_superuser; } t_connection_user; #define T_CONNECTION_USER_INITIALIZER { "", false } typedef struct { char filepath[MAXPGPATH]; char filename[MAXPGPATH]; bool in_data_directory; } t_configfile_info; #define T_CONFIGFILE_INFO_INITIALIZER { "", "", false } typedef struct { int size; int entries; t_configfile_info **files; } t_configfile_list; #define T_CONFIGFILE_LIST_INITIALIZER { 0, 0, NULL } typedef struct { uint64 system_identifier; TimeLineID timeline; XLogRecPtr xlogpos; } t_system_identification; #define T_SYSTEM_IDENTIFICATION_INITIALIZER { \ UNKNOWN_SYSTEM_IDENTIFIER, \ UNKNOWN_TIMELINE_ID, \ InvalidXLogRecPtr \ } typedef struct RepmgrdInfo { int node_id; int pid; char pid_text[MAXLEN]; char pid_file[MAXLEN]; bool pg_running; char pg_running_text[MAXLEN]; RecoveryType recovery_type; bool running; char repmgrd_running[MAXLEN]; bool paused; bool wal_paused_pending_wal; int upstream_last_seen; char upstream_last_seen_text[MAXLEN]; } RepmgrdInfo; /* macros */ #define is_streaming_replication(x) (x == PRIMARY || x == STANDBY) #define format_lsn(x) (uint32) (x >> 32), (uint32) x /* utility functions */ XLogRecPtr parse_lsn(const char *str); bool atobool(const char *value); /* connection functions */ PGconn *establish_db_connection(const char *conninfo, const bool exit_on_error); PGconn *establish_db_connection_quiet(const char *conninfo); PGconn *establish_db_connection_by_params(t_conninfo_param_list *param_list, const bool exit_on_error); PGconn *establish_db_connection_with_replacement_param(const char *conninfo, const char *param, const char *value, const bool exit_on_error); PGconn *establish_replication_connection_from_conn(PGconn *conn, const char *repluser); PGconn *establish_replication_connection_from_conninfo(const char *conninfo, const char *repluser); PGconn *establish_primary_db_connection(PGconn *conn, const bool exit_on_error); PGconn *get_primary_connection(PGconn *standby_conn, int *primary_id, char *primary_conninfo_out); PGconn *get_primary_connection_quiet(PGconn *standby_conn, int *primary_id, char *primary_conninfo_out); PGconn *duplicate_connection(PGconn *conn, const char *user, bool replication); void close_connection(PGconn **conn); /* conninfo manipulation functions */ bool get_conninfo_value(const char *conninfo, const char *keyword, char *output); bool get_conninfo_default_value(const char *param, char *output, int maxlen); void initialize_conninfo_params(t_conninfo_param_list *param_list, bool set_defaults); void free_conninfo_params(t_conninfo_param_list *param_list); void copy_conninfo_params(t_conninfo_param_list *dest_list, t_conninfo_param_list *source_list); void conn_to_param_list(PGconn *conn, t_conninfo_param_list *param_list); void param_set(t_conninfo_param_list *param_list, const char *param, const char *value); void param_set_ine(t_conninfo_param_list *param_list, const char *param, const char *value); char *param_get(t_conninfo_param_list *param_list, const char *param); bool validate_conninfo_string(const char *conninfo_str, char **errmsg); bool parse_conninfo_string(const char *conninfo_str, t_conninfo_param_list *param_list, char **errmsg, bool ignore_local_params); char *param_list_to_string(t_conninfo_param_list *param_list); char *normalize_conninfo_string(const char *conninfo_str); bool has_passfile(void); /* transaction functions */ bool begin_transaction(PGconn *conn); bool commit_transaction(PGconn *conn); bool rollback_transaction(PGconn *conn); /* GUC manipulation functions */ bool set_config(PGconn *conn, const char *config_param, const char *config_value); bool set_config_bool(PGconn *conn, const char *config_param, bool state); int guc_set(PGconn *conn, const char *parameter, const char *op, const char *value); bool get_pg_setting(PGconn *conn, const char *setting, char *output); bool get_pg_setting_bool(PGconn *conn, const char *setting, bool *output); bool get_pg_setting_int(PGconn *conn, const char *setting, int *output); bool alter_system_int(PGconn *conn, const char *name, int value); bool pg_reload_conf(PGconn *conn); /* server information functions */ bool get_cluster_size(PGconn *conn, char *size); int get_server_version(PGconn *conn, char *server_version_buf); RecoveryType get_recovery_type(PGconn *conn); int get_primary_node_id(PGconn *conn); int get_ready_archive_files(PGconn *conn, const char *data_directory); bool identify_system(PGconn *repl_conn, t_system_identification *identification); uint64 system_identifier(PGconn *conn); TimeLineHistoryEntry *get_timeline_history(PGconn *repl_conn, TimeLineID tli); /* user/role information functions */ bool can_execute_pg_promote(PGconn *conn); bool connection_has_pg_monitor_role(PGconn *conn, const char *subrole); bool is_replication_role(PGconn *conn, char *rolname); bool is_superuser_connection(PGconn *conn, t_connection_user *userinfo); /* repmgrd shared memory functions */ bool repmgrd_set_local_node_id(PGconn *conn, int local_node_id); int repmgrd_get_local_node_id(PGconn *conn); bool repmgrd_check_local_node_id(PGconn *conn); BackupState server_in_exclusive_backup_mode(PGconn *conn); void repmgrd_set_pid(PGconn *conn, pid_t repmgrd_pid, const char *pidfile); pid_t repmgrd_get_pid(PGconn *conn); bool repmgrd_is_running(PGconn *conn); bool repmgrd_is_paused(PGconn *conn); bool repmgrd_pause(PGconn *conn, bool pause); pid_t get_wal_receiver_pid(PGconn *conn); int repmgrd_get_upstream_node_id(PGconn *conn); bool repmgrd_set_upstream_node_id(PGconn *conn, int node_id); /* extension functions */ ExtensionStatus get_repmgr_extension_status(PGconn *conn, t_extension_versions *extversions); /* node management functions */ void checkpoint(PGconn *conn); bool vacuum_table(PGconn *conn, const char *table); bool promote_standby(PGconn *conn, bool wait, int wait_seconds); bool resume_wal_replay(PGconn *conn); /* node record functions */ t_server_type parse_node_type(const char *type); const char *get_node_type_string(t_server_type type); RecordStatus get_node_record(PGconn *conn, int node_id, t_node_info *node_info); RecordStatus refresh_node_record(PGconn *conn, int node_id, t_node_info *node_info); RecordStatus get_node_record_with_upstream(PGconn *conn, int node_id, t_node_info *node_info); RecordStatus get_node_record_by_name(PGconn *conn, const char *node_name, t_node_info *node_info); t_node_info *get_node_record_pointer(PGconn *conn, int node_id); bool get_local_node_record(PGconn *conn, int node_id, t_node_info *node_info); bool get_primary_node_record(PGconn *conn, t_node_info *node_info); bool get_all_node_records(PGconn *conn, NodeInfoList *node_list); bool get_all_nodes_count(PGconn *conn, int *count); void get_downstream_node_records(PGconn *conn, int node_id, NodeInfoList *nodes); void get_active_sibling_node_records(PGconn *conn, int node_id, int upstream_node_id, NodeInfoList *node_list); bool get_child_nodes(PGconn *conn, int node_id, NodeInfoList *node_list); void get_node_records_by_priority(PGconn *conn, NodeInfoList *node_list); bool get_all_node_records_with_upstream(PGconn *conn, NodeInfoList *node_list); bool get_downstream_nodes_with_missing_slot(PGconn *conn, int this_node_id, NodeInfoList *noede_list); bool create_node_record(PGconn *conn, char *repmgr_action, t_node_info *node_info); bool update_node_record(PGconn *conn, char *repmgr_action, t_node_info *node_info); bool delete_node_record(PGconn *conn, int node); bool truncate_node_records(PGconn *conn); bool update_node_record_set_active(PGconn *conn, int this_node_id, bool active); bool update_node_record_set_primary(PGconn *conn, int this_node_id); bool update_node_record_set_active_standby(PGconn *conn, int this_node_id); bool update_node_record_set_upstream(PGconn *conn, int this_node_id, int new_upstream_node_id); bool update_node_record_status(PGconn *conn, int this_node_id, char *type, int upstream_node_id, bool active); bool update_node_record_conn_priority(PGconn *conn, t_configuration_options *options); bool update_node_record_slot_name(PGconn *primary_conn, int node_id, char *slot_name); bool witness_copy_node_records(PGconn *primary_conn, PGconn *witness_conn); void clear_node_info_list(NodeInfoList *nodes); /* PostgreSQL configuration file location functions */ bool get_datadir_configuration_files(PGconn *conn, KeyValueList *list); bool get_configuration_file_locations(PGconn *conn, t_configfile_list *list); void config_file_list_init(t_configfile_list *list, int max_size); void config_file_list_add(t_configfile_list *list, const char *file, const char *filename, bool in_data_dir); /* event functions */ bool create_event_record(PGconn *conn, t_configuration_options *options, int node_id, char *event, bool successful, char *details); bool create_event_notification(PGconn *conn, t_configuration_options *options, int node_id, char *event, bool successful, char *details); bool create_event_notification_extended(PGconn *conn, t_configuration_options *options, int node_id, char *event, bool successful, char *details, t_event_info *event_info); PGresult *get_event_records(PGconn *conn, int node_id, const char *node_name, const char *event, bool all, int limit); /* replication slot functions */ void create_slot_name(char *slot_name, int node_id); bool create_replication_slot_sql(PGconn *conn, char *slot_name, PQExpBufferData *error_msg); bool create_replication_slot_replprot(PGconn *conn, PGconn *repl_conn, char *slot_name, PQExpBufferData *error_msg); bool drop_replication_slot_sql(PGconn *conn, char *slot_name); bool drop_replication_slot_replprot(PGconn *repl_conn, char *slot_name); RecordStatus get_slot_record(PGconn *conn, char *slot_name, t_replication_slot *record); int get_free_replication_slot_count(PGconn *conn, int *max_replication_slots); int get_inactive_replication_slots(PGconn *conn, KeyValueList *list); /* tablespace functions */ bool get_tablespace_name_by_location(PGconn *conn, const char *location, char *name); /* asynchronous query functions */ bool cancel_query(PGconn *conn, int timeout); int wait_connection_availability(PGconn *conn, int timeout); /* node availability functions */ bool is_server_available(const char *conninfo); bool is_server_available_quiet(const char *conninfo); bool is_server_available_params(t_conninfo_param_list *param_list); ExecStatusType connection_ping(PGconn *conn); ExecStatusType connection_ping_reconnect(PGconn *conn); /* monitoring functions */ void add_monitoring_record(PGconn *primary_conn, PGconn *local_conn, int primary_node_id, int local_node_id, char *monitor_standby_timestamp, XLogRecPtr primary_last_wal_location, XLogRecPtr last_wal_receive_lsn, char *last_xact_replay_timestamp, long long unsigned int replication_lag_bytes, long long unsigned int apply_lag_bytes ); int get_number_of_monitoring_records_to_delete(PGconn *primary_conn, int keep_history, int node_id); bool delete_monitoring_records(PGconn *primary_conn, int keep_history, int node_id); /* node voting functions */ void initialize_voting_term(PGconn *conn); int get_current_term(PGconn *conn); void increment_current_term(PGconn *conn); bool announce_candidature(PGconn *conn, t_node_info *this_node, t_node_info *other_node, int electoral_term); void notify_follow_primary(PGconn *conn, int primary_node_id); bool get_new_primary(PGconn *conn, int *primary_node_id); void reset_voting_status(PGconn *conn); /* replication status functions */ XLogRecPtr get_primary_current_lsn(PGconn *conn); XLogRecPtr get_node_current_lsn(PGconn *conn); XLogRecPtr get_last_wal_receive_location(PGconn *conn); void init_replication_info(ReplInfo *replication_info); bool get_replication_info(PGconn *conn, t_server_type node_type, ReplInfo *replication_info); int get_replication_lag_seconds(PGconn *conn); TimeLineID get_node_timeline(PGconn *conn, char *timeline_id_str); void get_node_replication_stats(PGconn *conn, t_node_info *node_info); NodeAttached is_downstream_node_attached(PGconn *conn, char *node_name, char **node_state); void set_upstream_last_seen(PGconn *conn, int upstream_node_id); int get_upstream_last_seen(PGconn *conn, t_server_type node_type); bool is_wal_replay_paused(PGconn *conn, bool check_pending_wal); /* repmgrd status functions */ CheckStatus get_repmgrd_status(PGconn *conn); /* miscellaneous debugging functions */ const char *print_node_status(NodeStatus node_status); const char *print_pqping_status(PGPing ping_status); #endif /* _REPMGR_DBUTILS_H_ */ repmgr-5.3.1/dirutil.c000066400000000000000000000235721420262710000146370ustar00rootroot00000000000000/* * * dirmod.c * directory handling functions * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #include #include #include #include #include #include #include #include /* NB: postgres_fe must be included BEFORE check_dir */ #include #include #include "dirutil.h" #include "strutil.h" #include "log.h" #include "controldata.h" static int unlink_dir_callback(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf); /* PID can be negative if backend is standalone */ typedef long pgpid_t; /* * Check if a directory exists, and if so whether it is empty. * * This function is used for checking both the data directory * and tablespace directories. */ DataDirState check_dir(const char *path) { DIR *chkdir = NULL; struct dirent *file = NULL; int result = DIR_EMPTY; errno = 0; chkdir = opendir(path); if (!chkdir) return (errno == ENOENT) ? DIR_NOENT : DIR_ERROR; while ((file = readdir(chkdir)) != NULL) { if (strcmp(".", file->d_name) == 0 || strcmp("..", file->d_name) == 0) { /* skip this and parent directory */ continue; } else { result = DIR_NOT_EMPTY; break; } } closedir(chkdir); if (errno != 0) return DIR_ERROR; /* some kind of I/O error? */ return result; } /* * Create directory with error log message when failing */ bool create_dir(const char *path) { char create_dir_path[MAXPGPATH]; /* mkdir_p() may modify the supplied path */ strncpy(create_dir_path, path, MAXPGPATH); if (mkdir_p(create_dir_path, 0700) == 0) return true; log_error(_("unable to create directory \"%s\""), create_dir_path); log_detail("%s", strerror(errno)); return false; } bool set_dir_permissions(const char *path, int server_version_num) { struct stat stat_buf; bool no_group_access = (server_version_num != UNKNOWN_SERVER_VERSION_NUM) && (server_version_num < 110000); /* * At this point the path should exist, so this check is very * much just-in-case. */ if (stat(path, &stat_buf) != 0) { if (errno == ENOENT) { log_warning(_("directory \"%s\" does not exist"), path); } else { log_warning(_("could not read permissions of directory \"%s\""), path); log_detail("%s", strerror(errno)); } return false; } /* * If mode is not 0700 or 0750, attempt to change. */ if ((no_group_access == true && (stat_buf.st_mode & (S_IRWXG | S_IRWXO))) || (no_group_access == false && (stat_buf.st_mode & (S_IWGRP | S_IRWXO)))) { /* * Currently we default to 0700. * There is no facility to override this directly, * but the user can manually create the directory with * the desired permissions. */ if (chmod(path, 0700) != 0) { log_error(_("unable to change permissions of directory \"%s\""), path); log_detail("%s", strerror(errno)); return false; } return true; } /* Leave as-is */ return true; } /* function from initdb.c */ /* source adapted from FreeBSD /src/bin/mkdir/mkdir.c */ /* * this tries to build all the elements of a path to a directory a la mkdir -p * we assume the path is in canonical form, i.e. uses / as the separator * we also assume it isn't null. * * note that on failure, the path arg has been modified to show the particular * directory level we had problems with. */ int mkdir_p(char *path, mode_t omode) { struct stat sb; mode_t numask, oumask; int first, last, retval; char *p; p = path; oumask = 0; retval = 0; if (p[0] == '/') /* Skip leading '/'. */ ++p; for (first = 1, last = 0; !last; ++p) { if (p[0] == '\0') last = 1; else if (p[0] != '/') continue; *p = '\0'; if (!last && p[1] == '\0') last = 1; if (first) { /* * POSIX 1003.2: For each dir operand that does not name an * existing directory, effects equivalent to those caused by the * following command shall occur: * * mkdir -p -m $(umask -S),u+wx $(dirname dir) && mkdir [-m mode] * dir * * We change the user's umask and then restore it, instead of * doing chmod's. */ oumask = umask(0); numask = oumask & ~(S_IWUSR | S_IXUSR); (void) umask(numask); first = 0; } if (last) (void) umask(oumask); /* check for pre-existing directory; ok if it's a parent */ if (stat(path, &sb) == 0) { if (!S_ISDIR(sb.st_mode)) { if (last) errno = EEXIST; else errno = ENOTDIR; retval = 1; break; } } else if (mkdir(path, last ? omode : S_IRWXU | S_IRWXG | S_IRWXO) < 0) { retval = 1; break; } if (!last) *p = '/'; } if (!first && !last) (void) umask(oumask); return retval; } bool is_pg_dir(const char *path) { char dirpath[MAXPGPATH] = ""; struct stat sb; /* test pgdata */ snprintf(dirpath, MAXPGPATH, "%s/PG_VERSION", path); if (stat(dirpath, &sb) == 0) return true; /* TODO: sanity check other files */ return false; } /* * Attempt to determine if a PostgreSQL data directory is in use * by reading the pidfile. This is the same mechanism used by * "pg_ctl". * * This function will abort with appropriate log messages if a file error * is encountered, as the user will need to address the situation before * any further useful progress can be made. */ PgDirState is_pg_running(const char *path) { long pid; FILE *pidf; char pid_file[MAXPGPATH]; /* it's reasonable to assume the pidfile name will not change */ snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", path); pidf = fopen(pid_file, "r"); if (pidf == NULL) { /* * No PID file - PostgreSQL shouldn't be running. From 9.3 (the * earliest version we care about) removal of the PID file will * cause the postmaster to shut down, so it's highly unlikely * that PostgreSQL will still be running. */ if (errno == ENOENT) { return PG_DIR_NOT_RUNNING; } else { log_error(_("unable to open PostgreSQL PID file \"%s\""), pid_file); log_detail("%s", strerror(errno)); exit(ERR_BAD_CONFIG); } } /* * In the unlikely event we're unable to extract a PID from the PID file, * log a warning but assume we're not dealing with a running instance * as PostgreSQL should have shut itself down in these cases anyway. */ if (fscanf(pidf, "%ld", &pid) != 1) { /* Is the file empty? */ if (ftell(pidf) == 0 && feof(pidf)) { log_warning(_("PostgreSQL PID file \"%s\" is empty"), path); } else { log_warning(_("invalid data in PostgreSQL PID file \"%s\""), path); } fclose(pidf); return PG_DIR_NOT_RUNNING; } fclose(pidf); if (pid == getpid()) return PG_DIR_NOT_RUNNING; if (pid == getppid()) return PG_DIR_NOT_RUNNING; if (kill(pid, 0) == 0) return PG_DIR_RUNNING; return PG_DIR_NOT_RUNNING; } bool create_pg_dir(const char *path, bool force) { /* Check this directory can be used as a PGDATA dir */ switch (check_dir(path)) { case DIR_NOENT: /* Directory does not exist, attempt to create it. */ log_info(_("creating directory \"%s\"..."), path); if (!create_dir(path)) { log_error(_("unable to create directory \"%s\"..."), path); return false; } break; case DIR_EMPTY: /* * Directory exists but empty, fix permissions and use it. * * Note that at this point the caller might not know the server * version number, so in this case "set_dir_permissions()" will * accept 0750 as a valid setting. As this is invalid in Pg10 and * earlier, the caller should call "set_dir_permissions()" again * when it has the number. * * We need to do the permissions check here in any case to catch * fatal permissions early. */ log_info(_("checking and correcting permissions on existing directory \"%s\""), path); if (!set_dir_permissions(path, UNKNOWN_SERVER_VERSION_NUM)) { return false; } break; case DIR_NOT_EMPTY: /* exists but is not empty */ log_warning(_("directory \"%s\" exists but is not empty"), path); if (is_pg_dir(path)) { if (force == true) { log_notice(_("-F/--force provided - deleting existing data directory \"%s\""), path); nftw(path, unlink_dir_callback, 64, FTW_DEPTH | FTW_PHYS); /* recreate the directory ourselves to ensure permissions are correct */ if (!create_dir(path)) { log_error(_("unable to create directory \"%s\"..."), path); return false; } return true; } return false; } else { if (force == true) { log_notice(_("deleting existing directory \"%s\""), path); nftw(path, unlink_dir_callback, 64, FTW_DEPTH | FTW_PHYS); /* recreate the directory ourselves to ensure permissions are correct */ if (!create_dir(path)) { log_error(_("unable to create directory \"%s\"..."), path); return false; } return true; } return false; } break; case DIR_ERROR: log_error(_("could not access directory \"%s\"") , path); log_detail("%s", strerror(errno)); return false; } return true; } int rmdir_recursive(const char *path) { return nftw(path, unlink_dir_callback, 64, FTW_DEPTH | FTW_PHYS); } static int unlink_dir_callback(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { int rv = remove(fpath); if (rv) perror(fpath); return rv; } repmgr-5.3.1/dirutil.h000066400000000000000000000025131420262710000146340ustar00rootroot00000000000000/* * dirutil.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _DIRUTIL_H_ #define _DIRUTIL_H_ typedef enum { DIR_ERROR = -1, DIR_NOENT, DIR_EMPTY, DIR_NOT_EMPTY } DataDirState; typedef enum { PG_DIR_ERROR = -1, PG_DIR_NOT_RUNNING, PG_DIR_RUNNING } PgDirState; extern int mkdir_p(char *path, mode_t omode); extern bool set_dir_permissions(const char *path, int server_version_num); extern DataDirState check_dir(const char *path); extern bool create_dir(const char *path); extern bool is_pg_dir(const char *path); extern PgDirState is_pg_running(const char *path); extern bool create_pg_dir(const char *path, bool force); extern int rmdir_recursive(const char *path); #endif repmgr-5.3.1/doc/000077500000000000000000000000001420262710000135535ustar00rootroot00000000000000repmgr-5.3.1/doc/.gitignore000066400000000000000000000001241420262710000155400ustar00rootroot00000000000000HTML.index bookindex.xml html-stamp html/ repmgr.html version.xml *.fo *.pdf *.sgml repmgr-5.3.1/doc/Makefile000066400000000000000000000047221420262710000152200ustar00rootroot00000000000000# Make "html" the default target, since that is what most people tend # to want to use. html: all: html subdir = doc repmgr_top_builddir = .. include $(repmgr_top_builddir)/Makefile.global XMLINCLUDE = --path . ifndef XMLLINT XMLLINT = $(missing) xmllint endif ifndef XSLTPROC XSLTPROC = $(missing) xsltproc endif ifndef FOP FOP = $(missing) fop endif override XSLTPROCFLAGS += --stringparam repmgr.version '$(REPMGR_VERSION)' GENERATED_XML = version.xml ALLXML := $(wildcard $(srcdir)/*.xml) $(GENERATED_XML) version.xml: $(repmgr_top_builddir)/repmgr_version.h { \ echo ""; \ echo ""; \ } > $@ ## ## HTML ## html: html-stamp html-stamp: stylesheet.xsl repmgr.xml $(ALLXML) $(XMLLINT) $(XMLINCLUDE) --noout --valid $(word 2,$^) $(XSLTPROC) $(XMLINCLUDE) $(XSLTPROCFLAGS) $(XSLTPROC_HTML_FLAGS) $(wordlist 1,2,$^) cp $(srcdir)/stylesheet.css $(srcdir)/website-docs.css html/ touch $@ # single-page HTML repmgr.html: stylesheet-html-nochunk.xsl repmgr.xml $(ALLXML) $(XMLLINT) $(XMLINCLUDE) --noout --valid $(word 2,$^) $(XSLTPROC) $(XMLINCLUDE) $(XSLTPROCFLAGS) $(XSLTPROC_HTML_FLAGS) -o $@ $(wordlist 1,2,$^) zip: html cp -r html repmgr-docs-$(REPMGR_VERSION) zip -r repmgr-docs-$(REPMGR_VERSION).zip repmgr-docs-$(REPMGR_VERSION) rm -rf repmgr-docs-$(REPMGR_VERSION) ## ## Print ## repmgr.pdf: $(error Invalid target; use repmgr-A4.pdf or repmgr-US.pdf as targets) # Standard paper size repmgr-A4.fo: stylesheet-fo.xsl repmgr.xml $(ALLXML) $(XMLLINT) $(XMLINCLUDE) --noout --valid $(word 2,$^) $(XSLTPROC) $(XMLINCLUDE) $(XSLTPROCFLAGS) --stringparam paper.type A4 -o $@ $(wordlist 1,2,$^) repmgr-A4.pdf: repmgr-A4.fo $(FOP) -fo $< -pdf $@ # North American paper size repmgr-US.fo: stylesheet-fo.xsl repmgr.xml $(ALLXML) $(XMLLINT) $(XMLINCLUDE) --noout --valid $(word 2,$^) $(XSLTPROC) $(XMLINCLUDE) $(XSLTPROCFLAGS) --stringparam paper.type USletter -o $@ $(wordlist 1,2,$^) repmgr-US.pdf: repmgr-US.fo $(FOP) -fo $< -pdf $@ install: html @$(MKDIR_P) $(DESTDIR)$(docdir)/$(docmoduledir)/repmgr @$(INSTALL_DATA) $(wildcard html/*.html) $(wildcard html/*.css) $(DESTDIR)$(docdir)/$(docmoduledir)/repmgr @echo Installed docs to $(DESTDIR)$(docdir)/$(docmoduledir)/repmgr clean: rm -f html-stamp rm -f HTML.index $(GENERATED_XML) rm -f repmgr.html rm -f repmgr-A4.pdf rm -f repmgr-US.pdf rm -f *.fo rm -f html/* maintainer-clean: rm -rf html .PHONY: html repmgr-5.3.1/doc/appendix-faq.xml000066400000000000000000000570031420262710000166570ustar00rootroot00000000000000 FAQ (Frequently Asked Questions) FAQ (Frequently Asked Questions) General What's the difference between the repmgr versions? &repmgr; 4 is a complete rewrite of the previous &repmgr; code base and implements &repmgr; as a PostgreSQL extension. It supports all PostgreSQL versions from 9.3 (although some &repmgr; features are not available for PostgreSQL 9.3 and 9.4). &repmgr; 5 is fundamentally the same code base as &repmgr; 4, but provides support for the revised replication configuration mechanism in PostgreSQL 12. Support for PostgreSQL 9.3 is no longer available from &repmgr; 5.2. &repmgr; 3.x builds on the improved replication facilities added in PostgreSQL 9.3, as well as improved automated failover support via &repmgrd;, and is not compatible with PostgreSQL 9.2 and earlier. We recommend upgrading to &repmgr; 4, as the &repmgr; 3.x series is no longer maintained. &repmgr; 2.x supports PostgreSQL 9.0 ~ 9.3. While it is compatible with PostgreSQL 9.3, we recommend using repmgr 4.x. &repmgr; 2.x is no longer maintained. See also &repmgr; compatibility matrix and Should I upgrade &repmgr;?. What's the advantage of using replication slots? Replication slots, introduced in PostgreSQL 9.4, ensure that the primary server will retain WAL files until they have been consumed by all standby servers. This means standby servers should never fail due to not being able to retrieve required WAL files from the primary. However this does mean that if a standby is no longer connected to the primary, the presence of the replication slot will cause WAL files to be retained indefinitely, and eventually lead to disk space exhaustion. Our recommended configuration is to configure Barman as a fallback source of WAL files, rather than maintain replication slots for each standby. See also: Using Barman as a WAL file source. How many replication slots should I define in <varname>max_replication_slots</varname>? Normally at least same number as the number of standbys which will connect to the node. Note that changes to max_replication_slots require a server restart to take effect, and as there is no particular penalty for unused replication slots, setting a higher figure will make adding new nodes easier. Does &repmgr; support hash indexes? Before PostgreSQL 10, hash indexes were not WAL logged and are therefore not suitable for use in streaming replication in PostgreSQL 9.6 and earlier. See the PostgreSQL documentation for details. From PostgreSQL 10, this restriction has been lifted and hash indexes can be used in a streaming replication cluster. Can &repmgr; assist with upgrading a PostgreSQL cluster? For minor version upgrades, e.g. from 9.6.7 to 9.6.8, a common approach is to upgrade a standby to the latest version, perform a switchover promoting it to a primary, then upgrade the former primary. For major version upgrades (e.g. from PostgreSQL 9.6 to PostgreSQL 10), the traditional approach is to "reseed" a cluster by upgrading a single node with pg_upgrade and recloning standbys from this. To minimize downtime during major upgrades from PostgreSQL 9.4 and later, pglogical can be used to set up a parallel cluster using the newer PostgreSQL version, which can be kept in sync with the existing production cluster until the new cluster is ready to be put into production. What does this error mean: <literal>ERROR: could not access file "$libdir/repmgr"</literal>? It means the &repmgr; extension code is not installed in the PostgreSQL application directory. This typically happens when using PostgreSQL packages provided by a third-party vendor, which often have different filesystem layouts. Either use PostgreSQL packages provided by the community or EnterpriseDB; if this is not possible, contact your vendor for assistance. How can I obtain old versions of &repmgr; packages? See appendix for details. Is &repmgr; required for streaming replication? No. &repmgr; (together with &repmgrd;) assists with managing replication. It does not actually perform replication, which is part of the core PostgreSQL functionality. Will replication stop working if &repmgr; is uninstalled? No. See preceding question. Does it matter if different &repmgr; versions are present in the replication cluster? Yes. If different "major" &repmgr; versions (e.g. 3.3.x and 4.1.x) are present, &repmgr; (in particular &repmgrd;) may not run, or run properly, or in the worst case (if different &repmgrd; versions are running and there are differences in the failover implementation) break your replication cluster. If different "minor" &repmgr; versions (e.g. 4.1.1 and 4.1.6) are installed, &repmgr; will function, but we strongly recommend always running the same version to ensure there are no unexpected surprises, e.g. a newer version behaving slightly differently to the older version. See also Should I upgrade &repmgr;?. Should I upgrade &repmgr;? Yes. We don't release new versions for fun, you know. Upgrading may require a little effort, but running an older &repmgr; version with bugs which have since been fixed may end up costing you more effort. The same applies to PostgreSQL itself. Why do I need to specify the data directory location in repmgr.conf? In some circumstances &repmgr; may need to access a PostgreSQL data directory while the PostgreSQL server is not running, e.g. to confirm it shut down cleanly during a switchover. Additionally, this provides support when using &repmgr; on PostgreSQL 9.6 and earlier, where the repmgr user is not a superuser; in that case the repmgr user will not be able to access the data_directory configuration setting, access to which is restricted to superusers. In PostgreSQL 10 and later, non-superusers can be added to the default role (or the meta-role ) which will enable them to read this setting. Are &repmgr; packages compatible with <literal>$third_party_vendor</literal>'s packages? &repmgr; packages provided by EnterpriseDB are compatible with the community-provided PostgreSQL packages and specified software provided by EnterpriseDB. A number of other vendors provide their own versions of PostgreSQL packages, often with different package naming schemes and/or file locations. We cannot guarantee that &repmgr; packages will be compatible with these packages. It may be possible to override package dependencies (e.g. rpm --nodeps for CentOS-based systems or dpkg --force-depends for Debian-based systems). <command>repmgr</command> Can I register an existing PostgreSQL server with repmgr? Yes, any existing PostgreSQL server which is part of the same replication cluster can be registered with &repmgr;. There's no requirement for a standby to have been cloned using &repmgr;. Can I use a standby not cloned by &repmgr; as a &repmgr; node? For a standby which has been manually cloned or recovered from an external backup manager such as Barman, the command repmgr standby clone --replication-conf-only can be used to create the correct recovery.conf file for use with &repmgr; (and will create a replication slot if required). Once this has been done, register the node as usual. What does &repmgr; write in <filename>recovery.conf</filename>, and what options can be set there? See section Customising recovery.conf. How can a failed primary be re-added as a standby? This is a two-stage process. First, the failed primary's data directory must be re-synced with the current primary; secondly the failed primary needs to be re-registered as a standby. It's possible to use pg_rewind to re-synchronise the existing data directory, which will usually be much faster than re-cloning the server. However pg_rewind can only be used if PostgreSQL either has wal_log_hints enabled, or data checksums were enabled when the cluster was initialized. Note that pg_rewind is available as part of the core PostgreSQL distribution from PostgreSQL 9.5, and as a third-party utility for PostgreSQL 9.3 and 9.4. &repmgr; provides the command repmgr node rejoin which can optionally execute pg_rewind; see the documentation for details, in particular the section . If pg_rewind cannot be used, then the data directory will need to be re-cloned from scratch. Is there an easy way to check my primary server is correctly configured for use with &repmgr;? Execute repmgr standby clone with the --dry-run option; this will report any configuration problems which need to be rectified. When cloning a standby, how can I get &repmgr; to copy <filename>postgresql.conf</filename> and <filename>pg_hba.conf</filename> from the PostgreSQL configuration directory in <filename>/etc</filename>? Use the command line option --copy-external-config-files. For more details see . Do I need to include <literal>shared_preload_libraries = 'repmgr'</literal> in <filename>postgresql.conf</filename> if I'm not using &repmgrd;? No, the repmgr shared library is only needed when running &repmgrd;. If you later decide to run &repmgrd;, you just need to add shared_preload_libraries = 'repmgr' and restart PostgreSQL. I've provided replication permission for the <literal>repmgr</literal> user in <filename>pg_hba.conf</filename> but <command>repmgr</command>/&repmgrd; complains it can't connect to the server... Why? repmgr and &repmgrd; need to be able to connect to the repmgr database with a normal connection to query metadata. The replication connection permission is for PostgreSQL's streaming replication (and doesn't necessarily need to be the repmgr user). When cloning a standby, why do I need to provide the connection parameters for the primary server on the command line, not in the configuration file? Cloning a standby is a one-time action; the role of the server being cloned from could change, so fixing it in the configuration file would create confusion. If &repmgr; needs to establish a connection to the primary server, it can retrieve this from the repmgr.nodes table on the local node, and if necessary scan the replication cluster until it locates the active primary. When cloning a standby, how do I ensure the WAL files are placed in a custom directory? Provide the option --waldir (--xlogdir in PostgreSQL 9.6 and earlier) with the absolute path to the WAL directory in pg_basebackup_options. For more details see . In &repmgr; 5.2 and later, this setting will also be honoured when cloning from Barman. Why is there no foreign key on the <literal>node_id</literal> column in the <literal>repmgr.events</literal> table? Under some circumstances event notifications can be generated for servers which have not yet been registered; it's also useful to retain a record of events which includes servers removed from the replication cluster which no longer have an entry in the repmgr.nodes table. Why are some values in <filename>recovery.conf</filename> surrounded by pairs of single quotes? This is to ensure that user-supplied values which are written as parameter values in recovery.conf are escaped correctly and do not cause errors when recovery.conf is parsed. The escaping is performed by an internal PostgreSQL routine, which leaves strings consisting of digits and alphabetical characters only as-is, but wraps everything else in pairs of single quotes, even if the string does not contain any characters which need escaping. How can I exclude &repmgr; metadata from <application>pg_dump</application> output? Beginning with &repmgr; 5.2, the metadata tables associated with the &repmgr; extension (repmgr.nodes, repmgr.events and repmgr.monitoring_history) have been marked as dumpable as they contain configuration and user-generated data. To exclude these from pg_dump output, add the flag . To exclude individual &repmgr; metadata tables from pg_dump output, add the flag e.g. . This flag can be provided multiple times to exclude individual tables, &repmgrd; How can I prevent a node from ever being promoted to primary? In repmgr.conf, set its priority to a value of 0; apply the changed setting with repmgr standby register --force. Additionally, if failover is set to manual, the node will never be considered as a promotion candidate. Does &repmgrd; support delayed standbys? &repmgrd; can monitor delayed standbys - those set up with recovery_min_apply_delay set to a non-zero value in recovery.conf - but as it's not currently possible to directly examine the value applied to the standby, &repmgrd; may not be able to properly evaluate the node as a promotion candidate. We recommend that delayed standbys are explicitly excluded from promotion by setting priority to 0 in repmgr.conf. Note that after registering a delayed standby, &repmgrd; will only start once the metadata added in the primary node has been replicated. How can I get &repmgrd; to rotate its logfile? Configure your system's logrotate service to do this; see . I've recloned a failed primary 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. &repmgrd; ignores pg_bindir when executing <varname>promote_command</varname> or <varname>follow_command</varname> promote_command or follow_command can be user-defined scripts, so &repmgr; will not apply even if executing &repmgr;. Always provide the full path; see for more details. &repmgrd; aborts startup with the error "<literal>upstream node must be running before repmgrd can start</literal>" &repmgrd; does this to avoid starting up on a replication cluster which is not in a healthy state. If the upstream is unavailable, &repmgrd; may initiate a failover immediately after starting up, which could have unintended side-effects, particularly if &repmgrd; is not running on other nodes. In particular, it's possible that the node's local copy of the repmgr.nodes copy is out-of-date, which may lead to incorrect failover behaviour. The onus is therefore on the administrator to manually set the cluster to a stable, healthy state before starting &repmgrd;. repmgr-5.3.1/doc/appendix-packages.xml000066400000000000000000000463361420262710000176750ustar00rootroot00000000000000 &repmgr; package details packages This section provides technical details about various &repmgr; binary packages, such as location of the installed binaries and configuration files. CentOS Packages packages CentOS packages CentOS package information Currently, &repmgr; RPM packages are provided for versions 6.x and 7.x of CentOS. These should also work on matching versions of Red Hat Enterprise Linux, Scientific Linux and Oracle Enterprise Linux; together with CentOS, these are the same RedHat-based distributions for which the main community project (PGDG) provides packages (see the PostgreSQL RPM Building Project page for details). Note these &repmgr; RPM packages are not designed to work with SuSE/OpenSuSE. &repmgr; packages are designed to be compatible with community-provided PostgreSQL packages. They may not work with vendor-specific packages such as those provided by RedHat for RHEL customers, as the filesystem layout may be different to the community RPMs. Please contact your support vendor for assistance. CentOS repositories &repmgr; packages are available from the public 2ndQuadrant repository, and also the PostgreSQL community repository. The 2ndQuadrant repository is updated immediately after each &repmgr; release. 2ndQuadrant public repository Repository URL: https://dl.2ndquadrant.com/ Repository documentation: https://repmgr.org/docs/current/installation-packages.html#INSTALLATION-PACKAGES-REDHAT-2NDQ
PostgreSQL community repository (PGDG) Repository URL: https://yum.postgresql.org/repopackages.php Repository documentation: https://yum.postgresql.org/
CentOS package details The two tables below list relevant information, paths, commands etc. for the &repmgr; packages on CentOS 7 (with systemd) and CentOS 6 (no systemd). Substitute the appropriate PostgreSQL major version number for your installation. For PostgreSQL 9.6 and lower, the CentOS packages use a mixture of 9.6 and 96 in various places to designate the major version; e.g. the package name is repmgr96, but the binary directory is /var/lib/pgsql/9.6/data. From PostgreSQL 10, the first part of the version number (e.g. 10) is the major version, so there is more consistency in file/path/package naming (package repmgr10, binary directory /var/lib/pgsql/10/data). CentOS 7 packages Package name example: repmgr11-4.4.0-1.rhel7.x86_64 Metapackage: (none) Installation command: yum install repmgr11 Binary location: /usr/pgsql-11/bin repmgr in default path: NO Configuration file location: /etc/repmgr/11/repmgr.conf Data directory: /var/lib/pgsql/11/data repmgrd service command: systemctl [start|stop|restart|reload] repmgr11 repmgrd service file location: /usr/lib/systemd/system/repmgr11.service repmgrd log file location: (not specified by package; set in repmgr.conf)
CentOS 6 packages Package name example: repmgr96-4.0.4-1.rhel6.x86_64 Metapackage: (none) Installation command: yum install repmgr96 Binary location: /usr/pgsql-9.6/bin repmgr in default path: NO Configuration file location: /etc/repmgr/9.6/repmgr.conf Data directory: /var/lib/pgsql/9.6/data repmgrd service command: service [start|stop|restart|reload] repmgr-9.6 repmgrd service file location: /etc/init.d/repmgr-9.6 repmgrd log file location: /var/log/repmgr/repmgrd-9.6.log
Debian/Ubuntu Packages packages Debian/Ubuntu packages Debian/Ubuntu package information &repmgr; .deb packages are provided by 2ndQuadrant as well as the PostgreSQL Community APT repository, and are available for each community-supported PostgreSQL version, currently supported Debian releases, and currently supported Ubuntu LTS releases. APT repositories 2ndQuadrant public repository Repository URL: https://dl.2ndquadrant.com/ Repository documentation: https://repmgr.org/docs/current/installation-packages.html#INSTALLATION-PACKAGES-DEBIAN
PostgreSQL Community APT repository (PGDG) Repository URL: https://apt.postgresql.org/ Repository documentation: https://wiki.postgresql.org/wiki/Apt
Debian/Ubuntu package details The table below lists relevant information, paths, commands etc. for the &repmgr; packages on Debian 9.x ("Stretch"). Substitute the appropriate PostgreSQL major version number for your installation. See also for some specifics related to configuring the &repmgrd; daemon. Debian 9.x packages Package name example: postgresql-11-repmgr Metapackage: repmgr-common Installation command: apt-get install postgresql-11-repmgr Binary location: /usr/lib/postgresql/11/bin repmgr in default path: Yes (via wrapper script /usr/bin/repmgr) Configuration file location: (not set by package) Data directory: /var/lib/postgresql/11/main PostgreSQL service command: systemctl [start|stop|restart|reload] postgresql@11-main repmgrd service command: systemctl [start|stop|restart|reload] repmgrd repmgrd service file location: /etc/init.d/repmgrd (defaults in: /etc/defaults/repmgrd) repmgrd log file location: (not specified by package; set in repmgr.conf)
When using Debian packages, instead of using the systemd service command directly, it's recommended to execute pg_ctlcluster (as root, either directly or via sudo), e.g.: pg_ctlcluster 11 main [start|stop|restart|reload] For pre-systemd systems, pg_ctlcluster can be executed directly by the postgres user.
Snapshot packages snapshot packages packages snapshots For testing new features and bug fixes, from time to time 2ndQuadrant provides so-called "snapshot packages" via its public repository. These packages are built from the &repmgr; source at a particular point in time, and are not formal releases. We do not recommend installing these packages in a production environment unless specifically advised. To install a snapshot package, it's necessary to install the 2ndQuadrant public snapshot repository, following the instructions here: https://dl.2ndquadrant.com/default/release/site/ but replace release with snapshot in the appropriate URL. For example, to install the snapshot RPM repository for PostgreSQL 9.6, execute (as root): curl https://dl.2ndquadrant.com/default/snapshot/get/9.6/rpm | bash or as a normal user with root sudo access: curl https://dl.2ndquadrant.com/default/snapshot/get/9.6/rpm | sudo bash Alternatively you can browse the repository here: https://dl.2ndquadrant.com/default/snapshot/browse/. Once the repository is installed, installing or updating &repmgr; will result in the latest snapshot package being installed. The package name will be formatted like this: repmgr96-4.1.1-0.0git320.g5113ab0.1.el7.x86_64.rpm containing the snapshot build number (here: 320) and the hash of the git commit it was built from (here: g5113ab0). Note that the next formal release (in the above example 4.1.1), once available, will install in place of any snapshot builds. Installing old package versions old packages packages old versions installation old package versions Debian/Ubuntu An archive of old packages (3.3.2 and later) for Debian/Ubuntu-based systems is available here: https://apt-archive.postgresql.org/ RHEL/CentOS Old versions can be located with e.g.: yum --showduplicates list repmgr96 (substitute the appropriate package name; see ) and installed with: yum install {package_name}-{version} where {package_name} is the base package name (e.g. repmgr96) and {version} is the version listed by the yum --showduplicates list ... command, e.g. 4.0.6-1.rhel6. For example: yum install repmgr96-4.0.6-1.rhel6 repmgr 3 packages Old &repmgr; 3 RPM packages (3.2 and later) can be retrieved from the (deprecated) 2ndQuadrant repository at http://packages.2ndquadrant.com/repmgr/yum/ by installing the appropriate repository RPM: http://packages.2ndquadrant.com/repmgr/yum-repo-rpms/repmgr-fedora-1.0-1.noarch.rpm http://packages.2ndquadrant.com/repmgr/yum-repo-rpms/repmgr-rhel-1.0-1.noarch.rpm Information for packagers packages information for packagers We recommend patching the following parameters when building the package as built-in default values for user convenience. These values can nevertheless be overridden by the user, if desired. Configuration file location: the default configuration file location can be hard-coded by patching package_conf_file in configfile.c: /* packagers: if feasible, patch configuration file path into "package_conf_file" */ char package_conf_file[MAXPGPATH] = ""; See also: PID file location: the default &repmgrd; PID file location can be hard-coded by patching package_pid_file in repmgrd.c: /* packagers: if feasible, patch PID file path into "package_pid_file" */ char package_pid_file[MAXPGPATH] = ""; See also:
repmgr-5.3.1/doc/appendix-release-notes.xml000066400000000000000000003246531420262710000206660ustar00rootroot00000000000000 Release notes Release notes Changes to each &repmgr; release are documented in the release notes. Please read the release notes for all versions between your current version and the version you are plan to upgrade to before performing an upgrade, as there may be version-specific upgrade steps. See also: Release 5.3.1 Tue 15 February, 2022 &repmgr; 5.3.1 is a minor release. Bug fixes Fix upgrade path from &repmgr; 4.2 and 4.3 to &repmgr; 5.3. &repmgrd;: ensure potentially open connections are closed. In some cases, when recovering from degraded state in local node monitoring, new connection was opened to the local node without closing the old one, which will result in memory leakage. Release 5.3.0 Tue 12 October, 2021 &repmgr; 5.3.0 is a major release. This release provides support for PostgreSQL 14, released in September 2021. Improvements repmgr standby switchover: Improve handling of node rejoin failure on the demotion candidate. Previously &repmgr; did not check whether repmgr node rejoin actually succeeded on the demotion candidate, and would always wait up to node_rejoin_timeout seconds for it to attach to the promotion candidate, even if this would never happen. This makes it easier to identify unexpected events during a switchover operation, such as the demotion candidate being unexpectedly restarted by an external process. Note that the output of the repmgr node rejoin operation on the demotion candidate will now be logged to a temporary file on that node; the location of the file will be reported in the error message, if one is emitted. &repmgrd;: at startup, if node record is marked as "inactive", attempt to set it to "active". This behaviour can be overridden by setting the configuration parameter repmgrd_exit_on_inactive_node to true. repmgr node rejoin: emit rejoin target note information as NOTICE. This makes it clearer what &repmgr; is trying to do. repmgr node check: option added to check &repmgrd; status. Add %p event notification parameter providing the node ID of the former primary for the repmgrd_failover_promote event. Bug fixes repmgr standby clone: if using on a node which was set up without replication slots, but the &repmgr; configuration was since changed to , &repmgr; will now set slot_name in the node record, if it was previously empty. &repmgrd;: rename internal shared library functions to minimize the risk of clashes with other shared libraries. This does not affect user-facing SQL functions. However an upgrade of the installed extension version is required. &repmgrd;: ensure short option is accepted. Release 5.2.1 Mon 7 December, 2020 &repmgr; 5.2.1 is a minor release. Improvements repmgr standby clone: option added, overriding any setting present in repmgr.conf. Bug fixes Configuration: fix parsing of configuration parameter. GitHub #672. repmgr standby clone: handle case where postgresql.auto.conf is absent on the source node. repmgr standby clone: in PostgreSQL 11 and later, an existing data directory's permissions will not be changed to if they are already set to . &repmgrd;: prevent termination when local node not available and is set. GitHub #675. &repmgrd;: ensure is correctly handled. GitHub #673. repmgr witness --help: fix witness unregister description. GitHub #676. Release 5.2.0 Thu 22 October, 2020 &repmgr; 5.2.0 is a major release. This release provides support for PostgreSQL 13, released in September 2020. This release removes support for PostgreSQL 9.3, which was designated EOL in November 2018. General improvements Configuration: support include, include_dir and include_if_exists directives (see ). repmgr standby switchover: Improve sanity check failure log output from the demotion candidate. If database connection configuration is not consistent across all nodes, it's possible remote &repmgr; invocations (e.g. during switchover, from the promotion candidate to the demotion candidate) will not be able to connect to the database. This will now be explicitly reported as a database connection failure, rather than as a failure of the respective sanity check. repmgr cluster crosscheck / repmgr cluster matrix: improve text mode output format, in particular so that node identifiers of arbitrary length are displayed correctly. repmgr primary unregister: the can be provided to unregister an active primary node, provided it has no registered standby nodes. repmgr standby clone: new option to run PostgreSQL's pg_verifybackup utility after cloning a standby to verify the integrity of the copied data (PostgreSQL 13 and later). repmgr standby clone: when cloning from Barman, setting (PostgreSQL 9.6 and earlier: ) in will cause &repmgr; to create a WAL directory outside of the main data directory and symlink it from there, in the same way as would happen when cloning using pg_basebackup. repmgr standby follow: In PostgreSQL 13 and later, a standby no longer requires a restart to follow a new upstream node. The old behaviour (always restarting the standby to follow a new node) can be restored by setting the configuration file parameter standby_follow_restart to true. repmgr node rejoin: enable a node to attach to a target node even the target node has a lower timeline (PostgreSQL 9.6 and later). repmgr node rejoin: in PostgreSQL 13 and later, support pg_rewind's ability to automatically run crash recovery on a PostgreSQL instance which was not shut down cleanly. repmgr node check: option added to check if &repmgr; can connect to the database on the local node. repmgr node check: report database connection error if the was provided. Improve handling of pg_control read errors. It is now possible to dump the contents of &repmgr; metadata tables with pg_dump. repmgrd enhancements Following additional parameters can be provided to failover_validation_command: %n: node ID %a: node name %v: number of visible nodes %u: number of shared upstream nodes %t: total number of nodes Configuration option always_promote (default: false) to control whether a node should be promoted if the &repmgr; metadata is not up-to-date on that node. Bug fixes repmgr standby clone: fix issue with cloning from Barman where the tablespace mapping file was not flushed to disk before attempting to retrieve files from Barman. GitHub #650. repmgr node rejoin: ensure that when verifying a standby node has attached to its upstream, the node has started streaming before confirming the success of the rejoin operation. &repmgrd;: ensure primary connection is reset if same as upstream. GitHub #633. Release 5.1.0 Mon 13 April, 2020 &repmgr; 5.1.0 is a major release. For details on how to upgrade an existing &repmgr; installation, see documentation section Upgrading a major version release. If &repmgrd; is in use, a PostgreSQL restart is required; in that case we suggest combining this &repmgr; upgrade with the next PostgreSQL minor release, which will require a PostgreSQL restart in any case. Compatibility changes The repmgr standby clone option has been renamed to . will still be accepted as an alias. General improvements The requirement that the &repmgr; user is a database superuser has been removed as far as possible. In theory, &repmgr; can be operated with a normal database user for managing the &repmgr; database, and a separate replication user for managing replication connections (and replication slots, if these are in use). Some operations will still require superuser permissions, e.g. for issuing a CHECKPOINT as par of a switchover operation; in this case a valid superuser should be provided with the / option. repmgr standby clone: Warn if neither of data page checksums or are active, as this will preclude later usage of pg_rewind. repmgr standby promote: when executed with , the method which would be used to promote the node will be displayed. repmgr standby follow: Improve logging and checking of potential failure situations. repmgr standby switchover: Replication configuration files (PostgreSQL 11 and earlier: recovery.conf; PostgreSQL 12 and later: postgresql.auto.conf) will be checked to ensure they are owned by the same user who owns the PostgreSQL data directory. repmgr standby switchover: Provide additional information in output. repmgr standby switchover: Checks that the demotion candidate's registered repmgr.conf file can be found, to prevent confusing references to an incorrectly configured data directory. GitHub 615. repmgr node check: accept option /. GitHub #621. repmgr node check: add option to check whether the node is attached to the expected upstream node. Bug fixes Ensure repmgr node rejoin checks for available replication slots on the rejoin target. repmgr standby follow and repmgr node rejoin will now return an error code if the operation fails if a replication slot is not available or cannot be created on the follow/rejoin target. repmgr standby promote: in , display promote command which will be executed. repmgr standby promote will check if the repmgr user has permission to execute pg_promote() and fall back to pg_ctl promote if necessary. repmgr standby switchover: check for demotion candidate reattachment as late as possible to avoid spurious failure reports. &repmgrd;: check for presence of and on receipt of SIGHUP. GitHub 614. Fix situation where replication connections were not created correctly, which could lead to spurious replication connection failures in some situations, e.g. where password files are used. Ensure postgresql.auto.conf is created with correct permissions (PostgreSQL 12 and later). Release 5.0 Tue 15 October, 2019 &repmgr; 5.0 is a major release. For details on how to upgrade an existing &repmgr; installation, see documentation section Upgrading a major version release. If &repmgrd; is in use, a PostgreSQL restart is required; in that case we suggest combining this &repmgr; upgrade with the next PostgreSQL minor release, which will require a PostgreSQL restart in any case. Compatibility changes Configuration file parsing has been made stricter &repmgr; now parses configuration files in the same way that PostgreSQL itself does, which means some files used with earlier &repmgr; versions may need slight modification before they can be used with &repmgr; 5 and later. The main change is that string parameters should always be enclosed in single quotes. For example, in &repmgr; 4.4 and earlier, the following repmgr.conf entry was valid: conninfo=host=node1 user=repmgr dbname=repmgr connect_timeout=2 This must now be changed to: conninfo='host=node1 user=repmgr dbname=repmgr connect_timeout=2' Note that simple string identifiers (e.g. node_name=node1) may remain unquoted, though we recommend always enclosing strings in single quotes. Additionally, leading/trailing white space between single quotes will no longer be trimmed; the entire string between single quotes will be taken literally. Strings enclosed in double quotes (e.g. node_name="node1") will now be rejected; previously they were accepted, but the double quotes were interpreted as part of the string, which was a frequent cause of confusion. This syntax matches that used by PostgreSQL itself. Some "repmgr daemon ..." commands renamed Some "repmgr daemon ..." commands have been renamed to "repmgr service ..." as they have a cluster-wide effect and to avoid giving the impression they affect only the local &repmgr; daemon. The following commands are affected: repmgr daemon pause (now repmgr service pause) repmgr daemon unpause (now repmgr service unpause) repmgr daemon status (now repmgr service status) The "repmgr daemon ..." form will still be accepted for backwards compatibility. Some deprecated command line options removed The following command line options, which have been deprecated since &repmgr; 3.3 (and which no longer had any effect other than to generate a warning about their use) have been removed: General enhancements Support for PostgreSQL 12 added. Beginning with PostgreSQL 12, replication configuration has been integrated into the main PostgreSQL configuraton system and the conventional recovery.conf file is no longer valid. &repmgr; has been modified to be compatible with this change. &repmgr; additionally takes advantage of the new pg_promote() function, which enables a standby to be promoted to primary using an SQL command. For an overview of general changes to replication configuration, see this blog entry: Replication configuration changes in PostgreSQL 12 The &repmgr; configuration file is now parsed using flex, meaning it will be parsed in the same way as PostgreSQL parses its own configuration files. This makes configuration file parsing more robust and consistent. See item Configuration file parsing has been made stricter for details. repmgr standby clone: checks for availability of the &repmgr; extension on the upstream node have been improved and error messages improved. When executing &repmgr; remotely, if the &repmgr; log level was explicitly provided (with /), that log level will be passed to the remote &repmgr;. This makes it possible to return log output when executing repmgr remotely at a different level to the one defined in the remote &repmgr;'s repmgr.conf. This is particularly useful when DEBUG output is required. Bug fixes Check role membership when trying to read pg_settings. Previously &repmgr; assumed only superusers could read pg_settings, but from PostgreSQL 10, all members of the roles pg_read_all_settings or pg_monitor are permitted to do this as well. &repmgrd;: Fix handling of upstream node change check. &repmgrd; has a check to see if the upstream node has unexpectedly changed, e.g. if the repmgrd service is paused and the PostgreSQL instance has been pointed to another node. However this check was relying on the node record on the local node being up-to-date, which may not be the case immediately after a failover, when the node is still replaying records updated prior to the node's own record being updated. In this case it will mistakenly assume the node is following the original primary and attempt to restart monitoring, which will fail as the original primary is no longer available. To prevent this, the node's record on the upstream node is checked to see if the reported upstream node_id matches the expected node_id. GitHub #587/#588. Release 4.4 Thu 27 June, 2019 &repmgr; 4.4 is a major release. For details on how to upgrade an existing &repmgr; installation, see documentation section Upgrading a major version release. If &repmgrd; is in use, a PostgreSQL restart is required; in that case we suggest combining this &repmgr; upgrade with the next PostgreSQL minor release, which will require a PostgreSQL restart in any case. On Debian-based systems, including Ubuntu, if using &repmgrd; please ensure that in the file /etc/init.d/repmgrd, the parameter REPMGRD_OPTS contains "--daemonize=false", e.g.: # additional options REPMGRD_OPTS="--daemonize=false" For further details, see repmgrd configuration on Debian/Ubuntu. repmgr client enhancements repmgr standby clone: prevent a standby from being cloned from a witness server (PostgreSQL 9.6 and later only). repmgr witness register: prevent a witness server from being registered on the replication cluster primary server (PostgreSQL 9.6 and later only). Registering a witness on the primary node would defeat the purpose of having a witness server, which is intended to remain running even if the cluster's primary goes down. repmgr standby follow: note that an active, reachable cluster primary is required for this command; and provide a more helpful error message if no reachable primary could be found. &repmgr;: when executing repmgr standby switchover, if is not supplied, list all nodes which repmgr considers to be siblings (this will include the witness server, if in use), and which will remain attached to the old primary. &repmgr;: when executing repmgr standby switchover, ignore nodes which are unreachable and marked as inactive. Previously it would abort if any node was unreachable, as that means it was unable to check if repmgrd is running. However if the node has been marked as inactive in the repmgr metadata, it's reasonable to assume the node is no longer part of the replication cluster and does not need to be checked. repmgr standby switchover and repmgr standby promote: when executing with the option, continue checks as far as possible even if errors are encountered. repmgr standby promote: add (similar to repmgr standby switchover). If using &repmgrd;, when invoking repmgr standby promote (either directly via the , or in a script called via ), must not be included as a command line option for repmgr standby promote. repmgr standby switchover: add to unpause all &repmgrd; instances after executing a switchover. This will ensure that any &repmgrd; instances which were paused before the switchover will be unpaused. repmgr daemon status: make output similar to that of repmgr cluster show for consistency and to make it easier to identify nodes not in the expected state. repmgr cluster show: display each node's timeline ID (PostgreSQL 9.6 and later only). repmgr cluster show and repmgr daemon status: show the upstream node name as reported by each individual node - this helps visualise situations where the cluster is in an unexpected state, and provide a better idea of the actual cluster state. For example, if a cluster has divided somehow and a set of nodes are following a new primary, when running either of these commands, &repmgr; will now show the name of the primary those nodes are actually following, rather than the now outdated node name recorded on the other side of the "split". A warning will also be issued about the unexpected situation. repmgr cluster show and repmgr daemon status: check if a node is attached to its advertised upstream node, and issue a warning if the node is not attached. repmgrd enhancements On the primary node, &repmgrd; is now able to monitor standby connections and, if the number of nodes connected falls below a certain (configurable) value, execute a custom script. This provided an additional method for fencing an isolated primary node, and/or taking other action if one or more standys become disconnected. See section Monitoring standby disconnections on the primary node for more details. In a failover situation, &repmgrd; nodes on the standbys of the failed primary are now able confirm among themselves that none can still see the primary before continuing with the failover. The repmgr.conf option must be set to true to enable this functionality. See section for more details. Bug fixes Ensure BDR2-specific functionality cannot be used on BDR3 and later. The BDR support present in &repmgr; is for specific BDR2 use cases. &repmgr;: when executing repmgr standby clone in mode, ensure provision of the option does not result in an existing data directory being modified in any way. &repmgr;: when executing repmgr primary register with the option, if another primary record exists but the associated node is unreachable (or running as a standby), set that node's record to inactive to enable the current node to be registered as a primary. &repmgr;: when executing repmgr standby clone with the , ensure that application_name is set correctly in primary_conninfo. &repmgr;: when executing repmgr standby switchover, don't abort if one or more nodes are not reachable and they are marked as inactive. &repmgr;: canonicalize the data directory path when parsing the configuration file, so the provided path matches the path PostgreSQL reports as its data directory. Otherwise, if e.g. the data directory is configured with a trailing slash, repmgr node check --data-directory-config will return a spurious error. &repmgrd;: fix memory leak which occurs while the monitored PostgreSQL node is not running. Other The &repmgr; documentation has been converted to DocBook XML format, as currently used by the main PostgreSQL project. This means it can now be built against any PostgreSQL version from 9.5 (previously it was not possible to build the documentation against PostgreSQL 10 or later), and makes it easier to provide the documentation in other formats such as PDF. For further details see: Release 4.3 Tue April 2, 2019 &repmgr; 4.3 is a major release. For details on how to upgrade an existing &repmgr; installation, see documentation section Upgrading a major version release. If &repmgrd; is in use, a PostgreSQL restart is required; in that case we suggest combining this &repmgr; upgrade with the next PostgreSQL minor release, which will require a PostgreSQL restart in any case. On Debian-based systems, including Ubuntu, if using &repmgrd; please ensure that in the file /etc/init.d/repmgrd, the parameter REPMGRD_OPTS contains "--daemonize=false", e.g.: # additional options REPMGRD_OPTS="--daemonize=false" For further details, see repmgrd configuration on Debian/Ubuntu. repmgr client enhancements repmgr standby follow: option can now be used to specify another standby to follow. repmgr standby follow: verify that it is actually possible to follow another node. repmgr node rejoin: verify that it is actually possible to attach the node to the current primary. New commands repmgr daemon start and repmgr daemon stop: these provide a standardized way of starting and stopping &repmgrd;. GitHub #528. These commands require the configuration file settings repmgrd_service_start_command and repmgrd_service_stop_command in repmgr.conf to be set. repmgr daemon status additionally displays the node priority and the interval (in seconds) since the &repmgrd; instance last verified its upstream node was available. Add option to repmgr cluster show (GitHub #521). This makes it easier to copy the output into emails, chats etc. as a compact table. repmgr cluster show: differentiate between unreachable nodes and nodes which are running but rejecting connections. This makes it possible to see whether a node is unreachable at network level, or if it is running but rejecting connections for some reason. Add to repmgr standby promote (GitHub #522). repmgr --version-number outputs the "raw" repmgr version number (e.g. 40300). This is intended for use by scripts etc. requiring an easily parseable representation of the &repmgr; version. repmgr node check --data-directory-config option added; this is to confirm &repmgr; is correctly configured. GitHub #523. Add check to repmgr standby switchover to ensure the data directory on the demotion candidate is configured correctly in repmgr.conf. This is to ensure that &repmgr;, when remotely executed on the demotion candidate, can correctly verify that PostgreSQL on the demotion candidate was shut down cleanly. GitHub #523. repmgrd enhancements &repmgrd; will no longer consider nodes where &repmgrd; is not running as promotion candidates. Previously, if &repmgrd; was not running on a node, but that node qualified as the promotion candidate, it would never be promoted due to the absence of a running &repmgrd;. Add option to enable selection of the method &repmgrd; uses to determine whether the upstream node is available. Possible values are ping (default; uses PQping() to determine server availability), connection (attempts to make a new connection to the upstream node), and query (determines server availability by executing an SQL statement on the node via the existing connection). New configuration option to allow an external mechanism to validate the failover decision made by &repmgrd;. New configuration option to force standbys to disconnect their WAL receivers before making a failover decision. In a failover situation, &repmgrd; will not attempt to promote a node if another primary has already appeared (e.g. by being promoted manually). GitHub #420. Bug fixes repmgr cluster show: fix display of node IDs with multiple digits. ensure repmgr primary unregister behaves correctly when executed on a witness server. GitHub #548. ensure repmgr standby register fails when is the same as the local node ID. &repmgr;: when executing repmgr standby clone, recheck primary/upstream connection(s) after the data copy operation is complete, as these may have gone away. &repmgr;: when executing repmgr standby switchover, prevent escaping issues with connection URIs when executing repmgr node rejoin on the demotion candidate. GitHub #525. &repmgr;: when executing repmgr standby switchover, verify the standby (promotion candidate) is currently attached to the primary (demotion candidate). GitHub #519. &repmgr;: when executing repmgr standby switchover, avoid a potential race condition when comparing received WAL on the standby to the primary's shutdown location, as the standby's walreceiver may not have yet flushed all received WAL to disk. GitHub #518. &repmgr;: when executing repmgr witness register, check the node to connected is actually the primary (i.e. not the witness server). GitHub #528. repmgr node check will only consider physical replication slots, as the purpose of slot checks is to warn about potential issues with streaming replication standbys which are no longer attached. &repmgrd;: on a cascaded standby, don't fail over if failover=manual. GitHub #531. Release 4.2 Wed October 24, 2018 &repmgr; 4.2 is a major release, with the main new feature being the ability to pause repmgrd, e.g. during planned maintenance operations. Various other usability enhancements and a couple of bug fixes are also included; see notes below for details. A restart of the PostgreSQL server is required for this release. For detailed upgrade instructions, see Upgrading a major version release. On Debian-based systems, including Ubuntu, if using &repmgrd; please ensure that the in the file /etc/init.d/repmgrd, the parameter REPMGRD_OPTS contains "--daemonize=false", e.g.: # additional options REPMGRD_OPTS="--daemonize=false" For further details, see repmgrd daemon configuration on Debian/Ubuntu. Configuration file changes New parameter shutdown_check_timeout (default: 60 seconds) added; this provides an explicit timeout for repmgr standby switchover to check that the demotion candidate (current primary) has shut down. Previously, the parameters reconnect_attempts and reconnect_interval were used to calculate a timeout, but these are actually intended for primary failure detection. (GitHub #504). New parameter repmgr_bindir added, to facilitate remote invocation of repmgr when the repmgr binary is located somewhere other than the PostgreSQL binary directory, as it cannot be assumed all package maintainers will install &repmgr; there. This parameter is optional; if not set (the default), &repmgr; will fall back to (if set). (GitHub #246). repmgr enhancements repmgr cluster cleanup now accepts the option to delete records for only one node. (GitHub #493). When running repmgr cluster matrix and repmgr cluster crosscheck, &repmgr; will report nodes unreachable via SSH, and emit return code ERR_BAD_SSH. (GitHub #246). Users relying on repmgr cluster crosscheck to return a non-zero return code as a way of detecting connectivity errors should be aware that ERR_BAD_SSH will be returned if there is an SSH connection error from the node where the command is executed, even if the command is able to establish that PostgreSQL connectivity is fine. Therefore the exact return code should be checked to determine what kind of connectivity error has been detected. repmgrd enhancements &repmgrd; can now be "paused", i.e. instructed not to take any action such as a failover, even if the prerequisites for such an action are detected. This removes the need to stop &repmgrd; on all nodes when performing a planned operation such as a switchover. For further details, see Pausing repmgrd. Bug fixes &repmgr;: fix "Missing replication slots" label in repmgr node check. (GitHub #507) &repmgrd;: fix parsing of option. Release 4.1.1 Wed September 5, 2018 repmgr 4.1.1 contains a number of usability enhancements and bug fixes. We recommend upgrading to this version as soon as possible. This release can be installed as a simple package upgrade from repmgr 4.0 ~ 4.1.0; &repmgrd; (if running) should be restarted. See for more details. repmgr enhancements repmgr standby switchover --dry-run no longer copies external configuration files to test they can be copied; this avoids making any changes to the target system. (GitHub #491). repmgr cluster cleanup: add cluster_cleanup event. (GitHub #492). repmgr standby switchover: improve detection of free walsenders. (GitHub #495). Improve messages emitted during repmgr standby promote. repmgrd enhancements Always reopen the log file after receiving SIGHUP. Previously this only happened if a configuration file change was detected. (GitHub #485). Report version number after logger initialisation. (GitHub #487). Improve cascaded standby failover handling. (GitHub #480). Improve reconnection handling after brief network outages; if monitoring data being collected, this could lead to orphaned sessions on the primary. (GitHub #480). Check promote_command and follow_command are defined when reloading configuration. These were checked on startup but not reload by &repmgrd;, which made it possible to make &repmgrd; with invalid values. It's unlikely anyone would want to do this, but we should make it impossible anyway. (GitHub #486). Other Text of any failed queries will now be logged as ERROR to assist logfile analysis at log levels higher than DEBUG. (GitHub #498). Bug fixes repmgr node rejoin: remove new upstream's replication slot if it still exists on the rejoined standby. (GitHub #499). &repmgrd;: fix startup on witness node when local data is stale. (GitHub #488, #489). Truncate version string reported by PostgreSQL if necessary; some distributions insert additional detail after the actual version. (GitHub #490). Release 4.1.0 Tue July 31, 2018 &repmgr; 4.1.0 introduces some changes to &repmgrd; behaviour and some additional configuration parameters. This release can be installed as a simple package upgrade from repmgr 4.0 ~ 4.0.6. The following post-upgrade steps must be carried out: Execute ALTER EXTENSION repmgr UPDATE on the primary server in the database where &repmgr; is installed. &repmgrd; must be restarted on all nodes where it is running. A restart of the PostgreSQL server is not required for this release (unless upgrading from repmgr 3.x). See for more details. Configuration changes are backwards-compatible and no changes to repmgr.conf are required. However users should review the changes listed below. Repository changes Coinciding with this release, the 2ndQuadrant repository structure has changed. See section for details, particularly if you are using a RPM-based system. Configuration file changes Default for is now . This produces additional informative log output, without creating excessive additional log file volume, and matches the setting assumed for examples in the documentation. (GitHub #470). recovery_min_apply_delay now accepts a minimum value of zero (GitHub #448). repmgr enhancements repmgr: always exit with an error if an unrecognised command line option is provided. This matches the behaviour of other PostgreSQL utilities such as psql. (GitHub #464). repmgr: add option to suppress non-error output. (GitHub #468). repmgr cluster show, repmgr node check and repmgr node status return non-zero exit code if node status issues detected. (GitHub #456). Add output option for repmgr cluster event. (GitHub #471). repmgr witness unregister can be run on any node, by providing the ID of the witness node with . (GitHub #472). repmgr standby switchover will refuse to run if an exclusive backup is taking place on the current primary. (GitHub #476). repmgrd enhancements &repmgrd;: create a PID file by default (GitHub #457). For details, see . &repmgrd;: daemonize process by default. In case, for whatever reason, the user does not wish to daemonize the process, provide . (GitHub #458). Bug fixes repmgr standby register --wait-sync: fix behaviour when no timeout provided. repmgr cluster cleanup: add missing help options. (GitHub #461/#462). Ensure witness node follows new primary after switchover. (GitHub #453). repmgr node check and repmgr node status: fix witness node handling. (GitHub #451). When using repmgr standby clone with and replication slots, ensure primary_slot_name is set correctly. (GitHub #474). Release 4.0.6 Thu June 14, 2018 &repmgr; 4.0.6 contains a number of bug fixes and usability enhancements. We recommend upgrading to this version as soon as possible. This release can be installed as a simple package upgrade from repmgr 4.0 ~ 4.0.5; &repmgrd; (if running) should be restarted. See for more details. Usability enhancements repmgr cluster crosscheck and repmgr cluster matrix: return non-zero exit code if node connection issues detected (GitHub #447) repmgr standby clone: Improve handling of external configuration file copying, including consideration in check (GitHub #443) When using , force log level to INFO to ensure output will always be displayed (GitHub #441) repmgr standby clone: Improve documentation of mode (GitHub #438) repmgr standby clone: Don't require presence of user parameter in conninfo string (GitHub #437) Bug fixes repmgr witness register: prevent registration of a witness server with the same name as an existing node repmgr standby follow: check node has actually connected to new primary before reporting success (GitHub #444) repmgr node rejoin: Fix bug when parsing parameter (GitHub #442) &repmgrd;: ensure local node is counted as quorum member (GitHub #439) Release 4.0.5 Wed May 2, 2018 &repmgr; 4.0.5 contains a number of usability enhancements related to pg_rewind usage, recovery.conf generation and (in &repmgrd;) handling of various corner-case situations, as well as a number of bug fixes. Usability enhancements Various documentation improvements, with particular emphasis on the importance of setting appropriate service commands instead of relying on pg_ctl. Poll demoted primary after restart as a standby during a switchover operation (GitHub #408). Add configuration parameter (GitHub #424). Add sanity check if not supplied when executing (GitHub #395). Enable pg_rewind to be used with PostgreSQL 9.3/9.4 (GitHub #413). When generating replication connection strings, set dbname=replication if appropriate (GitHub #421). Enable provision of in recovery.conf (GitHub #416). Actively check for node to rejoin cluster (GitHub #415). &repmgrd;: set connect_timeout=2 (if not explicitly set) when pinging a server. Bug fixes Fix display of conninfo parsing error messages. Fix minimum accepted value for degraded_monitoring_timeout (GitHub #411). Fix superuser password handling (GitHub #400) Fix parsing of archive_ready_critical configuration file parameter (GitHub #426). Fix repmgr cluster crosscheck output (GitHub #389) Fix memory leaks in witness code (GitHub #402). &repmgrd;: handle pg_ctl promote timeout (GitHub #425). &repmgrd;: handle failover situation with only two nodes in the primary location, and at least one node in another location (GitHub #407). &repmgrd;: prevent standby connection handle from going stale. Release 4.0.4 Fri Mar 9, 2018 &repmgr; 4.0.4 contains some bug fixes and and a number of usability enhancements related to logging/diagnostics, event notifications and pre-action checks. This release can be installed as a simple package upgrade from repmgr 4.0 ~ 4.0.3; &repmgrd; (if running) should be restarted. See for more details. It is not possible to perform a switchover where the demotion candidate is running &repmgr; 4.0.2 or lower; all nodes should be upgraded to the latest version (4.0.4). This is due to additional checks introduced in 4.0.3 which require the presence of 4.0.3 or later versions on all nodes. Usability enhancements add repmgr standby clone --recovery-conf-only option to enable integration of a standby cloned from another source into a &repmgr; cluster (GitHub #382) remove restriction on using replication slots when cloning from a Barman server (GitHub #379) make repmgr standby promote timeout values configurable (GitHub #387) add missing options to main --help output (GitHub #391, #392) Bug fixes ensure repmgr node rejoin honours the option (GitHub #383) improve replication slot warnings generated by repmgr node status (GitHub #385) fix --superuser handling when cloning a standby (GitHub #380) &repmgrd;: improve detection of status change from primary to standby &repmgrd;: improve reconnection to the local node after a failover (previously a connection error due to the node starting up was being interpreted as the node being unavailable) &repmgrd;: when running on a witness server, correctly connect to new primary after a failover &repmgrd;: add event notification repmgrd_shutdown (GitHub #393) Release 4.0.3 Thu Feb 15, 2018 &repmgr; 4.0.3 contains some bug fixes and and a number of usability enhancements related to logging/diagnostics, event notifications and pre-action checks. This release can be installed as a simple package upgrade from repmgr 4.0 ~ 4.0.2; repmgrd (if running) should be restarted. It is not possible to perform a switchover where the demotion candidate is running &repmgr; 4.0.2 or lower; all nodes should be upgraded to 4.0.3. This is due to additional checks introduced in 4.0.3 which require the presence of 4.0.3 or later versions on all nodes. Usability enhancements improve repmgr standby switchover behaviour when pg_ctl is used to control the server and logging output is not explicitly redirected improve repmgr standby switchover log messages and provide new exit code ERR_SWITCHOVER_INCOMPLETE when old primary could not be shut down cleanly add check to verify the demotion candidate can make a replication connection to the promotion candidate before executing a switchover (GitHub #370) add check for sufficient walsenders and replication slots on the promotion candidate before executing repmgr standby switchover (GitHub #371) add --dry-run mode to repmgr standby follow (GitHub #368) provide information about the primary node for repmgr standby register and repmgr standby follow event notifications (GitHub #375) add standby_register_sync event notification, which is fired when repmgr standby register is run with the option and the new or updated standby node record has synchronised to the standby (GitHub #374) when running repmgr cluster show, if any node is unreachable, output the error message encountered in the list of warnings (GitHub #369) Bug fixes ensure an inactive data directory can be overwritten when cloning a standby (GitHub #366) repmgr node status upstream node display fixed (GitHub #363) repmgr primary unregister: clarify usage and fix --help output (GitHub #373) parsing of pg_basebackup_options fixed (GitHub #376) ensure the pg_subtrans directory is created when cloning a standby in Barman mode repmgr witness register: fix primary node check (GitHub #377). Release 4.0.2 Thu Jan 18, 2018 &repmgr; 4.0.2 contains some bug fixes and small usability enhancements. This release can be installed as a simple package upgrade from &repmgr; 4.0.1 or 4.0; &repmgrd; (if running) should be restarted. Usability enhancements Recognize the / option for repmgr cluster event to hide the Details column (GitHub #360) Add "--wait-start" option for repmgr standby register (GitHub #356) Add %p event notification parameter for repmgr standby switchover Bug fixes Add missing -W option to getopt_long() invocation (GitHub #350) Automatically create slot name if missing (GitHub #343) Fixes to parsing output of remote repmgr invocations (GitHub #349) When registering BDR nodes, automatically create missing connection replication set (GitHub #347) Handle missing node record in repmgr node rejoin (GitHub #358) Documentation The documentation can now be built as a single HTML file (GitHub pull request #353) Release 4.0.1 Wed Dec 13, 2017 &repmgr; 4.0.1 is a bugfix release. Bug fixes ensure correct return codes are returned for repmgr node check --action= operations (GitHub #340) Fix when repmgr schema not set in search path (GitHub #341) When using --force-rewind with delete any replication slots copied by pg_rewind (GitHub #334) Only perform sanity check on accessibility of configuration files outside the data directory when --copy-external-config-files provided (GitHub #342) Initialise "voting_term" table in application, not extension SQL (GitHub #344) Release 4.0.0 Tue Nov 21, 2017 repmgr 4.0 is an entirely new version of &repmgr;, implementing &repmgr; as a native PostgreSQL extension, adding new and improving existing features, and making &repmgr; more user-friendly and intuitive to use. The new code base will make it easier to add additional functionality for future releases. With the new version, the opportunity has been taken to make some changes in the way &repmgr; is set up and configured. In particular changes have been made to some configuration file settings consistency for and clarity. Changes are covered in detail below To standardise terminology, from this release primary is used to denote the read/write node in a streaming replication cluster. master is still accepted as an alias for &repmgr; commands (e.g. repmgr master register). For detailed instructions on upgrading from repmgr 3.x, see . Features and improvements improved switchover: the switchover process has been improved and streamlined, speeding up the switchover process and can also instruct other standbys to follow the new primary once the switchover has completed. See for more details. "--dry-run" option: many &repmgr; commands now provide a --dry-run option which will execute the command as far as possible without making any changes, which will enable possible issues to be identified before the intended operation is actually carried out. easier upgrades: &repmgr; is now implemented as a native PostgreSQL extension, which means future upgrades can be carried out by installing the upgraded package and issuing ALTER EXTENSION repmgr UPDATE. improved logging output: &repmgr; (and &repmgrd;) now provide more explicit logging output giving a better picture of what is going on. Where appropriate, DETAIL and HINT log lines provide additional detail and suggestions for resolving problems. Additionally, &repmgrd; now emits informational log lines at regular, configurable intervals to confirm that it's running correctly and which node(s) it's monitoring. automatic configuration file location in packages: Many operating system packages place the &repmgr; configuration files in a version-specific subdirectory, e.g. /etc/repmgr/9.6/repmgr.conf; &repmgr; now makes it easy for package maintainers to provide a patch with the actual file location, meaning repmgr.conf does not need to be provided explicitly. This is currently the case for 2ndQuadrant-provided .deb and .rpm packages. monitoring and status checks: New commands and providing information about a node's status and replication-related monitoring output. node rejoin: New commands enables a failed primary to be rejoined to a replication cluster, optionally using pg_rewind to synchronise its data, (note that pg_rewind may not be useable in some circumstances). automatic failover: improved detection of node status; promotion decision based on a consensual model, with the promoted primary explicitly informing other standbys to follow it. The &repmgrd; daemon will continue functioning even if the monitored PostgreSQL instance is down, and resume monitoring if it reappears. Additionally, if the instance's role has changed (typically from a primary to a standby, e.g. following reintegration of a failed primary using ) &repmgrd; will automatically resume monitoring it as a standby. new documentation: the existing documentation spread over multiple text files has been consolidated into DocBook format (as used by the main PostgreSQL project) and is now available online in HTML format. The DocBook files can easily be used to create versions of the documentation in other formats such as PDF. New command line options --dry-run: &repmgr; will attempt to perform the action as far as possible without making any changes to the database --upstream-node-id: use to specify the upstream node the standby will connect later stream from, when cloning and registering a standby. This replaces the configuration file parameter upstream_node. as the upstream node is set when the standby is initially cloned, but can change over the lifetime of an installation (due to failovers, switchovers etc.) so it's pointless/confusing keeping the original value around in repmgr.conf. Changed command line options repmgr --replication-user has been deprecated; it has been replaced by the configuration file option replication_user. The value (which defaults to the user provided in the conninfo string) will be stored in the &repmgr; metadata for use by and . --recovery-min-apply-delay is now a configuration file parameter recovery_min_apply_delay, to ensure the setting does not get lost when a standby follows a new upstream. --no-conninfo-password is deprecated; a password included in the environment variable PGPASSWORD will no longer be added to primary_conninfo by default; to force the inclusion of a password (not recommended), use the new configuration file parameter use_primary_conninfo_password. For details, ee section . &repmgrd; --monitoring-history is deprecated and is replaced by the configuration file option monitoring_history. This enables the setting to be changed without having to modify system service files. Configuration file changes Required settings The following 4 parameters are mandatory in repmgr.conf: node_id node_name conninfo data_directory Renamed settings Some settings have been renamed for clarity and consistency: node is now node_id name is now node_name barman_server is now barman_host master_reponse_timeout is now async_query_timeout (to better indicate its purpose) The following configuration file parameters have been renamed for consistency with other parameters (and conform to the pattern used by PostgreSQL itself, which uses the prefix log_ for logging parameters): loglevel is now log_level logfile is now log_file logfacility is now log_facility Removed settings cluster has been removed upstream_node - see note about --upstream-node-id above retry_promote_interval_secsthis is now redundant due to changes in the failover/promotion mechanism; the new equivalent is primary_notification_timeout Logging changes default value for log_level is INFO rather than NOTICE. new parameter log_status_interval, which causes &repmgrd; to emit a status log line at the specified interval repmgrd The shared library has been renamed from repmgr_funcs to repmgr, meaning shared_preload_libraries in postgresql.conf needs to be updated to the new name: shared_preload_libraries = 'repmgr' repmgr-5.3.1/doc/appendix-signatures.xml000066400000000000000000000027051420262710000202730ustar00rootroot00000000000000 Verifying digital signatures repmgr source code signing key The signing key ID used for repmgr source code bundles is: 0x297F1DCC. To download the repmgr source key to your computer: curl -s https://repmgr.org/download/SOURCE-GPG-KEY-repmgr | gpg --import gpg --fingerprint 0x297F1DCC then verify that the fingerprint is the expected value: 085A BE38 6FD9 72CE 6365 340D 8365 683D 297F 1DCC For checking tarballs, first download and import the repmgr source signing key as shown above. Then download both source tarball and the detached key (e.g. repmgr-4.0beta1.tar.gz and repmgr-4.0beta1.tar.gz.asc) from https://repmgr.org/download/ and use gpg to verify the key, e.g.: gpg --verify repmgr-4.0beta1.tar.gz.asc repmgr-5.3.1/doc/appendix-support.xml000066400000000000000000000067541420262710000176330ustar00rootroot00000000000000 &repmgr; support support 2ndQuadrant provides 24x7 production support for &repmgr; and other PostgreSQL products, including configuration assistance, installation verification and training for running a robust replication cluster. For further details see: https://2ndquadrant.com/en/support/ A mailing list/forum is provided via Google groups to discuss contributions or issues: https://groups.google.com/group/repmgr. Please report bugs and other issues to: https://github.com/EnterpriseDB/repmgr. Please read the following section before submitting questions or issue reports. Reporting Issues support reporting issues When asking questions or reporting issues, it is extremely helpful if the following information is included: PostgreSQL version &repmgr; version How was &repmgr; installed? From source? From packages? If so from which repository? repmgr.conf files (suitably anonymized if necessary) Contents of the repmgr.nodes table (suitably anonymized if necessary) PostgreSQL 11 and earlier: contents of the recovery.conf file (suitably anonymized if necessary). PostgreSQL 12 and later: contents of the postgresql.auto.conf file (suitably anonymized if necessary), and whether or not the PostgreSQL data directory contains the files standby.signal and/or recovery.signal. If issues are encountered with a &repmgr; client command, please provide the output of that command executed with the options , which will ensure &repmgr; emits the maximum level of logging output. If issues are encountered with &repmgrd;, please provide relevant extracts from the &repmgr; log files and if possible the PostgreSQL log itself. Please ensure these logs do not contain any confidential data. In all cases it is extremely useful to receive as much detail as possible on how to reliably reproduce an issue. repmgr-5.3.1/doc/changes-in-repmgr4.md000066400000000000000000000003161420262710000174670ustar00rootroot00000000000000Changes in repmgr 4 =================== This document has been integrated into the main `repmgr` documentation and is now located here: > [Release notes](https://repmgr.org/docs/current/release-4.0.html) repmgr-5.3.1/doc/cloning-standbys.xml000066400000000000000000000576421420262710000175710ustar00rootroot00000000000000 Cloning standbys Cloning a standby from Barman cloning from Barman Barman cloning a standby can use 2ndQuadrant's Barman application to clone a standby (and also as a fallback source for WAL files). Barman (aka PgBarman) should be considered as an integral part of any PostgreSQL replication cluster. For more details see: https://www.pgbarman.org/. Barman support provides the following advantages: the primary node does not need to perform a new backup every time a new standby is cloned a standby node can be disconnected for longer periods without losing the ability to catch up, and without causing accumulation of WAL files on the primary node WAL management on the primary becomes much easier as there's no need to use replication slots, and wal_keep_segments (PostgreSQL 13 and later: wal_keep_size) does not need to be set. Currently &repmgr;'s support for cloning from Barman is implemented by using rsync to clone from the Barman server. It is therefore not able to make use of Barman's parallel restore facility, which is executed on the Barman server and clones to the target server. Barman's parallel restore facility can be used by executing it manually on the Barman server and configuring replication on the resulting cloned standby using repmgr standby clone --replication-conf-only. Prerequisites for cloning from Barman In order to enable Barman support for repmgr standby clone, following prerequisites must be met: the Barman catalogue must include at least one valid backup for this server; the barman_host setting in repmgr.conf is set to the SSH hostname of the Barman server; the barman_server setting in repmgr.conf is the same as the server configured in Barman. For example, assuming Barman is located on the host "barmansrv" under the "barman" user account, repmgr.conf should contain the following entries: barman_host='barman@barmansrv' barman_server='pg' Here pg corresponds to a section in Barman's configuration file for a specific server backup configuration, which would look something like: [pg] description = "Main cluster" ... More details on Barman configuration can be found in the Barman documentation's configuration section. To use a non-default Barman configuration file on the Barman server, specify this in repmgr.conf with barman_config: barman_config='/path/to/barman.conf' We also recommend configuring the restore_command setting in repmgr.conf to use the barman-wal-restore script (see section below). If you have a non-default SSH configuration on the Barman server, e.g. using a port other than 22, then you can set those parameters in a dedicated Host section in ~/.ssh/config corresponding to the value of barman_host in repmgr.conf. See the Host section in man 5 ssh_config for more details. If you wish to place WAL files in a location outside the main PostgreSQL data directory, set (PostgreSQL 9.6 and earlier: ) in to the target directory (must be an absolute filepath). &repmgr; will create and symlink to this directory in exactly the same way pg_basebackup would. It's now possible to clone a standby from Barman, e.g.: $ repmgr -f /etc/repmgr.conf -h node1 -U repmgr -d repmgr standby clone NOTICE: destination directory "/var/lib/postgresql/data" provided INFO: connecting to Barman server to verify backup for "test_cluster" INFO: checking and correcting permissions on existing directory "/var/lib/postgresql/data" INFO: creating directory "/var/lib/postgresql/data/repmgr"... INFO: connecting to Barman server to fetch server parameters INFO: connecting to source node DETAIL: current installation size is 30 MB NOTICE: retrieving backup from Barman... (...) NOTICE: standby clone (from Barman) complete NOTICE: you can now start your PostgreSQL server HINT: for example: pg_ctl -D /var/lib/postgresql/data start Barman support is automatically enabled if barman_server is set. Normally it is good practice to use Barman, for instance when fetching a base backup while cloning a standby; in any case, Barman mode can be disabled using the --without-barman command line option. Using Barman as a WAL file source Barman fetching archived WAL As a fallback in case streaming replication is interrupted, PostgreSQL can optionally retrieve WAL files from an archive, such as that provided by Barman. This is done by setting restore_command in recovery.conf to a valid shell command which can retrieve a specified WAL file from the archive. barman-wal-restore is a Python script provided as part of the barman-cli package (Barman 2.0 ~ 2.7) or as part of the core Barman distribution (Barman 2.8 and later). To use barman-wal-restore with &repmgr;, assuming Barman is located on the host "barmansrv" under the "barman" user account, and that barman-wal-restore is located as an executable at /usr/bin/barman-wal-restore, repmgr.conf should include the following lines: barman_host='barman@barmansrv' barman_server='pg' restore_command='/usr/bin/barman-wal-restore barmansrv pg %f %p' barman-wal-restore supports command line switches to control parallelism (--parallel=N) and compression (--bzip2, --gzip). Cloning and replication slots cloning replication slots replication slots cloning Replication slots were introduced with PostgreSQL 9.4 and are designed to ensure that any standby connected to the primary using a replication slot will always be able to retrieve the required WAL files. This removes the need to manually manage WAL file retention by estimating the number of WAL files that need to be maintained on the primary using wal_keep_segments (PostgreSQL 13 and later: wal_keep_size). Do however be aware that if a standby is disconnected, WAL will continue to accumulate on the primary until either the standby reconnects or the replication slot is dropped. To enable &repmgr; to use replication slots, set the boolean parameter use_replication_slots in repmgr.conf: use_replication_slots=true Replication slots must be enabled in postgresql.conf by setting the parameter max_replication_slots to at least the number of expected standbys (changes to this parameter require a server restart). When cloning a standby, &repmgr; will automatically generate an appropriate slot name, which is stored in the repmgr.nodes table, and create the slot on the upstream node: repmgr=# SELECT node_id, upstream_node_id, active, node_name, type, priority, slot_name FROM repmgr.nodes ORDER BY node_id; node_id | upstream_node_id | active | node_name | type | priority | slot_name ---------+------------------+--------+-----------+---------+----------+--------------- 1 | | t | node1 | primary | 100 | repmgr_slot_1 2 | 1 | t | node2 | standby | 100 | repmgr_slot_2 3 | 1 | t | node3 | standby | 100 | repmgr_slot_3 (3 rows) repmgr=# SELECT slot_name, slot_type, active, active_pid FROM pg_replication_slots ; slot_name | slot_type | active | active_pid ---------------+-----------+--------+------------ repmgr_slot_2 | physical | t | 23658 repmgr_slot_3 | physical | t | 23687 (2 rows) Note that a slot name will be created by default for the primary but not actually used unless the primary is converted to a standby using e.g. repmgr standby switchover. Further information on replication slots in the PostgreSQL documentation: https://www.postgresql.org/docs/current/interactive/warm-standby.html#STREAMING-REPLICATION-SLOTS While replication slots can be useful for streaming replication, it's recommended to monitor for inactive slots as these will cause WAL files to build up indefinitely, possibly leading to server failure. As an alternative we recommend using 2ndQuadrant's Barman, which offloads WAL management to a separate server, removing the requirement to use a replication slot for each individual standby to reserve WAL. See section for more details on using &repmgr; together with Barman. Cloning and cascading replication cloning cascading replication Cascading replication, introduced with PostgreSQL 9.2, enables a standby server to replicate from another standby server rather than directly from the primary, meaning replication changes "cascade" down through a hierarchy of servers. This can be used to reduce load on the primary and minimize bandwidth usage between sites. For more details, see the PostgreSQL cascading replication documentation. &repmgr; supports cascading replication. When cloning a standby, set the command-line parameter --upstream-node-id to the node_id of the server the standby should connect to, and &repmgr; will create recovery.conf to point to it. Note that if --upstream-node-id is not explicitly provided, &repmgr; will set the standby's recovery.conf to point to the primary node. To demonstrate cascading replication, first ensure you have a primary and standby set up as shown in the . Then create an additional standby server with repmgr.conf looking like this: node_id=3 node_name=node3 conninfo='host=node3 user=repmgr dbname=repmgr' data_directory='/var/lib/postgresql/data' Clone this standby (using the connection parameters for the existing standby), ensuring --upstream-node-id is provide with the node_id of the previously created standby (if following the example, this will be 2): $ repmgr -h node2 -U repmgr -d repmgr -f /etc/repmgr.conf standby clone --upstream-node-id=2 NOTICE: using configuration file "/etc/repmgr.conf" NOTICE: destination directory "/var/lib/postgresql/data" provided INFO: connecting to upstream node INFO: connected to source node, checking its state NOTICE: checking for available walsenders on upstream node (2 required) INFO: sufficient walsenders available on upstream node (2 required) INFO: successfully connected to source node DETAIL: current installation size is 29 MB INFO: creating directory "/var/lib/postgresql/data"... NOTICE: starting backup (using pg_basebackup)... HINT: this may take some time; consider using the -c/--fast-checkpoint option INFO: executing: 'pg_basebackup -l "repmgr base backup" -D /var/lib/postgresql/data -h node2 -U repmgr -X stream ' NOTICE: standby clone (using pg_basebackup) complete NOTICE: you can now start your PostgreSQL server HINT: for example: pg_ctl -D /var/lib/postgresql/data start then register it (note that --upstream-node-id must be provided here too): $ repmgr -f /etc/repmgr.conf standby register --upstream-node-id=2 NOTICE: standby node "node2" (ID: 2) successfully registered After starting the standby, the cluster will look like this, showing that node3 is attached to node2, not the primary (node1). $ repmgr -f /etc/repmgr.conf cluster show ID | Name | Role | Status | Upstream | Location | Connection string ----+-------+---------+-----------+----------+----------+-------------------------------------- 1 | node1 | primary | * running | | default | host=node1 dbname=repmgr user=repmgr 2 | node2 | standby | running | node1 | default | host=node2 dbname=repmgr user=repmgr 3 | node3 | standby | running | node2 | default | host=node3 dbname=repmgr user=repmgr Under some circumstances when setting up a cascading replication cluster, you may wish to clone a downstream standby whose upstream node does not yet exist. In this case you can clone from the primary (or another upstream node); provide the parameter --upstream-conninfo to explicitly set the upstream's primary_conninfo string in recovery.conf. Advanced cloning options cloning advanced options pg_basebackup options when cloning a standby As &repmgr; uses pg_basebackup to clone a standby, it's possible to provide additional parameters for pg_basebackup to customise the cloning process. By default, pg_basebackup performs a checkpoint before beginning the backup process. However, a normal checkpoint may take some time to complete; a fast checkpoint can be forced with repmgr standby clone's -c/--fast-checkpoint option. Note that this may impact performance of the server being cloned from (typically the primary) so should be used with care. If Barman is set up for the cluster, it's possible to clone the standby directly from Barman, without any impact on the server the standby is being cloned from. For more details see . Other options can be passed to pg_basebackup by including them in the repmgr.conf setting pg_basebackup_options. Not that by default, &repmgr; executes pg_basebackup with (PostgreSQL 9.6 and earlier: ) set to stream. From PostgreSQL 9.6, if replication slots are in use, it will also create a replication slot before running the base backup, and execute pg_basebackup with the option set to the name of the previously created replication slot. These parameters can set by the user in pg_basebackup_options, in which case they will override the &repmgr; default values. However normally there's no reason to do this. If using a separate directory to store WAL files, provide the option --waldir (--xlogdir in PostgreSQL 9.6 and earlier) with the absolute path to the WAL directory. Any WALs generated during the cloning process will be copied here, and a symlink will automatically be created from the main data directory. The --waldir (--xlogdir) option, if present in pg_basebackup_options, will be honoured by &repmgr; when cloning from Barman (&repmgr; 5.2 and later). See the PostgreSQL pg_basebackup documentation for more details of available options. Managing passwords cloning using passwords If replication connections to a standby's upstream server are password-protected, the standby must be able to provide the password so it can begin streaming replication. The recommended way to do this is to store the password in the postgres system user's ~/.pgpass file. For more information on using the password file, see the documentation section . If using a pgpass file, an entry for the replication user (by default the user who connects to the repmgr database) must be provided, with database name set to replication, e.g.: node1:5432:replication:repmgr:12345 If, for whatever reason, you wish to include the password in recovery.conf, set use_primary_conninfo_password to true in repmgr.conf. This will read a password set in PGPASSWORD (but not ~/.pgpass) and place it into the primary_conninfo string in recovery.conf. Note that PGPASSWORD will need to be set during any action which causes recovery.conf to be rewritten, e.g. . Separate replication user In some circumstances it might be desirable to create a dedicated replication-only user (in addition to the user who manages the &repmgr; metadata). In this case, the replication user should be set in repmgr.conf via the parameter replication_user; &repmgr; will use this value when making replication connections and generating recovery.conf. This value will also be stored in the parameter repmgr.nodes table for each node; it no longer needs to be explicitly specified when cloning a node or executing . Tablespace mapping tablespace mapping &repmgr; provides a configuration file option, which will makes it possible to map the tablespace on the source node to a different location on the local node. To use this, add to repmgr.conf like this: tablespace_mapping='/var/lib/pgsql/tblspc1=/data/pgsql/tblspc1' where the left-hand value represents the tablespace on the source node, and the right-hand value represents the tablespace on the standby to be cloned. This parameter can be provided multiple times. repmgr-5.3.1/doc/configuration-file-log-settings.xml000066400000000000000000000075521420262710000225070ustar00rootroot00000000000000 Log settings repmgr.conf log settings log settings configuration in repmgr.conf By default, &repmgr; and &repmgrd; write log output to STDERR. An alternative log destination can be specified (either a file or syslog). The &repmgr; application itself will continue to write log output to STDERR even if another log destination is configured, as otherwise any output resulting from a command line operation will "disappear" into the log. This behaviour can be overriden with the command line option , which will redirect all logging output to the configured log destination. This is recommended when &repmgr; is executed by another application, particularly &repmgrd;, to enable log output generated by the &repmgr; application to be stored for later reference. log_level (string) log_level configuration file parameter One of , , , , , , or . Default is . Note that will produce a substantial amount of log output and should not be enabled in normal use. log_facility (string) log_facility configuration file parameter Logging facility: possible values are (default), or for syslog integration, one of , , , , . log_file (string) log_file configuration file parameter If is set to , log output can be redirected to the specified file. See for information on configuring log rotation. log_status_interval (integer) log_status_interval configuration file parameter This setting causes &repmgrd; to emit a status log line at the specified interval (in seconds, default 300) describing &repmgrd;'s current state, e.g.: [2018-07-12 00:47:32] [INFO] monitoring connection to upstream node "node1" (ID: 1) repmgr-5.3.1/doc/configuration-file-optional-settings.xml000066400000000000000000000163661420262710000235560ustar00rootroot00000000000000 Optional configuration file settings repmgr.conf optional settings This section documents a subset of optional configuration settings; for a full for a full and annotated view of all configuration options see the see the sample repmgr.conf file config_directory (string) config_directory configuration file parameter If PostgreSQL configuration files are located outside the data directory, specify the directory where the main postgresql.conf file is located. This enables explicit provision of an external configuration file directory, which if set will be passed to pg_ctl as the parameter. Otherwise pg_ctl will default to using the data directory, which will cause some operations to fail if the configuration files are not present there. This is implemented primarily for feature completeness and for development/testing purposes. Users who have installed &repmgr; from a package should not rely on to stop/start/restart PostgreSQL, instead they should set the appropriate for their operating system. For more details see . replication_user (string) replication_user configuration file parameter PostgreSQL user to make replication connections with. If not set defaults, to the user defined in . replication_type (string) replication_type configuration file parameter Must be physical (the default). location (string) location configuration file parameter An arbitrary string defining the location of the node; this is used during failover to check visibility of the current primary node. For more details see . use_replication_slots (boolean) use_replication_slots configuration file parameter Whether to use physical replication slots. When using replication slots, max_replication_slots should be configured for at least the number of standbys which will connect to the primary. ssh_options (string) ssh_options configuration file parameter Options to append to the ssh command when executed by &repmgr;. We recommend adding -q to suppress any superfluous SSH chatter such as login banners, and also an explicit value, e.g.: ssh_options='-q -o ConnectTimeout=10' pg_bindir (string) pg_bindir configuration file parameter Path to the PostgreSQL binary directory (location of pg_ctl, pg_basebackup etc.). Only required if these are not in the system PATH. When &repmgr; is executed via SSH (e.g. when running repmgr standby switchover, repmgr cluster matrix or repmgr cluster crosscheck, or if it is executed as cronjob), a login shell will not be used and only the default system PATH will be set. Therefore it's recommended to set pg_bindir so &repmgr; can correctly invoke binaries on a remote system and avoid potential path issues. Debian/Ubuntu users: you will probably need to set this to the directory where pg_ctl is located, e.g. /usr/lib/postgresql/9.6/bin/. NOTE: pg_bindir is only used when &repmgr; directly executes PostgreSQL binaries; any user-defined scripts must be specified with the full path. See the sample repmgr.conf file for a full and annotated view of all configuration options. repmgr-5.3.1/doc/configuration-file-required-settings.xml000066400000000000000000000071041420262710000235370ustar00rootroot00000000000000 Required configuration file settings repmgr.conf required settings Each repmgr.conf file must contain the following parameters: node_id (int) node_id configuration file parameter A unique integer greater than zero which identifies the node. node_name (string) node_name configuration file parameter An arbitrary (but unique) string; we recommend using the server's hostname or another identifier unambiguously associated with the server to avoid confusion. Avoid choosing names which reflect the node's current role, e.g. primary or standby1 as roles can change and if you end up in a solution where the current primary is called standby1 (for example), things will be confusing to say the least. The string's maximum length is 63 characters and it should contain only printable ASCII characters. conninfo (string) conninfo configuration file parameter Database connection information as a conninfo string. All servers in the cluster must be able to connect to the local node using this string. For details on conninfo strings, see section Connection Strings in the PosgreSQL documentation. If repmgrd is in use, consider explicitly setting connect_timeout in the conninfo string to determine the length of time which elapses before a network connection attempt is abandoned; for details see the PostgreSQL documentation. data_directory (string) data_directory configuration file parameter The node's data directory. This is needed by repmgr when performing operations when the PostgreSQL instance is not running and there's no other way of determining the data directory. See for further configuration options. repmgr-5.3.1/doc/configuration-file-service-commands.xml000066400000000000000000000133271420262710000233240ustar00rootroot00000000000000 Service command settings repmgr.conf service command settings service command settings configuration in repmgr.conf In some circumstances, &repmgr; (and &repmgrd;) need to be able to stop, start or restart PostgreSQL. &repmgr; commands which need to do this include repmgr standby follow, repmgr standby switchover and repmgr node rejoin. By default, &repmgr; will use PostgreSQL's pg_ctl utility to control the PostgreSQL server. However this can lead to various problems, particularly when PostgreSQL has been installed from packages, and especially so if systemd is in use. If using systemd, ensure you have RemoveIPC set to off. See the PostgreSQL documentation section systemd RemoveIPC and also the systemd entry in the PostgreSQL wiki for details. With this in mind, we recommend to always configure &repmgr; to use the available system service commands. To do this, specify the appropriate command for each action in repmgr.conf using the following configuration parameters: service_start_command service_stop_command service_restart_command service_reload_command &repmgr; will not apply when executing any of these commands; these can be user-defined scripts so must always be specified with the full path. It's also possible to specify a service_promote_command. This is intended for systems which provide a package-level promote command, such as Debian's pg_ctlcluster, to promote the PostgreSQL from standby to primary. If your packaging system does not provide such a command, it can be left empty, and &repmgr; will generate the appropriate `pg_ctl ... promote` command. Do not confuse this with promote_command, which is used by &repmgrd; to execute . To confirm which command &repmgr; will execute for each action, use repmgr node service --list-actions --action=..., e.g.: repmgr -f /etc/repmgr.conf node service --list-actions --action=stop repmgr -f /etc/repmgr.conf node service --list-actions --action=start repmgr -f /etc/repmgr.conf node service --list-actions --action=restart repmgr -f /etc/repmgr.conf node service --list-actions --action=reload These commands will be executed by the system user which &repmgr; runs as (usually postgres) and will probably require passwordless sudo access to be able to execute the command. For example, using systemd on CentOS 7, the service commands can be set as follows: service_start_command = 'sudo systemctl start postgresql-9.6' service_stop_command = 'sudo systemctl stop postgresql-9.6' service_restart_command = 'sudo systemctl restart postgresql-9.6' service_reload_command = 'sudo systemctl reload postgresql-9.6' and /etc/sudoers should be set as follows: Defaults:postgres !requiretty postgres ALL = NOPASSWD: /usr/bin/systemctl stop postgresql-9.6, \ /usr/bin/systemctl start postgresql-9.6, \ /usr/bin/systemctl restart postgresql-9.6, \ /usr/bin/systemctl reload postgresql-9.6 pg_ctlcluster service command settings Debian/Ubuntu users: instead of calling sudo systemctl directly, use sudo pg_ctlcluster, e.g.: service_start_command = 'sudo pg_ctlcluster 9.6 main start' service_stop_command = 'sudo pg_ctlcluster 9.6 main stop' service_restart_command = 'sudo pg_ctlcluster 9.6 main restart' service_reload_command = 'sudo pg_ctlcluster 9.6 main reload' and set /etc/sudoers accordingly. While pg_ctlcluster will work when executed as user postgres, it's strongly recommended to use sudo pg_ctlcluster on systemd systems, to ensure systemd has a correct picture of the PostgreSQL application state. repmgr-5.3.1/doc/configuration-file.xml000066400000000000000000000262621420262710000200710ustar00rootroot00000000000000 Configuration file repmgr.conf configuration repmgr.conf repmgr and &repmgrd; use a common configuration file, by default called repmgr.conf (although any name can be used if explicitly specified). repmgr.conf must contain a number of required parameters, including the database connection string for the local node and the location of its data directory; other values will be inferred from defaults if not explicitly supplied. See section for more details. Configuration file format repmgr.conf format repmgr.conf is a plain text file with one parameter/value combination per line. Whitespace is insignificant (except within a quoted parameter value) and blank lines are ignored. Hash marks (#) designate the remainder of the line as a comment. Parameter values that are not simple identifiers or numbers should be single-quoted. To embed a single quote in a parameter value, write either two quotes (preferred) or backslash-quote. Example of a valid repmgr.conf file: # repmgr.conf node_id=1 node_name= node1 conninfo ='host=node1 dbname=repmgr user=repmgr connect_timeout=2' data_directory = '/var/lib/pgsql/12/data' Beginning with repmgr 5.0, configuration file parsing has been tightened up and now matches the way PostgreSQL itself parses configuration files. This means repmgr.conf files used with earlier &repmgr; versions may need slight modification before they can be used with &repmgr; 5 and later. The main change is that &repmgr; requires most string values to be enclosed in single quotes. For example, this was previously valid: conninfo=host=node1 user=repmgr dbname=repmgr connect_timeout=2 but must now be changed to: conninfo='host=node1 user=repmgr dbname=repmgr connect_timeout=2' Configuration file include directives repmgr.conf include directives From &repmgr; 5.2, the configuration file can contain the following include directives: : include the specified file, either as an absolute path or path relative to the current file : include the specified file. The file is specified as an absolute path or path relative to the current file. However, if it does not exist, an error will not be raised. : include files in the specified directory which have the .conf suffix. The directory is specified either as an absolute path or path relative to the current file These behave in exactly the same way as the PostgreSQL configuration file processing; see the PostgreSQL documentation for additional details. Configuration file items The following sections document some sections of the configuration file: For a full list of annotated configuration items, see the file repmgr.conf.sample. For &repmgrd;-specific settings, see . The following parameters in the configuration file can be overridden with command line options: -L/--log-level overrides log_level in repmgr.conf -b/--pg_bindir overrides pg_bindir in repmgr.conf Configuration file location repmgr.conf location The configuration file will be searched for in the following locations: a configuration file specified by the -f/--config-file command line option a location specified by the package maintainer (if repmgr as installed from a package and the package maintainer has specified the configuration file location) repmgr.conf in the local directory /etc/repmgr.conf the directory reported by pg_config --sysconfdir In examples provided in this documentation, it is assumed the configuration file is located at /etc/repmgr.conf. If &repmgr; is installed from a package, the configuration file will probably be located at another location specified by the packager; see appendix for configuration file locations in different packaging systems. Note that if a file is explicitly specified with -f/--config-file, an error will be raised if it is not found or not readable, and no attempt will be made to check default locations; this is to prevent repmgr unexpectedly reading the wrong configuration file. If providing the configuration file location with -f/--config-file, avoid using a relative path, particularly when executing and , as &repmgr; stores the configuration file location in the repmgr metadata for use when &repmgr; is executed remotely (e.g. during ). &repmgr; will attempt to convert the a relative path into an absolute one, but this may not be the same as the path you would explicitly provide (e.g. ./repmgr.conf might be converted to /path/to/./repmgr.conf, whereas you'd normally write /path/to/repmgr.conf). Configuration file and PostgreSQL major version upgrades repmgr.conf PostgreSQL major version upgrades When upgrading the PostgreSQL cluster to a new major version, repmgr.conf will probably needed to be updated. Usually and will need to be modified, particularly if the default package locations are used, as these usually change. It's also possible the location of repmgr.conf itself will change (e.g. from /etc/repmgr/11/repmgr.conf to /etc/repmgr/12/repmgr.conf). This is stored as part of the &repmgr; metadata and is used by &repmgr; to execute &repmgr; remotely (e.g. during a switchover operation). If the content and/or location of repmgr.conf has changed, the &repmgr; metadata needs to be updated to reflect this. The &repmgr; metadata can be updated on each node with: repmgr primary register --force -f /path/to/repmgr.conf repmgr standby register --force -f /path/to/repmgr.conf repmgr witness register --force -f /path/to/repmgr.conf -h primary_host repmgr-5.3.1/doc/configuration-password-management.xml000066400000000000000000000157241420262710000231270ustar00rootroot00000000000000 Password Management passwords Password Management Options passwords options for managing For security purposes it's desirable to protect database access using a password. PostgreSQL has three ways of providing a password: including the password in the string (e.g. "host=node1 dbname=repmgr user=repmgr password=foo") exporting the password as an environment variable (PGPASSWORD) storing the password in a dedicated password file We strongly advise against including the password in the string, as this will result in the database password being exposed in various places, including in the repmgr.conf file, the repmgr.nodes table, any output generated by &repmgr; which lists the node strings (e.g. repmgr cluster show) and in the &repmgr; log file, particularly at . Currently &repmgr; does not fully support use of the option in the string. Exporting the password as an environment variable (PGPASSWORD) is considered less insecure, but the PostgreSQL documentation explicitly recommends against doing this:
Environment Variables PGPASSWORD behaves the same as the connection parameter. Use of this environment variable is not recommended for security reasons, as some operating systems allow non-root users to see process environment variables via ps; instead consider using a password file.
The most secure option for managing passwords is to use a dedicated password file; see the following section for more details.
Using a password file pgpass .pgpass passwords using a password file The most secure way of storing passwords is in a password file, which by default is ~/.pgpass. This file can only be read by the system user who owns the file, and PostgreSQL will refuse to use the file unless read/write permissions are restricted to the file owner. The password(s) contained in the file will not be directly accessed by &repmgr; (or any other libpq-based client software such as psql). For full details see the PostgreSQL password file documentation. For use with &repmgr;, the ~/.pgpass must two entries for each node in the replication cluster: one for the &repmgr; user who accesses the &repmgr; metadatabase, and one for replication connections (regardless of whether a dedicated replication user is used). The file must be present on each node in the replication cluster. A ~/.pgpass file for a 3-node cluster where the repmgr database user is used for both for accessing the &repmgr; metadatabase and for replication connections would look like this: node1:5432:repmgr:repmgr:foo node1:5432:replication:repmgr:foo node2:5432:repmgr:repmgr:foo node2:5432:replication:repmgr:foo node3:5432:repmgr:repmgr:foo node3:5432:replication:repmgr:foo If a dedicated replication user (here: repluser) is in use, the file would look like this: node1:5432:repmgr:repmgr:foo node1:5432:replication:repluser:foo node2:5432:repmgr:repmgr:foo node2:5432:replication:repluser:foo node3:5432:repmgr:repmgr:foo node3:5432:replication:repluser:foo If you are planning to use the / option, there must also be an entry enabling the superuser to connect to the &repmgr; database. Assuming the superuser is postgres, the file would look like this: node1:5432:repmgr:repmgr:foo node1:5432:repmgr:postgres:foo node1:5432:replication:repluser:foo node2:5432:repmgr:repmgr:foo node2:5432:repmgr:postgres:foo node2:5432:replication:repluser:foo node3:5432:repmgr:repmgr:foo node3:5432:repmgr:postgres:foo node3:5432:replication:repluser:foo The ~/.pgpass file can be simplified with the use of wildcards if there is no requirement to restrict provision of passwords to particular hosts, ports or databases. The preceding file could then be formatted like this: *:*:*:repmgr:foo *:*:*:postgres:foo It's possible to specify an alternative location for the ~/.pgpass file, either via the environment variable PGPASSFILE, or (from PostgreSQL 9.6) using the passfile parameter in connection strings. If using the passfile parameter, it's essential to ensure the file is in the same location on all nodes, as when connecting to a remote node, the file referenced is the one on the local node. Additionally, you must specify the passfile location in repmgr.conf with the option so &repmgr; can write the correct path when creating the parameter for replication configuration on standbys.
repmgr-5.3.1/doc/configuration-permissions.xml000066400000000000000000000020131420262710000215110ustar00rootroot00000000000000 repmgr database user permissions configuration database user permissions &repmgr; requires that the database defined in the conninfo setting contains the repmgr extension. The database user defined in the conninfo setting must be able to access this database and the database objects contained within the extension. The repmgr extension can only be installed by a superuser. If the &repmgr; user is a superuser, &repmgr; will create the extension automatically. Alternatively, the extension can be created manually by a superuser (with "CREATE EXTENSION repmgr") before executing repmgr primary register. repmgr-5.3.1/doc/configuration.xml000066400000000000000000000310631420262710000171470ustar00rootroot00000000000000 repmgr configuration Prerequisites for configuration configuration prerequisites configuration ssh Following software must be installed on both servers: PostgreSQL repmgr At network level, connections between the PostgreSQL port (default: 5432) must be possible between all nodes. Passwordless SSH connectivity between all servers in the replication cluster is not required, but is necessary in the following cases: if you need &repmgr; to copy configuration files from outside the PostgreSQL data directory (as is the case with e.g. Debian packages); in this case rsync must also be installed on all servers. to perform switchover operations when executing repmgr cluster matrix and repmgr cluster crosscheck Consider setting ConnectTimeout to a low value in your SSH configuration. This will make it faster to detect any SSH connection errors. PostgreSQL configuration for &repmgr; configuration PostgreSQL PostgreSQL configuration The following PostgreSQL configuration parameters may need to be changed in order for &repmgr; (and replication itself) to function correctly. hot_standby PostgreSQL configuration must always be set to on, as &repmgr; needs to be able to connect to each server it manages. Note that defaults to on from PostgreSQL 10 and later; in PostgreSQL 9.6 and earlier, the default was off. PostgreSQL documentation: hot_standby. wal_level PostgreSQL configuration must be one of or (PostgreSQL 9.5 and earlier: one of or ). PostgreSQL documentation: wal_level. max_wal_senders PostgreSQL configuration must be set to a value of 2 or greater. In general you will need one WAL sender for each standby which will attach to the PostgreSQL instance; additionally &repmgr; will require two free WAL senders in order to clone further standbys. should be set to an appropriate value on all PostgreSQL instances in the replication cluster which may potentially become a primary server or (in cascading replication) the upstream server of a standby. PostgreSQL documentation: max_wal_senders. From PostgreSQL 12, must be set to the same or a higher value as the primary node (at the time the node was cloned), otherwise the standby will refuse to start (unless is set to off, which will prevent the node from accepting queries). max_replication_slots PostgreSQL configuration If you are intending to use replication slots, must be set to a non-zero value. should be set to an appropriate value on all PostgreSQL instances in the replication cluster which may potentially become a primary server or (in cascading replication) the upstream server of a standby. PostgreSQL documentation: max_replication_slots. wal_log_hints PostgreSQL configuration If you are intending to use pg_rewind, and the cluster was not initialised using data checksums, you may want to consider enabling . For more details see . PostgreSQL documentation: wal_log_hints. archive_mode PostgreSQL configuration We suggest setting to on (and to /bin/true; see below) even if you are currently not planning to use WAL file archiving. This will make it simpler to set up WAL file archiving if it is ever required, as changes to require a full PostgreSQL server restart, while changes can be applied via a normal configuration reload. However, &repmgr; itself does not require WAL file archiving. PostgreSQL documentation: archive_mode. archive_command PostgreSQL configuration If you have set to on but are not currently planning to use WAL file archiving, set to a command which does nothing but returns true, such as /bin/true. See above for details. PostgreSQL documentation: archive_command. / wal_keep_segments PostgreSQL configuration wal_keep_size PostgreSQL configuration Normally there is no need to set (PostgreSQL 13 and later: wal_keep_size; default: 0), as it is not a reliable way of ensuring that all required WAL segments are available to standbys. Replication slots and/or an archiving solution such as Barman are recommended to ensure standbys have a reliable source of WAL segments at all times. The only reason ever to set / is you have you have configured in repmgr.conf to include the setting --wal-method=fetch (PostgreSQL 9.6 and earlier: --xlog-method=fetch) and you have not set in repmgr.conf to fetch WAL files from a reliable source such as Barman, in which case you'll need to set to a sufficiently high number to ensure that all WAL files required by the standby are retained. However we do not recommend WAL retention in this way. PostgreSQL documentation: wal_keep_segments. See also the PostgreSQL configuration section in the Quick-start guide. &configuration-file; &configuration-file-required-settings; &configuration-file-optional-settings; &configuration-file-log-settings; &configuration-file-service-commands; &configuration-permissions; &configuration-password-management; repmgr-5.3.1/doc/event-notifications.xml000066400000000000000000000222611420262710000202700ustar00rootroot00000000000000 Event Notifications event notifications Each time &repmgr; or &repmgrd; perform a significant event, a record of that event is written into the repmgr.events table together with a timestamp, an indication of failure or success, and further details if appropriate. This is useful for gaining an overview of events affecting the replication cluster. However note that this table has advisory character and should be used in combination with the &repmgr; and PostgreSQL logs to obtain details of any events. Example output after a primary was registered and a standby cloned and registered: repmgr=# SELECT * from repmgr.events ; node_id | event | successful | event_timestamp | details ---------+------------------+------------+-------------------------------+------------------------------------------------------------------------------------- 1 | primary_register | t | 2016-01-08 15:04:39.781733+09 | 2 | standby_clone | t | 2016-01-08 15:04:49.530001+09 | Cloned from host 'repmgr_node1', port 5432; backup method: pg_basebackup; --force: N 2 | standby_register | t | 2016-01-08 15:04:50.621292+09 | (3 rows) Alternatively, use to output a formatted list of events. Additionally, event notifications can be passed to a user-defined program or script which can take further action, e.g. send email notifications. This is done by setting the event_notification_command parameter in repmgr.conf. The following format placeholders are provided for all event notifications: node ID event type success (1) or failure (0) timestamp details The values provided for %t and %d may contain spaces, so should be quoted in the provided command configuration, e.g.: event_notification_command='/path/to/some/script %n %e %s "%t" "%d"' The following parameters are provided for a subset of event notifications; their meaning may change according to context: node ID of the current primary ( and ) node ID of the demoted primary ( only) node ID of the former primary (repmgrd_failover_promote only) conninfo string of the primary node ( and ) name of the current primary node ( and ) The values provided for %c and %a may contain spaces, so should always be quoted. By default, all notification types will be passed to the designated script; the notification types can be filtered to explicitly named ones using the event_notifications parameter, e.g.: event_notifications='primary_register,standby_register,witness_register' Events generated by the &repmgr; command: cluster_created primary_register primary_unregister standby_clone standby_register standby_register_sync standby_unregister standby_promote standby_follow standby_switchover witness_register witness_unregister node_rejoin cluster_cleanup Events generated by &repmgrd; (streaming replication mode): repmgrd_start repmgrd_shutdown repmgrd_reload repmgrd_failover_promote repmgrd_failover_follow repmgrd_failover_aborted repmgrd_standby_reconnect repmgrd_promote_error repmgrd_local_disconnect repmgrd_local_reconnect repmgrd_upstream_disconnect repmgrd_upstream_reconnect standby_disconnect_manual standby_failure standby_recovery child_node_disconnect child_node_reconnect child_node_new_connect child_nodes_disconnect_command Note that under some circumstances (e.g. when no replication cluster primary could be located), it will not be possible to write an entry into the repmgr.events table, in which case executing a script via event_notification_command can serve as a fallback by generating some form of notification. repmgr-5.3.1/doc/filelist.xml000066400000000000000000000072711420262710000161170ustar00rootroot00000000000000 repmgr-5.3.1/doc/follow-new-primary.xml000066400000000000000000000041741420262710000200550ustar00rootroot00000000000000 Following a new primary Following a new primary repmgr standby follow Following the failure or removal of the replication cluster's existing primary server, can be used to make "orphaned" standbys follow the new primary and catch up to its current state. To demonstrate this, assuming a replication cluster in the same state as the end of the preceding section (), execute this: $ repmgr -f /etc/repmgr.conf standby follow INFO: changing node 3's primary to node 2 NOTICE: restarting server using "pg_ctl -l /var/log/postgresql/startup.log -w -D '/var/lib/postgresql/data' restart" waiting for server to shut down......... done server stopped waiting for server to start.... done server started NOTICE: STANDBY FOLLOW successful DETAIL: node 3 is now attached to node 2 The standby is now replicating from the new primary and repmgr cluster show output reflects this: $ repmgr -f /etc/repmgr.conf cluster show ID | Name | Role | Status | Upstream | Location | Connection string ----+-------+---------+-----------+----------+----------+-------------------------------------- 1 | node1 | primary | - failed | | default | host=node1 dbname=repmgr user=repmgr 2 | node2 | primary | * running | | default | host=node2 dbname=repmgr user=repmgr 3 | node3 | standby | running | node2 | default | host=node3 dbname=repmgr user=repmgr Note that with cascading replication, repmgr standby follow can also be used to detach a standby from its current upstream server and follow the primary. However it's currently not possible to have it follow another standby; we hope to improve this in a future release. repmgr-5.3.1/doc/install-packages.xml000066400000000000000000000253171420262710000175270ustar00rootroot00000000000000 Installing &repmgr; from packages installation from packages We recommend installing &repmgr; using the available packages for your system. RedHat/CentOS/Fedora installation on Red Hat/CentOS/Fedora etc. &repmgr; RPM packages for RedHat/CentOS variants and Fedora are available from the 2ndQuadrant public repository; see following section for details. Currently the 2ndQuadrant public repository provides support for RedHat/CentOS versions 5, 6 and 7. Support for version 8 is available via the PGDG repository; see below for details. RPM packages for &repmgr; are also available via Yum through the PostgreSQL Global Development Group (PGDG) RPM repository (https://yum.postgresql.org/). Follow the instructions for your distribution (RedHat, CentOS, Fedora, etc.) and architecture as detailed there. Note that it can take some days for new &repmgr; packages to become available via the this repository. &repmgr; RPM packages are designed to be compatible with the community-provided PostgreSQL packages and 2ndQuadrant's 2ndQPostgres. They may not work with vendor-specific packages such as those provided by RedHat for RHEL customers, as the PostgreSQL filesystem layout may be different to the community RPMs. Please contact your support vendor for assistance. See also FAQ entry . For more information on the package contents, including details of installation paths and relevant service commands, see the appendix section . 2ndQuadrant public RPM yum repository 2ndQuadrant provides a dedicated yum public repository for 2ndQuadrant software, including &repmgr;. We recommend using this for all future &repmgr; releases. General instructions for using this repository can be found on its homepage. Specific instructions for installing &repmgr; follow below. Installation Locate the repository RPM for your PostgreSQL version from the list at: https://dl.2ndquadrant.com/ Install the repository definition for your distribution and PostgreSQL version (this enables the 2ndQuadrant repository as a source of &repmgr; packages). For example, for PostgreSQL 11 on CentOS, execute: curl https://dl.2ndquadrant.com/default/release/get/11/rpm | sudo bash For PostgreSQL 9.6 on CentOS, execute: curl https://dl.2ndquadrant.com/default/release/get/9.6/rpm | sudo bash Verify that the repository is installed with: sudo yum repolist The output should contain two entries like this: 2ndquadrant-dl-default-release-pg11/7/x86_64 2ndQuadrant packages (PG11) for 7 - x86_64 18 2ndquadrant-dl-default-release-pg11-debug/7/x86_64 2ndQuadrant packages (PG11) for 7 - x86_64 - Debug 8 Install the &repmgr; version appropriate for your PostgreSQL version (e.g. repmgr10): sudo yum install repmgr11 For packages for PostgreSQL 9.6 and earlier, the package name does not contain a period between major and minor version numbers, e.g. repmgr96. To determine the names of available packages, execute: yum search repmgr Compatibility with PGDG Repositories The 2ndQuadrant &repmgr; yum repository packages use the same definitions and file system layout as the main PGDG repository. Normally yum will prioritize the repository with the most recent &repmgr; version. Once the PGDG repository has been updated, it doesn't matter which repository the packages are installed from. To ensure the 2ndQuadrant repository is always prioritised, install yum-plugin-priorities and set the repository priorities accordingly. Installing a specific package version To install a specific package version, execute yum --showduplicates list for the package in question: [root@localhost ~]# yum --showduplicates list repmgr11 Loaded plugins: fastestmirror Loading mirror speeds from cached hostfile * base: ftp.tsukuba.wide.ad.jp * epel: nrt.edge.kernel.org * extras: ftp.tsukuba.wide.ad.jp * updates: ftp.tsukuba.wide.ad.jp Installed Packages repmgr11.x86_64 4.4.0-1.rhel7 @pgdg11 Available Packages repmgr11.x86_64 4.2-1.el7 2ndquadrant-dl-default-release-pg11 repmgr11.x86_64 4.2-2.el7 2ndquadrant-dl-default-release-pg11 repmgr11.x86_64 4.3-1.el7 2ndquadrant-dl-default-release-pg11 repmgr11.x86_64 4.4-1.el7 2ndquadrant-dl-default-release-pg11 then append the appropriate version number to the package name with a hyphen, e.g.: [root@localhost ~]# yum install repmgr11-4.3-1.el7 Installing old packages See appendix Installing old package versions for details on how to retrieve older package versions. Debian/Ubuntu installation on Debian/Ubuntu etc. .deb packages for &repmgr; are available from the PostgreSQL Community APT repository (https://apt.postgresql.org/). Instructions can be found in the APT section of the PostgreSQL Wiki (https://wiki.postgresql.org/wiki/Apt). For more information on the package contents, including details of installation paths and relevant service commands, see the appendix section . 2ndQuadrant public apt repository for Debian/Ubuntu 2ndQuadrant provides a public apt repository for 2ndQuadrant software, including &repmgr;. General instructions for using this repository can be found on its homepage. Specific instructions for installing &repmgr; follow below. Installation Install the repository definition for your distribution and PostgreSQL version (this enables the 2ndQuadrant repository as a source of &repmgr; packages) by executing: curl https://dl.2ndquadrant.com/default/release/get/deb | sudo bash This will automatically install the following additional packages, if not already present: lsb-release apt-transport-https Install the &repmgr; version appropriate for your PostgreSQL version (e.g. repmgr11): sudo apt-get install postgresql-11-repmgr For packages for PostgreSQL 9.6 and earlier, the package name includes a period between major and minor version numbers, e.g. postgresql-9.6-repmgr. Installing old packages See appendix Installing old package versions for details on how to retrieve older package versions. repmgr-5.3.1/doc/install-requirements.xml000066400000000000000000000206071420262710000204710ustar00rootroot00000000000000 Requirements for installing repmgr installation requirements repmgr is developed and tested on Linux and OS X, but should work on any UNIX-like system supported by PostgreSQL itself. There is no support for Microsoft Windows. &repmgr; &repmgrversion; is compatible with all PostgreSQL versions from 9.4. See section &repmgr; compatibility matrix for an overview of version compatibility. If upgrading from &repmgr; 3.x, please see the section . All servers in the replication cluster must be running the same major version of PostgreSQL, and we recommend that they also run the same minor version. &repmgr; must be installed on each server in the replication cluster. If installing repmgr from packages, the package version must match the PostgreSQL version. If installing from source, &repmgr; must be compiled against the same major version. The same "major" &repmgr; version (e.g. &repmgrversion;.x) must be installed on all node in the replication cluster. We strongly recommend keeping all nodes on the same (preferably latest) "minor" &repmgr; version to minimize the risk of incompatibilities. If different "major" &repmgr; versions (e.g. 4.1.x and &repmgrversion;.x) are installed on different nodes, in the best case &repmgr; (in particular &repmgrd;) will not run. In the worst case, you will end up with a broken cluster. A dedicated system user for &repmgr; is not required; as many &repmgr; and &repmgrd; actions require direct access to the PostgreSQL data directory, these commands should be executed by the postgres user. See also Prerequisites for configuration for information on networking requirements. We recommend using a session multiplexer utility such as screen or tmux when performing long-running actions (such as cloning a database) on a remote server - this will ensure the &repmgr; action won't be prematurely terminated if your ssh session to the server is interrupted or closed. &repmgr; compatibility matrix repmgr compatibility matrix compatibility matrix The following table provides an overview of which &repmgr; version supports which PostgreSQL version. &repmgr; compatibility matrix &repmgr; version Supported? Latest release Supported PostgreSQL versions &repmgr; 5.3 YES &repmgrversion; (&releasedate;) 9.4, 9.5, 9.6, 10, 11, 12, 13, 14 &repmgr; 5.2 NO 5.2.1 (2020-12-07) 9.4, 9.5, 9.6, 10, 11, 12, 13 &repmgr; 5.1 NO 5.1.0 (2020-04-13) 9.3, 9.4, 9.5, 9.6, 10, 11, 12 &repmgr; 5.0 NO 5.0 (2019-10-15) 9.3, 9.4, 9.5, 9.6, 10, 11, 12 &repmgr; 4.x NO 4.4 (2019-06-27) 9.3, 9.4, 9.5, 9.6, 10, 11 &repmgr; 3.x NO 3.3.2 (2017-05-30) 9.3, 9.4, 9.5, 9.6 &repmgr; 2.x NO 2.0.3 (2015-04-16) 9.0, 9.1, 9.2, 9.3, 9.4
The &repmgr; 2.x and 3.x series are no longer maintained or supported. We strongly recommend upgrading to the latest &repmgr; version. Following the release of &repmgr; 5.0, there will be no further releases of the &repmgr; 4.x series. Note that &repmgr; 5.x is an incremental development of the 4.x series and &repmgr; 4.x users should upgrade to this as soon as possible.
PostgreSQL 9.4 support PostgreSQL 9.4 repmgr support Note that some &repmgr; functionality is not available in PostgreSQL 9.4: In PostgreSQL 9.4, pg_rewind is not part of the core distribution. pg_rewind will need to be compiled separately to be able to use any &repmgr; functionality which takes advantage of it. PostgreSQL 9.3 has reached the end of its community support period (final release was 9.3.25 in November 2018) and will no longer be updated with security or bugfixes. Beginning with &repmgr; 5.2, &repmgr; no longer supports PostgreSQL 9.3. PostgreSQL 9.4 has reached the end of its community support period (final release was 9.4.26 in February 2020) and will no longer be updated with security or bugfixes. We recommend that users of these versions migrate to a supported PostgreSQL version as soon as possible. For further details, see the PostgreSQL Versioning Policy.
repmgr-5.3.1/doc/install-source.xml000066400000000000000000000233731420262710000172510ustar00rootroot00000000000000 Installing &repmgr; from source installation from source Prerequisites for installing from source To install &repmgr; the prerequisites for compiling &postgres; must be installed. These are described in &postgres;'s documentation on build requirements and build requirements for documentation. Most mainstream Linux distributions and other UNIX variants provide simple ways to install the prerequisites from packages. Debian and Ubuntu: First add the apt.postgresql.org repository to your sources.list if you have not already done so, and ensure the source repository is enabled. If not configured, the source repository can be added by including a deb-src line as a copy of the existing deb line in the repository file, which is usually /etc/apt/sources.list.d/pgdg.list, e.g.: deb https://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main deb-src https://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main Then install the prerequisites for building PostgreSQL with e.g.: sudo apt-get update sudo apt-get build-dep postgresql-9.6 Select the appropriate PostgreSQL version for your target repmgr version. If using apt-get build-dep is not possible, the following packages may need to be installed manually: flex libedit-dev libkrb5-dev libpam0g-dev libreadline-dev libselinux1-dev libssl-dev libxml2-dev libxslt1-dev RHEL or CentOS 6.x or 7.x: install the appropriate repository RPM for your system from yum.postgresql.org. Then install the prerequisites for building PostgreSQL with: sudo yum check-update sudo yum groupinstall "Development Tools" sudo yum install yum-utils openjade docbook-dtds docbook-style-dsssl docbook-style-xsl sudo yum-builddep postgresql96 Select the appropriate PostgreSQL version for your target repmgr version. If using yum-builddep is not possible, the following packages may need to be installed manually: flex libselinux-devel libxml2-devel libxslt-devel openssl-devel pam-devel readline-devel If building against PostgreSQL 11 or later configured with the option (this is the case with the PGDG-provided packages) you'll also need to install the llvm-toolset-7-clang package. This is available via the Software Collections (SCL) Repository. Getting &repmgr; source code There are two ways to get the &repmgr; source code: with git, or by downloading tarballs of released versions. Using <application>git</application> to get the &repmgr; sources Use git if you expect to update often, you want to keep track of development or if you want to contribute changes to &repmgr;. There is no reason not to use git if you're familiar with it. The source for &repmgr; is maintained at https://github.com/EnterpriseDB/repmgr. There are also tags for each &repmgr; release, e.g. v4.4.0. Clone the source code using git: git clone https://github.com/EnterpriseDB/repmgr For more information on using git see git-scm.com. Downloading release source tarballs Official release source code is uploaded as tarballs to the &repmgr; website along with a tarball checksum and a matching GnuPG signature. See http://repmgr.org/ for the download information. See for information on verifying digital signatures. You will need to download the repmgr source, e.g. repmgr-4.0.tar.gz. You may optionally verify the package checksums from the .md5 files and/or verify the GnuPG signatures per . After you unpack the source code archives using tar xf the installation process is the same as if you were installing from a git clone. Installation of &repmgr; from source To installing &repmgr; from source, simply execute: ./configure && make install Ensure pg_config for the target PostgreSQL version is in $PATH. Building &repmgr; documentation The &repmgr; documentation is (like the main PostgreSQL project) written in DocBook XML format. To build it locally as HTML, you'll need to install the required packages as described in the PostgreSQL documentation. The minimum PostgreSQL version for building the &repmgr; documentation is PostgreSQL 9.5. In &repmgr; 4.3 and earlier, the documentation can only be built against PostgreSQL 9.6 or earlier. To build the documentation as HTML, execute: ./configure && make doc The generated HTML files will be placed in the doc/html subdirectory of your source tree. To build the documentation as a single HTML file, after configuring and building the main &repmgr; source as described above, execute: ./configure && make doc-repmgr.html To build the documentation as a PDF file, after configuring and building the main &repmgr; source as described above, execute: ./configure && make doc-repmgr-A4.pdf repmgr-5.3.1/doc/install.xml000066400000000000000000000014651420262710000157510ustar00rootroot00000000000000 Installation installation &repmgr; can be installed from binary packages provided by your operating system's packaging system, or from source. In general we recommend using binary packages, unless unavailable for your operating system. Source installs are mainly useful if you want to keep track of the very latest repmgr development and contribute to development. They're also the only option if there are no packages for your operating system yet. Before installing &repmgr; make sure you satisfy the . &install-requirements; &install-packages; &install-source; repmgr-5.3.1/doc/legal.xml000066400000000000000000000021251420262710000153610ustar00rootroot00000000000000 2017 2010-2021 EnterpriseDB Corporation Legal Notice repmgr is Copyright © 2010-2021 by EnterpriseDB Corporation All rights reserved. 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 https://www.gnu.org/licenses/ to obtain one. repmgr-5.3.1/doc/overview.xml000066400000000000000000000207641420262710000161540ustar00rootroot00000000000000 repmgr overview This chapter provides a high-level overview of &repmgr;'s components and functionality. Concepts concepts This guide assumes that you are familiar with PostgreSQL administration and streaming replication concepts. For further details on streaming replication, see the PostgreSQL documentation section on streaming replication. The following terms are used throughout the &repmgr; documentation. replication cluster In the &repmgr; documentation, "replication cluster" refers to the network of PostgreSQL servers connected by streaming replication. node A node is a single PostgreSQL server within a replication cluster. upstream node The node a standby server connects to, in order to receive streaming replication. This is either the primary server, or in the case of cascading replication, another standby. failover This is the action which occurs if a primary server fails and a suitable standby is promoted as the new primary. The &repmgrd; daemon supports automatic failover to minimise downtime. switchover In certain circumstances, such as hardware or operating system maintenance, it's necessary to take a primary server offline; in this case a controlled switchover is necessary, whereby a suitable standby is promoted and the existing primary removed from the replication cluster in a controlled manner. The &repmgr; command line client provides this functionality. fencing In a failover situation, following the promotion of a new standby, it's essential that the previous primary does not unexpectedly come back on line, which would result in a split-brain situation. To prevent this, the failed primary should be isolated from applications, i.e. "fenced off". witness server &repmgr; provides functionality to set up a so-called "witness server" to assist in determining a new primary server in a failover situation with more than one standby. The witness server itself is not part of the replication cluster, although it does contain a copy of the repmgr metadata schema. The purpose of a witness server is to provide a "casting vote" where servers in the replication cluster are split over more than one location. In the event of a loss of connectivity between locations, the presence or absence of the witness server will decide whether a server at that location is promoted to primary; this is to prevent a "split-brain" situation where an isolated location interprets a network outage as a failure of the (remote) primary and promotes a (local) standby. A witness server only needs to be created if &repmgrd; is in use. Components &repmgr; is a suite of open-source tools to manage replication and failover within a cluster of PostgreSQL servers. It supports and enhances PostgreSQL's built-in streaming replication, which provides a single read/write primary server and one or more read-only standbys containing near-real time copies of the primary server's database. It provides two main tools: repmgr A command-line tool used to perform administrative tasks such as: setting up standby servers promoting a standby server to primary switching over primary and standby servers displaying the status of servers in the replication cluster repmgrd A daemon which actively monitors servers in a replication cluster and performs the following tasks: monitoring and recording replication performance performing failover by detecting failure of the primary and promoting the most suitable standby server provide notifications about events in the cluster to a user-defined script which can perform tasks such as sending alerts by email Repmgr user and metadata In order to effectively manage a replication cluster, &repmgr; needs to store information about the servers in the cluster in a dedicated database schema. This schema is automatically created by the &repmgr; extension, which is installed during the first step in initializing a &repmgr;-administered cluster (repmgr primary register) and contains the following objects: Tables repmgr.events: records events of interest repmgr.nodes: connection and status information for each server in the replication cluster repmgr.monitoring_history: historical standby monitoring information written by &repmgrd; Views repmgr.show_nodes: based on the table repmgr.nodes, additionally showing the name of the server's upstream node repmgr.replication_status: when &repmgrd;'s monitoring is enabled, shows current monitoring status for each standby. The &repmgr; metadata schema can be stored in an existing database or in its own dedicated database. Note that the &repmgr; metadata schema cannot reside on a database server which is not part of the replication cluster managed by &repmgr;. A database user must be available for &repmgr; to access this database and perform necessary changes. This user does not need to be a superuser, however some operations such as initial installation of the &repmgr; extension will require a superuser connection (this can be specified where required with the command line option --superuser). repmgr-5.3.1/doc/promoting-standby.xml000066400000000000000000000100051420262710000177510ustar00rootroot00000000000000 Promoting a standby server with repmgr promoting a standby repmgr standby promote If a primary server fails or needs to be removed from the replication cluster, a new primary server must be designated, to ensure the cluster continues to function correctly. This can be done with , which promotes the standby on the current server to primary. To demonstrate this, set up a replication cluster with a primary and two attached standby servers so that the cluster looks like this: $ repmgr -f /etc/repmgr.conf cluster show ID | Name | Role | Status | Upstream | Location | Connection string ----+-------+---------+-----------+----------+----------+-------------------------------------- 1 | node1 | primary | * running | | default | host=node1 dbname=repmgr user=repmgr 2 | node2 | standby | running | node1 | default | host=node2 dbname=repmgr user=repmgr 3 | node3 | standby | running | node1 | default | host=node3 dbname=repmgr user=repmgr Stop the current primary with e.g.: $ pg_ctl -D /var/lib/postgresql/data -m fast stop At this point the replication cluster will be in a partially disabled state, with both standbys accepting read-only connections while attempting to connect to the stopped primary. Note that the &repmgr; metadata table will not yet have been updated; executing will note the discrepancy: $ repmgr -f /etc/repmgr.conf cluster show ID | Name | Role | Status | Upstream | Location | Connection string ----+-------+---------+---------------+----------+----------+-------------------------------------- 1 | node1 | primary | ? unreachable | | default | host=node1 dbname=repmgr user=repmgr 2 | node2 | standby | running | node1 | default | host=node2 dbname=repmgr user=repmgr 3 | node3 | standby | running | node1 | default | host=node3 dbname=repmgr user=repmgr WARNING: following issues were detected node "node1" (ID: 1) is registered as an active primary but is unreachable Now promote the first standby with: $ repmgr -f /etc/repmgr.conf standby promote This will produce output similar to the following: INFO: connecting to standby database NOTICE: promoting standby DETAIL: promoting server using "pg_ctl -l /var/log/postgresql/startup.log -w -D '/var/lib/postgresql/data' promote" server promoting INFO: reconnecting to promoted server NOTICE: STANDBY PROMOTE successful DETAIL: node 2 was successfully promoted to primary Executing will show the current state; as there is now an active primary, the previous warning will not be displayed: $ repmgr -f /etc/repmgr.conf cluster show ID | Name | Role | Status | Upstream | Location | Connection string ----+-------+---------+-----------+----------+----------+-------------------------------------- 1 | node1 | primary | - failed | | default | host=node1 dbname=repmgr user=repmgr 2 | node2 | primary | * running | | default | host=node2 dbname=repmgr user=repmgr 3 | node3 | standby | running | node1 | default | host=node3 dbname=repmgr user=repmgr However the sole remaining standby (node3) is still trying to replicate from the failed primary; must now be executed to rectify this situation (see for example). repmgr-5.3.1/doc/quickstart.xml000066400000000000000000000520071420262710000164730ustar00rootroot00000000000000 Quick-start guide quickstart This section gives a quick introduction to &repmgr;, including setting up a sample &repmgr; installation and a basic replication cluster. These instructions for demonstration purposes and are not suitable for a production install, as issues such as account security considerations, and system administration best practices are omitted. To upgrade an existing &repmgr; 3.x installation, see section . Prerequisites for setting up a basic replication cluster with &repmgr; The following section will describe how to set up a basic replication cluster with a primary and a standby server using the repmgr command line tool. We'll assume the primary is called node1 with IP address 192.168.1.11, and the standby is called node2 with IP address 192.168.1.12 Following software must be installed on both servers: PostgreSQL repmgr (matching the installed PostgreSQL major version) At network level, connections between the PostgreSQL port (default: 5432) must be possible in both directions. If you want repmgr to copy configuration files which are located outside the PostgreSQL data directory, and/or to test switchover functionality, you will also need passwordless SSH connections between both servers, and rsync should be installed. For testing repmgr, it's possible to use multiple PostgreSQL instances running on different ports on the same computer, with passwordless SSH access to localhost enabled. PostgreSQL configuration On the primary server, a PostgreSQL instance must be initialised and running. The following replication settings may need to be adjusted: # Enable replication connections; set this value to at least one more # than the number of standbys which will connect to this server # (note that repmgr will execute "pg_basebackup" in WAL streaming mode, # which requires two free WAL senders). # # See: https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-MAX-WAL-SENDERS max_wal_senders = 10 # If using replication slots, set this value to at least one more # than the number of standbys which will connect to this server. # Note that repmgr will only make use of replication slots if # "use_replication_slots" is set to "true" in "repmgr.conf". # (If you are not intending to use replication slots, this value # can be set to "0"). # # See: https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-MAX-REPLICATION-SLOTS max_replication_slots = 10 # Ensure WAL files contain enough information to enable read-only queries # on the standby. # # PostgreSQL 9.5 and earlier: one of 'hot_standby' or 'logical' # PostgreSQL 9.6 and later: one of 'replica' or 'logical' # ('hot_standby' will still be accepted as an alias for 'replica') # # See: https://www.postgresql.org/docs/current/runtime-config-wal.html#GUC-WAL-LEVEL wal_level = 'hot_standby' # Enable read-only queries on a standby # (Note: this will be ignored on a primary but we recommend including # it anyway, in case the primary later becomes a standby) # # See: https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-HOT-STANDBY hot_standby = on # Enable WAL file archiving # # See: https://www.postgresql.org/docs/current/runtime-config-wal.html#GUC-ARCHIVE-MODE archive_mode = on # Set archive command to a dummy command; this can later be changed without # needing to restart the PostgreSQL instance. # # See: https://www.postgresql.org/docs/current/runtime-config-wal.html#GUC-ARCHIVE-COMMAND archive_command = '/bin/true' Rather than editing these settings in the default postgresql.conf file, create a separate file such as postgresql.replication.conf and include it from the end of the main configuration file with: include 'postgresql.replication.conf'. Additionally, if you are intending to use pg_rewind, and the cluster was not initialised using data checksums, you may want to consider enabling wal_log_hints; for more details see . See also the PostgreSQL configuration section in the repmgr configuration guide. Create the repmgr user and database Create a dedicated PostgreSQL superuser account and a database for the &repmgr; metadata, e.g. createuser -s repmgr createdb repmgr -O repmgr For the examples in this document, the name repmgr will be used for both user and database, but any names can be used. For the sake of simplicity, the repmgr user is created as a superuser. If desired, it's possible to create the repmgr user as a normal user. However for certain operations superuser permissions are required; in this case the command line option --superuser can be provided to specify a superuser. It's also assumed that the repmgr user will be used to make the replication connection from the standby to the primary; again this can be overridden by specifying a separate replication user when registering each node. &repmgr; will install the repmgr extension, which creates a repmgr schema containing the &repmgr;'s metadata tables as well as other functions and views. We also recommend that you set the repmgr user's search path to include this schema name, e.g. ALTER USER repmgr SET search_path TO repmgr, "$user", public; Configuring authentication in pg_hba.conf Ensure the repmgr user has appropriate permissions in pg_hba.conf and can connect in replication mode; pg_hba.conf should contain entries similar to the following: local replication repmgr trust host replication repmgr 127.0.0.1/32 trust host replication repmgr 192.168.1.0/24 trust local repmgr repmgr trust host repmgr repmgr 127.0.0.1/32 trust host repmgr repmgr 192.168.1.0/24 trust Note that these are simple settings for testing purposes. Adjust according to your network environment and authentication requirements. Preparing the standby On the standby, do not create a PostgreSQL instance (i.e. do not execute initdb or any database creation scripts provided by packages), but do ensure the destination data directory (and any other directories which you want PostgreSQL to use) exist and are owned by the postgres system user. Permissions must be set to 0700 (drwx------). &repmgr; will place a copy of the primary's database files in this directory. It will however refuse to run if a PostgreSQL instance has already been created there. Check the primary database is reachable from the standby using psql: psql 'host=node1 user=repmgr dbname=repmgr connect_timeout=2' &repmgr; stores connection information as libpq connection strings throughout. This documentation refers to them as conninfo strings; an alternative name is DSN (data source name). We'll use these in place of the -h hostname -d databasename -U username syntax. repmgr configuration file Create a repmgr.conf file on the primary server. The file must contain at least the following parameters: node_id=1 node_name='node1' conninfo='host=node1 user=repmgr dbname=repmgr connect_timeout=2' data_directory='/var/lib/postgresql/data' repmgr.conf should not be stored inside the PostgreSQL data directory, as it could be overwritten when setting up or reinitialising the PostgreSQL server. See sections and for further details about repmgr.conf. &repmgr; only uses when it executes PostgreSQL binaries directly. For user-defined scripts such as and the various s, you must always explicitly provide the full path to the binary or script being executed, even if it is &repmgr; itself. This is because these options can contain user-defined scripts in arbitrary locations, so prepending may break them. For Debian-based distributions we recommend explicitly setting to the directory where pg_ctl and other binaries not in the standard path are located. For PostgreSQL 9.6 this would be /usr/lib/postgresql/9.6/bin/. If your distribution places the &repmgr; binaries in a location other than the PostgreSQL installation directory, specify this with to enable &repmgr; to perform operations (e.g. repmgr cluster crosscheck) on other nodes. See the file repmgr.conf.sample for details of all available configuration parameters. Register the primary server To enable &repmgr; to support a replication cluster, the primary node must be registered with &repmgr;. This installs the repmgr extension and metadata objects, and adds a metadata record for the primary server: $ repmgr -f /etc/repmgr.conf primary register INFO: connecting to primary database... NOTICE: attempting to install extension "repmgr" NOTICE: "repmgr" extension successfully installed NOTICE: primary node record (id: 1) registered Verify status of the cluster like this: $ repmgr -f /etc/repmgr.conf cluster show ID | Name | Role | Status | Upstream | Connection string ----+-------+---------+-----------+----------+-------------------------------------------------------- 1 | node1 | primary | * running | | host=node1 dbname=repmgr user=repmgr connect_timeout=2 The record in the repmgr metadata table will look like this: repmgr=# SELECT * FROM repmgr.nodes; -[ RECORD 1 ]----+------------------------------------------------------- node_id | 1 upstream_node_id | active | t node_name | node1 type | primary location | default priority | 100 conninfo | host=node1 dbname=repmgr user=repmgr connect_timeout=2 repluser | repmgr slot_name | config_file | /etc/repmgr.conf Each server in the replication cluster will have its own record. If &repmgrd; is in use, the fields upstream_node_id, active and type will be updated when the node's status or role changes. Clone the standby server Create a repmgr.conf file on the standby server. It must contain at least the same parameters as the primary's repmgr.conf, but with the mandatory values node, node_name, conninfo (and possibly data_directory) adjusted accordingly, e.g.: node_id=2 node_name='node2' conninfo='host=node2 user=repmgr dbname=repmgr connect_timeout=2' data_directory='/var/lib/postgresql/data' Use the --dry-run option to check the standby can be cloned: $ repmgr -h node1 -U repmgr -d repmgr -f /etc/repmgr.conf standby clone --dry-run NOTICE: using provided configuration file "/etc/repmgr.conf" NOTICE: destination directory "/var/lib/postgresql/data" provided INFO: connecting to source node NOTICE: checking for available walsenders on source node (2 required) INFO: sufficient walsenders available on source node (2 required) NOTICE: standby will attach to upstream node 1 HINT: consider using the -c/--fast-checkpoint option INFO: all prerequisites for "standby clone" are met If no problems are reported, the standby can then be cloned with: $ repmgr -h node1 -U repmgr -d repmgr -f /etc/repmgr.conf standby clone NOTICE: using configuration file "/etc/repmgr.conf" NOTICE: destination directory "/var/lib/postgresql/data" provided INFO: connecting to source node NOTICE: checking for available walsenders on source node (2 required) INFO: sufficient walsenders available on source node (2 required) INFO: creating directory "/var/lib/postgresql/data"... NOTICE: starting backup (using pg_basebackup)... HINT: this may take some time; consider using the -c/--fast-checkpoint option INFO: executing: pg_basebackup -l "repmgr base backup" -D /var/lib/postgresql/data -h node1 -U repmgr -X stream NOTICE: standby clone (using pg_basebackup) complete NOTICE: you can now start your PostgreSQL server HINT: for example: pg_ctl -D /var/lib/postgresql/data start This has cloned the PostgreSQL data directory files from the primary node1 using PostgreSQL's pg_basebackup utility. A recovery.conf file containing the correct parameters to start streaming from this primary server will be created automatically. By default, any configuration files in the primary's data directory will be copied to the standby. Typically these will be postgresql.conf, postgresql.auto.conf, pg_hba.conf and pg_ident.conf. These may require modification before the standby is started. Make any adjustments to the standby's PostgreSQL configuration files now, then start the server. For more details on repmgr standby clone, see the command reference. A more detailed overview of cloning options is available in the administration manual. Verify replication is functioning Connect to the primary server and execute: repmgr=# SELECT * FROM pg_stat_replication; -[ RECORD 1 ]----+------------------------------ pid | 19111 usesysid | 16384 usename | repmgr application_name | node2 client_addr | 192.168.1.12 client_hostname | client_port | 50378 backend_start | 2017-08-28 15:14:19.851581+09 backend_xmin | state | streaming sent_location | 0/7000318 write_location | 0/7000318 flush_location | 0/7000318 replay_location | 0/7000318 sync_priority | 0 sync_state | async This shows that the previously cloned standby (node2 shown in the field application_name) has connected to the primary from IP address 192.168.1.12. From PostgreSQL 9.6 you can also use the view pg_stat_wal_receiver to check the replication status from the standby. repmgr=# SELECT * FROM pg_stat_wal_receiver; Expanded display is on. -[ RECORD 1 ]---------+-------------------------------------------------------------------------------- pid | 18236 status | streaming receive_start_lsn | 0/3000000 receive_start_tli | 1 received_lsn | 0/7000538 received_tli | 1 last_msg_send_time | 2017-08-28 15:21:26.465728+09 last_msg_receipt_time | 2017-08-28 15:21:26.465774+09 latest_end_lsn | 0/7000538 latest_end_time | 2017-08-28 15:20:56.418735+09 slot_name | sender_host | node1 sender_port | 5432 conninfo | user=repmgr dbname=replication host=node1 application_name=node2 Note that the conninfo value is that generated in recovery.conf and will differ slightly from the primary's conninfo as set in repmgr.conf - among others it will contain the connecting node's name as application_name. Register the standby Register the standby server with: $ repmgr -f /etc/repmgr.conf standby register NOTICE: standby node "node2" (ID: 2) successfully registered Check the node is registered by executing repmgr cluster show on the standby: $ repmgr -f /etc/repmgr.conf cluster show ID | Name | Role | Status | Upstream | Location | Priority | Timeline | Connection string ----+-------+---------+-----------+----------+----------+----------+----------+-------------------------------------- 1 | node1 | primary | * running | | default | 100 | 1 | host=node1 dbname=repmgr user=repmgr 2 | node2 | standby | running | node1 | default | 100 | 1 | host=node2 dbname=repmgr user=repmgr Both nodes are now registered with &repmgr; and the records have been copied to the standby server. repmgr-5.3.1/doc/repmgr-cluster-cleanup.xml000066400000000000000000000041401420262710000206740ustar00rootroot00000000000000 repmgr cluster cleanup repmgr cluster cleanup repmgr cluster cleanup purge monitoring history Description Purges monitoring history from the repmgr.monitoring_history table to prevent excessive table growth. By default all data will be removed; Use the option to specify the number of days of monitoring history to retain. This command can be executed manually or as a cronjob. Usage This command requires a valid repmgr.conf file for the node on which it is executed; no additional arguments are required. Notes Monitoring history will only be written if &repmgrd; is active, and monitoring_history is set to true in repmgr.conf. Event notifications A cluster_cleanup event notification will be generated. Options Only delete monitoring records for the specified node. See also For more details see the sections and . repmgr-5.3.1/doc/repmgr-cluster-crosscheck.xml000066400000000000000000000061401420262710000213760ustar00rootroot00000000000000 repmgr cluster crosscheck repmgr cluster crosscheck repmgr cluster crosscheck cross-checks connections between each combination of nodes Description repmgr cluster crosscheck is similar to , but cross-checks connections between each combination of nodes. In "Example 3" in we have no information about the state of node3. However by running repmgr cluster crosscheck it's possible to get a better overview of the cluster situation: $ repmgr -f /etc/repmgr.conf cluster crosscheck Name | Id | 1 | 2 | 3 -------+----+----+----+---- node1 | 1 | * | * | x node2 | 2 | * | * | * node3 | 3 | * | * | * What happened is that repmgr cluster crosscheck merged its own repmgr cluster matrix with the repmgr cluster matrix output from node2; the latter is able to connect to node3 and therefore determine the state of outbound connections from that node. Exit codes One of the following exit codes will be emitted by repmgr cluster crosscheck: The check completed successfully and all nodes are reachable. One or more nodes could not be accessed via SSH. This only applies to nodes unreachable from the node where this command is executed. It's also possible that the crosscheck establishes that connections between PostgreSQL on all nodes are functioning, even if SSH access between some nodes is not possible. PostgreSQL on one or more nodes could not be reached. This error code overrides . repmgr-5.3.1/doc/repmgr-cluster-event.xml000066400000000000000000000051771420262710000204010ustar00rootroot00000000000000 repmgr cluster event repmgr cluster event repmgr cluster event output a formatted list of cluster events Description Outputs a formatted list of cluster events, as stored in the repmgr.events table. Usage Output is in reverse chronological order, and can be filtered with the following options: --all: outputs all entries --limit: set the maximum number of entries to output (default: 20) --node-id: restrict entries to node with this ID --node-name: restrict entries to node with this name --event: filter specific event (see for a full list) The "Details" column can be omitted by providing --compact. Output format --csv: generate output in CSV format. Note that the Details column will currently not be emitted in CSV format. Example $ repmgr -f /etc/repmgr.conf cluster event --event=standby_register Node ID | Name | Event | OK | Timestamp | Details ---------+-------+------------------+----+---------------------+------------------------------------------------------- 3 | node3 | standby_register | t | 2019-04-16 10:59:59 | standby registration succeeded; upstream node ID is 1 2 | node2 | standby_register | t | 2019-04-16 10:59:57 | standby registration succeeded; upstream node ID is 1 repmgr-5.3.1/doc/repmgr-cluster-matrix.xml000066400000000000000000000112161420262710000205530ustar00rootroot00000000000000 repmgr cluster matrix repmgr cluster matrix repmgr cluster matrix runs repmgr cluster show on each node and summarizes output Description repmgr cluster matrix runs repmgr cluster show on each node and arranges the results in a matrix, recording success or failure. repmgr cluster matrix requires a valid repmgr.conf file on each node. Additionally, passwordless ssh connections are required between all nodes. Example Example 1 (all nodes up): $ repmgr -f /etc/repmgr.conf cluster matrix Name | Id | 1 | 2 | 3 -------+----+----+----+---- node1 | 1 | * | * | * node2 | 2 | * | * | * node3 | 3 | * | * | * Example 2 (node1 and node2 up, node3 down): $ repmgr -f /etc/repmgr.conf cluster matrix Name | Id | 1 | 2 | 3 -------+----+----+----+---- node1 | 1 | * | * | x node2 | 2 | * | * | x node3 | 3 | ? | ? | ? Each row corresponds to one server, and indicates the result of testing an outbound connection from that server. Since node3 is down, all the entries in its row are filled with ?, meaning that there we cannot test outbound connections. The other two nodes are up; the corresponding rows have x in the column corresponding to node3, meaning that inbound connections to that node have failed, and * in the columns corresponding to node1 and node2, meaning that inbound connections to these nodes have succeeded. Example 3 (all nodes up, firewall dropping packets originating from node1 and directed to port 5432 on node3) - running repmgr cluster matrix from node1 gives the following output: $ repmgr -f /etc/repmgr.conf cluster matrix Name | Id | 1 | 2 | 3 -------+----+----+----+---- node1 | 1 | * | * | x node2 | 2 | * | * | * node3 | 3 | ? | ? | ? Note this may take some time depending on the connect_timeout setting in the node conninfo strings; default is 1 minute which means without modification the above command would take around 2 minutes to run; see comment elsewhere about setting connect_timeout) The matrix tells us that we cannot connect from node1 to node3, and that (therefore) we don't know the state of any outbound connection from node3. In this case, the command will produce a more useful result. Exit codes One of the following exit codes will be emitted by repmgr cluster matrix: The check completed successfully and all nodes are reachable. One or more nodes could not be accessed via SSH. PostgreSQL on one or more nodes could not be reached. This error code overrides . repmgr-5.3.1/doc/repmgr-cluster-show.xml000066400000000000000000000217311420262710000202320ustar00rootroot00000000000000 repmgr cluster show repmgr cluster show repmgr cluster show display information about each registered node in the replication cluster Description Displays information about each registered node in the replication cluster. This command polls each registered server and shows its role (primary / standby) and status. It polls each server directly and can be run on any node in the cluster; this is also useful when analyzing connectivity from a particular node. For PostgreSQL 9.6 and later, the output will also contain the node's current timeline ID. Node availability is tested by connecting from the node where repmgr cluster show is executed, and does not necessarily imply the node is down. See and to get better overviews of connections between nodes. Execution This command requires either a valid repmgr.conf file or a database connection string to one of the registered nodes; no additional arguments are needed. To show database connection errors when polling nodes, run the command in --verbose mode. Example $ repmgr -f /etc/repmgr.conf cluster show ID | Name | Role | Status | Upstream | Location | Priority | Timeline | Connection string ----+-------+---------+-----------+----------+----------+----------+----------+----------------------------------------- 1 | node1 | primary | * running | | default | 100 | 1 | host=db_node1 dbname=repmgr user=repmgr 2 | node2 | standby | running | node1 | default | 100 | 1 | host=db_node2 dbname=repmgr user=repmgr 3 | node3 | standby | running | node1 | default | 100 | 1 | host=db_node3 dbname=repmgr user=repmgr 4 | node4 | standby | running | node1 | default | 100 | 1 | host=db_node4 dbname=repmgr user=repmgr 5 | node5 | witness | * running | node1 | default | 0 | n/a | host=db_node5 dbname=repmgr user=repmgr Notes The column Role shows the expected server role according to the &repmgr; metadata. Status shows whether the server is running or unreachable. If the node has an unexpected role not reflected in the &repmgr; metadata, e.g. a node was manually promoted to primary, this will be highlighted with an exclamation mark. If a connection to the node cannot be made, this will be highlighted with a question mark. Note that the node will only be shown as ? unreachable if a connection is not possible at network level; if the PostgreSQL instance on the node is pingable but not accepting connections, it will be shown as ? running. In the following example, executed on node3, node1 is not reachable at network level and assumed to be down; node2 has been promoted to primary (but node3 is not attached to it, and its metadata has not yet been updated); node4 is running but rejecting connections (from node3 at least). ID | Name | Role | Status | Upstream | Location | Priority | Timeline | Connection string ----+-------+---------+----------------------+----------+----------+----------+----------+---------------------------------------------------- 1 | node1 | primary | ? unreachable | | default | 100 | | host=db_node1 dbname=repmgr user=repmgr 2 | node2 | standby | ! running as primary | ? node1 | default | 100 | 2 | host=db_node2 dbname=repmgr user=repmgr 3 | node3 | standby | running | ? node1 | default | 100 | 1 | host=db_node3 dbname=repmgr user=repmgr 4 | node4 | standby | ? running | ? node1 | default | 100 | | host=db_node4 dbname=repmgr user=repmgr WARNING: following issues were detected - unable to connect to node "node1" (ID: 1) - node "node1" (ID: 1) is registered as an active primary but is unreachable - node "node2" (ID: 2) is registered as standby but running as primary - unable to connect to node "node2" (ID: 2)'s upstream node "node1" (ID: 1) - unable to determine if node "node2" (ID: 2) is attached to its upstream node "node1" (ID: 1) - unable to connect to node "node3" (ID: 3)'s upstream node "node1" (ID: 1) - unable to determine if node "node3" (ID: 3) is attached to its upstream node "node1" (ID: 1) - unable to connect to node "node4" (ID: 4) HINT: execute with --verbose option to see connection error messages To diagnose connection issues, execute repmgr cluster show with the option; this will display the error message for each failed connection attempt. Use and to diagnose connection issues across the whole replication cluster. Options repmgr cluster show accepts an optional parameter --csv, which outputs the replication cluster's status in a simple CSV format, suitable for parsing by scripts, e.g.: $ repmgr -f /etc/repmgr.conf cluster show --csv 1,-1,-1 2,0,0 3,0,1 The columns have following meanings: node ID availability (0 = available, -1 = unavailable) recovery state (0 = not in recovery, 1 = in recovery, -1 = unknown) Suppress display of the conninfo column. Suppress warnings about connection issues. Display the full text of any database connection error messages Exit codes One of the following exit codes will be emitted by repmgr cluster show: No issues were detected. An issue was encountered while attempting to retrieve &repmgr; metadata. &repmgr; was unable to connect to the local PostgreSQL instance. One or more issues were detected with the replication configuration, e.g. a node was not in its expected state. See also , , repmgr-5.3.1/doc/repmgr-daemon-start.xml000066400000000000000000000146501420262710000201730ustar00rootroot00000000000000 repmgr daemon start repmgrd starting repmgr daemon start repmgr daemon start Start the &repmgrd; daemon on the local node Description This command starts the &repmgrd; service on the local node. By default, &repmgr; will wait for up to 15 seconds to confirm that &repmgrd; started. This behaviour can be overridden by specifying a different value using the option, or disabled altogether with the option. The repmgr.conf parameter repmgrd_service_start_command must be set for repmgr daemon start to work; see section for details. Options Check prerequisites but don't actually attempt to start &repmgrd;. This action will output the command which would be executed. Wait for the specified number of seconds to confirm that &repmgrd; started successfully. Note that providing is the equivalent of . Don't wait to confirm that &repmgrd; started successfully. This is equivalent to providing . Configuration file settings The following parameter in repmgr.conf is relevant to repmgr daemon start: repmgrd_service_start_command with "repmgr daemon start" repmgr daemon start will execute the command defined by the repmgrd_service_start_command parameter in repmgr.conf. This must be set to a shell command which will start &repmgrd;; if &repmgr; was installed from a package, this will be the service command defined by the package. For more details see Appendix: &repmgr; package details. If &repmgr; was installed from a system package, and you do not configure repmgrd_service_start_command to an appropriate service command, this may result in the system becoming confused about the state of the &repmgrd; service; this is particularly the case with systemd. Exit codes One of the following exit codes will be emitted by repmgr daemon start: The &repmgrd; start command (defined in repmgrd_service_start_command) was successfully executed. If the option was provided, &repmgr; will confirm that &repmgrd; has actually started up. repmgrd_service_start_command is not defined in repmgr.conf. &repmgr; was unable to connect to the local PostgreSQL node. PostgreSQL must be running before &repmgrd; can be started. Additionally, unless the option was provided, &repmgr; needs to be able to connect to the local PostgreSQL node to determine the state of &repmgrd;. The &repmgrd; start command (defined in repmgrd_service_start_command) was not successfully executed. This can also mean that &repmgr; was unable to confirm whether &repmgrd; successfully started (unless the option was provided). See also , , , , repmgr-5.3.1/doc/repmgr-daemon-stop.xml000066400000000000000000000146221420262710000200220ustar00rootroot00000000000000 repmgr daemon stop repmgrd stopping repmgr daemon stop repmgr daemon stop Stop the &repmgrd; daemon on the local node Description This command stops the &repmgrd; daemon on the local node. By default, &repmgr; will wait for up to 15 seconds to confirm that &repmgrd; stopped. This behaviour can be overridden by specifying a different value using the option, or disabled altogether with the option. If PostgreSQL is not running on the local node, under some circumstances &repmgr; may not be able to confirm if &repmgrd; has actually stopped. The repmgr.conf parameter repmgrd_service_stop_command must be set for repmgr daemon stop to work; see section for details. Configuration repmgr daemon stop will execute the command defined by the repmgrd_service_stop_command parameter in repmgr.conf. This must be set to a shell command which will stop &repmgrd;; if &repmgr; was installed from a package, this will be the service command defined by the package. For more details see Appendix: &repmgr; package details. If &repmgr; was installed from a system package, and you do not configure repmgrd_service_stop_command to an appropriate service command, this may result in the system becoming confused about the state of the &repmgrd; service; this is particularly the case with systemd. Options Check prerequisites but don't actually attempt to stop &repmgrd;. This action will output the command which would be executed. Wait for the specified number of seconds to confirm that &repmgrd; stopped successfully. Note that providing is the equivalent of . Don't wait to confirm that &repmgrd; stopped successfully. This is equivalent to providing . Configuration file settings The following parameter in repmgr.conf is relevant to repmgr daemon stop: repmgrd_service_stop_command with "repmgr daemon stop" repmgr daemon stop will execute the command defined by the repmgrd_service_stop_command parameter in repmgr.conf. This must be set to a shell command which will stop &repmgrd;; if &repmgr; was installed from a package, this will be the service command defined by the package. For more details see Appendix: &repmgr; package details. If &repmgr; was installed from a system package, and you do not configure repmgrd_service_stop_command to an appropriate service command, this may result in the system becoming confused about the state of the &repmgrd; service; this is particularly the case with systemd. Exit codes One of the following exit codes will be emitted by repmgr daemon stop: &repmgrd; could be stopped. repmgrd_service_stop_command is not defined in repmgr.conf. &repmgrd; could not be stopped. See also , , , , repmgr-5.3.1/doc/repmgr-node-check.xml000066400000000000000000000224201420262710000175670ustar00rootroot00000000000000 repmgr node check repmgr node check repmgr node check performs some health checks on a node from a replication perspective Description Performs some health checks on a node from a replication perspective. This command must be run on the local node. Currently &repmgr; performs health checks on physical replication slots only, with the aim of warning about streaming replication standbys which have become detached and the associated risk of uncontrolled WAL file growth. Example Execution on the primary server: $ repmgr -f /etc/repmgr.conf node check Node "node1": Server role: OK (node is primary) Replication lag: OK (N/A - node is primary) WAL archiving: OK (0 pending files) Upstream connection: OK (N/A - is primary) Downstream servers: OK (2 of 2 downstream nodes attached) Replication slots: OK (node has no physical replication slots) Missing replication slots: OK (node has no missing physical replication slots) Configured data directory: OK (configured "data_directory" is "/var/lib/postgresql/data") Execution on a standby server: $ repmgr -f /etc/repmgr.conf node check Node "node2": Server role: OK (node is standby) Replication lag: OK (0 seconds) WAL archiving: OK (0 pending archive ready files) Upstream connection: OK (node "node2" (ID: 2) is attached to expected upstream node "node1" (ID: 1)) Downstream servers: OK (this node has no downstream nodes) Replication slots: OK (node has no physical replication slots) Missing physical replication slots: OK (node has no missing physical replication slots) Configured data directory: OK (configured "data_directory" is "/var/lib/postgresql/data") Individual checks Each check can be performed individually by supplying an additional command line parameter, e.g.: $ repmgr node check --role OK (node is primary) Parameters for individual checks are as follows: : checks if the node has the expected role : checks if the node is lagging by more than replication_lag_warning or replication_lag_critical : checks for WAL files which have not yet been archived, and returns WARNING or CRITICAL if the number exceeds archive_ready_warning or archive_ready_critical respectively. : checks that the expected downstream nodes are attached : checks that the node is attached to its expected upstream : checks there are no inactive physical replication slots : checks there are no missing physical replication slots : checks the data directory configured in repmgr.conf matches the actual data directory. This check is not directly related to replication, but is useful to verify &repmgr; is correctly configured. repmgrd A separate check is available to verify whether &repmgrd; is running, This is not included in the general output, as this does not per-se constitute a check of the node's replication status. : checks whether &repmgrd; is running. If &repmgrd; is running but paused, status 1 (WARNING) is returned. Additional checks Several checks are provided for diagnostic purposes and are not included in the general output: : checks if &repmgr; can connect to the database on the local node. This option is particularly useful in combination with SSH, as it can be used to troubleshoot connection issues encountered when &repmgr; is executed remotely (e.g. during a switchover operation). : checks if the file containing replication configuration (PostgreSQL 12 and later: postgresql.auto.conf; PostgreSQL 11 and earlier: recovery.conf) is owned by the same user who owns the data directory. Incorrect ownership of these files (e.g. if they are owned by root) will cause operations which need to update the replication configuration (e.g. repmgr standby follow or repmgr standby promote) to fail. Connection options /: connect as the named superuser instead of the &repmgr; user Output format : generate output in CSV format (not available for individual checks) : generate output in a Nagios-compatible format (for individual checks only) Exit codes When executing repmgr node check with one of the individual checks listed above, &repmgr; will emit one of the following Nagios-style exit codes (even if is not supplied): 0: OK 1: WARNING 2: ERROR 3: UNKNOWN One of the following exit codes will be emitted by repmgr status check if no individual check was specified. No issues were detected. One or more issues were detected. See also , repmgr-5.3.1/doc/repmgr-node-rejoin.xml000066400000000000000000000475461420262710000200200ustar00rootroot00000000000000 repmgr node rejoin repmgr node rejoin repmgr node rejoin rejoin a dormant (stopped) node to the replication cluster Description Enables a dormant (stopped) node to be rejoined to the replication cluster. This can optionally use pg_rewind to re-integrate a node which has diverged from the rest of the cluster, typically a failed primary. Note that repmgr node rejoin can only be used to attach a standby to the current primary, not another standby. If the node is running and needs to be attached to the current primary, use . Note can only be used for standbys which have not diverged from the rest of the cluster. Usage repmgr node rejoin -d '$conninfo' where $conninfo is the PostgreSQL conninfo string of the current primary node (or that of any reachable node in the cluster, but not the local node). This is so that &repmgr; can fetch up-to-date information about the current state of the cluster. repmgr.conf for the stopped node *must* be supplied explicitly if not otherwise available. Options Check prerequisites but don't actually execute the rejoin. Execute pg_rewind. It is only necessary to provide the pg_rewind path if using PostgreSQL 9.4, and pg_rewind is not installed in the PostgreSQL bin directory. comma-separated list of configuration files to retain after executing pg_rewind. Currently pg_rewind will overwrite the local node's configuration files with the files from the source node, so it's advisable to use this option to ensure they are kept. Directory to temporarily store configuration files specified with ; default: /tmp. Don't wait for the node to rejoin cluster. If this option is supplied, &repmgr; will restart the node but not wait for it to connect to the primary. Configuration file settings node_rejoin_timeout: the maximum length of time (in seconds) to wait for the node to reconnect to the replication cluster (defaults to the value set in standby_reconnect_timeout, 60 seconds). Note that standby_reconnect_timeout must be set to a value equal to or greater than node_rejoin_timeout. Event notifications A node_rejoin event notification will be generated. Exit codes One of the following exit codes will be emitted by repmgr node rejoin: The node rejoin succeeded; or if was provided, no issues were detected which would prevent the node rejoin. A configuration issue was detected which prevented &repmgr; from continuing with the node rejoin. The node could not be restarted. The node rejoin operation failed. Notes Currently repmgr node rejoin can only be used to attach a standby to the current primary, not another standby. The node's PostgreSQL instance must have been shut down cleanly. If this was not the case, it will need to be started up until it has reached a consistent recovery point, then shut down cleanly. In PostgreSQL 13 and later, this will be done automatically if the is provided (even if an actual rewind is not necessary). With PostgreSQL 12 and earlier, PostgreSQL will need to be started and shut down manually; see below for the best way to do this. If PostgreSQL is started in single-user mode and input is directed from /dev/null/, it will perform recovery then immediately quit, and will then be in a state suitable for use by pg_rewind. rm -f /var/lib/pgsql/data/recovery.conf postgres --single -D /var/lib/pgsql/data/ < /dev/null Note that standby.signal (PostgreSQL 11 and earlier: recovery.conf) must be removed from the data directory for PostgreSQL to be able to start in single user mode. Using <command>pg_rewind</command> pg_rewind using with "repmgr node rejoin" repmgr node rejoin can optionally use pg_rewind to re-integrate a node which has diverged from the rest of the cluster, typically a failed primary. pg_rewind is available in PostgreSQL 9.5 and later as part of the core distribution, and can be installed from external sources for PostgreSQL 9.4. pg_rewind requires that either wal_log_hints is enabled, or that data checksums were enabled when the cluster was initialized. See the pg_rewind documentation for details. We strongly recommend familiarizing yourself with pg_rewind before attempting to use it with &repmgr;, as while it is an extremely useful tool, it is not a "magic bullet" which can resolve all problematic replication situations. A typical use-case for pg_rewind is when a scenario like the following is encountered: $ repmgr node rejoin -f /etc/repmgr.conf -d 'host=node3 dbname=repmgr user=repmgr' \ --force-rewind --config-files=postgresql.local.conf,postgresql.conf --verbose --dry-run NOTICE: rejoin target is node "node3" (node ID: 3) INFO: replication connection to the rejoin target node was successful INFO: local and rejoin target system identifiers match DETAIL: system identifier is 6652184002263212600 ERROR: this node cannot attach to rejoin target node 3 DETAIL: rejoin target server's timeline 2 forked off current database system timeline 1 before current recovery point 0/610D710 HINT: use --force-rewind to execute pg_rewind Here, node3 was promoted to a primary while the local node was still attached to the previous primary; this can potentially happen during e.g. a network split. pg_rewind can re-sync the local node with node3, removing the need for a full reclone. To have repmgr node rejoin use pg_rewind, pass the command line option --force-rewind, which will tell &repmgr; to execute pg_rewind to ensure the node can be rejoined successfully. <command>pg_rewind</command> and configuration file retention pg_rewind configuration file retention Be aware that if pg_rewind is executed and actually performs a rewind operation, any configuration files in the PostgreSQL data directory will be overwritten with those from the source server. To prevent this happening, provide a comma-separated list of files to retain using the command line option; the specified files will be archived in a temporary directory (whose parent directory can be specified with , default: /tmp) and restored once the rewind operation is complete. Example using <command>repmgr node rejoin</command> and <command>pg_rewind</command> pg_rewind configuration file retention Example, first using , then actually executing the node rejoin command. $ repmgr node rejoin -f /etc/repmgr.conf -d 'host=node3 dbname=repmgr user=repmgr' \ --config-files=postgresql.local.conf,postgresql.conf --verbose --force-rewind --dry-run NOTICE: rejoin target is node "node3" (node ID: 3) INFO: replication connection to the rejoin target node was successful INFO: local and rejoin target system identifiers match DETAIL: system identifier is 6652460429293670710 NOTICE: pg_rewind execution required for this node to attach to rejoin target node 3 DETAIL: rejoin target server's timeline 2 forked off current database system timeline 1 before current recovery point 0/610D710 INFO: prerequisites for using pg_rewind are met INFO: file "postgresql.local.conf" would be copied to "/tmp/repmgr-config-archive-node2/postgresql.local.conf" INFO: file "postgresql.replication-setup.conf" would be copied to "/tmp/repmgr-config-archive-node2/postgresql.replication-setup.conf" INFO: pg_rewind would now be executed DETAIL: pg_rewind command is: pg_rewind -D '/var/lib/postgresql/data' --source-server='host=node3 dbname=repmgr user=repmgr' INFO: prerequisites for executing NODE REJOIN are met If is used with the option, this checks the prerequisites for using pg_rewind, but is not an absolute guarantee that actually executing pg_rewind will succeed. See also section below. $ repmgr node rejoin -f /etc/repmgr.conf -d 'host=node3 dbname=repmgr user=repmgr' \ --config-files=postgresql.local.conf,postgresql.conf --verbose --force-rewind NOTICE: pg_rewind execution required for this node to attach to rejoin target node 3 DETAIL: rejoin target server's timeline 2 forked off current database system timeline 1 before current recovery point 0/610D710 NOTICE: executing pg_rewind DETAIL: pg_rewind command is "pg_rewind -D '/var/lib/postgresql/data' --source-server='host=node3 dbname=repmgr user=repmgr'" NOTICE: 2 files copied to /var/lib/postgresql/data NOTICE: setting node 2's upstream to node 3 NOTICE: starting server using "pg_ctl -l /var/log/postgres/startup.log -w -D '/var/lib/pgsql/data' start" NOTICE: NODE REJOIN successful DETAIL: node 2 is now attached to node 3 Caveats when using <command>repmgr node rejoin</command> repmgr node rejoin caveats repmgr node rejoin attempts to determine whether it will succeed by comparing the timelines and relative WAL positions of the local node (rejoin candidate) and primary (rejoin target). This is particularly important if planning to use pg_rewind, which currently (as of PostgreSQL 12) may appear to succeed (or indicate there is no action needed) but potentially allow an impossible action, such as trying to rejoin a standby to a primary which is behind the standby. &repmgr; will prevent this situation from occurring. Currently it is not possible to detect a situation where the rejoin target is a standby which has been "promoted" by removing recovery.conf (PostgreSQL 12 and later: standby.signal) and restarting it. In this case there will be no information about the point the rejoin target diverged from the current standby; the rejoin operation will fail and the current standby's PostgreSQL log will contain entries with the text "record with incorrect prev-link". In PostgreSQL 9.5 and earlier, it is not possible to use pg_rewind to attach to a target node with a lower timeline than the local node. We strongly recommend running repmgr node rejoin with the option first. Additionally it might be a good idea to execute the pg_rewind command displayed by &repmgr; with the pg_rewind option. Note that pg_rewind does not indicate that it is running in mode. In all current PostgreSQL versions (as of September 2020), pg_rewind contains a corner-case bug which affects standbys in a very specific situation. This situation occurs when a standby was shut down before its primary node, and an attempt is made to attach this standby to another primary in the same cluster (following a "split brain" situation where the standby was connected to the wrong primary). In this case, &repmgr; will correctly determine that pg_rewind should be executed, however pg_rewind incorrectly decides that no action is necessary. In this situation, &repmgr; will report something like: NOTICE: pg_rewind execution required for this node to attach to rejoin target node 1 DETAIL: rejoin target server's timeline 3 forked off current database system timeline 2 before current recovery point 0/7019C10 but when executed, pg_rewind will report: pg_rewind: servers diverged at WAL location 0/7015540 on timeline 2 pg_rewind: no rewind required and if an attempt is made to attach the standby to the new primary, PostgreSQL logs on the standby will contain errors like: [2020-09-07 15:01:41 UTC] LOG: 00000: replication terminated by primary server [2020-09-07 15:01:41 UTC] DETAIL: End of WAL reached on timeline 2 at 0/7015540. [2020-09-07 15:01:41 UTC] LOG: 00000: new timeline 3 forked off current database system timeline 2 before current recovery point 0/7019C10 Currently it is not possible to resolve this situation using pg_rewind. A patch has been successfully submitted and will be included the next PostgreSQL minor release round, scheduled for February 2021. As a workaround, start the primary server the standby was previously attached to, and ensure the standby can be attached to it. If pg_rewind was actually executed, it will have copied in the .history file from the target primary server; this must be removed. repmgr node rejoin can then be used to attach the standby to the original primary. Ensure any changes pending on the primary have propagated to the standby. Then shut down the primary server first, before shutting down the standby. It should then be possible to use repmgr node rejoin to attach the standby to the new primary. See also repmgr-5.3.1/doc/repmgr-node-service.xml000066400000000000000000000115671420262710000201640ustar00rootroot00000000000000 repmgr node service repmgr node service repmgr node service show or execute the system service command to stop/start/restart/reload/promote a node Description Shows or executes the system service command to stop/start/restart/reload a node. This command is mainly meant for internal &repmgr; usage, but is useful for confirming the command configuration. Options Log the steps which would be taken, including displaying the command which would be executed. The action to perform. One of start, stop, restart, reload or promote. If the parameter is provided together with , the command which would be executed will be printed. List all configured commands. If the parameter is provided together with , the command which would be executed for that particular action will be printed. Issue a CHECKPOINT before stopping or restarting the node. Note that a superuser connection is required to be able to execute the CHECKPOINT command. / Connect as the named superuser instead of the normal &repmgr; user. Exit codes One of the following exit codes will be emitted by repmgr node service: No issues were detected. Execution of the system service command failed. Examples See what action would be taken for a restart: [postgres@node1 ~]$ repmgr -f /etc/repmgr/12/repmgr.conf node service --action=restart --checkpoint --dry-run INFO: a CHECKPOINT would be issued here INFO: would execute server command "sudo service postgresql-12 restart" Restart the PostgreSQL instance: [postgres@node1 ~]$ repmgr -f /etc/repmgr/12/repmgr.conf node service --action=restart --checkpoint NOTICE: issuing CHECKPOINT DETAIL: executing server command "sudo service postgresql-12 restart" Redirecting to /bin/systemctl restart postgresql-12.service List all commands: [postgres@node1 ~]$ repmgr -f /etc/repmgr/12/repmgr.conf node service --list-actions Following commands would be executed for each action: start: "sudo service postgresql-12 start" stop: "sudo service postgresql-12 stop" restart: "sudo service postgresql-12 restart" reload: "sudo service postgresql-12 reload" promote: "/usr/pgsql-12/bin/pg_ctl -w -D '/var/lib/pgsql/12/data' promote" List a single command: [postgres@node1 ~]$ repmgr -f /etc/repmgr/12/repmgr.conf node service --list-actions --action=promote /usr/pgsql-12/bin/pg_ctl -w -D '/var/lib/pgsql/12/data' promote repmgr-5.3.1/doc/repmgr-node-status.xml000066400000000000000000000044441420262710000200430ustar00rootroot00000000000000 repmgr node status repmgr node status repmgr node status show overview of a node's basic information and replication status Description Displays an overview of a node's basic information and replication status. This command must be run on the local node. Example $ repmgr -f /etc/repmgr.conf node status Node "node1": PostgreSQL version: 10beta1 Total data size: 30 MB Conninfo: host=node1 dbname=repmgr user=repmgr connect_timeout=2 Role: primary WAL archiving: off Archive command: (none) Replication connections: 2 (of maximal 10) Replication slots: 0 (of maximal 10) Replication lag: n/a Output format --csv: generate output in CSV format Exit codes One of the following exit codes will be emitted by repmgr node status: No issues were detected. One or more issues were detected. See also See to diagnose issues and for an overview of all nodes in the cluster. repmgr-5.3.1/doc/repmgr-primary-register.xml000066400000000000000000000076571420262710000211130ustar00rootroot00000000000000 repmgr primary register repmgr primary register repmgr primary register initialise a repmgr installation and register the primary node Description repmgr primary register registers a primary node in a streaming replication cluster, and configures it for use with &repmgr;, including installing the &repmgr; extension. This command needs to be executed before any standby nodes are registered. &repmgr; will attempt to install the &repmgr; extension as part of this command, however this will fail if the repmgr user is not a superuser. It's possible to install the &repmgr; extension manually before executing repmgr primary register; in this case &repmgr; will detect the presence of the extension and skip that step. Execution Execute with the option to check what would happen without actually registering the primary. If providing the configuration file location with , avoid using a relative path, as &repmgr; stores the configuration file location in the repmgr metadata for use when &repmgr; is executed remotely (e.g. during ). &repmgr; will attempt to convert the a relative path into an absolute one, but this may not be the same as the path you would explicitly provide (e.g. ./repmgr.conf might be converted to /path/to/./repmgr.conf, whereas you'd normally write /path/to/repmgr.conf). repmgr master register can be used as an alias for repmgr primary register. User permission requirements The repmgr user must be a superuser in order for &repmgr; to be able to install the repmgr extension. If this is not the case, the repmgr extension can be installed manually before executing repmgr primary register. A future &repmgr; release will enable the provision of a name for the installation of the extension. Options Check prerequisites but don't actually register the primary. , Overwrite an existing node record Event notifications Following event notifications will be generated: cluster_created primary_register repmgr-5.3.1/doc/repmgr-primary-unregister.xml000066400000000000000000000046241420262710000214450ustar00rootroot00000000000000 repmgr primary unregister repmgr primary unregister repmgr primary unregister unregister an inactive primary node Description repmgr primary unregister unregisters an inactive primary node from the &repmgr; metadata. This is typically when the primary has failed and is being removed from the cluster after a new primary has been promoted. Execution repmgr primary unregister can be run on any active &repmgr; node, with the ID of the node to unregister passed as . Execute with the --dry-run option to check what would happen without actually unregistering the node. repmgr master unregister can be used as an alias for repmgr primary unregister. Options Check prerequisites but don't actually unregister the primary. ID of the inactive primary to be unregistered. Forcibly unregister the node if it is registered as an active primary, as long as it has no registered standbys; or if it is registered as a primary but running as a standby. Event notifications A primary_unregister event notification will be generated. repmgr-5.3.1/doc/repmgr-service-pause.xml000066400000000000000000000073071420262710000203510ustar00rootroot00000000000000 repmgr service pause repmgrd pausing repmgr service pause repmgr service pause Instruct all &repmgrd; instances in the replication cluster to pause failover operations Description This command can be run on any active node in the replication cluster to instruct all running &repmgrd; instances to "pause" themselves, i.e. take no action (such as promoting themselves or following a new primary) if a failover event is detected. This functionality is useful for performing maintenance operations, such as switchovers or upgrades, which might otherwise trigger a failover if &repmgrd; is running normally. It's important to wait a few seconds after restarting PostgreSQL on any node before running repmgr service pause, as the &repmgrd; instance on the restarted node will take a second or two before it has updated its status. will instruct all previously paused &repmgrd; instances to resume normal failover operation. Prerequisites PostgreSQL must be accessible on all nodes (using the conninfo string shown by repmgr cluster show) from the node where repmgr service pause is executed. Execution repmgr service pause can be executed on any active node in the replication cluster. A valid repmgr.conf file is required. It will have no effect on previously paused nodes. Example $ repmgr -f /etc/repmgr.conf service pause NOTICE: node 1 (node1) paused NOTICE: node 2 (node2) paused NOTICE: node 3 (node3) paused Options Check if nodes are reachable but don't pause &repmgrd;. Exit codes One of the following exit codes will be emitted by repmgr service unpause: &repmgrd; could be paused on all nodes. &repmgrd; could not be paused on one or mode nodes. See also , , , , repmgr-5.3.1/doc/repmgr-service-status.xml000066400000000000000000000164041420262710000205550ustar00rootroot00000000000000 repmgr service status repmgrd displaying service status repmgr service status repmgr service status display information about the status of &repmgrd; on each node in the cluster Description This command provides an overview over all active nodes in the cluster and the state of each node's &repmgrd; instance. It can be used to check the result of and operations. Prerequisites PostgreSQL should be accessible on all nodes (using the conninfo string shown by repmgr cluster show) from the node where repmgr service status is executed. Execution repmgr service status can be executed on any active node in the replication cluster. A valid repmgr.conf file is required. If a node is not accessible, or PostgreSQL itself is not running on the node, &repmgr; will not be able to determine the status of that node's &repmgrd; instance, and "n/a" will be displayed in the node's repmgrd column. After restarting PostgreSQL on any node, the &repmgrd; instance will take a second or two before it is able to update its status. Until then, &repmgrd; will be shown as not running. Examples &repmgrd; running normally on all nodes: $ repmgr -f /etc/repmgr.conf service status ID | Name | Role | Status | Upstream | repmgrd | PID | Paused? | Upstream last seen ----+-------+---------+-----------+----------+---------+-------+---------+-------------------- 1 | node1 | primary | * running | | running | 96563 | no | n/a 2 | node2 | standby | running | node1 | running | 96572 | no | 1 second(s) ago 3 | node3 | standby | running | node1 | running | 96584 | no | 0 second(s) ago &repmgrd; paused on all nodes (using ): $ repmgr -f /etc/repmgr.conf service status ID | Name | Role | Status | Upstream | repmgrd | PID | Paused? | Upstream last seen ----+-------+---------+-----------+----------+---------+-------+---------+-------------------- 1 | node1 | primary | * running | | running | 96563 | yes | n/a 2 | node2 | standby | running | node1 | running | 96572 | yes | 1 second(s) ago 3 | node3 | standby | running | node1 | running | 96584 | yes | 0 second(s) ago &repmgrd; not running on one node: $ repmgr -f /etc/repmgr.conf service status ID | Name | Role | Status | Upstream | repmgrd | PID | Paused? | Upstream last seen ----+-------+---------+-----------+----------+-------------+-------+---------+-------------------- 1 | node1 | primary | * running | | running | 96563 | yes | n/a 2 | node2 | standby | running | node1 | not running | n/a | n/a | n/a 3 | node3 | standby | running | node1 | running | 96584 | yes | 0 second(s) ago Options repmgr service status accepts an optional parameter --csv, which outputs the replication cluster's status in a simple CSV format, suitable for parsing by scripts, e.g.: $ repmgr -f /etc/repmgr.conf service status --csv 1,node1,primary,1,1,5722,1,100,-1,default 2,node2,standby,1,0,-1,1,100,1,default 3,node3,standby,1,1,5779,1,100,1,default The columns have following meanings: node ID node name node type (primary or standby) PostgreSQL server running (1 = running, 0 = not running) &repmgrd; running (1 = running, 0 = not running, -1 = unknown) &repmgrd; PID (-1 if not running or status unknown) &repmgrd; paused (1 = paused, 0 = not paused, -1 = unknown) &repmgrd; node priority interval in seconds since the node's upstream was last seen (this will be -1 if the value could not be retrieved, or the node is primary) node location Display additional information (location, priority) about the &repmgr; configuration. Display the full text of any database connection error messages. See also , , , , , repmgr-5.3.1/doc/repmgr-service-unpause.xml000066400000000000000000000065631420262710000207170ustar00rootroot00000000000000 repmgr service unpause repmgrd unpausing repmgr service unpause repmgr service unpause Instruct all &repmgrd; instances in the replication cluster to resume failover operations Description This command can be run on any active node in the replication cluster to instruct all running &repmgrd; instances to "unpause" (following a previous execution of ) and resume normal failover/monitoring operation. It's important to wait a few seconds after restarting PostgreSQL on any node before running repmgr service pause, as the &repmgrd; instance on the restarted node will take a second or two before it has updated its status. Prerequisites PostgreSQL must be accessible on all nodes (using the conninfo string shown by repmgr cluster show) from the node where repmgr service pause is executed. Execution repmgr service unpause can be executed on any active node in the replication cluster. A valid repmgr.conf file is required. It will have no effect on nodes which are not already paused. Example $ repmgr -f /etc/repmgr.conf service unpause NOTICE: node 1 (node1) unpaused NOTICE: node 2 (node2) unpaused NOTICE: node 3 (node3) unpaused Options Check if nodes are reachable but don't unpause &repmgrd;. Exit codes One of the following exit codes will be emitted by repmgr service unpause: &repmgrd; could be unpaused on all nodes. &repmgrd; could not be unpaused on one or mode nodes. See also , , , , repmgr-5.3.1/doc/repmgr-standby-clone.xml000066400000000000000000000410261420262710000203340ustar00rootroot00000000000000 repmgr standby clone cloning repmgr standby clone repmgr standby clone clone a PostgreSQL standby node from another PostgreSQL node Description repmgr standby clone clones a PostgreSQL node from another PostgreSQL node, typically the primary, but optionally from any other node in the cluster or from Barman. It creates the replication configuration required to attach the cloned node to the primary node (or another standby, if cascading replication is in use). repmgr standby clone does not start the standby, and after cloning a standby, the command repmgr standby register must be executed to notify &repmgr; of its existence. Handling configuration files Note that by default, all configuration files in the source node's data directory will be copied to the cloned node. Typically these will be postgresql.conf, postgresql.auto.conf, pg_hba.conf and pg_ident.conf. These may require modification before the standby is started. In some cases (e.g. on Debian or Ubuntu Linux installations), PostgreSQL's configuration files are located outside of the data directory and will not be copied by default. &repmgr; can copy these files, either to the same location on the standby server (provided appropriate directory and file permissions are available), or into the standby's data directory. This requires passwordless SSH access to the primary server. Add the option to the repmgr standby clone command; by default files will be copied to the same path as on the upstream server. Note that the user executing repmgr must have write access to those directories. To have the configuration files placed in the standby's data directory, specify --copy-external-config-files=pgdata, but note that any include directives in the copied files may need to be updated. When executing repmgr standby clone with the aand options, &repmgr; will check the SSH connection to the source node, but will not verify whether the files can actually be copied. During the actual clone operation, a check will be made before the database itself is cloned to determine whether the files can actually be copied; if any problems are encountered, the clone operation will be aborted, enabling the user to fix any issues before retrying the clone operation. For reliable configuration file management we recommend using a configuration management tool such as Ansible, Chef, Puppet or Salt. Customising replication configuration recovery.conf customising with "repmgr standby clone" replication configuration customising with "repmgr standby clone" By default, &repmgr; will create a minimal replication configuration containing following parameters: primary_conninfo primary_slot_name (if replication slots in use) For PostgreSQL 11 and earlier, these parameters will also be set: standby_mode (always 'on') recovery_target_timeline (always 'latest') The following additional parameters can be specified in repmgr.conf for inclusion in the replication configuration: restore_command archive_cleanup_command recovery_min_apply_delay We recommend using Barman to manage WAL file archiving. For more details on combining &repmgr; and Barman, in particular using restore_command to configure Barman as a backup source of WAL files, see . Managing WAL during the cloning process When initially cloning a standby, you will need to ensure that all required WAL files remain available while the cloning is taking place. To ensure this happens when using the default pg_basebackup method, &repmgr; will set pg_basebackup's --wal-method parameter to stream, which will ensure all WAL files generated during the cloning process are streamed in parallel with the main backup. Note that this requires two replication connections to be available (&repmgr; will verify sufficient connections are available before attempting to clone, and this can be checked before performing the clone using the --dry-run option). To override this behaviour, in repmgr.conf set pg_basebackup's --wal-method parameter to fetch: pg_basebackup_options='--wal-method=fetch' and ensure that wal_keep_segments (PostgreSQL 13 and later: wal_keep_size) is set to an appropriately high value. Note however that this is not a particularly reliable way of ensuring sufficient WAL is retained and is not recommended. See the pg_basebackup documentation for details. If using PostgreSQL 9.6 or earlier, replace --wal-method with --xlog-method. Placing WAL files into a different directory To ensure that WAL files are placed in a directory outside of the main data directory (e.g. to keep them on a separate disk for performance reasons), specify the location with (PostgreSQL 9.6 and earlier: ) in the repmgr.conf parameter , e.g.: pg_basebackup_options='--waldir=/path/to/wal-directory' This setting will also be honored by &repmgr; when cloning from Barman (&repmgr; 5.2 and later). Using a standby cloned by another method replication configuration generating for a standby cloned by another method recovery.conf generating for a standby cloned by another method &repmgr; supports standbys cloned by another method (e.g. using barman's barman recover command). To integrate the standby as a &repmgr; node, once the standby has been cloned, ensure the repmgr.conf file is created for the node, and that it has been registered using repmgr standby register. To register a standby which is not running, execute repmgr standby register --force and provide the connection details for the primary. See for more details. Then execute the command repmgr standby clone --replication-conf-only. This will create the recovery.conf file needed to attach the node to its upstream (in PostgreSQL 12 and later: append replication configuration to postgresql.auto.conf), and will also create a replication slot on the upstream node if required. The upstream node must be running so the correct replication configuration can be obtained. If the standby is running, the replication configuration will not be written unless the option is provided. Execute repmgr standby clone --replication-conf-only --dry-run to check the prerequisites for creating the recovery configuration, and display the configuration changes which would be made without actually making any changes. In PostgreSQL 13 and later, the PostgreSQL configuration must be reloaded for replication configuration changes to take effect. In PostgreSQL 12 and earlier, the PostgreSQL instance must be restarted for replication configuration changes to take effect. Options Connection string of the upstream node to use for cloning. Check prerequisites but don't actually clone the standby. If specified, the contents of the generated recovery configuration will be displayed but not written. Force fast checkpoint (not effective when cloning from Barman). Copy configuration files located outside the data directory on the source node to the same path on the standby (default) or to the PostgreSQL data directory. When using Barman, do not connect to upstream node. Set PostgreSQL configuration parameter to the provided value. This overrides any provided via repmgr.conf. For more details on this parameter, see: recovery_min_apply_delay. Remote system username for SSH operations (default: current local system username). Create recovery configuration for a previously cloned instance. In PostgreSQL 12 and later, the replication configuration will be written to postgresql.auto.conf. In PostgreSQL 11 and earlier, the replication configuration will be written to recovery.conf. User to make replication connections with (optional, not usually required). If the &repmgr; user is not a superuser, the name of a valid superuser must be provided with this option. primary_conninfo value to include in the recovery configuration when the intended upstream server does not yet exist. Note that &repmgr; may modify the provided value, in particular to set the correct application_name. ID of the upstream node to replicate from (optional, defaults to primary node) Verify a cloned node using the pg_verifybackup utility (PostgreSQL 13 and later). This option can currently only be used when cloning directly from an upstream node. Do not use Barman even if configured. Event notifications A standby_clone event notification will be generated. See also See for details about various aspects of cloning. repmgr-5.3.1/doc/repmgr-standby-follow.xml000066400000000000000000000230721420262710000205370ustar00rootroot00000000000000 repmgr standby follow repmgr standby follow repmgr standby follow attach a running standby to a new upstream node Description Attaches the standby ("follow candidate") to a new upstream node ("follow target"). Typically this will be the primary, but this command can also be used to attach the standby to another standby. This command requires a valid repmgr.conf file for the standby, either specified explicitly with -f/--config-file or located in a default location; no additional arguments are required. The standby node ("follow candidate") must be running. If the new upstream ("follow target") is not the primary, the cluster primary must be running and accessible from the standby node. To re-add an inactive node to the replication cluster, use . By default &repmgr; will attempt to attach the standby to the current primary. If is provided, &repmgr; will attempt to attach the standby to the specified node, which can be another standby. In PostgreSQL 12 and earlier, this command will force a restart of PostgreSQL on the standby node. In PostgreSQL 13 and later, by default this command will signal PostgreSQL to reload its configuration, which will cause PostgreSQL to follow the new upstream without a restart. If this behaviour is not desired for whatever reason, the configuration file parameter standby_follow_restart can be set true to always force a restart. repmgr standby follow will wait up to standby_follow_timeout seconds (default: 30) to verify the standby has actually connected to the new upstream node. If is set for the standby, it will not attach to the new upstream node until it has replayed available WAL. Conversely, if the standby is attached to an upstream standby which has set, the upstream standby's replay state may actually be behind that of its new downstream node. Example $ repmgr -f /etc/repmgr.conf standby follow INFO: setting node 3's primary to node 2 NOTICE: restarting server using "pg_ctl -l /var/log/postgres/startup.log -w -D '/var/lib/postgres/data' restart" waiting for server to shut down........ done server stopped waiting for server to start.... done server started NOTICE: STANDBY FOLLOW successful DETAIL: node 3 is now attached to node 2 Options Check prerequisites but don't actually follow a new upstream node. This will also verify whether the standby is capable of following the new upstream node. If a standby was turned into a primary by removing recovery.conf (PostgreSQL 12 and later: standby.signal), &repmgr; will not be able to determine whether that primary's timeline has diverged from the timeline of the standby ("follow candidate"). We recommend always to use repmgr standby promote to promote a standby to primary, as this will ensure that the new primary will perform a timeline switch (making it practical to check for timeline divergence) and also that &repmgr; metadata is updated correctly. Node ID of the new upstream node ("follow target"). If not provided, &repmgr; will attempt to follow the current primary node. Note that when using &repmgrd;, should always be configured; see Automatic failover configuration for details. Wait for a primary to appear. &repmgr; will wait for up to primary_follow_timeout seconds (default: 60 seconds) to verify that the standby is following the new primary. This value can be defined in repmgr.conf. Execution Execute with the --dry-run option to test the follow operation as far as possible, without actually changing the status of the node. Note that &repmgr; will first attempt to determine whether the standby ("follow candidate") is capable of following the new upstream node ("follow target"). If, for example, the new upstream node has diverged from this node's timeline, for example if the new upstream node was promoted to primary while this node was still attached to the original primary, it will not be possible to follow the new upstream node, and &repmgr; will emit an error message like this: ERROR: this node cannot attach to follow target node "node3" (ID 3) DETAIL: follow target server's timeline 2 forked off current database system timeline 1 before current recovery point 0/6108880 In this case, it may be possible to have this node follow the new upstream using repmgr node rejoin with the to execute pg_rewind. This does mean that transactions which exist on this node, but not the new upstream, will be lost. Exit codes One of the following exit codes will be emitted by repmgr standby follow: The follow operation succeeded; or if was provided, no issues were detected which would prevent the follow operation. A configuration issue was detected which prevented &repmgr; from continuing with the follow operation. The node could not be restarted. &repmgr; was unable to establish a database connection to one of the nodes. &repmgr; was unable to complete the follow command. Event notifications A standby_follow event notification will be generated. If provided, &repmgr; will substitute the placeholders %p with the node ID of the node being followed, %c with its conninfo string, and %a with its node name. See also repmgr-5.3.1/doc/repmgr-standby-promote.xml000066400000000000000000000302301420262710000207140ustar00rootroot00000000000000 repmgr standby promote repmgr standby promote repmgr standby promote promote a standby to a primary Description Promotes a standby to a primary if the current primary has failed. This command requires a valid repmgr.conf file for the standby, either specified explicitly with -f/--config-file or located in a default location; no additional arguments are required. If &repmgrd; is active, you must execute repmgr service pause (&repmgr; 4.2 - 4.4: repmgr service pause) to temporarily disable &repmgrd; while making any changes to the replication cluster. If the standby promotion succeeds, the server will not need to be restarted. However any other standbys will need to follow the new primary, and will need to be restarted to do this. Beginning with repmgr 4.4, the option can be used to have all other standbys (and a witness server, if in use) follow the new primary. If using &repmgrd;, when invoking repmgr standby promote (either directly via the , or in a script called via ), must not be included as a command line option for repmgr standby promote. In repmgr 4.3 and earlier, repmgr standby follow must be executed on each standby individually. &repmgr; will wait for up to promote_check_timeout seconds (default: 60) to verify that the standby has been promoted, and will check the promotion every promote_check_interval seconds (default: 1 second). Both values can be defined in repmgr.conf. In PostgreSQL 12 and earlier, if WAL replay is paused on the standby, and not all WAL files on the standby have been replayed, &repmgr; will not attempt to promote it. This is because if WAL replay is paused, PostgreSQL itself will not react to a promote command until WAL replay is resumed and all pending WAL has been replayed. This means attempting to promote PostgreSQL in this state will leave PostgreSQL in a condition where the promotion may occur at a unpredictable point in the future. Note that if the standby is in archive recovery, &repmgr; will not be able to determine if more WAL is pending replay, and will abort the promotion attempt if WAL replay is paused. This restriction does not apply to PostgreSQL 13 and later. Example $ repmgr -f /etc/repmgr.conf standby promote NOTICE: promoting standby to primary DETAIL: promoting server "node2" (ID: 2) using "pg_ctl -l /var/log/postgres/startup.log -w -D '/var/lib/postgres/data' promote" server promoting NOTICE: STANDBY PROMOTE successful DETAIL: server "node2" (ID: 2) was successfully promoted to primary User permission requirements pg_promote() (PostgreSQL 12 and later) From PostgreSQL 12, &repmgr; will attempt to use the built-in pg_promote() function to promote a standby to primary. By default, execution of pg_promote() is restricted to superusers. If the repmgr user does not have permission to execute pg_promote(), &repmgr; will fall back to using "pg_ctl promote". Execute repmgr standby promote with the to check whether the &repmgr; user has permission to execute pg_promote(). If the repmgr user is not a superuser, execution permission for this function can be granted with e.g.: GRANT EXECUTE ON FUNCTION pg_catalog.pg_promote TO repmgr Note that permissions are only effective for the database they are granted in, so this must be executed in the &repmgr; database to be effective. For more details on pg_promote(), see the PostgreSQL documentation. Options Check if this node can be promoted, but don't carry out the promotion. Have all sibling nodes (nodes formerly attached to the same upstream node as the promotion candidate) follow this node after it has been promoted. Note that a witness server, if in use, is also counted as a "sibling node" as it needs to be instructed to synchronise its metadata with the new primary. Do not provide this option when configuring &repmgrd;'s . Ignore warnings and continue anyway. This option is relevant in the following situations if was specified: If one or more sibling nodes was not reachable via SSH, the standby will be promoted anyway. If the promotion candidate has insufficient free walsenders to accommodate the standbys which will be attached to it, the standby will be promoted anyway. If replication slots are in use but the promotion candidate has insufficient free replication slots to accommodate the standbys which will be attached to it, the standby will be promoted anyway. Note that if the / option is used when any of the above situations is encountered, the onus is on the user to manually resolve any resulting issues. Configuration file settings The following parameters in repmgr.conf are relevant to the promote operation: promote_check_interval with "repmgr standby promote " promote_check_interval: interval (in seconds, default: 1 second) to wait between each check to determine whether the standby has been promoted. promote_check_timeout with "repmgr standby promote " promote_check_timeout: time (in seconds, default: 60 seconds) to wait to verify that the standby has been promoted before exiting with ERR_PROMOTION_FAIL. service_promote_command with "repmgr standby promote " service_promote_command: a command which will be executed instead of pg_ctl promote or (in PostgreSQL 12 and later) pg_promote(). This is intended for systems which provide a package-level promote command, such as Debian's pg_ctlcluster, to promote the PostgreSQL from standby to primary. Exit codes Following exit codes can be emitted by repmgr standby promote: The standby was successfully promoted to primary. &repmgr; was unable to connect to the local PostgreSQL node. PostgreSQL must be running before the node can be promoted. The node could not be promoted to primary for one of the following reasons: there is an existing primary node in the replication cluster the node is not a standby WAL replay is paused on the node execution of the PostgreSQL promote command failed Event notifications A standby_promote event notification will be generated. repmgr-5.3.1/doc/repmgr-standby-register.xml000066400000000000000000000170401420262710000210570ustar00rootroot00000000000000 repmgr standby register repmgr standby register repmgr standby register add a standby's information to the &repmgr; metadata Description repmgr standby register adds a standby's information to the &repmgr; metadata. This command needs to be executed to enable promote/follow operations and to allow &repmgrd; to work with the node. An existing standby can be registered using this command. Execute with the --dry-run option to check what would happen without actually registering the standby. If providing the configuration file location with -f/--config-file, avoid using a relative path, as &repmgr; stores the configuration file location in the repmgr metadata for use when &repmgr; is executed remotely (e.g. during ). &repmgr; will attempt to convert the a relative path into an absolute one, but this may not be the same as the path you would explicitly provide (e.g. ./repmgr.conf might be converted to /path/to/./repmgr.conf, whereas you'd normally write /path/to/repmgr.conf). Waiting for the the standby to start By default, &repmgr; will wait 30 seconds for the standby to become available before aborting with a connection error. This is useful when setting up a standby from a script, as the standby may not have fully started up by the time repmgr standby register is executed. To change the timeout, pass the desired value with the --wait-start option. A value of 0 will disable the timeout. The timeout will be ignored if -F/--force was provided. Waiting for the registration to propagate to the standby Depending on your environment and workload, it may take some time for the standby's node record to propagate from the primary to the standby. Some actions (such as starting &repmgrd;) require that the standby's node record is present and up-to-date to function correctly. By providing the option to the repmgr standby register command, &repmgr; will wait until the record is synchronised before exiting. An optional timeout (in seconds) can be added to this option (e.g. ). Registering an inactive node Under some circumstances you may wish to register a standby which is not yet running; this can be the case when using provisioning tools to create a complex replication cluster, or if the node was not cloned by &repmgr;. In this case, by using the option and providing the connection parameters to the primary server, the standby can be registered even if it has not yet been started. Connection parameters can either be provided either as a conninfo string (e.g. or as individual connection parameters (, , , etc.). Similarly, with cascading replication it may be necessary to register a standby whose upstream node has not yet been registered - in this case, using will result in the creation of an inactive placeholder record for the upstream node, which will however later need to be registered with the option too. When used with repmgr standby register, care should be taken that use of the option does not result in an incorrectly configured cluster. Registering a node not cloned by repmgr If you've cloned a standby using another method (e.g. barman's barman recover command), register the node as detailed in section then execute repmgr standby clone --replication-conf-only to generate the appropriate replication configuration. Options Check prerequisites but don't actually register the standby. / Overwrite an existing node record ID of the upstream node to replicate from (optional) wait for the standby to start (timeout in seconds, default 30 seconds) wait for the node record to synchronise to the standby (optional timeout in seconds) Event notifications A standby_register event notification will be generated immediately after the node record is updated on the primary. If the option is provided, a standby_register_sync event notification will be generated immediately after the node record has synchronised to the standby. If provided, &repmgr; will substitute the placeholders %p with the node ID of the primary node, %c with its conninfo string, and %a with its node name. repmgr-5.3.1/doc/repmgr-standby-switchover.xml000066400000000000000000000367361420262710000214450ustar00rootroot00000000000000 repmgr standby switchover repmgr standby switchover repmgr standby switchover promote a standby to primary and demote the existing primary to a standby Description Promotes a standby to primary and demotes the existing primary to a standby. This command must be run on the standby to be promoted, and requires a passwordless SSH connection to the current primary. If other nodes are connected to the demotion candidate, &repmgr; can instruct these to follow the new primary if the option --siblings-follow is specified. This requires a passwordless SSH connection between the promotion candidate (new primary) and the nodes attached to the demotion candidate (existing primary). Note that a witness server, if in use, is also counted as a "sibling node" as it needs to be instructed to synchronise its metadata with the new primary. Performing a switchover is a non-trivial operation. In particular it relies on the current primary being able to shut down cleanly and quickly. &repmgr; will attempt to check for potential issues but cannot guarantee a successful switchover. &repmgr; will refuse to perform the switchover if an exclusive backup is running on the current primary, or if WAL replay is paused on the standby. For more details on performing a switchover, including preparation and configuration, see section . From repmgr 4.2, &repmgr; will instruct any running &repmgrd; instances to pause operations while the switchover is being carried out, to prevent &repmgrd; from unintentionally promoting a node. For more details, see . Users of &repmgr; versions prior to 4.2 should ensure that &repmgrd; is not running on any nodes while a switchover is being executed. User permission requirements CHECKPOINT &repmgr; executes CHECKPOINT on the demotion candidate as part of the shutdown process to ensure it shuts down as smoothly as possible. Note that CHECKPOINT requires database superuser permissions to execute. If the repmgr user is not a superuser, the name of a superuser should be provided with the /. If &repmgr; is unable to execute the CHECKPOINT command, the switchover can still be carried out, albeit at a greater risk that the demotion candidate may not be able to shut down as smoothly as might otherwise have been the case. pg_promote() (PostgreSQL 12 and later) From PostgreSQL 12, &repmgr; defaults to using the built-in pg_promote() function to promote a standby to primary. Note that execution of pg_promote() is restricted to superusers or to any user who has been granted execution permission for this function. If the &repmgr; user is not permitted to execute pg_promote(), &repmgr; will fall back to using "pg_ctl promote". For more details see repmgr standby promote. Options Promote standby to primary, even if it is behind or has diverged from the original primary. The original primary will be shut down in any case, and will need to be manually reintegrated into the replication cluster. Check prerequisites but don't actually execute a switchover. Success of does not imply the switchover will complete successfully, only that the prerequisites for performing the operation are met. Ignore warnings and continue anyway. Specifically, if a problem is encountered when shutting down the current primary, using will cause &repmgr; to continue by promoting the standby to be the new primary, and if is specified, attach any other standbys to the new primary. Use pg_rewind to reintegrate the old primary if necessary (and the prerequisites for using pg_rewind are met). If using PostgreSQL 9.4, and the pg_rewind binary is not installed in the PostgreSQL bin directory, provide its full path. For more details see also . System username for remote SSH operations (defaults to local system user). Don't pause &repmgrd; while executing a switchover. This option should not be used unless you take steps by other means to ensure &repmgrd; is paused or not running on all nodes. This option cannot be used together with . Always unpause all &repmgrd; instances after executing a switchover. This will ensure that any &repmgrd; instances which were paused before the switchover will be unpaused. This option cannot be used together with . Have nodes attached to the old primary follow the new primary. This will also ensure that a witness node, if in use, is updated with the new primary's data. In a future &repmgr; release, will be applied by default. / Use the named superuser instead of the normal &repmgr; user to perform actions requiring superuser permissions. Configuration file settings The following parameters in repmgr.conf are relevant to the switchover operation: replication_lag_critical with "repmgr standby switchover" If replication lag (in seconds) on the standby exceeds this value, the switchover will be aborted (unless the -F/--force option is provided) shutdown_check_timeout with "repmgr standby switchover" The maximum number of seconds to wait for the demotion candidate (current primary) to shut down, before aborting the switchover. Note that this parameter is set on the node where repmgr standby switchover is executed (promotion candidate); setting it on the demotion candidate (former primary) will have no effect. In versions prior to &repmgr; 4.2, repmgr standby switchover would use the values defined in reconnect_attempts and reconnect_interval to determine the timeout for demotion candidate shutdown. wal_receive_check_timeout with "repmgr standby switchover" After the primary has shut down, the maximum number of seconds to wait for the walreceiver on the standby to flush WAL to disk before comparing WAL receive location with the primary's shut down location. standby_reconnect_timeout with "repmgr standby switchover" The maximum number of seconds to attempt to wait for the demotion candidate (former primary) to reconnect to the promoted primary (default: 60 seconds) Note that this parameter is set on the node where repmgr standby switchover is executed (promotion candidate); setting it on the demotion candidate (former primary) will have no effect. node_rejoin_timeout with "repmgr standby switchover" maximum number of seconds to attempt to wait for the demotion candidate (former primary) to reconnect to the promoted primary (default: 60 seconds) Note that this parameter is set on the the demotion candidate (former primary); setting it on the node where repmgr standby switchover is executed will have no effect. However, this value must be less than on the promotion candidate (the node where repmgr standby switchover is executed). Execution Execute with the --dry-run option to test the switchover as far as possible without actually changing the status of either node. External database connections, e.g. from an application, should not be permitted while the switchover is taking place. In particular, active transactions on the primary can potentially disrupt the shutdown process. Event notifications standby_switchover and standby_promote event notifications will be generated for the new primary, and a node_rejoin event notification for the former primary (new standby). If using an event notification script, standby_switchover will populate the placeholder parameter %p with the node ID of the former primary. Exit codes One of the following exit codes will be emitted by repmgr standby switchover: The switchover completed successfully; or if was provided, no issues were detected which would prevent the switchover operation. The switchover could not be executed. The switchover was executed but a problem was encountered. Typically this means the former primary could not be reattached as a standby. Check preceding log messages for more information. See also , For more details on performing a switchover operation, see the section . repmgr-5.3.1/doc/repmgr-standby-unregister.xml000066400000000000000000000042141420262710000214210ustar00rootroot00000000000000 repmgr standby unregister repmgr standby unregister repmgr standby unregister remove a standby's information from the &repmgr; metadata Description Unregisters a standby with &repmgr;. This command does not affect the actual replication, just removes the standby's entry from the &repmgr; metadata. Execution To unregister a running standby, execute: repmgr standby unregister -f /etc/repmgr.conf This will remove the standby record from &repmgr;'s internal metadata table (repmgr.nodes). A standby_unregister event notification will be recorded in the repmgr.events table. If the standby is not running, the command can be executed on another node by providing the id of the node to be unregistered using the command line parameter --node-id, e.g. executing the following command on the primary server will unregister the standby with id 3: repmgr standby unregister -f /etc/repmgr.conf --node-id=3 Options node_id of the node to unregister (optional) Event notifications A standby_unregister event notification will be generated. repmgr-5.3.1/doc/repmgr-witness-register.xml000066400000000000000000000062121420262710000211060ustar00rootroot00000000000000 repmgr witness register witness server repmgr witness register repmgr witness register add a witness node's information to the &repmgr; metadata Description repmgr witness register adds a witness server's node record to the &repmgr; metadata, and if necessary initialises the witness node by installing the &repmgr; extension and copying the &repmgr; metadata to the witness server. This command needs to be executed to enable use of the witness server with &repmgrd;. When executing repmgr witness register, database connection information for the cluster primary server must also be provided. In most cases it's only necessary to provide the primary's hostname with the / option; &repmgr; will automatically use the user and dbname values defined in the conninfo string defined in the witness node's repmgr.conf, unless these are explicitly provided as command line options. The primary server must be registered with repmgr primary register before the witness server can be registered. Execute with the option to check what would happen without actually registering the witness server. Example $ repmgr -f /etc/repmgr.conf witness register -h node1 INFO: connecting to witness node "node3" (ID: 3) INFO: connecting to primary node NOTICE: attempting to install extension "repmgr" NOTICE: "repmgr" extension successfully installed INFO: witness registration complete NOTICE: witness node "node3" (ID: 3) successfully registered Options Check prerequisites but don't actually register the witness / Overwrite an existing node record Event notifications A witness_register event notification will be generated. repmgr-5.3.1/doc/repmgr-witness-unregister.xml000066400000000000000000000063751420262710000214630ustar00rootroot00000000000000 repmgr witness unregister repmgr witness unregister repmgr witness unregister remove a witness node's information to the &repmgr; metadata Description repmgr witness unregister removes a witness server's node record from the &repmgr; metadata. The node does not have to be running to be unregistered, however if this is the case then either provide connection information for the primary server, or execute repmgr witness unregister on a running node and provide the parameter with the node ID of the witness server. Execute with the --dry-run option to check what would happen without actually registering the witness server. Examples Unregistering a running witness node: $ repmgr -f /etc/repmgr.conf witness unregister INFO: connecting to witness node "node3" (ID: 3) INFO: unregistering witness node 3 INFO: witness unregistration complete DETAIL: witness node with UD 3 successfully unregistered Unregistering a non-running witness node: $ repmgr -f /etc/repmgr.conf witness unregister -h node1 -p 5501 -F INFO: connecting to node "node3" (ID: 3) NOTICE: unable to connect to node "node3" (ID: 3), removing node record on cluster primary only INFO: unregistering witness node 3 INFO: witness unregistration complete DETAIL: witness node with id ID 3 successfully unregistered Notes This command will not make any changes to the witness node itself and will neither remove any data from the witness database nor stop the PostgreSQL instance. A witness node which has been unregistered, can be re-registered with repmgr witness register --force. Options Check prerequisites but don't actually unregister the witness. Unregister witness server with the specified node ID. Event notifications A witness_unregister event notification will be generated. repmgr-5.3.1/doc/repmgr.xml000066400000000000000000000073431420262710000156000ustar00rootroot00000000000000 %version; %filelist; repmgr"> repmgrd"> PostgreSQL"> ]> repmgr &repmgrversion; Documentation EnterpriseDB Corporation repmgr &repmgrversion; &legal; This is the official documentation of &repmgr; &repmgrversion; for use with PostgreSQL 9.4 - PostgreSQL 14. &repmgr; is being continually developed and we strongly recommend using the latest version. Please check the repmgr website for details about the current &repmgr; version as well as the current repmgr documentation. &repmgr; is developed by 2ndQuadrant (EDB) along with contributions from other individuals and organisations. Contributions from the community are appreciated and welcome - get in touch via github or the mailing list/forum. Multiple 2ndQuadrant (EDB) customers contribute funding to make repmgr development possible. &repmgr; is fully supported by 2ndQuadrant (EDB)'s 24/7 Production Support. EnterpriseDB Corporation, a Major Sponsor of the PostgreSQL project, continues to maintain &repmgr;. We welcome participation from other organisations and individual developers. repmgr PostgreSQL replication asynchronous HA high-availability Getting started &overview; &install; &quickstart; repmgr administration manual &configuration; &cloning-standbys; &promoting-standby; &follow-new-primary; &switchover; &event-notifications; &upgrading-repmgr; Using repmgrd &repmgrd-overview; &repmgrd-automatic-failover; &repmgrd-configuration; &repmgrd-operation; repmgr command reference &repmgr-primary-register; &repmgr-primary-unregister; &repmgr-standby-clone; &repmgr-standby-register; &repmgr-standby-unregister; &repmgr-standby-promote; &repmgr-standby-follow; &repmgr-standby-switchover; &repmgr-witness-register; &repmgr-witness-unregister; &repmgr-node-status; &repmgr-node-check; &repmgr-node-rejoin; &repmgr-node-service; &repmgr-cluster-show; &repmgr-cluster-matrix; &repmgr-cluster-crosscheck; &repmgr-cluster-event; &repmgr-cluster-cleanup; &repmgr-service-status; &repmgr-service-pause; &repmgr-service-unpause; &repmgr-daemon-start; &repmgr-daemon-stop; &appendix-release-notes; &appendix-signatures; &appendix-faq; &appendix-packages; &appendix-support; repmgr-5.3.1/doc/repmgrd-automatic-failover.xml000066400000000000000000001135201420262710000215300ustar00rootroot00000000000000 Automatic failover with repmgrd repmgrd automatic failover &repmgrd; is a management and monitoring daemon which runs on each node in a replication cluster. It can automate actions such as failover and updating standbys to follow the new primary, as well as providing monitoring information about the state of each standby. Using a witness server repmgrd witness server witness server repmgrd A is a normal PostgreSQL instance which is not part of the streaming replication cluster; its purpose is, if a failover situation occurs, to provide proof that it is the primary server itself which is unavailable, rather than e.g. a network split between different physical locations. A typical use case for a witness server is a two-node streaming replication setup, where the primary and standby are in different locations (data centres). By creating a witness server in the same location (data centre) as the primary, if the primary becomes unavailable it's possible for the standby to decide whether it can promote itself without risking a "split brain" scenario: if it can't see either the witness or the primary server, it's likely there's a network-level interruption and it should not promote itself. If it can see the witness but not the primary, this proves there is no network interruption and the primary itself is unavailable, and it can therefore promote itself (and ideally take action to fence the former primary). Never install a witness server on the same physical host as another node in the replication cluster managed by &repmgr; - it's essential the witness is not affected in any way by failure of another node. For more complex replication scenarios, e.g. with multiple datacentres, it may be preferable to use location-based failover, which ensures that only nodes in the same location as the primary will ever be promotion candidates; see for more details. A witness server will only be useful if &repmgrd; is in use. Creating a witness server To create a witness server, set up a normal PostgreSQL instance on a server in the same physical location as the cluster's primary server. This instance should not be on the same physical host as the primary server, as otherwise if the primary server fails due to hardware issues, the witness server will be lost too. &repmgr; 3.3 and earlier provided a repmgr create witness command, which would automatically create a PostgreSQL instance. However this often resulted in an unsatisfactory, hard-to-customise instance. The witness server should be configured in the same way as a normal &repmgr; node; see section . Register the witness server with . This will create the &repmgr; extension on the witness server, and make a copy of the &repmgr; metadata. As the witness server is not part of the replication cluster, further changes to the &repmgr; metadata will be synchronised by &repmgrd;. Once the witness server has been configured, &repmgrd; should be started. To unregister a witness server, use . Handling network splits with repmgrd repmgrd network splits network splits A common pattern for replication cluster setups is to spread servers over more than one datacentre. This can provide benefits such as geographically- distributed read replicas and DR (disaster recovery capability). However this also means there is a risk of disconnection at network level between datacentre locations, which would result in a split-brain scenario if servers in a secondary data centre were no longer able to see the primary in the main data centre and promoted a standby among themselves. &repmgr; enables provision of "" to artificially create a quorum of servers in a particular location, ensuring that nodes in another location will not elect a new primary if they are unable to see the majority of nodes. However this approach does not scale well, particularly with more complex replication setups, e.g. where the majority of nodes are located outside of the primary datacentre. It also means the witness node needs to be managed as an extra PostgreSQL instance outside of the main replication cluster, which adds administrative and programming complexity. repmgr4 introduces the concept of location: each node is associated with an arbitrary location string (default is default); this is set in repmgr.conf, e.g.: node_id=1 node_name=node1 conninfo='host=node1 user=repmgr dbname=repmgr connect_timeout=2' data_directory='/var/lib/postgresql/data' location='dc1' In a failover situation, &repmgrd; will check if any servers in the same location as the current primary node are visible. If not, &repmgrd; will assume a network interruption and not promote any node in any other location (it will however enter degraded monitoring mode until a primary becomes visible). Primary visibility consensus repmgrd primary visibility consensus primary_visibility_consensus In more complex replication setups, particularly where replication occurs between multiple datacentres, it's possible that some but not all standbys get cut off from the primary (but not from the other standbys). In this situation, normally it's not desirable for any of the standbys which have been cut off to initiate a failover, as the primary is still functioning and standbys are connected. Beginning with &repmgr; 4.4 it is now possible for the affected standbys to build a consensus about whether the primary is still available to some standbys ("primary visibility consensus"). This is done by polling each standby (and the witness, if present) for the time it last saw the primary; if any have seen the primary very recently, it's reasonable to infer that the primary is still available and a failover should not be started. The time the primary was last seen by each node can be checked by executing repmgr service status (&repmgr; 4.2 - 4.4: repmgr daemon status) which includes this in its output, e.g.: $ repmgr -f /etc/repmgr.conf service status ID | Name | Role | Status | Upstream | repmgrd | PID | Paused? | Upstream last seen ----+-------+---------+-----------+----------+---------+-------+---------+-------------------- 1 | node1 | primary | * running | | running | 27259 | no | n/a 2 | node2 | standby | running | node1 | running | 27272 | no | 1 second(s) ago 3 | node3 | standby | running | node1 | running | 27282 | no | 0 second(s) ago 4 | node4 | witness | * running | node1 | running | 27298 | no | 1 second(s) ago To enable this functionality, in repmgr.conf set: primary_visibility_consensus=true must be set to true on all nodes for it to be effective. The following sample &repmgrd; log output demonstrates the behaviour in a situation where one of three standbys is no longer able to connect to the primary, but can connect to the two other standbys ("sibling nodes"): [2019-05-17 05:36:12] [WARNING] unable to reconnect to node 1 after 3 attempts [2019-05-17 05:36:12] [INFO] 2 active sibling nodes registered [2019-05-17 05:36:12] [INFO] local node's last receive lsn: 0/7006E58 [2019-05-17 05:36:12] [INFO] checking state of sibling node "node3" (ID: 3) [2019-05-17 05:36:12] [INFO] node "node3" (ID: 3) reports its upstream is node 1, last seen 1 second(s) ago [2019-05-17 05:36:12] [NOTICE] node 3 last saw primary node 1 second(s) ago, considering primary still visible [2019-05-17 05:36:12] [INFO] last receive LSN for sibling node "node3" (ID: 3) is: 0/7006E58 [2019-05-17 05:36:12] [INFO] node "node3" (ID: 3) has same LSN as current candidate "node2" (ID: 2) [2019-05-17 05:36:12] [INFO] checking state of sibling node "node4" (ID: 4) [2019-05-17 05:36:12] [INFO] node "node4" (ID: 4) reports its upstream is node 1, last seen 0 second(s) ago [2019-05-17 05:36:12] [NOTICE] node 4 last saw primary node 0 second(s) ago, considering primary still visible [2019-05-17 05:36:12] [INFO] last receive LSN for sibling node "node4" (ID: 4) is: 0/7006E58 [2019-05-17 05:36:12] [INFO] node "node4" (ID: 4) has same LSN as current candidate "node2" (ID: 2) [2019-05-17 05:36:12] [INFO] 2 nodes can see the primary [2019-05-17 05:36:12] [DETAIL] following nodes can see the primary: - node "node3" (ID: 3): 1 second(s) ago - node "node4" (ID: 4): 0 second(s) ago [2019-05-17 05:36:12] [NOTICE] cancelling failover as some nodes can still see the primary [2019-05-17 05:36:12] [NOTICE] election cancelled [2019-05-17 05:36:14] [INFO] node "node2" (ID: 2) monitoring upstream node "node1" (ID: 1) in degraded state In this situation it will cancel the failover and enter degraded monitoring node, waiting for the primary to reappear. Standby disconnection on failover repmgrd standby disconnection on failover standby disconnection on failover If is set to true in repmgr.conf, in a failover situation &repmgrd; will forcibly disconnect the local node's WAL receiver, and wait for the WAL receiver on all sibling nodes to be disconnected, before making a failover decision. is available with PostgreSQL 9.5 and later. Additionally this requires that the repmgr database user is a superuser. By doing this, it's possible to ensure that, at the point the failover decision is made, no nodes are receiving data from the primary and their LSN location will be static. must be set to the same value on all nodes. Note that when using there will be a delay of 5 seconds plus however many seconds it takes to confirm the WAL receiver is disconnected before &repmgrd; proceeds with the failover decision. &repmgrd; will wait up to seconds (default: 30) to confirm that the WAL receiver on all sibling nodes hase been disconnected before proceding with the failover operation. If the timeout is reached, the failover operation will go ahead anyway. Following the failover operation, no matter what the outcome, each node will reconnect its WAL receiver. If using , we recommend that the option is also used. Failover validation repmgrd failover validation failover validation From repmgr 4.3, &repmgr; makes it possible to provide a script to &repmgrd; which, in a failover situation, will be executed by the promotion candidate (the node which has been selected to be the new primary) to confirm whether the node should actually be promoted. To use this, in repmgr.conf to a script executable by the postgres system user, e.g.: failover_validation_command=/path/to/script.sh %n The %n parameter will be replaced with the node ID when the script is executed. A number of other parameters are also available, see section "" for details. This script must return an exit code of 0 to indicate the node should promote itself. Any other value will result in the promotion being aborted and the election rerun. There is a pause of seconds before the election is rerun. Sample &repmgrd; log file output during which the failover validation script rejects the proposed promotion candidate: [2019-03-13 21:01:30] [INFO] visible nodes: 2; total nodes: 2; no nodes have seen the primary within the last 4 seconds [2019-03-13 21:01:30] [NOTICE] promotion candidate is "node2" (ID: 2) [2019-03-13 21:01:30] [NOTICE] executing "failover_validation_command" [2019-03-13 21:01:30] [DETAIL] /usr/local/bin/failover-validation.sh 2 [2019-03-13 21:01:30] [INFO] output returned by failover validation command: Node ID: 2 [2019-03-13 21:01:30] [NOTICE] failover validation command returned a non-zero value: "1" [2019-03-13 21:01:30] [NOTICE] promotion candidate election will be rerun [2019-03-13 21:01:30] [INFO] 1 followers to notify [2019-03-13 21:01:30] [NOTICE] notifying node "node3" (ID: 3) to rerun promotion candidate selection INFO: node 3 received notification to rerun promotion candidate election [2019-03-13 21:01:30] [NOTICE] rerunning election after 15 seconds ("election_rerun_interval") repmgrd and cascading replication repmgrd cascading replication cascading replication repmgrd Cascading replication - where a standby can connect to an upstream node and not the primary server itself - was introduced in PostgreSQL 9.2. &repmgr; and &repmgrd; support cascading replication by keeping track of the relationship between standby servers - each node record is stored with the node id of its upstream ("parent") server (except of course the primary server). In a failover situation where the primary node fails and a top-level standby is promoted, a standby connected to another standby will not be affected and continue working as normal (even if the upstream standby it's connected to becomes the primary node). If however the node's direct upstream fails, the "cascaded standby" will attempt to reconnect to that node's parent (unless failover is set to manual in repmgr.conf). Monitoring standby disconnections on the primary node repmgrd standby disconnection repmgrd child node disconnection This functionality is available in &repmgr; 4.4 and later. When running on the primary node, &repmgrd; can monitor connections and in particular disconnections by its attached child nodes (standbys, and if in use, the witness server), and optionally execute a custom command if certain criteria are met (such as the number of attached nodes falling to zero following a failover to a new primary); this command can be used for example to "fence" the node and ensure it is isolated from any applications attempting to access the replication cluster. Currently &repmgrd; can only detect disconnections of streaming replication standbys and cannot determine whether a standby has disconnected and fallen back to archive recovery. See section caveats below. Standby disconnections monitoring process and criteria &repmgrd; monitors attached child nodes and decides whether to invoke the user-defined command based on the following process and criteria: Every few seconds (defined by the configuration parameter child_nodes_check_interval; default: 5 seconds, a value of 0 disables this altogether), &repmgrd; queries the pg_stat_replication system view and compares the nodes present there against the list of nodes registered with &repmgr; which should be attached to the primary. If a witness server is in use, &repmgrd; connects to it and checks which upstream node it is following. If a child node (standby) is no longer present in pg_stat_replication, &repmgrd; notes the time it detected the node's absence, and additionally generates a child_node_disconnect event. If a witness server is in use, and it is no longer following the primary, or not reachable at all, &repmgrd; notes the time it detected the node's absence, and additionally generates a child_node_disconnect event. If a child node (standby) which was absent from pg_stat_replication reappears, &repmgrd; clears the time it detected the node's absence, and additionally generates a child_node_reconnect event. If a witness server is in use, which was previously not reachable or not following the primary node, has become reachable and is following the primary node, &repmgrd; clears the time it detected the node's absence, and additionally generates a child_node_reconnect event. If an entirely new child node (standby or witness) is detected, &repmgrd; adds it to its internal list and additionally generates a child_node_new_connect event. If the child_nodes_disconnect_command parameter is set in repmgr.conf, &repmgrd; will then loop through all child nodes. If it determines that insufficient child nodes are connected, and a minimum of child_nodes_disconnect_timeout seconds (default: 30) has elapsed since the last node became disconnected, &repmgrd; will then execute the child_nodes_disconnect_command script. By default, the child_nodes_disconnect_command will only be executed if all child nodes are disconnected. If child_nodes_connected_min_count is set, the child_nodes_disconnect_command script will be triggered if the number of connected child nodes falls below the specified value (e.g. if set to 2, the script will be triggered if only one child node is connected). Alternatively, if child_nodes_disconnect_min_count and more than that number of child nodes disconnects, the script will be triggered. By default, a witness node, if in use, will not be counted as a child node for the purposes of determining whether to execute child_nodes_disconnect_command. To enable the witness node to be counted as a child node, set child_nodes_connected_include_witness in repmgr.conf to true (and reload the configuration if &repmgrd; is running). Note that child nodes which are not attached when &repmgrd; starts will not be considered as missing, as &repmgrd; cannot know why they are not attached. Standby disconnections monitoring process example This example shows typical &repmgrd; log output from a three-node cluster (primary and two child nodes), with child_nodes_connected_min_count set to 2. &repmgrd; on the primary has started up, while two child nodes are being provisioned: [2019-04-24 15:25:33] [INFO] monitoring primary node "node1" (ID: 1) in normal state [2019-04-24 15:25:35] [NOTICE] new node "node2" (ID: 2) has connected [2019-04-24 15:25:35] [NOTICE] 1 (of 1) child nodes are connected, but at least 2 child nodes required [2019-04-24 15:25:35] [INFO] no child nodes have detached since repmgrd startup (...) [2019-04-24 15:25:44] [NOTICE] new node "node3" (ID: 3) has connected [2019-04-24 15:25:46] [INFO] monitoring primary node "node1" (ID: 1) in normal state (...) One of the child nodes has disconnected; &repmgrd; is now waiting child_nodes_disconnect_timeout seconds before executing child_nodes_disconnect_command: [2019-04-24 15:28:11] [INFO] monitoring primary node "node1" (ID: 1) in normal state [2019-04-24 15:28:17] [INFO] monitoring primary node "node1" (ID: 1) in normal state [2019-04-24 15:28:19] [NOTICE] node "node3" (ID: 3) has disconnected [2019-04-24 15:28:19] [NOTICE] 1 (of 2) child nodes are connected, but at least 2 child nodes required [2019-04-24 15:28:19] [INFO] most recently detached child node was 3 (ca. 0 seconds ago), not triggering "child_nodes_disconnect_command" [2019-04-24 15:28:19] [DETAIL] "child_nodes_disconnect_timeout" set To 30 seconds (...) child_nodes_disconnect_command is executed once: [2019-04-24 15:28:49] [INFO] most recently detached child node was 3 (ca. 30 seconds ago), triggering "child_nodes_disconnect_command" [2019-04-24 15:28:49] [INFO] "child_nodes_disconnect_command" is: "/usr/bin/fence-all-the-things.sh" [2019-04-24 15:28:51] [NOTICE] 1 (of 2) child nodes are connected, but at least 2 child nodes required [2019-04-24 15:28:51] [INFO] "child_nodes_disconnect_command" was previously executed, taking no action Standby disconnections monitoring caveats The following caveats should be considered if you are intending to use this functionality. If a child node is configured to use archive recovery, it's possible that the child node will disconnect from the primary node and fall back to archive recovery. In this case &repmgrd; will nevertheless register a node disconnection. &repmgr; relies on application_name in the child node's primary_conninfo string to be the same as the node name defined in the node's repmgr.conf file. Furthermore, this application_name must be unique across the replication cluster. If a custom application_name is used, or the application_name is not unique across the replication cluster, &repmgr; will not be able to reliably monitor child node connections. Standby disconnections monitoring process configuration The following parameters, set in repmgr.conf, control how child node disconnection monitoring operates. child_nodes_check_interval child_nodes_check_interval child node disconnection monitoring Interval (in seconds) after which &repmgrd; queries the pg_stat_replication system view and compares the nodes present there against the list of nodes registered with repmgr which should be attached to the primary. Default is 5 seconds, a value of 0 disables this check altogether. child_nodes_disconnect_command child_nodes_disconnect_command child node disconnection monitoring User-definable script to be executed when &repmgrd; determines that an insufficient number of child nodes are connected. By default the script is executed when no child nodes are executed, but the execution threshold can be modified by setting one of child_nodes_connected_min_count orchild_nodes_disconnect_min_count (see below). The child_nodes_disconnect_command script can be any user-defined script or program. It must be able to be executed by the system user under which the PostgreSQL server itself runs (usually postgres). If child_nodes_disconnect_command is not set, no action will be taken. If specified, the following format placeholder will be substituted when executing child_nodes_disconnect_command: ID of the node executing the child_nodes_disconnect_command script. The child_nodes_disconnect_command script will only be executed once while the criteria for its execution are met. If the criteria for its execution are no longer met (i.e. some child nodes have reconnected), it will be executed again if the criteria for its execution are met again. The child_nodes_disconnect_command script will not be executed if &repmgrd; is paused. child_nodes_disconnect_timeout child_nodes_disconnect_timeout child node disconnection monitoring If &repmgrd; determines that an insufficient number of child nodes are connected, it will wait for the specified number of seconds to execute the child_nodes_disconnect_command. Default: 30 seconds. child_nodes_connected_min_count child_nodes_connected_min_count child node disconnection monitoring If the number of child nodes connected falls below the number specified in this parameter, the child_nodes_disconnect_command script will be executed. For example, if child_nodes_connected_min_count is set to 2, the child_nodes_disconnect_command script will be executed if one or no child nodes are connected. Note that child_nodes_connected_min_count overrides any value set in child_nodes_disconnect_min_count. If neither of child_nodes_connected_min_count or child_nodes_disconnect_min_count are set, the child_nodes_disconnect_command script will be executed when no child nodes are connected. A witness node, if in use, will not be counted as a child node unless child_nodes_connected_include_witness is set to true. child_nodes_disconnect_min_count child_nodes_disconnect_min_count child node disconnection monitoring If the number of disconnected child nodes exceeds the number specified in this parameter, the child_nodes_disconnect_command script will be executed. For example, if child_nodes_disconnect_min_count is set to 2, the child_nodes_disconnect_command script will be executed if more than two child nodes are disconnected. Note that any value set in child_nodes_disconnect_min_count will be overriden by child_nodes_connected_min_count. If neither of child_nodes_connected_min_count or child_nodes_disconnect_min_count are set, the child_nodes_disconnect_command script will be executed when no child nodes are connected. A witness node, if in use, will not be counted as a child node unless child_nodes_connected_include_witness is set to true. child_nodes_connected_include_witness child_nodes_connected_include_witness child node disconnection monitoring Whether to count the witness node (if in use) as a child node when determining whether to execute child_nodes_disconnect_command. Default to false. Standby disconnections monitoring process event notifications The following event notifications may be generated: child_node_disconnect child_node_disconnect event notification This event is generated after &repmgrd; detects that a child node is no longer streaming from the primary node. Example: $ repmgr cluster event --event=child_node_disconnect Node ID | Name | Event | OK | Timestamp | Details ---------+-------+-----------------------+----+---------------------+-------------------------------------------- 1 | node1 | child_node_disconnect | t | 2019-04-24 12:41:36 | node "node3" (ID: 3) has disconnected child_node_reconnect child_node_reconnect event notification This event is generated after &repmgrd; detects that a child node has resumed streaming from the primary node. Example: $ repmgr cluster event --event=child_node_reconnect Node ID | Name | Event | OK | Timestamp | Details ---------+-------+----------------------+----+---------------------+------------------------------------------------------------ 1 | node1 | child_node_reconnect | t | 2019-04-24 12:42:19 | node "node3" (ID: 3) has reconnected after 42 seconds child_node_new_connect child_node_new_connect event notification This event is generated after &repmgrd; detects that a new child node has been registered with &repmgr; and has connected to the primary. Example: $ repmgr cluster event --event=child_node_new_connect Node ID | Name | Event | OK | Timestamp | Details ---------+-------+------------------------+----+---------------------+--------------------------------------------- 1 | node1 | child_node_new_connect | t | 2019-04-24 12:41:30 | new node "node3" (ID: 3) has connected child_nodes_disconnect_command child_nodes_disconnect_command event notification This event is generated after &repmgrd; detects that sufficient child nodes have been disconnected for a sufficient amount of time to trigger execution of the child_nodes_disconnect_command. Example: $ repmgr cluster event --event=child_nodes_disconnect_command Node ID | Name | Event | OK | Timestamp | Details ---------+-------+--------------------------------+----+---------------------+-------------------------------------------------------- 1 | node1 | child_nodes_disconnect_command | t | 2019-04-24 13:08:17 | "child_nodes_disconnect_command" successfully executed repmgr-5.3.1/doc/repmgrd-configuration.xml000066400000000000000000001203521420262710000206050ustar00rootroot00000000000000 repmgrd setup and configuration repmgrd configuration &repmgrd; is a daemon process which runs on each PostgreSQL node, monitoring the local node, and (unless it's the primary node) the upstream server (the primary server or with cascading replication, another standby) which it's connected to. &repmgrd; can be configured to provide failover capability in case the primary or upstream node becomes unreachable, and/or provide monitoring data to the &repmgr; metadatabase. From &repmgr; 4.4, when running on the primary node, &repmgrd; can also monitor standby disconnections/reconnections (see ). repmgrd configuration To use &repmgrd;, its associated function library must be included via postgresql.conf with: shared_preload_libraries = 'repmgr' Changing this setting requires a restart of PostgreSQL; for more details see the PostgreSQL documentation. The following configuraton options apply to &repmgrd; in all circumstances: monitor_interval_secs The interval (in seconds, default: 2) to check the availability of the upstream node. connection_check_type The option is used to select the method &repmgrd; uses to determine whether the upstream node is available. Possible values are: ping (default) - uses PQping() to determine server availability connection - determines server availability by attempting to make a new connection to the upstream node query - determines server availability by executing an SQL statement on the node via the existing connection The query is a minimal throwaway query - SELECT 1 - which is used to determine that the server can accept queries. reconnect_attempts The number of attempts (default: 6) will be made to reconnect to an unreachable upstream node before initiating a failover. There will be an interval of seconds between each reconnection attempt. reconnect_interval Interval (in seconds, default: 10) between attempts to reconnect to an unreachable upstream node. The number of reconnection attempts is defined by the parameter . degraded_monitoring_timeout Interval (in seconds) after which &repmgrd; will terminate if either of the servers (local node and or upstream node) being monitored is no longer available (degraded monitoring mode). -1 (default) disables this timeout completely. See also repmgr.conf.sample for an annotated sample configuration file. Required configuration for automatic failover The following &repmgrd; options must be set in repmgr.conf: Example: failover=automatic promote_command='/usr/bin/repmgr standby promote -f /etc/repmgr.conf --log-to-file' follow_command='/usr/bin/repmgr standby follow -f /etc/repmgr.conf --log-to-file --upstream-node-id=%n' Details of each option are as follows: failover can be one of automatic or manual. If is set to manual, &repmgrd; will not take any action if a failover situation is detected, and the node may need to be modified manually (e.g. by executing repmgr standby follow). promote_command The program or script defined in will be executed in a failover situation when &repmgrd; determines that the current node is to become the new primary node. Normally is set as &repmgr;'s repmgr standby promote command. When invoking repmgr standby promote (either directly via the , or in a script called via ), must not be included as a command line option for repmgr standby promote. It is also possible to provide a shell script to e.g. perform user-defined tasks before promoting the current node. In this case the script must at some point execute repmgr standby promote to promote the node; if this is not done, &repmgr; metadata will not be updated and &repmgr; will no longer function reliably. Example: promote_command='/usr/bin/repmgr standby promote -f /etc/repmgr.conf --log-to-file' Note that the --log-to-file option will cause output generated by the &repmgr; command, when executed by &repmgrd;, to be logged to the same destination configured to receive log output for &repmgrd;. &repmgr; will not apply when executing or ; these can be user-defined scripts so must always be specified with the full path. follow_command The program or script defined in will be executed in a failover situation when &repmgrd; determines that the current node is to follow the new primary node. Normally is set as &repmgr;'s repmgr standby follow command. The parameter should provide the --upstream-node-id=%n option to repmgr standby follow; the %n will be replaced by &repmgrd; with the ID of the new primary node. If this is not provided, repmgr standby follow will attempt to determine the new primary by itself, but if the original primary comes back online after the new primary is promoted, there is a risk that repmgr standby follow will result in the node continuing to follow the original primary. It is also possible to provide a shell script to e.g. perform user-defined tasks before promoting the current node. In this case the script must at some point execute repmgr standby follow to promote the node; if this is not done, &repmgr; metadata will not be updated and &repmgr; will no longer function reliably. Example: follow_command='/usr/bin/repmgr standby follow -f /etc/repmgr.conf --log-to-file --upstream-node-id=%n' Note that the --log-to-file option will cause output generated by the &repmgr; command, when executed by &repmgrd;, to be logged to the same destination configured to receive log output for &repmgrd;. &repmgr; will not apply when executing or ; these can be user-defined scripts so must always be specified with the full path. Optional configuration for automatic failover The following configuraton options can be used to fine-tune automatic failover: priority Indicates a preferred priority (default: 100) for promoting nodes; a value of zero prevents the node being promoted to primary. Note that the priority setting is only applied if two or more nodes are determined as promotion candidates; in that case the node with the higher priority is selected. failover_validation_command User-defined script to execute for an external mechanism to validate the failover decision made by &repmgrd;. This option must be identically configured on all nodes. One or more of the following parameter placeholders may be provided, which will be replaced by repmgrd with the appropriate value: %n: node ID %a: node name %v: number of visible nodes %u: number of shared upstream nodes %t: total number of nodes See also: Failover validation. primary_visibility_consensus If true, only continue with failover if no standbys (or the witness server, if present) have seen the primary node recently. This option must be identically configured on all nodes. always_promote Default: false. If true, promote the local node even if its &repmgr; metadata is not up-to-date. Normally &repmgr; expects its metadata (stored in the repmgr.nodes table) to be up-to-date so &repmgrd; can take the correct action during a failover. However it's possible that updates made on the primary may not have propagated to the standby (promotion candidate). In this case &repmgrd; will default to not promoting the standby. This behaviour can be overridden by setting to true. standby_disconnect_on_failover In a failover situation, disconnect the local node's WAL receiver. This option is available from PostgreSQL 9.5 and later. This option must be identically configured on all nodes. Additionally the &repmgr; user must be a superuser for this option. &repmgrd; will refuse to start if this option is set but either of these prerequisites is not met. See also: Standby disconnection on failover. repmgrd_exit_on_inactive_node This parameter is available in &repmgr; 5.3 and later. If a node was marked as inactive but is running, and this option is set to true, &repmgrd; will abort on startup. By default, is set to false, in which case &repmgrd; will set the node record to active on startup. Setting this parameter to true causes &repmgrd; to behave in the same way it did in &repmgr; 5.2 and earlier. The following options can be used to further fine-tune failover behaviour. In practice it's unlikely these will need to be changed from their default values, but are available as configuration options should the need arise. election_rerun_interval If is set, and the command returns an error, pause the specified amount of seconds (default: 15) before rerunning the election. sibling_nodes_disconnect_timeout If is true, the maximum length of time (in seconds, default: 30) to wait for other standbys to confirm they have disconnected their WAL receivers. Configuring &repmgrd; and pgbouncer to fence a failed primary node fencing using repmgrd and pgbouncer to fence a failed primary node PgBouncer using repmgrd and pgbouncer to fence a failed primary node For further details and a reference implementation, see the separate document Fencing a failed master node with repmgrd and PgBouncer. PostgreSQL service configuration repmgrd PostgreSQL service configuration If using automatic failover, currently &repmgrd; will need to execute repmgr standby follow to restart PostgreSQL on standbys to have them follow a new primary. To ensure this happens smoothly, it's essential to provide the appropriate system/service restart command appropriate to your operating system via service_restart_command in repmgr.conf. If you don't do this, &repmgrd; will default to using pg_ctl, which can result in unexpected problems, particularly on systemd-based systems. For more details, see . repmgrd service configuration repmgrd repmgrd service configuration If you are intending to use the repmgr daemon start and repmgr daemon stop commands, the following parameters must be set in repmgr.conf: repmgrd_service_start_command repmgrd_service_stop_command Example (for &repmgr; with PostgreSQL 12 on CentOS 7): repmgrd_service_start_command='sudo systemctl repmgr12 start' repmgrd_service_stop_command='sudo systemctl repmgr12 stop' For more details see the reference page for each command. Monitoring configuration repmgrd monitoring configuration To enable monitoring, set: monitoring_history=yes in repmgr.conf. Monitoring data is written at the interval defined by the option (see above). For more details on monitoring, see . For information on monitoring standby disconnections, see . Applying configuration changes to repmgrd repmgrd applying configuration changes To apply configuration file changes to a running &repmgrd; daemon, execute the operating system's &repmgrd; service reload command (see for examples), or for instances which were manually started, execute kill -HUP, e.g. kill -HUP `cat /tmp/repmgrd.pid`. Check the &repmgrd; log to see what changes were applied, or if any issues were encountered when reloading the configuration. Note that only the following subset of configuration file parameters can be changed on a running &repmgrd; daemon: async_query_timeout child_nodes_check_interval child_nodes_connected_include_witness child_nodes_connected_min_count child_nodes_disconnect_command child_nodes_disconnect_min_count child_nodes_disconnect_timeout connection_check_type conninfo degraded_monitoring_timeout event_notification_command event_notifications failover_validation_command failover follow_command log_facility log_file log_level log_status_interval monitor_interval_secs monitoring_history primary_notification_timeout primary_visibility_consensus always_promote promote_command reconnect_attempts reconnect_interval retry_promote_interval_secs repmgrd_standby_startup_timeout sibling_nodes_disconnect_timeout standby_disconnect_on_failover The following set of configuration file parameters must be updated via repmgr standby register --force, as they require changes to the repmgr.nodes table so they are visible to all nodes in the replication cluster: node_id node_name data_directory location priority After executing repmgr standby register --force, &repmgrd; must be restarted for the changes to take effect. repmgrd daemon repmgrd starting and stopping If installed from a package, the &repmgrd; can be started via the operating system's service command, e.g. in systemd using systemctl. See appendix for details of service commands for different distributions. The commands repmgr daemon start and repmgr daemon stop can be used as convenience wrappers to start and stop &repmgrd; on the local node. repmgr daemon start and repmgr daemon stop require that the appropriate start/stop commands are configured as repmgrd_service_start_command and repmgrd_service_stop_command in repmgr.conf. &repmgrd; can be started manually like this: repmgrd -f /etc/repmgr.conf --pid-file /tmp/repmgrd.pid and stopped with kill `cat /tmp/repmgrd.pid`. Adjust paths as appropriate. repmgrd's PID file repmgrd PID file PID file repmgrd &repmgrd; will generate a PID file by default. This is a behaviour change from previous versions (earlier than 4.1), where the PID file had to be explicitly specified with the command line parameter . The PID file can be specified in repmgr.conf with the configuration parameter repmgrd_pid_file. It can also be specified on the command line (as in previous versions) with the command line parameter . Note this will override any value set in repmgr.conf with repmgrd_pid_file. may be deprecated in future releases. If a PID file location was specified by the package maintainer, &repmgrd; will use that. This only applies if &repmgr; was installed from a package and the package maintainer has specified the PID file location. If none of the above apply, &repmgrd; will create a PID file in the operating system's temporary directory (as determined by the environment variable TMPDIR, or if that is not set, will use /tmp). To prevent a PID file being generated at all, provide the command line option . To see which PID file &repmgrd; would use, execute &repmgrd; with the option . &repmgrd; will not start if this option is provided. Note that the value shown is the file &repmgrd; would use next time it starts, and is not necessarily the PID file currently in use. repmgrd daemon configuration on Debian/Ubuntu repmgrd Debian/Ubuntu and daemon configuration Debian/Ubuntu repmgrd daemon configuration If &repmgr; was installed from Debian/Ubuntu packages, additional configuration is required before &repmgrd; is started as a daemon. This is done via the file /etc/default/repmgrd, which by default looks like this: # default settings for repmgrd. This file is source by /bin/sh from # /etc/init.d/repmgrd # disable repmgrd by default so it won't get started upon installation # valid values: yes/no REPMGRD_ENABLED=no # configuration file (required) #REPMGRD_CONF="/path/to/repmgr.conf" # additional options REPMGRD_OPTS="--daemonize=false" # user to run repmgrd as #REPMGRD_USER=postgres # repmgrd binary #REPMGRD_BIN=/usr/bin/repmgrd # pid file #REPMGRD_PIDFILE=/var/run/repmgrd.pid Set REPMGRD_ENABLED to yes, and REPMGRD_CONF to the repmgr.conf file you are using. See for details of the Debian/Ubuntu packages and typical file locations (including repmgr.conf). From &repmgrd; 4.1, ensure REPMGRD_OPTS includes , as daemonization is handled by the service command. If using systemd, you may need to execute systemctl daemon-reload. Also, if you attempted to start &repmgrd; using systemctl start repmgrd, you'll need to execute systemctl stop repmgrd. Because that's how systemd rolls. repmgrd daemon monitoring repmgrd monitoring monitoring repmgrd The command repmgr service status provides an overview of the &repmgrd; daemon status (including pause status) on all nodes in the cluster. From &repmgr; 5.3, repmgr node check --repmgrd can be used to check the status of &repmgrd; (including pause status) on the local node. repmgrd connection settings In addition to the &repmgr; configuration settings, parameters in the conninfo string influence how &repmgr; makes a network connection to PostgreSQL. In particular, if another server in the replication cluster is unreachable at network level, system network settings will influence the length of time it takes to determine that the connection is not possible. In particular explicitly setting a parameter for connect_timeout should be considered; the effective minimum value of 2 (seconds) will ensure that a connection failure at network level is reported as soon as possible, otherwise depending on the system settings (e.g. tcp_syn_retries in Linux) a delay of a minute or more is possible. For further details on conninfo network connection parameters, see the PostgreSQL documentation. repmgrd log rotation log rotation repmgrd repmgrd log rotation To ensure the current &repmgrd; logfile (specified in repmgr.conf with the parameter ) does not grow indefinitely, configure your system's logrotate to regularly rotate it. Sample configuration to rotate logfiles weekly with retention for up to 52 weeks and rotation forced if a file grows beyond 100Mb: /var/log/repmgr/repmgrd.log { missingok compress rotate 52 maxsize 100M weekly create 0600 postgres postgres postrotate /usr/bin/killall -HUP repmgrd endscript } repmgr-5.3.1/doc/repmgrd-node-fencing.md000066400000000000000000000116221420262710000200710ustar00rootroot00000000000000Fencing a failed master node with repmgrd and pgbouncer ======================================================= With automatic failover, it's essential to ensure that a failed primary 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 primary from your application and ensure that all connections which should go to the primary 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 primary 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 primary and redirect write connections to the new primary. The script consists of two sections: * the promotion command itself * commands to reconfigure `pgbouncer` on all nodes Note that it requires password-less SSH access from the `repmgr` nodes to all the `pgbouncer` nodes to be able to update the `pgbouncer` configuration files. For the purposes of this demonstration, we'll assume there are 3 nodes (primary 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`. * * * > *NOTE*: in this self-contained demonstration, `pgbouncer` is running on the > database servers, however in a production environment it will make more > sense to run `pgbouncer` on either separate nodes or the application server. * * * `/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" # 1. Promote this node from standby to primary repmgr standby promote -f /etc/repmgr.conf --log-to-file # 2. 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.nodes \ WHERE active = TRUE AND type='primary'" >> $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.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 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. repmgr-5.3.1/doc/repmgrd-operation.xml000066400000000000000000000411351420262710000177370ustar00rootroot00000000000000 repmgrd operation repmgrd operation Pausing the repmgrd service repmgrd pausing pausing repmgrd In normal operation, &repmgrd; monitors the state of the PostgreSQL node it is running on, and will take appropriate action if problems are detected, e.g. (if so configured) promote the node to primary, if the existing primary has been determined as failed. However, &repmgrd; is unable to distinguish between planned outages (such as performing a switchover or installing PostgreSQL maintenance released), and an actual server outage. In versions prior to &repmgr; 4.2 it was necessary to stop &repmgrd; on all nodes (or at least on all nodes where &repmgrd; is configured for automatic failover) to prevent &repmgrd; from making unintentional changes to the replication cluster. From &repmgr; 4.2, &repmgrd; can now be "paused", i.e. instructed not to take any action such as performing a failover. This can be done from any node in the cluster, removing the need to stop/restart each &repmgrd; individually. For major PostgreSQL upgrades, e.g. from PostgreSQL 11 to PostgreSQL 12, &repmgrd; should be shut down completely and only started up once the &repmgr; packages for the new PostgreSQL major version have been installed. Prerequisites for pausing &repmgrd; In order to be able to pause/unpause &repmgrd;, following prerequisites must be met: &repmgr; 4.2 or later must be installed on all nodes. The same major &repmgr; version (e.g. 4.2) must be installed on all nodes (and preferably the same minor version). PostgreSQL on all nodes must be accessible from the node where the pause/unpause operation is executed, using the conninfo string shown by repmgr cluster show. These conditions are required for normal &repmgr; operation in any case. Pausing/unpausing &repmgrd; To pause &repmgrd;, execute repmgr service pause (&repmgr; 4.2 - 4.4: repmgr daemon pause), e.g.: $ repmgr -f /etc/repmgr.conf service pause NOTICE: node 1 (node1) paused NOTICE: node 2 (node2) paused NOTICE: node 3 (node3) paused The state of &repmgrd; on each node can be checked with repmgr service status (&repmgr; 4.2 - 4.4: repmgr daemon status), e.g.: $ repmgr -f /etc/repmgr.conf service status ID | Name | Role | Status | repmgrd | PID | Paused? ----+-------+---------+---------+---------+------+--------- 1 | node1 | primary | running | running | 7851 | yes 2 | node2 | standby | running | running | 7889 | yes 3 | node3 | standby | running | running | 7918 | yes If executing a switchover with repmgr standby switchover, &repmgr; will automatically pause/unpause the &repmgrd; service as part of the switchover process. If the primary (in this example, node1) is stopped, &repmgrd; running on one of the standbys (here: node2) will react like this: [2019-08-28 12:22:21] [WARNING] unable to connect to upstream node "node1" (node ID: 1) [2019-08-28 12:22:21] [INFO] checking state of node 1, 1 of 5 attempts [2019-08-28 12:22:21] [INFO] sleeping 1 seconds until next reconnection attempt ... [2019-08-28 12:22:24] [INFO] sleeping 1 seconds until next reconnection attempt [2019-08-28 12:22:25] [INFO] checking state of node 1, 5 of 5 attempts [2019-08-28 12:22:25] [WARNING] unable to reconnect to node 1 after 5 attempts [2019-08-28 12:22:25] [NOTICE] node is paused [2019-08-28 12:22:33] [INFO] node "node2" (ID: 2) monitoring upstream node "node1" (node ID: 1) in degraded state [2019-08-28 12:22:33] [DETAIL] repmgrd paused by administrator [2019-08-28 12:22:33] [HINT] execute "repmgr service unpause" to resume normal failover mode If the primary becomes available again (e.g. following a software upgrade), &repmgrd; will automatically reconnect, e.g.: [2019-08-28 12:25:41] [NOTICE] reconnected to upstream node "node1" (ID: 1) after 8 seconds, resuming monitoring To unpause the &repmgrd; service, execute repmgr service unpause ((&repmgr; 4.2 - 4.4: repmgr daemon unpause), e.g.: $ repmgr -f /etc/repmgr.conf service unpause NOTICE: node 1 (node1) unpaused NOTICE: node 2 (node2) unpaused NOTICE: node 3 (node3) unpaused If the previous primary is no longer accessible when &repmgrd; is unpaused, no failover action will be taken. Instead, a new primary must be manually promoted using repmgr standby promote, and any standbys attached to the new primary with repmgr standby follow. This is to prevent execution of repmgr service unpause resulting in the automatic promotion of a new primary, which may be a problem particularly in larger clusters, where &repmgrd; could select a different promotion candidate to the one intended by the administrator. Details on the &repmgrd; pausing mechanism The pause state of each node will be stored over a PostgreSQL restart. repmgr service pause and repmgr service unpause can be executed even if &repmgrd; is not running; in this case, &repmgrd; will start up in whichever pause state has been set. repmgr service pause and repmgr service unpause do not start/stop &repmgrd;. The commands repmgr daemon start and repmgr daemon stop (if correctly configured) can be used to start/stop &repmgrd; on individual nodes. repmgrd and paused WAL replay repmgrd paused WAL replay If WAL replay has been paused (using pg_wal_replay_pause(), on PostgreSQL 9.6 and earlier pg_xlog_replay_pause()), in a failover situation &repmgrd; will automatically resume WAL replay. This is because if WAL replay is paused, but WAL is pending replay, PostgreSQL cannot be promoted until WAL replay is resumed. repmgr standby promote will refuse to promote a node in this state, as the PostgreSQL promote command will not be acted on until WAL replay is resumed, leaving the cluster in a potentially unstable state. In this case it is up to the user to decide whether to resume WAL replay. "degraded monitoring" mode repmgrd degraded monitoring degraded monitoring In certain circumstances, &repmgrd; is not able to fulfill its primary mission of monitoring the node's upstream server. In these cases it enters "degraded monitoring" mode, where &repmgrd; remains active but is waiting for the situation to be resolved. Situations where this happens are: a failover situation has occurred, no nodes in the primary node's location are visible a failover situation has occurred, but no promotion candidate is available a failover situation has occurred, but the promotion candidate could not be promoted a failover situation has occurred, but the node was unable to follow the new primary a failover situation has occurred, but no primary has become available a failover situation has occurred, but automatic failover is not enabled for the node repmgrd is monitoring the primary node, but it is not available (and no other node has been promoted as primary) Example output in a situation where there is only one standby with failover=manual, and the primary node is unavailable (but is later restarted): [2017-08-29 10:59:19] [INFO] node "node2" (ID: 2) monitoring upstream node "node1" (ID: 1) in normal state (automatic failover disabled) [2017-08-29 10:59:33] [WARNING] unable to connect to upstream node "node1" (ID: 1) [2017-08-29 10:59:33] [INFO] checking state of node 1, 1 of 5 attempts [2017-08-29 10:59:33] [INFO] sleeping 1 seconds until next reconnection attempt (...) [2017-08-29 10:59:37] [INFO] checking state of node 1, 5 of 5 attempts [2017-08-29 10:59:37] [WARNING] unable to reconnect to node 1 after 5 attempts [2017-08-29 10:59:37] [NOTICE] this node is not configured for automatic failover so will not be considered as promotion candidate [2017-08-29 10:59:37] [NOTICE] no other nodes are available as promotion candidate [2017-08-29 10:59:37] [HINT] use "repmgr standby promote" to manually promote this node [2017-08-29 10:59:37] [INFO] node "node2" (ID: 2) monitoring upstream node "node1" (ID: 1) in degraded state (automatic failover disabled) [2017-08-29 10:59:53] [INFO] node "node2" (ID: 2) monitoring upstream node "node1" (ID: 1) in degraded state (automatic failover disabled) [2017-08-29 11:00:45] [NOTICE] reconnected to upstream node "node1" (ID: 1) after 68 seconds, resuming monitoring [2017-08-29 11:00:57] [INFO] node "node2" (ID: 2) monitoring upstream node "node1" (ID: 1) in normal state (automatic failover disabled) By default, repmgrd will continue in degraded monitoring mode indefinitely. However a timeout (in seconds) can be set with degraded_monitoring_timeout, after which &repmgrd; will terminate. If &repmgrd; is monitoring a primary mode which has been stopped and manually restarted as a standby attached to a new primary, it will automatically detect the status change and update the node record to reflect the node's new status as an active standby. It will then resume monitoring the node as a standby. Storing monitoring data repmgrd monitoring monitoring with repmgrd When &repmgrd; is running with the option monitoring_history=true, it will constantly write standby node status information to the monitoring_history table, providing a near-real time overview of replication status on all nodes in the cluster. The view replication_status shows the most recent state for each node, e.g.: repmgr=# select * from repmgr.replication_status; -[ RECORD 1 ]-------------+------------------------------ primary_node_id | 1 standby_node_id | 2 standby_name | node2 node_type | standby active | t last_monitor_time | 2017-08-24 16:28:41.260478+09 last_wal_primary_location | 0/6D57A00 last_wal_standby_location | 0/5000000 replication_lag | 29 MB replication_time_lag | 00:00:11.736163 apply_lag | 15 MB communication_time_lag | 00:00:01.365643 The interval in which monitoring history is written is controlled by the configuration parameter monitor_interval_secs; default is 2. As this can generate a large amount of monitoring data in the table repmgr.monitoring_history. it's advisable to regularly purge historical data using the command; use the -k/--keep-history option to specify how many day's worth of data should be retained. It's possible to use &repmgrd; to run in monitoring mode only (without automatic failover capability) for some or all nodes by setting failover=manual in the node's repmgr.conf file. In the event of the node's upstream failing, no failover action will be taken and the node will require manual intervention to be reattached to replication. If this occurs, an event notification standby_disconnect_manual will be created. Note that when a standby node is not streaming directly from its upstream node, e.g. recovering WAL from an archive, apply_lag will always appear as 0 bytes. If monitoring history is enabled, the contents of the repmgr.monitoring_history table will be replicated to attached standbys. This means there will be a small but constant stream of replication activity which may not be desirable. To prevent this, convert the table to an UNLOGGED one with: ALTER TABLE repmgr.monitoring_history SET UNLOGGED; This will however mean that monitoring history will not be available on another node following a failover, and the view repmgr.replication_status will not work on standbys. repmgr-5.3.1/doc/repmgrd-overview.xml000066400000000000000000000222251420262710000176040ustar00rootroot00000000000000 repmgrd overview repmgrd overview &repmgrd; ("replication manager daemon") is a management and monitoring daemon which runs on each node in a replication cluster. It can automate actions such as failover and updating standbys to follow the new primary, as well as providing monitoring information about the state of each standby. &repmgrd; is designed to be straightforward to set up and does not require additional external infrastructure. Functionality provided by &repmgrd; includes: wide range of configuration options option to execute custom scripts ("event notifications at different points in the failover sequence ability to pause repmgrd operation on all nodes with a single command optional witness server "location" configuration option to restrict potential promotion candidates to a single location (e.g. when nodes are spread over multiple data centres) choice of method to determine node availability (PostgreSQL ping, query execution or new connection) retention of monitoring statistics (optional) repmgrd demonstration To demonstrate automatic failover, set up a 3-node replication cluster (one primary and two standbys streaming directly from the primary) so that the cluster looks something like this: $ repmgr -f /etc/repmgr.conf cluster show --compact ID | Name | Role | Status | Upstream | Location | Prio. ----+-------+---------+-----------+----------+----------+------- 1 | node1 | primary | * running | | default | 100 2 | node2 | standby | running | node1 | default | 100 3 | node3 | standby | running | node1 | default | 100 See section Required configuration for automatic failover for an example of minimal repmgr.conf file settings suitable for use with &repmgrd;. Start &repmgrd; on each standby and verify that it's running by examining the log output, which at log level INFO will look like this: [2019-08-15 07:14:42] [NOTICE] repmgrd (repmgrd 5.0) starting up [2019-08-15 07:14:42] [INFO] connecting to database "host=node2 dbname=repmgr user=repmgr connect_timeout=2" INFO: set_repmgrd_pid(): provided pidfile is /var/run/repmgr/repmgrd-12.pid [2019-08-15 07:14:42] [NOTICE] starting monitoring of node "node2" (ID: 2) [2019-08-15 07:14:42] [INFO] monitoring connection to upstream node "node1" (ID: 1) Each &repmgrd; should also have recorded its successful startup as an event: $ repmgr -f /etc/repmgr.conf cluster event --event=repmgrd_start Node ID | Name | Event | OK | Timestamp | Details ---------+-------+---------------+----+---------------------+-------------------------------------------------------- 3 | node3 | repmgrd_start | t | 2019-08-15 07:14:42 | monitoring connection to upstream node "node1" (ID: 1) 2 | node2 | repmgrd_start | t | 2019-08-15 07:14:41 | monitoring connection to upstream node "node1" (ID: 1) 1 | node1 | repmgrd_start | t | 2019-08-15 07:14:39 | monitoring cluster primary "node1" (ID: 1) Now stop the current primary server with e.g.: pg_ctl -D /var/lib/postgresql/data -m immediate stop This will force the primary to shut down straight away, aborting all processes and transactions. This will cause a flurry of activity in the &repmgrd; log files as each &repmgrd; detects the failure of the primary and a failover decision is made. This is an extract from the log of a standby server (node2) which has promoted to new primary after failure of the original primary (node1). [2019-08-15 07:27:50] [WARNING] unable to connect to upstream node "node1" (ID: 1) [2019-08-15 07:27:50] [INFO] checking state of node 1, 1 of 3 attempts [2019-08-15 07:27:50] [INFO] sleeping 5 seconds until next reconnection attempt [2019-08-15 07:27:55] [INFO] checking state of node 1, 2 of 3 attempts [2019-08-15 07:27:55] [INFO] sleeping 5 seconds until next reconnection attempt [2019-08-15 07:28:00] [INFO] checking state of node 1, 3 of 3 attempts [2019-08-15 07:28:00] [WARNING] unable to reconnect to node 1 after 3 attempts [2019-08-15 07:28:00] [INFO] primary and this node have the same location ("default") [2019-08-15 07:28:00] [INFO] local node's last receive lsn: 0/900CBF8 [2019-08-15 07:28:00] [INFO] node 3 last saw primary node 12 second(s) ago [2019-08-15 07:28:00] [INFO] last receive LSN for sibling node "node3" (ID: 3) is: 0/900CBF8 [2019-08-15 07:28:00] [INFO] node "node3" (ID: 3) has same LSN as current candidate "node2" (ID: 2) [2019-08-15 07:28:00] [INFO] visible nodes: 2; total nodes: 2; no nodes have seen the primary within the last 4 seconds [2019-08-15 07:28:00] [NOTICE] promotion candidate is "node2" (ID: 2) [2019-08-15 07:28:00] [NOTICE] this node is the winner, will now promote itself and inform other nodes [2019-08-15 07:28:00] [INFO] promote_command is: "/usr/pgsql-12/bin/repmgr -f /etc/repmgr/12/repmgr.conf standby promote" NOTICE: promoting standby to primary DETAIL: promoting server "node2" (ID: 2) using "/usr/pgsql-12/bin/pg_ctl -w -D '/var/lib/pgsql/12/data' promote" NOTICE: waiting up to 60 seconds (parameter "promote_check_timeout") for promotion to complete NOTICE: STANDBY PROMOTE successful DETAIL: server "node2" (ID: 2) was successfully promoted to primary [2019-08-15 07:28:01] [INFO] 3 followers to notify [2019-08-15 07:28:01] [NOTICE] notifying node "node3" (ID: 3) to follow node 2 INFO: node 3 received notification to follow node 2 [2019-08-15 07:28:01] [INFO] switching to primary monitoring mode [2019-08-15 07:28:01] [NOTICE] monitoring cluster primary "node2" (ID: 2) The cluster status will now look like this, with the original primary (node1) marked as inactive, and standby node3 now following the new primary (node2): $ repmgr -f /etc/repmgr.conf cluster show --compact ID | Name | Role | Status | Upstream | Location | Prio. ----+-------+---------+-----------+----------+----------+------- 1 | node1 | primary | - failed | | default | 100 2 | node2 | primary | * running | | default | 100 3 | node3 | standby | running | node2 | default | 100 repmgr cluster event will display a summary of what happened to each server during the failover: $ repmgr -f /etc/repmgr.conf cluster event Node ID | Name | Event | OK | Timestamp | Details ---------+-------+----------------------------+----+---------------------+------------------------------------------------------------- 3 | node3 | repmgrd_failover_follow | t | 2019-08-15 07:38:03 | node 3 now following new upstream node 2 3 | node3 | standby_follow | t | 2019-08-15 07:38:02 | standby attached to upstream node "node2" (ID: 2) 2 | node2 | repmgrd_reload | t | 2019-08-15 07:38:01 | monitoring cluster primary "node2" (ID: 2) 2 | node2 | repmgrd_failover_promote | t | 2019-08-15 07:38:01 | node 2 promoted to primary; old primary 1 marked as failed 2 | node2 | standby_promote | t | 2019-08-15 07:38:01 | server "node2" (ID: 2) was successfully promoted to primary repmgr-5.3.1/doc/stylesheet-common.xsl000066400000000000000000000051021420262710000177600ustar00rootroot00000000000000 1 0 2 1 ? ? repmgr-5.3.1/doc/stylesheet-fo.xsl000066400000000000000000000070551420262710000171050ustar00rootroot00000000000000 3 wrap solid 1pt black 12pt 12pt 6pt 6pt center 1em 0.8em 1.2em , ISBN repmgr-5.3.1/doc/stylesheet-html-common.xsl000066400000000000000000000247271420262710000207400ustar00rootroot00000000000000 %common.entities; ]> pgsql-docs@lists.postgresql.org 2 , ISBN appendix toc,title article/appendix nop article toc,title book toc,title chapter toc,title part toc,title preface toc,title qandadiv toc qandaset toc reference toc,title sect1 toc sect2 toc sect3 toc sect4 toc sect5 toc section toc set toc,title

| id-
repmgr-5.3.1/doc/stylesheet-html-nochunk.xsl000066400000000000000000000014701420262710000211030ustar00rootroot00000000000000 repmgr-5.3.1/doc/stylesheet-speedup-common.xsl000066400000000000000000000100261420262710000214240ustar00rootroot00000000000000 en repmgr-5.3.1/doc/stylesheet-speedup-xhtml.xsl000066400000000000000000000403611420262710000212750ustar00rootroot00000000000000 , Error: If you change $chunk.section.depth, then you must update the performance-optimized chunk-all-sections-template. repmgr-5.3.1/doc/stylesheet.css000066400000000000000000000152461420262710000164660ustar00rootroot00000000000000/* PostgreSQL.org Documentation Style */ @import 'website-docs.css'; /* requires global.css, table.css and text.css to be loaded before this file! */ body { font-family: verdana, sans-serif; font-size: 76%; background: url("/resources/background.png") repeat-x scroll left top transparent; padding: 15px 4%; margin: 0; } /* monospace font size fix */ pre, code, kbd, samp, tt { font-family: monospace,monospace; font-size: 1em; } div.NAVHEADER table { margin-left: 0; } /* Container Definitions */ #docContainerWrap { text-align: center; /* Win IE5 */ } #docContainer { margin: 0 auto; width: 90%; padding-bottom: 2em; display: block; text-align: left; /* Win IE5 */ } #docHeader { background-image: url("/media/img/docs/bg_hdr.png"); height: 83px; margin: 0px; padding: 0px; display: block; } #docHeaderLogo { position: relative; width: 206px; height: 83px; border: 0px; padding: 0px; margin: 0 0 0 20px; } #docHeaderLogo img { border: 0px; } #docNavSearchContainer { padding-bottom: 2px; } #docNav, #docVersions { position: relative; text-align: left; margin-left: 10px; margin-top: 5px; color: #666; font-size: 0.95em; } #docSearch { position: relative; text-align: right; padding: 0; margin: 0; color: #666; } #docTextSize { text-align: right; white-space: nowrap; margin-top: 7px; font-size: 0.95em; } #docSearch form { position: relative; top: 5px; right: 0; margin: 0; /* need for IE 5.5 OSX */ text-align: right; /* need for IE 5.5 OSX */ white-space: nowrap; /* for Opera */ } #docSearch form label { color: #666; font-size: 0.95em; } #docSearch form input { font-size: 0.95em; } #docSearch form #submit { font-size: 0.95em; background: #7A7A7A; color: #fff; border: 1px solid #7A7A7A; padding: 1px 4px; } #docSearch form #q { width: 170px; font-size: 0.95em; border: 1px solid #7A7A7A; background: #E1E1E1; color: #000000; padding: 2px; } .frmDocSearch { padding: 0; margin: 0; display: inline; } .inpDocSearch { padding: 0; margin: 0; color: #000; } #docContent { position: relative; margin-left: 10px; margin-right: 10px; margin-top: 40px; } #docFooter { position: relative; font-size: 0.9em; color: #666; line-height: 1.3em; margin-left: 10px; margin-right: 10px; } #docComments { margin-top: 10px; } #docClear { clear: both; margin: 0; padding: 0; } /* Heading Definitions */ h1, h2, h3 { font-weight: bold; margin-top: 2ex; color: #444; } h1 { font-size: 1.4em; } h2 { font-size: 1.2em !important; } h3 { font-size: 1.1em; } h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover { color: #444; text-decoration: none; } /* Text Styles */ div.SECT2 { margin-top: 4ex; } div.SECT3 { margin-top: 3ex; margin-left: 3ex; } .txtCurrentLocation { font-weight: bold; } p, ol, ul, li { line-height: 1.5em; } .txtCommentsWrap { border: 2px solid #F5F5F5; width: 100%; } .txtCommentsContent { background: #F5F5F5; padding: 3px; } .txtCommentsPoster { float: left; } .txtCommentsDate { float: right; } .txtCommentsComment { padding: 3px; } #docContainer pre code, #docContainer pre tt, #docContainer pre pre, #docContainer tt tt, #docContainer tt code, #docContainer tt pre { font-size: 1em; } pre.LITERALLAYOUT, .SCREEN, .SYNOPSIS, .PROGRAMLISTING, .REFSYNOPSISDIV p, table.CAUTION, table.WARNING, blockquote.NOTE, blockquote.TIP, table.CALSTABLE { -moz-box-shadow: 3px 3px 5px #DFDFDF; -webkit-box-shadow: 3px 3px 5px #DFDFDF; -khtml-box-shadow: 3px 3px 5px #DFDFDF; -o-box-shadow: 3px 3px 5px #DFDFDF; box-shadow: 3px 3px 5px #DFDFDF; } pre.LITERALLAYOUT, .SCREEN, .SYNOPSIS, .PROGRAMLISTING, .REFSYNOPSISDIV p, table.CAUTION, table.WARNING, blockquote.NOTE, blockquote.TIP { color: black; border-width: 1px; border-style: solid; padding: 2ex; margin: 2ex 0 2ex 2ex; overflow: auto; -moz-border-radius: 8px; -webkit-border-radius: 8px; -khtml-border-radius: 8px; border-radius: 8px; } pre.LITERALLAYOUT, pre.SYNOPSIS, pre.PROGRAMLISTING, .REFSYNOPSISDIV p, .SCREEN { border-color: #CFCFCF; background-color: #F7F7F7; } blockquote.NOTE, blockquote.TIP { border-color: #DBDBCC; background-color: #EEEEDD; padding: 14px; width: 572px; } blockquote.NOTE, blockquote.TIP, table.CAUTION, table.WARNING { margin: 4ex auto; } blockquote.NOTE p, blockquote.TIP p { margin: 0; } blockquote.NOTE pre, blockquote.NOTE code, blockquote.TIP pre, blockquote.TIP code { margin-left: 0; margin-right: 0; -moz-box-shadow: none; -webkit-box-shadow: none; -khtml-box-shadow: none; -o-box-shadow: none; box-shadow: none; } .emphasis, .c2 { font-weight: bold; } .REPLACEABLE { font-style: italic; } /* Table Styles */ table { margin-left: 2ex; } table.CALSTABLE td, table.CALSTABLE th, table.CAUTION td, table.CAUTION th, table.WARNING td, table.WARNING th { border-style: solid; } table.CALSTABLE, table.CAUTION, table.WARNING { border-spacing: 0; border-collapse: collapse; } table.CALSTABLE { margin: 2ex 0 2ex 2ex; background-color: #E0ECEF; border: 2px solid #A7C6DF; } table.CALSTABLE tr:hover td { background-color: #EFEFEF; } table.CALSTABLE td { background-color: #FFF; } table.CALSTABLE td, table.CALSTABLE th { border: 1px solid #A7C6DF; padding: 0.5ex 0.5ex; } table.CAUTION, table.WARNING { border-collapse: separate; display: block; padding: 0; max-width: 600px; } table.CAUTION { background-color: #F5F5DC; border-color: #DEDFA7; } table.WARNING { background-color: #FFD7D7; border-color: #DF421E; } table.CAUTION td, table.CAUTION th, table.WARNING td, table.WARNING th { border-width: 0; padding-left: 2ex; padding-right: 2ex; } table.CAUTION td, table.CAUTION th { border-color: #F3E4D5 } table.WARNING td, table.WARNING th { border-color: #FFD7D7; } td.c1, td.c2, td.c3, td.c4, td.c5, td.c6 { font-size: 1.1em; font-weight: bold; border-bottom: 0px solid #FFEFEF; padding: 1ex 2ex 0; } /* Link Styles */ #docNav a { font-weight: bold; } a:link, a:visited, a:active, a:hover { text-decoration: underline; } a:link, a:active { color:#0066A2; } a:visited { color:#004E66; } a:hover { color:#000000; } #docFooter a:link, #docFooter a:visited, #docFooter a:active { color:#666; } #docContainer code.FUNCTION tt { font-size: 1em; } div.header { color: #444; margin-top: 5px; } div.footer { text-align: center; background-image: url("/resources/footerl.png"), url("/resources/footerr.png"), url("/resources/footerc.png"); background-position: left top, right top, center top; background-repeat: no-repeat, no-repeat, repeat-x; padding-top: 45px; } img { border-style: none; } repmgr-5.3.1/doc/stylesheet.dsl000066400000000000000000000763121420262710000164610ustar00rootroot00000000000000 ]]> ]]> ]]> ]> (define draft-mode #f) ;; Don't show manpage volume numbers (define %refentry-xref-manvolnum% #f) ;; Don't use graphics for callouts. (We could probably do that, but ;; it needs extra work.) (define %callout-graphics% #f) ;; Show comments during the development stage. (define %show-comments% draft-mode) ;; Force a chapter TOC even if it includes only a single entry (define %force-chapter-toc% #t) ;; Don't append period if run-in title ends with any of these ;; characters. We had to add the colon here. This is fixed in ;; stylesheets version 1.71, so it can be removed sometime. (define %content-title-end-punct% '(#\. #\! #\? #\:)) ;; No automatic punctuation after honorific name parts (define %honorific-punctuation% "") ;; Change display of some elements (element command ($mono-seq$)) (element envar ($mono-seq$)) (element lineannotation ($italic-seq$)) (element literal ($mono-seq$)) (element option ($mono-seq$)) (element parameter ($mono-seq$)) (element structfield ($mono-seq$)) (element structname ($mono-seq$)) (element symbol ($mono-seq$)) (element token ($mono-seq$)) (element type ($mono-seq$)) (element varname ($mono-seq$)) (element (programlisting emphasis) ($bold-seq$)) ;; to highlight sections of code ;; Special support for Tcl synopses (element optional (if (equal? (attribute-string (normalize "role")) "tcl") (make sequence (literal "?") ($charseq$) (literal "?")) (make sequence (literal %arg-choice-opt-open-str%) ($charseq$) (literal %arg-choice-opt-close-str%)))) ;; Avoid excessive cross-reference labels (define (auto-xref-indirect? target ancestor) (cond ; ;; Always add indirect references to another book ; ((member (gi ancestor) (book-element-list)) ; #t) ;; Add indirect references to the section or component a block ;; is in iff chapters aren't autolabelled. (Otherwise "Figure 1-3" ;; is sufficient) ((and (member (gi target) (block-element-list)) (not %chapter-autolabel%)) #t) ;; Add indirect references to the component a section is in if ;; the sections are not autolabelled ((and (member (gi target) (section-element-list)) (member (gi ancestor) (component-element-list)) (not %section-autolabel%)) #t) (else #f))) ;; Bibliography things ;; Use the titles of bibliography entries in cross-references (define biblio-xref-title #t) ;; Process bibliography entry components in the order shown below, not ;; in the order they appear in the document. (I suppose this should ;; be made to fit some publishing standard.) (define %biblioentry-in-entry-order% #f) (define (biblioentry-inline-elements) (list (normalize "author") (normalize "authorgroup") (normalize "title") (normalize "subtitle") (normalize "volumenum") (normalize "edition") (normalize "othercredit") (normalize "contrib") (normalize "editor") (normalize "publishername") (normalize "confgroup") (normalize "publisher") (normalize "isbn") (normalize "issn") (normalize "pubsnumber") (normalize "date") (normalize "pubdate") (normalize "pagenums") (normalize "bibliomisc"))) (mode biblioentry-inline-mode (element confgroup (make sequence (literal "Proc. ") (next-match))) (element isbn (make sequence (literal "ISBN ") (process-children))) (element issn (make sequence (literal "ISSN ") (process-children)))) ;; The rules in the default stylesheet for productname format it as a ;; paragraph. This may be suitable for productname directly within ;; *info, but it's nonsense when productname is used inline, as we do. (mode book-titlepage-recto-mode (element (para productname) ($charseq$))) (mode book-titlepage-verso-mode (element (para productname) ($charseq$))) ;; Add more here if needed... ;; Replace a sequence of whitespace in a string by a single space (define (normalize-whitespace str #!optional (whitespace '(#\space #\U-000D))) (let loop ((characters (string->list str)) (result '()) (prev-was-space #f)) (if (null? characters) (list->string (reverse result)) (let ((c (car characters)) (rest (cdr characters))) (if (member c whitespace) (if prev-was-space (loop rest result #t) (loop rest (cons #\space result) #t)) (loop rest (cons c result) #f)))))) string (time) #t))))) ;; Block elements are allowed in PARA in DocBook, but not in P in ;; HTML. With %fix-para-wrappers% turned on, the stylesheets attempt ;; to avoid putting block elements in HTML P tags by outputting ;; additional end/begin P pairs around them. (define %fix-para-wrappers% #t) ;; ...but we need to do some extra work to make the above apply to PRE ;; as well. (mostly pasted from dbverb.dsl) (define ($verbatim-display$ indent line-numbers?) (let ((content (make element gi: "PRE" attributes: (list (list "CLASS" (gi))) (if (or indent line-numbers?) ($verbatim-line-by-line$ indent line-numbers?) (process-children))))) (if %shade-verbatim% (make element gi: "TABLE" attributes: ($shade-verbatim-attr$) (make element gi: "TR" (make element gi: "TD" content))) (make sequence (para-check) content (para-check 'restart))))) ;; ...and for notes. (element note (make sequence (para-check) ($admonition$) (para-check 'restart))) ;;; XXX The above is very ugly. It might be better to run 'tidy' on ;;; the resulting *.html files. ;; Format multiple terms in varlistentry vertically, instead ;; of comma-separated. (element (varlistentry term) (make sequence (process-children-trim) (if (not (last-sibling?)) (make empty-element gi: "BR") (empty-sosofo)))) ;; Customization of header ;; - make title a link to the home page ;; - add tool tips to Prev/Next links ;; - add Up link ;; (overrides dbnavig.dsl) (define (default-header-nav-tbl-noff elemnode prev next prevsib nextsib) (let* ((r1? (nav-banner? elemnode)) (r1-sosofo (make element gi: "TR" (make element gi: "TH" attributes: (list (list "COLSPAN" "4") (list "ALIGN" "center") (list "VALIGN" "bottom")) (make element gi: "A" attributes: (list (list "HREF" (href-to (nav-home elemnode)))) (nav-banner elemnode))))) (r2? (or (not (node-list-empty? prev)) (not (node-list-empty? next)) (nav-context? elemnode))) (r2-sosofo (make element gi: "TR" (make element gi: "TD" attributes: (list (list "WIDTH" "10%") (list "ALIGN" "left") (list "VALIGN" "top")) (if (node-list-empty? prev) (make entity-ref name: "nbsp") (make element gi: "A" attributes: (list (list "TITLE" (element-title-string prev)) (list "HREF" (href-to prev)) (list "ACCESSKEY" "P")) (gentext-nav-prev prev)))) (make element gi: "TD" attributes: (list (list "WIDTH" "10%") (list "ALIGN" "left") (list "VALIGN" "top")) (if (nav-up? elemnode) (nav-up elemnode) (nav-home-link elemnode))) (make element gi: "TD" attributes: (list (list "WIDTH" "60%") (list "ALIGN" "center") (list "VALIGN" "bottom")) (nav-context elemnode)) (make element gi: "TD" attributes: (list (list "WIDTH" "20%") (list "ALIGN" "right") (list "VALIGN" "top")) (if (node-list-empty? next) (make entity-ref name: "nbsp") (make element gi: "A" attributes: (list (list "TITLE" (element-title-string next)) (list "HREF" (href-to next)) (list "ACCESSKEY" "N")) (gentext-nav-next next))))))) (if (or r1? r2?) (make element gi: "DIV" attributes: '(("CLASS" "NAVHEADER")) (make element gi: "TABLE" attributes: (list (list "SUMMARY" "Header navigation table") (list "WIDTH" %gentext-nav-tblwidth%) (list "BORDER" "0") (list "CELLPADDING" "0") (list "CELLSPACING" "0")) (if r1? r1-sosofo (empty-sosofo)) (if r2? r2-sosofo (empty-sosofo))) (make empty-element gi: "HR" attributes: (list (list "ALIGN" "LEFT") (list "WIDTH" %gentext-nav-tblwidth%)))) (empty-sosofo)))) ;; Put index "quicklinks" (A | B | C | ...) at the top of the bookindex page. (element index (let ((preamble (node-list-filter-by-not-gi (children (current-node)) (list (normalize "indexentry")))) (indexdivs (node-list-filter-by-gi (children (current-node)) (list (normalize "indexdiv")))) (entries (node-list-filter-by-gi (children (current-node)) (list (normalize "indexentry"))))) (html-document (with-mode head-title-mode (literal (element-title-string (current-node)))) (make element gi: "DIV" attributes: (list (list "CLASS" (gi))) ($component-separator$) ($component-title$) (if (node-list-empty? indexdivs) (empty-sosofo) (make element gi: "P" attributes: (list (list "CLASS" "INDEXDIV-QUICKLINKS")) (with-mode indexdiv-quicklinks-mode (process-node-list indexdivs)))) (process-node-list preamble) (if (node-list-empty? entries) (empty-sosofo) (make element gi: "DL" (process-node-list entries))))))) (mode indexdiv-quicklinks-mode (element indexdiv (make sequence (make element gi: "A" attributes: (list (list "HREF" (href-to (current-node)))) (element-title-sosofo)) (if (not (last-sibling?)) (literal " | ") (literal ""))))) ;; Changed to strip and normalize index term content (overrides ;; dbindex.dsl) (define (htmlindexterm) (let* ((attr (gi (current-node))) (content (data (current-node))) (string (strip (normalize-whitespace content))) ;; changed (sortas (attribute-string (normalize "sortas")))) (make sequence (make formatting-instruction data: attr) (if sortas (make sequence (make formatting-instruction data: "[") (make formatting-instruction data: sortas) (make formatting-instruction data: "]")) (empty-sosofo)) (make formatting-instruction data: " ") (make formatting-instruction data: string) (htmlnewline)))) (define ($html-body-start$) (if website-build (make empty-element gi: "!--#include virtual=\"/resources/docs-header.html\"--") (empty-sosofo))) (define ($html-body-end$) (if website-build (make empty-element gi: "!--#include virtual=\"/resources/docs-footer.html\"--") (empty-sosofo))) ]]> (string->number (attribute-string (normalize "columns"))) 0) (string->number (attribute-string (normalize "columns"))) 1) 1)) (members (select-elements (children (current-node)) (normalize "member")))) (cond ((equal? type (normalize "inline")) (if (equal? (gi (parent (current-node))) (normalize "para")) (process-children) (make paragraph space-before: %para-sep% space-after: %para-sep% start-indent: (inherited-start-indent)))) ((equal? type (normalize "vert")) (my-simplelist-vert members)) ((equal? type (normalize "horiz")) (simplelist-table 'row cols members))))) (element member (let ((type (inherited-attribute-string (normalize "type")))) (cond ((equal? type (normalize "inline")) (make sequence (process-children) (if (not (last-sibling?)) (literal ", ") (literal "")))) ((equal? type (normalize "vert")) (make paragraph space-before: 0pt space-after: 0pt)) ((equal? type (normalize "horiz")) (make paragraph quadding: 'start (process-children)))))) ;; Jadetex doesn't handle links to the content of tables, so ;; indexterms that point to table entries will go nowhere. We fix ;; this by pointing the index entry to the table itself instead, which ;; should be equally useful in practice. (define (find-parent-table nd) (let ((table (ancestor-member nd ($table-element-list$)))) (if (node-list-empty? table) nd table))) ;; (The function below overrides the one in print/dbindex.dsl.) (define (indexentry-link nd) (let* ((id (attribute-string (normalize "role") nd)) (prelim-target (find-indexterm id)) (target (find-parent-table prelim-target)) (preferred (not (node-list-empty? (select-elements (children (current-node)) (normalize "emphasis"))))) (sosofo (if (node-list-empty? target) (literal "?") (make link destination: (node-list-address target) (with-mode toc-page-number-mode (process-node-list target)))))) (if preferred (make sequence font-weight: 'bold sosofo) sosofo))) ;; By default, the part and reference title pages get wrong page ;; numbers: The first title page gets roman numerals carried over from ;; preface/toc -- we want Arabic numerals. We also need to make sure ;; that page-number-restart is set of #f explicitly, because otherwise ;; it will carry over from the previous component, which is not good. ;; ;; (This looks worse than it is. It's copied from print/dbttlpg.dsl ;; and common/dbcommon.dsl and modified in minor detail.) (define (first-part?) (let* ((book (ancestor (normalize "book"))) (nd (ancestor-member (current-node) (append (component-element-list) (division-element-list)))) (bookch (children book))) (let loop ((nl bookch)) (if (node-list-empty? nl) #f (if (equal? (gi (node-list-first nl)) (normalize "part")) (if (node-list=? (node-list-first nl) nd) #t #f) (loop (node-list-rest nl))))))) (define (first-reference?) (let* ((book (ancestor (normalize "book"))) (nd (ancestor-member (current-node) (append (component-element-list) (division-element-list)))) (bookch (children book))) (let loop ((nl bookch)) (if (node-list-empty? nl) #f (if (equal? (gi (node-list-first nl)) (normalize "reference")) (if (node-list=? (node-list-first nl) nd) #t #f) (loop (node-list-rest nl))))))) (define (part-titlepage elements #!optional (side 'recto)) (let ((nodelist (titlepage-nodelist (if (equal? side 'recto) (reference-titlepage-recto-elements) (reference-titlepage-verso-elements)) elements)) ;; partintro is a special case... (partintro (node-list-first (node-list-filter-by-gi elements (list (normalize "partintro")))))) (if (part-titlepage-content? elements side) (make simple-page-sequence page-n-columns: %titlepage-n-columns% ;; Make sure that page number format is correct. page-number-format: ($page-number-format$) ;; Make sure that the page number is set to 1 if this is the ;; first part in the book page-number-restart?: (first-part?) input-whitespace-treatment: 'collapse use: default-text-style ;; This hack is required for the RTF backend. If an external-graphic ;; is the first thing on the page, RTF doesn't seem to do the right ;; thing (the graphic winds up on the baseline of the first line ;; of the page, left justified). This "one point rule" fixes ;; that problem. (make paragraph line-spacing: 1pt (literal "")) (let loop ((nl nodelist) (lastnode (empty-node-list))) (if (node-list-empty? nl) (empty-sosofo) (make sequence (if (or (node-list-empty? lastnode) (not (equal? (gi (node-list-first nl)) (gi lastnode)))) (part-titlepage-before (node-list-first nl) side) (empty-sosofo)) (cond ((equal? (gi (node-list-first nl)) (normalize "subtitle")) (part-titlepage-subtitle (node-list-first nl) side)) ((equal? (gi (node-list-first nl)) (normalize "title")) (part-titlepage-title (node-list-first nl) side)) (else (part-titlepage-default (node-list-first nl) side))) (loop (node-list-rest nl) (node-list-first nl))))) (if (and %generate-part-toc% %generate-part-toc-on-titlepage% (equal? side 'recto)) (make display-group (build-toc (current-node) (toc-depth (current-node)))) (empty-sosofo)) ;; PartIntro is a special case (if (and (equal? side 'recto) (not (node-list-empty? partintro)) %generate-partintro-on-titlepage%) ($process-partintro$ partintro #f) (empty-sosofo))) (empty-sosofo)))) (define (reference-titlepage elements #!optional (side 'recto)) (let ((nodelist (titlepage-nodelist (if (equal? side 'recto) (reference-titlepage-recto-elements) (reference-titlepage-verso-elements)) elements)) ;; partintro is a special case... (partintro (node-list-first (node-list-filter-by-gi elements (list (normalize "partintro")))))) (if (reference-titlepage-content? elements side) (make simple-page-sequence page-n-columns: %titlepage-n-columns% ;; Make sure that page number format is correct. page-number-format: ($page-number-format$) ;; Make sure that the page number is set to 1 if this is the ;; first part in the book page-number-restart?: (first-reference?) input-whitespace-treatment: 'collapse use: default-text-style ;; This hack is required for the RTF backend. If an external-graphic ;; is the first thing on the page, RTF doesn't seem to do the right ;; thing (the graphic winds up on the baseline of the first line ;; of the page, left justified). This "one point rule" fixes ;; that problem. (make paragraph line-spacing: 1pt (literal "")) (let loop ((nl nodelist) (lastnode (empty-node-list))) (if (node-list-empty? nl) (empty-sosofo) (make sequence (if (or (node-list-empty? lastnode) (not (equal? (gi (node-list-first nl)) (gi lastnode)))) (reference-titlepage-before (node-list-first nl) side) (empty-sosofo)) (cond ((equal? (gi (node-list-first nl)) (normalize "author")) (reference-titlepage-author (node-list-first nl) side)) ((equal? (gi (node-list-first nl)) (normalize "authorgroup")) (reference-titlepage-authorgroup (node-list-first nl) side)) ((equal? (gi (node-list-first nl)) (normalize "corpauthor")) (reference-titlepage-corpauthor (node-list-first nl) side)) ((equal? (gi (node-list-first nl)) (normalize "editor")) (reference-titlepage-editor (node-list-first nl) side)) ((equal? (gi (node-list-first nl)) (normalize "subtitle")) (reference-titlepage-subtitle (node-list-first nl) side)) ((equal? (gi (node-list-first nl)) (normalize "title")) (reference-titlepage-title (node-list-first nl) side)) (else (reference-titlepage-default (node-list-first nl) side))) (loop (node-list-rest nl) (node-list-first nl))))) (if (and %generate-reference-toc% %generate-reference-toc-on-titlepage% (equal? side 'recto)) (make display-group (build-toc (current-node) (toc-depth (current-node)))) (empty-sosofo)) ;; PartIntro is a special case (if (and (equal? side 'recto) (not (node-list-empty? partintro)) %generate-partintro-on-titlepage%) ($process-partintro$ partintro #f) (empty-sosofo))) (empty-sosofo)))) ]]> repmgr-5.3.1/doc/stylesheet.xsl000066400000000000000000000160071420262710000165000ustar00rootroot00000000000000 stylesheet.css https://www.postgresql.org/media/css/docs.css repmgr-5.3.1/doc/switchover.xml000066400000000000000000000454521420262710000165040ustar00rootroot00000000000000 Performing a switchover with repmgr switchover A typical use-case for replication is a combination of primary and standby server, with the standby serving as a backup which can easily be activated in case of a problem with the primary. Such an unplanned failover would normally be handled by promoting the standby, after which an appropriate action must be taken to restore the old primary. In some cases however it's desirable to promote the standby in a planned way, e.g. so maintenance can be performed on the primary; this kind of switchover is supported by the command. repmgr standby switchover differs from other &repmgr; actions in that it also performs actions on other servers (the demotion candidate, and optionally any other servers which are to follow the new primary), which means passwordless SSH access is required to those servers from the one where repmgr standby switchover is executed. repmgr standby switchover performs a relatively complex series of operations on two servers, and should therefore be performed after careful preparation and with adequate attention. In particular you should be confident that your network environment is stable and reliable. Additionally you should be sure that the current primary can be shut down quickly and cleanly. In particular, access from applications should be minimalized or preferably blocked completely. Also be aware that if there is a backlog of files waiting to be archived, PostgreSQL will not shut down until archiving completes. We recommend running repmgr standby switchover at the most verbose logging level (--log-level=DEBUG --verbose) and capturing all output to assist troubleshooting any problems. Please also read carefully the sections and below. Preparing for switchover switchover preparation As mentioned in the previous section, success of the switchover operation depends on &repmgr; being able to shut down the current primary server quickly and cleanly. Ensure that the promotion candidate has sufficient free walsenders available (PostgreSQL configuration item max_wal_senders), and if replication slots are in use, at least one free slot is available for the demotion candidate ( PostgreSQL configuration item max_replication_slots). Ensure that a passwordless SSH connection is possible from the promotion candidate (standby) to the demotion candidate (current primary). If --siblings-follow will be used, ensure that passwordless SSH connections are possible from the promotion candidate to all nodes attached to the demotion candidate (including the witness server, if in use). &repmgr; expects to find the &repmgr; binary in the same path on the remote server as on the local server. Double-check which commands will be used to stop/start/restart the current primary; this can be done by e.g. executing repmgr node service on the current primary: repmgr -f /etc/repmgr.conf node service --list-actions --action=stop repmgr -f /etc/repmgr.conf node service --list-actions --action=start repmgr -f /etc/repmgr.conf node service --list-actions --action=restart These commands can be defined in repmgr.conf with , and . If &repmgr; is installed from a package. you should set these commands to use the appropriate service commands defined by the package/operating system as these will ensure PostgreSQL is stopped/started properly taking into account configuration and log file locations etc. If the options aren't defined, &repmgr; will fall back to using pg_ctl to stop/start/restart PostgreSQL, which may not work properly, particularly when executed on a remote server. For more details, see . On systemd systems we strongly recommend using the appropriate systemctl commands (typically run via sudo) to ensure systemd is informed about the status of the PostgreSQL service. If using sudo for the systemctl calls, make sure the sudo specification doesn't require a real tty for the user. If not set this way, repmgr will fail to stop the primary. See the documentation section for further details. Check that access from applications is minimalized or preferably blocked completely, so applications are not unexpectedly interrupted. If an exclusive backup is running on the current primary, or if WAL replay is paused on the standby, &repmgr; will not perform the switchover. Check there is no significant replication lag on standbys attached to the current primary. If WAL file archiving is set up, check that there is no backlog of files waiting to be archived, as PostgreSQL will not finally shut down until all of these have been archived. If there is a backlog exceeding archive_ready_warning WAL files, &repmgr; will emit a warning before attempting to perform a switchover; you can also check manually with repmgr node check --archive-ready. From repmgr 4.2, &repmgr; will instruct any running &repmgrd; instances to pause operations while the switchover is being carried out, to prevent &repmgrd; from unintentionally promoting a node. For more details, see . Users of &repmgr; versions prior to 4.2 should ensure that &repmgrd; is not running on any nodes while a switchover is being executed. Finally, consider executing repmgr standby switchover with the --dry-run option; this will perform any necessary checks and inform you about success/failure, and stop before the first actual command is run (which would be the shutdown of the current primary). Example output: $ repmgr standby switchover -f /etc/repmgr.conf --siblings-follow --dry-run NOTICE: checking switchover on node "node2" (ID: 2) in --dry-run mode INFO: SSH connection to host "node1" succeeded INFO: archive mode is "off" INFO: replication lag on this standby is 0 seconds INFO: all sibling nodes are reachable via SSH NOTICE: local node "node2" (ID: 2) will be promoted to primary; current primary "node1" (ID: 1) will be demoted to standby INFO: following shutdown command would be run on node "node1": "pg_ctl -l /var/log/postgresql/startup.log -D '/var/lib/postgresql/data' -m fast -W stop" INFO: parameter "shutdown_check_timeout" is set to 60 seconds Be aware that checks the prerequisites for performing the switchover and some basic sanity checks on the state of the database which might effect the switchover operation (e.g. replication lag); it cannot however guarantee the switchover operation will succeed. In particular, if the current primary does not shut down cleanly, &repmgr; will not be able to reliably execute the switchover (as there would be a danger of divergence between the former and new primary nodes). See for a full list of available command line options and repmgr.conf settings relevant to performing a switchover. Switchover and pg_rewind pg_rewind using with "repmgr standby switchover" If the demotion candidate does not shut down smoothly or cleanly, there's a risk it will have a slightly divergent timeline and will not be able to attach to the new primary. To fix this situation without needing to reclone the old primary, it's possible to use the pg_rewind utility, which will usually be able to resync the two servers. To have &repmgr; execute pg_rewind if it detects this situation after promoting the new primary, add the option. If &repmgr; detects a situation where it needs to execute pg_rewind, it will execute a CHECKPOINT on the new primary before executing pg_rewind. For more details on pg_rewind, see: https://www.postgresql.org/docs/current/app-pgrewind.html. pg_rewind has been part of the core PostgreSQL distribution since version 9.5. Users of PostgreSQL 9.4 will need to manually install it; the source code is available here: https://github.com/vmware/pg_rewind. If the pg_rewind binary is not installed in the PostgreSQL bin directory, provide its full path on the demotion candidate with . Note that building the 9.4 version of pg_rewind requires the PostgreSQL source code. Executing the switchover command switchover execution To demonstrate switchover, we will assume a replication cluster with a primary (node1) and one standby (node2); after the switchover node2 should become the primary with node1 following it. The switchover command must be run from the standby which is to be promoted, and in its simplest form looks like this: $ repmgr -f /etc/repmgr.conf standby switchover NOTICE: executing switchover on node "node2" (ID: 2) INFO: searching for primary node INFO: checking if node 1 is primary INFO: current primary node is 1 INFO: SSH connection to host "node1" succeeded INFO: archive mode is "off" INFO: replication lag on this standby is 0 seconds NOTICE: local node "node2" (ID: 2) will be promoted to primary; current primary "node1" (ID: 1) will be demoted to standby NOTICE: stopping current primary node "node1" (ID: 1) NOTICE: issuing CHECKPOINT DETAIL: executing server command "pg_ctl -l /var/log/postgres/startup.log -D '/var/lib/pgsql/data' -m fast -W stop" INFO: checking primary status; 1 of 6 attempts NOTICE: current primary has been cleanly shut down at location 0/3001460 NOTICE: promoting standby to primary DETAIL: promoting server "node2" (ID: 2) using "pg_ctl -l /var/log/postgres/startup.log -w -D '/var/lib/pgsql/data' promote" server promoting NOTICE: STANDBY PROMOTE successful DETAIL: server "node2" (ID: 2) was successfully promoted to primary INFO: setting node 1's primary to node 2 NOTICE: starting server using "pg_ctl -l /var/log/postgres/startup.log -w -D '/var/lib/pgsql/data' restart" NOTICE: NODE REJOIN successful DETAIL: node 1 is now attached to node 2 NOTICE: switchover was successful DETAIL: node "node2" is now primary NOTICE: STANDBY SWITCHOVER is complete The old primary is now replicating as a standby from the new primary, and the cluster status will now look like this: $ repmgr -f /etc/repmgr.conf cluster show ID | Name | Role | Status | Upstream | Location | Connection string ----+-------+---------+-----------+----------+----------+-------------------------------------- 1 | node1 | standby | running | node2 | default | host=node1 dbname=repmgr user=repmgr 2 | node2 | primary | * running | | default | host=node2 dbname=repmgr user=repmgr If &repmgrd; is in use, it's worth double-checking that all nodes are unpaused by executing repmgr service status (&repmgr; 4.2 - 4.4: repmgr daemon status). Users of &repmgr; versions prior to 4.2 will need to manually restart &repmgrd; on all nodes after the switchover is completed. Caveats switchover caveats If using PostgreSQL 9.4, you should ensure that the shutdown command is configured to use PostgreSQL's fast shutdown mode (the default in 9.5 and later). If relying on pg_ctl to perform database server operations, you should include -m fast in pg_ctl_options in repmgr.conf. pg_rewind *requires* that either wal_log_hints is enabled, or that data checksums were enabled when the cluster was initialized. See the pg_rewind documentation for details. Troubleshooting switchover issues switchover troubleshooting As emphasised previously, performing a switchover is a non-trivial operation and there are a number of potential issues which can occur. While &repmgr; attempts to perform sanity checks, there's no guaranteed way of determining the success of a switchover without actually carrying it out. Demotion candidate (old primary) does not shut down &repmgr; may abort a switchover with a message like: ERROR: shutdown of the primary server could not be confirmed HINT: check the primary server status before performing any further actions This means the shutdown of the old primary has taken longer than &repmgr; expected, and it has given up waiting. In this case, check the PostgreSQL log on the primary server to see what is going on. It's entirely possible the shutdown process is just taking longer than the timeout set by the configuration parameter shutdown_check_timeout (default: 60 seconds), in which case you may need to adjust this parameter. Note that shutdown_check_timeout is set on the node where repmgr standby switchover is executed (promotion candidate); setting it on the demotion candidate (former primary) will have no effect. If the primary server has shut down cleanly, and no other node has been promoted, it is safe to restart it, in which case the replication cluster will be restored to its original configuration. Switchover aborts with an "exclusive backup" error &repmgr; may abort a switchover with a message like: ERROR: unable to perform a switchover while primary server is in exclusive backup mode HINT: stop backup before attempting the switchover This means an exclusive backup is running on the current primary; interrupting this will not only abort the backup, but potentially leave the primary with an ambiguous backup state. To proceed, either wait until the backup has finished, or cancel it with the command SELECT pg_stop_backup(). For more details see the PostgreSQL documentation section Making an exclusive low level backup. repmgr-5.3.1/doc/upgrading-from-repmgr3.md000066400000000000000000000003601420262710000203720ustar00rootroot00000000000000Upgrading from repmgr 3 ======================= This document has been integrated into the main `repmgr` documentation and is now located here: > [Upgrading from repmgr 3.x](https://repmgr.org/docs/current/upgrading-from-repmgr-3.html) repmgr-5.3.1/doc/upgrading-repmgr.xml000066400000000000000000000503721420262710000175560ustar00rootroot00000000000000 Upgrading repmgr upgrading &repmgr; is updated regularly with minor releases (e.g. 4.0.1 to 4.0.2) containing bugfixes and other minor improvements. Any substantial new functionality will be included in a major release (e.g. 4.0 to 4.1). Upgrading repmgr 4.x and later upgrading repmgr 4.x and later From version 4, &repmgr; consists of three elements: the repmgr and &repmgrd; executables the objects for the &repmgr; PostgreSQL extension (SQL files for creating/updating repmgr metadata, and the extension control file) the shared library module used by &repmgrd; which is resident in the PostgreSQL backend With minor releases, usually changes are only made to the repmgr and &repmgrd; executables. In this case, the upgrade is quite straightforward, and is simply a case of installing the new version, and restarting &repmgrd; (if running). For major releases, the &repmgr; PostgreSQL extension will need to be updated to the latest version. Additionally, if the shared library module has been updated (this is sometimes, but not always the case), PostgreSQL itself will need to be restarted on each node. Always check the release notes for every release as they may contain upgrade instructions particular to individual versions. Upgrading a minor version release upgrading minor release The process for installing minor version upgrades is quite straightforward: install the new &repmgr; version restart &repmgrd; on all nodes where it is running Some packaging systems (e.g. Debian/Ubuntu may restart &repmgrd; as part of the package upgrade process. Minor version upgrades can be performed in any order on the nodes in the replication cluster. A PostgreSQL restart is not required for minor version upgrades. The same &repmgr; "major version" (e.g. 4.2) must be installed on all nodes in the replication cluster. While it's possible to have differing &repmgr; "minor versions" (e.g. 4.2.1) on different nodes, we strongly recommend updating all nodes to the latest minor version. Upgrading a major version release upgrading major release "major version" upgrades need to be planned more carefully, as they may include changes to the &repmgr; metadata (which need to be propagated from the primary to all standbys) and/or changes to the shared object file used by &repmgrd; (which require a PostgreSQL restart). With this in mind, Stop &repmgrd; (if in use) on all nodes where it is running. Disable the &repmgrd; service on all nodes where it is in use; this is to prevent packages from prematurely restarting &repmgrd;. Install the updated package (or compile the updated source) on all nodes. If running a systemd-based Linux distribution, execute (as root, or with appropriate sudo permissions): systemctl daemon-reload If the &repmgr; shared library module has been updated (check the release notes!), restart PostgreSQL, then &repmgrd; (if in use) on each node, The order in which this is applied to individual nodes is not critical, and it's also fine to restart PostgreSQL on all nodes first before starting &repmgrd;. Note that if the upgrade requires a PostgreSQL restart, &repmgrd; will only function correctly once all nodes have been restarted. On the primary node, execute ALTER EXTENSION repmgr UPDATE in the database where &repmgr; is installed. Reenable the &repmgrd; service on all nodes where it is in use, and ensure it is running. If the &repmgr; upgrade requires a PostgreSQL restart, combine the &repmgr; upgrade with a PostgreSQL minor version upgrade, which will require a restart in any case. New PostgreSQL minor versions are usually released every couple of months; see the Roadmap for the current schedule. Checking repmgrd status after an upgrade upgrading checking repmgrd status From repmgr 4.2, once the upgrade is complete, execute the repmgr service status (&repmgr; 4.2 - 4.4: repmgr daemon status) command (on any node) to show an overview of the status of &repmgrd; on all nodes. pg_upgrade and repmgr upgrading pg_upgrade pg_upgrade pg_upgrade requires that if any functions are dependent on a shared library, this library must be present in both the old and new installations before pg_upgrade can be executed. To minimize the risk of any upgrade issues (particularly if an upgrade to a new major &repmgr; version is involved), we recommend upgrading &repmgr; on the old server before running pg_upgrade to ensure that old and new versions are the same. This issue applies to any PostgreSQL extension which has dependencies on a shared library. For further details please see the pg_upgrade documentation. If replication slots are in use, bear in mind these will not be recreated by pg_upgrade. These will need to be recreated manually. Use repmgr node check to determine which replication slots need to be recreated. Upgrading standbys with pg_upgrade and rsync If you are intending to upgrade a standby using the rsync method described in the pg_upgrade documentation, you must ensure the standby's replication configuration is present and correct before starting the standby. Use repmgr standby clone --replication-conf-only to generate the correct replication configuration. If upgrading from PostgreSQL 11 or earlier, be sure to delete recovery.conf, if present, otherwise PostgreSQL will refuse to start. Upgrading from repmgr 3.x upgrading from repmgr 3.x The upgrade process consists of two steps: converting the repmgr.conf configuration files upgrading the repmgr schema using CREATE EXTENSION (PostgreSQL 12 and earlier) A script is provided to assist with converting repmgr.conf. The schema upgrade (which converts the &repmgr; metadata into a packaged PostgreSQL extension) is normally carried out automatically when the &repmgr; extension is created. The shared library has been renamed from repmgr_funcs to repmgr - if it's set in shared_preload_libraries in postgresql.conf it will need to be updated to the new name: shared_preload_libraries = 'repmgr' Converting repmgr.conf configuration files With a completely new repmgr version, we've taken the opportunity to rename some configuration items for clarity and consistency, both between the configuration file and the column names in repmgr.nodes (e.g. node to node_id), and also for consistency with PostgreSQL naming conventions (e.g. loglevel to log_level). Other configuration items have been changed to command line options, and vice-versa, e.g. to avoid hard-coding items such as a a node's upstream ID, which might change over time. &repmgr; will issue a warning about deprecated/altered options. Changed parameters in "repmgr.conf" Following parameters have been added: data_directory: this is mandatory and must contain the path to the node's data directory monitoring_history: this replaces the &repmgrd; command line option --monitoring-history Following parameters have been renamed: Parameters renamed in repmgr4 repmgr3 repmgr4 node node_id loglevel log_level logfacility log_facility logfile log_file barman_server barman_host master_reponse_timeout async_query_timeout
From &repmgr; 4, barman_server refers to the server configured in Barman (in &repmgr; 3, the deprecated cluster parameter was used for this); the physical Barman hostname is configured with barman_host (see for details). Following parameters have been removed: cluster: is no longer required and will be ignored. upstream_node: is replaced by the command-line parameter --upstream-node-id
Conversion script To assist with conversion of repmgr.conf files, a Perl script is provided in contrib/convert-config.pl. Use like this: $ ./convert-config.pl /etc/repmgr.conf node_id=2 node_name='node2' conninfo='host=node2 dbname=repmgr user=repmgr connect_timeout=2' pg_ctl_options='-l /var/log/postgres/startup.log' rsync_options='--exclude=postgresql.local.conf --archive' log_level='INFO' pg_basebackup_options='--no-slot' data_directory='' The converted file is printed to STDOUT and the original file is not changed. Please note that the the conversion script will add an empty placeholder parameter for data_directory, which is a required parameter from &repmgr; 4. This must be manually modified to contain the correct data directory.
Upgrading the repmgr schema (PostgreSQL 12 and earlier) Ensure &repmgrd; is not running, or any cron jobs which execute the repmgr binary. Install the latest &repmgr; package; any repmgr 3.x packages should be uninstalled (if not automatically uninstalled already by your packaging system). Upgrading from repmgr 3.1.1 or earlier If you don't care about any data from the existing &repmgr; installation, (e.g. the contents of the events and monitoring tables), the following steps can be skipped; proceed to . If your repmgr version is 3.1.1 or earlier, you will need to update the schema to the latest version in the 3.x series (3.3.2) before converting the installation to repmgr 4. To do this, apply the following upgrade scripts as appropriate for your current version: repmgr3.0_repmgr3.1.sql repmgr3.1.1_repmgr3.1.2.sql For more details see the repmgr 3 upgrade notes. Manually create the repmgr extension In the database used by the existing &repmgr; installation, execute: CREATE EXTENSION repmgr FROM unpackaged This will move and convert all objects from the existing schema into the new, standard repmgr schema. There must be only one schema matching repmgr_% in the database, otherwise this step may not work. Upgrading the repmgr schema (PostgreSQL 13 and later) Beginning with PostgreSQL 13, the CREATE EXTENSION ... FROM unpackaged syntax is no longer available. In the unlikely event you have ended up with an installation running PostgreSQL 13 or later and containing the legacy &repmgr; schema, there is no convenient way of upgrading this; instead you'll just need to re-register the nodes as detailed in the following section, which will create the &repmgr; extension automatically. Any historical data you wish to retain (e.g. the contents of the events and monitoring tables) will need to be exported manually. Re-register each node This is necessary to update the repmgr metadata with some additional items. On the primary node, execute e.g. repmgr primary register -f /etc/repmgr.conf --force If not already present (e.g. after executing CREATE EXTENSION repmgr FROM unpackaged), the &repmgr; extension will be automatically created by repmgr primary register. On each standby node, execute e.g. repmgr standby register -f /etc/repmgr.conf --force Check the data is updated as expected by examining the repmgr.nodes table; restart &repmgrd; if required. The original repmgr_$cluster schema can be dropped at any time. Drop the legacy repmgr schema Once the cluster has been registered with the current &repmgr; version, the legacy repmgr_$cluster schema can be dropped at any time with: DROP SCHEMA repmgr_$cluster CASCADE (substitute $cluster with the value of the clustername variable used in &repmgr; 3.x).
repmgr-5.3.1/doc/website-docs.css000066400000000000000000000167571420262710000166750ustar00rootroot00000000000000/* PostgreSQL.org Documentation Style */ /* * Documentation generated by XSL stylesheets has lower-case class * names, older documentation generated by DSSSL stylesheets has * upper-case class names, so we need to support both for a while. In * some cases, the elements and classes differ further between the two * stylesheets. */ /* requires global.css, table.css and text.css to be loaded before this file! */ body { font-size: 76%; } .navheader table, .NAVHEADER table { margin-left: 0; } /* Container Definitions */ #docContainerWrap { text-align: center; /* Win IE5 */ } #docContainer { margin: 0 auto; width: 90%; padding-bottom: 2em; display: block; text-align: left; /* Win IE5 */ } #docHeader { background-image: url("/media/img/docs/bg_hdr.png"); height: 83px; margin: 0px; padding: 0px; display: block; } #docHeaderLogo { position: relative; width: 206px; height: 83px; border: 0px; padding: 0px; margin: 0 0 0 20px; } #docHeaderLogo img { border: 0px; } #docNavSearchContainer { padding-bottom: 2px; } #docNav, #docVersions { position: relative; text-align: left; margin-left: 10px; margin-top: 5px; color: #666; font-size: 0.95em; } #docSearch { position: relative; text-align: right; padding: 0; margin: 0; color: #666; } #docTextSize { text-align: right; white-space: nowrap; margin-top: 7px; font-size: 0.95em; } #docSearch form { position: relative; top: 5px; right: 0; margin: 0; /* need for IE 5.5 OSX */ text-align: right; /* need for IE 5.5 OSX */ white-space: nowrap; /* for Opera */ } #docSearch form label { color: #666; font-size: 0.95em; } #docSearch form input { font-size: 0.95em; } #docSearch form #submit { font-size: 0.95em; background: #7A7A7A; color: #fff; border: 1px solid #7A7A7A; padding: 1px 4px; } #docSearch form #q { width: 170px; font-size: 0.95em; border: 1px solid #7A7A7A; background: #E1E1E1; color: #000000; padding: 2px; } .frmDocSearch { padding: 0; margin: 0; display: inline; } .inpDocSearch { padding: 0; margin: 0; color: #000; } #docContent { position: relative; margin-left: 10px; margin-right: 10px; margin-top: 40px; } #docFooter { position: relative; font-size: 0.9em; color: #666; line-height: 1.3em; margin-left: 10px; margin-right: 10px; } #docComments { margin-top: 10px; } #docClear { clear: both; margin: 0; padding: 0; } /* Heading Definitions */ h1, h2, h3 { font-weight: bold; margin-top: 2ex; } h1 { font-size: 1.4em; } h2 { font-size: 1.2em !important; } h3 { font-size: 1.1em; } h1 a:hover { color: #EC5800; text-decoration: none; } h2 a:hover, h3 a:hover, h4 a:hover { color: #666666; text-decoration: none; } /* * Change color of h2 chunk titles in XSL build. (In DSSSL build, * these will be h1, which is already handled elsewhere.) */ .titlepage h2.title, .refnamediv h2 { color: #EC5800; } /* Text Styles */ div.sect2, div.SECT2 { margin-top: 4ex; } div.sect3, div.SECT3 { margin-top: 3ex; margin-left: 3ex; } .txtCurrentLocation { font-weight: bold; } p, ol, ul, li { line-height: 1.5em; } .txtCommentsWrap { border: 2px solid #F5F5F5; width: 100%; } .txtCommentsContent { background: #F5F5F5; padding: 3px; } .txtCommentsPoster { float: left; } .txtCommentsDate { float: right; } .txtCommentsComment { padding: 3px; } #docContainer pre code, #docContainer pre tt, #docContainer pre pre, #docContainer tt tt, #docContainer tt code, #docContainer tt pre { font-size: 1em; } pre.literallayout, .screen, .synopsis, .programlisting, .refsynopsisdiv p, .caution, .warning, .note, .tip, .table table, .informaltable table, pre.LITERALLAYOUT, .SCREEN, .SYNOPSIS, .PROGRAMLISTING, .REFSYNOPSISDIV p, table.CAUTION, table.WARNING, blockquote.NOTE, blockquote.TIP, table.CALSTABLE { -moz-box-shadow: 3px 3px 5px #DFDFDF; -webkit-box-shadow: 3px 3px 5px #DFDFDF; -khtml-box-shadow: 3px 3px 5px #DFDFDF; -o-box-shadow: 3px 3px 5px #DFDFDF; box-shadow: 3px 3px 5px #DFDFDF; } pre.literallayout, .screen, .synopsis, .programlisting, .refsynopsisdiv p, .caution, .warning, .note, .tip, pre.LITERALLAYOUT, .SCREEN, .SYNOPSIS, .PROGRAMLISTING, .REFSYNOPSISDIV p, table.CAUTION, table.WARNING, blockquote.NOTE, blockquote.TIP { color: black; border-width: 1px; border-style: solid; padding: 2ex; margin: 2ex 0 2ex 2ex; overflow: auto; -moz-border-radius: 8px; -webkit-border-radius: 8px; -khtml-border-radius: 8px; border-radius: 8px; } pre.literallayout, pre.synopsis, pre.programlisting, .refsynopsisdiv p, .screen, pre.LITERALLAYOUT, pre.SYNOPSIS, pre.PROGRAMLISTING, .REFSYNOPSISDIV p, .SCREEN { border-color: #CFCFCF; background-color: #F7F7F7; } .note, .tip, blockquote.NOTE, blockquote.TIP { border-color: #DBDBCC; background-color: #EEEEDD; padding: 14px; width: 572px; } .note, .tip, .caution, .warning, blockquote.NOTE, blockquote.TIP, table.CAUTION, table.WARNING { margin: 4ex auto; } .note p, .tip p, blockquote.NOTE p, blockquote.TIP p { margin: 0; } .note pre, .note code, .tip pre, .tip code, blockquote.NOTE pre, blockquote.NOTE code, blockquote.TIP pre, blockquote.TIP code { margin-left: 0; margin-right: 0; -moz-box-shadow: none; -webkit-box-shadow: none; -khtml-box-shadow: none; -o-box-shadow: none; box-shadow: none; } .caution, .warning { max-width: 600px; } .tip h3, .note h3, .caution h3, .warning h3 { text-align: center; } .emphasis, .c2 { font-weight: bold; } .replaceable, .REPLACEABLE { font-style: italic; } /* Table Styles */ table { margin-left: 2ex; } .table table td, .table table th, .informaltable table td, .informaltable table th, table.CALSTABLE td, table.CALSTABLE th, table.CAUTION td, table.CAUTION th, table.WARNING td, table.WARNING th { border-style: solid; } .table table, .informaltable table, table.CALSTABLE, table.CAUTION, table.WARNING { border-spacing: 0; border-collapse: collapse; } .table table, .informaltable table, table.CALSTABLE { margin: 2ex 0 2ex 2ex; background-color: #E0ECEF; border: 2px solid #A7C6DF; } .table table tr:hover td, .informaltable table tr:hover td, table.CALSTABLE tr:hover td { background-color: #EFEFEF; } .table table td, .informaltable table td, table.CALSTABLE td { background-color: #FFF; } .table table td, .table table th, .informaltable table td, .informaltable table th, table.CALSTABLE td, table.CALSTABLE th { border: 1px solid #A7C6DF; padding: 0.5ex 0.5ex; } table.CAUTION, table.WARNING { border-collapse: separate; display: block; padding: 0; max-width: 600px; } .caution, table.CAUTION { background-color: #F5F5DC; border-color: #DEDFA7; } .warning, table.WARNING { background-color: #FFD7D7; border-color: #DF421E; } table.CAUTION td, table.CAUTION th, table.WARNING td, table.WARNING th { border-width: 0; padding-left: 2ex; padding-right: 2ex; } table.CAUTION td, table.CAUTION th { border-color: #F3E4D5 } table.WARNING td, table.WARNING th { border-color: #FFD7D7; } td.c1, td.c2, td.c3, td.c4, td.c5, td.c6 { font-size: 1.1em; font-weight: bold; border-bottom: 0px solid #FFEFEF; padding: 1ex 2ex 0; } /* Link Styles */ #docNav a { font-weight: bold; } a:link, a:visited, a:active, a:hover { text-decoration: underline; } a:link, a:active { color:#0066A2; } a:visited { color:#004E66; } a:hover { color:#000000; } #docFooter a:link, #docFooter a:visited, #docFooter a:active { color:#666; } #docContainer code.function tt, #docContainer code.FUNCTION tt { font-size: 1em; } repmgr-5.3.1/errcode.h000066400000000000000000000030671420262710000146100ustar00rootroot00000000000000/* * errcode.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _ERRCODE_H_ #define _ERRCODE_H_ /* Exit return codes */ #define SUCCESS 0 #define ERR_BAD_CONFIG 1 #define ERR_BAD_RSYNC 2 #define ERR_BAD_PIDFILE 3 #define ERR_NO_RESTART 4 #define ERR_LOCAL_COMMAND 5 #define ERR_DB_CONN 6 #define ERR_DB_QUERY 7 #define ERR_PROMOTION_FAIL 8 #define ERR_MONITORING_TIMEOUT 9 #define ERR_STR_OVERFLOW 10 #define ERR_FAILOVER_FAIL 11 #define ERR_BAD_SSH 12 #define ERR_SYS_FAILURE 13 #define ERR_BAD_BASEBACKUP 14 #define ERR_INTERNAL 15 #define ERR_MONITORING_FAIL 16 #define ERR_BAD_BACKUP_LABEL 17 #define ERR_SWITCHOVER_FAIL 18 #define ERR_BARMAN 19 #define ERR_REGISTRATION_SYNC 20 #define ERR_OUT_OF_MEMORY 21 #define ERR_SWITCHOVER_INCOMPLETE 22 #define ERR_FOLLOW_FAIL 23 #define ERR_REJOIN_FAIL 24 #define ERR_NODE_STATUS 25 #define ERR_REPMGRD_PAUSE 26 #define ERR_REPMGRD_SERVICE 27 #endif /* _ERRCODE_H_ */ repmgr-5.3.1/expected/000077500000000000000000000000001420262710000146075ustar00rootroot00000000000000repmgr-5.3.1/expected/repmgr_extension.out000066400000000000000000000050121420262710000207260ustar00rootroot00000000000000-- minimal SQL tests -- -- comprehensive tests will require a working replication cluster -- set up using the "repmgr" binary and with "repmgrd" running -- extension CREATE EXTENSION repmgr; -- tables SELECT * FROM repmgr.nodes; node_id | upstream_node_id | active | node_name | type | location | priority | conninfo | repluser | slot_name | config_file ---------+------------------+--------+-----------+------+----------+----------+----------+----------+-----------+------------- (0 rows) SELECT * FROM repmgr.events; node_id | event | successful | event_timestamp | details ---------+-------+------------+-----------------+--------- (0 rows) SELECT * FROM repmgr.monitoring_history; primary_node_id | standby_node_id | last_monitor_time | last_apply_time | last_wal_primary_location | last_wal_standby_location | replication_lag | apply_lag -----------------+-----------------+-------------------+-----------------+---------------------------+---------------------------+-----------------+----------- (0 rows) -- views SELECT * FROM repmgr.replication_status; primary_node_id | standby_node_id | standby_name | node_type | active | last_monitor_time | last_wal_primary_location | last_wal_standby_location | replication_lag | replication_time_lag | apply_lag | communication_time_lag -----------------+-----------------+--------------+-----------+--------+-------------------+---------------------------+---------------------------+-----------------+----------------------+-----------+------------------------ (0 rows) SELECT * FROM repmgr.show_nodes; node_id | node_name | active | upstream_node_id | upstream_node_name | type | priority | conninfo ---------+-----------+--------+------------------+--------------------+------+----------+---------- (0 rows) -- functions SELECT repmgr.get_new_primary(); get_new_primary ----------------- -1 (1 row) SELECT repmgr.notify_follow_primary(-1); notify_follow_primary ----------------------- (1 row) SELECT repmgr.notify_follow_primary(NULL); notify_follow_primary ----------------------- (1 row) SELECT repmgr.reset_voting_status(); reset_voting_status --------------------- (1 row) SELECT repmgr.set_local_node_id(-1); set_local_node_id ------------------- (1 row) SELECT repmgr.set_local_node_id(NULL); set_local_node_id ------------------- (1 row) SELECT repmgr.standby_get_last_updated(); standby_get_last_updated -------------------------- (1 row) SELECT repmgr.standby_set_last_updated(); standby_set_last_updated -------------------------- (1 row) repmgr-5.3.1/log.c000066400000000000000000000204641420262710000137410ustar00rootroot00000000000000/* * log.c - Logging methods * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #include "repmgr.h" #include #ifdef HAVE_SYSLOG #include #endif #include #include #include "log.h" #define DEFAULT_IDENT "repmgr" #ifdef HAVE_SYSLOG #define DEFAULT_SYSLOG_FACILITY LOG_LOCAL0 #endif /* #define REPMGR_DEBUG */ static int detect_log_facility(const char *facility); static void _stderr_log_with_level(const char *level_name, int level, const char *fmt, va_list ap) __attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 0))); int log_type = REPMGR_STDERR; int log_level = LOG_INFO; int last_log_level = LOG_INFO; int verbose_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 stderr_log_with_level(const char *level_name, int level, const char *fmt,...) { va_list arglist; va_start(arglist, fmt); _stderr_log_with_level(level_name, level, fmt, arglist); va_end(arglist); } static void _stderr_log_with_level(const char *level_name, int level, const char *fmt, va_list ap) { char buf[100]; /* * Store the requested level so that if there's a subsequent log_hint() or * log_detail(), we can suppress that if --terse was specified, */ last_log_level = level; if (log_level >= level) { /* Format log line prefix with timestamp if in daemon mode */ if (logger_output_mode == OM_DAEMON) { time_t t; struct tm *tm; time(&t); tm = localtime(&t); strftime(buf, sizeof(buf), "[%Y-%m-%d %H:%M:%S]", tm); fprintf(stderr, "%s [%s] ", buf, level_name); } else { fprintf(stderr, "%s: ", level_name); } vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); fflush(stderr); } } void log_hint(const char *fmt,...) { va_list ap; if (terse_logging == false) { va_start(ap, fmt); _stderr_log_with_level("HINT", last_log_level, fmt, ap); va_end(ap); } } void log_detail(const char *fmt,...) { va_list ap; if (terse_logging == false) { va_start(ap, fmt); _stderr_log_with_level("DETAIL", last_log_level, fmt, ap); va_end(ap); } } void log_verbose(int level, const char *fmt,...) { va_list ap; va_start(ap, fmt); if (verbose_logging == true) { switch (level) { case LOG_EMERG: _stderr_log_with_level("EMERG", level, fmt, ap); break; case LOG_ALERT: _stderr_log_with_level("ALERT", level, fmt, ap); break; case LOG_CRIT: _stderr_log_with_level("CRIT", level, fmt, ap); break; case LOG_ERROR: _stderr_log_with_level("ERROR", level, fmt, ap); break; case LOG_WARNING: _stderr_log_with_level("WARNING", level, fmt, ap); break; case LOG_NOTICE: _stderr_log_with_level("NOTICE", level, fmt, ap); break; case LOG_INFO: _stderr_log_with_level("INFO", level, fmt, ap); break; case LOG_DEBUG: _stderr_log_with_level("DEBUG", level, fmt, ap); break; } } va_end(ap); } bool logger_init(t_configuration_options *opts, const char *ident) { char *level = opts->log_level; char *facility = opts->log_facility; int l; int f; #ifdef HAVE_SYSLOG int syslog_facility = DEFAULT_SYSLOG_FACILITY; #endif #ifdef REPMGR_DEBUG printf("logger initialisation (Level: %s, Facility: %s)\n", level, facility); #endif if (!ident) { ident = DEFAULT_IDENT; } if (level && *level) { l = detect_log_level(level); #ifdef REPMGR_DEBUG printf("assigned level for logger: %d\n", l); #endif if (l >= 0) log_level = l; else 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) { f = detect_log_facility(facility); #ifdef REPMGR_DEBUG printf("assigned facility for logger: %d\n", f); #endif if (f == 0) { /* No syslog requested, just stderr */ #ifdef REPMGR_DEBUG printf(_("using stderr for logging\n")); #endif } else if (f == -1) { stderr_log_warning(_("cannot detect log facility %s (use any of LOCAL0, LOCAL1, ..., LOCAL7, USER or STDERR)\n"), facility); } #ifdef HAVE_SYSLOG else { syslog_facility = f; log_type = REPMGR_SYSLOG; } #endif } #ifdef HAVE_SYSLOG if (log_type == REPMGR_SYSLOG) { setlogmask(LOG_UPTO(log_level)); openlog(ident, LOG_CONS | LOG_PID | LOG_NDELAY, syslog_facility); stderr_log_notice(_("setup syslog (level: %s, facility: %s)\n"), level, facility); } #endif if (*opts->log_file) { FILE *fd; /* * Check if we can write to the specified file before redirecting * stderr - if freopen() fails, stderr output will vanish into the * ether and the user won't know what's going on. */ fd = fopen(opts->log_file, "a"); if (fd == NULL) { stderr_log_error(_("unable to open specified log file \"%s\" for writing: %s\n"), opts->log_file, strerror(errno)); stderr_log_error(_("Terminating\n")); exit(ERR_BAD_CONFIG); } fclose(fd); stderr_log_notice(_("redirecting logging output to \"%s\"\n"), opts->log_file); fd = freopen(opts->log_file, "a", stderr); /* * It's possible freopen() may still fail due to e.g. a race * condition; as it's not feasible to restore stderr after a failed * freopen(), we'll write to stdout as a last resort. */ if (fd == NULL) { printf(_("unable to open specified log file %s for writing: %s\n"), opts->log_file, strerror(errno)); printf(_("terminating\n")); exit(ERR_BAD_CONFIG); } } return true; } bool logger_shutdown(void) { #ifdef HAVE_SYSLOG if (log_type == REPMGR_SYSLOG) closelog(); #endif return true; } /* * Indicate whether extra-verbose logging is required. This will * generate a lot of output, particularly debug logging, and should * not be permanently enabled in production. * * NOTE: in previous repmgr versions, this option forced the log * level to INFO. */ void logger_set_verbose(void) { verbose_logging = true; } /* * Indicate whether some non-critical log messages can be omitted. * Currently this includes warnings about irrelevant command line * options and hints. */ void logger_set_terse(void) { terse_logging = true; } void logger_set_level(int new_log_level) { log_level = new_log_level; } void logger_set_min_level(int min_log_level) { if (min_log_level > log_level) log_level = min_log_level; } int detect_log_level(const char *level) { if (!strcasecmp(level, "DEBUG")) return LOG_DEBUG; if (!strcasecmp(level, "INFO")) return LOG_INFO; if (!strcasecmp(level, "NOTICE")) return LOG_NOTICE; if (!strcasecmp(level, "WARNING")) return LOG_WARNING; if (!strcasecmp(level, "ERROR")) return LOG_ERROR; if (!strcasecmp(level, "ALERT")) return LOG_ALERT; if (!strcasecmp(level, "CRIT")) return LOG_CRIT; if (!strcasecmp(level, "EMERG")) return LOG_EMERG; return -1; } static int detect_log_facility(const char *facility) { int local = 0; if (!strncmp(facility, "LOCAL", 5) && strlen(facility) == 6) { local = atoi(&facility[5]); switch (local) { case 0: return LOG_LOCAL0; break; case 1: return LOG_LOCAL1; break; case 2: return LOG_LOCAL2; break; case 3: return LOG_LOCAL3; break; case 4: return LOG_LOCAL4; break; case 5: return LOG_LOCAL5; break; case 6: return LOG_LOCAL6; break; case 7: return LOG_LOCAL7; break; } } else if (!strcmp(facility, "USER")) { return LOG_USER; } else if (!strcmp(facility, "STDERR")) { return 0; } return -1; } repmgr-5.3.1/log.h000066400000000000000000000107471420262710000137510ustar00rootroot00000000000000/* * log.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _REPMGR_LOG_H_ #define _REPMGR_LOG_H_ #include "repmgr.h" #define REPMGR_SYSLOG 1 #define REPMGR_STDERR 2 #define OM_COMMAND_LINE 1 #define OM_DAEMON 2 #define DEFAULT_LOG_STATUS_INTERVAL 300 extern void stderr_log_with_level(const char *level_name, int level, const char *fmt,...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 4))); #define LOG_EMERG 0 /* system is unusable */ #define LOG_ALERT 1 /* action must be taken immediately */ #define LOG_CRIT 2 /* critical conditions */ #define LOG_ERROR 3 /* error conditions */ #define LOG_WARNING 4 /* warning conditions */ #define LOG_NOTICE 5 /* normal but significant condition */ #define LOG_INFO 6 /* informational */ #define LOG_DEBUG 7 /* debug-level messages */ /* Standard error logging */ #define stderr_log_debug(...) stderr_log_with_level("DEBUG", LOG_DEBUG, __VA_ARGS__) #define stderr_log_info(...) stderr_log_with_level("INFO", LOG_INFO, __VA_ARGS__) #define stderr_log_notice(...) stderr_log_with_level("NOTICE", LOG_NOTICE, __VA_ARGS__) #define stderr_log_warning(...) stderr_log_with_level("WARNING", LOG_WARNING, __VA_ARGS__) #define stderr_log_error(...) stderr_log_with_level("ERROR", LOG_ERROR, __VA_ARGS__) #define stderr_log_crit(...) stderr_log_with_level("CRITICAL", LOG_CRIT, __VA_ARGS__) #define stderr_log_alert(...) stderr_log_with_level("ALERT", LOG_ALERT, __VA_ARGS__) #define stderr_log_emerg(...) stderr_log_with_level("EMERGENCY", LOG_EMERG, __VA_ARGS__) #ifdef HAVE_SYSLOG #include #define log_debug(...) \ if (log_type == REPMGR_SYSLOG) \ syslog(LOG_DEBUG, __VA_ARGS__); \ else \ stderr_log_debug(__VA_ARGS__); #define log_info(...) \ { \ if (log_type == REPMGR_SYSLOG) syslog(LOG_INFO, __VA_ARGS__); \ else stderr_log_info(__VA_ARGS__); \ } #define log_notice(...) \ { \ if (log_type == REPMGR_SYSLOG) syslog(LOG_NOTICE, __VA_ARGS__); \ else stderr_log_notice(__VA_ARGS__); \ } #define log_warning(...) \ { \ if (log_type == REPMGR_SYSLOG) syslog(LOG_WARNING, __VA_ARGS__); \ else stderr_log_warning(__VA_ARGS__); \ } #define log_error(...) \ { \ if (log_type == REPMGR_SYSLOG) syslog(LOG_ERROR, __VA_ARGS__); \ else stderr_log_error(__VA_ARGS__); \ } #define log_crit(...) \ { \ if (log_type == REPMGR_SYSLOG) syslog(LOG_CRIT, __VA_ARGS__); \ else stderr_log_crit(__VA_ARGS__); \ } #define log_alert(...) \ { \ if (log_type == REPMGR_SYSLOG) syslog(LOG_ALERT, __VA_ARGS__); \ else stderr_log_alert(__VA_ARGS__); \ } #define log_emerg(...) \ { \ if (log_type == REPMGR_SYSLOG) syslog(LOG_ALERT, __VA_ARGS__); \ else stderr_log_alert(__VA_ARGS__); \ } #else #define log_debug(...) stderr_log_debug(__VA_ARGS__) #define log_info(...) stderr_log_info(__VA_ARGS__) #define log_notice(...) stderr_log_notice(__VA_ARGS__) #define log_warning(...) stderr_log_warning(__VA_ARGS__) #define log_error(...) stderr_log_error(__VA_ARGS__) #define log_crit(...) stderr_log_crit(__VA_ARGS__) #define log_alert(...) stderr_log_alert(__VA_ARGS__) #define log_emerg(...) stderr_log_emerg(__VA_ARGS__) #endif int detect_log_level(const char *level); /* Logger initialisation and shutdown */ bool logger_init(t_configuration_options *opts, const char *ident); bool logger_shutdown(void); void logger_set_verbose(void); void logger_set_terse(void); void logger_set_min_level(int min_log_level); void logger_set_level(int new_log_level); void log_detail(const char *fmt,...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2))); void log_hint(const char *fmt,...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2))); void log_verbose(int level, const char *fmt,...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3))); extern int log_type; extern int log_level; extern int verbose_logging; extern int terse_logging; extern int logger_output_mode; #endif /* _REPMGR_LOG_H_ */ repmgr-5.3.1/repmgr--4.0--4.1.sql000066400000000000000000000002101420262710000157450ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit repmgr-5.3.1/repmgr--4.0.sql000066400000000000000000000124551420262710000154060ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit CREATE TABLE repmgr.nodes ( node_id INTEGER PRIMARY KEY, upstream_node_id INTEGER NULL REFERENCES nodes (node_id) DEFERRABLE, active BOOLEAN NOT NULL DEFAULT TRUE, node_name TEXT NOT NULL, type TEXT NOT NULL CHECK (type IN('primary','standby','witness','bdr')), location TEXT NOT NULL DEFAULT 'default', priority INT NOT NULL DEFAULT 100, conninfo TEXT NOT NULL, repluser VARCHAR(63) NOT NULL, slot_name TEXT NULL, config_file TEXT NOT NULL ); CREATE TABLE repmgr.events ( node_id INTEGER NOT NULL, event TEXT NOT NULL, successful BOOLEAN NOT NULL DEFAULT TRUE, event_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, details TEXT NULL ); DO $repmgr$ DECLARE DECLARE server_version_num INT; BEGIN SELECT setting FROM pg_catalog.pg_settings WHERE name = 'server_version_num' INTO server_version_num; IF server_version_num >= 90400 THEN EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location PG_LSN NOT NULL, last_wal_standby_location PG_LSN, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; ELSE EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location TEXT NOT NULL, last_wal_standby_location TEXT, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; END IF; END$repmgr$; CREATE INDEX idx_monitoring_history_time ON repmgr.monitoring_history (last_monitor_time, standby_node_id); CREATE VIEW repmgr.show_nodes AS SELECT n.node_id, n.node_name, n.active, n.upstream_node_id, un.node_name AS upstream_node_name, n.type, n.priority, n.conninfo FROM repmgr.nodes n LEFT JOIN repmgr.nodes un ON un.node_id = n.upstream_node_id; CREATE TABLE repmgr.voting_term ( term INT NOT NULL ); CREATE UNIQUE INDEX voting_term_restrict ON repmgr.voting_term ((TRUE)); CREATE RULE voting_term_delete AS ON DELETE TO repmgr.voting_term DO INSTEAD NOTHING; /* ================= */ /* repmgrd functions */ /* ================= */ /* monitoring functions */ CREATE FUNCTION set_local_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION get_local_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'get_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION standby_set_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_set_last_updated' LANGUAGE C STRICT; CREATE FUNCTION standby_get_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_get_last_updated' LANGUAGE C STRICT; /* failover functions */ CREATE FUNCTION notify_follow_primary(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'notify_follow_primary' LANGUAGE C STRICT; CREATE FUNCTION get_new_primary() RETURNS INT AS 'MODULE_PATHNAME', 'get_new_primary' LANGUAGE C STRICT; CREATE FUNCTION reset_voting_status() RETURNS VOID AS 'MODULE_PATHNAME', 'reset_voting_status' LANGUAGE C STRICT; CREATE FUNCTION am_bdr_failover_handler(INT) RETURNS BOOL AS 'MODULE_PATHNAME', 'am_bdr_failover_handler' LANGUAGE C STRICT; CREATE FUNCTION unset_bdr_failover_handler() RETURNS VOID AS 'MODULE_PATHNAME', 'unset_bdr_failover_handler' LANGUAGE C STRICT; CREATE VIEW repmgr.replication_status AS SELECT m.primary_node_id, m.standby_node_id, n.node_name AS standby_name, n.type AS node_type, n.active, last_monitor_time, CASE WHEN n.type='standby' THEN m.last_wal_primary_location ELSE NULL END AS last_wal_primary_location, m.last_wal_standby_location, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.replication_lag) ELSE NULL END AS replication_lag, CASE WHEN n.type='standby' THEN CASE WHEN replication_lag > 0 THEN age(now(), m.last_apply_time) ELSE '0'::INTERVAL END ELSE NULL END AS replication_time_lag, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.apply_lag) ELSE NULL END AS apply_lag, AGE(NOW(), CASE WHEN pg_catalog.pg_is_in_recovery() THEN repmgr.standby_get_last_updated() ELSE m.last_monitor_time END) AS communication_time_lag FROM repmgr.monitoring_history m JOIN repmgr.nodes n ON m.standby_node_id = n.node_id WHERE (m.standby_node_id, m.last_monitor_time) IN ( SELECT m1.standby_node_id, MAX(m1.last_monitor_time) FROM repmgr.monitoring_history m1 GROUP BY 1 ); repmgr-5.3.1/repmgr--4.1--4.2.sql000066400000000000000000000015101420262710000157530ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit CREATE FUNCTION get_repmgrd_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_repmgrd_pid' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pidfile() RETURNS TEXT AS 'MODULE_PATHNAME', 'get_repmgrd_pidfile' LANGUAGE C STRICT; CREATE FUNCTION set_repmgrd_pid(INT, TEXT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_repmgrd_pid' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_is_running() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_running' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_pause(BOOL) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgrd_pause' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_is_paused() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_paused' LANGUAGE C STRICT; repmgr-5.3.1/repmgr--4.1.sql000066400000000000000000000124541420262710000154060ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit CREATE TABLE repmgr.nodes ( node_id INTEGER PRIMARY KEY, upstream_node_id INTEGER NULL REFERENCES nodes (node_id) DEFERRABLE, active BOOLEAN NOT NULL DEFAULT TRUE, node_name TEXT NOT NULL, type TEXT NOT NULL CHECK (type IN('primary','standby','witness','bdr')), location TEXT NOT NULL DEFAULT 'default', priority INT NOT NULL DEFAULT 100, conninfo TEXT NOT NULL, repluser VARCHAR(63) NOT NULL, slot_name TEXT NULL, config_file TEXT NOT NULL ); CREATE TABLE repmgr.events ( node_id INTEGER NOT NULL, event TEXT NOT NULL, successful BOOLEAN NOT NULL DEFAULT TRUE, event_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, details TEXT NULL ); DO $repmgr$ DECLARE DECLARE server_version_num INT; BEGIN SELECT setting FROM pg_catalog.pg_settings WHERE name = 'server_version_num' INTO server_version_num; IF server_version_num >= 90400 THEN EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location PG_LSN NOT NULL, last_wal_standby_location PG_LSN, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; ELSE EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location TEXT NOT NULL, last_wal_standby_location TEXT, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; END IF; END$repmgr$; CREATE INDEX idx_monitoring_history_time ON repmgr.monitoring_history (last_monitor_time, standby_node_id); CREATE VIEW repmgr.show_nodes AS SELECT n.node_id, n.node_name, n.active, n.upstream_node_id, un.node_name AS upstream_node_name, n.type, n.priority, n.conninfo FROM repmgr.nodes n LEFT JOIN repmgr.nodes un ON un.node_id = n.upstream_node_id; CREATE TABLE repmgr.voting_term ( term INT NOT NULL ); CREATE UNIQUE INDEX voting_term_restrict ON repmgr.voting_term ((TRUE)); CREATE RULE voting_term_delete AS ON DELETE TO repmgr.voting_term DO INSTEAD NOTHING; /* ================= */ /* repmgrd functions */ /* ================= */ /* monitoring functions */ CREATE FUNCTION set_local_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION get_local_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'get_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION standby_set_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_set_last_updated' LANGUAGE C STRICT; CREATE FUNCTION standby_get_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_get_last_updated' LANGUAGE C STRICT; /* failover functions */ CREATE FUNCTION notify_follow_primary(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'notify_follow_primary' LANGUAGE C STRICT; CREATE FUNCTION get_new_primary() RETURNS INT AS 'MODULE_PATHNAME', 'get_new_primary' LANGUAGE C STRICT; CREATE FUNCTION reset_voting_status() RETURNS VOID AS 'MODULE_PATHNAME', 'reset_voting_status' LANGUAGE C STRICT; CREATE FUNCTION am_bdr_failover_handler(INT) RETURNS BOOL AS 'MODULE_PATHNAME', 'am_bdr_failover_handler' LANGUAGE C STRICT; CREATE FUNCTION unset_bdr_failover_handler() RETURNS VOID AS 'MODULE_PATHNAME', 'unset_bdr_failover_handler' LANGUAGE C STRICT; CREATE VIEW repmgr.replication_status AS SELECT m.primary_node_id, m.standby_node_id, n.node_name AS standby_name, n.type AS node_type, n.active, last_monitor_time, CASE WHEN n.type='standby' THEN m.last_wal_primary_location ELSE NULL END AS last_wal_primary_location, m.last_wal_standby_location, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.replication_lag) ELSE NULL END AS replication_lag, CASE WHEN n.type='standby' THEN CASE WHEN replication_lag > 0 THEN age(now(), m.last_apply_time) ELSE '0'::INTERVAL END ELSE NULL END AS replication_time_lag, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.apply_lag) ELSE NULL END AS apply_lag, AGE(NOW(), CASE WHEN pg_catalog.pg_is_in_recovery() THEN repmgr.standby_get_last_updated() ELSE m.last_monitor_time END) AS communication_time_lag FROM repmgr.monitoring_history m JOIN repmgr.nodes n ON m.standby_node_id = n.node_id WHERE (m.standby_node_id, m.last_monitor_time) IN ( SELECT m1.standby_node_id, MAX(m1.last_monitor_time) FROM repmgr.monitoring_history m1 GROUP BY 1 ); repmgr-5.3.1/repmgr--4.2--4.3.sql000066400000000000000000000007651420262710000157700ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION repmgr UPDATE" to load this file. \quit -- This script is intentionally empty and exists to skip the CREATE FUNCTION -- commands contained in the 4.2--4.3 and 4.3--4.4 extension upgrade scripts, -- which reference C functions which no longer exist in 5.3 and later. -- -- These functions will be explicitly created in the 5.2--5.3 extension -- upgrade step with the correct C function references. repmgr-5.3.1/repmgr--4.2.sql000066400000000000000000000137551420262710000154140ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit CREATE TABLE repmgr.nodes ( node_id INTEGER PRIMARY KEY, upstream_node_id INTEGER NULL REFERENCES nodes (node_id) DEFERRABLE, active BOOLEAN NOT NULL DEFAULT TRUE, node_name TEXT NOT NULL, type TEXT NOT NULL CHECK (type IN('primary','standby','witness','bdr')), location TEXT NOT NULL DEFAULT 'default', priority INT NOT NULL DEFAULT 100, conninfo TEXT NOT NULL, repluser VARCHAR(63) NOT NULL, slot_name TEXT NULL, config_file TEXT NOT NULL ); CREATE TABLE repmgr.events ( node_id INTEGER NOT NULL, event TEXT NOT NULL, successful BOOLEAN NOT NULL DEFAULT TRUE, event_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, details TEXT NULL ); DO $repmgr$ DECLARE DECLARE server_version_num INT; BEGIN SELECT setting FROM pg_catalog.pg_settings WHERE name = 'server_version_num' INTO server_version_num; IF server_version_num >= 90400 THEN EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location PG_LSN NOT NULL, last_wal_standby_location PG_LSN, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; ELSE EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location TEXT NOT NULL, last_wal_standby_location TEXT, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; END IF; END$repmgr$; CREATE INDEX idx_monitoring_history_time ON repmgr.monitoring_history (last_monitor_time, standby_node_id); CREATE VIEW repmgr.show_nodes AS SELECT n.node_id, n.node_name, n.active, n.upstream_node_id, un.node_name AS upstream_node_name, n.type, n.priority, n.conninfo FROM repmgr.nodes n LEFT JOIN repmgr.nodes un ON un.node_id = n.upstream_node_id; CREATE TABLE repmgr.voting_term ( term INT NOT NULL ); CREATE UNIQUE INDEX voting_term_restrict ON repmgr.voting_term ((TRUE)); CREATE RULE voting_term_delete AS ON DELETE TO repmgr.voting_term DO INSTEAD NOTHING; /* ================= */ /* repmgrd functions */ /* ================= */ /* monitoring functions */ CREATE FUNCTION set_local_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION get_local_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'get_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION standby_set_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_set_last_updated' LANGUAGE C STRICT; CREATE FUNCTION standby_get_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_get_last_updated' LANGUAGE C STRICT; /* failover functions */ CREATE FUNCTION notify_follow_primary(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'notify_follow_primary' LANGUAGE C STRICT; CREATE FUNCTION get_new_primary() RETURNS INT AS 'MODULE_PATHNAME', 'get_new_primary' LANGUAGE C STRICT; CREATE FUNCTION reset_voting_status() RETURNS VOID AS 'MODULE_PATHNAME', 'reset_voting_status' LANGUAGE C STRICT; CREATE FUNCTION am_bdr_failover_handler(INT) RETURNS BOOL AS 'MODULE_PATHNAME', 'am_bdr_failover_handler' LANGUAGE C STRICT; CREATE FUNCTION unset_bdr_failover_handler() RETURNS VOID AS 'MODULE_PATHNAME', 'unset_bdr_failover_handler' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_repmgrd_pid' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pidfile() RETURNS TEXT AS 'MODULE_PATHNAME', 'get_repmgrd_pidfile' LANGUAGE C STRICT; CREATE FUNCTION set_repmgrd_pid(INT, TEXT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_repmgrd_pid' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_is_running() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_running' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_pause(BOOL) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgrd_pause' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_is_paused() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_paused' LANGUAGE C STRICT; CREATE VIEW repmgr.replication_status AS SELECT m.primary_node_id, m.standby_node_id, n.node_name AS standby_name, n.type AS node_type, n.active, last_monitor_time, CASE WHEN n.type='standby' THEN m.last_wal_primary_location ELSE NULL END AS last_wal_primary_location, m.last_wal_standby_location, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.replication_lag) ELSE NULL END AS replication_lag, CASE WHEN n.type='standby' THEN CASE WHEN replication_lag > 0 THEN age(now(), m.last_apply_time) ELSE '0'::INTERVAL END ELSE NULL END AS replication_time_lag, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.apply_lag) ELSE NULL END AS apply_lag, AGE(NOW(), CASE WHEN pg_catalog.pg_is_in_recovery() THEN repmgr.standby_get_last_updated() ELSE m.last_monitor_time END) AS communication_time_lag FROM repmgr.monitoring_history m JOIN repmgr.nodes n ON m.standby_node_id = n.node_id WHERE (m.standby_node_id, m.last_monitor_time) IN ( SELECT m1.standby_node_id, MAX(m1.last_monitor_time) FROM repmgr.monitoring_history m1 GROUP BY 1 ); repmgr-5.3.1/repmgr--4.3--4.4.sql000066400000000000000000000007461420262710000157710ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION repmgr UPDATE" to load this file. \quit -- This script is intentionally empty and exists to skip the CREATE FUNCTION -- commands contained in the 4.3--4.4 extension upgrade script, which reference -- C functions which no longer exist in 5.3 and later. -- -- These functions will be explicitly created in the 5.2--5.3 extension -- upgrade step with the correct C function references. repmgr-5.3.1/repmgr--4.3.sql000066400000000000000000000145641420262710000154140ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit CREATE TABLE repmgr.nodes ( node_id INTEGER PRIMARY KEY, upstream_node_id INTEGER NULL REFERENCES nodes (node_id) DEFERRABLE, active BOOLEAN NOT NULL DEFAULT TRUE, node_name TEXT NOT NULL, type TEXT NOT NULL CHECK (type IN('primary','standby','witness','bdr')), location TEXT NOT NULL DEFAULT 'default', priority INT NOT NULL DEFAULT 100, conninfo TEXT NOT NULL, repluser VARCHAR(63) NOT NULL, slot_name TEXT NULL, config_file TEXT NOT NULL ); CREATE TABLE repmgr.events ( node_id INTEGER NOT NULL, event TEXT NOT NULL, successful BOOLEAN NOT NULL DEFAULT TRUE, event_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, details TEXT NULL ); DO $repmgr$ DECLARE DECLARE server_version_num INT; BEGIN SELECT setting FROM pg_catalog.pg_settings WHERE name = 'server_version_num' INTO server_version_num; IF server_version_num >= 90400 THEN EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location PG_LSN NOT NULL, last_wal_standby_location PG_LSN, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; ELSE EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location TEXT NOT NULL, last_wal_standby_location TEXT, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; END IF; END$repmgr$; CREATE INDEX idx_monitoring_history_time ON repmgr.monitoring_history (last_monitor_time, standby_node_id); CREATE VIEW repmgr.show_nodes AS SELECT n.node_id, n.node_name, n.active, n.upstream_node_id, un.node_name AS upstream_node_name, n.type, n.priority, n.conninfo FROM repmgr.nodes n LEFT JOIN repmgr.nodes un ON un.node_id = n.upstream_node_id; CREATE TABLE repmgr.voting_term ( term INT NOT NULL ); CREATE UNIQUE INDEX voting_term_restrict ON repmgr.voting_term ((TRUE)); CREATE RULE voting_term_delete AS ON DELETE TO repmgr.voting_term DO INSTEAD NOTHING; /* ================= */ /* repmgrd functions */ /* ================= */ /* monitoring functions */ CREATE FUNCTION set_local_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION get_local_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'get_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION standby_set_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_set_last_updated' LANGUAGE C STRICT; CREATE FUNCTION standby_get_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_get_last_updated' LANGUAGE C STRICT; CREATE FUNCTION set_upstream_last_seen() RETURNS VOID AS 'MODULE_PATHNAME', 'set_upstream_last_seen' LANGUAGE C STRICT; CREATE FUNCTION get_upstream_last_seen() RETURNS INT AS 'MODULE_PATHNAME', 'get_upstream_last_seen' LANGUAGE C STRICT; /* failover functions */ CREATE FUNCTION notify_follow_primary(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'notify_follow_primary' LANGUAGE C STRICT; CREATE FUNCTION get_new_primary() RETURNS INT AS 'MODULE_PATHNAME', 'get_new_primary' LANGUAGE C STRICT; CREATE FUNCTION reset_voting_status() RETURNS VOID AS 'MODULE_PATHNAME', 'reset_voting_status' LANGUAGE C STRICT; CREATE FUNCTION am_bdr_failover_handler(INT) RETURNS BOOL AS 'MODULE_PATHNAME', 'am_bdr_failover_handler' LANGUAGE C STRICT; CREATE FUNCTION unset_bdr_failover_handler() RETURNS VOID AS 'MODULE_PATHNAME', 'unset_bdr_failover_handler' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_repmgrd_pid' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pidfile() RETURNS TEXT AS 'MODULE_PATHNAME', 'get_repmgrd_pidfile' LANGUAGE C STRICT; CREATE FUNCTION set_repmgrd_pid(INT, TEXT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_repmgrd_pid' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_is_running() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_running' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_pause(BOOL) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgrd_pause' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_is_paused() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_paused' LANGUAGE C STRICT; CREATE FUNCTION get_wal_receiver_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_wal_receiver_pid' LANGUAGE C STRICT; /* views */ CREATE VIEW repmgr.replication_status AS SELECT m.primary_node_id, m.standby_node_id, n.node_name AS standby_name, n.type AS node_type, n.active, last_monitor_time, CASE WHEN n.type='standby' THEN m.last_wal_primary_location ELSE NULL END AS last_wal_primary_location, m.last_wal_standby_location, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.replication_lag) ELSE NULL END AS replication_lag, CASE WHEN n.type='standby' THEN CASE WHEN replication_lag > 0 THEN age(now(), m.last_apply_time) ELSE '0'::INTERVAL END ELSE NULL END AS replication_time_lag, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.apply_lag) ELSE NULL END AS apply_lag, AGE(NOW(), CASE WHEN pg_catalog.pg_is_in_recovery() THEN repmgr.standby_get_last_updated() ELSE m.last_monitor_time END) AS communication_time_lag FROM repmgr.monitoring_history m JOIN repmgr.nodes n ON m.standby_node_id = n.node_id WHERE (m.standby_node_id, m.last_monitor_time) IN ( SELECT m1.standby_node_id, MAX(m1.last_monitor_time) FROM repmgr.monitoring_history m1 GROUP BY 1 ); repmgr-5.3.1/repmgr--4.4--5.0.sql000066400000000000000000000003201420262710000157530ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit ALTER FUNCTION set_repmgrd_pid(INT, TEXT) RETURNS NULL ON NULL INPUT; repmgr-5.3.1/repmgr--4.4.sql000066400000000000000000000151561420262710000154130ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit CREATE TABLE repmgr.nodes ( node_id INTEGER PRIMARY KEY, upstream_node_id INTEGER NULL REFERENCES nodes (node_id) DEFERRABLE, active BOOLEAN NOT NULL DEFAULT TRUE, node_name TEXT NOT NULL, type TEXT NOT NULL CHECK (type IN('primary','standby','witness','bdr')), location TEXT NOT NULL DEFAULT 'default', priority INT NOT NULL DEFAULT 100, conninfo TEXT NOT NULL, repluser VARCHAR(63) NOT NULL, slot_name TEXT NULL, config_file TEXT NOT NULL ); CREATE TABLE repmgr.events ( node_id INTEGER NOT NULL, event TEXT NOT NULL, successful BOOLEAN NOT NULL DEFAULT TRUE, event_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, details TEXT NULL ); DO $repmgr$ DECLARE DECLARE server_version_num INT; BEGIN SELECT setting FROM pg_catalog.pg_settings WHERE name = 'server_version_num' INTO server_version_num; IF server_version_num >= 90400 THEN EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location PG_LSN NOT NULL, last_wal_standby_location PG_LSN, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; ELSE EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location TEXT NOT NULL, last_wal_standby_location TEXT, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; END IF; END$repmgr$; CREATE INDEX idx_monitoring_history_time ON repmgr.monitoring_history (last_monitor_time, standby_node_id); CREATE VIEW repmgr.show_nodes AS SELECT n.node_id, n.node_name, n.active, n.upstream_node_id, un.node_name AS upstream_node_name, n.type, n.priority, n.conninfo FROM repmgr.nodes n LEFT JOIN repmgr.nodes un ON un.node_id = n.upstream_node_id; CREATE TABLE repmgr.voting_term ( term INT NOT NULL ); CREATE UNIQUE INDEX voting_term_restrict ON repmgr.voting_term ((TRUE)); CREATE RULE voting_term_delete AS ON DELETE TO repmgr.voting_term DO INSTEAD NOTHING; /* ================= */ /* repmgrd functions */ /* ================= */ /* monitoring functions */ CREATE FUNCTION set_local_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION get_local_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'get_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION standby_set_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_set_last_updated' LANGUAGE C STRICT; CREATE FUNCTION standby_get_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_get_last_updated' LANGUAGE C STRICT; CREATE FUNCTION set_upstream_last_seen(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_upstream_last_seen' LANGUAGE C STRICT; CREATE FUNCTION get_upstream_last_seen() RETURNS INT AS 'MODULE_PATHNAME', 'get_upstream_last_seen' LANGUAGE C STRICT; CREATE FUNCTION get_upstream_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'get_upstream_node_id' LANGUAGE C STRICT; CREATE FUNCTION set_upstream_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_upstream_node_id' LANGUAGE C STRICT; /* failover functions */ CREATE FUNCTION notify_follow_primary(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'notify_follow_primary' LANGUAGE C STRICT; CREATE FUNCTION get_new_primary() RETURNS INT AS 'MODULE_PATHNAME', 'get_new_primary' LANGUAGE C STRICT; CREATE FUNCTION reset_voting_status() RETURNS VOID AS 'MODULE_PATHNAME', 'reset_voting_status' LANGUAGE C STRICT; CREATE FUNCTION am_bdr_failover_handler(INT) RETURNS BOOL AS 'MODULE_PATHNAME', 'am_bdr_failover_handler' LANGUAGE C STRICT; CREATE FUNCTION unset_bdr_failover_handler() RETURNS VOID AS 'MODULE_PATHNAME', 'unset_bdr_failover_handler' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_repmgrd_pid' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pidfile() RETURNS TEXT AS 'MODULE_PATHNAME', 'get_repmgrd_pidfile' LANGUAGE C STRICT; CREATE FUNCTION set_repmgrd_pid(INT, TEXT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_repmgrd_pid' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_is_running() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_running' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_pause(BOOL) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgrd_pause' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_is_paused() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_paused' LANGUAGE C STRICT; CREATE FUNCTION get_wal_receiver_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_wal_receiver_pid' LANGUAGE C STRICT; /* views */ CREATE VIEW repmgr.replication_status AS SELECT m.primary_node_id, m.standby_node_id, n.node_name AS standby_name, n.type AS node_type, n.active, last_monitor_time, CASE WHEN n.type='standby' THEN m.last_wal_primary_location ELSE NULL END AS last_wal_primary_location, m.last_wal_standby_location, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.replication_lag) ELSE NULL END AS replication_lag, CASE WHEN n.type='standby' THEN CASE WHEN replication_lag > 0 THEN age(now(), m.last_apply_time) ELSE '0'::INTERVAL END ELSE NULL END AS replication_time_lag, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.apply_lag) ELSE NULL END AS apply_lag, AGE(NOW(), CASE WHEN pg_catalog.pg_is_in_recovery() THEN repmgr.standby_get_last_updated() ELSE m.last_monitor_time END) AS communication_time_lag FROM repmgr.monitoring_history m JOIN repmgr.nodes n ON m.standby_node_id = n.node_id WHERE (m.standby_node_id, m.last_monitor_time) IN ( SELECT m1.standby_node_id, MAX(m1.last_monitor_time) FROM repmgr.monitoring_history m1 GROUP BY 1 ); repmgr-5.3.1/repmgr--5.0--5.1.sql000066400000000000000000000003411420262710000157540ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit DROP FUNCTION am_bdr_failover_handler(INT); DROP FUNCTION unset_bdr_failover_handler(); repmgr-5.3.1/repmgr--5.0.sql000066400000000000000000000145611420262710000154070ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit CREATE TABLE repmgr.nodes ( node_id INTEGER PRIMARY KEY, upstream_node_id INTEGER NULL REFERENCES nodes (node_id) DEFERRABLE, active BOOLEAN NOT NULL DEFAULT TRUE, node_name TEXT NOT NULL, type TEXT NOT NULL CHECK (type IN('primary','standby','witness','bdr')), location TEXT NOT NULL DEFAULT 'default', priority INT NOT NULL DEFAULT 100, conninfo TEXT NOT NULL, repluser VARCHAR(63) NOT NULL, slot_name TEXT NULL, config_file TEXT NOT NULL ); CREATE TABLE repmgr.events ( node_id INTEGER NOT NULL, event TEXT NOT NULL, successful BOOLEAN NOT NULL DEFAULT TRUE, event_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, details TEXT NULL ); DO $repmgr$ DECLARE DECLARE server_version_num INT; BEGIN SELECT setting FROM pg_catalog.pg_settings WHERE name = 'server_version_num' INTO server_version_num; IF server_version_num >= 90400 THEN EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location PG_LSN NOT NULL, last_wal_standby_location PG_LSN, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; ELSE EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location TEXT NOT NULL, last_wal_standby_location TEXT, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; END IF; END$repmgr$; CREATE INDEX idx_monitoring_history_time ON repmgr.monitoring_history (last_monitor_time, standby_node_id); CREATE VIEW repmgr.show_nodes AS SELECT n.node_id, n.node_name, n.active, n.upstream_node_id, un.node_name AS upstream_node_name, n.type, n.priority, n.conninfo FROM repmgr.nodes n LEFT JOIN repmgr.nodes un ON un.node_id = n.upstream_node_id; CREATE TABLE repmgr.voting_term ( term INT NOT NULL ); CREATE UNIQUE INDEX voting_term_restrict ON repmgr.voting_term ((TRUE)); CREATE RULE voting_term_delete AS ON DELETE TO repmgr.voting_term DO INSTEAD NOTHING; /* ================= */ /* repmgrd functions */ /* ================= */ /* monitoring functions */ CREATE FUNCTION set_local_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION get_local_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'get_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION standby_set_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_set_last_updated' LANGUAGE C STRICT; CREATE FUNCTION standby_get_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_get_last_updated' LANGUAGE C STRICT; CREATE FUNCTION set_upstream_last_seen(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_upstream_last_seen' LANGUAGE C STRICT; CREATE FUNCTION get_upstream_last_seen() RETURNS INT AS 'MODULE_PATHNAME', 'get_upstream_last_seen' LANGUAGE C STRICT; CREATE FUNCTION get_upstream_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'get_upstream_node_id' LANGUAGE C STRICT; CREATE FUNCTION set_upstream_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_upstream_node_id' LANGUAGE C STRICT; /* failover functions */ CREATE FUNCTION notify_follow_primary(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'notify_follow_primary' LANGUAGE C STRICT; CREATE FUNCTION get_new_primary() RETURNS INT AS 'MODULE_PATHNAME', 'get_new_primary' LANGUAGE C STRICT; CREATE FUNCTION reset_voting_status() RETURNS VOID AS 'MODULE_PATHNAME', 'reset_voting_status' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_repmgrd_pid' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pidfile() RETURNS TEXT AS 'MODULE_PATHNAME', 'get_repmgrd_pidfile' LANGUAGE C STRICT; CREATE FUNCTION set_repmgrd_pid(INT, TEXT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_repmgrd_pid' LANGUAGE C CALLED ON NULL INPUT; CREATE FUNCTION repmgrd_is_running() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_running' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_pause(BOOL) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgrd_pause' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_is_paused() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_paused' LANGUAGE C STRICT; CREATE FUNCTION get_wal_receiver_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_wal_receiver_pid' LANGUAGE C STRICT; /* views */ CREATE VIEW repmgr.replication_status AS SELECT m.primary_node_id, m.standby_node_id, n.node_name AS standby_name, n.type AS node_type, n.active, last_monitor_time, CASE WHEN n.type='standby' THEN m.last_wal_primary_location ELSE NULL END AS last_wal_primary_location, m.last_wal_standby_location, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.replication_lag) ELSE NULL END AS replication_lag, CASE WHEN n.type='standby' THEN CASE WHEN replication_lag > 0 THEN age(now(), m.last_apply_time) ELSE '0'::INTERVAL END ELSE NULL END AS replication_time_lag, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.apply_lag) ELSE NULL END AS apply_lag, AGE(NOW(), CASE WHEN pg_catalog.pg_is_in_recovery() THEN repmgr.standby_get_last_updated() ELSE m.last_monitor_time END) AS communication_time_lag FROM repmgr.monitoring_history m JOIN repmgr.nodes n ON m.standby_node_id = n.node_id WHERE (m.standby_node_id, m.last_monitor_time) IN ( SELECT m1.standby_node_id, MAX(m1.last_monitor_time) FROM repmgr.monitoring_history m1 GROUP BY 1 ); repmgr-5.3.1/repmgr--5.1--5.2.sql000066400000000000000000000005301420262710000157560ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit SELECT pg_catalog.pg_extension_config_dump('repmgr.nodes', ''); SELECT pg_catalog.pg_extension_config_dump('repmgr.events', ''); SELECT pg_catalog.pg_extension_config_dump('repmgr.monitoring_history', ''); repmgr-5.3.1/repmgr--5.1.sql000066400000000000000000000145611420262710000154100ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit CREATE TABLE repmgr.nodes ( node_id INTEGER PRIMARY KEY, upstream_node_id INTEGER NULL REFERENCES nodes (node_id) DEFERRABLE, active BOOLEAN NOT NULL DEFAULT TRUE, node_name TEXT NOT NULL, type TEXT NOT NULL CHECK (type IN('primary','standby','witness','bdr')), location TEXT NOT NULL DEFAULT 'default', priority INT NOT NULL DEFAULT 100, conninfo TEXT NOT NULL, repluser VARCHAR(63) NOT NULL, slot_name TEXT NULL, config_file TEXT NOT NULL ); CREATE TABLE repmgr.events ( node_id INTEGER NOT NULL, event TEXT NOT NULL, successful BOOLEAN NOT NULL DEFAULT TRUE, event_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, details TEXT NULL ); DO $repmgr$ DECLARE DECLARE server_version_num INT; BEGIN SELECT setting FROM pg_catalog.pg_settings WHERE name = 'server_version_num' INTO server_version_num; IF server_version_num >= 90400 THEN EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location PG_LSN NOT NULL, last_wal_standby_location PG_LSN, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; ELSE EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location TEXT NOT NULL, last_wal_standby_location TEXT, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; END IF; END$repmgr$; CREATE INDEX idx_monitoring_history_time ON repmgr.monitoring_history (last_monitor_time, standby_node_id); CREATE VIEW repmgr.show_nodes AS SELECT n.node_id, n.node_name, n.active, n.upstream_node_id, un.node_name AS upstream_node_name, n.type, n.priority, n.conninfo FROM repmgr.nodes n LEFT JOIN repmgr.nodes un ON un.node_id = n.upstream_node_id; CREATE TABLE repmgr.voting_term ( term INT NOT NULL ); CREATE UNIQUE INDEX voting_term_restrict ON repmgr.voting_term ((TRUE)); CREATE RULE voting_term_delete AS ON DELETE TO repmgr.voting_term DO INSTEAD NOTHING; /* ================= */ /* repmgrd functions */ /* ================= */ /* monitoring functions */ CREATE FUNCTION set_local_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION get_local_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'get_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION standby_set_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_set_last_updated' LANGUAGE C STRICT; CREATE FUNCTION standby_get_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_get_last_updated' LANGUAGE C STRICT; CREATE FUNCTION set_upstream_last_seen(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_upstream_last_seen' LANGUAGE C STRICT; CREATE FUNCTION get_upstream_last_seen() RETURNS INT AS 'MODULE_PATHNAME', 'get_upstream_last_seen' LANGUAGE C STRICT; CREATE FUNCTION get_upstream_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'get_upstream_node_id' LANGUAGE C STRICT; CREATE FUNCTION set_upstream_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_upstream_node_id' LANGUAGE C STRICT; /* failover functions */ CREATE FUNCTION notify_follow_primary(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'notify_follow_primary' LANGUAGE C STRICT; CREATE FUNCTION get_new_primary() RETURNS INT AS 'MODULE_PATHNAME', 'get_new_primary' LANGUAGE C STRICT; CREATE FUNCTION reset_voting_status() RETURNS VOID AS 'MODULE_PATHNAME', 'reset_voting_status' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_repmgrd_pid' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pidfile() RETURNS TEXT AS 'MODULE_PATHNAME', 'get_repmgrd_pidfile' LANGUAGE C STRICT; CREATE FUNCTION set_repmgrd_pid(INT, TEXT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_repmgrd_pid' LANGUAGE C CALLED ON NULL INPUT; CREATE FUNCTION repmgrd_is_running() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_running' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_pause(BOOL) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgrd_pause' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_is_paused() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_paused' LANGUAGE C STRICT; CREATE FUNCTION get_wal_receiver_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_wal_receiver_pid' LANGUAGE C STRICT; /* views */ CREATE VIEW repmgr.replication_status AS SELECT m.primary_node_id, m.standby_node_id, n.node_name AS standby_name, n.type AS node_type, n.active, last_monitor_time, CASE WHEN n.type='standby' THEN m.last_wal_primary_location ELSE NULL END AS last_wal_primary_location, m.last_wal_standby_location, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.replication_lag) ELSE NULL END AS replication_lag, CASE WHEN n.type='standby' THEN CASE WHEN replication_lag > 0 THEN age(now(), m.last_apply_time) ELSE '0'::INTERVAL END ELSE NULL END AS replication_time_lag, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.apply_lag) ELSE NULL END AS apply_lag, AGE(NOW(), CASE WHEN pg_catalog.pg_is_in_recovery() THEN repmgr.standby_get_last_updated() ELSE m.last_monitor_time END) AS communication_time_lag FROM repmgr.monitoring_history m JOIN repmgr.nodes n ON m.standby_node_id = n.node_id WHERE (m.standby_node_id, m.last_monitor_time) IN ( SELECT m1.standby_node_id, MAX(m1.last_monitor_time) FROM repmgr.monitoring_history m1 GROUP BY 1 ); repmgr-5.3.1/repmgr--5.2--5.3.sql000066400000000000000000000035661420262710000157740ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit CREATE OR REPLACE FUNCTION set_local_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgr_set_local_node_id' LANGUAGE C STRICT; CREATE OR REPLACE FUNCTION repmgr.get_local_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'repmgr_get_local_node_id' LANGUAGE C STRICT; CREATE OR REPLACE FUNCTION standby_set_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'repmgr_standby_set_last_updated' LANGUAGE C STRICT; CREATE OR REPLACE FUNCTION standby_get_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'repmgr_standby_get_last_updated' LANGUAGE C STRICT; CREATE OR REPLACE FUNCTION set_upstream_last_seen(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgr_set_upstream_last_seen' LANGUAGE C STRICT; CREATE OR REPLACE FUNCTION get_upstream_last_seen() RETURNS INT AS 'MODULE_PATHNAME', 'repmgr_get_upstream_last_seen' LANGUAGE C STRICT; CREATE OR REPLACE FUNCTION get_upstream_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'repmgr_get_upstream_node_id' LANGUAGE C STRICT; CREATE OR REPLACE FUNCTION set_upstream_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgr_set_upstream_node_id' LANGUAGE C STRICT; /* failover functions */ CREATE OR REPLACE FUNCTION notify_follow_primary(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgr_notify_follow_primary' LANGUAGE C STRICT; CREATE OR REPLACE FUNCTION get_new_primary() RETURNS INT AS 'MODULE_PATHNAME', 'repmgr_get_new_primary' LANGUAGE C STRICT; CREATE OR REPLACE FUNCTION reset_voting_status() RETURNS VOID AS 'MODULE_PATHNAME', 'repmgr_reset_voting_status' LANGUAGE C STRICT; CREATE OR REPLACE FUNCTION get_wal_receiver_pid() RETURNS INT AS 'MODULE_PATHNAME', 'repmgr_get_wal_receiver_pid' LANGUAGE C STRICT; repmgr-5.3.1/repmgr--5.2.sql000066400000000000000000000134521420262710000154070ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit CREATE TABLE repmgr.nodes ( node_id INTEGER PRIMARY KEY, upstream_node_id INTEGER NULL REFERENCES nodes (node_id) DEFERRABLE, active BOOLEAN NOT NULL DEFAULT TRUE, node_name TEXT NOT NULL, type TEXT NOT NULL CHECK (type IN('primary','standby','witness','bdr')), location TEXT NOT NULL DEFAULT 'default', priority INT NOT NULL DEFAULT 100, conninfo TEXT NOT NULL, repluser VARCHAR(63) NOT NULL, slot_name TEXT NULL, config_file TEXT NOT NULL ); SELECT pg_catalog.pg_extension_config_dump('repmgr.nodes', ''); CREATE TABLE repmgr.events ( node_id INTEGER NOT NULL, event TEXT NOT NULL, successful BOOLEAN NOT NULL DEFAULT TRUE, event_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, details TEXT NULL ); SELECT pg_catalog.pg_extension_config_dump('repmgr.events', ''); CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location PG_LSN NOT NULL, last_wal_standby_location PG_LSN, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ); CREATE INDEX idx_monitoring_history_time ON repmgr.monitoring_history (last_monitor_time, standby_node_id); SELECT pg_catalog.pg_extension_config_dump('repmgr.monitoring_history', ''); CREATE VIEW repmgr.show_nodes AS SELECT n.node_id, n.node_name, n.active, n.upstream_node_id, un.node_name AS upstream_node_name, n.type, n.priority, n.conninfo FROM repmgr.nodes n LEFT JOIN repmgr.nodes un ON un.node_id = n.upstream_node_id; CREATE TABLE repmgr.voting_term ( term INT NOT NULL ); CREATE UNIQUE INDEX voting_term_restrict ON repmgr.voting_term ((TRUE)); CREATE RULE voting_term_delete AS ON DELETE TO repmgr.voting_term DO INSTEAD NOTHING; /* ================= */ /* repmgrd functions */ /* ================= */ /* monitoring functions */ CREATE FUNCTION set_local_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION get_local_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'get_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION standby_set_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_set_last_updated' LANGUAGE C STRICT; CREATE FUNCTION standby_get_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_get_last_updated' LANGUAGE C STRICT; CREATE FUNCTION set_upstream_last_seen(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_upstream_last_seen' LANGUAGE C STRICT; CREATE FUNCTION get_upstream_last_seen() RETURNS INT AS 'MODULE_PATHNAME', 'get_upstream_last_seen' LANGUAGE C STRICT; CREATE FUNCTION get_upstream_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'get_upstream_node_id' LANGUAGE C STRICT; CREATE FUNCTION set_upstream_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_upstream_node_id' LANGUAGE C STRICT; /* failover functions */ CREATE FUNCTION notify_follow_primary(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'notify_follow_primary' LANGUAGE C STRICT; CREATE FUNCTION get_new_primary() RETURNS INT AS 'MODULE_PATHNAME', 'get_new_primary' LANGUAGE C STRICT; CREATE FUNCTION reset_voting_status() RETURNS VOID AS 'MODULE_PATHNAME', 'reset_voting_status' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_repmgrd_pid' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pidfile() RETURNS TEXT AS 'MODULE_PATHNAME', 'get_repmgrd_pidfile' LANGUAGE C STRICT; CREATE FUNCTION set_repmgrd_pid(INT, TEXT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_repmgrd_pid' LANGUAGE C CALLED ON NULL INPUT; CREATE FUNCTION repmgrd_is_running() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_running' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_pause(BOOL) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgrd_pause' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_is_paused() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_paused' LANGUAGE C STRICT; CREATE FUNCTION get_wal_receiver_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_wal_receiver_pid' LANGUAGE C STRICT; /* views */ CREATE VIEW repmgr.replication_status AS SELECT m.primary_node_id, m.standby_node_id, n.node_name AS standby_name, n.type AS node_type, n.active, last_monitor_time, CASE WHEN n.type='standby' THEN m.last_wal_primary_location ELSE NULL END AS last_wal_primary_location, m.last_wal_standby_location, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.replication_lag) ELSE NULL END AS replication_lag, CASE WHEN n.type='standby' THEN CASE WHEN replication_lag > 0 THEN age(now(), m.last_apply_time) ELSE '0'::INTERVAL END ELSE NULL END AS replication_time_lag, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.apply_lag) ELSE NULL END AS apply_lag, AGE(NOW(), CASE WHEN pg_catalog.pg_is_in_recovery() THEN repmgr.standby_get_last_updated() ELSE m.last_monitor_time END) AS communication_time_lag FROM repmgr.monitoring_history m JOIN repmgr.nodes n ON m.standby_node_id = n.node_id WHERE (m.standby_node_id, m.last_monitor_time) IN ( SELECT m1.standby_node_id, MAX(m1.last_monitor_time) FROM repmgr.monitoring_history m1 GROUP BY 1 ); repmgr-5.3.1/repmgr--5.3.sql000066400000000000000000000135761420262710000154170ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit CREATE TABLE repmgr.nodes ( node_id INTEGER PRIMARY KEY, upstream_node_id INTEGER NULL REFERENCES nodes (node_id) DEFERRABLE, active BOOLEAN NOT NULL DEFAULT TRUE, node_name TEXT NOT NULL, type TEXT NOT NULL CHECK (type IN('primary','standby','witness','bdr')), location TEXT NOT NULL DEFAULT 'default', priority INT NOT NULL DEFAULT 100, conninfo TEXT NOT NULL, repluser VARCHAR(63) NOT NULL, slot_name TEXT NULL, config_file TEXT NOT NULL ); SELECT pg_catalog.pg_extension_config_dump('repmgr.nodes', ''); CREATE TABLE repmgr.events ( node_id INTEGER NOT NULL, event TEXT NOT NULL, successful BOOLEAN NOT NULL DEFAULT TRUE, event_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, details TEXT NULL ); SELECT pg_catalog.pg_extension_config_dump('repmgr.events', ''); CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location PG_LSN NOT NULL, last_wal_standby_location PG_LSN, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ); CREATE INDEX idx_monitoring_history_time ON repmgr.monitoring_history (last_monitor_time, standby_node_id); SELECT pg_catalog.pg_extension_config_dump('repmgr.monitoring_history', ''); CREATE VIEW repmgr.show_nodes AS SELECT n.node_id, n.node_name, n.active, n.upstream_node_id, un.node_name AS upstream_node_name, n.type, n.priority, n.conninfo FROM repmgr.nodes n LEFT JOIN repmgr.nodes un ON un.node_id = n.upstream_node_id; CREATE TABLE repmgr.voting_term ( term INT NOT NULL ); CREATE UNIQUE INDEX voting_term_restrict ON repmgr.voting_term ((TRUE)); CREATE RULE voting_term_delete AS ON DELETE TO repmgr.voting_term DO INSTEAD NOTHING; /* ================= */ /* repmgrd functions */ /* ================= */ /* monitoring functions */ CREATE FUNCTION set_local_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgr_set_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION get_local_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'repmgr_get_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION standby_set_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'repmgr_standby_set_last_updated' LANGUAGE C STRICT; CREATE FUNCTION standby_get_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'repmgr_standby_get_last_updated' LANGUAGE C STRICT; CREATE FUNCTION set_upstream_last_seen(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgr_set_upstream_last_seen' LANGUAGE C STRICT; CREATE FUNCTION get_upstream_last_seen() RETURNS INT AS 'MODULE_PATHNAME', 'repmgr_get_upstream_last_seen' LANGUAGE C STRICT; CREATE FUNCTION get_upstream_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'repmgr_get_upstream_node_id' LANGUAGE C STRICT; CREATE FUNCTION set_upstream_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgr_set_upstream_node_id' LANGUAGE C STRICT; /* failover functions */ CREATE FUNCTION notify_follow_primary(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgr_notify_follow_primary' LANGUAGE C STRICT; CREATE FUNCTION get_new_primary() RETURNS INT AS 'MODULE_PATHNAME', 'repmgr_get_new_primary' LANGUAGE C STRICT; CREATE FUNCTION reset_voting_status() RETURNS VOID AS 'MODULE_PATHNAME', 'repmgr_reset_voting_status' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_repmgrd_pid' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pidfile() RETURNS TEXT AS 'MODULE_PATHNAME', 'get_repmgrd_pidfile' LANGUAGE C STRICT; CREATE FUNCTION set_repmgrd_pid(INT, TEXT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_repmgrd_pid' LANGUAGE C CALLED ON NULL INPUT; CREATE FUNCTION repmgrd_is_running() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_running' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_pause(BOOL) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgrd_pause' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_is_paused() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_paused' LANGUAGE C STRICT; CREATE FUNCTION get_wal_receiver_pid() RETURNS INT AS 'MODULE_PATHNAME', 'repmgr_get_wal_receiver_pid' LANGUAGE C STRICT; /* views */ CREATE VIEW repmgr.replication_status AS SELECT m.primary_node_id, m.standby_node_id, n.node_name AS standby_name, n.type AS node_type, n.active, last_monitor_time, CASE WHEN n.type='standby' THEN m.last_wal_primary_location ELSE NULL END AS last_wal_primary_location, m.last_wal_standby_location, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.replication_lag) ELSE NULL END AS replication_lag, CASE WHEN n.type='standby' THEN CASE WHEN replication_lag > 0 THEN age(now(), m.last_apply_time) ELSE '0'::INTERVAL END ELSE NULL END AS replication_time_lag, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.apply_lag) ELSE NULL END AS apply_lag, AGE(NOW(), CASE WHEN pg_catalog.pg_is_in_recovery() THEN repmgr.standby_get_last_updated() ELSE m.last_monitor_time END) AS communication_time_lag FROM repmgr.monitoring_history m JOIN repmgr.nodes n ON m.standby_node_id = n.node_id WHERE (m.standby_node_id, m.last_monitor_time) IN ( SELECT m1.standby_node_id, MAX(m1.last_monitor_time) FROM repmgr.monitoring_history m1 GROUP BY 1 ); repmgr-5.3.1/repmgr--unpackaged--4.0.sql000066400000000000000000000170051420262710000175570ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit -- extract the current schema name -- NOTE: this assumes there will be only one schema matching 'repmgr_%'; -- user is responsible for ensuring this is the case CREATE TEMPORARY TABLE repmgr_old_schema (schema_name TEXT); INSERT INTO repmgr_old_schema (schema_name) SELECT nspname AS schema_name FROM pg_catalog.pg_namespace WHERE nspname LIKE 'repmgr_%' LIMIT 1; -- move old objects into new schema DO $repmgr$ DECLARE old_schema TEXT; BEGIN SELECT schema_name FROM repmgr_old_schema INTO old_schema; EXECUTE format('ALTER TABLE %I.repl_nodes SET SCHEMA repmgr', old_schema); EXECUTE format('ALTER TABLE %I.repl_events SET SCHEMA repmgr', old_schema); EXECUTE format('ALTER TABLE %I.repl_monitor SET SCHEMA repmgr', old_schema); EXECUTE format('DROP VIEW IF EXISTS %I.repl_show_nodes', old_schema); EXECUTE format('DROP VIEW IF EXISTS %I.repl_status', old_schema); END$repmgr$; -- convert "repmgr_$cluster.repl_nodes" to "repmgr.nodes" CREATE TABLE repmgr.nodes ( node_id INTEGER PRIMARY KEY, upstream_node_id INTEGER NULL REFERENCES repmgr.nodes (node_id) DEFERRABLE, active BOOLEAN NOT NULL DEFAULT TRUE, node_name TEXT NOT NULL, type TEXT NOT NULL CHECK (type IN('primary','standby','witness','bdr')), location TEXT NOT NULL DEFAULT 'default', priority INT NOT NULL DEFAULT 100, conninfo TEXT NOT NULL, repluser VARCHAR(63) NOT NULL, slot_name TEXT NULL, config_file TEXT NOT NULL ); INSERT INTO repmgr.nodes (node_id, upstream_node_id, active, node_name, type, location, priority, conninfo, repluser, slot_name, config_file) SELECT id, upstream_node_id, active, name, CASE WHEN type = 'master' THEN 'primary' ELSE type END, 'default', priority, conninfo, 'unknown', slot_name, 'unknown' FROM repmgr.repl_nodes ORDER BY id; -- convert "repmgr_$cluster.repl_event" to "event" ALTER TABLE repmgr.repl_events RENAME TO events; -- create new table "repmgr.voting_term" CREATE TABLE repmgr.voting_term ( term INT NOT NULL ); CREATE UNIQUE INDEX voting_term_restrict ON repmgr.voting_term ((TRUE)); CREATE RULE voting_term_delete AS ON DELETE TO repmgr.voting_term DO INSTEAD NOTHING; INSERT INTO repmgr.voting_term (term) VALUES (1); -- convert "repmgr_$cluster.repl_monitor" to "monitoring_history" DO $repmgr$ DECLARE DECLARE server_version_num INT; BEGIN SELECT setting FROM pg_catalog.pg_settings WHERE name = 'server_version_num' INTO server_version_num; IF server_version_num >= 90400 THEN EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location PG_LSN NOT NULL, last_wal_standby_location PG_LSN, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; INSERT INTO repmgr.monitoring_history (primary_node_id, standby_node_id, last_monitor_time, last_apply_time, last_wal_primary_location, last_wal_standby_location, replication_lag, apply_lag) SELECT primary_node, standby_node, last_monitor_time, last_apply_time, last_wal_primary_location::pg_lsn, last_wal_standby_location::pg_lsn, replication_lag, apply_lag FROM repmgr.repl_monitor; ELSE EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location TEXT NOT NULL, last_wal_standby_location TEXT, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; INSERT INTO repmgr.monitoring_history (primary_node_id, standby_node_id, last_monitor_time, last_apply_time, last_wal_primary_location, last_wal_standby_location, replication_lag, apply_lag) SELECT primary_node, standby_node, last_monitor_time, last_apply_time, last_wal_primary_location, last_wal_standby_location, replication_lag, apply_lag FROM repmgr.repl_monitor; END IF; END$repmgr$; CREATE INDEX idx_monitoring_history_time ON repmgr.monitoring_history (last_monitor_time, standby_node_id); CREATE VIEW repmgr.show_nodes AS SELECT n.node_id, n.node_name, n.active, n.upstream_node_id, un.node_name AS upstream_node_name, n.type, n.priority, n.conninfo FROM repmgr.nodes n LEFT JOIN repmgr.nodes un ON un.node_id = n.upstream_node_id; /* ================= */ /* repmgrd functions */ /* ================= */ /* monitoring functions */ CREATE FUNCTION set_local_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION get_local_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'get_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION standby_set_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS '$libdir/repmgr', 'standby_set_last_updated' LANGUAGE C STRICT; CREATE FUNCTION standby_get_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS '$libdir/repmgr', 'standby_get_last_updated' LANGUAGE C STRICT; /* failover functions */ CREATE FUNCTION notify_follow_primary(INT) RETURNS VOID AS '$libdir/repmgr', 'notify_follow_primary' LANGUAGE C STRICT; CREATE FUNCTION get_new_primary() RETURNS INT AS '$libdir/repmgr', 'get_new_primary' LANGUAGE C STRICT; CREATE FUNCTION reset_voting_status() RETURNS VOID AS '$libdir/repmgr', 'reset_voting_status' LANGUAGE C STRICT; CREATE FUNCTION am_bdr_failover_handler(INT) RETURNS BOOL AS '$libdir/repmgr', 'am_bdr_failover_handler' LANGUAGE C STRICT; CREATE FUNCTION unset_bdr_failover_handler() RETURNS VOID AS '$libdir/repmgr', 'unset_bdr_failover_handler' LANGUAGE C STRICT; CREATE VIEW repmgr.replication_status AS SELECT m.primary_node_id, m.standby_node_id, n.node_name AS standby_name, n.type AS node_type, n.active, last_monitor_time, CASE WHEN n.type='standby' THEN m.last_wal_primary_location ELSE NULL END AS last_wal_primary_location, m.last_wal_standby_location, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.replication_lag) ELSE NULL END AS replication_lag, CASE WHEN n.type='standby' THEN CASE WHEN replication_lag > 0 THEN age(now(), m.last_apply_time) ELSE '0'::INTERVAL END ELSE NULL END AS replication_time_lag, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.apply_lag) ELSE NULL END AS apply_lag, AGE(NOW(), CASE WHEN pg_catalog.pg_is_in_recovery() THEN repmgr.standby_get_last_updated() ELSE m.last_monitor_time END) AS communication_time_lag FROM repmgr.monitoring_history m JOIN repmgr.nodes n ON m.standby_node_id = n.node_id WHERE (m.standby_node_id, m.last_monitor_time) IN ( SELECT m1.standby_node_id, MAX(m1.last_monitor_time) FROM repmgr.monitoring_history m1 GROUP BY 1 ); /* drop old tables */ DROP TABLE repmgr.repl_nodes; DROP TABLE repmgr.repl_monitor; -- remove temporary table DROP TABLE repmgr_old_schema; repmgr-5.3.1/repmgr--unpackaged--5.1.sql000066400000000000000000000211151420262710000175560ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit -- extract the current schema name -- NOTE: this assumes there will be only one schema matching 'repmgr_%'; -- user is responsible for ensuring this is the case CREATE TEMPORARY TABLE repmgr_old_schema (schema_name TEXT); INSERT INTO repmgr_old_schema (schema_name) SELECT nspname AS schema_name FROM pg_catalog.pg_namespace WHERE nspname LIKE 'repmgr_%' LIMIT 1; -- move old objects into new schema DO $repmgr$ DECLARE old_schema TEXT; BEGIN SELECT schema_name FROM repmgr_old_schema INTO old_schema; EXECUTE format('ALTER TABLE %I.repl_nodes SET SCHEMA repmgr', old_schema); EXECUTE format('ALTER TABLE %I.repl_events SET SCHEMA repmgr', old_schema); EXECUTE format('ALTER TABLE %I.repl_monitor SET SCHEMA repmgr', old_schema); EXECUTE format('DROP VIEW IF EXISTS %I.repl_show_nodes', old_schema); EXECUTE format('DROP VIEW IF EXISTS %I.repl_status', old_schema); END$repmgr$; -- convert "repmgr_$cluster.repl_nodes" to "repmgr.nodes" CREATE TABLE repmgr.nodes ( node_id INTEGER PRIMARY KEY, upstream_node_id INTEGER NULL REFERENCES repmgr.nodes (node_id) DEFERRABLE, active BOOLEAN NOT NULL DEFAULT TRUE, node_name TEXT NOT NULL, type TEXT NOT NULL CHECK (type IN('primary','standby','witness','bdr')), location TEXT NOT NULL DEFAULT 'default', priority INT NOT NULL DEFAULT 100, conninfo TEXT NOT NULL, repluser VARCHAR(63) NOT NULL, slot_name TEXT NULL, config_file TEXT NOT NULL ); INSERT INTO repmgr.nodes (node_id, upstream_node_id, active, node_name, type, location, priority, conninfo, repluser, slot_name, config_file) SELECT id, upstream_node_id, active, name, CASE WHEN type = 'master' THEN 'primary' ELSE type END, 'default', priority, conninfo, 'unknown', slot_name, 'unknown' FROM repmgr.repl_nodes ORDER BY id; -- convert "repmgr_$cluster.repl_event" to "event" ALTER TABLE repmgr.repl_events RENAME TO events; -- create new table "repmgr.voting_term" CREATE TABLE repmgr.voting_term ( term INT NOT NULL ); CREATE UNIQUE INDEX voting_term_restrict ON repmgr.voting_term ((TRUE)); CREATE RULE voting_term_delete AS ON DELETE TO repmgr.voting_term DO INSTEAD NOTHING; INSERT INTO repmgr.voting_term (term) VALUES (1); -- convert "repmgr_$cluster.repl_monitor" to "monitoring_history" DO $repmgr$ DECLARE DECLARE server_version_num INT; BEGIN SELECT setting FROM pg_catalog.pg_settings WHERE name = 'server_version_num' INTO server_version_num; IF server_version_num >= 90400 THEN EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location PG_LSN NOT NULL, last_wal_standby_location PG_LSN, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; INSERT INTO repmgr.monitoring_history (primary_node_id, standby_node_id, last_monitor_time, last_apply_time, last_wal_primary_location, last_wal_standby_location, replication_lag, apply_lag) SELECT primary_node, standby_node, last_monitor_time, last_apply_time, last_wal_primary_location::pg_lsn, last_wal_standby_location::pg_lsn, replication_lag, apply_lag FROM repmgr.repl_monitor; ELSE EXECUTE $repmgr_func$ CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location TEXT NOT NULL, last_wal_standby_location TEXT, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ) $repmgr_func$; INSERT INTO repmgr.monitoring_history (primary_node_id, standby_node_id, last_monitor_time, last_apply_time, last_wal_primary_location, last_wal_standby_location, replication_lag, apply_lag) SELECT primary_node, standby_node, last_monitor_time, last_apply_time, last_wal_primary_location, last_wal_standby_location, replication_lag, apply_lag FROM repmgr.repl_monitor; END IF; END$repmgr$; CREATE INDEX idx_monitoring_history_time ON repmgr.monitoring_history (last_monitor_time, standby_node_id); CREATE VIEW repmgr.show_nodes AS SELECT n.node_id, n.node_name, n.active, n.upstream_node_id, un.node_name AS upstream_node_name, n.type, n.priority, n.conninfo FROM repmgr.nodes n LEFT JOIN repmgr.nodes un ON un.node_id = n.upstream_node_id; /* ================= */ /* repmgrd functions */ /* ================= */ /* monitoring functions */ CREATE FUNCTION set_local_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION get_local_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'get_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION standby_set_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_set_last_updated' LANGUAGE C STRICT; CREATE FUNCTION standby_get_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_get_last_updated' LANGUAGE C STRICT; CREATE FUNCTION set_upstream_last_seen(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_upstream_last_seen' LANGUAGE C STRICT; CREATE FUNCTION get_upstream_last_seen() RETURNS INT AS 'MODULE_PATHNAME', 'get_upstream_last_seen' LANGUAGE C STRICT; CREATE FUNCTION get_upstream_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'get_upstream_node_id' LANGUAGE C STRICT; CREATE FUNCTION set_upstream_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_upstream_node_id' LANGUAGE C STRICT; /* failover functions */ CREATE FUNCTION notify_follow_primary(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'notify_follow_primary' LANGUAGE C STRICT; CREATE FUNCTION get_new_primary() RETURNS INT AS 'MODULE_PATHNAME', 'get_new_primary' LANGUAGE C STRICT; CREATE FUNCTION reset_voting_status() RETURNS VOID AS 'MODULE_PATHNAME', 'reset_voting_status' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_repmgrd_pid' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pidfile() RETURNS TEXT AS 'MODULE_PATHNAME', 'get_repmgrd_pidfile' LANGUAGE C STRICT; CREATE FUNCTION set_repmgrd_pid(INT, TEXT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_repmgrd_pid' LANGUAGE C CALLED ON NULL INPUT; CREATE FUNCTION repmgrd_is_running() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_running' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_pause(BOOL) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgrd_pause' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_is_paused() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_paused' LANGUAGE C STRICT; CREATE FUNCTION get_wal_receiver_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_wal_receiver_pid' LANGUAGE C STRICT; /* views */ CREATE VIEW repmgr.replication_status AS SELECT m.primary_node_id, m.standby_node_id, n.node_name AS standby_name, n.type AS node_type, n.active, last_monitor_time, CASE WHEN n.type='standby' THEN m.last_wal_primary_location ELSE NULL END AS last_wal_primary_location, m.last_wal_standby_location, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.replication_lag) ELSE NULL END AS replication_lag, CASE WHEN n.type='standby' THEN CASE WHEN replication_lag > 0 THEN age(now(), m.last_apply_time) ELSE '0'::INTERVAL END ELSE NULL END AS replication_time_lag, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.apply_lag) ELSE NULL END AS apply_lag, AGE(NOW(), CASE WHEN pg_catalog.pg_is_in_recovery() THEN repmgr.standby_get_last_updated() ELSE m.last_monitor_time END) AS communication_time_lag FROM repmgr.monitoring_history m JOIN repmgr.nodes n ON m.standby_node_id = n.node_id WHERE (m.standby_node_id, m.last_monitor_time) IN ( SELECT m1.standby_node_id, MAX(m1.last_monitor_time) FROM repmgr.monitoring_history m1 GROUP BY 1 ); /* drop old tables */ DROP TABLE repmgr.repl_nodes; DROP TABLE repmgr.repl_monitor; -- remove temporary table DROP TABLE repmgr_old_schema; repmgr-5.3.1/repmgr--unpackaged--5.2.sql000066400000000000000000000175051420262710000175670ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit -- extract the current schema name -- NOTE: this assumes there will be only one schema matching 'repmgr_%'; -- user is responsible for ensuring this is the case CREATE TEMPORARY TABLE repmgr_old_schema (schema_name TEXT); INSERT INTO repmgr_old_schema (schema_name) SELECT nspname AS schema_name FROM pg_catalog.pg_namespace WHERE nspname LIKE 'repmgr_%' LIMIT 1; -- move old objects into new schema DO $repmgr$ DECLARE old_schema TEXT; BEGIN SELECT schema_name FROM repmgr_old_schema INTO old_schema; EXECUTE format('ALTER TABLE %I.repl_nodes SET SCHEMA repmgr', old_schema); EXECUTE format('ALTER TABLE %I.repl_events SET SCHEMA repmgr', old_schema); EXECUTE format('ALTER TABLE %I.repl_monitor SET SCHEMA repmgr', old_schema); EXECUTE format('DROP VIEW IF EXISTS %I.repl_show_nodes', old_schema); EXECUTE format('DROP VIEW IF EXISTS %I.repl_status', old_schema); END$repmgr$; -- convert "repmgr_$cluster.repl_nodes" to "repmgr.nodes" CREATE TABLE repmgr.nodes ( node_id INTEGER PRIMARY KEY, upstream_node_id INTEGER NULL REFERENCES repmgr.nodes (node_id) DEFERRABLE, active BOOLEAN NOT NULL DEFAULT TRUE, node_name TEXT NOT NULL, type TEXT NOT NULL CHECK (type IN('primary','standby','witness','bdr')), location TEXT NOT NULL DEFAULT 'default', priority INT NOT NULL DEFAULT 100, conninfo TEXT NOT NULL, repluser VARCHAR(63) NOT NULL, slot_name TEXT NULL, config_file TEXT NOT NULL ); INSERT INTO repmgr.nodes (node_id, upstream_node_id, active, node_name, type, location, priority, conninfo, repluser, slot_name, config_file) SELECT id, upstream_node_id, active, name, CASE WHEN type = 'master' THEN 'primary' ELSE type END, 'default', priority, conninfo, 'unknown', slot_name, 'unknown' FROM repmgr.repl_nodes ORDER BY id; -- convert "repmgr_$cluster.repl_event" to "event" CREATE TABLE repmgr.events ( node_id INTEGER NOT NULL, event TEXT NOT NULL, successful BOOLEAN NOT NULL DEFAULT TRUE, event_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, details TEXT NULL ); INSERT INTO repmgr.events (node_id, event, successful, event_timestamp, details) SELECT node_id, event, successful, event_timestamp, details FROM repmgr.repl_events; -- create new table "repmgr.voting_term" CREATE TABLE repmgr.voting_term ( term INT NOT NULL ); CREATE UNIQUE INDEX voting_term_restrict ON repmgr.voting_term ((TRUE)); CREATE RULE voting_term_delete AS ON DELETE TO repmgr.voting_term DO INSTEAD NOTHING; INSERT INTO repmgr.voting_term (term) VALUES (1); -- convert "repmgr_$cluster.repl_monitor" to "monitoring_history" CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location PG_LSN NOT NULL, last_wal_standby_location PG_LSN, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ); INSERT INTO repmgr.monitoring_history (primary_node_id, standby_node_id, last_monitor_time, last_apply_time, last_wal_primary_location, last_wal_standby_location, replication_lag, apply_lag) SELECT primary_node, standby_node, last_monitor_time, last_apply_time, last_wal_primary_location::pg_lsn, last_wal_standby_location::pg_lsn, replication_lag, apply_lag FROM repmgr.repl_monitor; CREATE INDEX idx_monitoring_history_time ON repmgr.monitoring_history (last_monitor_time, standby_node_id); CREATE VIEW repmgr.show_nodes AS SELECT n.node_id, n.node_name, n.active, n.upstream_node_id, un.node_name AS upstream_node_name, n.type, n.priority, n.conninfo FROM repmgr.nodes n LEFT JOIN repmgr.nodes un ON un.node_id = n.upstream_node_id; /* ================= */ /* repmgrd functions */ /* ================= */ /* monitoring functions */ CREATE FUNCTION set_local_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION get_local_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'get_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION standby_set_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_set_last_updated' LANGUAGE C STRICT; CREATE FUNCTION standby_get_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'standby_get_last_updated' LANGUAGE C STRICT; CREATE FUNCTION set_upstream_last_seen(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_upstream_last_seen' LANGUAGE C STRICT; CREATE FUNCTION get_upstream_last_seen() RETURNS INT AS 'MODULE_PATHNAME', 'get_upstream_last_seen' LANGUAGE C STRICT; CREATE FUNCTION get_upstream_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'get_upstream_node_id' LANGUAGE C STRICT; CREATE FUNCTION set_upstream_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_upstream_node_id' LANGUAGE C STRICT; /* failover functions */ CREATE FUNCTION notify_follow_primary(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'notify_follow_primary' LANGUAGE C STRICT; CREATE FUNCTION get_new_primary() RETURNS INT AS 'MODULE_PATHNAME', 'get_new_primary' LANGUAGE C STRICT; CREATE FUNCTION reset_voting_status() RETURNS VOID AS 'MODULE_PATHNAME', 'reset_voting_status' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_repmgrd_pid' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pidfile() RETURNS TEXT AS 'MODULE_PATHNAME', 'get_repmgrd_pidfile' LANGUAGE C STRICT; CREATE FUNCTION set_repmgrd_pid(INT, TEXT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_repmgrd_pid' LANGUAGE C CALLED ON NULL INPUT; CREATE FUNCTION repmgrd_is_running() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_running' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_pause(BOOL) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgrd_pause' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_is_paused() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_paused' LANGUAGE C STRICT; CREATE FUNCTION get_wal_receiver_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_wal_receiver_pid' LANGUAGE C STRICT; /* views */ CREATE VIEW repmgr.replication_status AS SELECT m.primary_node_id, m.standby_node_id, n.node_name AS standby_name, n.type AS node_type, n.active, last_monitor_time, CASE WHEN n.type='standby' THEN m.last_wal_primary_location ELSE NULL END AS last_wal_primary_location, m.last_wal_standby_location, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.replication_lag) ELSE NULL END AS replication_lag, CASE WHEN n.type='standby' THEN CASE WHEN replication_lag > 0 THEN age(now(), m.last_apply_time) ELSE '0'::INTERVAL END ELSE NULL END AS replication_time_lag, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.apply_lag) ELSE NULL END AS apply_lag, AGE(NOW(), CASE WHEN pg_catalog.pg_is_in_recovery() THEN repmgr.standby_get_last_updated() ELSE m.last_monitor_time END) AS communication_time_lag FROM repmgr.monitoring_history m JOIN repmgr.nodes n ON m.standby_node_id = n.node_id WHERE (m.standby_node_id, m.last_monitor_time) IN ( SELECT m1.standby_node_id, MAX(m1.last_monitor_time) FROM repmgr.monitoring_history m1 GROUP BY 1 ); /* drop old tables */ DROP TABLE repmgr.repl_nodes; DROP TABLE repmgr.repl_monitor; DROP TABLE repmgr.repl_events; -- remove temporary table DROP TABLE repmgr_old_schema; repmgr-5.3.1/repmgr--unpackaged--5.3.sql000066400000000000000000000176311420262710000175700ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION repmgr" to load this file. \quit -- extract the current schema name -- NOTE: this assumes there will be only one schema matching 'repmgr_%'; -- user is responsible for ensuring this is the case CREATE TEMPORARY TABLE repmgr_old_schema (schema_name TEXT); INSERT INTO repmgr_old_schema (schema_name) SELECT nspname AS schema_name FROM pg_catalog.pg_namespace WHERE nspname LIKE 'repmgr_%' LIMIT 1; -- move old objects into new schema DO $repmgr$ DECLARE old_schema TEXT; BEGIN SELECT schema_name FROM repmgr_old_schema INTO old_schema; EXECUTE format('ALTER TABLE %I.repl_nodes SET SCHEMA repmgr', old_schema); EXECUTE format('ALTER TABLE %I.repl_events SET SCHEMA repmgr', old_schema); EXECUTE format('ALTER TABLE %I.repl_monitor SET SCHEMA repmgr', old_schema); EXECUTE format('DROP VIEW IF EXISTS %I.repl_show_nodes', old_schema); EXECUTE format('DROP VIEW IF EXISTS %I.repl_status', old_schema); END$repmgr$; -- convert "repmgr_$cluster.repl_nodes" to "repmgr.nodes" CREATE TABLE repmgr.nodes ( node_id INTEGER PRIMARY KEY, upstream_node_id INTEGER NULL REFERENCES repmgr.nodes (node_id) DEFERRABLE, active BOOLEAN NOT NULL DEFAULT TRUE, node_name TEXT NOT NULL, type TEXT NOT NULL CHECK (type IN('primary','standby','witness','bdr')), location TEXT NOT NULL DEFAULT 'default', priority INT NOT NULL DEFAULT 100, conninfo TEXT NOT NULL, repluser VARCHAR(63) NOT NULL, slot_name TEXT NULL, config_file TEXT NOT NULL ); INSERT INTO repmgr.nodes (node_id, upstream_node_id, active, node_name, type, location, priority, conninfo, repluser, slot_name, config_file) SELECT id, upstream_node_id, active, name, CASE WHEN type = 'master' THEN 'primary' ELSE type END, 'default', priority, conninfo, 'unknown', slot_name, 'unknown' FROM repmgr.repl_nodes ORDER BY id; -- convert "repmgr_$cluster.repl_event" to "event" CREATE TABLE repmgr.events ( node_id INTEGER NOT NULL, event TEXT NOT NULL, successful BOOLEAN NOT NULL DEFAULT TRUE, event_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, details TEXT NULL ); INSERT INTO repmgr.events (node_id, event, successful, event_timestamp, details) SELECT node_id, event, successful, event_timestamp, details FROM repmgr.repl_events; -- create new table "repmgr.voting_term" CREATE TABLE repmgr.voting_term ( term INT NOT NULL ); CREATE UNIQUE INDEX voting_term_restrict ON repmgr.voting_term ((TRUE)); CREATE RULE voting_term_delete AS ON DELETE TO repmgr.voting_term DO INSTEAD NOTHING; INSERT INTO repmgr.voting_term (term) VALUES (1); -- convert "repmgr_$cluster.repl_monitor" to "monitoring_history" CREATE TABLE repmgr.monitoring_history ( primary_node_id INTEGER NOT NULL, standby_node_id INTEGER NOT NULL, last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, last_apply_time TIMESTAMP WITH TIME ZONE, last_wal_primary_location PG_LSN NOT NULL, last_wal_standby_location PG_LSN, replication_lag BIGINT NOT NULL, apply_lag BIGINT NOT NULL ); INSERT INTO repmgr.monitoring_history (primary_node_id, standby_node_id, last_monitor_time, last_apply_time, last_wal_primary_location, last_wal_standby_location, replication_lag, apply_lag) SELECT primary_node, standby_node, last_monitor_time, last_apply_time, last_wal_primary_location::pg_lsn, last_wal_standby_location::pg_lsn, replication_lag, apply_lag FROM repmgr.repl_monitor; CREATE INDEX idx_monitoring_history_time ON repmgr.monitoring_history (last_monitor_time, standby_node_id); CREATE VIEW repmgr.show_nodes AS SELECT n.node_id, n.node_name, n.active, n.upstream_node_id, un.node_name AS upstream_node_name, n.type, n.priority, n.conninfo FROM repmgr.nodes n LEFT JOIN repmgr.nodes un ON un.node_id = n.upstream_node_id; /* ================= */ /* repmgrd functions */ /* ================= */ /* monitoring functions */ CREATE FUNCTION set_local_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgr_set_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION get_local_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'repmgr_get_local_node_id' LANGUAGE C STRICT; CREATE FUNCTION standby_set_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'repmgr_standby_set_last_updated' LANGUAGE C STRICT; CREATE FUNCTION standby_get_last_updated() RETURNS TIMESTAMP WITH TIME ZONE AS 'MODULE_PATHNAME', 'repmgr_standby_get_last_updated' LANGUAGE C STRICT; CREATE FUNCTION set_upstream_last_seen(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgr_set_upstream_last_seen' LANGUAGE C STRICT; CREATE FUNCTION get_upstream_last_seen() RETURNS INT AS 'MODULE_PATHNAME', 'repmgr_get_upstream_last_seen' LANGUAGE C STRICT; CREATE FUNCTION get_upstream_node_id() RETURNS INT AS 'MODULE_PATHNAME', 'repmgr_get_upstream_node_id' LANGUAGE C STRICT; CREATE FUNCTION set_upstream_node_id(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgr_set_upstream_node_id' LANGUAGE C STRICT; /* failover functions */ CREATE FUNCTION notify_follow_primary(INT) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgr_notify_follow_primary' LANGUAGE C STRICT; CREATE FUNCTION get_new_primary() RETURNS INT AS 'MODULE_PATHNAME', 'repmgr_get_new_primary' LANGUAGE C STRICT; CREATE FUNCTION reset_voting_status() RETURNS VOID AS 'MODULE_PATHNAME', 'repmgr_reset_voting_status' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pid() RETURNS INT AS 'MODULE_PATHNAME', 'get_repmgrd_pid' LANGUAGE C STRICT; CREATE FUNCTION get_repmgrd_pidfile() RETURNS TEXT AS 'MODULE_PATHNAME', 'get_repmgrd_pidfile' LANGUAGE C STRICT; CREATE FUNCTION set_repmgrd_pid(INT, TEXT) RETURNS VOID AS 'MODULE_PATHNAME', 'set_repmgrd_pid' LANGUAGE C CALLED ON NULL INPUT; CREATE FUNCTION repmgrd_is_running() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_running' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_pause(BOOL) RETURNS VOID AS 'MODULE_PATHNAME', 'repmgrd_pause' LANGUAGE C STRICT; CREATE FUNCTION repmgrd_is_paused() RETURNS BOOL AS 'MODULE_PATHNAME', 'repmgrd_is_paused' LANGUAGE C STRICT; CREATE FUNCTION get_wal_receiver_pid() RETURNS INT AS 'MODULE_PATHNAME', 'repmgr_get_wal_receiver_pid' LANGUAGE C STRICT; /* views */ CREATE VIEW repmgr.replication_status AS SELECT m.primary_node_id, m.standby_node_id, n.node_name AS standby_name, n.type AS node_type, n.active, last_monitor_time, CASE WHEN n.type='standby' THEN m.last_wal_primary_location ELSE NULL END AS last_wal_primary_location, m.last_wal_standby_location, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.replication_lag) ELSE NULL END AS replication_lag, CASE WHEN n.type='standby' THEN CASE WHEN replication_lag > 0 THEN age(now(), m.last_apply_time) ELSE '0'::INTERVAL END ELSE NULL END AS replication_time_lag, CASE WHEN n.type='standby' THEN pg_catalog.pg_size_pretty(m.apply_lag) ELSE NULL END AS apply_lag, AGE(NOW(), CASE WHEN pg_catalog.pg_is_in_recovery() THEN repmgr.standby_get_last_updated() ELSE m.last_monitor_time END) AS communication_time_lag FROM repmgr.monitoring_history m JOIN repmgr.nodes n ON m.standby_node_id = n.node_id WHERE (m.standby_node_id, m.last_monitor_time) IN ( SELECT m1.standby_node_id, MAX(m1.last_monitor_time) FROM repmgr.monitoring_history m1 GROUP BY 1 ); /* drop old tables */ DROP TABLE repmgr.repl_nodes; DROP TABLE repmgr.repl_monitor; DROP TABLE repmgr.repl_events; -- remove temporary table DROP TABLE repmgr_old_schema; repmgr-5.3.1/repmgr-action-cluster.c000066400000000000000000001141101420262710000173760ustar00rootroot00000000000000/* * repmgr-action-cluster.c * * Implements cluster information actions for the repmgr command line utility * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #include "repmgr.h" #include "compat.h" #include "repmgr-client-global.h" #include "repmgr-action-cluster.h" #define SHOW_HEADER_COUNT 9 typedef enum { SHOW_ID = 0, SHOW_NAME, SHOW_ROLE, SHOW_STATUS, SHOW_UPSTREAM_NAME, SHOW_LOCATION, SHOW_PRIORITY, SHOW_TIMELINE_ID, SHOW_CONNINFO } ShowHeader; #define EVENT_HEADER_COUNT 6 typedef enum { EV_NODE_ID = 0, EV_NODE_NAME, EV_EVENT, EV_SUCCESS, EV_TIMESTAMP, EV_DETAILS } EventHeader; struct ColHeader headers_show[SHOW_HEADER_COUNT]; struct ColHeader headers_event[EVENT_HEADER_COUNT]; static int build_cluster_matrix(t_node_matrix_rec ***matrix_rec_dest, ItemList *warnings, int *error_code); static int build_cluster_crosscheck(t_node_status_cube ***cube_dest, ItemList *warnings, int *error_code); static void cube_set_node_status(t_node_status_cube **cube, int n, int node_id, int matrix_node_id, int connection_node_id, int connection_status); /* * CLUSTER SHOW * * Parameters: * --compact * --csv * --terse * --verbose */ void do_cluster_show(void) { PGconn *conn = NULL; NodeInfoList nodes = T_NODE_INFO_LIST_INITIALIZER; NodeInfoListCell *cell = NULL; int i = 0; ItemList warnings = {NULL, NULL}; bool success = false; bool error_found = false; bool connection_error_found = false; /* Connect to local database to obtain cluster connection data */ log_verbose(LOG_INFO, _("connecting to database")); if (strlen(config_file_options.conninfo)) conn = establish_db_connection(config_file_options.conninfo, true); else conn = establish_db_connection_by_params(&source_conninfo, true); success = get_all_node_records_with_upstream(conn, &nodes); if (success == false) { /* get_all_node_records_with_upstream() will print error message */ PQfinish(conn); exit(ERR_BAD_CONFIG); } if (nodes.node_count == 0) { log_error(_("no node records were found")); log_hint(_("ensure at least one node is registered")); PQfinish(conn); exit(ERR_BAD_CONFIG); } /* Initialize column headers */ strncpy(headers_show[SHOW_ID].title, _("ID"), MAXLEN); strncpy(headers_show[SHOW_NAME].title, _("Name"), MAXLEN); strncpy(headers_show[SHOW_ROLE].title, _("Role"), MAXLEN); strncpy(headers_show[SHOW_STATUS].title, _("Status"), MAXLEN); strncpy(headers_show[SHOW_UPSTREAM_NAME].title, _("Upstream"), MAXLEN); strncpy(headers_show[SHOW_LOCATION].title, _("Location"), MAXLEN); if (runtime_options.compact == true) { strncpy(headers_show[SHOW_PRIORITY].title, _("Prio."), MAXLEN); strncpy(headers_show[SHOW_TIMELINE_ID].title, _("TLI"), MAXLEN); } else { strncpy(headers_show[SHOW_PRIORITY].title, _("Priority"), MAXLEN); strncpy(headers_show[SHOW_TIMELINE_ID].title, _("Timeline"), MAXLEN); } strncpy(headers_show[SHOW_CONNINFO].title, _("Connection string"), MAXLEN); /* * NOTE: if repmgr is ever localized into non-ASCII locales, use * pg_wcssize() or similar to establish printed column length */ for (i = 0; i < SHOW_HEADER_COUNT; i++) { headers_show[i].display = true; /* Don't display timeline on pre-9.6 clusters */ if (i == SHOW_TIMELINE_ID) { if (PQserverVersion(conn) < 90600) { headers_show[i].display = false; } } /* if --compact provided, don't display conninfo */ if (runtime_options.compact == true) { if (i == SHOW_CONNINFO) { headers_show[i].display = false; } } if (headers_show[i].display == true) { headers_show[i].max_length = strlen(headers_show[i].title); } } /* * TODO: count nodes marked as "? unreachable" and add a hint about * the other cluster commands for better determining whether * unreachable. */ for (cell = nodes.head; cell; cell = cell->next) { PQExpBufferData node_status; PQExpBufferData upstream; PQExpBufferData buf; cell->node_info->replication_info = palloc0(sizeof(ReplInfo)); if (cell->node_info->replication_info == NULL) { log_error(_("unable to allocate memory")); exit(ERR_INTERNAL); } init_replication_info(cell->node_info->replication_info); cell->node_info->conn = establish_db_connection_quiet(cell->node_info->conninfo); if (PQstatus(cell->node_info->conn) != CONNECTION_OK) { connection_error_found = true; if (runtime_options.verbose) { char error[MAXLEN]; strncpy(error, PQerrorMessage(cell->node_info->conn), MAXLEN); item_list_append_format(&warnings, "when attempting to connect to node \"%s\" (ID: %i), following error encountered :\n\"%s\"", cell->node_info->node_name, cell->node_info->node_id, trim(error)); } else { item_list_append_format(&warnings, "unable to connect to node \"%s\" (ID: %i)", cell->node_info->node_name, cell->node_info->node_id); } } else { /* NOP on pre-9.6 servers */ cell->node_info->replication_info->timeline_id = get_node_timeline(cell->node_info->conn, cell->node_info->replication_info->timeline_id_str); } initPQExpBuffer(&node_status); initPQExpBuffer(&upstream); if (format_node_status(cell->node_info, &node_status, &upstream, &warnings) == true) error_found = true; snprintf(cell->node_info->details, sizeof(cell->node_info->details), "%s", node_status.data); snprintf(cell->node_info->upstream_node_name, sizeof(cell->node_info->upstream_node_name), "%s", upstream.data); termPQExpBuffer(&node_status); termPQExpBuffer(&upstream); PQfinish(cell->node_info->conn); cell->node_info->conn = NULL; initPQExpBuffer(&buf); appendPQExpBuffer(&buf, "%i", cell->node_info->node_id); headers_show[SHOW_ID].cur_length = strlen(buf.data); termPQExpBuffer(&buf); headers_show[SHOW_ROLE].cur_length = strlen(get_node_type_string(cell->node_info->type)); headers_show[SHOW_NAME].cur_length = strlen(cell->node_info->node_name); headers_show[SHOW_STATUS].cur_length = strlen(cell->node_info->details); headers_show[SHOW_UPSTREAM_NAME].cur_length = strlen(cell->node_info->upstream_node_name); initPQExpBuffer(&buf); appendPQExpBuffer(&buf, "%i", cell->node_info->priority); headers_show[SHOW_PRIORITY].cur_length = strlen(buf.data); termPQExpBuffer(&buf); headers_show[SHOW_LOCATION].cur_length = strlen(cell->node_info->location); /* Format timeline ID */ if (cell->node_info->type == WITNESS) { /* The witness node's timeline ID is irrelevant */ strncpy(cell->node_info->replication_info->timeline_id_str, _("n/a"), MAXLEN); } headers_show[SHOW_TIMELINE_ID].cur_length = strlen(cell->node_info->replication_info->timeline_id_str); headers_show[SHOW_CONNINFO].cur_length = strlen(cell->node_info->conninfo); for (i = 0; i < SHOW_HEADER_COUNT; i++) { if (runtime_options.compact == true) { if (headers_show[i].display == false) continue; } if (headers_show[i].cur_length > headers_show[i].max_length) { headers_show[i].max_length = headers_show[i].cur_length; } } } /* Print column header row (text mode only) */ if (runtime_options.output_mode == OM_TEXT) { print_status_header(SHOW_HEADER_COUNT, headers_show); } for (cell = nodes.head; cell; cell = cell->next) { if (runtime_options.output_mode == OM_CSV) { int connection_status = (cell->node_info->node_status == NODE_STATUS_UP) ? 0 : -1; int recovery_type = RECTYPE_UNKNOWN; /* * here we explicitly convert the RecoveryType to integer values * to avoid implicit dependency on the values in the enum */ switch (cell->node_info->recovery_type) { case RECTYPE_UNKNOWN: recovery_type = -1; break; case RECTYPE_PRIMARY: recovery_type = 0; break; case RECTYPE_STANDBY: recovery_type = 1; break; } printf("%i,%i,%i\n", cell->node_info->node_id, connection_status, recovery_type); } else { printf(" %-*i ", headers_show[SHOW_ID].max_length, cell->node_info->node_id); printf("| %-*s ", headers_show[SHOW_NAME].max_length, cell->node_info->node_name); printf("| %-*s ", headers_show[SHOW_ROLE].max_length, get_node_type_string(cell->node_info->type)); printf("| %-*s ", headers_show[SHOW_STATUS].max_length, cell->node_info->details); printf("| %-*s ", headers_show[SHOW_UPSTREAM_NAME].max_length, cell->node_info->upstream_node_name); printf("| %-*s ", headers_show[SHOW_LOCATION].max_length, cell->node_info->location); printf("| %-*i ", headers_show[SHOW_PRIORITY].max_length, cell->node_info->priority); if (headers_show[SHOW_TIMELINE_ID].display == true) { printf("| %-*s ", headers_show[SHOW_TIMELINE_ID].max_length, cell->node_info->replication_info->timeline_id_str); } if (headers_show[SHOW_CONNINFO].display == true) { printf("| %-*s", headers_show[SHOW_CONNINFO].max_length, cell->node_info->conninfo); } puts(""); } } clear_node_info_list(&nodes); PQfinish(conn); /* emit any warnings */ if (warnings.head != NULL && runtime_options.terse == false && runtime_options.output_mode != OM_CSV) { ItemListCell *cell = NULL; PQExpBufferData warning; initPQExpBuffer(&warning); appendPQExpBufferStr(&warning, _("following issues were detected\n")); for (cell = warnings.head; cell; cell = cell->next) { appendPQExpBuffer(&warning, _(" - %s\n"), cell->string); } puts(""); log_warning("%s", warning.data); termPQExpBuffer(&warning); if (runtime_options.verbose == false && connection_error_found == true) { log_hint(_("execute with --verbose option to see connection error messages")); } } /* * If warnings were noted, even if they're not displayed (e.g. in --csv node), * that means something's not right so we need to emit a non-zero exit code. */ if (warnings.head != NULL) { error_found = true; } if (error_found == true) { exit(ERR_NODE_STATUS); } } /* * CLUSTER EVENT * * Parameters: * --limit[=20] * --all * --node-[id|name] * --event * --csv * --compact */ void do_cluster_event(void) { PGconn *conn = NULL; PGresult *res; int i = 0; int column_count = EVENT_HEADER_COUNT; conn = establish_db_connection(config_file_options.conninfo, true); res = get_event_records(conn, runtime_options.node_id, runtime_options.node_name, runtime_options.event, runtime_options.all, runtime_options.limit); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to execute event query:\n %s"), PQerrorMessage(conn)); PQclear(res); PQfinish(conn); exit(ERR_DB_QUERY); } if (PQntuples(res) == 0) { /* print this message directly, rather than as a log line */ printf(_("no matching events found\n")); PQclear(res); PQfinish(conn); return; } strncpy(headers_event[EV_NODE_ID].title, _("Node ID"), MAXLEN); strncpy(headers_event[EV_NODE_NAME].title, _("Name"), MAXLEN); strncpy(headers_event[EV_EVENT].title, _("Event"), MAXLEN); strncpy(headers_event[EV_SUCCESS].title, _("OK"), MAXLEN); strncpy(headers_event[EV_TIMESTAMP].title, _("Timestamp"), MAXLEN); strncpy(headers_event[EV_DETAILS].title, _("Details"), MAXLEN); /* * If --compact or --csv provided, simply omit the "Details" column. * In --csv mode we'd need to quote/escape the contents "Details" column, * which is doable but which will remain a TODO for now. */ if (runtime_options.compact == true || runtime_options.output_mode == OM_CSV) column_count --; for (i = 0; i < column_count; i++) { headers_event[i].max_length = strlen(headers_event[i].title); } for (i = 0; i < PQntuples(res); i++) { int j; for (j = 0; j < column_count; j++) { headers_event[j].cur_length = strlen(PQgetvalue(res, i, j)); if (headers_event[j].cur_length > headers_event[j].max_length) { headers_event[j].max_length = headers_event[j].cur_length; } } } if (runtime_options.output_mode == OM_TEXT) { for (i = 0; i < column_count; i++) { if (i == 0) printf(" "); else printf(" | "); printf("%-*s", headers_event[i].max_length, headers_event[i].title); } printf("\n"); printf("-"); for (i = 0; i < column_count; i++) { int j; for (j = 0; j < headers_event[i].max_length; j++) printf("-"); if (i < (column_count - 1)) printf("-+-"); else printf("-"); } printf("\n"); } for (i = 0; i < PQntuples(res); i++) { int j; if (runtime_options.output_mode == OM_CSV) { for (j = 0; j < column_count; j++) { printf("%s", PQgetvalue(res, i, j)); if ((j + 1) < column_count) { printf(","); } } } else { printf(" "); for (j = 0; j < column_count; j++) { printf("%-*s", headers_event[j].max_length, PQgetvalue(res, i, j)); if (j < (column_count - 1)) printf(" | "); } } printf("\n"); } PQclear(res); PQfinish(conn); if (runtime_options.output_mode == OM_TEXT) puts(""); } void do_cluster_crosscheck(void) { int i = 0, n = 0; t_node_status_cube **cube; bool connection_error_found = false; int error_code = SUCCESS; ItemList warnings = {NULL, NULL}; n = build_cluster_crosscheck(&cube, &warnings, &error_code); if (runtime_options.output_mode == OM_CSV) { for (i = 0; i < n; i++) { int j; for (j = 0; j < n; j++) { int max_node_status = -2; int node_ix = 0; for (node_ix = 0; node_ix < n; node_ix++) { int node_status = cube[node_ix]->matrix_list_rec[i]->node_status_list[j]->node_status; if (node_status > max_node_status) max_node_status = node_status; } printf("%i,%i,%i\n", cube[i]->node_id, cube[j]->node_id, max_node_status); if (max_node_status == -1) { connection_error_found = true; } } } } else { /* output header contains node name, node ID and one column for each node in the cluster */ struct ColHeader *headers_crosscheck = NULL; int header_count = n + 2; int header_id = 2; headers_crosscheck = palloc0(sizeof(ColHeader) * header_count); /* Initialize column headers */ strncpy(headers_crosscheck[0].title, _("Name"), MAXLEN); strncpy(headers_crosscheck[1].title, _("ID"), MAXLEN); for (i = 0; i < n; i++) { maxlen_snprintf(headers_crosscheck[header_id].title, "%i", cube[i]->node_id); header_id++; } /* Initialize column max values */ for (i = 0; i < header_count; i++) { headers_crosscheck[i].display = true; headers_crosscheck[i].max_length = strlen(headers_crosscheck[i].title); headers_crosscheck[i].cur_length = headers_crosscheck[i].max_length; /* We can derive the maximum node ID length for the ID column from * the generated matrix node ID headers */ if (i >= 2 && headers_crosscheck[i].max_length > headers_crosscheck[1].max_length) headers_crosscheck[1].max_length = headers_crosscheck[i].max_length; } for (i = 0; i < n; i++) { if (strlen(cube[i]->node_name) > headers_crosscheck[0].max_length) { headers_crosscheck[0].max_length = strlen(cube[i]->node_name); } } print_status_header(header_count, headers_crosscheck); for (i = 0; i < n; i++) { int column_node_ix; printf(" %-*s | %-*i ", headers_crosscheck[0].max_length, cube[i]->node_name, headers_crosscheck[1].max_length, cube[i]->node_id); for (column_node_ix = 0; column_node_ix < n; column_node_ix++) { int max_node_status = -2; int node_ix = 0; char c; /* * The value of entry (i,j) is equal to the maximum value of all * the (i,j,k). Indeed: * * - if one of the (i,j,k) is 0 (node up), then 0 (the node is * up); * * - if the (i,j,k) are either -1 (down) or -2 (unknown), then -1 * (the node is down); * * - if all the (i,j,k) are -2 (unknown), then -2 (the node is in * an unknown state). */ for (node_ix = 0; node_ix < n; node_ix++) { int node_status = cube[node_ix]->matrix_list_rec[i]->node_status_list[column_node_ix]->node_status; if (node_status > max_node_status) max_node_status = node_status; } switch (max_node_status) { case -2: c = '?'; break; case -1: c = 'x'; connection_error_found = true; break; case 0: c = '*'; break; default: log_error("unexpected node status value %i", max_node_status); exit(ERR_INTERNAL); } printf("| %-*c ", headers_crosscheck[column_node_ix + 2].max_length, c); } printf("\n"); } pfree(headers_crosscheck); if (warnings.head != NULL && runtime_options.terse == false) { log_warning(_("following problems detected:")); print_item_list(&warnings); } } /* clean up allocated cube array */ { int h, j; for (h = 0; h < n; h++) { for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { free(cube[h]->matrix_list_rec[i]->node_status_list[j]); } free(cube[h]->matrix_list_rec[i]->node_status_list); free(cube[h]->matrix_list_rec[i]); } free(cube[h]->matrix_list_rec); free(cube[h]); } free(cube); } /* errors detected by build_cluster_crosscheck() have priority */ if (connection_error_found == true) { error_code = ERR_NODE_STATUS; } exit(error_code); } /* * CLUSTER MATRIX * * Parameters: * --csv */ void do_cluster_matrix() { int i = 0, j = 0, n = 0; t_node_matrix_rec **matrix_rec_list; bool connection_error_found = false; int error_code = SUCCESS; ItemList warnings = {NULL, NULL}; n = build_cluster_matrix(&matrix_rec_list, &warnings, &error_code); if (runtime_options.output_mode == OM_CSV) { for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { printf("%d,%d,%d\n", matrix_rec_list[i]->node_id, matrix_rec_list[i]->node_status_list[j]->node_id, matrix_rec_list[i]->node_status_list[j]->node_status); if (matrix_rec_list[i]->node_status_list[j]->node_status == -2 || matrix_rec_list[i]->node_status_list[j]->node_status == -1) { connection_error_found = true; } } } } else { /* output header contains node name, node ID and one column for each node in the cluster */ struct ColHeader *headers_matrix = NULL; int header_count = n + 2; int header_id = 2; headers_matrix = palloc0(sizeof(ColHeader) * header_count); /* Initialize column headers */ strncpy(headers_matrix[0].title, _("Name"), MAXLEN); strncpy(headers_matrix[1].title, _("ID"), MAXLEN); for (i = 0; i < n; i++) { maxlen_snprintf(headers_matrix[header_id].title, "%i", matrix_rec_list[i]->node_id); header_id++; } /* Initialize column max values */ for (i = 0; i < header_count; i++) { headers_matrix[i].display = true; headers_matrix[i].max_length = strlen(headers_matrix[i].title); headers_matrix[i].cur_length = headers_matrix[i].max_length; /* We can derive the maximum node ID length for the ID column from * the generated matrix node ID headers */ if (i >= 2 && headers_matrix[i].max_length > headers_matrix[1].max_length) headers_matrix[1].max_length = headers_matrix[i].max_length; } for (i = 0; i < n; i++) { if (strlen(matrix_rec_list[i]->node_name) > headers_matrix[0].max_length) { headers_matrix[0].max_length = strlen(matrix_rec_list[i]->node_name); } } print_status_header(header_count, headers_matrix); for (i = 0; i < n; i++) { printf(" %-*s | %-*i ", headers_matrix[0].max_length, matrix_rec_list[i]->node_name, headers_matrix[1].max_length, matrix_rec_list[i]->node_id); for (j = 0; j < n; j++) { char c; switch (matrix_rec_list[i]->node_status_list[j]->node_status) { case -2: c = '?'; break; case -1: c = 'x'; connection_error_found = true; break; case 0: c = '*'; break; default: log_error("unexpected node status value %i", matrix_rec_list[i]->node_status_list[j]->node_status); exit(ERR_INTERNAL); } printf("| %-*c ", headers_matrix[j + 2].max_length, c); } printf("\n"); } pfree(headers_matrix); if (warnings.head != NULL && runtime_options.terse == false) { log_warning(_("following problems detected:")); print_item_list(&warnings); } } for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { free(matrix_rec_list[i]->node_status_list[j]); } free(matrix_rec_list[i]->node_status_list); free(matrix_rec_list[i]); } free(matrix_rec_list); /* actual database connection errors have priority */ if (connection_error_found == true) { error_code = ERR_NODE_STATUS; } exit(error_code); } static void matrix_set_node_status(t_node_matrix_rec **matrix_rec_list, int n, int node_id, int connection_node_id, int connection_status) { int i, j; for (i = 0; i < n; i++) { if (matrix_rec_list[i]->node_id == node_id) { for (j = 0; j < n; j++) { if (matrix_rec_list[i]->node_status_list[j]->node_id == connection_node_id) { matrix_rec_list[i]->node_status_list[j]->node_status = connection_status; break; } } break; } } } static int build_cluster_matrix(t_node_matrix_rec ***matrix_rec_dest, ItemList *warnings, int *error_code) { PGconn *conn = NULL; int i = 0, j = 0; int local_node_id = UNKNOWN_NODE_ID; int node_count = 0; NodeInfoList nodes = T_NODE_INFO_LIST_INITIALIZER; NodeInfoListCell *cell = NULL; PQExpBufferData command; PQExpBufferData command_output; t_node_matrix_rec **matrix_rec_list; /* obtain node list from the database */ log_info(_("connecting to database")); if (strlen(config_file_options.conninfo)) { conn = establish_db_connection(config_file_options.conninfo, true); local_node_id = config_file_options.node_id; } else { conn = establish_db_connection_by_params(&source_conninfo, true); local_node_id = runtime_options.node_id; } if (get_all_node_records(conn, &nodes) == false) { /* get_all_node_records() will display the error */ PQfinish(conn); exit(ERR_BAD_CONFIG); } PQfinish(conn); conn = NULL; if (nodes.node_count == 0) { log_error(_("unable to retrieve any node records")); exit(ERR_BAD_CONFIG); } /* * Allocate an empty matrix record list * * -2 == NULL ? -1 == Error x 0 == OK */ matrix_rec_list = (t_node_matrix_rec **) pg_malloc0(sizeof(t_node_matrix_rec) * nodes.node_count); i = 0; /* Initialise matrix structure for each node */ for (cell = nodes.head; cell; cell = cell->next) { NodeInfoListCell *cell_j; matrix_rec_list[i] = (t_node_matrix_rec *) pg_malloc0(sizeof(t_node_matrix_rec)); matrix_rec_list[i]->node_id = cell->node_info->node_id; strncpy(matrix_rec_list[i]->node_name, cell->node_info->node_name, sizeof(matrix_rec_list[i]->node_name)); matrix_rec_list[i]->node_status_list = (t_node_status_rec **) pg_malloc0(sizeof(t_node_status_rec) * nodes.node_count); j = 0; for (cell_j = nodes.head; cell_j; cell_j = cell_j->next) { matrix_rec_list[i]->node_status_list[j] = (t_node_status_rec *) pg_malloc0(sizeof(t_node_status_rec)); matrix_rec_list[i]->node_status_list[j]->node_id = cell_j->node_info->node_id; matrix_rec_list[i]->node_status_list[j]->node_status = -2; /* default unknown */ j++; } i++; } /* Fetch `repmgr cluster show --csv` output for each node */ i = 0; for (cell = nodes.head; cell; cell = cell->next) { int connection_status = 0; t_conninfo_param_list remote_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; char *host = NULL, *p = NULL; int connection_node_id = cell->node_info->node_id; int x, y; PGconn *node_conn = NULL; initialize_conninfo_params(&remote_conninfo, false); parse_conninfo_string(cell->node_info->conninfo, &remote_conninfo, NULL, false); host = param_get(&remote_conninfo, "host"); node_conn = establish_db_connection_quiet(cell->node_info->conninfo); connection_status = (PQstatus(node_conn) == CONNECTION_OK) ? 0 : -1; matrix_set_node_status(matrix_rec_list, nodes.node_count, local_node_id, connection_node_id, connection_status); if (connection_status) { free_conninfo_params(&remote_conninfo); PQfinish(node_conn); node_conn = NULL; continue; } /* We don't need to issue `cluster show --csv` for the local node */ if (connection_node_id == local_node_id) { free_conninfo_params(&remote_conninfo); PQfinish(node_conn); node_conn = NULL; continue; } initPQExpBuffer(&command); /* * We'll pass cluster name and database connection string to the * remote repmgr - those are the only values it needs to work, and * saves us making assumptions about the location of repmgr.conf */ appendPQExpBufferChar(&command, '"'); make_remote_repmgr_path(&command, cell->node_info); appendPQExpBufferStr(&command, " cluster show --csv --terse"); /* * Usually we'll want NOTICE as the log level, but if the user * explicitly provided one with --log-level, that will be passed * in the remote repmgr invocation. */ if (runtime_options.log_level[0] == '\0') { appendPQExpBufferStr(&command, " -L NOTICE"); } appendPQExpBufferChar(&command, '"'); log_verbose(LOG_DEBUG, "build_cluster_matrix(): executing:\n %s", command.data); initPQExpBuffer(&command_output); (void) remote_command(host, runtime_options.remote_user, command.data, config_file_options.ssh_options, &command_output); p = command_output.data; termPQExpBuffer(&command); /* no output returned - probably SSH error */ if (p[0] == '\0' || p[0] == '\n') { item_list_append_format(warnings, "node %i inaccessible via SSH", connection_node_id); *error_code = ERR_BAD_SSH; } else { for (j = 0; j < nodes.node_count; j++) { if (sscanf(p, "%d,%d", &x, &y) != 2) { matrix_set_node_status(matrix_rec_list, nodes.node_count, connection_node_id, x, -2); item_list_append_format(warnings, "unable to parse --csv output for node %i; output returned was:\n\"%s\"", connection_node_id, p); *error_code = ERR_INTERNAL; } else { matrix_set_node_status(matrix_rec_list, nodes.node_count, connection_node_id, x, (y == -1) ? -1 : 0); } while (*p && (*p != '\n')) p++; if (*p == '\n') p++; } } termPQExpBuffer(&command_output); PQfinish(node_conn); free_conninfo_params(&remote_conninfo); } *matrix_rec_dest = matrix_rec_list; node_count = nodes.node_count; clear_node_info_list(&nodes); return node_count; } static int build_cluster_crosscheck(t_node_status_cube ***dest_cube, ItemList *warnings, int *error_code) { PGconn *conn = NULL; int h, i, j; NodeInfoList nodes = T_NODE_INFO_LIST_INITIALIZER; NodeInfoListCell *cell = NULL; t_node_status_cube **cube; int node_count = 0; /* We need to connect to get the list of nodes */ log_info(_("connecting to database")); if (strlen(config_file_options.conninfo)) conn = establish_db_connection(config_file_options.conninfo, true); else conn = establish_db_connection_by_params(&source_conninfo, true); if (get_all_node_records(conn, &nodes) == false) { /* get_all_node_records() will display the error */ PQfinish(conn); exit(ERR_BAD_CONFIG); } PQfinish(conn); conn = NULL; if (nodes.node_count == 0) { log_error(_("unable to retrieve any node records")); exit(ERR_BAD_CONFIG); } /* * Allocate an empty cube matrix structure * * -2 == NULL -1 == Error 0 == OK */ cube = (t_node_status_cube **) pg_malloc(sizeof(t_node_status_cube *) * nodes.node_count); h = 0; for (cell = nodes.head; cell; cell = cell->next) { NodeInfoListCell *cell_i = NULL; cube[h] = (t_node_status_cube *) pg_malloc(sizeof(t_node_status_cube)); cube[h]->node_id = cell->node_info->node_id; strncpy(cube[h]->node_name, cell->node_info->node_name, sizeof(cube[h]->node_name)); cube[h]->matrix_list_rec = (t_node_matrix_rec **) pg_malloc(sizeof(t_node_matrix_rec) * nodes.node_count); i = 0; for (cell_i = nodes.head; cell_i; cell_i = cell_i->next) { NodeInfoListCell *cell_j; cube[h]->matrix_list_rec[i] = (t_node_matrix_rec *) pg_malloc0(sizeof(t_node_matrix_rec)); cube[h]->matrix_list_rec[i]->node_id = cell_i->node_info->node_id; /* we don't need the name here */ cube[h]->matrix_list_rec[i]->node_name[0] = '\0'; cube[h]->matrix_list_rec[i]->node_status_list = (t_node_status_rec **) pg_malloc0(sizeof(t_node_status_rec *) * nodes.node_count); j = 0; for (cell_j = nodes.head; cell_j; cell_j = cell_j->next) { cube[h]->matrix_list_rec[i]->node_status_list[j] = (t_node_status_rec *) pg_malloc0(sizeof(t_node_status_rec)); cube[h]->matrix_list_rec[i]->node_status_list[j]->node_id = cell_j->node_info->node_id; cube[h]->matrix_list_rec[i]->node_status_list[j]->node_status = -2; /* default unknown */ j++; } i++; } h++; } /* * Build the connection cube */ i = 0; for (cell = nodes.head; cell; cell = cell->next) { int remote_node_id = UNKNOWN_NODE_ID; PQExpBufferData command; PQExpBufferData command_output; char *p = NULL; remote_node_id = cell->node_info->node_id; initPQExpBuffer(&command); make_remote_repmgr_path(&command, cell->node_info); appendPQExpBufferStr(&command, " cluster matrix --csv --terse"); /* * Usually we'll want NOTICE as the log level, but if the user * explicitly provided one with --log-level, that will be passed * in the remote repmgr invocation. */ if (runtime_options.log_level[0] == '\0') { appendPQExpBufferStr(&command, " -L NOTICE"); } initPQExpBuffer(&command_output); if (cube[i]->node_id == config_file_options.node_id) { (void) local_command_simple(command.data, &command_output); } else { t_conninfo_param_list remote_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; char *host = NULL; PQExpBufferData quoted_command; initPQExpBuffer("ed_command); appendPQExpBuffer("ed_command, "\"%s\"", command.data); initialize_conninfo_params(&remote_conninfo, false); parse_conninfo_string(cell->node_info->conninfo, &remote_conninfo, NULL, false); host = param_get(&remote_conninfo, "host"); log_verbose(LOG_DEBUG, "build_cluster_crosscheck(): executing\n %s", quoted_command.data); (void) remote_command(host, runtime_options.remote_user, quoted_command.data, config_file_options.ssh_options, &command_output); free_conninfo_params(&remote_conninfo); termPQExpBuffer("ed_command); } termPQExpBuffer(&command); p = command_output.data; if (p[0] == '\0' || p[0] == '\n') { item_list_append_format(warnings, "node %i inaccessible via SSH", remote_node_id); termPQExpBuffer(&command_output); *error_code = ERR_BAD_SSH; continue; } for (j = 0; j < (nodes.node_count * nodes.node_count); j++) { int matrix_rec_node_id; int node_status_node_id; int node_status; if (sscanf(p, "%d,%d,%d", &matrix_rec_node_id, &node_status_node_id, &node_status) != 3) { cube_set_node_status(cube, nodes.node_count, remote_node_id, matrix_rec_node_id, node_status_node_id, -2); *error_code = ERR_INTERNAL; } else { cube_set_node_status(cube, nodes.node_count, remote_node_id, matrix_rec_node_id, node_status_node_id, node_status); } while (*p && (*p != '\n')) p++; if (*p == '\n') p++; } termPQExpBuffer(&command_output); i++; } *dest_cube = cube; node_count = nodes.node_count; clear_node_info_list(&nodes); return node_count; } static void cube_set_node_status(t_node_status_cube **cube, int n, int execute_node_id, int matrix_node_id, int connection_node_id, int connection_status) { int h, i, j; for (h = 0; h < n; h++) { if (cube[h]->node_id == execute_node_id) { for (i = 0; i < n; i++) { if (cube[h]->matrix_list_rec[i]->node_id == matrix_node_id) { for (j = 0; j < n; j++) { if (cube[h]->matrix_list_rec[i]->node_status_list[j]->node_id == connection_node_id) { cube[h]->matrix_list_rec[i]->node_status_list[j]->node_status = connection_status; break; } } break; } } } } } void do_cluster_cleanup(void) { PGconn *conn = NULL; PGconn *primary_conn = NULL; int entries_to_delete = 0; PQExpBufferData event_details; conn = establish_db_connection(config_file_options.conninfo, true); /* check if there is a primary in this cluster */ log_info(_("connecting to primary server")); primary_conn = establish_primary_db_connection(conn, true); PQfinish(conn); log_debug(_("number of days of monitoring history to retain: %i"), runtime_options.keep_history); entries_to_delete = get_number_of_monitoring_records_to_delete(primary_conn, runtime_options.keep_history, runtime_options.node_id); if (entries_to_delete < 0) { log_error(_("unable to query number of monitoring records to clean up")); PQfinish(primary_conn); exit(ERR_DB_QUERY); } else if (entries_to_delete == 0) { log_info(_("no monitoring records to delete")); PQfinish(primary_conn); return; } log_debug("at least %i monitoring records for deletion", entries_to_delete); initPQExpBuffer(&event_details); if (delete_monitoring_records(primary_conn, runtime_options.keep_history, runtime_options.node_id) == false) { appendPQExpBufferStr(&event_details, _("unable to delete monitoring records")); log_error("%s", event_details.data); log_detail("%s", PQerrorMessage(primary_conn)); create_event_notification(primary_conn, &config_file_options, config_file_options.node_id, "cluster_cleanup", false, event_details.data); PQfinish(primary_conn); exit(ERR_DB_QUERY); } if (vacuum_table(primary_conn, "repmgr.monitoring_history") == false) { /* annoying if this fails, but not fatal */ log_warning(_("unable to vacuum table \"repmgr.monitoring_history\"")); log_detail("%s", PQerrorMessage(primary_conn)); } else { log_info(_("vacuum of table \"repmgr.monitoring_history\" completed")); } if (runtime_options.keep_history == 0) { appendPQExpBufferStr(&event_details, _("all monitoring records deleted")); } else { appendPQExpBufferStr(&event_details, _("monitoring records deleted")); } if (runtime_options.node_id != UNKNOWN_NODE_ID) appendPQExpBuffer(&event_details, _(" for node %i"), runtime_options.node_id); if (runtime_options.keep_history > 0) appendPQExpBuffer(&event_details, _("; records newer than %i day(s) retained"), runtime_options.keep_history); create_event_notification(primary_conn, &config_file_options, config_file_options.node_id, "cluster_cleanup", true, event_details.data); log_notice("%s", event_details.data); termPQExpBuffer(&event_details); PQfinish(primary_conn); return; } void do_cluster_help(void) { print_help_header(); printf(_("Usage:\n")); printf(_(" %s [OPTIONS] cluster show\n"), progname()); printf(_(" %s [OPTIONS] cluster matrix\n"), progname()); printf(_(" %s [OPTIONS] cluster crosscheck\n"), progname()); printf(_(" %s [OPTIONS] cluster event\n"), progname()); printf(_(" %s [OPTIONS] cluster cleanup\n"), progname()); puts(""); printf(_("CLUSTER SHOW\n")); puts(""); printf(_(" \"cluster show\" displays a list showing the status of each node in the cluster.\n")); puts(""); printf(_(" Configuration file or database connection required.\n")); puts(""); printf(_(" --csv emit output as CSV (with a subset of fields)\n")); printf(_(" --compact display only a subset of fields\n")); puts(""); printf(_("CLUSTER MATRIX\n")); puts(""); printf(_(" \"cluster matrix\" displays a matrix showing connectivity between nodes, seen from this node.\n")); puts(""); printf(_(" Configuration file or database connection required.\n")); puts(""); printf(_(" --csv emit output as CSV\n")); puts(""); printf(_("CLUSTER CROSSCHECK\n")); puts(""); printf(_(" \"cluster crosscheck\" displays a matrix showing connectivity between nodes, seen from all nodes.\n")); puts(""); printf(_(" Configuration file or database connection required.\n")); puts(""); printf(_(" --csv emit output as CSV\n")); puts(""); printf(_("CLUSTER EVENT\n")); puts(""); printf(_(" \"cluster event\" lists recent events logged in the \"repmgr.events\" table.\n")); puts(""); printf(_(" --limit maximum number of events to display (default: %i)\n"), CLUSTER_EVENT_LIMIT); printf(_(" --all display all events (overrides --limit)\n")); printf(_(" --event filter specific event\n")); printf(_(" --node-id restrict entries to node with this ID\n")); printf(_(" --node-name restrict entries to node with this name\n")); printf(_(" --compact omit \"Details\" column")); printf(_(" --csv emit output as CSV\n")); puts(""); printf(_("CLUSTER CLEANUP\n")); puts(""); printf(_(" \"cluster cleanup\" purges records from the \"repmgr.monitoring_history\" table.\n")); puts(""); printf(_(" -k, --keep-history=VALUE retain indicated number of days of history (default: 0)\n")); puts(""); printf(_("%s home page: <%s>\n"), "repmgr", REPMGR_URL); } repmgr-5.3.1/repmgr-action-cluster.h000066400000000000000000000025261420262710000174120ustar00rootroot00000000000000/* * repmgr-action-cluster.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _REPMGR_ACTION_CLUSTER_H_ #define _REPMGR_ACTION_CLUSTER_H_ typedef struct { int node_id; int node_status; } t_node_status_rec; typedef struct { int node_id; char node_name[NAMEDATALEN]; t_node_status_rec **node_status_list; } t_node_matrix_rec; typedef struct { int node_id; char node_name[NAMEDATALEN]; t_node_matrix_rec **matrix_list_rec; } t_node_status_cube; extern void do_cluster_show(void); extern void do_cluster_event(void); extern void do_cluster_crosscheck(void); extern void do_cluster_matrix(void); extern void do_cluster_cleanup(void); extern void do_cluster_help(void); #endif repmgr-5.3.1/repmgr-action-daemon.c000066400000000000000000000212631420262710000171660ustar00rootroot00000000000000/* * repmgr-action-daemon.c * * Implements repmgrd actions for the repmgr command line utility * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #include #include /* for stat() */ #include "repmgr.h" #include "repmgr-client-global.h" #include "repmgr-action-daemon.h" #define REPMGR_SERVICE_STOP_START_WAIT 15 #define REPMGR_SERVICE_STATUS_START_HINT _("use \"repmgr service status\" to confirm that repmgrd was successfully started") #define REPMGR_SERVICE_STATUS_STOP_HINT _("use \"repmgr service status\" to confirm that repmgrd was successfully stopped") void do_daemon_start(void) { PGconn *conn = NULL; PQExpBufferData repmgrd_command; PQExpBufferData output_buf; bool success; if (config_file_options.repmgrd_service_start_command[0] == '\0') { log_error(_("\"repmgrd_service_start_command\" is not set")); log_hint(_("set \"repmgrd_service_start_command\" in \"repmgr.conf\"")); exit(ERR_BAD_CONFIG); } log_verbose(LOG_INFO, _("connecting to local node")); conn = establish_db_connection(config_file_options.conninfo, false); if (PQstatus(conn) != CONNECTION_OK) { /* TODO: if PostgreSQL is not available, have repmgrd loop and retry connection */ log_error(_("unable to connect to local node")); log_detail(_("PostgreSQL must be running before \"repmgrd\" can be started")); exit(ERR_DB_CONN); } /* * if local connection available, check if repmgr.so is installed, and * whether repmgrd is running */ check_shared_library(conn); if (is_repmgrd_running(conn) == true) { pid_t pid = UNKNOWN_PID; log_error(_("repmgrd appears to be running already")); pid = repmgrd_get_pid(conn); if (pid != UNKNOWN_PID) log_detail(_("repmgrd PID is %i"), pid); else log_warning(_("unable to determine repmgrd PID")); PQfinish(conn); exit(ERR_REPMGRD_SERVICE); } PQfinish(conn); initPQExpBuffer(&repmgrd_command); appendPQExpBufferStr(&repmgrd_command, config_file_options.repmgrd_service_start_command); if (runtime_options.dry_run == true) { log_info(_("prerequisites for starting repmgrd met")); log_detail("following command would be executed:\n %s", repmgrd_command.data); exit(SUCCESS); } log_notice(_("executing: \"%s\""), repmgrd_command.data); initPQExpBuffer(&output_buf); success = local_command(repmgrd_command.data, &output_buf); termPQExpBuffer(&repmgrd_command); if (success == false) { log_error(_("unable to start repmgrd")); if (output_buf.data[0] != '\0') log_detail("%s", output_buf.data); termPQExpBuffer(&output_buf); exit(ERR_REPMGRD_SERVICE); } termPQExpBuffer(&output_buf); if (runtime_options.no_wait == true || runtime_options.wait == 0) { log_hint(REPMGR_SERVICE_STATUS_START_HINT); } else { int i = 0; int timeout = REPMGR_SERVICE_STOP_START_WAIT; if (runtime_options.wait_provided) timeout = runtime_options.wait; conn = establish_db_connection(config_file_options.conninfo, false); if (PQstatus(conn) != CONNECTION_OK) { log_notice(_("unable to connect to local node")); log_hint(REPMGR_SERVICE_STATUS_START_HINT); exit(ERR_DB_CONN); } for (;;) { if (is_repmgrd_running(conn) == true) { log_notice(_("repmgrd was successfully started")); PQfinish(conn); break; } if (i == timeout) { PQfinish(conn); log_error(_("repmgrd does not appear to have started after %i seconds"), timeout); log_hint(REPMGR_SERVICE_STATUS_START_HINT); exit(ERR_REPMGRD_SERVICE); } log_debug("sleeping 1 second; %i of %i attempts to determine if repmgrd is running", i, runtime_options.wait); sleep(1); i++; } } } void do_daemon_stop(void) { PGconn *conn = NULL; PQExpBufferData repmgrd_command; PQExpBufferData output_buf; bool success; bool have_db_connection = true; pid_t pid = UNKNOWN_PID; if (config_file_options.repmgrd_service_stop_command[0] == '\0') { log_error(_("\"repmgrd_service_stop_command\" is not set")); log_hint(_("set \"repmgrd_service_stop_command\" in \"repmgr.conf\"")); exit(ERR_BAD_CONFIG); } /* * if local connection available, check if repmgr.so is installed, and * whether repmgrd is running */ log_verbose(LOG_INFO, _("connecting to local node")); conn = establish_db_connection(config_file_options.conninfo, false); if (PQstatus(conn) != CONNECTION_OK) { /* * a PostgreSQL connection is not required to stop repmgrd, */ log_warning(_("unable to connect to local node")); have_db_connection = false; } else { check_shared_library(conn); if (is_repmgrd_running(conn) == false) { log_error(_("repmgrd appears to be stopped already")); PQfinish(conn); exit(ERR_REPMGRD_SERVICE); } /* Attempt to fetch the PID, in case we need it later */ pid = repmgrd_get_pid(conn); log_debug("retrieved pid is %i", pid); } PQfinish(conn); initPQExpBuffer(&repmgrd_command); appendPQExpBufferStr(&repmgrd_command, config_file_options.repmgrd_service_stop_command); if (runtime_options.dry_run == true) { log_info(_("prerequisites for stopping repmgrd met")); log_detail("following command would be executed:\n %s", repmgrd_command.data); exit(SUCCESS); } log_notice(_("executing: \"%s\""), repmgrd_command.data); initPQExpBuffer(&output_buf); success = local_command(repmgrd_command.data, &output_buf); termPQExpBuffer(&repmgrd_command); if (success == false) { log_error(_("unable to stop repmgrd")); if (output_buf.data[0] != '\0') log_detail("%s", output_buf.data); termPQExpBuffer(&output_buf); exit(ERR_REPMGRD_SERVICE); } termPQExpBuffer(&output_buf); if (runtime_options.no_wait == true || runtime_options.wait == 0) { if (have_db_connection == true) log_hint(REPMGR_SERVICE_STATUS_STOP_HINT); } else { int i = 0; int timeout = REPMGR_SERVICE_STOP_START_WAIT; /* * */ if (pid == UNKNOWN_PID) { /* * XXX attempt to get pidfile from config * and get contents * ( see check_and_create_pid_file() ) * if PID still unknown, exit here */ log_warning(_("unable to determine repmgrd PID")); if (have_db_connection == true) log_hint(REPMGR_SERVICE_STATUS_STOP_HINT); exit(ERR_REPMGRD_SERVICE); } if (runtime_options.wait_provided) timeout = runtime_options.wait; for (;;) { if (kill(pid, 0) == -1) { if (errno == ESRCH) { log_notice(_("repmgrd was successfully stopped")); exit(SUCCESS); } else { log_error(_("unable to determine status of process with PID %i"), pid); log_detail("%s", strerror(errno)); exit(ERR_REPMGRD_SERVICE); } } if (i == timeout) { log_error(_("repmgrd does not appear to have stopped after %i seconds"), timeout); if (have_db_connection == true) log_hint(REPMGR_SERVICE_STATUS_START_HINT); exit(ERR_REPMGRD_SERVICE); } log_debug("sleeping 1 second; %i of %i attempts to determine if repmgrd with PID %i is running", i, timeout, pid); sleep(1); i++; } } } void do_daemon_help(void) { print_help_header(); printf(_("Usage:\n")); printf(_(" %s [OPTIONS] daemon start\n"), progname()); printf(_(" %s [OPTIONS] daemon stop\n"), progname()); puts(""); printf(_("DAEMON START\n")); puts(""); printf(_(" \"daemon start\" attempts to start repmgrd on the local node\n")); puts(""); printf(_(" --dry-run check prerequisites but don't start repmgrd\n")); printf(_(" -w/--wait wait for repmgrd to start (default: %i seconds)\n"), REPMGR_SERVICE_STOP_START_WAIT); printf(_(" --no-wait don't wait for repmgrd to start\n")); puts(""); printf(_("DAEMON STOP\n")); puts(""); printf(_(" \"daemon stop\" attempts to stop repmgrd on the local node\n")); puts(""); printf(_(" --dry-run check prerequisites but don't stop repmgrd\n")); printf(_(" -w/--wait wait for repmgrd to stop (default: %i seconds)\n"), REPMGR_SERVICE_STOP_START_WAIT); printf(_(" --no-wait don't wait for repmgrd to stop\n")); puts(""); puts(""); printf(_("%s home page: <%s>\n"), "repmgr", REPMGR_URL); } repmgr-5.3.1/repmgr-action-daemon.h000066400000000000000000000016221420262710000171700ustar00rootroot00000000000000/* * repmgr-action-daemon.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _REPMGR_ACTION_DAEMON_H_ #define _REPMGR_ACTION_DAEMON_H_ extern void do_daemon_start(void); extern void do_daemon_stop(void); extern void do_daemon_help(void); #endif repmgr-5.3.1/repmgr-action-node.c000066400000000000000000002654051420262710000166600ustar00rootroot00000000000000/* * repmgr-action-node.c * * Implements actions available for any kind of node * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #include #include #include "repmgr.h" #include "controldata.h" #include "dirutil.h" #include "dbutils.h" #include "compat.h" #include "repmgr-client-global.h" #include "repmgr-action-node.h" #include "repmgr-action-standby.h" static bool copy_file(const char *src_file, const char *dest_file); static void format_archive_dir(PQExpBufferData *archive_dir); static t_server_action parse_server_action(const char *action); static const char *output_repmgrd_status(CheckStatus status); static void exit_optformat_error(const char *error, int errcode); static void _do_node_service_list_actions(t_server_action action); static void _do_node_status_is_shutdown_cleanly(void); static void _do_node_archive_config(void); static void _do_node_restore_config(void); static void do_node_check_replication_connection(void); static CheckStatus do_node_check_archive_ready(PGconn *conn, OutputMode mode, CheckStatusList *list_output); static CheckStatus do_node_check_downstream(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output); static CheckStatus do_node_check_upstream(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output); static CheckStatus do_node_check_replication_lag(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output); static CheckStatus do_node_check_role(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output); static CheckStatus do_node_check_slots(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output); static CheckStatus do_node_check_missing_slots(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output); static CheckStatus do_node_check_data_directory(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output); static CheckStatus do_node_check_repmgrd(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output); static CheckStatus do_node_check_replication_config_owner(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output); static CheckStatus do_node_check_db_connection(PGconn *conn, OutputMode mode); /* * NODE STATUS * * Can only be run on the local node, as it needs to be able to * read the data directory. * * Parameters: * --is-shutdown-cleanly (for internal use only) * --csv */ void do_node_status(void) { PGconn *conn = NULL; t_node_info node_info = T_NODE_INFO_INITIALIZER; char cluster_size[MAXLEN]; PQExpBufferData output; KeyValueList node_status = {NULL, NULL}; KeyValueListCell *cell = NULL; NodeInfoList missing_slots = T_NODE_INFO_LIST_INITIALIZER; ItemList warnings = {NULL, NULL}; RecoveryType recovery_type = RECTYPE_UNKNOWN; ReplInfo replication_info; t_recovery_conf recovery_conf = T_RECOVERY_CONF_INITIALIZER; char data_dir[MAXPGPATH] = ""; char server_version_str[MAXVERSIONSTR] = ""; /* * A database connection is *not* required for this check */ if (runtime_options.is_shutdown_cleanly == true) { return _do_node_status_is_shutdown_cleanly(); } init_replication_info(&replication_info); /* config file required, so we should have "conninfo" and "data_directory" */ conn = establish_db_connection(config_file_options.conninfo, true); strncpy(data_dir, config_file_options.data_directory, MAXPGPATH); (void)get_server_version(conn, server_version_str); /* check node exists */ if (get_node_record_with_upstream(conn, config_file_options.node_id, &node_info) != RECORD_FOUND) { log_error(_("no record found for node %i"), config_file_options.node_id); PQfinish(conn); exit(ERR_BAD_CONFIG); } if (get_cluster_size(conn, cluster_size) == false) strncpy(cluster_size, _("unknown"), MAXLEN); recovery_type = get_recovery_type(conn); get_node_replication_stats(conn, &node_info); key_value_list_set(&node_status, "PostgreSQL version", server_version_str); key_value_list_set(&node_status, "Total data size", cluster_size); key_value_list_set(&node_status, "Conninfo", node_info.conninfo); if (runtime_options.verbose == true) { uint64 local_system_identifier = get_system_identifier(config_file_options.data_directory); if (local_system_identifier == UNKNOWN_SYSTEM_IDENTIFIER) { key_value_list_set(&node_status, "System identifier", "unknown"); item_list_append_format(&warnings, _("unable to retrieve system identifier from pg_control")); } else { key_value_list_set_format(&node_status, "System identifier", "%lu", local_system_identifier); } } key_value_list_set(&node_status, "Role", get_node_type_string(node_info.type)); switch (node_info.type) { case PRIMARY: if (recovery_type == RECTYPE_STANDBY) { item_list_append(&warnings, _("- node is registered as primary but running as standby")); } break; case STANDBY: if (recovery_type == RECTYPE_PRIMARY) { item_list_append(&warnings, _("- node is registered as standby but running as primary")); } break; default: break; } if (guc_set(conn, "archive_mode", "=", "off")) { key_value_list_set(&node_status, "WAL archiving", "off"); key_value_list_set(&node_status, "Archive command", "(none)"); } else { /* "archive_mode" is not "off", i.e. one of "on", "always" */ bool enabled = true; PQExpBufferData archiving_status; char archive_command[MAXLEN] = ""; initPQExpBuffer(&archiving_status); /* * if the node is a standby, and "archive_mode" is "on", archiving will * actually be disabled. */ if (recovery_type == RECTYPE_STANDBY) { if (guc_set(conn, "archive_mode", "=", "on")) enabled = false; } if (enabled == true) { appendPQExpBufferStr(&archiving_status, "enabled"); } else { appendPQExpBufferStr(&archiving_status, "disabled"); } if (enabled == false && recovery_type == RECTYPE_STANDBY) { if (PQserverVersion(conn) >= 90500) { appendPQExpBufferStr(&archiving_status, " (on standbys \"archive_mode\" must be set to \"always\" to be effective)"); } else { appendPQExpBufferStr(&archiving_status, " (\"archive_mode\" has no effect on standbys)"); } } key_value_list_set(&node_status, "WAL archiving", archiving_status.data); termPQExpBuffer(&archiving_status); get_pg_setting(conn, "archive_command", archive_command); key_value_list_set(&node_status, "Archive command", archive_command); } { int ready_files; ready_files = get_ready_archive_files(conn, data_dir); if (ready_files == ARCHIVE_STATUS_DIR_ERROR) { item_list_append_format(&warnings, "- unable to check archive_status directory\n"); } else { if (runtime_options.output_mode == OM_CSV) { key_value_list_set_format(&node_status, "WALs pending archiving", "%i", ready_files); } else { key_value_list_set_format(&node_status, "WALs pending archiving", "%i pending files", ready_files); } } if (guc_set(conn, "archive_mode", "=", "off")) { key_value_list_set_output_mode(&node_status, "WALs pending archiving", OM_CSV); } } if (node_info.max_wal_senders >= 0) { /* In CSV mode, raw values supplied as well */ key_value_list_set_format(&node_status, "Replication connections", "%i (of maximal %i)", node_info.attached_wal_receivers, node_info.max_wal_senders); } else if (node_info.max_wal_senders == 0) { key_value_list_set_format(&node_status, "Replication connections", "disabled"); } /* check for attached nodes */ { NodeInfoList downstream_nodes = T_NODE_INFO_LIST_INITIALIZER; NodeInfoListCell *node_cell = NULL; ItemList missing_nodes = {NULL, NULL}; int missing_nodes_count = 0; int expected_nodes_count = 0; get_downstream_node_records(conn, config_file_options.node_id, &downstream_nodes); /* if a witness node is present, we'll need to remove this from the total */ expected_nodes_count = downstream_nodes.node_count; for (node_cell = downstream_nodes.head; node_cell; node_cell = node_cell->next) { /* skip witness server */ if (node_cell->node_info->type == WITNESS) { expected_nodes_count --; continue; } if (is_downstream_node_attached(conn, node_cell->node_info->node_name, NULL) != NODE_ATTACHED) { missing_nodes_count++; item_list_append_format(&missing_nodes, "%s (ID: %i)", node_cell->node_info->node_name, node_cell->node_info->node_id); } } if (missing_nodes_count) { ItemListCell *missing_cell = NULL; item_list_append_format(&warnings, _("- %i of %i downstream nodes not attached:"), missing_nodes_count, expected_nodes_count); for (missing_cell = missing_nodes.head; missing_cell; missing_cell = missing_cell->next) { item_list_append_format(&warnings, " - %s\n", missing_cell->string); } } } if (node_info.max_replication_slots == 0) { key_value_list_set(&node_status, "Replication slots", "disabled"); } else { PQExpBufferData slotinfo; /* * check for missing replication slots - we do this regardless of * what "max_replication_slots" is set to, in case the downstream * node was configured with "use_replication_slots=true" and is * expecting a replication slot to be available */ get_downstream_nodes_with_missing_slot(conn, config_file_options.node_id, &missing_slots); if (missing_slots.node_count > 0) { NodeInfoListCell *missing_slot_cell = NULL; item_list_append_format(&warnings, _("- replication slots missing for following %i node(s):"), missing_slots.node_count); for (missing_slot_cell = missing_slots.head; missing_slot_cell; missing_slot_cell = missing_slot_cell->next) { item_list_append_format(&warnings, _(" - %s (ID: %i, slot name: \"%s\")"), missing_slot_cell->node_info->node_name, missing_slot_cell->node_info->node_id, missing_slot_cell->node_info->slot_name); } } initPQExpBuffer(&slotinfo); appendPQExpBuffer(&slotinfo, "%i physical (of maximal %i; %i missing)", node_info.active_replication_slots + node_info.inactive_replication_slots, node_info.max_replication_slots, missing_slots.node_count); if (node_info.inactive_replication_slots > 0) { KeyValueList inactive_replication_slots = {NULL, NULL}; KeyValueListCell *cell = NULL; (void) get_inactive_replication_slots(conn, &inactive_replication_slots); appendPQExpBuffer(&slotinfo, "; %i inactive", node_info.inactive_replication_slots); item_list_append_format(&warnings, _("- node has %i inactive physical replication slots"), node_info.inactive_replication_slots); for (cell = inactive_replication_slots.head; cell; cell = cell->next) { item_list_append_format(&warnings, " - %s", cell->key); } key_value_list_free(&inactive_replication_slots); } key_value_list_set(&node_status, "Replication slots", slotinfo.data); termPQExpBuffer(&slotinfo); } if (node_info.type == STANDBY) { key_value_list_set_format(&node_status, "Upstream node", "%s (ID: %i)", node_info.upstream_node_name, node_info.upstream_node_id); get_replication_info(conn, node_info.type, &replication_info); key_value_list_set_format(&node_status, "Replication lag", "%i seconds", replication_info.replication_lag_time); key_value_list_set_format(&node_status, "Last received LSN", "%X/%X", format_lsn(replication_info.last_wal_receive_lsn)); key_value_list_set_format(&node_status, "Last replayed LSN", "%X/%X", format_lsn(replication_info.last_wal_replay_lsn)); } else { key_value_list_set(&node_status, "Upstream node", "(none)"); key_value_list_set_output_mode(&node_status, "Upstream node", OM_CSV); key_value_list_set(&node_status, "Replication lag", "n/a"); key_value_list_set(&node_status, "Last received LSN", "(none)"); key_value_list_set_output_mode(&node_status, "Last received LSN", OM_CSV); key_value_list_set(&node_status, "Last replayed LSN", "(none)"); key_value_list_set_output_mode(&node_status, "Last replayed LSN", OM_CSV); } parse_recovery_conf(data_dir, &recovery_conf); /* format output */ initPQExpBuffer(&output); if (runtime_options.output_mode == OM_CSV) { appendPQExpBuffer(&output, "\"Node name\",\"%s\"\n", node_info.node_name); appendPQExpBuffer(&output, "\"Node ID\",\"%i\"\n", node_info.node_id); for (cell = node_status.head; cell; cell = cell->next) { appendPQExpBuffer(&output, "\"%s\",\"%s\"\n", cell->key, cell->value); } /* we'll add the raw data as well */ appendPQExpBuffer(&output, "\"max_wal_senders\",%i\n", node_info.max_wal_senders); appendPQExpBuffer(&output, "\"occupied_wal_senders\",%i\n", node_info.attached_wal_receivers); appendPQExpBuffer(&output, "\"max_replication_slots\",%i\n", node_info.max_replication_slots); appendPQExpBuffer(&output, "\"active_replication_slots\",%i\n", node_info.active_replication_slots); /* output inactive slot information */ appendPQExpBuffer(&output, "\"inactive_replication_slots\",%i", node_info.inactive_replication_slots); if (node_info.inactive_replication_slots) { KeyValueList inactive_replication_slots = {NULL, NULL}; KeyValueListCell *cell = NULL; (void) get_inactive_replication_slots(conn, &inactive_replication_slots); for (cell = inactive_replication_slots.head; cell; cell = cell->next) { appendPQExpBuffer(&output, ",\"%s\"", cell->key); } key_value_list_free(&inactive_replication_slots); } /* output missing slot information */ appendPQExpBufferChar(&output, '\n'); appendPQExpBuffer(&output, "\"missing_replication_slots\",%i", missing_slots.node_count); if (missing_slots.node_count > 0) { NodeInfoListCell *missing_slot_cell = NULL; for (missing_slot_cell = missing_slots.head; missing_slot_cell; missing_slot_cell = missing_slot_cell->next) { appendPQExpBuffer(&output, ",\"%s\"", missing_slot_cell->node_info->slot_name); } } } else { appendPQExpBuffer(&output, "Node \"%s\":\n", node_info.node_name); for (cell = node_status.head; cell; cell = cell->next) { if (cell->output_mode == OM_NOT_SET) appendPQExpBuffer(&output, "\t%s: %s\n", cell->key, cell->value); } } puts(output.data); termPQExpBuffer(&output); if (warnings.head != NULL && runtime_options.terse == false && runtime_options.output_mode == OM_TEXT) { log_warning(_("following issue(s) were detected:")); print_item_list(&warnings); log_hint(_("execute \"repmgr node check\" for more details")); } clear_node_info_list(&missing_slots); key_value_list_free(&node_status); item_list_free(&warnings); PQfinish(conn); /* * If warnings were noted, even if they're not displayed (e.g. in --csv node), * that means something's not right so we need to emit a non-zero exit code. */ if (warnings.head != NULL) { exit(ERR_NODE_STATUS); } return; } /* * Returns information about the running state of the node. * For internal use during "standby switchover". * * Returns "longopt" output: * * --status=(RUNNING|SHUTDOWN|UNCLEAN_SHUTDOWN|UNKNOWN) * --last-checkpoint=... */ static void _do_node_status_is_shutdown_cleanly(void) { PGPing ping_status; PQExpBufferData output; DBState db_state; XLogRecPtr checkPoint = InvalidXLogRecPtr; NodeStatus node_status = NODE_STATUS_UNKNOWN; initPQExpBuffer(&output); appendPQExpBufferStr(&output, "--state="); /* sanity-check we're dealing with a PostgreSQL directory */ if (is_pg_dir(config_file_options.data_directory) == false) { appendPQExpBufferStr(&output, "UNKNOWN"); printf("%s\n", output.data); termPQExpBuffer(&output); return; } ping_status = PQping(config_file_options.conninfo); switch (ping_status) { case PQPING_OK: node_status = NODE_STATUS_UP; break; case PQPING_REJECT: node_status = NODE_STATUS_UP; break; case PQPING_NO_ATTEMPT: case PQPING_NO_RESPONSE: /* status not yet clear */ break; } /* check what pg_control says */ if (get_db_state(config_file_options.data_directory, &db_state) == false) { /* * Unable to retrieve the database state from pg_control */ node_status = NODE_STATUS_UNKNOWN; log_verbose(LOG_DEBUG, "unable to determine db state"); goto return_state; } log_verbose(LOG_DEBUG, "db state now: %s", describe_db_state(db_state)); if (db_state != DB_SHUTDOWNED && db_state != DB_SHUTDOWNED_IN_RECOVERY) { if (node_status != NODE_STATUS_UP) { node_status = NODE_STATUS_UNCLEAN_SHUTDOWN; } /* server is still responding but shutting down */ else if (db_state == DB_SHUTDOWNING) { node_status = NODE_STATUS_SHUTTING_DOWN; } } checkPoint = get_latest_checkpoint_location(config_file_options.data_directory); if (checkPoint == InvalidXLogRecPtr) { /* unable to read pg_control, don't know what's happening */ node_status = NODE_STATUS_UNKNOWN; } else if (node_status == NODE_STATUS_UNKNOWN) { /* * if still "UNKNOWN" at this point, then the node must be cleanly shut * down */ node_status = NODE_STATUS_DOWN; } return_state: log_verbose(LOG_DEBUG, "node status determined as: %s", print_node_status(node_status)); appendPQExpBuffer(&output, "%s", print_node_status(node_status)); if (node_status == NODE_STATUS_DOWN) { appendPQExpBuffer(&output, " --last-checkpoint-lsn=%X/%X", format_lsn(checkPoint)); } printf("%s\n", output.data); termPQExpBuffer(&output); return; } static void exit_optformat_error(const char *error, int errcode) { PQExpBufferData output; Assert(runtime_options.output_mode == OM_OPTFORMAT); initPQExpBuffer(&output); appendPQExpBuffer(&output, "--error=%s", error); printf("%s\n", output.data); termPQExpBuffer(&output); exit(errcode); } /* * Configuration file required */ void do_node_check(void) { PGconn *conn = NULL; PQExpBufferData output; t_node_info node_info = T_NODE_INFO_INITIALIZER; CheckStatus return_code; CheckStatusList status_list = {NULL, NULL}; CheckStatusListCell *cell = NULL; bool issue_detected = false; bool exit_on_connection_error = true; /* for internal use */ if (runtime_options.has_passfile == true) { return_code = has_passfile() ? 0 : 1; exit(return_code); } /* for use by "standby switchover" */ if (runtime_options.replication_connection == true) { do_node_check_replication_connection(); exit(SUCCESS); } if (runtime_options.db_connection == true) { exit_on_connection_error = false; } /* * If --optformat was provided, we'll assume this is a remote invocation * and instead of exiting with an error, we'll return an error string to * so the remote invoker will know what's happened. */ if (runtime_options.output_mode == OM_OPTFORMAT) { exit_on_connection_error = false; } if (config_file_options.conninfo[0] != '\0') { t_conninfo_param_list node_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; char *errmsg = NULL; bool parse_success = false; initialize_conninfo_params(&node_conninfo, false); parse_success = parse_conninfo_string(config_file_options.conninfo, &node_conninfo, &errmsg, false); if (parse_success == false) { if (runtime_options.output_mode == OM_OPTFORMAT) { exit_optformat_error("CONNINFO_PARSE", ERR_BAD_CONFIG); } log_error(_("unable to parse conninfo string \"%s\" for local node"), config_file_options.conninfo); log_detail("%s", errmsg); exit(ERR_BAD_CONFIG); } /* * If --superuser option provided, attempt to connect as the specified user */ if (runtime_options.superuser[0] != '\0') { conn = establish_db_connection_with_replacement_param( config_file_options.conninfo, "user", runtime_options.superuser, exit_on_connection_error); } else { conn = establish_db_connection_by_params(&node_conninfo, exit_on_connection_error); } } else { conn = establish_db_connection_by_params(&source_conninfo, exit_on_connection_error); } /* * --db-connection option provided */ if (runtime_options.db_connection == true) { return_code = do_node_check_db_connection(conn, runtime_options.output_mode); PQfinish(conn); exit(return_code); } /* * If we've reached here, and the connection is invalid, then --optformat was provided */ if (PQstatus(conn) != CONNECTION_OK) { exit_optformat_error("DB_CONNECTION", ERR_DB_CONN); } if (get_node_record(conn, config_file_options.node_id, &node_info) != RECORD_FOUND) { log_error(_("no record found for node %i"), config_file_options.node_id); PQfinish(conn); exit(ERR_BAD_CONFIG); } /* add replication statistics to node record */ get_node_replication_stats(conn, &node_info); /* * handle specific checks ====================== */ if (runtime_options.archive_ready == true) { return_code = do_node_check_archive_ready(conn, runtime_options.output_mode, NULL); PQfinish(conn); exit(return_code); } if (runtime_options.upstream == true) { return_code = do_node_check_upstream(conn, runtime_options.output_mode, &node_info, NULL); PQfinish(conn); exit(return_code); } if (runtime_options.downstream == true) { return_code = do_node_check_downstream(conn, runtime_options.output_mode, &node_info, NULL); PQfinish(conn); exit(return_code); } if (runtime_options.replication_lag == true) { return_code = do_node_check_replication_lag(conn, runtime_options.output_mode, &node_info, NULL); PQfinish(conn); exit(return_code); } if (runtime_options.role == true) { return_code = do_node_check_role(conn, runtime_options.output_mode, &node_info, NULL); PQfinish(conn); exit(return_code); } if (runtime_options.slots == true) { return_code = do_node_check_slots(conn, runtime_options.output_mode, &node_info, NULL); PQfinish(conn); exit(return_code); } if (runtime_options.missing_slots == true) { return_code = do_node_check_missing_slots(conn, runtime_options.output_mode, &node_info, NULL); PQfinish(conn); exit(return_code); } if (runtime_options.data_directory_config == true) { return_code = do_node_check_data_directory(conn, runtime_options.output_mode, &node_info, NULL); PQfinish(conn); exit(return_code); } if (runtime_options.repmgrd == true) { return_code = do_node_check_repmgrd(conn, runtime_options.output_mode, &node_info, NULL); PQfinish(conn); exit(return_code); } if (runtime_options.replication_config_owner == true) { return_code = do_node_check_replication_config_owner(conn, runtime_options.output_mode, &node_info, NULL); PQfinish(conn); exit(return_code); } if (runtime_options.output_mode == OM_NAGIOS) { log_error(_("--nagios can only be used with a specific check")); log_hint(_("execute \"repmgr node --help\" for details")); PQfinish(conn); exit(ERR_BAD_CONFIG); } /* output general overview */ initPQExpBuffer(&output); /* order functions are called is also output order */ if (do_node_check_role(conn, runtime_options.output_mode, &node_info, &status_list) != CHECK_STATUS_OK) issue_detected = true; if (do_node_check_replication_lag(conn, runtime_options.output_mode, &node_info, &status_list) != CHECK_STATUS_OK) issue_detected = true; if (do_node_check_archive_ready(conn, runtime_options.output_mode, &status_list) != CHECK_STATUS_OK) issue_detected = true; if (do_node_check_upstream(conn, runtime_options.output_mode, &node_info, &status_list) != CHECK_STATUS_OK) issue_detected = true; if (do_node_check_downstream(conn, runtime_options.output_mode, &node_info, &status_list) != CHECK_STATUS_OK) issue_detected = true; if (do_node_check_slots(conn, runtime_options.output_mode, &node_info, &status_list) != CHECK_STATUS_OK) issue_detected = true; if (do_node_check_missing_slots(conn, runtime_options.output_mode, &node_info, &status_list) != CHECK_STATUS_OK) issue_detected = true; if (do_node_check_data_directory(conn, runtime_options.output_mode, &node_info, &status_list) != CHECK_STATUS_OK) issue_detected = true; if (runtime_options.output_mode == OM_CSV) { appendPQExpBuffer(&output, "\"Node name\",\"%s\"\n", node_info.node_name); appendPQExpBuffer(&output, "\"Node ID\",\"%i\"\n", node_info.node_id); for (cell = status_list.head; cell; cell = cell->next) { appendPQExpBuffer(&output, "\"%s\",\"%s\"", cell->item, output_check_status(cell->status)); if (strlen(cell->details)) { appendPQExpBuffer(&output, ",\"%s\"", cell->details); } appendPQExpBufferChar(&output, '\n'); } } else { appendPQExpBuffer(&output, "Node \"%s\":\n", node_info.node_name); for (cell = status_list.head; cell; cell = cell->next) { appendPQExpBuffer(&output, "\t%s: %s", cell->item, output_check_status(cell->status)); if (strlen(cell->details)) { appendPQExpBuffer(&output, " (%s)", cell->details); } appendPQExpBufferChar(&output, '\n'); } } printf("%s", output.data); termPQExpBuffer(&output); check_status_list_free(&status_list); PQfinish(conn); if (issue_detected == true) { exit(ERR_NODE_STATUS); } } static void do_node_check_replication_connection(void) { PGconn *local_conn = NULL; PGconn *repl_conn = NULL; t_node_info node_record = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; PQExpBufferData output; initPQExpBuffer(&output); appendPQExpBufferStr(&output, "--connection="); if (runtime_options.remote_node_id == UNKNOWN_NODE_ID) { appendPQExpBufferStr(&output, "UNKNOWN"); printf("%s\n", output.data); termPQExpBuffer(&output); return; } /* retrieve remote node record from local database */ local_conn = establish_db_connection(config_file_options.conninfo, false); if (PQstatus(local_conn) != CONNECTION_OK) { appendPQExpBufferStr(&output, "CONNECTION_ERROR"); printf("%s\n", output.data); termPQExpBuffer(&output); return; } record_status = get_node_record(local_conn, runtime_options.remote_node_id, &node_record); PQfinish(local_conn); if (record_status != RECORD_FOUND) { appendPQExpBufferStr(&output, "UNKNOWN"); printf("%s\n", output.data); termPQExpBuffer(&output); return; } repl_conn = establish_replication_connection_from_conninfo(node_record.conninfo, node_record.repluser); if (PQstatus(repl_conn) != CONNECTION_OK) { appendPQExpBufferStr(&output, "BAD"); printf("%s\n", output.data); termPQExpBuffer(&output); return; } PQfinish(repl_conn); appendPQExpBufferStr(&output, "OK"); printf("%s\n", output.data); termPQExpBuffer(&output); return; } static CheckStatus do_node_check_archive_ready(PGconn *conn, OutputMode mode, CheckStatusList *list_output) { int ready_archive_files = 0; CheckStatus status = CHECK_STATUS_UNKNOWN; PQExpBufferData details; if (mode == OM_CSV && list_output == NULL) { log_error(_("--csv output not provided with --archive-ready option")); PQfinish(conn); exit(ERR_BAD_CONFIG); } initPQExpBuffer(&details); ready_archive_files = get_ready_archive_files(conn, config_file_options.data_directory); if (ready_archive_files > config_file_options.archive_ready_critical) { status = CHECK_STATUS_CRITICAL; switch (mode) { case OM_OPTFORMAT: appendPQExpBuffer(&details, "--files=%i --threshold=%i", ready_archive_files, config_file_options.archive_ready_critical); break; case OM_NAGIOS: appendPQExpBuffer(&details, "%i pending archive ready files | files=%i;%i;%i", ready_archive_files, ready_archive_files, config_file_options.archive_ready_warning, config_file_options.archive_ready_critical); break; case OM_TEXT: appendPQExpBuffer(&details, "%i pending archive ready files, critical threshold: %i", ready_archive_files, config_file_options.archive_ready_critical); break; default: break; } } else if (ready_archive_files > config_file_options.archive_ready_warning) { status = CHECK_STATUS_WARNING; switch (mode) { case OM_OPTFORMAT: appendPQExpBuffer(&details, "--files=%i --threshold=%i", ready_archive_files, config_file_options.archive_ready_warning); break; case OM_NAGIOS: appendPQExpBuffer(&details, "%i pending archive ready files | files=%i;%i;%i", ready_archive_files, ready_archive_files, config_file_options.archive_ready_warning, config_file_options.archive_ready_critical); break; case OM_TEXT: appendPQExpBuffer(&details, "%i pending archive ready files (threshold: %i)", ready_archive_files, config_file_options.archive_ready_warning); break; default: break; } } else if (ready_archive_files < 0) { status = CHECK_STATUS_UNKNOWN; switch (mode) { case OM_OPTFORMAT: break; case OM_NAGIOS: case OM_TEXT: appendPQExpBufferStr(&details, "unable to check archive_status directory"); break; default: break; } } else { status = CHECK_STATUS_OK; switch (mode) { case OM_OPTFORMAT: appendPQExpBuffer(&details, "--files=%i", ready_archive_files); break; case OM_NAGIOS: appendPQExpBuffer(&details, "%i pending archive ready files | files=%i;%i;%i", ready_archive_files, ready_archive_files, config_file_options.archive_ready_warning, config_file_options.archive_ready_critical); break; case OM_TEXT: appendPQExpBuffer(&details, "%i pending archive ready files", ready_archive_files); break; default: break; } } switch (mode) { case OM_OPTFORMAT: { printf("--status=%s %s\n", output_check_status(status), details.data); } break; case OM_NAGIOS: printf("REPMGR_ARCHIVE_READY %s: %s\n", output_check_status(status), details.data); break; case OM_CSV: case OM_TEXT: if (list_output != NULL) { check_status_list_set(list_output, "WAL archiving", status, details.data); } else { printf("%s (%s)\n", output_check_status(status), details.data); } default: break; } termPQExpBuffer(&details); return status; } static CheckStatus do_node_check_downstream(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output) { NodeInfoList downstream_nodes = T_NODE_INFO_LIST_INITIALIZER; NodeInfoListCell *cell = NULL; int missing_nodes_count = 0; int expected_nodes_count = 0; CheckStatus status = CHECK_STATUS_OK; ItemList missing_nodes = {NULL, NULL}; ItemList attached_nodes = {NULL, NULL}; PQExpBufferData details; if (mode == OM_CSV && list_output == NULL) { log_error(_("--csv output not provided with --downstream option")); PQfinish(conn); exit(ERR_BAD_CONFIG); } initPQExpBuffer(&details); get_downstream_node_records(conn, config_file_options.node_id, &downstream_nodes); /* if a witness node is present, we'll need to remove this from the total */ expected_nodes_count = downstream_nodes.node_count; for (cell = downstream_nodes.head; cell; cell = cell->next) { /* skip witness server */ if (cell->node_info->type == WITNESS) { expected_nodes_count --; continue; } if (is_downstream_node_attached(conn, cell->node_info->node_name, NULL) != NODE_ATTACHED) { missing_nodes_count++; item_list_append_format(&missing_nodes, "%s (ID: %i)", cell->node_info->node_name, cell->node_info->node_id); } else { item_list_append_format(&attached_nodes, "%s (ID: %i)", cell->node_info->node_name, cell->node_info->node_id); } } if (node_info->type == WITNESS) { /* witness is not connecting to any upstream */ appendPQExpBufferStr(&details, _("N/A - node is a witness")); } else if (missing_nodes_count == 0) { if (expected_nodes_count == 0) appendPQExpBufferStr(&details, "this node has no downstream nodes"); else appendPQExpBuffer(&details, "%i of %i downstream nodes attached", expected_nodes_count - missing_nodes_count, expected_nodes_count); } else { ItemListCell *missing_cell = NULL; bool first = true; status = CHECK_STATUS_CRITICAL; appendPQExpBuffer(&details, "%i of %i downstream nodes not attached", missing_nodes_count, expected_nodes_count); if (mode != OM_NAGIOS) { appendPQExpBufferStr(&details, "; missing: "); for (missing_cell = missing_nodes.head; missing_cell; missing_cell = missing_cell->next) { if (first == false) appendPQExpBufferStr(&details, ", "); else first = false; if (first == false) appendPQExpBufferStr(&details, missing_cell->string); } } } switch (mode) { case OM_NAGIOS: { printf("REPMGR_DOWNSTREAM_SERVERS %s: %s | ", output_check_status(status), details.data); if (missing_nodes_count) { ItemListCell *missing_cell = NULL; bool first = true; printf("missing: "); for (missing_cell = missing_nodes.head; missing_cell; missing_cell = missing_cell->next) { if (first == false) printf(", "); else first = false; if (first == false) printf("%s", missing_cell->string); } } if (expected_nodes_count - missing_nodes_count) { ItemListCell *attached_cell = NULL; bool first = true; if (missing_nodes_count) printf("; "); printf("attached: "); for (attached_cell = attached_nodes.head; attached_cell; attached_cell = attached_cell->next) { if (first == false) printf(", "); else first = false; if (first == false) printf("%s", attached_cell->string); } } printf("\n"); } break; case OM_CSV: case OM_TEXT: if (list_output != NULL) { check_status_list_set(list_output, "Downstream servers", status, details.data); } else { printf("%s (%s)\n", output_check_status(status), details.data); } default: break; } termPQExpBuffer(&details); clear_node_info_list(&downstream_nodes); return status; } static CheckStatus do_node_check_upstream(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output) { PGconn *upstream_conn = NULL; t_node_info upstream_node_info = T_NODE_INFO_INITIALIZER; PQExpBufferData details; CheckStatus status = CHECK_STATUS_OK; if (mode == OM_CSV && list_output == NULL) { log_error(_("--csv output not provided with --upstream option")); PQfinish(conn); exit(ERR_BAD_CONFIG); } initPQExpBuffer(&details); if (node_info->type == WITNESS) { /* witness is not connecting to any upstream */ appendPQExpBufferStr(&details, _("N/A - node is a witness")); } else if (get_node_record(conn, node_info->upstream_node_id, &upstream_node_info) != RECORD_FOUND) { if (get_recovery_type(conn) == RECTYPE_STANDBY) { appendPQExpBuffer(&details, _("node \"%s\" (ID: %i) is a standby but no upstream record found"), node_info->node_name, node_info->node_id); status = CHECK_STATUS_CRITICAL; } else { appendPQExpBufferStr(&details, _("N/A - node is primary")); } } else { upstream_conn = establish_db_connection(upstream_node_info.conninfo, true); /* check our node is connected */ if (is_downstream_node_attached(upstream_conn, config_file_options.node_name, NULL) != NODE_ATTACHED) { appendPQExpBuffer(&details, _("node \"%s\" (ID: %i) is not attached to expected upstream node \"%s\" (ID: %i)"), node_info->node_name, node_info->node_id, upstream_node_info.node_name, upstream_node_info.node_id); status = CHECK_STATUS_CRITICAL; } else { appendPQExpBuffer(&details, _("node \"%s\" (ID: %i) is attached to expected upstream node \"%s\" (ID: %i)"), node_info->node_name, node_info->node_id, upstream_node_info.node_name, upstream_node_info.node_id); } } switch (mode) { case OM_NAGIOS: { printf("REPMGR_UPSTREAM_SERVER %s: %s | ", output_check_status(status), details.data); } break; case OM_TEXT: if (list_output != NULL) { check_status_list_set(list_output, "Upstream connection", status, details.data); } else { printf("%s (%s)\n", output_check_status(status), details.data); } default: break; } termPQExpBuffer(&details); return status; } static CheckStatus do_node_check_replication_lag(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output) { CheckStatus status = CHECK_STATUS_OK; int lag_seconds = 0; PQExpBufferData details; if (mode == OM_CSV && list_output == NULL) { log_error(_("--csv output not provided with --replication-lag option")); PQfinish(conn); exit(ERR_BAD_CONFIG); } initPQExpBuffer(&details); if (node_info->recovery_type == RECTYPE_PRIMARY) { switch (mode) { case OM_OPTFORMAT: appendPQExpBufferStr(&details, "--lag=0"); break; case OM_NAGIOS: appendPQExpBuffer(&details, "0 seconds | lag=0;%i;%i", config_file_options.replication_lag_warning, config_file_options.replication_lag_critical); break; case OM_TEXT: if (node_info->type == WITNESS) { appendPQExpBufferStr(&details, "N/A - node is witness"); } else { appendPQExpBufferStr(&details, "N/A - node is primary"); } break; default: break; } } else { lag_seconds = get_replication_lag_seconds(conn); log_debug("lag seconds: %i", lag_seconds); if (lag_seconds >= config_file_options.replication_lag_critical) { status = CHECK_STATUS_CRITICAL; switch (mode) { case OM_OPTFORMAT: appendPQExpBuffer(&details, "--lag=%i --threshold=%i", lag_seconds, config_file_options.replication_lag_critical); break; case OM_NAGIOS: appendPQExpBuffer(&details, "%i seconds | lag=%i;%i;%i", lag_seconds, lag_seconds, config_file_options.replication_lag_warning, config_file_options.replication_lag_critical); break; case OM_TEXT: appendPQExpBuffer(&details, "%i seconds, critical threshold: %i)", lag_seconds, config_file_options.replication_lag_critical); break; default: break; } } else if (lag_seconds > config_file_options.replication_lag_warning) { status = CHECK_STATUS_WARNING; switch (mode) { case OM_OPTFORMAT: appendPQExpBuffer(&details, "--lag=%i --threshold=%i", lag_seconds, config_file_options.replication_lag_warning); break; case OM_NAGIOS: appendPQExpBuffer(&details, "%i seconds | lag=%i;%i;%i", lag_seconds, lag_seconds, config_file_options.replication_lag_warning, config_file_options.replication_lag_critical); break; case OM_TEXT: appendPQExpBuffer(&details, "%i seconds, warning threshold: %i)", lag_seconds, config_file_options.replication_lag_warning); break; default: break; } } else if (lag_seconds == UNKNOWN_REPLICATION_LAG) { status = CHECK_STATUS_UNKNOWN; switch (mode) { case OM_OPTFORMAT: break; case OM_NAGIOS: case OM_TEXT: appendPQExpBufferStr(&details, "unable to query replication lag"); break; default: break; } } else { status = CHECK_STATUS_OK; switch (mode) { case OM_OPTFORMAT: appendPQExpBuffer(&details, "--lag=%i", lag_seconds); break; case OM_NAGIOS: appendPQExpBuffer(&details, "%i seconds | lag=%i;%i;%i", lag_seconds, lag_seconds, config_file_options.replication_lag_warning, config_file_options.replication_lag_critical); break; case OM_TEXT: appendPQExpBuffer(&details, "%i seconds", lag_seconds); break; default: break; } } } switch (mode) { case OM_OPTFORMAT: printf("--status=%s %s\n", output_check_status(status), details.data); break; case OM_NAGIOS: printf("REPMGR_REPLICATION_LAG %s: %s\n", output_check_status(status), details.data); break; case OM_CSV: case OM_TEXT: if (list_output != NULL) { check_status_list_set(list_output, "Replication lag", status, details.data); } else { printf("%s (%s)\n", output_check_status(status), details.data); } default: break; } termPQExpBuffer(&details); return status; } static CheckStatus do_node_check_role(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output) { CheckStatus status = CHECK_STATUS_OK; PQExpBufferData details; RecoveryType recovery_type = get_recovery_type(conn); if (mode == OM_CSV && list_output == NULL) { log_error(_("--csv output not provided with --role option")); PQfinish(conn); exit(ERR_BAD_CONFIG); } initPQExpBuffer(&details); switch (node_info->type) { case PRIMARY: if (recovery_type == RECTYPE_STANDBY) { status = CHECK_STATUS_CRITICAL; appendPQExpBufferStr(&details, _("node is registered as primary but running as standby")); } else { appendPQExpBufferStr(&details, _("node is primary")); } break; case STANDBY: if (recovery_type == RECTYPE_PRIMARY) { status = CHECK_STATUS_CRITICAL; appendPQExpBufferStr(&details, _("node is registered as standby but running as primary")); } else { appendPQExpBufferStr(&details, _("node is standby")); } break; case WITNESS: if (recovery_type == RECTYPE_STANDBY) { status = CHECK_STATUS_CRITICAL; appendPQExpBufferStr(&details, _("node is registered as witness but running as standby")); } else { appendPQExpBufferStr(&details, _("node is witness")); } break; default: break; } switch (mode) { case OM_NAGIOS: printf("REPMGR_SERVER_ROLE %s: %s\n", output_check_status(status), details.data); break; case OM_CSV: case OM_TEXT: if (list_output != NULL) { check_status_list_set(list_output, "Server role", status, details.data); } else { printf("%s (%s)\n", output_check_status(status), details.data); } default: break; } termPQExpBuffer(&details); return status; } static CheckStatus do_node_check_slots(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output) { CheckStatus status = CHECK_STATUS_OK; PQExpBufferData details; if (mode == OM_CSV && list_output == NULL) { log_error(_("--csv output not provided with --slots option")); PQfinish(conn); exit(ERR_BAD_CONFIG); } initPQExpBuffer(&details); if (node_info->total_replication_slots == 0) { appendPQExpBufferStr(&details, _("node has no physical replication slots")); } else if (node_info->inactive_replication_slots == 0) { appendPQExpBuffer(&details, _("%i of %i physical replication slots are active"), node_info->total_replication_slots, node_info->total_replication_slots); } else if (node_info->inactive_replication_slots > 0) { status = CHECK_STATUS_CRITICAL; appendPQExpBuffer(&details, _("%i of %i physical replication slots are inactive"), node_info->inactive_replication_slots, node_info->total_replication_slots); } switch (mode) { case OM_NAGIOS: printf("REPMGR_INACTIVE_SLOTS %s: %s | slots=%i;%i\n", output_check_status(status), details.data, node_info->total_replication_slots, node_info->inactive_replication_slots); break; case OM_CSV: case OM_TEXT: if (list_output != NULL) { check_status_list_set(list_output, "Replication slots", status, details.data); } else { printf("%s (%s)\n", output_check_status(status), details.data); } default: break; } termPQExpBuffer(&details); return status; } static CheckStatus do_node_check_missing_slots(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output) { CheckStatus status = CHECK_STATUS_OK; PQExpBufferData details; NodeInfoList missing_slots = T_NODE_INFO_LIST_INITIALIZER; if (mode == OM_CSV && list_output == NULL) { log_error(_("--csv output not provided with --missing-slots option")); PQfinish(conn); exit(ERR_BAD_CONFIG); } initPQExpBuffer(&details); get_downstream_nodes_with_missing_slot(conn, config_file_options.node_id, &missing_slots); if (missing_slots.node_count == 0) { appendPQExpBufferStr(&details, _("node has no missing physical replication slots")); } else { NodeInfoListCell *missing_slot_cell = NULL; bool first_element = true; status = CHECK_STATUS_CRITICAL; appendPQExpBuffer(&details, _("%i physical replication slots are missing"), missing_slots.node_count); if (missing_slots.node_count) { appendPQExpBufferStr(&details, ": "); for (missing_slot_cell = missing_slots.head; missing_slot_cell; missing_slot_cell = missing_slot_cell->next) { if (first_element == true) { first_element = false; } else { appendPQExpBufferStr(&details, ", "); } appendPQExpBufferStr(&details, missing_slot_cell->node_info->slot_name); } } } switch (mode) { case OM_NAGIOS: { printf("REPMGR_MISSING_SLOTS %s: %s | missing_slots=%i", output_check_status(status), details.data, missing_slots.node_count); if (missing_slots.node_count) { NodeInfoListCell *missing_slot_cell = NULL; bool first_element = true; printf(";"); for (missing_slot_cell = missing_slots.head; missing_slot_cell; missing_slot_cell = missing_slot_cell->next) { if (first_element == true) { first_element = false; } else { printf(","); } printf("%s", missing_slot_cell->node_info->slot_name); } } printf("\n"); break; } case OM_CSV: case OM_TEXT: if (list_output != NULL) { check_status_list_set(list_output, "Missing physical replication slots", status, details.data); } else { printf("%s (%s)\n", output_check_status(status), details.data); } default: break; } clear_node_info_list(&missing_slots); termPQExpBuffer(&details); return status; } CheckStatus do_node_check_data_directory(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output) { CheckStatus status = CHECK_STATUS_OK; char actual_data_directory[MAXPGPATH] = ""; PQExpBufferData details; if (mode == OM_CSV && list_output == NULL) { log_error(_("--csv output not provided with --data-directory-config option")); PQfinish(conn); exit(ERR_BAD_CONFIG); } initPQExpBuffer(&details); /* * Check actual data directory matches that in repmgr.conf; note this requires * a superuser connection */ if (connection_has_pg_monitor_role(conn, "pg_read_all_settings") == true) { /* we expect to have a database connection */ if (get_pg_setting(conn, "data_directory", actual_data_directory) == false) { appendPQExpBuffer(&details, _("unable to determine current \"data_directory\"")); status = CHECK_STATUS_UNKNOWN; } if (strncmp(actual_data_directory, config_file_options.data_directory, MAXPGPATH) != 0) { if (mode != OM_NAGIOS) { appendPQExpBuffer(&details, _("configured \"data_directory\" is \"%s\"; "), config_file_options.data_directory); } appendPQExpBuffer(&details, "actual data directory is \"%s\"", actual_data_directory); status = CHECK_STATUS_CRITICAL; } else { appendPQExpBuffer(&details, _("configured \"data_directory\" is \"%s\""), config_file_options.data_directory); } } /* * If no superuser connection available, sanity-check that the configuration directory looks * like a PostgreSQL directory and hope it's the right one. */ else { if (mode == OM_TEXT) { log_info(_("connection is not a superuser connection, falling back to simple check")); if (PQserverVersion(conn) >= 100000) { log_hint(_("provide a superuser with -S/--superuser, or add the \"%s\" user to role \"pg_read_all_settings\" or \"pg_monitor\""), PQuser(conn)); } } if (is_pg_dir(config_file_options.data_directory) == false) { if (mode == OM_NAGIOS) { appendPQExpBufferStr(&details, _("configured \"data_directory\" is not a PostgreSQL data directory")); } else { appendPQExpBuffer(&details, _("configured \"data_directory\" \"%s\" is not a PostgreSQL data directory"), actual_data_directory); } status = CHECK_STATUS_CRITICAL; } else { appendPQExpBuffer(&details, _("configured \"data_directory\" is \"%s\""), config_file_options.data_directory); } } switch (mode) { case OM_OPTFORMAT: printf("--configured-data-directory=%s\n", output_check_status(status)); break; case OM_NAGIOS: printf("REPMGR_DATA_DIRECTORY %s: %s", output_check_status(status), config_file_options.data_directory); if (status == CHECK_STATUS_CRITICAL) { printf(" | %s", details.data); } puts(""); break; case OM_CSV: case OM_TEXT: if (list_output != NULL) { check_status_list_set(list_output, "Configured data directory", status, details.data); } else { printf("%s (%s)\n", output_check_status(status), details.data); } default: break; } termPQExpBuffer(&details); return status; } CheckStatus do_node_check_repmgrd(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output) { CheckStatus status = CHECK_STATUS_OK; if (mode == OM_CSV && list_output == NULL) { log_error(_("--csv output not provided with --repmgrd option")); PQfinish(conn); exit(ERR_BAD_CONFIG); } status = get_repmgrd_status(conn); switch (mode) { case OM_OPTFORMAT: printf("--repmgrd=%s\n", output_check_status(status)); break; case OM_NAGIOS: printf("REPMGRD %s: %s\n", output_check_status(status), output_repmgrd_status(status)); break; case OM_CSV: case OM_TEXT: if (list_output != NULL) { check_status_list_set(list_output, "repmgrd", status, output_repmgrd_status(status)); } else { printf("%s (%s)\n", output_check_status(status), output_repmgrd_status(status)); } default: break; } return status; } /* * This is not included in the general list output */ static CheckStatus do_node_check_replication_config_owner(PGconn *conn, OutputMode mode, t_node_info *node_info, CheckStatusList *list_output) { CheckStatus status = CHECK_STATUS_OK; PQExpBufferData errmsg; PQExpBufferData details; if (mode != OM_OPTFORMAT) { log_error(_("--replication-config-owner option can only be used with --optformat")); PQfinish(conn); exit(ERR_BAD_CONFIG); } initPQExpBuffer(&errmsg); initPQExpBuffer(&details); if (check_replication_config_owner(PQserverVersion(conn), config_file_options.data_directory, &errmsg, &details) == false) { status = CHECK_STATUS_CRITICAL; } printf("--replication-config-owner=%s\n", output_check_status(status)); return status; } /* * This is not included in the general list output */ static CheckStatus do_node_check_db_connection(PGconn *conn, OutputMode mode) { CheckStatus status = CHECK_STATUS_OK; PQExpBufferData details; if (mode == OM_CSV) { log_error(_("--csv output not provided with --db-connection option")); PQfinish(conn); exit(ERR_BAD_CONFIG); } /* This check is for configuration diagnostics only */ if (mode == OM_NAGIOS) { log_error(_("--nagios output not provided with --db-connection option")); PQfinish(conn); exit(ERR_BAD_CONFIG); } initPQExpBuffer(&details); if (PQstatus(conn) != CONNECTION_OK) { t_conninfo_param_list conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; int c; status = CHECK_STATUS_CRITICAL; initialize_conninfo_params(&conninfo, false); conn_to_param_list(conn, &conninfo); appendPQExpBufferStr(&details, "connection parameters used:"); for (c = 0; c < conninfo.size && conninfo.keywords[c] != NULL; c++) { if (conninfo.values[c] != NULL && conninfo.values[c][0] != '\0') { appendPQExpBuffer(&details, " %s=%s", conninfo.keywords[c], conninfo.values[c]); } } } if (mode == OM_OPTFORMAT) { printf("--db-connection=%s\n", output_check_status(status)); } else if (mode == OM_TEXT) { printf("%s (%s)\n", output_check_status(status), details.data); } termPQExpBuffer(&details); return status; } void do_node_service(void) { t_server_action action = ACTION_UNKNOWN; char data_dir[MAXPGPATH] = ""; char command[MAXLEN] = ""; PQExpBufferData output; action = parse_server_action(runtime_options.action); if (action == ACTION_UNKNOWN) { log_error(_("unknown value \"%s\" provided for parameter --action"), runtime_options.action); log_hint(_("valid values are \"start\", \"stop\", \"restart\", \"reload\" and \"promote\"")); exit(ERR_BAD_CONFIG); } if (runtime_options.list_actions == true) { return _do_node_service_list_actions(action); } if (data_dir_required_for_action(action)) { get_node_config_directory(data_dir); if (data_dir[0] == '\0') { log_error(_("unable to determine data directory for action")); exit(ERR_BAD_CONFIG); } } if ((action == ACTION_STOP || action == ACTION_RESTART) && runtime_options.checkpoint == true) { PGconn *conn = NULL; if (config_file_options.conninfo[0] != '\0') { /* * If --superuser option provided, attempt to connect as the specified user */ if (runtime_options.superuser[0] != '\0') { conn = establish_db_connection_with_replacement_param( config_file_options.conninfo, "user", runtime_options.superuser, true); } else { conn = establish_db_connection(config_file_options.conninfo, true); } } else { conn = establish_db_connection_by_params(&source_conninfo, true); } if (is_superuser_connection(conn, NULL) == false) { if (runtime_options.dry_run == true) { log_warning(_("a CHECKPOINT would be issued here but no superuser connection is available")); } else { log_warning(_("a superuser connection is required to issue a CHECKPOINT")); } log_hint(_("provide a superuser with -S/--superuser")); } else { if (runtime_options.dry_run == true) { log_info(_("a CHECKPOINT would be issued here")); } else { log_notice(_("issuing CHECKPOINT on node \"%s\" (ID: %i) "), config_file_options.node_name, config_file_options.node_id); checkpoint(conn); } } PQfinish(conn); } get_server_action(action, command, data_dir); if (runtime_options.dry_run == true) { log_info(_("would execute server command \"%s\""), command); return; } /* * log level is "DETAIL" here as this command is intended to be executed * by another repmgr process (e.g. during standby switchover); that repmgr * should emit a "NOTICE" about the intent of the command. */ log_detail(_("executing server command \"%s\""), command); initPQExpBuffer(&output); if (local_command(command, &output) == false) { termPQExpBuffer(&output); exit(ERR_LOCAL_COMMAND); } termPQExpBuffer(&output); } static void _do_node_service_list_actions(t_server_action action) { char command[MAXLEN] = ""; char data_dir[MAXPGPATH] = ""; bool data_dir_required = false; /* do we need to provide a data directory for any of the actions? */ if (data_dir_required_for_action(ACTION_START)) data_dir_required = true; if (data_dir_required_for_action(ACTION_STOP)) data_dir_required = true; if (data_dir_required_for_action(ACTION_RESTART)) data_dir_required = true; if (data_dir_required_for_action(ACTION_RELOAD)) data_dir_required = true; if (data_dir_required_for_action(ACTION_PROMOTE)) data_dir_required = true; if (data_dir_required == true) { get_node_config_directory(data_dir); } /* show command for specific action only */ if (action != ACTION_NONE) { get_server_action(action, command, data_dir); printf("%s\n", command); return; } puts(_("Following commands would be executed for each action:")); puts(""); get_server_action(ACTION_START, command, data_dir); printf(" start: \"%s\"\n", command); get_server_action(ACTION_STOP, command, data_dir); printf(" stop: \"%s\"\n", command); get_server_action(ACTION_RESTART, command, data_dir); printf(" restart: \"%s\"\n", command); get_server_action(ACTION_RELOAD, command, data_dir); printf(" reload: \"%s\"\n", command); get_server_action(ACTION_PROMOTE, command, data_dir); printf(" promote: \"%s\"\n", command); puts(""); } static t_server_action parse_server_action(const char *action_name) { if (action_name[0] == '\0') return ACTION_NONE; if (strcasecmp(action_name, "start") == 0) return ACTION_START; if (strcasecmp(action_name, "stop") == 0) return ACTION_STOP; if (strcasecmp(action_name, "restart") == 0) return ACTION_RESTART; if (strcasecmp(action_name, "reload") == 0) return ACTION_RELOAD; if (strcasecmp(action_name, "promote") == 0) return ACTION_PROMOTE; return ACTION_UNKNOWN; } /* * Rejoin a dormant (shut down) node to the replication cluster; this * is typically a former primary which needs to be demoted to a standby. * * Note that "repmgr node rejoin" is also executed by * "repmgr standby switchover" after promoting the new primary. * * Parameters: * --dry-run * --force-rewind[=VALUE] * --config-files * --config-archive-dir * -W/--no-wait */ void do_node_rejoin(void) { PGconn *upstream_conn = NULL; RecoveryType primary_recovery_type = RECTYPE_UNKNOWN; PGconn *primary_conn = NULL; DBState db_state; PGPing status; bool is_shutdown = true; int server_version_num = UNKNOWN_SERVER_VERSION_NUM; bool hide_standby_signal = false; PQExpBufferData command; PQExpBufferData command_output; PQExpBufferData follow_output; struct stat statbuf; t_node_info primary_node_record = T_NODE_INFO_INITIALIZER; t_node_info local_node_record = T_NODE_INFO_INITIALIZER; bool success = true; int follow_error_code = SUCCESS; /* check node is not actually running */ status = PQping(config_file_options.conninfo); switch (status) { case PQPING_NO_ATTEMPT: log_error(_("unable to determine status of server")); exit(ERR_BAD_CONFIG); case PQPING_OK: is_shutdown = false; break; case PQPING_REJECT: is_shutdown = false; break; case PQPING_NO_RESPONSE: /* status not yet clear */ break; } if (get_db_state(config_file_options.data_directory, &db_state) == false) { log_error(_("unable to determine database state from pg_control")); exit(ERR_BAD_CONFIG); } if (is_shutdown == false) { log_error(_("database is still running in state \"%s\""), describe_db_state(db_state)); log_hint(_("\"repmgr node rejoin\" cannot be executed on a running node")); exit(ERR_REJOIN_FAIL); } /* * Server version number required to determine whether pg_rewind will run * crash recovery (Pg 13 and later). */ server_version_num = get_pg_version(config_file_options.data_directory, NULL); if (server_version_num == UNKNOWN_SERVER_VERSION_NUM) { /* This is very unlikely to happen */ log_error(_("unable to determine database version")); exit(ERR_BAD_CONFIG); } log_verbose(LOG_DEBUG, "server version number is: %i", server_version_num); /* check if cleanly shut down */ if (db_state != DB_SHUTDOWNED && db_state != DB_SHUTDOWNED_IN_RECOVERY) { if (db_state == DB_SHUTDOWNING) { log_error(_("database is still shutting down")); } else if (server_version_num >= 130000 && runtime_options.force_rewind_used == true) { log_warning(_("database is not shut down cleanly")); log_detail(_("--force-rewind provided, pg_rewind will automatically perform recovery")); /* * If pg_rewind is executed, the first change it will make * is to start the server in single user mode, which will fail * in the presence of "standby.signal", so we'll "hide" it * (actually delete and recreate). */ hide_standby_signal = true; } else { /* * If the database was not shut down cleanly, it *might* rejoin correctly * after starting up and recovering, but better to ensure the database * can recover before trying anything else. */ log_error(_("database is not shut down cleanly")); if (server_version_num >= 130000) { log_hint(_("provide --force-rewind to run recovery")); } else { if (runtime_options.force_rewind_used == true) { log_detail(_("pg_rewind will not be able to run")); } log_hint(_("database should be restarted then shut down cleanly after crash recovery completes")); } exit(ERR_REJOIN_FAIL); } } /* check provided upstream connection */ upstream_conn = establish_db_connection_by_params(&source_conninfo, true); if (get_primary_node_record(upstream_conn, &primary_node_record) == false) { log_error(_("unable to retrieve primary node record")); log_hint(_("check the provided database connection string is for a \"repmgr\" database")); PQfinish(upstream_conn); exit(ERR_BAD_CONFIG); } /* * Emit a notice about the identity of the rejoin target */ log_notice(_("rejoin target is node \"%s\" (ID: %i)"), primary_node_record.node_name, primary_node_record.node_id); /* connect to registered primary and check it's not in recovery */ primary_conn = establish_db_connection(primary_node_record.conninfo, false); if (PQstatus(primary_conn) != CONNECTION_OK) { RecoveryType upstream_recovery_type = get_recovery_type(upstream_conn); log_error(_("unable to connect to current registered primary \"%s\" (ID: %i)"), primary_node_record.node_name, primary_node_record.node_id); log_detail(_("registered primary node conninfo is: \"%s\""), primary_node_record.conninfo); /* * Catch case where provided upstream is not in recovery, but is also * not registered as primary */ if (upstream_recovery_type == RECTYPE_PRIMARY) { log_warning(_("provided upstream connection string is for a server which is not in recovery, but not registered as primary")); log_hint(_("fix repmgr metadata configuration before continuing")); } PQfinish(upstream_conn); exit(ERR_BAD_CONFIG); } PQfinish(upstream_conn); primary_recovery_type = get_recovery_type(primary_conn); if (primary_recovery_type != RECTYPE_PRIMARY) { log_error(_("primary server is registered as node \"%s\" (ID: %i), but server is not a primary"), primary_node_record.node_name, primary_node_record.node_id); /* TODO: hint about checking cluster */ PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } /* * Fetch the local node record - we'll need this later, and it acts as an * additional sanity-check that the node is known to the primary. */ if (get_node_record(primary_conn, config_file_options.node_id, &local_node_record) != RECORD_FOUND) { log_error(_("unable to retrieve node record for the local node")); log_hint(_("check the local node is registered with the current primary \"%s\" (ID: %i)"), primary_node_record.node_name, primary_node_record.node_id); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } /* * Sanity-check replication slot availability */ if (config_file_options.use_replication_slots) { bool slots_available = check_replication_slots_available(primary_node_record.node_id, primary_conn); if (slots_available == false) { PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } } /* * sanity-check that it will actually be possible to stream from the new upstream */ { bool can_rejoin; TimeLineID tli = get_min_recovery_end_timeline(config_file_options.data_directory); XLogRecPtr min_recovery_location = get_min_recovery_location(config_file_options.data_directory); /* * It's possible this was a former primary, so the minRecoveryPoint* * fields may be empty. */ if (min_recovery_location == InvalidXLogRecPtr) min_recovery_location = get_latest_checkpoint_location(config_file_options.data_directory); if (tli == 0) tli = get_timeline(config_file_options.data_directory); can_rejoin = check_node_can_attach(tli, min_recovery_location, primary_conn, &primary_node_record, true); if (can_rejoin == false) { PQfinish(primary_conn); exit(ERR_REJOIN_FAIL); } } /* * --force-rewind specified - check prerequisites, and attempt to execute * (if --dry-run provided, just output the command which would be executed) */ if (runtime_options.force_rewind_used == true) { PQExpBufferData msg; PQExpBufferData filebuf; int ret; /* * Check that pg_rewind can be used */ initPQExpBuffer(&msg); if (can_use_pg_rewind(primary_conn, config_file_options.data_directory, &msg) == false) { log_error(_("--force-rewind specified but pg_rewind cannot be used")); log_detail("%s", msg.data); termPQExpBuffer(&msg); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } appendPQExpBufferStr(&msg, _("prerequisites for using pg_rewind are met")); if (runtime_options.dry_run == true) { log_info("%s", msg.data); } else { log_verbose(LOG_INFO, "%s", msg.data); } termPQExpBuffer(&msg); /* * Archive requested configuration files. * * In --dry-run mode this acts as a check that the files can be archived, though * errors will only be logged; any copied files will be deleted and --dry-run * execution will continue. */ _do_node_archive_config(); /* execute pg_rewind */ initPQExpBuffer(&command); if (runtime_options.force_rewind_path[0] != '\0') { appendPQExpBuffer(&command, "%s -D ", runtime_options.force_rewind_path); } else { make_pg_path(&command, "pg_rewind"); appendPQExpBufferStr(&command, " -D "); } appendShellString(&command, config_file_options.data_directory); appendPQExpBuffer(&command, " --source-server='%s'", primary_node_record.conninfo); if (runtime_options.dry_run == true) { log_info(_("pg_rewind would now be executed")); log_detail(_("pg_rewind command is:\n %s"), command.data); } else { log_notice(_("executing pg_rewind")); log_detail(_("pg_rewind command is \"%s\""), command.data); /* * In Pg13 and later, pg_rewind will attempt to start up a server which * was not cleanly shut down in single user mode. This will fail if * "standby.signal" is present. We'll remove it and restore it after * pg_rewind runs. */ if (hide_standby_signal == true) { char standby_signal_file_path[MAXPGPATH] = ""; log_notice(_("temporarily removing \"standby.signal\"")); log_detail(_("this is required so pg_rewind can fix the unclean shutdown")); make_standby_signal_path(config_file_options.data_directory, standby_signal_file_path); if (unlink(standby_signal_file_path) < 0 && errno != ENOENT) { log_error(_("unable to remove \"standby.signal\" file in data directory \"%s\""), standby_signal_file_path); log_detail("%s", strerror(errno)); exit(ERR_REJOIN_FAIL); } } initPQExpBuffer(&command_output); ret = local_command(command.data, &command_output); termPQExpBuffer(&command); if (hide_standby_signal == true) { /* * Restore standby.signal if we previously removed it, regardless * of whether the pg_rewind operation failed. */ log_notice(_("recreating \"standby.signal\"")); write_standby_signal(config_file_options.data_directory); } if (ret == false) { log_error(_("pg_rewind execution failed")); log_detail("%s", command_output.data); termPQExpBuffer(&command_output); exit(ERR_REJOIN_FAIL); } termPQExpBuffer(&command_output); /* Restore any previously archived config files */ _do_node_restore_config(); initPQExpBuffer(&filebuf); /* remove any recovery.done file copied in by pg_rewind */ appendPQExpBuffer(&filebuf, "%s/recovery.done", config_file_options.data_directory); if (stat(filebuf.data, &statbuf) == 0) { log_verbose(LOG_INFO, _("deleting \"recovery.done\"")); if (unlink(filebuf.data) == -1) { log_warning(_("unable to delete \"%s\""), filebuf.data); log_detail("%s", strerror(errno)); } } termPQExpBuffer(&filebuf); /* * Delete any replication slots copied in by pg_rewind. * * TODO: * - from PostgreSQL 11, this will be handled by pg_rewind, so * we can skip this step from that version; see commit * 266b6acb312fc440c1c1a2036aa9da94916beac6 * - possibly delete contents of various other directories * as per the above commit for pre-PostgreSQL 11 */ { PQExpBufferData slotdir_path; DIR *slotdir; struct dirent *slotdir_ent; initPQExpBuffer(&slotdir_path); appendPQExpBuffer(&slotdir_path, "%s/pg_replslot", config_file_options.data_directory); slotdir = opendir(slotdir_path.data); if (slotdir == NULL) { log_warning(_("unable to open replication slot directory \"%s\""), slotdir_path.data); log_detail("%s", strerror(errno)); } else { while ((slotdir_ent = readdir(slotdir)) != NULL) { struct stat statbuf; PQExpBufferData slotdir_ent_path; if (strcmp(slotdir_ent->d_name, ".") == 0 || strcmp(slotdir_ent->d_name, "..") == 0) continue; initPQExpBuffer(&slotdir_ent_path); appendPQExpBuffer(&slotdir_ent_path, "%s/%s", slotdir_path.data, slotdir_ent->d_name); if (stat(slotdir_ent_path.data, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode)) { termPQExpBuffer(&slotdir_ent_path); continue; } log_debug("deleting slot directory \"%s\"", slotdir_ent_path.data); if (rmdir_recursive(slotdir_ent_path.data) != 0 && errno != EEXIST) { log_warning(_("unable to delete replication slot directory \"%s\""), slotdir_ent_path.data); log_detail("%s", strerror(errno)); log_hint(_("directory may need to be manually removed")); } termPQExpBuffer(&slotdir_ent_path); } closedir(slotdir); } termPQExpBuffer(&slotdir_path); } } } if (runtime_options.dry_run == true) { log_info(_("prerequisites for executing NODE REJOIN are met")); exit(SUCCESS); } initPQExpBuffer(&follow_output); /* * do_standby_follow_internal() can handle situations where the follow * target is not the primary, so requires database handles to both * (even if they point to the same node). For the time being, * "node rejoin" will only attach a standby to the primary. */ success = do_standby_follow_internal(primary_conn, primary_conn, &primary_node_record, &follow_output, ERR_REJOIN_FAIL, &follow_error_code); if (success == false) { log_error(_("NODE REJOIN failed")); if (strlen(follow_output.data)) log_detail("%s", follow_output.data); create_event_notification(primary_conn, &config_file_options, config_file_options.node_id, "node_rejoin", success, follow_output.data); PQfinish(primary_conn); termPQExpBuffer(&follow_output); exit(follow_error_code); } /* * Actively check that node actually started and connected to primary, * if not exit with ERR_REJOIN_FAIL. * * This check can be overridden with -W/--no-wait, in which case a one-time * check will be carried out. */ if (runtime_options.no_wait == false) { standy_join_status join_success = check_standby_join(primary_conn, &primary_node_record, &local_node_record); create_event_notification(primary_conn, &config_file_options, config_file_options.node_id, "node_rejoin", join_success == JOIN_SUCCESS ? true : false, follow_output.data); if (join_success != JOIN_SUCCESS) { termPQExpBuffer(&follow_output); log_error(_("NODE REJOIN failed")); if (join_success == JOIN_FAIL_NO_PING) { log_detail(_("local node \"%s\" did not become available start after %i seconds"), config_file_options.node_name, config_file_options.node_rejoin_timeout); } else { log_detail(_("no active record for local node \"%s\" found in node \"%s\"'s \"pg_stat_replication\" table"), config_file_options.node_name, primary_node_record.node_name); } log_hint(_("check the PostgreSQL log on the local node")); exit(ERR_REJOIN_FAIL); } } else { /* -W/--no-wait provided - check once */ NodeAttached node_attached = is_downstream_node_attached(primary_conn, config_file_options.node_name, NULL); if (node_attached == NODE_ATTACHED) success = true; } /* * Handle replication slots: * - if a slot for the new upstream exists, delete that * - warn about any other inactive replication slots */ if (runtime_options.force_rewind_used == false && config_file_options.use_replication_slots) { PGconn *local_conn = NULL; local_conn = establish_db_connection(config_file_options.conninfo, false); if (PQstatus(local_conn) != CONNECTION_OK) { log_warning(_("unable to connect to local node to check replication slot status")); log_hint(_("execute \"repmgr node check\" to check inactive slots and drop manually if necessary")); } else { KeyValueList inactive_replication_slots = {NULL, NULL}; KeyValueListCell *cell = NULL; int inactive_count = 0; PQExpBufferData slotinfo; drop_replication_slot_if_exists(local_conn, config_file_options.node_id, primary_node_record.slot_name); (void) get_inactive_replication_slots(local_conn, &inactive_replication_slots); initPQExpBuffer(&slotinfo); for (cell = inactive_replication_slots.head; cell; cell = cell->next) { appendPQExpBuffer(&slotinfo, " - %s (%s)", cell->key, cell->value); inactive_count++; } if (inactive_count > 0) { log_warning(_("%i inactive replication slots detected"), inactive_count); log_detail(_("inactive replication slots:\n%s"), slotinfo.data); log_hint(_("these replication slots may need to be removed manually")); } termPQExpBuffer(&slotinfo); PQfinish(local_conn); } } if (success == true) { log_notice(_("NODE REJOIN successful")); log_detail("%s", follow_output.data); } else { /* * if we reach here, no record found in upstream node's pg_stat_replication */ log_notice(_("NODE REJOIN has completed but node is not yet reattached to upstream")); log_hint(_("you will need to manually check the node's replication status")); } termPQExpBuffer(&follow_output); return; } /* * Currently for testing purposes only, not documented; * use at own risk! */ void do_node_control(void) { PGconn *conn = NULL; pid_t wal_receiver_pid = UNKNOWN_PID; conn = establish_db_connection(config_file_options.conninfo, true); if (runtime_options.disable_wal_receiver == true) { wal_receiver_pid = disable_wal_receiver(conn); PQfinish(conn); if (wal_receiver_pid == UNKNOWN_PID) exit(ERR_BAD_CONFIG); exit(SUCCESS); } if (runtime_options.enable_wal_receiver == true) { wal_receiver_pid = enable_wal_receiver(conn, true); PQfinish(conn); if (wal_receiver_pid == UNKNOWN_PID) exit(ERR_BAD_CONFIG); exit(SUCCESS); } log_error(_("no option provided")); PQfinish(conn); } /* * For "internal" use by `node rejoin` on the local node when * called by "standby switchover" from the remote node. * * This archives any configuration files in the data directory, which may be * overwritten by pg_rewind. * * Requires configuration file, optionally --config-archive-dir */ static void _do_node_archive_config(void) { PQExpBufferData archive_dir; struct stat statbuf; struct dirent *arcdir_ent; DIR *arcdir; KeyValueList config_files = {NULL, NULL}; KeyValueListCell *cell = NULL; int copied_count = 0; initPQExpBuffer(&archive_dir); format_archive_dir(&archive_dir); /* sanity-check directory path */ if (stat(archive_dir.data, &statbuf) == -1) { if (errno != ENOENT) { log_error(_("error encountered when checking archive directory \"%s\""), archive_dir.data); log_detail("%s", strerror(errno)); termPQExpBuffer(&archive_dir); exit(ERR_BAD_CONFIG); } /* attempt to create and open the directory */ if (mkdir(archive_dir.data, S_IRWXU) != 0 && errno != EEXIST) { log_error(_("unable to create temporary archive directory \"%s\""), archive_dir.data); log_detail("%s", strerror(errno)); termPQExpBuffer(&archive_dir); exit(ERR_BAD_CONFIG); } if (runtime_options.dry_run == true) { log_verbose(LOG_INFO, "temporary archive directory \"%s\" created", archive_dir.data); } } else if (!S_ISDIR(statbuf.st_mode)) { log_error(_("\"%s\" exists but is not a directory"), archive_dir.data); termPQExpBuffer(&archive_dir); exit(ERR_BAD_CONFIG); } arcdir = opendir(archive_dir.data); /* always attempt to open the directory */ if (arcdir == NULL) { log_error(_("unable to open archive directory \"%s\""), archive_dir.data); log_detail("%s", strerror(errno)); termPQExpBuffer(&archive_dir); exit(ERR_BAD_CONFIG); } if (runtime_options.dry_run == false) { /* * attempt to remove any existing files in the directory * TODO: collate problem files into list */ while ((arcdir_ent = readdir(arcdir)) != NULL) { PQExpBufferData arcdir_ent_path; initPQExpBuffer(&arcdir_ent_path); appendPQExpBuffer(&arcdir_ent_path, "%s/%s", archive_dir.data, arcdir_ent->d_name); if (stat(arcdir_ent_path.data, &statbuf) == 0 && !S_ISREG(statbuf.st_mode)) { termPQExpBuffer(&arcdir_ent_path); continue; } if (unlink(arcdir_ent_path.data) == -1) { log_error(_("unable to delete file in temporary archive directory")); log_detail(_("file is: \"%s\""), arcdir_ent_path.data); log_detail("%s", strerror(errno)); closedir(arcdir); termPQExpBuffer(&arcdir_ent_path); exit(ERR_BAD_CONFIG); } termPQExpBuffer(&arcdir_ent_path); } } closedir(arcdir); /* * extract list of config files from --config-files */ { int i = 0; int j = 0; int config_file_len = strlen(runtime_options.config_files); char filenamebuf[MAXPGPATH] = ""; PQExpBufferData pathbuf; for (j = 0; j < config_file_len; j++) { if (runtime_options.config_files[j] == ',') { int filename_len = j - i; if (filename_len >= MAXPGPATH) filename_len = MAXPGPATH - 1; strncpy(filenamebuf, runtime_options.config_files + i, filename_len); filenamebuf[filename_len] = '\0'; initPQExpBuffer(&pathbuf); appendPQExpBuffer(&pathbuf, "%s/%s", config_file_options.data_directory, filenamebuf); key_value_list_set(&config_files, filenamebuf, pathbuf.data); termPQExpBuffer(&pathbuf); i = j + 1; } } if (i < config_file_len) { int filename_len = config_file_len - i; strncpy(filenamebuf, runtime_options.config_files + i, filename_len); filenamebuf[filename_len] = '\0'; initPQExpBuffer(&pathbuf); appendPQExpBuffer(&pathbuf, "%s/%s", config_file_options.data_directory, filenamebuf); key_value_list_set(&config_files, filenamebuf, pathbuf.data); termPQExpBuffer(&pathbuf); } } for (cell = config_files.head; cell; cell = cell->next) { PQExpBufferData dest_file; initPQExpBuffer(&dest_file); appendPQExpBuffer(&dest_file, "%s/%s", archive_dir.data, cell->key); if (stat(cell->value, &statbuf) == -1) { log_warning(_("specified file \"%s\" not found, skipping"), cell->value); } else { if (runtime_options.dry_run == true) { log_info("file \"%s\" would be copied to \"%s\"", cell->key, dest_file.data); copied_count++; } else { log_verbose(LOG_DEBUG, "copying \"%s\" to \"%s\"", cell->key, dest_file.data); copy_file(cell->value, dest_file.data); copied_count++; } } termPQExpBuffer(&dest_file); } if (runtime_options.dry_run == true) { log_verbose(LOG_INFO, _("%i files would have been copied to \"%s\""), copied_count, archive_dir.data); } else { log_verbose(LOG_INFO, _("%i files copied to \"%s\""), copied_count, archive_dir.data); } if (runtime_options.dry_run == true) { /* * Delete directory in --dry-run mode - it should be empty unless it's been * interfered with for some reason, in which case manual intervention is * required */ if (rmdir(archive_dir.data) != 0 && errno != EEXIST) { log_warning(_("unable to delete directory \"%s\""), archive_dir.data); log_detail("%s", strerror(errno)); log_hint(_("directory may need to be manually removed")); } else { log_verbose(LOG_INFO, "temporary archive directory \"%s\" deleted", archive_dir.data); } } termPQExpBuffer(&archive_dir); } /* * Intended mainly for "internal" use by `standby switchover`, which * calls this on the target server to restore any configuration files * to the data directory, which may have been overwritten by an operation * like pg_rewind * * Not designed to be called if the instance is running, but does * not currently check. * * Requires -D/--pgdata, optionally --config-archive-dir * * Removes --config-archive-dir after successful copy */ static void _do_node_restore_config(void) { PQExpBufferData archive_dir; DIR *arcdir; struct dirent *arcdir_ent; int copied_count = 0; bool copy_ok = true; initPQExpBuffer(&archive_dir); format_archive_dir(&archive_dir); arcdir = opendir(archive_dir.data); if (arcdir == NULL) { log_error(_("unable to open archive directory \"%s\""), archive_dir.data); log_detail("%s", strerror(errno)); termPQExpBuffer(&archive_dir); exit(ERR_BAD_CONFIG); } while ((arcdir_ent = readdir(arcdir)) != NULL) { struct stat statbuf; PQExpBufferData src_file_path; PQExpBufferData dest_file_path; initPQExpBuffer(&src_file_path); appendPQExpBuffer(&src_file_path, "%s/%s", archive_dir.data, arcdir_ent->d_name); /* skip non-files */ if (stat(src_file_path.data, &statbuf) == 0 && !S_ISREG(statbuf.st_mode)) { termPQExpBuffer(&src_file_path); continue; } initPQExpBuffer(&dest_file_path); appendPQExpBuffer(&dest_file_path, "%s/%s", config_file_options.data_directory, arcdir_ent->d_name); log_verbose(LOG_DEBUG, "copying \"%s\" to \"%s\"", src_file_path.data, dest_file_path.data); if (copy_file(src_file_path.data, dest_file_path.data) == false) { copy_ok = false; log_warning(_("unable to copy \"%s\" to \"%s\""), arcdir_ent->d_name, runtime_options.data_dir); } else { unlink(src_file_path.data); copied_count++; } termPQExpBuffer(&dest_file_path); termPQExpBuffer(&src_file_path); } closedir(arcdir); log_notice(_("%i files copied to %s"), copied_count, config_file_options.data_directory); if (copy_ok == false) { log_warning(_("unable to copy all files from \"%s\""), archive_dir.data); } else { /* * Finally, delete directory - it should be empty unless it's been * interfered with for some reason, in which case manual intervention is * required */ if (rmdir(archive_dir.data) != 0 && errno != EEXIST) { log_warning(_("unable to delete directory \"%s\""), archive_dir.data); log_detail("%s", strerror(errno)); log_hint(_("directory may need to be manually removed")); } else { log_verbose(LOG_INFO, "directory \"%s\" deleted", archive_dir.data); } } termPQExpBuffer(&archive_dir); return; } static void format_archive_dir(PQExpBufferData *archive_dir) { appendPQExpBuffer(archive_dir, "%s/repmgr-config-archive-%s", runtime_options.config_archive_dir, config_file_options.node_name); log_verbose(LOG_DEBUG, "using archive directory \"%s\"", archive_dir->data); } static bool copy_file(const char *src_file, const char *dest_file) { FILE *ptr_old, *ptr_new; int a = 0; ptr_old = fopen(src_file, "r"); if (ptr_old == NULL) return false; ptr_new = fopen(dest_file, "w"); if (ptr_new == NULL) { fclose(ptr_old); return false; } chmod(dest_file, S_IRUSR | S_IWUSR); while (1) { a = fgetc(ptr_old); if (!feof(ptr_old)) { fputc(a, ptr_new); } else { break; } } fclose(ptr_new); fclose(ptr_old); return true; } static const char * output_repmgrd_status(CheckStatus status) { switch (status) { case CHECK_STATUS_OK: return "repmgrd running"; case CHECK_STATUS_WARNING: return "repmgrd running but paused"; case CHECK_STATUS_CRITICAL: return "repmgrd not running"; case CHECK_STATUS_UNKNOWN: return "repmgrd status unknown"; } return "UNKNOWN"; } void do_node_help(void) { print_help_header(); printf(_("Usage:\n")); printf(_(" %s [OPTIONS] node status\n"), progname()); printf(_(" %s [OPTIONS] node check\n"), progname()); printf(_(" %s [OPTIONS] node rejoin\n"), progname()); printf(_(" %s [OPTIONS] node service\n"), progname()); puts(""); printf(_("NODE STATUS\n")); puts(""); printf(_(" \"node status\" displays an overview of a node's basic information and replication status.\n")); puts(""); printf(_(" Configuration file required, runs on local node only.\n")); puts(""); printf(_(" --csv emit output as CSV\n")); puts(""); printf(_("NODE CHECK\n")); puts(""); printf(_(" \"node check\" performs some health checks on a node from a replication perspective.\n")); puts(""); printf(_(" Configuration file required, runs on local node only.\n")); puts(""); printf(_(" Connection options:\n")); printf(_(" -S, --superuser=USERNAME superuser to use, if repmgr user is not superuser\n")); puts(""); printf(_(" Output options:\n")); printf(_(" --csv emit output as CSV (not available for individual check output)\n")); printf(_(" --nagios emit output in Nagios format (individual check output only)\n")); puts(""); printf(_(" Following options check an individual status:\n")); printf(_(" --archive-ready number of WAL files ready for archiving\n")); printf(_(" --downstream whether all downstream nodes are connected\n")); printf(_(" --upstream whether the node is connected to its upstream\n")); printf(_(" --replication-lag replication lag in seconds (standbys only)\n")); printf(_(" --role check node has expected role\n")); printf(_(" --slots check for inactive replication slots\n")); printf(_(" --missing-slots check for missing replication slots\n")); printf(_(" --repmgrd check if repmgrd is running\n")); printf(_(" --data-directory-config check repmgr's data directory configuration\n")); puts(""); printf(_("NODE REJOIN\n")); puts(""); printf(_(" \"node rejoin\" enables a dormant (stopped) node to be rejoined to the replication cluster.\n")); puts(""); printf(_(" Configuration file required, runs on local node only.\n")); puts(""); printf(_(" --dry-run check that the prerequisites are met for rejoining the node\n" \ " (including usability of \"pg_rewind\" if requested)\n")); printf(_(" --force-rewind[=VALUE] execute \"pg_rewind\" if necessary\n")); printf(_(" (PostgreSQL 9.4 - provide full \"pg_rewind\" path)\n")); printf(_(" --config-files comma-separated list of configuration files to retain\n" \ " after executing \"pg_rewind\"\n")); printf(_(" --config-archive-dir directory to temporarily store retained configuration files\n" \ " (default: /tmp)\n")); printf(_(" -W, --no-wait don't wait for the node to rejoin cluster\n")); puts(""); printf(_("NODE SERVICE\n")); puts(""); printf(_(" \"node service\" executes a system service command to stop/start/restart/reload a node\n" \ " or optionally display which command would be executed\n")); puts(""); printf(_(" Configuration file required, runs on local node only.\n")); puts(""); printf(_(" --dry-run show what action would be performed, but don't execute it\n")); printf(_(" --action action to perform (one of \"start\", \"stop\", \"restart\" or \"reload\")\n")); printf(_(" --list-actions show what command would be performed for each action\n")); printf(_(" --checkpoint issue a CHECKPOINT before stopping or restarting the node\n")); printf(_(" -S, --superuser=USERNAME superuser to use, if repmgr user is not superuser\n")); puts(""); printf(_("%s home page: <%s>\n"), "repmgr", REPMGR_URL); } repmgr-5.3.1/repmgr-action-node.h000066400000000000000000000020251420262710000166500ustar00rootroot00000000000000/* * repmgr-action-node.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _REPMGR_ACTION_NODE_H_ #define _REPMGR_ACTION_NODE_H_ extern void do_node_status(void); extern void do_node_check(void); extern void do_node_rejoin(void); extern void do_node_service(void); extern void do_node_control(void); extern void do_node_help(void); #endif /* _REPMGR_ACTION_NODE_H_ */ repmgr-5.3.1/repmgr-action-primary.c000066400000000000000000000377001420262710000174110ustar00rootroot00000000000000/* * repmgr-action-primary.c * * Implements primary actions for the repmgr command line utility * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #include "repmgr.h" #include "repmgr-client-global.h" #include "repmgr-action-primary.h" /* * do_primary_register() * * Event(s): * - primary_register */ void do_primary_register(void) { PGconn *conn = NULL; PGconn *primary_conn = NULL; int current_primary_id = UNKNOWN_NODE_ID; RecoveryType recovery_type = RECTYPE_UNKNOWN; t_node_info node_info = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; bool record_created = false; PQExpBufferData event_description; log_info(_("connecting to primary database...")); conn = establish_db_connection(config_file_options.conninfo, true); log_verbose(LOG_INFO, _("connected to server, checking its state")); /* verify that node is running a supported server version */ check_server_version(conn, "primary", true, NULL); /* check that node is actually a primary */ recovery_type = get_recovery_type(conn); if (recovery_type != RECTYPE_PRIMARY) { if (recovery_type == RECTYPE_STANDBY) { log_error(_("server is in standby mode and cannot be registered as a primary")); PQfinish(conn); exit(ERR_BAD_CONFIG); } log_error(_("unable to determine server's recovery type")); PQfinish(conn); exit(ERR_DB_CONN); } log_verbose(LOG_INFO, _("server is not in recovery")); /* * create the repmgr extension if it doesn't already exist; * note that create_repmgr_extension() will take into account * the --dry-run option */ if (!create_repmgr_extension(conn)) { PQfinish(conn); exit(ERR_BAD_CONFIG); } /* * In --dry-run mode we can't proceed any further as the following code * attempts to query the repmgr metadata, which won't exist until the * extension is installed */ if (runtime_options.dry_run == true) { PQfinish(conn); return; } initialize_voting_term(conn); begin_transaction(conn); /* * Check for an active primary node record with a different ID. This * shouldn't happen, but could do if an existing primary was shut down * without being unregistered. */ current_primary_id = get_primary_node_id(conn); if (current_primary_id != NODE_NOT_FOUND && current_primary_id != config_file_options.node_id) { log_debug("current active primary node ID is %i", current_primary_id); primary_conn = establish_primary_db_connection(conn, false); if (PQstatus(primary_conn) == CONNECTION_OK) { if (get_recovery_type(primary_conn) == RECTYPE_PRIMARY) { log_error(_("there is already an active registered primary (ID: %i) in this cluster"), current_primary_id); log_detail(_("a streaming replication cluster can have only one primary node")); log_hint(_("ensure this node is shut down before registering a new primary")); PQfinish(primary_conn); rollback_transaction(conn); PQfinish(conn); exit(ERR_BAD_CONFIG); } log_warning(_("node %is is registered as primary but running as a standby"), current_primary_id); PQfinish(primary_conn); } log_notice(_("setting node %i's node record to inactive"), current_primary_id); update_node_record_set_active(conn, current_primary_id, false); } /* * Check whether there's an existing record for this node, and update it * if --force set */ record_status = get_node_record(conn, config_file_options.node_id, &node_info); if (record_status == RECORD_FOUND) { if (!runtime_options.force) { log_error(_("this node is already registered")); log_hint(_("use -F/--force to overwrite the existing node record")); rollback_transaction(conn); PQfinish(conn); exit(ERR_BAD_CONFIG); } } init_node_record(&node_info); /* set type to "primary" and unset upstream_node_id */ node_info.type = PRIMARY; node_info.upstream_node_id = NO_UPSTREAM_NODE; initPQExpBuffer(&event_description); if (record_status == RECORD_FOUND) { record_created = update_node_record(conn, "primary register", &node_info); if (record_created == true) { appendPQExpBufferStr(&event_description, "existing primary record updated"); } else { appendPQExpBuffer(&event_description, "error encountered while updating primary record:\n%s", PQerrorMessage(conn)); } } else { record_created = create_node_record(conn, "primary register", &node_info); if (record_created == false) { appendPQExpBuffer(&event_description, "error encountered while creating primary record:\n%s", PQerrorMessage(conn)); } } if (record_created == false) { rollback_transaction(conn); } else { commit_transaction(conn); } /* Log the event */ create_event_notification( conn, &config_file_options, config_file_options.node_id, "primary_register", record_created, event_description.data); termPQExpBuffer(&event_description); PQfinish(conn); if (record_created == false) { log_notice(_("unable to register primary node - see preceding messages")); exit(ERR_DB_QUERY); } if (record_status == RECORD_FOUND) { log_notice(_("primary node record (ID: %i) updated"), config_file_options.node_id); } else { log_notice(_("primary node record (ID: %i) registered"), config_file_options.node_id); } return; } /* * do_primary_unregister() * * Event(s): * - primary_unregister */ void do_primary_unregister(void) { PGconn *primary_conn = NULL; PGconn *local_conn = NULL; t_node_info local_node_info = T_NODE_INFO_INITIALIZER; t_node_info primary_node_info = T_NODE_INFO_INITIALIZER; t_node_info *target_node_info_ptr = NULL; PGconn *target_node_conn = NULL; NodeInfoList downstream_nodes = T_NODE_INFO_LIST_INITIALIZER; /* We must be able to connect to the local node */ local_conn = establish_db_connection(config_file_options.conninfo, true); /* Get local node record */ get_local_node_record(local_conn, config_file_options.node_id, &local_node_info); /* * Obtain a connection to the current primary node - if this isn't * possible, abort as we won't be able to update the "nodes" table anyway. */ primary_conn = establish_primary_db_connection(local_conn, false); if (PQstatus(primary_conn) != CONNECTION_OK) { log_error(_("unable to connect to primary server")); if (get_primary_node_record(local_conn, &primary_node_info) == true) { log_detail(_("current primary registered as node \"%s\" (ID: %i, conninfo: \"%s\")"), primary_node_info.node_name, primary_node_info.node_id, primary_node_info.conninfo); } log_hint(_("you may need to promote this standby or ask it to look for a new primary to follow")); PQfinish(local_conn); exit(ERR_DB_CONN); } /* Local connection no longer required */ PQfinish(local_conn); if (get_primary_node_record(primary_conn, &primary_node_info) == false) { log_error(_("unable to retrieve record for primary node")); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } /* Target node is local node? */ if (target_node_info.node_id == UNKNOWN_NODE_ID) { target_node_info_ptr = &primary_node_info; } else if (target_node_info.node_id == config_file_options.node_id) { target_node_info_ptr = &local_node_info; } /* Target node is explicitly specified, and is not local node */ else { target_node_info_ptr = &target_node_info; } /* * Sanity-check the target node is not a witness */ if (target_node_info_ptr->type == WITNESS) { log_error(_("node \"%s\" (ID: %i) is a witness server, unable to unregister"), target_node_info_ptr->node_name, target_node_info_ptr->node_id); if (target_node_info_ptr->type == STANDBY) { log_hint(_("the node can be unregistered with \"repmgr witness unregister\"")); } PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } /* * Check for downstream nodes - if any still defined, we won't be able to * delete the node record due to foreign key constraints. */ get_downstream_node_records(primary_conn, target_node_info_ptr->node_id, &downstream_nodes); if (downstream_nodes.node_count > 0) { NodeInfoListCell *cell = NULL; PQExpBufferData detail; if (downstream_nodes.node_count == 1) { log_error(_("%i other node still has this node as its upstream node"), downstream_nodes.node_count); } else { log_error(_("%i other nodes still have this node as their upstream node"), downstream_nodes.node_count); } log_hint(_("ensure these nodes are following the current primary with \"repmgr standby follow\"")); initPQExpBuffer(&detail); for (cell = downstream_nodes.head; cell; cell = cell->next) { appendPQExpBuffer(&detail, " %s (ID: %i)\n", cell->node_info->node_name, cell->node_info->node_id); } log_detail(_("the affected node(s) are:\n%s"), detail.data); termPQExpBuffer(&detail); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } target_node_conn = establish_db_connection_quiet(target_node_info_ptr->conninfo); /* If node not reachable, check that the record is for a primary node */ if (PQstatus(target_node_conn) != CONNECTION_OK) { if (target_node_info_ptr->type != PRIMARY) { log_error(_("node \"%s\" (ID: %i) is not a primary, unable to unregister"), target_node_info_ptr->node_name, target_node_info_ptr->node_id); if (target_node_info_ptr->type == STANDBY) { log_hint(_("the node can be unregistered with \"repmgr standby unregister\"")); } PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } } /* If we can connect to the node, perform some sanity checks on it */ else { bool can_unregister = true; RecoveryType recovery_type = get_recovery_type(target_node_conn); /* Node appears to be a standby */ if (recovery_type == RECTYPE_STANDBY) { /* * We'll refuse to do anything unless the node record shows it as * a primary */ if (target_node_info_ptr->type != PRIMARY) { log_error(_("node \"%s\" (ID: %i) is a %s, unable to unregister"), target_node_info_ptr->node_name, target_node_info_ptr->node_id, get_node_type_string(target_node_info_ptr->type)); can_unregister = false; } /* * If --F/--force not set, hint that it might be appropriate to * register the node as a standby rather than unregister as * primary */ else if (!runtime_options.force) { log_error(_("node \"%s\" (ID: %i) is running as a standby, unable to unregister"), target_node_info_ptr->node_name, target_node_info_ptr->node_id); log_hint(_("the node can be registered as a standby with \"repmgr standby register --force\"")); log_hint(_("use \"repmgr primary unregister --force\" to remove this node's metadata entirely")); can_unregister = false; } if (can_unregister == false) { PQfinish(target_node_conn); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } } else if (recovery_type == RECTYPE_PRIMARY) { t_node_info primary_node_info = T_NODE_INFO_INITIALIZER; bool primary_record_found = false; primary_record_found = get_primary_node_record(primary_conn, &primary_node_info); if (primary_record_found == false) { log_error(_("node \"%s\" (ID: %i) is a primary node, but no primary node record found"), target_node_info_ptr->node_name, target_node_info_ptr->node_id); log_hint(_("register this node as primary with \"repmgr primary register --force\"")); PQfinish(target_node_conn); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } /* * This appears to be the cluster primary - cowardly refuse to * delete the record, unless --force is supplied. */ if (primary_node_info.node_id == target_node_info_ptr->node_id && !runtime_options.force) { log_error(_("node \"%s\" (ID: %i) is the current primary node, unable to unregister"), target_node_info_ptr->node_name, target_node_info_ptr->node_id); if (primary_node_info.active == false) { log_hint(_("node is marked as inactive, activate with \"repmgr primary register --force\"")); } PQfinish(target_node_conn); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } } /* We don't need the target node connection any more */ PQfinish(target_node_conn); } if (target_node_info_ptr->active == true) { if (!runtime_options.force) { log_error(_("node \"%s\" (ID: %i) is marked as active, unable to unregister"), target_node_info_ptr->node_name, target_node_info_ptr->node_id); log_hint(_("run \"repmgr primary unregister --force\" to unregister this node")); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } } if (runtime_options.dry_run == true) { log_notice(_("node \"%s\" (ID: %i) would now be unregistered"), target_node_info_ptr->node_name, target_node_info_ptr->node_id); log_hint(_("run the same command without the --dry-run option to unregister this node")); } else { PQExpBufferData event_details; bool delete_success = delete_node_record(primary_conn, target_node_info_ptr->node_id); if (delete_success == false) { log_error(_("unable to unregister node \"%s\" (ID: %i)"), target_node_info_ptr->node_name, target_node_info_ptr->node_id); PQfinish(primary_conn); exit(ERR_DB_QUERY); } initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("node \"%s\" (ID: %i) unregistered"), target_node_info_ptr->node_name, target_node_info_ptr->node_id); if (target_node_info_ptr->node_id != config_file_options.node_id) { appendPQExpBuffer(&event_details, _(" from node \"%s\" (ID: %i)"), config_file_options.node_name, config_file_options.node_id); } create_event_notification(primary_conn, &config_file_options, config_file_options.node_id, "primary_unregister", true, event_details.data); termPQExpBuffer(&event_details); log_info(_("node \"%s\" (ID: %i) was successfully unregistered"), target_node_info_ptr->node_name, target_node_info_ptr->node_id); } PQfinish(primary_conn); return; } void do_primary_help(void) { print_help_header(); printf(_("Usage:\n")); printf(_(" %s [OPTIONS] primary register\n"), progname()); printf(_(" %s [OPTIONS] primary unregister\n"), progname()); puts(""); printf(_(" Note: \"%s master ...\" can be used as an alias\n"), progname()); puts(""); printf(_("PRIMARY REGISTER\n")); puts(""); printf(_(" \"primary register\" initialises the repmgr cluster and registers the primary node.\n")); puts(""); printf(_(" --dry-run check that the prerequisites are met for registering the primary\n" \ " (including availability of the repmgr extension)\n")); printf(_(" -F, --force overwrite an existing node record\n")); puts(""); printf(_("PRIMARY UNREGISTER\n")); puts(""); printf(_(" \"primary unregister\" unregisters an inactive primary node.\n")); puts(""); printf(_(" --dry-run check what would happen, but don't actually unregister the primary\n")); printf(_(" --node-id ID of the inactive primary node to unregister.\n")); printf(_(" -F, --force force removal of an active record\n")); puts(""); printf(_("%s home page: <%s>\n"), "repmgr", REPMGR_URL); } repmgr-5.3.1/repmgr-action-primary.h000066400000000000000000000016411420262710000174110ustar00rootroot00000000000000/* * repmgr-action-primary.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _REPMGR_ACTION_PRIMARY_H_ #define _REPMGR_ACTION_PRIMARY_H_ extern void do_primary_register(void); extern void do_primary_unregister(void); extern void do_primary_help(void); #endif repmgr-5.3.1/repmgr-action-service.c000066400000000000000000000366251420262710000173730ustar00rootroot00000000000000/* * repmgr-action-service.c * * Implements repmgrd actions for the repmgr command line utility * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #include #include /* for stat() */ #include "repmgr.h" #include "repmgr-client-global.h" #include "repmgr-action-service.h" /* * Possibly also show: * - repmgrd start time? * - repmgrd mode * - priority * - whether promotion candidate (due to zero priority/different location) */ typedef enum { STATUS_ID = 0, STATUS_NAME, STATUS_ROLE, STATUS_PG, STATUS_UPSTREAM_NAME, STATUS_LOCATION, STATUS_PRIORITY, STATUS_REPMGRD, STATUS_PID, STATUS_PAUSED, STATUS_UPSTREAM_LAST_SEEN } StatusHeader; #define STATUS_HEADER_COUNT 11 struct ColHeader headers_status[STATUS_HEADER_COUNT]; static void fetch_node_records(PGconn *conn, NodeInfoList *node_list); static void _do_repmgr_pause(bool pause); void do_service_status(void) { PGconn *conn = NULL; NodeInfoList nodes = T_NODE_INFO_LIST_INITIALIZER; NodeInfoListCell *cell = NULL; int i; RepmgrdInfo **repmgrd_info; ItemList warnings = {NULL, NULL}; bool connection_error_found = false; /* Connect to local database to obtain cluster connection data */ log_verbose(LOG_INFO, _("connecting to database")); if (strlen(config_file_options.conninfo)) conn = establish_db_connection(config_file_options.conninfo, true); else conn = establish_db_connection_by_params(&source_conninfo, true); fetch_node_records(conn, &nodes); repmgrd_info = (RepmgrdInfo **) pg_malloc0(sizeof(RepmgrdInfo *) * nodes.node_count); if (repmgrd_info == NULL) { log_error(_("unable to allocate memory")); exit(ERR_OUT_OF_MEMORY); } strncpy(headers_status[STATUS_ID].title, _("ID"), MAXLEN); strncpy(headers_status[STATUS_NAME].title, _("Name"), MAXLEN); strncpy(headers_status[STATUS_ROLE].title, _("Role"), MAXLEN); strncpy(headers_status[STATUS_PG].title, _("Status"), MAXLEN); strncpy(headers_status[STATUS_UPSTREAM_NAME].title, _("Upstream"), MAXLEN); /* following only displayed with the --detail option */ strncpy(headers_status[STATUS_LOCATION].title, _("Location"), MAXLEN); if (runtime_options.compact == true) strncpy(headers_status[STATUS_PRIORITY].title, _("Prio."), MAXLEN); else strncpy(headers_status[STATUS_PRIORITY].title, _("Priority"), MAXLEN); strncpy(headers_status[STATUS_REPMGRD].title, _("repmgrd"), MAXLEN); strncpy(headers_status[STATUS_PID].title, _("PID"), MAXLEN); strncpy(headers_status[STATUS_PAUSED].title, _("Paused?"), MAXLEN); if (runtime_options.compact == true) strncpy(headers_status[STATUS_UPSTREAM_LAST_SEEN].title, _("Upstr. last"), MAXLEN); else strncpy(headers_status[STATUS_UPSTREAM_LAST_SEEN].title, _("Upstream last seen"), MAXLEN); for (i = 0; i < STATUS_HEADER_COUNT; i++) { headers_status[i].max_length = strlen(headers_status[i].title); headers_status[i].display = true; } if (runtime_options.detail == false) { headers_status[STATUS_LOCATION].display = false; headers_status[STATUS_PRIORITY].display = false; } i = 0; for (cell = nodes.head; cell; cell = cell->next) { int j; PQExpBufferData node_status; PQExpBufferData upstream; repmgrd_info[i] = pg_malloc0(sizeof(RepmgrdInfo)); repmgrd_info[i]->node_id = cell->node_info->node_id; repmgrd_info[i]->pid = UNKNOWN_PID; repmgrd_info[i]->recovery_type = RECTYPE_UNKNOWN; repmgrd_info[i]->paused = false; repmgrd_info[i]->running = false; repmgrd_info[i]->pg_running = true; repmgrd_info[i]->wal_paused_pending_wal = false; repmgrd_info[i]->upstream_last_seen = -1; cell->node_info->conn = establish_db_connection_quiet(cell->node_info->conninfo); if (PQstatus(cell->node_info->conn) != CONNECTION_OK) { connection_error_found = true; if (runtime_options.verbose) { char error[MAXLEN]; strncpy(error, PQerrorMessage(cell->node_info->conn), MAXLEN); item_list_append_format(&warnings, "when attempting to connect to node \"%s\" (ID: %i), following error encountered :\n\"%s\"", cell->node_info->node_name, cell->node_info->node_id, trim(error)); } else { item_list_append_format(&warnings, "unable to connect to node \"%s\" (ID: %i)", cell->node_info->node_name, cell->node_info->node_id); } repmgrd_info[i]->pg_running = false; maxlen_snprintf(repmgrd_info[i]->repmgrd_running, "%s", _("n/a")); maxlen_snprintf(repmgrd_info[i]->pid_text, "%s", _("n/a")); } else { cell->node_info->node_status = NODE_STATUS_UP; cell->node_info->recovery_type = get_recovery_type(cell->node_info->conn); repmgrd_info[i]->pid = repmgrd_get_pid(cell->node_info->conn); repmgrd_info[i]->running = repmgrd_is_running(cell->node_info->conn); if (repmgrd_info[i]->running == true) { maxlen_snprintf(repmgrd_info[i]->repmgrd_running, "%s", _("running")); } else { maxlen_snprintf(repmgrd_info[i]->repmgrd_running, "%s", _("not running")); } if (repmgrd_info[i]->pid == UNKNOWN_PID) { maxlen_snprintf(repmgrd_info[i]->pid_text, "%s", _("n/a")); } else { maxlen_snprintf(repmgrd_info[i]->pid_text, "%i", repmgrd_info[i]->pid); } repmgrd_info[i]->paused = repmgrd_is_paused(cell->node_info->conn); repmgrd_info[i]->recovery_type = get_recovery_type(cell->node_info->conn); if (repmgrd_info[i]->recovery_type == RECTYPE_STANDBY) { repmgrd_info[i]->wal_paused_pending_wal = is_wal_replay_paused(cell->node_info->conn, true); if (repmgrd_info[i]->wal_paused_pending_wal == true) { item_list_append_format(&warnings, _("WAL replay is paused on node \"%s\" (ID: %i) with WAL replay pending; this node cannot be manually promoted until WAL replay is resumed"), cell->node_info->node_name, cell->node_info->node_id); } } repmgrd_info[i]->upstream_last_seen = get_upstream_last_seen(cell->node_info->conn, cell->node_info->type); if (repmgrd_info[i]->upstream_last_seen < 0) { maxlen_snprintf(repmgrd_info[i]->upstream_last_seen_text, "%s", _("n/a")); } else { if (runtime_options.compact == true) { maxlen_snprintf(repmgrd_info[i]->upstream_last_seen_text, _("%i sec(s) ago"), repmgrd_info[i]->upstream_last_seen); } else { maxlen_snprintf(repmgrd_info[i]->upstream_last_seen_text, _("%i second(s) ago"), repmgrd_info[i]->upstream_last_seen); } } } initPQExpBuffer(&node_status); initPQExpBuffer(&upstream); (void)format_node_status(cell->node_info, &node_status, &upstream, &warnings); snprintf(repmgrd_info[i]->pg_running_text, sizeof(cell->node_info->details), "%s", node_status.data); snprintf(cell->node_info->upstream_node_name, sizeof(cell->node_info->upstream_node_name), "%s", upstream.data); termPQExpBuffer(&node_status); termPQExpBuffer(&upstream); PQfinish(cell->node_info->conn); headers_status[STATUS_NAME].cur_length = strlen(cell->node_info->node_name); headers_status[STATUS_ROLE].cur_length = strlen(get_node_type_string(cell->node_info->type)); headers_status[STATUS_PG].cur_length = strlen(repmgrd_info[i]->pg_running_text); headers_status[STATUS_UPSTREAM_NAME].cur_length = strlen(cell->node_info->upstream_node_name); if (runtime_options.detail == true) { PQExpBufferData buf; headers_status[STATUS_LOCATION].cur_length = strlen(cell->node_info->location); initPQExpBuffer(&buf); appendPQExpBuffer(&buf, "%i", cell->node_info->priority); headers_status[STATUS_PRIORITY].cur_length = strlen(buf.data); termPQExpBuffer(&buf); } headers_status[STATUS_PID].cur_length = strlen(repmgrd_info[i]->pid_text); headers_status[STATUS_REPMGRD].cur_length = strlen(repmgrd_info[i]->repmgrd_running); headers_status[STATUS_UPSTREAM_LAST_SEEN].cur_length = strlen(repmgrd_info[i]->upstream_last_seen_text); for (j = 0; j < STATUS_HEADER_COUNT; j++) { if (headers_status[j].cur_length > headers_status[j].max_length) { headers_status[j].max_length = headers_status[j].cur_length; } } i++; } /* Print column header row (text mode only) */ if (runtime_options.output_mode == OM_TEXT) { print_status_header(STATUS_HEADER_COUNT, headers_status); } i = 0; for (cell = nodes.head; cell; cell = cell->next) { if (runtime_options.output_mode == OM_CSV) { int running = repmgrd_info[i]->running ? 1 : 0; int paused = repmgrd_info[i]->paused ? 1 : 0; /* If PostgreSQL is not running, repmgrd status is unknown */ if (repmgrd_info[i]->pg_running == false) { running = -1; paused = -1; } printf("%i,%s,%s,%i,%i,%i,%i,%i,%i,%s\n", cell->node_info->node_id, cell->node_info->node_name, get_node_type_string(cell->node_info->type), repmgrd_info[i]->pg_running ? 1 : 0, running, repmgrd_info[i]->pid, paused, cell->node_info->priority, repmgrd_info[i]->pid == UNKNOWN_PID ? -1 : repmgrd_info[i]->upstream_last_seen, cell->node_info->location); } else { printf(" %-*i ", headers_status[STATUS_ID].max_length, cell->node_info->node_id); printf("| %-*s ", headers_status[STATUS_NAME].max_length, cell->node_info->node_name); printf("| %-*s ", headers_status[STATUS_ROLE].max_length, get_node_type_string(cell->node_info->type)); printf("| %-*s ", headers_status[STATUS_PG].max_length, repmgrd_info[i]->pg_running_text); printf("| %-*s ", headers_status[STATUS_UPSTREAM_NAME].max_length, cell->node_info->upstream_node_name); if (runtime_options.detail == true) { printf("| %-*s ", headers_status[STATUS_LOCATION].max_length, cell->node_info->location); printf("| %-*i ", headers_status[STATUS_PRIORITY].max_length, cell->node_info->priority); } printf("| %-*s ", headers_status[STATUS_REPMGRD].max_length, repmgrd_info[i]->repmgrd_running); printf("| %-*s ", headers_status[STATUS_PID].max_length, repmgrd_info[i]->pid_text); if (repmgrd_info[i]->pid == UNKNOWN_PID) { printf("| %-*s ", headers_status[STATUS_PAUSED].max_length, _("n/a")); printf("| %-*s ", headers_status[STATUS_UPSTREAM_LAST_SEEN].max_length, _("n/a")); } else { printf("| %-*s ", headers_status[STATUS_PAUSED].max_length, repmgrd_info[i]->paused ? _("yes") : _("no")); printf("| %-*s ", headers_status[STATUS_UPSTREAM_LAST_SEEN].max_length, repmgrd_info[i]->upstream_last_seen_text); } printf("\n"); } pfree(repmgrd_info[i]); i++; } pfree(repmgrd_info); /* emit any warnings */ if (warnings.head != NULL && runtime_options.terse == false && runtime_options.output_mode != OM_CSV) { ItemListCell *cell = NULL; PQExpBufferData warning; initPQExpBuffer(&warning); appendPQExpBufferStr(&warning, _("following issues were detected\n")); for (cell = warnings.head; cell; cell = cell->next) { appendPQExpBuffer(&warning, _(" - %s\n"), cell->string); } puts(""); log_warning("%s", warning.data); termPQExpBuffer(&warning); if (runtime_options.verbose == false && connection_error_found == true) { log_hint(_("execute with --verbose option to see connection error messages")); } } } void do_service_pause(void) { _do_repmgr_pause(true); } void do_service_unpause(void) { _do_repmgr_pause(false); } static void _do_repmgr_pause(bool pause) { PGconn *conn = NULL; NodeInfoList nodes = T_NODE_INFO_LIST_INITIALIZER; NodeInfoListCell *cell = NULL; int i; int error_nodes = 0; /* Connect to local database to obtain cluster connection data */ log_verbose(LOG_INFO, _("connecting to database")); if (strlen(config_file_options.conninfo)) conn = establish_db_connection(config_file_options.conninfo, true); else conn = establish_db_connection_by_params(&source_conninfo, true); fetch_node_records(conn, &nodes); i = 0; for (cell = nodes.head; cell; cell = cell->next) { log_verbose(LOG_DEBUG, "pausing node %i (%s)", cell->node_info->node_id, cell->node_info->node_name); cell->node_info->conn = establish_db_connection_quiet(cell->node_info->conninfo); if (PQstatus(cell->node_info->conn) != CONNECTION_OK) { log_warning(_("unable to connect to node %i"), cell->node_info->node_id); error_nodes++; } else { if (runtime_options.dry_run == true) { if (pause == true) { log_info(_("would pause node %i (%s) "), cell->node_info->node_id, cell->node_info->node_name); } else { log_info(_("would unpause node %i (%s) "), cell->node_info->node_id, cell->node_info->node_name); } } else { bool success = repmgrd_pause(cell->node_info->conn, pause); if (success == false) error_nodes++; log_notice(_("node %i (%s) %s"), cell->node_info->node_id, cell->node_info->node_name, success == true ? pause == true ? "paused" : "unpaused" : pause == true ? "not paused" : "not unpaused"); } PQfinish(cell->node_info->conn); } i++; } if (error_nodes > 0) { if (pause == true) { log_error(_("unable to pause %i node(s)"), error_nodes); } else { log_error(_("unable to unpause %i node(s)"), error_nodes); } log_hint(_("execute \"repmgr service status\" to view current status")); exit(ERR_REPMGRD_PAUSE); } exit(SUCCESS); } void fetch_node_records(PGconn *conn, NodeInfoList *node_list) { bool success = get_all_node_records_with_upstream(conn, node_list); if (success == false) { /* get_all_node_records() will display any error message */ PQfinish(conn); exit(ERR_BAD_CONFIG); } if (node_list->node_count == 0) { log_error(_("no node records were found")); log_hint(_("ensure at least one node is registered")); PQfinish(conn); exit(ERR_BAD_CONFIG); } } void do_service_help(void) { print_help_header(); printf(_("Usage:\n")); printf(_(" %s [OPTIONS] service status\n"), progname()); printf(_(" %s [OPTIONS] service pause\n"), progname()); printf(_(" %s [OPTIONS] service unpause\n"), progname()); puts(""); printf(_("SERVICE STATUS\n")); puts(""); printf(_(" \"service status\" shows the status of repmgrd on each node in the cluster\n")); puts(""); printf(_(" --csv emit output as CSV\n")); printf(_(" --detail show additional detail\n")); printf(_(" --verbose show text of database connection error messages\n")); puts(""); printf(_("SERVICE PAUSE\n")); puts(""); printf(_(" \"service pause\" instructs repmgrd on each node to pause failover detection\n")); puts(""); printf(_(" --dry-run check if nodes are reachable but don't pause repmgrd\n")); puts(""); printf(_("SERVICE UNPAUSE\n")); puts(""); printf(_(" \"service unpause\" instructs repmgrd on each node to resume failover detection\n")); puts(""); printf(_(" --dry-run check if nodes are reachable but don't unpause repmgrd\n")); puts(""); puts(""); printf(_("%s home page: <%s>\n"), "repmgr", REPMGR_URL); } repmgr-5.3.1/repmgr-action-service.h000066400000000000000000000017011420262710000173630ustar00rootroot00000000000000/* * repmgr-action-service.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _REPMGR_ACTION_SERVICE_H_ #define _REPMGR_ACTION_SERVICE_H_ extern void do_service_status(void); extern void do_service_pause(void); extern void do_service_unpause(void); extern void do_service_help(void); #endif repmgr-5.3.1/repmgr-action-standby.c000066400000000000000000007615011420262710000173750ustar00rootroot00000000000000/* * repmgr-action-standby.c * * Implements standby actions for the repmgr command line utility * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #include #include #include "repmgr.h" #include "dirutil.h" #include "compat.h" #include "controldata.h" #include "repmgr-client-global.h" #include "repmgr-action-standby.h" typedef struct TablespaceDataListCell { struct TablespaceDataListCell *next; char *name; char *oid; char *location; /* Optional pointer to a file containing a list of tablespace files to copy from Barman */ FILE *fptr; } TablespaceDataListCell; typedef struct TablespaceDataList { TablespaceDataListCell *head; TablespaceDataListCell *tail; } TablespaceDataList; typedef struct { int reachable_sibling_node_count; int reachable_sibling_nodes_with_slot_count; int unreachable_sibling_node_count; int min_required_wal_senders; int min_required_free_slots; } SiblingNodeStats; #define T_SIBLING_NODES_STATS_INITIALIZER { \ 0, \ 0, \ 0, \ 0, \ 0 \ } static PGconn *primary_conn = NULL; static PGconn *source_conn = NULL; static char local_data_directory[MAXPGPATH] = ""; static bool upstream_conninfo_found = false; static int upstream_node_id = UNKNOWN_NODE_ID; static t_conninfo_param_list recovery_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; static char recovery_conninfo_str[MAXLEN] = ""; static char upstream_repluser[NAMEDATALEN] = ""; static char upstream_user[NAMEDATALEN] = ""; static int source_server_version_num = UNKNOWN_SERVER_VERSION_NUM; static t_configfile_list config_files = T_CONFIGFILE_LIST_INITIALIZER; static standy_clone_mode mode = pg_basebackup; /* used by barman mode */ static char local_repmgr_tmp_directory[MAXPGPATH] = ""; static char datadir_list_filename[MAXLEN] = ""; static char barman_command_buf[MAXLEN] = ""; /* * To enable "standby clone" to run with lowest possible user * privileges, we'll need to determine which actions need to * be run and which of the available users, which will be one * of the repmgr user, the replication user (if available) or * the superuser (if available). */ static t_user_type SettingsUser = REPMGR_USER; static void _do_standby_promote_internal(PGconn *conn); static void _do_create_replication_conf(void); static void check_barman_config(void); static void check_source_server(void); static void check_source_server_via_barman(void); static bool check_upstream_config(PGconn *conn, int server_version_num, t_node_info *node_info, bool exit_on_error); static void check_primary_standby_version_match(PGconn *conn, PGconn *primary_conn); static void check_recovery_type(PGconn *conn); static void initialise_direct_clone(t_node_info *local_node_record, t_node_info *upstream_node_record); static int run_basebackup(t_node_info *node_record); static int run_file_backup(t_node_info *node_record); static void copy_configuration_files(bool delete_after_copy); static void tablespace_data_append(TablespaceDataList *list, const char *name, const char *oid, const char *location); static void get_barman_property(char *dst, char *name, char *local_repmgr_directory); static int get_tablespace_data_barman(char *, TablespaceDataList *); static char *make_barman_ssh_command(char *buf); static bool create_recovery_file(t_node_info *node_record, t_conninfo_param_list *primary_conninfo, int server_version_num, char *dest, bool as_file); static void write_primary_conninfo(PQExpBufferData *dest, t_conninfo_param_list *param_list); static bool check_sibling_nodes(NodeInfoList *sibling_nodes, SiblingNodeStats *sibling_nodes_stats); static bool check_free_wal_senders(int available_wal_senders, SiblingNodeStats *sibling_nodes_stats, bool *dry_run_success); static bool check_free_slots(t_node_info *local_node_record, SiblingNodeStats *sibling_nodes_stats, bool *dry_run_success); static void sibling_nodes_follow(t_node_info *local_node_record, NodeInfoList *sibling_nodes, SiblingNodeStats *sibling_nodes_stats); static t_remote_error_type parse_remote_error(const char *error); static CheckStatus parse_check_status(const char *status_str); static NodeStatus parse_node_status_is_shutdown_cleanly(const char *node_status_output, XLogRecPtr *checkPoint); static CheckStatus parse_node_check_archiver(const char *node_check_output, int *files, int *threshold, t_remote_error_type *remote_error); static ConnectionStatus parse_remote_node_replication_connection(const char *node_check_output); static bool parse_data_directory_config(const char *node_check_output, t_remote_error_type *remote_error); static bool parse_replication_config_owner(const char *node_check_output); static CheckStatus parse_db_connection(const char *db_connection); /* * STANDBY CLONE * * Event(s): * - standby_clone * * Parameters: * --upstream-conninfo * --upstream-node-id * --no-upstream-connection * -F/--force * --dry-run * -c/--fast-checkpoint * --copy-external-config-files * -R/--remote-user * --replication-user (only required if no upstream record) * --without-barman * --replication-conf-only (--recovery-conf-only) * --verify-backup (PostgreSQL 13 and later) */ void do_standby_clone(void) { PQExpBufferData event_details; int r = 0; /* dummy node record */ t_node_info local_node_record = T_NODE_INFO_INITIALIZER; t_node_info upstream_node_record = T_NODE_INFO_INITIALIZER; bool local_data_directory_provided = false; initialize_conninfo_params(&recovery_conninfo, false); /* * Copy the provided data directory; if a configuration file was provided, * use the (mandatory) value from that; if -D/--pgdata was provided, use * that. * * Note that barman mode requires -D/--pgdata. */ get_node_data_directory(local_data_directory); if (local_data_directory[0] != '\0') { local_data_directory_provided = true; log_notice(_("destination directory \"%s\" provided"), local_data_directory); } else { /* * If a configuration file is provided, repmgr will error out after * parsing it if no data directory is provided; this check is for * niche use-cases where no configuration file is provided. */ log_error(_("no data directory provided")); log_hint(_("use -D/--pgdata to explicitly specify a data directory")); exit(ERR_BAD_CONFIG); } /* * --replication-conf-only provided - we'll handle that separately */ if (runtime_options.replication_conf_only == true) { return _do_create_replication_conf(); } /* * conninfo params for the actual upstream node (which might be different * to the node we're cloning from) to write to recovery.conf */ mode = get_standby_clone_mode(); if (mode == barman) { /* * Not currently possible to use --verify-backup with Barman */ if (runtime_options.verify_backup == true) { log_error(_("--verify-backup option cannot be used when cloning from Barman backups")); exit(ERR_BAD_CONFIG); } /* * Sanity-check barman connection and installation; * this will exit with ERR_BARMAN if problems found. */ check_barman_config(); } init_node_record(&local_node_record); local_node_record.type = STANDBY; /* * Initialise list of conninfo parameters which will later be used to * create the "primary_conninfo" recovery parameter. * * We'll initialise it with the host settings specified on the command * line. As it's possible the standby will be cloned from a node different * to its intended upstream, we'll later attempt to fetch the upstream * node record and overwrite the values set here with those from the * upstream node record (excluding that record's application_name) */ copy_conninfo_params(&recovery_conninfo, &source_conninfo); /* Set the default application name to this node's name */ if (config_file_options.node_id != UNKNOWN_NODE_ID) { char application_name[MAXLEN] = ""; param_set(&recovery_conninfo, "application_name", config_file_options.node_name); get_conninfo_value(config_file_options.conninfo, "application_name", application_name); if (strlen(application_name) && strncmp(application_name, config_file_options.node_name, sizeof(config_file_options.node_name)) != 0) { log_notice(_("\"application_name\" is set in repmgr.conf but will be replaced by the node name")); } } else { /* * this will only happen in corner cases where the node is being * cloned without a configuration file; fall back to "repmgr" if no * application_name provided */ char *application_name = param_get(&source_conninfo, "application_name"); if (application_name == NULL) param_set(&recovery_conninfo, "application_name", "repmgr"); } /* * Do some sanity checks on the proposed data directory; if it exists: * - check it's openable * - check if there's an instance running * * We do this here so the check can be part of a --dry-run. */ switch (check_dir(local_data_directory)) { case DIR_ERROR: log_error(_("unable to access specified data directory \"%s\""), local_data_directory); log_detail("%s", strerror(errno)); exit(ERR_BAD_CONFIG); break; case DIR_NOENT: /* * directory doesn't exist * TODO: in --dry-run mode, attempt to create and delete? */ break; case DIR_EMPTY: /* Present but empty */ break; case DIR_NOT_EMPTY: /* Present but not empty */ if (is_pg_dir(local_data_directory)) { /* even -F/--force is not enough to overwrite an active directory... */ if (is_pg_running(local_data_directory)) { log_error(_("specified data directory \"%s\" appears to contain a running PostgreSQL instance"), local_data_directory); log_hint(_("ensure the target data directory does not contain a running PostgreSQL instance")); exit(ERR_BAD_CONFIG); } } break; default: break; } /* * By default attempt to connect to the source node. This will fail if no * connection is possible, unless in Barman mode, in which case we can * fall back to connecting to the source node via Barman (if available). */ if (runtime_options.no_upstream_connection == false) { RecordStatus record_status = RECORD_NOT_FOUND; /* * This connects to the source node and performs sanity checks, also * sets "recovery_conninfo_str", "upstream_repluser", "upstream_user" and * "upstream_node_id" and creates a connection handle in "source_conn". * * Will error out if source connection not possible and not in * "barman" mode. */ check_source_server(); if (runtime_options.verify_backup == true) { /* * --verify-backup available for PostgreSQL 13 and later */ if (PQserverVersion(source_conn) < 130000) { log_error(_("--verify-backup available for PostgreSQL 13 and later")); exit(ERR_BAD_CONFIG); } } /* attempt to retrieve upstream node record */ record_status = get_node_record(source_conn, upstream_node_id, &upstream_node_record); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve record for upstream node %i"), upstream_node_id); exit(ERR_BAD_CONFIG); } } else { upstream_node_id = runtime_options.upstream_node_id; } /* * if --upstream-conninfo was supplied, use that (will overwrite value set * by check_source_server(), but that's OK) */ if (runtime_options.upstream_conninfo[0] != '\0') { strncpy(recovery_conninfo_str, runtime_options.upstream_conninfo, MAXLEN); upstream_conninfo_found = true; } else if (mode == barman && PQstatus(source_conn) != CONNECTION_OK) { /* * Here we don't have a connection to the upstream node (either * because --no-upstream-connection was supplied, or * check_source_server() was unable to make a connection, and * --upstream-conninfo wasn't supplied. * * As we're executing in Barman mode we can try and connect via the * Barman server to extract the upstream node's conninfo string. * * To do this we need to extract Barman's conninfo string, replace the * database name with the repmgr one (they could well be different) * and remotely execute psql. * * This attempts to set "recovery_conninfo_str". */ check_source_server_via_barman(); } if (recovery_conninfo_str[0] == '\0') { log_error(_("unable to determine a connection string to use as \"primary_conninfo\"")); log_hint(_("use \"--upstream-conninfo\" to explicitly provide a value for \"primary_conninfo\"")); if (PQstatus(source_conn) == CONNECTION_OK) PQfinish(source_conn); exit(ERR_BAD_CONFIG); } if (upstream_conninfo_found == true) { /* * parse returned upstream conninfo string to recovery * primary_conninfo params */ char *errmsg = NULL; bool parse_success = false; log_verbose(LOG_DEBUG, "parsing upstream conninfo string \"%s\"", recovery_conninfo_str); /* * parse_conninfo_string() here will remove the upstream's * `application_name`, if set */ parse_success = parse_conninfo_string(recovery_conninfo_str, &recovery_conninfo, &errmsg, true); if (parse_success == false) { log_error(_("unable to parse conninfo string \"%s\" for upstream node"), recovery_conninfo_str); log_detail("%s", errmsg); if (PQstatus(source_conn) == CONNECTION_OK) PQfinish(source_conn); exit(ERR_BAD_CONFIG); } if (upstream_repluser[0] != '\0') { /* Write the replication user from the node's upstream record */ param_set(&recovery_conninfo, "user", upstream_repluser); } } else { /* * If no upstream node record found, we'll abort with an error here, * unless -F/--force is used, in which case we'll use the parameters * provided on the command line (and assume the user knows what * they're doing). */ if (upstream_node_id == UNKNOWN_NODE_ID) { log_error(_("unable to determine upstream node")); if (PQstatus(source_conn) == CONNECTION_OK) PQfinish(source_conn); exit(ERR_BAD_CONFIG); } if (!runtime_options.force) { log_error(_("no record found for upstream node (upstream_node_id: %i)"), upstream_node_id); log_hint(_("use -F/--force to create \"primary_conninfo\" based on command-line parameters")); if (PQstatus(source_conn) == CONNECTION_OK) PQfinish(source_conn); exit(ERR_BAD_CONFIG); } } /* * If copying of external configuration files requested, and any are * detected, perform sanity checks */ if (PQstatus(source_conn) == CONNECTION_OK && runtime_options.copy_external_config_files == true) { PGconn *superuser_conn = NULL; PGconn *privileged_conn = NULL; bool external_config_files = false; int i = 0; /* * Obtain configuration file locations * * We'll check to see whether the configuration files are in the data * directory - if not we'll have to copy them via SSH, if copying * requested. * * This will require superuser permissions, so we'll attempt to * connect as -S/--superuser (if provided), otherwise check the * current connection user has superuser rights. * * XXX: if configuration files are symlinks to targets outside the * data directory, they won't be copied by pg_basebackup, but we can't * tell this from the below query; we'll probably need to add a check * for their presence and if missing force copy by SSH */ if (SettingsUser == REPMGR_USER) { privileged_conn = source_conn; } else { get_superuser_connection(&source_conn, &superuser_conn, &privileged_conn); } if (get_configuration_file_locations(privileged_conn, &config_files) == false) { log_notice(_("unable to proceed without establishing configuration file locations")); PQfinish(source_conn); if (superuser_conn != NULL) PQfinish(superuser_conn); exit(ERR_BAD_CONFIG); } /* check if any files actually outside the data directory */ for (i = 0; i < config_files.entries; i++) { t_configfile_info *file = config_files.files[i]; if (file->in_data_directory == false) { external_config_files = true; break; } } if (external_config_files == true) { int r; PQExpBufferData msg; initPQExpBuffer(&msg); appendPQExpBuffer(&msg, _("external configuration files detected, checking SSH connection to host \"%s\""), runtime_options.host); if (runtime_options.dry_run == true) { log_notice("%s", msg.data); } else { log_verbose(LOG_INFO, "%s", msg.data); } termPQExpBuffer(&msg); r = test_ssh_connection(runtime_options.host, runtime_options.remote_user); if (r != 0) { log_error(_("remote host \"%s\" is not reachable via SSH - unable to copy external configuration files"), runtime_options.host); if (superuser_conn != NULL) PQfinish(superuser_conn); PQfinish(source_conn); exit(ERR_BAD_CONFIG); } initPQExpBuffer(&msg); appendPQExpBuffer(&msg, _("SSH connection to host \"%s\" succeeded"), runtime_options.host); if (runtime_options.dry_run == true) { log_info("%s", msg.data); } else { log_verbose(LOG_INFO, "%s", msg.data); } termPQExpBuffer(&msg); /* * Here we'll attempt an initial test copy of the detected external * files, to detect any issues before we run the base backup. * * Note this will exit with an error, unless -F/--force supplied. * * We don't do this during a --dry-run as it may introduce unexpected changes * on the local node; during an actual clone operation, any problems with * copying files will be detected early and the operation aborted before * the actual database cloning commences. * * TODO: put the files in a temporary directory and move to their final * destination once the database has been cloned. */ if (runtime_options.dry_run == false) { if (runtime_options.copy_external_config_files_destination == CONFIG_FILE_SAMEPATH) { /* * Files will be placed in the same path as on the source server; * don't delete after copying. */ copy_configuration_files(false); } else { /* * Files will be placed in the data directory - delete after copying. * They'll be copied again later; see TODO above. */ copy_configuration_files(true); } } } if (superuser_conn != NULL) PQfinish(superuser_conn); } if (runtime_options.dry_run == true) { /* * If replication slots in use, sanity-check whether we can create them * with the available user permissions. */ if (config_file_options.use_replication_slots == true && PQstatus(source_conn) == CONNECTION_OK) { PQExpBufferData msg; bool success = true; initPQExpBuffer(&msg); /* * "create_replication_slot()" knows about --dry-run mode and * will perform checks but not actually create the slot. */ success = create_replication_slot(source_conn, local_node_record.slot_name, &upstream_node_record, &msg); if (success == false) { log_error(_("prerequisites not met for creating a replication slot on upstream node %i"), upstream_node_record.node_id); termPQExpBuffer(&msg); exit(ERR_BAD_CONFIG); } termPQExpBuffer(&msg); } if (upstream_node_id != UNKNOWN_NODE_ID) { log_notice(_("standby will attach to upstream node %i"), upstream_node_id); } else { log_warning(_("unable to determine a valid upstream node id")); } if (mode == pg_basebackup && runtime_options.fast_checkpoint == false) { log_hint(_("consider using the -c/--fast-checkpoint option")); } if (mode == pg_basebackup) { /* * In --dry-run mode, this will just output the pg_basebackup command which * would be executed. */ run_basebackup(&local_node_record); } PQfinish(source_conn); log_info(_("all prerequisites for \"standby clone\" are met")); exit(SUCCESS); } if (mode != barman) { initialise_direct_clone(&local_node_record, &upstream_node_record); } switch (mode) { case pg_basebackup: log_notice(_("starting backup (using pg_basebackup)...")); break; case barman: log_notice(_("retrieving backup from Barman...")); break; default: /* should never reach here */ log_error(_("unknown clone mode")); } if (mode == pg_basebackup) { if (runtime_options.fast_checkpoint == false) { log_hint(_("this may take some time; consider using the -c/--fast-checkpoint option")); } } switch (mode) { case pg_basebackup: r = run_basebackup(&local_node_record); break; case barman: r = run_file_backup(&local_node_record); break; default: /* should never reach here */ log_error(_("unknown clone mode")); } /* If the backup failed then exit */ if (r != SUCCESS) { /* If a replication slot was previously created, drop it */ if (config_file_options.use_replication_slots == true) { /* * In the case where a standby is being cloned from a node other than its * intended upstream, We can't be sure of the source node's node_id. This * is only required by "drop_replication_slot_if_exists()" to determine * from the node's record whether it has a different replication user, and * as in this case that would need to be supplied via "--replication-user" * it's not a problem. */ drop_replication_slot_if_exists(source_conn, UNKNOWN_NODE_ID, local_node_record.slot_name); } log_error(_("unable to take a base backup of the source server")); log_hint(_("data directory (\"%s\") may need to be cleaned up manually"), local_data_directory); PQfinish(source_conn); exit(r); } /* * Run pg_verifybackup here if requested, before any alterations are made * to the data directory. */ if (mode == pg_basebackup && runtime_options.verify_backup == true) { PQExpBufferData command; int r; struct stat st; initPQExpBuffer(&command); make_pg_path(&command, "pg_verifybackup"); /* check command actually exists */ if (stat(command.data, &st) != 0) { log_error(_("unable to find expected binary \"%s\""), command.data); log_detail("%s", strerror(errno)); exit(ERR_BAD_CONFIG); } appendPQExpBufferChar(&command, ' '); /* Somewhat inconsistent, but pg_verifybackup doesn't accept a -D option */ appendShellString(&command, local_data_directory); log_debug("executing:\n %s", command.data); r = system(command.data); termPQExpBuffer(&command); if (r != 0) { log_error(_("unable to verify backup")); exit(ERR_BAD_BASEBACKUP); } log_verbose(LOG_INFO, _("backup successfully verified")); } /* * If `--copy-external-config-files` was provided, copy any configuration * files detected to the appropriate location. Any errors encountered will * not be treated as fatal. * * This won't run in Barman mode as "config_files" is only populated in * "initialise_direct_clone()", which isn't called in Barman mode. */ if (runtime_options.copy_external_config_files == true && config_files.entries > 0) { /* * If "--copy-external-config-files=samepath" was used, the files will already * have been copied. */ if (runtime_options.copy_external_config_files_destination == CONFIG_FILE_PGDATA) copy_configuration_files(false); } /* Write the recovery.conf file */ if (create_recovery_file(&local_node_record, &recovery_conninfo, source_server_version_num, local_data_directory, true) == false) { /* create_recovery_file() will log an error */ if (source_server_version_num >= 120000) { log_notice(_("unable to write replication configuration; see preceding error messages")); } else { log_notice(_("unable to create recovery.conf; see preceding error messages")); } log_hint(_("data directory (\"%s\") may need to be cleaned up manually"), local_data_directory); PQfinish(source_conn); exit(ERR_BAD_CONFIG); } switch (mode) { case pg_basebackup: log_notice(_("standby clone (using pg_basebackup) complete")); break; case barman: log_notice(_("standby clone (from Barman) complete")); break; } /* * Do a final check on the data directory permissions - if the user * is cloning into an existing directory set to 0750, and the server * is Pg10 or earlier, Pg will refuse to start. We might not have * known the server version when creating the data directory * (mainly if cloning from Barman with no upstream connection), hence * the additional check here. */ set_dir_permissions(local_data_directory, source_server_version_num); /* * TODO: It might be nice to provide an option to have repmgr start the * PostgreSQL server automatically */ log_notice(_("you can now start your PostgreSQL server")); if (config_file_options.service_start_command[0] != '\0') { log_hint(_("for example: %s"), config_file_options.service_start_command); } else if (local_data_directory_provided) { log_hint(_("for example: pg_ctl -D %s start"), local_data_directory); } else { log_hint(_("for example: /etc/init.d/postgresql start")); } /* * XXX forgetting to (re) register the standby is a frequent cause of * error; we should consider having repmgr automatically register the * standby, either by default with an option "--no-register", or an option * "--register". * * Note that "repmgr standby register" requires the standby to be running * - if not, and we just update the node record, we'd have an incorrect * representation of the replication cluster. Best combined with an * automatic start of the server (see note above) */ /* * Check for an existing node record, and output the appropriate command * for registering or re-registering. */ { t_node_info node_record = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; record_status = get_node_record(primary_conn, config_file_options.node_id, &node_record); if (record_status == RECORD_FOUND) { log_hint(_("after starting the server, you need to re-register this standby with \"repmgr standby register --force\" to update the existing node record")); } else { log_hint(_("after starting the server, you need to register this standby with \"repmgr standby register\"")); } } /* Log the event */ initPQExpBuffer(&event_details); /* Add details about relevant runtime options used */ appendPQExpBuffer(&event_details, _("cloned from host \"%s\", port %s"), runtime_options.host, runtime_options.port); appendPQExpBufferStr(&event_details, _("; backup method: ")); switch (mode) { case pg_basebackup: appendPQExpBufferStr(&event_details, "pg_basebackup"); break; case barman: appendPQExpBufferStr(&event_details, "barman"); break; } appendPQExpBuffer(&event_details, _("; --force: %s"), runtime_options.force ? "Y" : "N"); create_event_notification(primary_conn, &config_file_options, config_file_options.node_id, "standby_clone", true, event_details.data); if (primary_conn != source_conn && PQstatus(primary_conn) == CONNECTION_OK) PQfinish(primary_conn); if (PQstatus(source_conn) == CONNECTION_OK) PQfinish(source_conn); exit(r); } void check_barman_config(void) { PQExpBufferData command; bool command_ok = false; /* * Check that there is at least one valid backup */ log_info(_("connecting to Barman server to verify backup for \"%s\""), config_file_options.barman_server); initPQExpBuffer(&command); appendPQExpBuffer(&command, "%s show-backup %s latest > /dev/null", make_barman_ssh_command(barman_command_buf), config_file_options.barman_server); command_ok = local_command(command.data, NULL); if (command_ok == false) { log_error(_("no valid backup for server \"%s\" was found in the Barman catalogue"), config_file_options.barman_server); log_detail(_("command executed was:\n %s"), command.data), log_hint(_("refer to the Barman documentation for more information")); termPQExpBuffer(&command); exit(ERR_BARMAN); } else if (runtime_options.dry_run == true) { log_info(_("valid backup for server \"%s\" found in the Barman catalogue"), config_file_options.barman_server); } termPQExpBuffer(&command); /* * Attempt to create data directory (unless --dry-run specified, * in which case do nothing; warnings will be emitted elsewhere about * any issues with the data directory) */ if (runtime_options.dry_run == false) { if (!create_pg_dir(local_data_directory, runtime_options.force)) { log_error(_("unable to use directory %s"), local_data_directory); log_hint(_("use -F/--force option to force this directory to be overwritten")); exit(ERR_BAD_CONFIG); } /* * Create the local repmgr subdirectory */ maxlen_snprintf(local_repmgr_tmp_directory, "%s/repmgr", local_data_directory); maxlen_snprintf(datadir_list_filename, "%s/data.txt", local_repmgr_tmp_directory); if (!create_pg_dir(local_repmgr_tmp_directory, runtime_options.force)) { log_error(_("unable to create directory \"%s\""), local_repmgr_tmp_directory); exit(ERR_BAD_CONFIG); } } /* * Fetch server parameters from Barman */ log_info(_("connecting to Barman server to fetch server parameters")); initPQExpBuffer(&command); if (runtime_options.dry_run == true) { appendPQExpBuffer(&command, "%s show-server %s > /dev/null", make_barman_ssh_command(barman_command_buf), config_file_options.barman_server); } else { appendPQExpBuffer(&command, "%s show-server %s > %s/show-server.txt", make_barman_ssh_command(barman_command_buf), config_file_options.barman_server, local_repmgr_tmp_directory); } command_ok = local_command(command.data, NULL); if (command_ok == false) { log_error(_("unable to fetch server parameters from Barman server")); log_detail(_("command executed was:\n %s"), command.data), termPQExpBuffer(&command); exit(ERR_BARMAN); } else if (runtime_options.dry_run == true) { log_info(_("server parameters were successfully fetched from Barman server")); } termPQExpBuffer(&command); } /* * _do_create_replication_conf() * * Create replication configuration for a previously cloned instance. * * Prerequisites: * * - data directory must be provided, either explicitly or via * repmgr.conf * - the instance should not be running * - an existing "recovery.conf" file can only be overwritten with * -F/--force (Pg11 and earlier) * - connection parameters for an existing, running node must be provided * - --upstream-node-id, if provided, will be "primary_conninfo", * otherwise primary node id; node must exist; unless -F/--force * provided, must be active and connection possible * - if replication slots in use, create (respect --dry-run) * * not compatible with --no-upstream-connection * */ static void _do_create_replication_conf(void) { t_node_info local_node_record = T_NODE_INFO_INITIALIZER; t_node_info upstream_node_record = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; char recovery_file_path[MAXPGPATH + sizeof(RECOVERY_COMMAND_FILE)] = ""; struct stat st; bool node_is_running = false; bool slot_creation_required = false; PGconn *upstream_conn = NULL; PGconn *upstream_repl_conn = NULL; get_node_data_directory(local_data_directory); if (local_data_directory[0] == '\0') { log_error(_("no data directory provided")); log_hint(_("provide the node's \"repmgr.conf\" file with -f/--config-file or the data directory with -D/--pgdata")); exit(ERR_BAD_CONFIG); } /* check connection */ source_conn = establish_db_connection_by_params(&source_conninfo, true); /* Verify that source is a supported server version */ (void) check_server_version(source_conn, "source node", true, NULL); /* * Do some sanity checks on the data directory to make sure * it contains a valid but dormant instance */ switch (check_dir(local_data_directory)) { case DIR_ERROR: log_error(_("unable to access specified data directory \"%s\""), local_data_directory); log_detail("%s", strerror(errno)); PQfinish(source_conn); exit(ERR_BAD_CONFIG); break; case DIR_NOENT: log_error(_("specified data directory \"%s\" does not exist"), local_data_directory); PQfinish(source_conn); exit(ERR_BAD_CONFIG); break; case DIR_EMPTY: log_error(_("specified data directory \"%s\" is empty"), local_data_directory); PQfinish(source_conn); exit(ERR_BAD_CONFIG); break; case DIR_NOT_EMPTY: /* Present but not empty */ if (!is_pg_dir(local_data_directory)) { log_error(_("specified data directory \"%s\" does not contain a PostgreSQL instance"), local_data_directory); PQfinish(source_conn); exit(ERR_BAD_CONFIG); } if (is_pg_running(local_data_directory)) { if (runtime_options.force == false) { log_error(_("specified data directory \"%s\" appears to contain a running PostgreSQL instance"), local_data_directory); if (PQserverVersion(source_conn) >= 120000) { log_hint(_("use -F/--force to create replication configuration anyway")); } else { log_hint(_("use -F/--force to create \"recovery.conf\" anyway")); } exit(ERR_BAD_CONFIG); } node_is_running = true; if (runtime_options.dry_run == true) { if (PQserverVersion(source_conn) >= 120000) { log_warning(_("replication configuration would be created in an active data directory")); } else { log_warning(_("\"recovery.conf\" would be created in an active data directory")); } } else { if (PQserverVersion(source_conn) >= 120000) { log_warning(_("creating replication configuration in an active data directory")); } else { log_warning(_("creating \"recovery.conf\" in an active data directory")); } } } break; default: break; } /* determine node for primary_conninfo */ if (runtime_options.upstream_node_id != UNKNOWN_NODE_ID) { upstream_node_id = runtime_options.upstream_node_id; } else { /* if --upstream-node-id not specifically supplied, get primary node id */ upstream_node_id = get_primary_node_id(source_conn); if (upstream_node_id == NODE_NOT_FOUND) { log_error(_("unable to determine primary node for this replication cluster")); PQfinish(source_conn); exit(ERR_BAD_CONFIG); } log_debug("primary node determined as: %i", upstream_node_id); } /* attempt to retrieve upstream node record */ record_status = get_node_record(source_conn, upstream_node_id, &upstream_node_record); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve node record for upstream node %i"), upstream_node_id); if (record_status == RECORD_ERROR) { log_detail("%s", PQerrorMessage(source_conn)); } exit(ERR_BAD_CONFIG); } /* attempt to retrieve local node record */ record_status = get_node_record(source_conn, config_file_options.node_id, &local_node_record); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve node record for local node %i"), config_file_options.node_id); if (record_status == RECORD_ERROR) { log_detail("%s", PQerrorMessage(source_conn)); } else { log_hint(_("standby must be registered before replication can be configured")); } exit(ERR_BAD_CONFIG); } PQfinish(source_conn); /* connect to upstream (which could be different to source) */ upstream_conn = establish_db_connection(upstream_node_record.conninfo, false); if (PQstatus(upstream_conn) != CONNECTION_OK) { log_error(_("unable to connect to upstream node \"%s\" (ID: %i)"), upstream_node_record.node_name, upstream_node_id); exit(ERR_BAD_CONFIG); } /* Set the application name to this node's name */ if (config_file_options.node_name[0] != '\0') param_set(&recovery_conninfo, "application_name", config_file_options.node_name); /* Set the replication user from the primary node record */ param_set(&recovery_conninfo, "user", upstream_node_record.repluser); initialize_conninfo_params(&recovery_conninfo, false); /* We ignore any application_name set in the primary's conninfo */ parse_conninfo_string(upstream_node_record.conninfo, &recovery_conninfo, NULL, true); /* check that a replication connection can be made (--force = override) */ upstream_repl_conn = establish_db_connection_by_params(&recovery_conninfo, false); if (PQstatus(upstream_repl_conn) != CONNECTION_OK) { if (runtime_options.force == false) { log_error(_("unable to initiate replication connection to upstream node \"%s\" (ID: %i)"), upstream_node_record.node_name, upstream_node_id); PQfinish(upstream_conn); exit(ERR_BAD_CONFIG); } } /* if replication slots are in use, perform some checks */ if (config_file_options.use_replication_slots == true) { PQExpBufferData msg; t_replication_slot slot_info = T_REPLICATION_SLOT_INITIALIZER; /* * Check the node record has slot_name set; if not we'll need to * update it. */ if (local_node_record.slot_name[0] == '\0') { PGconn *primary_conn = NULL; create_slot_name(local_node_record.slot_name, local_node_record.node_id); /* Check we can connect to the primary so we can update the record */ if (get_recovery_type(upstream_conn) == RECTYPE_PRIMARY) { primary_conn = upstream_conn; } else { primary_conn = establish_primary_db_connection(upstream_conn, false); if (primary_conn == NULL) { log_error(_("unable to connect to primary to update slot name for node \"%s\" (ID: %i)"), local_node_record.node_name, local_node_record.node_id); PQfinish(upstream_conn); termPQExpBuffer(&msg); exit(ERR_BAD_CONFIG); } } if (runtime_options.dry_run == true) { log_info(_("would set \"slot_name\" for node \"%s\" (ID: %i) to \"%s\""), local_node_record.node_name, local_node_record.node_id, local_node_record.slot_name); } else { bool success = update_node_record_slot_name(primary_conn, local_node_record.node_id, local_node_record.slot_name); if (primary_conn != upstream_conn) PQfinish(primary_conn); if (success == false) { log_error(_("unable to update slot name for node \"%s\" (ID: %i)"), local_node_record.node_name, local_node_record.node_id); PQfinish(upstream_conn); exit(ERR_BAD_CONFIG); } } } record_status = get_slot_record(upstream_conn, local_node_record.slot_name, &slot_info); /* check if replication slot exists*/ if (record_status == RECORD_FOUND) { if (slot_info.active == true) { initPQExpBuffer(&msg); appendPQExpBuffer(&msg, _("an active replication slot named \"%s\" already exists on upstream node \"%s\" (ID: %i)"), local_node_record.slot_name, upstream_node_record.node_name, upstream_node_id); if (runtime_options.force == false && runtime_options.dry_run == false) { log_error("%s", msg.data); log_hint(_("use -F/--force to continue anyway")); termPQExpBuffer(&msg); PQfinish(upstream_conn); exit(ERR_BAD_CONFIG); } log_warning("%s", msg.data); termPQExpBuffer(&msg); } else { log_info(_("an inactive replication slot for this node exists on the upstream node")); } } /* if not, if check one can and should be created */ else { get_node_replication_stats(upstream_conn, &upstream_node_record); if (upstream_node_record.max_replication_slots > upstream_node_record.total_replication_slots) { slot_creation_required = true; } else { initPQExpBuffer(&msg); appendPQExpBuffer(&msg, _("insufficient free replication slots on upstream node \"%s\" (ID: %i)"), upstream_node_record.node_name, upstream_node_id); if (runtime_options.force == false && runtime_options.dry_run == false) { log_error("%s", msg.data); log_hint(_("use -F/--force to continue anyway")); termPQExpBuffer(&msg); PQfinish(upstream_conn); exit(ERR_BAD_CONFIG); } log_warning("%s", msg.data); termPQExpBuffer(&msg); } } } /* check if recovery.conf exists (Pg11 and earlier only) */ if (PQserverVersion(upstream_conn) < 120000) { snprintf(recovery_file_path, sizeof(recovery_file_path), "%s/%s", local_data_directory, RECOVERY_COMMAND_FILE); if (stat(recovery_file_path, &st) == -1) { if (errno != ENOENT) { log_error(_("unable to check for existing \"recovery.conf\" file in \"%s\""), local_data_directory); log_detail("%s", strerror(errno)); exit(ERR_BAD_CONFIG); } } else { if (runtime_options.force == false) { log_error(_("\"recovery.conf\" already exists in \"%s\""), local_data_directory); log_hint(_("use -F/--force to overwrite an existing \"recovery.conf\" file")); exit(ERR_BAD_CONFIG); } if (runtime_options.dry_run == true) { log_warning(_("the existing \"recovery.conf\" file would be overwritten")); } else { log_warning(_("the existing \"recovery.conf\" file will be overwritten")); } } } if (runtime_options.dry_run == true) { char recovery_conf_contents[MAXLEN] = ""; create_recovery_file(&local_node_record, &recovery_conninfo, PQserverVersion(upstream_conn), recovery_conf_contents, false); if (PQserverVersion(upstream_conn) >= 120000) { log_info(_("following items would be added to \"postgresql.auto.conf\" in \"%s\""), local_data_directory); } else { log_info(_("would create \"recovery.conf\" file in \"%s\""), local_data_directory); } log_detail(_("\n%s"), recovery_conf_contents); } else { if (!create_recovery_file(&local_node_record, &recovery_conninfo, PQserverVersion(upstream_conn), local_data_directory, true)) { if (PQserverVersion(upstream_conn) >= 120000) { log_error(_("unable to write replication configuration to \"postgresql.auto.conf\"")); } else { log_error(_("unable to create \"recovery.conf\"")); } } else { if (PQserverVersion(upstream_conn) >= 120000) { log_notice(_("replication configuration written to \"postgresql.auto.conf\"")); } else { log_notice(_("\"recovery.conf\" created as \"%s\""), recovery_file_path); } if (node_is_running == true) { if (PQserverVersion(upstream_conn) >= 130000) { log_hint(_("configuration must be reloaded for the configuration changes to take effect")); } else if (PQserverVersion(upstream_conn) >= 120000) { log_hint(_("node must be restarted for the configuration changes to take effect")); } else { log_hint(_("node must be restarted for the new file to take effect")); } } } } /* Pg12 and later: add standby.signal, if not already there */ if (PQserverVersion(upstream_conn) >= 120000) { if (runtime_options.dry_run == true) { log_info(_("would write \"standby.signal\" file")); } else { if (write_standby_signal(local_data_directory) == false) { log_error(_("unable to write \"standby.signal\" file")); } } } /* add replication slot, if required */ if (slot_creation_required == true) { PQExpBufferData msg; initPQExpBuffer(&msg); if (runtime_options.dry_run == true) { /* * In --dry-run mode this will check availability * of a user who can create replication slots. */ // XXX check return value create_replication_slot(upstream_conn, local_node_record.slot_name, NULL, &msg); log_info(_("would create replication slot \"%s\" on upstream node \"%s\" (ID: %i)"), local_node_record.slot_name, upstream_node_record.node_name, upstream_node_id); } else { if (create_replication_slot(upstream_conn, local_node_record.slot_name, NULL, &msg) == false) { log_error("%s", msg.data); PQfinish(upstream_conn); termPQExpBuffer(&msg); exit(ERR_BAD_CONFIG); } log_notice(_("replication slot \"%s\" created on upstream node \"%s\" (ID: %i)"), local_node_record.slot_name, upstream_node_record.node_name, upstream_node_id); } termPQExpBuffer(&msg); } PQfinish(upstream_conn); return; } /* * do_standby_register() * * Event(s): * - standby_register * - standby_register_sync */ /* XXX check --upstream-node-id works when re-registering */ void do_standby_register(void) { PGconn *conn = NULL; PGconn *primary_conn = NULL; bool record_created = false; t_node_info node_record = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; PQExpBufferData details; /* so we can pass info about the primary to event notification scripts */ t_event_info event_info = T_EVENT_INFO_INITIALIZER; t_node_info primary_node_record = T_NODE_INFO_INITIALIZER; int primary_node_id = UNKNOWN_NODE_ID; bool dry_run_ok = true; log_info(_("connecting to local node \"%s\" (ID: %i)"), config_file_options.node_name, config_file_options.node_id); conn = establish_db_connection_quiet(config_file_options.conninfo); /* * If unable to connect, and --force not provided, wait up to --wait-start * seconds (default: 0) for the node to become reachable. * * Not that if --force provided, we don't wait for the node to start, as * the normal use case will be re-registering an existing node, or * registering an inactive/not-yet-extant one; we'll do the * error handling for those cases in the next code block */ if (PQstatus(conn) != CONNECTION_OK && runtime_options.force == false) { bool conn_ok = false; int timer = 0; for (;;) { if (timer == runtime_options.wait_start) break; sleep(1); log_verbose(LOG_INFO, _("%i of %i connection attempts"), timer + 1, runtime_options.wait_start); conn = establish_db_connection_quiet(config_file_options.conninfo); if (PQstatus(conn) == CONNECTION_OK) { conn_ok = true; break; } timer++; } if (conn_ok == true) { log_info(_("connected to local node \"%s\" (ID: %i) after %i seconds"), config_file_options.node_name, config_file_options.node_id, timer); } } /* * If still unable to connect, continue only if -F/--force provided, * and primary connection parameters provided. */ if (PQstatus(conn) != CONNECTION_OK) { if (runtime_options.force == false) { log_error(_("unable to connect to local node \"%s\" (ID: %i)"), config_file_options.node_name, config_file_options.node_id); log_detail("\n%s", PQerrorMessage(conn)); log_hint(_("to register a standby which is not running, provide primary connection parameters and use option -F/--force")); exit(ERR_BAD_CONFIG); } if (runtime_options.connection_param_provided == false) { log_error(_("unable to connect to local node \"%s\" (ID: %i)"), config_file_options.node_name, config_file_options.node_id); log_hint(_("to register a standby which is not running, additionally provide the primary connection parameters")); exit(ERR_BAD_CONFIG); } } /* connection OK - check this is actually a standby */ else { if (runtime_options.connection_param_provided) { log_warning(_("database connection parameters not required when the standby to be registered is running")); log_detail(_("repmgr uses the \"conninfo\" parameter in \"repmgr.conf\" to connect to the standby")); } check_recovery_type(conn); } /* check if there is a primary in this cluster */ log_info(_("connecting to primary database")); /* Normal case - we can connect to the local node */ if (PQstatus(conn) == CONNECTION_OK) { primary_conn = get_primary_connection(conn, &primary_node_id, NULL); } /* * otherwise user is forcing a registration of a (potentially) inactive (or * not-yet-extant) node and must have supplied primary connection info */ else { primary_conn = establish_db_connection_by_params(&source_conninfo, false); } /* * no amount of --force will make it possible to register the standby * without a primary server to connect to */ if (PQstatus(primary_conn) != CONNECTION_OK) { log_error(_("unable to connect to the primary database")); log_hint(_("a primary node must be configured before registering a standby node")); exit(ERR_BAD_CONFIG); } /* * Populate "event_info" with info about the primary for event notifications */ record_status = get_node_record(primary_conn, primary_node_id, &primary_node_record); event_info.node_id = primary_node_id; event_info.node_name = primary_node_record.node_name; event_info.conninfo_str = primary_node_record.conninfo; /* * Verify that standby and primary are supported and compatible server * versions * * If the user is registering an inactive standby, we'll trust they know * what they're doing */ if (PQstatus(conn) == CONNECTION_OK) { check_primary_standby_version_match(conn, primary_conn); } /* * Check that an active node with the same node_name doesn't exist already */ record_status = get_node_record_by_name(primary_conn, config_file_options.node_name, &node_record); if (record_status == RECORD_FOUND) { if (node_record.active == true && node_record.node_id != config_file_options.node_id) { log_error(_("node %i exists already with node_name \"%s\""), node_record.node_id, config_file_options.node_name); PQfinish(primary_conn); if (PQstatus(conn) == CONNECTION_OK) PQfinish(conn); exit(ERR_BAD_CONFIG); } } /* Check if node record exists */ record_status = get_node_record(primary_conn, config_file_options.node_id, &node_record); if (record_status == RECORD_FOUND && !runtime_options.force) { log_error(_("node %i is already registered"), config_file_options.node_id); log_hint(_("use option -F/--force to overwrite an existing node record")); PQfinish(primary_conn); if (PQstatus(conn) == CONNECTION_OK) PQfinish(conn); exit(ERR_BAD_CONFIG); } /* * If an upstream node is defined, check if that node exists and is active. * * If it doesn't exist, and --force set, create a minimal inactive record, * in the assumption that the user knows what they are doing (usually some kind * of provisioning where multiple servers are created in parallel) and will * create the active record later. */ if (runtime_options.upstream_node_id != NO_UPSTREAM_NODE) { RecordStatus upstream_record_status = RECORD_NOT_FOUND; t_node_info upstream_node_record = T_NODE_INFO_INITIALIZER; if (runtime_options.upstream_node_id == config_file_options.node_id) { log_error(_("provided node ID for --upstream-node-id (%i) is the same as the configured local node ID (%i)"), runtime_options.upstream_node_id, config_file_options.node_id); PQfinish(primary_conn); if (PQstatus(conn) == CONNECTION_OK) PQfinish(conn); exit(ERR_BAD_CONFIG); } upstream_record_status = get_node_record(primary_conn, runtime_options.upstream_node_id, &upstream_node_record); /* create placeholder upstream record if -F/--force set */ if (upstream_record_status != RECORD_FOUND) { t_node_info placeholder_upstream_node_record = T_NODE_INFO_INITIALIZER; if (!runtime_options.force) { log_error(_("no record found for upstream node %i"), runtime_options.upstream_node_id); /* footgun alert - only do this if you know what you're doing */ log_hint(_("use option -F/--force to create a dummy upstream record")); PQfinish(primary_conn); if (PQstatus(conn) == CONNECTION_OK) PQfinish(conn); exit(ERR_BAD_CONFIG); } log_notice(_("creating placeholder record for upstream node %i"), runtime_options.upstream_node_id); placeholder_upstream_node_record.node_id = runtime_options.upstream_node_id; placeholder_upstream_node_record.type = STANDBY; placeholder_upstream_node_record.upstream_node_id = NO_UPSTREAM_NODE; strncpy(placeholder_upstream_node_record.conninfo, runtime_options.upstream_conninfo, MAXLEN); placeholder_upstream_node_record.active = false; record_created = create_node_record(primary_conn, "standby register", &placeholder_upstream_node_record); /* * It's possible, in the kind of scenario this functionality is * intended to support, that there's a race condition where the * node's actual record gets inserted, causing the insert of the * placeholder record to fail. If this is the case, we don't worry * about this insert failing; if not we bail out. * * TODO: teach create_node_record() to use ON CONFLICT DO NOTHING * for 9.5 and later. */ if (record_created == false) { upstream_record_status = get_node_record(primary_conn, runtime_options.upstream_node_id, &placeholder_upstream_node_record); if (upstream_record_status != RECORD_FOUND) { log_error(_("unable to create placeholder record for upstream node %i"), runtime_options.upstream_node_id); PQfinish(primary_conn); if (PQstatus(conn) == CONNECTION_OK) PQfinish(conn); exit(ERR_BAD_CONFIG); } log_info(_("a record for upstream node %i was already created"), runtime_options.upstream_node_id); } } else if (node_record.active == false) { /* * upstream node is inactive and --force not supplied - refuse to * register */ if (!runtime_options.force) { log_error(_("record for upstream node %i is marked as inactive"), runtime_options.upstream_node_id); log_hint(_("use option -F/--force to register a standby with an inactive upstream node")); PQfinish(primary_conn); if (PQstatus(conn) == CONNECTION_OK) PQfinish(conn); exit(ERR_BAD_CONFIG); } /* * user is using the --force - notify about the potential footgun */ log_notice(_("registering node %i with inactive upstream node %i"), config_file_options.node_id, runtime_options.upstream_node_id); } /* check upstream node is accessible and this node is connected */ else { PGconn *upstream_conn = NULL; upstream_conn = establish_db_connection(upstream_node_record.conninfo, false); if (PQstatus(upstream_conn) != CONNECTION_OK) { if (!runtime_options.force) { log_error(_("unable to connect to upstream node \"%s\" (ID: %i)"), upstream_node_record.node_name, upstream_node_record.node_id); log_hint(_("use -F/--force to continue anyway")); PQfinish(primary_conn); if (PQstatus(conn) == CONNECTION_OK) PQfinish(conn); exit(ERR_BAD_CONFIG); } log_warning(_("unable to connect to upstream node \"%s\" (ID: %i) but continuing anyway"), upstream_node_record.node_name, upstream_node_record.node_id); } else { /* check our standby is connected */ if (is_downstream_node_attached(upstream_conn, config_file_options.node_name, NULL) == NODE_ATTACHED) { log_verbose(LOG_INFO, _("local node is attached to specified upstream node %i"), runtime_options.upstream_node_id); } else { if (!runtime_options.force) { log_error(_("this node does not appear to be attached to upstream node \"%s\" (ID: %i)"), upstream_node_record.node_name, upstream_node_record.node_id); log_detail(_("no record for application name \"%s\" found in \"pg_stat_replication\""), config_file_options.node_name); log_hint(_("use -F/--force to continue anyway")); PQfinish(primary_conn); if (PQstatus(conn) == CONNECTION_OK) PQfinish(conn); exit(ERR_BAD_CONFIG); } log_warning(_("this node does not appear to be attached to upstream node \"%s\" (ID: %i)"), upstream_node_record.node_name, upstream_node_record.node_id); } PQfinish(upstream_conn); } } } /* * populate node record structure with current values set in repmgr.conf * and/or the command line (this will overwrite any existing values, which * is what we want when updating the record) */ init_node_record(&node_record); node_record.type = STANDBY; /* if --upstream-node-id not provided, set to primary node id */ if (node_record.upstream_node_id == UNKNOWN_NODE_ID) { node_record.upstream_node_id = primary_node_id; } /* * If --upstream-node-id not provided, we're defaulting to the primary as * upstream node. If local node is available, double-check that it's attached * to the primary, in case --upstream-node-id was an accidental omission. * * Currently we'll only do this for newly registered nodes. */ if (runtime_options.upstream_node_id == NO_UPSTREAM_NODE && PQstatus(conn) == CONNECTION_OK) { /* only do this if record does not exist */ if (record_status != RECORD_FOUND) { log_warning(_("--upstream-node-id not supplied, assuming upstream node is primary (node ID: %i)"), primary_node_id); /* check our standby is connected */ if (is_downstream_node_attached(primary_conn, config_file_options.node_name, NULL) == NODE_ATTACHED) { log_verbose(LOG_INFO, _("local node is attached to primary")); } else if (runtime_options.force == false) { log_error(_("local node not attached to primary node %i"), primary_node_id); /* TODO: 9.6 and later, display detail from pg_stat_wal_receiver */ log_hint(_("specify the actual upstream node id with --upstream-node-id, or use -F/--force to continue anyway")); if (runtime_options.dry_run == true) { dry_run_ok = false; } else { PQfinish(primary_conn); PQfinish(conn); exit(ERR_BAD_CONFIG); } } else { log_warning(_("local node not attached to primary node %i"), primary_node_id); log_notice(_("-F/--force supplied, continuing anyway")); } } } if (runtime_options.dry_run == true) { PQfinish(primary_conn); if (PQstatus(conn) == CONNECTION_OK) PQfinish(conn); if (dry_run_ok == false) { log_warning(_("issue(s) encountered; see preceding log messages")); exit(ERR_BAD_CONFIG); } log_info(_("all prerequisites for \"standby register\" are met")); exit(SUCCESS); } /* * node record exists - update it (at this point we have already * established that -F/--force is in use) */ if (record_status == RECORD_FOUND) { record_created = update_node_record(primary_conn, "standby register", &node_record); } else { record_created = create_node_record(primary_conn, "standby register", &node_record); } initPQExpBuffer(&details); if (record_created == false) { appendPQExpBuffer(&details, _("standby registration failed; provided upstream node ID was %i"), node_record.upstream_node_id); if (runtime_options.force == true) appendPQExpBufferStr(&details, _(" (-F/--force option was used)")); create_event_notification_extended( primary_conn, &config_file_options, config_file_options.node_id, "standby_register", false, details.data, &event_info); termPQExpBuffer(&details); PQfinish(primary_conn); primary_conn = NULL; if (PQstatus(conn) == CONNECTION_OK) PQfinish(conn); exit(ERR_BAD_CONFIG); } appendPQExpBuffer(&details, _("standby registration succeeded; upstream node ID is %i"), node_record.upstream_node_id); if (runtime_options.force == true) appendPQExpBufferStr(&details, _(" (-F/--force option was used)")); /* Log the event */ create_event_notification_extended( primary_conn, &config_file_options, config_file_options.node_id, "standby_register", true, details.data, &event_info); termPQExpBuffer(&details); /* * If --wait-sync option set, wait for the records to synchronise * (unless 0 seconds provided, which disables it, which is the same as * not providing the option). The default value is -1, which means * no timeout. */ if (PQstatus(conn) == CONNECTION_OK && runtime_options.wait_register_sync == true && runtime_options.wait_register_sync_seconds != 0) { bool sync_ok = false; int timer = 0; RecordStatus node_record_status = RECORD_NOT_FOUND; t_node_info node_record_on_primary = T_NODE_INFO_INITIALIZER; t_node_info node_record_on_standby = T_NODE_INFO_INITIALIZER; node_record_status = get_node_record(primary_conn, config_file_options.node_id, &node_record_on_primary); if (node_record_status != RECORD_FOUND) { log_error(_("unable to retrieve node record from primary")); PQfinish(primary_conn); PQfinish(conn); exit(ERR_REGISTRATION_SYNC); } for (;;) { bool records_match = true; /* * If timeout set to a positive value, check if we've reached it and * exit the loop */ if (runtime_options.wait_register_sync_seconds > 0 && runtime_options.wait_register_sync_seconds == timer) break; node_record_status = get_node_record(conn, config_file_options.node_id, &node_record_on_standby); if (node_record_status == RECORD_NOT_FOUND) { /* no record available yet on standby */ records_match = false; } else if (node_record_status == RECORD_FOUND) { /* compare relevant fields */ if (node_record_on_standby.upstream_node_id != node_record_on_primary.upstream_node_id) records_match = false; if (node_record_on_standby.type != node_record_on_primary.type) records_match = false; if (node_record_on_standby.priority != node_record_on_primary.priority) records_match = false; if (strcmp(node_record_on_standby.location, node_record_on_primary.location) != 0) records_match = false; if (node_record_on_standby.active != node_record_on_primary.active) records_match = false; if (strcmp(node_record_on_standby.node_name, node_record_on_primary.node_name) != 0) records_match = false; if (strcmp(node_record_on_standby.conninfo, node_record_on_primary.conninfo) != 0) records_match = false; if (strcmp(node_record_on_standby.slot_name, node_record_on_primary.slot_name) != 0) records_match = false; if (records_match == true) { sync_ok = true; break; } } sleep(1); timer++; } /* Log the event */ initPQExpBuffer(&details); if (sync_ok == false) { appendPQExpBuffer(&details, _("node record was not synchronised after %i seconds"), runtime_options.wait_register_sync_seconds); } else { appendPQExpBuffer(&details, _("node record synchronised after %i seconds"), timer); } create_event_notification_extended( primary_conn, &config_file_options, config_file_options.node_id, "standby_register_sync", sync_ok, details.data, &event_info); if (sync_ok == false) { log_error("%s", details.data); termPQExpBuffer(&details); PQfinish(primary_conn); PQfinish(conn); exit(ERR_REGISTRATION_SYNC); } log_info(_("node record on standby synchronised from primary")); log_detail("%s", details.data); termPQExpBuffer(&details); } PQfinish(primary_conn); if (PQstatus(conn) == CONNECTION_OK) PQfinish(conn); log_info(_("standby registration complete")); log_notice(_("standby node \"%s\" (ID: %i) successfully registered"), config_file_options.node_name, config_file_options.node_id); return; } /* * do_standby_unregister() * * Event(s): * - standby_unregister */ void do_standby_unregister(void) { PGconn *conn = NULL; PGconn *primary_conn = NULL; int target_node_id = UNKNOWN_NODE_ID; t_node_info node_info = T_NODE_INFO_INITIALIZER; bool node_record_deleted = false; log_info(_("connecting to local standby")); conn = establish_db_connection(config_file_options.conninfo, true); /* check if there is a primary in this cluster */ log_info(_("connecting to primary database")); primary_conn = get_primary_connection(conn, NULL, NULL); if (PQstatus(primary_conn) != CONNECTION_OK) { log_error(_("unable to connect to primary server")); log_detail("\n%s", PQerrorMessage(conn)); exit(ERR_BAD_CONFIG); } /* * if --node-id was specified, unregister that node rather than the * current one - this enables inactive nodes to be unregistered. */ if (runtime_options.node_id != UNKNOWN_NODE_ID) target_node_id = runtime_options.node_id; else target_node_id = config_file_options.node_id; /* Check node exists and is really a standby */ if (get_node_record(primary_conn, target_node_id, &node_info) != RECORD_FOUND) { log_error(_("no record found for node %i"), target_node_id); PQfinish(primary_conn); PQfinish(conn); exit(ERR_BAD_CONFIG); } if (node_info.type != STANDBY) { log_error(_("node %i is not a standby server"), target_node_id); PQfinish(primary_conn); PQfinish(conn); exit(ERR_BAD_CONFIG); } /* Now unregister the standby */ log_notice(_("unregistering node %i"), target_node_id); node_record_deleted = delete_node_record(primary_conn, target_node_id); if (node_record_deleted == false) { PQfinish(primary_conn); PQfinish(conn); exit(ERR_BAD_CONFIG); } /* Log the event */ create_event_notification(primary_conn, &config_file_options, target_node_id, "standby_unregister", true, NULL); PQfinish(primary_conn); PQfinish(conn); log_info(_("standby unregistration complete")); return; } /* * do_standby_promote() * * Event(s): * - standby_promote */ void do_standby_promote(void) { PGconn *local_conn = NULL; PGconn *current_primary_conn = NULL; RecoveryType recovery_type = RECTYPE_UNKNOWN; int existing_primary_id = UNKNOWN_NODE_ID; RecordStatus record_status = RECORD_NOT_FOUND; t_node_info local_node_record = T_NODE_INFO_INITIALIZER; NodeInfoList sibling_nodes = T_NODE_INFO_LIST_INITIALIZER; SiblingNodeStats sibling_nodes_stats = T_SIBLING_NODES_STATS_INITIALIZER; int available_wal_senders = 0; bool dry_run_success = true; local_conn = establish_db_connection(config_file_options.conninfo, true); log_verbose(LOG_INFO, _("connected to standby, checking its state")); /* Verify that standby is a supported server version */ (void) check_server_version(local_conn, "standby", true, NULL); /* Check we are in a standby node */ recovery_type = get_recovery_type(local_conn); if (recovery_type != RECTYPE_STANDBY) { if (recovery_type == RECTYPE_PRIMARY) { log_error(_("STANDBY PROMOTE can only be executed on a standby node")); PQfinish(local_conn); exit(ERR_PROMOTION_FAIL); } else { log_error(_("unable to determine node's recovery state")); PQfinish(local_conn); exit(ERR_DB_CONN); } } else if (runtime_options.dry_run == true) { log_info(_("node is a standby")); } record_status = get_node_record(local_conn, config_file_options.node_id, &local_node_record); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve node record for node %i"), config_file_options.node_id); PQfinish(local_conn); exit(ERR_DB_QUERY); } /* * In PostgreSQL 12 and earlier, executing "pg_ctl ... promote" when WAL * replay is paused and WAL is pending replay will mean the standby will * not promote until replay is resumed. * * As that could happen at any time outside repmgr's control, we * need to avoid leaving a "ticking timebomb" which might cause * an unexpected status change in the replication cluster. */ if (PQserverVersion(local_conn) < 130000) { ReplInfo replication_info; bool replay_paused = false; init_replication_info(&replication_info); if (get_replication_info(local_conn, STANDBY, &replication_info) == false) { log_error(_("unable to retrieve replication information from local node")); PQfinish(local_conn); exit(ERR_PROMOTION_FAIL); } /* * If the local node is recovering from archive, we can't tell * whether there's still WAL which needs to be replayed, so * we'll abort if WAL replay is paused. */ if (replication_info.receiving_streamed_wal == false) { /* just a simple check for paused WAL replay */ replay_paused = is_wal_replay_paused(local_conn, false); if (replay_paused == true) { log_error(_("WAL replay is paused on this node")); log_detail(_("node is in archive recovery and is not safe to promote in this state")); log_detail(_("replay paused at %X/%X"), format_lsn(replication_info.last_wal_replay_lsn)); } } else { /* check that replay is pause *and* WAL is pending replay */ replay_paused = is_wal_replay_paused(local_conn, true); if (replay_paused == true) { log_error(_("WAL replay is paused on this node but not all WAL has been replayed")); log_detail(_("replay paused at %X/%X; last WAL received is %X/%X"), format_lsn(replication_info.last_wal_replay_lsn), format_lsn(replication_info.last_wal_receive_lsn)); } } if (replay_paused == true) { if (PQserverVersion(local_conn) >= 100000) log_hint(_("execute \"pg_wal_replay_resume()\" to unpause WAL replay")); else log_hint(_("execute \"pg_xlog_replay_resume()\" to npause WAL replay")); PQfinish(local_conn); exit(ERR_PROMOTION_FAIL); } } /* check that there's no existing primary */ current_primary_conn = get_primary_connection_quiet(local_conn, &existing_primary_id, NULL); if (PQstatus(current_primary_conn) == CONNECTION_OK) { log_error(_("this replication cluster already has an active primary server")); if (existing_primary_id != UNKNOWN_NODE_ID) { t_node_info primary_rec; get_node_record(local_conn, existing_primary_id, &primary_rec); log_detail(_("current primary is \"%s\" (ID: %i)"), primary_rec.node_name, existing_primary_id); } PQfinish(current_primary_conn); PQfinish(local_conn); exit(ERR_PROMOTION_FAIL); } else if (runtime_options.dry_run == true) { log_info(_("no active primary server found in this replication cluster")); } PQfinish(current_primary_conn); /* * populate local node record with current state of various replication-related * values, so we can check for sufficient walsenders and replication slots */ get_node_replication_stats(local_conn, &local_node_record); available_wal_senders = local_node_record.max_wal_senders - local_node_record.attached_wal_receivers; /* * Get list of sibling nodes; if --siblings-follow specified, * check they're reachable; if not, the list will be used to warn * about nodes which will not follow the new primary */ get_active_sibling_node_records(local_conn, local_node_record.node_id, local_node_record.upstream_node_id, &sibling_nodes); if (check_sibling_nodes(&sibling_nodes, &sibling_nodes_stats) == false) { PQfinish(local_conn); exit(ERR_BAD_CONFIG); } /* * check there are sufficient free walsenders - obviously there's potential * for a later race condition if some walsenders come into use before the * promote operation gets around to attaching the sibling nodes, but * this should catch any actual existing configuration issue (and if anyone's * performing a promote in such an unstable environment, they only have * themselves to blame). */ if (check_free_wal_senders(available_wal_senders, &sibling_nodes_stats, &dry_run_success) == false) { if (runtime_options.dry_run == false || runtime_options.force == false) { PQfinish(local_conn); exit(ERR_BAD_CONFIG); } } /* * if replication slots are required by siblings, * check the promotion candidate has sufficient free slots */ if (check_free_slots(&local_node_record, &sibling_nodes_stats, &dry_run_success) == false) { if (runtime_options.dry_run == false || runtime_options.force == false) { PQfinish(local_conn); exit(ERR_BAD_CONFIG); } } /* * In --dry-run mode, note which promotion method will be used. * For Pg12 and later, check whether pg_promote() can be executed. */ if (runtime_options.dry_run == true) { if (config_file_options.service_promote_command[0] != '\0') { log_info(_("node will be promoted using command defined in \"service_promote_command\"")); log_detail(_("\"service_promote_command\" is \"%s\""), config_file_options.service_promote_command); } else if (PQserverVersion(local_conn) >= 120000) { if (can_execute_pg_promote(local_conn) == false) { log_info(_("node will be promoted using \"pg_ctl promote\"")); log_detail(_("user \"%s\" does not have permission to execute \"pg_promote()\""), PQuser(local_conn)); } else { log_info(_("node will be promoted using the \"pg_promote()\" function")); } } else { log_info(_("node will be promoted using \"pg_ctl promote\"")); } } if (runtime_options.dry_run == true) { PQfinish(local_conn); if (dry_run_success == false) { log_error(_("prerequisites for executing STANDBY PROMOTE are *not* met")); log_hint(_("see preceding error messages")); exit(ERR_BAD_CONFIG); } log_info(_("prerequisites for executing STANDBY PROMOTE are met")); exit(SUCCESS); } _do_standby_promote_internal(local_conn); /* * If --siblings-follow specified, attempt to make them follow the new * primary */ if (runtime_options.siblings_follow == true && sibling_nodes.node_count > 0) { sibling_nodes_follow(&local_node_record, &sibling_nodes, &sibling_nodes_stats); } clear_node_info_list(&sibling_nodes); return; } static void _do_standby_promote_internal(PGconn *conn) { int i; bool promote_success = false; PQExpBufferData details; RecoveryType recovery_type = RECTYPE_UNKNOWN; t_node_info local_node_record = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; char data_dir[MAXPGPATH]; get_node_config_directory(data_dir); /* fetch local node record so we can add detail in log messages */ record_status = get_node_record(conn, config_file_options.node_id, &local_node_record); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve record for node %i"), config_file_options.node_id); PQfinish(conn); exit(ERR_BAD_CONFIG); } /* * Promote standby to primary. * * "pg_ctl promote: returns immediately and (prior to 10.0) has no -w * option so we can't be sure when or if the promotion completes. For now * we'll poll the server until the default timeout (60 seconds) * * For PostgreSQL 12+, use the pg_promote() function, unless one of * "service_promote_command" or "use_pg_ctl_promote" is set. */ { bool use_pg_promote = false; if (PQserverVersion(conn) >= 120000) { use_pg_promote = true; if (config_file_options.service_promote_command[0] != '\0') { use_pg_promote = false; } else if (can_execute_pg_promote(conn) == false) { use_pg_promote = false; log_info(_("user \"%s\" does not have permission to execute \"pg_promote()\", falling back to \"pg_ctl promote\""), PQuser(conn)); } } log_notice(_("promoting standby to primary")); if (use_pg_promote == true) { log_detail(_("promoting server \"%s\" (ID: %i) using pg_promote()"), local_node_record.node_name, local_node_record.node_id); /* * We'll check for promotion success ourselves, but will abort * if some unrecoverable error prevented the function from being * executed. */ if (!promote_standby(conn, false, 0)) { log_error(_("unable to promote server from standby to primary")); exit(ERR_PROMOTION_FAIL); } } else { char script[MAXLEN]; int r; get_server_action(ACTION_PROMOTE, script, (char *) data_dir); log_detail(_("promoting server \"%s\" (ID: %i) using \"%s\""), local_node_record.node_name, local_node_record.node_id, script); r = system(script); if (r != 0) { log_error(_("unable to promote server from standby to primary")); exit(ERR_PROMOTION_FAIL); } } } log_notice(_("waiting up to %i seconds (parameter \"promote_check_timeout\") for promotion to complete"), config_file_options.promote_check_timeout); for (i = 0; i < config_file_options.promote_check_timeout; i += config_file_options.promote_check_interval) { recovery_type = get_recovery_type(conn); if (recovery_type == RECTYPE_PRIMARY) { promote_success = true; break; } sleep(config_file_options.promote_check_interval); } if (promote_success == false) { if (recovery_type == RECTYPE_STANDBY) { log_error(_("STANDBY PROMOTE failed, node is still a standby")); log_detail(_("node still in recovery after %i seconds"), config_file_options.promote_check_timeout); log_hint(_("the node may need more time to promote itself, check the PostgreSQL log for details")); PQfinish(conn); exit(ERR_PROMOTION_FAIL); } else { log_error(_("connection to node lost")); PQfinish(conn); exit(ERR_DB_CONN); } } log_verbose(LOG_INFO, _("standby promoted to primary after %i second(s)"), i); /* update node information to reflect new status */ if (update_node_record_set_primary(conn, config_file_options.node_id) == false) { initPQExpBuffer(&details); appendPQExpBuffer(&details, _("unable to update node record for node %i"), config_file_options.node_id); log_error("%s", details.data); create_event_notification(NULL, &config_file_options, config_file_options.node_id, "standby_promote", false, details.data); exit(ERR_DB_QUERY); } initPQExpBuffer(&details); appendPQExpBuffer(&details, _("server \"%s\" (ID: %i) was successfully promoted to primary"), local_node_record.node_name, local_node_record.node_id); log_notice(_("STANDBY PROMOTE successful")); log_detail("%s", details.data); /* Log the event */ create_event_notification(conn, &config_file_options, config_file_options.node_id, "standby_promote", true, details.data); termPQExpBuffer(&details); return; } /* * Follow a new primary. * * Node must be running. To start an inactive node and point it at a * new primary, use "repmgr node rejoin". * * TODO: enable provision of new primary's conninfo parameters, which * will be necessary if the primary's information has changed, but * was not replicated to the current standby. */ void do_standby_follow(void) { PGconn *local_conn = NULL; t_node_info local_node_record = T_NODE_INFO_INITIALIZER; PGconn *primary_conn = NULL; int primary_node_id = UNKNOWN_NODE_ID; PGconn *follow_target_conn = NULL; int follow_target_node_id = UNKNOWN_NODE_ID; t_node_info follow_target_node_record = T_NODE_INFO_INITIALIZER; bool follow_target_is_primary = true; RecordStatus record_status = RECORD_NOT_FOUND; /* so we can pass info about the primary to event notification scripts */ t_event_info event_info = T_EVENT_INFO_INITIALIZER; int timer = 0; PQExpBufferData follow_output; bool success = false; int follow_error_code = SUCCESS; log_verbose(LOG_DEBUG, "do_standby_follow()"); local_conn = establish_db_connection(config_file_options.conninfo, false); if (PQstatus(local_conn) != CONNECTION_OK) { log_hint(_("use \"repmgr node rejoin\" to re-add an inactive node to the replication cluster")); exit(ERR_DB_CONN); } log_verbose(LOG_INFO, _("connected to local node")); /* check this is a standby */ check_recovery_type(local_conn); /* attempt to retrieve local node record */ record_status = get_node_record(local_conn, config_file_options.node_id, &local_node_record); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve record for local node %i"), config_file_options.node_id); PQfinish(local_conn); exit(ERR_BAD_CONFIG); } /* * --upstream-node-id provided - attempt to follow that node */ if (runtime_options.upstream_node_id != UNKNOWN_NODE_ID) { /* we can't follow ourselves */ if (runtime_options.upstream_node_id == config_file_options.node_id) { log_error(_("provided --upstream-node-id %i is the current node"), runtime_options.upstream_node_id); PQfinish(local_conn); exit(ERR_FOLLOW_FAIL); } follow_target_node_id = runtime_options.upstream_node_id; record_status = get_node_record(local_conn, follow_target_node_id, &follow_target_node_record); /* but we must follow a node which exists (=registered) */ if (record_status != RECORD_FOUND) { log_error(_("unable to find record for intended upstream node %i"), runtime_options.upstream_node_id); PQfinish(local_conn); exit(ERR_FOLLOW_FAIL); } } /* * otherwise determine the current primary and attempt to follow that */ else { log_notice(_("attempting to find and follow current primary")); } /* * Attempt to connect to follow target - if this was provided with --upstream-node-id, * we'll connect to that, otherwise we'll attempt to find the current primary. * * If --wait provided, loop for up `primary_follow_timeout` seconds * before giving up * * XXX add `upstream_follow_timeout` ? */ for (timer = 0; timer < config_file_options.primary_follow_timeout; timer++) { /* --upstream-node-id provided - connect to specified node*/ if (follow_target_node_id != UNKNOWN_NODE_ID) { follow_target_conn = establish_db_connection_quiet(follow_target_node_record.conninfo); } /* attempt to find current primary node */ else { follow_target_conn = get_primary_connection_quiet(local_conn, &follow_target_node_id, NULL); } if (PQstatus(follow_target_conn) == CONNECTION_OK || runtime_options.wait_provided == false) { break; } sleep(1); } /* unable to connect to the follow target */ if (PQstatus(follow_target_conn) != CONNECTION_OK) { if (follow_target_node_id == UNKNOWN_NODE_ID) { log_error(_("unable to find a primary node")); } else { log_error(_("unable to connect to target node %i"), follow_target_node_id); } if (runtime_options.wait_provided == true) { if (follow_target_node_id == UNKNOWN_NODE_ID) { log_detail(_("no primary appeared after %i seconds"), config_file_options.primary_follow_timeout); } else { log_detail(_("unable to connect to target node %i after %i seconds"), follow_target_node_id, config_file_options.primary_follow_timeout); } log_hint(_("alter \"primary_follow_timeout\" in \"repmgr.conf\" to change this value")); } PQfinish(local_conn); exit(ERR_FOLLOW_FAIL); } /* --upstream-node-id not provided - retrieve record for node determined as primary */ if (runtime_options.upstream_node_id == UNKNOWN_NODE_ID) { if (runtime_options.dry_run == true) { log_info(_("connected to node %i, checking for current primary"), follow_target_node_id); } else { log_verbose(LOG_INFO, _("connected to node %i, checking for current primary"), follow_target_node_id); } record_status = get_node_record(follow_target_conn, follow_target_node_id, &follow_target_node_record); if (record_status != RECORD_FOUND) { log_error(_("unable to find record for follow target node %i"), follow_target_node_id); PQfinish(follow_target_conn); exit(ERR_FOLLOW_FAIL); } } /* * Populate "event_info" with info about the node to follow for event notifications * * XXX need to differentiate between primary and non-primary? */ event_info.node_id = follow_target_node_id; event_info.node_name = follow_target_node_record.node_name; event_info.conninfo_str = follow_target_node_record.conninfo; /* * Check whether follow target is in recovery, so we know later whether * we'll need to open a connection to the primary to update the metadata. * Also emit an informative message. */ { PQExpBufferData node_info_msg; RecoveryType recovery_type = RECTYPE_UNKNOWN; initPQExpBuffer(&node_info_msg); recovery_type = get_recovery_type(follow_target_conn); /* * unlikely this will happen, but it's conceivable the follow target will * have vanished since we last talked to it, or something */ if (recovery_type == RECTYPE_UNKNOWN) { log_error(_("unable to determine recovery type of follow target")); PQfinish(follow_target_conn); exit(ERR_FOLLOW_FAIL); } if (recovery_type == RECTYPE_PRIMARY) { follow_target_is_primary = true; appendPQExpBuffer(&node_info_msg, _("follow target is primary node \"%s\" (ID: %i)"), follow_target_node_record.node_name, follow_target_node_id); } else { follow_target_is_primary = false; appendPQExpBuffer(&node_info_msg, _("follow target is standby node \"%s\" (ID: %i)"), follow_target_node_record.node_name, follow_target_node_id); } if (runtime_options.dry_run == true) { log_info("%s", node_info_msg.data); } else { log_verbose(LOG_INFO, "%s", node_info_msg.data); } termPQExpBuffer(&node_info_msg); } /* * if replication slots in use, check at least one free slot is available * on the follow target */ if (config_file_options.use_replication_slots) { bool slots_available = check_replication_slots_available(follow_target_node_id, follow_target_conn); if (slots_available == false) { PQfinish(follow_target_conn); PQfinish(local_conn); exit(ERR_FOLLOW_FAIL); } } /* XXX check this is not current upstream anyway */ /* check if we can attach to the follow target */ { PGconn *local_repl_conn = NULL; t_system_identification local_identification = T_SYSTEM_IDENTIFICATION_INITIALIZER; bool can_follow; XLogRecPtr local_xlogpos = get_node_current_lsn(local_conn); /* Check local replication connection - we want to execute IDENTIFY_SYSTEM * to get the current timeline ID, which might not yet be written to * pg_control. * * TODO: from 9.6, query "pg_stat_wal_receiver" via the existing local connection */ local_repl_conn = establish_replication_connection_from_conn(local_conn, local_node_record.repluser); if (PQstatus(local_repl_conn) != CONNECTION_OK) { log_error(_("unable to establish a replication connection to the local node")); PQfinish(local_conn); PQfinish(follow_target_conn); exit(ERR_FOLLOW_FAIL); } else if (runtime_options.dry_run == true) { log_info(_("replication connection to the local node was successful")); } success = identify_system(local_repl_conn, &local_identification); PQfinish(local_repl_conn); if (success == false) { log_error(_("unable to query the local node's system identification")); PQfinish(local_conn); PQfinish(follow_target_conn); exit(ERR_FOLLOW_FAIL); } can_follow = check_node_can_attach(local_identification.timeline, local_xlogpos, follow_target_conn, &follow_target_node_record, false); if (can_follow == false) { PQfinish(local_conn); PQfinish(follow_target_conn); exit(ERR_FOLLOW_FAIL); } } PQfinish(local_conn); /* * Here we'll need a connection to the primary, if the upstream is not a primary. */ if (follow_target_is_primary == false) { /* * We'll try and establish primary from follow target, in the assumption its node * record is more up-to-date. */ primary_conn = get_primary_connection_quiet(follow_target_conn, &primary_node_id, NULL); /* * If follow target is not primary and no other primary could be found, * abort because we won't be able to update the node record. */ if (PQstatus(primary_conn) != CONNECTION_OK) { log_error(_("unable to determine the cluster primary")); log_detail(_("an active primary node is required for \"repmgr standby follow\"")); PQfinish(follow_target_conn); exit(ERR_FOLLOW_FAIL); } } else { primary_conn = follow_target_conn; } if (runtime_options.dry_run == true) { log_info(_("prerequisites for executing STANDBY FOLLOW are met")); exit(SUCCESS); } initPQExpBuffer(&follow_output); success = do_standby_follow_internal( primary_conn, follow_target_conn, &follow_target_node_record, &follow_output, ERR_FOLLOW_FAIL, &follow_error_code); /* unable to restart the standby */ if (success == false) { create_event_notification_extended( follow_target_conn, &config_file_options, config_file_options.node_id, "standby_follow", success, follow_output.data, &event_info); PQfinish(follow_target_conn); if (follow_target_is_primary == false) PQfinish(primary_conn); log_notice(_("STANDBY FOLLOW failed")); if (strlen( follow_output.data )) log_detail("%s", follow_output.data); termPQExpBuffer(&follow_output); exit(follow_error_code); } termPQExpBuffer(&follow_output); initPQExpBuffer(&follow_output); /* * Wait up to "standby_follow_timeout" seconds for standby to connect to * upstream. * For 9.6 and later, we could check pg_stat_wal_receiver on the local node. */ /* assume success, necessary if standby_follow_timeout is zero */ success = true; for (timer = 0; timer < config_file_options.standby_follow_timeout; timer++) { NodeAttached node_attached = is_downstream_node_attached(follow_target_conn, config_file_options.node_name, NULL); if (node_attached == NODE_ATTACHED) { success = true; break; } log_verbose(LOG_DEBUG, "sleeping %i of max %i seconds waiting for standby to attach to primary", timer + 1, config_file_options.standby_follow_timeout); sleep(1); } if (success == true) { log_notice(_("STANDBY FOLLOW successful")); appendPQExpBuffer(&follow_output, "standby attached to upstream node \"%s\" (ID: %i)", follow_target_node_record.node_name, follow_target_node_id); } else { log_error(_("STANDBY FOLLOW failed")); appendPQExpBuffer(&follow_output, "standby did not attach to upstream node \"%s\" (ID: %i) after %i seconds", follow_target_node_record.node_name, follow_target_node_id, config_file_options.standby_follow_timeout); } log_detail("%s", follow_output.data); create_event_notification_extended( primary_conn, &config_file_options, config_file_options.node_id, "standby_follow", success, follow_output.data, &event_info); termPQExpBuffer(&follow_output); PQfinish(follow_target_conn); if (follow_target_is_primary == false) PQfinish(primary_conn); if (success == false) exit(ERR_FOLLOW_FAIL); return; } /* * Perform the actual "follow" operation; this is executed by * "node rejoin" too. */ bool do_standby_follow_internal(PGconn *primary_conn, PGconn *follow_target_conn, t_node_info *follow_target_node_record, PQExpBufferData *output, int general_error_code, int *error_code) { t_node_info local_node_record = T_NODE_INFO_INITIALIZER; int original_upstream_node_id = UNKNOWN_NODE_ID; t_node_info original_upstream_node_record = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; char *errmsg = NULL; bool remove_old_replication_slot = false; /* * Fetch our node record so we can write application_name, if set, and to * get the current upstream node ID, which we'll need to know if replication * slots are in use and we want to delete this node's slot on the current * upstream. */ record_status = get_node_record(primary_conn, config_file_options.node_id, &local_node_record); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve record for node %i"), config_file_options.node_id); *error_code = ERR_BAD_CONFIG; return false; } /* * If replication slots are in use, we'll need to create a slot on the * follow target */ if (config_file_options.use_replication_slots) { /* * Here we add a sanity check for the "slot_name" field - it's possible * the node was initially registered with "use_replication_slots=false" * but the configuration was subsequently changed, leaving the field NULL. * * To avoid annoying failures we can just update the node record and proceed. */ if (!strlen(local_node_record.slot_name)) { create_slot_name(local_node_record.slot_name, config_file_options.node_id); log_notice(_("setting node %i's slot name to \"%s\""), config_file_options.node_id, local_node_record.slot_name); update_node_record_slot_name(primary_conn, config_file_options.node_id, local_node_record.slot_name); } if (create_replication_slot(follow_target_conn, local_node_record.slot_name, NULL, output) == false) { log_error("%s", output->data); *error_code = general_error_code; return false; } } /* * Store the original upstream node id so we can delete the * replication slot, if it exists. */ if (local_node_record.upstream_node_id != UNKNOWN_NODE_ID) { original_upstream_node_id = local_node_record.upstream_node_id; } else { original_upstream_node_id = follow_target_node_record->node_id; } if (config_file_options.use_replication_slots && runtime_options.host_param_provided == false) { /* * Only attempt to delete the old replication slot if the old upstream * node is known and is different to the follow target node. */ if (original_upstream_node_id != UNKNOWN_NODE_ID && original_upstream_node_id != follow_target_node_record->node_id) { remove_old_replication_slot = true; } } /* Fetch original upstream's record */ if (remove_old_replication_slot == true) { PGconn *local_conn = NULL; RecordStatus upstream_record_status = RECORD_NOT_FOUND; /* abort if local connection not available */ local_conn = establish_db_connection(config_file_options.conninfo, true); upstream_record_status = get_node_record(local_conn, original_upstream_node_id, &original_upstream_node_record); PQfinish(local_conn); if (upstream_record_status != RECORD_FOUND) { log_warning(_("unable to retrieve node record for old upstream node %i"), original_upstream_node_id); log_detail(_("replication slot will need to be removed manually")); } } /* Initialise connection parameters to write as "primary_conninfo" */ initialize_conninfo_params(&recovery_conninfo, false); /* We ignore any application_name set in the primary's conninfo */ parse_conninfo_string(follow_target_node_record->conninfo, &recovery_conninfo, &errmsg, true); /* Set the application name to this node's name */ param_set(&recovery_conninfo, "application_name", config_file_options.node_name); /* Set the replication user from the follow target node record */ param_set(&recovery_conninfo, "user", follow_target_node_record->repluser); log_notice(_("setting node %i's upstream to node %i"), config_file_options.node_id, follow_target_node_record->node_id); if (!create_recovery_file(&local_node_record, &recovery_conninfo, PQserverVersion(primary_conn), config_file_options.data_directory, true)) { *error_code = general_error_code; return false; } /* * start/restart the service */ { char server_command[MAXLEN] = ""; bool server_up = is_server_available(config_file_options.conninfo); char *action = NULL; bool success; PQExpBufferData output_buf; initPQExpBuffer(&output_buf); if (server_up == true) { if (PQserverVersion(primary_conn) >= 130000 && config_file_options.standby_follow_restart == false) { /* PostgreSQL 13 and later: we'll send SIGHUP via pg_ctl */ get_server_action(ACTION_RELOAD, server_command, config_file_options.data_directory); success = local_command(server_command, &output_buf); if (success == true) { goto cleanup; } /* In the unlikely event that fails, we'll fall back to a restart */ log_warning(_("unable to reload server configuration")); } if (config_file_options.service_restart_command[0] == '\0') { /* no "service_restart_command" defined - stop and start using pg_ctl */ action = "stopp"; /* sic */ get_server_action(ACTION_STOP_WAIT, server_command, config_file_options.data_directory); /* if translation needed, generate messages in the preceding if/else */ log_notice(_("%sing server using \"%s\""), action, server_command); success = local_command(server_command, &output_buf); if (success == false) { log_error(_("unable to %s server"), action); *error_code = ERR_NO_RESTART; return false; } action = "start"; get_server_action(ACTION_START, server_command, config_file_options.data_directory); /* if translation needed, generate messages in the preceding if/else */ log_notice(_("%sing server using \"%s\""), action, server_command); success = local_command(server_command, &output_buf); if (success == false) { log_error(_("unable to %s server"), action); *error_code = ERR_NO_RESTART; return false; } } else { action = "restart"; get_server_action(ACTION_RESTART, server_command, config_file_options.data_directory); /* if translation needed, generate messages in the preceding if/else */ log_notice(_("%sing server using \"%s\""), action, server_command); success = local_command(server_command, &output_buf); if (success == false) { log_error(_("unable to %s server"), action); *error_code = ERR_NO_RESTART; return false; } } } else { action = "start"; get_server_action(ACTION_START, server_command, config_file_options.data_directory); /* if translation needed, generate messages in the preceding if/else */ log_notice(_("%sing server using \"%s\""), action, server_command); success = local_command(server_command, &output_buf); if (success == false) { log_error(_("unable to %s server"), action); *error_code = ERR_NO_RESTART; return false; } } } cleanup: /* * If replication slots are in use, and an inactive one for this node * exists on the former upstream, drop it. * * Note that if this function is called by do_standby_switchover(), the * "repmgr node rejoin" command executed on the demotion candidate may already * have removed the slot, so there may be nothing to do. */ if (remove_old_replication_slot == true) { if (original_upstream_node_record.node_id != UNKNOWN_NODE_ID) { PGconn *old_upstream_conn = establish_db_connection_quiet(original_upstream_node_record.conninfo); if (PQstatus(old_upstream_conn) != CONNECTION_OK) { log_warning(_("unable to connect to old upstream node %i to remove replication slot"), original_upstream_node_id); log_hint(_("if reusing this node, you should manually remove any inactive replication slots")); } else { drop_replication_slot_if_exists(old_upstream_conn, original_upstream_node_id, local_node_record.slot_name); PQfinish(old_upstream_conn); } } } /* * It's possible this node was an inactive primary - update the relevant * fields to ensure it's marked as an active standby */ if (update_node_record_status(primary_conn, config_file_options.node_id, "standby", follow_target_node_record->node_id, true) == false) { appendPQExpBufferStr(output, _("unable to update upstream node")); return false; } appendPQExpBuffer(output, _("node %i is now attached to node %i"), config_file_options.node_id, follow_target_node_record->node_id); return true; } /* * Perform a switchover by: * * - stopping current primary node * - promoting this standby node to primary * - forcing the previous primary node to follow this node * * Where running and not already paused, repmgrd will be paused (and * subsequently unpaused), unless --repmgrd-no-pause provided. * * Note that this operation can only be considered to have failed completely * ("ERR_SWITCHOVER_FAIL") in these situations: * * - the prerequisites for a switchover are not met * - the demotion candidate could not be shut down cleanly * - the promotion candidate could not be promoted * * All other failures (demotion candidate did not connect to new primary etc.) * are considered partial failures ("ERR_SWITCHOVER_INCOMPLETE") * * TODO: * - make connection test timeouts/intervals configurable (see below) */ void do_standby_switchover(void) { PGconn *local_conn = NULL; PGconn *superuser_conn = NULL; PGconn *remote_conn = NULL; t_node_info local_node_record = T_NODE_INFO_INITIALIZER; /* the remote server is the primary to be demoted */ char remote_conninfo[MAXCONNINFO] = ""; char remote_host[MAXLEN] = ""; int remote_node_id = UNKNOWN_NODE_ID; t_node_info remote_node_record = T_NODE_INFO_INITIALIZER; int remote_repmgr_version = UNKNOWN_REPMGR_VERSION_NUM; RecordStatus record_status = RECORD_NOT_FOUND; RecoveryType recovery_type = RECTYPE_UNKNOWN; PQExpBufferData remote_command_str; PQExpBufferData command_output; PQExpBufferData node_rejoin_options; PQExpBufferData logmsg; PQExpBufferData detailmsg; PQExpBufferData event_details; int r, i; bool command_success = false; bool shutdown_success = false; bool dry_run_success = true; /* this flag will use to generate the final message generated */ bool switchover_success = true; XLogRecPtr remote_last_checkpoint_lsn = InvalidXLogRecPtr; ReplInfo replication_info; /* store list of configuration files on the demotion candidate */ KeyValueList remote_config_files = {NULL, NULL}; /* temporary log file for "repmgr node rejoin" on the demotion candidate */ char node_rejoin_log[MAXPGPATH] = ""; NodeInfoList sibling_nodes = T_NODE_INFO_LIST_INITIALIZER; SiblingNodeStats sibling_nodes_stats = T_SIBLING_NODES_STATS_INITIALIZER; /* this will be calculated as max_wal_senders - COUNT(*) FROM pg_stat_replication */ int available_wal_senders = 0; t_event_info event_info = T_EVENT_INFO_INITIALIZER; /* used for handling repmgrd pause/unpause */ NodeInfoList all_nodes = T_NODE_INFO_LIST_INITIALIZER; RepmgrdInfo **repmgrd_info = NULL; int repmgrd_running_count = 0; /* number of free walsenders required on promotion candidate * (at least one will be required for the demotion candidate) */ sibling_nodes_stats.min_required_wal_senders = 1; /* * SANITY CHECKS * * We'll be doing a bunch of operations on the remote server (primary to * be demoted) - careful checks needed before proceeding. */ local_conn = establish_db_connection(config_file_options.conninfo, true); /* Verify that standby is a supported server version */ (void) check_server_version(local_conn, "standby", true, NULL); record_status = get_node_record(local_conn, config_file_options.node_id, &local_node_record); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve node record for node %i"), config_file_options.node_id); PQfinish(local_conn); exit(ERR_DB_QUERY); } if (!is_streaming_replication(local_node_record.type)) { log_error(_("switchover can only performed with streaming replication")); PQfinish(local_conn); exit(ERR_BAD_CONFIG); } if (runtime_options.dry_run == true) { log_notice(_("checking switchover on node \"%s\" (ID: %i) in --dry-run mode"), local_node_record.node_name, local_node_record.node_id); } else { log_notice(_("executing switchover on node \"%s\" (ID: %i)"), local_node_record.node_name, local_node_record.node_id); } /* * If -S/--superuser option provided, check that a superuser connection can be made * to the local database. We'll check the remote superuser connection later, */ if (runtime_options.superuser[0] != '\0') { if (runtime_options.dry_run == true) { log_info(_("validating connection to local database for superuser \"%s\""), runtime_options.superuser); } superuser_conn = establish_db_connection_with_replacement_param( config_file_options.conninfo, "user", runtime_options.superuser, false); if (PQstatus(superuser_conn) != CONNECTION_OK) { log_error(_("unable to connect to local database \"%s\" as provided superuser \"%s\""), PQdb(superuser_conn), runtime_options.superuser); exit(ERR_BAD_CONFIG); } if (is_superuser_connection(superuser_conn, NULL) == false) { log_error(_("connection established to local database \"%s\" for provided superuser \"%s\" is not a superuser connection"), PQdb(superuser_conn), runtime_options.superuser); exit(ERR_BAD_CONFIG); } if (runtime_options.dry_run == true) { log_info(_("successfully established connection to local database \"%s\" for provided superuser \"%s\""), PQdb(superuser_conn), runtime_options.superuser); } } /* * Warn if no superuser connection is available. */ if (superuser_conn == NULL && is_superuser_connection(local_conn, NULL) == false) { log_warning(_("no superuser connection available")); log_detail(_("it is recommended to perform switchover operations with a database superuser")); log_hint(_("provide the name of a superuser with -S/--superuser")); } /* Check that this is a standby */ recovery_type = get_recovery_type(local_conn); if (recovery_type != RECTYPE_STANDBY) { log_error(_("switchover must be executed from the standby node to be promoted")); if (recovery_type == RECTYPE_PRIMARY) { log_detail(_("this node (ID: %i) is the primary"), local_node_record.node_id); } PQfinish(local_conn); exit(ERR_SWITCHOVER_FAIL); } /* * Check that the local replication configuration file is owned by the data * directory owner. * * For PostgreSQL 11 and earlier, if PostgreSQL is not able to rename "recovery.conf", * promotion will fail. * * For PostgreSQL 12 and later, promotion will not fail even if "postgresql.auto.conf" * is owned by another user, but we'll check just in case, as it is indicative of a * poorly configured setup. In any case we will need to check "postgresql.auto.conf" on * the demotion candidate as the rejoin will fail if we are unable to to write to that. */ initPQExpBuffer(&logmsg); initPQExpBuffer(&detailmsg); if (check_replication_config_owner(PQserverVersion(local_conn), config_file_options.data_directory, &logmsg, &detailmsg) == false) { log_error("%s", logmsg.data); log_detail("%s", detailmsg.data); termPQExpBuffer(&logmsg); termPQExpBuffer(&detailmsg); PQfinish(local_conn); exit(ERR_BAD_CONFIG); } termPQExpBuffer(&logmsg); termPQExpBuffer(&detailmsg); /* check remote server connection and retrieve its record */ remote_conn = get_primary_connection(local_conn, &remote_node_id, remote_conninfo); if (PQstatus(remote_conn) != CONNECTION_OK) { log_error(_("unable to connect to current primary node")); log_hint(_("check that the cluster is correctly configured and this standby is registered")); PQfinish(local_conn); exit(ERR_DB_CONN); } record_status = get_node_record(remote_conn, remote_node_id, &remote_node_record); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve node record for current primary (node %i)"), remote_node_id); PQfinish(local_conn); PQfinish(remote_conn); exit(ERR_DB_QUERY); } log_verbose(LOG_DEBUG, "remote node name is \"%s\"", remote_node_record.node_name); /* * Check this standby is attached to the demotion candidate */ if (local_node_record.upstream_node_id != remote_node_record.node_id) { log_error(_("local node \"%s\" (ID: %i) is not a downstream of demotion candidate primary \"%s\" (ID: %i)"), local_node_record.node_name, local_node_record.node_id, remote_node_record.node_name, remote_node_record.node_id); if (local_node_record.upstream_node_id == UNKNOWN_NODE_ID) log_detail(_("local node has no registered upstream node")); else log_detail(_("registered upstream node ID is %i"), local_node_record.upstream_node_id); log_hint(_("execute \"repmgr standby register --force\" to update the local node's metadata")); PQfinish(local_conn); PQfinish(remote_conn); exit(ERR_BAD_CONFIG); } if (is_downstream_node_attached(remote_conn, local_node_record.node_name, NULL) != NODE_ATTACHED) { log_error(_("local node \"%s\" (ID: %i) is not attached to demotion candidate \"%s\" (ID: %i)"), local_node_record.node_name, local_node_record.node_id, remote_node_record.node_name, remote_node_record.node_id); PQfinish(local_conn); PQfinish(remote_conn); exit(ERR_BAD_CONFIG); } /* * In PostgreSQL 12 and earlier, check that WAL replay on the standby * is *not* paused, as that could lead to unexpected behaviour when the * standby is promoted. * * For switchover we'll mandate that WAL replay *must not* be paused. * For a promote operation we can proceed if WAL replay is paused and * there is no more available WAL to be replayed, as we can be sure the * primary is down already, but in a switchover context there's * potentially a window for more WAL to be received before we shut down * the primary completely. */ if (PQserverVersion(local_conn) < 130000 && is_wal_replay_paused(local_conn, false) == true) { ReplInfo replication_info; init_replication_info(&replication_info); if (get_replication_info(local_conn, STANDBY, &replication_info) == false) { log_error(_("unable to retrieve replication information from local node")); PQfinish(local_conn); exit(ERR_SWITCHOVER_FAIL); } log_error(_("WAL replay is paused on this node and it is not safe to proceed")); log_detail(_("replay paused at %X/%X; last WAL received is %X/%X"), format_lsn(replication_info.last_wal_replay_lsn), format_lsn(replication_info.last_wal_receive_lsn)); if (PQserverVersion(local_conn) >= 100000) log_hint(_("execute \"pg_wal_replay_resume()\" to unpause WAL replay")); else log_hint(_("execute \"pg_xlog_replay_resume()\" to unpause WAL replay")); PQfinish(local_conn); exit(ERR_SWITCHOVER_FAIL); } /* * Check that there are no exclusive backups running on the primary. * We don't want to end up damaging the backup and also leaving the server in an * state where there's control data saying it's in backup mode but there's no * backup_label in PGDATA. * If the user wants to do the switchover anyway, they should first stop the * backup that's running. */ if (server_in_exclusive_backup_mode(remote_conn) != BACKUP_STATE_NO_BACKUP) { log_error(_("unable to perform a switchover while primary server is in exclusive backup mode")); log_hint(_("stop backup before attempting the switchover")); PQfinish(local_conn); PQfinish(remote_conn); exit(ERR_SWITCHOVER_FAIL); } /* this will fill the %p event notification parameter */ event_info.node_id = remote_node_record.node_id; /* keep a running total of how many nodes will require a replication slot */ if (remote_node_record.slot_name[0] != '\0') { sibling_nodes_stats.min_required_free_slots++; } /* * If --force-rewind specified, check pg_rewind can be used, and * pre-emptively fetch the list of configuration files which should be * archived */ if (runtime_options.force_rewind_used == true) { PQExpBufferData reason; PQExpBufferData msg; initPQExpBuffer(&reason); if (can_use_pg_rewind(remote_conn, config_file_options.data_directory, &reason) == false) { log_error(_("--force-rewind specified but pg_rewind cannot be used")); log_detail("%s", reason.data); termPQExpBuffer(&reason); PQfinish(local_conn); PQfinish(remote_conn); exit(ERR_BAD_CONFIG); } termPQExpBuffer(&reason); initPQExpBuffer(&msg); appendPQExpBufferStr(&msg, _("prerequisites for using pg_rewind are met")); if (runtime_options.dry_run == true) { log_info("%s", msg.data); } else { log_verbose(LOG_INFO, "%s", msg.data); } termPQExpBuffer(&msg); get_datadir_configuration_files(remote_conn, &remote_config_files); } /* * Check that we can connect by SSH to the remote (current primary) server */ get_conninfo_value(remote_conninfo, "host", remote_host); r = test_ssh_connection(remote_host, runtime_options.remote_user); if (r != 0) { log_error(_("unable to connect via SSH to host \"%s\", user \"%s\""), remote_host, runtime_options.remote_user); PQfinish(remote_conn); PQfinish(local_conn); exit(ERR_BAD_CONFIG); } else { PQExpBufferData msg; initPQExpBuffer(&msg); appendPQExpBuffer(&msg, _("SSH connection to host \"%s\" succeeded"), remote_host); if (runtime_options.dry_run == true) { log_info("%s", msg.data); } else { log_verbose(LOG_INFO, "%s", msg.data); } termPQExpBuffer(&msg); } /* check remote repmgr binary can be found */ initPQExpBuffer(&remote_command_str); make_remote_repmgr_path(&remote_command_str, &remote_node_record); /* * Here we're executing an arbitrary repmgr command which is guaranteed to * succeed if repmgr is executed. We'll extract the actual version number in the * next step. */ appendPQExpBufferStr(&remote_command_str, "--version >/dev/null 2>&1 && echo \"1\" || echo \"0\""); initPQExpBuffer(&command_output); command_success = remote_command(remote_host, runtime_options.remote_user, remote_command_str.data, config_file_options.ssh_options, &command_output); termPQExpBuffer(&remote_command_str); if (command_success == false || command_output.data[0] == '0') { PQExpBufferData hint; log_error(_("unable to execute \"%s\" on \"%s\""), progname(), remote_host); if (strlen(command_output.data) > 2) log_detail("%s", command_output.data); termPQExpBuffer(&command_output); initPQExpBuffer(&hint); appendPQExpBufferStr(&hint, _("check \"pg_bindir\" is set to the correct path in \"repmgr.conf\"; current value: ")); if (strlen(config_file_options.pg_bindir)) { appendPQExpBuffer(&hint, "\"%s\"", config_file_options.pg_bindir); } else { appendPQExpBufferStr(&hint, "(not set)"); } log_hint("%s", hint.data); termPQExpBuffer(&hint); PQfinish(remote_conn); PQfinish(local_conn); exit(ERR_BAD_CONFIG); } termPQExpBuffer(&command_output); /* * Now we're sure the binary can be executed, fetch its version number. */ initPQExpBuffer(&remote_command_str); make_remote_repmgr_path(&remote_command_str, &remote_node_record); appendPQExpBufferStr(&remote_command_str, "--version 2>/dev/null"); initPQExpBuffer(&command_output); command_success = remote_command(remote_host, runtime_options.remote_user, remote_command_str.data, config_file_options.ssh_options, &command_output); termPQExpBuffer(&remote_command_str); if (command_success == true) { remote_repmgr_version = parse_repmgr_version(command_output.data); if (remote_repmgr_version == UNKNOWN_REPMGR_VERSION_NUM) { log_error(_("unable to parse \"%s\"'s reported version on \"%s\""), progname(), remote_host); PQfinish(remote_conn); PQfinish(local_conn); exit(ERR_BAD_CONFIG); } log_debug(_("\"%s\" version on \"%s\" is %i"), progname(), remote_host, remote_repmgr_version ); } else { log_error(_("unable to execute \"%s\" on \"%s\""), progname(), remote_host); if (strlen(command_output.data) > 2) log_detail("%s", command_output.data); termPQExpBuffer(&command_output); PQfinish(remote_conn); PQfinish(local_conn); exit(ERR_BAD_CONFIG); } termPQExpBuffer(&command_output); /* * Check if the expected remote repmgr.conf file exists */ initPQExpBuffer(&remote_command_str); appendPQExpBuffer(&remote_command_str, "test -f %s && echo 1 || echo 0", remote_node_record.config_file); initPQExpBuffer(&command_output); command_success = remote_command(remote_host, runtime_options.remote_user, remote_command_str.data, config_file_options.ssh_options, &command_output); termPQExpBuffer(&remote_command_str); if (command_success == false || command_output.data[0] == '0') { log_error(_("expected configuration file not found on the demotion candidate \"%s\" (ID: %i)"), remote_node_record.node_name, remote_node_record.node_id); log_detail(_("registered configuration file is \"%s\""), remote_node_record.config_file); log_hint(_("ensure the configuration file is in the expected location, or re-register \"%s\" to update the configuration file location"), remote_node_record.node_name); PQfinish(remote_conn); PQfinish(local_conn); termPQExpBuffer(&command_output); exit(ERR_BAD_CONFIG); } /* * Sanity-check remote "data_directory" is correctly configured in repmgr.conf. * * This is important as we'll need to be able to run "repmgr node status" on the data * directory after the remote (demotion candidate) has shut down. */ initPQExpBuffer(&remote_command_str); make_remote_repmgr_path(&remote_command_str, &remote_node_record); /* * --data-directory-config is available from repmgr 4.3; it will fail * if the remote repmgr is an earlier version, but the version should match * anyway. */ appendPQExpBufferStr(&remote_command_str, "node check --data-directory-config --optformat -LINFO 2>/dev/null"); initPQExpBuffer(&command_output); command_success = remote_command(remote_host, runtime_options.remote_user, remote_command_str.data, config_file_options.ssh_options, &command_output); termPQExpBuffer(&remote_command_str); if (command_success == false) { log_error(_("unable to execute \"%s node check --data-directory-config\" on \"%s\":"), progname(), remote_host); log_detail("%s", command_output.data); PQfinish(remote_conn); PQfinish(local_conn); termPQExpBuffer(&command_output); exit(ERR_BAD_CONFIG); } /* check remote repmgr has the data directory correctly configured */ { t_remote_error_type remote_error = REMOTE_ERROR_NONE; if (parse_data_directory_config(command_output.data, &remote_error) == false) { if (remote_error != REMOTE_ERROR_NONE) { log_error(_("unable to run data directory check on node \"%s\" (ID: %i)"), remote_node_record.node_name, remote_node_record.node_id); if (remote_error == REMOTE_ERROR_DB_CONNECTION) { PQExpBufferData ssh_command; /* can happen if the connection configuration is not consistent across nodes */ log_detail(_("an error was encountered when attempting to connect to PostgreSQL on node \"%s\" (ID: %i)"), remote_node_record.node_name, remote_node_record.node_id); /* output a helpful hint to help diagnose the issue */ initPQExpBuffer(&remote_command_str); make_remote_repmgr_path(&remote_command_str, &remote_node_record); appendPQExpBufferStr(&remote_command_str, "node check --db-connection"); initPQExpBuffer(&ssh_command); make_remote_command(remote_host, runtime_options.remote_user, remote_command_str.data, config_file_options.ssh_options, &ssh_command); log_hint(_("diagnose with:\n %s"), ssh_command.data); termPQExpBuffer(&remote_command_str); termPQExpBuffer(&ssh_command); } else if (remote_error == REMOTE_ERROR_CONNINFO_PARSE) { /* highly unlikely */ log_detail(_("an error was encountered when parsing the \"conninfo\" parameter in \"repmgr.conf\" on node \"%s\" (ID: %i)"), remote_node_record.node_name, remote_node_record.node_id); } else { log_detail(_("an unknown error was encountered when attempting to connect to PostgreSQL on node \"%s\" (ID: %i)"), remote_node_record.node_name, remote_node_record.node_id); } } else { log_error(_("\"data_directory\" parameter in \"repmgr.conf\" on \"%s\" (ID: %i) is incorrectly configured"), remote_node_record.node_name, remote_node_record.node_id); log_hint(_("execute \"repmgr node check --data-directory-config\" on \"%s\" (ID: %i) to diagnose the issue"), remote_node_record.node_name, remote_node_record.node_id); } PQfinish(remote_conn); PQfinish(local_conn); termPQExpBuffer(&command_output); exit(ERR_BAD_CONFIG); } } termPQExpBuffer(&command_output); if (runtime_options.dry_run == true) { log_info(_("able to execute \"%s\" on remote host \"%s\""), progname(), remote_host); } /* * If -S/--superuser option provided, check that a superuser connection can be made * to the local database on the remote node. */ if (runtime_options.superuser[0] != '\0') { CheckStatus status = CHECK_STATUS_UNKNOWN; initPQExpBuffer(&remote_command_str); make_remote_repmgr_path(&remote_command_str, &remote_node_record); appendPQExpBuffer(&remote_command_str, "node check --db-connection --superuser=%s --optformat -LINFO 2>/dev/null", runtime_options.superuser); initPQExpBuffer(&command_output); command_success = remote_command(remote_host, runtime_options.remote_user, remote_command_str.data, config_file_options.ssh_options, &command_output); termPQExpBuffer(&remote_command_str); if (command_success == false) { log_error(_("unable to execute \"%s node check --db-connection\" on \"%s\":"), progname(), remote_host); log_detail("%s", command_output.data); PQfinish(remote_conn); PQfinish(local_conn); termPQExpBuffer(&command_output); exit(ERR_BAD_CONFIG); } status = parse_db_connection(command_output.data); if (status != CHECK_STATUS_OK) { PQExpBufferData ssh_command; log_error(_("unable to connect locally as superuser \"%s\" on node \"%s\" (ID: %i)"), runtime_options.superuser, remote_node_record.node_name, remote_node_record.node_id); /* output a helpful hint to help diagnose the issue */ initPQExpBuffer(&remote_command_str); make_remote_repmgr_path(&remote_command_str, &remote_node_record); appendPQExpBuffer(&remote_command_str, "node check --db-connection --superuser=%s", runtime_options.superuser); initPQExpBuffer(&ssh_command); make_remote_command(remote_host, runtime_options.remote_user, remote_command_str.data, config_file_options.ssh_options, &ssh_command); log_hint(_("diagnose with:\n %s"), ssh_command.data); termPQExpBuffer(&remote_command_str); termPQExpBuffer(&ssh_command); exit(ERR_DB_CONN); } termPQExpBuffer(&command_output); } /* * For PostgreSQL 12 and later, check "postgresql.auto.conf" is owned by the * correct user, otherwise the node will probably not be able to attach to * the promotion candidate (and is a sign of bad configuration anyway) so we * will complain vocally. * * We'll only do this if we've determined the remote repmgr binary is new * enough to have the "node check --replication-config-owner" option. */ if (PQserverVersion(local_conn) >= 120000 && remote_repmgr_version >= 50100) { initPQExpBuffer(&remote_command_str); make_remote_repmgr_path(&remote_command_str, &remote_node_record); appendPQExpBufferStr(&remote_command_str, "node check --replication-config-owner --optformat -LINFO 2>/dev/null"); initPQExpBuffer(&command_output); command_success = remote_command(remote_host, runtime_options.remote_user, remote_command_str.data, config_file_options.ssh_options, &command_output); termPQExpBuffer(&remote_command_str); if (command_success == false) { log_error(_("unable to execute \"%s node check --replication-config-owner\" on \"%s\":"), progname(), remote_host); log_detail("%s", command_output.data); PQfinish(remote_conn); PQfinish(local_conn); termPQExpBuffer(&command_output); exit(ERR_BAD_CONFIG); } if (parse_replication_config_owner(command_output.data) == false) { log_error(_("\"%s\" file on \"%s\" has incorrect ownership"), PG_AUTOCONF_FILENAME, remote_node_record.node_name); log_hint(_("check the file has the same owner/group as the data directory")); PQfinish(remote_conn); PQfinish(local_conn); termPQExpBuffer(&command_output); exit(ERR_BAD_CONFIG); } termPQExpBuffer(&command_output); } /* * populate local node record with current state of various replication-related * values, so we can check for sufficient walsenders and replication slots */ get_node_replication_stats(local_conn, &local_node_record); available_wal_senders = local_node_record.max_wal_senders - local_node_record.attached_wal_receivers; /* * Get list of sibling nodes; if --siblings-follow specified, * check they're reachable; if not, the list will be used to warn * about nodes which will remain attached to the demotion candidate */ get_active_sibling_node_records(local_conn, local_node_record.node_id, local_node_record.upstream_node_id, &sibling_nodes); if (check_sibling_nodes(&sibling_nodes, &sibling_nodes_stats) == false) { PQfinish(local_conn); exit(ERR_BAD_CONFIG); } /* * check there are sufficient free walsenders - obviously there's potential * for a later race condition if some walsenders come into use before the * switchover operation gets around to attaching the sibling nodes, but * this should catch any actual existing configuration issue (and if anyone's * performing a switchover in such an unstable environment, they only have * themselves to blame). */ if (check_free_wal_senders(available_wal_senders, &sibling_nodes_stats, &dry_run_success) == false) { if (runtime_options.dry_run == false) { PQfinish(local_conn); exit(ERR_BAD_CONFIG); } } /* check demotion candidate can make replication connection to promotion candidate */ { initPQExpBuffer(&remote_command_str); make_remote_repmgr_path(&remote_command_str, &remote_node_record); appendPQExpBuffer(&remote_command_str, "node check --remote-node-id=%i --replication-connection", local_node_record.node_id); initPQExpBuffer(&command_output); command_success = remote_command(remote_host, runtime_options.remote_user, remote_command_str.data, config_file_options.ssh_options, &command_output); termPQExpBuffer(&remote_command_str); if (command_success == true) { ConnectionStatus conn_status = parse_remote_node_replication_connection(command_output.data); switch(conn_status) { case CONN_OK: if (runtime_options.dry_run == true) { log_info(_("demotion candidate is able to make replication connection to promotion candidate")); } break; case CONN_BAD: log_error(_("demotion candidate is unable to make replication connection to promotion candidate")); exit(ERR_BAD_CONFIG); break; default: log_error(_("unable to determine whether demotion candidate is able to make replication connection to promotion candidate")); exit(ERR_BAD_CONFIG); break; } termPQExpBuffer(&command_output); } } /* check archive/replication status */ { int lag_seconds = 0; CheckStatus status = CHECK_STATUS_UNKNOWN; /* archive status - check when "archive_mode" is activated */ if (guc_set(remote_conn, "archive_mode", "!=", "off")) { int files = 0; int threshold = 0; t_remote_error_type remote_error = REMOTE_ERROR_NONE; initPQExpBuffer(&remote_command_str); make_remote_repmgr_path(&remote_command_str, &remote_node_record); appendPQExpBufferStr(&remote_command_str, "node check --terse -LERROR --archive-ready --optformat"); initPQExpBuffer(&command_output); command_success = remote_command(remote_host, runtime_options.remote_user, remote_command_str.data, config_file_options.ssh_options, &command_output); termPQExpBuffer(&remote_command_str); if (command_success == true) { status = parse_node_check_archiver(command_output.data, &files, &threshold, &remote_error); } termPQExpBuffer(&command_output); switch (status) { case CHECK_STATUS_UNKNOWN: { if (runtime_options.force == false || remote_error == REMOTE_ERROR_DB_CONNECTION) { log_error(_("unable to check number of pending archive files on demotion candidate \"%s\""), remote_node_record.node_name); if (remote_error == REMOTE_ERROR_DB_CONNECTION) log_detail(_("an error was encountered when attempting to connect to PostgreSQL on node \"%s\" (ID: %i)"), remote_node_record.node_name, remote_node_record.node_id); else log_hint(_("use -F/--force to continue anyway")); PQfinish(remote_conn); PQfinish(local_conn); exit(ERR_SWITCHOVER_FAIL); } log_warning(_("unable to check number of pending archive files on demotion candidate \"%s\""), remote_node_record.node_name); log_notice(_("-F/--force set, continuing with switchover")); } break; case CHECK_STATUS_CRITICAL: { if (runtime_options.force == false) { log_error(_("number of pending archive files on demotion candidate \"%s\" is critical"), remote_node_record.node_name); log_detail(_("%i pending archive files (critical threshold: %i)"), files, threshold); log_hint(_("PostgreSQL will not shut down until all files are archived; use -F/--force to continue anyway")); PQfinish(remote_conn); PQfinish(local_conn); exit(ERR_SWITCHOVER_FAIL); } log_warning(_("number of pending archive files on demotion candidate \"%s\" exceeds the critical threshold"), remote_node_record.node_name); log_detail(_("%i pending archive files (critical threshold: %i)"), files, threshold); log_notice(_("-F/--force set, continuing with switchover")); } break; case CHECK_STATUS_WARNING: { log_warning(_("number of pending archive files on demotion candidate \"%s\" exceeds the warning threshold"), remote_node_record.node_name); log_detail(_("%i pending archive files (warning threshold: %i)"), files, threshold); log_hint(_("PostgreSQL will not shut down until all files are archived")); } break; case CHECK_STATUS_OK: { PQExpBufferData msg; initPQExpBuffer(&msg); appendPQExpBuffer(&msg, _("%i pending archive files"), files); if (runtime_options.dry_run == true) { log_info("%s", msg.data); } else { log_verbose(LOG_INFO, "%s", msg.data); } termPQExpBuffer(&msg); } } } else { char *msg = _("archive mode is \"off\""); if (runtime_options.dry_run == true) { log_info("%s", msg); } else { log_verbose(LOG_INFO, "%s", msg); } } /* * check replication lag on promotion candidate (TODO: check on all * nodes attached to demotion candidate) */ lag_seconds = get_replication_lag_seconds(local_conn); log_debug("lag is %i ", lag_seconds); if (lag_seconds >= config_file_options.replication_lag_critical) { if (runtime_options.force == false) { log_error(_("replication lag on this node is critical")); log_detail(_("lag is %i seconds (critical threshold: %i)"), lag_seconds, config_file_options.replication_lag_critical); log_hint(_("PostgreSQL on the demotion candidate will not shut down until pending WAL is flushed to the standby; use -F/--force to continue anyway")); PQfinish(remote_conn); PQfinish(local_conn); exit(ERR_SWITCHOVER_FAIL); } log_warning(_("replication lag on this node is critical")); log_detail(_("lag is %i seconds (critical threshold: %i)"), lag_seconds, config_file_options.replication_lag_critical); log_notice(_("-F/--force set, continuing with switchover")); } else if (lag_seconds >= config_file_options.replication_lag_warning) { log_warning(_("replication lag on this node is warning")); log_detail(_("lag is %i seconds (warning threshold: %i)"), lag_seconds, config_file_options.replication_lag_warning); } else if (lag_seconds == UNKNOWN_REPLICATION_LAG) { if (runtime_options.force == false) { log_error(_("unable to check replication lag on local node")); log_hint(_("use -F/--force to continue anyway")); PQfinish(remote_conn); PQfinish(local_conn); exit(ERR_SWITCHOVER_FAIL); } log_warning(_("unable to check replication lag on local node")); log_notice(_("-F/--force set, continuing with switchover")); } /* replication lag is below warning threshold */ else { PQExpBufferData msg; initPQExpBuffer(&msg); appendPQExpBuffer(&msg, _("replication lag on this standby is %i seconds"), lag_seconds); if (runtime_options.dry_run == true) { log_info("%s", msg.data); } else { log_verbose(LOG_INFO, "%s", msg.data); } termPQExpBuffer(&msg); } } PQfinish(remote_conn); /* * if replication slots are required by demotion candidate and/or siblings, * check the promotion candidate has sufficient free slots */ if (check_free_slots(&local_node_record, &sibling_nodes_stats, &dry_run_success) == false) { if (runtime_options.dry_run == false) { PQfinish(local_conn); exit(ERR_BAD_CONFIG); } } /* * Attempt to pause all repmgrd instances, unless user explicitly * specifies not to. */ if (runtime_options.repmgrd_no_pause == false) { NodeInfoListCell *cell = NULL; ItemList repmgrd_connection_errors = {NULL, NULL}; int i = 0; int unreachable_node_count = 0; get_all_node_records(local_conn, &all_nodes); repmgrd_info = (RepmgrdInfo **) pg_malloc0(sizeof(RepmgrdInfo *) * all_nodes.node_count); log_notice(_("attempting to pause repmgrd on %i nodes"), all_nodes.node_count); for (cell = all_nodes.head; cell; cell = cell->next) { repmgrd_info[i] = pg_malloc0(sizeof(RepmgrdInfo)); repmgrd_info[i]->node_id = cell->node_info->node_id; repmgrd_info[i]->pid = UNKNOWN_PID; repmgrd_info[i]->paused = false; repmgrd_info[i]->running = false; repmgrd_info[i]->pg_running = true; cell->node_info->conn = establish_db_connection_quiet(cell->node_info->conninfo); if (PQstatus(cell->node_info->conn) != CONNECTION_OK) { /* * unable to connect; treat this as an error */ repmgrd_info[i]->pg_running = false; /* * Only worry about unreachable nodes if they're marked as active * in the repmgr metadata. */ if (cell->node_info->active == true) { unreachable_node_count++; item_list_append_format(&repmgrd_connection_errors, _("unable to connect to node \"%s\" (ID: %i):\n%s"), cell->node_info->node_name, cell->node_info->node_id, PQerrorMessage(cell->node_info->conn)); } PQfinish(cell->node_info->conn); cell->node_info->conn = NULL; i++; continue; } repmgrd_info[i]->running = repmgrd_is_running(cell->node_info->conn); repmgrd_info[i]->pid = repmgrd_get_pid(cell->node_info->conn); repmgrd_info[i]->paused = repmgrd_is_paused(cell->node_info->conn); if (repmgrd_info[i]->running == true) repmgrd_running_count++; i++; } if (unreachable_node_count > 0) { PQExpBufferData msg; PQExpBufferData detail; ItemListCell *cell; initPQExpBuffer(&msg); appendPQExpBuffer(&msg, _("unable to connect to %i of %i node(s), unable to pause all repmgrd instances"), unreachable_node_count, all_nodes.node_count); initPQExpBuffer(&detail); for (cell = repmgrd_connection_errors.head; cell; cell = cell->next) { appendPQExpBuffer(&detail, " %s\n", cell->string); } if (runtime_options.force == false) { log_error("%s", msg.data); } else { log_warning("%s", msg.data); } log_detail(_("following node(s) unreachable:\n%s"), detail.data); termPQExpBuffer(&msg); termPQExpBuffer(&detail); /* tell user about footgun */ if (runtime_options.force == false) { log_hint(_("use -F/--force to continue anyway")); clear_node_info_list(&sibling_nodes); clear_node_info_list(&all_nodes); exit(ERR_SWITCHOVER_FAIL); } } /* pause repmgrd on all reachable nodes */ if (repmgrd_running_count > 0) { i = 0; for (cell = all_nodes.head; cell; cell = cell->next) { /* * Skip if node was unreachable */ if (repmgrd_info[i]->pg_running == false) { log_warning(_("node \"%s\" (ID: %i) unreachable, unable to pause repmgrd"), cell->node_info->node_name, cell->node_info->node_id); i++; continue; } /* * Skip if repmgrd not running on node */ if (repmgrd_info[i]->running == false) { log_notice(_("repmgrd not running on node \"%s\" (ID: %i), not pausing"), cell->node_info->node_name, cell->node_info->node_id); i++; continue; } /* * Skip if node is already paused. Note we won't unpause these, to * leave the repmgrd instances in the cluster in the same state they * were before the switchover. */ if (repmgrd_info[i]->paused == true) { PQfinish(cell->node_info->conn); cell->node_info->conn = NULL; i++; continue; } if (runtime_options.dry_run == true) { log_info(_("would pause repmgrd on node \"%s\" (ID: %i)"), cell->node_info->node_name, cell->node_info->node_id); } else { /* XXX check result */ log_debug("pausing repmgrd on node \"%s\" (ID: %i)", cell->node_info->node_name, cell->node_info->node_id); (void) repmgrd_pause(cell->node_info->conn, true); } PQfinish(cell->node_info->conn); cell->node_info->conn = NULL; i++; } } else { /* close all connections - we'll reestablish later */ for (cell = all_nodes.head; cell; cell = cell->next) { if (cell->node_info->conn != NULL) { PQfinish(cell->node_info->conn); cell->node_info->conn = NULL; } } } } /* * Sanity checks completed - prepare for the switchover */ if (runtime_options.dry_run == true) { log_notice(_("local node \"%s\" (ID: %i) would be promoted to primary; " "current primary \"%s\" (ID: %i) would be demoted to standby"), local_node_record.node_name, local_node_record.node_id, remote_node_record.node_name, remote_node_record.node_id); } else { log_notice(_("local node \"%s\" (ID: %i) will be promoted to primary; " "current primary \"%s\" (ID: %i) will be demoted to standby"), local_node_record.node_name, local_node_record.node_id, remote_node_record.node_name, remote_node_record.node_id); } /* * Stop the remote primary * * We'll issue the pg_ctl command but not force it not to wait; we'll * check the connection from here - and error out if no shutdown is * detected after a certain time. */ initPQExpBuffer(&remote_command_str); initPQExpBuffer(&command_output); make_remote_repmgr_path(&remote_command_str, &remote_node_record); if (runtime_options.dry_run == true) { appendPQExpBufferStr(&remote_command_str, "node service --terse -LERROR --list-actions --action=stop"); } else { log_notice(_("stopping current primary node \"%s\" (ID: %i)"), remote_node_record.node_name, remote_node_record.node_id); appendPQExpBufferStr(&remote_command_str, "node service --action=stop --checkpoint"); if (runtime_options.superuser[0] != '\0') { appendPQExpBuffer(&remote_command_str, " --superuser=%s", runtime_options.superuser); } } /* XXX handle failure */ (void) remote_command(remote_host, runtime_options.remote_user, remote_command_str.data, config_file_options.ssh_options, &command_output); termPQExpBuffer(&remote_command_str); /* * --dry-run ends here with display of command which would be used to shut * down the remote server */ if (runtime_options.dry_run == true) { /* we use a buffer here as it will be modified by string_remove_trailing_newlines() */ char shutdown_command[MAXLEN] = ""; strncpy(shutdown_command, command_output.data, MAXLEN); termPQExpBuffer(&command_output); string_remove_trailing_newlines(shutdown_command); log_info(_("following shutdown command would be run on node \"%s\":\n \"%s\""), remote_node_record.node_name, shutdown_command); log_info(_("parameter \"shutdown_check_timeout\" is set to %i seconds"), config_file_options.shutdown_check_timeout); clear_node_info_list(&sibling_nodes); key_value_list_free(&remote_config_files); if (dry_run_success == false) { log_error(_("prerequisites for executing STANDBY SWITCHOVER are *not* met")); log_hint(_("see preceding error messages")); exit(ERR_BAD_CONFIG); } log_info(_("prerequisites for executing STANDBY SWITCHOVER are met")); exit(SUCCESS); } termPQExpBuffer(&command_output); shutdown_success = false; /* loop for timeout waiting for current primary to stop */ for (i = 0; i < config_file_options.shutdown_check_timeout; i++) { /* Check whether primary is available */ PGPing ping_res; log_info(_("checking for primary shutdown; %i of %i attempts (\"shutdown_check_timeout\")"), i + 1, config_file_options.shutdown_check_timeout); ping_res = PQping(remote_conninfo); log_debug("ping status is: %s", print_pqping_status(ping_res)); /* database server could not be contacted */ if (ping_res == PQPING_NO_RESPONSE || ping_res == PQPING_NO_ATTEMPT) { bool command_success; /* * remote server can't be contacted at protocol level - that * doesn't necessarily mean it's shut down, so we'll ask its * repmgr to check at data directory level, and if shut down also * return the last checkpoint LSN. */ initPQExpBuffer(&remote_command_str); make_remote_repmgr_path(&remote_command_str, &remote_node_record); appendPQExpBufferStr(&remote_command_str, "node status --is-shutdown-cleanly"); initPQExpBuffer(&command_output); command_success = remote_command(remote_host, runtime_options.remote_user, remote_command_str.data, config_file_options.ssh_options, &command_output); termPQExpBuffer(&remote_command_str); if (command_success == true) { NodeStatus status = parse_node_status_is_shutdown_cleanly(command_output.data, &remote_last_checkpoint_lsn); log_verbose(LOG_DEBUG, "remote node status is: %s", print_node_status(status)); if (status == NODE_STATUS_DOWN && remote_last_checkpoint_lsn != InvalidXLogRecPtr) { shutdown_success = true; log_notice(_("current primary has been cleanly shut down at location %X/%X"), format_lsn(remote_last_checkpoint_lsn)); termPQExpBuffer(&command_output); break; } /* remote node did not shut down cleanly */ else if (status == NODE_STATUS_UNCLEAN_SHUTDOWN) { if (!runtime_options.force) { log_error(_("current primary did not shut down cleanly, aborting")); log_hint(_("use -F/--force to promote current standby")); termPQExpBuffer(&command_output); exit(ERR_SWITCHOVER_FAIL); } log_error(_("current primary did not shut down cleanly, continuing anyway")); shutdown_success = true; break; } else if (status == NODE_STATUS_SHUTTING_DOWN) { log_info(_("remote node is still shutting down")); } } termPQExpBuffer(&command_output); } log_debug("sleeping 1 second until next check"); sleep(1); } if (shutdown_success == false) { log_error(_("shutdown of the primary server could not be confirmed")); log_hint(_("check the primary server status before performing any further actions")); exit(ERR_SWITCHOVER_FAIL); } /* this is unlikely to happen, but check and handle gracefully anyway */ if (PQstatus(local_conn) != CONNECTION_OK) { log_warning(_("connection to local node lost, reconnecting...")); log_detail("\n%s", PQerrorMessage(local_conn)); PQfinish(local_conn); local_conn = establish_db_connection(config_file_options.conninfo, false); if (PQstatus(local_conn) != CONNECTION_OK) { log_error(_("unable to reconnect to local node \"%s\""), local_node_record.node_name); exit(ERR_DB_CONN); } log_verbose(LOG_INFO, _("successfully reconnected to local node")); } init_replication_info(&replication_info); /* * Compare standby's last WAL receive location with the primary's last * checkpoint LSN. We'll loop for a while as it's possible the standby's * walreceiver has not yet flushed all received WAL to disk. */ { bool notice_emitted = false; for (i = 0; i < config_file_options.wal_receive_check_timeout; i++) { get_replication_info(local_conn, STANDBY, &replication_info); if (replication_info.last_wal_receive_lsn >= remote_last_checkpoint_lsn) break; /* * We'll only output this notice if it looks like we're going to have * to wait for WAL to be flushed. */ if (notice_emitted == false) { log_notice(_("waiting up to %i seconds (parameter \"wal_receive_check_timeout\") for received WAL to flush to disk"), config_file_options.wal_receive_check_timeout); notice_emitted = true; } log_info(_("sleeping %i of maximum %i seconds waiting for standby to flush received WAL to disk"), i + 1, config_file_options.wal_receive_check_timeout); sleep(1); } } if (replication_info.last_wal_receive_lsn < remote_last_checkpoint_lsn) { log_warning(_("local node \"%s\" is behind shutdown primary \"%s\""), local_node_record.node_name, remote_node_record.node_name); log_detail(_("local node last receive LSN is %X/%X, primary shutdown checkpoint LSN is %X/%X"), format_lsn(replication_info.last_wal_receive_lsn), format_lsn(remote_last_checkpoint_lsn)); if (runtime_options.always_promote == false) { log_notice(_("aborting switchover")); log_hint(_("use --always-promote to force promotion of standby")); PQfinish(local_conn); exit(ERR_SWITCHOVER_FAIL); } } log_debug("local node last receive LSN is %X/%X, primary shutdown checkpoint LSN is %X/%X", format_lsn(replication_info.last_wal_receive_lsn), format_lsn(remote_last_checkpoint_lsn)); /* * optionally add a delay before promoting the standby; this is mainly * useful for testing (e.g. for reappearance of the original primary) and * is not documented. */ if (config_file_options.promote_delay > 0) { log_debug("sleeping %i seconds before promoting standby", config_file_options.promote_delay); sleep(config_file_options.promote_delay); } /* * Promote standby (local node). * * If PostgreSQL 12 or later, and -S/--superuser provided, we will provide * a superuser connection so that pg_promote() can be used. */ if (PQserverVersion(local_conn) >= 120000 && superuser_conn != NULL) { _do_standby_promote_internal(superuser_conn); } else { _do_standby_promote_internal(local_conn); } /* * If pg_rewind is requested, issue a checkpoint immediately after promoting * the local node, as pg_rewind compares timelines on the basis of the value * in pg_control, which is written at the first checkpoint, which might not * occur immediately. */ if (runtime_options.force_rewind_used == true) { PGconn *checkpoint_conn = local_conn; if (superuser_conn != NULL) { checkpoint_conn = superuser_conn; } if (is_superuser_connection(checkpoint_conn, NULL) == true) { log_notice(_("issuing CHECKPOINT on node \"%s\" (ID: %i) "), config_file_options.node_name, config_file_options.node_id); checkpoint(superuser_conn); } else { log_warning(_("no superuser connection available, unable to issue CHECKPOINT")); } } /* * Execute "repmgr node rejoin" to create recovery.conf and start the * remote server. Additionally execute "pg_rewind", if required and * requested. */ initPQExpBuffer(&node_rejoin_options); /* * Don't wait for repmgr on the remote node to report the success * of the rejoin operation - we'll check it from here. */ appendPQExpBufferStr(&node_rejoin_options, " --no-wait"); if (replication_info.last_wal_receive_lsn < remote_last_checkpoint_lsn) { KeyValueListCell *cell = NULL; bool first_entry = true; if (runtime_options.force_rewind_used == false) { log_error(_("new primary diverges from former primary and --force-rewind not provided")); log_hint(_("the former primary will need to be restored manually, or use \"repmgr node rejoin\"")); termPQExpBuffer(&node_rejoin_options); PQfinish(local_conn); exit(ERR_SWITCHOVER_FAIL); } appendPQExpBufferStr(&node_rejoin_options, " --force-rewind"); if (runtime_options.force_rewind_path[0] != '\0') { appendPQExpBuffer(&node_rejoin_options, "=%s", runtime_options.force_rewind_path); } appendPQExpBufferStr(&node_rejoin_options, " --config-files="); for (cell = remote_config_files.head; cell; cell = cell->next) { if (first_entry == false) appendPQExpBufferChar(&node_rejoin_options, ','); else first_entry = false; appendPQExpBufferStr(&node_rejoin_options, cell->key); } appendPQExpBufferChar(&node_rejoin_options, ' '); } key_value_list_free(&remote_config_files); initPQExpBuffer(&remote_command_str); make_remote_repmgr_path(&remote_command_str, &remote_node_record); /* * Here we'll coerce the local node's connection string into * "param=value" format, in case it's configured in URI format, * to simplify escaping issues when passing the string to the * remote node. */ { char *conninfo_normalized = normalize_conninfo_string(local_node_record.conninfo); appendPQExpBuffer(&remote_command_str, "%s -d ", node_rejoin_options.data); appendRemoteShellString(&remote_command_str, conninfo_normalized); appendPQExpBufferStr(&remote_command_str, " node rejoin"); pfree(conninfo_normalized); } /* */ snprintf(node_rejoin_log, MAXPGPATH, #if defined(__i386__) || defined(__i386) "/tmp/node-rejoin.%u.log", (unsigned)time(NULL) #else "/tmp/node-rejoin.%lu.log", (unsigned long)time(NULL) #endif ); appendPQExpBuffer(&remote_command_str, " > %s 2>&1 && echo \"1\" || echo \"0\"", node_rejoin_log); termPQExpBuffer(&node_rejoin_options); log_debug("executing:\n %s", remote_command_str.data); initPQExpBuffer(&command_output); command_success = remote_command(remote_host, runtime_options.remote_user, remote_command_str.data, config_file_options.ssh_options, &command_output); termPQExpBuffer(&remote_command_str); initPQExpBuffer(&logmsg); initPQExpBuffer(&detailmsg); /* This is failure to execute the ssh command */ if (command_success == false) { log_error(_("rejoin failed with error code %i"), r); switchover_success = false; appendPQExpBuffer(&logmsg, _("unable to execute \"repmgr node rejoin\" on demotion candidate \"%s\" (ID: %i)"), remote_node_record.node_name, remote_node_record.node_id); appendPQExpBufferStr(&detailmsg, command_output.data); } else { standy_join_status join_success = JOIN_UNKNOWN; /* "rempgr node rejoin" failed on the demotion candidate */ if (command_output.data[0] == '0') { appendPQExpBuffer(&logmsg, _("execution of \"repmgr node rejoin\" on demotion candidate \"%s\" (ID: %i) failed"), remote_node_record.node_name, remote_node_record.node_id); /* * Speculatively check if the demotion candidate has been restarted, e.g. by * an external watchdog process which isn't aware a switchover is happening. * This falls into the category "thing outside of our control which shouldn't * happen, but if it does, make it easier to find out what happened". */ remote_conn = establish_db_connection(remote_node_record.conninfo, false); if (PQstatus(remote_conn) == CONNECTION_OK) { if (get_recovery_type(remote_conn) == RECTYPE_PRIMARY) { appendPQExpBuffer(&detailmsg, _("PostgreSQL instance on demotion candidate \"%s\" (ID: %i) is running as a primary\n"), remote_node_record.node_name, remote_node_record.node_id); log_warning("%s", detailmsg.data); } } PQfinish(remote_conn); appendPQExpBuffer(&detailmsg, "check log file \"%s\" on \"%s\" for details", node_rejoin_log, remote_node_record.node_name); switchover_success = false; join_success = JOIN_COMMAND_FAIL; } else { join_success = check_standby_join(local_conn, &local_node_record, &remote_node_record); switch (join_success) { case JOIN_FAIL_NO_PING: appendPQExpBuffer(&logmsg, _("node \"%s\" (ID: %i) promoted to primary, but demotion candidate \"%s\" (ID: %i) did not become available"), config_file_options.node_name, config_file_options.node_id, remote_node_record.node_name, remote_node_record.node_id); switchover_success = false; break; case JOIN_FAIL_NO_REPLICATION: appendPQExpBuffer(&logmsg, _("node \"%s\" (ID: %i) promoted to primary, but demotion candidate \"%s\" (ID: %i) did not connect to the new primary"), config_file_options.node_name, config_file_options.node_id, remote_node_record.node_name, remote_node_record.node_id); switchover_success = false; break; case JOIN_SUCCESS: appendPQExpBuffer(&logmsg, _("node \"%s\" (ID: %i) promoted to primary, node \"%s\" (ID: %i) demoted to standby"), config_file_options.node_name, config_file_options.node_id, remote_node_record.node_name, remote_node_record.node_id); break; /* check_standby_join() does not return this */ case JOIN_COMMAND_FAIL: break; /* should never happen*/ case JOIN_UNKNOWN: appendPQExpBuffer(&logmsg, "unable to determine success of node rejoin action for demotion candidate \"%s\" (ID: %i)", remote_node_record.node_name, remote_node_record.node_id); switchover_success = false; break; } if (switchover_success == false) { appendPQExpBuffer(&detailmsg, "check the PostgreSQL log file on demotion candidate \"%s\" (ID: %i)", remote_node_record.node_name, remote_node_record.node_id); } } } if (switchover_success == true) { /* TODO: verify demotion candidates's node record was updated correctly */ log_notice("%s", logmsg.data); } else { log_error("%s", logmsg.data); } initPQExpBuffer(&event_details); appendPQExpBufferStr(&event_details, logmsg.data); if (detailmsg.data[0] != '\0') { log_detail("%s", detailmsg.data); appendPQExpBuffer(&event_details, "\n%s", detailmsg.data); } create_event_notification_extended(local_conn, &config_file_options, config_file_options.node_id, "standby_switchover", switchover_success, event_details.data, &event_info); termPQExpBuffer(&event_details); termPQExpBuffer(&logmsg); termPQExpBuffer(&detailmsg); termPQExpBuffer(&command_output); /* * If --siblings-follow specified, attempt to make them follow the new * primary */ if (runtime_options.siblings_follow == true && sibling_nodes.node_count > 0) { sibling_nodes_follow(&local_node_record, &sibling_nodes, &sibling_nodes_stats); } clear_node_info_list(&sibling_nodes); /* * Clean up remote node (primary demoted to standby). It's possible that the node is * still starting up, so poll for a while until we get a connection. */ for (i = 0; i < config_file_options.standby_reconnect_timeout; i++) { remote_conn = establish_db_connection(remote_node_record.conninfo, false); if (PQstatus(remote_conn) == CONNECTION_OK) break; log_info(_("sleeping 1 second; %i of %i attempts (\"standby_reconnect_timeout\") to reconnect to demoted primary"), i + 1, config_file_options.standby_reconnect_timeout); sleep(1); } /* check new standby (old primary) is reachable */ if (PQstatus(remote_conn) != CONNECTION_OK) { switchover_success = false; /* TODO: double-check whether new standby has attached */ log_warning(_("switchover did not fully complete")); log_detail(_("node \"%s\" (ID: %i) is now primary but node \"%s\" (ID: %i) is not reachable"), local_node_record.node_name, local_node_record.node_id, remote_node_record.node_name, remote_node_record.node_id); if (config_file_options.use_replication_slots == true) { log_hint(_("any inactive replication slots on the old primary will need to be dropped manually")); } } else { NodeAttached node_attached; /* * We were able to connect to the former primary - attempt to drop * this node's former replication slot, if it exists. */ if (config_file_options.use_replication_slots == true) { drop_replication_slot_if_exists(remote_conn, remote_node_record.node_id, local_node_record.slot_name); } /* * Do a final check that the standby has connected - it's possible * the standby became reachable but has not connected (or became disconnected). */ node_attached = is_downstream_node_attached(local_conn, remote_node_record.node_name, NULL); if (node_attached == NODE_ATTACHED) { switchover_success = true; log_notice(_("switchover was successful")); log_detail(_("node \"%s\" is now primary and node \"%s\" is attached as standby"), local_node_record.node_name, remote_node_record.node_name); } else { log_notice(_("switchover is incomplete")); log_detail(_("node \"%s\" is now primary but node \"%s\" is not attached as standby"), local_node_record.node_name, remote_node_record.node_name); switchover_success = false; } } PQfinish(remote_conn); PQfinish(local_conn); /* * Attempt to unpause all paused repmgrd instances, unless user explicitly * specifies not to. */ if (runtime_options.repmgrd_no_pause == false) { if (repmgrd_running_count > 0) { ItemList repmgrd_unpause_errors = {NULL, NULL}; NodeInfoListCell *cell = NULL; int i = 0; int error_node_count = 0; for (cell = all_nodes.head; cell; cell = cell->next) { if (repmgrd_info[i]->paused == true && runtime_options.repmgrd_force_unpause == false) { log_debug("repmgrd on node \"%s\" (ID: %i) paused before switchover, --repmgrd-force-unpause not provided, not unpausing", cell->node_info->node_name, cell->node_info->node_id); i++; continue; } log_debug("unpausing repmgrd on node \"%s\" (ID: %i)", cell->node_info->node_name, cell->node_info->node_id); cell->node_info->conn = establish_db_connection_quiet(cell->node_info->conninfo); if (PQstatus(cell->node_info->conn) == CONNECTION_OK) { if (repmgrd_pause(cell->node_info->conn, false) == false) { item_list_append_format(&repmgrd_unpause_errors, _("unable to unpause node \"%s\" (ID: %i)"), cell->node_info->node_name, cell->node_info->node_id); error_node_count++; } } else { item_list_append_format(&repmgrd_unpause_errors, _("unable to connect to node \"%s\" (ID: %i):\n%s"), cell->node_info->node_name, cell->node_info->node_id, PQerrorMessage(cell->node_info->conn)); error_node_count++; } i++; } if (error_node_count > 0) { PQExpBufferData detail; ItemListCell *cell; initPQExpBuffer(&detail); for (cell = repmgrd_unpause_errors.head; cell; cell = cell->next) { appendPQExpBuffer(&detail, " %s\n", cell->string); } log_warning(_("unable to unpause repmgrd on %i node(s)"), error_node_count); log_detail(_("errors encountered for following node(s):\n%s"), detail.data); log_hint(_("check node connection and status; unpause manually with \"repmgr service unpause\"")); termPQExpBuffer(&detail); } } clear_node_info_list(&all_nodes); } if (switchover_success == true) { log_notice(_("STANDBY SWITCHOVER has completed successfully")); } else { log_notice(_("STANDBY SWITCHOVER has completed with issues")); log_hint(_("see preceding log message(s) for details")); exit(ERR_SWITCHOVER_INCOMPLETE); } return; } static void check_source_server() { char cluster_size[MAXLEN]; char *connstr = NULL; t_node_info upstream_node_record = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; ExtensionStatus extension_status = REPMGR_UNKNOWN; t_extension_versions extversions = T_EXTENSION_VERSIONS_INITIALIZER; /* Attempt to connect to the upstream server to verify its configuration */ log_verbose(LOG_DEBUG, "check_source_server()"); log_info(_("connecting to source node")); connstr = param_list_to_string(&source_conninfo); log_detail(_("connection string is: %s"), connstr); pfree(connstr); source_conn = establish_db_connection_by_params(&source_conninfo, false); /* * Unless in barman mode, exit with an error; * establish_db_connection_by_params() will have already logged an error * message */ if (PQstatus(source_conn) != CONNECTION_OK) { PQfinish(source_conn); source_conn = NULL; if (mode == barman) return; exit(ERR_DB_CONN); } /* * If a connection was established, perform some sanity checks on the * provided upstream connection. */ source_server_version_num = check_server_version(source_conn, "primary", true, NULL); /* * It's not essential to know the cluster size, but useful to sanity-check * we can actually run a query before going any further. */ if (get_cluster_size(source_conn, cluster_size) == false) exit(ERR_DB_QUERY); log_detail(_("current installation size is %s"), cluster_size); /* * If the upstream node is a standby, try to connect to the primary too so * we can write an event record */ if (get_recovery_type(source_conn) == RECTYPE_STANDBY) { primary_conn = get_primary_connection(source_conn, NULL, NULL); if (PQstatus(primary_conn) != CONNECTION_OK) { log_error(_("unable to connect to primary node")); exit(ERR_BAD_CONFIG); } } else { primary_conn = source_conn; } /* * Sanity-check that the primary node has a repmgr extension - if not * present, fail with an error unless -F/--force is used (to enable repmgr * to be used as a standalone clone tool). */ extension_status = get_repmgr_extension_status(primary_conn, &extversions); if (extension_status != REPMGR_INSTALLED) { if (!runtime_options.force) { /* this is unlikely to happen */ if (extension_status == REPMGR_UNKNOWN) { log_error(_("unable to determine status of \"repmgr\" extension")); log_detail("%s", PQerrorMessage(primary_conn)); PQfinish(source_conn); exit(ERR_DB_QUERY); } if (extension_status == REPMGR_AVAILABLE) { log_error(_("repmgr extension is available but not installed in database \"%s\""), param_get(&source_conninfo, "dbname")); log_hint(_("check that you are cloning from the database where \"repmgr\" is installed")); } else if (extension_status == REPMGR_UNAVAILABLE) { log_error(_("repmgr extension is not available on the upstream node")); } else if (extension_status == REPMGR_OLD_VERSION_INSTALLED) { log_error(_("an older version of the extension is installed on the upstream node")); log_detail(_("version %s is installed but newer version %s is available"), extversions.installed_version, extversions.default_version); log_hint(_("upgrade \"repmgr\" on the source node first")); } PQfinish(source_conn); exit(ERR_BAD_CONFIG); } log_warning(_("repmgr extension not found on source node")); } else { /* * If upstream is not a standby, retrieve its node records * and attempt to connect to one; we'll then compare * that node's system identifier to that of the source * connection, to ensure we're cloning from a node which is * part of the physical replication cluster. This is mainly * to prevent cloning a standby from a witness server. * * Note that it doesn't matter if the node from the node record * list is the same as the source node; also if the source node * does not have any node records, there's not a lot we can do. * * This check will be only carried out on PostgreSQL 9.6 and * later, as this is a precautionary check and we can retrieve the system * identifier with a normal connection. */ if (runtime_options.dry_run == true) { log_info(_("\"repmgr\" extension is installed in database \"%s\""), param_get(&source_conninfo, "dbname")); } if (get_recovery_type(source_conn) == RECTYPE_PRIMARY && PQserverVersion(source_conn) >= 90600) { uint64 source_system_identifier = system_identifier(source_conn); if (source_system_identifier != UNKNOWN_SYSTEM_IDENTIFIER) { NodeInfoList all_nodes = T_NODE_INFO_LIST_INITIALIZER; NodeInfoListCell *cell = NULL; get_all_node_records(source_conn, &all_nodes); log_debug("%i node records returned by source node", all_nodes.node_count); /* loop through its nodes table */ for (cell = all_nodes.head; cell; cell = cell->next) { /* exclude the witness node, as its system identifier will be different, of course */ if (cell->node_info->type == WITNESS) continue; cell->node_info->conn = establish_db_connection_quiet(cell->node_info->conninfo); if (PQstatus(cell->node_info->conn) == CONNECTION_OK) { uint64 test_system_identifier = system_identifier(cell->node_info->conn); PQfinish(cell->node_info->conn); cell->node_info->conn = NULL; if (test_system_identifier != UNKNOWN_SYSTEM_IDENTIFIER) { if (source_system_identifier != test_system_identifier) { log_error(_("source node's system identifier does not match other nodes in the replication cluster")); log_detail(_("source node's system identifier is %lu, replication cluster member \"%s\"'s system identifier is %lu"), source_system_identifier, cell->node_info->node_name, test_system_identifier); log_hint(_("check that the source node is not a witness server")); PQfinish(source_conn); source_conn = NULL; exit(ERR_BAD_CONFIG); } /* identifiers match - our work here is done */ break; } } else { PQfinish(cell->node_info->conn); cell->node_info->conn = NULL; } } clear_node_info_list(&all_nodes); } } } /* * Check the local directory to see if it appears to be a PostgreSQL * data directory. * * Note: a previous call to check_dir() will have checked whether it contains * a running PostgreSQL instance. */ if (is_pg_dir(local_data_directory)) { const char *msg = _("target data directory appears to be a PostgreSQL data directory"); const char *hint = _("use -F/--force to overwrite the existing data directory"); if (runtime_options.force == false && runtime_options.dry_run == false) { log_error("%s", msg); log_detail(_("target data directory is \"%s\""), local_data_directory); log_hint("%s", hint); PQfinish(source_conn); exit(ERR_BAD_CONFIG); } if (runtime_options.dry_run == true) { if (runtime_options.force == true) { log_warning("%s and will be overwritten", msg); log_detail(_("target data directory is \"%s\""), local_data_directory); } else { log_warning("%s", msg); log_detail(_("target data directory is \"%s\""), local_data_directory); log_hint("%s", hint); } } } /* * Attempt to find the upstream node record */ if (runtime_options.upstream_node_id == NO_UPSTREAM_NODE) upstream_node_id = get_primary_node_id(source_conn); else upstream_node_id = runtime_options.upstream_node_id; log_debug("upstream_node_id determined as %i", upstream_node_id); if (upstream_node_id != UNKNOWN_NODE_ID) { t_node_info other_node_record = T_NODE_INFO_INITIALIZER; record_status = get_node_record(source_conn, upstream_node_id, &upstream_node_record); if (record_status == RECORD_FOUND) { t_conninfo_param_list upstream_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; char *upstream_conninfo_user; initialize_conninfo_params(&upstream_conninfo, false); parse_conninfo_string(upstream_node_record.conninfo, &upstream_conninfo, NULL, false); strncpy(recovery_conninfo_str, upstream_node_record.conninfo, MAXLEN); strncpy(upstream_repluser, upstream_node_record.repluser, NAMEDATALEN); upstream_conninfo_user = param_get(&upstream_conninfo, "user"); if (upstream_conninfo_user != NULL) { strncpy(upstream_user, upstream_conninfo_user, NAMEDATALEN); } else { get_conninfo_default_value("user", upstream_user, NAMEDATALEN); } log_verbose(LOG_DEBUG, "upstream_user is \"%s\"", upstream_user); upstream_conninfo_found = true; } /* * Check that there's no existing node record with the same name but * different ID. */ record_status = get_node_record_by_name(source_conn, config_file_options.node_name, &other_node_record); if (record_status == RECORD_FOUND && other_node_record.node_id != config_file_options.node_id) { log_error(_("another node (ID: %i) already exists with node_name \"%s\""), other_node_record.node_id, config_file_options.node_name); PQfinish(source_conn); exit(ERR_BAD_CONFIG); } } /* Check the source node is configured sufficiently to be able to clone from */ check_upstream_config(source_conn, source_server_version_num, &upstream_node_record, true); /* * Work out which users need to perform which tasks. * * Here we'll check the qualifications of the repmgr user as we have the * connection open; replication and superuser connections will be opened * when required and any errors will be raised at that point. */ /* * If the user wants to copy configuration files located outside the * data directory, we'll need to be able to query the upstream node's data * directory location, which is available only to superusers or members * of the appropriate role. */ if (runtime_options.copy_external_config_files == true) { /* * This will check if the user is superuser or (from Pg10) is a member * of "pg_read_all_settings"/"pg_monitor" */ if (connection_has_pg_monitor_role(source_conn, "pg_read_all_settings") == true) { SettingsUser = REPMGR_USER; } else if (runtime_options.superuser[0] != '\0') { SettingsUser = SUPERUSER; } else { log_error(_("--copy-external-config-files requires a user with permission to read the data directory on the source node")); if (PQserverVersion(source_conn) >= 100000) { log_hint(_("the repmgr user must be superuser or member of role \"pg_monitor\" or \"pg_read_all_settings\", or a superuser provided with -S/--superuser")); } else { log_hint(_("the repmgr user must be superuser, or a superuser provided with -S/--superuser")); } exit(ERR_BAD_CONFIG); } } /* * To create replication slots, we'll need a user with the REPLICATION * privilege, or a superuser. */ if (config_file_options.use_replication_slots == true) { } } static void check_source_server_via_barman() { char buf[MAXLEN] = ""; char barman_conninfo_str[MAXLEN] = ""; t_conninfo_param_list barman_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; char *errmsg = NULL; bool parse_success = false, command_success = false; char where_condition[MAXLEN]; PQExpBufferData command_output; PQExpBufferData repmgr_conninfo_buf; int c = 0; get_barman_property(barman_conninfo_str, "conninfo", local_repmgr_tmp_directory); initialize_conninfo_params(&barman_conninfo, false); /* * parse_conninfo_string() here will remove the upstream's * `application_name`, if set */ parse_success = parse_conninfo_string(barman_conninfo_str, &barman_conninfo, &errmsg, true); if (parse_success == false) { log_error(_("Unable to parse barman conninfo string \"%s\":\n%s"), barman_conninfo_str, errmsg); exit(ERR_BARMAN); } /* Overwrite database name in the parsed parameter list */ param_set(&barman_conninfo, "dbname", runtime_options.dbname); /* Rebuild the Barman conninfo string */ initPQExpBuffer(&repmgr_conninfo_buf); for (c = 0; c < barman_conninfo.size && barman_conninfo.keywords[c] != NULL; c++) { if (repmgr_conninfo_buf.len != 0) appendPQExpBufferChar(&repmgr_conninfo_buf, ' '); appendPQExpBuffer(&repmgr_conninfo_buf, "%s=", barman_conninfo.keywords[c]); appendConnStrVal(&repmgr_conninfo_buf, barman_conninfo.values[c]); } log_verbose(LOG_DEBUG, "repmgr database conninfo string on barman server: %s", repmgr_conninfo_buf.data); if (upstream_node_id == UNKNOWN_NODE_ID) { maxlen_snprintf(where_condition, "type='primary' AND active IS TRUE"); } else { maxlen_snprintf(where_condition, "node_id=%i", upstream_node_id); } initPQExpBuffer(&command_output); maxlen_snprintf(buf, "psql -AqtX -d \\\"%s\\\" -c \\\"" " SELECT conninfo" " FROM repmgr.nodes" " WHERE %s" " AND active IS TRUE" "\\\"", repmgr_conninfo_buf.data, where_condition); termPQExpBuffer(&repmgr_conninfo_buf); command_success = remote_command(config_file_options.barman_host, runtime_options.remote_user, buf, config_file_options.ssh_options, &command_output); if (command_success == false) { log_error(_("unable to execute database query via Barman server")); exit(ERR_BARMAN); } maxlen_snprintf(recovery_conninfo_str, "%s", command_output.data); string_remove_trailing_newlines(recovery_conninfo_str); upstream_conninfo_found = true; log_verbose(LOG_DEBUG, "upstream node conninfo string extracted via barman server: %s", recovery_conninfo_str); termPQExpBuffer(&command_output); } /* * check_upstream_config() * * Perform sanity check on upstream server configuration before starting cloning * process * * TODO: * - check user is qualified to perform base backup */ static bool check_upstream_config(PGconn *conn, int server_version_num, t_node_info *upstream_node_record, bool exit_on_error) { int i; bool config_ok = true; char *wal_error_message = NULL; t_basebackup_options backup_options = T_BASEBACKUP_OPTIONS_INITIALIZER; bool backup_options_ok = true; ItemList backup_option_errors = {NULL, NULL}; bool wal_method_stream = true; standy_clone_mode mode; bool pg_setting_ok; /* * Detecting the intended cloning mode */ mode = get_standby_clone_mode(); /* * Parse "pg_basebackup_options", if set, to detect whether --wal-method * has been set to something other than `stream` (i.e. `fetch`), as this * will influence some checks */ backup_options_ok = parse_pg_basebackup_options(config_file_options.pg_basebackup_options, &backup_options, server_version_num, &backup_option_errors); if (backup_options_ok == false) { if (exit_on_error == true) { log_error(_("error(s) encountered parsing \"pg_basebackup_options\"")); print_error_list(&backup_option_errors, LOG_ERR); log_hint(_("\"pg_basebackup_options\" is: \"%s\""), config_file_options.pg_basebackup_options); exit(ERR_BAD_CONFIG); } config_ok = false; } if (strlen(backup_options.wal_method) && strcmp(backup_options.wal_method, "stream") != 0) wal_method_stream = false; { char *levels_pre96[] = { "hot_standby", "logical", NULL, }; /* * Note that in 9.6+, "hot_standby" and "archive" are accepted as * aliases for "replica", but current_setting() will of course always * return "replica" */ char *levels_96plus[] = { "replica", "logical", NULL, }; char **levels; int j = 0; if (server_version_num < 90600) { levels = (char **) levels_pre96; wal_error_message = _("parameter \"wal_level\" must be set to \"hot_standby\" or \"logical\""); } else { levels = (char **) levels_96plus; wal_error_message = _("parameter \"wal_level\" must be set to \"replica\" or \"logical\""); } do { i = guc_set(conn, "wal_level", "=", levels[j]); if (i) { break; } j++; } while (levels[j] != NULL); } if (i == 0 || i == -1) { if (i == 0) { log_error("%s", wal_error_message); } if (exit_on_error == true) { PQfinish(conn); exit(ERR_BAD_CONFIG); } config_ok = false; } if (config_file_options.use_replication_slots == true) { pg_setting_ok = get_pg_setting_int(conn, "max_replication_slots", &i); if (pg_setting_ok == false || i < 1) { if (pg_setting_ok == true) { log_error(_("parameter \"max_replication_slots\" must be set to at least 1 to enable replication slots")); log_detail(_("current value is %i"), i); log_hint(_("\"max_replication_slots\" should be set to at least the number of expected standbys")); if (exit_on_error == true) { PQfinish(conn); exit(ERR_BAD_CONFIG); } config_ok = false; } } if (pg_setting_ok == true && i > 0 && runtime_options.dry_run == true) { log_info(_("parameter \"max_replication_slots\" set to %i"), i); } } /* * physical replication slots not available or not requested - check if * there are any circumstances where "wal_keep_segments" should be set */ else if (mode != barman) { bool check_wal_keep_segments = false; /* * A non-zero "wal_keep_segments" value will almost certainly be * required if pg_basebackup is being used with --xlog-method=fetch, * *and* no restore command has been specified */ if (wal_method_stream == false && strcmp(config_file_options.restore_command, "") == 0) { check_wal_keep_segments = true; } if (check_wal_keep_segments == true) { const char *wal_keep_parameter_name = "wal_keep_size"; if (PQserverVersion(conn) < 130000) wal_keep_parameter_name = "wal_keep_segments"; pg_setting_ok = get_pg_setting_int(conn, wal_keep_parameter_name, &i); if (pg_setting_ok == false || i < 1) { if (pg_setting_ok == true) { log_error(_("parameter \"%s\" on the upstream server must be be set to a non-zero value"), wal_keep_parameter_name); log_hint(_("Choose a value sufficiently high enough to retain enough WAL " "until the standby has been cloned and started.\n " "Alternatively set up WAL archiving using e.g. PgBarman and configure " "'restore_command' in repmgr.conf to fetch WALs from there.")); log_hint(_("In PostgreSQL 9.4 and later, replication slots can be used, which " "do not require \"%s\" to be set " "(set parameter \"use_replication_slots\" in repmgr.conf to enable)\n"), wal_keep_parameter_name); } if (exit_on_error == true) { PQfinish(conn); exit(ERR_BAD_CONFIG); } config_ok = false; } if (pg_setting_ok == true && i > 0 && runtime_options.dry_run == true) { log_info(_("parameter \"%s\" set to %i"), wal_keep_parameter_name, i); } } } if (config_file_options.use_replication_slots == false) { log_info(_("replication slot usage not requested; no replication slot will be set up for this standby")); } /* * If archive_mode is enabled, check that 'archive_command' is non empty * (however it's not practical to check that it actually represents a * valid command). * * From PostgreSQL 9.5, archive_mode can be one of 'off', 'on' or 'always' * so for ease of backwards compatibility, rather than explicitly check * for an enabled mode, check that it's not "off". */ if (guc_set(conn, "archive_mode", "!=", "off")) { i = guc_set(conn, "archive_command", "!=", ""); if (i == 0 || i == -1) { if (i == 0) log_error(_("parameter \"archive_command\" must be set to a valid command")); if (exit_on_error == true) { PQfinish(conn); exit(ERR_BAD_CONFIG); } config_ok = false; } } /* * Check that 'hot_standby' is on. This isn't strictly necessary for the * primary server, however the assumption is that we'll be cloning * standbys and thus copying the primary configuration; this way the * standby will be correctly configured by default. */ i = guc_set(conn, "hot_standby", "=", "on"); if (i == 0 || i == -1) { if (i == 0) { log_error(_("parameter \"hot_standby\" must be set to \"on\"")); } if (exit_on_error == true) { PQfinish(conn); exit(ERR_BAD_CONFIG); } config_ok = false; } pg_setting_ok = get_pg_setting_int(conn, "max_wal_senders", &i); if (pg_setting_ok == false || i < 1) { if (pg_setting_ok == true) { log_error(_("parameter \"max_wal_senders\" must be set to be at least %i"), i); log_hint(_("\"max_wal_senders\" should be set to at least the number of expected standbys")); } if (exit_on_error == true) { PQfinish(conn); exit(ERR_BAD_CONFIG); } config_ok = false; } else if (pg_setting_ok == true && i > 0 && runtime_options.dry_run == true) { log_info(_("parameter \"max_wal_senders\" set to %i"), i); } /* * If using pg_basebackup, ensure sufficient replication connections can * be made. There's no guarantee they'll still be available by the time * pg_basebackup is executed, but there's nothing we can do about that. * This check is mainly intended to warn about missing replication permissions * and/or lack of available walsenders. */ if (mode == pg_basebackup) { PGconn **connections; int i; int available_wal_senders; int min_replication_connections = 1; int possible_replication_connections = 0; t_conninfo_param_list repl_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; /* * work out how many replication connections are required (1 or 2) */ if (wal_method_stream == true) min_replication_connections += 1; log_notice(_("checking for available walsenders on the source node (%i required)"), min_replication_connections); /* * check how many free walsenders are available */ get_node_replication_stats(conn, upstream_node_record); available_wal_senders = upstream_node_record->max_wal_senders - upstream_node_record->attached_wal_receivers; if (available_wal_senders < min_replication_connections) { log_error(_("insufficient free walsenders on the source node")); log_detail(_("%i free walsenders required, %i free walsenders available"), min_replication_connections, available_wal_senders); log_hint(_("increase \"max_wal_senders\" on the source node by at least %i"), (upstream_node_record->attached_wal_receivers + min_replication_connections) - upstream_node_record->max_wal_senders); if (exit_on_error == true) { PQfinish(conn); exit(ERR_BAD_CONFIG); } } else if (runtime_options.dry_run == true) { log_info(_("sufficient walsenders available on the source node")); log_detail(_("%i required, %i available"), min_replication_connections, available_wal_senders); } /* * Sufficient free walsenders appear to be available, check if * we can connect to them. We check that the required number * of connections can be made e.g. to rule out a very restrictive * "CONNECTION LIMIT" setting. */ log_notice(_("checking replication connections can be made to the source server (%i required)"), min_replication_connections); /* * Make a copy of the connection parameter arrays, and append * "replication". */ initialize_conninfo_params(&repl_conninfo, false); conn_to_param_list(conn, &repl_conninfo); param_set(&repl_conninfo, "replication", "1"); if (runtime_options.replication_user[0] != '\0') { param_set(&repl_conninfo, "user", runtime_options.replication_user); } else if (upstream_repluser[0] != '\0') { param_set(&repl_conninfo, "user", upstream_repluser); } else if (upstream_node_record->repluser[0] != '\0') { param_set(&repl_conninfo, "user", upstream_node_record->repluser); } if (strcmp(param_get(&repl_conninfo, "user"), upstream_user) != 0) { param_set(&repl_conninfo, "dbname", "replication"); } connections = pg_malloc0(sizeof(PGconn *) * min_replication_connections); /* * Attempt to create the minimum number of required concurrent * connections */ for (i = 0; i < min_replication_connections; i++) { PGconn *replication_conn; replication_conn = establish_db_connection_by_params(&repl_conninfo, false); if (PQstatus(replication_conn) == CONNECTION_OK) { connections[i] = replication_conn; possible_replication_connections++; } } /* Close previously created connections */ for (i = 0; i < possible_replication_connections; i++) { PQfinish(connections[i]); } pfree(connections); free_conninfo_params(&repl_conninfo); if (possible_replication_connections < min_replication_connections) { config_ok = false; log_error(_("unable to establish necessary replication connections")); log_hint(_("check replication permissions on the source server")); if (exit_on_error == true) { PQfinish(conn); exit(ERR_BAD_CONFIG); } } if (runtime_options.dry_run == true) { log_info(_("required number of replication connections could be made to the source server")); log_detail(_("%i replication connections required"), min_replication_connections); } else { log_verbose(LOG_INFO, _("sufficient replication connections could be made to the source server (%i required)"), min_replication_connections); } } /* * Finally, add some checks for recommended settings */ { bool data_checksums = false; bool wal_log_hints = false; /* data_checksums available from PostgreSQL 9.3; can be read by any user */ if (get_pg_setting_bool(conn, "data_checksums", &data_checksums) == false) { /* highly unlikely this will happen */ log_error(_("unable to determine value for \"data_checksums\"")); exit(ERR_BAD_CONFIG); } /* wal_log_hints available from PostgreSQL 9.4; can be read by any user */ if (get_pg_setting_bool(conn, "wal_log_hints", &wal_log_hints) == false) { /* highly unlikely this will happen */ log_error(_("unable to determine value for \"wal_log_hints\"")); exit(ERR_BAD_CONFIG); } if (data_checksums == false && wal_log_hints == false) { log_warning(_("data checksums are not enabled and \"wal_log_hints\" is \"off\"")); log_detail(_("pg_rewind requires \"wal_log_hints\" to be enabled")); } } return config_ok; } /* * initialise_direct_clone() * * In pg_basebackup mode, configure the target data directory * if necessary, and fetch information about tablespaces and configuration * files. * * Event(s): * - standby_clone */ static void initialise_direct_clone(t_node_info *local_node_record, t_node_info *upstream_node_record) { /* * Check the destination data directory can be used (in Barman mode, this * directory will already have been created) */ if (!create_pg_dir(local_data_directory, runtime_options.force)) { log_error(_("unable to use directory \"%s\""), local_data_directory); log_hint(_("use -F/--force to force this directory to be overwritten")); exit(ERR_BAD_CONFIG); } /* * Check that tablespaces named in any `tablespace_mapping` configuration * file parameters exist. * * pg_basebackup doesn't verify mappings, so any errors will not be * caught. We'll do that here as a value-added service. * */ if (config_file_options.tablespace_mapping.head != NULL) { TablespaceListCell *cell; KeyValueList not_found = {NULL, NULL}; int total = 0, matched = 0; bool success = false; for (cell = config_file_options.tablespace_mapping.head; cell; cell = cell->next) { char *old_dir_escaped = escape_string(source_conn, cell->old_dir); char name[MAXLEN] = ""; success = get_tablespace_name_by_location(source_conn, old_dir_escaped, name); pfree(old_dir_escaped); if (success == true) { matched++; } else { key_value_list_set(¬_found, cell->old_dir, ""); } total++; } if (not_found.head != NULL) { PQExpBufferData detail; KeyValueListCell *kv_cell; log_error(_("%i of %i mapped tablespaces not found"), total - matched, total); initPQExpBuffer(&detail); for (kv_cell = not_found.head; kv_cell; kv_cell = kv_cell->next) { appendPQExpBuffer( &detail, " %s\n", kv_cell->key); } log_detail(_("following tablespaces not found:\n%s"), detail.data); termPQExpBuffer(&detail); exit(ERR_BAD_CONFIG); } } /* * If replication slots requested, create appropriate slot on the source * node; this must be done before pg_basebackup is called. * * Note: if the source node is different to the specified upstream node, * we'll need to drop the slot and recreate it on the upstream. * * TODO: skip this for Pg10, and ensure temp slot option used * * Replication slots are not supported (and not very useful anyway) in * Barman mode. */ if (config_file_options.use_replication_slots == true) { PQExpBufferData event_details; initPQExpBuffer(&event_details); if (create_replication_slot(source_conn, local_node_record->slot_name, upstream_node_record, &event_details) == false) { log_error("%s", event_details.data); create_event_notification(primary_conn, &config_file_options, config_file_options.node_id, "standby_clone", false, event_details.data); PQfinish(source_conn); exit(ERR_DB_QUERY); } termPQExpBuffer(&event_details); log_verbose(LOG_INFO, _("replication slot \"%s\" created on source node"), local_node_record->slot_name); } return; } static int run_basebackup(t_node_info *node_record) { PQExpBufferData params; PQExpBufferData script; int r = SUCCESS; TablespaceListCell *cell = NULL; t_basebackup_options backup_options = T_BASEBACKUP_OPTIONS_INITIALIZER; /* * Parse the pg_basebackup_options provided in repmgr.conf - we'll want to * check later whether certain options were set by the user */ parse_pg_basebackup_options(config_file_options.pg_basebackup_options, &backup_options, source_server_version_num, NULL); /* Create pg_basebackup command line options */ initPQExpBuffer(¶ms); appendPQExpBuffer(¶ms, " -D %s", local_data_directory); /* * conninfo string provided - pass it to pg_basebackup as the -d option * (pg_basebackup doesn't require or want a database name, but for * consistency with other applications accepts a conninfo string under * -d/--dbname) */ if (runtime_options.conninfo_provided == true) { t_conninfo_param_list conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; char *conninfo_str = NULL; initialize_conninfo_params(&conninfo, false); /* string will already have been parsed */ (void) parse_conninfo_string(runtime_options.dbname, &conninfo, NULL, false); if (runtime_options.replication_user[0] != '\0') { param_set(&conninfo, "user", runtime_options.replication_user); } else if (upstream_repluser[0] != '\0') { param_set(&conninfo, "user", upstream_repluser); } else { param_set(&conninfo, "user", node_record->repluser); } conninfo_str = param_list_to_string(&conninfo); appendPQExpBuffer(¶ms, " -d '%s'", conninfo_str); pfree(conninfo_str); } /* * Connection parameters not passed to repmgr as conninfo string - provide * them individually to pg_basebackup (-d/--dbname not required) */ else { if (strlen(runtime_options.host)) { appendPQExpBuffer(¶ms, " -h %s", runtime_options.host); } if (strlen(runtime_options.port)) { appendPQExpBuffer(¶ms, " -p %s", runtime_options.port); } if (strlen(runtime_options.replication_user)) { appendPQExpBuffer(¶ms, " -U %s", runtime_options.replication_user); } else if (strlen(upstream_repluser)) { appendPQExpBuffer(¶ms, " -U %s", upstream_repluser); } else if (strlen(node_record->repluser)) { appendPQExpBuffer(¶ms, " -U %s", node_record->repluser); } else if (strlen(runtime_options.username)) { appendPQExpBuffer(¶ms, " -U %s", runtime_options.username); } } if (runtime_options.fast_checkpoint) { appendPQExpBufferStr(¶ms, " -c fast"); } if (config_file_options.tablespace_mapping.head != NULL) { for (cell = config_file_options.tablespace_mapping.head; cell; cell = cell->next) { appendPQExpBuffer(¶ms, " -T %s=%s", cell->old_dir, cell->new_dir); } } /* * To ensure we have all the WALs needed during basebackup execution we * stream them as the backup is taking place. * * From 9.6, if replication slots are in use, we'll have previously * created a slot with reserved LSN, and will stream from that slot to * avoid WAL buildup on the primary using the -S/--slot, which requires * -X/--xlog-method=stream (from 10, -X/--wal-method=stream) */ if (!strlen(backup_options.wal_method)) { appendPQExpBufferStr(¶ms, " -X stream"); } /* * From 9.6, pg_basebackup accepts -S/--slot, which forces WAL streaming * to use the specified replication slot. If replication slot usage is * specified, the slot will already have been created. * * NOTE: currently there's no way of disabling the --slot option while * using --xlog-method=stream - it's hard to imagine a use case for this, * so no provision has been made for doing it. * * NOTE: It's possible to set 'pg_basebackup_options' with an invalid * combination of values for --wal-method (--xlog-method) and --slot - * we're not checking that, just that we're not overriding any * user-supplied values */ if (source_server_version_num >= 90600 && config_file_options.use_replication_slots) { bool slot_add = true; /* * Check whether 'pg_basebackup_options' in repmgr.conf has the --slot * option set, or if --wal-method (--xlog-method) is set to a value * other than "stream" (in which case we can't use --slot). */ if (strlen(backup_options.slot) || (strlen(backup_options.wal_method) && strcmp(backup_options.wal_method, "stream") != 0)) { slot_add = false; } if (slot_add == true) { appendPQExpBuffer(¶ms, " -S %s", node_record->slot_name); } } initPQExpBuffer(&script); make_pg_path(&script, "pg_basebackup"); appendPQExpBuffer(&script, " -l \"repmgr base backup\" %s %s", params.data, config_file_options.pg_basebackup_options); termPQExpBuffer(¶ms); if (runtime_options.dry_run == true) { log_info(_("would execute:\n %s"), script.data); termPQExpBuffer(&script); return SUCCESS; } log_info(_("executing:\n %s"), script.data); /* * As of 9.4, pg_basebackup only ever returns 0 or 1 */ r = system(script.data); termPQExpBuffer(&script); if (r != 0) return ERR_BAD_BASEBACKUP; /* check connections are still available */ (void)connection_ping_reconnect(primary_conn); if (source_conn != primary_conn) (void)connection_ping_reconnect(source_conn); /* * If replication slots in use, check the created slot is on the correct * node; the slot will initially get created on the source node, and will * need to be dropped and recreated on the actual upstream node if these * differ. */ if (config_file_options.use_replication_slots && upstream_node_id != UNKNOWN_NODE_ID) { t_node_info upstream_node_record = T_NODE_INFO_INITIALIZER; t_replication_slot slot_info = T_REPLICATION_SLOT_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; bool slot_exists_on_upstream = false; record_status = get_node_record(source_conn, upstream_node_id, &upstream_node_record); /* * If there's no upstream record, there's no point in trying to create * a replication slot on the designated upstream, as the assumption is * it won't exist at this point. */ if (record_status != RECORD_FOUND) { log_warning(_("no record exists for designated upstream node %i"), upstream_node_id); log_hint(_("you'll need to create the replication slot (\"%s\") manually"), node_record->slot_name); } else { PGconn *upstream_conn = NULL; upstream_conn = establish_db_connection(upstream_node_record.conninfo, false); /* * It's possible the upstream node is not yet running, in which case we'll * have to rely on the user taking action to create the slot */ if (PQstatus(upstream_conn) != CONNECTION_OK) { log_warning(_("unable to connect to upstream node to create replication slot")); /* * TODO: if slot creation also handled by "standby register", update warning */ log_hint(_("you may need to create the replication slot manually")); } else { record_status = get_slot_record(upstream_conn, node_record->slot_name, &slot_info); if (record_status == RECORD_FOUND) { log_verbose(LOG_INFO, _("replication slot \"%s\" already exists on upstream node %i"), node_record->slot_name, upstream_node_id); slot_exists_on_upstream = true; } else { PQExpBufferData event_details; log_notice(_("creating replication slot \"%s\" on upstream node %i"), node_record->slot_name, upstream_node_id); initPQExpBuffer(&event_details); if (create_replication_slot(upstream_conn, node_record->slot_name, &upstream_node_record, &event_details) == false) { log_error("%s", event_details.data); create_event_notification(primary_conn, &config_file_options, config_file_options.node_id, "standby_clone", false, event_details.data); PQfinish(source_conn); exit(ERR_DB_QUERY); } termPQExpBuffer(&event_details); } PQfinish(upstream_conn); } } if (slot_info.active == false) { if (slot_exists_on_upstream == false) { /* delete slot on source server */ if (drop_replication_slot_if_exists(source_conn, UNKNOWN_NODE_ID, node_record->slot_name) == true) { log_notice(_("replication slot \"%s\" deleted on source node"), node_record->slot_name); } else { log_error(_("unable to delete replication slot \"%s\" on source node"), node_record->slot_name); } } } /* * if replication slot is still active (shouldn't happen), emit a * warning */ else { log_warning(_("replication slot \"%s\" is still active on source node"), node_record->slot_name); } } return SUCCESS; } /* * Perform a filesystem backup using rsync. * * From repmgr 4 this is only used for Barman backups. */ static int run_file_backup(t_node_info *local_node_record) { int r = SUCCESS, i; char command[MAXLEN] = ""; char filename[MAXLEN] = ""; char buf[MAXLEN] = ""; char basebackups_directory[MAXLEN] = ""; char backup_id[MAXLEN] = ""; TablespaceDataList tablespace_list = {NULL, NULL}; TablespaceDataListCell *cell_t = NULL; PQExpBufferData tablespace_map; bool tablespace_map_rewrite = false; /* For the foreseeable future, no other modes are supported */ Assert(mode == barman); if (mode == barman) { t_basebackup_options backup_options = T_BASEBACKUP_OPTIONS_INITIALIZER; /* * Locate Barman's base backups directory */ get_barman_property(basebackups_directory, "basebackups_directory", local_repmgr_tmp_directory); /* * Read the list of backup files into a local file. In the process: * * - determine the backup ID * - check, and remove, the prefix * - detect tablespaces * - filter files in one list per tablespace */ { FILE *fi; /* input stream */ FILE *fd; /* output for data.txt */ char prefix[MAXLEN] = ""; char output[MAXLEN] = ""; int n = 0; char *p = NULL, *q = NULL; maxlen_snprintf(command, "%s list-files --target=data %s latest", make_barman_ssh_command(barman_command_buf), config_file_options.barman_server); log_verbose(LOG_DEBUG, "executing:\n %s", command); fi = popen(command, "r"); if (fi == NULL) { log_error("cannot launch command: %s", command); exit(ERR_BARMAN); } fd = fopen(datadir_list_filename, "w"); if (fd == NULL) { log_error("cannot open file: %s", datadir_list_filename); exit(ERR_BARMAN); } maxlen_snprintf(prefix, "%s/", basebackups_directory); while (fgets(output, MAXLEN, fi) != NULL) { /* * Remove prefix */ p = string_skip_prefix(prefix, output); if (p == NULL) { log_error("unexpected output from \"barman list-files\""); log_detail("%s", output); exit(ERR_BARMAN); } /* * Remove and note backup ID; copy backup.info */ if (!strcmp(backup_id, "")) { FILE *fi2; n = strcspn(p, "/"); strncpy(backup_id, p, n); strncat(prefix, backup_id, MAXLEN - 1); strncat(prefix, "/", MAXLEN - 1); p = string_skip_prefix(backup_id, p); if (p == NULL) { log_error("unexpected output from \"barman list-files\""); log_detail("%s", output); exit(ERR_BARMAN); } p = string_skip_prefix("/", p); /* * Copy backup.info */ maxlen_snprintf(command, "rsync -a %s:%s/%s/backup.info %s", config_file_options.barman_host, basebackups_directory, backup_id, local_repmgr_tmp_directory); (void) local_command(command, NULL); /* * Get tablespace data */ maxlen_snprintf(filename, "%s/backup.info", local_repmgr_tmp_directory); fi2 = fopen(filename, "r"); if (fi2 == NULL) { log_error("cannot open file: %s", filename); exit(ERR_INTERNAL); } while (fgets(buf, MAXLEN, fi2) != NULL) { q = string_skip_prefix("tablespaces=", buf); if (q != NULL && strncmp(q, "None\n", 5)) { get_tablespace_data_barman(q, &tablespace_list); } q = string_skip_prefix("version=", buf); if (q != NULL) { source_server_version_num = strtol(q, NULL, 10); } } fclose(fi2); unlink(filename); continue; } /* * Skip backup.info */ if (string_skip_prefix("backup.info", p)) continue; /* * Filter data directory files */ if ((q = string_skip_prefix("data/", p)) != NULL) { fputs(q, fd); continue; } /* * Filter other files (i.e. tablespaces) */ for (cell_t = tablespace_list.head; cell_t; cell_t = cell_t->next) { if ((q = string_skip_prefix(cell_t->oid, p)) != NULL && *q == '/') { if (cell_t->fptr == NULL) { maxlen_snprintf(filename, "%s/%s.txt", local_repmgr_tmp_directory, cell_t->oid); cell_t->fptr = fopen(filename, "w"); if (cell_t->fptr == NULL) { log_error("cannot open file: %s", filename); exit(ERR_INTERNAL); } } fputs(q + 1, cell_t->fptr); break; } } } fclose(fd); pclose(fi); } /* For 9.5 and greater, create our own tablespace_map file */ if (source_server_version_num >= 90500) { initPQExpBuffer(&tablespace_map); } /* * As of Barman version 1.6.1, the file structure of a backup is as * follows: * * base/ - base backup wals/ - WAL files associated to the backup * * base/ - backup files * * here ID has the standard timestamp form yyyymmddThhmmss * * base//backup.info - backup metadata, in text format * base//data - data directory base// - * tablespace with the given oid */ /* * Copy all backup files from the Barman server */ maxlen_snprintf(command, "rsync --progress -a --files-from=%s %s:%s/%s/data %s", datadir_list_filename, config_file_options.barman_host, basebackups_directory, backup_id, local_data_directory); (void) local_command( command, NULL); unlink(datadir_list_filename); /* * At this point we should have the source server version number. * If not, try and extract it from the data directory. */ if (source_server_version_num == UNKNOWN_SERVER_VERSION_NUM) { log_warning(_("server version number is unknown")); source_server_version_num = get_pg_version(local_data_directory, NULL); /* * In the unlikely we are still unable to obtain the server * version number, there's not a lot which can be done. */ if (source_server_version_num == UNKNOWN_SERVER_VERSION_NUM) { log_error(_("unable to extract server version number from the data directory, aborting")); exit(ERR_BAD_CONFIG); } log_notice(_("server version number is: %i"), source_server_version_num); } /* * Parse the pg_basebackup_options provided in repmgr.conf - we need to * check if --waldir/--xlogdir was provided. */ parse_pg_basebackup_options(config_file_options.pg_basebackup_options, &backup_options, source_server_version_num, NULL); /* * We must create some PGDATA subdirectories because they are not * included in the Barman backup. * * See class RsyncBackupExecutor in the Barman source * (barman/backup_executor.py) for a definitive list of excluded * directories. */ { const char *const dirs[] = { /* Only from 10 */ "pg_wal", /* Only from 9.5 */ "pg_commit_ts", /* Only from 9.4 */ "pg_dynshmem", "pg_logical", "pg_logical/snapshots", "pg_logical/mappings", "pg_replslot", /* Present in all versions from 9.3 */ "pg_notify", "pg_serial", "pg_snapshots", "pg_stat", "pg_stat_tmp", "pg_subtrans", "pg_tblspc", "pg_twophase", /* Present from at least 9.3, but removed in 10 */ "pg_xlog", /* Array delimiter */ 0 }; /* * This array determines the major version each of the above directories * first appears in; or if the value is negative, which from major version * the directory does not appear in. */ const int vers[] = { 100000, 90500, 90400, 90400, 90400, 90400, 90400, 0, 0, 0, 0, 0, 0, 0, 0, -100000 }; for (i = 0; dirs[i]; i++) { /* directory exists in newer versions than this server - skip */ if (vers[i] > 0 && source_server_version_num < vers[i]) continue; /* * directory existed in earlier versions than this server but * has been removed/renamed - skip */ if (vers[i] < 0 && source_server_version_num >= abs(vers[i])) continue; maxlen_snprintf(filename, "%s/%s", local_data_directory, dirs[i]); /* * If --waldir/--xlogdir specified in "pg_basebackup_options", * create a symlink rather than make a directory. */ if (strcmp(dirs[i], "pg_wal") == 0 || strcmp(dirs[i], "pg_xlog") == 0) { if (backup_options.waldir[0] != '\0') { if (create_pg_dir(backup_options.waldir, false) == false) { /* create_pg_dir() will log specifics */ log_error(_("unable to create an empty directory for WAL files")); log_hint(_("see preceding error messages")); exit(ERR_BAD_CONFIG); } if (symlink(backup_options.waldir, filename) != 0) { log_error(_("could not create symbolic link \"%s\""), filename); exit(ERR_BAD_CONFIG); } continue; } } if (mkdir(filename, S_IRWXU) != 0 && errno != EEXIST) { log_error(_("unable to create the %s directory"), dirs[i]); exit(ERR_INTERNAL); } } } } for (cell_t = tablespace_list.head; cell_t; cell_t = cell_t->next) { bool mapping_found = false; TablespaceListCell *cell = NULL; char *tblspc_dir_dest = NULL; /* * Check if tablespace path matches one of the provided tablespace * mappings */ if (config_file_options.tablespace_mapping.head != NULL) { for (cell = config_file_options.tablespace_mapping.head; cell; cell = cell->next) { if (strcmp(cell_t->location, cell->old_dir) == 0) { mapping_found = true; break; } } } if (mapping_found == true) { tblspc_dir_dest = cell->new_dir; log_debug(_("mapping source tablespace \"%s\" (OID %s) to \"%s\""), cell_t->location, cell_t->oid, tblspc_dir_dest); } else { tblspc_dir_dest = cell_t->location; } /* * Tablespace file copy */ if (mode == barman) { create_pg_dir(tblspc_dir_dest, false); if (cell_t->fptr != NULL) /* cell_t->fptr == NULL iff the tablespace is * empty */ { /* close the file to ensure the contents are flushed to disk */ fclose(cell_t->fptr); maxlen_snprintf(command, "rsync --progress -a --files-from=%s/%s.txt %s:%s/%s/%s %s", local_repmgr_tmp_directory, cell_t->oid, config_file_options.barman_host, basebackups_directory, backup_id, cell_t->oid, tblspc_dir_dest); (void) local_command(command, NULL); maxlen_snprintf(filename, "%s/%s.txt", local_repmgr_tmp_directory, cell_t->oid); unlink(filename); } } /* * If a valid mapping was provided for this tablespace, arrange for it * to be remapped (if no tablespace mapping was provided, the link * will be copied as-is by pg_basebackup and no action is required) */ if (mapping_found == true || mode == barman) { /* 9.5 and later - append to the tablespace_map file */ if (source_server_version_num >= 90500) { tablespace_map_rewrite = true; appendPQExpBuffer(&tablespace_map, "%s %s\n", cell_t->oid, tblspc_dir_dest); } /* * Pre-9.5, we have to manipulate the symlinks in pg_tblspc/ * ourselves */ else { PQExpBufferData tblspc_symlink; initPQExpBuffer(&tblspc_symlink); appendPQExpBuffer(&tblspc_symlink, "%s/pg_tblspc/%s", local_data_directory, cell_t->oid); if (unlink(tblspc_symlink.data) < 0 && errno != ENOENT) { log_error(_("unable to remove tablespace symlink %s"), tblspc_symlink.data); log_detail("%s", strerror(errno)); r = ERR_BAD_BASEBACKUP; goto stop_backup; } if (symlink(tblspc_dir_dest, tblspc_symlink.data) < 0) { log_error(_("unable to create tablespace symlink from %s to %s"), tblspc_symlink.data, tblspc_dir_dest); r = ERR_BAD_BASEBACKUP; goto stop_backup; } } } } /* * For 9.5 and later, if tablespace remapping was requested, we'll need to * rewrite the tablespace map file ourselves. The tablespace map file is * read on startup and any links created by the backend; we could do this * ourselves like for pre-9.5 servers, but it's better to rely on * functionality the backend provides. */ if (source_server_version_num >= 90500 && tablespace_map_rewrite == true) { PQExpBufferData tablespace_map_filename; FILE *tablespace_map_file; initPQExpBuffer(&tablespace_map_filename); appendPQExpBuffer(&tablespace_map_filename, "%s/%s", local_data_directory, TABLESPACE_MAP); /* * Unlink any existing file (it should be there, but we don't care if * it isn't) */ if (unlink(tablespace_map_filename.data) < 0 && errno != ENOENT) { log_error(_("unable to remove tablespace_map file \"%s\""), tablespace_map_filename.data); log_detail("%s", strerror(errno)); r = ERR_BAD_BASEBACKUP; goto stop_backup; } tablespace_map_file = fopen(tablespace_map_filename.data, "w"); if (tablespace_map_file == NULL) { log_error(_("unable to create tablespace_map file \"%s\""), tablespace_map_filename.data); r = ERR_BAD_BASEBACKUP; goto stop_backup; } if (fputs(tablespace_map.data, tablespace_map_file) == EOF) { fclose(tablespace_map_file); log_error(_("unable to write to tablespace_map file \"%s\""), tablespace_map_filename.data); r = ERR_BAD_BASEBACKUP; goto stop_backup; } fclose(tablespace_map_file); termPQExpBuffer(&tablespace_map_filename); termPQExpBuffer(&tablespace_map); } stop_backup: if (mode == barman) { /* * In Barman mode, remove local_repmgr_tmp_directory, * which contains various temporary files containing Barman metadata. */ rmtree(local_repmgr_tmp_directory, true); } /* * if replication slots in use, create replication slot */ if (r == SUCCESS) { if (config_file_options.use_replication_slots == true) { bool slot_warning = false; if (runtime_options.no_upstream_connection == true) { slot_warning = true; } else { t_node_info upstream_node_record = T_NODE_INFO_INITIALIZER; t_replication_slot slot_info = T_REPLICATION_SLOT_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; PGconn *upstream_conn = NULL; /* check connections are still available */ (void)connection_ping_reconnect(primary_conn); if (source_conn != primary_conn) (void)connection_ping_reconnect(source_conn); (void)connection_ping_reconnect(source_conn); record_status = get_node_record(source_conn, upstream_node_id, &upstream_node_record); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve node record for upstream node %i"), upstream_node_id); slot_warning = true; } else { upstream_conn = establish_db_connection(upstream_node_record.conninfo, false); if (PQstatus(upstream_conn) != CONNECTION_OK) { log_error(_("unable to connect to upstream node %i to create a replication slot"), upstream_node_id); slot_warning = true; } else { record_status = get_slot_record(upstream_conn, local_node_record->slot_name, &slot_info); if (record_status == RECORD_FOUND) { log_verbose(LOG_INFO, _("replication slot \"%s\" already exists on upstream node %i"), local_node_record->slot_name, upstream_node_id); } else { PQExpBufferData errmsg; bool success; initPQExpBuffer(&errmsg); success = create_replication_slot(upstream_conn, local_node_record->slot_name, &upstream_node_record, &errmsg); if (success == false) { log_error(_("unable to create replication slot \"%s\" on upstream node %i"), local_node_record->slot_name, upstream_node_id); log_detail("%s", errmsg.data); slot_warning = true; } else { log_notice(_("replication slot \"%s\" created on upstream node \"%s\" (ID: %i)"), local_node_record->slot_name, upstream_node_record.node_name, upstream_node_id ); } termPQExpBuffer(&errmsg); } PQfinish(upstream_conn); } } } if (slot_warning == true) { log_warning(_("\"use_replication_slots\" specified but a replication slot could not be created")); log_hint(_("ensure a replication slot called \"%s\" is created on the upstream node (ID: %i)"), local_node_record->slot_name, upstream_node_id); } } } return r; } static char * make_barman_ssh_command(char *buf) { static char config_opt[MAXLEN] = ""; if (strlen(config_file_options.barman_config)) maxlen_snprintf(config_opt, " --config=%s", config_file_options.barman_config); maxlen_snprintf(buf, "ssh %s barman%s", config_file_options.barman_host, config_opt); return buf; } static int get_tablespace_data_barman(char *tablespace_data_barman, TablespaceDataList *tablespace_list) { /* * Example: [('main', 24674, '/var/lib/postgresql/tablespaces/9.5/main'), * ('alt', 24678, '/var/lib/postgresql/tablespaces/9.5/alt')] */ char name[MAXLEN] = ""; char oid[MAXLEN] = ""; char location[MAXPGPATH] = ""; char *p = tablespace_data_barman; int i = 0; tablespace_list->head = NULL; tablespace_list->tail = NULL; p = string_skip_prefix("[", p); if (p == NULL) return -1; while (*p == '(') { p = string_skip_prefix("('", p); if (p == NULL) return -1; i = strcspn(p, "'"); strncpy(name, p, i); name[i] = 0; p = string_skip_prefix("', ", p + i); if (p == NULL) return -1; i = strcspn(p, ","); strncpy(oid, p, i); oid[i] = 0; p = string_skip_prefix(", '", p + i); if (p == NULL) return -1; i = strcspn(p, "'"); strncpy(location, p, i); location[i] = 0; p = string_skip_prefix("')", p + i); if (p == NULL) return -1; tablespace_data_append(tablespace_list, name, oid, location); if (*p == ']') break; p = string_skip_prefix(", ", p); if (p == NULL) return -1; } return SUCCESS; } void get_barman_property(char *dst, char *name, char *local_repmgr_directory) { PQExpBufferData command_output; char buf[MAXLEN] = ""; char command[MAXLEN] = ""; char *p = NULL; initPQExpBuffer(&command_output); maxlen_snprintf(command, "grep \"^[[:space:]]%s:\" %s/show-server.txt", name, local_repmgr_tmp_directory); (void) local_command(command, &command_output); maxlen_snprintf(buf, "\t%s: ", name); p = string_skip_prefix(buf, command_output.data); if (p == NULL) { log_error("unexpected output from Barman: %s", command_output.data); exit(ERR_INTERNAL); } strncpy(dst, p, MAXLEN); string_remove_trailing_newlines(dst); termPQExpBuffer(&command_output); } static void copy_configuration_files(bool delete_after_copy) { int i, r; t_configfile_info *file = NULL; char *host = NULL; /* get host from upstream record */ host = param_get(&recovery_conninfo, "host"); if (host == NULL) host = runtime_options.host; log_notice(_("copying external configuration files from upstream node \"%s\""), host); for (i = 0; i < config_files.entries; i++) { PQExpBufferData dest_path; file = config_files.files[i]; /* * Skip files in the data directory - these will be copied during the * main backup */ if (file->in_data_directory == true) continue; initPQExpBuffer(&dest_path); if (runtime_options.copy_external_config_files_destination == CONFIG_FILE_SAMEPATH) { appendPQExpBufferStr(&dest_path, file->filepath); } else { appendPQExpBuffer(&dest_path, "%s/%s", local_data_directory, file->filename); } r = copy_remote_files(runtime_options.host, runtime_options.remote_user, file->filepath, dest_path.data, false, source_server_version_num); /* * TODO: collate errors into list */ if (WEXITSTATUS(r)) { log_error(_("standby clone: unable to copy config file \"%s\""), file->filename); log_hint(_("see preceding messages for details")); if (runtime_options.force == false) exit(ERR_BAD_RSYNC); } /* * This is to check we can actually copy the files before running the * main clone operation */ if (delete_after_copy == true) { /* this is very unlikely to happen, but log in case it does */ if (unlink(dest_path.data) < 0 && errno != ENOENT) { log_warning(_("unable to delete %s"), dest_path.data); log_detail("%s", strerror(errno)); } } termPQExpBuffer(&dest_path); } return; } static void tablespace_data_append(TablespaceDataList *list, const char *name, const char *oid, const char *location) { TablespaceDataListCell *cell = NULL; int oid_len = strlen(oid); int name_len = strlen(name); int location_len = strlen(location); cell = (TablespaceDataListCell *) pg_malloc0(sizeof(TablespaceDataListCell)); if (cell == NULL) { log_error(_("unable to allocate memory; terminating")); exit(ERR_OUT_OF_MEMORY); } cell->oid = pg_malloc0(1 + oid_len); cell->name = pg_malloc0(1 + name_len); cell->location = pg_malloc0(1 + location_len); strncpy(cell->oid, oid, oid_len); strncpy(cell->name, name, name_len); strncpy(cell->location, location, location_len); if (list->tail) list->tail->next = cell; else list->head = cell; list->tail = cell; } /* * check_primary_standby_version_match() * * Check server versions of supplied connections are compatible for * replication purposes. * * Exits on error. */ static void check_primary_standby_version_match(PGconn *conn, PGconn *primary_conn) { char standby_version[MAXVERSIONSTR] = ""; int standby_version_num = UNKNOWN_SERVER_VERSION_NUM; char primary_version[MAXVERSIONSTR] = ""; int primary_version_num = UNKNOWN_SERVER_VERSION_NUM; standby_version_num = check_server_version(conn, "standby", true, standby_version); /* Verify that primary is a supported server version */ primary_version_num = check_server_version(conn, "primary", false, primary_version); if (primary_version_num < 0) { PQfinish(conn); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } /* primary and standby version should match */ if ((primary_version_num / 100) != (standby_version_num / 100)) { PQfinish(conn); PQfinish(primary_conn); log_error(_("PostgreSQL versions on primary (%s) and standby (%s) must match"), primary_version, standby_version); exit(ERR_BAD_CONFIG); } } static void check_recovery_type(PGconn *conn) { RecoveryType recovery_type = get_recovery_type(conn); if (recovery_type != RECTYPE_STANDBY) { if (recovery_type == RECTYPE_PRIMARY) { log_error(_("this node should be a standby (%s)"), config_file_options.conninfo); PQfinish(conn); exit(ERR_BAD_CONFIG); } else { log_error(_("connection to node (%s) lost"), config_file_options.conninfo); PQfinish(conn); exit(ERR_DB_CONN); } } } /* * Creates recovery configuration for a standby. * * A database connection pointer is required for escaping primary_conninfo * parameters. When cloning from Barman and --no-upstream-connection supplied, * this might not be available. */ static bool create_recovery_file(t_node_info *node_record, t_conninfo_param_list *primary_conninfo, int server_version_num, char *dest, bool as_file) { PQExpBufferData recovery_file_buf; PQExpBufferData primary_conninfo_buf; char recovery_file_path[MAXPGPATH] = ""; FILE *recovery_file; mode_t um; KeyValueList recovery_config = {NULL, NULL}; KeyValueListCell *cell = NULL; initPQExpBuffer(&primary_conninfo_buf); /* standby_mode = 'on' (Pg 11 and earlier) */ if (server_version_num < 120000) { key_value_list_set(&recovery_config, "standby_mode", "on"); } /* primary_conninfo = '...' */ write_primary_conninfo(&primary_conninfo_buf, primary_conninfo); key_value_list_set(&recovery_config, "primary_conninfo", primary_conninfo_buf.data); /* * recovery_target_timeline = 'latest' * * PostgreSQL 11 and earlier only; 'latest' is the default from PostgreSQL 12. */ if (server_version_num < 120000) { key_value_list_set(&recovery_config, "recovery_target_timeline", "latest"); } /* recovery_min_apply_delay = ... (optional) */ if (config_file_options.recovery_min_apply_delay_provided == true) { key_value_list_set(&recovery_config, "recovery_min_apply_delay", config_file_options.recovery_min_apply_delay); } /* primary_slot_name = '...' (optional, for 9.4 and later) */ if (config_file_options.use_replication_slots) { key_value_list_set(&recovery_config, "primary_slot_name", node_record->slot_name); } /* * If restore_command is set, we use it as restore_command in * recovery.conf */ if (config_file_options.restore_command[0] != '\0') { char *escaped = escape_recovery_conf_value(config_file_options.restore_command); key_value_list_set(&recovery_config, "restore_command", escaped); free(escaped); } /* archive_cleanup_command (optional) */ if (config_file_options.archive_cleanup_command[0] != '\0') { char *escaped = escape_recovery_conf_value(config_file_options.archive_cleanup_command); key_value_list_set(&recovery_config, "archive_cleanup_command", escaped); free(escaped); } /* * Caller requests the generated file to be written into a buffer */ if (as_file == false) { /* create file in buffer */ initPQExpBuffer(&recovery_file_buf); for (cell = recovery_config.head; cell; cell = cell->next) { appendPQExpBuffer(&recovery_file_buf, "%s = '%s'\n", cell->key, cell->value); } maxlen_snprintf(dest, "%s", recovery_file_buf.data); termPQExpBuffer(&recovery_file_buf); return true; } /* * PostgreSQL 12 and later: modify postgresql.auto.conf */ if (server_version_num >= 120000) { if (modify_auto_conf(dest, &recovery_config) == false) { return false; } if (write_standby_signal(dest) == false) { return false; } return true; } /* * PostgreSQL 11 and earlier: write recovery.conf */ maxpath_snprintf(recovery_file_path, "%s/%s", dest, RECOVERY_COMMAND_FILE); log_debug("create_recovery_file(): creating \"%s\"...", recovery_file_path); /* Set umask to 0600 */ um = umask((~(S_IRUSR | S_IWUSR)) & (S_IRWXG | S_IRWXO)); recovery_file = fopen(recovery_file_path, "w"); umask(um); if (recovery_file == NULL) { log_error(_("unable to create recovery.conf file at \"%s\""), recovery_file_path); log_detail("%s", strerror(errno)); return false; } for (cell = recovery_config.head; cell; cell = cell->next) { initPQExpBuffer(&recovery_file_buf); appendPQExpBuffer(&recovery_file_buf, "%s = '%s'\n", cell->key, cell->value); log_debug("recovery.conf line: %s", recovery_file_buf.data); if (fputs(recovery_file_buf.data, recovery_file) == EOF) { log_error(_("unable to write to recovery file at \"%s\""), recovery_file_path); fclose(recovery_file); termPQExpBuffer(&recovery_file_buf); return false; } termPQExpBuffer(&recovery_file_buf); } fclose(recovery_file); return true; } static void write_primary_conninfo(PQExpBufferData *dest, t_conninfo_param_list *param_list) { PQExpBufferData conninfo_buf; bool application_name_provided = false; bool password_provided = false; int c; char *escaped = NULL; t_conninfo_param_list env_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; initialize_conninfo_params(&env_conninfo, true); initPQExpBuffer(&conninfo_buf); for (c = 0; c < param_list->size && param_list->keywords[c] != NULL; c++) { /* * Skip empty settings and ones which don't make any sense in * recovery.conf */ if (strcmp(param_list->keywords[c], "dbname") == 0 || strcmp(param_list->keywords[c], "replication") == 0 || (param_list->values[c] == NULL) || (param_list->values[c] != NULL && param_list->values[c][0] == '\0')) continue; /* only include "password" if explicitly requested */ if (strcmp(param_list->keywords[c], "password") == 0) { password_provided = true; } if (conninfo_buf.len != 0) appendPQExpBufferChar(&conninfo_buf, ' '); if (strcmp(param_list->keywords[c], "application_name") == 0) application_name_provided = true; appendPQExpBuffer(&conninfo_buf, "%s=", param_list->keywords[c]); appendConnStrVal(&conninfo_buf, param_list->values[c]); } /* "application_name" not provided - default to repmgr node name */ if (application_name_provided == false) { if (strlen(config_file_options.node_name)) { appendPQExpBufferStr(&conninfo_buf, " application_name="); appendConnStrVal(&conninfo_buf, config_file_options.node_name); } else { appendPQExpBufferStr(&conninfo_buf, " application_name=repmgr"); } } /* no password provided explicitly */ if (password_provided == false) { if (config_file_options.use_primary_conninfo_password == true) { const char *password = param_get(&env_conninfo, "password"); if (password != NULL) { appendPQExpBufferStr(&conninfo_buf, " password="); appendConnStrVal(&conninfo_buf, password); } } } /* passfile provided as configuration option */ if (config_file_options.passfile[0] != '\0') { /* check if the libpq we're using supports "passfile=" */ if (has_passfile() == true) { appendPQExpBufferStr(&conninfo_buf, " passfile="); appendConnStrVal(&conninfo_buf, config_file_options.passfile); } } escaped = escape_recovery_conf_value(conninfo_buf.data); appendPQExpBufferStr(dest, escaped); free(escaped); free_conninfo_params(&env_conninfo); termPQExpBuffer(&conninfo_buf); } /* * For "standby promote" and "standby follow", check for sibling nodes. * If "--siblings-follow" was specified, fill the provided SiblingNodeStats * struct with some aggregate info about the nodes for later * decision making. */ static bool check_sibling_nodes(NodeInfoList *sibling_nodes, SiblingNodeStats *sibling_nodes_stats) { char host[MAXLEN] = ""; NodeInfoListCell *cell; int r; /* * If --siblings-follow not specified, warn about any extant * siblings which will not follow the new primary */ if (runtime_options.siblings_follow == false) { if (sibling_nodes->node_count > 0) { PQExpBufferData nodes; NodeInfoListCell *cell; initPQExpBuffer(&nodes); for (cell = sibling_nodes->head; cell; cell = cell->next) { appendPQExpBuffer(&nodes, " %s (node ID: %i", cell->node_info->node_name, cell->node_info->node_id); if (cell->node_info->type == WITNESS) { appendPQExpBufferStr(&nodes, ", witness server"); } appendPQExpBufferChar(&nodes, ')'); if (cell->next) appendPQExpBufferStr(&nodes, "\n"); } log_warning(_("%i sibling nodes found, but option \"--siblings-follow\" not specified"), sibling_nodes->node_count); log_detail(_("these nodes will remain attached to the current primary:\n%s"), nodes.data); termPQExpBuffer(&nodes); } return true; } log_verbose(LOG_INFO, _("%i active sibling nodes found"), sibling_nodes->node_count); if (sibling_nodes->node_count == 0) { log_warning(_("option \"--sibling-nodes\" specified, but no sibling nodes exist")); return true; } for (cell = sibling_nodes->head; cell; cell = cell->next) { /* get host from node record */ get_conninfo_value(cell->node_info->conninfo, "host", host); r = test_ssh_connection(host, runtime_options.remote_user); if (r != 0) { cell->node_info->reachable = false; sibling_nodes_stats->unreachable_sibling_node_count++; } else { cell->node_info->reachable = true; sibling_nodes_stats->reachable_sibling_node_count++; sibling_nodes_stats->min_required_wal_senders++; if (cell->node_info->slot_name[0] != '\0') { sibling_nodes_stats->reachable_sibling_nodes_with_slot_count++; sibling_nodes_stats->min_required_free_slots++; } } } if (sibling_nodes_stats->unreachable_sibling_node_count > 0) { if (runtime_options.force == false) { log_error(_("%i of %i sibling nodes unreachable via SSH:"), sibling_nodes_stats->unreachable_sibling_node_count, sibling_nodes->node_count); } else { log_warning(_("%i of %i sibling nodes unreachable via SSH:"), sibling_nodes_stats->unreachable_sibling_node_count, sibling_nodes->node_count); } /* display list of unreachable sibling nodes */ for (cell = sibling_nodes->head; cell; cell = cell->next) { if (cell->node_info->reachable == true) continue; log_detail(" %s (ID: %i)", cell->node_info->node_name, cell->node_info->node_id); } if (runtime_options.force == false) { log_hint(_("use -F/--force to proceed in any case")); return false; } if (runtime_options.dry_run == true) { log_detail(_("F/--force specified, would proceed anyway")); } else { log_detail(_("F/--force specified, proceeding anyway")); } } else { char *msg = _("all sibling nodes are reachable via SSH"); if (runtime_options.dry_run == true) { log_info("%s", msg); } else { log_verbose(LOG_INFO, "%s", msg); } } return true; } static bool check_free_wal_senders(int available_wal_senders, SiblingNodeStats *sibling_nodes_stats, bool *dry_run_success) { if (available_wal_senders < sibling_nodes_stats->min_required_wal_senders) { if (runtime_options.force == false || runtime_options.dry_run == true) { log_error(_("insufficient free walsenders on promotion candidate")); log_detail(_("at least %i walsenders required but only %i free walsenders on promotion candidate"), sibling_nodes_stats->min_required_wal_senders, available_wal_senders); log_hint(_("increase parameter \"max_wal_senders\" or use -F/--force to proceed in any case")); if (runtime_options.dry_run == true) { *dry_run_success = false; } else { return false; } } else { log_warning(_("insufficient free walsenders on promotion candidate")); log_detail(_("at least %i walsenders required but only %i free walsender(s) on promotion candidate"), sibling_nodes_stats->min_required_wal_senders, available_wal_senders); return false; } } else { if (runtime_options.dry_run == true) { log_info(_("%i walsenders required, %i available"), sibling_nodes_stats->min_required_wal_senders, available_wal_senders); } } return true; } static bool check_free_slots(t_node_info *local_node_record, SiblingNodeStats *sibling_nodes_stats, bool *dry_run_success) { if (sibling_nodes_stats->min_required_free_slots > 0 ) { int available_slots = local_node_record->max_replication_slots - local_node_record->active_replication_slots; log_debug("minimum of %i free slots (%i for siblings) required; %i available", sibling_nodes_stats->min_required_free_slots, sibling_nodes_stats->reachable_sibling_nodes_with_slot_count, available_slots); if (available_slots < sibling_nodes_stats->min_required_free_slots) { if (runtime_options.force == false || runtime_options.dry_run == true) { log_error(_("insufficient free replication slots to attach all nodes")); log_detail(_("at least %i additional replication slots required but only %i free slots available on promotion candidate"), sibling_nodes_stats->min_required_free_slots, available_slots); log_hint(_("increase parameter \"max_replication_slots\" or use -F/--force to proceed in any case")); if (runtime_options.dry_run == true) { *dry_run_success = false; } else { return false; } } } else { if (runtime_options.dry_run == true) { log_info(_("%i replication slots required, %i available"), sibling_nodes_stats->min_required_free_slots, available_slots); } } } return true; } static void sibling_nodes_follow(t_node_info *local_node_record, NodeInfoList *sibling_nodes, SiblingNodeStats *sibling_nodes_stats) { int failed_follow_count = 0; char host[MAXLEN] = ""; NodeInfoListCell *cell = NULL; PQExpBufferData remote_command_str; PQExpBufferData command_output; log_notice(_("executing STANDBY FOLLOW on %i of %i siblings"), sibling_nodes->node_count - sibling_nodes_stats->unreachable_sibling_node_count, sibling_nodes->node_count); for (cell = sibling_nodes->head; cell; cell = cell->next) { bool success = false; /* skip nodes previously determined as unreachable */ if (cell->node_info->reachable == false) continue; initPQExpBuffer(&remote_command_str); make_remote_repmgr_path(&remote_command_str, cell->node_info); if (cell->node_info->type == WITNESS) { PGconn *witness_conn = NULL; /* TODO: create "repmgr witness resync" or similar */ appendPQExpBuffer(&remote_command_str, "witness register -d \\'%s\\' --force 2>/dev/null && echo \"1\" || echo \"0\"", local_node_record->conninfo); /* * Notify the witness repmgrd about the new primary, as at this point it will be assuming * a failover situation is in place. It will detect the new primary at some point, this * just speeds up the process. * * In the unlikely event repmgrd is not running or not in use, this will have no effect. */ witness_conn = establish_db_connection_quiet(cell->node_info->conninfo); if (PQstatus(witness_conn) == CONNECTION_OK) { notify_follow_primary(witness_conn, local_node_record->node_id); } PQfinish(witness_conn); } else { appendPQExpBufferStr(&remote_command_str, "standby follow 2>/dev/null && echo \"1\" || echo \"0\""); } get_conninfo_value(cell->node_info->conninfo, "host", host); log_debug("executing:\n %s", remote_command_str.data); initPQExpBuffer(&command_output); success = remote_command(host, runtime_options.remote_user, remote_command_str.data, config_file_options.ssh_options, &command_output); termPQExpBuffer(&remote_command_str); if (success == false || command_output.data[0] == '0') { if (cell->node_info->type == WITNESS) { log_warning(_("WITNESS REGISTER failed on node \"%s\""), cell->node_info->node_name); } else { log_warning(_("STANDBY FOLLOW failed on node \"%s\""), cell->node_info->node_name); } failed_follow_count++; } termPQExpBuffer(&command_output); } if (failed_follow_count == 0) { log_info(_("STANDBY FOLLOW successfully executed on all reachable sibling nodes")); } else { log_warning(_("execution of STANDBY FOLLOW failed on %i sibling nodes"), failed_follow_count); } /* * TODO: double-check all expected nodes are in pg_stat_replication * and entries in repmgr.nodes match */ } static t_remote_error_type parse_remote_error(const char *error) { if (error[0] == '\0') return REMOTE_ERROR_UNKNOWN; if (strcasecmp(error, "DB_CONNECTION") == 0) return REMOTE_ERROR_DB_CONNECTION; if (strcasecmp(error, "CONNINFO_PARSE") == 0) return REMOTE_ERROR_CONNINFO_PARSE; return REMOTE_ERROR_UNKNOWN; } static CheckStatus parse_check_status(const char *status_str) { CheckStatus status = CHECK_STATUS_UNKNOWN; if (strncmp(status_str, "OK", MAXLEN) == 0) { status = CHECK_STATUS_OK; } else if (strncmp(status_str, "WARNING", MAXLEN) == 0) { status = CHECK_STATUS_WARNING; } else if (strncmp(status_str, "CRITICAL", MAXLEN) == 0) { status = CHECK_STATUS_CRITICAL; } else if (strncmp(status_str, "UNKNOWN", MAXLEN) == 0) { status = CHECK_STATUS_UNKNOWN; } return status; } static NodeStatus parse_node_status_is_shutdown_cleanly(const char *node_status_output, XLogRecPtr *checkPoint) { NodeStatus node_status = NODE_STATUS_UNKNOWN; int c = 0, argc_item = 0; char **argv_array = NULL; int optindex = 0; /* We're only interested in these options */ struct option node_status_options[] = { {"last-checkpoint-lsn", required_argument, NULL, 'L'}, {"state", required_argument, NULL, 'S'}, {NULL, 0, NULL, 0} }; /* Don't attempt to tokenise an empty string */ if (!strlen(node_status_output)) { *checkPoint = InvalidXLogRecPtr; return node_status; } argc_item = parse_output_to_argv(node_status_output, &argv_array); /* Reset getopt's optind variable */ optind = 0; /* Prevent getopt from emitting errors */ opterr = 0; while ((c = getopt_long(argc_item, argv_array, "L:S:", node_status_options, &optindex)) != -1) { switch (c) { /* --last-checkpoint-lsn */ case 'L': *checkPoint = parse_lsn(optarg); break; /* --state */ case 'S': { if (strncmp(optarg, "RUNNING", MAXLEN) == 0) { node_status = NODE_STATUS_UP; } else if (strncmp(optarg, "SHUTDOWN", MAXLEN) == 0) { node_status = NODE_STATUS_DOWN; } else if (strncmp(optarg, "UNCLEAN_SHUTDOWN", MAXLEN) == 0) { node_status = NODE_STATUS_UNCLEAN_SHUTDOWN; } else if (strncmp(optarg, "UNKNOWN", MAXLEN) == 0) { node_status = NODE_STATUS_UNKNOWN; } } break; } } free_parsed_argv(&argv_array); return node_status; } static ConnectionStatus parse_remote_node_replication_connection(const char *node_check_output) { ConnectionStatus conn_status = CONN_UNKNOWN; int c = 0, argc_item = 0; char **argv_array = NULL; int optindex = 0; /* We're only interested in these options */ struct option node_check_options[] = { {"connection", required_argument, NULL, 'c'}, {NULL, 0, NULL, 0} }; /* Don't attempt to tokenise an empty string */ if (!strlen(node_check_output)) { return CONN_UNKNOWN; } argc_item = parse_output_to_argv(node_check_output, &argv_array); /* Reset getopt's optind variable */ optind = 0; /* Prevent getopt from emitting errors */ opterr = 0; while ((c = getopt_long(argc_item, argv_array, "L:S:", node_check_options, &optindex)) != -1) { switch (c) { /* --connection */ case 'c': { if (strncmp(optarg, "OK", MAXLEN) == 0) { conn_status = CONN_OK; } else if (strncmp(optarg, "BAD", MAXLEN) == 0) { conn_status = CONN_BAD; } else if (strncmp(optarg, "UNKNOWN", MAXLEN) == 0) { conn_status = CONN_UNKNOWN; } } break; } } free_parsed_argv(&argv_array); return conn_status; } static CheckStatus parse_node_check_archiver(const char *node_check_output, int *files, int *threshold, t_remote_error_type *remote_error) { CheckStatus status = CHECK_STATUS_UNKNOWN; int c = 0, argc_item = 0; char **argv_array = NULL; int optindex = 0; /* We're only interested in these options */ struct option node_check_options[] = { {"status", required_argument, NULL, 'S'}, {"files", required_argument, NULL, 'f'}, {"threshold", required_argument, NULL, 't'}, {"error", required_argument, NULL, 'E'}, {NULL, 0, NULL, 0} }; *files = 0; *threshold = 0; /* Don't attempt to tokenise an empty string */ if (!strlen(node_check_output)) { return status; } argc_item = parse_output_to_argv(node_check_output, &argv_array); /* Reset getopt's optind variable */ optind = 0; /* Prevent getopt from emitting errors */ opterr = 0; while ((c = getopt_long(argc_item, argv_array, "f:S:t:", node_check_options, &optindex)) != -1) { switch (c) { /* --files */ case 'f': *files = atoi(optarg); break; case 't': *threshold = atoi(optarg); break; /* --status */ case 'S': status = parse_check_status(optarg); break; case 'E': { *remote_error = parse_remote_error(optarg); status = CHECK_STATUS_UNKNOWN; } break; } } free_parsed_argv(&argv_array); return status; } static bool parse_data_directory_config(const char *node_check_output, t_remote_error_type *remote_error) { bool config_ok = true; int c = 0, argc_item = 0; char **argv_array = NULL; int optindex = 0; /* We're only interested in these options */ struct option node_check_options[] = { {"configured-data-directory", required_argument, NULL, 'C'}, {"error", required_argument, NULL, 'E'}, {NULL, 0, NULL, 0} }; /* Don't attempt to tokenise an empty string */ if (!strlen(node_check_output)) { return false; } argc_item = parse_output_to_argv(node_check_output, &argv_array); /* Reset getopt's optind variable */ optind = 0; /* Prevent getopt from emitting errors */ opterr = 0; while ((c = getopt_long(argc_item, argv_array, "C:E:", node_check_options, &optindex)) != -1) { switch (c) { /* --configured-data-directory */ case 'C': { /* we only care whether it's "OK" or not */ if (strncmp(optarg, "OK", 2) != 0) config_ok = false; } break; case 'E': { *remote_error = parse_remote_error(optarg); config_ok = false; } break; } } free_parsed_argv(&argv_array); return config_ok; } static bool parse_replication_config_owner(const char *node_check_output) { bool config_ok = true; int c = 0, argc_item = 0; char **argv_array = NULL; int optindex = 0; /* We're only interested in these options */ struct option node_check_options[] = { {"replication-config-owner", required_argument, NULL, 'C'}, {NULL, 0, NULL, 0} }; /* Don't attempt to tokenise an empty string */ if (!strlen(node_check_output)) { return false; } argc_item = parse_output_to_argv(node_check_output, &argv_array); /* Reset getopt's optind variable */ optind = 0; /* Prevent getopt from emitting errors */ opterr = 0; while ((c = getopt_long(argc_item, argv_array, "C:", node_check_options, &optindex)) != -1) { switch (c) { /* --configured-data-directory */ case 'C': { /* we only care whether it's "OK" or not */ if (strncmp(optarg, "OK", 2) != 0) config_ok = false; } break; } } free_parsed_argv(&argv_array); return config_ok; } static CheckStatus parse_db_connection(const char *db_connection) { CheckStatus status = CHECK_STATUS_UNKNOWN; int c = 0, argc_item = 0; char **argv_array = NULL; int optindex = 0; /* We're only interested in this option */ struct option node_check_options[] = { {"db-connection", required_argument, NULL, 'c'}, {NULL, 0, NULL, 0} }; /* Don't attempt to tokenise an empty string */ if (!strlen(db_connection)) { return false; } argc_item = parse_output_to_argv(db_connection, &argv_array); /* Reset getopt's optind variable */ optind = 0; /* Prevent getopt from emitting errors */ opterr = 0; while ((c = getopt_long(argc_item, argv_array, "c:", node_check_options, &optindex)) != -1) { switch (c) { /* --db-connection */ case 'c': { status = parse_check_status(optarg); } break; } } free_parsed_argv(&argv_array); return status; } void do_standby_help(void) { print_help_header(); printf(_("Usage:\n")); printf(_(" %s [OPTIONS] standby clone\n"), progname()); printf(_(" %s [OPTIONS] standby register\n"), progname()); printf(_(" %s [OPTIONS] standby unregister\n"), progname()); printf(_(" %s [OPTIONS] standby promote\n"), progname()); printf(_(" %s [OPTIONS] standby follow\n"), progname()); printf(_(" %s [OPTIONS] standby switchover\n"), progname()); puts(""); printf(_("STANDBY CLONE\n")); puts(""); printf(_(" \"standby clone\" clones a standby from the primary or an upstream node.\n")); puts(""); printf(_(" -d, --dbname=conninfo conninfo of the upstream node to use for cloning.\n")); printf(_(" -c, --fast-checkpoint force fast checkpoint\n")); printf(_(" --copy-external-config-files[={samepath|pgdata}]\n" \ " copy configuration files located outside the \n" \ " data directory to the same path on the standby (default) or to the\n" \ " PostgreSQL data directory\n")); printf(_(" --dry-run perform checks but don't actually clone the standby\n")); printf(_(" --no-upstream-connection when using Barman, do not connect to upstream node\n")); printf(_(" -R, --remote-user=USERNAME database server username for SSH operations (default: \"%s\")\n"), runtime_options.username); printf(_(" --replication-user user to make replication connections with (optional, not usually required)\n")); printf(_(" -S, --superuser=USERNAME superuser to use, if repmgr user is not superuser\n")); printf(_(" --upstream-conninfo \"primary_conninfo\" value to write in recovery.conf\n" \ " when the intended upstream server does not yet exist\n")); printf(_(" --upstream-node-id ID of the upstream node to replicate from (optional, defaults to primary node)\n")); #if (PG_VERSION_NUM >= 130000) printf(_(" --verify-backup verify a cloned node using the \"pg_verifybackup\" utility\n")); #endif printf(_(" --without-barman do not clone from Barman even if configured\n")); printf(_(" --replication-conf-only generate replication configuration for a previously cloned instance\n")); printf(_(" --recovery-min-apply-delay set PostgreSQL configuration parameter \"recovery_min_apply_delay\"\n" \ " (overrides any setting in repmgr.conf)\n")); puts(""); printf(_("STANDBY REGISTER\n")); puts(""); printf(_(" \"standby register\" registers the standby node.\n")); puts(""); printf(_(" -F, --force overwrite an existing node record, or if primary connection\n" \ " parameters supplied, create record even if standby offline\n")); printf(_(" --upstream-node-id ID of the upstream node to replicate from (optional)\n")); printf(_(" --wait-start=VALUE wait for the standby to start (timeout in seconds, default %i)\n"), DEFAULT_WAIT_START); printf(_(" --wait-sync[=VALUE] wait for the node record to synchronise to the standby\n" \ " (optional timeout in seconds)\n")); puts(""); printf(_("STANDBY UNREGISTER\n")); puts(""); printf(_(" \"standby unregister\" unregisters an inactive standby node.\n")); puts(""); printf(_(" --node-id ID of node to unregister (optional, used when the node to\n" \ " unregister is offline)\n")); puts(""); printf(_("STANDBY PROMOTE\n")); puts(""); printf(_(" \"standby promote\" promotes a standby node to primary.\n")); puts(""); printf(_(" --dry-run perform checks etc. but don't actually promote the node\n")); printf(_(" -F, --force ignore warnings and continue anyway\n")); printf(_(" --siblings-follow have other standbys follow new primary\n")); puts(""); printf(_("STANDBY FOLLOW\n")); puts(""); printf(_(" \"standby follow\" instructs a standby node to follow a new primary.\n")); puts(""); printf(_(" --dry-run perform checks but don't actually follow the new primary\n")); printf(_(" --upstream-node-id node ID of the new primary\n")); printf(_(" -W, --wait wait for a primary to appear\n")); puts(""); printf(_("STANDBY SWITCHOVER\n")); puts(""); printf(_(" \"standby switchover\" promotes a standby node to primary, and demotes the previous primary to a standby.\n")); puts(""); printf(_(" --always-promote promote standby even if behind original primary\n")); printf(_(" --dry-run perform checks etc. but don't actually execute switchover\n")); printf(_(" -F, --force ignore warnings and continue anyway\n")); printf(_(" --force-rewind[=VALUE] use \"pg_rewind\" to reintegrate the old primary if necessary\n")); printf(_(" (PostgreSQL 9.4 - provide \"pg_rewind\" path)\n")); printf(_(" -R, --remote-user=USERNAME database server username for SSH operations (default: \"%s\")\n"), runtime_options.username); printf(_(" -S, --superuser=USERNAME superuser to use, if repmgr user is not superuser\n")); printf(_(" --repmgrd-no-pause don't pause repmgrd\n")); printf(_(" --siblings-follow have other standbys follow new primary\n")); puts(""); printf(_("%s home page: <%s>\n"), "repmgr", REPMGR_URL); } repmgr-5.3.1/repmgr-action-standby.h000066400000000000000000000024471420262710000173770ustar00rootroot00000000000000/* * repmgr-action-standby.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _REPMGR_ACTION_STANDBY_H_ #define _REPMGR_ACTION_STANDBY_H_ extern void do_standby_clone(void); extern void do_standby_register(void); extern void do_standby_unregister(void); extern void do_standby_promote(void); extern void do_standby_follow(void); extern void do_standby_switchover(void); extern void do_standby_help(void); extern bool do_standby_follow_internal(PGconn *primary_conn, PGconn *follow_target_conn, t_node_info *follow_target_node_record, PQExpBufferData *output, int general_error_code, int *error_code); #endif /* _REPMGR_ACTION_STANDBY_H_ */ repmgr-5.3.1/repmgr-action-witness.c000066400000000000000000000370501420262710000174200ustar00rootroot00000000000000/* * repmgr-action-witness.c * * Implements witness actions for the repmgr command line utility * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #include #include "repmgr.h" #include "dirutil.h" #include "compat.h" #include "controldata.h" #include "repmgr-client-global.h" #include "repmgr-action-witness.h" static char repmgr_user[MAXLEN]; static char repmgr_db[MAXLEN]; void do_witness_register(void) { PGconn *witness_conn = NULL; PGconn *primary_conn = NULL; int primary_node_id = UNKNOWN_NODE_ID; RecoveryType recovery_type = RECTYPE_UNKNOWN; ExtensionStatus extension_status = REPMGR_UNKNOWN; NodeInfoList nodes = T_NODE_INFO_LIST_INITIALIZER; t_node_info node_record = T_NODE_INFO_INITIALIZER; t_node_info primary_node_record = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; bool record_created = false; log_info(_("connecting to witness node \"%s\" (ID: %i)"), config_file_options.node_name, config_file_options.node_id); witness_conn = establish_db_connection_quiet(config_file_options.conninfo); if (PQstatus(witness_conn) != CONNECTION_OK) { log_error(_("unable to connect to witness node \"%s\" (ID: %i)"), config_file_options.node_name, config_file_options.node_id); log_detail("\n%s", PQerrorMessage(witness_conn)); log_hint(_("the witness node must be running before it can be registered")); exit(ERR_BAD_CONFIG); } /* check witness node's recovery type */ recovery_type = get_recovery_type(witness_conn); if (recovery_type == RECTYPE_STANDBY) { log_error(_("provided node is a standby")); log_hint(_("a witness node must run on an independent primary server")); PQfinish(witness_conn); exit(ERR_BAD_CONFIG); } /* connect to primary with provided parameters */ log_info(_("connecting to primary node")); /* * Extract the repmgr user and database names from the conninfo string * provided in repmgr.conf */ get_conninfo_value(config_file_options.conninfo, "user", repmgr_user); get_conninfo_value(config_file_options.conninfo, "dbname", repmgr_db); param_set_ine(&source_conninfo, "user", repmgr_user); param_set_ine(&source_conninfo, "dbname", repmgr_db); /* We need to connect to check configuration and copy it */ primary_conn = establish_db_connection_by_params(&source_conninfo, false); if (PQstatus(primary_conn) != CONNECTION_OK) { log_error(_("unable to connect to the primary node")); log_hint(_("a primary node must be configured before registering a witness node")); PQfinish(witness_conn); exit(ERR_BAD_CONFIG); } /* check primary node's recovery type */ recovery_type = get_recovery_type(primary_conn); if (recovery_type == RECTYPE_STANDBY) { log_error(_("provided primary node is a standby")); log_hint(_("provide the connection details of the cluster's primary server")); PQfinish(witness_conn); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } /* check we can determine the primary node */ primary_node_id = get_primary_node_id(primary_conn); if (primary_node_id == UNKNOWN_NODE_ID) { log_error(_("unable to determine the cluster's primary node")); log_hint(_("ensure the primary node connection details are correct and that it is registered")); PQfinish(witness_conn); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } record_status = get_node_record(primary_conn, primary_node_id, &primary_node_record); PQfinish(primary_conn); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve record for primary node %i"), primary_node_id); PQfinish(witness_conn); exit(ERR_BAD_CONFIG); } /* * Reconnect to the primary node's conninfo - this will * protect against the situation where the witness connection * details were provided, and we're actually connected to the * witness server. */ primary_conn = establish_db_connection_quiet(primary_node_record.conninfo); if (PQstatus(primary_conn) != CONNECTION_OK) { log_error(_("unable to reconnect to the primary node (node %i)"), primary_node_id); log_detail(_("primary node's conninfo is \"%s\""), primary_node_record.conninfo); PQfinish(witness_conn); exit(ERR_BAD_CONFIG); } /* Sanity check witness node is not part of main cluster. */ if (PQserverVersion(primary_conn) >= 90600 && PQserverVersion(witness_conn) >= 90600) { uint64 primary_system_identifier = system_identifier(primary_conn); uint64 witness_system_identifier = system_identifier(witness_conn); if (primary_system_identifier == witness_system_identifier && primary_system_identifier != UNKNOWN_SYSTEM_IDENTIFIER) { log_error(_("witness node cannot be in the same cluster as the primary node")); log_detail(_("database system identifiers on primary node and provided witness node match (%lu)"), primary_system_identifier); log_hint(_("the witness node must be created on a separate read/write node")); PQfinish(witness_conn); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } } /* create repmgr extension, if does not exist */ if (runtime_options.dry_run == false && !create_repmgr_extension(witness_conn)) { PQfinish(witness_conn); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } /* * check if node record exists on primary, overwrite if -F/--force provided, * otherwise exit with error */ record_status = get_node_record(primary_conn, config_file_options.node_id, &node_record); if (record_status == RECORD_FOUND) { /* * If node is not a witness, cowardly refuse to do anything, let the * user work out what's the correct thing to do. */ if (node_record.type != WITNESS) { log_error(_("node \"%s\" (ID: %i) is already registered as a %s node"), config_file_options.node_name, config_file_options.node_id, get_node_type_string(node_record.type)); log_hint(_("use \"repmgr %s unregister\" to remove a non-witness node record"), get_node_type_string(node_record.type)); PQfinish(witness_conn); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } if (!runtime_options.force) { log_error(_("witness node is already registered")); log_hint(_("use option -F/--force to reregister the node")); PQfinish(witness_conn); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } } /* * Check that an active node with the same node_name doesn't exist already */ record_status = get_node_record_by_name(primary_conn, config_file_options.node_name, &node_record); if (record_status == RECORD_FOUND) { if (node_record.active == true && node_record.node_id != config_file_options.node_id) { log_error(_("node %i exists already with node_name \"%s\""), node_record.node_id, config_file_options.node_name); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } } extension_status = get_repmgr_extension_status(witness_conn, NULL); /* * Check if the witness database already contains node records; * only do this if the extension is actually installed. */ if (extension_status == REPMGR_INSTALLED || extension_status == REPMGR_OLD_VERSION_INSTALLED) { /* * if repmgr.nodes contains entries, exit with error unless * -F/--force provided (which will cause the existing records * to be overwritten) */ if (get_all_node_records(witness_conn, &nodes) == false) { /* get_all_node_records() will display the error */ PQfinish(witness_conn); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } log_verbose(LOG_DEBUG, "%i node records found", nodes.node_count); if (nodes.node_count > 0) { if (!runtime_options.force) { log_error(_("witness node is already initialised and contains node records")); log_hint(_("use option -F/--force to reinitialise the node")); PQfinish(primary_conn); PQfinish(witness_conn); exit(ERR_BAD_CONFIG); } } clear_node_info_list(&nodes); } if (runtime_options.dry_run == true) { log_info(_("prerequisites for registering the witness node are met")); PQfinish(primary_conn); PQfinish(witness_conn); exit(SUCCESS); } /* create record on primary */ /* * node record exists - update it (at this point we have already * established that -F/--force is in use) */ init_node_record(&node_record); /* these values are mandatory, setting them to anything else has no point */ node_record.type = WITNESS; node_record.priority = 0; node_record.upstream_node_id = primary_node_id; if (record_status == RECORD_FOUND) { record_created = update_node_record(primary_conn, "witness register", &node_record); } else { record_created = create_node_record(primary_conn, "witness register", &node_record); } if (record_created == false) { log_error(_("unable to create or update node record on primary")); PQfinish(primary_conn); PQfinish(witness_conn); exit(ERR_BAD_CONFIG); } /* sync records from primary */ if (witness_copy_node_records(primary_conn, witness_conn) == false) { log_error(_("unable to copy repmgr node records from primary")); PQfinish(primary_conn); PQfinish(witness_conn); exit(ERR_BAD_CONFIG); } { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("witness registration succeeded; upstream node ID is %i"), node_record.upstream_node_id); /* create event */ create_event_notification(primary_conn, &config_file_options, config_file_options.node_id, "witness_register", true, event_details.data); termPQExpBuffer(&event_details); } PQfinish(primary_conn); PQfinish(witness_conn); log_info(_("witness registration complete")); log_notice(_("witness node \"%s\" (ID: %i) successfully registered"), config_file_options.node_name, config_file_options.node_id); return; } void do_witness_unregister(void) { PGconn *local_conn = NULL; PGconn *primary_conn = NULL; t_node_info node_record = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; bool node_record_deleted = false; bool local_node_available = true; int witness_node_id = UNKNOWN_NODE_ID; if (runtime_options.node_id != UNKNOWN_NODE_ID) { /* user has specified the witness node id */ witness_node_id = runtime_options.node_id; } else { /* assume witness node is local node */ witness_node_id = config_file_options.node_id; } log_info(_("connecting to node \"%s\" (ID: %i)"), config_file_options.node_name, config_file_options.node_id); local_conn = establish_db_connection_quiet(config_file_options.conninfo); if (PQstatus(local_conn) != CONNECTION_OK) { if (!runtime_options.force) { log_error(_("unable to connect to node \"%s\" (ID: %i)"), config_file_options.node_name, config_file_options.node_id); log_detail("\n%s", PQerrorMessage(local_conn)); exit(ERR_BAD_CONFIG); } log_notice(_("unable to connect to witness node \"%s\" (ID: %i), removing node record on cluster primary only"), config_file_options.node_name, config_file_options.node_id); local_node_available = false; } if (local_node_available == true) { primary_conn = get_primary_connection_quiet(local_conn, NULL, NULL); } else { /* * Assume user has provided connection details for the primary server */ primary_conn = establish_db_connection_by_params(&source_conninfo, false); } if (PQstatus(primary_conn) != CONNECTION_OK) { log_error(_("unable to connect to primary")); log_detail("\n%s", PQerrorMessage(primary_conn)); if (local_node_available == true) { PQfinish(local_conn); } else if (runtime_options.connection_param_provided == false) { log_hint(_("provide connection details for the primary server")); } exit(ERR_BAD_CONFIG); } /* Check node exists and is really a witness */ record_status = get_node_record(primary_conn, witness_node_id, &node_record); if (record_status != RECORD_FOUND) { log_error(_("no record found for node %i"), witness_node_id); if (local_node_available == true) PQfinish(local_conn); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } if (node_record.type != WITNESS) { /* * The node (either explicitly provided with --node-id, or the local node) * is not a witness. * * TODO: scan node list and print hint about identity of known witness servers. */ log_error(_("node %i is not a witness node"), config_file_options.node_id); log_detail(_("node %i is a %s node"), config_file_options.node_id, get_node_type_string(node_record.type)); if (local_node_available == true) PQfinish(local_conn); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } if (runtime_options.dry_run == true) { log_info(_("prerequisites for unregistering the witness node are met")); if (local_node_available == true) PQfinish(local_conn); PQfinish(primary_conn); exit(SUCCESS); } log_info(_("unregistering witness node %i"), witness_node_id); node_record_deleted = delete_node_record(primary_conn, witness_node_id); if (node_record_deleted == false) { PQfinish(primary_conn); if (local_node_available == true) PQfinish(local_conn); PQfinish(local_conn); exit(ERR_BAD_CONFIG); } { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBufferStr(&event_details, _("witness unregistration succeeded")); /* create event */ create_event_notification(primary_conn, &config_file_options, witness_node_id, "witness_unregister", true, event_details.data); termPQExpBuffer(&event_details); } PQfinish(primary_conn); if (local_node_available == true) PQfinish(local_conn); log_info(_("witness unregistration complete")); log_detail(_("witness node with ID %i successfully unregistered"), witness_node_id); return; } void do_witness_help(void) { print_help_header(); printf(_("Usage:\n")); printf(_(" %s [OPTIONS] witness register\n"), progname()); printf(_(" %s [OPTIONS] witness unregister\n"), progname()); puts(""); printf(_("WITNESS REGISTER\n")); puts(""); printf(_(" \"witness register\" registers a witness node.\n")); puts(""); printf(_(" Requires provision of connection information for the primary node,\n")); printf(_(" typically usually just the host name.\n")); puts(""); printf(_(" -h/--host host name of the primary node\n")); printf(_(" --dry-run check prerequisites but don't make any changes\n")); printf(_(" -F, --force overwrite an existing node record\n")); puts(""); printf(_("WITNESS UNREGISTER\n")); puts(""); printf(_(" \"witness unregister\" unregisters a witness node.\n")); puts(""); printf(_(" --dry-run check prerequisites but don't make any changes\n")); printf(_(" -F, --force unregister when witness node not running\n")); printf(_(" --node-id node ID of the witness node (provide if executing on\n")); printf(_(" another node)\n")); puts(""); printf(_("%s home page: <%s>\n"), "repmgr", REPMGR_URL); } repmgr-5.3.1/repmgr-action-witness.h000066400000000000000000000017101420262710000174170ustar00rootroot00000000000000/* * repmgr-action-witness.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _REPMGR_ACTION_WITNESS_H_ #define _REPMGR_ACTION_WITNESS_H_ extern void do_witness_register(void); extern void do_witness_unregister(void); extern void do_witness_help(void); #endif /* _REPMGR_ACTION_WITNESS_H_ */ repmgr-5.3.1/repmgr-client-global.h000066400000000000000000000210161420262710000171650ustar00rootroot00000000000000/* * repmgr-client-global.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _REPMGR_CLIENT_GLOBAL_H_ #define _REPMGR_CLIENT_GLOBAL_H_ #include "configfile.h" /* values for --copy-external-config-files */ #define CONFIG_FILE_SAMEPATH 1 #define CONFIG_FILE_PGDATA 2 /* default value for "cluster event --limit"*/ #define CLUSTER_EVENT_LIMIT 20 typedef struct { /* configuration metadata */ bool conninfo_provided; bool connection_param_provided; bool host_param_provided; bool limit_provided; bool wait_provided; /* general configuration options */ char config_file[MAXPGPATH]; bool dry_run; bool force; char pg_bindir[MAXLEN]; /* overrides setting in repmgr.conf */ int wait; bool no_wait; bool compact; bool detail; bool dump_config; /* logging options */ char log_level[MAXLEN]; /* overrides setting in repmgr.conf */ bool log_to_file; bool quiet; bool terse; bool verbose; /* output options */ bool csv; bool nagios; bool optformat; /* standard connection options */ char dbname[MAXLEN]; char host[MAXLEN]; char username[MAXLEN]; char port[MAXLEN]; /* other connection options */ char remote_user[MAXLEN]; char superuser[MAXLEN]; /* general node options */ int node_id; char node_name[NAMEDATALEN]; char data_dir[MAXPGPATH]; int remote_node_id; /* "standby clone" options */ bool copy_external_config_files; int copy_external_config_files_destination; bool fast_checkpoint; bool rsync_only; bool no_upstream_connection; char recovery_min_apply_delay[MAXLEN]; /* overrides setting in repmgr.conf */ char replication_user[MAXLEN]; char upstream_conninfo[MAXLEN]; bool without_barman; bool replication_conf_only; bool verify_backup; /* "standby clone"/"standby follow" options */ int upstream_node_id; /* "standby register" options */ bool wait_register_sync; int wait_register_sync_seconds; int wait_start; /* "standby switchover" options */ bool always_promote; bool force_rewind_used; char force_rewind_path[MAXPGPATH]; bool siblings_follow; bool repmgrd_no_pause; bool repmgrd_force_unpause; /* "node status" options */ bool is_shutdown_cleanly; /* "node check" options */ bool archive_ready; bool downstream; bool upstream; bool replication_lag; bool role; bool slots; bool missing_slots; bool has_passfile; bool replication_connection; bool repmgrd; bool data_directory_config; bool replication_config_owner; bool db_connection; /* "node rejoin" options */ char config_files[MAXLEN]; /* "node service" options */ char action[MAXLEN]; bool check; bool list_actions; bool checkpoint; /* "cluster event" options */ bool all; char event[MAXLEN]; int limit; /* "cluster cleanup" options */ int keep_history; /* following options for internal use */ char config_archive_dir[MAXPGPATH]; OutputMode output_mode; /* set through provision of --csv, --nagios or --optformat */ bool disable_wal_receiver; bool enable_wal_receiver; } t_runtime_options; #define T_RUNTIME_OPTIONS_INITIALIZER { \ /* configuration metadata */ \ false, false, false, false, false, \ /* general configuration options */ \ "", false, false, "", -1, false, false, false, false, \ /* logging options */ \ "", false, false, false, false, \ /* output options */ \ false, false, false, \ /* database connection options */ \ "", "", "", "", \ /* other connection options */ \ "", "", \ /* general node options */ \ UNKNOWN_NODE_ID, "", "", UNKNOWN_NODE_ID, \ /* "standby clone" options */ \ false, CONFIG_FILE_SAMEPATH, false, false, false, "", "", "", \ false, false, false, \ /* "standby clone"/"standby follow" options */ \ NO_UPSTREAM_NODE, \ /* "standby register" options */ \ false, -1, DEFAULT_WAIT_START, \ /* "standby switchover" options */ \ false, false, "", false, false, false, \ /* "node status" options */ \ false, \ /* "node check" options */ \ false, false, false, false, false, false, false, false, false, false, false, false, false, \ /* "node rejoin" options */ \ "", \ /* "node service" options */ \ "", false, false, false, \ /* "cluster event" options */ \ false, "", CLUSTER_EVENT_LIMIT, \ /* "cluster cleanup" options */ \ 0, \ /* following options for internal use */ \ "/tmp", OM_TEXT, false, false \ } typedef enum { barman, pg_basebackup } standy_clone_mode; typedef enum { ACTION_UNKNOWN = -1, ACTION_NONE, ACTION_START, ACTION_STOP, ACTION_STOP_WAIT, ACTION_RESTART, ACTION_RELOAD, ACTION_PROMOTE } t_server_action; typedef enum { USER_TYPE_UNKNOWN = -1, REPMGR_USER, REPLICATION_USER_OPT, REPLICATION_USER_NODE, SUPERUSER } t_user_type; typedef enum { JOIN_UNKNOWN = -1, JOIN_SUCCESS, JOIN_COMMAND_FAIL, JOIN_FAIL_NO_PING, JOIN_FAIL_NO_REPLICATION } standy_join_status; typedef enum { REMOTE_ERROR_UNKNOWN = -1, REMOTE_ERROR_NONE, REMOTE_ERROR_DB_CONNECTION, REMOTE_ERROR_CONNINFO_PARSE } t_remote_error_type; typedef struct ColHeader { char title[MAXLEN]; int max_length; int cur_length; bool display; } ColHeader; /* globally available configuration structures */ extern t_runtime_options runtime_options; extern t_conninfo_param_list source_conninfo; extern t_node_info target_node_info; /* global variables */ extern bool config_file_required; extern char pg_bindir[MAXLEN]; /* global functions */ extern int check_server_version(PGconn *conn, char *server_type, bool exit_on_error, char *server_version_string); extern bool create_repmgr_extension(PGconn *conn); extern int test_ssh_connection(char *host, char *remote_user); extern standy_clone_mode get_standby_clone_mode(void); extern int copy_remote_files(char *host, char *remote_user, char *remote_path, char *local_path, bool is_directory, int server_version_num); extern void print_error_list(ItemList *error_list, int log_level); extern void make_pg_path(PQExpBufferData *buf, const char *file); extern void get_superuser_connection(PGconn **conn, PGconn **superuser_conn, PGconn **privileged_conn); extern void make_remote_repmgr_path(PQExpBufferData *outputbuf, t_node_info *remote_node_record); extern void make_repmgrd_path(PQExpBufferData *output_buf); /* display functions */ extern bool format_node_status(t_node_info *node_info, PQExpBufferData *node_status, PQExpBufferData *upstream, ItemList *warnings); extern void print_help_header(void); extern void print_status_header(int cols, ColHeader *headers); /* server control functions */ extern void get_server_action(t_server_action action, char *script, char *data_dir); extern bool data_dir_required_for_action(t_server_action action); extern void get_node_config_directory(char *config_dir_buf); extern void get_node_data_directory(char *data_dir_buf); extern void init_node_record(t_node_info *node_record); extern bool can_use_pg_rewind(PGconn *conn, const char *data_directory, PQExpBufferData *reason); extern void make_standby_signal_path(const char *data_dir, char *buf); extern bool write_standby_signal(const char *data_dir); extern bool create_replication_slot(PGconn *conn, char *slot_name, t_node_info *upstream_node_record, PQExpBufferData *error_msg); extern bool drop_replication_slot_if_exists(PGconn *conn, int node_id, char *slot_name); extern standy_join_status check_standby_join(PGconn *primary_conn, t_node_info *primary_node_record, t_node_info *standby_node_record); extern bool check_replication_slots_available(int node_id, PGconn* conn); extern bool check_node_can_attach(TimeLineID local_tli, XLogRecPtr local_xlogpos, PGconn *follow_target_conn, t_node_info *follow_target_node_record, bool is_rejoin); extern bool check_replication_config_owner(int pg_version, const char *data_directory, PQExpBufferData *error_msg, PQExpBufferData *detail_msg); extern void check_shared_library(PGconn *conn); extern bool is_repmgrd_running(PGconn *conn); extern int parse_repmgr_version(const char *version_string); #endif /* _REPMGR_CLIENT_GLOBAL_H_ */ repmgr-5.3.1/repmgr-client.c000066400000000000000000003445711420262710000157400ustar00rootroot00000000000000/* * repmgr-client.c - Command interpreter for the repmgr package * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * This module is a command-line utility to easily setup a cluster of * hot standby servers for an HA environment * * Commands implemented are: * * [ PRIMARY | MASTER ] REGISTER * [ PRIMARY | MASTER ] UNREGISTER * * STANDBY CLONE * STANDBY REGISTER * STANDBY UNREGISTER * STANDBY PROMOTE * STANDBY FOLLOW * STANDBY SWITCHOVER * * CLUSTER SHOW * CLUSTER EVENT * CLUSTER CROSSCHECK * CLUSTER MATRIX * CLUSTER CLEANUP * * NODE STATUS * NODE CHECK * NODE REJOIN * NODE SERVICE * NODE CONTROL * * SERVICE STATUS * SERVICE PAUSE * SERVICE UNPAUSE * * DAEMON START * DAEMON STOP * * 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 . */ #include #include #include #include #include "repmgr.h" #include "compat.h" #include "controldata.h" #include "repmgr-client.h" #include "repmgr-client-global.h" #include "repmgr-action-primary.h" #include "repmgr-action-standby.h" #include "repmgr-action-witness.h" #include "repmgr-action-node.h" #include "repmgr-action-cluster.h" #include "repmgr-action-service.h" #include "repmgr-action-daemon.h" #include /* for PG_TEMP_FILE_PREFIX */ /* globally available variables * * ============================ */ t_runtime_options runtime_options = T_RUNTIME_OPTIONS_INITIALIZER; /* conninfo params for the node we're operating on */ t_conninfo_param_list source_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; bool config_file_required = true; char pg_bindir[MAXPGPATH] = ""; /* * if --node-id/--node-name provided, place that node's record here * for later use */ t_node_info target_node_info = T_NODE_INFO_INITIALIZER; /* used by create_replication_slot() */ static t_user_type ReplicationSlotUser = USER_TYPE_UNKNOWN; /* Collate command line errors and warnings here for friendlier reporting */ static ItemList cli_errors = {NULL, NULL}; static ItemList cli_warnings = {NULL, NULL}; static void _determine_replication_slot_user(PGconn *conn, t_node_info *upstream_node_record, char **replication_user); int main(int argc, char **argv) { t_conninfo_param_list default_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; int optindex = 0; int c; char *repmgr_command = NULL; char *repmgr_action = NULL; bool valid_repmgr_command_found = true; int action = NO_ACTION; char *dummy_action = ""; bool help_option = false; bool option_error_found = false; set_progname(argv[0]); /* * Tell the logger we're a command-line program - this will ensure any * output logged before the logger is initialized will be formatted * correctly. Can be overridden with "--log-to-file". */ logger_output_mode = OM_COMMAND_LINE; /* * Initialize and pre-populate conninfo parameters; these will be * overwritten if matching command line parameters are provided. * * Only some actions will need these, but we need to do this before the * command line is parsed. * * Note: PQconndefaults() does not provide a default value for "dbname", * but if none is provided will default to "username" when the connection * is made. We won't set "dbname" here if no default available, as that * would break the libpq behaviour if non-default username is provided. */ initialize_conninfo_params(&default_conninfo, true); for (c = 0; c < default_conninfo.size && default_conninfo.keywords[c]; c++) { if (strcmp(default_conninfo.keywords[c], "host") == 0 && (default_conninfo.values[c] != NULL)) { strncpy(runtime_options.host, default_conninfo.values[c], MAXLEN); } else if (strcmp(default_conninfo.keywords[c], "hostaddr") == 0 && (default_conninfo.values[c] != NULL)) { strncpy(runtime_options.host, default_conninfo.values[c], MAXLEN); } else if (strcmp(default_conninfo.keywords[c], "port") == 0 && (default_conninfo.values[c] != NULL)) { strncpy(runtime_options.port, default_conninfo.values[c], MAXLEN); } else if (strcmp(default_conninfo.keywords[c], "dbname") == 0 && (default_conninfo.values[c] != NULL)) { strncpy(runtime_options.dbname, default_conninfo.values[c], MAXLEN); } else if (strcmp(default_conninfo.keywords[c], "user") == 0 && (default_conninfo.values[c] != NULL)) { strncpy(runtime_options.username, default_conninfo.values[c], MAXLEN); } } free_conninfo_params(&default_conninfo); initialize_conninfo_params(&source_conninfo, false); /* set default user for -R/--remote-user */ { struct passwd *pw = getpwuid(geteuid()); if (pw == NULL) { fprintf(stderr, _("could not get current user name: %s\n"), strerror(errno)); exit(ERR_BAD_CONFIG); } strncpy(runtime_options.username, pw->pw_name, MAXLEN); } /* Make getopt emit errors */ opterr = 1; while ((c = getopt_long(argc, argv, "?Vb:f:FwWd:h:p:U:R:S:D:ck:L:qtvC:", long_options, &optindex)) != -1) { /* * NOTE: some integer parameters (e.g. -p/--port) are stored * internally as strings. We use repmgr_atoi() to check these but * discard the returned integer; repmgr_atoi() will append the error * message to the provided list. */ switch (c) { /* * Options which cause repmgr to exit in this block; these are * the only ones which can be executed as root user */ case OPT_HELP: /* --help */ help_option = true; break; /* -V/--version */ case 'V': /* * in contrast to repmgr3 and earlier, we only display the * repmgr version as it's not specific to a particular * PostgreSQL version */ printf("%s %s\n", progname(), REPMGR_VERSION); exit(SUCCESS); /* --version-number */ case OPT_VERSION_NUMBER: printf("%i\n", REPMGR_VERSION_NUM); exit(SUCCESS); /*------------------------------ * general configuration options *------------------------------ */ /* -b/--pg_bindir */ case 'b': strncpy(runtime_options.pg_bindir, optarg, MAXLEN); break; /* -f/--config-file */ case 'f': strncpy(runtime_options.config_file, optarg, MAXLEN); break; /* --dry-run */ case OPT_DRY_RUN: runtime_options.dry_run = true; break; /* -F/--force */ case 'F': runtime_options.force = true; break; /* --replication-user (primary/standby register only) */ case OPT_REPLICATION_USER: strncpy(runtime_options.replication_user, optarg, MAXLEN); break; /* -w/--wait */ case 'w': runtime_options.wait_provided = true; if (optarg != NULL) { runtime_options.wait = repmgr_atoi(optarg, "--wait", &cli_errors, 0); } break; /* -W/--no-wait */ case 'W': runtime_options.no_wait = true; break; /* --compact */ case OPT_COMPACT: runtime_options.compact = true; break; /* --detail */ case OPT_DETAIL: runtime_options.detail = true; break; /* --dump-config */ case OPT_DUMP_CONFIG: runtime_options.dump_config = true; break; /*---------------------------- * database connection options *---------------------------- */ /* * These are the standard database connection options; with * the exception of -d/--dbname (which could be a conninfo * string) we'll also set these values in "source_conninfo" * (overwriting preset values from environment variables). */ /* -d/--dbname */ case 'd': strncpy(runtime_options.dbname, optarg, MAXLEN); /* * dbname will be set in source_conninfo later after checking * if it's a conninfo string */ runtime_options.connection_param_provided = true; break; /* -h/--host */ case 'h': strncpy(runtime_options.host, optarg, MAXLEN); param_set(&source_conninfo, "host", optarg); runtime_options.connection_param_provided = true; runtime_options.host_param_provided = true; break; case 'p': /* * minimum TCP port number is 1; in practice PostgreSQL * won't be running on a privileged port, but we don't want * to be concerned with that level of checking */ (void) repmgr_atoi(optarg, "-p/--port", &cli_errors, 1); param_set(&source_conninfo, "port", optarg); strncpy(runtime_options.port, optarg, MAXLEN); runtime_options.connection_param_provided = true; break; /* -U/--user */ case 'U': strncpy(runtime_options.username, optarg, MAXLEN); param_set(&source_conninfo, "user", optarg); runtime_options.connection_param_provided = true; break; /*------------------------- * other connection options *------------------------- */ /* -R/--remote_user */ case 'R': strncpy(runtime_options.remote_user, optarg, MAXLEN); break; /* -S/--superuser */ case 'S': strncpy(runtime_options.superuser, optarg, MAXLEN); break; /*------------- * node options *------------- */ /* -D/--pgdata/--data-dir */ case 'D': strncpy(runtime_options.data_dir, optarg, MAXPGPATH); break; /* --node-id */ case OPT_NODE_ID: runtime_options.node_id = repmgr_atoi(optarg, "--node-id", &cli_errors, MIN_NODE_ID); break; /* --node-name */ case OPT_NODE_NAME: { if (strlen(optarg) < sizeof(runtime_options.node_name)) strncpy(runtime_options.node_name, optarg, sizeof(runtime_options.node_name)); else item_list_append_format(&cli_errors, _("value for \"--node-name\" must contain fewer than %lu characters"), sizeof(runtime_options.node_name)); break; } /* --remote-node-id */ case OPT_REMOTE_NODE_ID: runtime_options.remote_node_id = repmgr_atoi(optarg, "--remote-node-id", &cli_errors, MIN_NODE_ID); break; /* * standby options * --------------- */ /* --upstream-node-id */ case OPT_UPSTREAM_NODE_ID: runtime_options.upstream_node_id = repmgr_atoi(optarg, "--upstream-node-id", &cli_errors, MIN_NODE_ID); break; /*------------------------ * "standby clone" options *------------------------ */ /* -c/--fast-checkpoint */ case 'c': runtime_options.fast_checkpoint = true; break; /* --copy-external-config-files(=[samepath|pgdata]) */ case OPT_COPY_EXTERNAL_CONFIG_FILES: runtime_options.copy_external_config_files = true; if (optarg != NULL) { if (strcmp(optarg, "samepath") == 0) { runtime_options.copy_external_config_files_destination = CONFIG_FILE_SAMEPATH; } /* allow "data_directory" as synonym for "pgdata" */ else if (strcmp(optarg, "pgdata") == 0 || strcmp(optarg, "data_directory") == 0) { runtime_options.copy_external_config_files_destination = CONFIG_FILE_PGDATA; } else { item_list_append(&cli_errors, _("value provided for \"--copy-external-config-files\" must be \"samepath\" or \"pgdata\"")); } } break; /* --no-upstream-connection */ case OPT_NO_UPSTREAM_CONNECTION: runtime_options.no_upstream_connection = true; break; case OPT_UPSTREAM_CONNINFO: strncpy(runtime_options.upstream_conninfo, optarg, MAXLEN); break; case OPT_WITHOUT_BARMAN: runtime_options.without_barman = true; break; case OPT_REPLICATION_CONF_ONLY: runtime_options.replication_conf_only = true; break; /* --recovery-min-apply-delay */ case OPT_RECOVERY_MIN_APPLY_DELAY: strncpy(runtime_options.recovery_min_apply_delay, optarg, sizeof(runtime_options.recovery_min_apply_delay)); break; /* --verify-backup */ case OPT_VERIFY_BACKUP: runtime_options.verify_backup = true; break; /*--------------------------- * "standby register" options *--------------------------- */ case OPT_WAIT_START: runtime_options.wait_start = repmgr_atoi(optarg, "--wait-start", &cli_errors, 0); break; case OPT_WAIT_SYNC: runtime_options.wait_register_sync = true; if (optarg != NULL) { runtime_options.wait_register_sync_seconds = repmgr_atoi(optarg, "--wait-sync", &cli_errors, 0); } break; /*----------------------------- * "standby switchover" options *----------------------------- */ case OPT_ALWAYS_PROMOTE: runtime_options.always_promote = true; break; case OPT_FORCE_REWIND: runtime_options.force_rewind_used = true; if (optarg != NULL) { strncpy(runtime_options.force_rewind_path, optarg, MAXPGPATH); } break; case OPT_SIBLINGS_FOLLOW: runtime_options.siblings_follow = true; break; case OPT_REPMGRD_NO_PAUSE: runtime_options.repmgrd_no_pause = true; break; case OPT_REPMGRD_FORCE_UNPAUSE: runtime_options.repmgrd_force_unpause = true; break; /*---------------------- * "node status" options *---------------------- */ case OPT_IS_SHUTDOWN_CLEANLY: runtime_options.is_shutdown_cleanly = true; break; /*--------------------- * "node check" options *-------------------- */ case OPT_ARCHIVE_READY: runtime_options.archive_ready = true; break; case OPT_DOWNSTREAM: runtime_options.downstream = true; break; case OPT_UPSTREAM: runtime_options.upstream = true; break; case OPT_REPLICATION_LAG: runtime_options.replication_lag = true; break; case OPT_ROLE: runtime_options.role = true; break; case OPT_SLOTS: runtime_options.slots = true; break; case OPT_MISSING_SLOTS: runtime_options.missing_slots = true; break; case OPT_HAS_PASSFILE: runtime_options.has_passfile = true; break; case OPT_REPL_CONN: runtime_options.replication_connection = true; break; case OPT_DATA_DIRECTORY_CONFIG: runtime_options.data_directory_config = true; break; case OPT_REPMGRD: runtime_options.repmgrd = true; break; case OPT_REPLICATION_CONFIG_OWNER: runtime_options.replication_config_owner = true; break; case OPT_DB_CONNECTION: runtime_options.db_connection = true; break; /*-------------------- * "node rejoin" options *-------------------- */ case OPT_CONFIG_FILES: strncpy(runtime_options.config_files, optarg, MAXLEN); break; case OPT_CONFIG_ARCHIVE_DIR: /* TODO: check this is an absolute path */ strncpy(runtime_options.config_archive_dir, optarg, MAXPGPATH); break; /*----------------------- * "node service" options *----------------------- */ /* --action (repmgr node service --action) */ case OPT_ACTION: strncpy(runtime_options.action, optarg, MAXLEN); break; case OPT_LIST_ACTIONS: runtime_options.list_actions = true; break; case OPT_CHECKPOINT: runtime_options.checkpoint = true; break; /*------------------------ * "cluster event" options *------------------------ */ case OPT_EVENT: strncpy(runtime_options.event, optarg, MAXLEN); break; case OPT_LIMIT: runtime_options.limit = repmgr_atoi(optarg, "--limit", &cli_errors, 1); runtime_options.limit_provided = true; break; case OPT_ALL: runtime_options.all = true; break; /*------------------------ * "cluster cleanup" options *------------------------ */ /* -k/--keep-history */ case 'k': runtime_options.keep_history = repmgr_atoi(optarg, "-k/--keep-history", &cli_errors, 0); break; /*---------------- * logging options *---------------- */ /* -L/--log-level */ case 'L': { int detected_log_level = detect_log_level(optarg); if (detected_log_level != -1) { strncpy(runtime_options.log_level, optarg, MAXLEN); } else { PQExpBufferData invalid_log_level; initPQExpBuffer(&invalid_log_level); appendPQExpBuffer(&invalid_log_level, _("invalid log level \"%s\" provided"), optarg); item_list_append(&cli_errors, invalid_log_level.data); termPQExpBuffer(&invalid_log_level); } break; } /* --log-to-file */ case OPT_LOG_TO_FILE: runtime_options.log_to_file = true; logger_output_mode = OM_DAEMON; break; /* --quiet */ case 'q': runtime_options.quiet = true; break; /* --terse */ case 't': runtime_options.terse = true; break; /* --verbose */ case 'v': runtime_options.verbose = true; break; /*--------------- * output options *--------------- */ case OPT_CSV: runtime_options.csv = true; break; case OPT_NAGIOS: runtime_options.nagios = true; break; case OPT_OPTFORMAT: runtime_options.optformat = true; break; /*--------------------------------- * undocumented options for testing *---------------------------------- */ case OPT_DISABLE_WAL_RECEIVER: runtime_options.disable_wal_receiver = true; break; case OPT_ENABLE_WAL_RECEIVER: runtime_options.enable_wal_receiver = true; break; /*----------------------------- * options deprecated since 4.0 *----------------------------- */ case OPT_CHECK_UPSTREAM_CONFIG: item_list_append(&cli_warnings, _("--check-upstream-config is deprecated; use --dry-run instead")); break; /* -C/--remote-config-file */ case 'C': item_list_append(&cli_warnings, _("--remote-config-file is no longer required")); break; case ':': /* missing option argument */ option_error_found = true; break; case '?': /* Actual help option given? */ if (strcmp(argv[optind - 1], "-?") == 0) { help_option = true; } else { option_error_found = true; } break; default: /* invalid option */ option_error_found = true; break; } } /* * If -d/--dbname appears to be a conninfo string, validate by attempting * to parse it (and if successful, store the parsed parameters) */ if (runtime_options.dbname[0]) { if (strncmp(runtime_options.dbname, "postgresql://", 13) == 0 || strncmp(runtime_options.dbname, "postgres://", 11) == 0 || strchr(runtime_options.dbname, '=') != NULL) { char *errmsg = NULL; PQconninfoOption *opts; runtime_options.conninfo_provided = true; opts = PQconninfoParse(runtime_options.dbname, &errmsg); if (opts == NULL) { PQExpBufferData conninfo_error; initPQExpBuffer(&conninfo_error); appendPQExpBuffer(&conninfo_error, _("error parsing conninfo:\n%s"), errmsg); item_list_append(&cli_errors, conninfo_error.data); termPQExpBuffer(&conninfo_error); pfree(errmsg); } else { /* * Store any parameters provided in the conninfo string in our * internal array; also overwrite any options set in * runtime_options.(host|port|username), as the conninfo * settings take priority */ PQconninfoOption *opt; for (opt = opts; opt->keyword != NULL; opt++) { if (opt->val != NULL && opt->val[0] != '\0') { param_set(&source_conninfo, opt->keyword, opt->val); } if (strcmp(opt->keyword, "host") == 0 && (opt->val != NULL && opt->val[0] != '\0')) { strncpy(runtime_options.host, opt->val, MAXLEN); runtime_options.host_param_provided = true; } if (strcmp(opt->keyword, "hostaddr") == 0 && (opt->val != NULL && opt->val[0] != '\0')) { strncpy(runtime_options.host, opt->val, MAXLEN); runtime_options.host_param_provided = true; } else if (strcmp(opt->keyword, "port") == 0 && (opt->val != NULL && opt->val[0] != '\0')) { strncpy(runtime_options.port, opt->val, MAXLEN); } else if (strcmp(opt->keyword, "user") == 0 && (opt->val != NULL && opt->val[0] != '\0')) { strncpy(runtime_options.username, opt->val, MAXLEN); } } PQconninfoFree(opts); } } else { param_set(&source_conninfo, "dbname", runtime_options.dbname); } } /* * Disallow further running as root to prevent directory ownership * problems. We check this here to give the root user a chance to execute * --help/--version options. */ if (geteuid() == 0 && help_option == false) { fprintf(stderr, _("%s: cannot be run as root\n" "Please log in (using, e.g., \"su\") as the " "(unprivileged) user that owns " "the data directory.\n" ), progname()); free_conninfo_params(&source_conninfo); exit(ERR_BAD_CONFIG); } /* Exit here already if errors in command line options found */ if (cli_errors.head != NULL) { free_conninfo_params(&source_conninfo); exit_with_cli_errors(&cli_errors, NULL); } /*---------- * Determine the node type and action; following are valid: * * { PRIMARY | MASTER } REGISTER | * STANDBY { REGISTER | UNREGISTER | CLONE [node] | PROMOTE | FOLLOW [node] | SWITCHOVER } | * WITNESS { CREATE | REGISTER | UNREGISTER } * NODE { STATUS | CHECK | REJOIN | SERVICE } | * CLUSTER { CROSSCHECK | MATRIX | SHOW | EVENT | CLEANUP } * SERVICE { STATUS | PAUSE | UNPAUSE | START | STOP } * * [node] is an optional hostname, provided instead of the -h/--host * option * --------- */ if (optind < argc) { repmgr_command = argv[optind++]; } if (optind < argc) { repmgr_action = argv[optind++]; } else { repmgr_action = dummy_action; } if (repmgr_command != NULL) { if (strcasecmp(repmgr_command, "PRIMARY") == 0 || strcasecmp(repmgr_command, "MASTER") == 0) { if (help_option == true) { do_primary_help(); exit(SUCCESS); } if (strcasecmp(repmgr_action, "REGISTER") == 0) action = PRIMARY_REGISTER; else if (strcasecmp(repmgr_action, "UNREGISTER") == 0) action = PRIMARY_UNREGISTER; /* allow "primary check"/"primary status" as aliases for "node check"/"node status" */ else if (strcasecmp(repmgr_action, "CHECK") == 0) action = NODE_CHECK; else if (strcasecmp(repmgr_action, "STATUS") == 0) action = NODE_STATUS; } else if (strcasecmp(repmgr_command, "STANDBY") == 0) { if (help_option == true) { do_standby_help(); exit(SUCCESS); } if (strcasecmp(repmgr_action, "CLONE") == 0) action = STANDBY_CLONE; else if (strcasecmp(repmgr_action, "REGISTER") == 0) action = STANDBY_REGISTER; else if (strcasecmp(repmgr_action, "UNREGISTER") == 0) action = STANDBY_UNREGISTER; else if (strcasecmp(repmgr_action, "PROMOTE") == 0) action = STANDBY_PROMOTE; else if (strcasecmp(repmgr_action, "FOLLOW") == 0) action = STANDBY_FOLLOW; else if (strcasecmp(repmgr_action, "SWITCHOVER") == 0) action = STANDBY_SWITCHOVER; /* allow "standby check"/"standby status" as aliases for "node check"/"node status" */ else if (strcasecmp(repmgr_action, "CHECK") == 0) action = NODE_CHECK; else if (strcasecmp(repmgr_action, "STATUS") == 0) action = NODE_STATUS; } else if (strcasecmp(repmgr_command, "WITNESS") == 0) { if (help_option == true) { do_witness_help(); exit(SUCCESS); } else if (strcasecmp(repmgr_action, "REGISTER") == 0) action = WITNESS_REGISTER; else if (strcasecmp(repmgr_action, "UNREGISTER") == 0) action = WITNESS_UNREGISTER; } else if (strcasecmp(repmgr_command, "NODE") == 0) { if (help_option == true) { do_node_help(); exit(SUCCESS); } if (strcasecmp(repmgr_action, "CHECK") == 0) action = NODE_CHECK; else if (strcasecmp(repmgr_action, "STATUS") == 0) action = NODE_STATUS; else if (strcasecmp(repmgr_action, "REJOIN") == 0) action = NODE_REJOIN; else if (strcasecmp(repmgr_action, "SERVICE") == 0) action = NODE_SERVICE; else if (strcasecmp(repmgr_action, "CONTROL") == 0) action = NODE_CONTROL; } else if (strcasecmp(repmgr_command, "CLUSTER") == 0) { if (help_option == true) { do_cluster_help(); exit(SUCCESS); } if (strcasecmp(repmgr_action, "SHOW") == 0) action = CLUSTER_SHOW; else if (strcasecmp(repmgr_action, "EVENT") == 0) action = CLUSTER_EVENT; /* allow "CLUSTER EVENTS" as synonym for "CLUSTER EVENT" */ else if (strcasecmp(repmgr_action, "EVENTS") == 0) action = CLUSTER_EVENT; else if (strcasecmp(repmgr_action, "CROSSCHECK") == 0) action = CLUSTER_CROSSCHECK; else if (strcasecmp(repmgr_action, "MATRIX") == 0) action = CLUSTER_MATRIX; else if (strcasecmp(repmgr_action, "CLEANUP") == 0) action = CLUSTER_CLEANUP; } else if (strcasecmp(repmgr_command, "SERVICE") == 0) { if (help_option == true) { do_service_help(); exit(SUCCESS); } if (strcasecmp(repmgr_action, "STATUS") == 0) action = SERVICE_STATUS; else if (strcasecmp(repmgr_action, "PAUSE") == 0) action = SERVICE_PAUSE; else if (strcasecmp(repmgr_action, "UNPAUSE") == 0) action = SERVICE_UNPAUSE; } else if (strcasecmp(repmgr_command, "DAEMON") == 0) { if (help_option == true) { do_daemon_help(); exit(SUCCESS); } if (strcasecmp(repmgr_action, "START") == 0) action = DAEMON_START; else if (strcasecmp(repmgr_action, "STOP") == 0) action = DAEMON_STOP; /* allow "daemon" as an alias for "service" for repmgr 4.x compatibility */ if (strcasecmp(repmgr_action, "STATUS") == 0) action = SERVICE_STATUS; else if (strcasecmp(repmgr_action, "PAUSE") == 0) action = SERVICE_PAUSE; else if (strcasecmp(repmgr_action, "UNPAUSE") == 0) action = SERVICE_UNPAUSE; } else { valid_repmgr_command_found = false; } } if (help_option == true) { do_help(); exit(SUCCESS); } if (action == NO_ACTION) { PQExpBufferData command_error; initPQExpBuffer(&command_error); if (repmgr_command == NULL) { appendPQExpBuffer(&command_error, _("no repmgr command provided")); } else if (valid_repmgr_command_found == false && repmgr_action[0] == '\0') { appendPQExpBuffer(&command_error, _("unknown repmgr command '%s'"), repmgr_command); } else if (repmgr_action[0] == '\0') { appendPQExpBuffer(&command_error, _("no action provided for command '%s'"), repmgr_command); } else { appendPQExpBuffer(&command_error, _("unknown repmgr action '%s %s'"), repmgr_command, repmgr_action); } item_list_append(&cli_errors, command_error.data); } /* * STANDBY CLONE historically accepts the upstream hostname as an * additional argument */ if (action == STANDBY_CLONE) { if (optind < argc) { if (runtime_options.host_param_provided == true) { PQExpBufferData additional_host_arg; initPQExpBuffer(&additional_host_arg); appendPQExpBuffer(&additional_host_arg, _("host name provided both with %s and as an extra parameter"), runtime_options.conninfo_provided == true ? "host=" : "-h/--host"); item_list_append(&cli_errors, additional_host_arg.data); } else { strncpy(runtime_options.host, argv[optind++], MAXLEN); param_set(&source_conninfo, "host", runtime_options.host); runtime_options.host_param_provided = true; } } } if (optind < argc) { PQExpBufferData too_many_args; initPQExpBuffer(&too_many_args); appendPQExpBuffer(&too_many_args, _("too many command-line arguments (first extra is \"%s\")"), argv[optind]); item_list_append(&cli_errors, too_many_args.data); } /* * The configuration file is not required for some actions (e.g. 'standby * clone'), however if available we'll parse it anyway for options like * 'log_level', 'use_replication_slots' etc. */ load_config(runtime_options.config_file, runtime_options.verbose, runtime_options.terse, argv[0]); /* * Handle options which must be executed without a repmgr command */ if (runtime_options.dump_config == true) { if (repmgr_command != NULL) { fprintf(stderr, _("--dump-config cannot be used in combination with a repmgr command")); exit(ERR_BAD_CONFIG); } dump_config(); exit(SUCCESS); } check_cli_parameters(action); /* * Command-line parameter --recovery-min-apply-delay overrides the equivalent * setting in the config file. Note we'll need to parse it here to handle * any formatting errors. */ if (*runtime_options.recovery_min_apply_delay != '\0') { parse_time_unit_parameter("--recovery-min-apply-delay", runtime_options.recovery_min_apply_delay, config_file_options.recovery_min_apply_delay, &cli_errors); config_file_options.recovery_min_apply_delay_provided = true; } /* * Sanity checks for command line parameters completed by now; any further * errors will be runtime ones */ if (cli_errors.head != NULL) { free_conninfo_params(&source_conninfo); exit_with_cli_errors(&cli_errors, valid_repmgr_command_found == true ? repmgr_command : NULL); } /* no errors detected by repmgr, but getopt might have */ if (option_error_found == true) { if (valid_repmgr_command_found == true) { printf(_("Try \"%s --help\" or \"%s %s --help\" for more information.\n"), progname(), progname(), repmgr_command); } else { printf(_("Try \"repmgr --help\" for more information.\n")); } free_conninfo_params(&source_conninfo); exit(ERR_BAD_CONFIG); } /* * Print any warnings about inappropriate command line options, unless * -t/--terse set */ if (cli_warnings.head != NULL && runtime_options.terse == false) { log_warning(_("following problems with command line parameters detected:")); print_item_list(&cli_warnings); } /* * post-processing following command line parameter checks * ======================================================= */ if (runtime_options.csv == true) { runtime_options.output_mode = OM_CSV; } else if (runtime_options.nagios == true) { runtime_options.output_mode = OM_NAGIOS; } else if (runtime_options.optformat == true) { runtime_options.output_mode = OM_OPTFORMAT; } /* * Check for configuration file items which can be overridden by runtime * options * ===================================================================== */ /* * Command-line parameter -L/--log-level overrides any setting in config * file */ if (*runtime_options.log_level != '\0') { strncpy(config_file_options.log_level, runtime_options.log_level, MAXLEN); } /* * Initialise pg_bindir - command line parameter will override any setting * in the configuration file */ if (!strlen(runtime_options.pg_bindir)) { strncpy(runtime_options.pg_bindir, config_file_options.pg_bindir, MAXLEN); } /* Add trailing slash */ if (strlen(runtime_options.pg_bindir)) { int len = strlen(runtime_options.pg_bindir); if (runtime_options.pg_bindir[len - 1] != '/') { maxlen_snprintf(pg_bindir, "%s/", runtime_options.pg_bindir); } else { strncpy(pg_bindir, runtime_options.pg_bindir, MAXLEN); } } /* * Initialize the logger. We've previously requested STDERR logging only * to ensure the repmgr command doesn't have its output diverted to a * logging facility (which usually doesn't make sense for a command line * program). * * If required (e.g. when calling repmgr from repmgrd), this behaviour can * be overridden with "--log-to-file". */ logger_init(&config_file_options, progname()); if (runtime_options.verbose) logger_set_verbose(); if (runtime_options.terse) logger_set_terse(); /* * If --dry-run specified, ensure log_level is at least LOG_INFO, regardless * of what's in the configuration file or -L/--log-level parameter, otherwise * some or output might not be displayed. */ if (runtime_options.dry_run == true) { logger_set_min_level(LOG_INFO); } /* * If -q/--quiet supplied, suppress any non-ERROR log output. * This overrides everything else; we'll leave it up to the user to deal with the * consequences of e.g. running --dry-run together with -q/--quiet. */ if (runtime_options.quiet == true) { logger_set_level(LOG_ERROR); } /* * Node configuration information is not needed for all actions, with * STANDBY CLONE being the main exception. */ if (config_file_required) { /* * if a configuration file was provided, the configuration file parser * will already have errored out if no valid node_id found */ if (config_file_options.node_id == NODE_NOT_FOUND) { free_conninfo_params(&source_conninfo); log_error(_("no node information was found - please supply a configuration file")); exit(ERR_BAD_CONFIG); } } /* * If a node was specified (by --node-id or --node-name), check it exists * (and pre-populate a record for later use). * * At this point check_cli_parameters() will already have determined if * provision of these is valid for the action, otherwise it unsets them. * * We need to check this much later than other command line parameters as * we need to wait until the configuration file is parsed and we can * obtain the conninfo string. */ if (runtime_options.node_id != UNKNOWN_NODE_ID || runtime_options.node_name[0] != '\0') { PGconn *conn = NULL; RecordStatus record_status = RECORD_NOT_FOUND; log_verbose(LOG_DEBUG, "connecting to local node to retrieve record for node specified with --node-id or --node-name"); if (strlen(config_file_options.conninfo)) conn = establish_db_connection(config_file_options.conninfo, true); else conn = establish_db_connection_by_params(&source_conninfo, true); if (runtime_options.node_id != UNKNOWN_NODE_ID) { record_status = get_node_record(conn, runtime_options.node_id, &target_node_info); if (record_status != RECORD_FOUND) { log_error(_("node %i (specified with --node-id) not found"), runtime_options.node_id); PQfinish(conn); free_conninfo_params(&source_conninfo); exit(ERR_BAD_CONFIG); } } else if (runtime_options.node_name[0] != '\0') { char *escaped = escape_string(conn, runtime_options.node_name); if (escaped == NULL) { log_error(_("unable to escape value provided for --node-name")); PQfinish(conn); free_conninfo_params(&source_conninfo); exit(ERR_BAD_CONFIG); } record_status = get_node_record_by_name(conn, escaped, &target_node_info); pfree(escaped); if (record_status != RECORD_FOUND) { log_error(_("node \"%s\" (specified with --node-name) not found"), runtime_options.node_name); PQfinish(conn); free_conninfo_params(&source_conninfo); exit(ERR_BAD_CONFIG); } } PQfinish(conn); } switch (action) { /* PRIMARY */ case PRIMARY_REGISTER: do_primary_register(); break; case PRIMARY_UNREGISTER: do_primary_unregister(); break; /* STANDBY */ case STANDBY_CLONE: do_standby_clone(); break; case STANDBY_REGISTER: do_standby_register(); break; case STANDBY_UNREGISTER: do_standby_unregister(); break; case STANDBY_PROMOTE: do_standby_promote(); break; case STANDBY_FOLLOW: do_standby_follow(); break; case STANDBY_SWITCHOVER: do_standby_switchover(); break; /* WITNESS */ case WITNESS_REGISTER: do_witness_register(); break; case WITNESS_UNREGISTER: do_witness_unregister(); break; /* NODE */ case NODE_STATUS: do_node_status(); break; case NODE_CHECK: do_node_check(); break; case NODE_REJOIN: do_node_rejoin(); break; case NODE_SERVICE: do_node_service(); break; case NODE_CONTROL: do_node_control(); break; /* CLUSTER */ case CLUSTER_SHOW: do_cluster_show(); break; case CLUSTER_EVENT: do_cluster_event(); break; case CLUSTER_CROSSCHECK: do_cluster_crosscheck(); break; case CLUSTER_MATRIX: do_cluster_matrix(); break; case CLUSTER_CLEANUP: do_cluster_cleanup(); break; /* SERVICE */ case SERVICE_STATUS: do_service_status(); break; case SERVICE_PAUSE: do_service_pause(); break; case SERVICE_UNPAUSE: do_service_unpause(); break; /* DAEMON */ case DAEMON_START: do_daemon_start(); break; case DAEMON_STOP: do_daemon_stop(); break; default: /* An action will have been determined by this point */ break; } free_conninfo_params(&source_conninfo); return SUCCESS; } /* * Check for useless or conflicting parameters, and also whether a * configuration file is required. * * Messages will be added to the command line warning and error lists * as appropriate. */ static void check_cli_parameters(const int action) { /* * ======================================================================== * check all parameters required for an action are provided, and warn * about ineffective actions * ======================================================================== */ switch (action) { case PRIMARY_REGISTER: /* no required parameters */ break; case STANDBY_CLONE: { standy_clone_mode mode = get_standby_clone_mode(); config_file_required = false; if (mode == barman) { if (runtime_options.copy_external_config_files) { item_list_append(&cli_warnings, _("--copy-external-config-files ineffective in Barman mode")); } if (runtime_options.fast_checkpoint) { item_list_append(&cli_warnings, _("-c/--fast-checkpoint has no effect in Barman mode")); } } else { if (!runtime_options.host_param_provided) { item_list_append_format(&cli_errors, _("host name for the source node must be provided with -h/--host when executing %s"), action_name(action)); } if (!runtime_options.connection_param_provided) { item_list_append_format(&cli_errors, _("database connection parameters for the source node must be provided when executing %s"), action_name(action)); } /* * If -D/--pgdata was provided, but config_file_options.pgdata * is set, warn that -D/--pgdata will be ignored. */ if (runtime_options.data_dir[0] && config_file_options.data_directory[0]) { item_list_append(&cli_warnings, _("-D/--pgdata will be ignored if a repmgr configuration file is provided")); } if (*runtime_options.upstream_conninfo) { if (*runtime_options.replication_user) { item_list_append(&cli_warnings, _("--replication-user ineffective when specifying --upstream-conninfo")); } } if (runtime_options.no_upstream_connection == true) { item_list_append(&cli_warnings, _("--no-upstream-connection only effective in Barman mode")); } } if (strlen(config_file_options.config_directory)) { if (runtime_options.copy_external_config_files == false) { item_list_append(&cli_warnings, _("\"config_directory\" set in repmgr.conf, but --copy-external-config-files not provided")); } } } break; case STANDBY_FOLLOW: { /* * if `repmgr standby follow` executed with host params, * ensure data directory was provided */ } break; case WITNESS_REGISTER: { if (!runtime_options.host_param_provided) { item_list_append_format(&cli_errors, _("host name for the source node must be provided with -h/--host when executing %s"), action_name(action)); } } break; case NODE_CHECK: if (runtime_options.has_passfile == true) { config_file_required = false; } break; case NODE_STATUS: if (runtime_options.node_id != UNKNOWN_NODE_ID) { item_list_append( &cli_warnings, "--node-id will be ignored; \"repmgr node status\" can only be executed on the local node"); } if (runtime_options.node_name[0] != '\0') { item_list_append( &cli_warnings, "--node-name will be ignored; \"repmgr node status\" can only be executed on the local node"); } break; case NODE_REJOIN: if (runtime_options.connection_param_provided == false) { item_list_append( &cli_errors, "database connection parameters for an available node must be provided when executing NODE REJOIN"); } break; case CLUSTER_SHOW: case CLUSTER_MATRIX: case CLUSTER_CROSSCHECK: if (runtime_options.connection_param_provided) config_file_required = false; break; case CLUSTER_EVENT: /* no required parameters */ break; } /* * ======================================================================== * warn if parameters provided for an action where they're not relevant * ======================================================================== */ /* --host etc. */ if (runtime_options.connection_param_provided) { switch (action) { case STANDBY_CLONE: case STANDBY_FOLLOW: case STANDBY_REGISTER: case WITNESS_REGISTER: case WITNESS_UNREGISTER: case CLUSTER_SHOW: case CLUSTER_MATRIX: case CLUSTER_CROSSCHECK: case NODE_REJOIN: break; default: item_list_append_format(&cli_warnings, _("database connection parameters not required when executing %s"), action_name(action)); } } /* -D/--pgdata */ if (runtime_options.data_dir[0]) { switch (action) { case STANDBY_CLONE: case STANDBY_FOLLOW: case NODE_SERVICE: break; default: item_list_append_format(&cli_warnings, _("-D/--pgdata not required when executing %s"), action_name(action)); } } /* * --node-id * * NOTE: overrides --node-name, if present */ if (runtime_options.node_id != UNKNOWN_NODE_ID) { switch (action) { case PRIMARY_UNREGISTER: case STANDBY_UNREGISTER: case WITNESS_UNREGISTER: case CLUSTER_CLEANUP: case CLUSTER_EVENT: case CLUSTER_MATRIX: case CLUSTER_CROSSCHECK: break; default: item_list_append_format(&cli_warnings, _("--node-id not required when executing %s"), action_name(action)); runtime_options.node_id = UNKNOWN_NODE_ID; } } if (runtime_options.node_name[0]) { switch (action) { case STANDBY_UNREGISTER: case CLUSTER_EVENT: if (runtime_options.node_id != UNKNOWN_NODE_ID) { item_list_append(&cli_warnings, _("--node-id provided, ignoring --node-name")); memset(runtime_options.node_name, 0, sizeof(runtime_options.node_name)); } break; default: item_list_append_format(&cli_warnings, _("--node-name not required when executing %s"), action_name(action)); memset(runtime_options.node_name, 0, sizeof(runtime_options.node_name)); } } if (runtime_options.upstream_node_id != UNKNOWN_NODE_ID) { switch (action) { case STANDBY_CLONE: case STANDBY_REGISTER: case STANDBY_FOLLOW: break; default: item_list_append_format(&cli_warnings, _("--upstream-node-id will be ignored when executing %s"), action_name(action)); } } if (runtime_options.replication_user[0]) { switch (action) { case PRIMARY_REGISTER: case STANDBY_REGISTER: case STANDBY_CLONE: break; case STANDBY_FOLLOW: item_list_append_format(&cli_warnings, _("--replication-user ignored when executing %s"), action_name(action)); break; default: item_list_append_format(&cli_warnings, _("--replication-user not required when executing %s"), action_name(action)); } } if (runtime_options.superuser[0]) { switch (action) { case STANDBY_CLONE: case STANDBY_SWITCHOVER: case NODE_CHECK: case NODE_SERVICE: break; default: item_list_append_format(&cli_warnings, _("--superuser ignored when executing %s"), action_name(action)); } } if (runtime_options.replication_conf_only == true) { switch (action) { case STANDBY_CLONE: break; default: item_list_append_format(&cli_warnings, _("--create-recovery-conf will be ignored when executing %s"), action_name(action)); } } if (runtime_options.event[0]) { switch (action) { case CLUSTER_EVENT: break; default: item_list_append_format(&cli_warnings, _("--event not required when executing %s"), action_name(action)); } } if (runtime_options.limit_provided) { switch (action) { case CLUSTER_EVENT: break; default: item_list_append_format(&cli_warnings, _("--limit not required when executing %s"), action_name(action)); } } if (runtime_options.all) { switch (action) { case CLUSTER_EVENT: if (runtime_options.limit_provided == true) { runtime_options.all = false; item_list_append(&cli_warnings, _("--limit provided, ignoring --all")); } break; default: item_list_append_format(&cli_warnings, _("--all not required when executing %s"), action_name(action)); } } /* --wait/--no-wait */ if (runtime_options.wait_provided == true && runtime_options.no_wait == true) { item_list_append_format(&cli_errors, _("both --wait and --no-wait options provided")); } else { if (runtime_options.wait_provided) { switch (action) { case DAEMON_START: case DAEMON_STOP: case STANDBY_FOLLOW: break; default: item_list_append_format(&cli_warnings, _("--wait will be ignored when executing %s"), action_name(action)); } } else if (runtime_options.no_wait) { switch (action) { case DAEMON_START: case DAEMON_STOP: case NODE_REJOIN: break; default: item_list_append_format(&cli_warnings, _("--no-wait will be ignored when executing %s"), action_name(action)); } } } /* repmgr node service --action */ if (runtime_options.action[0] != '\0') { switch (action) { case NODE_SERVICE: break; default: item_list_append_format(&cli_warnings, _("--action will be ignored when executing %s"), action_name(action)); } } /* repmgr node status --is-shutdown-cleanly */ if (runtime_options.is_shutdown_cleanly == true) { switch (action) { case NODE_STATUS: break; default: item_list_append_format(&cli_warnings, _("--is-shutdown-cleanly will be ignored when executing %s"), action_name(action)); } } if (runtime_options.always_promote == true) { switch (action) { case STANDBY_SWITCHOVER: break; default: item_list_append_format(&cli_warnings, _("--always-promote will be ignored when executing %s"), action_name(action)); } } if (runtime_options.force_rewind_used == true) { switch (action) { case STANDBY_SWITCHOVER: case NODE_REJOIN: break; default: item_list_append_format(&cli_warnings, _("--force-rewind will be ignored when executing %s"), action_name(action)); } } if (runtime_options.repmgrd_no_pause == true) { switch (action) { case STANDBY_SWITCHOVER: break; default: item_list_append_format(&cli_warnings, _("--repmgrd-no-pause will be ignored when executing %s"), action_name(action)); } } if (runtime_options.repmgrd_force_unpause == true) { switch (action) { case STANDBY_SWITCHOVER: if (runtime_options.repmgrd_no_pause == true) item_list_append(&cli_errors, _("--repmgrd-force-unpause and --repmgrd-no-pause cannot be used together")); break; default: item_list_append_format(&cli_warnings, _("--repmgrd-force-unpause will be ignored when executing %s"), action_name(action)); } } if (runtime_options.config_files[0] != '\0') { switch (action) { case NODE_REJOIN: break; default: item_list_append_format(&cli_warnings, _("--config-files will be ignored when executing %s"), action_name(action)); } } if (runtime_options.dry_run == true) { switch (action) { case PRIMARY_REGISTER: case PRIMARY_UNREGISTER: case STANDBY_CLONE: case STANDBY_REGISTER: case STANDBY_FOLLOW: case STANDBY_SWITCHOVER: case STANDBY_PROMOTE: case WITNESS_REGISTER: case WITNESS_UNREGISTER: case NODE_REJOIN: case NODE_SERVICE: case SERVICE_PAUSE: case SERVICE_UNPAUSE: case SERVICE_STATUS: case DAEMON_START: case DAEMON_STOP: break; default: item_list_append_format(&cli_warnings, _("--dry-run is not effective when executing %s"), action_name(action)); } } /* check only one of --csv, --nagios and --optformat used */ { int used_options = 0; if (runtime_options.csv == true) used_options++; if (runtime_options.nagios == true) used_options++; if (runtime_options.optformat == true) used_options++; if (used_options > 1) { /* TODO: list which options were used */ item_list_append(&cli_errors, "only one of --csv, --nagios and --optformat can be used"); } } /* --compact */ if (runtime_options.compact == true) { switch (action) { case CLUSTER_SHOW: case CLUSTER_EVENT: case SERVICE_STATUS: break; default: item_list_append_format(&cli_warnings, _("--compact is not effective when executing %s"), action_name(action)); } } /* --detail */ if (runtime_options.detail == true) { switch (action) { case SERVICE_STATUS: break; default: item_list_append_format(&cli_warnings, _("--detail is not effective when executing %s"), action_name(action)); } } /* --siblings-follow */ if (runtime_options.siblings_follow == true) { switch (action) { case STANDBY_PROMOTE: case STANDBY_SWITCHOVER: break; default: item_list_append_format(&cli_warnings, _("----siblings-follow is not effective when executing %s"), action_name(action)); } } /* --disable-wal-receiver / --enable-wal-receiver */ if (runtime_options.disable_wal_receiver == true || runtime_options.enable_wal_receiver == true) { switch (action) { case NODE_CONTROL: { if (runtime_options.disable_wal_receiver == true && runtime_options.enable_wal_receiver == true) { item_list_append(&cli_errors, _("provide either --disable-wal-receiver or --enable-wal-receiver")); } } break; default: item_list_append_format(&cli_warnings, _("--disable-wal-receiver / --enable-wal-receiver not effective when executing %s"), action_name(action)); } } } /* * Generate formatted node status output for display by "cluster show" and * "service status". */ bool format_node_status(t_node_info *node_info, PQExpBufferData *node_status, PQExpBufferData *upstream, ItemList *warnings) { bool error_found = false; t_node_info remote_node_rec = T_NODE_INFO_INITIALIZER; RecordStatus remote_node_rec_found = RECORD_NOT_FOUND; if (PQstatus(node_info->conn) == CONNECTION_OK) { node_info->node_status = NODE_STATUS_UP; node_info->recovery_type = get_recovery_type(node_info->conn); /* get node's copy of its record so we can see what it thinks its status is */ remote_node_rec_found = get_node_record_with_upstream(node_info->conn, node_info->node_id, &remote_node_rec); } else { /* check if node is reachable, but just not letting us in */ if (is_server_available_quiet(node_info->conninfo)) node_info->node_status = NODE_STATUS_REJECTED; else node_info->node_status = NODE_STATUS_DOWN; node_info->recovery_type = RECTYPE_UNKNOWN; } /* format node status info */ switch (node_info->type) { case PRIMARY: { /* node is reachable */ if (node_info->node_status == NODE_STATUS_UP) { if (node_info->active == true) { switch (node_info->recovery_type) { case RECTYPE_PRIMARY: appendPQExpBufferStr(node_status, "* running"); break; case RECTYPE_STANDBY: appendPQExpBufferStr(node_status, "! running as standby"); item_list_append_format(warnings, "node \"%s\" (ID: %i) is registered as primary but running as standby", node_info->node_name, node_info->node_id); break; case RECTYPE_UNKNOWN: appendPQExpBufferStr(node_status, "! unknown"); item_list_append_format(warnings, "node \"%s\" (ID: %i) has unknown replication status", node_info->node_name, node_info->node_id); break; } } else { if (node_info->recovery_type == RECTYPE_PRIMARY) { appendPQExpBufferStr(node_status, "! running"); item_list_append_format(warnings, "node \"%s\" (ID: %i) is running but the repmgr node record is inactive", node_info->node_name, node_info->node_id); } else { appendPQExpBufferStr(node_status, "! running as standby"); item_list_append_format(warnings, "node \"%s\" (ID: %i) is registered as an inactive primary but running as standby", node_info->node_name, node_info->node_id); } } } /* node is up but cannot connect */ else if (node_info->node_status == NODE_STATUS_REJECTED) { if (node_info->active == true) { appendPQExpBufferStr(node_status, "? running"); } else { appendPQExpBufferStr(node_status, "! running"); error_found = true; } } /* node is unreachable */ else { /* node is unreachable but marked active */ if (node_info->active == true) { appendPQExpBufferStr(node_status, "? unreachable"); item_list_append_format(warnings, "node \"%s\" (ID: %i) is registered as an active primary but is unreachable", node_info->node_name, node_info->node_id); } /* node is unreachable and marked as inactive */ else { appendPQExpBufferStr(node_status, "- failed"); error_found = true; } } } break; case STANDBY: { /* node is reachable */ if (node_info->node_status == NODE_STATUS_UP) { if (node_info->active == true) { switch (node_info->recovery_type) { case RECTYPE_STANDBY: appendPQExpBufferStr(node_status, " running"); break; case RECTYPE_PRIMARY: appendPQExpBufferStr(node_status, "! running as primary"); item_list_append_format(warnings, "node \"%s\" (ID: %i) is registered as standby but running as primary", node_info->node_name, node_info->node_id); break; case RECTYPE_UNKNOWN: appendPQExpBufferStr(node_status, "! unknown"); item_list_append_format( warnings, "node \"%s\" (ID: %i) has unknown replication status", node_info->node_name, node_info->node_id); break; } } else { if (node_info->recovery_type == RECTYPE_STANDBY) { appendPQExpBufferStr(node_status, "! running"); item_list_append_format(warnings, "node \"%s\" (ID: %i) is running but the repmgr node record is inactive", node_info->node_name, node_info->node_id); } else { appendPQExpBufferStr(node_status, "! running as primary"); item_list_append_format(warnings, "node \"%s\" (ID: %i) is running as primary but the repmgr node record is inactive", node_info->node_name, node_info->node_id); } } /* warn about issue with paused WAL replay */ if (is_wal_replay_paused(node_info->conn, true)) { item_list_append_format(warnings, _("WAL replay is paused on node \"%s\" (ID: %i) with WAL replay pending; this node cannot be manually promoted until WAL replay is resumed"), node_info->node_name, node_info->node_id); } } /* node is up but cannot connect */ else if (node_info->node_status == NODE_STATUS_REJECTED) { if (node_info->active == true) { appendPQExpBufferStr(node_status, "? running"); } else { appendPQExpBufferStr(node_status, "! running"); error_found = true; } } /* node is unreachable */ else { /* node is unreachable but marked active */ if (node_info->active == true) { appendPQExpBufferStr(node_status, "? unreachable"); item_list_append_format(warnings, "node \"%s\" (ID: %i) is registered as an active standby but is unreachable", node_info->node_name, node_info->node_id); } else { appendPQExpBufferStr(node_status, "- failed"); error_found = true; } } } break; case WITNESS: { /* node is reachable */ if (node_info->node_status == NODE_STATUS_UP) { if (node_info->active == true) { appendPQExpBufferStr(node_status, "* running"); } else { appendPQExpBufferStr(node_status, "! running"); error_found = true; } } /* node is up but cannot connect */ else if (node_info->node_status == NODE_STATUS_REJECTED) { if (node_info->active == true) { appendPQExpBufferStr(node_status, "? rejected"); } else { appendPQExpBufferStr(node_status, "! failed"); error_found = true; } } /* node is unreachable */ else { if (node_info->active == true) { appendPQExpBufferStr(node_status, "? unreachable"); } else { appendPQExpBufferStr(node_status, "- failed"); error_found = true; } } } break; case UNKNOWN: { /* this should never happen */ appendPQExpBufferStr(node_status, "? unknown node type"); error_found = true; } break; } /* format node upstream info */ if (remote_node_rec_found == RECORD_NOT_FOUND) { /* * Unable to retrieve the node's copy of its own record - copy the * name from our own copy of the record */ appendPQExpBuffer(upstream, "? %s", node_info->upstream_node_name); } else if (remote_node_rec.type == WITNESS) { /* no upstream - unlikely to happen */ if (remote_node_rec.upstream_node_id == NO_UPSTREAM_NODE) { appendPQExpBufferStr(upstream, "! "); item_list_append_format(warnings, "node \"%s\" (ID: %i) is a witness but reports it has no upstream node", node_info->node_name, node_info->node_id); } /* mismatch between reported upstream and upstream in local node's metadata */ else if (node_info->upstream_node_id != remote_node_rec.upstream_node_id) { appendPQExpBufferStr(upstream, "! "); if (node_info->upstream_node_id != remote_node_rec.upstream_node_id) { item_list_append_format(warnings, "node \"%s\" (ID: %i) reports a different upstream (reported: \"%s\", expected \"%s\")", node_info->node_name, node_info->node_id, remote_node_rec.upstream_node_name, node_info->upstream_node_name); } } else { t_node_info upstream_node_rec = T_NODE_INFO_INITIALIZER; RecordStatus upstream_node_rec_found = get_node_record(node_info->conn, node_info->upstream_node_id, &upstream_node_rec); if (upstream_node_rec_found != RECORD_FOUND) { appendPQExpBufferStr(upstream, "? "); item_list_append_format(warnings, "unable to find record for upstream node ID %i", node_info->upstream_node_id); } else { PGconn *upstream_conn = establish_db_connection_quiet(upstream_node_rec.conninfo); if (PQstatus(upstream_conn) != CONNECTION_OK) { appendPQExpBufferStr(upstream, "? "); item_list_append_format(warnings, "unable to connect to node \"%s\" (ID: %i)'s upstream node \"%s\" (ID: %i)", node_info->node_name, node_info->node_id, upstream_node_rec.node_name, upstream_node_rec.node_id); } PQfinish(upstream_conn); } } appendPQExpBufferStr(upstream, remote_node_rec.upstream_node_name); } else if (remote_node_rec.type == STANDBY) { if (node_info->upstream_node_id != NO_UPSTREAM_NODE && node_info->upstream_node_id == remote_node_rec.upstream_node_id) { /* * expected and reported upstreams match - check if node is actually * connected to the upstream */ NodeAttached attached_to_upstream = NODE_ATTACHED_UNKNOWN; char *replication_state = NULL; t_node_info upstream_node_rec = T_NODE_INFO_INITIALIZER; RecordStatus upstream_node_rec_found = get_node_record(node_info->conn, node_info->upstream_node_id, &upstream_node_rec); if (upstream_node_rec_found != RECORD_FOUND) { item_list_append_format(warnings, "unable to find record for upstream node ID %i", node_info->upstream_node_id); } else { PGconn *upstream_conn = establish_db_connection_quiet(upstream_node_rec.conninfo); if (PQstatus(upstream_conn) != CONNECTION_OK) { item_list_append_format(warnings, "unable to connect to node \"%s\" (ID: %i)'s upstream node \"%s\" (ID: %i)", node_info->node_name, node_info->node_id, upstream_node_rec.node_name, upstream_node_rec.node_id); } else { attached_to_upstream = is_downstream_node_attached(upstream_conn, node_info->node_name, &replication_state); } PQfinish(upstream_conn); } if (attached_to_upstream == NODE_ATTACHED_UNKNOWN) { appendPQExpBufferStr(upstream, "? "); item_list_append_format(warnings, "unable to determine if node \"%s\" (ID: %i) is attached to its upstream node \"%s\" (ID: %i)", node_info->node_name, node_info->node_id, upstream_node_rec.node_name, upstream_node_rec.node_id); } if (attached_to_upstream == NODE_NOT_ATTACHED) { appendPQExpBufferStr(upstream, "? "); item_list_append_format(warnings, "node \"%s\" (ID: %i) attached to its upstream node \"%s\" (ID: %i) in state \"%s\"", node_info->node_name, node_info->node_id, upstream_node_rec.node_name, upstream_node_rec.node_id, replication_state); } else if (attached_to_upstream == NODE_DETACHED) { appendPQExpBufferStr(upstream, "! "); item_list_append_format(warnings, "node \"%s\" (ID: %i) is not attached to its upstream node \"%s\" (ID: %i)", node_info->node_name, node_info->node_id, upstream_node_rec.node_name, upstream_node_rec.node_id); } appendPQExpBufferStr(upstream, node_info->upstream_node_name); } else { if (node_info->upstream_node_id != NO_UPSTREAM_NODE && remote_node_rec.upstream_node_id == NO_UPSTREAM_NODE) { appendPQExpBufferChar(upstream, '!'); item_list_append_format(warnings, "node \"%s\" (ID: %i) reports it has no upstream (expected: \"%s\")", node_info->node_name, node_info->node_id, node_info->upstream_node_name); } else if (node_info->upstream_node_id != NO_UPSTREAM_NODE && remote_node_rec.upstream_node_id != NO_UPSTREAM_NODE) { appendPQExpBuffer(upstream, "! %s", remote_node_rec.upstream_node_name); item_list_append_format(warnings, "node \"%s\" (ID: %i) reports a different upstream (reported: \"%s\", expected \"%s\")", node_info->node_name, node_info->node_id, remote_node_rec.upstream_node_name, node_info->upstream_node_name); } } } return error_found; } static const char * action_name(const int action) { switch (action) { case PRIMARY_REGISTER: return "PRIMARY REGISTER"; case PRIMARY_UNREGISTER: return "PRIMARY UNREGISTER"; case STANDBY_CLONE: return "STANDBY CLONE"; case STANDBY_REGISTER: return "STANDBY REGISTER"; case STANDBY_UNREGISTER: return "STANDBY UNREGISTER"; case STANDBY_PROMOTE: return "STANDBY PROMOTE"; case STANDBY_FOLLOW: return "STANDBY FOLLOW"; case STANDBY_SWITCHOVER: return "STANDBY SWITCHOVER"; case WITNESS_REGISTER: return "WITNESS REGISTER"; case WITNESS_UNREGISTER: return "WITNESS UNREGISTER"; case NODE_STATUS: return "NODE STATUS"; case NODE_CHECK: return "NODE CHECK"; case NODE_REJOIN: return "NODE REJOIN"; case NODE_SERVICE: return "NODE SERVICE"; case NODE_CONTROL: return "NODE CONTROL"; case CLUSTER_SHOW: return "CLUSTER SHOW"; case CLUSTER_CLEANUP: return "CLUSTER CLEANUP"; case CLUSTER_EVENT: return "CLUSTER EVENT"; case CLUSTER_MATRIX: return "CLUSTER MATRIX"; case CLUSTER_CROSSCHECK: return "CLUSTER CROSSCHECK"; case SERVICE_STATUS: return "SERVICE STATUS"; case SERVICE_PAUSE: return "SERVICE PAUSE"; case SERVICE_UNPAUSE: return "SERVICE UNPAUSE"; case DAEMON_START: return "DAEMON START"; case DAEMON_STOP: return "DAEMON STOP"; } return "UNKNOWN ACTION"; } void print_error_list(ItemList *error_list, int log_level) { ItemListCell *cell = NULL; for (cell = error_list->head; cell; cell = cell->next) { switch (log_level) { /* Currently we only need errors and warnings */ case LOG_ERROR: log_error("%s", cell->string); break; case LOG_WARNING: log_warning("%s", cell->string); break; } } } void print_status_header(int cols, ColHeader *headers) { int i, di; int max_cols = 0; /* count how many columns we actually need to display */ for (i = 0; i < cols; i++) { if (headers[i].display == true) max_cols ++; } for (i = 0; i < cols; i++) { if (headers[i].display == false) continue; if (i == 0) printf(" "); else printf(" | "); printf("%-*s", headers[i].max_length, headers[i].title); } printf("\n"); printf("-"); di = 0; for (i = 0; i < cols; i++) { int j; if (headers[i].display == false) continue; for (j = 0; j < headers[i].max_length; j++) printf("-"); if (di < (max_cols - 1)) printf("-+-"); else printf("-"); di++; } printf("\n"); } void print_help_header(void) { printf(_("%s: replication management tool for PostgreSQL\n"), progname()); puts(""); /* add a big friendly warning if root is executing "repmgr --help" */ if (geteuid() == 0) { printf(_(" **************************************************\n")); printf(_(" *** repmgr must be executed by a non-superuser ***\n")); printf(_(" **************************************************\n")); puts(""); } } static void do_help(void) { print_help_header(); printf(_("Usage:\n")); printf(_(" %s [OPTIONS] primary {register|unregister}\n"), progname()); printf(_(" %s [OPTIONS] standby {register|unregister|clone|promote|follow|switchover}\n"), progname()); printf(_(" %s [OPTIONS] node {status|check|rejoin|service}\n"), progname()); printf(_(" %s [OPTIONS] cluster {show|event|matrix|crosscheck|cleanup}\n"), progname()); printf(_(" %s [OPTIONS] witness {register|unregister}\n"), progname()); printf(_(" %s [OPTIONS] service {status|pause|unpause}\n"), progname()); printf(_(" %s [OPTIONS] daemon {start|stop}\n"), progname()); puts(""); printf(_(" Execute \"%s {primary|standby|node|cluster|witness|service} --help\" to see command-specific options\n"), progname()); puts(""); printf(_("General options:\n")); printf(_(" -?, --help show this help, then exit\n")); printf(_(" -V, --version output version information, then exit\n")); printf(_(" --version-number output version number, then exit\n")); puts(""); printf(_("General configuration options:\n")); printf(_(" -b, --pg_bindir=PATH path to PostgreSQL binaries (optional)\n")); printf(_(" -f, --config-file=PATH path to the repmgr configuration file\n")); printf(_(" -F, --force force potentially dangerous operations to happen\n")); puts(""); printf(_("Database connection options:\n")); printf(_(" -d, --dbname=DBNAME database to connect to (default: ")); if (runtime_options.dbname[0] != '\0') printf(_("\"%s\")\n"), runtime_options.dbname); else printf(_("\"%s\")\n"), runtime_options.username); printf(_(" -h, --host=HOSTNAME database server host")); if (runtime_options.host[0] != '\0') printf(_(" (default: \"%s\")"), runtime_options.host); printf(_("\n")); printf(_(" -p, --port=PORT database server port (default: \"%s\")\n"), runtime_options.port); printf(_(" -U, --username=USERNAME database user name to connect as (default: \"%s\")\n"), runtime_options.username); puts(""); printf(_("Node-specific options:\n")); printf(_(" -D, --pgdata=DIR location of the node's data directory \n")); printf(_(" --node-id specify a node by id (only available for some operations)\n")); printf(_(" --node-name specify a node by name (only available for some operations)\n")); puts(""); printf(_("Logging options:\n")); printf(_(" --dry-run show what would happen for action, but don't execute it\n")); printf(_(" -L, --log-level set log level (overrides configuration file; default: NOTICE)\n")); printf(_(" --log-to-file log to file (or logging facility) defined in repmgr.conf\n")); printf(_(" -q, --quiet suppress all log output apart from errors\n")); printf(_(" -t, --terse don't display detail, hints and other non-critical output\n")); printf(_(" -v, --verbose display additional log output (useful for debugging)\n")); puts(""); printf(_("%s home page: <%s>\n"), "repmgr", REPMGR_URL); } /* * Create the repmgr extension, and grant access for the repmgr * user if not a superuser. * * Note: * This is one of two places where superuser rights are required. * We should also consider possible scenarios where a non-superuser * has sufficient privileges to install the extension. */ bool create_repmgr_extension(PGconn *conn) { PQExpBufferData query; PGresult *res; ExtensionStatus extension_status = REPMGR_UNKNOWN; t_connection_user userinfo = T_CONNECTION_USER_INITIALIZER; bool is_superuser = false; PGconn *superuser_conn = NULL; PGconn *schema_create_conn = NULL; t_extension_versions extversions = T_EXTENSION_VERSIONS_INITIALIZER; extension_status = get_repmgr_extension_status(conn, &extversions); switch (extension_status) { case REPMGR_UNKNOWN: log_error(_("unable to determine status of \"repmgr\" extension")); return false; case REPMGR_UNAVAILABLE: log_error(_("\"repmgr\" extension is not available")); return false; case REPMGR_OLD_VERSION_INSTALLED: log_error(_("an older version of the \"repmgr\" extension is installed")); log_detail(_("version %s is installed but newer version %s is available"), extversions.installed_version, extversions.default_version); log_hint(_("update the installed extension version by executing \"ALTER EXTENSION repmgr UPDATE\" in the repmgr database")); return false; case REPMGR_INSTALLED: log_info(_("\"repmgr\" extension is already installed")); return true; case REPMGR_AVAILABLE: if (runtime_options.dry_run == true) { log_notice(_("would now attempt to install extension \"repmgr\"")); } else { log_notice(_("attempting to install extension \"repmgr\"")); } break; } /* 3. Attempt to get a superuser connection */ is_superuser = is_superuser_connection(conn, &userinfo); get_superuser_connection(&conn, &superuser_conn, &schema_create_conn); if (runtime_options.dry_run == true) return true; /* 4. Create extension */ res = PQexec(schema_create_conn, "CREATE EXTENSION repmgr"); if ((PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)) { log_error(_("unable to create \"repmgr\" extension:\n %s"), PQerrorMessage(schema_create_conn)); log_hint(_("check that the provided user has sufficient privileges for CREATE EXTENSION")); PQclear(res); if (superuser_conn != NULL) PQfinish(superuser_conn); return false; } PQclear(res); /* 5. If not superuser, grant usage */ if (is_superuser == false) { initPQExpBuffer(&query); appendPQExpBuffer(&query, "GRANT USAGE ON SCHEMA repmgr TO %s", userinfo.username); res = PQexec(schema_create_conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_error(_("unable to grant usage on \"repmgr\" extension to %s:\n %s"), userinfo.username, PQerrorMessage(schema_create_conn)); PQclear(res); if (superuser_conn != 0) PQfinish(superuser_conn); return false; } initPQExpBuffer(&query); appendPQExpBuffer(&query, "GRANT ALL ON ALL TABLES IN SCHEMA repmgr TO %s", userinfo.username); res = PQexec(schema_create_conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_error(_("unable to grant permission on tables on \"repmgr\" extension to %s:\n %s"), userinfo.username, PQerrorMessage(schema_create_conn)); PQclear(res); if (superuser_conn != NULL) PQfinish(superuser_conn); return false; } } if (superuser_conn != NULL) PQfinish(superuser_conn); log_notice(_("\"repmgr\" extension successfully installed")); create_event_notification(conn, &config_file_options, config_file_options.node_id, "cluster_created", true, NULL); return true; } /** * check_server_version() * * Verify that the server is MIN_SUPPORTED_VERSION_NUM or later * * PGconn *conn: * the connection to check * * char *server_type: * either "primary" or "standby"; used to format error message * * bool exit_on_error: * exit if reported server version is too low; optional to enable some callers * to perform additional cleanup * * char *server_version_string * passed to get_server_version(), which will place the human-readable * server version string there (e.g. "9.4.0") */ int check_server_version(PGconn *conn, char *server_type, bool exit_on_error, char *server_version_string) { char version_string[MAXVERSIONSTR] = ""; int conn_server_version_num = get_server_version(conn, version_string); /* Copy the version string, if the caller wants it */ if (server_version_string != NULL) strncpy(server_version_string, version_string, MAXVERSIONSTR); if (conn_server_version_num < MIN_SUPPORTED_VERSION_NUM) { if (conn_server_version_num > 0) { log_error(_("%s requires %s to be PostgreSQL %s or later"), progname(), server_type, MIN_SUPPORTED_VERSION); log_detail(_("%s server version is %s"), server_type, version_string); } if (exit_on_error == true) { PQfinish(conn); exit(ERR_BAD_CONFIG); } return UNKNOWN_SERVER_VERSION_NUM; } /* * If it's clear a particular repmgr feature branch won't be able to support * PostgreSQL from a particular PostgreSQL release onwards (e.g. 4.4 with PostgreSQL * 12 and later due to recovery.conf removal), set MAX_UNSUPPORTED_VERSION and * MAX_UNSUPPORTED_VERSION_NUM in "repmgr.h" to define the first PostgreSQL * version which can't be supported. */ #ifdef MAX_UNSUPPORTED_VERSION_NUM if (conn_server_version_num >= MAX_UNSUPPORTED_VERSION_NUM) { if (conn_server_version_num > 0) { log_error(_("%s %s does not support PostgreSQL %s or later"), progname(), REPMGR_VERSION, MAX_UNSUPPORTED_VERSION); log_detail(_("%s server version is %s"), server_type, version_string); log_hint(_("For details of supported versions see: https://repmgr.org/docs/current/install-requirements.html#INSTALL-COMPATIBILITY-MATRIX")); } if (exit_on_error == true) { PQfinish(conn); exit(ERR_BAD_CONFIG); } return UNKNOWN_SERVER_VERSION_NUM; } #endif return conn_server_version_num; } int test_ssh_connection(char *host, char *remote_user) { char script[MAXLEN] = ""; int r = 1, i; /* * On some OS, true is located in a different place than in Linux we have * to try them all until all alternatives are gone or we found `true' * because the target OS may differ from the source OS */ const char *bin_true_paths[] = { "/bin/true", "/usr/bin/true", NULL }; for (i = 0; bin_true_paths[i] && r != 0; ++i) { if (!remote_user[0]) maxlen_snprintf(script, "ssh -o Batchmode=yes %s %s %s 2>/dev/null", config_file_options.ssh_options, host, bin_true_paths[i]); else maxlen_snprintf(script, "ssh -o Batchmode=yes %s %s -l %s %s 2>/dev/null", config_file_options.ssh_options, host, remote_user, bin_true_paths[i]); log_verbose(LOG_DEBUG, _("test_ssh_connection(): executing %s"), script); r = system(script); } if (r != 0) log_warning(_("unable to connect to remote host \"%s\" via SSH"), host); return r; } /* * get_superuser_connection() * * Check if provided connection "conn" is a superuser connection, if not attempt to * make a superuser connection "superuser_conn" with the provided --superuser parameter. * * "privileged_conn" is set to whichever connection is the superuser connection. */ void get_superuser_connection(PGconn **conn, PGconn **superuser_conn, PGconn **privileged_conn) { t_connection_user userinfo = T_CONNECTION_USER_INITIALIZER; t_conninfo_param_list conninfo_params = T_CONNINFO_PARAM_LIST_INITIALIZER; bool is_superuser = false; /* this should never happen */ if (PQstatus(*conn) != CONNECTION_OK) { log_error(_("no database connection available")); log_detail("\n%s", PQerrorMessage(*conn)); exit(ERR_INTERNAL); } is_superuser = is_superuser_connection(*conn, &userinfo); if (is_superuser == true) { *privileged_conn = *conn; return; } if (runtime_options.superuser[0] == '\0') { log_error(_("\"%s\" is not a superuser and no superuser name supplied"), userinfo.username); log_hint(_("supply a valid superuser name with -S/--superuser")); PQfinish(*conn); exit(ERR_BAD_CONFIG); } initialize_conninfo_params(&conninfo_params, false); conn_to_param_list(*conn, &conninfo_params); param_set(&conninfo_params, "user", runtime_options.superuser); *superuser_conn = establish_db_connection_by_params(&conninfo_params, false); if (PQstatus(*superuser_conn) != CONNECTION_OK) { log_error(_("unable to establish superuser connection as \"%s\""), runtime_options.superuser); PQfinish(*conn); exit(ERR_BAD_CONFIG); } /* check provided superuser really is superuser */ if (!is_superuser_connection(*superuser_conn, NULL)) { log_error(_("\"%s\" is not a superuser"), runtime_options.superuser); PQfinish(*superuser_conn); PQfinish(*conn); exit(ERR_BAD_CONFIG); } log_debug("established superuser connection as \"%s\"", runtime_options.superuser); *privileged_conn = *superuser_conn; return; } standy_clone_mode get_standby_clone_mode(void) { standy_clone_mode mode; if (*config_file_options.barman_host != '\0' && runtime_options.without_barman == false) mode = barman; else mode = pg_basebackup; return mode; } void make_pg_path(PQExpBufferData *buf, const char *file) { appendPQExpBuffer(buf, "%s%s", pg_bindir, file); } int copy_remote_files(char *host, char *remote_user, char *remote_path, char *local_path, bool is_directory, int server_version_num) { PQExpBufferData rsync_flags; char script[MAXLEN] = ""; char host_string[MAXLEN] = ""; int r = 0; initPQExpBuffer(&rsync_flags); if (*config_file_options.rsync_options == '\0') { appendPQExpBufferStr(&rsync_flags, "--archive --checksum --compress --progress --rsh=ssh"); } else { appendPQExpBufferStr(&rsync_flags, config_file_options.rsync_options); } if (runtime_options.force) { appendPQExpBufferStr(&rsync_flags, " --delete --checksum"); } if (!remote_user[0]) { maxlen_snprintf(host_string, "%s", host); } else { maxlen_snprintf(host_string, "%s@%s", remote_user, host); } /* * When copying the main PGDATA directory, certain files and contents of * certain directories need to be excluded. * * See function 'sendDir()' in 'src/backend/replication/basebackup.c' - * we're basically simulating what pg_basebackup does, but with rsync * rather than the BASEBACKUP replication protocol command. * * *However* currently we'll always copy the contents of the 'pg_replslot' * directory and delete later if appropriate. */ if (is_directory) { /* Files which we don't want */ appendPQExpBufferStr(&rsync_flags, " --exclude=postmaster.pid --exclude=postmaster.opts --exclude=global/pg_control"); appendPQExpBufferStr(&rsync_flags, " --exclude=recovery.conf --exclude=recovery.done"); /* * Ideally we'd use PG_AUTOCONF_FILENAME from utils/guc.h, but * that has too many dependencies for a mere client program. */ appendPQExpBuffer(&rsync_flags, " --exclude=%s.tmp", PG_AUTOCONF_FILENAME); /* Temporary files which we don't want, if they exist */ appendPQExpBuffer(&rsync_flags, " --exclude=%s*", PG_TEMP_FILE_PREFIX); /* Directories which we don't want */ if (server_version_num >= 100000) { appendPQExpBufferStr(&rsync_flags, " --exclude=pg_wal/* --exclude=log/*"); } else { appendPQExpBufferStr(&rsync_flags, " --exclude=pg_xlog/* --exclude=pg_log/*"); } appendPQExpBufferStr(&rsync_flags, " --exclude=pg_stat_tmp/*"); maxlen_snprintf(script, "rsync %s %s:%s/* %s", rsync_flags.data, host_string, remote_path, local_path); } else { maxlen_snprintf(script, "rsync %s %s:%s %s", rsync_flags.data, host_string, remote_path, local_path); } termPQExpBuffer(&rsync_flags); log_info(_("rsync command line:\n %s"), script); r = system(script); log_debug("copy_remote_files(): r = %i; WIFEXITED: %i; WEXITSTATUS: %i", r, WIFEXITED(r), WEXITSTATUS(r)); /* exit code 24 indicates vanished files, which isn't a problem for us */ if (WIFEXITED(r) && WEXITSTATUS(r) && WEXITSTATUS(r) != 24) log_verbose(LOG_WARNING, "copy_remote_files(): rsync returned unexpected exit status %i", WEXITSTATUS(r)); return r; } void make_remote_repmgr_path(PQExpBufferData *output_buf, t_node_info *remote_node_record) { if (config_file_options.repmgr_bindir[0] != '\0') { int len = strlen(config_file_options.repmgr_bindir); appendPQExpBufferStr(output_buf, config_file_options.repmgr_bindir); /* Add trailing slash */ if (config_file_options.repmgr_bindir[len - 1] != '/') { appendPQExpBufferChar(output_buf, '/'); } } else if (pg_bindir[0] != '\0') { appendPQExpBufferStr(output_buf, pg_bindir); } appendPQExpBuffer(output_buf, "%s -f %s ", progname(), remote_node_record->config_file); /* * If --log-level was explicitly supplied, pass that through * to the remote repmgr client too. */ if (runtime_options.log_level[0] != '\0') { appendPQExpBuffer(output_buf, " -L %s ", runtime_options.log_level); } } void make_repmgrd_path(PQExpBufferData *output_buf) { if (config_file_options.repmgr_bindir[0] != '\0') { int len = strlen(config_file_options.repmgr_bindir); appendPQExpBufferStr(output_buf, config_file_options.repmgr_bindir); /* Add trailing slash */ if (config_file_options.repmgr_bindir[len - 1] != '/') { appendPQExpBufferChar(output_buf, '/'); } } else if (pg_bindir[0] != '\0') { appendPQExpBufferStr(output_buf, pg_bindir); } appendPQExpBuffer(output_buf, "repmgrd -f %s ", config_file_path); } /* ======================== */ /* server control functions */ /* ======================== */ void get_server_action(t_server_action action, char *script, char *data_dir) { PQExpBufferData command; if (data_dir == NULL || data_dir[0] == '\0') data_dir = "(none provided)"; switch (action) { case ACTION_NONE: script[0] = '\0'; return; case ACTION_START: { if (config_file_options.service_start_command[0] != '\0') { maxlen_snprintf(script, "%s", config_file_options.service_start_command); } else { initPQExpBuffer(&command); make_pg_path(&command, "pg_ctl"); appendPQExpBuffer(&command, " %s -w -D ", config_file_options.pg_ctl_options); appendShellString(&command, data_dir); appendPQExpBuffer(&command, " start"); strncpy(script, command.data, MAXLEN); termPQExpBuffer(&command); } return; } case ACTION_STOP: case ACTION_STOP_WAIT: { if (config_file_options.service_stop_command[0] != '\0') { maxlen_snprintf(script, "%s", config_file_options.service_stop_command); } else { initPQExpBuffer(&command); make_pg_path(&command, "pg_ctl"); appendPQExpBuffer(&command, " %s -D ", config_file_options.pg_ctl_options); appendShellString(&command, data_dir); if (action == ACTION_STOP_WAIT) appendPQExpBuffer(&command, " -w"); else appendPQExpBuffer(&command, " -W"); appendPQExpBuffer(&command, " -m fast stop"); strncpy(script, command.data, MAXLEN); termPQExpBuffer(&command); } return; } case ACTION_RESTART: { if (config_file_options.service_restart_command[0] != '\0') { maxlen_snprintf(script, "%s", config_file_options.service_restart_command); } else { initPQExpBuffer(&command); make_pg_path(&command, "pg_ctl"); appendPQExpBuffer(&command, " %s -w -D ", config_file_options.pg_ctl_options); appendShellString(&command, data_dir); appendPQExpBuffer(&command, " restart"); strncpy(script, command.data, MAXLEN); termPQExpBuffer(&command); } return; } case ACTION_RELOAD: { if (config_file_options.service_reload_command[0] != '\0') { maxlen_snprintf(script, "%s", config_file_options.service_reload_command); } else { initPQExpBuffer(&command); make_pg_path(&command, "pg_ctl"); appendPQExpBuffer(&command, " %s -w -D ", config_file_options.pg_ctl_options); appendShellString(&command, data_dir); appendPQExpBuffer(&command, " reload"); strncpy(script, command.data, MAXLEN); termPQExpBuffer(&command); } return; } case ACTION_PROMOTE: { if (config_file_options.service_promote_command[0] != '\0') { maxlen_snprintf(script, "%s", config_file_options.service_promote_command); } else { initPQExpBuffer(&command); make_pg_path(&command, "pg_ctl"); appendPQExpBuffer(&command, " %s -w -D ", config_file_options.pg_ctl_options); appendShellString(&command, data_dir); appendPQExpBuffer(&command, " promote"); strncpy(script, command.data, MAXLEN); termPQExpBuffer(&command); } return; } default: return; } return; } bool data_dir_required_for_action(t_server_action action) { switch (action) { case ACTION_NONE: return false; case ACTION_START: if (config_file_options.service_start_command[0] != '\0') { return false; } return true; case ACTION_STOP: case ACTION_STOP_WAIT: if (config_file_options.service_stop_command[0] != '\0') { return false; } return true; case ACTION_RESTART: if (config_file_options.service_restart_command[0] != '\0') { return false; } return true; case ACTION_RELOAD: if (config_file_options.service_reload_command[0] != '\0') { return false; } return true; case ACTION_PROMOTE: if (config_file_options.service_promote_command[0] != '\0') { return false; } return true; default: return false; } return false; } /* * Copy the location of the configuration file directory into the * provided buffer; if "config_directory" provided, use that, otherwise * default to the data directory. * * This is primarily intended for use with "pg_ctl" (which itself shouldn't * be used outside of development environments). */ void get_node_config_directory(char *config_dir_buf) { if (config_file_options.config_directory[0] != '\0') { strncpy(config_dir_buf, config_file_options.config_directory, MAXPGPATH); return; } if (config_file_options.data_directory[0] != '\0') { strncpy(config_dir_buf, config_file_options.data_directory, MAXPGPATH); return; } return; } void get_node_data_directory(char *data_dir_buf) { /* * the configuration file setting has priority, and will always be set * when a configuration file was provided */ if (config_file_options.data_directory[0] != '\0') { strncpy(data_dir_buf, config_file_options.data_directory, MAXPGPATH); return; } if (runtime_options.data_dir[0] != '\0') { strncpy(data_dir_buf, runtime_options.data_dir, MAXPGPATH); return; } return; } /* * initialise a node record from the provided configuration * parameters */ void init_node_record(t_node_info *node_record) { node_record->node_id = config_file_options.node_id; node_record->upstream_node_id = runtime_options.upstream_node_id; node_record->priority = config_file_options.priority; node_record->active = true; if (config_file_options.location[0] != '\0') strncpy(node_record->location, config_file_options.location, MAXLEN); else strncpy(node_record->location, "default", MAXLEN); strncpy(node_record->node_name, config_file_options.node_name, sizeof(node_record->node_name)); strncpy(node_record->conninfo, config_file_options.conninfo, MAXLEN); strncpy(node_record->config_file, config_file_path, MAXPGPATH); if (config_file_options.replication_user[0] != '\0') { /* replication user explicitly provided in configuration file */ strncpy(node_record->repluser, config_file_options.replication_user, NAMEDATALEN); } else { /* use the "user" value from "conninfo" */ char repluser[MAXLEN] = ""; (void) get_conninfo_value(config_file_options.conninfo, "user", repluser); strncpy(node_record->repluser, repluser, NAMEDATALEN); } if (config_file_options.use_replication_slots == true) { create_slot_name(node_record->slot_name, config_file_options.node_id); } } bool can_use_pg_rewind(PGconn *conn, const char *data_directory, PQExpBufferData *reason) { bool can_use = true; /* "full_page_writes" must be on in any case */ if (guc_set(conn, "full_page_writes", "=", "off")) { appendPQExpBuffer(reason, _("\"full_page_writes\" must be set to \"on\"")); can_use = false; } /* * "wal_log_hints" off - are data checksums available? Note: we're * checking the local pg_control file here as the value will be the same * throughout the cluster and saves a round-trip to the demotion * candidate. */ if (guc_set(conn, "wal_log_hints", "=", "on") == false) { int data_checksum_version = get_data_checksum_version(data_directory); if (data_checksum_version == UNKNOWN_DATA_CHECKSUM_VERSION) { if (can_use == false) appendPQExpBuffer(reason, "; "); appendPQExpBuffer(reason, _("\"wal_log_hints\" is set to \"off\" but unable to determine data checksum version")); can_use = false; } else if (data_checksum_version == 0) { if (can_use == false) appendPQExpBuffer(reason, "; "); appendPQExpBuffer(reason, _("\"wal_log_hints\" is set to \"off\" and data checksums are disabled")); can_use = false; } } return can_use; } void make_standby_signal_path(const char *data_dir, char *buf) { snprintf(buf, MAXPGPATH, "%s/%s", data_dir, STANDBY_SIGNAL_FILE); } /* * create standby.signal (PostgreSQL 12 and later) */ bool write_standby_signal(const char *data_dir) { char standby_signal_file_path[MAXPGPATH] = ""; FILE *file; mode_t um; Assert(data_dir != NULL); make_standby_signal_path(data_dir, standby_signal_file_path); /* Set umask to 0600 */ um = umask((~(S_IRUSR | S_IWUSR)) & (S_IRWXG | S_IRWXO)); file = fopen(standby_signal_file_path, "w"); umask(um); if (file == NULL) { log_error(_("unable to create %s file at \"%s\""), STANDBY_SIGNAL_FILE, standby_signal_file_path); log_detail("%s", strerror(errno)); return false; } if (fputs("# created by repmgr\n", file) == EOF) { log_error(_("unable to write to %s file at \"%s\""), STANDBY_SIGNAL_FILE, standby_signal_file_path); fclose(file); return false; } fclose(file); return true; } /* * NOTE: * - the provided connection should be for the normal repmgr user * - if upstream_node_record is not NULL, its "repluser" entry, if * set, will be used as the fallback replication user */ bool create_replication_slot(PGconn *conn, char *slot_name, t_node_info *upstream_node_record, PQExpBufferData *error_msg) { PGconn *slot_conn = NULL; bool use_replication_protocol = false; bool success = true; char *replication_user = NULL; _determine_replication_slot_user(conn, upstream_node_record, &replication_user); /* * If called in --dry-run context, if the replication slot user is not the * repmgr user, attempt to validate the connection. */ if (runtime_options.dry_run == true) { switch (ReplicationSlotUser) { case USER_TYPE_UNKNOWN: log_error("unable to determine user for replication slot creation"); return false; case REPMGR_USER: log_info(_("replication slots will be created by user \"%s\""), PQuser(conn)); return true; case REPLICATION_USER_NODE: case REPLICATION_USER_OPT: { PGconn *repl_conn = duplicate_connection(conn, replication_user, true); if (repl_conn == NULL || PQstatus(repl_conn) != CONNECTION_OK) { log_error(_("unable to create replication connection as user \"%s\""), replication_user); log_detail("%s", PQerrorMessage(repl_conn)); PQfinish(repl_conn); return false; } log_info(_("replication slots will be created by replication user \"%s\""), replication_user); PQfinish(repl_conn); return true; } case SUPERUSER: { PGconn *superuser_conn = duplicate_connection(conn, runtime_options.superuser, false); if (superuser_conn == NULL || PQstatus(superuser_conn )!= CONNECTION_OK) { log_error(_("unable to create superuser connection as user \"%s\""), runtime_options.superuser); log_detail("%s", PQerrorMessage(superuser_conn)); PQfinish(superuser_conn); return false; } log_info(_("replication slots will be created by superuser \"%s\""), runtime_options.superuser); PQfinish(superuser_conn); } } } /* * If we can't create a replication slot with the connection provided to * the function, create an connection with appropriate permissions. */ switch (ReplicationSlotUser) { case USER_TYPE_UNKNOWN: log_error("unable to determine user for replication slot creation"); return false; case REPMGR_USER: slot_conn = conn; log_info(_("creating replication slot as user \"%s\""), PQuser(conn)); break; case REPLICATION_USER_NODE: case REPLICATION_USER_OPT: { slot_conn = duplicate_connection(conn, replication_user, true); if (slot_conn == NULL || PQstatus(slot_conn) != CONNECTION_OK) { log_error(_("unable to create replication connection as user \"%s\""), runtime_options.replication_user); log_detail("%s", PQerrorMessage(slot_conn)); PQfinish(slot_conn); return false; } use_replication_protocol = true; log_info(_("creating replication slot as replication user \"%s\""), replication_user); } break; case SUPERUSER: { slot_conn = duplicate_connection(conn, runtime_options.superuser, false); if (slot_conn == NULL || PQstatus(slot_conn )!= CONNECTION_OK) { log_error(_("unable to create super connection as user \"%s\""), runtime_options.superuser); log_detail("%s", PQerrorMessage(slot_conn)); PQfinish(slot_conn); return false; } log_info(_("creating replication slot as superuser \"%s\""), runtime_options.superuser); } break; } if (use_replication_protocol == true) { success = create_replication_slot_replprot(conn, slot_conn, slot_name, error_msg); } else { success = create_replication_slot_sql(slot_conn, slot_name, error_msg); } if (slot_conn != conn) PQfinish(slot_conn); return success; } bool drop_replication_slot_if_exists(PGconn *conn, int node_id, char *slot_name) { t_node_info node_record = T_NODE_INFO_INITIALIZER; t_replication_slot slot_info = T_REPLICATION_SLOT_INITIALIZER; RecordStatus record_status; char *replication_user = NULL; bool success = true; if (node_id != UNKNOWN_NODE_ID) { record_status = get_node_record(conn, node_id, &node_record); } _determine_replication_slot_user(conn, &node_record, &replication_user); record_status = get_slot_record(conn, slot_name, &slot_info); log_verbose(LOG_DEBUG, "attempting to delete slot \"%s\" on node %i", slot_name, node_id); if (record_status != RECORD_FOUND) { /* this is not a bad good thing */ log_verbose(LOG_INFO, _("slot \"%s\" does not exist on node %i, nothing to remove"), slot_name, node_id); return true; } if (slot_info.active == false) { if (drop_replication_slot_sql(conn, slot_name) == true) { log_notice(_("replication slot \"%s\" deleted on node %i"), slot_name, node_id); } else { log_error(_("unable to delete replication slot \"%s\" on node %i"), slot_name, node_id); success = false; } } /* * If an active replication slot exists, call Houston as we have a * problem. */ else { log_warning(_("replication slot \"%s\" is still active on node %i"), slot_name, node_id); success = false; } return success; } static void _determine_replication_slot_user(PGconn *conn, t_node_info *upstream_node_record, char **replication_user) { /* * If not previously done, work out which user will be responsible * for creating replication slots. */ if (ReplicationSlotUser == USER_TYPE_UNKNOWN) { /* * Is the repmgr user a superuser? */ if (is_superuser_connection(conn, NULL)) { ReplicationSlotUser = REPMGR_USER; } /* * Does the repmgr user have the REPLICATION role? * Note we don't care here whether the repmgr user can actually * make a replication connection, we're just confirming that the * connection we have has the appropriate permissions. */ else if (is_replication_role(conn, NULL)) { ReplicationSlotUser = REPMGR_USER; } /* * Is a superuser provided with --superuser? * We'll check later whether we can make a connection as that user. */ else if (runtime_options.superuser[0] != '\0') { ReplicationSlotUser = SUPERUSER; } /* * Is a replication user provided with --replication-user? * We'll check later whether we can make a replication connection as that user. * Overrides any replication user defined in the upstream node record. */ else if (runtime_options.replication_user[0] != '\0') { ReplicationSlotUser = REPLICATION_USER_OPT; *replication_user = runtime_options.replication_user; } /* * Is the upstream's node record provided, and does it have a different * replication user? * We'll check later whether we can make a replication connection as that user. */ else if (upstream_node_record != NULL && upstream_node_record->node_id != UNKNOWN_NODE_ID && strncmp(upstream_node_record->repluser, PQuser(conn), NAMEDATALEN) != 0) { ReplicationSlotUser = REPLICATION_USER_NODE; *replication_user = upstream_node_record->repluser; } } } bool check_replication_slots_available(int node_id, PGconn* conn) { int max_replication_slots = UNKNOWN_VALUE; int free_slots = get_free_replication_slot_count(conn, &max_replication_slots); if (free_slots < 0) { log_error(_("unable to determine number of free replication slots on node %i"), node_id); return false; } if (free_slots == 0) { log_error(_("no free replication slots available on node %i"), node_id); log_hint(_("consider increasing \"max_replication_slots\" (current value: %i)"), max_replication_slots); return false; } else if (runtime_options.dry_run == true) { log_info(_("replication slots in use, %i free slots on node %i"), node_id, free_slots); } return true; } /* * Check whether the specified standby has joined to its upstream. * * This is used by "standby switchover" and "node rejoin" to check * the success of a node rejoin operation. * * IMPORTANT: the timeout settings will be taken from the node where the check * is performed, which might not be the standby itself. */ standy_join_status check_standby_join(PGconn *upstream_conn, t_node_info *upstream_node_record, t_node_info *standby_node_record) { int i; bool available = false; for (i = 0; i < config_file_options.standby_reconnect_timeout; i++) { if (is_server_available(config_file_options.conninfo)) { log_verbose(LOG_INFO, _("node \"%s\" (ID: %i) is pingable"), standby_node_record->node_name, standby_node_record->node_id); available = true; break; } if (i % 5 == 0) { log_verbose(LOG_INFO, _("waiting for node \"%s\" (ID: %i) to respond to pings; %i of max %i attempts (parameter \"node_rejoin_timeout\")"), standby_node_record->node_name, standby_node_record->node_id, i + 1, config_file_options.node_rejoin_timeout); } else { log_debug("sleeping 1 second waiting for node \"%s\" (ID: %i) to respond to pings; %i of max %i attempts", standby_node_record->node_name, standby_node_record->node_id, i + 1, config_file_options.node_rejoin_timeout); } sleep(1); } /* node did not become available */ if (available == false) { return JOIN_FAIL_NO_PING; } for (; i < config_file_options.node_rejoin_timeout; i++) { char *node_state = NULL; NodeAttached node_attached = is_downstream_node_attached(upstream_conn, standby_node_record->node_name, &node_state); if (node_attached == NODE_ATTACHED) { log_verbose(LOG_INFO, _("node \"%s\" (ID: %i) has attached to its upstream node"), standby_node_record->node_name, standby_node_record->node_id); return JOIN_SUCCESS; } if (i % 5 == 0) { log_info(_("waiting for node \"%s\" (ID: %i) to connect to new primary; %i of max %i attempts (parameter \"node_rejoin_timeout\")"), standby_node_record->node_name, standby_node_record->node_id, i + 1, config_file_options.node_rejoin_timeout); if (node_attached == NODE_NOT_ATTACHED) { log_detail(_("node \"%s\" (ID: %i) is currently attached to its upstream node in state \"%s\""), upstream_node_record->node_name, standby_node_record->node_id, node_state); } else { log_detail(_("checking for record in node \"%s\"'s \"pg_stat_replication\" table where \"application_name\" is \"%s\""), upstream_node_record->node_name, standby_node_record->node_name); } } else { log_debug("sleeping 1 second waiting for node \"%s\" (ID: %i) to connect to new primary; %i of max %i attempts", standby_node_record->node_name, standby_node_record->node_id, i + 1, config_file_options.node_rejoin_timeout); } sleep(1); } return JOIN_FAIL_NO_REPLICATION; } /* * Here we'll perform some timeline sanity checks to ensure the follow target * can actually be followed or rejoined. * * See also comment for check_node_can_follow() in repmgrd-physical.c . */ bool check_node_can_attach(TimeLineID local_tli, XLogRecPtr local_xlogpos, PGconn *follow_target_conn, t_node_info *follow_target_node_record, bool is_rejoin) { uint64 local_system_identifier = UNKNOWN_SYSTEM_IDENTIFIER; PGconn *follow_target_repl_conn = NULL; t_system_identification follow_target_identification = T_SYSTEM_IDENTIFICATION_INITIALIZER; bool success = true; const char *action = is_rejoin == true ? "rejoin" : "follow"; /* check replication connection */ follow_target_repl_conn = establish_replication_connection_from_conn(follow_target_conn, follow_target_node_record->repluser); if (PQstatus(follow_target_repl_conn) != CONNECTION_OK) { log_error(_("unable to establish a replication connection to the %s target node"), action); return false; } else if (runtime_options.dry_run == true) { log_info(_("replication connection to the %s target node was successful"), action); } /* check system_identifiers match */ if (identify_system(follow_target_repl_conn, &follow_target_identification) == false) { log_error(_("unable to query the %s target node's system identification"), action); PQfinish(follow_target_repl_conn); return false; } local_system_identifier = get_system_identifier(config_file_options.data_directory); /* * Check for things that should never happen, but expect the unexpected anyway. */ if (local_system_identifier == UNKNOWN_SYSTEM_IDENTIFIER) { /* * We don't return immediately here so subsequent checks can be * made, but indicate the node will not be able to rejoin. */ success = false; if (runtime_options.dry_run == true) { log_warning(_("unable to retrieve system identifier from pg_control")); } else { log_error(_("unable to retrieve system identifier from pg_control, aborting")); } } else if (follow_target_identification.system_identifier != local_system_identifier) { /* * It's never going to be possible to rejoin a node from another cluster, * so no need to bother with further checks. */ log_error(_("this node is not part of the %s target node's replication cluster"), action); log_detail(_("this node's system identifier is %lu, %s target node's system identifier is %lu"), local_system_identifier, action, follow_target_identification.system_identifier); PQfinish(follow_target_repl_conn); return false; } else if (runtime_options.dry_run == true) { log_info(_("local and %s target system identifiers match"), action); log_detail(_("system identifier is %lu"), local_system_identifier); } /* check timelines */ log_verbose(LOG_DEBUG, "local timeline: %i; %s target timeline: %i", local_tli, action, follow_target_identification.timeline); /* * The upstream's timeline is lower than ours - we cannot follow, and rejoin * requires PostgreSQL 9.6 and later. */ if (follow_target_identification.timeline < local_tli) { /* * "repmgr standby follow" is impossible in this case */ if (is_rejoin == false) { log_error(_("this node's timeline is ahead of the %s target node's timeline"), action); log_detail(_("this node's timeline is %i, %s target node's timeline is %i"), local_tli, action, follow_target_identification.timeline); if (PQserverVersion(follow_target_conn) >= 90600) { log_hint(_("use \"repmgr node rejoin --force-rewind\" to reattach this node")); } PQfinish(follow_target_repl_conn); return false; } /* * pg_rewind can only rejoin to a lower timeline from PostgreSQL 9.6 */ if (PQserverVersion(follow_target_conn) < 90600) { log_error(_("this node's timeline is ahead of the %s target node's timeline"), action); log_detail(_("this node's timeline is %i, %s target node's timeline is %i"), local_tli, action, follow_target_identification.timeline); if (runtime_options.force_rewind_used == true) { log_hint(_("pg_rewind can only be used to rejoin to a node with a lower timeline from PostgreSQL 9.6")); } PQfinish(follow_target_repl_conn); return false; } if (runtime_options.force_rewind_used == false) { log_notice(_("pg_rewind execution required for this node to attach to rejoin target node %i"), follow_target_node_record->node_id); log_hint(_("provide --force-rewind")); PQfinish(follow_target_repl_conn); return false; } } /* timelines are the same - check relative positions */ else if (follow_target_identification.timeline == local_tli) { XLogRecPtr follow_target_xlogpos = get_node_current_lsn(follow_target_conn); if (local_xlogpos == InvalidXLogRecPtr || follow_target_xlogpos == InvalidXLogRecPtr) { log_error(_("unable to compare LSN positions")); PQfinish(follow_target_repl_conn); return false; } if (local_xlogpos <= follow_target_xlogpos) { log_info(_("timelines are same, this server is not ahead")); log_detail(_("local node lsn is %X/%X, %s target lsn is %X/%X"), format_lsn(local_xlogpos), action, format_lsn(follow_target_xlogpos)); } else { /* * Unable to follow or join to a node we're ahead of, if we're on the * same timeline. Also, pg_rewind does not detect this situation, * as there is no definitive fork point. * * Note that Pg will still happily attach to the upstream in state "streaming" * for a while but then detach with an endless stream of * "record with incorrect prev-link" errors. */ log_error(_("this node ahead of the %s target on the same timeline (%i)"), action, local_tli); log_detail(_("local node lsn is %X/%X, %s target lsn is %X/%X"), format_lsn(local_xlogpos), action, format_lsn(follow_target_xlogpos)); if (is_rejoin == true) { log_hint(_("the --force-rewind option is ineffective in this case")); } success = false; } } else { /* * upstream has higher timeline - check where it forked off from this node's timeline */ TimeLineHistoryEntry *follow_target_history = get_timeline_history(follow_target_repl_conn, local_tli + 1); if (follow_target_history == NULL) { /* get_timeline_history() will emit relevant error messages */ PQfinish(follow_target_repl_conn); return false; } log_debug("local tli: %i; local_xlogpos: %X/%X; follow_target_history->tli: %i; follow_target_history->end: %X/%X", local_tli, format_lsn(local_xlogpos), follow_target_history->tli, format_lsn(follow_target_history->end)); /* * Local node has proceeded beyond the follow target's fork, so we * definitely can't attach. * * This could be the case if the follow target was promoted, but does * not contain all changes which are being replayed to this standby. */ if (local_xlogpos > follow_target_history->end) { if (is_rejoin == true && runtime_options.force_rewind_used == true) { log_notice(_("pg_rewind execution required for this node to attach to rejoin target node %i"), follow_target_node_record->node_id); } else { log_error(_("this node cannot attach to %s target node %i"), action, follow_target_node_record->node_id); success = false; } log_detail(_("%s target server's timeline %i forked off current database system timeline %i before current recovery point %X/%X"), action, local_tli + 1, local_tli, format_lsn(local_xlogpos)); if (is_rejoin == true && runtime_options.force_rewind_used == false) { log_hint(_("use --force-rewind to execute pg_rewind")); } } if (success == true) { if (is_rejoin == false || (is_rejoin == true && runtime_options.force_rewind_used == false)) { log_info(_("local node %i can attach to %s target node %i"), config_file_options.node_id, action, follow_target_node_record->node_id); log_detail(_("local node's recovery point: %X/%X; %s target node's fork point: %X/%X"), format_lsn(local_xlogpos), action, format_lsn(follow_target_history->end)); } } pfree(follow_target_history); } PQfinish(follow_target_repl_conn); return success; } /* * Check that the replication configuration file is owned by the user who * owns the data directory. */ extern bool check_replication_config_owner(int pg_version, const char *data_directory, PQExpBufferData *error_msg, PQExpBufferData *detail_msg) { PQExpBufferData replication_config_file; struct stat dirstat; struct stat confstat; if (stat(data_directory, &dirstat)) { if (error_msg != NULL) { appendPQExpBuffer(error_msg, "unable to check ownership of data directory \"%s\"", data_directory); appendPQExpBufferStr(detail_msg, strerror(errno)); } return false; } initPQExpBuffer(&replication_config_file); appendPQExpBuffer(&replication_config_file, "%s/%s", config_file_options.data_directory, pg_version >= 120000 ? PG_AUTOCONF_FILENAME : RECOVERY_COMMAND_FILE); stat(replication_config_file.data, &confstat); if (confstat.st_uid == dirstat.st_uid) { termPQExpBuffer(&replication_config_file); return true; } if (error_msg != NULL) { char conf_owner[MAXLEN]; char dir_owner[MAXLEN]; struct passwd *pw; pw = getpwuid(confstat.st_uid); if (!pw) { maxlen_snprintf(conf_owner, "(unknown user %i)", confstat.st_uid); } else { strncpy(conf_owner, pw->pw_name, MAXLEN); } pw = getpwuid(dirstat.st_uid); if (!pw) { maxlen_snprintf(conf_owner, "(unknown user %i)", dirstat.st_uid); } else { strncpy(dir_owner, pw->pw_name, MAXLEN); } appendPQExpBuffer(error_msg, "ownership error for file \"%s\"", replication_config_file.data); appendPQExpBuffer(detail_msg, "file owner is \"%s\", data directory owner is \"%s\"", conf_owner, dir_owner); } termPQExpBuffer(&replication_config_file); return false; } /* * Simple check to see if "shared_preload_libraries" includes "repmgr". * Parsing "shared_preload_libraries" is non-trivial, as it's potentially * a comma-separated list, and worse may not be readable by the repmgr * user. * * Instead, we check if a function which should return a value returns * NULL; this indicates the shared library is not installed. */ void check_shared_library(PGconn *conn) { bool ok = repmgrd_check_local_node_id(conn); if (ok == true) return; log_error(_("repmgrd not configured for this node")); log_hint(_("ensure \"shared_preload_libraries\" includes \"repmgr\" and restart PostgreSQL")); PQfinish(conn); exit(ERR_BAD_CONFIG); } bool is_repmgrd_running(PGconn *conn) { pid_t pid; bool is_running = false; pid = repmgrd_get_pid(conn); if (pid != UNKNOWN_PID) { if (kill(pid, 0) != -1) { is_running = true; } } return is_running; } /** * Parse the string returned by "repmgr --version", e.g. "repmgr 4.1.2", * and return it as a version integer (e.g. 40102). * * This is required for backwards compatibility as versions prior to * 4.3 do not have the --version-number option. */ int parse_repmgr_version(const char *version_string) { int series, major, minor; int version_integer = UNKNOWN_REPMGR_VERSION_NUM; PQExpBufferData sscanf_string; initPQExpBuffer(&sscanf_string); appendPQExpBuffer(&sscanf_string, "%s ", progname()); appendPQExpBufferStr(&sscanf_string, "%i.%i.%i"); if (sscanf(version_string, sscanf_string.data, &series, &major, &minor) == 3) { version_integer = (series * 10000) + (major * 100) + minor; } else { resetPQExpBuffer(&sscanf_string); appendPQExpBuffer(&sscanf_string, "%s ", progname()); appendPQExpBufferStr(&sscanf_string, "%i.%i"); if (sscanf(version_string, "repmgr %i.%i", &series, &major) == 2) { version_integer = (series * 10000) + (major * 100); } } return version_integer; } repmgr-5.3.1/repmgr-client.h000066400000000000000000000211611420262710000157300ustar00rootroot00000000000000/* * repmgr-client.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _REPMGR_CLIENT_H_ #define _REPMGR_CLIENT_H_ #include #include "log.h" #define NO_ACTION 0 /* Dummy default action */ #define PRIMARY_REGISTER 1 #define PRIMARY_UNREGISTER 2 #define STANDBY_REGISTER 3 #define STANDBY_UNREGISTER 4 #define STANDBY_CLONE 5 #define STANDBY_PROMOTE 6 #define STANDBY_FOLLOW 7 #define STANDBY_SWITCHOVER 8 #define WITNESS_REGISTER 9 #define WITNESS_UNREGISTER 10 #define NODE_STATUS 11 #define NODE_CHECK 12 #define NODE_SERVICE 13 #define NODE_REJOIN 14 #define NODE_CONTROL 15 #define CLUSTER_SHOW 16 #define CLUSTER_CLEANUP 17 #define CLUSTER_MATRIX 18 #define CLUSTER_CROSSCHECK 19 #define CLUSTER_EVENT 20 #define SERVICE_STATUS 21 #define SERVICE_PAUSE 22 #define SERVICE_UNPAUSE 23 #define DAEMON_START 24 #define DAEMON_STOP 25 /* command line options without short versions */ #define OPT_HELP 1001 #define OPT_COPY_EXTERNAL_CONFIG_FILES 1002 #define OPT_CSV 1003 #define OPT_NODE_ID 1004 #define OPT_NODE_NAME 1005 #define OPT_WITHOUT_BARMAN 1006 #define OPT_NO_UPSTREAM_CONNECTION 1007 #define OPT_WAIT_SYNC 1008 #define OPT_LOG_TO_FILE 1009 #define OPT_UPSTREAM_CONNINFO 1010 #define OPT_REPLICATION_USER 1011 #define OPT_EVENT 1012 #define OPT_LIMIT 1013 #define OPT_ALL 1014 #define OPT_DRY_RUN 1015 #define OPT_UPSTREAM_NODE_ID 1016 #define OPT_ACTION 1017 #define OPT_LIST_ACTIONS 1018 #define OPT_CHECKPOINT 1019 #define OPT_IS_SHUTDOWN_CLEANLY 1020 #define OPT_ALWAYS_PROMOTE 1021 #define OPT_FORCE_REWIND 1022 #define OPT_NAGIOS 1023 #define OPT_ARCHIVE_READY 1024 #define OPT_OPTFORMAT 1025 #define OPT_REPLICATION_LAG 1026 #define OPT_CONFIG_FILES 1027 #define OPT_SIBLINGS_FOLLOW 1028 #define OPT_ROLE 1029 #define OPT_DOWNSTREAM 1030 #define OPT_UPSTREAM 1031 #define OPT_SLOTS 1032 #define OPT_HAS_PASSFILE 1033 #define OPT_WAIT_START 1034 #define OPT_REPL_CONN 1035 #define OPT_REMOTE_NODE_ID 1036 #define OPT_REPLICATION_CONF_ONLY 1037 #define OPT_NO_WAIT 1038 #define OPT_MISSING_SLOTS 1039 #define OPT_REPMGRD_NO_PAUSE 1040 #define OPT_VERSION_NUMBER 1041 #define OPT_DATA_DIRECTORY_CONFIG 1042 #define OPT_COMPACT 1043 #define OPT_DETAIL 1044 #define OPT_REPMGRD_FORCE_UNPAUSE 1045 #define OPT_REPLICATION_CONFIG_OWNER 1046 #define OPT_DB_CONNECTION 1047 #define OPT_VERIFY_BACKUP 1048 #define OPT_RECOVERY_MIN_APPLY_DELAY 1049 #define OPT_REPMGRD 1050 /* These options are for internal use only */ #define OPT_CONFIG_ARCHIVE_DIR 2001 #define OPT_DISABLE_WAL_RECEIVER 2002 #define OPT_ENABLE_WAL_RECEIVER 2003 #define OPT_DUMP_CONFIG 2004 /* deprecated since 4.0 */ #define OPT_CHECK_UPSTREAM_CONFIG 999 static struct option long_options[] = { /* general options */ {"help", no_argument, NULL, OPT_HELP}, {"version", no_argument, NULL, 'V'}, {"version-number", no_argument, NULL, OPT_VERSION_NUMBER}, /* general configuration options */ {"config-file", required_argument, NULL, 'f'}, {"dry-run", no_argument, NULL, OPT_DRY_RUN}, {"force", no_argument, NULL, 'F'}, {"pg_bindir", required_argument, NULL, 'b'}, {"wait", optional_argument, NULL, 'w'}, {"no-wait", no_argument, NULL, 'W'}, {"compact", no_argument, NULL, OPT_COMPACT}, {"detail", no_argument, NULL, OPT_DETAIL}, {"dump-config", no_argument, NULL, OPT_DUMP_CONFIG}, /* connection options */ {"dbname", required_argument, NULL, 'd'}, {"host", required_argument, NULL, 'h'}, {"port", required_argument, NULL, 'p'}, {"remote-user", required_argument, NULL, 'R'}, {"superuser", required_argument, NULL, 'S'}, {"username", required_argument, NULL, 'U'}, /* general node options */ {"pgdata", required_argument, NULL, 'D'}, {"node-id", required_argument, NULL, OPT_NODE_ID}, {"node-name", required_argument, NULL, OPT_NODE_NAME}, {"remote-node-id", required_argument, NULL, OPT_REMOTE_NODE_ID}, /* logging options */ {"log-level", required_argument, NULL, 'L'}, {"log-to-file", no_argument, NULL, OPT_LOG_TO_FILE}, {"quiet", no_argument, NULL, 'q'}, {"terse", no_argument, NULL, 't'}, {"verbose", no_argument, NULL, 'v'}, /* output options */ {"csv", no_argument, NULL, OPT_CSV}, {"nagios", no_argument, NULL, OPT_NAGIOS}, {"optformat", no_argument, NULL, OPT_OPTFORMAT}, /* "standby clone" options */ {"copy-external-config-files", optional_argument, NULL, OPT_COPY_EXTERNAL_CONFIG_FILES}, {"fast-checkpoint", no_argument, NULL, 'c'}, {"no-upstream-connection", no_argument, NULL, OPT_NO_UPSTREAM_CONNECTION}, {"replication-user", required_argument, NULL, OPT_REPLICATION_USER}, {"upstream-conninfo", required_argument, NULL, OPT_UPSTREAM_CONNINFO}, {"upstream-node-id", required_argument, NULL, OPT_UPSTREAM_NODE_ID}, {"without-barman", no_argument, NULL, OPT_WITHOUT_BARMAN}, {"replication-conf-only", no_argument, NULL, OPT_REPLICATION_CONF_ONLY}, {"verify-backup", no_argument, NULL, OPT_VERIFY_BACKUP }, {"recovery-min-apply-delay", required_argument, NULL, OPT_RECOVERY_MIN_APPLY_DELAY }, /* deprecate this once Pg11 and earlier are unsupported */ {"recovery-conf-only", no_argument, NULL, OPT_REPLICATION_CONF_ONLY}, /* "standby register" options */ {"wait-start", required_argument, NULL, OPT_WAIT_START}, {"wait-sync", optional_argument, NULL, OPT_WAIT_SYNC}, /* "standby switchover" options * * Note: --force-rewind accepted to pass to "node rejoin" */ {"always-promote", no_argument, NULL, OPT_ALWAYS_PROMOTE}, {"siblings-follow", no_argument, NULL, OPT_SIBLINGS_FOLLOW}, {"repmgrd-no-pause", no_argument, NULL, OPT_REPMGRD_NO_PAUSE}, {"repmgrd-force-unpause", no_argument, NULL, OPT_REPMGRD_FORCE_UNPAUSE}, /* "node status" options */ {"is-shutdown-cleanly", no_argument, NULL, OPT_IS_SHUTDOWN_CLEANLY}, /* "node check" options */ {"archive-ready", no_argument, NULL, OPT_ARCHIVE_READY}, {"downstream", no_argument, NULL, OPT_DOWNSTREAM}, {"upstream", no_argument, NULL, OPT_UPSTREAM}, {"replication-lag", no_argument, NULL, OPT_REPLICATION_LAG}, {"role", no_argument, NULL, OPT_ROLE}, {"slots", no_argument, NULL, OPT_SLOTS}, {"missing-slots", no_argument, NULL, OPT_MISSING_SLOTS}, {"repmgrd", no_argument, NULL, OPT_REPMGRD}, {"has-passfile", no_argument, NULL, OPT_HAS_PASSFILE}, {"replication-connection", no_argument, NULL, OPT_REPL_CONN}, {"data-directory-config", no_argument, NULL, OPT_DATA_DIRECTORY_CONFIG}, {"replication-config-owner", no_argument, NULL, OPT_REPLICATION_CONFIG_OWNER}, {"db-connection", no_argument, NULL, OPT_DB_CONNECTION}, /* "node rejoin" options */ {"config-files", required_argument, NULL, OPT_CONFIG_FILES}, {"config-archive-dir", required_argument, NULL, OPT_CONFIG_ARCHIVE_DIR}, {"force-rewind", optional_argument, NULL, OPT_FORCE_REWIND}, /* "node service" options */ {"action", required_argument, NULL, OPT_ACTION}, {"list-actions", no_argument, NULL, OPT_LIST_ACTIONS}, {"checkpoint", no_argument, NULL, OPT_CHECKPOINT}, /* "cluster event" options */ {"all", no_argument, NULL, OPT_ALL}, {"event", required_argument, NULL, OPT_EVENT}, {"limit", required_argument, NULL, OPT_LIMIT}, /* "cluster cleanup" options */ {"keep-history", required_argument, NULL, 'k'}, /* undocumented options for testing */ {"disable-wal-receiver", no_argument, NULL, OPT_DISABLE_WAL_RECEIVER}, {"enable-wal-receiver", no_argument, NULL, OPT_ENABLE_WAL_RECEIVER}, /* deprecated */ {"check-upstream-config", no_argument, NULL, OPT_CHECK_UPSTREAM_CONFIG}, /* previously used by "standby switchover" */ {"remote-config-file", required_argument, NULL, 'C'}, {NULL, 0, NULL, 0} }; static void do_help(void); static const char *action_name(const int action); static void check_cli_parameters(const int action); #endif /* _REPMGR_CLIENT_H_ */ repmgr-5.3.1/repmgr.c000066400000000000000000000362051420262710000144540ustar00rootroot00000000000000/* * repmgr.c - repmgr extension * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * This is the actual extension code; see repmgr-client.c for the code which * generates the repmgr binary * * 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 . */ #include "postgres.h" #include "fmgr.h" #include "access/xlog.h" #include "miscadmin.h" #include "replication/walreceiver.h" #include "storage/fd.h" #include "storage/ipc.h" #include "storage/lwlock.h" #include "storage/procarray.h" #include "storage/shmem.h" #include "storage/spin.h" #include "utils/builtins.h" #include "utils/pg_lsn.h" #include "utils/timestamp.h" #include "lib/stringinfo.h" #include "access/xact.h" #include "utils/snapmgr.h" #include "pgstat.h" #include "voting.h" #define UNKNOWN_NODE_ID -1 #define ELECTION_RERUN_NOTIFICATION -2 #define UNKNOWN_PID -1 #define TRANCHE_NAME "repmgrd" #define REPMGRD_STATE_FILE PGSTAT_STAT_PERMANENT_DIRECTORY "/repmgrd_state.txt" #define REPMGRD_STATE_FILE_BUF_SIZE 128 PG_MODULE_MAGIC; typedef enum { LEADER_NODE, FOLLOWER_NODE, CANDIDATE_NODE } NodeState; typedef struct repmgrdSharedState { LWLockId lock; /* protects search/modification */ TimestampTz last_updated; int local_node_id; int repmgrd_pid; char repmgrd_pidfile[MAXPGPATH]; bool repmgrd_paused; /* streaming failover */ int upstream_node_id; TimestampTz upstream_last_seen; NodeVotingStatus voting_status; int current_electoral_term; int candidate_node_id; bool follow_new_primary; } repmgrdSharedState; static repmgrdSharedState *shared_state = NULL; static shmem_startup_hook_type prev_shmem_startup_hook = NULL; void _PG_init(void); void _PG_fini(void); static void repmgr_shmem_startup(void); PG_FUNCTION_INFO_V1(repmgr_set_local_node_id); PG_FUNCTION_INFO_V1(repmgr_get_local_node_id); PG_FUNCTION_INFO_V1(repmgr_standby_set_last_updated); PG_FUNCTION_INFO_V1(repmgr_standby_get_last_updated); PG_FUNCTION_INFO_V1(repmgr_set_upstream_last_seen); PG_FUNCTION_INFO_V1(repmgr_get_upstream_last_seen); PG_FUNCTION_INFO_V1(repmgr_get_upstream_node_id); PG_FUNCTION_INFO_V1(repmgr_set_upstream_node_id); PG_FUNCTION_INFO_V1(repmgr_notify_follow_primary); PG_FUNCTION_INFO_V1(repmgr_get_new_primary); PG_FUNCTION_INFO_V1(repmgr_reset_voting_status); PG_FUNCTION_INFO_V1(set_repmgrd_pid); PG_FUNCTION_INFO_V1(get_repmgrd_pid); PG_FUNCTION_INFO_V1(get_repmgrd_pidfile); PG_FUNCTION_INFO_V1(repmgrd_is_running); PG_FUNCTION_INFO_V1(repmgrd_pause); PG_FUNCTION_INFO_V1(repmgrd_is_paused); PG_FUNCTION_INFO_V1(repmgr_get_wal_receiver_pid); /* * Module load callback */ void _PG_init(void) { if (!process_shared_preload_libraries_in_progress) return; RequestAddinShmemSpace(MAXALIGN(sizeof(repmgrdSharedState))); #if (PG_VERSION_NUM >= 90600) RequestNamedLWLockTranche(TRANCHE_NAME, 1); #else RequestAddinLWLocks(1); #endif /* * Install hooks. */ prev_shmem_startup_hook = shmem_startup_hook; shmem_startup_hook = repmgr_shmem_startup; } /* * Module unload callback */ void _PG_fini(void) { /* Uninstall hook */ shmem_startup_hook = prev_shmem_startup_hook; } /* * shmem_startup hook: allocate or attach to shared memory, */ static void repmgr_shmem_startup(void) { bool found; if (prev_shmem_startup_hook) prev_shmem_startup_hook(); /* reset in case this is a restart within the postmaster */ shared_state = NULL; /* * Create or attach to the shared memory state */ LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); shared_state = ShmemInitStruct("repmgrd shared state", sizeof(repmgrdSharedState), &found); if (!found) { /* Initialise shared memory struct */ #if (PG_VERSION_NUM >= 90600) shared_state->lock = &(GetNamedLWLockTranche(TRANCHE_NAME))->lock; #else shared_state->lock = LWLockAssign(); #endif shared_state->local_node_id = UNKNOWN_NODE_ID; shared_state->repmgrd_pid = UNKNOWN_PID; memset(shared_state->repmgrd_pidfile, 0, MAXPGPATH); shared_state->repmgrd_paused = false; shared_state->current_electoral_term = 0; shared_state->upstream_node_id = UNKNOWN_NODE_ID; /* arbitrary "magic" date to indicate this field hasn't been updated */ shared_state->upstream_last_seen = POSTGRES_EPOCH_JDATE; shared_state->voting_status = VS_NO_VOTE; shared_state->candidate_node_id = UNKNOWN_NODE_ID; shared_state->follow_new_primary = false; } LWLockRelease(AddinShmemInitLock); } /* ==================== */ /* monitoring functions */ /* ==================== */ Datum repmgr_set_local_node_id(PG_FUNCTION_ARGS) { int local_node_id = UNKNOWN_NODE_ID; int stored_node_id = UNKNOWN_NODE_ID; int paused = -1; if (!shared_state) PG_RETURN_NULL(); if (PG_ARGISNULL(0)) PG_RETURN_NULL(); local_node_id = PG_GETARG_INT32(0); /* read state file and if exists/valid, update "repmgrd_paused" */ { FILE *file = NULL; file = AllocateFile(REPMGRD_STATE_FILE, PG_BINARY_R); if (file != NULL) { int buffer_size = REPMGRD_STATE_FILE_BUF_SIZE; char buffer[REPMGRD_STATE_FILE_BUF_SIZE]; if (fgets(buffer, buffer_size, file) != NULL) { if (sscanf(buffer, "%i:%i", &stored_node_id, &paused) != 2) { elog(WARNING, "unable to parse repmgrd state file"); } else { elog(DEBUG1, "node_id: %i; paused: %i", stored_node_id, paused); } } FreeFile(file); } } LWLockAcquire(shared_state->lock, LW_EXCLUSIVE); /* only set local_node_id once, as it should never change */ if (shared_state->local_node_id == UNKNOWN_NODE_ID) { shared_state->local_node_id = local_node_id; } /* only update if state file valid */ if (stored_node_id == shared_state->local_node_id) { if (paused == 0) { shared_state->repmgrd_paused = false; } else if (paused == 1) { shared_state->repmgrd_paused = true; } } LWLockRelease(shared_state->lock); PG_RETURN_VOID(); } Datum repmgr_get_local_node_id(PG_FUNCTION_ARGS) { int local_node_id = UNKNOWN_NODE_ID; if (!shared_state) PG_RETURN_NULL(); LWLockAcquire(shared_state->lock, LW_SHARED); local_node_id = shared_state->local_node_id; LWLockRelease(shared_state->lock); PG_RETURN_INT32(local_node_id); } /* update and return last updated with current timestamp */ Datum repmgr_standby_set_last_updated(PG_FUNCTION_ARGS) { TimestampTz last_updated = GetCurrentTimestamp(); if (!shared_state) PG_RETURN_NULL(); LWLockAcquire(shared_state->lock, LW_EXCLUSIVE); shared_state->last_updated = last_updated; LWLockRelease(shared_state->lock); PG_RETURN_TIMESTAMPTZ(last_updated); } /* get last updated timestamp */ Datum repmgr_standby_get_last_updated(PG_FUNCTION_ARGS) { TimestampTz last_updated; /* Safety check... */ if (!shared_state) PG_RETURN_NULL(); LWLockAcquire(shared_state->lock, LW_SHARED); last_updated = shared_state->last_updated; LWLockRelease(shared_state->lock); PG_RETURN_TIMESTAMPTZ(last_updated); } Datum repmgr_set_upstream_last_seen(PG_FUNCTION_ARGS) { int upstream_node_id = UNKNOWN_NODE_ID; if (!shared_state) PG_RETURN_VOID(); if (PG_ARGISNULL(0)) PG_RETURN_NULL(); upstream_node_id = PG_GETARG_INT32(0); LWLockAcquire(shared_state->lock, LW_EXCLUSIVE); shared_state->upstream_last_seen = GetCurrentTimestamp(); shared_state->upstream_node_id = upstream_node_id; LWLockRelease(shared_state->lock); PG_RETURN_VOID(); } Datum repmgr_get_upstream_last_seen(PG_FUNCTION_ARGS) { long secs; int microsecs; TimestampTz last_seen; if (!shared_state) PG_RETURN_INT32(-1); LWLockAcquire(shared_state->lock, LW_SHARED); last_seen = shared_state->upstream_last_seen; LWLockRelease(shared_state->lock); /* * "last_seen" is initialised with the PostgreSQL epoch as a * "magic" value to indicate the field hasn't ever been updated * by repmgrd. We return -1 instead, rather than imply that the * primary was last seen at the turn of the century. */ if (last_seen == POSTGRES_EPOCH_JDATE) PG_RETURN_INT32(-1); TimestampDifference(last_seen, GetCurrentTimestamp(), &secs, µsecs); /* let's hope repmgrd never runs for more than a century or so without seeing a primary */ PG_RETURN_INT32((uint32)secs); } Datum repmgr_get_upstream_node_id(PG_FUNCTION_ARGS) { int upstream_node_id = UNKNOWN_NODE_ID; if (!shared_state) PG_RETURN_NULL(); LWLockAcquire(shared_state->lock, LW_SHARED); upstream_node_id = shared_state->upstream_node_id; LWLockRelease(shared_state->lock); PG_RETURN_INT32(upstream_node_id); } Datum repmgr_set_upstream_node_id(PG_FUNCTION_ARGS) { int upstream_node_id = UNKNOWN_NODE_ID; int local_node_id = UNKNOWN_NODE_ID; if (!shared_state) PG_RETURN_NULL(); if (PG_ARGISNULL(0)) PG_RETURN_NULL(); upstream_node_id = PG_GETARG_INT32(0); LWLockAcquire(shared_state->lock, LW_SHARED); local_node_id = shared_state->local_node_id; LWLockRelease(shared_state->lock); if (local_node_id == upstream_node_id) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errmsg("upstream node id cannot be the same as the local node id")))); LWLockAcquire(shared_state->lock, LW_EXCLUSIVE); shared_state->upstream_node_id = upstream_node_id; LWLockRelease(shared_state->lock); PG_RETURN_VOID(); } /* ===================*/ /* failover functions */ /* ===================*/ Datum repmgr_notify_follow_primary(PG_FUNCTION_ARGS) { int primary_node_id = UNKNOWN_NODE_ID; if (!shared_state) PG_RETURN_VOID(); if (PG_ARGISNULL(0)) PG_RETURN_VOID(); primary_node_id = PG_GETARG_INT32(0); LWLockAcquire(shared_state->lock, LW_SHARED); /* only do something if local_node_id is initialised */ if (shared_state->local_node_id != UNKNOWN_NODE_ID) { if (primary_node_id == ELECTION_RERUN_NOTIFICATION) { elog(INFO, "node %i received notification to rerun promotion candidate election", shared_state->local_node_id); } else { elog(INFO, "node %i received notification to follow node %i", shared_state->local_node_id, primary_node_id); } LWLockRelease(shared_state->lock); LWLockAcquire(shared_state->lock, LW_EXCLUSIVE); /* Explicitly set the primary node id */ shared_state->candidate_node_id = primary_node_id; shared_state->follow_new_primary = true; } LWLockRelease(shared_state->lock); PG_RETURN_VOID(); } Datum repmgr_get_new_primary(PG_FUNCTION_ARGS) { int new_primary_node_id = UNKNOWN_NODE_ID; if (!shared_state) PG_RETURN_INT32(UNKNOWN_NODE_ID); LWLockAcquire(shared_state->lock, LW_SHARED); if (shared_state->follow_new_primary == true) new_primary_node_id = shared_state->candidate_node_id; LWLockRelease(shared_state->lock); if (new_primary_node_id == UNKNOWN_NODE_ID) PG_RETURN_INT32(UNKNOWN_NODE_ID); PG_RETURN_INT32(new_primary_node_id); } Datum repmgr_reset_voting_status(PG_FUNCTION_ARGS) { if (!shared_state) PG_RETURN_NULL(); LWLockAcquire(shared_state->lock, LW_SHARED); /* only do something if local_node_id is initialised */ if (shared_state->local_node_id != UNKNOWN_NODE_ID) { LWLockRelease(shared_state->lock); LWLockAcquire(shared_state->lock, LW_EXCLUSIVE); shared_state->voting_status = VS_NO_VOTE; shared_state->candidate_node_id = UNKNOWN_NODE_ID; shared_state->follow_new_primary = false; } LWLockRelease(shared_state->lock); PG_RETURN_VOID(); } /* * Returns the repmgrd pid; or NULL if none set; or -1 if set but repmgrd * process not running (TODO!) */ Datum get_repmgrd_pid(PG_FUNCTION_ARGS) { int repmgrd_pid = UNKNOWN_PID; if (!shared_state) PG_RETURN_NULL(); LWLockAcquire(shared_state->lock, LW_SHARED); repmgrd_pid = shared_state->repmgrd_pid; LWLockRelease(shared_state->lock); PG_RETURN_INT32(repmgrd_pid); } /* * Returns the repmgrd pidfile */ Datum get_repmgrd_pidfile(PG_FUNCTION_ARGS) { char repmgrd_pidfile[MAXPGPATH]; if (!shared_state) PG_RETURN_NULL(); memset(repmgrd_pidfile, 0, MAXPGPATH); LWLockAcquire(shared_state->lock, LW_SHARED); strncpy(repmgrd_pidfile, shared_state->repmgrd_pidfile, MAXPGPATH); LWLockRelease(shared_state->lock); if (repmgrd_pidfile[0] == '\0') PG_RETURN_NULL(); PG_RETURN_TEXT_P(cstring_to_text(repmgrd_pidfile)); } Datum set_repmgrd_pid(PG_FUNCTION_ARGS) { int repmgrd_pid = UNKNOWN_PID; char *repmgrd_pidfile = NULL; if (!shared_state) PG_RETURN_VOID(); if (PG_ARGISNULL(0)) { repmgrd_pid = UNKNOWN_PID; } else { repmgrd_pid = PG_GETARG_INT32(0); } elog(DEBUG3, "set_repmgrd_pid(): provided pid is %i", repmgrd_pid); if (repmgrd_pid != UNKNOWN_PID && !PG_ARGISNULL(1)) { repmgrd_pidfile = text_to_cstring(PG_GETARG_TEXT_PP(1)); elog(INFO, "set_repmgrd_pid(): provided pidfile is %s", repmgrd_pidfile); } LWLockAcquire(shared_state->lock, LW_EXCLUSIVE); shared_state->repmgrd_pid = repmgrd_pid; memset(shared_state->repmgrd_pidfile, 0, MAXPGPATH); if (repmgrd_pidfile != NULL) { strncpy(shared_state->repmgrd_pidfile, repmgrd_pidfile, MAXPGPATH); } LWLockRelease(shared_state->lock); PG_RETURN_VOID(); } Datum repmgrd_is_running(PG_FUNCTION_ARGS) { int repmgrd_pid = UNKNOWN_PID; int kill_ret; if (!shared_state) PG_RETURN_NULL(); LWLockAcquire(shared_state->lock, LW_SHARED); repmgrd_pid = shared_state->repmgrd_pid; LWLockRelease(shared_state->lock); /* No PID registered - assume not running */ if (repmgrd_pid == UNKNOWN_PID) { PG_RETURN_BOOL(false); } kill_ret = kill(repmgrd_pid, 0); if (kill_ret == 0) { PG_RETURN_BOOL(true); } PG_RETURN_BOOL(false); } Datum repmgrd_pause(PG_FUNCTION_ARGS) { bool pause; FILE *file = NULL; StringInfoData buf; if (!shared_state) PG_RETURN_NULL(); if (PG_ARGISNULL(0)) PG_RETURN_NULL(); pause = PG_GETARG_BOOL(0); LWLockAcquire(shared_state->lock, LW_EXCLUSIVE); shared_state->repmgrd_paused = pause; LWLockRelease(shared_state->lock); /* write state to file */ file = AllocateFile(REPMGRD_STATE_FILE, PG_BINARY_W); if (file == NULL) { elog(WARNING, "unable to allocate %s", REPMGRD_STATE_FILE); PG_RETURN_VOID(); } elog(DEBUG1, "allocated"); initStringInfo(&buf); LWLockAcquire(shared_state->lock, LW_SHARED); appendStringInfo(&buf, "%i:%i", shared_state->local_node_id, pause ? 1 : 0); LWLockRelease(shared_state->lock); if (fwrite(buf.data, strlen(buf.data) + 1, 1, file) != 1) { elog(WARNING, _("unable to write to file %s"), REPMGRD_STATE_FILE); } pfree(buf.data); FreeFile(file); PG_RETURN_VOID(); } Datum repmgrd_is_paused(PG_FUNCTION_ARGS) { bool is_paused; if (!shared_state) PG_RETURN_NULL(); LWLockAcquire(shared_state->lock, LW_SHARED); is_paused = shared_state->repmgrd_paused; LWLockRelease(shared_state->lock); PG_RETURN_BOOL(is_paused); } Datum repmgr_get_wal_receiver_pid(PG_FUNCTION_ARGS) { int wal_receiver_pid; if (!shared_state) PG_RETURN_NULL(); wal_receiver_pid = WalRcv->pid; PG_RETURN_INT32(wal_receiver_pid); } repmgr-5.3.1/repmgr.conf.sample000066400000000000000000000511211420262710000164310ustar00rootroot00000000000000################################################### # repmgr sample configuration file ################################################### # Some configuration items will be set with a default value; this # is noted for each item. Where no default value is shown, the # parameter will be treated as empty or false. # # repmgr parses its configuration file in the same way as PostgreSQL itself # does. In particular, strings must be enclosed in single quotes (although # simple identifiers may be provided as-is). # # For details on the configuration file format see the documentation at: # # https://repmgr.org/docs/current/configuration-file.html#CONFIGURATION-FILE-FORMAT # # ============================================================================= # Required configuration items # ============================================================================= # # repmgr and repmgrd require the following items to be explicitly configured. #node_id= # A unique integer greater than zero #node_name='' # An arbitrary (but unique) string; we recommend # using the server's hostname or another identifier # unambiguously associated with the server to avoid # confusion. Avoid choosing names which reflect the # node's current role, e.g. 'primary' or 'standby1', # as roles can change and it will be confusing if # the current primary is called 'standby1'. # The string's maximum length is 63 characters and it should # contain only printable ASCII characters. #conninfo='' # Database connection information as a conninfo string. # All servers in the cluster must be able to connect to # the local node using this string. # # For details on conninfo strings, see: # https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING # # If repmgrd is in use, consider explicitly setting # "connect_timeout" in the conninfo string to determine # the length of time which elapses before a network # connection attempt is abandoned; for details see: # https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-CONNECT-TIMEOUT #data_directory='' # The node's data directory. This is needed by repmgr # when performing operations when the PostgreSQL instance # is not running and there's no other way of determining # the data directory. # ============================================================================= # Optional configuration items # ============================================================================= #------------------------------------------------------------------------------ # Server settings #------------------------------------------------------------------------------ #config_directory='' # If configuration files are located outside the data # directory, specify the directory where the main # postgresql.conf file is located. #------------------------------------------------------------------------------ # Replication settings #------------------------------------------------------------------------------ #replication_user='repmgr' # User to make replication connections with, if not set # defaults to the user defined in "conninfo". #replication_type='physical' # Must "physical" (the default). #location='default' # An arbitrary string defining the location of the node; this # is used during failover to check visibility of the # current primary node. For further details see: # https://repmgr.org/docs/current/repmgrd-network-split.html #use_replication_slots=no # whether to use physical replication slots # NOTE: when using replication slots, # 'max_replication_slots' should be configured for # at least the number of standbys which will connect # to the primary. #------------------------------------------------------------------------------ # Witness server settings #------------------------------------------------------------------------------ #witness_sync_interval=15 # interval (in seconds) to synchronise node records # to the witness server #------------------------------------------------------------------------------ # Logging settings #------------------------------------------------------------------------------ # # 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`. #log_level='INFO' # Log level: possible values are DEBUG, INFO, NOTICE, # WARNING, ERROR, ALERT, CRIT or EMERG #log_facility='STDERR' # Logging facility: possible values are STDERR, or for # syslog integration, one of LOCAL0, LOCAL1, ..., LOCAL7, USER #log_file='' # STDERR can be redirected to an arbitrary file #log_status_interval=300 # interval (in seconds) for repmgrd to log a status message #------------------------------------------------------------------------------ # Event notification settings #------------------------------------------------------------------------------ # event notifications can be passed to an arbitrary external program # together with the following parameters: # # %n - node ID # %e - event type # %s - success (1 or 0) # %t - timestamp # %d - details # # the values provided for "%t" and "%d" will probably contain spaces, # so should be quoted in the provided command configuration, e.g.: # # event_notification_command='/path/to/some/script %n %e %s "%t" "%d"' # # By default, all notifications will be passed; the notification types # can be filtered to explicitly named ones, e.g.: # # event_notifications=primary_register,standby_register #event_notification_command='' # An external program or script which # can be executed by the user under which # repmgr/repmgrd are run. #event_notifications='' # A commas-separated list of notification # types #------------------------------------------------------------------------------ # Environment/command settings #------------------------------------------------------------------------------ #pg_bindir='' # Path to PostgreSQL binary directory (location # of pg_ctl, pg_basebackup etc.). Only needed # if these files are not in the system $PATH. # # Debian/Ubuntu users: you will probably need to # set this to the directory where `pg_ctl` is located, # e.g. /usr/lib/postgresql/9.6/bin/ # # *NOTE* "pg_bindir" is only used when repmgr directly # executes PostgreSQL binaries; any user-defined scripts # *must* be specified with the full path #repmgr_bindir='' # Path to repmgr binary directory (location of the repmgr # binary. Only needed if the repmgr executable is not in # the system $PATH or the path defined in "pg_bindir". #use_primary_conninfo_password=false # explicitly set "password" in "primary_conninfo" # using the value contained in the environment variable # PGPASSWORD #passfile='' # path to .pgpass file to include in "primary_conninfo" #------------------------------------------------------------------------------ # external command options #------------------------------------------------------------------------------ # # Options which can be passed to external commands invoked by repmgr/repmgrd. # # Examples: # # pg_ctl_options='-s' # pg_basebackup_options='--label=repmgr_backup' # rsync_options=--archive --checksum --compress --progress --rsh="ssh -o \"StrictHostKeyChecking no\"" # ssh_options=-o "StrictHostKeyChecking no" #pg_ctl_options='' # Options to append to "pg_ctl" #pg_basebackup_options='' # Options to append to "pg_basebackup" # (Note: when cloning from Barman, repmgr will honour any # --waldir/--xlogdir setting present in "pg_basebackup_options" #rsync_options='' # Options to append to "rsync" ssh_options='-q -o ConnectTimeout=10' # Options to append to "ssh" #------------------------------------------------------------------------------ # "standby clone" settings #------------------------------------------------------------------------------ # # These settings apply when cloning a standby ("repmgr standby clone"). # # Examples: # # tablespace_mapping='/path/to/original/tablespace=/path/to/new/tablespace' # restore_command = 'cp /path/to/archived/wals/%f %p' #tablespace_mapping='' # Tablespaces can be remapped from one # file system location to another. This # parameter can be provided multiple times. #restore_command='' # This will be included in the recovery configuration # generated by repmgr. #archive_cleanup_command='' # This will be included in the recovery configuration # generated by repmgr. Note we recommend using Barman for # managing WAL archives (see: https://www.pgbarman.org ) #recovery_min_apply_delay= # If provided, "recovery_min_apply_delay" will be set to # this value (PostgreSQL 9.4 and later). Value can be # an integer representing milliseconds, or a string # representing a period of time (e.g. '5 min'). #------------------------------------------------------------------------------ # "standby promote" settings #------------------------------------------------------------------------------ # These settings apply when instructing a standby to promote itself to the # new primary ("repmgr standby promote"). #promote_check_timeout=60 # The length of time (in seconds) to wait # for the new primary to finish promoting #promote_check_interval=1 # The interval (in seconds) to check whether # the new primary has finished promoting #------------------------------------------------------------------------------ # "standby follow" settings #------------------------------------------------------------------------------ # These settings apply when instructing a standby to follow the new primary # ("repmgr standby follow"). #primary_follow_timeout=60 # The max length of time (in seconds) to wait # for the new primary to become available #standby_follow_timeout=30 # The max length of time (in seconds) to wait # for the standby to connect to the primary #standby_follow_restart=false # Restart the standby instead of sending a SIGHUP # (only for PostgreSQL 13 and later) #------------------------------------------------------------------------------ # "standby switchover" settings #------------------------------------------------------------------------------ # These settings apply when switching roles between a primary and a standby # ("repmgr standby switchover"). #shutdown_check_timeout=60 # The max length of time (in seconds) to wait for the demotion # candidate (current primary) to shut down #standby_reconnect_timeout=60 # The max length of time (in seconds) to wait # for the demoted standby to reconnect to the promoted # primary (note: this value should be equal to or greater # than that set for "node_rejoin_timeout") #wal_receive_check_timeout=30 # The max length of time (in seconds) to wait for the walreceiver # on the standby to flush WAL to disk before comparing location # with the shut-down primary #------------------------------------------------------------------------------ # "node rejoin" settings #------------------------------------------------------------------------------ # These settings apply when reintegrating a node into a replication cluster # with "repmgrd_node_rejoin" #node_rejoin_timeout=60 # The maximum length of time (in seconds) to wait for # the node to reconnect to the replication cluster #------------------------------------------------------------------------------ # Barman options #------------------------------------------------------------------------------ #barman_server='' # The barman configuration section #barman_host='' # The host name of the barman server #barman_config='' # The Barman configuration file on the # Barman server (needed if the file is # in a non-standard location) #------------------------------------------------------------------------------ # Failover and monitoring settings (repmgrd) #------------------------------------------------------------------------------ # # These settings are only applied when repmgrd is running. Values shown # are defaults. #failover='manual' # one of 'automatic', 'manual'. # determines what action to take in the event of upstream failure # # 'automatic': repmgrd will automatically attempt to promote the # node or follow the new upstream node # 'manual': repmgrd will take no action and the node will require # manual attention to reattach it to replication #priority=100 # indicates a preferred priority for promoting nodes; # a value of zero prevents the node being promoted to primary # (default: 100) #connection_check_type=ping # How to check availability of the upstream node; valid options: # 'ping': use PQping() to check if the node is accepting connections # 'connection': attempt to make a new connection to the node # 'query': execute an SQL statement on the node via the existing connection #reconnect_attempts=6 # Number of attempts which will be made to reconnect to an unreachable # primary (or other upstream node) #reconnect_interval=10 # Interval between attempts to reconnect to an unreachable # primary (or other upstream node) #promote_command='' # command repmgrd executes when promoting a new primary; use something like: # # repmgr standby promote -f /etc/repmgr.conf # #follow_command='' # command repmgrd executes when instructing a standby to follow a new primary; # use something like: # # repmgr standby follow -f /etc/repmgr.conf --upstream-node-id=%n # #primary_notification_timeout=60 # Interval (in seconds) which repmgrd on a standby # will wait for a notification from the new primary, # before falling back to degraded monitoring #repmgrd_standby_startup_timeout=60 # Interval (in seconds) which repmgrd on a standby will wait # for the the local node to restart and become ready to accept connections after # executing "follow_command" (defaults to the value set in "standby_reconnect_timeout") #monitoring_history=no # Whether to write monitoring data to the "monitoring_history" table #monitor_interval_secs=2 # Interval (in seconds) at which to write monitoring data #degraded_monitoring_timeout=-1 # Interval (in seconds) after which repmgrd will terminate if the # server(s) being monitored are no longer available. -1 (default) # disables the timeout completely. #async_query_timeout=60 # Interval (in seconds) which repmgrd will wait before # cancelling an asynchronous query. #repmgrd_pid_file= # Path of PID file to use for repmgrd; if not set, a PID file will # be generated in a temporary directory specified by the environment # variable $TMPDIR, or if not set, in "/tmp". This value can be overridden # by the command line option "-p/--pid-file"; the command line option # "--no-pid-file" will force PID file creation to be skipped. # Note: there is normally no need to set this, particularly if # repmgr was installed from packages. #repmgrd_exit_on_inactive_node=false # If "true", and the node record is marked as "inactive", abort repmgrd startup #standby_disconnect_on_failover=false # If "true", in a failover situation wait for all standbys to # disconnect their WAL receivers before electing a new primary # (PostgreSQL 9.5 and later only; repmgr user must be a superuser for this) #sibling_nodes_disconnect_timeout=30 # If "standby_disconnect_on_failover" is true, the maximum length of time # (in seconds) to wait for other standbys to confirm they have disconnected their # WAL receivers #primary_visibility_consensus=false # If "true", only continue with failover if no standbys have seen # the primary node recently. *Must* be the same on all nodes. #always_promote=false # Always promote a node, even if repmgr metadata is outdated #failover_validation_command='' # Script to execute for an external mechanism to validate the failover # decision made by repmgrd. One or both of the following parameter placeholders # should be provided, which will be replaced by repmgrd with the appropriate # value: %n (node_id), %a (node_name). *Must* be the same on all nodes. #election_rerun_interval=15 # if "failover_validation_command" is set, and the command returns # an error, pause the specified amount of seconds before rerunning the election. # # The following items are relevant for repmgrd running on the primary, # and will be ignored on non-primary nodes #child_nodes_check_interval=5 # Interval (in seconds) to check for attached child nodes (standbys) #child_nodes_connected_min_count=-1 # Minimum number of child nodes which must remain connected, otherwise # disconnection command will be triggered #child_nodes_disconnect_min_count=-1 # Minimum number of disconnected child nodes required to execute disconnection command # (ignored if "child_nodes_connected_min_count" set) #child_nodes_disconnect_timeout=30 # Interval between child node disconnection and disconnection command execution #child_nodes_disconnect_command='' # Command to execute if child node disconnection detected #------------------------------------------------------------------------------ # service control commands #------------------------------------------------------------------------------ # # repmgr provides options to override the default pg_ctl commands # used to stop, start, restart, reload and promote the PostgreSQL cluster # # These options are useful when PostgreSQL has been installed from a package # which provides OS-level service commands. In environments using an init system # such as systemd, which keeps track of the state of various services, it is # essential that the service commands are correctly configured and pg_ctl is # not executed directly. # # 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 can set # # service_start_command = 'sudo systemctl start postgresql-9.6' # (...) # # and then use the following sudoers configuration: # # # this is required when running sudo over ssh without -t: # Defaults:postgres !requiretty # postgres ALL = NOPASSWD: /usr/bin/systemctl stop postgresql-9.6, \ # /usr/bin/systemctl start postgresql-9.6, \ # /usr/bin/systemctl restart postgresql-9.6 # # Debian/Ubuntu users: use "sudo pg_ctlcluster" to execute service control commands. # # For more details, see: https://repmgr.org/docs/current/configuration-service-commands.html #service_start_command = '' #service_stop_command = '' #service_restart_command = '' #service_reload_command = '' #service_promote_command = '' # This parameter is intended for systems which provide a # package-level promote command, such as Debian's # "pg_ctlcluster". *IMPORTANT*: it is *not* a substitute # for "promote_command"; do not use "repmgr standby promote" # (or a script which executes "repmgr standby promote") here. # Used by "repmgr service (start|stop)" to control repmgrd # #repmgrd_service_start_command = '' #repmgrd_service_stop_command = '' #------------------------------------------------------------------------------ # Status check thresholds #------------------------------------------------------------------------------ # Various warning/critical thresholds used by "repmgr node check". #archive_ready_warning=16 # repmgr node check --archive-ready #archive_ready_critical=128 # # Numbers of files pending archiving via PostgreSQL's # "archive_command" configuration parameter. If # files can't be archived fast enough, or the archive # command is failing, the buildup of files can # cause various issues, such as server shutdown being # delayed until all files are archived, or excessive # space being occupied by unarchived files. # # Note that these values will be checked when executing # "repmgr standby switchover" to warn about potential # issues with shutting down the demotion candidate. #replication_lag_warning=300 # repmgr node check --replication-lag #replication_lag_critical=600 # # Note that these values will be checked when executing # "repmgr standby switchover" to warn about potential # issues with shutting down the demotion candidate. repmgr-5.3.1/repmgr.control000066400000000000000000000002421420262710000157020ustar00rootroot00000000000000# repmgr extension comment = 'Replication manager for PostgreSQL' default_version = '5.3' module_pathname = '$libdir/repmgr' relocatable = false schema = repmgr repmgr-5.3.1/repmgr.h000066400000000000000000000121741420262710000144600ustar00rootroot00000000000000/* * repmgr.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _REPMGR_CONFIG_H #define _REPMGR_CONFIG_H #include #endif #ifdef vsnprintf #undef vsnprintf #endif #ifdef snprintf #undef snprintf #endif #ifdef vsprintf #undef vsprintf #endif #ifdef sprintf #undef sprintf #endif #ifdef vfprintf #undef vfprintf #endif #ifdef fprintf #undef fprintf #endif #ifdef vprintf #undef vprintf #endif #ifdef printf #undef printf #endif #ifdef strerror #undef strerror #endif #ifdef strerror_r #undef strerror_r #endif #ifndef _REPMGR_H_ #define _REPMGR_H_ #include #include #include #include #include #include #include #include #include "repmgr_version.h" #include "errcode.h" #include "strutil.h" #include "configfile.h" #include "dbutils.h" #include "log.h" #include "sysutils.h" #define MIN_SUPPORTED_VERSION "9.4" #define MIN_SUPPORTED_VERSION_NUM 90400 #define UNKNOWN_SERVER_VERSION_NUM -1 #define UNKNOWN_REPMGR_VERSION_NUM -1 #define UNKNOWN_TIMELINE_ID -1 #define UNKNOWN_SYSTEM_IDENTIFIER 0 #define UNKNOWN_DATA_CHECKSUM_VERSION -1 #define UNKNOWN_PID -1 #define UNKNOWN_REPLICATION_LAG -1 #define UNKNOWN_VALUE -1 #define NODE_NOT_FOUND -1 #define NO_UPSTREAM_NODE -1 #define UNKNOWN_NODE_ID -1 #define MIN_NODE_ID 1 #define ELECTION_RERUN_NOTIFICATION -2 #define VOTING_TERM_NOT_SET -1 #define ARCHIVE_STATUS_DIR_ERROR -1 #define NO_DEGRADED_MONITORING_ELAPSED -1 #define WALRECEIVER_DISABLE_TIMEOUT_VALUE 86400000 /* milliseconds */ /* * Default command line option parameter values */ #define DEFAULT_WAIT_START 30 /* seconds */ /* * Default configuration file parameter values - ensure repmgr.conf.sample * is update if any of these are changed */ #define DEFAULT_USE_REPLICATION_SLOTS false #define DEFAULT_USE_PRIMARY_CONNINFO_PASSWORD false #define DEFAULT_PROMOTE_CHECK_TIMEOUT 60 /* seconds */ #define DEFAULT_PROMOTE_CHECK_INTERVAL 1 /* seconds */ #define DEFAULT_PRIMARY_FOLLOW_TIMEOUT 60 /* seconds */ #define DEFAULT_STANDBY_FOLLOW_TIMEOUT 30 /* seconds */ #define DEFAULT_STANDBY_FOLLOW_RESTART false #define DEFAULT_SHUTDOWN_CHECK_TIMEOUT 60 /* seconds */ #define DEFAULT_STANDBY_RECONNECT_TIMEOUT 60 /* seconds */ #define DEFAULT_NODE_REJOIN_TIMEOUT 60 /* seconds */ #define DEFAULT_ARCHIVE_READY_WARNING 16 /* WAL files */ #define DEFAULT_ARCHIVE_READY_CRITICAL 128 /* WAL files */ #define DEFAULT_REPLICATION_TYPE REPLICATION_TYPE_PHYSICAL #define DEFAULT_REPLICATION_LAG_WARNING 300 /* seconds */ #define DEFAULT_REPLICATION_LAG_CRITICAL 600 /* seconds */ #define DEFAULT_WITNESS_SYNC_INTERVAL 15 /* seconds */ #define DEFAULT_WAL_RECEIVE_CHECK_TIMEOUT 30 /* seconds */ #define DEFAULT_LOCATION "default" #define DEFAULT_PRIORITY 100 #define DEFAULT_MONITORING_INTERVAL 2 /* seconds */ #define DEFAULT_RECONNECTION_ATTEMPTS 6 /* seconds */ #define DEFAULT_RECONNECTION_INTERVAL 10 /* seconds */ #define DEFAULT_MONITORING_HISTORY false #define DEFAULT_DEGRADED_MONITORING_TIMEOUT -1 /* seconds */ #define DEFAULT_ASYNC_QUERY_TIMEOUT 60 /* seconds */ #define DEFAULT_PRIMARY_NOTIFICATION_TIMEOUT 60 /* seconds */ #define DEFAULT_REPMGRD_STANDBY_STARTUP_TIMEOUT -1 /*seconds */ #define DEFAULT_REPMGRD_EXIT_ON_INACTIVE_NODE false, #define DEFAULT_STANDBY_DISCONNECT_ON_FAILOVER false #define DEFAULT_SIBLING_NODES_DISCONNECT_TIMEOUT 30 /* seconds */ #define DEFAULT_CONNECTION_CHECK_TYPE CHECK_PING #define DEFAULT_PRIMARY_VISIBILITY_CONSENSUS false #define DEFAULT_ALWAYS_PROMOTE false #define DEFAULT_ELECTION_RERUN_INTERVAL 15 /* seconds */ #define DEFAULT_CHILD_NODES_CHECK_INTERVAL 5 /* seconds */ #define DEFAULT_CHILD_NODES_DISCONNECT_MIN_COUNT -1 #define DEFAULT_CHILD_NODES_CONNECTED_MIN_COUNT -1 #define DEFAULT_CHILD_NODES_CONNECTED_INCLUDE_WITNESS false #define DEFAULT_CHILD_NODES_DISCONNECT_TIMEOUT 30 /* seconds */ #define DEFAULT_SSH_OPTIONS "-q -o ConnectTimeout=10" #ifndef RECOVERY_COMMAND_FILE #define RECOVERY_COMMAND_FILE "recovery.conf" #endif #ifndef STANDBY_SIGNAL_FILE #define STANDBY_SIGNAL_FILE "standby.signal" #define RECOVERY_SIGNAL_FILE "recovery.signal" #endif #ifndef TABLESPACE_MAP #define TABLESPACE_MAP "tablespace_map" #endif #define REPMGR_URL "https://repmgr.org/" #endif /* _REPMGR_H_ */ repmgr-5.3.1/repmgr_version.h.in000066400000000000000000000003611420262710000166250ustar00rootroot00000000000000#define REPMGR_VERSION_DATE "" #define REPMGR_VERSION "5.3.1" #define REPMGR_VERSION_NUM 50301 #define REPMGR_EXTENSION_VERSION "5.3" #define REPMGR_EXTENSION_NUM 50300 #define REPMGR_RELEASE_DATE "2022-02-15" #define PG_ACTUAL_VERSION_NUM repmgr-5.3.1/repmgrd-physical.c000066400000000000000000004766161420262710000164500ustar00rootroot00000000000000/* * repmgrd-physical.c - physical (streaming) replication functionality for repmgrd * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #include #include "repmgr.h" #include "repmgrd.h" #include "repmgrd-physical.h" typedef enum { FAILOVER_STATE_UNKNOWN = -1, FAILOVER_STATE_NONE, FAILOVER_STATE_PROMOTED, FAILOVER_STATE_PROMOTION_FAILED, FAILOVER_STATE_PRIMARY_REAPPEARED, FAILOVER_STATE_LOCAL_NODE_FAILURE, FAILOVER_STATE_WAITING_NEW_PRIMARY, FAILOVER_STATE_FOLLOW_NEW_PRIMARY, FAILOVER_STATE_REQUIRES_MANUAL_FAILOVER, FAILOVER_STATE_FOLLOWED_NEW_PRIMARY, FAILOVER_STATE_FOLLOWING_ORIGINAL_PRIMARY, FAILOVER_STATE_NO_NEW_PRIMARY, FAILOVER_STATE_FOLLOW_FAIL, FAILOVER_STATE_NODE_NOTIFICATION_ERROR, FAILOVER_STATE_ELECTION_RERUN } FailoverState; typedef enum { ELECTION_NOT_CANDIDATE = -1, ELECTION_WON, ELECTION_LOST, ELECTION_CANCELLED, ELECTION_RERUN } ElectionResult; typedef struct election_stats { int visible_nodes; int shared_upstream_nodes; int all_nodes; } election_stats; typedef struct t_child_node_info { int node_id; char node_name[NAMEDATALEN]; t_server_type type; NodeAttached attached; instr_time detached_time; struct t_child_node_info *next; } t_child_node_info; typedef struct t_child_node_info_list { t_child_node_info *head; t_child_node_info *tail; int node_count; } t_child_node_info_list; #define T_CHILD_NODE_INFO_LIST_INITIALIZER { \ NULL, \ NULL, \ 0 \ } static PGconn *upstream_conn = NULL; static PGconn *primary_conn = NULL; static FailoverState failover_state = FAILOVER_STATE_UNKNOWN; static int primary_node_id = UNKNOWN_NODE_ID; static t_node_info upstream_node_info = T_NODE_INFO_INITIALIZER; static instr_time last_monitoring_update; static bool child_nodes_disconnect_command_executed = false; static ElectionResult do_election(NodeInfoList *sibling_nodes, int *new_primary_id); static const char *_print_election_result(ElectionResult result); static FailoverState promote_self(void); static void notify_followers(NodeInfoList *standby_nodes, int follow_node_id); static void check_connection(t_node_info *node_info, PGconn **conn); static bool check_primary_status(int degraded_monitoring_elapsed); static void check_primary_child_nodes(t_child_node_info_list *local_child_nodes); static bool wait_primary_notification(int *new_primary_id); static FailoverState follow_new_primary(int new_primary_id); static FailoverState witness_follow_new_primary(int new_primary_id); static void reset_node_voting_status(void); static bool do_primary_failover(void); static bool do_upstream_standby_failover(void); static bool do_witness_failover(void); static bool update_monitoring_history(void); static void handle_sighup(PGconn **conn, t_server_type server_type); static const char *format_failover_state(FailoverState failover_state); static ElectionResult execute_failover_validation_command(t_node_info *node_info, election_stats *stats); static void parse_failover_validation_command(const char *template, t_node_info *node_info, election_stats *stats, PQExpBufferData *out); static bool check_node_can_follow(PGconn *local_conn, XLogRecPtr local_xlogpos, PGconn *follow_target_conn, t_node_info *follow_target_node_info); static void check_witness_attached(t_node_info *node_info, bool startup); static t_child_node_info *append_child_node_record(t_child_node_info_list *nodes, int node_id, const char *node_name, t_server_type type, NodeAttached attached); static void remove_child_node_record(t_child_node_info_list *nodes, int node_id); static void clear_child_node_info_list(t_child_node_info_list *nodes); static void parse_child_nodes_disconnect_command(char *parsed_command, char *template, int reporting_node_id); static void execute_child_nodes_disconnect_command(NodeInfoList *db_child_node_records, t_child_node_info_list *local_child_nodes); static int try_primary_reconnect(PGconn **conn, PGconn *local_conn, t_node_info *node_info); void handle_sigint_physical(SIGNAL_ARGS) { PGconn *writeable_conn; PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("%s signal received"), postgres_signal_arg == SIGTERM ? "TERM" : "INT"); log_notice("%s", event_details.data); if (local_node_info.type == PRIMARY) writeable_conn = local_conn; else writeable_conn = primary_conn; if (PQstatus(writeable_conn) == CONNECTION_OK) create_event_notification(writeable_conn, &config_file_options, config_file_options.node_id, "repmgrd_shutdown", true, event_details.data); termPQExpBuffer(&event_details); terminate(SUCCESS); } /* perform some sanity checks on the node's configuration */ void do_physical_node_check(PGconn *conn) { /* * If node record is "inactive"; if not, attempt to set it to "active". * * Usually it will have become inactive due to e.g. a standby being shut down * while repmgrd was running in an unpaused state. In this case it's * perfectly reasonable to automatically mark the node as "active". */ if (local_node_info.active == false) { char *hint = "Check that \"repmgr (primary|standby) register\" was executed for this node"; RecoveryType recovery_type = get_recovery_type(conn); /* * If the local node's recovery status is incompatible with its registered * status, e.g. registered as primary but running as a standby, refuse to start. * * This typically happens when a failed primary is recloned but the node was not * re-registered, leaving the cluster in a potentially ambiguous state. In * this case it would not be possible or desirable to attempt to set the * node to active; the user should ensure the cluster is in the correct state. */ if (recovery_type != RECTYPE_UNKNOWN && local_node_info.type != UNKNOWN) { bool require_reregister = false; PQExpBufferData event_details; initPQExpBuffer(&event_details); if (recovery_type == RECTYPE_STANDBY && local_node_info.type != STANDBY) { appendPQExpBuffer(&event_details, _("node is registered as a %s but running as a standby"), get_node_type_string(local_node_info.type)); require_reregister = true; } else if (recovery_type == RECTYPE_PRIMARY && local_node_info.type == STANDBY) { log_error(_("node is registered as a standby but running as a %s"), get_node_type_string(local_node_info.type)); require_reregister = true; } if (require_reregister == true) { log_error("%s", event_details.data); log_hint(_("%s"), hint); create_event_notification(NULL, &config_file_options, config_file_options.node_id, "repmgrd_start", false, event_details.data); termPQExpBuffer(&event_details); terminate(ERR_BAD_CONFIG); } termPQExpBuffer(&event_details); } /* * Attempt to set node record active (unless explicitly configured not to) */ log_notice(_("setting node record for node \"%s\" (ID: %i) to \"active\""), local_node_info.node_name, local_node_info.node_id); if (config_file_options.repmgrd_exit_on_inactive_node == false) { PGconn *primary_conn = get_primary_connection(conn, NULL, NULL); bool success = true; if (PQstatus(primary_conn) != CONNECTION_OK) { log_error(_("unable to connect to the primary node to activate the node record")); success = false; } else { success = update_node_record_set_active(primary_conn, local_node_info.node_id, true); PQfinish(primary_conn); } if (success == true) { local_node_info.active = true; } } /* * Corner-case where it was not possible to set the node to "active" */ if (local_node_info.active == false) { switch (config_file_options.failover) { /* "failover" is an enum, all values should be covered here */ case FAILOVER_AUTOMATIC: log_error(_("this node is marked as inactive and cannot be used as a failover target")); log_hint(_("%s"), hint); create_event_notification(NULL, &config_file_options, config_file_options.node_id, "repmgrd_start", false, "node is inactive and cannot be used as a failover target"); terminate(ERR_BAD_CONFIG); break; case FAILOVER_MANUAL: log_warning(_("this node is marked as inactive and will be passively monitored only")); log_hint(_("%s"), hint); break; } } } if (config_file_options.failover == FAILOVER_AUTOMATIC) { /* * Check that "promote_command" and "follow_command" are defined, otherwise repmgrd * won't be able to perform any useful action in a failover situation. */ bool required_param_missing = false; if (config_file_options.promote_command[0] == '\0') { log_error(_("\"promote_command\" must be defined in the configuration file")); if (config_file_options.service_promote_command[0] != '\0') { /* * "service_promote_command" is *not* a substitute for "promote_command"; * it is intended for use in those systems (e.g. Debian) where there's a service * level promote command (e.g. pg_ctlcluster). * * "promote_command" should either execute "repmgr standby promote" directly, or * a script which executes "repmgr standby promote". This is essential, as the * repmgr metadata is updated by "repmgr standby promote". * * "service_promote_command", if set, will be executed by "repmgr standby promote", * but never by repmgrd. * */ log_hint(_("\"service_promote_command\" is set, but can only be executed by \"repmgr standby promote\"")); } required_param_missing = true; } if (config_file_options.follow_command[0] == '\0') { log_error(_("\"follow_command\" must be defined in the configuration file")); required_param_missing = true; } if (required_param_missing == true) { log_hint(_("add the missing configuration parameter(s) and start repmgrd again")); terminate(ERR_BAD_CONFIG); } } } /* * repmgrd running on the primary server */ void monitor_streaming_primary(void) { instr_time log_status_interval_start; instr_time child_nodes_check_interval_start; t_child_node_info_list local_child_nodes = T_CHILD_NODE_INFO_LIST_INITIALIZER; reset_node_voting_status(); repmgrd_set_upstream_node_id(local_conn, NO_UPSTREAM_NODE); { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("monitoring cluster primary \"%s\" (ID: %i)"), local_node_info.node_name, local_node_info.node_id); /* Log startup event */ if (startup_event_logged == false) { create_event_notification(local_conn, &config_file_options, config_file_options.node_id, "repmgrd_start", true, event_details.data); startup_event_logged = true; } else { create_event_notification(local_conn, &config_file_options, config_file_options.node_id, "repmgrd_reload", true, event_details.data); } log_notice("%s", event_details.data); termPQExpBuffer(&event_details); } INSTR_TIME_SET_CURRENT(log_status_interval_start); INSTR_TIME_SET_CURRENT(child_nodes_check_interval_start); local_node_info.node_status = NODE_STATUS_UP; /* * get list of expected and attached nodes */ { NodeInfoList db_child_node_records = T_NODE_INFO_LIST_INITIALIZER; bool success = get_child_nodes(local_conn, config_file_options.node_id, &db_child_node_records); if (!success) { log_error(_("unable to retrieve list of child nodes")); } else { NodeInfoListCell *cell; for (cell = db_child_node_records.head; cell; cell = cell->next) { /* * At startup, if a node for which a repmgr record exists, is not found * in pg_stat_replication, we can't know whether it has become detached, or * (e.g. during a provisioning operation) is a new node which has not yet * attached. We set the status to "NODE_ATTACHED_UNKNOWN" to stop repmgrd * emitting bogus "node has become detached" alerts. */ (void) append_child_node_record(&local_child_nodes, cell->node_info->node_id, cell->node_info->node_name, cell->node_info->type, cell->node_info->attached == NODE_ATTACHED ? NODE_ATTACHED : NODE_ATTACHED_UNKNOWN); /* * witness will not be "attached" in the normal way */ if (cell->node_info->type == WITNESS) { check_witness_attached(cell->node_info, true); } if (cell->node_info->attached == NODE_ATTACHED) { log_info(_("child node \"%s\" (ID: %i) is attached"), cell->node_info->node_name, cell->node_info->node_id); } else { log_info(_("child node \"%s\" (ID: %i) is not yet attached"), cell->node_info->node_name, cell->node_info->node_id); } } } } while (true) { /* * TODO: cache node list here, refresh at `node_list_refresh_interval` * also return reason for inavailability so we can log it */ (void) connection_ping(local_conn); check_connection(&local_node_info, &local_conn); if (PQstatus(local_conn) != CONNECTION_OK) { /* local node is down, we were expecting it to be up */ if (local_node_info.node_status == NODE_STATUS_UP) { instr_time local_node_unreachable_start; INSTR_TIME_SET_CURRENT(local_node_unreachable_start); { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBufferStr(&event_details, _("unable to connect to local node")); log_warning("%s", event_details.data); /* * as we're monitoring the primary, no point in trying to * write the event to the database * * TODO: possibly add pre-action event here */ create_event_notification(NULL, &config_file_options, config_file_options.node_id, "repmgrd_local_disconnect", true, event_details.data); termPQExpBuffer(&event_details); } local_node_info.node_status = NODE_STATUS_UNKNOWN; try_reconnect(&local_conn, &local_node_info); if (local_node_info.node_status == NODE_STATUS_UP) { int local_node_unreachable_elapsed = calculate_elapsed(local_node_unreachable_start); int stored_local_node_id = UNKNOWN_NODE_ID; PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("reconnected to local node after %i seconds"), local_node_unreachable_elapsed); log_notice("%s", event_details.data); create_event_notification(local_conn, &config_file_options, config_file_options.node_id, "repmgrd_local_reconnect", true, event_details.data); termPQExpBuffer(&event_details); /* * If the local node was restarted, we'll need to reinitialise values * stored in shared memory. */ stored_local_node_id = repmgrd_get_local_node_id(local_conn); if (stored_local_node_id == UNKNOWN_NODE_ID) { repmgrd_set_local_node_id(local_conn, config_file_options.node_id); repmgrd_set_pid(local_conn, getpid(), pid_file); } /* * check that the local node is still primary, otherwise switch * to standby monitoring */ if (check_primary_status(NO_DEGRADED_MONITORING_ELAPSED) == false) return; goto loop; } monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); log_notice(_("unable to connect to local node, falling back to degraded monitoring")); } } if (monitoring_state == MS_DEGRADED) { int degraded_monitoring_elapsed = calculate_elapsed(degraded_monitoring_start); if (config_file_options.degraded_monitoring_timeout > 0 && degraded_monitoring_elapsed > config_file_options.degraded_monitoring_timeout) { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("degraded monitoring timeout (%i seconds) exceeded, terminating"), degraded_monitoring_elapsed); log_notice("%s", event_details.data); create_event_notification(NULL, &config_file_options, config_file_options.node_id, "repmgrd_shutdown", true, event_details.data); termPQExpBuffer(&event_details); terminate(ERR_MONITORING_TIMEOUT); } log_debug("monitoring node in degraded state for %i seconds", degraded_monitoring_elapsed); if (is_server_available(local_node_info.conninfo) == true) { close_connection(&local_conn); local_conn = establish_db_connection(local_node_info.conninfo, false); if (PQstatus(local_conn) != CONNECTION_OK) { log_warning(_("node appears to be up but no connection could be made")); close_connection(&local_conn); } else { local_node_info.node_status = NODE_STATUS_UP; if (check_primary_status(degraded_monitoring_elapsed) == false) return; goto loop; } } /* * possibly attempt to find another node from cached list check if * there's a new primary - if so add hook for fencing? loop, if * starts up check status, switch monitoring mode */ } else { if (config_file_options.child_nodes_check_interval > 0) { int child_nodes_check_interval_elapsed = calculate_elapsed(child_nodes_check_interval_start); if (child_nodes_check_interval_elapsed >= config_file_options.child_nodes_check_interval) { INSTR_TIME_SET_CURRENT(child_nodes_check_interval_start); check_primary_child_nodes(&local_child_nodes); } } } loop: /* check node is still primary, if not restart monitoring */ if (check_primary_status(NO_DEGRADED_MONITORING_ELAPSED) == false) return; /* emit "still alive" log message at regular intervals, if requested */ if (config_file_options.log_status_interval > 0) { int log_status_interval_elapsed = calculate_elapsed(log_status_interval_start); if (log_status_interval_elapsed >= config_file_options.log_status_interval) { log_info(_("monitoring primary node \"%s\" (ID: %i) in %s state"), local_node_info.node_name, local_node_info.node_id, print_monitoring_state(monitoring_state)); if (monitoring_state == MS_DEGRADED) { log_detail(_("waiting for the node to become available")); } INSTR_TIME_SET_CURRENT(log_status_interval_start); } } if (got_SIGHUP) { handle_sighup(&local_conn, PRIMARY); } log_verbose(LOG_DEBUG, "sleeping %i seconds (parameter \"monitor_interval_secs\")", config_file_options.monitor_interval_secs); sleep(config_file_options.monitor_interval_secs); } } /* * If monitoring a primary, it's possible that after an outage of the local node * (due to e.g. a switchover), the node has come back as a standby. We therefore * need to verify its status and if everything looks OK, restart monitoring in * standby mode. * * Returns "true" to indicate repmgrd should continue monitoring the node as * a primary; "false" indicates repmgrd should start monitoring the node as * a standby. */ bool check_primary_status(int degraded_monitoring_elapsed) { PGconn *new_primary_conn; RecordStatus record_status; bool resume_monitoring = true; RecoveryType recovery_type = get_recovery_type(local_conn); if (recovery_type == RECTYPE_UNKNOWN) { log_warning(_("unable to determine node recovery status")); /* "true" to indicate repmgrd should continue monitoring in degraded state */ return true; } /* node is still primary - resume monitoring */ if (recovery_type == RECTYPE_PRIMARY) { if (degraded_monitoring_elapsed != NO_DEGRADED_MONITORING_ELAPSED) { PQExpBufferData event_details; monitoring_state = MS_NORMAL; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("reconnected to primary node after %i seconds, resuming monitoring"), degraded_monitoring_elapsed); create_event_notification(local_conn, &config_file_options, config_file_options.node_id, "repmgrd_local_reconnect", true, event_details.data); log_notice("%s", event_details.data); termPQExpBuffer(&event_details); } return true; } /* the node is now a standby */ { PQExpBufferData event_details; initPQExpBuffer(&event_details); if (degraded_monitoring_elapsed != NO_DEGRADED_MONITORING_ELAPSED) { appendPQExpBuffer(&event_details, _("reconnected to node after %i seconds, node is now a standby, switching to standby monitoring"), degraded_monitoring_elapsed); } else { appendPQExpBufferStr(&event_details, _("node is now a standby, switching to standby monitoring")); } log_notice("%s", event_details.data); termPQExpBuffer(&event_details); } primary_node_id = UNKNOWN_NODE_ID; new_primary_conn = get_primary_connection_quiet(local_conn, &primary_node_id, NULL); if (PQstatus(new_primary_conn) != CONNECTION_OK) { if (primary_node_id == UNKNOWN_NODE_ID) { log_warning(_("unable to determine a new primary node")); } else { log_warning(_("unable to connect to new primary node %i"), primary_node_id); log_detail("\n%s", PQerrorMessage(new_primary_conn)); } close_connection(&new_primary_conn); /* "true" to indicate repmgrd should continue monitoring in degraded state */ return true; } log_debug("primary node ID is now %i", primary_node_id); record_status = get_node_record(new_primary_conn, config_file_options.node_id, &local_node_info); /* * If, for whatever reason, the new primary has no record of this node, * we won't be able to perform proper monitoring. In that case * terminate and let the user sort out the situation. */ if (record_status == RECORD_NOT_FOUND) { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("no metadata record found for this node on current primary %i"), primary_node_id); log_error("%s", event_details.data); log_hint(_("check that 'repmgr (primary|standby) register' was executed for this node")); close_connection(&new_primary_conn); create_event_notification(NULL, &config_file_options, config_file_options.node_id, "repmgrd_shutdown", false, event_details.data); termPQExpBuffer(&event_details); terminate(ERR_BAD_CONFIG); } log_debug("node %i is registered with type = %s", config_file_options.node_id, get_node_type_string(local_node_info.type)); /* * node has recovered but metadata not updated - we can do that ourselves, */ if (local_node_info.type == PRIMARY) { log_notice(_("node \"%s\" (ID: %i) still registered as primary, setting to standby"), config_file_options.node_name, config_file_options.node_id); if (update_node_record_set_active_standby(new_primary_conn, config_file_options.node_id) == false) { resume_monitoring = false; } else { /* refresh our copy of the node record from the primary */ record_status = get_node_record(new_primary_conn, config_file_options.node_id, &local_node_info); /* this is unlikely to happen */ if (record_status != RECORD_FOUND) { log_warning(_("unable to retrieve local node record from primary node %i"), primary_node_id); resume_monitoring = false; } } } if (resume_monitoring == true) { PQExpBufferData event_details; initPQExpBuffer(&event_details); if (degraded_monitoring_elapsed != NO_DEGRADED_MONITORING_ELAPSED) { monitoring_state = MS_NORMAL; log_notice(_("former primary has been restored as standby after %i seconds, updating node record and resuming monitoring"), degraded_monitoring_elapsed); appendPQExpBuffer(&event_details, _("node restored as standby after %i seconds, monitoring connection to upstream node %i"), degraded_monitoring_elapsed, local_node_info.upstream_node_id); } else { if (local_node_info.upstream_node_id == UNKNOWN_NODE_ID) { /* * If upstream_node_id is not set, it's possible that following a switchover * of some kind (possibly forced in some way), the updated node record has * not yet propagated to the local node. In this case however we can safely * assume we're monitoring the primary. */ appendPQExpBuffer(&event_details, _("node has become a standby, monitoring connection to primary node %i"), primary_node_id); } else { appendPQExpBuffer(&event_details, _("node has become a standby, monitoring connection to upstream node %i"), local_node_info.upstream_node_id); } } create_event_notification(new_primary_conn, &config_file_options, config_file_options.node_id, "repmgrd_standby_reconnect", true, event_details.data); termPQExpBuffer(&event_details); close_connection(&new_primary_conn); /* restart monitoring as standby */ return false; } /* continue monitoring as before */ return true; } static void check_primary_child_nodes(t_child_node_info_list *local_child_nodes) { NodeInfoList db_child_node_records = T_NODE_INFO_LIST_INITIALIZER; NodeInfoListCell *cell; /* lists for newly attached and missing nodes */ t_child_node_info_list disconnected_child_nodes = T_CHILD_NODE_INFO_LIST_INITIALIZER; t_child_node_info_list reconnected_child_nodes = T_CHILD_NODE_INFO_LIST_INITIALIZER; t_child_node_info_list new_child_nodes = T_CHILD_NODE_INFO_LIST_INITIALIZER; bool success = get_child_nodes(local_conn, config_file_options.node_id, &db_child_node_records); if (!success) { /* unlikely this will happen, but if it does, we'll try again next time round */ log_error(_("unable to retrieve list of child nodes")); return; } if (db_child_node_records.node_count == 0) { /* no registered child nodes - nothing to do */ return; } /* * compare DB records with our internal list; * this will tell us about: * - previously known nodes and their current status * - newly registered nodes we didn't know about * * We'll need to compare the opposite way to check for nodes * which are in the internal list, but which have now vanished */ for (cell = db_child_node_records.head; cell; cell = cell->next) { t_child_node_info *local_child_node_rec; bool local_child_node_rec_found = false; /* * witness will not be "attached" in the normal way */ if (cell->node_info->type == WITNESS) { check_witness_attached(cell->node_info, false); } log_debug("child node: %i; attached: %s", cell->node_info->node_id, cell->node_info->attached == NODE_ATTACHED ? "yes" : "no"); for (local_child_node_rec = local_child_nodes->head; local_child_node_rec; local_child_node_rec = local_child_node_rec->next) { if (local_child_node_rec->node_id == cell->node_info->node_id) { local_child_node_rec_found = true; break; } } if (local_child_node_rec_found == true) { /* our node record shows node attached, DB record indicates detached */ if (local_child_node_rec->attached == NODE_ATTACHED && cell->node_info->attached == NODE_DETACHED) { t_child_node_info *detached_child_node; local_child_node_rec->attached = NODE_DETACHED; INSTR_TIME_SET_CURRENT(local_child_node_rec->detached_time); detached_child_node = append_child_node_record(&disconnected_child_nodes, local_child_node_rec->node_id, local_child_node_rec->node_name, local_child_node_rec->type, NODE_DETACHED); detached_child_node->detached_time = local_child_node_rec->detached_time; } /* our node record shows node detached, DB record indicates attached */ else if (local_child_node_rec->attached == NODE_DETACHED && cell->node_info->attached == NODE_ATTACHED) { t_child_node_info *attached_child_node; local_child_node_rec->attached = NODE_ATTACHED; attached_child_node = append_child_node_record(&reconnected_child_nodes, local_child_node_rec->node_id, local_child_node_rec->node_name, local_child_node_rec->type, NODE_ATTACHED); attached_child_node->detached_time = local_child_node_rec->detached_time; INSTR_TIME_SET_ZERO(local_child_node_rec->detached_time); } else if (local_child_node_rec->attached == NODE_ATTACHED_UNKNOWN && cell->node_info->attached == NODE_ATTACHED) { local_child_node_rec->attached = NODE_ATTACHED; append_child_node_record(&new_child_nodes, local_child_node_rec->node_id, local_child_node_rec->node_name, local_child_node_rec->type, NODE_ATTACHED); } } else { /* node we didn't know about before */ NodeAttached attached = cell->node_info->attached; /* * node registered but not attached - set state to "UNKNOWN" * to prevent a bogus "reattach" event being generated */ if (attached == NODE_DETACHED) attached = NODE_ATTACHED_UNKNOWN; (void) append_child_node_record(local_child_nodes, cell->node_info->node_id, cell->node_info->node_name, cell->node_info->type, attached); (void) append_child_node_record(&new_child_nodes, cell->node_info->node_id, cell->node_info->node_name, cell->node_info->type, attached); } } /* * Check if any nodes in local list are no longer in list returned * from database. */ { t_child_node_info *local_child_node_rec; bool db_node_rec_found = false; for (local_child_node_rec = local_child_nodes->head; local_child_node_rec; local_child_node_rec = local_child_node_rec->next) { for (cell = db_child_node_records.head; cell; cell = cell->next) { if (cell->node_info->node_id == local_child_node_rec->node_id) { db_node_rec_found = true; break; } } if (db_node_rec_found == false) { log_notice(_("%s node \"%s\" (ID: %i) is no longer connected or registered"), get_node_type_string(local_child_node_rec->type), local_child_node_rec->node_name, local_child_node_rec->node_id); remove_child_node_record(local_child_nodes, local_child_node_rec->node_id); } } } /* generate "child_node_disconnect" events */ if (disconnected_child_nodes.node_count > 0) { t_child_node_info *child_node_rec; for (child_node_rec = disconnected_child_nodes.head; child_node_rec; child_node_rec = child_node_rec->next) { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("%s node \"%s\" (ID: %i) has disconnected"), get_node_type_string(child_node_rec->type), child_node_rec->node_name, child_node_rec->node_id); log_notice("%s", event_details.data); create_event_notification(local_conn, &config_file_options, local_node_info.node_id, "child_node_disconnect", true, event_details.data); termPQExpBuffer(&event_details); } } /* generate "child_node_reconnect" events */ if (reconnected_child_nodes.node_count > 0) { t_child_node_info *child_node_rec; for (child_node_rec = reconnected_child_nodes.head; child_node_rec; child_node_rec = child_node_rec->next) { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("%s node \"%s\" (ID: %i) has reconnected after %i seconds"), get_node_type_string(child_node_rec->type), child_node_rec->node_name, child_node_rec->node_id, calculate_elapsed( child_node_rec->detached_time )); log_notice("%s", event_details.data); create_event_notification(local_conn, &config_file_options, local_node_info.node_id, "child_node_reconnect", true, event_details.data); termPQExpBuffer(&event_details); } } /* generate "child_node_new_connect" events */ if (new_child_nodes.node_count > 0) { t_child_node_info *child_node_rec; for (child_node_rec = new_child_nodes.head; child_node_rec; child_node_rec = child_node_rec->next) { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("new %s \"%s\" (ID: %i) has connected"), get_node_type_string(child_node_rec->type), child_node_rec->node_name, child_node_rec->node_id); log_notice("%s", event_details.data); create_event_notification(local_conn, &config_file_options, local_node_info.node_id, "child_node_new_connect", true, event_details.data); termPQExpBuffer(&event_details); } } if (config_file_options.child_nodes_disconnect_command[0] != '\0') { bool repmgrd_paused = repmgrd_is_paused(local_conn); if (repmgrd_paused == false) { /* check criteria for execution, and execute if criteria met */ execute_child_nodes_disconnect_command(&db_child_node_records, local_child_nodes); } } clear_child_node_info_list(&disconnected_child_nodes); clear_child_node_info_list(&reconnected_child_nodes); clear_child_node_info_list(&new_child_nodes); clear_node_info_list(&db_child_node_records); } void execute_child_nodes_disconnect_command(NodeInfoList *db_child_node_records, t_child_node_info_list *local_child_nodes) { /* * script will only be executed if the number of attached * standbys is lower than this number */ int min_required_connected_count = 1; int connected_count = 0; NodeInfoListCell *cell; /* * Calculate minimum number of nodes which need to be connected * (if the total falls below that, "child_nodes_disconnect_command" * will be executed) */ if (config_file_options.child_nodes_connected_min_count > 0) { min_required_connected_count = config_file_options.child_nodes_connected_min_count; } else if (config_file_options.child_nodes_disconnect_min_count > 0) { int child_node_count = db_child_node_records->node_count; if (config_file_options.child_nodes_connected_include_witness == false) { /* reduce total, if witness server in child node list */ for (cell = db_child_node_records->head; cell; cell = cell->next) { if (cell->node_info->type == WITNESS) { child_node_count--; break; } } } min_required_connected_count = (child_node_count - config_file_options.child_nodes_disconnect_min_count) + 1; } /* calculate number of connected child nodes */ for (cell = db_child_node_records->head; cell; cell = cell->next) { /* exclude witness server from total, if necessary */ if (config_file_options.child_nodes_connected_include_witness == false && cell->node_info->type == WITNESS) continue; if (cell->node_info->attached == NODE_ATTACHED) connected_count ++; } log_debug("connected: %i; min required: %i", connected_count, min_required_connected_count); if (connected_count < min_required_connected_count) { log_notice(_("%i (of %i) child nodes are connected, but at least %i child nodes required"), connected_count, db_child_node_records->node_count, min_required_connected_count); if (child_nodes_disconnect_command_executed == false) { t_child_node_info *child_node_rec; /* set these for informative purposes */ int most_recently_disconnected_node_id = UNKNOWN_NODE_ID; int most_recently_disconnected_elapsed = -1; bool most_recent_disconnect_below_threshold = false; instr_time current_time_base; INSTR_TIME_SET_CURRENT(current_time_base); for (child_node_rec = local_child_nodes->head; child_node_rec; child_node_rec = child_node_rec->next) { instr_time current_time = current_time_base; int seconds_since_detached; /* exclude witness server from calculation, if requested */ if (config_file_options.child_nodes_connected_include_witness == false && child_node_rec->type == WITNESS) continue; if (child_node_rec->attached != NODE_DETACHED) continue; INSTR_TIME_SUBTRACT(current_time, child_node_rec->detached_time); seconds_since_detached = (int) INSTR_TIME_GET_DOUBLE(current_time); if (seconds_since_detached < config_file_options.child_nodes_disconnect_timeout) { most_recent_disconnect_below_threshold = true; } if (most_recently_disconnected_node_id == UNKNOWN_NODE_ID) { most_recently_disconnected_node_id = child_node_rec->node_id; most_recently_disconnected_elapsed = seconds_since_detached; } else if (seconds_since_detached < most_recently_disconnected_elapsed) { most_recently_disconnected_node_id = child_node_rec->node_id; most_recently_disconnected_elapsed = seconds_since_detached; } } if (most_recent_disconnect_below_threshold == false && most_recently_disconnected_node_id != UNKNOWN_NODE_ID) { char parsed_child_nodes_disconnect_command[MAXPGPATH]; int child_nodes_disconnect_command_result; PQExpBufferData event_details; bool success = true; parse_child_nodes_disconnect_command(parsed_child_nodes_disconnect_command, config_file_options.child_nodes_disconnect_command, local_node_info.node_id); log_info(_("most recently detached child node was %i (ca. %i seconds ago), triggering \"child_nodes_disconnect_command\""), most_recently_disconnected_node_id, most_recently_disconnected_elapsed); log_info(_("\"child_nodes_disconnect_command\" is:\n \"%s\""), parsed_child_nodes_disconnect_command); child_nodes_disconnect_command_result = system(parsed_child_nodes_disconnect_command); initPQExpBuffer(&event_details); if (child_nodes_disconnect_command_result != 0) { success = false; appendPQExpBufferStr(&event_details, _("unable to execute \"child_nodes_disconnect_command\"")); log_error("%s", event_details.data); } else { appendPQExpBufferStr(&event_details, _("\"child_nodes_disconnect_command\" successfully executed")); log_info("%s", event_details.data); } create_event_notification(local_conn, &config_file_options, local_node_info.node_id, "child_nodes_disconnect_command", success, event_details.data); termPQExpBuffer(&event_details); child_nodes_disconnect_command_executed = true; } else if (most_recently_disconnected_node_id != UNKNOWN_NODE_ID) { log_info(_("most recently detached child node was %i (ca. %i seconds ago), not triggering \"child_nodes_disconnect_command\""), most_recently_disconnected_node_id, most_recently_disconnected_elapsed); log_detail(_("\"child_nodes_disconnect_timeout\" set to %i seconds"), config_file_options.child_nodes_disconnect_timeout); } else { log_info(_("no child nodes have detached since repmgrd startup")); } } else { log_info(_("\"child_nodes_disconnect_command\" was previously executed, taking no action")); } } else { /* * "child_nodes_disconnect_command" was executed, but for whatever reason * enough child nodes have returned to clear the threshold; in that case reset * the executed flag so we can execute the command again, if necessary */ if (child_nodes_disconnect_command_executed == true) { log_notice(_("%i (of %i) child nodes are now connected, meeting minimum requirement of %i child nodes"), connected_count, db_child_node_records->node_count, min_required_connected_count); child_nodes_disconnect_command_executed = false; } } } /* * repmgrd running on a standby server */ void monitor_streaming_standby(void) { RecordStatus record_status; instr_time log_status_interval_start; MonitoringState local_monitoring_state = MS_NORMAL; instr_time local_degraded_monitoring_start; int last_known_upstream_node_id = UNKNOWN_NODE_ID; log_debug("monitor_streaming_standby()"); reset_node_voting_status(); INSTR_TIME_SET_ZERO(last_monitoring_update); /* * If no upstream node id is specified in the metadata, we'll try and * determine the current cluster primary in the assumption we should * connect to that by default. */ if (local_node_info.upstream_node_id == UNKNOWN_NODE_ID) { upstream_conn = get_primary_connection(local_conn, &local_node_info.upstream_node_id, NULL); /* * Terminate if there doesn't appear to be an active cluster primary. * There could be one or more nodes marked as inactive primaries, and * one of them could actually be a primary, but we can't sensibly * monitor in that state. */ if (local_node_info.upstream_node_id == NODE_NOT_FOUND) { log_error(_("unable to determine an active primary for this cluster, terminating")); terminate(ERR_BAD_CONFIG); } log_debug("upstream node ID determined as %i", local_node_info.upstream_node_id); (void) get_node_record(upstream_conn, local_node_info.upstream_node_id, &upstream_node_info); } else { log_debug("upstream node ID in local node record is %i", local_node_info.upstream_node_id); record_status = get_node_record(local_conn, local_node_info.upstream_node_id, &upstream_node_info); /* * Terminate if we can't find the record for the node we're supposed to * monitor. This is a "fix-the-config" situation, not a lot else we can * do. */ if (record_status == RECORD_NOT_FOUND) { log_error(_("no record found for upstream node (ID: %i), terminating"), local_node_info.upstream_node_id); log_hint(_("ensure the upstream node is registered correctly")); terminate(ERR_DB_CONN); } else if (record_status == RECORD_ERROR) { log_error(_("unable to retrieve record for upstream node (ID: %i), terminating"), local_node_info.upstream_node_id); terminate(ERR_DB_CONN); } log_debug("connecting to upstream node %i: \"%s\"", upstream_node_info.node_id, upstream_node_info.conninfo); upstream_conn = establish_db_connection(upstream_node_info.conninfo, false); } /* * Upstream node must be running at repmgrd startup. * * We could possibly have repmgrd skip to degraded monitoring mode until * it comes up, but there doesn't seem to be much point in doing that. */ if (PQstatus(upstream_conn) != CONNECTION_OK) { close_connection(&upstream_conn); log_error(_("unable connect to upstream node (ID: %i), terminating"), local_node_info.upstream_node_id); log_hint(_("upstream node must be running before repmgrd can start")); terminate(ERR_DB_CONN); } record_status = get_node_record(upstream_conn, local_node_info.node_id, &local_node_info); if (upstream_node_info.node_id == local_node_info.node_id) { close_connection(&upstream_conn); return; } last_known_upstream_node_id = local_node_info.upstream_node_id; /* * refresh upstream node record from upstream node, so it's as up-to-date * as possible */ record_status = get_node_record(upstream_conn, upstream_node_info.node_id, &upstream_node_info); if (upstream_node_info.type == STANDBY) { log_debug("upstream node is standby, connecting to primary"); /* * Currently cascaded standbys need to be able to connect to the * primary. We could possibly add a limited connection mode for cases * where this isn't possible, but that will complicate things further. */ primary_conn = establish_primary_db_connection(upstream_conn, false); if (PQstatus(primary_conn) != CONNECTION_OK) { close_connection(&primary_conn); log_error(_("unable to connect to primary node")); log_hint(_("ensure the primary node is reachable from this node")); terminate(ERR_DB_CONN); } log_verbose(LOG_DEBUG, "connected to primary"); } else { log_debug("upstream node is primary"); primary_conn = upstream_conn; } /* * It's possible monitoring has been restarted after some outage which * resulted in the local node being marked as inactive; if so mark it * as active again. */ if (local_node_info.active == false) { if (update_node_record_set_active(primary_conn, local_node_info.node_id, true) == true) { PQExpBufferData event_details; initPQExpBuffer(&event_details); local_node_info.active = true; } } if (PQstatus(primary_conn) == CONNECTION_OK) { primary_node_id = get_primary_node_id(primary_conn); log_debug("primary_node_id is %i", primary_node_id); } else { primary_node_id = get_primary_node_id(local_conn); log_debug("primary_node_id according to local records is %i", primary_node_id); } /* Log startup event */ if (startup_event_logged == false) { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("monitoring connection to upstream node \"%s\" (ID: %i)"), upstream_node_info.node_name, upstream_node_info.node_id); create_event_notification(primary_conn, &config_file_options, config_file_options.node_id, "repmgrd_start", true, event_details.data); startup_event_logged = true; log_info("%s", event_details.data); termPQExpBuffer(&event_details); } monitoring_state = MS_NORMAL; INSTR_TIME_SET_CURRENT(log_status_interval_start); upstream_node_info.node_status = NODE_STATUS_UP; while (true) { bool upstream_check_result; log_verbose(LOG_DEBUG, "checking %s", upstream_node_info.conninfo); if (upstream_node_info.type == PRIMARY) { upstream_check_result = check_upstream_connection(&upstream_conn, upstream_node_info.conninfo, &primary_conn); } else { upstream_check_result = check_upstream_connection(&upstream_conn, upstream_node_info.conninfo, NULL); } if (upstream_check_result == true) { set_upstream_last_seen(local_conn, upstream_node_info.node_id); } else { /* upstream node is down, we were expecting it to be up */ if (upstream_node_info.node_status == NODE_STATUS_UP) { instr_time upstream_node_unreachable_start; INSTR_TIME_SET_CURRENT(upstream_node_unreachable_start); upstream_node_info.node_status = NODE_STATUS_UNKNOWN; { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("unable to connect to upstream node \"%s\" (ID: %i)"), upstream_node_info.node_name, upstream_node_info.node_id); /* TODO: possibly add pre-action event here */ if (upstream_node_info.type == STANDBY) { create_event_record(primary_conn, &config_file_options, config_file_options.node_id, "repmgrd_upstream_disconnect", true, event_details.data); } else { /* primary connection lost - script notification only */ create_event_record(NULL, &config_file_options, config_file_options.node_id, "repmgrd_upstream_disconnect", true, event_details.data); } log_warning("%s", event_details.data); termPQExpBuffer(&event_details); } /* * if local node is unreachable, make a last-minute attempt to reconnect * before continuing with the failover process */ if (PQstatus(local_conn) != CONNECTION_OK) { check_connection(&local_node_info, &local_conn); } if (upstream_node_info.type == PRIMARY) { primary_node_id = try_primary_reconnect(&upstream_conn, local_conn, &upstream_node_info); /* * We were notified by the the primary during our own reconnection * retry phase, in which case we can leave the failover process early * and connect to the new primary. */ if (primary_node_id == ELECTION_RERUN_NOTIFICATION) { if (do_primary_failover() == true) { primary_node_id = get_primary_node_id(local_conn); return; } } if (primary_node_id != UNKNOWN_NODE_ID && primary_node_id != ELECTION_RERUN_NOTIFICATION) { follow_new_primary(primary_node_id); return; } } else { try_reconnect(&upstream_conn, &upstream_node_info); } /* Upstream node has recovered - log and continue */ if (upstream_node_info.node_status == NODE_STATUS_UP) { int upstream_node_unreachable_elapsed = calculate_elapsed(upstream_node_unreachable_start); PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("reconnected to upstream node after %i seconds"), upstream_node_unreachable_elapsed); log_notice("%s", event_details.data); if (upstream_node_info.type == PRIMARY) { primary_conn = upstream_conn; if (get_recovery_type(primary_conn) == RECTYPE_STANDBY) { ExecStatusType ping_result; /* * we're returning at the end of this block and no longer require the * event details buffer */ termPQExpBuffer(&event_details); log_notice(_("current upstream node \"%s\" (ID: %i) is not primary, restarting monitoring"), upstream_node_info.node_name, upstream_node_info.node_id); close_connection(&upstream_conn); local_node_info.upstream_node_id = UNKNOWN_NODE_ID; /* check local connection */ ping_result = connection_ping(local_conn); if (ping_result != PGRES_TUPLES_OK) { int i; close_connection(&local_conn); for (i = 0; i < config_file_options.repmgrd_standby_startup_timeout; i++) { local_conn = establish_db_connection(local_node_info.conninfo, false); if (PQstatus(local_conn) == CONNECTION_OK) break; close_connection(&local_conn); log_debug("sleeping 1 second; %i of %i attempts to reconnect to local node", i + 1, config_file_options.repmgrd_standby_startup_timeout); sleep(1); } } return; } } create_event_notification(primary_conn, &config_file_options, config_file_options.node_id, "repmgrd_upstream_reconnect", true, event_details.data); termPQExpBuffer(&event_details); goto loop; } /* upstream is still down after reconnect attempt(s) */ if (upstream_node_info.node_status == NODE_STATUS_DOWN) { bool failover_done = false; if (PQstatus(local_conn) == CONNECTION_OK && repmgrd_is_paused(local_conn)) { log_notice(_("repmgrd on this node is paused")); log_detail(_("no failover will be carried out")); log_hint(_("execute \"repmgr service unpause\" to resume normal failover mode")); monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); } else { if (upstream_node_info.type == PRIMARY) { failover_done = do_primary_failover(); } else if (upstream_node_info.type == STANDBY) { failover_done = do_upstream_standby_failover(); if (failover_done == false) { monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); } } /* * XXX it's possible it will make sense to return in all * cases to restart monitoring */ if (failover_done == true) { primary_node_id = get_primary_node_id(local_conn); return; } } } } } if (monitoring_state == MS_DEGRADED) { int degraded_monitoring_elapsed = calculate_elapsed(degraded_monitoring_start); bool upstream_check_result; if (config_file_options.degraded_monitoring_timeout > 0 && degraded_monitoring_elapsed > config_file_options.degraded_monitoring_timeout) { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("degraded monitoring timeout (%i seconds) exceeded, terminating"), degraded_monitoring_elapsed); log_notice("%s", event_details.data); create_event_notification(NULL, &config_file_options, config_file_options.node_id, "repmgrd_shutdown", true, event_details.data); termPQExpBuffer(&event_details); terminate(ERR_MONITORING_TIMEOUT); } log_debug("monitoring upstream node %i in degraded state for %i seconds", upstream_node_info.node_id, degraded_monitoring_elapsed); if (upstream_node_info.type == PRIMARY) { upstream_check_result = check_upstream_connection(&upstream_conn, upstream_node_info.conninfo, &primary_conn); } else { upstream_check_result = check_upstream_connection(&upstream_conn, upstream_node_info.conninfo, NULL); } if (upstream_check_result == true) { if (config_file_options.connection_check_type != CHECK_QUERY) { close_connection(&upstream_conn); upstream_conn = establish_db_connection(upstream_node_info.conninfo, false); } if (PQstatus(upstream_conn) == CONNECTION_OK) { PQExpBufferData event_details; log_debug("upstream node %i has recovered", upstream_node_info.node_id); /* XXX check here if upstream is still primary */ /* * -> will be a problem if another node was promoted in * the meantime */ /* and upstream is now former primary */ /* XXX scan other nodes to see if any has become primary */ upstream_node_info.node_status = NODE_STATUS_UP; monitoring_state = MS_NORMAL; if (upstream_node_info.type == PRIMARY) { primary_conn = upstream_conn; } else { if (primary_conn != NULL && PQstatus(primary_conn) != CONNECTION_OK) { close_connection(&primary_conn); } if (primary_conn == NULL) { primary_conn = establish_primary_db_connection(upstream_conn, false); } } initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("reconnected to upstream node \"%s\" (ID: %i) after %i seconds, resuming monitoring"), upstream_node_info.node_name, upstream_node_info.node_id, degraded_monitoring_elapsed); create_event_notification(primary_conn, &config_file_options, config_file_options.node_id, "repmgrd_upstream_reconnect", true, event_details.data); log_notice("%s", event_details.data); termPQExpBuffer(&event_details); goto loop; } } else { /* * unable to connect to former primary - check if another node * has been promoted */ /* local node has been promoted */ if (get_recovery_type(local_conn) == RECTYPE_PRIMARY) { log_notice(_("local node is primary, checking local node state")); /* * It's possible the promote command timed out, but the promotion itself * succeeded. In this case failover state will be FAILOVER_STATE_PROMOTION_FAILED; * we can update the node record ourselves and resume primary monitoring. */ if (failover_state == FAILOVER_STATE_PROMOTION_FAILED) { int degraded_monitoring_elapsed; int former_upstream_node_id = local_node_info.upstream_node_id; NodeInfoList sibling_nodes = T_NODE_INFO_LIST_INITIALIZER; PQExpBufferData event_details; t_event_info event_info = T_EVENT_INFO_INITIALIZER; update_node_record_set_primary(local_conn, local_node_info.node_id); record_status = get_node_record(local_conn, local_node_info.node_id, &local_node_info); degraded_monitoring_elapsed = calculate_elapsed(degraded_monitoring_start); log_notice(_("resuming monitoring as primary node after %i seconds"), degraded_monitoring_elapsed); initPQExpBuffer(&event_details); appendPQExpBufferStr(&event_details, _("promotion command failed but promotion completed successfully")); event_info.node_id = former_upstream_node_id; create_event_notification_extended(local_conn, &config_file_options, local_node_info.node_id, "repmgrd_failover_promote", true, event_details.data, &event_info); termPQExpBuffer(&event_details); /* notify former siblings that they should now follow this node */ get_active_sibling_node_records(local_conn, local_node_info.node_id, former_upstream_node_id, &sibling_nodes); notify_followers(&sibling_nodes, local_node_info.node_id); clear_node_info_list(&sibling_nodes); /* this will restart monitoring in primary mode */ monitoring_state = MS_NORMAL; return; } /* * There may be a delay between the node being promoted * and the local record being updated, so if the node * record still shows it as a standby, do nothing, we'll * catch the update during the next loop. (e.g. node was * manually promoted) we'll do nothing, as the repmgr * metadata is now out-of-sync. If it does get fixed, * we'll catch it here on a future iteration. */ /* refresh own internal node record */ record_status = refresh_node_record(local_conn, local_node_info.node_id, &local_node_info); if (local_node_info.type == PRIMARY) { int degraded_monitoring_elapsed = calculate_elapsed(degraded_monitoring_start); log_notice(_("resuming monitoring as primary node after %i seconds"), degraded_monitoring_elapsed); /* this will restart monitoring in primary mode */ monitoring_state = MS_NORMAL; return; } } if (config_file_options.failover == FAILOVER_AUTOMATIC && repmgrd_is_paused(local_conn) == false) { NodeInfoList sibling_nodes = T_NODE_INFO_LIST_INITIALIZER; get_active_sibling_node_records(local_conn, local_node_info.node_id, local_node_info.upstream_node_id, &sibling_nodes); if (sibling_nodes.node_count > 0) { NodeInfoListCell *cell; t_node_info *follow_node_info = NULL; log_debug("scanning %i node records to detect new primary...", sibling_nodes.node_count); for (cell = sibling_nodes.head; cell; cell = cell->next) { /* skip local node check, we did that above */ if (cell->node_info->node_id == local_node_info.node_id) { continue; } /* skip witness node - we can't possibly "follow" that */ if (cell->node_info->type == WITNESS) { continue; } cell->node_info->conn = establish_db_connection(cell->node_info->conninfo, false); if (PQstatus(cell->node_info->conn) != CONNECTION_OK) { close_connection(&cell->node_info->conn); log_debug("unable to connect to %i ... ", cell->node_info->node_id); close_connection(&cell->node_info->conn); continue; } if (get_recovery_type(cell->node_info->conn) == RECTYPE_PRIMARY) { follow_node_info = cell->node_info; close_connection(&cell->node_info->conn); break; } close_connection(&cell->node_info->conn); } if (follow_node_info != NULL) { log_info(_("node \"%s\" (node ID: %i) detected as primary"), follow_node_info->node_name, follow_node_info->node_id); follow_new_primary(follow_node_info->node_id); } } clear_node_info_list(&sibling_nodes); } } } loop: /* emit "still alive" log message at regular intervals, if requested */ if (config_file_options.log_status_interval > 0) { int log_status_interval_elapsed = calculate_elapsed(log_status_interval_start); if (log_status_interval_elapsed >= config_file_options.log_status_interval) { PQExpBufferData monitoring_summary; initPQExpBuffer(&monitoring_summary); appendPQExpBuffer(&monitoring_summary, _("node \"%s\" (ID: %i) monitoring upstream node \"%s\" (ID: %i) in %s state"), local_node_info.node_name, local_node_info.node_id, upstream_node_info.node_name, upstream_node_info.node_id, print_monitoring_state(monitoring_state)); if (config_file_options.failover == FAILOVER_MANUAL) { appendPQExpBufferStr(&monitoring_summary, _(" (automatic failover disabled)")); } log_info("%s", monitoring_summary.data); termPQExpBuffer(&monitoring_summary); if (monitoring_state == MS_DEGRADED && config_file_options.failover == FAILOVER_AUTOMATIC) { if (PQstatus(local_conn) == CONNECTION_OK && repmgrd_is_paused(local_conn)) { log_detail(_("repmgrd paused by administrator")); log_hint(_("execute \"repmgr service unpause\" to resume normal failover mode")); } else { log_detail(_("waiting for upstream or another primary to reappear")); } } /* * Add update about monitoring updates. * * Note: with cascaded replication, it's possible we're still able to write * monitoring history to the primary even if the upstream is still reachable. */ if (PQstatus(primary_conn) == CONNECTION_OK && config_file_options.monitoring_history == true) { if (INSTR_TIME_IS_ZERO(last_monitoring_update)) { log_detail(_("no monitoring statistics have been written yet")); } else { log_detail(_("last monitoring statistics update was %i seconds ago"), calculate_elapsed(last_monitoring_update)); } } INSTR_TIME_SET_CURRENT(log_status_interval_start); } } if (PQstatus(primary_conn) == CONNECTION_OK && config_file_options.monitoring_history == true) { bool success = update_monitoring_history(); if (success == false && PQstatus(primary_conn) != CONNECTION_OK && upstream_node_info.type == STANDBY) { primary_conn = establish_primary_db_connection(local_conn, false); if (PQstatus(primary_conn) == CONNECTION_OK) { (void)update_monitoring_history(); } } } else { if (config_file_options.monitoring_history == true) { log_verbose(LOG_WARNING, _("monitoring_history requested but primary connection not available")); } /* * if monitoring not in use, we'll need to ensure the local connection * handle isn't stale */ (void) connection_ping(local_conn); } /* * handle local node failure * * currently we'll just check the connection, and try to reconnect * * TODO: add timeout, after which we run in degraded state */ check_connection(&local_node_info, &local_conn); if (PQstatus(local_conn) != CONNECTION_OK) { if (local_node_info.active == true) { bool success = true; PQExpBufferData event_details; initPQExpBuffer(&event_details); local_node_info.active = false; appendPQExpBuffer(&event_details, _("unable to connect to local node \"%s\" (ID: %i), marking inactive"), local_node_info.node_name, local_node_info.node_id); log_notice("%s", event_details.data); if (PQstatus(primary_conn) == CONNECTION_OK) { if (update_node_record_set_active(primary_conn, local_node_info.node_id, false) == false) { success = false; log_warning(_("unable to mark node \"%s\" (ID: %i) as inactive"), local_node_info.node_name, local_node_info.node_id); } } create_event_notification(primary_conn, &config_file_options, local_node_info.node_id, "standby_failure", success, event_details.data); termPQExpBuffer(&event_details); } if (local_monitoring_state == MS_NORMAL) { log_info("entering degraded monitoring for the local node"); local_monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(local_degraded_monitoring_start); } } else { int stored_local_node_id = UNKNOWN_NODE_ID; if (local_monitoring_state == MS_DEGRADED) { log_info(_("connection to local node recovered after %i seconds"), calculate_elapsed(local_degraded_monitoring_start)); local_monitoring_state = MS_NORMAL; /* * Check if anything has changed since the local node came back on line; * we may need to restart monitoring. */ refresh_node_record(local_conn, local_node_info.node_id, &local_node_info); if (last_known_upstream_node_id != local_node_info.upstream_node_id) { log_notice(_("upstream for local node \"%s\" (ID: %i) appears to have changed, restarting monitoring"), local_node_info.node_name, local_node_info.node_id); log_detail(_("currently monitoring upstream %i; new upstream is %i"), last_known_upstream_node_id, local_node_info.upstream_node_id); close_connection(&upstream_conn); return; } /* * */ if (local_node_info.type != STANDBY) { log_notice(_("local node \"%s\" (ID: %i) is no longer a standby, restarting monitoring"), local_node_info.node_name, local_node_info.node_id); close_connection(&upstream_conn); return; } } /* * If the local node was restarted, we'll need to reinitialise values * stored in shared memory. */ stored_local_node_id = repmgrd_get_local_node_id(local_conn); if (stored_local_node_id == UNKNOWN_NODE_ID) { repmgrd_set_local_node_id(local_conn, config_file_options.node_id); repmgrd_set_pid(local_conn, getpid(), pid_file); } if (PQstatus(primary_conn) == CONNECTION_OK) { if (get_recovery_type(primary_conn) == RECTYPE_STANDBY) { log_notice(_("current upstream node \"%s\" (ID: %i) is not primary, restarting monitoring"), upstream_node_info.node_name, upstream_node_info.node_id); close_connection(&primary_conn); local_node_info.upstream_node_id = UNKNOWN_NODE_ID; return; } } /* we've reconnected to the local node after an outage */ if (local_node_info.active == false) { if (PQstatus(primary_conn) == CONNECTION_OK) { if (update_node_record_set_active(primary_conn, local_node_info.node_id, true) == true) { PQExpBufferData event_details; initPQExpBuffer(&event_details); local_node_info.active = true; appendPQExpBuffer(&event_details, _("reconnected to local node \"%s\" (ID: %i), marking active"), local_node_info.node_name, local_node_info.node_id); log_notice("%s", event_details.data); create_event_notification(primary_conn, &config_file_options, local_node_info.node_id, "standby_recovery", true, event_details.data); termPQExpBuffer(&event_details); } } } } if (got_SIGHUP) { handle_sighup(&local_conn, STANDBY); } refresh_node_record(local_conn, local_node_info.node_id, &local_node_info); if (local_monitoring_state == MS_NORMAL && last_known_upstream_node_id != local_node_info.upstream_node_id) { /* * It's possible that after a change of upstream, the local node record will not * yet have been updated with the new upstream node ID. Therefore we check the * node record on the upstream, and if that matches "last_known_upstream_node_id", * take that as the correct value. */ if (monitoring_state == MS_NORMAL) { t_node_info node_info_on_upstream = T_NODE_INFO_INITIALIZER; record_status = get_node_record(primary_conn, config_file_options.node_id, &node_info_on_upstream); if (last_known_upstream_node_id == node_info_on_upstream.upstream_node_id) { local_node_info.upstream_node_id = last_known_upstream_node_id; } } if (last_known_upstream_node_id != local_node_info.upstream_node_id) { log_notice(_("local node \"%s\" (ID: %i)'s upstream appears to have changed, restarting monitoring"), local_node_info.node_name, local_node_info.node_id); log_detail(_("currently monitoring upstream %i; new upstream is %i"), last_known_upstream_node_id, local_node_info.upstream_node_id); close_connection(&upstream_conn); return; } } log_verbose(LOG_DEBUG, "sleeping %i seconds (parameter \"monitor_interval_secs\")", config_file_options.monitor_interval_secs); sleep(config_file_options.monitor_interval_secs); } } void monitor_streaming_witness(void) { instr_time log_status_interval_start; instr_time witness_sync_interval_start; RecordStatus record_status; int primary_node_id = UNKNOWN_NODE_ID; reset_node_voting_status(); log_debug("monitor_streaming_witness()"); /* * At this point we can't trust the local copy of "repmgr.nodes", as * it may not have been updated. We'll scan the cluster to find the * current primary and refresh the copy from that before proceeding * further. */ primary_conn = get_primary_connection_quiet(local_conn, &primary_node_id, NULL); /* * Primary node should be running at repmgrd startup. * * Otherwise we'll skip to degraded monitoring. */ if (PQstatus(primary_conn) == CONNECTION_OK) { PQExpBufferData event_details; char *event_type = startup_event_logged == false ? "repmgrd_start" : "repmgrd_upstream_reconnect"; /* synchronise local copy of "repmgr.nodes", in case it was stale */ witness_copy_node_records(primary_conn, local_conn); /* * refresh upstream node record from primary, so it's as up-to-date * as possible */ record_status = get_node_record(primary_conn, primary_node_id, &upstream_node_info); /* * This is unlikely to happen; if it does emit a warning for diagnostic * purposes and plough on regardless. * * A check for the existence of the record will have already been carried out * in main(). */ if (record_status != RECORD_FOUND) { log_warning(_("unable to retrieve node record from primary")); } initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("witness monitoring connection to primary node \"%s\" (ID: %i)"), upstream_node_info.node_name, upstream_node_info.node_id); log_info("%s", event_details.data); create_event_notification(primary_conn, &config_file_options, config_file_options.node_id, event_type, true, event_details.data); if (startup_event_logged == false) startup_event_logged = true; termPQExpBuffer(&event_details); monitoring_state = MS_NORMAL; INSTR_TIME_SET_CURRENT(log_status_interval_start); INSTR_TIME_SET_CURRENT(witness_sync_interval_start); upstream_node_info.node_status = NODE_STATUS_UP; } else { log_warning(_("unable to connect to primary")); log_detail("\n%s", PQerrorMessage(primary_conn)); /* * Here we're unable to connect to a primary despite having scanned all * known nodes, so we'll grab the record of the node we think is primary * and continue straight to degraded monitoring in the hope a primary * will appear. */ primary_node_id = get_primary_node_id(local_conn); log_notice(_("setting primary_node_id to last known ID %i"), primary_node_id); record_status = get_node_record(local_conn, primary_node_id, &upstream_node_info); /* * This is unlikely to happen, but if for whatever reason there's * no primary record in the local table, we should just give up */ if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve node record for last known primary %i"), primary_node_id); log_hint(_("execute \"repmgr witness register --force\" to sync the local node records")); PQfinish(local_conn); terminate(ERR_BAD_CONFIG); } monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); upstream_node_info.node_status = NODE_STATUS_DOWN; } while (true) { if (check_upstream_connection(&primary_conn, upstream_node_info.conninfo, NULL) == true) { set_upstream_last_seen(local_conn, upstream_node_info.node_id); } else { if (upstream_node_info.node_status == NODE_STATUS_UP) { instr_time upstream_node_unreachable_start; INSTR_TIME_SET_CURRENT(upstream_node_unreachable_start); upstream_node_info.node_status = NODE_STATUS_UNKNOWN; { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("unable to connect to primary node \"%s\" (ID: %i)"), upstream_node_info.node_name, upstream_node_info.node_id); create_event_record(NULL, &config_file_options, config_file_options.node_id, "repmgrd_upstream_disconnect", true, event_details.data); termPQExpBuffer(&event_details); } try_reconnect(&primary_conn, &upstream_node_info); /* Node has recovered - log and continue */ if (upstream_node_info.node_status == NODE_STATUS_UP) { int upstream_node_unreachable_elapsed = calculate_elapsed(upstream_node_unreachable_start); PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("reconnected to upstream node after %i seconds"), upstream_node_unreachable_elapsed); log_notice("%s", event_details.data); /* check upstream is still primary */ if (get_recovery_type(primary_conn) != RECTYPE_PRIMARY) { log_notice(_("current upstream node \"%s\" (ID: %i) is not primary, restarting monitoring"), upstream_node_info.node_name, upstream_node_info.node_id); close_connection(&primary_conn); termPQExpBuffer(&event_details); return; } create_event_notification(primary_conn, &config_file_options, config_file_options.node_id, "repmgrd_upstream_reconnect", true, event_details.data); termPQExpBuffer(&event_details); goto loop; } /* still down after reconnect attempt(s) */ if (upstream_node_info.node_status == NODE_STATUS_DOWN) { bool failover_done = false; failover_done = do_witness_failover(); /* * XXX it's possible it will make sense to return in all * cases to restart monitoring */ if (failover_done == true) { primary_node_id = get_primary_node_id(local_conn); return; } } } } if (monitoring_state == MS_DEGRADED) { int degraded_monitoring_elapsed = calculate_elapsed(degraded_monitoring_start); log_debug("monitoring node %i in degraded state for %i seconds", upstream_node_info.node_id, degraded_monitoring_elapsed); if (check_upstream_connection(&primary_conn, upstream_node_info.conninfo, NULL) == true) { if (config_file_options.connection_check_type != CHECK_QUERY) { close_connection(&primary_conn); primary_conn = establish_db_connection(upstream_node_info.conninfo, false); } if (PQstatus(primary_conn) == CONNECTION_OK) { PQExpBufferData event_details; upstream_node_info.node_status = NODE_STATUS_UP; monitoring_state = MS_NORMAL; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("reconnected to upstream node \"%s\" (ID: %i) after %i seconds, resuming monitoring"), upstream_node_info.node_name, upstream_node_info.node_id, degraded_monitoring_elapsed); log_notice("%s", event_details.data); /* check upstream is still primary */ if (get_recovery_type(primary_conn) != RECTYPE_PRIMARY) { log_notice(_("current upstream node \"%s\" (ID: %i) is not primary, restarting monitoring"), upstream_node_info.node_name, upstream_node_info.node_id); close_connection(&primary_conn); termPQExpBuffer(&event_details); return; } create_event_notification(primary_conn, &config_file_options, config_file_options.node_id, "repmgrd_upstream_reconnect", true, event_details.data); termPQExpBuffer(&event_details); goto loop; } } else { /* * unable to connect to former primary - check if another node * has been promoted */ NodeInfoList sibling_nodes = T_NODE_INFO_LIST_INITIALIZER; get_active_sibling_node_records(local_conn, local_node_info.node_id, local_node_info.upstream_node_id, &sibling_nodes); if (sibling_nodes.node_count > 0) { NodeInfoListCell *cell; t_node_info *follow_node_info = NULL; log_debug("scanning %i node records to detect new primary...", sibling_nodes.node_count); for (cell = sibling_nodes.head; cell; cell = cell->next) { /* skip local node check, we did that above */ if (cell->node_info->node_id == local_node_info.node_id) { continue; } /* skip node if configured as a witness node - we can't possibly "follow" that */ if (cell->node_info->type == WITNESS) { continue; } cell->node_info->conn = establish_db_connection(cell->node_info->conninfo, false); if (PQstatus(cell->node_info->conn) != CONNECTION_OK) { close_connection(&cell->node_info->conn); log_debug("unable to connect to %i ... ", cell->node_info->node_id); close_connection(&cell->node_info->conn); continue; } if (get_recovery_type(cell->node_info->conn) == RECTYPE_PRIMARY) { follow_node_info = cell->node_info; close_connection(&cell->node_info->conn); break; } close_connection(&cell->node_info->conn); } if (follow_node_info != NULL) { log_info(_("node \"%s\" (node ID: %i) detected as primary"), follow_node_info->node_name, follow_node_info->node_id); witness_follow_new_primary(follow_node_info->node_id); } } clear_node_info_list(&sibling_nodes); } } loop: /* * handle local node failure * * currently we'll just check the connection, and try to reconnect * * TODO: add timeout, after which we run in degraded state */ (void) connection_ping(local_conn); check_connection(&local_node_info, &local_conn); if (PQstatus(local_conn) != CONNECTION_OK) { if (local_node_info.active == true) { bool success = true; PQExpBufferData event_details; initPQExpBuffer(&event_details); local_node_info.active = false; appendPQExpBuffer(&event_details, _("unable to connect to local node \"%s\" (ID: %i), marking inactive"), local_node_info.node_name, local_node_info.node_id); log_notice("%s", event_details.data); if (PQstatus(primary_conn) == CONNECTION_OK) { if (update_node_record_set_active(primary_conn, local_node_info.node_id, false) == false) { success = false; log_warning(_("unable to mark node \"%s\" (ID: %i) as inactive"), local_node_info.node_name, local_node_info.node_id); } } create_event_notification(primary_conn, &config_file_options, local_node_info.node_id, "standby_failure", success, event_details.data); termPQExpBuffer(&event_details); } } else { /* we've reconnected to the local node after an outage */ if (local_node_info.active == false) { int stored_local_node_id = UNKNOWN_NODE_ID; if (PQstatus(primary_conn) == CONNECTION_OK) { if (update_node_record_set_active(primary_conn, local_node_info.node_id, true) == true) { PQExpBufferData event_details; initPQExpBuffer(&event_details); local_node_info.active = true; appendPQExpBuffer(&event_details, _("reconnected to local node \"%s\" (ID: %i), marking active"), local_node_info.node_name, local_node_info.node_id); log_notice("%s", event_details.data); create_event_notification(primary_conn, &config_file_options, local_node_info.node_id, "standby_recovery", true, event_details.data); termPQExpBuffer(&event_details); } } /* * If the local node was restarted, we'll need to reinitialise values * stored in shared memory. */ stored_local_node_id = repmgrd_get_local_node_id(local_conn); if (stored_local_node_id == UNKNOWN_NODE_ID) { repmgrd_set_local_node_id(local_conn, config_file_options.node_id); repmgrd_set_pid(local_conn, getpid(), pid_file); } } } /* * Refresh repmgr.nodes after "witness_sync_interval" seconds, and check if primary * has changed */ if (PQstatus(primary_conn) == CONNECTION_OK) { int witness_sync_interval_elapsed = calculate_elapsed(witness_sync_interval_start); if (witness_sync_interval_elapsed >= config_file_options.witness_sync_interval) { if (get_recovery_type(primary_conn) != RECTYPE_PRIMARY) { log_notice(_("current upstream node \"%s\" (ID: %i) is not primary, restarting monitoring"), upstream_node_info.node_name, upstream_node_info.node_id); close_connection(&primary_conn); return; } log_debug("synchronising witness node records"); witness_copy_node_records(primary_conn, local_conn); INSTR_TIME_SET_CURRENT(witness_sync_interval_start); } else { log_debug("seconds since last node record sync: %i (sync interval: %i)", witness_sync_interval_elapsed, config_file_options.witness_sync_interval) } } /* emit "still alive" log message at regular intervals, if requested */ if (config_file_options.log_status_interval > 0) { int log_status_interval_elapsed = calculate_elapsed(log_status_interval_start); if (log_status_interval_elapsed >= config_file_options.log_status_interval) { PQExpBufferData monitoring_summary; initPQExpBuffer(&monitoring_summary); appendPQExpBuffer(&monitoring_summary, _("witness node \"%s\" (ID: %i) monitoring primary node \"%s\" (ID: %i) in %s state"), local_node_info.node_name, local_node_info.node_id, upstream_node_info.node_name, upstream_node_info.node_id, print_monitoring_state(monitoring_state)); log_info("%s", monitoring_summary.data); termPQExpBuffer(&monitoring_summary); if (monitoring_state == MS_DEGRADED && config_file_options.failover == FAILOVER_AUTOMATIC) { log_detail(_("waiting for current or new primary to reappear")); } INSTR_TIME_SET_CURRENT(log_status_interval_start); } } if (got_SIGHUP) { handle_sighup(&local_conn, WITNESS); } log_verbose(LOG_DEBUG, "sleeping %i seconds (parameter \"monitor_interval_secs\")", config_file_options.monitor_interval_secs); sleep(config_file_options.monitor_interval_secs); } return; } static bool do_primary_failover(void) { ElectionResult election_result; bool final_result = false; NodeInfoList sibling_nodes = T_NODE_INFO_LIST_INITIALIZER; int new_primary_id = UNKNOWN_NODE_ID; /* * Double-check status of the local connection */ check_connection(&local_node_info, &local_conn); /* * if requested, disable WAL receiver and wait until WAL receivers on all * sibling nodes are disconnected */ if (config_file_options.standby_disconnect_on_failover == true) { NodeInfoListCell *cell = NULL; NodeInfoList check_sibling_nodes = T_NODE_INFO_LIST_INITIALIZER; int i; bool sibling_node_wal_receiver_connected = false; if (PQserverVersion(local_conn) < 90500) { log_warning(_("\"standby_disconnect_on_failover\" specified, but not available for this PostgreSQL version")); /* TODO: format server version */ log_detail(_("available from PostgreSQL 9.5, this PostgreSQL version is %i"), PQserverVersion(local_conn)); } else { disable_wal_receiver(local_conn); /* * Loop through all reachable sibling nodes to determine whether * they have disabled their WAL receivers. * * TODO: do_election() also calls get_active_sibling_node_records(), * consolidate calls if feasible * */ get_active_sibling_node_records(local_conn, local_node_info.node_id, local_node_info.upstream_node_id, &check_sibling_nodes); for (i = 0; i < config_file_options.sibling_nodes_disconnect_timeout; i++) { for (cell = check_sibling_nodes.head; cell; cell = cell->next) { if (cell->node_info->conn == NULL) cell->node_info->conn = establish_db_connection(cell->node_info->conninfo, false); if (PQstatus(cell->node_info->conn) != CONNECTION_OK) { log_warning(_("unable to query WAL receiver PID on node \"%s\" (ID: %i)"), cell->node_info->node_name, cell->node_info->node_id); close_connection(&cell->node_info->conn); } else { pid_t sibling_wal_receiver_pid = (pid_t)get_wal_receiver_pid(cell->node_info->conn); if (sibling_wal_receiver_pid == UNKNOWN_PID) { log_warning(_("unable to query WAL receiver PID on node %i"), cell->node_info->node_id); } else if (sibling_wal_receiver_pid > 0) { log_info(_("WAL receiver PID on node %i is %i"), cell->node_info->node_id, sibling_wal_receiver_pid); sibling_node_wal_receiver_connected = true; } } } if (sibling_node_wal_receiver_connected == false) { log_notice(_("WAL receiver disconnected on all sibling nodes")); break; } log_debug("sleeping %i of max %i seconds (\"sibling_nodes_disconnect_timeout\")", i + 1, config_file_options.sibling_nodes_disconnect_timeout); sleep(1); } if (sibling_node_wal_receiver_connected == true) { /* TODO: prevent any such nodes becoming promotion candidates */ log_warning(_("WAL receiver still connected on at least one sibling node")); } else { log_info(_("WAL receiver disconnected on all %i sibling nodes"), check_sibling_nodes.node_count); } clear_node_info_list(&check_sibling_nodes); } } /* attempt to initiate voting process */ election_result = do_election(&sibling_nodes, &new_primary_id); /* TODO add pre-event notification here */ failover_state = FAILOVER_STATE_UNKNOWN; log_debug("election result: %s", _print_election_result(election_result)); /* Reenable WAL receiver, if disabled */ if (config_file_options.standby_disconnect_on_failover == true) { /* adjust "wal_retrieve_retry_interval" but don't wait for WAL receiver to start */ enable_wal_receiver(local_conn, false); } /* election was cancelled and do_election() did not determine a new primary */ if (election_result == ELECTION_CANCELLED) { if (new_primary_id == UNKNOWN_NODE_ID) { log_notice(_("election cancelled")); clear_node_info_list(&sibling_nodes); return false; } log_info(_("follower node intending to follow new primary %i"), new_primary_id); failover_state = FAILOVER_STATE_FOLLOW_NEW_PRIMARY; } else if (election_result == ELECTION_RERUN) { log_notice(_("promotion candidate election will be rerun")); /* notify siblings that they should rerun the election too */ notify_followers(&sibling_nodes, ELECTION_RERUN_NOTIFICATION); failover_state = FAILOVER_STATE_ELECTION_RERUN; } else if (election_result == ELECTION_WON) { if (sibling_nodes.node_count > 0) { log_notice("this node is the winner, will now promote itself and inform other nodes"); } else { log_notice("this node is the only available candidate and will now promote itself"); } failover_state = promote_self(); } else if (election_result == ELECTION_LOST || election_result == ELECTION_NOT_CANDIDATE) { /* * if the node couldn't be promoted as it's not in the same location as the primary, * add an explanatory notice */ if (election_result == ELECTION_NOT_CANDIDATE && strncmp(upstream_node_info.location, local_node_info.location, MAXLEN) != 0) { log_notice(_("this node's location (\"%s\") is not the primary node location (\"%s\"), so node cannot be promoted"), local_node_info.location, upstream_node_info.location); } log_info(_("follower node awaiting notification from a candidate node")); failover_state = FAILOVER_STATE_WAITING_NEW_PRIMARY; } /* * node has determined a new primary is already available */ if (failover_state == FAILOVER_STATE_FOLLOW_NEW_PRIMARY) { failover_state = follow_new_primary(new_primary_id); } /* * node has decided it is a follower, so will await notification from the * candidate that it has promoted itself and can be followed */ else if (failover_state == FAILOVER_STATE_WAITING_NEW_PRIMARY) { /* TODO: rerun election if new primary doesn't appear after timeout */ /* either follow, self-promote or time out; either way resume monitoring */ if (wait_primary_notification(&new_primary_id) == true) { /* if primary has reappeared, no action needed */ if (new_primary_id == upstream_node_info.node_id) { failover_state = FAILOVER_STATE_FOLLOWING_ORIGINAL_PRIMARY; } /* if new_primary_id is self, promote */ else if (new_primary_id == local_node_info.node_id) { log_notice(_("this node is promotion candidate, promoting")); failover_state = promote_self(); get_active_sibling_node_records(local_conn, local_node_info.node_id, upstream_node_info.node_id, &sibling_nodes); } /* election rerun */ else if (new_primary_id == ELECTION_RERUN_NOTIFICATION) { log_notice(_("received notification from promotion candidate to rerun election")); failover_state = FAILOVER_STATE_ELECTION_RERUN; } else if (config_file_options.failover == FAILOVER_MANUAL) { /* automatic failover disabled */ t_node_info new_primary = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; record_status = get_node_record(local_conn, new_primary_id, &new_primary); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve metadata record for new primary node (ID: %i)"), new_primary_id); } else { PQExpBufferData event_details; PGconn *new_primary_conn; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("node \"%s\" (ID: %i) is in manual failover mode and is now disconnected from streaming replication"), local_node_info.node_name, local_node_info.node_id); new_primary_conn = establish_db_connection(new_primary.conninfo, false); create_event_notification(new_primary_conn, &config_file_options, local_node_info.node_id, "standby_disconnect_manual", /* * here "true" indicates the action has occurred as expected */ true, event_details.data); close_connection(&new_primary_conn); termPQExpBuffer(&event_details); } failover_state = FAILOVER_STATE_REQUIRES_MANUAL_FAILOVER; } else { failover_state = follow_new_primary(new_primary_id); } } else { failover_state = FAILOVER_STATE_NO_NEW_PRIMARY; } } log_verbose(LOG_DEBUG, "failover state is %s", format_failover_state(failover_state)); switch (failover_state) { case FAILOVER_STATE_PROMOTED: /* notify former siblings that they should now follow this node */ notify_followers(&sibling_nodes, local_node_info.node_id); /* pass control back down to start_monitoring() */ log_info(_("switching to primary monitoring mode")); failover_state = FAILOVER_STATE_NONE; final_result = true; break; case FAILOVER_STATE_ELECTION_RERUN: /* we no longer care about our former siblings */ clear_node_info_list(&sibling_nodes); log_notice(_("rerunning election after %i seconds (\"election_rerun_interval\")"), config_file_options.election_rerun_interval); sleep(config_file_options.election_rerun_interval); log_info(_("election rerun will now commence")); /* * mark the upstream node as "up" so another election is triggered * after we fall back to monitoring */ upstream_node_info.node_status = NODE_STATUS_UP; failover_state = FAILOVER_STATE_NONE; final_result = false; break; case FAILOVER_STATE_PRIMARY_REAPPEARED: /* * notify siblings that they should resume following the original * primary */ notify_followers(&sibling_nodes, upstream_node_info.node_id); /* pass control back down to start_monitoring() */ log_info(_("resuming %s monitoring mode"), get_node_type_string(local_node_info.type)); log_detail(_("original primary \"%s\" (ID: %i) reappeared"), upstream_node_info.node_name, upstream_node_info.node_id); failover_state = FAILOVER_STATE_NONE; final_result = true; break; case FAILOVER_STATE_FOLLOWED_NEW_PRIMARY: log_info(_("resuming %s monitoring mode"), get_node_type_string(local_node_info.type)); log_detail(_("following new primary \"%s\" (ID: %i)"), upstream_node_info.node_name, upstream_node_info.node_id); failover_state = FAILOVER_STATE_NONE; final_result = true; break; case FAILOVER_STATE_FOLLOWING_ORIGINAL_PRIMARY: log_info(_("resuming %s monitoring mode"), get_node_type_string(local_node_info.type)); log_detail(_("following original primary \"%s\" (ID: %i)"), upstream_node_info.node_name, upstream_node_info.node_id); failover_state = FAILOVER_STATE_NONE; final_result = true; break; case FAILOVER_STATE_PROMOTION_FAILED: monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); final_result = false; break; case FAILOVER_STATE_FOLLOW_FAIL: /* * for whatever reason we were unable to follow the new primary - * continue monitoring in degraded state */ monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); final_result = false; break; case FAILOVER_STATE_REQUIRES_MANUAL_FAILOVER: log_info(_("automatic failover disabled for this node, manual intervention required")); monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); final_result = false; break; case FAILOVER_STATE_NO_NEW_PRIMARY: case FAILOVER_STATE_WAITING_NEW_PRIMARY: /* pass control back down to start_monitoring() */ final_result = false; break; case FAILOVER_STATE_NODE_NOTIFICATION_ERROR: case FAILOVER_STATE_LOCAL_NODE_FAILURE: case FAILOVER_STATE_UNKNOWN: case FAILOVER_STATE_NONE: final_result = false; break; default: /* should never reach here */ log_warning(_("unhandled failover state %i"), failover_state); break; } /* we no longer care about our former siblings */ clear_node_info_list(&sibling_nodes); return final_result; } static bool update_monitoring_history(void) { ReplInfo replication_info; XLogRecPtr primary_last_wal_location = InvalidXLogRecPtr; long long unsigned int apply_lag_bytes = 0; long long unsigned int replication_lag_bytes = 0; /* both local and primary connections must be available */ if (PQstatus(primary_conn) != CONNECTION_OK) { log_warning(_("primary connection is not available, unable to update monitoring history")); return false; } if (PQstatus(local_conn) != CONNECTION_OK) { log_warning(_("local connection is not available, unable to update monitoring history")); return false; } init_replication_info(&replication_info); if (get_replication_info(local_conn, STANDBY, &replication_info) == false) { log_warning(_("unable to retrieve replication status information, unable to update monitoring history")); return false; } /* * This can be the case when a standby is starting up after following * a new primary, or when it has dropped back to archive recovery. * As long as we can connect to the primary, we can still provide lag information. */ if (replication_info.receiving_streamed_wal == false) { log_verbose(LOG_WARNING, _("standby %i not connected to streaming replication"), local_node_info.node_id); } primary_last_wal_location = get_primary_current_lsn(primary_conn); if (primary_last_wal_location == InvalidXLogRecPtr) { log_warning(_("unable to retrieve primary's current LSN")); return false; } /* calculate apply lag in bytes */ if (replication_info.last_wal_receive_lsn >= replication_info.last_wal_replay_lsn) { apply_lag_bytes = (long long unsigned int) (replication_info.last_wal_receive_lsn - replication_info.last_wal_replay_lsn); } else { /* if this happens, it probably indicates archive recovery */ apply_lag_bytes = 0; } /* calculate replication lag in bytes */ if (primary_last_wal_location >= replication_info.last_wal_receive_lsn) { replication_lag_bytes = (long long unsigned int) (primary_last_wal_location - replication_info.last_wal_receive_lsn); log_debug("replication lag in bytes is: %llu", replication_lag_bytes); } else { /* * This should never happen, but in case it does set replication lag * to zero */ log_warning("primary xlog location (%X/%X) is behind the standby receive location (%X/%X)", format_lsn(primary_last_wal_location), format_lsn(replication_info.last_wal_receive_lsn)); replication_lag_bytes = 0; } add_monitoring_record(primary_conn, local_conn, primary_node_id, local_node_info.node_id, replication_info.current_timestamp, primary_last_wal_location, replication_info.last_wal_receive_lsn, replication_info.last_xact_replay_timestamp, replication_lag_bytes, apply_lag_bytes); INSTR_TIME_SET_CURRENT(last_monitoring_update); log_verbose(LOG_DEBUG, "update_monitoring_history(): monitoring history update sent"); return true; } /* * do_upstream_standby_failover() * * Attach cascaded standby to another node, currently the primary. * * Note that in contrast to a primary failover, where one of the downstream * standby nodes will become a primary, a cascaded standby failover (where the * upstream standby has gone away) is "just" a case of attaching the standby to * another node. * * Currently we will try to attach the node to the cluster primary. * * TODO: As of repmgr 4.3, "repmgr standby follow" supports attaching a standby to another * standby node. We need to provide a selection of reconnection strategies as different * behaviour might be desirable in different situations. */ static bool do_upstream_standby_failover(void) { t_node_info primary_node_info = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; RecoveryType primary_type = RECTYPE_UNKNOWN; int i, standby_follow_result; char parsed_follow_command[MAXPGPATH] = ""; close_connection(&upstream_conn); /* * */ if (config_file_options.failover == FAILOVER_MANUAL) { log_notice(_("this node is not configured for automatic failover")); log_detail(_("parameter \"failover\" is set to \"manual\"")); return false; } if (get_primary_node_record(local_conn, &primary_node_info) == false) { log_error(_("unable to retrieve primary node record")); return false; } /* * Verify that we can still talk to the cluster primary, even though the * node's upstream is not available */ check_connection(&primary_node_info, &primary_conn); if (PQstatus(primary_conn) != CONNECTION_OK) { log_error(_("unable to connect to last known primary \"%s\" (ID: %i)"), primary_node_info.node_name, primary_node_info.node_id); close_connection(&primary_conn); monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); return false; } primary_type = get_recovery_type(primary_conn); if (primary_type != RECTYPE_PRIMARY) { if (primary_type == RECTYPE_STANDBY) { log_error(_("last known primary \"%s\" (ID: %i) is in recovery, not following"), primary_node_info.node_name, primary_node_info.node_id); } else { log_error(_("unable to determine status of last known primary \"%s\" (ID: %i), not following"), primary_node_info.node_name, primary_node_info.node_id); } close_connection(&primary_conn); monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); return false; } /* Close the connection to this server */ close_connection(&local_conn); log_debug(_("standby follow command is:\n \"%s\""), config_file_options.follow_command); /* * replace %n in "config_file_options.follow_command" with ID of primary * to follow. */ parse_follow_command(parsed_follow_command, config_file_options.follow_command, primary_node_info.node_id); standby_follow_result = system(parsed_follow_command); if (standby_follow_result != 0) { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("unable to execute follow command:\n %s"), config_file_options.follow_command); log_error("%s", event_details.data); /* * It may not possible to write to the event notification table but we * should be able to generate an external notification if required. */ create_event_notification(primary_conn, &config_file_options, local_node_info.node_id, "repmgrd_failover_follow", false, event_details.data); termPQExpBuffer(&event_details); } /* * It's possible that the standby is still starting up after the "follow_command" * completes, so poll for a while until we get a connection. * * NOTE: we've previously closed the local connection, so even if the follow command * failed for whatever reason and the local node remained up, we can re-open * the local connection. */ for (i = 0; i < config_file_options.repmgrd_standby_startup_timeout; i++) { local_conn = establish_db_connection(local_node_info.conninfo, false); if (PQstatus(local_conn) == CONNECTION_OK) break; close_connection(&local_conn); log_debug("sleeping 1 second; %i of %i (\"repmgrd_standby_startup_timeout\") attempts to reconnect to local node", i + 1, config_file_options.repmgrd_standby_startup_timeout); sleep(1); } if (PQstatus(local_conn) != CONNECTION_OK) { log_error(_("unable to reconnect to local node \"%s\" (ID: %i)"), local_node_info.node_name, local_node_info.node_id); return FAILOVER_STATE_FOLLOW_FAIL; } /* refresh shared memory settings which will have been zapped by the restart */ repmgrd_set_local_node_id(local_conn, config_file_options.node_id); repmgrd_set_pid(local_conn, getpid(), pid_file); /* * */ if (standby_follow_result != 0) { monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); return FAILOVER_STATE_FOLLOW_FAIL; } /* * update upstream_node_id to primary node (but only if follow command * was successful) */ { if (update_node_record_set_upstream(primary_conn, local_node_info.node_id, primary_node_info.node_id) == false) { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("unable to set node \"%s\" (ID: %i)'s new upstream ID to %i"), local_node_info.node_name, local_node_info.node_id, primary_node_info.node_id); log_error("%s", event_details.data); create_event_notification(NULL, &config_file_options, local_node_info.node_id, "repmgrd_failover_follow", false, event_details.data); termPQExpBuffer(&event_details); terminate(ERR_BAD_CONFIG); } } /* refresh own internal node record */ record_status = get_node_record(primary_conn, local_node_info.node_id, &local_node_info); /* * highly improbable this will happen, but in case we're unable to * retrieve our node record from the primary, update it ourselves, and * hope for the best */ if (record_status != RECORD_FOUND) { local_node_info.upstream_node_id = primary_node_info.node_id; } { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("node \"%s\" (ID: %i) is now following primary node \"%s\" (ID: %i)"), local_node_info.node_name, local_node_info.node_id, primary_node_info.node_name, primary_node_info.node_id); log_notice("%s", event_details.data); create_event_notification(primary_conn, &config_file_options, local_node_info.node_id, "repmgrd_failover_follow", true, event_details.data); termPQExpBuffer(&event_details); } /* keep the primary connection open */ return true; } /* * This promotes the local node using the "promote_command" configuration * parameter, which must be either "repmgr standby promote" or a script which * at some point executes "repmgr standby promote". * * TODO: make "promote_command" and execute the same code used by * "repmgr standby promote". */ static FailoverState promote_self(void) { char *promote_command; int r; /* Store details of the failed node here */ t_node_info failed_primary = T_NODE_INFO_INITIALIZER; RecordStatus record_status; /* * optionally add a delay before promoting the standby; this is mainly * useful for testing (e.g. for reappearance of the original primary) and * is not documented. */ if (config_file_options.promote_delay > 0) { log_debug("sleeping %i seconds before promoting standby", config_file_options.promote_delay); sleep(config_file_options.promote_delay); } if (local_node_info.upstream_node_id == UNKNOWN_NODE_ID) { /* * This is a corner-case situation where the repmgr metadata on the * promotion candidate is outdated and the local node's upstream_node_id * is not set. This is often an indication of potentially serious issues, * such as the local node being very far behind the primary, or not being * attached at all. * * In this case it may be desirable to restore the original primary. * This behaviour can be controlled by the "always_promote" configuration option. */ if (config_file_options.always_promote == false) { log_error(_("this node (ID: %i) does not have its upstream_node_id set, not promoting"), local_node_info.node_id); log_detail(_("the local node's metadata has not been updated since it became a standby")); log_hint(_("set \"always_promote\" to \"true\" to force promotion in this situation")); return FAILOVER_STATE_PROMOTION_FAILED; } else { log_warning(_("this node (ID: %i) does not have its upstream_node_id set, promoting anyway"), local_node_info.node_id); log_detail(_("\"always_promote\" is set to \"true\" ")); } } else { record_status = get_node_record(local_conn, local_node_info.upstream_node_id, &failed_primary); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve metadata record for failed upstream (ID: %i)"), local_node_info.upstream_node_id); return FAILOVER_STATE_PROMOTION_FAILED; } } /* the presence of this command has been established already */ promote_command = config_file_options.promote_command; log_info(_("promote_command is:\n \"%s\""), promote_command); if (log_type == REPMGR_STDERR && *config_file_options.log_file) { fflush(stderr); } r = system(promote_command); log_debug("result of promote_command: %i", WEXITSTATUS(r)); /* connection should stay up, but check just in case */ if (PQstatus(local_conn) != CONNECTION_OK) { log_warning(_("local database connection not available")); log_detail("\n%s", PQerrorMessage(local_conn)); close_connection(&local_conn); local_conn = establish_db_connection(local_node_info.conninfo, true); /* assume node failed */ if (PQstatus(local_conn) != CONNECTION_OK) { log_error(_("unable to reconnect to local node")); log_detail("\n%s", PQerrorMessage(local_conn)); close_connection(&local_conn); /* XXX handle this */ return FAILOVER_STATE_LOCAL_NODE_FAILURE; } } if (WIFEXITED(r) && WEXITSTATUS(r)) { int primary_node_id = UNKNOWN_NODE_ID; log_error(_("promote command failed")); log_detail(_("promote command exited with error code %i"), WEXITSTATUS(r)); log_info(_("checking if original primary node has reappeared")); upstream_conn = get_primary_connection(local_conn, &primary_node_id, NULL); if (PQstatus(upstream_conn) != CONNECTION_OK) { close_connection(&upstream_conn); } else if (primary_node_id == failed_primary.node_id) { PQExpBufferData event_details; log_notice(_("original primary \"%s\" (ID: %i) reappeared before this standby was promoted - no action taken"), failed_primary.node_name, failed_primary.node_id); initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("original primary \"%s\" (ID: %i) reappeared"), failed_primary.node_name, failed_primary.node_id); create_event_notification(upstream_conn, &config_file_options, local_node_info.node_id, "repmgrd_failover_abort", true, event_details.data); termPQExpBuffer(&event_details); /* XXX handle this! */ /* -> we'll need to let the other nodes know too.... */ /* no failover occurred but we'll want to restart connections */ return FAILOVER_STATE_PRIMARY_REAPPEARED; } create_event_notification(NULL, &config_file_options, local_node_info.node_id, "repmgrd_promote_error", true, ""); return FAILOVER_STATE_PROMOTION_FAILED; } /* * Promotion has succeeded - verify local connection is still available */ try_reconnect(&local_conn, &local_node_info); /* bump the electoral term */ increment_current_term(local_conn); { PQExpBufferData event_details; t_event_info event_info = T_EVENT_INFO_INITIALIZER; /* update own internal node record */ record_status = get_node_record(local_conn, local_node_info.node_id, &local_node_info); /* * XXX here we're assuming the promote command updated metadata */ initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("node \"%s\" (ID: %i) promoted to primary; old primary \"%s\" (ID: %i) marked as failed"), local_node_info.node_name, local_node_info.node_id, failed_primary.node_name, failed_primary.node_id); event_info.node_id = failed_primary.node_id; /* local_conn is now the primary connection */ create_event_notification_extended(local_conn, &config_file_options, local_node_info.node_id, "repmgrd_failover_promote", true, event_details.data, &event_info); termPQExpBuffer(&event_details); } return FAILOVER_STATE_PROMOTED; } /* * Notify follower nodes about which node to follow. Normally this * will be the current node, however if the original primary reappeared * before this node could be promoted, we'll inform the followers they * should resume monitoring the original primary. */ static void notify_followers(NodeInfoList *standby_nodes, int follow_node_id) { NodeInfoListCell *cell; log_info(_("%i followers to notify"), standby_nodes->node_count); for (cell = standby_nodes->head; cell; cell = cell->next) { log_verbose(LOG_DEBUG, "intending to notify node %i...", cell->node_info->node_id); if (PQstatus(cell->node_info->conn) != CONNECTION_OK) { log_info(_("reconnecting to node \"%s\" (ID: %i)..."), cell->node_info->node_name, cell->node_info->node_id); close_connection(&cell->node_info->conn); cell->node_info->conn = establish_db_connection(cell->node_info->conninfo, false); } if (PQstatus(cell->node_info->conn) != CONNECTION_OK) { log_warning(_("unable to reconnect to \"%s\" (ID: %i)"), cell->node_info->node_name, cell->node_info->node_id); log_detail("\n%s", PQerrorMessage(cell->node_info->conn)); close_connection(&cell->node_info->conn); continue; } if (follow_node_id == ELECTION_RERUN_NOTIFICATION) { log_notice(_("notifying node \"%s\" (ID: %i) to rerun promotion candidate selection"), cell->node_info->node_name, cell->node_info->node_id); } else { log_notice(_("notifying node \"%s\" (ID: %i) to follow node %i"), cell->node_info->node_name, cell->node_info->node_id, follow_node_id); } notify_follow_primary(cell->node_info->conn, follow_node_id); } } static bool wait_primary_notification(int *new_primary_id) { int i; for (i = 0; i < config_file_options.primary_notification_timeout; i++) { if (get_new_primary(local_conn, new_primary_id) == true) { log_debug("new primary is %i; elapsed: %i seconds", *new_primary_id, i); return true; } log_verbose(LOG_DEBUG, "waiting for new primary notification, %i of max %i seconds (\"primary_notification_timeout\")", i, config_file_options.primary_notification_timeout); sleep(1); } log_warning(_("no notification received from new primary after %i seconds"), config_file_options.primary_notification_timeout); monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); return false; } static FailoverState follow_new_primary(int new_primary_id) { char parsed_follow_command[MAXPGPATH] = ""; int i, r; /* Store details of the failed node here */ t_node_info failed_primary = T_NODE_INFO_INITIALIZER; t_node_info new_primary = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; bool new_primary_ok = false; log_verbose(LOG_DEBUG, "follow_new_primary(): new primary id is %i", new_primary_id); record_status = get_node_record(local_conn, new_primary_id, &new_primary); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve metadata record for new primary node (ID: %i)"), new_primary_id); return FAILOVER_STATE_FOLLOW_FAIL; } log_notice(_("attempting to follow new primary \"%s\" (node ID: %i)"), new_primary.node_name, new_primary_id); record_status = get_node_record(local_conn, local_node_info.upstream_node_id, &failed_primary); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve metadata record for failed primary (ID: %i)"), local_node_info.upstream_node_id); return FAILOVER_STATE_FOLLOW_FAIL; } /* XXX check if new_primary_id == failed_primary.node_id? */ if (log_type == REPMGR_STDERR && *config_file_options.log_file) { fflush(stderr); } upstream_conn = establish_db_connection(new_primary.conninfo, false); if (PQstatus(upstream_conn) == CONNECTION_OK) { RecoveryType primary_recovery_type = get_recovery_type(upstream_conn); if (primary_recovery_type == RECTYPE_PRIMARY) { new_primary_ok = true; } else { new_primary_ok = false; log_warning(_("new primary \"%s\" (node ID: %i) is in recovery"), new_primary.node_name, new_primary_id); close_connection(&upstream_conn); } } if (new_primary_ok == false) { return FAILOVER_STATE_FOLLOW_FAIL; } /* * disconnect from local node, as follow operation will result in a server * restart */ close_connection(&local_conn); /* * replace %n in "config_file_options.follow_command" with ID of primary * to follow. */ parse_follow_command(parsed_follow_command, config_file_options.follow_command, new_primary_id); log_debug(_("standby follow command is:\n \"%s\""), parsed_follow_command); /* execute the follow command */ r = system(parsed_follow_command); if (r != 0) { PGconn *old_primary_conn; /* * The "standby follow" command could still fail due to the original primary * reappearing before the candidate could promote itself ("repmgr * standby follow" will refuse to promote another node if the primary * is available). However the new primary will only instruct the other * nodes to follow it after it's successfully promoted itself, so this * case is highly unlikely. A slightly more likely scenario would * be the new primary becoming unavailable just after it's sent notifications * to its follower nodes, and the old primary becoming available again. */ old_primary_conn = establish_db_connection(failed_primary.conninfo, false); if (PQstatus(old_primary_conn) == CONNECTION_OK) { RecoveryType upstream_recovery_type = get_recovery_type(old_primary_conn); if (upstream_recovery_type == RECTYPE_PRIMARY) { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBufferStr(&event_details, _("original primary reappeared - no action taken")); log_notice("%s", event_details.data); create_event_notification(old_primary_conn, &config_file_options, local_node_info.node_id, "repmgrd_failover_aborted", true, event_details.data); termPQExpBuffer(&event_details); close_connection(&upstream_conn); close_connection(&old_primary_conn); return FAILOVER_STATE_PRIMARY_REAPPEARED; } log_notice(_("original primary reappeared as standby")); close_connection(&old_primary_conn); } close_connection(&upstream_conn); return FAILOVER_STATE_FOLLOW_FAIL; } /* * refresh local copy of local and primary node records - we get these * directly from the primary to ensure they're the current version */ record_status = get_node_record(upstream_conn, new_primary_id, &upstream_node_info); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve metadata record found for node %i"), new_primary_id); return FAILOVER_STATE_FOLLOW_FAIL; } record_status = get_node_record(upstream_conn, local_node_info.node_id, &local_node_info); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve metadata record found for node %i"), local_node_info.node_id); return FAILOVER_STATE_FOLLOW_FAIL; } /* * It's possible that the standby is still starting up after the "follow_command" * completes, so poll for a while until we get a connection. */ for (i = 0; i < config_file_options.repmgrd_standby_startup_timeout; i++) { local_conn = establish_db_connection(local_node_info.conninfo, false); if (PQstatus(local_conn) == CONNECTION_OK) break; close_connection(&local_conn); log_debug("sleeping 1 second; %i of %i attempts to reconnect to local node", i + 1, config_file_options.repmgrd_standby_startup_timeout); sleep(1); } if (local_conn == NULL || PQstatus(local_conn) != CONNECTION_OK) { log_error(_("unable to reconnect to local node \"%s\" (ID: %i)"), local_node_info.node_name, local_node_info.node_id); return FAILOVER_STATE_FOLLOW_FAIL; } /* refresh shared memory settings which will have been zapped by the restart */ repmgrd_set_local_node_id(local_conn, config_file_options.node_id); repmgrd_set_pid(local_conn, getpid(), pid_file); { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("node \"%s\" (ID: %i) now following new upstream node \"%s\" (ID: %i)"), local_node_info.node_name, local_node_info.node_id, upstream_node_info.node_name, upstream_node_info.node_id); log_notice("%s", event_details.data); create_event_notification(upstream_conn, &config_file_options, local_node_info.node_id, "repmgrd_failover_follow", true, event_details.data); termPQExpBuffer(&event_details); } return FAILOVER_STATE_FOLLOWED_NEW_PRIMARY; } static FailoverState witness_follow_new_primary(int new_primary_id) { t_node_info new_primary = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; bool new_primary_ok = false; record_status = get_node_record(local_conn, new_primary_id, &new_primary); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve metadata record for new primary node (ID: %i)"), new_primary_id); return FAILOVER_STATE_FOLLOW_FAIL; } /* TODO: check if new_primary_id == failed_primary.node_id? */ if (log_type == REPMGR_STDERR && *config_file_options.log_file) { fflush(stderr); } upstream_conn = establish_db_connection(new_primary.conninfo, false); if (PQstatus(upstream_conn) == CONNECTION_OK) { RecoveryType primary_recovery_type = get_recovery_type(upstream_conn); switch (primary_recovery_type) { case RECTYPE_PRIMARY: new_primary_ok = true; break; case RECTYPE_STANDBY: new_primary_ok = false; log_warning(_("new primary \"%s\" (node ID: %i) is in recovery"), new_primary.node_name, new_primary_id); break; case RECTYPE_UNKNOWN: new_primary_ok = false; log_warning(_("unable to determine status of new primary")); break; } } if (new_primary_ok == false) { close_connection(&upstream_conn); return FAILOVER_STATE_FOLLOW_FAIL; } /* set new upstream node ID on primary */ update_node_record_set_upstream(upstream_conn, local_node_info.node_id, new_primary_id); witness_copy_node_records(upstream_conn, local_conn); /* * refresh local copy of local and primary node records - we get these * directly from the primary to ensure they're the current version */ record_status = get_node_record(upstream_conn, new_primary_id, &upstream_node_info); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve metadata record found for node %i"), new_primary_id); return FAILOVER_STATE_FOLLOW_FAIL; } record_status = get_node_record(upstream_conn, local_node_info.node_id, &local_node_info); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve metadata record for node %i"), local_node_info.node_id); return FAILOVER_STATE_FOLLOW_FAIL; } { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("witness node \"%s\" (ID: %i) now following new primary node \"%s\" (ID: %i)"), local_node_info.node_name, local_node_info.node_id, upstream_node_info.node_name, upstream_node_info.node_id); log_notice("%s", event_details.data); create_event_notification(upstream_conn, &config_file_options, local_node_info.node_id, "repmgrd_failover_follow", true, event_details.data); termPQExpBuffer(&event_details); } return FAILOVER_STATE_FOLLOWED_NEW_PRIMARY; } static const char * _print_election_result(ElectionResult result) { switch (result) { case ELECTION_NOT_CANDIDATE: return "NOT CANDIDATE"; case ELECTION_WON: return "WON"; case ELECTION_LOST: return "LOST"; case ELECTION_CANCELLED: return "CANCELLED"; case ELECTION_RERUN: return "RERUN"; } /* should never reach here */ return "UNKNOWN"; } /* * Failover decision for nodes attached to the current primary. * * NB: this function sets "sibling_nodes"; caller (do_primary_failover) * expects to be able to read this list */ static ElectionResult do_election(NodeInfoList *sibling_nodes, int *new_primary_id) { int electoral_term = -1; NodeInfoListCell *cell = NULL; t_node_info *candidate_node = NULL; election_stats stats; ReplInfo local_replication_info; /* To collate details of nodes with primary visible for logging purposes */ PQExpBufferData nodes_with_primary_visible; /* * Check if at least one server in the primary's location is visible; if * not we'll assume a network split between this node and the primary * location, and not promote any standby. * * NOTE: this function is only ever called by standbys attached to the * current (unreachable) primary, so "upstream_node_info" will always * contain the primary node record. */ bool primary_location_seen = false; int nodes_with_primary_still_visible = 0; if (config_file_options.failover_delay > 0) { log_debug("sleeping %i seconds (\"failover_delay\") before initiating failover", config_file_options.failover_delay); sleep(config_file_options.failover_delay); } /* we're visible */ stats.visible_nodes = 1; stats.shared_upstream_nodes = 0; stats.all_nodes = 0; electoral_term = get_current_term(local_conn); if (electoral_term == -1) { log_error(_("unable to determine electoral term")); return ELECTION_NOT_CANDIDATE; } log_debug("do_election(): electoral term is %i", electoral_term); if (config_file_options.failover == FAILOVER_MANUAL) { log_notice(_("this node is not configured for automatic failover so will not be considered as promotion candidate, and will not follow the new primary")); log_detail(_("\"failover\" is set to \"manual\" in repmgr.conf")); log_hint(_("manually execute \"repmgr standby follow\" to have this node follow the new primary")); return ELECTION_NOT_CANDIDATE; } /* node priority is set to zero - don't become a candidate, and lose by default */ if (local_node_info.priority <= 0) { log_notice(_("this node's priority is %i so will not be considered as an automatic promotion candidate"), local_node_info.priority); return ELECTION_LOST; } /* get all active nodes attached to upstream, excluding self */ get_active_sibling_node_records(local_conn, local_node_info.node_id, upstream_node_info.node_id, sibling_nodes); log_info(_("%i active sibling nodes registered"), sibling_nodes->node_count); stats.shared_upstream_nodes = sibling_nodes->node_count + 1; get_all_nodes_count(local_conn, &stats.all_nodes); log_info(_("%i total nodes registered"), stats.all_nodes); if (strncmp(upstream_node_info.location, local_node_info.location, MAXLEN) != 0) { log_info(_("primary node \"%s\" (ID: %i) has location \"%s\", this node's location is \"%s\""), upstream_node_info.node_name, upstream_node_info.node_id, upstream_node_info.location, local_node_info.location); } else { log_info(_("primary node \"%s\" (ID: %i) and this node have the same location (\"%s\")"), upstream_node_info.node_name, upstream_node_info.node_id, local_node_info.location); } local_node_info.last_wal_receive_lsn = InvalidXLogRecPtr; /* fast path if no other standbys (or witness) exists - normally win by default */ if (sibling_nodes->node_count == 0) { if (strncmp(upstream_node_info.location, local_node_info.location, MAXLEN) == 0) { if (config_file_options.failover_validation_command[0] != '\0') { return execute_failover_validation_command(&local_node_info, &stats); } log_info(_("no other sibling nodes - we win by default")); return ELECTION_WON; } else { /* * If primary and standby have different locations set, the assumption * is that no action should be taken as we can't tell whether there's * been a network interruption or not. * * Normally a situation with primary and standby in different physical * locations would be handled by leaving the location as "default" and * setting up a witness server in the primary's location. */ log_debug("no other nodes, but primary and standby locations differ"); monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); return ELECTION_NOT_CANDIDATE; } } else { /* standby nodes found - check if we're in the primary location before checking theirs */ if (strncmp(upstream_node_info.location, local_node_info.location, MAXLEN) == 0) { primary_location_seen = true; } } /* get our lsn */ if (get_replication_info(local_conn, STANDBY, &local_replication_info) == false) { log_error(_("unable to retrieve replication information for local node")); return ELECTION_LOST; } /* check if WAL replay on local node is paused */ if (local_replication_info.wal_replay_paused == true) { log_debug("WAL replay is paused"); if (local_replication_info.last_wal_receive_lsn > local_replication_info.last_wal_replay_lsn) { log_warning(_("WAL replay on this node is paused and WAL is pending replay")); log_detail(_("replay paused at %X/%X; last WAL received is %X/%X"), format_lsn(local_replication_info.last_wal_replay_lsn), format_lsn(local_replication_info.last_wal_receive_lsn)); } /* attempt to resume WAL replay - unlikely this will fail, but just in case */ if (resume_wal_replay(local_conn) == false) { log_error(_("unable to resume WAL replay")); log_detail(_("this node cannot be reliably promoted")); return ELECTION_LOST; } log_notice(_("WAL replay forcibly resumed")); } local_node_info.last_wal_receive_lsn = local_replication_info.last_wal_receive_lsn; log_info(_("local node's last receive lsn: %X/%X"), format_lsn(local_node_info.last_wal_receive_lsn)); /* pointer to "winning" node, initially self */ candidate_node = &local_node_info; initPQExpBuffer(&nodes_with_primary_visible); for (cell = sibling_nodes->head; cell; cell = cell->next) { ReplInfo sibling_replication_info; log_info(_("checking state of sibling node \"%s\" (ID: %i)"), cell->node_info->node_name, cell->node_info->node_id); /* assume the worst case */ cell->node_info->node_status = NODE_STATUS_UNKNOWN; cell->node_info->conn = establish_db_connection(cell->node_info->conninfo, false); if (PQstatus(cell->node_info->conn) != CONNECTION_OK) { close_connection(&cell->node_info->conn); continue; } cell->node_info->node_status = NODE_STATUS_UP; stats.visible_nodes++; /* * see if the node is in the primary's location (but skip the check if * we've seen a node there already) */ if (primary_location_seen == false) { if (strncmp(cell->node_info->location, upstream_node_info.location, MAXLEN) == 0) { log_debug("node %i in primary location \"%s\"", cell->node_info->node_id, cell->node_info->location); primary_location_seen = true; } } /* * check if repmgrd running - skip if not * * TODO: include pid query in replication info query? * * NOTE: from Pg12 we could execute "pg_promote()" from a running repmgrd; * here we'll need to find a way of ensuring only one repmgrd does this */ if (repmgrd_get_pid(cell->node_info->conn) == UNKNOWN_PID) { log_warning(_("repmgrd not running on node \"%s\" (ID: %i), skipping"), cell->node_info->node_name, cell->node_info->node_id); continue; } if (get_replication_info(cell->node_info->conn, cell->node_info->type, &sibling_replication_info) == false) { log_warning(_("unable to retrieve replication information for node \"%s\" (ID: %i), skipping"), cell->node_info->node_name, cell->node_info->node_id); continue; } /* * Check if node is not in recovery - it may have been promoted * outside of the failover mechanism, in which case we may be able * to follow it. */ if (sibling_replication_info.in_recovery == false && cell->node_info->type != WITNESS) { bool can_follow; log_warning(_("node \"%s\" (ID: %i) is not in recovery"), cell->node_info->node_name, cell->node_info->node_id); /* * Node is not in recovery, but still reporting an upstream * node ID; possible it was promoted manually (e.g. with "pg_ctl promote"), * or (less likely) the node's repmgrd has just switched to primary * monitoring node but has not yet unset the upstream node ID in * shared memory. Either way, log this. */ if (sibling_replication_info.upstream_node_id != UNKNOWN_NODE_ID) { log_warning(_("node \"%s\" (ID: %i) still reports its upstream is node %i, last seen %i second(s) ago"), cell->node_info->node_name, cell->node_info->node_id, sibling_replication_info.upstream_node_id, sibling_replication_info.upstream_last_seen); } can_follow = check_node_can_follow(local_conn, local_node_info.last_wal_receive_lsn, cell->node_info->conn, cell->node_info); if (can_follow == true) { *new_primary_id = cell->node_info->node_id; termPQExpBuffer(&nodes_with_primary_visible); return ELECTION_CANCELLED; } /* * Tricky situation here - we'll assume the node is a rogue primary */ log_warning(_("not possible to attach to node \"%s\" (ID: %i), ignoring"), cell->node_info->node_name, cell->node_info->node_id); continue; } else { log_info(_("node \"%s\" (ID: %i) reports its upstream is node %i, last seen %i second(s) ago"), cell->node_info->node_name, cell->node_info->node_id, sibling_replication_info.upstream_node_id, sibling_replication_info.upstream_last_seen); } /* check if WAL replay on node is paused */ if (sibling_replication_info.wal_replay_paused == true) { /* * Theoretically the repmgrd on the node should have resumed WAL play * at this point. */ if (sibling_replication_info.last_wal_receive_lsn > sibling_replication_info.last_wal_replay_lsn) { log_warning(_("WAL replay on node \"%s\" (ID: %i) is paused and WAL is pending replay"), cell->node_info->node_name, cell->node_info->node_id); } } /* * Check if node has seen primary "recently" - if so, we may have "partial primary visibility". * For now we'll assume the primary is visible if it's been seen less than * monitor_interval_secs * 2 seconds ago. We may need to adjust this, and/or make the value * configurable. */ if (sibling_replication_info.upstream_last_seen >= 0 && sibling_replication_info.upstream_last_seen < (config_file_options.monitor_interval_secs * 2)) { if (sibling_replication_info.upstream_node_id != upstream_node_info.node_id) { log_warning(_("assumed sibling node \"%s\" (ID: %i) monitoring different upstream node %i"), cell->node_info->node_name, cell->node_info->node_id, sibling_replication_info.upstream_node_id); } else { nodes_with_primary_still_visible++; log_notice(_("%s node \"%s\" (ID: %i) last saw primary node %i second(s) ago, considering primary still visible"), get_node_type_string(cell->node_info->type), cell->node_info->node_name, cell->node_info->node_id, sibling_replication_info.upstream_last_seen); appendPQExpBuffer(&nodes_with_primary_visible, " - node \"%s\" (ID: %i): %i second(s) ago\n", cell->node_info->node_name, cell->node_info->node_id, sibling_replication_info.upstream_last_seen); } } else { log_info(_("%s node \"%s\" (ID: %i) last saw primary node %i second(s) ago"), get_node_type_string(cell->node_info->type), cell->node_info->node_name, cell->node_info->node_id, sibling_replication_info.upstream_last_seen); } /* don't interrogate a witness server */ if (cell->node_info->type == WITNESS) { log_debug("node %i is witness, not querying state", cell->node_info->node_id); continue; } /* don't check 0-priority nodes */ if (cell->node_info->priority <= 0) { log_info(_("node \"%s\" (ID: %i) has priority of %i, skipping"), cell->node_info->node_name, cell->node_info->node_id, cell->node_info->priority); continue; } /* get node's last receive LSN - if "higher" than current winner, current node is candidate */ cell->node_info->last_wal_receive_lsn = sibling_replication_info.last_wal_receive_lsn; log_info(_("last receive LSN for sibling node \"%s\" (ID: %i) is: %X/%X"), cell->node_info->node_name, cell->node_info->node_id, format_lsn(cell->node_info->last_wal_receive_lsn)); /* compare LSN */ if (cell->node_info->last_wal_receive_lsn > candidate_node->last_wal_receive_lsn) { /* other node is ahead */ log_info(_("node \"%s\" (ID: %i) is ahead of current candidate \"%s\" (ID: %i)"), cell->node_info->node_name, cell->node_info->node_id, candidate_node->node_name, candidate_node->node_id); candidate_node = cell->node_info; } /* LSN is same - tiebreak on priority, then node_id */ else if (cell->node_info->last_wal_receive_lsn == candidate_node->last_wal_receive_lsn) { log_info(_("node \"%s\" (ID: %i) has same LSN as current candidate \"%s\" (ID: %i)"), cell->node_info->node_name, cell->node_info->node_id, candidate_node->node_name, candidate_node->node_id); if (cell->node_info->priority > candidate_node->priority) { log_info(_("node \"%s\" (ID: %i) has higher priority (%i) than current candidate \"%s\" (ID: %i) (%i)"), cell->node_info->node_name, cell->node_info->node_id, cell->node_info->priority, candidate_node->node_name, candidate_node->node_id, candidate_node->priority); candidate_node = cell->node_info; } else if (cell->node_info->priority == candidate_node->priority) { if (cell->node_info->node_id < candidate_node->node_id) { log_info(_("node \"%s\" (ID: %i) has same priority but lower node_id than current candidate \"%s\" (ID: %i)"), cell->node_info->node_name, cell->node_info->node_id, candidate_node->node_name, candidate_node->node_id); candidate_node = cell->node_info; } } else { log_info(_("node \"%s\" (ID: %i) has lower priority (%i) than current candidate \"%s\" (ID: %i) (%i)"), cell->node_info->node_name, cell->node_info->node_id, cell->node_info->priority, candidate_node->node_name, candidate_node->node_id, candidate_node->priority); } } } if (primary_location_seen == false) { log_notice(_("no nodes from the primary location \"%s\" visible - assuming network split"), upstream_node_info.location); log_detail(_("node will enter degraded monitoring state waiting for reconnect")); monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); reset_node_voting_status(); termPQExpBuffer(&nodes_with_primary_visible); return ELECTION_CANCELLED; } if (nodes_with_primary_still_visible > 0) { log_info(_("%i nodes can see the primary"), nodes_with_primary_still_visible); log_detail(_("following nodes can see the primary:\n%s"), nodes_with_primary_visible.data); if (config_file_options.primary_visibility_consensus == true) { log_notice(_("cancelling failover as some nodes can still see the primary")); monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); reset_node_voting_status(); termPQExpBuffer(&nodes_with_primary_visible); return ELECTION_CANCELLED; } } termPQExpBuffer(&nodes_with_primary_visible); log_info(_("visible nodes: %i; total nodes: %i; no nodes have seen the primary within the last %i seconds"), stats.visible_nodes, stats.shared_upstream_nodes, (config_file_options.monitor_interval_secs * 2)); if (stats.visible_nodes <= (stats.shared_upstream_nodes / 2.0)) { log_notice(_("unable to reach a qualified majority of nodes")); log_detail(_("node will enter degraded monitoring state waiting for reconnect")); monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); reset_node_voting_status(); return ELECTION_CANCELLED; } log_notice(_("promotion candidate is \"%s\" (ID: %i)"), candidate_node->node_name, candidate_node->node_id); if (candidate_node->node_id == local_node_info.node_id) { /* * If "failover_validation_command" is set, execute that command * and decide the result based on the command's output */ if (config_file_options.failover_validation_command[0] != '\0') { return execute_failover_validation_command(candidate_node, &stats); } return ELECTION_WON; } return ELECTION_LOST; } /* * "failover" for the witness node; the witness has no part in the election * other than being reachable, so just needs to await notification from the * new primary */ static bool do_witness_failover(void) { int new_primary_id = UNKNOWN_NODE_ID; /* TODO add pre-event notification here */ failover_state = FAILOVER_STATE_UNKNOWN; if (wait_primary_notification(&new_primary_id) == true) { /* if primary has reappeared, no action needed */ if (new_primary_id == upstream_node_info.node_id) { failover_state = FAILOVER_STATE_FOLLOWING_ORIGINAL_PRIMARY; } else { failover_state = witness_follow_new_primary(new_primary_id); } } else { failover_state = FAILOVER_STATE_NO_NEW_PRIMARY; } log_verbose(LOG_DEBUG, "failover state is %s", format_failover_state(failover_state)); switch (failover_state) { case FAILOVER_STATE_PRIMARY_REAPPEARED: /* pass control back down to start_monitoring() */ log_info(_("resuming %s monitoring mode"),get_node_type_string(local_node_info.type)); log_detail(_("original primary \"%s\" (ID: %i) reappeared"), upstream_node_info.node_name, upstream_node_info.node_id); failover_state = FAILOVER_STATE_NONE; return true; case FAILOVER_STATE_FOLLOWED_NEW_PRIMARY: log_info(_("resuming %s monitoring mode"),get_node_type_string(local_node_info.type)); log_detail(_("following new primary \"%s\" (ID: %i)"), upstream_node_info.node_name, upstream_node_info.node_id); failover_state = FAILOVER_STATE_NONE; return true; case FAILOVER_STATE_FOLLOWING_ORIGINAL_PRIMARY: log_info(_("resuming %s monitoring mode"),get_node_type_string(local_node_info.type)); log_detail(_("following original primary \"%s\" (ID: %i)"), upstream_node_info.node_name, upstream_node_info.node_id); failover_state = FAILOVER_STATE_NONE; return true; case FAILOVER_STATE_FOLLOW_FAIL: /* * for whatever reason we were unable to follow the new primary - * continue monitoring in degraded state */ monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); return false; default: monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); return false; } /* should never reach here */ return false; } static void reset_node_voting_status(void) { failover_state = FAILOVER_STATE_NONE; if (PQstatus(local_conn) != CONNECTION_OK) { log_error(_("reset_node_voting_status(): local_conn not set")); log_detail("\n%s", PQerrorMessage(local_conn)); return; } reset_voting_status(local_conn); } static void check_connection(t_node_info *node_info, PGconn **conn) { if (is_server_available(node_info->conninfo) == false) { log_warning(_("connection to node \"%s\" (ID: %i) lost"), node_info->node_name, node_info->node_id); log_detail("\n%s", PQerrorMessage(*conn)); close_connection(conn); } if (PQstatus(*conn) != CONNECTION_OK) { log_info(_("attempting to reconnect to node \"%s\" (ID: %i)"), node_info->node_name, node_info->node_id); close_connection(conn); *conn = establish_db_connection(node_info->conninfo, false); if (PQstatus(*conn) != CONNECTION_OK) { close_connection(conn); log_warning(_("reconnection to node \"%s\" (ID: %i) failed"), node_info->node_name, node_info->node_id); } else { int stored_local_node_id = UNKNOWN_NODE_ID; log_info(_("reconnected to node \"%s\" (ID: %i)"), node_info->node_name, node_info->node_id); stored_local_node_id = repmgrd_get_local_node_id(*conn); if (stored_local_node_id == UNKNOWN_NODE_ID) { repmgrd_set_local_node_id(*conn, config_file_options.node_id); repmgrd_set_pid(local_conn, getpid(), pid_file); } } } } static const char * format_failover_state(FailoverState failover_state) { switch(failover_state) { case FAILOVER_STATE_UNKNOWN: return "UNKNOWN"; case FAILOVER_STATE_NONE: return "NONE"; case FAILOVER_STATE_PROMOTED: return "PROMOTED"; case FAILOVER_STATE_PROMOTION_FAILED: return "PROMOTION_FAILED"; case FAILOVER_STATE_PRIMARY_REAPPEARED: return "PRIMARY_REAPPEARED"; case FAILOVER_STATE_LOCAL_NODE_FAILURE: return "LOCAL_NODE_FAILURE"; case FAILOVER_STATE_WAITING_NEW_PRIMARY: return "WAITING_NEW_PRIMARY"; case FAILOVER_STATE_FOLLOW_NEW_PRIMARY: return "FOLLOW_NEW_PRIMARY"; case FAILOVER_STATE_REQUIRES_MANUAL_FAILOVER: return "REQUIRES_MANUAL_FAILOVER"; case FAILOVER_STATE_FOLLOWED_NEW_PRIMARY: return "FOLLOWED_NEW_PRIMARY"; case FAILOVER_STATE_FOLLOWING_ORIGINAL_PRIMARY: return "FOLLOWING_ORIGINAL_PRIMARY"; case FAILOVER_STATE_NO_NEW_PRIMARY: return "NO_NEW_PRIMARY"; case FAILOVER_STATE_FOLLOW_FAIL: return "FOLLOW_FAIL"; case FAILOVER_STATE_NODE_NOTIFICATION_ERROR: return "NODE_NOTIFICATION_ERROR"; case FAILOVER_STATE_ELECTION_RERUN: return "ELECTION_RERUN"; } /* should never reach here */ return "UNKNOWN_FAILOVER_STATE"; } static void handle_sighup(PGconn **conn, t_server_type server_type) { log_notice(_("received SIGHUP, reloading configuration")); if (reload_config(server_type)) { close_connection(conn); *conn = establish_db_connection(config_file_options.conninfo, true); } if (*config_file_options.log_file) { FILE *fd; log_debug("reopening %s", config_file_options.log_file); fd = freopen(config_file_options.log_file, "a", stderr); if (fd == NULL) { fprintf(stderr, "error reopening stderr to \"%s\": %s", config_file_options.log_file, strerror(errno)); } } got_SIGHUP = false; } static ElectionResult execute_failover_validation_command(t_node_info *node_info, election_stats *stats) { PQExpBufferData failover_validation_command; PQExpBufferData command_output; int return_value = -1; initPQExpBuffer(&failover_validation_command); initPQExpBuffer(&command_output); parse_failover_validation_command(config_file_options.failover_validation_command, node_info, stats, &failover_validation_command); log_notice(_("executing \"failover_validation_command\"")); log_detail("%s", failover_validation_command.data); /* we determine success of the command by the value placed into return_value */ (void) local_command_return_value(failover_validation_command.data, &command_output, &return_value); termPQExpBuffer(&failover_validation_command); if (command_output.data[0] != '\0') { log_info("output returned by failover validation command:\n%s", command_output.data); } else { log_info(_("no output returned from command")); } termPQExpBuffer(&command_output); if (return_value != 0) { /* create event here? */ log_notice(_("failover validation command returned a non-zero value: %i"), return_value); return ELECTION_RERUN; } log_notice(_("failover validation command returned zero")); return ELECTION_WON; } static void parse_failover_validation_command(const char *template, t_node_info *node_info, election_stats *stats, PQExpBufferData *out) { const char *src_ptr; for (src_ptr = template; *src_ptr; src_ptr++) { if (*src_ptr == '%') { switch (src_ptr[1]) { case '%': /* %%: replace with % */ src_ptr++; appendPQExpBufferChar(out, *src_ptr); break; case 'n': /* %n: node id */ src_ptr++; appendPQExpBuffer(out, "%i", node_info->node_id); break; case 'a': /* %a: node name */ src_ptr++; appendPQExpBufferStr(out, node_info->node_name); break; case 'v': /* %v: visible nodes count */ src_ptr++; appendPQExpBuffer(out, "%i", stats->visible_nodes); break; case 'u': /* %u: shared upstream nodes count */ src_ptr++; appendPQExpBuffer(out, "%i", stats->shared_upstream_nodes); break; case 't': /* %t: total nodes count */ src_ptr++; appendPQExpBuffer(out, "%i", stats->all_nodes); break; default: /* otherwise treat the % as not special */ appendPQExpBufferChar(out, *src_ptr); break; } } else { appendPQExpBufferChar(out, *src_ptr); } } return; } /* * Sanity-check whether the local node can follow the proposed upstream node. * * Note this function is very similar to check_node_can_attach() in * repmgr-client.c, however the later is very focussed on client-side * functionality (including log output related to --dry-run, pg_rewind etc.) * which we don't want here. */ static bool check_node_can_follow(PGconn *local_conn, XLogRecPtr local_xlogpos, PGconn *follow_target_conn, t_node_info *follow_target_node_info) { PGconn *local_repl_conn = NULL; t_system_identification local_identification = T_SYSTEM_IDENTIFICATION_INITIALIZER; PGconn *follow_target_repl_conn = NULL; t_system_identification follow_target_identification = T_SYSTEM_IDENTIFICATION_INITIALIZER; TimeLineHistoryEntry *follow_target_history = NULL; bool can_follow = true; bool success; local_repl_conn = establish_replication_connection_from_conn(local_conn, local_node_info.repluser); if (PQstatus(local_repl_conn) != CONNECTION_OK) { log_error(_("unable to establish a replication connection to the local node")); PQfinish(local_repl_conn); return false; } success = identify_system(local_repl_conn, &local_identification); PQfinish(local_repl_conn); if (success == false) { log_error(_("unable to query the local node's system identification")); return false; } /* check replication connection */ follow_target_repl_conn = establish_replication_connection_from_conn(follow_target_conn, follow_target_node_info->repluser); if (PQstatus(follow_target_repl_conn) != CONNECTION_OK) { log_error(_("unable to establish a replication connection to the follow target node")); PQfinish(follow_target_repl_conn); return false; } /* check system_identifiers match */ if (identify_system(follow_target_repl_conn, &follow_target_identification) == false) { log_error(_("unable to query the follow target node's system identification")); PQfinish(follow_target_repl_conn); return false; } /* * Check for thing that should never happen, but expect the unexpected anyway. */ if (follow_target_identification.system_identifier != local_identification.system_identifier) { log_error(_("this node is not part of the follow target node's replication cluster")); log_detail(_("this node's system identifier is %lu, follow target node's system identifier is %lu"), local_identification.system_identifier, follow_target_identification.system_identifier); PQfinish(follow_target_repl_conn); return false; } /* check timelines */ log_verbose(LOG_DEBUG, "local timeline: %i; follow target timeline: %i", local_identification.timeline, follow_target_identification.timeline); /* upstream's timeline is lower than ours - impossible case */ if (follow_target_identification.timeline < local_identification.timeline) { log_error(_("this node's timeline is ahead of the follow target node's timeline")); log_detail(_("this node's timeline is %i, follow target node's timeline is %i"), local_identification.timeline, follow_target_identification.timeline); PQfinish(follow_target_repl_conn); return false; } /* timeline is the same - check relative positions */ if (follow_target_identification.timeline == local_identification.timeline) { XLogRecPtr follow_target_xlogpos = get_node_current_lsn(follow_target_conn); if (local_xlogpos == InvalidXLogRecPtr || follow_target_xlogpos == InvalidXLogRecPtr) { log_error(_("unable to compare LSN positions")); PQfinish(follow_target_repl_conn); return false; } if (local_xlogpos <= follow_target_xlogpos) { log_info(_("timelines are same, this server is not ahead")); log_detail(_("local node lsn is %X/%X, follow target lsn is %X/%X"), format_lsn(local_xlogpos), format_lsn(follow_target_xlogpos)); } else { log_error(_("this node is ahead of the follow target")); log_detail(_("local node lsn is %X/%X, follow target lsn is %X/%X"), format_lsn(local_xlogpos), format_lsn(follow_target_xlogpos)); can_follow = false; } } else { /* * upstream has higher timeline - check where it forked off from this node's timeline */ follow_target_history = get_timeline_history(follow_target_repl_conn, local_identification.timeline + 1); if (follow_target_history == NULL) { /* get_timeline_history() will emit relevant error messages */ PQfinish(follow_target_repl_conn); return false; } log_debug("local tli: %i; local_xlogpos: %X/%X; follow_target_history->tli: %i; follow_target_history->end: %X/%X", (int)local_identification.timeline, format_lsn(local_xlogpos), follow_target_history->tli, format_lsn(follow_target_history->end)); /* * Local node has proceeded beyond the follow target's fork, so we * definitely can't attach. * * This could be the case if the follow target was promoted, but does * not contain all changes which are being replayed to this standby. */ if (local_xlogpos > follow_target_history->end) { log_error(_("this node cannot attach to follow target node \"%s\" (ID: %i)"), follow_target_node_info->node_name, follow_target_node_info->node_id); can_follow = false; log_detail(_("follow target server's timeline %lu forked off current database system timeline %lu before current recovery point %X/%X"), local_identification.system_identifier + 1, local_identification.system_identifier, format_lsn(local_xlogpos)); } if (can_follow == true) { log_info(_("local node \"%s\" (ID: %i) can attach to follow target node \"%s\" (ID: %i)"), config_file_options.node_name, config_file_options.node_id, follow_target_node_info->node_name, follow_target_node_info->node_id); log_detail(_("local node's recovery point: %X/%X; follow target node's fork point: %X/%X"), format_lsn(local_xlogpos), format_lsn(follow_target_history->end)); } } PQfinish(follow_target_repl_conn); if (follow_target_history) pfree(follow_target_history); return can_follow; } static void check_witness_attached(t_node_info *node_info, bool startup) { /* * connect and check upstream node id; at this point we don't care if it's * not reachable, only whether we can mark it as attached or not. */ PGconn *witness_conn = establish_db_connection_quiet(node_info->conninfo); if (PQstatus(witness_conn) == CONNECTION_OK) { int witness_upstream_node_id = repmgrd_get_upstream_node_id(witness_conn); log_debug("witness node %i's upstream node ID reported as %i", node_info->node_id, witness_upstream_node_id); if (witness_upstream_node_id == local_node_info.node_id) { node_info->attached = NODE_ATTACHED; } else { node_info->attached = NODE_DETACHED; } } else { node_info->attached = startup == true ? NODE_ATTACHED_UNKNOWN : NODE_DETACHED; } PQfinish(witness_conn); } static t_child_node_info * append_child_node_record(t_child_node_info_list *nodes, int node_id, const char *node_name, t_server_type type, NodeAttached attached) { t_child_node_info *child_node = pg_malloc0(sizeof(t_child_node_info)); child_node->node_id = node_id; snprintf(child_node->node_name, sizeof(child_node->node_name), "%s", node_name); child_node->type = type; child_node->attached = attached; if (nodes->tail) nodes->tail->next = child_node; else nodes->head = child_node; nodes->tail = child_node; nodes->node_count++; return child_node; } static void remove_child_node_record(t_child_node_info_list *nodes, int node_id) { t_child_node_info *node; t_child_node_info *prev_node = NULL; t_child_node_info *next_node = NULL; node = nodes->head; while (node != NULL) { next_node = node->next; if (node->node_id == node_id) { /* first node */ if (node == nodes->head) { nodes->head = next_node; } /* last node */ else if (next_node == NULL) { prev_node->next = NULL; } else { prev_node->next = next_node; } pfree(node); nodes->node_count--; return; } else { prev_node = node; } node = next_node; } } static void clear_child_node_info_list(t_child_node_info_list *nodes) { t_child_node_info *node; t_child_node_info *next_node; node = nodes->head; while (node != NULL) { next_node = node->next; pfree(node); node = next_node; } nodes->head = NULL; nodes->tail = NULL; nodes->node_count = 0; } static void parse_child_nodes_disconnect_command(char *parsed_command, char *template, int reporting_node_id) { const char *src_ptr = NULL; char *dst_ptr = NULL; char *end_ptr = NULL; dst_ptr = parsed_command; end_ptr = (parsed_command + MAXPGPATH) - 1; *end_ptr = '\0'; for (src_ptr = template; *src_ptr; src_ptr++) { if (*src_ptr == '%') { switch (src_ptr[1]) { case '%': /* %%: replace with % */ if (dst_ptr < end_ptr) { src_ptr++; *dst_ptr++ = *src_ptr; } break; case 'p': /* %p: node id of the reporting primary */ src_ptr++; snprintf(dst_ptr, end_ptr - dst_ptr, "%i", reporting_node_id); dst_ptr += strlen(dst_ptr); break; } } else { if (dst_ptr < end_ptr) *dst_ptr++ = *src_ptr; } } *dst_ptr = '\0'; return; } int try_primary_reconnect(PGconn **conn, PGconn *local_conn, t_node_info *node_info) { t_conninfo_param_list conninfo_params = T_CONNINFO_PARAM_LIST_INITIALIZER; int i; int max_attempts = config_file_options.reconnect_attempts; initialize_conninfo_params(&conninfo_params, false); /* we assume by now the conninfo string is parseable */ (void) parse_conninfo_string(node_info->conninfo, &conninfo_params, NULL, false); /* set some default values if not explicitly provided */ param_set_ine(&conninfo_params, "connect_timeout", "2"); param_set_ine(&conninfo_params, "fallback_application_name", "repmgr"); for (i = 0; i < max_attempts; i++) { time_t started_at = time(NULL); int up_to; bool sleep_now = false; int max_sleep_seconds; log_info(_("checking state of node \"%s\" (ID: %i), %i of %i attempts"), node_info->node_name, node_info->node_id, i + 1, max_attempts); if (is_server_available_params(&conninfo_params) == true) { PGconn *our_conn; log_notice(_("node \"%s\" (ID: %i) has recovered, reconnecting"), node_info->node_name, node_info->node_id); /* * Note: we could also handle the case where node is pingable but * connection denied due to connection exhaustion, by falling back to * degraded monitoring (make configurable) */ our_conn = establish_db_connection_by_params(&conninfo_params, false); if (PQstatus(our_conn) == CONNECTION_OK) { free_conninfo_params(&conninfo_params); log_info(_("connection to node \"%s\" (ID: %i) succeeded"), node_info->node_name, node_info->node_id); if (PQstatus(*conn) == CONNECTION_BAD) { log_verbose(LOG_INFO, _("original connection handle returned CONNECTION_BAD, using new connection")); close_connection(conn); *conn = our_conn; } else { ExecStatusType ping_result; ping_result = connection_ping(*conn); if (ping_result != PGRES_TUPLES_OK) { log_info(_("original connection no longer available, using new connection")); close_connection(conn); *conn = our_conn; } else { log_info(_("original connection is still available")); PQfinish(our_conn); } } node_info->node_status = NODE_STATUS_UP; return UNKNOWN_NODE_ID; } close_connection(&our_conn); log_notice(_("unable to reconnect to node \"%s\" (ID: %i)"), node_info->node_name, node_info->node_id); } /* * Experimental behaviour, see GitHub #662. */ if (config_file_options.reconnect_loop_sync == true) { up_to = (time(NULL) - started_at); max_sleep_seconds = (up_to == 0) ? config_file_options.reconnect_interval : (up_to % config_file_options.reconnect_interval); if (i + 1 <= max_attempts) sleep_now = true; } else { max_sleep_seconds = config_file_options.reconnect_interval; if (i + 1 < max_attempts) sleep_now = true; } if (sleep_now == true) { int j; log_info(_("sleeping up to %i seconds until next reconnection attempt"), max_sleep_seconds); for (j = 0; j < max_sleep_seconds; j++) { int new_primary_node_id; if (get_new_primary(local_conn, &new_primary_node_id) == true && new_primary_node_id != UNKNOWN_NODE_ID) { if (new_primary_node_id == ELECTION_RERUN_NOTIFICATION) { log_notice(_("received rerun notification")); } else { log_notice(_("received notification that new primary is node %i"), new_primary_node_id); } free_conninfo_params(&conninfo_params); return new_primary_node_id; } sleep(1); } } } log_warning(_("unable to reconnect to node \"%s\" (ID: %i) after %i attempts"), node_info->node_name, node_info->node_id, max_attempts); node_info->node_status = NODE_STATUS_DOWN; free_conninfo_params(&conninfo_params); return UNKNOWN_NODE_ID; } repmgr-5.3.1/repmgrd-physical.h000066400000000000000000000020161420262710000164300ustar00rootroot00000000000000/* * repmgrd-physical.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _REPMGRD_PHYSICAL_H_ #define _REPMGRD_PHYSICAL_H_ void do_physical_node_check(PGconn *conn); void monitor_streaming_primary(void); void monitor_streaming_standby(void); void monitor_streaming_witness(void); void handle_sigint_physical(SIGNAL_ARGS); #endif /* _REPMGRD_PHYSICAL_H_ */ repmgr-5.3.1/repmgrd.c000066400000000000000000000633751420262710000146300ustar00rootroot00000000000000/* * repmgrd.c - Replication manager daemon * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #include #include #include #include #include "repmgr.h" #include "repmgrd.h" #include "repmgrd-physical.h" #include "configfile.h" #include "voting.h" #define OPT_HELP 1 static char *config_file = NULL; static bool verbose = false; char pid_file[MAXPGPATH]; static bool daemonize = true; static bool show_pid_file = false; static bool no_pid_file = false; t_node_info local_node_info = T_NODE_INFO_INITIALIZER; PGconn *local_conn = NULL; /* Collate command line errors here for friendlier reporting */ static ItemList cli_errors = {NULL, NULL}; bool startup_event_logged = false; MonitoringState monitoring_state = MS_NORMAL; instr_time degraded_monitoring_start; /* * Record receipt of SIGHUP; will cause configuration file to be reread * at the appropriate point in the main loop. */ volatile sig_atomic_t got_SIGHUP = false; static void show_help(void); static void show_usage(void); static void daemonize_process(void); static void check_and_create_pid_file(const char *pid_file); static void start_monitoring(void); #ifndef WIN32 static void setup_event_handlers(void); static void handle_sighup(SIGNAL_ARGS); #endif int calculate_elapsed(instr_time start_time); void update_registration(PGconn *conn); void terminate(int retval); int main(int argc, char **argv) { int optindex; int c; char cli_log_level[MAXLEN] = ""; bool cli_monitoring_history = false; RecordStatus record_status; ExtensionStatus extension_status = REPMGR_UNKNOWN; t_extension_versions extversions = T_EXTENSION_VERSIONS_INITIALIZER; FILE *fd; static struct option long_options[] = { /* general options */ {"help", no_argument, NULL, OPT_HELP}, {"version", no_argument, NULL, 'V'}, /* configuration options */ {"config-file", required_argument, NULL, 'f'}, /* daemon options */ {"daemonize-short", optional_argument, NULL, 'd'}, {"daemonize", optional_argument, NULL, OPT_DAEMONIZE}, {"pid-file", required_argument, NULL, 'p'}, {"show-pid-file", no_argument, NULL, 's'}, {"no-pid-file", no_argument, NULL, OPT_NO_PID_FILE}, /* logging options */ {"log-level", required_argument, NULL, 'L'}, {"verbose", no_argument, NULL, 'v'}, /* legacy options */ {"monitoring-history", no_argument, NULL, 'm'}, {NULL, 0, NULL, 0} }; set_progname(argv[0]); /* Disallow running as root */ if (geteuid() == 0) { fprintf(stderr, _("%s: cannot be run as root\n" "Please log in (using, e.g., \"su\") as the " "(unprivileged) user that owns " "the data directory.\n" ), progname()); exit(1); } srand(time(NULL)); memset(pid_file, 0, MAXPGPATH); while ((c = getopt_long(argc, argv, "?Vf:L:vdp:sm", long_options, &optindex)) != -1) { switch (c) { /* general options */ case '?': /* Actual help option given */ if (strcmp(argv[optind - 1], "-?") == 0) { show_help(); exit(SUCCESS); } /* unknown option reported by getopt */ goto unknown_option; break; case OPT_HELP: show_help(); exit(SUCCESS); case 'V': /* * in contrast to repmgr3 and earlier, we only display the * repmgr version as it's not specific to a particular * PostgreSQL version */ printf("%s %s\n", progname(), REPMGR_VERSION); exit(SUCCESS); /* configuration options */ case 'f': config_file = optarg; break; /* daemon options */ case 'd': daemonize = true; break; case OPT_DAEMONIZE: daemonize = parse_bool(optarg, "-d/--daemonize", &cli_errors); break; case 'p': strncpy(pid_file, optarg, MAXPGPATH); break; case 's': show_pid_file = true; break; case OPT_NO_PID_FILE: no_pid_file = true; break; /* logging options */ /* -L/--log-level */ case 'L': { int detected_cli_log_level = detect_log_level(optarg); if (detected_cli_log_level != -1) { strncpy(cli_log_level, optarg, MAXLEN); } else { PQExpBufferData invalid_log_level; initPQExpBuffer(&invalid_log_level); appendPQExpBuffer(&invalid_log_level, _("invalid log level \"%s\" provided"), optarg); item_list_append(&cli_errors, invalid_log_level.data); termPQExpBuffer(&invalid_log_level); } break; } case 'v': verbose = true; break; /* legacy options */ case 'm': cli_monitoring_history = true; break; default: unknown_option: show_usage(); exit(ERR_BAD_CONFIG); } } /* Exit here already if errors in command line options found */ if (cli_errors.head != NULL) { exit_with_cli_errors(&cli_errors, NULL); } startup_event_logged = false; /* * 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 was * provided, an attempt will be made to find one in one of the default * locations). If no configuration file is available, or it can't be parsed * parse_config() will abort anyway, with an appropriate message. */ load_config(config_file, verbose, false, argv[0]); /* Determine pid file location, unless --no-pid-file supplied */ if (no_pid_file == false) { if (config_file_options.repmgrd_pid_file[0] != '\0') { if (pid_file[0] != '\0') { log_warning(_("\"repmgrd_pid_file\" will be overridden by --pid-file")); } else { strncpy(pid_file, config_file_options.repmgrd_pid_file, MAXPGPATH); } } /* no pid file provided - determine location */ if (pid_file[0] == '\0') { /* packagers: if feasible, patch PID file path into "package_pid_file" */ char package_pid_file[MAXPGPATH] = ""; if (package_pid_file[0] != '\0') { maxpath_snprintf(pid_file, "%s", package_pid_file); } else { const char *tmpdir = getenv("TMPDIR"); if (!tmpdir) tmpdir = "/tmp"; maxpath_snprintf(pid_file, "%s/repmgrd.pid", tmpdir); } } } else { /* --no-pid-file supplied - overwrite any value provided with --pid-file ... */ memset(pid_file, 0, MAXPGPATH); } /* If --show-pid-file supplied, output the location (if set) and exit */ if (show_pid_file == true) { printf("%s\n", pid_file); exit(SUCCESS); } /* Some configuration file items can be overridden by command line options */ /* * Command-line parameter -L/--log-level overrides any setting in config * file */ if (*cli_log_level != '\0') { strncpy(config_file_options.log_level, cli_log_level, MAXLEN); } /* * -m/--monitoring-history, if provided, will override repmgr.conf's * monitoring_history; this is for backwards compatibility as it's * possible this may be baked into various startup scripts. */ if (cli_monitoring_history == true) { config_file_options.monitoring_history = true; } fd = freopen("/dev/null", "r", stdin); if (fd == NULL) { fprintf(stderr, "error reopening stdin to \"/dev/null\":\n %s\n", strerror(errno)); } fd = freopen("/dev/null", "w", stdout); if (fd == NULL) { fprintf(stderr, "error reopening stdout to \"/dev/null\":\n %s\n", strerror(errno)); } logger_init(&config_file_options, progname()); log_notice(_("repmgrd (%s %s) starting up"), progname(), REPMGR_VERSION); if (verbose) logger_set_verbose(); if (log_type == REPMGR_SYSLOG) { fd = freopen("/dev/null", "w", stderr); if (fd == NULL) { fprintf(stderr, "error reopening stderr to \"/dev/null\":\n %s\n", strerror(errno)); } } log_info(_("connecting to database \"%s\""), config_file_options.conninfo); /* abort if local node not available at startup */ local_conn = establish_db_connection(config_file_options.conninfo, true); /* * sanity checks * * Note: previous repmgr versions checked the PostgreSQL version at this * point, but we'll skip that and assume the presence of a node record * means we're dealing with a supported installation. * * The absence of a node record will also indicate that either the node or * repmgr has not been properly configured. */ /* warn about any settings which might not be relevant for the current PostgreSQL version */ if (config_file_options.standby_disconnect_on_failover == true && PQserverVersion(local_conn) < 90500) { log_warning(_("\"standby_disconnect_on_failover\" specified, but not available for this PostgreSQL version")); /* TODO: format server version */ log_detail(_("available from PostgreSQL 9.5, this PostgreSQL version is %i"), PQserverVersion(local_conn)); } /* Check "repmgr" the extension is installed */ extension_status = get_repmgr_extension_status(local_conn, &extversions); if (extension_status == REPMGR_INSTALLED) { /* * extension is the latest available according to "pg_available_extensions" - * - does our (major) version match that? */ log_verbose(LOG_DEBUG, "expected extension version: %i; extension version: %i", REPMGR_EXTENSION_NUM, extversions.installed_version_num); if ((REPMGR_EXTENSION_NUM/100) < (extversions.installed_version_num / 100)) { log_error(_("this \"repmgr\" version is older than the installed \"repmgr\" extension version")); log_detail(_("\"repmgr\" version %s providing extension version %s is installed but extension is version %s"), REPMGR_VERSION, REPMGR_EXTENSION_VERSION, extversions.installed_version); log_hint(_("update the repmgr binaries to match the installed extension version")); close_connection(&local_conn); exit(ERR_BAD_CONFIG); } if ((REPMGR_EXTENSION_NUM/100) > (extversions.installed_version_num / 100)) { log_error(_("this \"repmgr\" version is newer than the installed \"repmgr\" extension version")); log_detail(_("\"repmgr\" version %s providing extension version %s is installed but extension is version %s"), REPMGR_VERSION, REPMGR_EXTENSION_VERSION, extversions.installed_version); log_hint(_("update the installed extension version by executing \"ALTER EXTENSION repmgr UPDATE\" in the repmgr database")); close_connection(&local_conn); exit(ERR_BAD_CONFIG); } } else { /* this is unlikely to happen */ if (extension_status == REPMGR_UNKNOWN) { log_error(_("unable to determine status of \"repmgr\" extension")); log_detail("\n%s", PQerrorMessage(local_conn)); close_connection(&local_conn); exit(ERR_DB_QUERY); } if (extension_status == REPMGR_OLD_VERSION_INSTALLED) { log_error(_("an older version of the \"repmgr\" extension is installed")); log_detail(_("extension version %s is installed but newer version %s is available"), extversions.installed_version, extversions.default_version); log_hint(_("verify the repmgr installation is updated properly before continuing")); } else { log_error(_("repmgr extension not found on this node")); if (extension_status == REPMGR_AVAILABLE) { log_detail(_("repmgr extension is available but not installed in database \"%s\""), PQdb(local_conn)); } else if (extension_status == REPMGR_UNAVAILABLE) { log_detail(_("repmgr extension is not available on this node")); } log_hint(_("check that this node is part of a repmgr cluster")); } close_connection(&local_conn); exit(ERR_BAD_CONFIG); } /* Retrieve record for this node from the local database */ record_status = get_node_record(local_conn, config_file_options.node_id, &local_node_info); /* * Terminate if we can't find the local node record. This is a * "fix-the-config" situation, not a lot else we can do. */ if (record_status != RECORD_FOUND) { log_error(_("no metadata record found for this node - terminating")); switch (config_file_options.replication_type) { case REPLICATION_TYPE_PHYSICAL: log_hint(_("check that 'repmgr (primary|standby) register' was executed for this node")); break; } close_connection(&local_conn); terminate(ERR_BAD_CONFIG); } repmgrd_set_local_node_id(local_conn, config_file_options.node_id); { /* * sanity-check that the shared library is loaded and shared memory * can be written by attempting to retrieve the previously stored node_id */ int stored_local_node_id = UNKNOWN_NODE_ID; stored_local_node_id = repmgrd_get_local_node_id(local_conn); if (stored_local_node_id == UNKNOWN_NODE_ID) { log_error(_("unable to write to shared memory")); log_hint(_("ensure \"shared_preload_libraries\" includes \"repmgr\"")); close_connection(&local_conn); terminate(ERR_BAD_CONFIG); } } if (config_file_options.replication_type == REPLICATION_TYPE_PHYSICAL) { log_debug("node id is %i, upstream node id is %i", local_node_info.node_id, local_node_info.upstream_node_id); do_physical_node_check(local_conn); } if (daemonize == true) { daemonize_process(); } if (pid_file[0] != '\0') { check_and_create_pid_file(pid_file); } repmgrd_set_pid(local_conn, getpid(), pid_file); #ifndef WIN32 setup_event_handlers(); #endif start_monitoring(); logger_shutdown(); return SUCCESS; } static void start_monitoring(void) { log_notice(_("starting monitoring of node \"%s\" (ID: %i)"), local_node_info.node_name, local_node_info.node_id); log_info(_("\"connection_check_type\" set to \"%s\""), print_connection_check_type(config_file_options.connection_check_type)); while (true) { switch (local_node_info.type) { case PRIMARY: monitor_streaming_primary(); break; case STANDBY: monitor_streaming_standby(); break; case WITNESS: monitor_streaming_witness(); break; case UNKNOWN: /* should never happen */ break; } } } void update_registration(PGconn *conn) { bool success = update_node_record_conn_priority(local_conn, &config_file_options); /* check values have actually changed */ if (success == false) { PQExpBufferData errmsg; initPQExpBuffer(&errmsg); appendPQExpBuffer(&errmsg, _("unable to update local node record:\n %s"), PQerrorMessage(conn)); create_event_record(conn, &config_file_options, config_file_options.node_id, "repmgrd_config_reload", false, errmsg.data); termPQExpBuffer(&errmsg); } return; } static void daemonize_process(void) { char *ptr, path[MAXPGPATH]; pid_t pid = fork(); int ret; switch (pid) { case -1: log_error(_("error in fork()")); log_detail("%s", strerror(errno)); exit(ERR_SYS_FAILURE); break; case 0: /* create independent session ID */ pid = setsid(); if (pid == (pid_t) -1) { log_error(_("error executing setsid()")); log_detail("%s", strerror(errno)); exit(ERR_SYS_FAILURE); } /* ensure that we are no longer able to open a terminal */ pid = fork(); /* error case */ if (pid == -1) { log_error(_("error executing fork()")); log_detail("%s", strerror(errno)); exit(ERR_SYS_FAILURE); } /* parent process */ if (pid != 0) { exit(0); } /* child process */ memset(path, 0, MAXPGPATH); for (ptr = config_file_path + strlen(config_file_path); ptr > config_file_path; --ptr) { if (*ptr == '/') { strncpy(path, config_file_path, ptr - config_file_path); } } if (*path == '\0') { *path = '/'; } log_debug("dir now %s", path); ret = chdir(path); if (ret != 0) { log_error(_("error changing directory to \"%s\":\n %s"), path, strerror(errno)); } break; default: /* parent process */ exit(0); } } static void check_and_create_pid_file(const char *pid_file) { struct stat st; FILE *fd; char buff[MAXLEN]; pid_t pid; size_t nread; if (stat(pid_file, &st) != -1) { memset(buff, 0, MAXLEN); fd = fopen(pid_file, "r"); if (fd == NULL) { log_error(_("PID file \"%s\" exists but could not opened for reading"), pid_file); log_hint(_("if repmgrd is no longer alive, remove the file and restart repmgrd")); exit(ERR_BAD_PIDFILE); } nread = fread(buff, MAXLEN - 1, 1, fd); if (nread == 0 && ferror(fd)) { log_error(_("error reading PID file \"%s\", aborting"), pid_file); exit(ERR_BAD_PIDFILE); } fclose(fd); pid = atoi(buff); if (pid != 0) { if (kill(pid, 0) != -1) { log_error(_("PID file \"%s\" exists and seems to contain a valid PID"), pid_file); log_hint(_("if repmgrd is no longer alive, remove the file and restart repmgrd")); exit(ERR_BAD_PIDFILE); } } } fd = fopen(pid_file, "w"); if (fd == NULL) { log_error(_("could not open PID file %s"), pid_file); exit(ERR_BAD_CONFIG); } fprintf(fd, "%d", getpid()); fclose(fd); } #ifndef WIN32 /* SIGHUP: set flag to re-read config file at next convenient time */ static void handle_sighup(SIGNAL_ARGS) { got_SIGHUP = true; } static void setup_event_handlers(void) { pqsignal(SIGHUP, handle_sighup); /* * we want to be able to write a "repmgrd_shutdown" event, so delegate * signal handling to the respective replication type handler, as it * will know best which database connection to use */ switch (config_file_options.replication_type) { case REPLICATION_TYPE_PHYSICAL: pqsignal(SIGINT, handle_sigint_physical); pqsignal(SIGTERM, handle_sigint_physical); break; } } #endif void show_usage(void) { fprintf(stderr, _("%s: replication management daemon for PostgreSQL\n"), progname()); fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname()); } void show_help(void) { printf(_("%s: replication management daemon for PostgreSQL\n"), progname()); puts(""); printf(_("%s monitors a cluster of servers and optionally performs failover.\n"), progname()); puts(""); printf(_("Usage:\n")); printf(_(" %s [OPTIONS]\n"), progname()); printf(_("\n")); printf(_("Options:\n")); puts(""); printf(_("General options:\n")); printf(_(" -?, --help show this help, then exit\n")); printf(_(" -V, --version output version information, then exit\n")); puts(""); printf(_("General configuration options:\n")); printf(_(" -v, --verbose output verbose activity information\n")); printf(_(" -f, --config-file=PATH path to the configuration file\n")); puts(""); printf(_("Daemon configuration options:\n")); printf(_(" -d\n")); printf(_(" --daemonize[=true/false]\n")); printf(_(" detach process from foreground (default: true)\n")); printf(_(" -p, --pid-file=PATH use the specified PID file\n")); printf(_(" -s, --show-pid-file show PID file which would be used by the current configuration\n")); printf(_(" --no-pid-file don't write a PID file\n")); puts(""); } bool check_upstream_connection(PGconn **conn, const char *conninfo, PGconn **paired_conn) { /* Check the connection status twice in case it changes after reset */ bool twice = false; log_debug("connection check type is \"%s\"", print_connection_check_type(config_file_options.connection_check_type)); /* * For the check types which do not involve using the existing database * connection, we'll perform the actual check, then as an additional * safeguard verify that the connection is still valid (as it might have * gone away during a brief outage between checks). */ if (config_file_options.connection_check_type != CHECK_QUERY) { bool success = true; if (config_file_options.connection_check_type == CHECK_PING) { success = is_server_available(conninfo); } else if (config_file_options.connection_check_type == CHECK_CONNECTION) { /* * This connection is thrown away, and we never execute a query on it. */ PGconn *test_conn = PQconnectdb(conninfo); log_debug("check_upstream_connection(): attempting to connect to \"%s\"", conninfo); if (PQstatus(test_conn) != CONNECTION_OK) { log_warning(_("unable to connect to \"%s\""), conninfo); log_detail("\n%s", PQerrorMessage(test_conn)); success = false; } PQfinish(test_conn); } if (success == false) return false; if (PQstatus(*conn) == CONNECTION_OK) return true; /* * Checks have succeeded, but the open connection to the primary has gone away, * possibly due to a brief outage between monitoring intervals - attempt to * reset it. */ log_notice(_("upstream is available but upstream connection has gone away, resetting")); PQfinish(*conn); *conn = establish_db_connection_quiet(conninfo); if (PQstatus(*conn) == CONNECTION_OK) { if (paired_conn != NULL) { log_debug("resetting paired connection"); *paired_conn = *conn; } return true; } return false; } for (;;) { if (PQstatus(*conn) != CONNECTION_OK) { log_debug("check_upstream_connection(): upstream connection has gone away, resetting"); if (twice) return false; /* reconnect */ PQfinish(*conn); *conn = establish_db_connection_quiet(conninfo); if (paired_conn != NULL) { log_debug("resetting paired connection"); *paired_conn = *conn; } twice = true; } else { if (!cancel_query(*conn, config_file_options.async_query_timeout)) goto failed; if (wait_connection_availability(*conn, config_file_options.async_query_timeout) != 1) goto failed; /* execute a simple query to verify connection availability */ if (PQsendQuery(*conn, config_file_options.connection_check_query) == 0) { log_warning(_("unable to send query to upstream")); log_detail("%s", PQerrorMessage(*conn)); goto failed; } if (wait_connection_availability(*conn, config_file_options.async_query_timeout) != 1) goto failed; break; failed: /* retry once */ if (twice) return false; /* reconnect */ log_debug("check_upstream_connection(): upstream connection not available, resetting"); PQfinish(*conn); *conn = establish_db_connection_quiet(conninfo); if (paired_conn != NULL) { log_debug("resetting paired connection"); *paired_conn = *conn; } twice = true; } } return true; } void try_reconnect(PGconn **conn, t_node_info *node_info) { PGconn *our_conn; t_conninfo_param_list conninfo_params = T_CONNINFO_PARAM_LIST_INITIALIZER; int i; int max_attempts = config_file_options.reconnect_attempts; initialize_conninfo_params(&conninfo_params, false); /* we assume by now the conninfo string is parseable */ (void) parse_conninfo_string(node_info->conninfo, &conninfo_params, NULL, false); /* set some default values if not explicitly provided */ param_set_ine(&conninfo_params, "connect_timeout", "2"); param_set_ine(&conninfo_params, "fallback_application_name", "repmgr"); for (i = 0; i < max_attempts; i++) { log_info(_("checking state of node %i, %i of %i attempts"), node_info->node_id, i + 1, max_attempts); if (is_server_available_params(&conninfo_params) == true) { log_notice(_("node %i has recovered, reconnecting"), node_info->node_id); /* * Note: we could also handle the case where node is pingable but * connection denied due to connection exhaustion, by falling back to * degraded monitoring (make configurable) */ our_conn = establish_db_connection_by_params(&conninfo_params, false); if (PQstatus(our_conn) == CONNECTION_OK) { free_conninfo_params(&conninfo_params); log_info(_("connection to node %i succeeded"), node_info->node_id); if (PQstatus(*conn) == CONNECTION_BAD) { log_verbose(LOG_INFO, _("original connection handle returned CONNECTION_BAD, using new connection")); close_connection(conn); *conn = our_conn; } else { ExecStatusType ping_result; ping_result = connection_ping(*conn); if (ping_result != PGRES_TUPLES_OK) { log_info(_("original connection no longer available, using new connection")); close_connection(conn); *conn = our_conn; } else { log_info(_("original connection is still available")); PQfinish(our_conn); } } node_info->node_status = NODE_STATUS_UP; return; } close_connection(&our_conn); log_notice(_("unable to reconnect to node \"%s\" (ID: %i)"), node_info->node_name, node_info->node_id); } if (i + 1 < max_attempts) { log_info(_("sleeping %i seconds until next reconnection attempt"), config_file_options.reconnect_interval); sleep(config_file_options.reconnect_interval); } } log_warning(_("unable to reconnect to node %i after %i attempts"), node_info->node_id, max_attempts); node_info->node_status = NODE_STATUS_DOWN; free_conninfo_params(&conninfo_params); return; } int calculate_elapsed(instr_time start_time) { instr_time current_time; INSTR_TIME_SET_CURRENT(current_time); INSTR_TIME_SUBTRACT(current_time, start_time); return (int) INSTR_TIME_GET_DOUBLE(current_time); } const char * print_monitoring_state(MonitoringState monitoring_state) { switch (monitoring_state) { case MS_NORMAL: return "normal"; case MS_DEGRADED: return "degraded"; } /* should never reach here */ return "UNKNOWN"; } void terminate(int retval) { if (PQstatus(local_conn) == CONNECTION_OK) repmgrd_set_pid(local_conn, UNKNOWN_PID, NULL); logger_shutdown(); if (pid_file[0] != '\0') { unlink(pid_file); } log_info(_("%s terminating..."), progname()); exit(retval); } repmgr-5.3.1/repmgrd.h000066400000000000000000000016011420262710000146150ustar00rootroot00000000000000/* * repmgrd.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 */ #ifndef _REPMGRD_H_ #define _REPMGRD_H_ #include #include "portability/instr_time.h" #define OPT_NO_PID_FILE 1000 #define OPT_DAEMONIZE 1001 extern volatile sig_atomic_t got_SIGHUP; extern MonitoringState monitoring_state; extern instr_time degraded_monitoring_start; extern t_node_info local_node_info; extern PGconn *local_conn; extern bool startup_event_logged; extern char pid_file[MAXPGPATH]; bool check_upstream_connection(PGconn **conn, const char *conninfo, PGconn **paired_conn); void try_reconnect(PGconn **conn, t_node_info *node_info); int calculate_elapsed(instr_time start_time); const char *print_monitoring_state(MonitoringState monitoring_state); void update_registration(PGconn *conn); void terminate(int retval); #endif /* _REPMGRD_H_ */ repmgr-5.3.1/sql/000077500000000000000000000000001420262710000136055ustar00rootroot00000000000000repmgr-5.3.1/sql/.gitignore000066400000000000000000000000601420262710000155710ustar00rootroot00000000000000# Might be created by repmgr3 /repmgr_funcs.sql repmgr-5.3.1/sql/repmgr_extension.sql000066400000000000000000000013121420262710000177130ustar00rootroot00000000000000-- minimal SQL tests -- -- comprehensive tests will require a working replication cluster -- set up using the "repmgr" binary and with "repmgrd" running -- extension CREATE EXTENSION repmgr; -- tables SELECT * FROM repmgr.nodes; SELECT * FROM repmgr.events; SELECT * FROM repmgr.monitoring_history; -- views SELECT * FROM repmgr.replication_status; SELECT * FROM repmgr.show_nodes; -- functions SELECT repmgr.get_new_primary(); SELECT repmgr.notify_follow_primary(-1); SELECT repmgr.notify_follow_primary(NULL); SELECT repmgr.reset_voting_status(); SELECT repmgr.set_local_node_id(-1); SELECT repmgr.set_local_node_id(NULL); SELECT repmgr.standby_get_last_updated(); SELECT repmgr.standby_set_last_updated(); repmgr-5.3.1/strutil.c000066400000000000000000000237711420262710000146720ustar00rootroot00000000000000/* * strutil.c * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #include #include #include #include "repmgr.h" #include "log.h" #include "strutil.h" static int xvsnprintf(char *str, size_t size, const char *format, va_list ap) __attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 0))); static void _key_value_list_set(KeyValueList *item_list, bool replace, const char *key, const char *value); static int xvsnprintf(char *str, size_t size, const char *format, va_list ap) { int retval; retval = vsnprintf(str, size, format, ap); if (retval >= (int) size) { log_error(_("buffer of specified size not large enough to format entire string '%s'"), str); exit(ERR_STR_OVERFLOW); } return retval; } int maxlen_snprintf(char *str, const char *format,...) { va_list arglist; int retval; va_start(arglist, format); retval = xvsnprintf(str, MAXLEN, format, arglist); va_end(arglist); return retval; } int maxpath_snprintf(char *str, const char *format,...) { va_list arglist; int retval; va_start(arglist, format); retval = xvsnprintf(str, MAXPGPATH, format, arglist); va_end(arglist); return retval; } void append_where_clause(PQExpBufferData *where_clause, const char *format,...) { va_list arglist; char stringbuf[MAXLEN]; va_start(arglist, format); (void) xvsnprintf(stringbuf, MAXLEN, format, arglist); va_end(arglist); if (where_clause->data[0] == '\0') { appendPQExpBufferStr(where_clause, " WHERE "); } else { appendPQExpBufferStr(where_clause, " AND "); } appendPQExpBufferStr(where_clause, stringbuf); } void item_list_append(ItemList *item_list, const char *message) { item_list_append_format(item_list, "%s", message); } void item_list_append_format(ItemList *item_list, const char *format,...) { ItemListCell *cell; va_list arglist; if (item_list == NULL) return; cell = (ItemListCell *) pg_malloc0(sizeof(ItemListCell)); if (cell == NULL) { log_error(_("unable to allocate memory; terminating.")); exit(ERR_OUT_OF_MEMORY); } cell->string = pg_malloc0(MAXLEN); va_start(arglist, format); (void) xvsnprintf(cell->string, MAXLEN, format, arglist); va_end(arglist); if (item_list->tail) item_list->tail->next = cell; else item_list->head = cell; item_list->tail = cell; } void item_list_free(ItemList *item_list) { ItemListCell *cell = NULL; ItemListCell *next_cell = NULL; cell = item_list->head; while (cell != NULL) { next_cell = cell->next; pfree(cell->string); pfree(cell); cell = next_cell; } } void key_value_list_set(KeyValueList *item_list, const char *key, const char *value) { _key_value_list_set(item_list, false, key, value); return; } void key_value_list_replace_or_set(KeyValueList *item_list, const char *key, const char *value) { _key_value_list_set(item_list, true, key, value); return; } void key_value_list_set_format(KeyValueList *item_list, const char *key, const char *value, ...) { va_list arglist; char formatted_value[MAXLEN]; va_start(arglist, value); (void) xvsnprintf(formatted_value, MAXLEN, value, arglist); va_end(arglist); return _key_value_list_set(item_list, false, key, formatted_value); } static void _key_value_list_set(KeyValueList *item_list, bool replace, const char *key, const char *value) { KeyValueListCell *cell = NULL; int keylen = 0; int vallen = 0; if (replace == true) { KeyValueListCell *prev_cell = NULL; KeyValueListCell *next_cell = NULL; for (cell = item_list->head; cell; cell = next_cell) { next_cell = cell->next; if (strcmp(cell->key, key) == 0) { if (item_list->head == cell) item_list->head = cell->next; if (prev_cell) { prev_cell->next = cell->next; if (item_list->tail == cell) item_list->tail = prev_cell; } else if (item_list->tail == cell) { item_list->tail = NULL; } pfree(cell->key); pfree(cell->value); pfree(cell); } else { prev_cell = cell; } } } cell = (KeyValueListCell *) pg_malloc0(sizeof(KeyValueListCell)); if (cell == NULL) { log_error(_("unable to allocate memory; terminating.")); exit(ERR_BAD_CONFIG); } keylen = strlen(key); vallen = strlen(value); cell->key = pg_malloc0(keylen + 1); cell->value = pg_malloc0(vallen + 1); cell->output_mode = OM_NOT_SET; strncpy(cell->key, key, keylen); strncpy(cell->value, value, vallen); if (item_list->tail) item_list->tail->next = cell; else item_list->head = cell; item_list->tail = cell; return; } void key_value_list_set_output_mode(KeyValueList *item_list, const char *key, OutputMode mode) { KeyValueListCell *cell = NULL; for (cell = item_list->head; cell; cell = cell->next) { if (strncmp(key, cell->key, MAXLEN) == 0) cell->output_mode = mode; } } const char * key_value_list_get(KeyValueList *item_list, const char *key) { return NULL; } void key_value_list_free(KeyValueList *item_list) { KeyValueListCell *cell; KeyValueListCell *next_cell; cell = item_list->head; while (cell != NULL) { next_cell = cell->next; pfree(cell->key); pfree(cell->value); pfree(cell); cell = next_cell; } } void check_status_list_set(CheckStatusList *list, const char *item, CheckStatus status, const char *details) { check_status_list_set_format(list, item, status, "%s", details); } void check_status_list_set_format(CheckStatusList *list, const char *item, CheckStatus status, const char *details,...) { CheckStatusListCell *cell; va_list arglist; int itemlen; cell = (CheckStatusListCell *) pg_malloc0(sizeof(CheckStatusListCell)); if (cell == NULL) { log_error(_("unable to allocate memory; terminating.")); exit(ERR_BAD_CONFIG); } itemlen = strlen(item); cell->item = pg_malloc0(itemlen + 1); cell->details = pg_malloc0(MAXLEN); cell->status = status; strncpy(cell->item, item, itemlen); va_start(arglist, details); (void) xvsnprintf(cell->details, MAXLEN, details, arglist); va_end(arglist); if (list->tail) list->tail->next = cell; else list->head = cell; list->tail = cell; return; } void check_status_list_free(CheckStatusList *list) { CheckStatusListCell *cell = NULL; CheckStatusListCell *next_cell = NULL; cell = list->head; while (cell != NULL) { next_cell = cell->next; pfree(cell->item); pfree(cell->details); pfree(cell); cell = next_cell; } } const char * output_check_status(CheckStatus status) { switch (status) { case CHECK_STATUS_OK: return "OK"; case CHECK_STATUS_WARNING: return "WARNING"; case CHECK_STATUS_CRITICAL: return "CRITICAL"; case CHECK_STATUS_UNKNOWN: return "UNKNOWN"; } return "UNKNOWN"; } /* * 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; } char * escape_string(PGconn *conn, const char *string) { char *escaped_string; int error; escaped_string = pg_malloc0(MAXLEN); (void) PQescapeStringConn(conn, escaped_string, string, MAXLEN, &error); if (error) { pfree(escaped_string); return NULL; } return escaped_string; } /* * simple function to escape double quotes only */ void escape_double_quotes(char *string, PQExpBufferData *out) { char *ptr; for (ptr = string; *ptr; ptr++) { if (*ptr == '"') { if ( (ptr == string) || (ptr > string && *(ptr - 1) != '\\')) { appendPQExpBufferChar(out, '\\'); } } appendPQExpBufferChar(out, *ptr); } return; } char * string_skip_prefix(const char *prefix, char *string) { int n; n = strlen(prefix); if (strncmp(prefix, string, n)) return NULL; else return string + n; } char * string_remove_trailing_newlines(char *string) { int n; n = strlen(string) - 1; while (n >= 0 && string[n] == '\n') string[n] = 0; return string; } char * trim(char *s) { /* Initialize start, end pointers */ char *s1 = s, *s2 = &s[strlen(s) - 1]; /* If string is empty, no action needed */ if (s2 < s1) return s; /* Trim and delimit right side */ while ((isspace(*s2)) && (s2 >= s1)) --s2; *(s2 + 1) = '\0'; /* String is all whitespace - no need for further processing */ if (s2 + 1 == s1) return s; /* Trim left side */ while ((isspace(*s1)) && (s1 < s2)) ++s1; /* Copy finished string */ memmove(s, s1, (s2 - s1) + 1); s[s2 - s1 + 1] = '\0'; return s; } void parse_follow_command(char *parsed_command, char *template, int node_id) { const char *src_ptr = NULL; char *dst_ptr = NULL; char *end_ptr = NULL; dst_ptr = parsed_command; end_ptr = parsed_command + MAXPGPATH - 1; *end_ptr = '\0'; for (src_ptr = template; *src_ptr; src_ptr++) { if (*src_ptr == '%') { switch (src_ptr[1]) { case '%': /* %%: replace with % */ if (dst_ptr < end_ptr) { src_ptr++; *dst_ptr++ = *src_ptr; } break; case 'n': /* %n: node id */ src_ptr++; snprintf(dst_ptr, end_ptr - dst_ptr, "%i", node_id); dst_ptr += strlen(dst_ptr); break; default: /* otherwise treat the % as not special */ if (dst_ptr < end_ptr) *dst_ptr++ = *src_ptr; break; } } else { if (dst_ptr < end_ptr) *dst_ptr++ = *src_ptr; } } *dst_ptr = '\0'; return; } const char * format_bool(bool value) { return value == true ? "true" : "false"; } repmgr-5.3.1/strutil.h000066400000000000000000000102731420262710000146700ustar00rootroot00000000000000/* * strutil.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _STRUTIL_H_ #define _STRUTIL_H_ #include #define MAXLEN 1024 #define MAX_QUERY_LEN 8192 #define MAXVERSIONSTR 16 /* same as defined in src/include/replication/walreceiver.h */ #define MAXCONNINFO 1024 #define STR(x) CppAsString(x) #define MAXLEN_STR STR(MAXLEN) /* * These values must match the Nagios return codes defined here: * * https://assets.nagios.com/downloads/nagioscore/docs/nagioscore/3/en/pluginapi.html */ typedef enum { CHECK_STATUS_OK = 0, CHECK_STATUS_WARNING = 1, CHECK_STATUS_CRITICAL = 2, CHECK_STATUS_UNKNOWN = 3 } CheckStatus; typedef enum { OM_NOT_SET = -1, OM_TEXT, OM_CSV, OM_NAGIOS, OM_OPTFORMAT } OutputMode; typedef struct ItemListCell { struct ItemListCell *next; char *string; } ItemListCell; typedef struct ItemList { ItemListCell *head; ItemListCell *tail; } ItemList; typedef struct KeyValueListCell { struct KeyValueListCell *next; char *key; char *value; OutputMode output_mode; } KeyValueListCell; typedef struct KeyValueList { KeyValueListCell *head; KeyValueListCell *tail; } KeyValueList; typedef struct CheckStatusListCell { struct CheckStatusListCell *next; char *item; CheckStatus status; char *details; } CheckStatusListCell; typedef struct CheckStatusList { CheckStatusListCell *head; CheckStatusListCell *tail; } CheckStatusList; extern int maxlen_snprintf(char *str, const char *format,...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3))); extern int maxpath_snprintf(char *str, const char *format,...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3))); extern void item_list_append(ItemList *item_list, const char *message); extern void item_list_append_format(ItemList *item_list, const char *format,...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3))); extern void item_list_free(ItemList *item_list); extern void key_value_list_set(KeyValueList *item_list, const char *key, const char *value); extern void key_value_list_replace_or_set(KeyValueList *item_list, const char *key, const char *value); extern void key_value_list_set_format(KeyValueList *item_list, const char *key, const char *value,...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 4))); extern void key_value_list_set_output_mode(KeyValueList *item_list, const char *key, OutputMode mode); extern const char *key_value_list_get(KeyValueList *item_list, const char *key); extern void key_value_list_free(KeyValueList *item_list); extern void check_status_list_set(CheckStatusList *list, const char *item, CheckStatus status, const char *details); extern void check_status_list_set_format(CheckStatusList *list, const char *item, CheckStatus status, const char *details,...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 4, 5))); extern void check_status_list_free(CheckStatusList *list); extern const char *output_check_status(CheckStatus status); extern char *escape_recovery_conf_value(const char *src); extern char *escape_string(PGconn *conn, const char *string); extern void escape_double_quotes(char *string, PQExpBufferData *out); extern void append_where_clause(PQExpBufferData *where_clause, const char *clause,...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3))); extern char *string_skip_prefix(const char *prefix, char *string); extern char *string_remove_trailing_newlines(char *string); extern char *trim(char *s); extern void parse_follow_command(char *parsed_command, char *template, int node_id); extern const char *format_bool(bool value); #endif /* _STRUTIL_H_ */ repmgr-5.3.1/sysutils.c000066400000000000000000000243041420262710000150540ustar00rootroot00000000000000/* * sysutils.c * * Functions which need to be executed on the local system. * * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #include #include "repmgr.h" static bool _local_command(const char *command, PQExpBufferData *outputbuf, bool simple, int *return_value); /* * Execute a command locally. "outputbuf" should either be an * initialised PQExpPuffer, or NULL */ bool local_command(const char *command, PQExpBufferData *outputbuf) { return _local_command(command, outputbuf, false, NULL); } bool local_command_return_value(const char *command, PQExpBufferData *outputbuf, int *return_value) { return _local_command(command, outputbuf, false, return_value); } bool local_command_simple(const char *command, PQExpBufferData *outputbuf) { return _local_command(command, outputbuf, true, NULL); } static bool _local_command(const char *command, PQExpBufferData *outputbuf, bool simple, int *return_value) { FILE *fp = NULL; char output[MAXLEN]; int retval = 0; bool success; char tmpfile_path[MAXPGPATH]; const char *tmpdir = getenv("TMPDIR"); int fd; PQExpBufferData command_final; if (!tmpdir) tmpdir = "/tmp"; maxpath_snprintf(tmpfile_path, "%s/repmgr_command.XXXXXX", tmpdir); fd = mkstemp(tmpfile_path); if (fd < 1) { log_error(_("unable to open temporary file")); return false; } initPQExpBuffer(&command_final); appendPQExpBufferStr(&command_final, command); appendPQExpBuffer(&command_final, " 2>%s", tmpfile_path); log_verbose(LOG_DEBUG, "executing:\n %s", command_final.data); if (outputbuf == NULL) { retval = system(command_final.data); termPQExpBuffer(&command_final); if (return_value != NULL) *return_value = WEXITSTATUS(retval); close(fd); return (retval == 0) ? true : false; } fp = popen(command_final.data, "r"); if (fp == NULL) { log_error(_("unable to execute local command:\n%s"), command_final.data); termPQExpBuffer(&command_final); close(fd); return false; } termPQExpBuffer(&command_final); while (fgets(output, MAXLEN, fp) != NULL) { appendPQExpBufferStr(outputbuf, output); if (!feof(fp) && simple == false) { break; } } retval = pclose(fp); /* 141 = SIGPIPE */ success = (WEXITSTATUS(retval) == 0 || WEXITSTATUS(retval) == 141) ? true : false; log_verbose(LOG_DEBUG, "result of command was %i (%i)", WEXITSTATUS(retval), retval); /* * Append any captured STDERR output */ fp = fopen(tmpfile_path, "r"); /* * Not critical if we can't open the file */ if (fp != NULL) { while (fgets(output, MAXLEN, fp) != NULL) { appendPQExpBufferStr(outputbuf, output); } fclose(fp); } unlink(tmpfile_path); if (return_value != NULL) *return_value = WEXITSTATUS(retval); if (outputbuf->data != NULL && outputbuf->data[0] != '\0') log_verbose(LOG_DEBUG, "local_command(): output returned was:\n%s", outputbuf->data); else log_verbose(LOG_DEBUG, "local_command(): no output returned"); return success; } /* * Execute a command via ssh on the remote host. * * TODO: implement SSH calls using libssh2. */ bool remote_command(const char *host, const char *user, const char *command, const char *ssh_options, PQExpBufferData *outputbuf) { FILE *fp; PQExpBufferData ssh_command; char output[MAXLEN] = ""; initPQExpBuffer(&ssh_command); make_remote_command(host, user, command, ssh_options, &ssh_command); log_debug("remote_command():\n %s", ssh_command.data); fp = popen(ssh_command.data, "r"); if (fp == NULL) { log_error(_("unable to execute remote command:\n %s"), ssh_command.data); termPQExpBuffer(&ssh_command); return false; } termPQExpBuffer(&ssh_command); if (outputbuf != NULL) { /* TODO: better error handling */ while (fgets(output, MAXLEN, fp) != NULL) { appendPQExpBufferStr(outputbuf, output); } } else { while (fgets(output, MAXLEN, fp) != NULL) { if (!feof(fp)) { break; } } } pclose(fp); if (outputbuf != NULL) { if (outputbuf->data != NULL && outputbuf->data[0] != '\0') log_verbose(LOG_DEBUG, "remote_command(): output returned was:\n%s", outputbuf->data); else log_verbose(LOG_DEBUG, "remote_command(): no output returned"); } return true; } void make_remote_command(const char *host, const char *user, const char *command, const char *ssh_options, PQExpBufferData *ssh_command) { PQExpBufferData ssh_host; initPQExpBuffer(&ssh_host); if (*user != '\0') { appendPQExpBuffer(&ssh_host, "%s@", user); } appendPQExpBufferStr(&ssh_host, host); appendPQExpBuffer(ssh_command, "ssh -o Batchmode=yes %s %s %s", ssh_options, ssh_host.data, command); termPQExpBuffer(&ssh_host); } pid_t disable_wal_receiver(PGconn *conn) { char buf[MAXLEN]; int wal_retrieve_retry_interval, new_wal_retrieve_retry_interval; pid_t wal_receiver_pid = UNKNOWN_PID; int kill_ret; int i, j; int max_retries = 2; if (is_superuser_connection(conn, NULL) == false) { log_error(_("superuser connection required")); return UNKNOWN_PID; } if (get_recovery_type(conn) == RECTYPE_PRIMARY) { log_error(_("node is not in recovery")); log_detail(_("wal receiver can only run on standby nodes")); return UNKNOWN_PID; } wal_receiver_pid = (pid_t)get_wal_receiver_pid(conn); if (wal_receiver_pid == UNKNOWN_PID) { log_warning(_("unable to retrieve wal receiver PID")); return UNKNOWN_PID; } get_pg_setting(conn, "wal_retrieve_retry_interval", buf); /* TODO: potentially handle atoi error, though unlikely at this point */ wal_retrieve_retry_interval = atoi(buf); new_wal_retrieve_retry_interval = wal_retrieve_retry_interval + WALRECEIVER_DISABLE_TIMEOUT_VALUE; if (wal_retrieve_retry_interval < WALRECEIVER_DISABLE_TIMEOUT_VALUE) { bool success; log_notice(_("setting \"wal_retrieve_retry_interval\" to %i milliseconds"), new_wal_retrieve_retry_interval); alter_system_int(conn, "wal_retrieve_retry_interval", new_wal_retrieve_retry_interval); success = pg_reload_conf(conn); if (success == false) { log_warning(_("unable to reload configuration")); return UNKNOWN_PID; } } /* * If, at this point, the WAL receiver is not running, we don't need to (and indeed can't) * kill it. */ if (wal_receiver_pid == 0) { log_warning(_("wal receiver not running")); return UNKNOWN_PID; } /* why 5? */ log_info(_("sleeping 5 seconds")); sleep(5); /* see comment below as to why we need a loop here */ for (i = 0; i < max_retries; i++) { log_notice(_("killing WAL receiver with PID %i"), (int)wal_receiver_pid); kill((int)wal_receiver_pid, SIGTERM); for (j = 0; j < 30; j++) { kill_ret = kill(wal_receiver_pid, 0); if (kill_ret != 0) { log_info(_("WAL receiver with pid %i killed"), (int)wal_receiver_pid); break; } sleep(1); } /* * Wait briefly to check that the WAL receiver has indeed gone away - * for reasons as yet unclear, after a server start/restart, immediately * after the first time a WAL receiver is killed, a new one is started * straight away, so we'll need to kill that too. */ sleep(1); wal_receiver_pid = (pid_t)get_wal_receiver_pid(conn); if (wal_receiver_pid == UNKNOWN_PID || wal_receiver_pid == 0) break; } return wal_receiver_pid; } pid_t enable_wal_receiver(PGconn *conn, bool wait_startup) { char buf[MAXLEN]; int wal_retrieve_retry_interval; pid_t wal_receiver_pid = UNKNOWN_PID; /* make timeout configurable */ int i, timeout = 30; if (PQstatus(conn) != CONNECTION_OK) { log_error(_("database connection not available")); return UNKNOWN_PID; } if (is_superuser_connection(conn, NULL) == false) { log_error(_("superuser connection required")); return UNKNOWN_PID; } if (get_recovery_type(conn) == RECTYPE_PRIMARY) { log_error(_("node is not in recovery")); log_detail(_("wal receiver can only run on standby nodes")); return UNKNOWN_PID; } if (get_pg_setting(conn, "wal_retrieve_retry_interval", buf) == false) { log_error(_("unable to retrieve \"wal_retrieve_retry_interval\"")); return UNKNOWN_PID; } /* TODO: potentially handle atoi error, though unlikely at this point */ wal_retrieve_retry_interval = atoi(buf); if (wal_retrieve_retry_interval > WALRECEIVER_DISABLE_TIMEOUT_VALUE) { int new_wal_retrieve_retry_interval = wal_retrieve_retry_interval - WALRECEIVER_DISABLE_TIMEOUT_VALUE; bool success; log_notice(_("setting \"wal_retrieve_retry_interval\" to %i ms"), new_wal_retrieve_retry_interval); success = alter_system_int(conn, "wal_retrieve_retry_interval", new_wal_retrieve_retry_interval); if (success == false) { log_warning(_("unable to change \"wal_retrieve_retry_interval\"")); return UNKNOWN_PID; } success = pg_reload_conf(conn); if (success == false) { log_warning(_("unable to reload configuration")); return UNKNOWN_PID; } } else { /* TODO: add threshold sanity check */ log_info(_("\"wal_retrieve_retry_interval\" is %i, not changing"), wal_retrieve_retry_interval); } if (wait_startup == false) return UNKNOWN_PID; for (i = 0; i < timeout; i++) { wal_receiver_pid = (pid_t)get_wal_receiver_pid(conn); if (wal_receiver_pid > 0) break; log_info(_("sleeping %i of maximum %i seconds waiting for WAL receiver to start up"), i + 1, timeout) sleep(1); } if (wal_receiver_pid == UNKNOWN_PID) { log_warning(_("unable to retrieve WAL receiver PID")); return UNKNOWN_PID; } else if (wal_receiver_pid == 0) { log_error(_("WAL receiver did not start up after %i seconds"), timeout); return UNKNOWN_PID; } log_info(_("WAL receiver started up with PID %i"), (int)wal_receiver_pid); return wal_receiver_pid; } repmgr-5.3.1/sysutils.h000066400000000000000000000026741420262710000150670ustar00rootroot00000000000000/* * sysutils.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _SYSUTILS_H_ #define _SYSUTILS_H_ extern bool local_command(const char *command, PQExpBufferData *outputbuf); extern bool local_command_return_value(const char *command, PQExpBufferData *outputbuf, int *return_value); extern bool local_command_simple(const char *command, PQExpBufferData *outputbuf); extern bool remote_command(const char *host, const char *user, const char *command, const char *ssh_options, PQExpBufferData *outputbuf); extern void make_remote_command(const char *host, const char *user, const char *command, const char *ssh_options, PQExpBufferData *ssh_command); extern pid_t disable_wal_receiver(PGconn *conn); extern pid_t enable_wal_receiver(PGconn *conn, bool wait_startup); #endif /* _SYSUTILS_H_ */ repmgr-5.3.1/voting.h000066400000000000000000000016071420262710000144710ustar00rootroot00000000000000/* * voting.h * Copyright (c) EnterpriseDB Corporation, 2010-2021 * * 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 . */ #ifndef _VOTING_H_ #define _VOTING_H_ typedef enum { VS_UNKNOWN = -1, VS_NO_VOTE, VS_VOTE_REQUEST_RECEIVED, VS_VOTE_INITIATED } NodeVotingStatus; #endif /* _VOTING_H_ */